CoolFace
Apppublic

FahimIA/Schema

sourceHugging Facemitupdated 1y agoView on Hugging Face
0likes
app.py1428 linesDownload Raw Back to root
1import gradio as gr2import json3from typing import Dict, List, Optional, Any, Tuple4from dataclasses import dataclass, field5from enum import Enum6import re7import tempfile8import os9import zipfile10 11 12# -----------------------------13# Data model14# -----------------------------15 16class FieldType(Enum):17    """Supported C# field types"""18    # Primitive types19    BYTE = "byte"20    SBYTE = "sbyte"21    SHORT = "short"22    USHORT = "ushort"23    INT = "int"24    UINT = "uint"25    LONG = "long"26    ULONG = "ulong"27    FLOAT = "float"28    DOUBLE = "double"29    BOOL = "bool"30    CHAR = "char"31    STRING = "string"32 33    # Unity types34    FLOAT2 = "float2"35    FLOAT3 = "float3"36    FLOAT4 = "float4"37    QUATERNION = "quaternion"38    FIXEDSTRING32 = "FixedString32Bytes"39    FIXEDSTRING64 = "FixedString64Bytes"40    FIXEDSTRING128 = "FixedString128Bytes"41 42    # Unity ECS types43    BLOB_ARRAY = "BlobArray"44    BLOB_ASSET_REFERENCE = "BlobAssetReference"45    ENTITY = "Entity"46 47    # Custom schema reference48    SCHEMA_REFERENCE = "SchemaReference"49 50 51@dataclass52class SchemaField:53    """Represents a field in a schema"""54    name: str55    field_type: str56    is_blob_array: bool = False57    default_value: Optional[str] = None58    is_reference: bool = False59    reference_type: Optional[str] = None60    description: Optional[str] = None61 62 63@dataclass64class SchemaDefinition:65    """Complete schema definition"""66    name: str67    namespace: str68    fields: List[SchemaField] = field(default_factory=list)69    implements_has_id: bool = True70    implements_equatable: bool = True71    custom_interfaces: List[str] = field(default_factory=list)72    generate_schema_class: bool = True73    generate_create_node: bool = True74    generate_schema_node: bool = True75    generate_blob_component: bool = True76    generate_authoring: bool = True77    folder_path: str = "Assets/Settings"78    menu_category: str = "Hold"79 80 81# -----------------------------82# Code generator83# -----------------------------84 85class CSharpCodeGenerator:86    """Generates C# code for schemas"""87 88    def __init__(self):89        self.using_statements = {90            "System",91            "Unity.Entities",92            "Unity.Collections",93            "Unity.Mathematics",94            "UnityEngine",95            "BovineLabs.Core.ObjectManagement"96        }97 98    def generate_data_struct(self, schema: SchemaDefinition) -> str:99        code_lines = []100        code_lines.extend([f"using {using};" for using in sorted(self.using_statements)])101        code_lines.append("")102 103        namespace = f"{schema.namespace}.Authoring.Data"104        code_lines.append(f"namespace {namespace}")105        code_lines.append("{")106 107        interfaces = []108        if schema.implements_has_id:109            interfaces.append("IHasID")110        if schema.implements_equatable:111            interfaces.append("IEquatable<ushort>")112        interfaces.extend(schema.custom_interfaces)113        interface_str = f" : {', '.join(interfaces)}" if interfaces else ""114 115        code_lines.append(f"    [Serializable]")116        code_lines.append(f"    public struct {schema.name}{interface_str}")117        code_lines.append("    {")118 119        # Fields120        for field in schema.fields:121            code_lines.append(f"        {self._generate_field_declaration(field)}")122 123        code_lines.append("")124 125        # Implement IHasID126        if schema.implements_has_id:127            id_field = next((f for f in schema.fields if f.name == "id"), None)128            if id_field:129                code_lines.extend([130                    "        public int ID",131                    "        {",132                    f"            get => {id_field.name};",133                    f"            set => {id_field.name} = ({id_field.field_type})value;",134                    "        }",135                    ""136                ])137 138        # Implement IEquatable139        if schema.implements_equatable:140            id_field = next((f for f in schema.fields if f.name == "id"), None)141            if id_field:142                code_lines.extend([143                    f"        public override int GetHashCode() => {id_field.name}.GetHashCode();",144                    f"        public bool Equals(ushort other) => {id_field.name} == other;",145                    ""146                ])147 148        code_lines.append("    }")149        code_lines.append("}")150        return "\n".join(code_lines)151 152    def generate_schema_class(self, schema: SchemaDefinition) -> str:153        code_lines = []154        code_lines.extend([f"using {using};" for using in sorted(self.using_statements)])155        code_lines.append("")156 157        namespace = f"{schema.namespace}.Authoring.Schemas"158        code_lines.append(f"namespace {namespace}")159        code_lines.append("{")160 161        schema_class_name = f"{schema.name}Schema"162        type_string = schema.name.replace("Schema", "")163 164        code_lines.extend([165            f"    [CreateAssetMenu(menuName = \"{schema.menu_category}/\" + TypeString + \"/Create \" + FieldName, fileName = FieldName)]",166            f"    [AutoRef(",167            f"        nameof({schema.name}Settings), nameof({schema.name}Settings.schemas),",168            f"        FieldName, TypeString + \"/\" + FieldName",169            f"    )]",170            f"    public class {schema_class_name} : BakingSchema<{schema.name}>",171            "    {",172            f"        private const string FieldName = nameof({schema_class_name});",173            f"        private const string TypeString = \"{type_string}\";",174            ""175        ])176 177        # Schema fields178        for field in schema.fields:179            if field.is_reference and not field.is_blob_array:180                code_lines.append(f"        public {field.reference_type}Schema {field.name}Schema;")181            else:182                code_lines.append(f"        {self._generate_schema_field_declaration(field)}")183 184        code_lines.append("")185 186        # ToData187        code_lines.extend([188            f"        public override {schema.name} ToData()",189            "        {",190            f"            return new {schema.name}",191            "            {"192        ])193        for field in schema.fields:194            if field.name == "id":195                code_lines.append("                id = (ushort)ID,")196            elif field.is_reference and not field.is_blob_array:197                code_lines.append(f"                {field.name} = (ushort){field.name}Schema.ID,")198            elif field.is_blob_array:199                # Blob arrays handled in blob builder methods200                continue201            else:202                code_lines.append(f"                {field.name} = {field.name},")203        code_lines.extend([204            "            };",205            "        }",206            ""207        ])208 209        # Blob conversion methods if any blob arrays210        if any(f.is_blob_array for f in schema.fields):211            code_lines.extend(self._generate_blob_conversion_methods(schema))212 213        code_lines.append("    }")214        code_lines.append("}")215        return "\n".join(code_lines)216 217    def generate_blob_component(self, schema: SchemaDefinition) -> str:218        code_lines = []219        using_statements = [220            f"{schema.namespace}.Authoring.Data",221            "Unity.Entities",222            "Unity.Collections"223        ]224        code_lines.extend([f"using {using};" for using in using_statements])225        code_lines.append("")226 227        namespace = f"{schema.namespace}.Authoring.BlobComponents"228        code_lines.append(f"namespace {namespace}")229        code_lines.append("{")230        struct_name = f"{schema.name}Blob"231        code_lines.extend([232            f"    public struct {struct_name} : IComponentData",233            "    {",234            f"        public BlobAssetReference<BlobArray<{schema.name}>> BlobAssetRef;",235            "    }",236            "}"237        ])238        return "\n".join(code_lines)239 240    def generate_authoring(self, schema: SchemaDefinition) -> str:241        code_lines = []242        using_statements = [243            "System",244            f"{schema.namespace}.Authoring.BlobComponents",245            f"{schema.namespace}.Authoring.Schemas",246            "Unity.Entities",247            "UnityEngine"248        ]249        code_lines.extend([f"using {using};" for using in using_statements])250        code_lines.append("")251 252        namespace = f"{schema.namespace}.Authoring"253        code_lines.append(f"namespace {namespace}")254        code_lines.append("{")255 256        class_name = f"{schema.name}Authoring"257        schema_class_name = f"{schema.name}Schema"258        blob_class_name = f"{schema.name}Blob"259 260        code_lines.extend([261            f"    public class {class_name} : MonoBehaviour",262            "    {",263            f"        public {schema_class_name}[] {schema.name.lower()}Schemas = Array.Empty<{schema_class_name}>();",264            "",265            f"        public class {schema.name}Baker : Baker<{class_name}>",266            "        {",267            f"            public override void Bake({class_name} authoring)",268            "            {",269            "                var entity = GetEntity(TransformUsageFlags.None);",270            f"                AddComponent(entity, new {blob_class_name}",271            "                {",272            f"                    BlobAssetRef = {schema_class_name}.ToAssetRef(authoring.{schema.name.lower()}Schemas)",273            "                });",274            "            }",275            "        }",276            "    }",277            "}"278        ])279        return "\n".join(code_lines)280 281    def generate_create_node(self, schema: SchemaDefinition) -> str:282        code_lines = []283        code_lines.extend([f"using {using};" for using in sorted(self.using_statements)])284        code_lines.append("")285 286        namespace = f"{schema.namespace}.Authoring.Schemas"287        code_lines.append(f"namespace {namespace}")288        code_lines.append("{")289 290        class_name = f"{schema.name}SchemaCreateNode"291        schema_class_name = f"{schema.name}Schema"292 293        code_lines.extend([294            "    [Serializable]",295            f"    internal class {class_name} : SchemaCreateNodeBase<{schema_class_name}>",296            "    {",297            f"        protected override string DefaultFolder => \"{schema.folder_path}/{schema.name}/{schema_class_name}\";",298            ""299        ])300 301        # Option constants for non-reference, non-id fields302        non_reference_fields = [f for f in schema.fields if not f.is_reference and f.name != "id"]303        for field in non_reference_fields:304            const_name = f"Opt{self._pascal(field.name)}"305            code_lines.append(f"        const string {const_name} = \"{self._pascal(field.name)}\";")306        if non_reference_fields:307            code_lines.append("")308 309        if non_reference_fields:310            code_lines.extend([311                "        protected override void DefineCustomOptions(INodeOptionDefinition ctx)",312                "        {"313            ])314            for field in non_reference_fields:315                const_name = f"Opt{self._pascal(field.name)}"316                default_val = field.default_value or self._get_default_value_for_type(field.field_type)317                code_lines.append(318                    f"            ctx.AddNodeOption<{self._get_csharp_type(field.field_type)}>({const_name}, \"{self._pascal(field.name)}\", defaultValue: {default_val});")319            code_lines.extend([320                "        }",321                ""322            ])323 324        reference_fields = [f for f in schema.fields if f.is_reference]325        if reference_fields:326            code_lines.extend([327                "        protected override void DefineCustomPorts(IPortDefinitionContext ctx)",328                "        {"329            ])330            for field in reference_fields:331                port_name = self._pascal(field.name)332                code_lines.append(333                    f"            ctx.AddInputPort<{field.reference_type}Schema>(\"{port_name}\").Build();")334            code_lines.extend([335                "        }",336                ""337            ])338 339        code_lines.extend([340            f"        protected override void ApplyCustomFields({schema_class_name} a)",341            "        {"342        ])343        # Apply options for non-ref fields344        for field in non_reference_fields:345            const_name = f"Opt{self._pascal(field.name)}"346            var_name = field.name347            default_val = field.default_value or self._get_default_value_for_type(field.field_type)348            code_lines.append(f"            {self._get_csharp_type(field.field_type)} {var_name} = {default_val};")349            code_lines.append(350                f"            var p{self._pascal(var_name)} = GetNodeOptionByName({const_name}); p{self._pascal(var_name)}?.TryGetValue(out {var_name});")351            code_lines.append(f"            a.{field.name} = {var_name};")352            code_lines.append("")353 354        # Apply refs355        for field in reference_fields:356            port_name = self._pascal(field.name)357            if field.is_blob_array:358                code_lines.extend([359                    f"            var {field.name}Port = GetInputPortByName(\"{port_name}\");",360                    f"            var connected{self._pascal(field.name)} = new System.Collections.Generic.List<IPort>();",361                    f"            {field.name}Port.GetConnectedPorts(connected{self._pascal(field.name)});",362                    f"            a.{field.name} = connected{self._pascal(field.name)}",363                    f"                .Select(MissionGraph.ResolvePortValue<{field.reference_type}Schema>)",364                    f"                .Where(g => g != null)",365                    f"                .Distinct()",366                    f"                .ToArray();",367                    ""368                ])369            else:370                code_lines.extend([371                    f"            var {field.name}Port = GetInputPortByName(\"{port_name}\");",372                    f"            var {field.name} = MissionGraph.ResolvePortValue<{field.reference_type}Schema>({field.name}Port);",373                    f"            if ({field.name} != null) a.{field.name}Schema = {field.name};",374                    ""375                ])376 377        code_lines.extend([378            "        }",379            "    }",380            "}"381        ])382 383        return "\n".join(code_lines)384 385    def generate_schema_node(self, schema: SchemaDefinition) -> str:386        schema_class_name = f"{schema.name}Schema"387        class_name = f"{schema_class_name}Node"388        return f"""using System;389 390namespace {schema.namespace}.Authoring.Schemas391{{392    [Serializable]393    internal class {class_name} : SchemaNode<{schema_class_name}> {{}}394}}"""395 396    # ---- helpers ----397    def _pascal(self, s: str) -> str:398        if not s:399            return s400        parts = re.split(r'[_\s]+', s)401        return "".join(p[:1].upper() + p[1:] for p in parts if p)402 403    def _generate_field_declaration(self, field: SchemaField) -> str:404        ftype = field.field_type405        if field.is_blob_array:406            ftype = f"BlobArray<{ftype}>"407        return f"public {ftype} {field.name};"408 409    def _generate_schema_field_declaration(self, field: SchemaField) -> str:410        # Schema side needs authoring-friendly containers411        if field.is_blob_array:412            if field.is_reference and field.reference_type:413                return f"public {field.reference_type}Schema[] {field.name} = Array.Empty<{field.reference_type}Schema>();"414            else:415                return f"public {field.field_type}[] {field.name} = Array.Empty<{field.field_type}>();"416        else:417            # normal value or single id backing field418            return f"public {field.field_type} {field.name};"419 420    def _generate_blob_conversion_methods(self, schema: SchemaDefinition) -> List[str]:421        lines = []422        schema_class_name = f"{schema.name}Schema"423        lines.extend([424            f"        public static BlobAssetReference<BlobArray<{schema.name}>> ToAssetRef({schema_class_name}[] schemas)",425            "        {",426            "            var builder = new BlobBuilder(Allocator.Temp);",427            f"            ref var blobArray = ref builder.ConstructRoot<BlobArray<{schema.name}>>();",428            "            ToBlobArray(ref builder, ref blobArray, schemas);",429            f"            var blobAssetRef = builder.CreateBlobAssetReference<BlobArray<{schema.name}>>(Allocator.Persistent);",430            "            builder.Dispose();",431            "            return blobAssetRef;",432            "        }",433            "",434            f"        public static void ToBlobArray(ref BlobBuilder builder, ref BlobArray<{schema.name}> blobArray, {schema_class_name}[] schemas)",435            "        {",436            f"            var items = builder.Allocate(ref blobArray, schemas.Length);",437            "",438            "            for (int i = 0; i < schemas.Length; i++)",439            "            {",440            "                if (schemas[i] == null) continue;",441            f"                items[i] = schemas[i].ToData();",442            ""443        ])444        # Blob array fields445        lines.extend(self._generate_blob_array_assignments(schema))446        lines.extend([447            "            }",448            "        }"449        ])450        return lines451 452    def _generate_blob_array_assignments(self, schema: SchemaDefinition) -> List[str]:453        lines = []454        for field in schema.fields:455            if not field.is_blob_array:456                continue457            lines.append(f"                // {field.name} blob array")458            lines.append(f"                if (schemas[i].{field.name} != null && schemas[i].{field.name}.Length > 0)")459            lines.append("                {")460            lines.append(461                f"                    var arr = builder.Allocate(ref items[i].{field.name}, schemas[i].{field.name}.Length);")462            lines.append(f"                    for (int j = 0; j < schemas[i].{field.name}.Length; j++)")463            lines.append("                    {")464            if field.is_reference:465                lines.append(f"                        arr[j] = (ushort)schemas[i].{field.name}[j].ID;")466            else:467                lines.append(f"                        arr[j] = schemas[i].{field.name}[j];")468            lines.append("                    }")469            lines.append("                }")470            lines.append(f"                else builder.Allocate(ref items[i].{field.name}, 0);")471            lines.append("")472        return lines473 474    def _get_csharp_type(self, field_type: str) -> str:475        type_mapping = {476            "float2": "Unity.Mathematics.float2",477            "float3": "Unity.Mathematics.float3",478            "float4": "Unity.Mathematics.float4",479            "quaternion": "Unity.Mathematics.quaternion",480            "FixedString32Bytes": "Unity.Collections.FixedString32Bytes",481            "FixedString64Bytes": "Unity.Collections.FixedString64Bytes",482            "FixedString128Bytes": "Unity.Collections.FixedString128Bytes"483        }484        return type_mapping.get(field_type, field_type)485 486    def _get_default_value_for_type(self, field_type: str) -> str:487        defaults = {488            "byte": "0", "sbyte": "0", "short": "0", "ushort": "0",489            "int": "0", "uint": "0u", "long": "0L", "ulong": "0UL",490            "float": "0f", "double": "0.0", "bool": "false",491            "char": "'\\0'", "string": "\"\"",492            "float2": "Unity.Mathematics.float2.zero",493            "float3": "Unity.Mathematics.float3.zero",494            "float4": "Unity.Mathematics.float4.zero",495            "quaternion": "Unity.Mathematics.quaternion.identity",496            "FixedString32Bytes": "default",497            "FixedString64Bytes": "default",498            "FixedString128Bytes": "default"499        }500        return defaults.get(field_type, "default")501 502 503# -----------------------------504# Schema manager505# -----------------------------506 507class SchemaManager:508    def __init__(self):509        self.schemas: Dict[str, SchemaDefinition] = {}510        self.code_generator = CSharpCodeGenerator()511 512    def add_schema(self, schema: SchemaDefinition) -> None:513        self.schemas[schema.name] = schema514 515    def get_schema(self, name: str) -> Optional[SchemaDefinition]:516        return self.schemas.get(name)517 518    def list_schema_names(self) -> List[str]:519        return list(self.schemas.keys())520 521    def generate_all_code(self, schema_name: str) -> Dict[str, str]:522        schema = self.get_schema(schema_name)523        if not schema:524            return {}525 526        cg = self.code_generator527        results = {}528        results[f"{schema.name}.cs"] = cg.generate_data_struct(schema)529        if schema.generate_schema_class:530            results[f"{schema.name}Schema.cs"] = cg.generate_schema_class(schema)531        if schema.generate_blob_component:532            results[f"{schema.name}Blob.cs"] = cg.generate_blob_component(schema)533        if schema.generate_authoring:534            results[f"{schema.name}Authoring.cs"] = cg.generate_authoring(schema)535        if schema.generate_create_node:536            results[f"{schema.name}SchemaCreateNode.cs"] = cg.generate_create_node(schema)537        if schema.generate_schema_node:538            results[f"{schema.name}SchemaNode.cs"] = cg.generate_schema_node(schema)539        return results540 541 542schema_manager = SchemaManager()543 544 545# -----------------------------546# Gradio app547# -----------------------------548 549def create_gradio_interface():550    with gr.Blocks(title="C# Schema Code Generator", theme=gr.themes.Soft()) as interface:551        gr.Markdown("# C# Unity ECS Schema Code Generator")552        gr.Markdown(553            "Generate Unity ECS-friendly C# code with live preview. Edit fields directly in the table, import from C# struct, and dump as code.")554 555        # State556        current_schema_state = gr.State(value=None)557 558        # Helpers559        def _pascal(s: str) -> str:560            if not s:561                return s562            parts = re.split(r"[_\s]+", s)563            return "".join(p[:1].upper() + p[1:] for p in parts if p)564 565        def _guess_reference(field_name: str) -> Optional[str]:566            # id itself is not a reference567            if not field_name:568                return None569            if field_name.lower() == "id":570                return None571            m = re.match(r"^(.*?)(?:Id|ID|id)$", field_name)572            if not m:573                return None574            base = m.group(1)575            if not base:576                return None577            return _pascal(base)578 579        def normalize_table(table_value: Any) -> List[List[Any]]:580            try:581                if hasattr(table_value, "to_dict"):582                    records = table_value.to_dict(orient="records")583                    rows = [[r.get("Name"), r.get("Type"), bool(r.get("Blob Array") or False),584                             bool(r.get("Reference") or False), r.get("Ref Type") or "", r.get("Default") or ""]585                            for r in records]586                else:587                    rows = table_value or []588            except Exception:589                rows = table_value or []590            # Ensure 6 columns and clean rows591            cleaned = []592            for r in rows:593                if not r:594                    continue595                r = list(r) + [""] * (6 - len(r))596                name = (r[0] or "").strip()597                if not name:598                    continue599                ftype = (r[1] or "").strip() or "ushort"600                is_blob = bool(r[2])601                is_ref = bool(r[3])602                ref_type = (r[4] or "").strip()603                default_val = (r[5] or "").strip()604                cleaned.append([name, ftype, is_blob, is_ref, ref_type, default_val])605            return cleaned606 607        def table_to_fields(rows: List[List[Any]], auto_infer: bool) -> List[SchemaField]:608            fields: List[SchemaField] = []609            seen = set()610            for r in rows:611                if len(r) < 2:612                    continue613                name = str(r[0]).strip()614                if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', name):615                    gr.Warning(f"Skipping invalid field name: {name}")616                    continue617                if name in seen:618                    gr.Warning(f"Duplicate field '{name}' ignored (keep first).")619                    continue620                seen.add(name)621                ftype = str(r[1]).strip() or "ushort"622                is_blob = bool(r[2])623                is_ref = bool(r[3])624                ref_type = (str(r[4]).strip() if r[4] else None)625                default_val = (str(r[5]).strip() if r[5] else None)626 627                # Auto-infer references for *Id fields628                if auto_infer and name.lower() != "id":629                    guessed = _guess_reference(name)630                    if guessed:631                        is_ref = True if not is_ref else is_ref632                        if not ref_type:633                            ref_type = guessed634 635                if is_ref and (not ref_type):636                    gr.Warning(f"Field '{name}' marked as reference but has no reference type.")637 638                fields.append(SchemaField(639                    name=name,640                    field_type=ftype,641                    is_blob_array=is_blob,642                    is_reference=is_ref,643                    reference_type=ref_type,644                    default_value=default_val645                ))646            return fields647 648        def fields_to_table(fields: List[SchemaField]) -> List[List[Any]]:649            out = []650            for f in fields:651                out.append([652                    f.name,653                    f.field_type,654                    bool(f.is_blob_array),655                    bool(f.is_reference),656                    f.reference_type or "",657                    f.default_value or ""658                ])659            return out660 661        def ensure_id_field_consistency(schema: SchemaDefinition):662            has_id_field = any(f.name == "id" for f in schema.fields)663            if schema.implements_has_id and not has_id_field:664                schema.fields.insert(0, SchemaField(name="id", field_type="ushort"))665            if not schema.implements_has_id and has_id_field:666                schema.fields = [f for f in schema.fields if f.name != "id"]667 668        def build_temp_schema(669                name: str, ns: str, folder: str, menu: str,670                gen_schema: bool, gen_blob: bool, gen_auth: bool, gen_create: bool, gen_node: bool,671                has_id: bool, equatable: bool, auto_infer: bool, table_value: Any672        ) -> Optional[SchemaDefinition]:673            name = (name or "").strip()674            if not name or not re.match(r'^[A-Z][a-zA-Z0-9]*$', name):675                return None676            schema = SchemaDefinition(677                name=name,678                namespace=(ns or "DefaultNamespace").strip(),679                folder_path=(folder or "Assets/Settings").strip(),680                menu_category=(menu or "Hold").strip(),681                generate_schema_class=bool(gen_schema),682                generate_blob_component=bool(gen_blob),683                generate_authoring=bool(gen_auth),684                generate_create_node=bool(gen_create),685                generate_schema_node=bool(gen_node),686                implements_has_id=bool(has_id),687                implements_equatable=bool(equatable),688                fields=table_to_fields(normalize_table(table_value), auto_infer)689            )690            ensure_id_field_consistency(schema)691            return schema692 693        def generate_code_preview(schema: Optional[SchemaDefinition]) -> List[str]:694            if not schema:695                return [""] * 6696            cg = schema_manager.code_generator697            files = {698                "data": cg.generate_data_struct(schema),699                "schema": cg.generate_schema_class(schema) if schema.generate_schema_class else "",700                "blob": cg.generate_blob_component(schema) if schema.generate_blob_component else "",701                "authoring": cg.generate_authoring(schema) if schema.generate_authoring else "",702                "create": cg.generate_create_node(schema) if schema.generate_create_node else "",703                "node": cg.generate_schema_node(schema) if schema.generate_schema_node else "",704            }705            return [files["data"], files["schema"], files["blob"], files["authoring"], files["create"], files["node"]]706 707        # -----------------------------708        # Left column (config)709        # -----------------------------710        with gr.Row():711            with gr.Column(scale=1):712                gr.Markdown("### Current Fields (Editable)")713                fields_display = gr.DataFrame(714                    headers=["Name", "Type", "Blob Array", "Reference", "Ref Type", "Default"],715                    datatype=["str", "str", "bool", "bool", "str", "str"],716                    interactive=True,717                    label="Current Fields"718                )719 720                with gr.Row():721                    create_schema_btn = gr.Button("Create/Update Schema", variant="primary")722                    update_from_table_btn = gr.Button("Apply Table Changes", variant="secondary")723 724                with gr.Row():725                    save_config_btn = gr.Button("Save Configuration", variant="secondary")726                    load_config_btn = gr.Button("Load Configuration", variant="secondary")727 728                # Config file input729                config_file = gr.File(label="Select Configuration (.json)", file_types=[".json"], visible=False)730 731                gr.Markdown("## Schema Configuration")732                with gr.Group():733                    gr.Markdown("### Basic Information")734                    schema_name = gr.Textbox(label="Schema Name", placeholder="e.g., Mission, Location, Name",735                                             value="Mission")736                    namespace = gr.Textbox(label="Namespace", placeholder="e.g., Missions.Missions",737                                           value="Missions.Missions")738                    folder_path = gr.Textbox(label="Folder Path", placeholder="Assets/Settings",739                                             value="Assets/Settings")740                    menu_category = gr.Textbox(label="Menu Category", placeholder="Hold", value="Hold")741 742                with gr.Group():743                    gr.Markdown("### Generation Options")744                    with gr.Row():745                        gen_schema_class = gr.Checkbox(label="Schema Class", value=True)746                        gen_blob_component = gr.Checkbox(label="Blob Component", value=True)747                    with gr.Row():748                        gen_authoring = gr.Checkbox(label="Authoring", value=True)749                        gen_create_node = gr.Checkbox(label="Create Node", value=True)750                    with gr.Row():751                        gen_schema_node = gr.Checkbox(label="Schema Node", value=True)752                        implements_has_id = gr.Checkbox(label="IHasID", value=True)753                    implements_equatable = gr.Checkbox(label="IEquatable", value=True)754                    auto_infer_id_refs_ui = gr.Checkbox(label="Auto-detect '*Id' fields as references", value=True)755 756                with gr.Group():757                    gr.Markdown("### Add Field")758                    field_name = gr.Textbox(label="Field Name", placeholder="e.g., stationId, nameId")759                    field_type = gr.Dropdown(760                        label="Field Type",761                        choices=[ft.value for ft in FieldType],762                        value=FieldType.USHORT.value763                    )764                    with gr.Row():765                        is_blob_array = gr.Checkbox(label="Blob Array", value=False)766                        is_reference = gr.Checkbox(label="Schema Reference", value=False)767                    reference_type = gr.Textbox(768                        label="Reference Type",769                        placeholder="e.g., Name, Location (for NameSchema, LocationSchema)",770                        visible=False,771                        lines=2772                    )773                    default_value = gr.Textbox(label="Default Value (optional)", placeholder='0, false, ""')774                    with gr.Row():775                        add_field_btn = gr.Button("Add Field", variant="primary")776                        clear_fields_btn = gr.Button("Clear All Fields", variant="secondary")777 778                gr.Markdown("### Export")779                with gr.Row():780                    export_all_btn = gr.Button("Export All Files", variant="primary")781                    export_zip_btn = gr.Button("Download as ZIP", variant="secondary")782                export_files = gr.Files(label="Exported Files", file_count="multiple")783                zip_file_output = gr.File(label="Exported ZIP")784 785            # -----------------------------786            # Right column (code)787            # -----------------------------788            with gr.Column(scale=1):789                # --- Code Dump / Import-Export UI ---790                gr.Markdown("Paste a C# data struct to populate the UI/table, or dump the current schema as code.")791                csharp_dump_in = gr.Code(label="Paste C# Data Struct", language="c", interactive=True, lines=18)792                infer_id_refs = gr.Checkbox(label="Infer '*Id' fields as references", value=True)793                import_from_csharp_btn = gr.Button("Import From C#", variant="primary")794                with gr.Accordion("Code Dump / Import-Export", open=False):795                    with gr.Row():796                        dump_csharp_btn = gr.Button("Dump Current as C# Struct", variant="secondary")797                        dump_json_btn = gr.Button("Dump Current as JSON", variant="secondary")798                    dump_output = gr.Code(label="Dump Output", language="c", interactive=False, lines=18)799 800                gr.Markdown("## Generated Code")801                with gr.Tabs():802                    with gr.TabItem("Data Struct"):803                        data_struct_code = gr.Code(language="c", label="Data Struct Code", interactive=False)804                    with gr.TabItem("Schema Class"):805                        schema_class_code = gr.Code(language="c", label="Schema Class Code", interactive=False)806                    with gr.TabItem("Blob Component"):807                        blob_component_code = gr.Code(language="c", label="Blob Component Code", interactive=False)808                    with gr.TabItem("Authoring"):809                        authoring_code = gr.Code(language="c", label="Authoring Code", interactive=False)810                    with gr.TabItem("Create Node"):811                        create_node_code = gr.Code(language="c", label="Create Node Code", interactive=False)812                    with gr.TabItem("Schema Node"):813                        schema_node_code = gr.Code(language="c", label="Schema Node Code", interactive=False)814 815        # -----------------------------816        # Handlers817        # -----------------------------818 819        def on_is_reference_change(is_ref: bool):820            return gr.update(visible=is_ref)821 822        is_reference.change(823            fn=on_is_reference_change,824            inputs=[is_reference],825            outputs=[reference_type]826        )827 828        def apply_ui_and_preview(name, ns, folder, menu,829                                 gen_schema, gen_blob, gen_auth, gen_create, gen_node,830                                 has_id, equatable, auto_infer, table_value, current_schema):831            schema = build_temp_schema(name, ns, folder, menu,832                                       gen_schema, gen_blob, gen_auth, gen_create, gen_node,833                                       has_id, equatable, auto_infer, table_value)834            code = generate_code_preview(schema)835            if schema:836                return schema, code[0], code[1], code[2], code[3], code[4], code[5], fields_to_table(schema.fields)837            else:838                return current_schema, *([""] * 6), normalize_table(table_value)839 840        # Hook live updates to all meta controls, auto-infer toggle, and table841        live_inputs = [842            schema_name, namespace, folder_path, menu_category,843            gen_schema_class, gen_blob_component, gen_authoring, gen_create_node, gen_schema_node,844            implements_has_id, implements_equatable, auto_infer_id_refs_ui, fields_display, current_schema_state845        ]846        live_outputs = [847            current_schema_state,848            data_struct_code, schema_class_code, blob_component_code, authoring_code, create_node_code,849            schema_node_code,850            fields_display851        ]852 853        for c in [schema_name, namespace, folder_path, menu_category,854                  gen_schema_class, gen_blob_component, gen_authoring, gen_create_node, gen_schema_node,855                  implements_has_id, implements_equatable, auto_infer_id_refs_ui]:856            c.change(fn=apply_ui_and_preview, inputs=live_inputs, outputs=live_outputs)857 858        fields_display.change(fn=apply_ui_and_preview, inputs=live_inputs, outputs=live_outputs)859 860        def add_field(861                name, ftype, is_blob, is_ref, ref_type, default_val,862                s_name, ns, folder, menu, gen_schema, gen_blob, gen_auth, gen_create, gen_node, has_id, equatable,863                auto_infer,864                table_value, current_schema865        ):866            if not name or not name.strip():867                gr.Warning("Field name cannot be empty.")868                return apply_ui_and_preview(s_name, ns, folder, menu, gen_schema, gen_blob, gen_auth, gen_create,869                                            gen_node,870                                            has_id, equatable, auto_infer, table_value, current_schema)871            if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', name.strip()):872                gr.Warning("Invalid field name. Use only letters, numbers, and underscores.")873                return apply_ui_and_preview(s_name, ns, folder, menu, gen_schema, gen_blob, gen_auth, gen_create,874                                            gen_node,875                                            has_id, equatable, auto_infer, table_value, current_schema)876 877            rows = normalize_table(table_value)878 879            # Auto-infer references for *Id fields when adding880            if auto_infer and name.strip().lower() != "id":881                guessed = _guess_reference(name.strip())882                if guessed and not is_ref:883                    is_ref = True884                    if not (ref_type and ref_type.strip()):885                        ref_type = guessed886 887            if any((r[0] or "").strip() == name.strip() for r in rows):888                gr.Warning(f"Field '{name.strip()}' already exists.")889            else:890                rows.append([name.strip(), ftype, bool(is_blob), bool(is_ref), (ref_type or "").strip(),891                             (default_val or "").strip()])892 893            return apply_ui_and_preview(s_name, ns, folder, menu, gen_schema, gen_blob, gen_auth, gen_create, gen_node,894                                        has_id, equatable, auto_infer, rows, current_schema)895 896        add_field_btn.click(897            fn=add_field,898            inputs=[899                field_name, field_type, is_blob_array, is_reference, reference_type, default_value,900                schema_name, namespace, folder_path, menu_category,901                gen_schema_class, gen_blob_component, gen_authoring, gen_create_node, gen_schema_node,902                implements_has_id, implements_equatable, auto_infer_id_refs_ui,903                fields_display, current_schema_state904            ],905            outputs=live_outputs906        )907 908        def clear_all_fields(909                s_name, ns, folder, menu, gen_schema, gen_blob, gen_auth, gen_create, gen_node, has_id, equatable,910                auto_infer,911                table_value, current_schema912        ):913            rows = []914            return apply_ui_and_preview(s_name, ns, folder, menu, gen_schema, gen_blob, gen_auth, gen_create, gen_node,915                                        has_id, equatable, auto_infer, rows, current_schema)916 917        clear_fields_btn.click(918            fn=clear_all_fields,919            inputs=[schema_name, namespace, folder_path, menu_category,920                    gen_schema_class, gen_blob_component, gen_authoring, gen_create_node, gen_schema_node,921                    implements_has_id, implements_equatable, auto_infer_id_refs_ui,922                    fields_display, current_schema_state],923            outputs=live_outputs924        )925 926        update_from_table_btn.click(927            fn=apply_ui_and_preview,928            inputs=live_inputs,929            outputs=live_outputs930        )931 932        def create_or_update_schema(933                s_name, ns, folder, menu, gen_schema, gen_blob, gen_auth, gen_create, gen_node, has_id, equatable,934                auto_infer,935                table_value, current_schema936        ):937            if not s_name or not re.match(r'^[A-Z][a-zA-Z0-9]*$', s_name.strip()):938                gr.Warning("Schema name must start with uppercase letter and contain only letters and numbers.")939                return apply_ui_and_preview(s_name, ns, folder, menu, gen_schema, gen_blob, gen_auth, gen_create,940                                            gen_node,941                                            has_id, equatable, auto_infer, table_value, current_schema)942 943            schema = build_temp_schema(s_name, ns, folder, menu,944                                       gen_schema, gen_blob, gen_auth, gen_create, gen_node,945                                       has_id, equatable, auto_infer, table_value)946            if not schema:947                gr.Warning("Please fix schema errors before creating.")948                return apply_ui_and_preview(s_name, ns, folder, menu, gen_schema, gen_blob, gen_auth, gen_create,949                                            gen_node,950                                            has_id, equatable, auto_infer, table_value, current_schema)951 952            schema_manager.add_schema(schema)953            code = schema_manager.generate_all_code(schema.name)954            return (955                schema,956                code.get(f"{schema.name}.cs", ""),957                code.get(f"{schema.name}Schema.cs", ""),958                code.get(f"{schema.name}Blob.cs", ""),959                code.get(f"{schema.name}Authoring.cs", ""),960                code.get(f"{schema.name}SchemaCreateNode.cs", ""),961                code.get(f"{schema.name}SchemaNode.cs", ""),962                fields_to_table(schema.fields)963            )964 965        create_schema_btn.click(966            fn=create_or_update_schema,967            inputs=[schema_name, namespace, folder_path, menu_category,968                    gen_schema_class, gen_blob_component, gen_authoring, gen_create_node, gen_schema_node,969                    implements_has_id, implements_equatable, auto_infer_id_refs_ui, fields_display,970                    current_schema_state],971            outputs=live_outputs972        )973 974        def save_configuration(current_schema: Optional[SchemaDefinition]):975            if not current_schema:976                gr.Warning("No schema to save.")977                return None978            config_data = {979                "name": current_schema.name,980                "namespace": current_schema.namespace,981                "folder_path": current_schema.folder_path,982                "menu_category": current_schema.menu_category,983                "generate_schema_class": current_schema.generate_schema_class,984                "generate_blob_component": current_schema.generate_blob_component,985                "generate_authoring": current_schema.generate_authoring,986                "generate_create_node": current_schema.generate_create_node,987                "generate_schema_node": current_schema.generate_schema_node,988                "implements_has_id": current_schema.implements_has_id,989                "implements_equatable": current_schema.implements_equatable,990                "fields": [991                    {992                        "name": f.name,993                        "field_type": f.field_type,994                        "is_blob_array": f.is_blob_array,995                        "is_reference": f.is_reference,996                        "reference_type": f.reference_type,997                        "default_value": f.default_value,998                        "description": f.description999                    } for f in current_schema.fields1000                ]1001            }1002            tf = tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False)1003            json.dump(config_data, tf, indent=2)1004            tf.close()1005            return tf.name1006 1007        save_config_btn.click(1008            fn=save_configuration,1009            inputs=[current_schema_state],1010            outputs=[config_file]1011        )1012 1013        def show_file_picker():1014            return gr.update(visible=True)1015 1016        load_config_btn.click(1017            fn=show_file_picker,1018            outputs=[config_file]1019        )1020 1021        def load_configuration(file_obj):1022            if not file_obj:1023                gr.Warning("No file selected.")1024                return (None, "", "", "", "", True, True, True, True, True, True, True, [], "", "", "", "", "", "")1025 1026            try:1027                path = file_obj.name if hasattr(file_obj, "name") else file_obj1028                with open(path, "r") as f:1029                    data = json.load(f)1030                fields = []1031                for fld in data.get("fields", []):1032                    fields.append(SchemaField(1033                        name=fld["name"],1034                        field_type=fld["field_type"],1035                        is_blob_array=fld.get("is_blob_array", False),1036                        is_reference=fld.get("is_reference", False),1037                        reference_type=fld.get("reference_type"),1038                        default_value=fld.get("default_value"),1039                        description=fld.get("description")1040                    ))1041                schema = SchemaDefinition(1042                    name=data["name"],1043                    namespace=data.get("namespace", "DefaultNamespace"),1044                    folder_path=data.get("folder_path", "Assets/Settings"),1045                    menu_category=data.get("menu_category", "Hold"),1046                    generate_schema_class=data.get("generate_schema_class", True),1047                    generate_blob_component=data.get("generate_blob_component", True),1048                    generate_authoring=data.get("generate_authoring", True),1049                    generate_create_node=data.get("generate_create_node", True),1050                    generate_schema_node=data.get("generate_schema_node", True),1051                    implements_has_id=data.get("implements_has_id", True),1052                    implements_equatable=data.get("implements_equatable", True),1053                    fields=fields1054                )1055                cg = schema_manager.code_generator1056                results = {1057                    "data": cg.generate_data_struct(schema),1058                    "schema": cg.generate_schema_class(schema) if schema.generate_schema_class else "",1059                    "blob": cg.generate_blob_component(schema) if schema.generate_blob_component else "",1060                    "authoring": cg.generate_authoring(schema) if schema.generate_authoring else "",1061                    "create": cg.generate_create_node(schema) if schema.generate_create_node else "",1062                    "node": cg.generate_schema_node(schema) if schema.generate_schema_node else "",1063                }1064                return (1065                    schema,1066                    schema.name, schema.namespace, schema.folder_path, schema.menu_category,1067                    schema.generate_schema_class, schema.generate_blob_component, schema.generate_authoring,1068                    schema.generate_create_node, schema.generate_schema_node,1069                    schema.implements_has_id, schema.implements_equatable,1070                    fields_to_table(schema.fields),1071                    results["data"], results["schema"], results["blob"], results["authoring"], results["create"],1072                    results["node"]1073                )1074            except Exception as e:1075                gr.Warning(f"Error loading configuration: {e}")1076                return (None, "", "", "", "", True, True, True, True, True, True, True, [], "", "", "", "", "", "")1077 1078        config_file.change(1079            fn=load_configuration,1080            inputs=[config_file],1081            outputs=[1082                current_schema_state,1083                schema_name, namespace, folder_path, menu_category,1084                gen_schema_class, gen_blob_component, gen_authoring, gen_create_node, gen_schema_node,1085                implements_has_id, implements_equatable,1086                fields_display,1087                data_struct_code, schema_class_code, blob_component_code, authoring_code, create_node_code,1088                schema_node_code1089            ]1090        )1091 1092        def export_all_files(current_schema: Optional[SchemaDefinition]):1093            if not current_schema:1094                gr.Warning("No schema to export.")1095                return []1096            generated = schema_manager.generate_all_code(current_schema.name) or {}1097            paths = []1098            for filename, code in generated.items():1099                tf = tempfile.NamedTemporaryFile(mode="w", suffix=".cs", delete=False)1100                tf.write(code)1101                tf.close()1102                target = os.path.join(os.path.dirname(tf.name), filename)1103                try:1104                    os.replace(tf.name, target)1105                except Exception:1106                    target = tf.name1107                paths.append(target)1108            return paths1109 1110        export_all_btn.click(1111            fn=export_all_files,1112            inputs=[current_schema_state],1113            outputs=[export_files]1114        )1115 1116        def export_zip(current_schema: Optional[SchemaDefinition]):1117            if not current_schema:1118                gr.Warning("No schema to export.")1119                return None1120            generated = schema_manager.generate_all_code(current_schema.name) or {}1121            if not generated:1122                gr.Warning("Nothing to export.")1123                return None1124            tf_zip = tempfile.NamedTemporaryFile(suffix=".zip", delete=False)1125            tf_zip.close()1126            with zipfile.ZipFile(tf_zip.name, "w", zipfile.ZIP_DEFLATED) as zipf:1127                for filename, code in generated.items():1128                    zipf.writestr(filename, code)1129            return tf_zip.name1130 1131        export_zip_btn.click(1132            fn=export_zip,1133            inputs=[current_schema_state],1134            outputs=[zip_file_output]1135        )1136 1137        # ---------- C# import helpers ----------1138        def _strip_comments(code: str) -> str:1139            code = re.sub(r"//.*", "", code)1140            code = re.sub(r"/\*.*?\*/", "", code, flags=re.S)1141            return code1142 1143        def _extract_block(text: str, start_pos: int) -> str:1144            i = text.find("{", start_pos)1145            if i == -1:1146                return ""1147            depth, j = 1, i + 11148            while j < len(text):1149                c = text[j]1150                if c == "{":1151                    depth += 11152                elif c == "}":1153                    depth -= 11154                    if depth == 0:1155                        return text[i + 1: j]1156                j += 11157            return ""1158 1159        def _shorten_type(t: str) -> str:1160            t = t.strip()1161            t = t.replace("Unity.Mathematics.", "")1162            t = t.replace("Unity.Collections.", "")1163            t = t.replace("System.", "")1164            aliases = {1165                "UInt16": "ushort", "Int16": "short", "UInt32": "uint", "Int32": "int",1166                "UInt64": "ulong", "Int64": "long", "Boolean": "bool", "String": "string",1167            }1168            return aliases.get(t, t)1169 1170        def _canonicalize_type(t: str) -> Tuple[str, bool]:1171            t = t.strip()1172            m = re.match(r"BlobArray\s*<\s*([^>]+)\s*>", t)1173            if m:1174                inner = _shorten_type(m.group(1).strip())1175                return inner, True1176            if t.endswith("[]"):1177                inner = _shorten_type(t[:-2].strip())1178                return inner, True1179            t2 = _shorten_type(t)1180            return t2, False1181 1182        def parse_csharp_to_schema(code_text: str, infer_refs: bool, folder: str, menu: str) -> Optional[1183            SchemaDefinition]:1184            if not code_text or not code_text.strip():1185                return None1186            code = _strip_comments(code_text)1187 1188            ns_match = re.search(r"\bnamespace\s+([A-Za-z0-9_.]+)", code)1189            ns_full = ns_match.group(1) if ns_match else "DefaultNamespace"1190            base_ns = re.sub(r"\.Authoring\.Data$", "", ns_full)1191 1192            st_match = re.search(r"\bpublic\s+struct\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?::\s*([^{]+))?\s*\{", code)1193            if not st_match:1194                return None1195            struct_name = st_match.group(1)1196            interfaces_raw = st_match.group(2) or ""1197            interfaces = [i.strip() for i in interfaces_raw.split(",")] if interfaces_raw else []1198            implements_has_id = any(i.startswith("IHasID") for i in interfaces)1199            implements_equatable = any(i.startswith("IEquatable") for i in interfaces)1200 

Showing the first 1,200 of 1428 lines. Download the file for the rest.