forked from Green-Sky/tomato
Green Sky
90ce4bda4e
344e03ac6 update single include file to v3.12.2 da56665b0 registry: make ::valid backward compatible f6f01ef1b snapshot: avoid warnings due to deprecated functions 0ed514628 now working on v3.12.2 a41421d86 update single include file to v3.12.1 c1f6b11f7 snapshot: reintroduce support to storage listeners b2233064a now working on version v3.12.1 cb974bf56 adjacency_matrix: fix in_edges() is off by 1 in some cases (close #1019) 7b7d82e6f doc: snapshot (close #984) 05c6898fc test: self-fixing archive example for snapshot classes 7ffa459a6 snapshot: drop ::get member template parameter 93e8e94e6 test: basic continuous loader c4e241662 snapshot: review basic_continuous_loader (and drop shrink) 9c25419b9 test: more on basic_snapshot_loader 1879830df snapshot: drop pointless assert 29298c0eb test: guarantee code coverage, we'll update the test later on 247abef1d test: rollback for code coverage purposes on the snapshot class 6994d98d2 test: typo 9a600ece2 test: snapshot f91226ef4 snapshot: share ::orphans implementation (to deprecate in future though) e366ffbd3 doc: snapshot 63b300d39 snapshot: again, dense_map::contains is a thing afb70d157 test: avoid warnings due to unused variables 49534eec0 snapshot: dense_map::contains is a thing fortunately 3f1277f7b snapshot: use the right allocator for the remote-local mapping 26fad4c38 test: basic snapshot loader 25b3afacf test: basic snapshot 2d25bbb09 snapshot: check registry type 0eb834582 snapshot: small cleanup 124a44052 test: use the new snapshot get functions in the test suite 5c704636e test: use the new snapshot get functions in the test suite 31fd94cc3 snapshot: cleanup to get ready to drop an internal function 573e43272 snapshot: reduce storage lookups 1d8943481 snapshot: drop useless function e0a1ef7c1 snapshot: check on member type class 48ac0e0eb snapshot: add basic_continuous_loader::get, deprecate ::entities and ::component bcb6234d9 snapshot: add basic_snapshot_loader::get, deprecate ::entities and ::component f96796326 snapshot: reject entity type in the range-get (now get instead of get_sparse) b22c55dd2 doc: typo 4ff5a536c snapshot: add basic_snapshot::get, deprecate ::entities and ::component fff5f578a test: avoid using deprecated functions in an example 0f44c8c92 doc: reflect recent changes 0b6ad0315 snapshot: * single element only archive functions required * avoid iterating elements more than once 2450b0bc6 test: minor changes (waiting for a rework) fc8eebf36 snapshot: use component_traits instead of is_empty_v e4f51f2b7 snapshot: avoid multiple lookups of the same storage 2c2216a89 doc: typo cafe85180 snapshot: deprecate multi-type component loading function 35e338cc9 snapshot: deprecate multi-type component loading function 8feeaaef7 doc: minor changes e7a3c4e37 snapshot: add missing [[deprecate(...)]] ea5c558bd snapshot: cleanup (waiting for further improvements) 94f0ed179 snapshot: deprecate multi-type component loading function 244c35949 snapshot: deprecate multi-type component loading function 1f24fea21 type_traits: formatting 8deaa09b2 test: perform static checks at compile-time 85bffb714 type_traits: std::tuple traits specialization for entt::type_list and entt::value_list (#1011) 325ca310d view: updated ::refresh d903e268f snapshot: minor changes f4b26756c snapshot: improved basic_snapshot::component fb3a34ee9 *: updated TODO 6902bb6c4 doc: typo 379819b2b test: cleanup 59abfbfb5 meta: refine policy check on value types for non-member data 6e2d87184 registry: avoid casting return types directly to better support empty storage 57ec3c85c registry: erase_if (close #977) 4afdf287f doc: minor changes 2810ac7cb registry: suppress a warning on msvc e0d27f9bf *: updated TODO de303c999 test: reverse-each for storage entity 1619e780f test: reverse each for plain storage classes a1e37eca6 storage: reverse-each c345e7456 doc: note on reverse iterations d166c026f snapshot: minor changes 5e639996d doc: minor changes dac2ef5a9 doc: typo 71d7888e8 snapshot: drop redundant check 84a4df9c4 doc: exclude-only views 95bc20319 doc: entity lifecycle 5a9f6d211 doc: cleanup a29302faa test: more on entity signals 75efa72c6 registry: cleanup ::erase 58a84665b registry: cleanup ::remove a5263384d doc: drop redundant comments c0e6759c6 doc: cleanup a little further d754f7431 doc: cleanup 1df539943 doc: drop pointless tags c284e6fee doc: minor changes 500239758 test: typo 319ecd808 organizer: fix organizer::vertex::prepare not creating component pools (#1014) d7891fabc doc: mention named pools support when registering listeners e287dd041 helper: minor changes 4dee9dde1 registry: named pools support for on_construct/on_update/on_destroy 9bae6e67b doc: update connection helper doc aa7a7ce25 doc: minor changes a969468c5 registry: de-deprecate :) on_construct/on_update/on_destroy a1e76fc63 doc: more about entity storage d8ed4ca35 registry: refine how entity storage is used internally 3248e3f91 helper: make sigh_helper work with named pools f00687e6f doc: updated registry documentation 5240c6b60 registry: deprecate on_construct/on_update/on_destroy 67604a88e natvis: update registry snippet 4242dfb8b registry: use entity storage directly as much as possible f96d8ee83 registry: prepare to split component storage and entity storage c147ec37c test: try to make gcc happy again 094ddbba3 meta: avoid shadow warnings 634630ca2 test: add missing template keywords (thanks msvc for ignoring them) d78c26f26 *: updated TODO fabc6c9bd test: full cross-registry entity-copy example with meta (not strictly required) b6e8ddd2a meta: fight against the small nuances of the language :) cf2bbae6e mixin: make it simpler to modify the underlying type 08799616d *: updated TODO 58bebf78d meta: reduce symbols and their sizes if possible d534fad3e doc: more about views 871dc7a40 doc: drop references to storage placeholders 1fe7c78f7 test: minor changes 22a65f80f test: cleanup 756ea8a38 *: updated TODO 12186cb40 registry: drop internal static storage variables from ::assure aa9ffb9ee registry: const ::storage<T>(...) returns a pointer to possibly null storage dcb5aed90 registry: lazily/partially initialize views in the ::view const function 34f6a747a registry: add support for non-existent pools to try_get 912cb2ad5 snapshot: constness review 885488b3d registry: any_of supports non-existing pools now 3d3d3ef2d registry: all_of supports non-existing pools now a7120b340 registry: coding style 51915205b test: cover stable multi-type model 4a3ee042e view: refine ::storage function 88a1b8d0d view: stable multi-type view ::each(cb) function 7e18a0f96 view: update ::use function c367082dd view: unchecked_refresh function 9f94b5306 view: double check on none_of 44ed10c50 view: stable multi type view ::find/::back/::front functions 1b2280941 view: stable multi type view ::begin/::end functions bdabbaa63 view: stable multi type view ::contains function c79c109b7 view: stable multi type view ::size_hint function f1a213382 registry: prepare to remove static storage from const assure 17dc06149 view: stable single type view ::each(cb) function 3b8d82330 view: drop unused return a20829e70 view: ::handle returns a pointer rather than a reference 5be2fdc15 view: stable single type view ::each() function 873b107e6 -: updated TODO 356bbbe53 view: stable single type view ::find function e3ce4e156 view: stable single type view ::front/::back functions e02050c51 view: stable single type view ::rbegin/::rend functions 26930633f view: stable single type view ::begin/::end functions b7a485767 view: stable single type view ::contains function f54cdccd4 view: stable single type view ::empty function 41c9a32f3 view: stable single type view ::size function 736ef3580 view: make operator bool work properly with partially initialized views 0128cbb4f test: minor changes ff0a40715 test: prepare test suite for safe invalid views 34f440386 view: avoid using storage, further prepare for empty safe views b1c78efb6 nativs: updated signal file 28f03ff9c meta: add missing checks on factory<...>::data a5fe61adb *: minor changes 457f5e59e view: rollback handle() usage and prepare to safe empty views 422fd284e group: refine group ::find function 6f3222573 view: refine single type view ::find function 366bbceb0 doc: use doxygen-awesome-css 7b7f81e08 doc: update reference.md cfe955f97 doc: update links.md 684ddc9de doc: minor changes f5d38a9ae doc: drop redundant doxy variable 447e3693f doc: updated doxy file (doxygen 1.9.6) 909490bf6 view: try to make g++ happy again d90363e4a view: make view pack also work with empty views ee5de744c view: add missing [[nodiscard]] d401c88a0 view: assert on null handles 80563b955 view: allow swapping storage elements of a view c74900057 sigh_mixin: avoid shadow warnings 78867d5c9 group: make msvc happy with constness on virtual functions d435fc779 basic_entt_traits: suppress a warning by gcc e6f76e0f9 view: try to make VS happy again :) 1c6b53360 test: minor changes 5c3d8360c view: turn ::use into a self-contained, non-const function 3882c7d9a view: turn ::refresh into a self contained, non-const function 15726218b view: doc 869bfc82c test: minor changes 0eb3d54b2 group: change signature of ::storage to return a (maybe null) pointer rather than a reference f83290f76 view: change signature of ::storage to return a (maybe null) pointer rather than a reference 686a3b9d7 registry: make storage_for_type available to the final user 4d57d5c32 registry: make ::storage<T> return type explicit 36c21cf7f registry: drop redundant traits usage 7ab10e193 test: minor changes 41467d35a -: updated TODO d351252a1 doc: entity storage c6cd4f701 doc: refine storage section 65889cca4 doc: brief mention of void storage f1914fd94 doc: rearrange a few things e53af7bef registry: minor changes b910cd261 *: updated TODO 58d331ca0 registry: minor changes 17f5b0a33 registry: avoid bumping version on destroy if not requested de386292b registry: deprecate ::each 88bf26a2f registry: deprecate ::assign 3caad4100 mixin: common internal owner_or_assert function 916203a24 test: stress assert on entity limit 62f1971f7 test: minor changes 4fde96357 natvis: updated registry snippet c3730b65f group: * unified model * drop group handler's size function (no longer required) 1ea072cd3 group: back to the unified model for group handlers bbe4582ee meta: minor changes 89ab5c328 meta: operator==/!= for meta_func 3a4672793 meta: operator==/!= for meta_prop 0a0446f35 meta: operator==/!= for meta_data (close #1002) fc58ff74b meta: operator==/!= for meta_handle (see #1002) fed6831cd locator: support to opaque structures (close #956) 1605c8d9d natvis: updated entity file d6641c7d8 -: updated TODO file 5079f38e9 storage: allow on_update signals on entity storage 1eab2a4a8 meta: fix constness detection for static functions in meta_type::invoke c33110765 test: cleanup 117b0bd67 test: more about storage<...>::patch 9b4a6f877 storage: use allocator_traits::destroy rather than destroy_at f4e6f2b37 group: suppress shadow warning 5971fb7aa -: updated TODO 10dfe7e93 sigh: allow disconnecting listeners during iterations (close #986) a9208a956 doc: fixed typo 1cc5b32ca test: cleanup f8a972a3c signal: drop sink::before 5b7cc2002 group: rollback some (no longer required) changes to the owning_group_descriptor bd34e7f2c group: drop nested groups support, prepare to the large group review and multi storage support 46fe29c3f group: make matching functions virtual for owning groups c50e2815c group: make owning_group_descriptor depend on the storage base type fbfee632d group: minor changes 77c59aabf group: group_handler::size function for owning groups ebb1e8a72 group: single check function for group handlers 1646217f0 group: make types explicit for the next/prev functions 645edfb2b group: decouple constructing and setting prev/next links 61f28298c group/registry: minor changes d19f97bf2 group: use ::handle() if possible 70c611a84 group: cleanup 286428c19 group: make common_type base of non-owning group handlers 6ec719bcf group: reduce the footprint of non-owning group handlers 11f9bb2d7 registry: use shared_ptr<void> for non-owning groups (prepare to drop the basic handler dependency) 5a1ba5ad7 regisrtry: decouple container types for groups cf094e7ef registry: finally split owning and non-owning groups as it ought to be 31808bd9a sigh: flip the last commit on its head and drop redundant functions rather than merging them 61a5173a7 sigh: merge a couple of functions ed6fe9e65 sigh/sink: refine internal definition e30fa8520 doc: cleanup ca1069e18 snapshot: avoid allocations if possible 70f73a094 snapshot: drop pointless checks 710fff0e3 entity: make get_t, exclude_t and owned_t constexpr constructible 660bc5843 entity: turn get_t, exclude_t and owned_t into proper classes (close #998) 13295a14e type_traits: v141 toolset workaround for value_list_diff 9ce07ff61 type_traits: value_list_diff[_t] b272e04ba type_traits: value_list_contains[_v] 28b11912a test: cleanup b9f096d12 type_traits: value_list_unique[_t] 8c60faa1d type_traits: value_list_index[_v] 1f93ea4ee snapshot: avoid unnecessary lookups 7ca77e53f snapshot: avoid unnecessary lookups 69397f365 snapshot: avoid unnecessary lookups f907bc066 snapshot: drop redundant checks and avoid unnecessary lookups bda52701f snapshot: avoid unnecessary lookups d26f7684c snapshot: minor changes 63d6c2bff snapshot: avoid unnecessary lookups cc45e7341 snapshot: also avoid using views if not required 5d092bcb1 snapshot: avoid unnecessary lookups 295c68841 snapshot: review ::orphans functions 2664b5255 observer: allocator support dd3632833 observer: configurable mask type c8c929e4a group: use type members properly d1ef7bf15 view: use type members properly 1ab23f17d group: early exit on signal races a72eb4693 group: minor changes 67579d062 -: updated TODO 766a233f3 view: base_type -> common_type 905671c23 runtime_view: base_type -> common_type 27c1383e4 group: base_type -> common_type 029ccc8f7 registry: base_type -> common_type cde40d586 group: drop unused using decl 6a16a8a20 group: auto init for owning groups 1a12dede6 group: auto init for non-owning groups 35a78b65e group: cleanup ada19432f group: support for index based sort 4998e9087 doc: minor changes 471c11c6d sparse_set: respect -> sort_as (naming is hard, you know) 3e13e0b59 group: sort/respect -> sort_as (also decoupled from group types) 53cd105f2 group: reuse pools as much as possible 24b31c379 group: reuse pools as much as possible def82b534 group: index based get a424f4ebf view: review get b8f0a8d8e doc: a couple of interesting articles/series (close #994) 7941226ef group: try to reuse pools when sorting and also please all compilers out there at the same time (aka me figthing ICEs again) 86bbb2f6b group: reuse pools when sorting 3c176f725 test: suppress warnings due to unused variables 3642c8a78 registry: drop [[nodiscard]] from ::group (close #991) 0e80d90a7 group: use storage<idx> as much as possible 4fdf2dccd group: update doc f8a997e6c group: minor changes 40f676ed1 test: drop unused include 5e346748e test: code coverage for groups and registry 3ef61fe01 meta: support meta member functions on primitive types 3885d280d test: cleanup f41b91419 meta: allow updating values on meta properties e0684f634 registry: cleanup/minor changes fb980a78c registry: further refine the group function(s) c2430ab48 doc: minor changes d36d9cb39 registry: further cleanup group functions 0017c08bb group: get pools from handlers e737ff747 group: get filter from handlers 945dc4093 group: split group handler functions 7ef008511 registry: drop group_data d2fa68813 registry/group: prepare to get rid of group_data f22a09a9a group: in-between change to simplify dropping group_data b0aba79a5 snapshot: minor changes 7c23e4a2f registry: minor changes 7fe035ce4 group: move group size from registry group_data to basic_group_handler 3e7160eda group: minor changes aaeb686ec group: common base class for group handlers 3fdf4884d group: prepare for group handler common base class 1b23ff4b9 registry: use common group handler types as keys for the group set 88dac318e group: wrap the len of owning groups to avoid changing it by mistake 520c2e660 group: make group handlers work with multiple storage of the same type f5d0d451b group: split pools and filter in the group handlers 8af6fc0cc group: use ::handle internally if possible c04b97a31 group: add ::handle function to all group types 1d85414dc doc: drop refs to registry::version (close #992) c6533827f group: fight with clang format from time to time :) b5803451b group: make owning groups work with their handlers 3417d66b2 group: make non-owning groups work with their handlers 1e61204e8 registry: deduce group handler type from group type 19c4857ef group: cleanup 66ea94898 registry/group: move group handler to group file as it ought to be ced6d21c3 registry: break dependency between registry and group handlers 429c7c45c registry: further cleanup things c03b1111a registry: small cleanup ebd7d3acd registry: storage based model with pools for groups 5aeec60cf registry: prepare to switch to storage based group handlers 620b4f751 registry: pass handlers to group callbacks 6d58004c1 registry: minor changes to simplify the implementation slightly df6d926de registry: prepare for a storage based group handler e63af24cb registry: turn the non-owning group handler in a storage 068d9f8ae registry: discard unused arguments from listeners if possible c19c848c4 test: suppress warnings due to unused variables 0bf0a0a8f doc: delegate 743e8678e delegate: also support functions that skip first elements (on second attempt only) a7ad1c06f delegate: prepare to support filtering on both sides b1af70e70 registry: avoid checking pools in the group handler if possible c87c3533e registry: avoid checking pools in the group handler if possible 4839a0ee6 registry: cleanup a0f0c44e6 registry: minor changes 74691dc1d group: just use meaningful names :) e4957badb registry: split group handler to further refine group management 46791c4c3 registry: turn group handler functions into static ones 56c391784 registry: prepare to rework groups 1fb13d3e9 doc: minor changes 535beb4e2 storage: drop unnecessary use of integral_constant 2d318b88c -: updated TODO b7f0b76ce entity/mixin: add missing include d30312f51 entity/helper: add missing include, drop unnecessary traits calls 30772848e meta: avoid unnecessary calls to std::move eca01a397 doc: add vcpkg badge and vcpkg.link (#985) 35ef0b7ac core: reduces the number of instantiations a bit 19ccba3a6 meta: reduces the number of instantiations a bit 207b7674a doc: fix typo 631c55ba9 storage: minor changes/tests e7b30fd36 storage: return iterator to elements rather than entities and only if it makes sense 3e959007b storage: ::insert returns an iterator to the range of inserted entities 07ec4ca23 -: updated TODO 6e4946b68 storage: uniform interface to simplify mixin implementation 47ea16f17 test: signals on entity creation/destruction 722857fc0 test: get rid of pointless template parameters 2125b3838 test: minor changes 289de7d57 test: exclude only views 25ecd8e79 test: minor changes 319dfdb07 test: filtered registry view 9dbbcac01 -: updated TODO f545c8e05 registry: deprecate ::release c68fa6a65 registry: make ::destroy work without ::release (the latter to be deprecated) d288ecd70 registry: make ::release use ::bump return value 312d3aba8 sparse_set: bump returns the version in use (for convenience) 4d2b2c6de registry: use traits_type::next if possible 80d55a226 test: increase code coverage d86a53935 test: suppress warnings due to unused variables 0f7098d0e -: updated TODO 8c96be1e9 registry: deprecate a bunch of functions because of the entity storage 37f396bfe registry: make entity storage storage as any other 75894dc40 storage: update traits_type for entity storage cdee000ce any: rollback a change that turns vs toolset v141 crazy 54ca62600 dispatcher: refine aggregate check 6f4280ed5 any: refine aggregate check ddf56b78c storage: backward compatibility on component requirements 53a854f54 any: just cleanup the code to make it easier to work with 4896acac7 storage: typo e3defeba2 test: suppress warnings due to unused variables 62079908c storage: use proper value type for entity storage e65a8f2e5 doc: add link to koala engine :) 9f27fb1e5 registry: further prepare to turn the entity storage into a plain pool 04d734e76 registry: prepare to turn the entity pool in a plain storage df50fa1b5 natvis: cleanup 051872b8c natvis: update registry definition 57ab9e7be registry: avoid using assure if not required 69d95ba75 test: more bench to stress a little an upcoming feature 9caf66d7c test: cleanup 74cb0d40c test: internal rework deac7f34b dispatcher: refine aggregate support a9883f27c storage: refine transparent aggregate support 85b1e57d8 sparse_set: drop fast_compact, expect full clear b7d8e0186 storage: make the entity storage perform a full clear rather than a fake one (still viable via erase) 390a56176 -: updated TODO file a1b888cce natvis: add optiona storage length item for entity storage 2107dd689 natvis: fix already existing errors due to renaming or design changes 1fca56afe storage: make it easier to refine the natvis file c0762a6a5 storage: add get/get_as_tuple to entity storage to make it suitable for use with views f48de1bac test: stress get/get_as_tuple for empty types c7dfce89e sigh_mixin: refine pop_all 822fafcd4 view: uniform implementation to simplify upcoming changes 1476d4ea9 sparse_set: refine ::respect c1c63777e -: updated TODO 2fab25ae8 registry: refine internal check 75d449152 -: updated TODO c7866fb21 storage: use entt traits next function if possible 87987bacd entity: added basic_entt_traits::next (with tests) bde0219fe snapshot: review basic_continuous_loader::entities ad64c849b storage: suppress warnings b808bb83b test: suppress warnings d0090d35f snapshot: try to make sizes an opaque value to the caller 7a1a06a24 sigh_mixin: avoid shadow warnings 000b17881 -: updated TODO 068b6ed49 registry: first (almost) backward compatible version with opaque hidden entity storage 0187fb48a test: sigh mixin for entity storage types 35a2b3844 sigh_mixin: also support entity storage types 4747c9a4c registry: extended checks to support swap-only entity storage types 7be8d8327 registry: make a couple of conditions opaque a5d6757d6 registry: prepare to get rid of the vector of entities 3f09d47c8 storage: remove redundant typename keyword 9c06d6ba0 registry: use type member names b7c819bf4 test: entity storage 9f31803ba storage: swap-only entity storage 1e7deff9c test: drop redundant checks 04ac15d8d test: minor changes 376218991 sigh_mixin: make pop_all use narrow view iterators if any 18d6e466d -: [[nodiscard]] as appropriate 095ecf314 group: extended_group_iterator::base to return the underlying iterator 433ed863e view: extended_view_iterator::base to return the underlying iterator 0dba68e75 storage: coding style/minor changes 1ab281582 storage: extended_storage_iterator::base to return the underlying iterator 2af5a725e doc: * updated copyright * udpated TODO list a86bf1332 test: try to make lcov happy 831054bff test: share as much as possible f94de1c06 test: rework lib stuff to share common files a3d9503a1 test: try to make lcov happy 3f2b15f9f test: try to make lcov happy e48817d51 test: try to make lcov happy d11cebe30 view: uniform design to also help natvis without having to poke into stl internals 77a5efb32 natvis: updated to_entity intrinsic 851006efe -: updated TODO 6fc6b2fb3 sigh_mixin: further improve ::pop_all ed17a2c48 sparse_set: ::contiguous function bd00e797a sparse_set: further refine pop_all to make it even faster e645c4928 -: updated TODO a425878e8 sparse_set/storage: clear is backward compatible now f3cd9d374 storage: fixed clear_all counter b3e93b084 registry: naming convention 314c189c4 test: minor changes 2bb2c5566 build: try to make lcov happy again d13c126e9 view: avoid name clashes 9b54ee37a flow: propagate allocator to generated graph + internal rework e1ead9d3e build: update coverage workflow cf61068dc mixin: suppress a warning with gcc11 82863f829 test: code coverage for range functionalities e4de59827 test: try to make lcov happy ccea4c920 memory: code coverage 89166f0e4 build: refine analyzer workflow 7a05a16c5 registry: slightly better destroy (yet not quite there though) d0854646c test: yet another test to stress the upcoming changes 1e9c9fe5f registry: better, faster range-remove + refine range-erase 80fac8d8e test: minor changes c774b9838 -: updated TODO 3fd0403cc registry: faster, better range-erase 6eb3347a3 test: a couple of extra functions to stress the upcoming changes 89bceaff7 -: updated TODO dc25c9c1a sparse_set: invoke release_sparse_pages before clearing the sparse array e68ba5870 sigh_mixin: add a missing include c68cb3375 entity: make deletion_policy publicly available via fwd.hpp 59f807fd0 sparse_set: suppress warnings due to unused expressions 232ffebc1 sparse_set: internal clear_all function 3cea845a0 sparse_set: sparse_set_iterator::data function 295f3b32e registry: a couple of extra move calls here and there 254da2c3c sparse_set: better, faster range remove ecd3b8d93 sparse_set: prevent rework errors as much as possible c673b9b17 sigh_mixin: slightly improved pop + review insert cd28de0d6 test: clear-stable bench 672f6a711 test: minor changes 3b50672b7 storage: restore storage_for/storage_type duality, it turned out to be very useful in practice f0613b1c6 sparse_set/storage: minor changes to reuse type members 2197e160e -: drop file pushed by mistake :) 2dccd9016 handle: discard entity on destruction 2f873f2dd -: storage_mixin.hpp -> mixin.hpp (non-storage mixins are also a thing) fde1a524e sparse_set: ::get -> ::value (to avoid hiding from derived classes) 055801047 doc: drop references to docsforge + minor changes 79a054a52 sigh_mixin: scope base_type properly d94e443a1 doc: drop outdated section 3862184e8 sigh_mixin: support self managed storage classes f40fa3c2f test: * use range destroy * avoid compiler optimizations 01bc93459 test (bench): the new entity storage enables the fast path in all cases 151bd0739 sparse_set: revert optmized range push, it prevents self-managed storage classes 935393aae sparse_set: better, faster range push fbfde4347 snapshot: avoid unused variable warnings 2ffbe115b component_traits: revert entity customization support 645973eb7 sparse_set: insert -> push 133230797 sparse_set: emplace -> push b700f5eb5 doc: typo e60dbdc52 sparse_set/storage: * rename swap_at in swap_or_move to capture the real purpose * define swap_at as a protected function to allow swapping from above c66623b33 sigh_mixin: avoid hiding basic_iterator type meber 62246d879 storage: avoid hiding basic_iterator type meber b35f13130 sparse_set: support swap-only mixins 3dd82633a -: drop storage_mixin.cpp, I forgot to do it a couple of commits ago :) 00231bf8a storage: make swap_at non-final to support checks on derived classes 58d392e81 -: minor changes 1d4d99d09 mixin: sigh_storage_mixin -> sigh_mixin fe3edf2c8 -: minor changes 0864ba042 -: drop useless typename 3a9698001 build: minor changes 423f7a555 is_equality_comparable: detect C-style arrays directly 5db8ad53a build: update gh workflow c2ab35780 view: make also VS toolset v141 happy 4fb558f14 view: further reduce instantiations 5762a8a08 view: reuse internal functions if possible ed4c67521 sparse_set/storage: drop move_element f15789846 config: ENTT_FAIL(msg) -> ENTT_ASSERT(false, msg) 6d20709e0 storage: minor changes a9a9853c0 sigh_storage_mixin: use entity_type from Type af14aa4c9 doc: more about signals (sigh_storage_mixin) 24d6b9881 test: minor changes 899f4baa6 storage: * drop storage_for]_t] * make storage_type[_t] deal with constness c1ab7ba02 sigh_storage_mixin: make all virtual member functions final 9d38f6020 registry: thanks MSVC for accepting invalid C++ code 0efa25cf6 sigh: cool, I keep doing the same error again and again apparently :) 6316b6045 registry: make it work with storage<void> also in C++17 f268fb60a entity: avoid breaking changes due to type members renaming 3520d6915 entity: add base_type 4da7a8451 entity: make checks work with 64b identifiers :) 382dfc3bb entity: strict check on entity/version masks b6dcdc816 entity: * also expose entity_mask and version mask to the final user * avoid default args with entt_traits::construct for backward compatibility c9d544089 doc: review/cleanup entity.md a bit (done) 3eb5faeed doc: review/cleanup entity.md a bit (work in progress) 7a328c7ed doc: updated links 6567aa195 doc: a note about listeners disconnection (close #958) 92319f011 entt_traits: split basic impl, simplify def 782d86b6e entt_traits: value_type -> type (cuz it's not a value type after all) c2cae37c1 entity_traits: make page_size type explicit 1026d26ec entt_traits: drop reserved value 7156803db test: local non-static constexpr variables f54ed5424 helper: local non-static constexpr variables f30b50195 algorithm: local non-static constexpr variables c90ab9aff sparse_set: * break dependency on traits_type::reserved * use a tombstone if all I need is a tombstone c2f6ca43f doc: graph (close #957) 3e5e41d88 test: cover some corner cases of the flow class 9eafc0431 flow: minor changes 0a82b777b component_traits: support specializations based on entity type 32bcc01a4 component: * make component_traits treat void properly * drop ignore_as_empty_v 9c3fe3546 nativs: entity module 83f8aed58 helper: use traits_type from storage class directly 2fd660274 snapshot: use public registry traits_type member type a554d406e registry: * public traits_type member type * break dependency on component_traits * use public storage traits_type member type 5f12f872e test: minor changes be4eb68a3 helper: * break dependency on component_traits * use public storage traits_type member type df5284d9e view: * break dependency on component_traits * use public storage traits_type member type 0e27d33e7 storage: public traits_type member type fe6e6ae73 sparse_set: public traits_type member type 9d29713ea entity: naming convention 270d0277d group: cleanup 0bd06c8d5 hashed_string: naming convention 733f215cc storage: break dependency between component_traits and storage_iterator ad01a69fe *: renaming/coding style dd9c1dade sparse_set: no need to differentiate template args for sparse_set_iterator b8f70519f doc: fixed typo 9b9d212dd *: coding style 3fe15969d doc: cleanup ec4bf222c meta: avoid the +1u trick for 0-sized arrays 1173908ee meta: avoid rebinding when forwarding requests 2595b8a92 doc: sigh_helper f4e2a8c76 sigh_builder: add all missing .template that msvc kindly accepted anyway 66e1a0565 entity: sigh_helper utility with tests (close #928) 87283dc41 storage: simplified impl in order to introduce multi-type storage more easily a802ebffe storage: * move storage_type[_t] and storage_for[_t] to fwd.hpp * no need to include storage.hpp when forward defining views b84b09421 doc: add Arch ECS to references.md (#954) 940fd0939 todo: add a note for a (soon to be released) change 920338be5 doc: add ecsact to links.md (thanks @zaucy for pointing this out) bcd1155b7 gh: add more gcc and clang versions 1dc88109e gh: update workflows 262c1f53c cmake: only enable -Wdocumentation for clang-cl 4af0a3a0d doc: cleanup be1641828 doc: cleanup b54a52fbf doc: fixed typo ae8815995 doc: fixed typo 62c764f68 doc: fixed typo 2c48cc10a cmake: enable documentation diagnostic for clang 82f286678 sigh: drop redundant function d56e5a269 registry: propagate allocator to context 1517b2951 doc: document delegate raw access bea7b43a1 delegate: target member function 2f878f8b5 sigh: refine ::collect fc68c1b29 view/group: cleanup 9081c185d meta: minor changes 7c4493f23 group: make filter storage available da4e73ab8 view: make filter storage available f3e7f98b4 registry: extra check when moving a registry 3925fc612 emitter: extra allocator check when moving c639130c1 dispatcher: extra allocator check when moving 75c311600 registry: cleanup e9e14eb49 meta: [[nodiscard]] d1558304f any: [[nodiscard]] 0531b530b snapshot: minor changes f9d0178dd workflow: bump iwyu version b66b8d37e test: suppress warning 05ef4c29d storage: minor changes 9c3d75669 test: cleanup include directives 93651e46f registry: drop [[deprecated]] functions ea901cbfa test: code coverage d5dc4f43e doc: meta.md 498e02f15 doc: core.md d0ea8f4f9 cmake: suppress some warnings for clang-cl, it goes a little wrong otherwise dec3b7bb3 test: suppress warnings 10bc8b05a test: use /W1 with VS (but for toolset v141, too bugged for that) ad77b54dc cmake: bump version to get some cool feature/update b6724b028 group: pass filter storage to groups (in-between change for full storage access) 54270b103 group: make them easily copyable/movable 31dc732a7 doc: graph.md f0e02d6d3 doc: container.md 156d6e4ea doc: poly.md 4375c1c3d doc: lib.md 24a9cd67e scheduler: forgot to add the fwd file to the previous commit :) ba8d522c1 doc: add the worst engine (love the name) to the list of links 3ae46214a doc: review process.md 5119fe8d7 scheduler: basic type model with default for common cases ed0319cdd view: avoid shadow warnings bc50da6a7 storage: suppress warnings with non copyable nor default constructible types 52b3b4c24 group: suppress warnings for unused variables in case of empty types 74bab529d test: minor changes b1b143917 meta: [[maybe_unused]] variable to avoid warnings with corner cases 7beb4c85c test: suppress a few warnings (entity) f3beb5670 test: suppress a few warnings (container) 446c67b69 test: suppress a few warnings (resource) c4507bd17 test: suppress a few warnings (poly) 61e872bb4 test: suppress a few warnings (meta) 9f22a3e23 test: suppress a few warnings (memory) 653dd5cd4 test: suppress a few warnings (tuple) bc53ed3be test: suppress a few warnings (flow) f935bbcce dense_set: suppress warnings due to possible narrowing conversions c7d505353 dense_map: suppress warnings due to possible narrowing conversions ea78f1d97 now working on version 3.12 REVERT: fef921132 update single include file REVERT: e52a93f8a ready to cut v3.11.1 REVERT: cd541f335 storage: * move storage_type[_t] and storage_for[_t] to fwd.hpp * no need to include storage.hpp when forward defining views REVERT: 255b8be8c view: avoid shadow warnings REVERT: 8cd7f064a storage: suppress warnings with non copyable nor default constructible types REVERT: 58ae4117c group: suppress warnings for unused variables in case of empty types REVERT: cfa1e805b meta: [[maybe_unused]] variable to avoid warnings with corner cases REVERT: ccedacec8 dense_set: suppress warnings due to possible narrowing conversions REVERT: 17578dc8c dense_map: suppress warnings due to possible narrowing conversions git-subtree-dir: external/entt/entt git-subtree-split: 344e03ac64a1f78424ab1150e2d4778e8df8431d
2345 lines
89 KiB
Markdown
2345 lines
89 KiB
Markdown
# Crash Course: entity-component system
|
|
|
|
<!--
|
|
@cond TURN_OFF_DOXYGEN
|
|
-->
|
|
# Table of Contents
|
|
|
|
* [Introduction](#introduction)
|
|
* [Design decisions](#design-decisions)
|
|
* [Type-less and bitset-free](#type-less-and-bitset-free)
|
|
* [Build your own](#build-your-own)
|
|
* [Pay per use](#pay-per-use)
|
|
* [All or nothing](#all-or-nothing)
|
|
* [Vademecum](#vademecum)
|
|
* [The Registry, the Entity and the Component](#the-registry-the-entity-and-the-component)
|
|
* [Observe changes](#observe-changes)
|
|
* [Entity lifecycle](#entity-lifecycle)
|
|
* [Listeners disconnection](#listeners-disconnection)
|
|
* [They call me Reactive System](#they-call-me-reactive-system)
|
|
* [Sorting: is it possible?](#sorting-is-it-possible)
|
|
* [Helpers](#helpers)
|
|
* [Null entity](#null-entity)
|
|
* [Tombstone](#tombstone)
|
|
* [To entity](#to-entity)
|
|
* [Dependencies](#dependencies)
|
|
* [Invoke](#invoke)
|
|
* [Connection helper](#connection-helper)
|
|
* [Handle](#handle)
|
|
* [Organizer](#organizer)
|
|
* [Context variables](#context-variables)
|
|
* [Aliased properties](#aliased-properties)
|
|
* [Snapshot: complete vs continuous](#snapshot-complete-vs-continuous)
|
|
* [Snapshot loader](#snapshot-loader)
|
|
* [Continuous loader](#continuous-loader)
|
|
* [Archives](#archives)
|
|
* [One example to rule them all](#one-example-to-rule-them-all)
|
|
* [Storage](#storage)
|
|
* [Component traits](#component-traits)
|
|
* [Empty type optimization](#empty-type-optimization)
|
|
* [Void storage](#void-storage)
|
|
* [Entity storage](#entity-storage)
|
|
* [One of a kind to the registry](#one-of-a-kind-to-the-registry)
|
|
* [Pointer stability](#pointer-stability)
|
|
* [In-place delete](#in-place-delete)
|
|
* [Hierarchies and the like](#hierarchies-and-the-like)
|
|
* [Meet the runtime](#meet-the-runtime)
|
|
* [A base class to rule them all](#a-base-class-to-rule-them-all)
|
|
* [Beam me up, registry](#beam-me-up-registry)
|
|
* [Views and Groups](#views-and-groups)
|
|
* [Views](#views)
|
|
* [Create once, reuse many times](#create-once-reuse-many-times)
|
|
* [Exclude-only](#exclude-only)
|
|
* [View pack](#view-pack)
|
|
* [Iteration order](#iteration-order)
|
|
* [Runtime views](#runtime-views)
|
|
* [Groups](#groups)
|
|
* [Full-owning groups](#full-owning-groups)
|
|
* [Partial-owning groups](#partial-owning-groups)
|
|
* [Non-owning groups](#non-owning-groups)
|
|
* [Types: const, non-const and all in between](#types-const-non-const-and-all-in-between)
|
|
* [Give me everything](#give-me-everything)
|
|
* [What is allowed and what is not](#what-is-allowed-and-what-is-not)
|
|
* [More performance, more constraints](#more-performance-more-constraints)
|
|
* [Multithreading](#multithreading)
|
|
* [Iterators](#iterators)
|
|
* [Const registry](#const-registry)
|
|
* [Beyond this document](#beyond-this-document)
|
|
<!--
|
|
@endcond TURN_OFF_DOXYGEN
|
|
-->
|
|
|
|
# Introduction
|
|
|
|
`EnTT` offers a header-only, tiny and easy to use entity-component system module
|
|
written in modern C++.<br/>
|
|
The entity-component-system (also known as _ECS_) is an architectural pattern
|
|
used mostly in game development.
|
|
|
|
# Design decisions
|
|
|
|
## Type-less and bitset-free
|
|
|
|
The library implements a sparse set based model that doesn't require users to
|
|
specify the set of components neither at compile-time nor at runtime.<br/>
|
|
This is why users can instantiate the core class simply like:
|
|
|
|
```cpp
|
|
entt::registry registry;
|
|
```
|
|
|
|
In place of its more annoying and error-prone counterpart:
|
|
|
|
```cpp
|
|
entt::registry<comp_0, comp_1, ..., comp_n> registry;
|
|
```
|
|
|
|
Furthermore, it isn't necessary to announce the existence of a component type.
|
|
When the time comes, just use it and that's all.
|
|
|
|
## Build your own
|
|
|
|
The ECS module (as well as the rest of the library) is designed as a set of
|
|
containers that are used as needed, just like a vector or any other container.
|
|
It doesn't attempt in any way to take over on the user codebase, nor to control
|
|
its main loop or process scheduling.<br/>
|
|
Unlike other more or less well known models, it also makes use of independent
|
|
pools that are extended via _static mixins_. The built-in signal support is an
|
|
example of this flexible design: defined as a mixin, it's easily disabled if not
|
|
needed. Similarly, the storage class has a specialization that shows how
|
|
everything is customizable down to the smallest detail.
|
|
|
|
## Pay per use
|
|
|
|
Everything is designed around the principle that users only have to pay for what
|
|
they want.
|
|
|
|
When it comes to using an entity-component system, the tradeoff is usually
|
|
between performance and memory usage. The faster it is, the more memory it uses.
|
|
Even worse, some approaches tend to heavily affect other functionalities like
|
|
the construction and destruction of components to favor iterations, even when it
|
|
isn't strictly required. In fact, slightly worse performance along non-critical
|
|
paths are the right price to pay to reduce memory usage and have overall better
|
|
performance.<br/>
|
|
`EnTT` follows a completely different approach. It gets the best out from the
|
|
basic data structures and gives users the possibility to pay more for higher
|
|
performance where needed.
|
|
|
|
## All or nothing
|
|
|
|
As a rule of thumb, a `T **` pointer (or whatever a custom pool returns) is
|
|
always available to directly access all the instances of a given component type
|
|
`T`.<br/>
|
|
This is one of the corner stones of the library. Many of the tools offered are
|
|
designed around this need and give the possibility to get this information.
|
|
|
|
# Vademecum
|
|
|
|
The `entt::entity` type implements the concept of _entity identifier_. An entity
|
|
(the _E_ of an _ECS_) is an opaque element to use as-is. Inspecting it isn't
|
|
recommended since its format can change in future.<br/>
|
|
Components (the _C_ of an _ECS_) are of any type, without any constraints, not
|
|
even that of being movable. No need to register them nor their types.<br/>
|
|
Systems (the _S_ of an _ECS_) are plain functions, functors, lambdas and so on.
|
|
It's not required to announce them in any case and have no requirements.
|
|
|
|
The next sections go into detail on how to use the entity-component system part
|
|
of the `EnTT` library.<br/>
|
|
This module is likely larger than what is described below. For more details,
|
|
please refer to the inline documentation.
|
|
|
|
# The Registry, the Entity and the Component
|
|
|
|
A registry stores and manages entities (or _identifiers_) and components.<br/>
|
|
The class template `basic_registry` lets users decide what the preferred type to
|
|
represent an entity is. Because `std::uint32_t` is large enough for almost any
|
|
case, there also exists the enum class `entt::entity` that _wraps_ it and the
|
|
alias `entt::registry` for `entt::basic_registry<entt::entity>`.
|
|
|
|
Entities are represented by _entity identifiers_. An entity identifier contains
|
|
information about the entity itself and its version.<br/>
|
|
User defined identifiers are allowed as enum classes and class types that define
|
|
an `entity_type` member of type `std::uint32_t` or `std::uint64_t`.
|
|
|
|
A registry is used both to construct and to destroy entities:
|
|
|
|
```cpp
|
|
// constructs a naked entity with no components and returns its identifier
|
|
auto entity = registry.create();
|
|
|
|
// destroys an entity and all its components
|
|
registry.destroy(entity);
|
|
```
|
|
|
|
The `create` member function also accepts a hint. Moreover, it has an overload
|
|
that gets two iterators to use to generate many entities at once efficiently.
|
|
Similarly, the `destroy` member function also works with a range of entities:
|
|
|
|
```cpp
|
|
// destroys all the entities in a range
|
|
auto view = registry.view<a_component, another_component>();
|
|
registry.destroy(view.begin(), view.end());
|
|
```
|
|
|
|
In addition to offering an overload to force the version upon destruction.<br/>
|
|
This function removes all components from an entity before releasing it. There
|
|
also exists a _lighter_ alternative that doesn't query component pools, for use
|
|
with orphaned entities:
|
|
|
|
```cpp
|
|
// releases an orphaned identifier
|
|
registry.release(entity);
|
|
```
|
|
|
|
As with the `destroy` function, also in this case entity ranges are supported
|
|
and it's possible to force a _version_.
|
|
|
|
In both cases, when an identifier is released, the registry can freely reuse it
|
|
internally. In particular, the version of an entity is increased (unless the
|
|
overload that forces a version is used instead of the default one).<br/>
|
|
Users can then _test_ identifiers by means of a registry:
|
|
|
|
```cpp
|
|
// returns true if the entity is still valid, false otherwise
|
|
bool b = registry.valid(entity);
|
|
|
|
// gets the actual version for the given entity
|
|
auto curr = registry.current(entity);
|
|
```
|
|
|
|
Or _inspect_ them using some functions meant for parsing an identifier as-is,
|
|
such as:
|
|
|
|
```cpp
|
|
// gets the version contained in the entity identifier
|
|
auto version = entt::to_version(entity);
|
|
```
|
|
|
|
Components are assigned to or removed from entities at any time.<br/>
|
|
The `emplace` member function template creates, initializes and assigns to an
|
|
entity the given component. It accepts a variable number of arguments to use to
|
|
construct the component itself:
|
|
|
|
```cpp
|
|
registry.emplace<position>(entity, 0., 0.);
|
|
|
|
// ...
|
|
|
|
auto &vel = registry.emplace<velocity>(entity);
|
|
vel.dx = 0.;
|
|
vel.dy = 0.;
|
|
```
|
|
|
|
The default storage _detects_ aggregate types internally and exploits aggregate
|
|
initialization when possible.<br/>
|
|
Therefore, it's not strictly necessary to define a constructor for each type.
|
|
|
|
The `insert` member function works with _ranges_ and is used to:
|
|
|
|
* Assign the same component to all entities at once when a type is specified as
|
|
a template parameter or an instance is passed as an argument:
|
|
|
|
```cpp
|
|
// default initialized type assigned by copy to all entities
|
|
registry.insert<position>(first, last);
|
|
|
|
// user-defined instance assigned by copy to all entities
|
|
registry.insert(from, to, position{0., 0.});
|
|
```
|
|
|
|
* Assign a set of components to the entities when a range is provided (the
|
|
length of the range of components **must** be the same of that of entities):
|
|
|
|
```cpp
|
|
// first and last specify the range of entities, instances points to the first element of the range of components
|
|
registry.insert<position>(first, last, instances);
|
|
```
|
|
|
|
If an entity already has the given component, the `replace` and `patch` member
|
|
function templates are used to update it:
|
|
|
|
```cpp
|
|
// replaces the component in-place
|
|
registry.patch<position>(entity, [](auto &pos) { pos.x = pos.y = 0.; });
|
|
|
|
// constructs a new instance from a list of arguments and replaces the component
|
|
registry.replace<position>(entity, 0., 0.);
|
|
```
|
|
|
|
When it's unknown whether an entity already owns an instance of a component,
|
|
`emplace_or_replace` is the function to use instead:
|
|
|
|
```cpp
|
|
registry.emplace_or_replace<position>(entity, 0., 0.);
|
|
```
|
|
|
|
This is a slightly faster alternative to the following snippet:
|
|
|
|
```cpp
|
|
if(registry.all_of<velocity>(entity)) {
|
|
registry.replace<velocity>(entity, 0., 0.);
|
|
} else {
|
|
registry.emplace<velocity>(entity, 0., 0.);
|
|
}
|
|
```
|
|
|
|
The `all_of` and `any_of` member functions may also be useful if in doubt about
|
|
whether or not an entity has all the components in a set or any of them:
|
|
|
|
```cpp
|
|
// true if entity has all the given components
|
|
bool all = registry.all_of<position, velocity>(entity);
|
|
|
|
// true if entity has at least one of the given components
|
|
bool any = registry.any_of<position, velocity>(entity);
|
|
```
|
|
|
|
If the goal is to delete a component from an entity that owns it, the `erase`
|
|
member function template is the way to go:
|
|
|
|
```cpp
|
|
registry.erase<position>(entity);
|
|
```
|
|
|
|
When in doubt whether the entity owns the component, use the `remove` member
|
|
function instead. It behaves similarly to `erase` but it drops the component if
|
|
and only if it exists, otherwise it returns safely to the caller:
|
|
|
|
```cpp
|
|
registry.remove<position>(entity);
|
|
```
|
|
|
|
The `clear` member function works similarly and is used to either:
|
|
|
|
* Erases all instances of the given components from the entities that own them:
|
|
|
|
```cpp
|
|
registry.clear<position>();
|
|
```
|
|
|
|
* Or destroy all entities in a registry at once:
|
|
|
|
```cpp
|
|
registry.clear();
|
|
```
|
|
|
|
Finally, references to components are obtained simply as:
|
|
|
|
```cpp
|
|
const auto &cregistry = registry;
|
|
|
|
// const and non-const reference
|
|
const auto &crenderable = cregistry.get<renderable>(entity);
|
|
auto &renderable = registry.get<renderable>(entity);
|
|
|
|
// const and non-const references
|
|
const auto [cpos, cvel] = cregistry.get<position, velocity>(entity);
|
|
auto [pos, vel] = registry.get<position, velocity>(entity);
|
|
```
|
|
|
|
If the existence of the component isn't certain, `try_get` is the more suitable
|
|
function instead.
|
|
|
|
## Observe changes
|
|
|
|
By default, each storage comes with a mixin that adds signal support to it.<br/>
|
|
This allows for fancy things like dependencies and reactive systems.
|
|
|
|
The `on_construct` member function returns a _sink_ (which is an object for
|
|
connecting and disconnecting listeners) for those interested in notifications
|
|
when a new instance of a given component type is created:
|
|
|
|
```cpp
|
|
// connects a free function
|
|
registry.on_construct<position>().connect<&my_free_function>();
|
|
|
|
// connects a member function
|
|
registry.on_construct<position>().connect<&my_class::member>(instance);
|
|
|
|
// disconnects a free function
|
|
registry.on_construct<position>().disconnect<&my_free_function>();
|
|
|
|
// disconnects a member function
|
|
registry.on_construct<position>().disconnect<&my_class::member>(instance);
|
|
```
|
|
|
|
Similarly, `on_destroy` and `on_update` are used to receive notifications about
|
|
the destruction and update of an instance, respectively.<br/>
|
|
Because of how C++ works, listeners attached to `on_update` are only invoked
|
|
following a call to `replace`, `emplace_or_replace` or `patch`.
|
|
|
|
Runtime pools are also supported by providing an identifier to the functions
|
|
above:
|
|
|
|
```cpp
|
|
registry.on_construct<position>("other"_hs).connect<&my_free_function>();
|
|
```
|
|
|
|
Refer to the following sections for more information about runtime pools.<br/>
|
|
In all cases, the function type of a listener is equivalent to the following:
|
|
|
|
```cpp
|
|
void(entt::registry &, entt::entity);
|
|
```
|
|
|
|
In all cases, listeners are provided with the registry that triggered the
|
|
notification and the involved entity.
|
|
|
|
Note also that:
|
|
|
|
* Listeners for the construction signals are invoked **after** components have
|
|
been assigned to entities.
|
|
|
|
* Listeners designed to observe changes are invoked **after** components have
|
|
been updated.
|
|
|
|
* Listeners for the destruction signals are invoked **before** components have
|
|
been removed from entities.
|
|
|
|
There are also some limitations on what a listener can and cannot do:
|
|
|
|
* Connecting and disconnecting other functions from within the body of a
|
|
listener should be avoided. It can lead to undefined behavior in some cases.
|
|
|
|
* Removing the component from within the body of a listener that observes the
|
|
construction or update of instances of a given type isn't allowed.
|
|
|
|
* Assigning and removing components from within the body of a listener that
|
|
observes the destruction of instances of a given type should be avoided. It
|
|
can lead to undefined behavior in some cases. This type of listeners is
|
|
intended to provide users with an easy way to perform cleanup and nothing
|
|
more.
|
|
|
|
Please, refer to the documentation of the signal class to know about all the
|
|
features it offers.<br/>
|
|
There are many useful but less known functionalities that aren't described here,
|
|
such as the connection objects or the possibility to attach listeners with a
|
|
list of parameters that is shorter than that of the signal itself.
|
|
|
|
### Entity lifecycle
|
|
|
|
Observing entities is also possible. In this case, the user must use the entity
|
|
type instead of the component type:
|
|
|
|
```cpp
|
|
registry.on_construct<entt::entity>().connect<&my_listener>();
|
|
```
|
|
|
|
Since entity storage is unique within a registry, if a _name_ is provided it's
|
|
ignored and therefore discarded.<br/>
|
|
As for the function signature, this is exactly the same as the components.
|
|
|
|
Entities support all types of signals: construct, destroy and update. The latter
|
|
is perhaps ambiguous as an entity is not truly _updated_. Rather, its identifier
|
|
is created and finally released.<br/>
|
|
Indeed, the update signal is meant to send _general notifications_ regarding an
|
|
entity. It can be triggered via the `patch` function, as is the case with
|
|
components:
|
|
|
|
```cpp
|
|
registry.patch<entt::entity>(entity);
|
|
```
|
|
|
|
Destroying an entity and then updating the version of an identifier **does not**
|
|
give rise to these types of signals under any circumstances instead.
|
|
|
|
### Listeners disconnection
|
|
|
|
The destruction order of the storage classes and therefore the disconnection of
|
|
the listeners is completely random.<br/>
|
|
There are no guarantees today and while a logic is easily discerned, it's not
|
|
guaranteed that it will remain so in the future.
|
|
|
|
For example, a listener getting disconnected after a component is discarded as a
|
|
result of pool destruction is most likely a recipe for problems.<br/>
|
|
Rather, it's advisable to invoke the `clear` function of the registry before
|
|
destroying it. This forces the deletion of all components and entities without
|
|
ever discarding the pools.<br/>
|
|
As a result, a listener that wants to access components, entities, or pools can
|
|
safely do so against a still valid registry, while checking for the existence of
|
|
the various elements as appropriate.
|
|
|
|
### They call me Reactive System
|
|
|
|
Signals are the basic tools to construct reactive systems, even if they aren't
|
|
enough on their own. `EnTT` tries to take another step in that direction with
|
|
the `observer` class template.<br/>
|
|
In order to explain what reactive systems are, this is a slightly revised quote
|
|
from the documentation of the library that first introduced this tool,
|
|
[Entitas](https://github.com/sschmid/Entitas-CSharp):
|
|
|
|
> Imagine you have 100 fighting units on the battlefield but only 10 of them
|
|
> changed their positions. Instead of using a normal system and updating all 100
|
|
> entities depending on the position, you can use a reactive system which will
|
|
> only update the 10 changed units. So efficient.
|
|
|
|
In `EnTT`, this means iterating over a reduced set of entities and components
|
|
than what would otherwise be returned from a view or group.<br/>
|
|
On these words, however, the similarities with the proposal of `Entitas` also
|
|
end. The rules of the language and the design of the library obviously impose
|
|
and allow different things.
|
|
|
|
An `observer` is initialized with an instance of a registry and a set of _rules_
|
|
that describes what are the entities to intercept. As an example:
|
|
|
|
```cpp
|
|
entt::observer observer{registry, entt::collector.update<sprite>()};
|
|
```
|
|
|
|
The class is default constructible and is reconfigured at any time by means of
|
|
the `connect` member function. Moreover, an observer is disconnected from the
|
|
underlying registry through the `disconnect` member function.<br/>
|
|
The `observer` offers also what is needed to query its _internal state_ and to
|
|
know if it's empty or how many entities it contains. Moreover, it can return a
|
|
raw pointer to the list of entities it contains.
|
|
|
|
However, the most important features of this class are that:
|
|
|
|
* It's iterable and therefore users can easily walk through the list of entities
|
|
by means of a range-for loop or the `each` member function.
|
|
|
|
* It's clearable and therefore users can consume the entities and literally
|
|
reset the observer after each iteration.
|
|
|
|
These aspects make the observer an incredibly powerful tool to know at any time
|
|
what are the entities that matched the given rules since the last time one
|
|
asked:
|
|
|
|
```cpp
|
|
for(const auto entity: observer) {
|
|
// ...
|
|
}
|
|
|
|
observer.clear();
|
|
```
|
|
|
|
The snippet above is equivalent to the following:
|
|
|
|
```cpp
|
|
observer.each([](const auto entity) {
|
|
// ...
|
|
});
|
|
```
|
|
|
|
At least as long as the `observer` isn't const. This means that the non-const
|
|
overload of `each` does also reset the underlying data structure before to
|
|
return to the caller, while the const overload does not for obvious reasons.
|
|
|
|
A `collector` is a utility aimed to generate a list of `matcher`s (the actual
|
|
rules) to use with an `observer`.<br/>
|
|
There are two types of `matcher`s:
|
|
|
|
* Observing matcher: an observer returns at least the entities for which one or
|
|
more of the given components have been updated and not yet destroyed.
|
|
|
|
```cpp
|
|
entt::collector.update<sprite>();
|
|
```
|
|
|
|
Where _updated_ means that all listeners attached to `on_update` are invoked.
|
|
In order for this to happen, specific functions such as `patch` must be used.
|
|
Refer to the specific documentation for more details.
|
|
|
|
* Grouping matcher: an observer returns at least the entities that would have
|
|
entered the given group if it existed and that would have not yet left it.
|
|
|
|
```cpp
|
|
entt::collector.group<position, velocity>(entt::exclude<destroyed>);
|
|
```
|
|
|
|
A grouping matcher supports also exclusion lists as well as single components.
|
|
|
|
Roughly speaking, an observing matcher intercepts the entities for which the
|
|
given components are updated while a grouping matcher tracks the entities that
|
|
have assigned the given components since the last time one asked.<br/>
|
|
If an entity already has all the components except one and the missing type is
|
|
assigned to it, the entity is intercepted by a grouping matcher.
|
|
|
|
In addition, matchers support filtering by means of a `where` clause:
|
|
|
|
```cpp
|
|
entt::collector.update<sprite>().where<position>(entt::exclude<velocity>);
|
|
```
|
|
|
|
This clause introduces a way to intercept entities if and only if they are
|
|
already part of a hypothetical group. If they are not, they aren't returned by
|
|
the observer, no matter if they matched the given rule.<br/>
|
|
In the example above, whenever the component `sprite` of an entity is updated,
|
|
the observer checks the entity itself to verify that it has at least `position`
|
|
and has not `velocity`. If one of the two conditions isn't satisfied, the entity
|
|
is discarded, no matter what.
|
|
|
|
A `where` clause accepts a theoretically unlimited number of types as well as
|
|
multiple elements in the exclusion list. Moreover, every matcher can have its
|
|
own clause and multiple clauses for the same matcher are combined in a single
|
|
one.
|
|
|
|
## Sorting: is it possible?
|
|
|
|
Sorting entities and components is possible using an in-place algorithm that
|
|
doesn't require memory allocations and is therefore quite convenient.<br/>
|
|
There are two functions that respond to slightly different needs:
|
|
|
|
* Components are sorted either directly:
|
|
|
|
```cpp
|
|
registry.sort<renderable>([](const auto &lhs, const auto &rhs) {
|
|
return lhs.z < rhs.z;
|
|
});
|
|
```
|
|
|
|
Or by accessing their entities:
|
|
|
|
```cpp
|
|
registry.sort<renderable>([](const entt::entity lhs, const entt::entity rhs) {
|
|
return entt::registry::entity(lhs) < entt::registry::entity(rhs);
|
|
});
|
|
```
|
|
|
|
There exists also the possibility to use a custom sort function object for
|
|
when the usage pattern is known.
|
|
|
|
* Components are sorted according to the order imposed by another component:
|
|
|
|
```cpp
|
|
registry.sort<movement, physics>();
|
|
```
|
|
|
|
In this case, instances of `movement` are arranged in memory so that cache
|
|
misses are minimized when the two components are iterated together.
|
|
|
|
As a side note, the use of groups limits the possibility of sorting pools of
|
|
components. Refer to the specific documentation for more details.
|
|
|
|
## Helpers
|
|
|
|
The so called _helpers_ are small classes and functions mainly designed to offer
|
|
built-in support for the most basic functionalities.
|
|
|
|
### Null entity
|
|
|
|
The `entt::null` variable models the concept of a _null entity_.<br/>
|
|
The library guarantees that the following expression always returns false:
|
|
|
|
```cpp
|
|
registry.valid(entt::null);
|
|
```
|
|
|
|
A registry rejects the null entity in all cases because it isn't considered
|
|
valid. It also means that the null entity cannot own components.<br/>
|
|
The type of the null entity is internal and should not be used for any purpose
|
|
other than defining the null entity itself. However, there exist implicit
|
|
conversions from the null entity to identifiers of any allowed type:
|
|
|
|
```cpp
|
|
entt::entity null = entt::null;
|
|
```
|
|
|
|
Similarly, the null entity compares to any other identifier:
|
|
|
|
```cpp
|
|
const auto entity = registry.create();
|
|
const bool null = (entity == entt::null);
|
|
```
|
|
|
|
As for its integral form, the null entity only affects the entity part of an
|
|
identifier and is instead completely transparent to its version.
|
|
|
|
Be aware that `entt::null` and entity 0 aren't the same thing. Likewise, a zero
|
|
initialized entity isn't the same as `entt::null`. Therefore, although
|
|
`entt::entity{}` is in some sense an alias for entity 0, none of them are used
|
|
to create a null entity.
|
|
|
|
### Tombstone
|
|
|
|
Similar to the null entity, the `entt::tombstone` variable models the concept of
|
|
a _tombstone_.<br/>
|
|
Once created, the integral form of the two values is the same, although they
|
|
affect different parts of an identifier. In fact, the tombstone only uses the
|
|
version part of it and is completely transparent to the entity part.
|
|
|
|
Also in this case, the following expression always returns false:
|
|
|
|
```cpp
|
|
registry.valid(entt::tombstone);
|
|
```
|
|
|
|
Moreover, users cannot set the tombstone version when releasing an entity:
|
|
|
|
```cpp
|
|
registry.destroy(entity, entt::tombstone);
|
|
```
|
|
|
|
In this case, a different version number is implicitly generated.<br/>
|
|
The type of a tombstone is internal and can change at any time. However, there
|
|
exist implicit conversions from a tombstone to identifiers of any allowed type:
|
|
|
|
```cpp
|
|
entt::entity null = entt::tombstone;
|
|
```
|
|
|
|
Similarly, the tombstone compares to any other identifier:
|
|
|
|
```cpp
|
|
const auto entity = registry.create();
|
|
const bool tombstone = (entity == entt::tombstone);
|
|
```
|
|
|
|
Be aware that `entt::tombstone` and entity 0 aren't the same thing. Likewise, a
|
|
zero initialized entity isn't the same as `entt::tombstone`. Therefore, although
|
|
`entt::entity{}` is in some sense an alias for entity 0, none of them are used
|
|
to create tombstones.
|
|
|
|
### To entity
|
|
|
|
This function accepts a registry and an instance of a component and returns the
|
|
entity associated with the latter:
|
|
|
|
```cpp
|
|
const auto entity = entt::to_entity(registry, position);
|
|
```
|
|
|
|
A null entity is returned in case the component doesn't belong to the registry.
|
|
|
|
### Dependencies
|
|
|
|
The `registry` class is designed to create short circuits between its member
|
|
functions. This greatly simplifies the definition of a _dependency_.<br/>
|
|
For example, the following adds (or replaces) the component `a_type` whenever
|
|
`my_type` is assigned to an entity:
|
|
|
|
```cpp
|
|
registry.on_construct<my_type>().connect<&entt::registry::emplace_or_replace<a_type>>();
|
|
```
|
|
|
|
Similarly, the code below removes `a_type` from an entity whenever `my_type` is
|
|
assigned to it:
|
|
|
|
```cpp
|
|
registry.on_construct<my_type>().connect<&entt::registry::remove<a_type>>();
|
|
```
|
|
|
|
A dependency is easily _broken_ as follows:
|
|
|
|
```cpp
|
|
registry.on_construct<my_type>().disconnect<&entt::registry::emplace_or_replace<a_type>>();
|
|
```
|
|
|
|
There are many other types of _dependencies_. In general, most of the functions
|
|
that accept an entity as their first argument are good candidates for this
|
|
purpose.
|
|
|
|
### Invoke
|
|
|
|
The `invoke` helper allows to _propagate_ a signal to a member function of a
|
|
component without having to _extend_ it:
|
|
|
|
```cpp
|
|
registry.on_construct<clazz>().connect<entt::invoke<&clazz::func>>();
|
|
```
|
|
|
|
All it does is pick up the _right_ component for the received entity and invoke
|
|
the requested method, passing on the arguments if necessary.
|
|
|
|
### Connection helper
|
|
|
|
Connecting signals can quickly become cumbersome.<br/>
|
|
This utility aims to simplify the process by grouping the calls:
|
|
|
|
```cpp
|
|
entt::sigh_helper{registry}
|
|
.with<position>()
|
|
.on_construct<&a_listener>()
|
|
.on_destroy<&another_listener>()
|
|
.with<velocity>("other"_hs)
|
|
.on_update<yet_another_listener>();
|
|
```
|
|
|
|
Runtime pools are also supported by providing an identifier when calling `with`,
|
|
as shown in the previous snippet. Refer to the following sections for more
|
|
information about runtime pools.<br/>
|
|
Obviously, this helper doesn't make the code disappear but it should at least
|
|
reduce the boilerplate in the most complex cases.
|
|
|
|
### Handle
|
|
|
|
A handle is a thin wrapper around an entity and a registry. It _replicates_ the
|
|
API of a registry by offering functions such as `get` or `emplace`. The
|
|
difference being that the entity is implicitly passed to the registry.<br/>
|
|
It's default constructible as an invalid handle that contains a null registry
|
|
and a null entity. When it contains a null registry, calling functions that
|
|
delegate execution to the registry causes undefined behavior. It's recommended
|
|
to test for validity with its implicit cast to `bool` if in doubt.<br/>
|
|
A handle is also non-owning, meaning that it's freely copied and moved around
|
|
without affecting its entity (in fact, handles happen to be trivially copyable).
|
|
An implication of this is that mutability becomes part of the type.
|
|
|
|
There are two aliases that use `entt::entity` as their default entity:
|
|
`entt::handle` and `entt::const_handle`.<br/>
|
|
Users can also easily create their own aliases for custom identifiers as:
|
|
|
|
```cpp
|
|
using my_handle = entt::basic_handle<entt::basic_registry<my_identifier>>;
|
|
using my_const_handle = entt::basic_handle<const entt::basic_registry<my_identifier>>;
|
|
```
|
|
|
|
Non-const handles are also implicitly convertible to const handles out of the
|
|
box but not the other way around.
|
|
|
|
This class is intended to simplify function signatures. In case of functions
|
|
that take a registry and an entity and do most of their work on that entity,
|
|
users might want to consider using handles, either const or non-const.
|
|
|
|
### Organizer
|
|
|
|
The `organizer` class template offers support for creating an execution graph
|
|
from a set of functions and their requirements on resources.<br/>
|
|
The resulting tasks aren't executed in any case. This isn't the goal of this
|
|
tool. Instead, they are returned to the user in the form of a graph that allows
|
|
for safe execution.
|
|
|
|
All functions are added in order of execution to the organizer:
|
|
|
|
```cpp
|
|
entt::organizer organizer;
|
|
|
|
// adds a free function to the organizer
|
|
organizer.emplace<&free_function>();
|
|
|
|
// adds a member function and an instance on which to invoke it to the organizer
|
|
clazz instance;
|
|
organizer.emplace<&clazz::member_function>(&instance);
|
|
|
|
// adds a decayed lambda directly
|
|
organizer.emplace(+[](const void *, entt::registry &) { /* ... */ });
|
|
```
|
|
|
|
These are the parameters that a free function or a member function can accept:
|
|
|
|
* A possibly constant reference to a registry.
|
|
* An `entt::basic_view` with any possible combination of storage classes.
|
|
* A possibly constant reference to any type `T` (that is, a context variable).
|
|
|
|
The function type for free functions and decayed lambdas passed as parameters to
|
|
`emplace` is `void(const void *, entt::registry &)` instead. The first parameter
|
|
is an optional pointer to user defined data to provide upon registration:
|
|
|
|
```cpp
|
|
clazz instance;
|
|
organizer.emplace(+[](const void *, entt::registry &) { /* ... */ }, &instance);
|
|
```
|
|
|
|
In all cases, it's also possible to associate a name with the task when creating
|
|
it. For example:
|
|
|
|
```cpp
|
|
organizer.emplace<&free_function>("func");
|
|
```
|
|
|
|
When a function is registered with the organizer, everything it accesses is
|
|
considered a _resource_ (views are _unpacked_ and their types are treated as
|
|
resources). The _constness_ of a type also dictates its access mode (RO/RW). In
|
|
turn, this affects the resulting graph, since it influences the possibility of
|
|
launching tasks in parallel.<br/>
|
|
As for the registry, if a function doesn't explicitly request it or requires a
|
|
constant reference to it, it's considered a read-only access. Otherwise, it's
|
|
considered as read-write access. All functions have the registry among their
|
|
resources.
|
|
|
|
When registering a function, users can also require resources that aren't in the
|
|
list of parameters of the function itself. These are declared as template
|
|
parameters:
|
|
|
|
```cpp
|
|
organizer.emplace<&free_function, position, velocity>("func");
|
|
```
|
|
|
|
Similarly, users can override the access mode of a type again via template
|
|
parameters:
|
|
|
|
```cpp
|
|
organizer.emplace<&free_function, const renderable>("func");
|
|
```
|
|
|
|
In this case, even if `renderable` appears among the parameters of the function
|
|
as not constant, it's treated as constant as regards the generation of the task
|
|
graph.
|
|
|
|
To generate the task graph, the organizer offers the `graph` member function:
|
|
|
|
```cpp
|
|
std::vector<entt::organizer::vertex> graph = organizer.graph();
|
|
```
|
|
|
|
A graph is returned in the form of an adjacency list. Each vertex offers the
|
|
following features:
|
|
|
|
* `ro_count` and `rw_count`: the number of resources accessed in read-only or
|
|
read-write mode.
|
|
|
|
* `ro_dependency` and `rw_dependency`: type info objects associated with the
|
|
parameters of the underlying function.
|
|
|
|
* `top_level`: true if a node is a top level one (it has no entering edges),
|
|
false otherwise.
|
|
|
|
* `info`: type info object associated with the underlying function.
|
|
|
|
* `name`: the name associated with the given vertex if any, a null pointer
|
|
otherwise.
|
|
|
|
* `callback`: a pointer to the function to execute and whose function type is
|
|
`void(const void *, entt::registry &)`.
|
|
|
|
* `data`: optional data to provide to the callback.
|
|
|
|
* `children`: the vertices reachable from the given node, in the form of indices
|
|
within the adjacency list.
|
|
|
|
Since the creation of pools and resources within the registry isn't necessarily
|
|
thread safe, each vertex also offers a `prepare` function which is used to setup
|
|
a registry for execution with the created graph:
|
|
|
|
```cpp
|
|
auto graph = organizer.graph();
|
|
entt::registry registry;
|
|
|
|
for(auto &&node: graph) {
|
|
node.prepare(registry);
|
|
}
|
|
```
|
|
|
|
The actual scheduling of the tasks is the responsibility of the user, who can
|
|
use the preferred tool.
|
|
|
|
## Context variables
|
|
|
|
Each registry has a _context_ associated with it, which is an `any` object map
|
|
accessible by both type and _name_ for convenience. The _name_ isn't really a
|
|
name though. In fact, it's a numeric id of type `id_type` used as a key for the
|
|
variable. Any value is accepted, even runtime ones.<br/>
|
|
The context is returned via the `ctx` functions and offers a minimal set of
|
|
feature including the following:
|
|
|
|
```cpp
|
|
// creates a new context variable by type and returns it
|
|
registry.ctx().emplace<my_type>(42, 'c');
|
|
|
|
// creates a new named context variable by type and returns it
|
|
registry.ctx().emplace_as<my_type>("my_variable"_hs, 42, 'c');
|
|
|
|
// inserts or assigns a context variable by (deduced) type and returns it
|
|
registry.ctx().insert_or_assign(my_type{42, 'c'});
|
|
|
|
// inserts or assigns a named context variable by (deduced) type and returns it
|
|
registry.ctx().insert_or_assign("my_variable"_hs, my_type{42, 'c'});
|
|
|
|
// gets the context variable by type as a non-const reference from a non-const registry
|
|
auto &var = registry.ctx().get<my_type>();
|
|
|
|
// gets the context variable by name as a const reference from either a const or a non-const registry
|
|
const auto &cvar = registry.ctx().get<const my_type>("my_variable"_hs);
|
|
|
|
// resets the context variable by type
|
|
registry.ctx().erase<my_type>();
|
|
|
|
// resets the context variable associated with the given name
|
|
registry.ctx().erase<my_type>("my_variable"_hs);
|
|
```
|
|
|
|
Context variable must be both default constructible and movable. If the supplied
|
|
type doesn't match that of the variable when using a _name_, the operation
|
|
fails.<br/>
|
|
For all users who want to use the context but don't want to create elements, the
|
|
`contains` and `find` functions are also available:
|
|
|
|
```cpp
|
|
const bool contains = registry.ctx().contains<my_type>();
|
|
const my_type *value = registry.ctx().find<const my_type>("my_variable"_hs);
|
|
```
|
|
|
|
Also in this case, both functions support constant types and accept a _name_ for
|
|
the variable to look up, as does `at`.
|
|
|
|
### Aliased properties
|
|
|
|
A context also supports creating _aliases_ for existing variables that aren't
|
|
directly managed by the registry. Const and therefore read-only variables are
|
|
also accepted.<br/>
|
|
To do that, the type used upon construction must be a reference type and an
|
|
lvalue is necessarily provided as an argument:
|
|
|
|
```cpp
|
|
time clock;
|
|
registry.ctx().emplace<time &>(clock);
|
|
```
|
|
|
|
Read-only aliased properties are created using const types instead:
|
|
|
|
```cpp
|
|
registry.ctx().emplace<const time &>(clock);
|
|
```
|
|
|
|
Note that `insert_or_assign` doesn't support aliased properties and users must
|
|
necessarily use `emplace` or `emplace_as` for this purpose.<br/>
|
|
When `insert_or_assign` is used to update an aliased property, it _converts_
|
|
the property itself into a non-aliased one.
|
|
|
|
From the point of view of the user, there are no differences between a variable
|
|
that is managed by the registry and an aliased property. However, read-only
|
|
variables aren't accessible as non-const references:
|
|
|
|
```cpp
|
|
// read-only variables only support const access
|
|
const my_type *ptr = registry.ctx().find<const my_type>();
|
|
const my_type &var = registry.ctx().get<const my_type>();
|
|
```
|
|
|
|
Aliased properties are erased as it happens with any other variable. Similarly,
|
|
it's also possible to assign them a _name_.
|
|
|
|
## Snapshot: complete vs continuous
|
|
|
|
This module comes with bare minimum support to serialization.<br/>
|
|
It doesn't convert components to bytes directly, there wasn't the need of
|
|
another tool for serialization out there. Instead, it accepts an opaque object
|
|
with a suitable interface (namely an _archive_) to serialize its internal data
|
|
structures and restore them later. The way types and instances are converted to
|
|
a bunch of bytes is completely in charge to the archive and thus to final users.
|
|
|
|
The goal of the serialization part is to allow users to make both a dump of the
|
|
entire registry or a narrower snapshot, that is to select only the components in
|
|
which they are interested.<br/>
|
|
Intuitively, the use cases are different. As an example, the first approach is
|
|
suitable for local save/restore functionalities while the latter is suitable for
|
|
creating client-server applications and for transferring somehow parts of the
|
|
representation side to side.
|
|
|
|
To take a snapshot of a registry, use the `snapshot` class:
|
|
|
|
```cpp
|
|
output_archive output;
|
|
|
|
entt::snapshot{registry}
|
|
.get<entt::entity>(output)
|
|
.get<a_component>(output)
|
|
.get<another_component>(output);
|
|
```
|
|
|
|
It isn't necessary to invoke all functions each and every time. What functions
|
|
to use in which case mostly depends on the goal.
|
|
|
|
When _getting_ an entity type, the snapshot class serializes all entities along
|
|
with their versions.<br/>
|
|
In all other case, entities and components from a given storage are passed to
|
|
the archive. Named pools are also supported:
|
|
|
|
```cpp
|
|
entt::snapshot{registry}.get<a_component>(output, "other"_hs);
|
|
```
|
|
|
|
There exists another version of the `get` member function that accepts a range
|
|
of entities to serialize. It can be used to _filter_ out those entities that
|
|
shouldn't be serialized for some reasons:
|
|
|
|
```cpp
|
|
const auto view = registry.view<serialize>();
|
|
output_archive output;
|
|
|
|
entt::snapshot{registry}
|
|
.get<a_component>(output, view.begin(), view.end())
|
|
.get<another_component>(output, view.begin(), view.end());
|
|
```
|
|
|
|
Once a snapshot is created, there exist mainly two _ways_ to load it: as a whole
|
|
and in a kind of _continuous mode_.<br/>
|
|
The following sections describe both loaders and archives in details.
|
|
|
|
### Snapshot loader
|
|
|
|
A snapshot loader requires that the destination registry be empty. It loads all
|
|
the data at once while keeping intact the identifiers that the entities
|
|
originally had:
|
|
|
|
```cpp
|
|
input_archive input;
|
|
|
|
entt::snapshot_loader{registry}
|
|
.get<entt::entity>(input)
|
|
.get<a_component>(input)
|
|
.get<another_component>(input)
|
|
.orphans();
|
|
```
|
|
|
|
It isn't necessary to invoke all functions each and every time. What functions
|
|
to use in which case mostly depends on the goal.<br/>
|
|
For obvious reasons, what is important is that the data are restored in exactly
|
|
the same order in which they were serialized.
|
|
|
|
When _getting_ an entity type, a snapshot loader restores all entities with the
|
|
versions that they originally had at the source.<br/>
|
|
In all other cases, entities and components are restored in a given storage. If
|
|
the registry doesn't contain the entity, it's also created accordingly. As for
|
|
the snapshot class, named pools are supported too:
|
|
|
|
```cpp
|
|
entt::snapshot_loader{registry}.get<a_component>(input, "other"_hs);
|
|
```
|
|
|
|
Finally, the `orphans` member function releases the entities that have no
|
|
components after a restore, if any.
|
|
|
|
### Continuous loader
|
|
|
|
A continuous loader is designed to load data from a source registry to a
|
|
(possibly) non-empty destination. The loader accommodates in a registry more
|
|
than one snapshot in a sort of _continuous loading_ that updates the destination
|
|
one step at a time.<br/>
|
|
Identifiers that entities originally had are not transferred to the target.
|
|
Instead, the loader maps remote identifiers to local ones while restoring a
|
|
snapshot. Wrapping the archive is a conveninent way of updating identifiers that
|
|
are part of components automatically (see the example below).<br/>
|
|
Another difference with the snapshot loader is that the continuous loader has an
|
|
internal state that must persist over time. Therefore, there is no reason to
|
|
limit its lifetime to that of a temporary object:
|
|
|
|
```cpp
|
|
entt::continuous_loader loader{registry};
|
|
input_archive input;
|
|
|
|
auto archive = [&loader, &input](auto &value) {
|
|
input(value);
|
|
|
|
if constexpr(std::is_same_v<std::remove_reference_t<decltype(value)>, dirty_component>) {
|
|
value.parent = loader.map(value.parent);
|
|
value.child = loader.map(value.child);
|
|
}
|
|
};
|
|
|
|
loader
|
|
.get<entt::entity>(input)
|
|
.get<a_component>(input)
|
|
.get<another_component>(input)
|
|
.get<dirty_component>(input)
|
|
.orphans();
|
|
```
|
|
|
|
It isn't necessary to invoke all functions each and every time. What functions
|
|
to use in which case mostly depends on the goal.<br/>
|
|
For obvious reasons, what is important is that the data are restored in exactly
|
|
the same order in which they were serialized.
|
|
|
|
When _getting_ an entity type, a loader restores groups of entities and maps
|
|
each entity to a local counterpart when required. For each remote identifier not
|
|
yet registered by the loader, a local identifier is created so as to keep the
|
|
local entity in sync with the remote one.<br/>
|
|
In all other cases, entities and components are restored in a given storage. If
|
|
the registry doesn't contain the entity, it's also tracked accordingly. As for
|
|
the snapshot class, named pools are supported too:
|
|
|
|
```cpp
|
|
loader.get<a_component>(input, "other"_hs);
|
|
```
|
|
|
|
Finally, the `orphans` member function releases the entities that have no
|
|
components after a restore, if any.
|
|
|
|
### Archives
|
|
|
|
Archives must publicly expose a predefined set of member functions. The API is
|
|
straightforward and consists only of a group of function call operators that
|
|
are invoked by the snapshot class and the loaders.
|
|
|
|
In particular:
|
|
|
|
* An output archive (the one used when creating a snapshot) exposes a function
|
|
call operator with the following signature to store entities:
|
|
|
|
```cpp
|
|
void operator()(entt::entity);
|
|
```
|
|
|
|
Where `entt::entity` is the type of the entities used by the registry.<br/>
|
|
Note that all member functions of the snapshot class also make an initial call
|
|
to store aside the _size_ of the set they are going to store. In this case,
|
|
the expected function type for the function call operator is:
|
|
|
|
```cpp
|
|
void operator()(std::underlying_type_t<entt::entity>);
|
|
```
|
|
|
|
In addition, an archive accepts (const) references to the types of component
|
|
to serialize. Therefore, given a type `T`, the archive offers a function call
|
|
operator with the following signature:
|
|
|
|
```cpp
|
|
void operator()(const T &);
|
|
```
|
|
|
|
The output archive can freely decide how to serialize the data. The registry
|
|
isn't affected at all by the decision.
|
|
|
|
* An input archive (the one used when restoring a snapshot) exposes a function
|
|
call operator with the following signature to load entities:
|
|
|
|
```cpp
|
|
void operator()(entt::entity &);
|
|
```
|
|
|
|
Where `entt::entity` is the type of the entities used by the registry. Each
|
|
time the function is invoked, the archive reads the next element from the
|
|
underlying storage and copies it in the given variable.<br/>
|
|
All member functions of a loader class also make an initial call to read the
|
|
_size_ of the set they are going to load. In this case, the expected function
|
|
type for the function call operator is:
|
|
|
|
```cpp
|
|
void operator()(std::underlying_type_t<entt::entity> &);
|
|
```
|
|
|
|
In addition, an archive accepts references to the types of component to
|
|
restore. Therefore, given a type `T`, the archive contains a function call
|
|
operator with the following signature:
|
|
|
|
```cpp
|
|
void operator()(T &);
|
|
```
|
|
|
|
Every time this operator is invoked, the archive reads the next element from
|
|
the underlying storage and copies it in the given variable.
|
|
|
|
### One example to rule them all
|
|
|
|
`EnTT` comes with some examples (actually some tests) that show how to integrate
|
|
a well known library for serialization as an archive. It uses
|
|
[`Cereal C++`](https://uscilab.github.io/cereal/) under the hood, mainly
|
|
because I wanted to learn how it works at the time I was writing the code.
|
|
|
|
The code **isn't** production-ready and it isn't neither the only nor (probably)
|
|
the best way to do it. However, feel free to use it at your own risk.<br/>
|
|
The basic idea is to store everything in a group of queues in memory, then bring
|
|
everything back to the registry with different loaders.
|
|
|
|
# Storage
|
|
|
|
Pools of components are _specialized versions_ of the sparse set class. Each
|
|
pool contains all the instances of a single component type and all the entities
|
|
to which it's assigned.<br/>
|
|
Sparse arrays are _paged_ to avoid wasting memory. Packed arrays of components
|
|
are also paged to have pointer stability upon additions. Packed arrays of
|
|
entities are not instead.<br/>
|
|
All pools rearranges their items in order to keep the internal arrays tightly
|
|
packed and maximize performance, unless full pointer stability is enabled.
|
|
|
|
## Component traits
|
|
|
|
In `EnTT`, almost everything is customizable. Pools are no exception.<br/>
|
|
In this case, the _standardized_ way to access all component properties is the
|
|
`component_traits` class.
|
|
|
|
Various parts of the library access component properties through this class. It
|
|
makes it possible to use any type as a component, as long as its specialization
|
|
of `component_traits` implements all the required functionalities.<br/>
|
|
The non-specialized version of this class contains the following members:
|
|
|
|
* `in_place_delete`: `Type::in_place_delete` if present, true for non-movable
|
|
types and false otherwise.
|
|
|
|
* `page_size`: `Type::page_size` if present, `ENTT_PACKED_PAGE` for non-empty
|
|
types and 0 otherwise.
|
|
|
|
Where `Type` is any type of component. Properties are customized by specializing
|
|
the above class and defining its members, or by adding only those of interest to
|
|
a component definition:
|
|
|
|
```cpp
|
|
struct transform {
|
|
static constexpr auto in_place_delete = true;
|
|
// ... other data members ...
|
|
};
|
|
```
|
|
|
|
The `component_traits` class template takes care of _extracting_ the properties
|
|
from the supplied type.<br/>
|
|
Plus, it's _sfinae-friendly_ and also supports feature-based specializations.
|
|
|
|
## Empty type optimization
|
|
|
|
An empty type `T` is such that `std::is_empty_v<T>` returns true. They also are
|
|
the same types for which _empty base optimization_ (EBO) is possible.<br/>
|
|
`EnTT` handles these types in a special way, optimizing both in terms of
|
|
performance and memory usage. However, this also has consequences that are worth
|
|
mentioning.
|
|
|
|
When an empty type is detected, it's not instantiated by default. Therefore,
|
|
only the entities to which it's assigned are made available. There doesn't exist
|
|
a way to _get_ empty types from a storage or a registry. Views and groups never
|
|
return their instances too (for example, during a call to `each`).<br/>
|
|
On the other hand, iterations are faster because only the entities to which the
|
|
type is assigned are considered. Moreover, less memory is used, mainly because
|
|
there doesn't exist any instance of the component, no matter how many entities
|
|
it is assigned to.
|
|
|
|
More in general, none of the feature offered by the library is affected, but for
|
|
the ones that require to return actual instances.<br/>
|
|
This optimization is disabled by defining the `ENTT_NO_ETO` macro. In this case,
|
|
empty types are treated like all other types. Setting a page size at component
|
|
level via the `component_traits` class template is another way to disable this
|
|
optimization selectively rather than globally.
|
|
|
|
## Void storage
|
|
|
|
A void storage (or `entt::storage<void>` or `entt::basic_storage<Type, void>`),
|
|
is a fully functional storage type used to create pools not associated with a
|
|
particular component type.<br/>
|
|
From a technical point of view, it's in all respects similar to a storage for
|
|
empty types when their optimization is enabled. Pagination is disabled as well
|
|
as pointer stability (as not necessary).<br/>
|
|
However, this should be preferred to using a simple sparse set. In particular,
|
|
a void storage offers all those feature normally offered by other storage types.
|
|
Therefore, it's a perfectly valid pool for use with views and groups or within a
|
|
registry.
|
|
|
|
## Entity storage
|
|
|
|
This storage is such that the component type is the same as the entity type, for
|
|
example `entt::storage<entt::entity>` or `entt::basic_storage<Type, Type>`.<br/>
|
|
For this type of pools, there is a specific specialization within `EnTT`. In
|
|
fact, entities are subject to different rules with respect to components
|
|
(although still customizable by the user if needed). In particular:
|
|
|
|
* Entities are never truly _deleted_. They are moved out of the list of entities
|
|
_in use_ and their versions are updated automatically.
|
|
|
|
* `emplace` as well as `insert` have a slightly different meaning than their
|
|
counterparts for components. In the case of an entity storage, these functions
|
|
_generate_ or _recycle_ identifiers rather than allowing them to be _assigned_
|
|
to existing entities.
|
|
|
|
* The `each` function iterates only the entities _in use_, that is, those not
|
|
marked as _ready for reuse_. To iterate all the entities it's necessary to
|
|
iterate the underlying sparse set instead.
|
|
|
|
Moreover, the entity storage offers a couple of additional utilities such as:
|
|
|
|
* The `in_use` function which is used to know how many entities are still
|
|
_in use_. When combined with `size`, it also makes it possible to know how
|
|
many entities are available for recycling.
|
|
|
|
* The `pack` function which is used to make a given set of entities contiguous.
|
|
This is particularly useful to pass valid lists of entities via iterators
|
|
(with access usually optimized within the library).
|
|
|
|
This kind of storage is designed to be used where any other storage is fine and
|
|
can therefore be combined with views, groups and so on.
|
|
|
|
### One of a kind to the registry
|
|
|
|
Within the registry, an entity storage is treated in all respects like any other
|
|
storage.<br/>
|
|
Therefore, it's possible to add mixins to it as well as retrieve it via the
|
|
`storage` function. It can also be used as storage in a view (for exclude-only
|
|
views for example):
|
|
|
|
```cpp
|
|
auto view = registry.view<entt::entity>(entt::exclude<my_type>);
|
|
```
|
|
|
|
However, it's also subject to a couple of exceptions, partly out of necessity
|
|
and partly for ease of use.
|
|
|
|
In particular, it's not possible to create multiple elements of this type.<br/>
|
|
This means that the _name_ used to retrieve this kind of storage is ignored and
|
|
the registry will only ever return the same element to the caller. For example:
|
|
|
|
```cpp
|
|
auto &other = registry.storage<entt::entity>("other"_hs);
|
|
```
|
|
|
|
In this case, the identifier is discarded as is. The call is in all respects
|
|
equivalent to the following:
|
|
|
|
```cpp
|
|
auto &storage = registry.storage<entt::entity>();
|
|
```
|
|
|
|
Because entity storage doesn't have a name, it can't be retrieved via the opaque
|
|
`storage` function either.<br/>
|
|
It would make no sense to try anyway, given that the type of the registry and
|
|
therefore its entity type are known regardless.
|
|
|
|
Finally, when the user asks the registry for an iterable object to visit all the
|
|
storage elements inside it as follows:
|
|
|
|
```cpp
|
|
for(auto [id, storage]: registry.each()) {
|
|
// ...
|
|
}
|
|
```
|
|
|
|
Entity storage is never returned. This simplifies many tasks (such as copying an
|
|
entity) and fits perfectly with the fact that this type of storage doesn't have
|
|
an identifier inside the registry.
|
|
|
|
## Pointer stability
|
|
|
|
The ability to achieve pointer stability for one, several or all components is a
|
|
direct consequence of the design of `EnTT` and of its default storage.<br/>
|
|
In fact, although it contains what is commonly referred to as a _packed array_,
|
|
the default storage is paged and doesn't suffer from invalidation of references
|
|
when it runs out of space and has to reallocate.<br/>
|
|
However, this isn't enough to ensure pointer stability in case of deletion. For
|
|
this reason, a _stable_ deletion method is also offered. This one is such that
|
|
the position of the elements is preserved by creating tombstones upon deletion
|
|
rather than trying to fill the holes that are created.
|
|
|
|
For performance reasons, `EnTT` favors storage compaction in all cases, although
|
|
often accessing a component occurs mostly randomly or traversing pools in a
|
|
non-linear order on the user side (as in the case of a hierarchy).<br/>
|
|
In other words, pointer stability is not automatic but is enabled on request.
|
|
|
|
### In-place delete
|
|
|
|
The library offers out of the box support for in-place deletion, thus offering
|
|
storage with completely stable pointers. This is achieved by specializing the
|
|
`component_traits` class or by adding the required properties to the component
|
|
definition when needed.<br/>
|
|
Views and groups adapt accordingly when they detect a storage with a different
|
|
deletion policy than the default. In particular:
|
|
|
|
* Groups are incompatible with stable storage and even refuse to compile.
|
|
* Multi type and runtime views are completely transparent to storage policies.
|
|
* Single type views for stable storage types offer the same interface of multi
|
|
type views. For example, only `size_hint` is available.
|
|
|
|
In other words, the more generic version of a view is provided in case of stable
|
|
storage, even for a single type view.<br/>
|
|
In no case a tombstone is returned from the view itself. Likewise, non-existent
|
|
components aren't returned, which could otherwise result in an UB.
|
|
|
|
### Hierarchies and the like
|
|
|
|
`EnTT` doesn't attempt in any way to offer built-in methods with hidden or
|
|
unclear costs to facilitate the creation of hierarchies.<br/>
|
|
There are various solutions to the problem, such as using the following class:
|
|
|
|
```cpp
|
|
struct relationship {
|
|
std::size_t children{};
|
|
entt::entity first{entt::null};
|
|
entt::entity prev{entt::null};
|
|
entt::entity next{entt::null};
|
|
entt::entity parent{entt::null};
|
|
// ... other data members ...
|
|
};
|
|
```
|
|
|
|
However, it should be pointed out that the possibility of having stable pointers
|
|
for one, many or all types solves the problem of hierarchies at the root in many
|
|
cases.<br/>
|
|
In fact, if a certain type of component is visited mainly in random order or
|
|
according to hierarchical relationships, using direct pointers has many
|
|
advantages:
|
|
|
|
```cpp
|
|
struct transform {
|
|
static constexpr auto in_place_delete = true;
|
|
|
|
transform *parent;
|
|
// ... other data members ...
|
|
};
|
|
```
|
|
|
|
Furthermore, it's quite common for a group of elements to be created close in
|
|
time and therefore fallback into adjacent positions, thus favoring locality even
|
|
on random accesses. Locality that isn't sacrificed over time given the stability
|
|
of storage positions, with undoubted performance advantages.
|
|
|
|
# Meet the runtime
|
|
|
|
`EnTT` takes advantage of what the language offers at compile-time. However,
|
|
this can have its downsides (well known to those familiar with type erasure
|
|
techniques).<br/>
|
|
To fill the gap, the library also provides a bunch of utilities and feature that
|
|
are very useful to handle types and pools at runtime.
|
|
|
|
## A base class to rule them all
|
|
|
|
Storage classes are fully self-contained types. They are _extended_ via mixins
|
|
to add more functionalities (generic or type specific). In addition, they offer
|
|
a basic set of functions that already allow users to go very far.<br/>
|
|
The aim is to limit the need for customizations as much as possible, offering
|
|
what is usually necessary for the vast majority of cases.
|
|
|
|
When a storage is used through its base class (for example, when its actual type
|
|
isn't known), there is always the possibility of receiving a `type_info` object
|
|
for the type of elements associated with the entities (if any):
|
|
|
|
```cpp
|
|
if(entt::type_id<velocity>() == base.type()) {
|
|
// ...
|
|
}
|
|
```
|
|
|
|
Furthermore, all features rely on internal functions that forward the calls to
|
|
the mixins. The latter can then make use of any information, which is set via
|
|
`bind`:
|
|
|
|
```cpp
|
|
base.bind(entt::forward_as_any(registry));
|
|
```
|
|
|
|
The `bind` function accepts an `entt::any` object, that is a _typed type-erased_
|
|
value.<br/>
|
|
This is how a registry _passes_ itself to all pools that support signals and
|
|
also why a storage keeps sending events without requiring the registry to be
|
|
passed to it every time.
|
|
|
|
Alongside these more specific things, there are also a couple of functions
|
|
designed to address some common requirements such as copying an entity.<br/>
|
|
In particular, the base class behind a storage offers the possibility to _take_
|
|
the value associated with an entity through an opaque pointer:
|
|
|
|
```cpp
|
|
const void *instance = base.value(entity);
|
|
```
|
|
|
|
Similarly, the non-specialized `push` function accepts an optional opaque
|
|
pointer and behaves differently depending on the case:
|
|
|
|
* When the pointer is null, the function tries to default-construct an instance
|
|
of the object to bind to the entity and returns true on success.
|
|
|
|
* When the pointer is non-null, the function tries to copy-construct an instance
|
|
of the object to bind to the entity and returns true on success.
|
|
|
|
This means that, starting from a reference to the base, it's possible to bind
|
|
components with entities without knowing their actual type and even initialize
|
|
them by copy if needed:
|
|
|
|
```cpp
|
|
// create a copy of an entity component by component
|
|
for(auto &&curr: registry.storage()) {
|
|
if(auto &storage = curr.second; storage.contains(src)) {
|
|
storage.push(dst, storage.value(src));
|
|
}
|
|
}
|
|
```
|
|
|
|
This is particularly useful to clone entities in an opaque way. In addition, the
|
|
decoupling of features allows for filtering or use of different copying policies
|
|
depending on the type.
|
|
|
|
## Beam me up, registry
|
|
|
|
`EnTT` allows the user to assign a _name_ (or rather, a numeric identifier) to a
|
|
type and then create multiple pools of the same type:
|
|
|
|
```cpp
|
|
using namespace entt::literals;
|
|
auto &&storage = registry.storage<velocity>("second pool"_hs);
|
|
```
|
|
|
|
If a name isn't provided, the default storage associated with the given type is
|
|
always returned.<br/>
|
|
Since the storage are also self-contained, the registry doesn't _duplicate_ its
|
|
own API for them. However, there is still no limit to the possibilities of use:
|
|
|
|
```cpp
|
|
auto &&other = registry.storage<velocity>("other"_hs);
|
|
|
|
registry.emplace<velocity>(entity);
|
|
storage.push(entity);
|
|
```
|
|
|
|
Anything that can be done via the registry interface can also be done directly
|
|
on the reference storage.<br/>
|
|
On the other hand, those calls involving all storage are guaranteed to also
|
|
_reach_ manually created ones:
|
|
|
|
```cpp
|
|
// removes the entity from both storage
|
|
registry.destroy(entity);
|
|
```
|
|
|
|
Finally, a storage of this type works with any view (which also accepts multiple
|
|
storages of the same type, if necessary):
|
|
|
|
```cpp
|
|
// direct initialization
|
|
entt::basic_view direct{
|
|
registry.storage<velocity>(),
|
|
registry.storage<velocity>("other"_hs)
|
|
};
|
|
|
|
// concatenation
|
|
auto join = registry.view<velocity>() | entt::basic_view{registry.storage<velocity>("other"_hs)};
|
|
```
|
|
|
|
The possibility of direct use of storage combined with the freedom of being able
|
|
to create and use more than one of the same type opens the door to the use of
|
|
`EnTT` _at runtime_, which was previously quite limited.
|
|
|
|
# Views and Groups
|
|
|
|
Views are a non-intrusive tool for working with entities and components without
|
|
affecting other functionalities or increasing memory consumption.<br/>
|
|
Groups are an intrusive tool to use to improve performance along critical paths
|
|
but which also has a price to pay for that.
|
|
|
|
There are mainly two kinds of views: _compile-time_ (also known as `view`) and
|
|
runtime (also known as `runtime_view`).<br/>
|
|
The former requires a compile-time list of component (or storage) types and can
|
|
make several optimizations because of that. The latter is constructed at runtime
|
|
using numerical type identifiers instead and is a bit slower to iterate.<br/>
|
|
In both cases, creating and destroying views isn't expensive at all since they
|
|
don't have any type of initialization.
|
|
|
|
Groups come in three different flavors: _full-owning groups_, _partial-owning
|
|
groups_ and _non-owning groups_. The main difference between them is in terms of
|
|
performance.<br/>
|
|
Groups can literally _own_ one or more component types. They are allowed to
|
|
rearrange pools so as to speed up iterations. Roughly speaking: the more
|
|
components a group owns, the faster it is to iterate them.
|
|
|
|
## Views
|
|
|
|
Single type views and multi type views behave differently and also have slightly
|
|
different APIs.
|
|
|
|
Single type views are specialized to give a performance boost in all cases.
|
|
There is nothing as fast as a single type view. They just walk through packed
|
|
(actually paged) arrays of elements and return them directly.<br/>
|
|
This kind of views also allow to get the exact number of elements they are going
|
|
to return.<br/>
|
|
Refer to the inline documentation for all the details.
|
|
|
|
Multi type views iterate entities that have at least all the given components.
|
|
During construction, they look at the number of elements available in each pool
|
|
and use the smallest set in order to speed up iterations.<br/>
|
|
This kind of views only allow to get the estimated number of elements they are
|
|
going to return.<br/>
|
|
Refer to the inline documentation for all the details.
|
|
|
|
Storing aside views isn't required as they are extremely cheap to construct. In
|
|
fact, this is even discouraged when creating a view from a const registry. Since
|
|
all storage are lazily initialized, they may not exist when the view is created.
|
|
Thus, while perfectly usable, the view may contain pending references that are
|
|
never reinitialized with the actual storage.
|
|
|
|
Views share the way they are created by means of a registry:
|
|
|
|
```cpp
|
|
// single type view
|
|
auto single = registry.view<position>();
|
|
|
|
// multi type view
|
|
auto multi = registry.view<position, velocity>();
|
|
```
|
|
|
|
Filtering entities by components is also supported:
|
|
|
|
```cpp
|
|
auto view = registry.view<position, velocity>(entt::exclude<renderable>);
|
|
```
|
|
|
|
To iterate a view, either use it in a range-for loop:
|
|
|
|
```cpp
|
|
auto view = registry.view<position, velocity, renderable>();
|
|
|
|
for(auto entity: view) {
|
|
// a component at a time ...
|
|
auto &position = view.get<position>(entity);
|
|
auto &velocity = view.get<velocity>(entity);
|
|
|
|
// ... multiple components ...
|
|
auto [pos, vel] = view.get<position, velocity>(entity);
|
|
|
|
// ... all components at once
|
|
auto [pos, vel, rend] = view.get(entity);
|
|
|
|
// ...
|
|
}
|
|
```
|
|
|
|
Or rely on the `each` member functions to iterate both entities and components
|
|
at once:
|
|
|
|
```cpp
|
|
// through a callback
|
|
registry.view<position, velocity>().each([](auto entity, auto &pos, auto &vel) {
|
|
// ...
|
|
});
|
|
|
|
// using an input iterator
|
|
for(auto &&[entity, pos, vel]: registry.view<position, velocity>().each()) {
|
|
// ...
|
|
}
|
|
```
|
|
|
|
Note that entities can also be excluded from the parameter list when received
|
|
through a callback and this can improve even further the performance during
|
|
iterations.<br/>
|
|
Since they aren't explicitly instantiated, empty components aren't returned in
|
|
any case.
|
|
|
|
As a side note, in the case of single type views, `get` accepts but doesn't
|
|
strictly require a template parameter, since the type is implicitly defined.
|
|
However, when the type isn't specified, the instance is returned using a tuple
|
|
for consistency with multi type views:
|
|
|
|
```cpp
|
|
auto view = registry.view<const renderable>();
|
|
|
|
for(auto entity: view) {
|
|
auto [renderable] = view.get(entity);
|
|
// ...
|
|
}
|
|
```
|
|
|
|
**Note**: prefer the `get` member function of a view instead of that of a
|
|
registry during iterations to get the types iterated by the view itself.
|
|
|
|
### Create once, reuse many times
|
|
|
|
Views support lazy initialization as well as _storage swapping_.<br/>
|
|
An empty (or partially initialized) view is such that it returns false when
|
|
converted to bool (to let the user know that it isn't fully initialized) but it
|
|
also works as-is like any other view.
|
|
|
|
In order to initialize a view one piece at a time, it allows users to inject
|
|
storage classes when available:
|
|
|
|
```cpp
|
|
entt::storage_for_t<velocity> storage{};
|
|
entt::view<entt::get_t<position, velocity>> view{};
|
|
|
|
view.storage(storage);
|
|
```
|
|
|
|
If there are multiple storages of the same type, it's possible to disambiguate
|
|
using the _index_ of the element to be replaced:
|
|
|
|
```cpp
|
|
view.storage<1>(storage);
|
|
```
|
|
|
|
The ability to literally _replace_ a storage in a view also opens up its reuse
|
|
with different sets of entities.<br/>
|
|
For example, to _filter_ a view based on two groups of entities with different
|
|
characteristics, there will be no need to reinitialize anything:
|
|
|
|
```cpp
|
|
entt::view<entt::get<my_type, void>> view{registry.storage<my_type>>()};
|
|
|
|
entt::storage_for_t<void> the_good{};
|
|
entt::storage_for_t<void> the_bad{};
|
|
|
|
// initialize the sets above as needed
|
|
|
|
view.storage(the_good);
|
|
|
|
for(auto [entt, elem]: view) {
|
|
// the good entities with their components here
|
|
}
|
|
|
|
view.storage(the_bad);
|
|
|
|
for(auto [entt, elem]: view) {
|
|
// the bad entities with their components here
|
|
}
|
|
```
|
|
|
|
Finally, it should be noted that the lack of a storage is treated to all intents
|
|
and purposes as if it were an _empty_ element.<br/>
|
|
Thus, a _get_ storage (as in `entt::get_t`) makes the view empty automatically
|
|
while an _exclude_ storage (as in `entt::exclude_t`) is ignored as if that part
|
|
of the filter didn't exist.
|
|
|
|
### Exclude-only
|
|
|
|
_Exclude-only_ views aren't really a thing in `EnTT`.<br/>
|
|
However, the same result can be achieved by combining the right storage into a
|
|
simple view.
|
|
|
|
If one gets to the root of the problem, the purpose of an exclude-only view is
|
|
to return entities that don't meet certain requirements.<br/>
|
|
Since entity storage, unlike exclude-only views, **is** a thing in `EnTT`, users
|
|
can leverage it for these kinds of queries. It's also guaranteed to be unique
|
|
within a registry and is always accessible when creating a view:
|
|
|
|
```cpp
|
|
auto view = registry.view<entt::entity>(entt::exclude<my_type>);
|
|
```
|
|
|
|
The returned view is such that it will return only the entities that don't have
|
|
the `my_type` component, regardless of what other components they have.
|
|
|
|
### View pack
|
|
|
|
Views are combined with each other to create new and more specific queries.<br/>
|
|
The type returned when combining multiple views together is itself a view, more
|
|
in general a multi component one.
|
|
|
|
Combining different views tries to mimic C++20 ranges:
|
|
|
|
```cpp
|
|
auto view = registry.view<position>();
|
|
auto other = registry.view<velocity>();
|
|
|
|
auto pack = view | other;
|
|
```
|
|
|
|
The constness of the types is preserved and their order depends on the order in
|
|
which the views are combined. For example, the pack above returns an instance of
|
|
`position` first and then one of `velocity`.<br/>
|
|
Since combining views generates views, a chain can be of arbitrary length and
|
|
the above type order rules apply sequentially.
|
|
|
|
### Iteration order
|
|
|
|
By default, a view is iterated along the pool that contains the smallest number
|
|
of elements.<br/>
|
|
For example, if the registry contains fewer `velocity`s than it contains
|
|
`position`s, then the order of the elements returned by the following view
|
|
depends on how the `velocity` components are arranged in their pool:
|
|
|
|
```cpp
|
|
for(auto entity: registry.view<positon, velocity>()) {
|
|
// ...
|
|
}
|
|
```
|
|
|
|
Moreover, the order of types when constructing a view doesn't matter. Neither
|
|
does the order of views in a view pack.<br/>
|
|
However, it's possible to _enforce_ iteration of a view by given component order
|
|
by means of the `use` function:
|
|
|
|
```cpp
|
|
for(auto entity : registry.view<position, velocity>().use<position>()) {
|
|
// ...
|
|
}
|
|
```
|
|
|
|
On the other hand, if all a user wants is to iterate the elements in reverse
|
|
order, this is possible for a single type view using its reverse iterators:
|
|
|
|
```cpp
|
|
auto view = registry.view<position>();
|
|
|
|
for(auto it = view.rbegin(), last = view.rend(); it != last; ++iter) {
|
|
// ...
|
|
}
|
|
```
|
|
|
|
Unfortunately, multi type views don't offer reverse iterators. Therefore, in
|
|
this case it's a must to implement this functionality manually or to use single
|
|
type views to lead the iteration.
|
|
|
|
### Runtime views
|
|
|
|
Multi type views iterate entities that have at least all the given components.
|
|
During construction, they look at the number of elements available in each pool
|
|
and use the smallest set in order to speed up iterations.<br/>
|
|
They offer more or less the same functionalities of a multi type view. However,
|
|
they don't expose a `get` member function and users should refer to the registry
|
|
that generated the view to access components.<br/>
|
|
Refer to the inline documentation for all the details.
|
|
|
|
Runtime views are pretty cheap to construct and should not be stored aside in
|
|
any case. They should be used immediately after creation and then they should be
|
|
thrown away.<br/>
|
|
To iterate a runtime view, either use it in a range-for loop:
|
|
|
|
```cpp
|
|
entt::runtime_view view{};
|
|
view.iterate(registry.storage<position>()).iterate(registry.storage<velocity>());
|
|
|
|
for(auto entity: view) {
|
|
// ...
|
|
}
|
|
```
|
|
|
|
Or rely on the `each` member function to iterate entities:
|
|
|
|
```cpp
|
|
entt::runtime_view{}
|
|
.iterate(registry.storage<position>())
|
|
.iterate(registry.storage<velocity>())
|
|
.each([](auto entity) {
|
|
// ...
|
|
});
|
|
```
|
|
|
|
Performance are exactly the same in both cases.<br/>
|
|
Filtering entities by components is also supported for this kind of views:
|
|
|
|
```cpp
|
|
entt::runtime_view view{};
|
|
view.iterate(registry.storage<position>()).exclude(registry.storage<velocity>());
|
|
```
|
|
|
|
Runtime views are meant for when users don't know at compile-time what types to
|
|
_use_ to iterate entities. The `storage` member function of a registry could be
|
|
useful in this regard.
|
|
|
|
## Groups
|
|
|
|
Groups are meant to iterate multiple components at once and to offer a faster
|
|
alternative to multi type views.<br/>
|
|
Groups overcome the performance of the other tools available but require to get
|
|
the ownership of components. This sets some constraints on their pools. On the
|
|
other hand, groups aren't an automatism that increases memory consumption,
|
|
affects functionalities and tries to optimize iterations for all the possible
|
|
combinations of components. Users can decide when to pay for groups and to what
|
|
extent.<br/>
|
|
The most interesting aspect of groups is that they fit _usage patterns_. Other
|
|
solutions around usually try to optimize everything, because it is known that
|
|
somewhere within the _everything_ there are also our usage patterns. However
|
|
this has a cost that isn't negligible, both in terms of performance and memory
|
|
usage. Ironically, users pay the price also for things they don't want and this
|
|
isn't something I like much. Even worse, one cannot easily disable such a
|
|
behavior. Groups work differently instead and are designed to optimize only the
|
|
real use cases when users find they need to.<br/>
|
|
Another nice-to-have feature of groups is that they have no impact on memory
|
|
consumption, put aside full non-owning groups that are pretty rare and should be
|
|
avoided as long as possible.
|
|
|
|
All groups affect to an extent the creation and destruction of their components.
|
|
This is due to the fact that they must _observe_ changes in the pools of
|
|
interest and arrange data _correctly_ when needed for the types they own.<br/>
|
|
In all cases, a group allows to get the exact number of elements it's going to
|
|
return.<br/>
|
|
Refer to the inline documentation for all the details.
|
|
|
|
Storing aside groups isn't required as they are extremely cheap to create, even
|
|
though valid groups can be copied without problems and reused freely.<br/>
|
|
A group performs an initialization step the very first time it's requested and
|
|
this could be quite costly. To avoid it, consider creating the group when no
|
|
components have been assigned yet. If the registry is empty, preparation is
|
|
extremely fast.
|
|
|
|
To iterate a group, either use it in a range-for loop:
|
|
|
|
```cpp
|
|
auto group = registry.group<position>(entt::get<velocity, renderable>);
|
|
|
|
for(auto entity: group) {
|
|
// a component at a time ...
|
|
auto &position = group.get<position>(entity);
|
|
auto &velocity = group.get<velocity>(entity);
|
|
|
|
// ... multiple components ...
|
|
auto [pos, vel] = group.get<position, velocity>(entity);
|
|
|
|
// ... all components at once
|
|
auto [pos, vel, rend] = group.get(entity);
|
|
|
|
// ...
|
|
}
|
|
```
|
|
|
|
Or rely on the `each` member functions to iterate both entities and components
|
|
at once:
|
|
|
|
```cpp
|
|
// through a callback
|
|
registry.group<position>(entt::get<velocity>).each([](auto entity, auto &pos, auto &vel) {
|
|
// ...
|
|
});
|
|
|
|
// using an input iterator
|
|
for(auto &&[entity, pos, vel]: registry.group<position>(entt::get<velocity>).each()) {
|
|
// ...
|
|
}
|
|
```
|
|
|
|
Note that entities can also be excluded from the parameter list when received
|
|
through a callback and this can improve even further the performance during
|
|
iterations.<br/>
|
|
Since they aren't explicitly instantiated, empty components aren't returned in
|
|
any case.
|
|
|
|
**Note**: prefer the `get` member function of a group instead of that of a
|
|
registry during iterations to get the types iterated by the group itself.
|
|
|
|
### Full-owning groups
|
|
|
|
A full-owning group is the fastest tool a user can expect to use to iterate
|
|
multiple components at once. It iterates all the components directly, no
|
|
indirection required.<br/>
|
|
This type of groups performs more or less as if users are accessing sequentially
|
|
a bunch of packed arrays of components all sorted identically, with no jumps nor
|
|
branches.
|
|
|
|
A full-owning group is created as:
|
|
|
|
```cpp
|
|
auto group = registry.group<position, velocity>();
|
|
```
|
|
|
|
Filtering entities by components is also supported:
|
|
|
|
```cpp
|
|
auto group = registry.group<position, velocity>({}, entt::exclude<renderable>);
|
|
```
|
|
|
|
Once created, the group gets the ownership of all the components specified in
|
|
the template parameter list and arranges their pools as needed.
|
|
|
|
Sorting owned components is no longer allowed once the group has been created.
|
|
However, full-owning groups are sorted using their `sort` member functions.
|
|
Sorting a full-owning group affects all its instances.
|
|
|
|
### Partial-owning groups
|
|
|
|
A partial-owning group works similarly to a full-owning group for the components
|
|
it owns, but relies on indirection to get components owned by other groups.<br/>
|
|
This isn't as fast as a full-owning group, but it's already much faster than a
|
|
view when there are only one or two free components to retrieve (the most common
|
|
cases likely). In the worst case, it's not slower than views anyway.
|
|
|
|
A partial-owning group is created as:
|
|
|
|
```cpp
|
|
auto group = registry.group<position>(entt::get<velocity>);
|
|
```
|
|
|
|
Filtering entities by components is also supported:
|
|
|
|
```cpp
|
|
auto group = registry.group<position>(entt::get<velocity>, entt::exclude<renderable>);
|
|
```
|
|
|
|
Once created, the group gets the ownership of all the components specified in
|
|
the template parameter list and arranges their pools as needed. The ownership of
|
|
the types provided via `entt::get` doesn't pass to the group instead.
|
|
|
|
Sorting owned components is no longer allowed once the group has been created.
|
|
However, partial-owning groups are sorted using their `sort` member functions.
|
|
Sorting a partial-owning group affects all its instances.
|
|
|
|
### Non-owning groups
|
|
|
|
Non-owning groups are usually fast enough, for sure faster than views and well
|
|
suited for most of the cases. However, they require custom data structures to
|
|
work properly and they increase memory consumption.<br/>
|
|
As a rule of thumb, users should avoid using non-owning groups, if possible.
|
|
|
|
A non-owning group is created as:
|
|
|
|
```cpp
|
|
auto group = registry.group<>(entt::get<position, velocity>);
|
|
```
|
|
|
|
Filtering entities by components is also supported:
|
|
|
|
```cpp
|
|
auto group = registry.group<>(entt::get<position, velocity>, entt::exclude<renderable>);
|
|
```
|
|
|
|
The group doesn't receive the ownership of any type of component in this
|
|
case. This type of groups is therefore the least performing in general, but also
|
|
the only one that can be used in any situation to slightly improve performance.
|
|
|
|
Non-owning groups are sorted using their `sort` member functions. Sorting a
|
|
non-owning group affects all its instances.
|
|
|
|
## Types: const, non-const and all in between
|
|
|
|
The `registry` class offers two overloads when it comes to constructing views
|
|
and groups: a const version and a non-const one. The former accepts only const
|
|
types as template parameters, the latter accepts both const and non-const types
|
|
instead.<br/>
|
|
It means that views and groups generated by a const registry also propagate the
|
|
constness to the types involved. As an example:
|
|
|
|
```cpp
|
|
entt::view<const position, const velocity> view = std::as_const(registry).view<const position, const velocity>();
|
|
```
|
|
|
|
Consider the following definition for a non-const view instead:
|
|
|
|
```cpp
|
|
entt::view<position, const velocity> view = registry.view<position, const velocity>();
|
|
```
|
|
|
|
In the example above, `view` is used to access either read-only or writable
|
|
`position` components while `velocity` components are read-only in all
|
|
cases.<br/>
|
|
Similarly, these statements are all valid:
|
|
|
|
```cpp
|
|
position &pos = view.get<position>(entity);
|
|
const position &cpos = view.get<const position>(entity);
|
|
const velocity &cpos = view.get<const velocity>(entity);
|
|
std::tuple<position &, const velocity &> tup = view.get<position, const velocity>(entity);
|
|
std::tuple<const position &, const velocity &> ctup = view.get<const position, const velocity>(entity);
|
|
```
|
|
|
|
It's not possible to get non-const references to `velocity` components from the
|
|
same view instead. Therefore, these result in compilation errors:
|
|
|
|
```cpp
|
|
velocity &cpos = view.get<velocity>(entity);
|
|
std::tuple<position &, velocity &> tup = view.get<position, velocity>(entity);
|
|
std::tuple<const position &, velocity &> ctup = view.get<const position, velocity>(entity);
|
|
```
|
|
|
|
The `each` member functions also propagates constness to its _return values_:
|
|
|
|
```cpp
|
|
view.each([](auto entity, position &pos, const velocity &vel) {
|
|
// ...
|
|
});
|
|
```
|
|
|
|
A caller can still refer to the `position` components through a const reference
|
|
because of the rules of the language that fortunately already allow it.
|
|
|
|
The same concepts apply to groups as well.
|
|
|
|
## Give me everything
|
|
|
|
Views and groups are narrow windows on the entire list of entities. They work by
|
|
filtering entities according to their components.<br/>
|
|
In some cases there may be the need to iterate all the entities still in use
|
|
regardless of their components. The registry offers a specific member function
|
|
to do that:
|
|
|
|
```cpp
|
|
registry.each([](auto entity) {
|
|
// ...
|
|
});
|
|
```
|
|
|
|
As a rule of thumb, consider using a view or a group if the goal is to iterate
|
|
entities that have a determinate set of components. These tools are usually much
|
|
faster than combining the `each` function with a bunch of custom tests.<br/>
|
|
In all the other cases, this is the way to go. For example, it's possible to
|
|
combine `each` with the `orphan` member function to clean up orphan entities
|
|
(that is, entities that are still in use and have no assigned components):
|
|
|
|
```cpp
|
|
registry.each([®istry](auto entity) {
|
|
if(registry.orphan(entity)) {
|
|
registry.release(entity);
|
|
}
|
|
});
|
|
```
|
|
|
|
In general, iterating all entities can result in poor performance. It should not
|
|
be done frequently to avoid the risk of a performance hit.<br/>
|
|
However, it's convenient when initializing an editor or to reclaim pending
|
|
identifiers.
|
|
|
|
## What is allowed and what is not
|
|
|
|
Most of the _ECS_ available out there don't allow to create and destroy entities
|
|
and components during iterations, nor to have pointer stability.<br/>
|
|
`EnTT` partially solves the problem with a few limitations:
|
|
|
|
* Creating entities and components is allowed during iterations in most cases
|
|
and it never invalidates already existing references.
|
|
|
|
* Deleting the current entity or removing its components is allowed during
|
|
iterations but it could invalidate references. For all the other entities,
|
|
destroying them or removing their iterated components isn't allowed and can
|
|
result in undefined behavior.
|
|
|
|
* When pointer stability is enabled for the type leading the iteration, adding
|
|
instances of the same type may or may not cause the entity involved to be
|
|
returned. Destroying entities and components is always allowed instead, even
|
|
if not currently iterated, without the risk of invalidating any references.
|
|
|
|
* In case of reverse iterations, adding or removing elements is not allowed
|
|
under any circumstances. It could quickly lead to undefined behaviors.
|
|
|
|
In other terms, iterators are rarely invalidated. Also, component references
|
|
aren't invalidated when a new element is added while they could be invalidated
|
|
upon destruction due to the _swap-and-pop_ policy, unless the type leading the
|
|
iteration undergoes in-place deletion.<br/>
|
|
As an example, consider the following snippet:
|
|
|
|
```cpp
|
|
registry.view<position>().each([&](const auto entity, auto &pos) {
|
|
registry.emplace<position>(registry.create(), 0., 0.);
|
|
// references remain stable after adding new instances
|
|
pos.x = 0.;
|
|
});
|
|
```
|
|
|
|
The `each` member function won't break (because iterators remain valid) nor will
|
|
any reference be invalidated. Instead, more attention should be paid to the
|
|
destruction of entities or the removal of components.<br/>
|
|
Use a common range-for loop and get components directly from the view or move
|
|
the deletion of entities and components at the end of the function to avoid
|
|
dangling pointers.
|
|
|
|
For all types that don't offer stable pointers, iterators are also invalidated
|
|
and the behavior is undefined if an entity is modified or destroyed and it's not
|
|
the one currently returned by the iterator nor a newly created one.<br/>
|
|
To work around it, possible approaches are:
|
|
|
|
* Store aside the entities and the components to be removed and perform the
|
|
operations at the end of the iteration.
|
|
|
|
* Mark entities and components with a proper tag component that indicates they
|
|
must be purged, then perform a second iteration to clean them up one by one.
|
|
|
|
A notable side effect of this feature is that the number of required allocations
|
|
is further reduced in most cases.
|
|
|
|
### More performance, more constraints
|
|
|
|
Groups are a faster alternative to views. However, the higher the performance,
|
|
the greater the constraints on what is allowed and what is not.<br/>
|
|
In particular, groups add in some rare cases a limitation on the creation of
|
|
components during iterations. It happens in quite particular cases. Given the
|
|
nature and the scope of the groups, it isn't something in which it will happen
|
|
to come across probably, but it's good to know it anyway.
|
|
|
|
First of all, it must be said that creating components while iterating a group
|
|
isn't a problem at all and is done freely as it happens with the views. The same
|
|
applies to the destruction of components and entities, for which the rules
|
|
mentioned above apply.
|
|
|
|
The additional limitation arises instead when a given component that is owned by
|
|
a group is iterated outside of it. In this case, adding components that are part
|
|
of the group itself may invalidate the iterators. There are no further
|
|
limitations to the destruction of components and entities.<br/>
|
|
Fortunately, this isn't always true. In fact, it almost never is and only
|
|
happens under certain conditions. In particular:
|
|
|
|
* Iterating a type of component that is part of a group with a single type view
|
|
and adding to an entity all the components required to get it into the group
|
|
may invalidate the iterators.
|
|
|
|
* Iterating a type of component that is part of a group with a multi type view
|
|
and adding to an entity all the components required to get it into the group
|
|
can invalidate the iterators, unless users specify another type of component
|
|
to use to induce the order of iteration of the view (in this case, the former
|
|
is treated as a free type and isn't affected by the limitation).
|
|
|
|
In other words, the limitation doesn't exist as long as a type is treated as a
|
|
free type (as an example with multi type views and partial- or non-owning
|
|
groups) or iterated with its own group, but it can occur if the type is used as
|
|
a main type to rule on an iteration.<br/>
|
|
This happens because groups own the pools of their components and organize the
|
|
data internally to maximize performance. Because of that, full consistency for
|
|
owned components is guaranteed only when they are iterated as part of their
|
|
groups or as free types with multi type views and groups in general.
|
|
|
|
# Multithreading
|
|
|
|
In general, the entire registry isn't thread safe as it is. Thread safety isn't
|
|
something that users should want out of the box for several reasons. Just to
|
|
mention one of them: performance.<br/>
|
|
Views, groups and consequently the approach adopted by `EnTT` are the great
|
|
exception to the rule. It's true that views, groups and iterators in general
|
|
aren't thread safe by themselves. Because of this users shouldn't try to iterate
|
|
a set of components and modify the same set concurrently. However:
|
|
|
|
* As long as a thread iterates the entities that have the component `X` or
|
|
assign and removes that component from a set of entities, another thread can
|
|
safely do the same with components `Y` and `Z` and everything work like just
|
|
fine. As a trivial example, users can freely execute the rendering system and
|
|
iterate the renderable entities while updating a physic component concurrently
|
|
on a separate thread.
|
|
|
|
* Similarly, a single set of components can be iterated by multiple threads as
|
|
long as the components are neither assigned nor removed in the meantime. In
|
|
other words, a hypothetical movement system can start multiple threads, each
|
|
of which will access the components that carry information about velocity and
|
|
position for its entities.
|
|
|
|
This kind of entity-component systems can be used in single threaded
|
|
applications as well as along with async stuff or multiple threads. Moreover,
|
|
typical thread based models for _ECS_ don't require a fully thread safe registry
|
|
to work. Actually, users can reach the goal with the registry as it is while
|
|
working with most of the common models.
|
|
|
|
Because of the few reasons mentioned above and many others not mentioned, users
|
|
are completely responsible for synchronization whether required. On the other
|
|
hand, they could get away with it without having to resort to particular
|
|
expedients.
|
|
|
|
Finally, `EnTT` is configured via a few compile-time definitions to make some of
|
|
its parts implicitly thread-safe, roughly speaking only the ones that really
|
|
make sense and can't be turned around.<br/>
|
|
In particular, when multiple instances of objects referencing the type index
|
|
generator (such as the `registry` class) are used in different threads, then it
|
|
might be useful to define `ENTT_USE_ATOMIC`.<br/>
|
|
See the relevant documentation for more information.
|
|
|
|
## Iterators
|
|
|
|
A special mention is needed for the iterators returned by views and groups. Most
|
|
of the time they meet the requirements of random access iterators, in all cases
|
|
they meet at least the requirements of forward iterators.<br/>
|
|
In other terms, they are suitable for use with the parallel algorithms of the
|
|
standard library. If it's not clear, this is a great thing.
|
|
|
|
As an example, this kind of iterators are used in combination with
|
|
`std::for_each` and `std::execution::par` to parallelize the visit and therefore
|
|
the update of the components returned by a view or a group, as long as the
|
|
constraints previously discussed are respected:
|
|
|
|
```cpp
|
|
auto view = registry.view<position, const velocity>();
|
|
|
|
std::for_each(std::execution::par_unseq, view.begin(), view.end(), [&view](auto entity) {
|
|
// ...
|
|
});
|
|
```
|
|
|
|
This can increase the throughput considerably, even without resorting to who
|
|
knows what artifacts that are difficult to maintain over time.
|
|
|
|
Unfortunately, because of the limitations of the current revision of the
|
|
standard, the parallel `std::for_each` accepts only forward iterators. This
|
|
means that the default iterators provided by the library cannot return proxy
|
|
objects as references and **must** return actual reference types instead.<br/>
|
|
This may change in the future and the iterators will almost certainly return
|
|
both the entities and a list of references to their components by default sooner
|
|
or later. Multi-pass guarantee won't break in any case and the performance
|
|
should even benefit from it further.
|
|
|
|
## Const registry
|
|
|
|
A const registry is also fully thread safe. This means that it's not able to
|
|
lazily initialize a missing storage when a view is generated.<br/>
|
|
The reason for this is easy to explain. To avoid requiring types to be
|
|
_announced_ in advance, a registry lazily creates the storage objects for the
|
|
different components. However, this isn't possible for a thread safe const
|
|
registry.
|
|
|
|
Returned views are always valid and behave as expected in the context of the
|
|
caller. However, they may contain dangling references to non-existing storage
|
|
when created from a const registry.<br/>
|
|
As a result, such a view may misbehave over time if it's kept aside for a second
|
|
use.<br/>
|
|
Therefore, if the general advice is to create views when necessary and discard
|
|
them immediately afterwards, this becomes almost a rule when it comes to views
|
|
generated from a const registry.
|
|
|
|
Fortunately, there is also a way to instantiate storage classes early when in
|
|
doubt or when there are special requirements.<br/>
|
|
Calling the `storage` method is equivalent to _announcing_ a particular storage,
|
|
so as to avoid running into problems. For those interested, there are also
|
|
alternative approaches, such as a single threaded tick for the registry warm-up,
|
|
but these are not always applicable.<br/>
|
|
In this case, views never risk becoming _invalid_.
|
|
|
|
# Beyond this document
|
|
|
|
There are many other features and functions not listed in this document.<br/>
|
|
`EnTT` and in particular its ECS part is in continuous development and some
|
|
things could be forgotten, others could have been omitted on purpose to reduce
|
|
the size of this file. Unfortunately, some parts may even be outdated and still
|
|
to be updated.
|
|
|
|
For further information, it's recommended to refer to the documentation included
|
|
in the code itself or join the official channels to ask a question.
|