{"id":15680595,"url":"https://github.com/sof3/dynec","last_synced_at":"2025-04-30T16:07:40.210Z","repository":{"id":44356653,"uuid":"456577949","full_name":"SOF3/dynec","owner":"SOF3","description":"An opinionated ECS-like framework","archived":false,"fork":false,"pushed_at":"2024-04-10T12:59:34.000Z","size":111673,"stargazers_count":11,"open_issues_count":10,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-10-04T16:43:49.652Z","etag":null,"topics":["cache-locality","ecs","ecs-framework","static-archetype"],"latest_commit_sha":null,"homepage":"https://sof3.github.io/dynec/master/book/index.html","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SOF3.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","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}},"created_at":"2022-02-07T16:06:19.000Z","updated_at":"2024-04-10T11:26:48.000Z","dependencies_parsed_at":"2024-04-10T10:52:24.870Z","dependency_job_id":null,"html_url":"https://github.com/SOF3/dynec","commit_stats":{"total_commits":170,"total_committers":2,"mean_commits":85.0,"dds":0.00588235294117645,"last_synced_commit":"bade2b3083f9348b740dd83e34af4478dadee353"},"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SOF3%2Fdynec","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SOF3%2Fdynec/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SOF3%2Fdynec/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SOF3%2Fdynec/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SOF3","download_url":"https://codeload.github.com/SOF3/dynec/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":222666838,"owners_count":17019883,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["cache-locality","ecs","ecs-framework","static-archetype"],"created_at":"2024-10-03T16:43:21.329Z","updated_at":"2024-11-02T03:04:30.671Z","avatar_url":"https://github.com/SOF3.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# dynec\nAn opinionated ECS-like framework.\n\n[![CI](https://github.com/SOF3/dynec/actions/workflows/ci.yml/badge.svg)](https://github.com/SOF3/dynec/actions/workflows/ci.yml)\n[![codecov](https://codecov.io/gh/SOF3/dynec/branch/master/graph/badge.svg?token=WAU2FOLHVW)](https://codecov.io/gh/SOF3/dynec)\n\n## What is ECS?\nECS is a data-oriented programming paradigm that focuses on optimizing the CPU cache.\nObjects (\"Entities\") store their data in \"Components\",\nwhich are processed in \"Systems\".\n\n## dynec has E, C and S, but it is not the typical ECS\ndynec is **statically archetyped**.\nThe archetype of an entity refers to the set of components it can have,\nwhich is comparable to the *class* of an object in OOP.\nIn dynec, entities cannot change to another archetype once created.\nEntities can still have optional components, but they must be known in advance.\n\nThis allows entity references to be strictly typed.\nWhen you hold an entity reference,\nyou are assured that all entities in the reference are present.\nEntities of different archetypes are stored separately,\nwhich further improves *cache locality*\n(since components of different archetypes are mostly unrelated).\nComponents can also declare that they must always be present on entities of an archetype,\nwhich give you more confidence that the component really exists.\n\nFurthermore, archetypes cannot be subtyped.\nThis means that, unlike the traditional ECS,\nthere is no \"join query\" that queries all entities with a subset of components present.\nIteration can only be performed on all entities of an archetype\n(it is also possible to iterate over all entities with a single component,\nbut this is for a different purpose).\nIf you want to fetch all entities with all of multiple components\nlike how you would usually do in other frameworks,\nyou probably wanted to split them to be a separate entity instead.\n\n### Doesn't this make polymorphism more difficult to use?\nI imagine your design is like this:\nsome entities have the \"pig\" archetype,\nsome have the \"bird\" archetype,\nboth share the common animal components,\nwhile \"bird\" also has the additional flight- and egg-related components.\n\nThis is not the perspective to organize entities in dynec.\nPigs and birds are both the same archetype, let's say \"animal\".\nThe term \"bird\" is just an umbrella term to refer to abilities such as flying and laying eggs.\nIt should not be a concept that appears in the code logic at all,\nbecause \"bird\" does not really mean anything at the programming level.\nIn a sense, entities in dynec are comparable to some optional components in traditional ECS.\n\nThis implies entities would have a lot of references among them \u0026mdash;\na bird entity needs to reference its flight management entity and egg-laying entity.\nAlthough this seems to complicate the design a lot,\nthis is actually inevitable in high-quality software\nwhere one-to-one relationship is probably rare compared to one-to-many relationship.\nFor example, a bird may lay multiple types of eggs,\nwhich would result in multiple egg-laying entities;\nthis cannot be trivially managed anyway.\n\n## Entities are (optionally) reference-counted and trackable\nWhen debug assertions are enabled, all entity references are counted.\nWhen an entity is deleted, dynec panics if there are still dangling references to the entity\nand searches for the dangling references from all components and states in the world.\nThis means we can be (mostly) confident that any entity reference points to a live one,\nand enables reduction of the size of a strong entity reference to one integer\nbecause strong reference should not be able to outlive the referenced entity\n(most ECS frameworks require another integer to store the \"generation\"\nto avoid dangling references from pointing to a new entity recreated at the same offset).\n\nDropping all references before an entity is deleted sounds troublesome to implement,\nbut dynec provides two solutions for this.\nFirst, dynec supports \"finalizer components\",\nwhere components serve as [asynchronous finalizers][k8s-finalizer].\nSystems can create finalizers that ensure that references to the entity are dropped\nbefore the actual deletion (and the dangling reference check) takes place.\nThis gives different systems the chance to clean up an entity\nwithout losing the context that describes the entity\n(because components are dropped after deletion and cannot be read anymore).\nSecond, if it is really necessary to retain the (dangling) entity reference,\nyou can store a \"weak reference\" instead \u0026mdash;\nweak references are also reference-counted, but they do not cause a dangling reference panic.\n\nNevertheless, in order to track where entities are located,\nall components and global and system-local states (basically all storages managed by dynec)\nmust implement a trait that supports scanning all owned strong/weak entity references.\ndynec provides a derive macro to achieve this,\nbut since Rust does not support specialization (yet),\nan `#[entity]` attribute needs to be applied on every field that may reference entities.\nHowever, with the use of static assertions,\nmost mistakes in implementing the trait can be revealed at compile time\n(exceptions are types with generic parameters, which require manual confirmation).\nDespite all the trouble, the ability to scan for entity references make more features feasible,\nincluding automatic system dependency declaration and entity rearrangement (described below).\n\n## Entities can have multiple components of the same type\nHow would we store the health of a player?\nWe create a `Health` component for player entities.\nWhat if we also want to store the hunger of a player?\nOK, we also add a `Hunger` component for player entities.\nNow, what if we have an unknown number of such attributes,\ndetermined at runtime (e.g. by plugins or an authoritative game server)?\nSince we cannot declare types dynamically, it seems we have to refactor into a map or a Vec.\nOr maybe a `SmallVec\u003c[Attribute; N]\u003e` to avoid heap allocation.\nWait, how much is `N`?\n\nIn dynec, we avoid this problem with \"isotope components\".\nSimilar to isotopes in chemistry,\nthere may be multiple components of the same type (`Attribute`) for the same entity,\nbut these components belong to different \"discriminants\" (e.g. the attribute ID).\nSo in terms of semantics, it looks as if we got a `HashMap\u003cAttributeId, Attribute\u003e` component,\nbut in terms of performance, each `AttributeId` gets allocated in a new storage\nas if it is a different component.\nThis design is also more efficient in the example here,\nbecause some systems may only want to manipulate health but not hunger,\nso it should be able to execute concurrently with systems that use the hunger attribute;\nit is also better for cache locality since it avoids striding attributes with unused values.\nThis is not possible in ECS frameworks that only support type-based component key,\nwhich lack flexibility for dynamically defined logic.\n\n## Entities can be rearranged to optimize random access (not yet implemented)\nOne of the reasons why ECS performs better than traditional OOP-based code style\nis that components are stored in a compact region instead of scattered around the heap,\nreducing the frequency of CPU cache penetration that causes slow memory access.\nHowever, when the amount of data is large,\nsince entities are typically randomly arranged (no less random than heap allocation),\nsystems may need to access components from entities arranged far apart.\nFor example, in the case of iterating over all edges in a network simulation\n(where nodes and edges are entities, and edges have components referencing the endpoint nodes),\nalthough the data describing the edge itself are contiguously arranged,\naccessing the data for the endpoint nodes would lead to random memory access,\ngreatly deterriorating the performance.\n\nIn dynec, since all entity references are trackable,\nit is possible to permute all entities of the same archetype\nso that relevant entities are located more closely.\nFor example, in the case of a spatial graph\n(where edge length is comparable to node density, i.e. very few super-long edges),\nwe can perform an quadtree/octree sort on all nodes and edges such that\niterating over all edge entities would process spatially nearby edges,\nwhich in turn accesses spatially nearby nodes,\nboth of which have higher chance of getting nearby memory allocation.\n\nOf course, entity rearrangement is only useful for scenarios\nwhere the ideal entity arrangement can be retained for a long period.\nFor example, it is useful to rearrange buildings on a map because they are mostly stationary,\nbut it is not useful to rearrange cars travelling on the map since their order quickly changes\n(unless cars have very slow speed or move in a similar direction as nearby cars).\nSince entity rearrangement requires mutable access to all component storages for an archetype\nand processes a lot of data at the same time,\nthis is a stop-the-world operation that must not be performed frequently,\nso the period for which the arrangement drifts away (such that rearrangement is necessary)\nshould be negligibly long such that user experience is not affected.\n\n[k8s-finalizer]: https://kubernetes.io/docs/concepts/overview/working-with-objects/finalizers/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsof3%2Fdynec","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsof3%2Fdynec","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsof3%2Fdynec/lists"}