{"id":13732287,"url":"https://github.com/Yelnats321/EntityPlus","last_synced_at":"2025-05-08T06:31:51.578Z","repository":{"id":196434990,"uuid":"60579882","full_name":"Yelnats321/EntityPlus","owner":"Yelnats321","description":"A C++14 Entity Component System","archived":false,"fork":false,"pushed_at":"2020-08-22T05:48:36.000Z","size":223,"stargazers_count":188,"open_issues_count":8,"forks_count":11,"subscribers_count":11,"default_branch":"master","last_synced_at":"2024-05-02T20:13:58.662Z","etag":null,"topics":["entity","entity-component","entity-component-system","gamedev"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Yelnats321.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE_1_0.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2016-06-07T03:44:29.000Z","updated_at":"2024-04-21T16:51:04.000Z","dependencies_parsed_at":"2023-10-01T19:53:43.251Z","dependency_job_id":null,"html_url":"https://github.com/Yelnats321/EntityPlus","commit_stats":null,"previous_names":["yelnats321/entityplus"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Yelnats321%2FEntityPlus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Yelnats321%2FEntityPlus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Yelnats321%2FEntityPlus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Yelnats321%2FEntityPlus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Yelnats321","download_url":"https://codeload.github.com/Yelnats321/EntityPlus/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":213757428,"owners_count":15634177,"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":["entity","entity-component","entity-component-system","gamedev"],"created_at":"2024-08-03T02:01:51.922Z","updated_at":"2024-08-03T02:09:23.199Z","avatar_url":"https://github.com/Yelnats321.png","language":"C++","readme":"# NOTICE:\nThis project is currently in the progress of being rewritten for C++17. Check out [this issue](../../issues/23) if you have any suggestions/know a good way to transition!\n\n# EntityPlus\nEntityPlus is an Entity Component System library written in C++14, offering fast compilation and runtime speeds. The library is header only, saving you the trouble of fidgeting with build systems and there are no external dependencies.\n\nThe ECS framework is an attempt to decouple data from mechanics. In doing so, it lets you create objects out of building blocks that mesh together to create a whole. It models a has-a relationship, letting you expand without worrying about dependency trees and inheritance. The three main aspects of an ECS framework are of course Entities, Components, and Systems.\n\n## Requirements\nEntityPlus requires C++14 conformance, and was mainly developed on MSVC. It has been tested to work on\n\n* MSVC 2015 update 3\n* Clang 3.5.0\n* GCC 5.3.0\n\n## Building\nSince EntityPlus is header only, there is no need to build the library to use it. Just `#include \u003centityplus/entity.h\u003e`! However, there are tests and examples you can build with cmake. \n\nTo build the tests, you need to specify the [catch](https://github.com/philsquared/Catch) directory. An in tree build could look like this:\n```\ncmake -D Catch_dir=dir/to/catch ..\nmake test\n```\n\n### Components\nComponents contain information. This can be anything, such as health, a piece of armor, or a status effect. An example component could be the identity of a person, which could be modeled like this:\n```c++\nstruct identity {\n    std::string name_;\n    int age_;\n    identity(std::string name, int age) : name_(name), age_(age) {}\n};\n```\nComponents don't have to be aggregate types, they can be as complicated as they need to be. For example, if we wanted a health component that would only let you heal to a maximum health, we could do it like this:\n```c++\nclass health {\n    int health_, maxHealth_;\npublic:\n    health(int health, int maxHealth) :\n        health_(health), maxHealth_(maxHealth){}\n    \n    int addHealth(int health) {\n        return health_ = std::max(health+health_, maxHealth);\n    }\n}\n```\nAs you may have noticed, these are just free classes. Usually, to use them with other ECSs you'd have to make them inherit from some common base, probably along with CRTP. However, EntityPlus takes advantage of the type system to eliminate these needs. To use these classes we have to simply specify them later.\n\nComponents must have a constructor, so aggregates are not allowed. This restriction is the same as all `emplace()` methods in the standard library. There are no other requirements.\n\n### Entities\nEntities model something. You can think of them as containers for components. If you want to model a player character, you might want a name, a measurement of their health, and an inventory. To use an entity, we must first create it. However, you can't just create a standalone `entity`, it needs context on where it exists. We use an `entity_manager` to manage all our `entity`s for us.\n```c++\nusing CompList = component_list\u003cidentity, health\u003e;\nusing TagList = tag_list\u003cstruct TA, struct TB\u003e;\nentity_manager\u003cCompList, TagList\u003e entityManager;\nusing entity_t = typename entity_manager\u003cCompList, TagList\u003e::entity_t;\n```\nDon't be scared by the syntax. Since we don't rely on inheritance or CRTP, we must give the `entity_manager` the list of components we will use with it, as well as a list of [tags](#tags). To create a list of components, we simply use a `component_list`. `component_list`s and `tag_list`s have to be unique, and the `component_list` and `tag_list` can't have overlapping types. If you mess up, you'll be told via compiler error.\n```c++\nerror C2338: component_list must be unique\n```\nNot so bad, right? EntityPlus is designed with the end user in mind, attempting to achieve as little template error bloat as possible. Almost all template errors will be reported in a simple and concise manner, with minimal errors as the end goal. With `C++17` most code will switch over to using `constexpr if` for errors, which will reduce the error call stack even further.\n\nNow that we have a manager, we can create an actual entity.\n```c++\nentity_t entity = entityManager.create_entity();\n```\nYou probably want to add those components to the entity.\n```c++\nauto retId = entity.add_component\u003cidentity\u003e(\"John\", 25);\nretId.first.name_ = \"Smith\";\nentity.add_component(health{100, 100});\n```\nIf we supply a component that wasn't part of the original component list, we will be told this at compile time. In fact, any sort of type mismatch will be presented as a user friendly error when you compile. Adding a component is quite similar to using `map::emplace()`, because the function forwards its args to the constructor and has a similar return semantic. A `pair\u003ccomponent\u0026, bool\u003e` is returned, indicating error or success and the component. The function can fail if a component of that type already exists, in which case the returned `component\u0026` is a reference to the already existing component. Otherwise, the function succeeded and the new component is returned.\n\nSometimes you know all the tags and components you want from the get go. You can create an entity with all these parts just as easily:\n\n```c++\nentity_t ent = entityManager.create_entity\u003cTA\u003e(A{3}, health{100, 200});\n```\n\nThe arguments are fully formed components you wish to add to the entity and the template arguments are the tags the entity should have once it's created.\n\nWhat happens if we create a copy of an entity? Well, since entities are just handles, this copy doesn't represent a new entity but instead refers to the same underlying data that you copied.\n\n```c++\nauto entityCopy = entity;\nassert(entityCopy.get_component\u003chealth\u003e() == entity.get_component\u003chealth\u003e();\n```\n\nWhat happens if we modify one copy of the entity? Well, the modified entity is the freshest, and so it is fine, but the old entity is stale. Using a stale entity will give you an error at best, but it can go unnoticed under certain circumstances (if using a release build). You can query the state of an entity with `get_status()`. The 4 statuses are OK, stale, deleted, and uninitialized. To make sure you have the newest version of an entity, you can use `sync()`, which will update your entity to the latest version. If the entity has been deleted, `sync()` will return false.\n\n### Systems\nThe last thing we want to do is manipulate our entities. Unlike some ECS frameworks, EntityPlus doesn't have a system manager or similar device. You can work with the entities in one of two ways. The first is querying for a list of them by type\n```c++\nauto ents = entityManager.get_entities\u003cidentity\u003e();\nfor (const auto \u0026ent : ents) {\n    std::cout \u003c\u003c ent.get_component\u003cidentity\u003e().name_ \u003c\u003c \"\\n\";\n}\n```\n`get_entities()` will return a `vector` of all the entities that contain the given components and tags.\n\nThe second, and faster way, of manipulating entities is by using lambdas (or any `Callable` really).\n```c++\nentityManager.for_each\u003cidentity\u003e([](auto ent, auto \u0026id) {\n    std::cout \u003c\u003c id.name_ \u003c\u003c \"\\n\";\n}\n```\n\nYou can supply as many tags/components as you want to both methods, so if you need all entities with `tag1` and `tag2` you can simply do `get_entities\u003ctag1, tag2\u003e()` or `for_each\u003ctag1, tag2\u003e(...)`. In addition, `for_each` has an optional control parameter, which you can modify to break out of the for loop early.\n\n```c++\nentity_t secretAgent;\nentityManager.for_each\u003ctag1, identity\u003e([\u0026](auto ent, auto \u0026id, control_block_t \u0026control) {\n\tif (id.name_ == \"Secret Agent\") {\n\t\tsecretAgent = ent;\n\t\tcontrol.breakout = true;\n\t}\n}\n```\n\nThat's about it! You can obviously wrap these methods in your own system classes, but having specific support for systems felt artificial and didn't add any impactful or useful changes to the flow of usage.\n\n### Tags\nTags are like components that have no data. They are simply a typename (and don't even have to be complete types) that is attached to an entity. An example could be a player tag for the entity that is controlled by a player. Tags can be used in any way a component is, but since there is no value associated with it except if it exists or not, it can only be toggled.\n\n```c++\nent.set_tag\u003cplayer_tag\u003e(true);\nassert(ent.get_tag\u003cplayer_tag\u003e() == true);\n```\n\n### Events\nEvents are orthogonal to ECS, but when used in conjunction they create better decoupled code. Because of this, events are fully integrated into the entity manager. The first two template arguments of the `event_manager` must be the same `component_list` and `tag_list` as the ones used for the `entity_manager`. Additional events can be used by supplying their type after the components/tags.\n\n```c++\nstruct MyCustomEvent {\n\tstd::string msg;\n};\n\nevent_manager\u003cCompList, TagList, MyCustomEvent\u003e eventManager;\nsubscriber_handle\u003centity_created\u003e handle;\nhandle = eventManager.subscribe\u003cMyCustomEvent\u003e([](const auto \u0026ev) {\n    std::cout \u003c\u003c ev.msg;\n});\neventManager.broadcast(MyCustomEvent{\"surprise!\"});\nhandle.unsubscribe();\n```\n\nSubscriber handles are ways of keeping track of a subscribers. They do not rely on the type of event manager, unlike entities, and can be stored just like any other object. They do not get invalidated either.\n\nThere are also special events that are generated by the entity manager, which is why we need the components/tags to be the same. To use an event manager with an entity manager, you must set it.\n\n```c++\nentityManager.set_event_manager(eventManager);\n```\n\nBy doing this, you can now be notified to a wide variety of state changes. For example, if you want to know whenever a component of type `health` is added to an entity, you can simply subscribe to that event.\n\n```c++\neventManager.subscribe\u003ccomponent_added\u003centity_t, health\u003e\u003e([](const auto \u0026event) {\n\tevent.ent.get_component\u003chealth\u003e() == event.component;\n}\n```\n\nHere is a full list of predefined events.\n\n```\nentity_created\u003centity_t\u003e\nentity_destroyed\u003centity_t\u003e\ncomponent_added\u003centity_t, Component\u003e\ncomponent_removed\u003centity_t, Component\u003e\ntag_added\u003centity_t, Tag\u003e\ntag_remved\u003centity_t, Tag\u003e\n```\n\nNote that a destructive event is issued at the earliest possible point while a constructive event is issued at the latest. This is so that you can use as much information inside the event handler as possible. It is rarely useful to know if an entity was destroyed if you can't access any of its components, and it is likewise useless to know that a component was added to an entity before the component exists. Component/tag removal events are also issued when an entity is being destroyed, however they are issued after an entity destroyed event for the aforementioned reason.\n\n\n### Exceptions and Error Codes\nEntityPlus can be configured to use either exceptions or error codes. The two types of exceptions are `invalid_component` and `bad_entity`, with corresponding error codes. The former is thrown when `get_component()` is called for an entity that does not own a component of that type. The latter is thrown when an entity is stale, belongs to another entity manager, or when the entity has already been deleted. These states can be queried by `get_status()` which returns a corresponding `entity_status`.\n\nTo enable error codes, you must `#define ENTITYPLUS_NO_EXCEPTIONS` and `set_error_callback()`, which takes a `std::function\u003cvoid(error_code_t code, const char *msg)\u003e` as an argument.\n\n## Performance\nEntityPlus was designed with performance in mind. Almost all information is stored contiguously (through `flat_map`s and `flat_set`s) and the code has been optimized for iteration (over insertion/deletion) as that is the most common operation when using ECS. \n\nThere is currently little tuning available, but additional enhancements are planned. Entity manager provides a `set_max_linear_dist` option. When iterating using `for_each`, the relative occurrence of a component is calculated against the maximum possible amounts of iterated entities. If this number is small, then a  linear search is used to find consecutive entities. Otherwise, a binary search is used instead.\n\nFor example, say there are 1000 entities. 500 of these entities have component A, but only 10 have component B. We call `for_each\u003cA,B\u003e`. Since we know the maximum amount of entities we will iterate is 10, we calculate the relative occurrence of these entities in `A`. 500/10 = 50, which means we are likely to iterate over 50 entities in a linear search before we find an entity that has both `A` and `B`. If we had `set_max_linear_dist(55)` then we would do a linear search through the entities that contain `A`. If instead we had `set_max_linear_dist(25)`, we would do a binary search. The default value is 64, and can be queried with `get_max_linear_dist()`.\n\n### Groups\nIf you know you will be querying some set of components/tags often, you can register an entity group. This means that under the hood, the entity manager will keep all entities with the components/tags together in a container so that when you need to iterate over the grouping it won't have to generate it on the fly. For example, if you know you will use components `A` and `B` together in a system, you can do this:\n```c++\nentity_grouping groupAB = entityManager.create_grouping\u003cA, B\u003e();\n\nfor_each\u003cA,B\u003e(...);\n\n// later\ngroupAB.destroy()\n```\nNow whenever you do a `for_each\u003cA,B\u003e()` or a `get_entities\u003cA,B\u003e()` the iterated entities will not have to be built dynamically but are already cached. Additionally, whenever you do a query like `for_each\u003cA,B,C\u003e()` the manager will only iterate through the smallest subset of tags/components it can find, which in this case would be the group `AB`, so you will get performance gains through that as well.\n\nThere are already pre-generated groupings for each component and tag, so you cannot create a grouping with an 0 or 1 items (since 0 is just every entity and 1 is just a single component/tag).\n\n### Benchmarks\nI've benchmarked EntityPlus against EntityX, another ECS library for C++11 on my Lenovo Y-40 which has an i7-4510U @ 2.00 GHz. Compiled using MSVC 2015 update 3 with hotfix on x64. The source for the benchmarks can be viewed [here](entityplus/benchmark.cpp). The time to add the components was very negligible and unlikely to impact performance much in the long run unless you're adding/removing components more than you are iterating over them.\n\n```\nEntity Count | Iterations | Probability | EntityPlus | EntityX\n----------------------------------------------------------------\n    1 000    | 1 000 000  |    1 in 3   |   1706 ms  |  20959 ms\n   10 000    | 1 000 000  |    1 in 3   |  16541 ms  | 208156 ms\n   30 000    |   100 000  |    1 in 3   |   5308 ms  |  63012 ms\n  100 000    |   100 000  |    1 in 5   |  14780 ms  | 133365 ms\n   10 000    | 1 000 000  |  1 in 1000  |    396 ms  |  16883 ms\n  100 000    | 1 000 000  |  1 in 1000  |   4610 ms  | 170271 ms\n   \n```\n\n### Big O Analysis\n```c++\nn = amount of entities\n\nEntity:\nhas_(component/tag) = O(1)\n(add/remove)_component = O(n)\nset_tag = O(n)\nget_component = O(log n)\nget_status = O(log n)\nsync = O(log n)\ndestroy = O(n)\n\nEntity Manager:\ncreate_entity = O(n)\nget_entities = O(n)\nfor_each = O(n)\ncreate_grouping = O(n)\n```\n\n## Reference\n### Entity\n```c++\nentity_status get_status() const \n```\n`Returns`: Status of `entity`, one of `OK`, `UNINITIALIZED`, `DELETED`, or `STALE`.\n\n```c++\ntemplate \u003ctypename Component\u003e\nbool has_component() const \n```\n`Returns`: `bool` indicating whether the `entity` has the `Component`. \n\n`Prerequisites`: `entity` is `OK`.\n\n```c++\ntemplate \u003ctypename Component, typename... Args\u003e\nstd::pair\u003cComponent\u0026, bool\u003e add_component(Args\u0026\u0026... args)\n\ntemplate \u003ctypename Component\u003e\nstd::pair\u003cstd::decay_t\u003cComponent\u003e\u0026, bool\u003e add_component(Component\u0026\u0026 comp)\n```\n`Returns`: `bool` indicating if the `Component` was added. If it was, a reference to the new `Component`. Otherwise, the old `Component`. Does not overwrite old `Component`.\n\n`Prerequisites`: `entity` is `OK`.\n\n`Throws`: `bad_entity` if the `entity` is not `OK`.\n\nCan invalidate references to all components of type `Component`, as well as a `for_each` involving `Component`.\n\nCan turn entity copies `STALE`.\n\n```c++\ntemplate \u003ctypename Component\u003e\nbool remove_component()\n```\n`Returns`: `bool` indicating if the `Component` was removed.\n\n`Prerequisites`: `entity` is `OK`.\n\nCan invalidate references to all components of type `Component`, as well as a `for_each` involving `Component`.\n\nCan turn entity copies `STALE`.\n\n```c++\ntemplate \u003ctypename Component\u003e\n(const) Component\u0026 get_component() (const) \n```\n`Returns`: The `Component` requested.\n\n`Prerequisites`: `entity` is `OK`.\n\n`Throws`: `bad_entity` if the `entity` is not `OK`. `invalid_component` if the `entity` does not own a `Component`.\n\n```c++\ntemplate \u003ctypename Tag\u003e\nbool has_tag() const \n```\n`Returns`: `bool` indicating if the `entity` has `Tag`.\n\n`Prerequisites`: `entity` is `OK`.\n\n```c++\ntemplate \u003ctypename Tag\u003e\nbool set_tag(bool set) \n```\n`Returns`: `bool` indicating if the `entity` had `Tag` before the call. The old value of `has_tag()`.\n\n`Prerequisites`: `entity` is `OK`.\n\n`Throws`: `bad_entity` if the `entity` is not `OK`.\n\nCan invalidate a `for_each` involving `Tag`, only if `set != set_tag\u003cTag\u003e(set)`.\n\nCan turn entity copies `STALE`.\n\n```c++\nbool sync()\n```\n\n`Returns`: `true` if the entity is still alive, false otherwise.\n\n`Prerequisites`: `entity` is not `UNINITIALIZED`.\n\n```c++\nvoid destroy()\n```\n`Prerequisites`: `entity` is `OK`.\n\n`Throws`: `bad_entity` if the `entity` is not `OK`.\n\nCan invalidate a `for_each`.\n\nTurns entity copies 'DELETED'.\n\n#### `entity` vs `entity_t`\n`entity` is the template class while `entity_t` is the template class with the same template arguments as the `entity_manager`. That is, `entity_t = entity\u003ccomponent_list, tag_list\u003e`.\n\n### Entity Manager\n```c++\ntemplate \u003ctypename... Tags, typename... Components\u003e\nentity_t create_entity(Components\u0026\u0026... comps)\n```\n`Returns`: `entity_t` that was created with the given `Tags` and `Components`.\n\nCan invalidate references to all components of types `Components`, as well as a `for_each` involving `Tags` or `Components`.\n\n```c++\ntemplate \u003ctypename... Ts\u003e\nreturn_container get_entities() \n```\n`Returns`: `return_container` of all the entities that have all the components/tags in `Ts...`.\n\n```c++\ntemplate \u003ctypename... Ts, typename Func\u003e\nvoid for_each(Func \u0026\u0026 func)\n```\nCalls `func` for each entity that has all the components/tags in `Ts...`. The arguments supplied to `func` are the entity, as well as all the components in `Ts...`.\n\n```c++\nstd::size_t get_max_linear_dist() const\n```\n\n```c++\nvoid set_max_linear_dist(std::size_t)\n```\n\n```c++\nvoid set_event_manager(const event_manager \u0026)\n```\n\n```c++\nvoid clear_event_manager()\n```\n\n```c++\ntemplate \u003ctypename... Ts\u003e\nentity_grouping create_grouping()\n```\n`Returns`: `entity_grouping` of the grouping created.\n\n\n### Entity Grouping\n```c++\nbool is_valid()\n```\n`Returns`: `true` if the entity grouping exists, `false` otherwise.\n\n```c++\nbool destroy()\n```\n`Returns`: `true` if the grouping was destroyed, `false` if the grouping doesn't exist.\n\n### Event Manager\n```c++\ntemplate \u003ctypename Event, typename Func\u003e\nsubscriber_handle\u003cEvent\u003e subscribe(Func \u0026\u0026 func);\n```\n`Returns`: A `subscriber_handle` for the `func`.\n\n```c++\ntemplate \u003ctypename Event\u003e\nvoid broadcast(const Event \u0026event) const\n```\n\n### Subscriber Handle\n```c++\nbool isValid() const\n```\n`Returns`: `true` if the handle holds onto a subscribed function, `false` otherwise.\n\n```c++\nbool unsubscribe()\n```\n`Returns`: `true` if the subscribed function was unsubscribed, `false` if there is no subscribed function.\n","funding_links":[],"categories":["[ECS Libraries](#contents)","ECS Libraries","GameProgramming"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FYelnats321%2FEntityPlus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FYelnats321%2FEntityPlus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FYelnats321%2FEntityPlus/lists"}