{"id":21653613,"url":"https://github.com/karnkaul/dens","last_synced_at":"2025-03-20T04:31:58.312Z","repository":{"id":133375084,"uuid":"425618779","full_name":"karnkaul/dens","owner":"karnkaul","description":"Archetype-based entity-component framework using C++20","archived":false,"fork":false,"pushed_at":"2022-05-18T00:40:48.000Z","size":41,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-01-25T06:11:21.009Z","etag":null,"topics":["archetype","cpp","cpp20","ecs","entity-framework"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/karnkaul.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"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,"publiccode":null,"codemeta":null}},"created_at":"2021-11-07T21:21:14.000Z","updated_at":"2022-12-05T15:53:24.000Z","dependencies_parsed_at":null,"dependency_job_id":"0fdb77d2-a363-4b60-a129-35f2237f6b6a","html_url":"https://github.com/karnkaul/dens","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karnkaul%2Fdens","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karnkaul%2Fdens/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karnkaul%2Fdens/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karnkaul%2Fdens/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/karnkaul","download_url":"https://codeload.github.com/karnkaul/dens/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244552028,"owners_count":20470973,"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":["archetype","cpp","cpp20","ecs","entity-framework"],"created_at":"2024-11-25T08:19:32.064Z","updated_at":"2025-03-20T04:31:58.307Z","avatar_url":"https://github.com/karnkaul.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# dens\n\n[![Build Status](https://github.com/karnkaul/dens/actions/workflows/ci.yml/badge.svg)](https://github.com/karnkaul/dens/actions/workflows/ci.yml)\n\nA lightweight and simple archetype-based entity-component framework. `dens` is simply short for \"dense\" or \"densely packed components\".\n\n## Features\n\n- Header-only interface\n- Based on archetypes, aka unique sets of component types\n- No type / component registrations required\n- Exclusion typelist for queries\n- Multiple simultaneous registries\n- Components stored directly as (type-erased) `std::vector\u003cT\u003e`\n- Minimal type erasure: only one `void*` and `reinterpret_cast` throughout library\n- Base class templates for systems and groups (of systems)\n\n### Limitations\n\n- Supports a maximum of one component instance per type per entity / concrete system instance per group\n\n## Usage\n\n### Examples\n\n```cpp\n#include \u003ciostream\u003e\n#include \u003cstring\u003e\n#include \u003cdens/registry.hpp\u003e\n\nint main() {\n  dens::registry r;\n  auto e1 = r.make_entity(); // no components attached\n  auto e2 = r.make_entity\u003cint, float\u003e(); // default construct and attach int and float\n\n  r.attach\u003cint\u003e(e1) = 42; // attach trivial\n  r.attach\u003cstd::string\u003e(e1, \"hello\"); // attach non-trivial\n\n  r.get\u003cint\u003e(e2) = -9; // get reference to int (must be attached)\n  if (auto f = r.find\u003cfloat\u003e(e2)) { *f = 3.14f; } // get pointer to float if attached\n\n  // iterate over all ints and floats\n  for (auto e : r.view\u003cint, float\u003e()) {\n    auto [i, f] = e.components;\n    std::cout \u003c\u003c r.name(e) \u003c\u003c \": int: \" \u003c\u003c i \u003c\u003c \"float: \" \u003c\u003c f \u003c\u003c \"\\n\";\n  }\n  // iterate over all strings that do not have floats or chars attached\n  for (auto e : r.view\u003cstd::string\u003e(dens::exclude\u003cfloat, char\u003e())) {\n    std::cout \u003c\u003c r.name(e) \u003c\u003c \": int: \" \u003c\u003c e.get\u003cint\u003e() \u003c\u003c \"\\n\";\n  }\n\n  std::cout \u003c\u003c \"string detached from e1: \" \u003c\u003c r.detach\u003cstd::string\u003e(e1) \u003c\u003c \"\\n\";\n  std::cout \u003c\u003c \"entity count: \" \u003c\u003c r.size() \u003c\u003c \"\\n\";\n\n  r.clear();\n}\n```\n\n```cpp\n#include \u003cdens/system_group.hpp\u003e\n\nstruct task_scheduler {};\n\nstruct sys_data {\n  task_scheduler\u0026 tasks;\n  float dt{};\n};\n\nusing sys_base = dens::system\u003csys_data\u003e;\nusing sys_group = dens::system_group\u003csys_data\u003e;\n\nclass foo_system : public sys_base {\n  void update(dens::registry const\u0026 registry) {\n    float const dt = data().dt;\n    // ...\n  }\n};\n\nclass scene {\n public:\n  scene() {\n    m_root.attach\u003cfoo_system\u003e();\n  }\n\n  void tick(float dt) {\n    m_root.update(m_registry, sys_data{.tasks = m_tasks, .dt = dt});\n  }\n\n private:\n  task_scheduler m_tasks;\n  dens::registry m_registry;\n  sys_group m_root;\n};\n```\n\n### Requirements\n\n- CMake\n- C++20 compiler (and stdlib)\n\n### Integration\n\n1. Clone repo / add a git submodule in an appropriate subdirectory, say `dens`\n1. Add library to project via: `add_subdirectory(dens)` and `target_link_libraries(foo dens::dens)`\n1. Use via `#include \u003cdens/registry.hpp\u003e`\n1. Configure with `DENS_BUILD_TESTS=ON` to build tests executables in `tests`\n\n### Architecture\n\nThe main motivations behind / goals for `dens` are for it to:\n\n- Be lightweight and simple\n- Focus on ergonomics without compromising too much on performance\n- Use minimal type erasure / weak typing\n\nFor a detailed background on ECS, Michele Caini's [excellent articles on it](https://skypjack.github.io/), particularly the one [describing archetypes](https://skypjack.github.io/2019-03-07-ecs-baf-part-2/) are recommended. As a brief recap: an **archetype** represents a specific collection of component types, and owns all entities that have that exact set of components attached. Adding/removing components to an entity results in it being moved between corresponding archetypes, and component queries returns a list of entities whose archetypes have at least the desired types attached.\n\n```\narchetype =\u003e components types\nA0 =\u003e P, Q\nA1 =\u003e P, S\nA2 =\u003e P, Q, R\n\nquery\u003cP\u003e =\u003e [A0, A1, A2]\nquery\u003cQ, P\u003e =\u003e [A0, A2]\n```\n\n#### Archetype\n\nIn `dens`, an `archetype` is a vector of uniquely identified `tarray`s, and a vector of entities, where each `tarray` holds a `std::vector\u003cT\u003e`. Each \"column\" represents a unique `entity` and its attached components. The sizes of all these vectors in an archetype are always equal: this is a required invariant.\n\n```\narchetype\u003cA, B\u003e\n+-------------------------------------------+\n| index     || 0 | 1 | 2 ||                 |\n| entity    || e2 | e1 | e3 ||              |\n| tarray\u003cA\u003e ||    A    |    A    |    A    ||\n| tarray\u003cB\u003e ||   B   |   B   |   B   ||     |\n+-------------------------------------------+\n```\n\nAn `entity` is a strongly typed pair of IDs (identifying the `registry` and `entity` each), which also functions as a primary key into an internal database of `record`s, maintained by the `registry`. A `record` identifies an entity's owning `archetype` (if any) and its index among the columns, and is updated whenever an entity changes its archetype or is swapped with another in the same archetype (index changed). A swap-to-back-and-pop approach is used whenever columns need to be moved, minimizing the number of column adjustments (at the cost of columns being stored in an unordered fashion).\n\n#### Registry\n\n`registry` is the primary database and user-facing interface, owning all `archetype`s and `record`s. A new `record` is created for each entity, initially with no associated `archetype`. As components are attached / detached, `archetype`s are fetched / created and components added / moved as necessary. Since components are stored as `std::vector\u003cT\u003e`s, each `T` must be move constructible (and _will_ be relocated on archetype migration). Destroying an entity erases its corresponding column from its `archetype` (if any) and removes its `record`. Such \"destroyed\" entities can be reused if needed: a record will simply be recreated for the same ID\u003csup\u003e**1**\u003c/sup\u003e.\n\n\u003e _\u003csup\u003e**1**\u003c/sup\u003eattempting to attach components to a default constructed entity / one not owned by the registry in question will trigger an assert._\n\n`registry::view\u003cT...\u003e()` returns a vector of `entity_view\u003cT...\u003e`, which comprises of an entity and references to its components (as `std::tuple\u003cT\u0026\u003e`). This list is built by probing existing archetypes and adding the columns of those which have at least all `T...`s to the result. An optional `exclude\u003cT...\u003e` argument can be passed to `view()`, which will be treated as a type blocklist (archetypes that do have any of those components will be skipped).\n\n#### System\n\n`dens` does not use / expect global / static data. Thus `system\u003cData\u003e` is a class template where `Data` is a customizable type, a const reference to which must be passed to each system's `update()`. `system\u003cData\u003e` is polymorphic and intended to be derived from to implement update-able systems. During updates a derived type may use `.data()` to obtain the passed `Data const\u0026`\u003csup\u003e**2**\u003c/sup\u003e.\n\n\u003e _\u003csup\u003e**2**\u003c/sup\u003eattempting to access `.data()` outside `update()` will trigger an assert._\n\n`system_group\u003cData\u003e` derives from `system\u003cData\u003e` and is capable of attaching unique instances of derived systems, each associated with a signed `order` of execution (default `0`). It can also be derived from and attached, to form a tree of groups. The root group will update all attached systems in a depth-first manner. All groups are updated on the main thread, `Data` can be used for delegating tasks during an update (as demonstrated in the example above).\n\n## Contributing\n\nPull/merge requests are welcome.\n\n**[Original Repository](https://github.com/karnkaul/dens)**\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkarnkaul%2Fdens","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkarnkaul%2Fdens","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkarnkaul%2Fdens/lists"}