{"id":37046982,"url":"https://github.com/simulation-tree/worlds","last_synced_at":"2026-01-14T05:32:05.985Z","repository":{"id":282257636,"uuid":"784446216","full_name":"simulation-tree/worlds","owner":"simulation-tree","description":"Native C# library for ECS","archived":false,"fork":false,"pushed_at":"2025-09-24T03:03:07.000Z","size":2052,"stargazers_count":1,"open_issues_count":5,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-24T11:23:54.145Z","etag":null,"topics":["csharp","dotnet","ecs","nativeaot"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/simulation-tree.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-04-09T21:45:23.000Z","updated_at":"2025-09-24T03:00:07.000Z","dependencies_parsed_at":"2025-03-13T16:29:45.519Z","dependency_job_id":"c88b0fb7-bcc6-46c5-8c56-1a08521e167c","html_url":"https://github.com/simulation-tree/worlds","commit_stats":null,"previous_names":["simulation-tree/worlds"],"tags_count":71,"template":false,"template_full_name":null,"purl":"pkg:github/simulation-tree/worlds","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simulation-tree%2Fworlds","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simulation-tree%2Fworlds/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simulation-tree%2Fworlds/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simulation-tree%2Fworlds/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/simulation-tree","download_url":"https://codeload.github.com/simulation-tree/worlds/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simulation-tree%2Fworlds/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28410594,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T01:52:23.358Z","status":"online","status_checked_at":"2026-01-14T02:00:06.678Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","dotnet","ecs","nativeaot"],"created_at":"2026-01-14T05:32:05.382Z","updated_at":"2026-01-14T05:32:05.977Z","avatar_url":"https://github.com/simulation-tree.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Worlds\n\n[![Test](https://github.com/simulation-tree/worlds/actions/workflows/test.yml/badge.svg)](https://github.com/simulation-tree/worlds/actions/workflows/test.yml)\n\nLibrary for implementing data as _components_, _arrays_, and _tags_, found on _entities_.\n\nEntities are stored within these _worlds_, which can then be serialized, deserialized, and\nappended to other worlds at runtime.\n\n### Creating worlds\n\n`World`s contain a `Schema`, describing which types are possible to use with it.\nWhen using a type that isn't registered while interacting with a world,\nerrors will be thrown in debug mode. All types that are used must be registered.\n\nSchemas can be loaded after a world is created, or by passing one to the constructor:\n```cs\nprivate static void Main()\n{\n    MetadataRegistryLoader.Load();              // register type metadata\n\n    Schema schema = new();\n    schema.RegisterComponent\u003cfloat\u003e();\n    schema.RegisterComponent\u003cint\u003e();\n    schema.RegisterComponent\u003cFruit\u003e();\n\n    using World world = new(schema);\n\n    uint entity = world.CreateEntity();\n    world.AddComponent(entity, 3.14f);\n    world.AddComponent(entity, 1337);\n    world.AddComponent(entity, new Fruit(25));\n}\n\npublic struct Fruit(uint value)\n{\n    public uint value = value;\n}\n```\n\u003e The `MetadataRegistryLoader` is part of the [`types`](https://github.com/simulation-tree/types) project and it initializes metadata for all types.\n\n### Schema loader\n\nIncluded is a generator for a `SchemaLoader` type for projects that have an entry point.\nIt ensures that all mentioned types with a world are registered. Saving the effort for\nmanually registering them, and making startup easier:\n```cs\nprivate static void Main()\n{\n    MetadataRegistryLoader.Load();              // register type metadata\n    Schema schema = SchemaLoader.Get();         // register components/arrays/tags\n    using World world = new(schema);\n\n    uint entity = world.CreateEntity();\n    world.AddComponent(entity, 3.14f);\n    world.AddComponent(entity, 1337);\n    world.AddComponent(entity, new Fruit(25));\n}\n```\n\n### Storing values in components\n\n```cs\nusing (World world = new())\n{\n    uint entity = world.CreateEntity();\n    world.AddComponent(entity, new Fruit(25));\n}\n```\n\n### Storing multiple values with arrays\n\nUnlike components, arrays offer a way to store multiple of the same type,\nand can be resized:\n```cs\nValues\u003cchar\u003e many = world.CreateArray(entity, \"Hello world\".AsSpan());\nmany.Length = 5;\nAssert.That(moreMany.AsSpan().ToString(), Is.EqualTo(\"Hello\"));\n\nmany.AddRange(\" there\".AsSpan());\nAssert.That(many.AsSpan().ToString(), Is.EqualTo(\"Hello there\"));\n```\n\n### Tagging entities\n\nEntities can be tagged with any type, and then queried for:\n```cs\npublic struct IsThing\n{\n}\n\nuint entity = world.CreateEntity();\nworld.AddTag\u003cIsThing\u003e(entity);\n\nAssert.That(world.Contains\u003cIsThing\u003e(entity), Is.True);\n```\n\n### Fetching data and querying\n\nPolling of components, and modifying them can be done through a few different ways.\n\n**Manual**\n\nThis approach performs the quickest:\n```cs\nuint sum = 0;\n\nvoid Do()\n{\n    int componentType = world.Schema.GetComponentType\u003cFruit\u003e();\n    int tagType = world.Schema.GetTagType\u003cIsThing\u003e();\n    ReadOnlySpan\u003cChunk\u003e chunks = world.Chunks;\n    for (int c = 0; c \u003c chunks.Length; c++)\n    {\n        Chunk chunk = chunks[c];\n        if (chunk.componentTypes.Contains(componentType) \u0026\u0026 !chunk.tagTypes.Contains(tagType))\n        {\n            Span\u003cFruit\u003e components = chunk.GetComponents\u003cFruit\u003e(componentType);\n            ReadOnlySpan\u003cuint\u003e entities = chunk.Entities;\n            for (int i = 0; i \u003c entities.Length; i++)\n            {\n                uint entity = entities[i];\n                ref Fruit component = ref components[i];\n                component.value *= 2;\n                sum += component.value;\n            }\n        }\n    }\n}\n```\n\n**ComponentQuery**\n\nThis approach is the next quicker, and requires less code to write:\n```cs\nuint sum = 0;\n\nvoid Do()\n{\n    ComponentQuery\u003cFruit\u003e query = new(world);\n    query.ExcludeTags\u003cIsThing\u003e();\n    foreach (var x in query)\n    {\n        uint entity = x.entity;\n        ref Fruit component = ref x.component1;\n        component.value *= 2;\n        sum += component.value;\n    }\n}\n```\n\n**Get methods**\n\nOther approaches through extension methods like `GetAllContaining` don't lend themselves\nto quicker runtimes:\n```cs\nuint sum;\n\nvoid Do()\n{\n    foreach (uint entity in world.GetAllContaining\u003cFruit\u003e())\n    {\n        if (world.ContainsTag\u003cIsThing\u003e(entity))\n        {\n            continue;\n        }\n\n        //this approach suffers from having to fetch each component individually\n        ref Fruit component = ref world.GetComponent\u003cFruit\u003e(entity);\n        component.value *= 2;\n        sum += component.value;\n    }\n}\n```\n\n### Relationship references to other entities\n\nComponents with `uint` values that are _meant_ to reference other entities will be\nsusceptible to drift after serialization. This is because the entity value represents\na position, that may be occupied by another existing entity.\n\nThis is solved by storing the references locally and accessing them with an `rint` index.\nWhen worlds are appended to another world, those referenced entities can shift together\nas they're added, preserving the relationship.\n\n```cs\npublic struct MyReference(rint entityReference)\n{\n    public rint entityReference = entityReference;\n}\n\nusing World dummyWorld = new(SchemaLoader.Get());\nuint firstEntity = dummyWorld.CreateEntity();\nuint secondEntity = dummyWorld.CreateEntity();\nrint entityReference = dummyWorld.AddReference(firstEntity, secondEntity);\ndummyWorld.AddComponent(firstEntity, new MyReference(entityReference));\n\n//after appending, find the original first entity and its referenced second entity\nworld.Append(dummyWorld);\nworld.TryGetFirst(out uint oldFirstEntity, out MyReference component);\nuint oldSecondEntity = world.GetReference(oldFirstEntity, component.entityReference);\n```\n\n### The `Entity` wrapper\n\nIn addition to the original API, can also use `Entity` instances. Which wrap the\n`uint` value for the entity and the `World` instance:\n```cs\nEntity entity = new(world);\nentity.AddComponent(new Fruit(1337));\n\nref Fruit component = ref entity.GetComponent\u003cFruit\u003e();\ncomponent.value *= 2;\n\nSpan\u003cchar\u003e text = entity.CreateArray\u003cchar\u003e(\"Hello world\".AsSpan());\n```\n\n### Forming entity types\n\nA commonly reused pattern with components is to formalize them into argued objects, where the\ntype is qualified by the data present on the entity. For example, if an entity\ncontains a `PlayerName`, then its a player entity. This design is supported with the\n`IEntity` interface and its required `Describe()` method:\n```cs\npublic struct PlayerName(ASCIIText32 name)\n{\n    public ASCIIText32 name = name;\n}\n\npublic readonly partial struct Player : IEntity\n{\n    public readonly ref ASCIIText32 Name =\u003e ref GetComponent\u003cPlayerName\u003e().name;\n\n    readonly void IEntity.Describe(ref Archetype archetype)\n    {\n        archetype.AddComponentType\u003cPlayerName\u003e();    \n    }\n\n    public Player(World world, ASCIIText32 name)\n    {\n        this.world = world;\n        value = world.CreateEntity(new PlayerName(name));\n    }\n}\n\n//creating a player using its type's constructor\nPlayer player = new(world, \"unnamed\");\n```\n\u003e Only entity types that are partial will have all of the world API available\n\nThese types can then be used to transform or interpret existing entities:\n```cs\n//creating an entity, and making it into a player\nEntity supposedPlayer = new(world);\nAssert.That(supposedPlayer.Is\u003cPlayer\u003e(), Is.False);\nsupposedPlayer.Become\u003cPlayer\u003e();\nAssert.That(supposedPlayer.Is\u003cPlayer\u003e(), Is.True);\n\nPlayer player = supposedPlayer.As\u003cPlayer\u003e();\nAssert.That(player.IsCompliant, Is.True);\nplayer.Name = \"New name\";\n```\n\nThese entity types can be implicitly casted to `Entity`, and explicitly back:\n```cs\nPlayer player = new(world, \"unnamed\");\nEntity entity = player;\nplayer = entity.As\u003cPlayer\u003e();\n```\n\n### Serializing and appending\n\nEach world instance is portable, and can be serialized and deserialized\nin another executable:\n```cs\nSchema schema = SchemaLoader.Get();\nusing World prefabWorld = new(schema);\nEntity entity = new(prefabWorld);\nentity.AddComponent(new Fruit(1337));\nentity.CreateArray\u003cchar\u003e(\"Hello world\".AsSpan());\n\nusing ByteWriter writer = new();\nwriter.WriteObject(prefabWorld);\nReadOnlySpan\u003cbyte\u003e bytes = writer.AsSpan();\n\nusing ByteReader reader = new(bytes);\nusing World loadedWorld = World.Deserialize(reader);\nusing World anotherWorld = new(schema);\nanotherWorld.Append(loadedWorld);\n```\n\n### Processing deserialized schemas\n\nWhen worlds are serialized, they contain the original schema that was used. Storing\nthe original `TypeLayout` values for describing each component/array/tag type.\nAllowing for them to be processed when loaded in a different executable, and rerouting types\nto other types if the original isn't:\n```cs\nusing World loadedWorld = World.Deserialize(reader, Process);\n\nstatic TypeLayout Process(TypeLayout type, DataType.Kind dataType)\n{\n    if (type.Name.SequenceEquals(\"Fruit\") \u0026\u0026 type.Size == sizeof(uint))\n    {\n        //Fruit type not in this project, change to uint\n        return MetadataRegistry.GetType\u003cuint\u003e();\n    }\n    else\n    {\n        return type;\n    }\n}\n```\n\n### Contributing and design\n\nThis library implements the \"[entity-component-system](https://en.wikipedia.org/wiki/Entity_component_system)\" pattern of the \"archetype\" variety.\n\nCreated for building programs of whatever kind, with an open door for targeting runtime efficiency. Favoring faster data access and iteration.\n\nContributions to this goal are welcome.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimulation-tree%2Fworlds","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsimulation-tree%2Fworlds","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimulation-tree%2Fworlds/lists"}