Squashed 'external/entt/entt/' changes from fef921132..344e03ac6
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
This commit is contained in:
parent
5c7231b7a3
commit
90ce4bda4e
18
.github/workflows/analyzer.yml
vendored
18
.github/workflows/analyzer.yml
vendored
@ -12,14 +12,14 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
|
||||
env:
|
||||
IWYU: 0.18
|
||||
LLVM: 14
|
||||
IWYU: 0.19
|
||||
LLVM: 15
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install llvm/clang
|
||||
# see: https://apt.llvm.org/
|
||||
run: |
|
||||
@ -39,17 +39,23 @@ jobs:
|
||||
git clone https://github.com/include-what-you-use/include-what-you-use.git --branch $IWYU --depth 1
|
||||
mkdir include-what-you-use/build
|
||||
cd include-what-you-use/build
|
||||
cmake -DCMAKE_C_COMPILER=clang-$LLVM -DCMAKE_CXX_COMPILER=clang++-$LLVM -DCMAKE_INSTALL_PREFIX=./ ..
|
||||
cmake -DCMAKE_C_COMPILER=clang-$LLVM \
|
||||
-DCMAKE_CXX_COMPILER=clang++-$LLVM \
|
||||
-DCMAKE_INSTALL_PREFIX=./ \
|
||||
..
|
||||
make -j4
|
||||
bin/include-what-you-use --version
|
||||
- name: Compile tests
|
||||
working-directory: build
|
||||
run: |
|
||||
export PATH=$PATH:${GITHUB_WORKSPACE}/build/include-what-you-use/build/bin
|
||||
cmake -DENTT_BUILD_TESTING=ON \
|
||||
cmake -DCMAKE_C_COMPILER=clang-$LLVM \
|
||||
-DCMAKE_CXX_COMPILER=clang++-$LLVM \
|
||||
-DENTT_BUILD_TESTING=ON \
|
||||
-DENTT_BUILD_BENCHMARK=ON \
|
||||
-DENTT_BUILD_EXAMPLE=ON \
|
||||
-DENTT_BUILD_LIB=ON \
|
||||
-DENTT_BUILD_SNAPSHOT=ON \
|
||||
-DCMAKE_CXX_INCLUDE_WHAT_YOU_USE="include-what-you-use;-Xiwyu;--mapping_file=${GITHUB_WORKSPACE}/entt.imp;-Xiwyu;--no_fwd_decls;-Xiwyu;--verbose=1" ..
|
||||
-DCMAKE_CXX_INCLUDE_WHAT_YOU_USE="include-what-you-use;-Xiwyu;--mapping_file=${GITHUB_WORKSPACE}/entt.imp;-Xiwyu;--no_fwd_decls;-Xiwyu;--verbose=1" \
|
||||
..
|
||||
make -j4
|
||||
|
129
.github/workflows/build.yml
vendored
129
.github/workflows/build.yml
vendored
@ -9,38 +9,61 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, ubuntu-20.04]
|
||||
compiler:
|
||||
- pkg: g++-7
|
||||
exe: g++-7
|
||||
- pkg: g++-8
|
||||
exe: g++-8
|
||||
- pkg: g++-9
|
||||
exe: g++-9
|
||||
- pkg: g++-10
|
||||
exe: g++-10
|
||||
- pkg: clang-8
|
||||
exe: clang++-8
|
||||
- pkg: clang-9
|
||||
exe: clang++-9
|
||||
- pkg: clang-10
|
||||
exe: clang++-10
|
||||
- pkg: clang-11
|
||||
exe: clang++-11
|
||||
- pkg: clang-12
|
||||
exe: clang++-12
|
||||
- { pkg: g++, exe: 'g++', version: 7 }
|
||||
- { pkg: g++, exe: 'g++', version: 8 }
|
||||
- { pkg: g++, exe: 'g++', version: 9 }
|
||||
- { pkg: g++, exe: 'g++', version: 10 }
|
||||
- { pkg: g++, exe: 'g++', version: 11 }
|
||||
- { pkg: g++, exe: 'g++', version: 12 }
|
||||
- { pkg: clang, exe: 'clang++', version: 8 }
|
||||
- { pkg: clang, exe: 'clang++', version: 9 }
|
||||
- { pkg: clang, exe: 'clang++', version: 10 }
|
||||
- { pkg: clang, exe: 'clang++', version: 11 }
|
||||
- { pkg: clang, exe: 'clang++', version: 12 }
|
||||
- { pkg: clang, exe: 'clang++', version: 13 }
|
||||
- { pkg: clang, exe: 'clang++', version: 14 }
|
||||
exclude:
|
||||
- os: ubuntu-latest
|
||||
compiler: { pkg: g++, exe: 'g++', version: 7 }
|
||||
- os: ubuntu-latest
|
||||
compiler: { pkg: g++, exe: 'g++', version: 8 }
|
||||
- os: ubuntu-latest
|
||||
compiler: { pkg: g++, exe: 'g++', version: 9 }
|
||||
- os: ubuntu-latest
|
||||
compiler: { pkg: clang, exe: 'clang++', version: 8 }
|
||||
- os: ubuntu-latest
|
||||
compiler: { pkg: clang, exe: 'clang++', version: 9 }
|
||||
- os: ubuntu-latest
|
||||
compiler: { pkg: clang, exe: 'clang++', version: 10 }
|
||||
- os: ubuntu-latest
|
||||
compiler: { pkg: clang, exe: 'clang++', version: 11 }
|
||||
- os: ubuntu-20.04
|
||||
compiler: { pkg: g++, exe: 'g++', version: 10 }
|
||||
- os: ubuntu-20.04
|
||||
compiler: { pkg: g++, exe: 'g++', version: 11 }
|
||||
- os: ubuntu-20.04
|
||||
compiler: { pkg: g++, exe: 'g++', version: 12 }
|
||||
- os: ubuntu-20.04
|
||||
compiler: { pkg: clang, exe: 'clang++', version: 12 }
|
||||
- os: ubuntu-20.04
|
||||
compiler: { pkg: clang, exe: 'clang++', version: 13 }
|
||||
- os: ubuntu-20.04
|
||||
compiler: { pkg: clang, exe: 'clang++', version: 14 }
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install compiler
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y ${{ matrix.compiler.pkg }}
|
||||
sudo apt install -y ${{ matrix.compiler.pkg }}-${{ matrix.compiler.version }}
|
||||
- name: Compile tests
|
||||
working-directory: build
|
||||
env:
|
||||
CXX: ${{ matrix.compiler.exe }}
|
||||
CXX: ${{ matrix.compiler.exe }}-${{ matrix.compiler.version }}
|
||||
run: |
|
||||
cmake -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON ..
|
||||
make -j4
|
||||
@ -50,32 +73,6 @@ jobs:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
run: ctest --timeout 30 -C Debug -j4
|
||||
|
||||
linux-extra:
|
||||
timeout-minutes: 15
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
compiler: [g++, clang++]
|
||||
id_type: ["std::uint32_t", "std::uint64_t"]
|
||||
cxx_std: [cxx_std_17, cxx_std_20]
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Compile tests
|
||||
working-directory: build
|
||||
env:
|
||||
CXX: ${{ matrix.compiler }}
|
||||
run: |
|
||||
cmake -DENTT_BUILD_TESTING=ON -DENTT_CXX_STD=${{ matrix.cxx_std }} -DENTT_ID_TYPE=${{ matrix.id_type }} ..
|
||||
make -j4
|
||||
- name: Run tests
|
||||
working-directory: build
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
run: ctest --timeout 30 -C Debug -j4
|
||||
|
||||
windows:
|
||||
timeout-minutes: 15
|
||||
|
||||
@ -93,7 +90,7 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Compile tests
|
||||
working-directory: build
|
||||
run: |
|
||||
@ -105,35 +102,12 @@ jobs:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
run: ctest --timeout 30 -C Debug -j4
|
||||
|
||||
windows-extra:
|
||||
timeout-minutes: 15
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
id_type: ["std::uint32_t", "std::uint64_t"]
|
||||
cxx_std: [cxx_std_17, cxx_std_20]
|
||||
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Compile tests
|
||||
working-directory: build
|
||||
run: |
|
||||
cmake -DENTT_BUILD_TESTING=ON -DENTT_CXX_STD=${{ matrix.cxx_std }} -DENTT_ID_TYPE=${{ matrix.id_type }} ..
|
||||
cmake --build . -j 4
|
||||
- name: Run tests
|
||||
working-directory: build
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
run: ctest --timeout 30 -C Debug -j4
|
||||
|
||||
macos:
|
||||
timeout-minutes: 15
|
||||
runs-on: macOS-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Compile tests
|
||||
working-directory: build
|
||||
run: |
|
||||
@ -145,23 +119,24 @@ jobs:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
run: ctest --timeout 30 -C Debug -j4
|
||||
|
||||
macos-extra:
|
||||
extra:
|
||||
timeout-minutes: 15
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-latest, macOS-latest, ubuntu-latest]
|
||||
id_type: ["std::uint32_t", "std::uint64_t"]
|
||||
cxx_std: [cxx_std_17, cxx_std_20]
|
||||
|
||||
runs-on: macOS-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Compile tests
|
||||
working-directory: build
|
||||
run: |
|
||||
cmake -DENTT_BUILD_TESTING=ON -DENTT_CXX_STD=${{ matrix.cxx_std }} -DENTT_ID_TYPE=${{ matrix.id_type }} ..
|
||||
make -j4
|
||||
cmake --build . -j 4
|
||||
- name: Run tests
|
||||
working-directory: build
|
||||
env:
|
||||
|
6
.github/workflows/coverage.yml
vendored
6
.github/workflows/coverage.yml
vendored
@ -9,11 +9,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Compile tests
|
||||
working-directory: build
|
||||
env:
|
||||
CXXFLAGS: "--coverage -fno-inline"
|
||||
CXXFLAGS: "--coverage -fno-elide-constructors -fno-inline -fno-default-inline"
|
||||
CXX: g++
|
||||
run: |
|
||||
cmake -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON ..
|
||||
@ -30,7 +30,7 @@ jobs:
|
||||
lcov -c -d . -o coverage.info
|
||||
lcov -l coverage.info
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v2
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: build/coverage.info
|
||||
|
2
.github/workflows/deploy.yml
vendored
2
.github/workflows/deploy.yml
vendored
@ -15,7 +15,7 @@ jobs:
|
||||
FORMULA: entt.rb
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Clone repository
|
||||
working-directory: build
|
||||
env:
|
||||
|
2
.github/workflows/sanitizer.yml
vendored
2
.github/workflows/sanitizer.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Compile tests
|
||||
working-directory: build
|
||||
env:
|
||||
|
@ -2,7 +2,7 @@
|
||||
# EnTT
|
||||
#
|
||||
|
||||
cmake_minimum_required(VERSION 3.12.4)
|
||||
cmake_minimum_required(VERSION 3.15.7)
|
||||
|
||||
#
|
||||
# Read project version
|
||||
@ -31,7 +31,7 @@ endif()
|
||||
|
||||
message(VERBOSE "*")
|
||||
message(VERBOSE "* ${PROJECT_NAME} v${PROJECT_VERSION} (${CMAKE_BUILD_TYPE})")
|
||||
message(VERBOSE "* Copyright (c) 2017-2022 Michele Caini <michele.caini@gmail.com>")
|
||||
message(VERBOSE "* Copyright (c) 2017-2023 Michele Caini <michele.caini@gmail.com>")
|
||||
message(VERBOSE "*")
|
||||
|
||||
#
|
||||
@ -143,6 +143,7 @@ if(ENTT_INCLUDE_HEADERS)
|
||||
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/fwd.hpp>
|
||||
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/group.hpp>
|
||||
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/handle.hpp>
|
||||
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/mixin.hpp>
|
||||
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/helper.hpp>
|
||||
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/observer.hpp>
|
||||
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/organizer.hpp>
|
||||
@ -151,7 +152,6 @@ if(ENTT_INCLUDE_HEADERS)
|
||||
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/snapshot.hpp>
|
||||
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/sparse_set.hpp>
|
||||
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/storage.hpp>
|
||||
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/storage_mixin.hpp>
|
||||
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/view.hpp>
|
||||
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/graph/adjacency_matrix.hpp>
|
||||
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/graph/dot.hpp>
|
||||
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2022 Michele Caini, author of EnTT
|
||||
Copyright (c) 2017-2023 Michele Caini, author of EnTT
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
19
README.md
19
README.md
@ -6,7 +6,8 @@
|
||||
[![Build Status](https://github.com/skypjack/entt/workflows/build/badge.svg)](https://github.com/skypjack/entt/actions)
|
||||
[![Coverage](https://codecov.io/gh/skypjack/entt/branch/master/graph/badge.svg)](https://codecov.io/gh/skypjack/entt)
|
||||
[![Try online](https://img.shields.io/badge/try-online-brightgreen)](https://godbolt.org/z/zxW73f)
|
||||
[![Documentation](https://img.shields.io/badge/docs-docsforge-blue)](http://entt.docsforge.com/)
|
||||
[![Vcpkg port](https://img.shields.io/vcpkg/v/entt)](https://vcpkg.link/ports/entt)
|
||||
[![Documentation](https://img.shields.io/badge/docs-doxygen-blue)](https://skypjack.github.io/entt/)
|
||||
[![Gitter chat](https://badges.gitter.im/skypjack/entt.png)](https://gitter.im/skypjack/entt)
|
||||
[![Discord channel](https://img.shields.io/discord/707607951396962417?logo=discord)](https://discord.gg/5BjPWBd)
|
||||
[![Donate](https://img.shields.io/badge/donate-paypal-blue.svg)](https://www.paypal.me/skypjack)
|
||||
@ -343,8 +344,8 @@ The documentation is based on [doxygen](http://www.doxygen.nl/). To build it:
|
||||
$ cmake .. -DENTT_BUILD_DOCS=ON
|
||||
$ make
|
||||
|
||||
The API reference will be created in HTML format within the directory
|
||||
`build/docs/html`. To navigate it with your favorite browser:
|
||||
The API reference is created in HTML format in the `build/docs/html` directory.
|
||||
To navigate it with your favorite browser:
|
||||
|
||||
$ cd build
|
||||
$ your_favorite_browser docs/html/index.html
|
||||
@ -353,10 +354,7 @@ The API reference will be created in HTML format within the directory
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
The same version is also available [online](https://skypjack.github.io/entt/)
|
||||
for the latest release, that is the last stable tag. If you are looking for
|
||||
something more pleasing to the eye, consider reading the nice-looking version
|
||||
available on [docsforge](https://entt.docsforge.com/): same documentation, much
|
||||
more pleasant to read.<br/>
|
||||
for the latest release, that is the last stable tag.<br/>
|
||||
Moreover, there exists a [wiki](https://github.com/skypjack/entt/wiki) dedicated
|
||||
to the project where users can find all related documentation pages.
|
||||
<!--
|
||||
@ -366,9 +364,8 @@ to the project where users can find all related documentation pages.
|
||||
# Tests
|
||||
|
||||
To compile and run the tests, `EnTT` requires *googletest*.<br/>
|
||||
`cmake` will download and compile the library before compiling anything else.
|
||||
In order to build the tests, set the `CMake` option `ENTT_BUILD_TESTING` to
|
||||
`ON`.
|
||||
`cmake` downloads and compiles the library before compiling anything else. In
|
||||
order to build the tests, set the `CMake` option `ENTT_BUILD_TESTING` to `ON`.
|
||||
|
||||
To build the most basic set of tests:
|
||||
|
||||
@ -420,7 +417,7 @@ know who has participated so far.
|
||||
|
||||
# License
|
||||
|
||||
Code and documentation Copyright (c) 2017-2022 Michele Caini.<br/>
|
||||
Code and documentation Copyright (c) 2017-2023 Michele Caini.<br/>
|
||||
Colorful logo Copyright (c) 2018-2021 Richard Caseres.
|
||||
|
||||
Code released under
|
||||
|
21
TODO
21
TODO
@ -1,27 +1,26 @@
|
||||
* debugging tools (#60): the issue online already contains interesting tips on this, look at it
|
||||
* work stealing job system (see #100) + mt scheduler based on const awareness for types
|
||||
|
||||
EXAMPLES
|
||||
* filter on runtime values/variables (not only types)
|
||||
* support to polymorphic types (see #859)
|
||||
|
||||
DOC:
|
||||
* storage<void>
|
||||
* custom storage/view
|
||||
* examples (and credits) from @alanjfs :)
|
||||
* update entity doc when the storage based model is in place
|
||||
* in-place O(1) release/destroy for non-orphaned entities, out-of-sync model
|
||||
|
||||
TODO (high prio):
|
||||
* remove the static storage from the const assure in the registry
|
||||
* check natvis files (periodically :)
|
||||
* resource cache: avoid using shared ptr with loader and the others
|
||||
* further optimize exclusion lists in multi type views (no existence check)
|
||||
* further improve the snapshot stuff, ie component functions
|
||||
* use fixture for storage tests to reduce loc number and duplication as much as possible
|
||||
* basic_view<...>::reach(...)
|
||||
* doc: bump entities
|
||||
|
||||
WIP:
|
||||
* get rid of observers, storage based views made them pointless - document alternatives
|
||||
* add storage getter for filters to views and groups
|
||||
* exploit the tombstone mechanism to allow enabling/disabling entities (see bump, compact and clear for further details)
|
||||
* basic_storage::bind for cross-registry setups (see and remove todo from entity_copy.cpp)
|
||||
* process scheduler: reviews, use free lists internally
|
||||
* dedicated entity storage, in-place O(1) release/destroy for non-orphaned entities, out-of-sync model
|
||||
* entity-only and exclude-only views (both solved with entity storage and storage<void>)
|
||||
* custom allocators all over (registry, ...)
|
||||
* add test for maximum number of entities reached
|
||||
* deprecate non-owning groups in favor of owning views and view packs, introduce lazy owning views
|
||||
* bring nested groups back in place (see bd34e7f)
|
||||
* work stealing job system (see #100) + mt scheduler based on const awareness for types
|
||||
|
@ -2,8 +2,22 @@
|
||||
# Doxygen configuration (documentation)
|
||||
#
|
||||
|
||||
set(DOXY_DEPS_DIRECTORY ${EnTT_SOURCE_DIR}/deps)
|
||||
FetchContent_Declare(
|
||||
doxygen-awesome-css
|
||||
GIT_REPOSITORY https://github.com/jothepro/doxygen-awesome-css
|
||||
GIT_TAG main
|
||||
GIT_SHALLOW 1
|
||||
)
|
||||
|
||||
FetchContent_GetProperties(doxygen-awesome-css)
|
||||
|
||||
if(NOT doxygen-awesome-css_POPULATED)
|
||||
FetchContent_Populate(doxygen-awesome-css)
|
||||
set(doxygen-awesome-css_INCLUDE_DIR ${doxygen-awesome-css_SOURCE_DIR})
|
||||
endif()
|
||||
|
||||
set(DOXY_SOURCE_DIRECTORY ${EnTT_SOURCE_DIR}/src)
|
||||
set(DOXY_CSS_DIRECTORY ${doxygen-awesome-css_INCLUDE_DIR})
|
||||
set(DOXY_DOCS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
set(DOXY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
|
142
docs/doxy.in
142
docs/doxy.in
@ -1,4 +1,4 @@
|
||||
# Doxyfile 1.9.4
|
||||
# Doxyfile 1.9.6
|
||||
|
||||
# This file describes the settings to be used by the documentation system
|
||||
# doxygen (www.doxygen.org) for a project.
|
||||
@ -19,7 +19,8 @@
|
||||
# configuration file:
|
||||
# doxygen -x [configFile]
|
||||
# Use doxygen to compare the used configuration file with the template
|
||||
# configuration file without replacing the environment variables:
|
||||
# configuration file without replacing the environment variables or CMake type
|
||||
# replacement variables:
|
||||
# doxygen -x_noenv [configFile]
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
@ -85,7 +86,7 @@ CREATE_SUBDIRS = NO
|
||||
# level increment doubles the number of directories, resulting in 4096
|
||||
# directories at level 8 which is the default and also the maximum value. The
|
||||
# sub-directories are organized in 2 levels, the first level always has a fixed
|
||||
# numer of 16 directories.
|
||||
# number of 16 directories.
|
||||
# Minimum value: 0, maximum value: 8, default value: 8.
|
||||
# This tag requires that the tag CREATE_SUBDIRS is set to YES.
|
||||
|
||||
@ -557,7 +558,8 @@ HIDE_UNDOC_MEMBERS = NO
|
||||
# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
|
||||
# undocumented classes that are normally visible in the class hierarchy. If set
|
||||
# to NO, these classes will be included in the various overviews. This option
|
||||
# has no effect if EXTRACT_ALL is enabled.
|
||||
# will also hide undocumented C++ concepts if enabled. This option has no effect
|
||||
# if EXTRACT_ALL is enabled.
|
||||
# The default value is: NO.
|
||||
|
||||
HIDE_UNDOC_CLASSES = NO
|
||||
@ -595,7 +597,8 @@ INTERNAL_DOCS = NO
|
||||
# Windows (including Cygwin) and MacOS, users should typically set this option
|
||||
# to NO, whereas on Linux or other Unix flavors it should typically be set to
|
||||
# YES.
|
||||
# The default value is: system dependent.
|
||||
# Possible values are: SYSTEM, NO and YES.
|
||||
# The default value is: SYSTEM.
|
||||
|
||||
CASE_SENSE_NAMES = YES
|
||||
|
||||
@ -847,6 +850,14 @@ WARN_IF_INCOMPLETE_DOC = YES
|
||||
|
||||
WARN_NO_PARAMDOC = YES
|
||||
|
||||
# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about
|
||||
# undocumented enumeration values. If set to NO, doxygen will accept
|
||||
# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag
|
||||
# will automatically be disabled.
|
||||
# The default value is: NO.
|
||||
|
||||
WARN_IF_UNDOC_ENUM_VAL = NO
|
||||
|
||||
# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
|
||||
# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS
|
||||
# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but
|
||||
@ -905,10 +916,21 @@ INPUT = @DOXY_SOURCE_DIRECTORY@ \
|
||||
# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
|
||||
# documentation (see:
|
||||
# https://www.gnu.org/software/libiconv/) for the list of possible encodings.
|
||||
# See also: INPUT_FILE_ENCODING
|
||||
# The default value is: UTF-8.
|
||||
|
||||
INPUT_ENCODING = UTF-8
|
||||
|
||||
# This tag can be used to specify the character encoding of the source files
|
||||
# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify
|
||||
# character encoding on a per file pattern basis. Doxygen will compare the file
|
||||
# name with each pattern and apply the encoding instead of the default
|
||||
# INPUT_ENCODING) if there is a match. The character encodings are a list of the
|
||||
# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding
|
||||
# "INPUT_ENCODING" for further information on supported encodings.
|
||||
|
||||
INPUT_FILE_ENCODING =
|
||||
|
||||
# If the value of the INPUT tag contains directories, you can use the
|
||||
# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
|
||||
# *.h) to filter out the source-files in the directories.
|
||||
@ -945,7 +967,7 @@ RECURSIVE = YES
|
||||
# Note that relative paths are relative to the directory from which doxygen is
|
||||
# run.
|
||||
|
||||
EXCLUDE = @DOXY_DEPS_DIRECTORY@
|
||||
EXCLUDE =
|
||||
|
||||
# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
|
||||
# directories that are symbolic links (a Unix file system feature) are excluded
|
||||
@ -1015,6 +1037,11 @@ IMAGE_PATH =
|
||||
# code is scanned, but not when the output code is generated. If lines are added
|
||||
# or removed, the anchors will not be placed correctly.
|
||||
#
|
||||
# Note that doxygen will use the data processed and written to standard output
|
||||
# for further processing, therefore nothing else, like debug statements or used
|
||||
# commands (so in case of a Windows batch file always use @echo OFF), should be
|
||||
# written to standard output.
|
||||
#
|
||||
# Note that for custom extensions or not directly supported extensions you also
|
||||
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
|
||||
# properly processed by doxygen.
|
||||
@ -1056,6 +1083,15 @@ FILTER_SOURCE_PATTERNS =
|
||||
|
||||
USE_MDFILE_AS_MAINPAGE = @PROJECT_SOURCE_DIR@/README.md
|
||||
|
||||
# The Fortran standard specifies that for fixed formatted Fortran code all
|
||||
# characters from position 72 are to be considered as comment. A common
|
||||
# extension is to allow longer lines before the automatic comment starts. The
|
||||
# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can
|
||||
# be processed before the automatic comment starts.
|
||||
# Minimum value: 7, maximum value: 10000, default value: 72.
|
||||
|
||||
FORTRAN_COMMENT_AFTER = 72
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to source browsing
|
||||
#---------------------------------------------------------------------------
|
||||
@ -1193,10 +1229,11 @@ CLANG_DATABASE_PATH =
|
||||
|
||||
ALPHABETICAL_INDEX = YES
|
||||
|
||||
# In case all classes in a project start with a common prefix, all classes will
|
||||
# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
|
||||
# can be used to specify a prefix (or a list of prefixes) that should be ignored
|
||||
# while generating the index headers.
|
||||
# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes)
|
||||
# that should be ignored while generating the index headers. The IGNORE_PREFIX
|
||||
# tag works for classes, function and member names. The entity will be placed in
|
||||
# the alphabetical list under the first letter of the entity name that remains
|
||||
# after removing the prefix.
|
||||
# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
|
||||
|
||||
IGNORE_PREFIX =
|
||||
@ -1265,7 +1302,7 @@ HTML_FOOTER =
|
||||
# obsolete.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
HTML_STYLESHEET =
|
||||
HTML_STYLESHEET = @DOXY_CSS_DIRECTORY@/doxygen-awesome.css
|
||||
|
||||
# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
|
||||
# cascading style sheets that are included after the standard style sheets
|
||||
@ -1275,7 +1312,12 @@ HTML_STYLESHEET =
|
||||
# Doxygen will copy the style sheet files to the output directory.
|
||||
# Note: The order of the extra style sheet files is of importance (e.g. the last
|
||||
# style sheet in the list overrules the setting of the previous ones in the
|
||||
# list). For an example see the documentation.
|
||||
# list).
|
||||
# Note: Since the styling of scrollbars can currently not be overruled in
|
||||
# Webkit/Chromium, the styling will be left out of the default doxygen.css if
|
||||
# one or more extra stylesheets have been specified. So if scrollbar
|
||||
# customization is desired it has to be added explicitly. For an example see the
|
||||
# documentation.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
HTML_EXTRA_STYLESHEET =
|
||||
@ -1290,6 +1332,19 @@ HTML_EXTRA_STYLESHEET =
|
||||
|
||||
HTML_EXTRA_FILES =
|
||||
|
||||
# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output
|
||||
# should be rendered with a dark or light theme.
|
||||
# Possible values are: LIGHT always generate light mode output, DARK always
|
||||
# generate dark mode output, AUTO_LIGHT automatically set the mode according to
|
||||
# the user preference, use light mode if no preference is set (the default),
|
||||
# AUTO_DARK automatically set the mode according to the user preference, use
|
||||
# dark mode if no preference is set and TOGGLE allow to user to switch between
|
||||
# light and dark mode via a button.
|
||||
# The default value is: AUTO_LIGHT.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
HTML_COLORSTYLE = LIGHT
|
||||
|
||||
# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
|
||||
# will adjust the colors in the style sheet and background images according to
|
||||
# this color. Hue is specified as an angle on a color-wheel, see
|
||||
@ -1653,17 +1708,6 @@ HTML_FORMULA_FORMAT = png
|
||||
|
||||
FORMULA_FONTSIZE = 10
|
||||
|
||||
# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
|
||||
# generated for formulas are transparent PNGs. Transparent PNGs are not
|
||||
# supported properly for IE 6.0, but are supported on all modern browsers.
|
||||
#
|
||||
# Note that when changing this option you need to delete any form_*.png files in
|
||||
# the HTML output directory before the changes have effect.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
FORMULA_TRANSPARENT = YES
|
||||
|
||||
# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
|
||||
# to create new LaTeX commands to be used in formulas as building blocks. See
|
||||
# the section "Including formulas" for details.
|
||||
@ -2379,26 +2423,38 @@ HAVE_DOT = YES
|
||||
|
||||
DOT_NUM_THREADS = 0
|
||||
|
||||
# When you want a differently looking font in the dot files that doxygen
|
||||
# generates you can specify the font name using DOT_FONTNAME. You need to make
|
||||
# sure dot is able to find the font, which can be done by putting it in a
|
||||
# standard location or by setting the DOTFONTPATH environment variable or by
|
||||
# setting DOT_FONTPATH to the directory containing the font.
|
||||
# The default value is: Helvetica.
|
||||
# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of
|
||||
# subgraphs. When you want a differently looking font in the dot files that
|
||||
# doxygen generates you can specify fontname, fontcolor and fontsize attributes.
|
||||
# For details please see <a href=https://graphviz.org/doc/info/attrs.html>Node,
|
||||
# Edge and Graph Attributes specification</a> You need to make sure dot is able
|
||||
# to find the font, which can be done by putting it in a standard location or by
|
||||
# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the
|
||||
# directory containing the font. Default graphviz fontsize is 14.
|
||||
# The default value is: fontname=Helvetica,fontsize=10.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
DOT_FONTNAME = Helvetica
|
||||
DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10"
|
||||
|
||||
# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
|
||||
# dot graphs.
|
||||
# Minimum value: 4, maximum value: 24, default value: 10.
|
||||
# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can
|
||||
# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. <a
|
||||
# href=https://graphviz.org/doc/info/arrows.html>Complete documentation about
|
||||
# arrows shapes.</a>
|
||||
# The default value is: labelfontname=Helvetica,labelfontsize=10.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
DOT_FONTSIZE = 10
|
||||
DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10"
|
||||
|
||||
# By default doxygen will tell dot to use the default font as specified with
|
||||
# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
|
||||
# the path where dot can find it using this tag.
|
||||
# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes
|
||||
# around nodes set 'shape=plain' or 'shape=plaintext' <a
|
||||
# href=https://www.graphviz.org/doc/info/shapes.html>Shapes specification</a>
|
||||
# The default value is: shape=box,height=0.2,width=0.4.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4"
|
||||
|
||||
# You can set the path where dot can find font specified with fontname in
|
||||
# DOT_COMMON_ATTR and others dot attributes.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
DOT_FONTPATH =
|
||||
@ -2641,18 +2697,6 @@ DOT_GRAPH_MAX_NODES = 50
|
||||
|
||||
MAX_DOT_GRAPH_DEPTH = 0
|
||||
|
||||
# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
|
||||
# background. This is disabled by default, because dot on Windows does not seem
|
||||
# to support this out of the box.
|
||||
#
|
||||
# Warning: Depending on the platform used, enabling this option may lead to
|
||||
# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
|
||||
# read).
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag HAVE_DOT is set to YES.
|
||||
|
||||
DOT_TRANSPARENT = NO
|
||||
|
||||
# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
|
||||
# files in one run (i.e. multiple -o and -T options on the command line). This
|
||||
# makes dot run faster, but since only newer versions of dot (>1.8.10) support
|
||||
|
@ -17,7 +17,6 @@
|
||||
* [ENTT_DISABLE_ASSERT](#entt_disable_assert)
|
||||
* [ENTT_NO_ETO](#entt_no_eto)
|
||||
* [ENTT_STANDARD_CPP](#entt_standard_cpp)
|
||||
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
|
@ -9,7 +9,6 @@
|
||||
* [Containers](#containers)
|
||||
* [Dense map](#dense-map)
|
||||
* [Dense set](#dense-set)
|
||||
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
@ -21,7 +20,7 @@ difficult to do better (although it's very easy to do worse, as many examples
|
||||
available online demonstrate).<br/>
|
||||
`EnTT` doesn't try in any way to replace what is offered by the standard. Quite
|
||||
the opposite, given the widespread use that is made of standard containers.<br/>
|
||||
However, the library also tries to fill a gap in features and functionality by
|
||||
However, the library also tries to fill a gap in features and functionalities by
|
||||
making available some containers initially developed for internal use.
|
||||
|
||||
This section of the library is likely to grow larger over time. However, for the
|
||||
@ -40,7 +39,7 @@ The implementation is based on _sparse sets_ and each bucket is identified by an
|
||||
implicit list within the packed array itself.
|
||||
|
||||
The interface is very close to its counterpart in the standard library, that is,
|
||||
`std::unordered_map`.<br/>
|
||||
the `std::unordered_map` class.<br/>
|
||||
However, both local and non-local iterators returned by a dense map belong to
|
||||
the input iterator category although they respectively model the concepts of a
|
||||
_forward iterator_ type and a _random access iterator_ type.<br/>
|
||||
@ -63,5 +62,5 @@ The implementation is based on _sparse sets_ and each bucket is identified by an
|
||||
implicit list within the packed array itself.
|
||||
|
||||
The interface is in all respects similar to its counterpart in the standard
|
||||
library, that is, `std::unordered_set`.<br/>
|
||||
library, that is, the `std::unordered_set` class.<br/>
|
||||
Therefore, there is no need to go into the API description.
|
||||
|
279
docs/md/core.md
279
docs/md/core.md
@ -46,25 +46,24 @@
|
||||
# Introduction
|
||||
|
||||
`EnTT` comes with a bunch of core functionalities mostly used by the other parts
|
||||
of the library itself.<br/>
|
||||
Hardly users will include these features in their code, but it's worth
|
||||
describing what `EnTT` offers so as not to reinvent the wheel in case of need.
|
||||
of the library.<br/>
|
||||
Many of these tools are also useful in everyday work. Therefore, it's worth
|
||||
describing them so as not to reinvent the wheel in case of need.
|
||||
|
||||
# Any as in any type
|
||||
|
||||
`EnTT` comes with its own `any` type. It may seem redundant considering that
|
||||
C++17 introduced `std::any`, but it is not (hopefully).<br/>
|
||||
`EnTT` offers its own `any` type. It may seem redundant considering that C++17
|
||||
introduced `std::any`, but it is not (hopefully).<br/>
|
||||
First of all, the _type_ returned by an `std::any` is a const reference to an
|
||||
`std::type_info`, an implementation defined class that's not something everyone
|
||||
wants to see in a software. Furthermore, there is no way to connect it with the
|
||||
type system of the library and therefore with its integrated RTTI support.<br/>
|
||||
Note that this class is largely used internally by the library itself.
|
||||
wants to see in a software. Furthermore, there is no way to bind it to the type
|
||||
system of the library and therefore with its integrated RTTI support.
|
||||
|
||||
The API is very similar to that of its most famous counterpart, mainly because
|
||||
this class serves the same purpose of being an opaque container for any type of
|
||||
value.<br/>
|
||||
Instances of `any` also minimize the number of allocations by relying on a well
|
||||
known technique called _small buffer optimization_ and a fake vtable.
|
||||
The `any` API is very similar to that of its most famous counterpart, mainly
|
||||
because this class serves the same purpose of being an opaque container for any
|
||||
type of value.<br/>
|
||||
Instances also minimize the number of allocations by relying on a well known
|
||||
technique called _small buffer optimization_ and a fake vtable.
|
||||
|
||||
Creating an object of the `any` type, whether empty or not, is trivial:
|
||||
|
||||
@ -93,8 +92,8 @@ Furthermore, an instance of `any` isn't tied to an actual type. Therefore, the
|
||||
wrapper is reconfigured when it's assigned a new object of a type other than
|
||||
the one it contains.
|
||||
|
||||
There exists also a way to directly assign a value to the variable contained by
|
||||
an `entt::any`, without necessarily replacing it. This is especially useful when
|
||||
There is also a way to directly assign a value to the variable contained by an
|
||||
`entt::any`, without necessarily replacing it. This is especially useful when
|
||||
the object is used in _aliasing mode_, as described below:
|
||||
|
||||
```cpp
|
||||
@ -108,15 +107,15 @@ any.assign(value);
|
||||
any.assign(std::move(value));
|
||||
```
|
||||
|
||||
The `any` class will also perform a check on the type information and whether or
|
||||
not the original type was copy or move assignable, as appropriate.<br/>
|
||||
In all cases, the `assign` function returns a boolean value to indicate the
|
||||
success or failure of the operation.
|
||||
The `any` class performs a check on the type information and whether or not the
|
||||
original type was copy or move assignable, as appropriate.<br/>
|
||||
In all cases, the `assign` function returns a boolean value that is true in case
|
||||
of success and false otherwise.
|
||||
|
||||
When in doubt about the type of object contained, the `type` member function of
|
||||
`any` returns a const reference to the `type_info` associated with its element,
|
||||
or `type_id<void>()` if the container is empty. The type is also used internally
|
||||
when comparing two `any` objects:
|
||||
When in doubt about the type of object contained, the `type` member function
|
||||
returns a const reference to the `type_info` associated with its element, or
|
||||
`type_id<void>()` if the container is empty.<br/>
|
||||
The type is also used internally when comparing two `any` objects:
|
||||
|
||||
```cpp
|
||||
if(any == empty) { /* ... */ }
|
||||
@ -125,7 +124,7 @@ if(any == empty) { /* ... */ }
|
||||
In this case, before proceeding with a comparison, it's verified that the _type_
|
||||
of the two objects is actually the same.<br/>
|
||||
Refer to the `EnTT` type system documentation for more details about how
|
||||
`type_info` works and on possible risks of a comparison.
|
||||
`type_info` works and the possible risks of a comparison.
|
||||
|
||||
A particularly interesting feature of this class is that it can also be used as
|
||||
an opaque container for const and non-const references:
|
||||
@ -153,22 +152,19 @@ entt::any ref = other.as_ref();
|
||||
```
|
||||
|
||||
In this case, it doesn't matter if the original container actually holds an
|
||||
object or acts already as a reference for unmanaged elements, the new instance
|
||||
thus created won't create copies and will only serve as a reference for the
|
||||
original item.<br/>
|
||||
This means that, starting from the example above, both `ref` and `other` will
|
||||
point to the same object, whether it's initially contained in `other` or already
|
||||
an unmanaged element.
|
||||
object or is as a reference for unmanaged elements already. The new instance
|
||||
thus created doesn't create copies and only serves as a reference for the
|
||||
original item.
|
||||
|
||||
As a side note, it's worth mentioning that, while everything works transparently
|
||||
when it comes to non-const references, there are some exceptions when it comes
|
||||
to const references.<br/>
|
||||
It's worth mentioning that, while everything works transparently when it comes
|
||||
to non-const references, there are some exceptions when it comes to const
|
||||
references.<br/>
|
||||
In particular, the `data` member function invoked on a non-const instance of
|
||||
`any` that wraps a const reference will return a null pointer in all cases.
|
||||
`any` that wraps a const reference returns a null pointer in all cases.
|
||||
|
||||
To cast an instance of `any` to a type, the library offers a set of `any_cast`
|
||||
functions in all respects similar to their most famous counterparts.<br/>
|
||||
The only difference is that, in the case of `EnTT`, these won't raise exceptions
|
||||
The only difference is that, in the case of `EnTT`, they won't raise exceptions
|
||||
but will only trigger an assert in debug mode, otherwise resulting in undefined
|
||||
behavior in case of misuse in release mode.
|
||||
|
||||
@ -188,31 +184,23 @@ using my_any = entt::basic_any<sizeof(double[4])>;
|
||||
This feature, in addition to allowing the choice of a size that best suits the
|
||||
needs of an application, also offers the possibility of forcing dynamic creation
|
||||
of objects during construction.<br/>
|
||||
In other terms, if the size is 0, `any` avoids the use of any optimization and
|
||||
always dynamically allocates objects (except for aliasing cases).
|
||||
|
||||
Note that the size of the internal storage as well as the alignment requirements
|
||||
are directly part of the type and therefore contribute to define different types
|
||||
that won't be able to interoperate with each other.
|
||||
In other terms, if the size is 0, `any` suppresses the small buffer optimization
|
||||
and always dynamically allocates objects (except for aliasing cases).
|
||||
|
||||
## Alignment requirement
|
||||
|
||||
The alignment requirement is optional and by default the most stringent (the
|
||||
largest) for any object whose size is at most equal to the one provided.<br/>
|
||||
The `basic_any` class template inspects the alignment requirements in each case,
|
||||
even when not provided and may decide not to use the small buffer optimization
|
||||
in order to meet them.
|
||||
|
||||
The alignment requirement is provided as an optional second parameter following
|
||||
the desired size for the internal storage:
|
||||
It's provided as an optional second parameter following the desired size for the
|
||||
internal storage:
|
||||
|
||||
```cpp
|
||||
using my_any = entt::basic_any<sizeof(double[4]), alignof(double[4])>;
|
||||
```
|
||||
|
||||
Note that the alignment requirements as well as the size of the internal storage
|
||||
are directly part of the type and therefore contribute to define different types
|
||||
that won't be able to interoperate with each other.
|
||||
The `basic_any` class template inspects the alignment requirements in each case,
|
||||
even when not provided and may decide not to use the small buffer optimization
|
||||
in order to meet them.
|
||||
|
||||
# Compressed pair
|
||||
|
||||
@ -225,8 +213,8 @@ is more important than having some cool and probably useless feature.
|
||||
|
||||
Although the API is very close to that of `std::pair` (apart from the fact that
|
||||
the template parameters are inferred from the constructor and therefore there is
|
||||
no` entt::make_compressed_pair`), the major difference is that `first` and
|
||||
`second` are functions for implementation needs:
|
||||
no `entt::make_compressed_pair`), the major difference is that `first` and
|
||||
`second` are functions for implementation requirements:
|
||||
|
||||
```cpp
|
||||
entt::compressed_pair pair{0, 3.};
|
||||
@ -239,16 +227,15 @@ intuition. At the end of the day, it's just a pair and nothing more.
|
||||
# Enum as bitmask
|
||||
|
||||
Sometimes it's useful to be able to use enums as bitmasks. However, enum classes
|
||||
aren't really suitable for the purpose out of the box. Main problem is that they
|
||||
don't convert implicitly to their underlying type.<br/>
|
||||
All that remains is to make a choice between using old-fashioned enums (with all
|
||||
their problems that I don't want to discuss here) or writing _ugly_ code.
|
||||
aren't really suitable for the purpose. Main problem is that they don't convert
|
||||
implicitly to their underlying type.<br/>
|
||||
The choice is then between using old-fashioned enums (with all their problems
|
||||
that I don't want to discuss here) or writing _ugly_ code.
|
||||
|
||||
Fortunately, there is also a third way: adding enough operators in the global
|
||||
scope to treat enum classes as bitmask transparently.<br/>
|
||||
The ultimate goal is to be able to write code like the following (or maybe
|
||||
something more meaningful, but this should give a grasp and remain simple at the
|
||||
same time):
|
||||
scope to treat enum classes as bitmasks transparently.<br/>
|
||||
The ultimate goal is to write code like the following (or maybe something more
|
||||
meaningful, but this should give a grasp and remain simple at the same time):
|
||||
|
||||
```cpp
|
||||
enum class my_flag {
|
||||
@ -261,11 +248,11 @@ const my_flag flags = my_flag::enabled;
|
||||
const bool is_enabled = !!(flags & my_flag::enabled);
|
||||
```
|
||||
|
||||
The problem with adding all operators to the global scope is that these will
|
||||
come into play even when not required, with the risk of introducing errors that
|
||||
are difficult to deal with.<br/>
|
||||
The problem with adding all operators to the global scope is that these come
|
||||
into play even when not required, with the risk of introducing errors that are
|
||||
difficult to deal with.<br/>
|
||||
However, C++ offers enough tools to get around this problem. In particular, the
|
||||
library requires users to register all enum classes for which bitmask support
|
||||
library requires users to register the enum classes for which bitmask support
|
||||
should be enabled:
|
||||
|
||||
```cpp
|
||||
@ -276,7 +263,7 @@ struct entt::enum_as_bitmask<my_flag>
|
||||
```
|
||||
|
||||
This is handy when dealing with enum classes defined by third party libraries
|
||||
and over which the users have no control. However, it's also verbose and can be
|
||||
and over which the user has no control. However, it's also verbose and can be
|
||||
avoided by adding a specific value to the enum class itself:
|
||||
|
||||
```cpp
|
||||
@ -289,23 +276,21 @@ enum class my_flag {
|
||||
```
|
||||
|
||||
In this case, there is no need to specialize the `enum_as_bitmask` traits, since
|
||||
`EnTT` will automatically detect the flag and enable the bitmask support.<br/>
|
||||
Once the enum class has been registered (in one way or the other) all the most
|
||||
common operators will be available, such as `&`, `|` but also `&=` and `|=`.
|
||||
`EnTT` automatically detects the flag and enables the bitmask support.<br/>
|
||||
Once the enum class is registered (in one way or the other), the most common
|
||||
operators such as `&`, `|` but also `&=` and `|=` are available for use.
|
||||
|
||||
Refer to the official documentation for the full list of operators.
|
||||
|
||||
# Hashed strings
|
||||
|
||||
A hashed string is a zero overhead unique identifier. Users can use
|
||||
human-readable identifiers in the codebase while using their numeric
|
||||
counterparts at runtime, thus without affecting performance.<br/>
|
||||
Hashed strings are human-readable identifiers in the codebase that turn into
|
||||
numeric values at runtime, thus without affecting performance.<br/>
|
||||
The class has an implicit `constexpr` constructor that chews a bunch of
|
||||
characters. Once created, all what one can do with it is getting back the
|
||||
original string through the `data` member function or converting the instance
|
||||
into a number.<br/>
|
||||
The good part is that a hashed string can be used wherever a constant expression
|
||||
is required and no _string-to-number_ conversion will take place at runtime if
|
||||
used carefully.
|
||||
characters. Once created, one can get the original string by means of the `data`
|
||||
member function or convert the instance into a number.<br/>
|
||||
A hashed string is well suited wherever a constant expression is required. No
|
||||
_string-to-number_ conversion will take place at runtime if used carefully.
|
||||
|
||||
Example of use:
|
||||
|
||||
@ -318,19 +303,18 @@ auto resource = load(entt::hashed_string{"gui/background"});
|
||||
```
|
||||
|
||||
There is also a _user defined literal_ dedicated to hashed strings to make them
|
||||
more user-friendly:
|
||||
more _user-friendly_:
|
||||
|
||||
```cpp
|
||||
using namespace entt::literals;
|
||||
constexpr auto str = "text"_hs;
|
||||
```
|
||||
|
||||
To use it, remember that all user defined literals in `EnTT` are enclosed in the
|
||||
`entt::literals` namespace. Therefore, the entire namespace or selectively the
|
||||
literal of interest must be explicitly included before each use, a bit like
|
||||
`std::literals`.<br/>
|
||||
Finally, in case users need to create hashed strings at runtime, this class also
|
||||
offers the necessary functionalities:
|
||||
User defined literals in `EnTT` are enclosed in the `entt::literals` namespace.
|
||||
Therefore, the entire namespace or selectively the literal of interest must be
|
||||
explicitly included before each use, a bit like `std::literals`.<br/>
|
||||
The class also offers the necessary functionalities to create hashed strings at
|
||||
runtime:
|
||||
|
||||
```cpp
|
||||
std::string orig{"text"};
|
||||
@ -343,16 +327,14 @@ const auto hash = entt::hashed_string::value(orig.c_str());
|
||||
```
|
||||
|
||||
This possibility shouldn't be exploited in tight loops, since the computation
|
||||
takes place at runtime and no longer at compile-time and could therefore impact
|
||||
takes place at runtime and no longer at compile-time. It could therefore affect
|
||||
performance to some degrees.
|
||||
|
||||
## Wide characters
|
||||
|
||||
The hashed string has a design that is close to that of an `std::basic_string`.
|
||||
It means that `hashed_string` is nothing more than an alias for
|
||||
`basic_hashed_string<char>`. For those who want to use the C++ type for wide
|
||||
character representation, there exists also the alias `hashed_wstring` for
|
||||
`basic_hashed_string<wchar_t>`.<br/>
|
||||
The `hashed_string` class is an alias for `basic_hashed_string<char>`. To use
|
||||
the C++ type for wide character representations, there exists also the alias
|
||||
`hashed_wstring` for `basic_hashed_string<wchar_t>`.<br/>
|
||||
In this case, the user defined literal to use to create hashed strings on the
|
||||
fly is `_hws`:
|
||||
|
||||
@ -360,16 +342,15 @@ fly is `_hws`:
|
||||
constexpr auto str = L"text"_hws;
|
||||
```
|
||||
|
||||
Note that the hash type of the `hashed_wstring` is the same of its counterpart.
|
||||
The hash type of `hashed_wstring` is the same as its counterpart.
|
||||
|
||||
## Conflicts
|
||||
|
||||
The hashed string class uses internally FNV-1a to compute the numeric
|
||||
counterpart of a string. Because of the _pigeonhole principle_, conflicts are
|
||||
possible. This is a fact.<br/>
|
||||
The hashed string class uses FNV-1a internally to hash strings. Because of the
|
||||
_pigeonhole principle_, conflicts are possible. This is a fact.<br/>
|
||||
There is no silver bullet to solve the problem of conflicts when dealing with
|
||||
hashing functions. In this case, the best solution seemed to be to give up.
|
||||
That's all.<br/>
|
||||
hashing functions. In this case, the best solution is likely to give up. That's
|
||||
all.<br/>
|
||||
After all, human-readable unique identifiers aren't something strictly defined
|
||||
and over which users have not the control. Choosing a slightly different
|
||||
identifier is probably the best solution to make the conflict disappear in this
|
||||
@ -377,8 +358,8 @@ case.
|
||||
|
||||
# Iterators
|
||||
|
||||
Writing and working with iterators isn't always easy and more often than not
|
||||
leads to duplicated code.<br/>
|
||||
Writing and working with iterators isn't always easy. More often than not it
|
||||
also leads to duplicated code.<br/>
|
||||
`EnTT` tries to overcome this problem by offering some utilities designed to
|
||||
make this hard work easier.
|
||||
|
||||
@ -431,7 +412,7 @@ user.
|
||||
## Iterable adaptor
|
||||
|
||||
Typically, a container class provides `begin` and `end` member functions (with
|
||||
their const counterparts) to be iterated by the user.<br/>
|
||||
their const counterparts) for iteration.<br/>
|
||||
However, it can happen that a class offers multiple iteration methods or allows
|
||||
users to iterate different sets of _elements_.
|
||||
|
||||
@ -452,8 +433,8 @@ by returning an iterable object for the purpose.
|
||||
There are a handful of tools within `EnTT` to interact with memory in one way or
|
||||
another.<br/>
|
||||
Some are geared towards simplifying the implementation of (internal or external)
|
||||
allocator aware containers. Others, on the other hand, are designed to help the
|
||||
developer with everyday problems.
|
||||
allocator aware containers. Others are designed to help the developer with
|
||||
everyday problems.
|
||||
|
||||
The former are very specific and for niche problems. These are tools designed to
|
||||
unwrap fancy or plain pointers (`to_address`) or to help forget the meaning of
|
||||
@ -481,7 +462,7 @@ modulus and for this reason preferred in many areas.
|
||||
|
||||
A nasty thing in C++ (at least up to C++20) is the fact that shared pointers
|
||||
support allocators while unique pointers don't.<br/>
|
||||
There is a proposal at the moment that also shows among the other things how
|
||||
There is a proposal at the moment that also shows (among the other things) how
|
||||
this can be implemented without any compiler support.
|
||||
|
||||
The `allocate_unique` function follows this proposal, making a virtue out of
|
||||
@ -498,13 +479,16 @@ the same feature.
|
||||
# Monostate
|
||||
|
||||
The monostate pattern is often presented as an alternative to a singleton based
|
||||
configuration system. This is exactly its purpose in `EnTT`. Moreover, this
|
||||
implementation is thread safe by design (hopefully).<br/>
|
||||
Keys are represented by hashed strings, values are basic types like `int`s or
|
||||
`bool`s. Values of different types can be associated to each key, even more than
|
||||
one at a time. Because of this, users must pay attention to use the same type
|
||||
both during an assignment and when they try to read back their data. Otherwise,
|
||||
they will probably incur in unexpected results.
|
||||
configuration system.<br/>
|
||||
This is exactly its purpose in `EnTT`. Moreover, this implementation is thread
|
||||
safe by design (hopefully).
|
||||
|
||||
Keys are integral values (easily obtained by hashed strings), values are basic
|
||||
types like `int`s or `bool`s. Values of different types can be associated with
|
||||
each key, even more than one at a time.<br/>
|
||||
Because of this, one should pay attention to use the same type both during an
|
||||
assignment and when trying to read back the data. Otherwise, there is the risk
|
||||
to incur in unexpected results.
|
||||
|
||||
Example of use:
|
||||
|
||||
@ -542,17 +526,10 @@ Basically, the whole system relies on a handful of classes. In particular:
|
||||
auto index = entt::type_index<a_type>::value();
|
||||
```
|
||||
|
||||
The returned value isn't guaranteed to be stable across different runs.
|
||||
The returned value isn't guaranteed to be stable across different runs.<br/>
|
||||
However, it can be very useful as index in associative and unordered
|
||||
associative containers or for positional accesses in a vector or an array.
|
||||
|
||||
So as not to conflict with the other tools available, the `family` class isn't
|
||||
used to generate these indexes. Therefore, the numeric identifiers returned by
|
||||
the two tools may differ.<br/>
|
||||
On the other hand, this leaves users with full powers over the `family` class
|
||||
and therefore the generation of custom runtime sequences of indices for their
|
||||
own purposes, if necessary.
|
||||
|
||||
An external generator can also be used if needed. In fact, `type_index` can be
|
||||
specialized by type and is also _sfinae-friendly_ in order to allow more
|
||||
refined specializations such as:
|
||||
@ -566,7 +543,7 @@ Basically, the whole system relies on a handful of classes. In particular:
|
||||
};
|
||||
```
|
||||
|
||||
Note that indexes **must** still be generated sequentially in this case.<br/>
|
||||
Indexes **must** be sequentially generated in this case.<br/>
|
||||
The tool is widely used within `EnTT`. Generating indices not sequentially
|
||||
would break an assumption and would likely lead to undesired behaviors.
|
||||
|
||||
@ -583,14 +560,14 @@ Basically, the whole system relies on a handful of classes. In particular:
|
||||
This function **can** use non-standard features of the language for its own
|
||||
purposes. This makes it possible to provide compile-time identifiers that
|
||||
remain stable across different runs.<br/>
|
||||
In all cases, users can prevent the library from using these features by means
|
||||
of the `ENTT_STANDARD_CPP` definition. In this case, there is no guarantee
|
||||
that identifiers remain stable across executions. Moreover, they are generated
|
||||
Users can prevent the library from using these features by means of the
|
||||
`ENTT_STANDARD_CPP` definition. In this case, there is no guarantee that
|
||||
identifiers remain stable across executions. Moreover, they are generated
|
||||
at runtime and are no longer a compile-time thing.
|
||||
|
||||
As for `type_index`, also `type_hash` is a _sfinae-friendly_ class that can be
|
||||
specialized in order to customize its behavior globally or on a per-type or
|
||||
per-traits basis.
|
||||
As it happens with `type_index`, also `type_hash` is a _sfinae-friendly_ class
|
||||
that can be specialized in order to customize its behavior globally or on a
|
||||
per-type or per-traits basis.
|
||||
|
||||
* The name associated with a given type:
|
||||
|
||||
@ -598,10 +575,9 @@ Basically, the whole system relies on a handful of classes. In particular:
|
||||
auto name = entt::type_name<a_type>::value();
|
||||
```
|
||||
|
||||
The name associated with a type is extracted from some information generally
|
||||
made available by the compiler in use. Therefore, it may differ depending on
|
||||
the compiler and may be empty in the event that this information isn't
|
||||
available.<br/>
|
||||
This value is extracted from some information generally made available by the
|
||||
compiler in use. Therefore, it may differ depending on the compiler and may be
|
||||
empty in the event that this information isn't available.<br/>
|
||||
For example, given the following class:
|
||||
|
||||
```cpp
|
||||
@ -612,21 +588,20 @@ Basically, the whole system relies on a handful of classes. In particular:
|
||||
when MSVC is in use.<br/>
|
||||
Most of the time the name is also retrieved at compile-time and is therefore
|
||||
always returned through an `std::string_view`. Users can easily access it and
|
||||
modify it as needed, for example by removing the word `struct` to standardize
|
||||
the result. `EnTT` won't do this for obvious reasons, since it requires
|
||||
copying and creating a new string potentially at runtime.
|
||||
modify it as needed, for example by removing the word `struct` to normalize
|
||||
the result. `EnTT` doesn't do this for obvious reasons, since it would be
|
||||
creating a new string at runtime otherwise.
|
||||
|
||||
This function **can** use non-standard features of the language for its own
|
||||
purposes. Users can prevent the library from using non-standard features by
|
||||
means of the `ENTT_STANDARD_CPP` definition. In this case, the name will be
|
||||
empty by default.
|
||||
purposes. Users can prevent the library from using these features by means of
|
||||
the `ENTT_STANDARD_CPP` definition. In this case, the name is just empty.
|
||||
|
||||
As for `type_index`, also `type_name` is a _sfinae-friendly_ class that can be
|
||||
specialized in order to customize its behavior globally or on a per-type or
|
||||
per-traits basis.
|
||||
As it happens with `type_index`, also `type_name` is a _sfinae-friendly_ class
|
||||
that can be specialized in order to customize its behavior globally or on a
|
||||
per-type or per-traits basis.
|
||||
|
||||
These are then combined into utilities that aim to offer an API that is somewhat
|
||||
similar to that offered by the language.
|
||||
similar to that made available by the standard library.
|
||||
|
||||
### Type info
|
||||
|
||||
@ -708,8 +683,8 @@ In fact, although this is quite rare, it's not entirely excluded.
|
||||
|
||||
Another case where two types are assigned the same identifier is when classes
|
||||
from different contexts (for example two or more libraries loaded at runtime)
|
||||
have the same fully qualified name. In this case, also `type_name` will return
|
||||
the same value for the two types.<br/>
|
||||
have the same fully qualified name. In this case, `type_name` returns the same
|
||||
value for the two types.<br/>
|
||||
Fortunately, there are several easy ways to deal with this:
|
||||
|
||||
* The most trivial one is to define the `ENTT_STANDARD_CPP` macro. Runtime
|
||||
@ -739,9 +714,9 @@ offered by this module.
|
||||
|
||||
### Size of
|
||||
|
||||
The standard operator `sizeof` complains when users provide it for example with
|
||||
function or incomplete types. On the other hand, it's guaranteed that its result
|
||||
is always nonzero, even if applied to an empty class type.<br/>
|
||||
The standard operator `sizeof` complains if users provide it with functions or
|
||||
incomplete types. On the other hand, it's guaranteed that its result is always
|
||||
non-zero, even if applied to an empty class type.<br/>
|
||||
This small class combines the two and offers an alternative to `sizeof` that
|
||||
works under all circumstances, returning zero if the type isn't supported:
|
||||
|
||||
@ -777,8 +752,8 @@ A utility to easily transfer the constness of a type to another type:
|
||||
using type = entt::constness_as_t<dst_type, const src_type>;
|
||||
```
|
||||
|
||||
The trait is subject to the rules of the language. Therefore, for example,
|
||||
transferring constness between references won't give the desired effect.
|
||||
The trait is subject to the rules of the language. For example, _transferring_
|
||||
constness between references won't give the desired effect.
|
||||
|
||||
### Member class type
|
||||
|
||||
@ -798,7 +773,7 @@ A utility to quickly find the n-th argument of a function, member function or
|
||||
data member (for blind operations on opaque types):
|
||||
|
||||
```cpp
|
||||
using type = entt::nt_argument_t<1u, &clazz::member>;
|
||||
using type = entt::nth_argument_t<1u, &clazz::member>;
|
||||
```
|
||||
|
||||
Disambiguation of overloaded functions is the responsibility of the user, should
|
||||
@ -825,8 +800,8 @@ registry.emplace<enemy_tag>(entity);
|
||||
|
||||
### Tag
|
||||
|
||||
Since `id_type` is very important and widely used in `EnTT`, there is a more
|
||||
user-friendly shortcut for the creation of integral constants based on it.<br/>
|
||||
Type `id_type` is very important and widely used in `EnTT`. Therefore, there is
|
||||
a more user-friendly shortcut for the creation of constants based on it.<br/>
|
||||
This shortcut is the alias template `entt::tag`.
|
||||
|
||||
If used in combination with hashed strings, it helps to use human-readable names
|
||||
@ -918,8 +893,8 @@ Perhaps a bit ugly to see in a codebase but it gets the job done at least.
|
||||
|
||||
## Runtime generator
|
||||
|
||||
To generate sequential numeric identifiers at runtime, `EnTT` offers the
|
||||
`family` class template:
|
||||
The `family` class template helps to generate sequential numeric identifiers for
|
||||
types at runtime:
|
||||
|
||||
```cpp
|
||||
// defines a custom generator
|
||||
@ -936,8 +911,8 @@ numeric identifier for the given type.<br/>
|
||||
The generator is customizable, so as to get different _sequences_ for different
|
||||
purposes if needed.
|
||||
|
||||
Please, note that identifiers aren't guaranteed to be stable across different
|
||||
runs. Indeed it mostly depends on the flow of execution.
|
||||
Identifiers aren't guaranteed to be stable across different runs. Indeed it
|
||||
mostly depends on the flow of execution.
|
||||
|
||||
# Utilities
|
||||
|
||||
|
1512
docs/md/entity.md
1512
docs/md/entity.md
File diff suppressed because it is too large
Load Diff
@ -193,7 +193,7 @@ to mitigate the problem makes it manageable.
|
||||
|
||||
## Which functions trigger which signals
|
||||
|
||||
The `registry` class offers three signals that are emitted following specific
|
||||
Storage classes offer three _signals_ that are emitted following specific
|
||||
operations. Maybe not everyone knows what these operations are, though.<br/>
|
||||
If this isn't clear, below you can find a _vademecum_ for this purpose:
|
||||
|
||||
|
175
docs/md/graph.md
175
docs/md/graph.md
@ -14,7 +14,6 @@
|
||||
* [Fake resources and order of execution](#fake-resources-and-order-of-execution)
|
||||
* [Sync points](#sync-points)
|
||||
* [Execution graph](#execution-graph)
|
||||
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
@ -23,15 +22,15 @@
|
||||
|
||||
`EnTT` doesn't aim to offer everything one needs to work with graphs. Therefore,
|
||||
anyone looking for this in the _graph_ submodule will be disappointed.<br/>
|
||||
Quite the opposite is true. This submodule is minimal and contains only the data
|
||||
structures and algorithms strictly necessary for the development of some tools
|
||||
such as the _flow builder_.
|
||||
Quite the opposite is true though. This submodule is minimal and contains only
|
||||
the data structures and algorithms strictly necessary for the development of
|
||||
some tools such as the _flow builder_.
|
||||
|
||||
# Data structures
|
||||
|
||||
As anticipated in the introduction, the aim isn't to offer all possible data
|
||||
structures suitable for representing and working with graphs. Many will likely
|
||||
be added or refined over time, however I want to discourage anyone expecting
|
||||
be added or refined over time. However I want to discourage anyone expecting
|
||||
tight scheduling on the subject.<br/>
|
||||
The data structures presented in this section are mainly useful for the
|
||||
development and support of some tools which are also part of the same submodule.
|
||||
@ -49,7 +48,7 @@ The `directed_tag` type _creates_ the graph as directed. There is also an
|
||||
`undirected_tag` counterpart which creates it as undirected.<br/>
|
||||
The interface deviates slightly from the typical double indexing of C and offers
|
||||
an API that is perhaps more familiar to a C++ programmer. Therefore, the access
|
||||
and modification of an element will take place via the `contains`, `insert` and
|
||||
and modification of an element takes place via the `contains`, `insert` and
|
||||
`erase` functions rather than a double call to an `operator[]`:
|
||||
|
||||
```cpp
|
||||
@ -60,14 +59,14 @@ if(adjacency_matrix.contains(0u, 1u)) {
|
||||
}
|
||||
```
|
||||
|
||||
Both `insert` and` erase` are idempotent functions which have no effect if the
|
||||
Both `insert` and` erase` are _idempotent_ functions which have no effect if the
|
||||
element already exists or has already been deleted.<br/>
|
||||
The first one returns an `std::pair` containing the iterator to the element and
|
||||
a boolean value indicating whether the element has been inserted or was already
|
||||
present. The second one instead returns the number of deleted elements (0 or 1).
|
||||
a boolean value indicating whether the element was newly inserted or not. The
|
||||
second one returns the number of deleted elements (0 or 1).
|
||||
|
||||
An adjacency matrix must be initialized with the number of elements (vertices)
|
||||
when constructing it but can also be resized later using the `resize` function:
|
||||
An adjacency matrix is initialized with the number of elements (vertices) when
|
||||
constructing it but can also be resized later using the `resize` function:
|
||||
|
||||
```cpp
|
||||
entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{3u};
|
||||
@ -82,8 +81,8 @@ for(auto &&vertex: adjacency_matrix.vertices()) {
|
||||
}
|
||||
```
|
||||
|
||||
Note that the same result can be obtained with the following snippet, since the
|
||||
vertices are unsigned integral values:
|
||||
The same result is obtained with the following snippet, since the vertices are
|
||||
plain unsigned integral values:
|
||||
|
||||
```cpp
|
||||
for(auto last = adjacency_matrix.size(), pos = {}; pos < last; ++pos) {
|
||||
@ -93,8 +92,8 @@ for(auto last = adjacency_matrix.size(), pos = {}; pos < last; ++pos) {
|
||||
|
||||
As for visiting the edges, a few functions are available.<br/>
|
||||
When the purpose is to visit all the edges of a given adjacency matrix, the
|
||||
`edges` function returns an iterable object that can be used to get them as
|
||||
pairs of vertices:
|
||||
`edges` function returns an iterable object that is used to get them as pairs of
|
||||
vertices:
|
||||
|
||||
```cpp
|
||||
for(auto [lhs, rhs]: adjacency_matrix.edges()) {
|
||||
@ -102,8 +101,8 @@ for(auto [lhs, rhs]: adjacency_matrix.edges()) {
|
||||
}
|
||||
```
|
||||
|
||||
On the other hand, if the goal is to visit all the in- or out-edges of a given
|
||||
vertex, the `in_edges` and `out_edges` functions are meant for that:
|
||||
If the goal is to visit all the in- or out-edges of a given vertex instead, the
|
||||
`in_edges` and `out_edges` functions are meant for that:
|
||||
|
||||
```cpp
|
||||
for(auto [lhs, rhs]: adjacency_matrix.out_edges(3u)) {
|
||||
@ -111,11 +110,11 @@ for(auto [lhs, rhs]: adjacency_matrix.out_edges(3u)) {
|
||||
}
|
||||
```
|
||||
|
||||
As might be expected, these functions expect the vertex to visit (that is, to
|
||||
return the in- or out-edges for) as an argument.<br/>
|
||||
Both the functions expect the vertex to visit (that is, to return the in- or
|
||||
out-edges for) as an argument.<br/>
|
||||
Finally, the adjacency matrix is an allocator-aware container and offers most of
|
||||
the functionality one would expect from this type of containers, such as `clear`
|
||||
or 'get_allocator` and so on.
|
||||
the functionalities one would expect from this type of containers, such as
|
||||
`clear` or 'get_allocator` and so on.
|
||||
|
||||
## Graphviz dot language
|
||||
|
||||
@ -129,19 +128,19 @@ std::ostringstream output{};
|
||||
entt::dot(output, adjacency_matrix);
|
||||
```
|
||||
|
||||
However, there is also the option of providing a callback to which the vertices
|
||||
are passed and which can be used to add (`dot`) properties to the output from
|
||||
time to time:
|
||||
It's also possible to provide a callback to which the vertices are passed and
|
||||
which can be used to add (`dot`) properties to the output as needed:
|
||||
|
||||
```cpp
|
||||
std::ostringstream output{};
|
||||
|
||||
entt::dot(output, adjacency_matrix, [](auto &output, auto vertex) {
|
||||
out << "label=\"v\"" << vertex << ",shape=\"box\"";
|
||||
});
|
||||
```
|
||||
|
||||
This second mode is particularly convenient when the user wants to associate
|
||||
data managed externally to the graph being converted.
|
||||
externally managed data to the graph being converted.
|
||||
|
||||
# Flow builder
|
||||
|
||||
@ -155,42 +154,42 @@ specified.<br/>
|
||||
Most of the functions in the API also return the flow builder itself, according
|
||||
to what is the common sense API when it comes to builder classes.
|
||||
|
||||
Once all tasks have been registered and resources assigned to them, an execution
|
||||
graph in the form of an adjacency matrix is returned to the user.<br/>
|
||||
Once all tasks are registered and resources assigned to them, an execution graph
|
||||
in the form of an adjacency matrix is returned to the user.<br/>
|
||||
This graph contains all the tasks assigned to the flow builder in the form of
|
||||
_vertices_. The _vertex_ itself can be used as an index to get the identifier
|
||||
passed during registration.
|
||||
_vertices_. The _vertex_ itself is used as an index to get the identifier passed
|
||||
during registration.
|
||||
|
||||
## Tasks and resources
|
||||
|
||||
Although these terms are used extensively in the documentation, the flow builder
|
||||
has no real concept of tasks and resources.<br/>
|
||||
This class works mainly with _identifiers_, that is, values of type `id_type`.
|
||||
That is, both tasks and resources are identified by integral values.<br/>
|
||||
In other terms, both tasks and resources are identified by integral values.<br/>
|
||||
This allows not to couple the class itself to the rest of the library or to any
|
||||
particular data structure. On the other hand, it requires the user to keep track
|
||||
of the association between identifiers and operations or actual data.
|
||||
|
||||
Once a flow builder has been created (which requires no constructor arguments),
|
||||
the first thing to do is to bind a task. This will indicate to the builder who
|
||||
intends to consume the resources that will be specified immediately after:
|
||||
Once a flow builder is created (which requires no constructor arguments), the
|
||||
first thing to do is to bind a task. This tells to the builder _who_ intends to
|
||||
consume the resources that are specified immediately after:
|
||||
|
||||
```cpp
|
||||
entt::flow builder{};
|
||||
builder.bind("task_1"_hs);
|
||||
```
|
||||
|
||||
Note that the example uses the `EnTT` hashed string to generate an identifier
|
||||
for the task.<br/>
|
||||
Indeed, the use of `id_type` as an identifier type is not by accident. In fact,
|
||||
The example uses the `EnTT` hashed string to generate an identifier for the
|
||||
task.<br/>
|
||||
Indeed, the use of `id_type` as an identifier type isn't by accident. In fact,
|
||||
it matches well with the internal hashed string class. Moreover, it's also the
|
||||
same type returned by the hash function of the internal RTTI system, in case the
|
||||
user wants to rely on that.<br/>
|
||||
However, being an integral value, it leaves the user full freedom to rely on his
|
||||
own tools if he deems it necessary.
|
||||
own tools if necessary.
|
||||
|
||||
Once a task has been associated with the flow builder, it can be assigned
|
||||
read-only or read-write resources, as appropriate:
|
||||
Once a task is associated with the flow builder, it's also assigned read-only or
|
||||
read-write resources as appropriate:
|
||||
|
||||
```cpp
|
||||
builder
|
||||
@ -203,13 +202,87 @@ builder
|
||||
|
||||
As mentioned, many functions return the builder itself and it's therefore easy
|
||||
to concatenate the different calls.<br/>
|
||||
Also in the case of resources, these are identified by numeric values of type
|
||||
Also in the case of resources, they are identified by numeric values of type
|
||||
`id_type`. As above, the choice is not entirely random. This goes well with the
|
||||
tools offered by the library while leaving room for maximum flexibility.
|
||||
|
||||
Finally, both the `ro` and` rw` functions also offer an overload that accepts a
|
||||
pair of iterators, so that one can pass a range of resources in one go.
|
||||
|
||||
### Rebinding
|
||||
|
||||
The `flow` class is resource based rather than task based. This means that graph
|
||||
generation is driven by resources and not by the order of _appearance_ of tasks
|
||||
during flow definition.<br/>
|
||||
Although this concept is particularly important, it's almost irrelevant for the
|
||||
vast majority of cases. However, it becomes relevant when _rebinding_ resources
|
||||
or tasks.
|
||||
|
||||
In fact, nothing prevents rebinding elements to a flow.<br/>
|
||||
However, the behavior changes slightly from case to case and has some nuances
|
||||
that it's worth knowing about.
|
||||
|
||||
Directly rebinding a resource without the task being replaced trivially results
|
||||
in the task's access mode for that resource being updated:
|
||||
|
||||
```cpp
|
||||
builder.bind("task"_hs).rw("resource"_hs).ro("resource"_hs)
|
||||
```
|
||||
|
||||
In this case, the resource is accessed in read-only mode, regardless of the
|
||||
first call to `rw`.<br/>
|
||||
Behind the scenes, the call doesn't actually _replace_ the previous one but is
|
||||
queued after it instead, overwriting it when generating the graph. Thus, a large
|
||||
number of resource rebindings may even impact processing times (very difficult
|
||||
to observe but theoretically possible).
|
||||
|
||||
Rebinding resources and also combining it with changes to tasks has far more
|
||||
implications instead.<br/>
|
||||
As mentioned, graph generation takes place starting from resources and not from
|
||||
tasks. Therefore, the result may not be as expected:
|
||||
|
||||
```cpp
|
||||
builder
|
||||
.bind("task_1"_hs)
|
||||
.ro("resource"_hs)
|
||||
.bind("task_2"_hs)
|
||||
.ro("resource"_hs)
|
||||
.bind("task_1"_hs)
|
||||
.rw("resource"_hs);
|
||||
```
|
||||
|
||||
What happens here is that the resource first _sees_ a read-only access request
|
||||
from the first task, then a read-write request from the second task and finally
|
||||
a new read-only request from the first task.<br/>
|
||||
Although this definition would probably be counted as an error, the resulting
|
||||
graph may be unexpected. This in fact consists of a single edge outgoing from
|
||||
the second task and directed to the first task.<br/>
|
||||
To intuitively understand what happens, it's enough to think of the fact that a
|
||||
task never has an edge pointing to itself.
|
||||
|
||||
While not obvious, this approach has its pros and cons like any other solution.
|
||||
For example, creating loops is actually simple in the context of resource-based
|
||||
graph generations:
|
||||
|
||||
```cpp
|
||||
builder
|
||||
.bind("task_1"_hs)
|
||||
.rw("resource"_hs)
|
||||
.bind("task_2"_hs)
|
||||
.rw("resource"_hs)
|
||||
.bind("task_1"_hs)
|
||||
.rw("resource"_hs);
|
||||
```
|
||||
|
||||
As expected, this definition leads to the creation of two edges that define a
|
||||
loop between the two tasks.
|
||||
|
||||
As a general rule, rebinding resources and tasks is highly discouraged because
|
||||
it could lead to subtle bugs if users don't know what they're doing.<br/>
|
||||
However, once the mechanisms of resource-based graph generation are understood,
|
||||
it can offer to the expert user a flexibility and a range of possibilities
|
||||
otherwise inaccessible.
|
||||
|
||||
## Fake resources and order of execution
|
||||
|
||||
The flow builder doesn't offer the ability to specify when a task should execute
|
||||
@ -217,10 +290,10 @@ before or after another task.<br/>
|
||||
In fact, the order of _registration_ on the resources also determines the order
|
||||
in which the tasks are processed during the generation of the execution graph.
|
||||
|
||||
However, there is a way to force the execution order of two processes.<br/>
|
||||
However, there is a way to _force_ the execution order of two processes.<br/>
|
||||
Briefly, since accessing a resource in opposite modes requires sequential rather
|
||||
than parallel scheduling, it's possible to make use of fake resources to force
|
||||
the order execution:
|
||||
than parallel scheduling, it's possible to make use of fake resources to rule on
|
||||
the execution order:
|
||||
|
||||
```cpp
|
||||
builder
|
||||
@ -235,10 +308,10 @@ builder
|
||||
.ro("fake"_hs)
|
||||
```
|
||||
|
||||
This snippet forces the execution of `task_2` and `task_3` **after** `task_1`.
|
||||
This is due to the fact that the latter sets a read-write requirement on a fake
|
||||
This snippet forces the execution of `task_1` **before** `task_2` and `task_3`.
|
||||
This is due to the fact that the former sets a read-write requirement on a fake
|
||||
resource that the other tasks also want to access in read-only mode.<br/>
|
||||
Similarly, it's possible to force a task to run after a certain group:
|
||||
Similarly, it's possible to force a task to run **after** a certain group:
|
||||
|
||||
```cpp
|
||||
builder
|
||||
@ -261,7 +334,7 @@ others tasks.
|
||||
|
||||
Sometimes it's useful to assign the role of _sync point_ to a node.<br/>
|
||||
Whether it accesses new resources or is simply a watershed, the procedure for
|
||||
assigning this role to a vertex is always the same: first it's tied to the flow
|
||||
assigning this role to a vertex is always the same. First it's tied to the flow
|
||||
builder, then the `sync` function is invoked:
|
||||
|
||||
```cpp
|
||||
@ -283,7 +356,7 @@ all specified constraints to return the best scheduling for the vertices:
|
||||
entt::adjacency_matrix<entt::directed_tag> graph = builder.graph();
|
||||
```
|
||||
|
||||
The search for the main vertices, that is those without in-edges, is usually the
|
||||
Searching for the main vertices (that is, those without in-edges) is usually the
|
||||
first thing required:
|
||||
|
||||
```cpp
|
||||
@ -294,6 +367,6 @@ for(auto &&vertex: graph) {
|
||||
}
|
||||
```
|
||||
|
||||
Starting from them, using the other functions appropriately (such as `out_edges`
|
||||
to retrieve the children of a given task or `edges` to access their identifiers)
|
||||
it will be possible to instantiate an execution graph.
|
||||
Then it's possible to instantiate an execution graph by means of other functions
|
||||
such as `out_edges` to retrieve the children of a given task or `edges` to get
|
||||
the identifiers.
|
||||
|
@ -19,14 +19,12 @@
|
||||
general and on GNU/Linux when default visibility was set to hidden. The
|
||||
limitation was mainly due to a custom utility used to assign unique, sequential
|
||||
identifiers with different types.<br/>
|
||||
Fortunately, nowadays using `EnTT` across boundaries is much easier.
|
||||
Fortunately, nowadays `EnTT` works smoothly across boundaries.
|
||||
|
||||
## Smooth until proven otherwise
|
||||
|
||||
Many classes in `EnTT` make extensive use of type erasure for their purposes.
|
||||
This isn't a problem on itself (in fact, it's the basis of an API so convenient
|
||||
to use). However, a way is needed to recognize the objects whose type has been
|
||||
erased on the other side of a boundary.<br/>
|
||||
This raises the need to identify objects whose type has been erased.<br/>
|
||||
The `type_hash` class template is how identifiers are generated and thus made
|
||||
available to the rest of the library. In general, this class doesn't arouse much
|
||||
interest. The only exception is when a conflict between identifiers occurs
|
||||
@ -36,13 +34,13 @@ The section dedicated to `type_info` contains all the details to get around the
|
||||
issue in a concise and elegant way. Please refer to the specific documentation.
|
||||
|
||||
When working with linked libraries, compile definitions `ENTT_API_EXPORT` and
|
||||
`ENTT_API_IMPORT` can be used where there is a need to import or export symbols,
|
||||
so as to make everything work nicely across boundaries.<br/>
|
||||
`ENTT_API_IMPORT` are to import or export symbols, so as to make everything work
|
||||
nicely across boundaries.<br/>
|
||||
On the other hand, everything should run smoothly when working with plugins or
|
||||
shared libraries that don't export any symbols.
|
||||
|
||||
For anyone who needs more details, the test suite contains multiple examples
|
||||
covering the most common cases (see the `lib` directory for all details).<br/>
|
||||
For those who need more details, the test suite contains many examples covering
|
||||
the most common cases (see the `lib` directory for all details).<br/>
|
||||
It goes without saying that it's impossible to cover **all** possible cases.
|
||||
However, what is offered should hopefully serve as a basis for all of them.
|
||||
|
||||
@ -70,8 +68,8 @@ entt::locator<entt::meta_ctx>::reset(handle);
|
||||
|
||||
From now on, both spaces refer to the same context and on it are attached all
|
||||
new meta types, no matter where they are created.<br/>
|
||||
Note that resetting the main context doesn't also propagate changes across
|
||||
boundaries. In other words, resetting a context results in the decoupling of the
|
||||
Note that _replacing_ the main context doesn't also propagate changes across
|
||||
boundaries. In other words, replacing a context results in the decoupling of the
|
||||
two sides and therefore a divergence in the contents.
|
||||
|
||||
## Memory Management
|
||||
|
@ -1,5 +1,22 @@
|
||||
# EnTT in Action
|
||||
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
# Table of Contents
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [EnTT in Action](#entt-in-action)
|
||||
* [Games](#games)
|
||||
* [Engines and the like](#engines-and-the-like)
|
||||
* [Articles, videos and blog posts](#articles-videos-and-blog-posts)
|
||||
* [Any Other Business](#any-other-business)
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
|
||||
# Introduction
|
||||
|
||||
`EnTT` is widely used in private and commercial applications. I cannot even
|
||||
mention most of them because of some signatures I put on some documents time
|
||||
ago. Fortunately, there are also people who took the time to implement open
|
||||
@ -7,13 +24,18 @@ source projects based on `EnTT` and didn't hold back when it came to documenting
|
||||
them.
|
||||
|
||||
Below an incomplete list of games, applications and articles that can be used as
|
||||
a reference. Where I put the word _apparently_ means that the use of `EnTT` is
|
||||
documented but the authors didn't make explicit announcements or contacted me
|
||||
directly.
|
||||
a reference.<br/>
|
||||
Where I put the word _apparently_ means that the use of `EnTT` is documented but
|
||||
the authors didn't make explicit announcements or contacted me directly.
|
||||
|
||||
I hope this list can grow much more in the future:
|
||||
If you know of other resources out there that are about `EnTT`, feel free to
|
||||
open an issue or a PR and I'll be glad to add them to this page.<br/>
|
||||
I hope the following lists can grow much more in the future.
|
||||
|
||||
# EnTT in Action
|
||||
|
||||
## Games
|
||||
|
||||
* Games:
|
||||
* [Minecraft](https://minecraft.net/en-us/attribution/) by
|
||||
[Mojang](https://mojang.com/): of course, **that** Minecraft, see the
|
||||
open source attributions page for more details.
|
||||
@ -103,7 +125,8 @@ I hope this list can grow much more in the future:
|
||||
* [Confetti Party](https://github.com/hexerei/entt-confetti): C++ sample
|
||||
application as a starting point using `EnTT` and `SDL2`.
|
||||
|
||||
* Engines and the like:
|
||||
## Engines and the like:
|
||||
|
||||
* [Aether Engine](https://hadean.com/spatial-simulation/)
|
||||
[v1.1+](https://docs.hadean.com/v1.1/Licenses/) by
|
||||
[Hadean](https://hadean.com/): a library designed for spatially partitioning
|
||||
@ -168,8 +191,19 @@ I hope this list can grow much more in the future:
|
||||
engine based on `SDL2` and `EnTT`.
|
||||
* [Ducktape](https://github.com/DucktapeEngine/Ducktape): an open source C++
|
||||
2D & 3D game engine that focuses on being fast and powerful.
|
||||
* [The Worst Engine](https://github.com/Parasik72/TWE): a game engine based on
|
||||
OpenGL.
|
||||
* [Ecsact](https://ecsact.dev/): a language aimed at describing ECS, with a
|
||||
[runtime implementation](https://github.com/ecsact-dev/ecsact_rt_entt) based
|
||||
on `EnTT`.
|
||||
* [AGE (Arc Game Engine)](https://github.com/MohitSethi99/ArcGameEngine): an
|
||||
open-source engine for building 2D & 3D real-time rendering and interactive
|
||||
contents.
|
||||
* [Kengine](https://github.com/phisko/kengine): the _Koala engine_ is a game
|
||||
engine entirely implemented as an entity-component-ystem.
|
||||
|
||||
## Articles, videos and blog posts:
|
||||
|
||||
* Articles, videos and blog posts:
|
||||
* [Some posts](https://skypjack.github.io/tags/#entt) on my personal
|
||||
[blog](https://skypjack.github.io/) are about `EnTT`, for those who want to
|
||||
know **more** on this project.
|
||||
@ -193,6 +227,12 @@ I hope this list can grow much more in the future:
|
||||
- ... And so on.
|
||||
[Check out](https://www.youtube.com/channel/UCQ-W1KE9EYfdxhL6S4twUNw) the
|
||||
_Game Engine Series_ by The Cherno for more videos.
|
||||
* [Warmonger Dynasty devlog series](https://david-delassus.medium.com/list/warmonger-dynasty-devlogs-f64b71f556de)
|
||||
by [linkdd](https://github.com/linkdd): an interesting walkthrough of
|
||||
developing a game (also) with EnTT.
|
||||
* [Use EnTT When You Need An ECS](https://www.codingwiththomas.com/blog/use-entt-when-you-need-an-ecs)
|
||||
by [Thomas](https://www.codingwiththomas.com/): I couldn't have said it
|
||||
better.
|
||||
* [Space Battle: Huge edition](http://victor.madtriangles.com/code%20experiment/2018/06/11/post-ecs-battle-huge.html):
|
||||
huge space battle built entirely from scratch.
|
||||
* [Space Battle](https://github.com/vblanco20-1/ECS_SpaceBattle): huge space
|
||||
@ -219,7 +259,8 @@ I hope this list can grow much more in the future:
|
||||
MMO(RPG)s and its [follow-up](https://youtu.be/yGlZeopx2hU) episode about
|
||||
player bots and full external ECS: a series definitely worth looking at.
|
||||
|
||||
* Any Other Business:
|
||||
## Any Other Business:
|
||||
|
||||
* [ArcGIS Runtime SDKs](https://developers.arcgis.com/arcgis-runtime/) by
|
||||
[Esri](https://www.esri.com/): they use `EnTT` for the internal ECS and the
|
||||
cross platform C++ rendering engine. The SDKs are utilized by a lot of
|
||||
@ -261,6 +302,3 @@ I hope this list can grow much more in the future:
|
||||
* GitHub contains also
|
||||
[many other examples](https://github.com/search?o=desc&q=%22skypjack%2Fentt%22&s=indexed&type=Code)
|
||||
of use of `EnTT` from which to take inspiration if interested.
|
||||
|
||||
If you know of other resources out there that are about `EnTT`, feel free to
|
||||
open an issue or a PR and I'll be glad to add them to this page.
|
||||
|
420
docs/md/meta.md
420
docs/md/meta.md
@ -67,17 +67,15 @@ recommended.
|
||||
|
||||
# Reflection in a nutshell
|
||||
|
||||
Reflection always starts from real types (users cannot reflect imaginary types
|
||||
and it would not make much sense, we wouldn't be talking about reflection
|
||||
anymore).<br/>
|
||||
To create a meta node, the library provides the `meta` function that accepts a
|
||||
type to reflect as a template parameter:
|
||||
Reflection always starts from actual C++ types. Users cannot reflect _imaginary_
|
||||
types.<br/>
|
||||
The `meta` function is where it all starts:
|
||||
|
||||
```cpp
|
||||
auto factory = entt::meta<my_type>();
|
||||
```
|
||||
|
||||
The returned value is a factory object to use to continue building the meta
|
||||
The returned value is a _factory object_ to use to continue building the meta
|
||||
type.
|
||||
|
||||
By default, a meta type is associated with the identifier returned by the
|
||||
@ -88,45 +86,42 @@ However, it's also possible to assign custom identifiers to meta types:
|
||||
auto factory = entt::meta<my_type>().type("reflected_type"_hs);
|
||||
```
|
||||
|
||||
Identifiers are important because users can retrieve meta types at runtime by
|
||||
searching for them by _name_ other than by type.<br/>
|
||||
On the other hand, there are cases in which users can be interested in adding
|
||||
features to a reflected type so that the reflection system can use it correctly
|
||||
under the hood, but they don't want to also make the type _searchable_. In this
|
||||
case, it's sufficient not to invoke `type`.
|
||||
Identifiers are used to _retrieve_ meta types at runtime by _name_ other than by
|
||||
type.<br/>
|
||||
However, users can be interested in adding features to a reflected type so that
|
||||
the reflection system can use it correctly under the hood, while they don't want
|
||||
to also make the type _searchable_. In this case, it's sufficient not to invoke
|
||||
`type`.
|
||||
|
||||
A factory is such that all its member functions return the factory itself or a
|
||||
decorated version of it. This object can be used to add the following:
|
||||
A factory is such that all its member functions return the factory itself. It's
|
||||
generally used to create the following:
|
||||
|
||||
* _Constructors_. Actual constructors can be assigned to a reflected type by
|
||||
specifying their list of arguments. Free functions (namely, factories) can be
|
||||
used as well, as long as the return type is the expected one. From a client's
|
||||
point of view, nothing changes if a constructor is a free function or an
|
||||
actual constructor.<br/>
|
||||
Use the `ctor` member function for this purpose:
|
||||
* _Constructors_. A constructors is assigned to a reflected type by specifying
|
||||
its _list of arguments_. Free functions are also accepted if the return type
|
||||
is the expected one. From a client perspective, nothing changes between a free
|
||||
function or an actual constructor:
|
||||
|
||||
```cpp
|
||||
entt::meta<my_type>().ctor<int, char>().ctor<&factory>();
|
||||
```
|
||||
|
||||
* _Destructors_. Free functions and member functions can be used as destructors
|
||||
of reflected types. The purpose is to give users the ability to free up
|
||||
resources that require special treatment before an object is actually
|
||||
destroyed.<br/>
|
||||
Use the `dtor` member function for this purpose:
|
||||
Meta default constructors are implicitly generated, if possible.
|
||||
|
||||
* _Destructors_. Both free functions and member functions are valid destructors:
|
||||
|
||||
```cpp
|
||||
entt::meta<my_type>().dtor<&destroy>();
|
||||
```
|
||||
|
||||
The purpose is to offer the possibility to free up resources that require
|
||||
_special treatment_ before an object is actually destroyed.<br/>
|
||||
A function should neither delete nor explicitly invoke the destructor of a
|
||||
given instance.
|
||||
|
||||
* _Data members_. Both real data members of the underlying type and static and
|
||||
global variables, as well as constants of any kind, can be attached to a meta
|
||||
type. From the point of view of the client, all the variables associated with
|
||||
the reflected type will appear as if they were part of the type itself.<br/>
|
||||
Use the `data` member function for this purpose:
|
||||
* _Data members_. Meta data members are actual data members of the underlying
|
||||
type but also static and global variables or constants of any kind. From the
|
||||
point of view of the client, all the variables associated with the reflected
|
||||
type appear as if they were part of the type itself:
|
||||
|
||||
```cpp
|
||||
entt::meta<my_type>()
|
||||
@ -135,13 +130,11 @@ decorated version of it. This object can be used to add the following:
|
||||
.data<&global_variable>("global"_hs);
|
||||
```
|
||||
|
||||
The function requires as an argument the identifier to give to the meta data
|
||||
once created. Users can then access meta data at runtime by searching for them
|
||||
by _name_.<br/>
|
||||
Data members can also be defined by means of a setter and getter pair. Setters
|
||||
and getters can be either free functions, class members or a mix of them, as
|
||||
long as they respect the required signatures. This approach is also convenient
|
||||
to create a read-only variable from a non-const data member:
|
||||
The `data` function requires the identifier to use for the meta data member.
|
||||
Users can then access it by _name_ at runtime.<br/>
|
||||
Data members are also defined by means of a setter and getter pair. These are
|
||||
either free functions, class members or a mix of them. This approach is also
|
||||
convenient to create read-only properties from a non-const data member:
|
||||
|
||||
```cpp
|
||||
entt::meta<my_type>().data<nullptr, &my_type::data_member>("member"_hs);
|
||||
@ -153,13 +146,10 @@ decorated version of it. This object can be used to add the following:
|
||||
entt::meta<my_type>().data<entt::value_list<&from_int, &from_string>, &my_type::data_member>("member"_hs);
|
||||
```
|
||||
|
||||
Refer to the inline documentation for all the details.
|
||||
|
||||
* _Member functions_. Both real member functions of the underlying type and free
|
||||
functions can be attached to a meta type. From the point of view of the
|
||||
client, all the functions associated with the reflected type will appear as if
|
||||
they were part of the type itself.<br/>
|
||||
Use the `func` member function for this purpose:
|
||||
* _Member functions_. Meta member functions are actual member functions of the
|
||||
underlying type but also plain free functions. From the point of view of the
|
||||
client, all the functions associated with the reflected type appear as if they
|
||||
were part of the type itself:
|
||||
|
||||
```cpp
|
||||
entt::meta<my_type>()
|
||||
@ -168,40 +158,31 @@ decorated version of it. This object can be used to add the following:
|
||||
.func<&free_function>("free"_hs);
|
||||
```
|
||||
|
||||
The function requires as an argument the identifier to give to the meta
|
||||
function once created. Users can then access meta functions at runtime by
|
||||
searching for them by _name_.<br/>
|
||||
The `func` function requires the identifier to use for the meta data function.
|
||||
Users can then access it by _name_ at runtime.<br/>
|
||||
Overloading of meta functions is supported. Overloaded functions are resolved
|
||||
at runtime by the reflection system according to the types of the arguments.
|
||||
|
||||
* _Base classes_. A base class is such that the underlying type is actually
|
||||
derived from it. In this case, the reflection system tracks the relationship
|
||||
and allows for implicit casts at runtime when required.<br/>
|
||||
Use the `base` member function for this purpose:
|
||||
derived from it:
|
||||
|
||||
```cpp
|
||||
entt::meta<derived_type>().base<base_type>();
|
||||
```
|
||||
|
||||
From now on, wherever a `base_type` is required, an instance of `derived_type`
|
||||
will also be accepted.
|
||||
The reflection system tracks the relationship and allows for implicit casts at
|
||||
runtime when required. In other terms, wherever a `base_type` is required, an
|
||||
instance of `derived_type` is also accepted.
|
||||
|
||||
* _Conversion functions_. Actual types can be converted, this is a fact. Just
|
||||
think of the relationship between a `double` and an `int` to see it. Similar
|
||||
to bases, conversion functions allow users to define conversions that will be
|
||||
implicitly performed by the reflection system when required.<br/>
|
||||
Use the `conv` member function for this purpose:
|
||||
* _Conversion functions_. Conversion functions allow users to define conversions
|
||||
that are implicitly performed by the reflection system when required:
|
||||
|
||||
```cpp
|
||||
entt::meta<double>().conv<int>();
|
||||
```
|
||||
|
||||
That's all, everything users need to create meta types and enjoy the reflection
|
||||
system. At first glance it may not seem that much, but users usually learn to
|
||||
appreciate it over time.<br/>
|
||||
Also, do not forget what these few lines hide under the hood: a built-in,
|
||||
non-intrusive and macro-free system for reflection in C++. Features that are
|
||||
definitely worth the price, at least for me.
|
||||
This is everything users need to create meta types. Refer to the inline
|
||||
documentation for further details.
|
||||
|
||||
## Any to the rescue
|
||||
|
||||
@ -214,13 +195,13 @@ The API is very similar to that of the `any` type. The class `meta_any` _wraps_
|
||||
many of the feature to infer a meta node, before forwarding some or all of the
|
||||
arguments to the underlying storage.<br/>
|
||||
Among the few relevant differences, `meta_any` adds support for containers and
|
||||
pointer-like types (see the following sections for more details), while `any`
|
||||
does not.<br/>
|
||||
Similar to `any`, this class can also be used to create _aliases_ for unmanaged
|
||||
pointer-like types, while `any` doesn't.<br/>
|
||||
Similar to `any`, this class is also used to create _aliases_ for unmanaged
|
||||
objects either with `forward_as_meta` or using the `std::in_place_type<T &>`
|
||||
disambiguation tag, as well as from an existing object by means of the `as_ref`
|
||||
member function. However, unlike `any`, `meta_any` treats an empty instance and
|
||||
one initialized with `void` differently:
|
||||
member function.<br/>
|
||||
Unlike `any` instead, `meta_any` treats an empty instance and one initialized
|
||||
with `void` differently:
|
||||
|
||||
```cpp
|
||||
entt::meta_any empty{};
|
||||
@ -229,21 +210,19 @@ entt::meta_any other{std::in_place_type<void>};
|
||||
|
||||
While `any` considers both as empty, `meta_any` treats objects initialized with
|
||||
`void` as if they were _valid_ ones. This allows to differentiate between failed
|
||||
function calls and function calls that are successful but return nothing.<br/>
|
||||
function calls and function calls that are successful but return nothing.
|
||||
|
||||
Finally, the member functions `try_cast`, `cast` and `allow_cast` are used to
|
||||
cast the underlying object to a given type (either a reference or a value type)
|
||||
or to _convert_ a `meta_any` in such a way that a cast becomes viable for the
|
||||
resulting object. There is in fact no `any_cast` equivalent for `meta_any`.
|
||||
resulting object.<br/>
|
||||
There is in fact no `any_cast` equivalent for `meta_any`.
|
||||
|
||||
## Enjoy the runtime
|
||||
|
||||
Once the web of reflected types has been constructed, it's a matter of using it
|
||||
at runtime where required.<br/>
|
||||
All this has the great merit that the reflection system stands in fact as a
|
||||
non-intrusive tool for the runtime, unlike the vast majority of the things
|
||||
offered by this library and closely linked to the compile-time.
|
||||
|
||||
To search for a reflected type there are a few options:
|
||||
Once the web of reflected types is constructed, it's a matter of using it at
|
||||
runtime where required.<br/>
|
||||
There are a few options to search for a reflected type:
|
||||
|
||||
```cpp
|
||||
// direct access to a reflected type
|
||||
@ -257,8 +236,8 @@ auto by_type_id = entt::resolve(entt::type_id<my_type>());
|
||||
```
|
||||
|
||||
There exists also an overload of the `resolve` function to use to iterate all
|
||||
the reflected types at once. It returns an iterable object that can be used in a
|
||||
range-for loop:
|
||||
reflected types at once. It returns an iterable object to be used in a range-for
|
||||
loop:
|
||||
|
||||
```cpp
|
||||
for(auto &&[id, type]: entt::resolve()) {
|
||||
@ -270,9 +249,7 @@ In all cases, the returned value is an instance of `meta_type` (possibly with
|
||||
its id). This kind of objects offer an API to know their _runtime identifiers_,
|
||||
to iterate all the meta objects associated with them and even to build instances
|
||||
of the underlying type.<br/>
|
||||
Refer to the inline documentation for all the details.
|
||||
|
||||
Meta data members and functions are accessed by name among the other things:
|
||||
Meta data members and functions are accessed by name:
|
||||
|
||||
* Meta data members:
|
||||
|
||||
@ -297,11 +274,11 @@ Meta data members and functions are accessed by name among the other things:
|
||||
A meta function object offers an API to query the underlying type (for
|
||||
example, to know if it's a const or a static function), to know the number of
|
||||
arguments, the meta return type and the meta types of the parameters. In
|
||||
addition, a meta function object can be used to invoke the underlying function
|
||||
and then get the return value in the form of a `meta_any` object.
|
||||
addition, a meta function object is used to invoke the underlying function and
|
||||
then get the return value in the form of a `meta_any` object.
|
||||
|
||||
All the meta objects thus obtained as well as the meta types can be explicitly
|
||||
converted to a boolean value to check if they are valid:
|
||||
All the meta objects thus obtained as well as the meta types explicitly convert
|
||||
to a boolean value to check for validity:
|
||||
|
||||
```cpp
|
||||
if(auto func = entt::resolve<my_type>().func("member"_hs); func) {
|
||||
@ -319,26 +296,23 @@ for(auto &&[id, type]: entt::resolve<my_type>().base()) {
|
||||
}
|
||||
```
|
||||
|
||||
A meta type can also be used to `construct` actual instances of the underlying
|
||||
Meta type are also used to `construct` actual instances of the underlying
|
||||
type.<br/>
|
||||
In particular, the `construct` member function accepts a variable number of
|
||||
arguments and searches for a match. It then returns a `meta_any` object that may
|
||||
or may not be initialized, depending on whether a suitable constructor has been
|
||||
found or not.
|
||||
or may not be initialized, depending on whether a suitable constructor was found
|
||||
or not.
|
||||
|
||||
There is no object that wraps the destructor of a meta type nor a `destroy`
|
||||
member function in its API. Destructors are invoked implicitly by `meta_any`
|
||||
behind the scenes and users have not to deal with them explicitly. Furthermore,
|
||||
they have no name, cannot be searched and wouldn't have member functions to
|
||||
expose anyway.<br/>
|
||||
Similarly, conversion functions aren't directly accessible. They are used
|
||||
they've no name, cannot be searched and wouldn't have member functions to expose
|
||||
anyway.<br/>
|
||||
Similarly, conversion functions aren't directly accessible. They're used
|
||||
internally by `meta_any` and the meta objects when needed.
|
||||
|
||||
Meta types and meta objects in general contain much more than what is said: a
|
||||
plethora of functions in addition to those listed whose purposes and uses go
|
||||
unfortunately beyond the scope of this document.<br/>
|
||||
I invite anyone interested in the subject to look at the code, experiment and
|
||||
read the inline documentation to get the best out of this powerful tool.
|
||||
Meta types and meta objects in general contain much more than what was said.
|
||||
Refer to the inline documentation for further details.
|
||||
|
||||
## Container support
|
||||
|
||||
@ -349,7 +323,7 @@ meta system in many cases.
|
||||
|
||||
To make a container be recognized as such by the meta system, users are required
|
||||
to provide specializations for either the `meta_sequence_container_traits` class
|
||||
or the `meta_associative_container_traits` class, according to the actual type
|
||||
or the `meta_associative_container_traits` class, according to the actual _type_
|
||||
of the container.<br/>
|
||||
`EnTT` already exports the specializations for some common classes. In
|
||||
particular:
|
||||
@ -386,11 +360,10 @@ if(any.type().is_sequence_container()) {
|
||||
|
||||
The method to use to get a proxy object for associative containers is
|
||||
`as_associative_container` instead.<br/>
|
||||
It goes without saying that it's not necessary to perform a double check.
|
||||
Instead, it's sufficient to query the meta type or verify that the proxy object
|
||||
is valid. In fact, proxies are contextually convertible to bool to know if they
|
||||
are valid. For example, invalid proxies are returned when the wrapped object
|
||||
isn't a container.<br/>
|
||||
It's not necessary to perform a double check actually. Instead, it's enough to
|
||||
query the meta type or verify that the proxy object is valid. In fact, proxies
|
||||
are contextually convertible to bool to check for validity. For example, invalid
|
||||
proxies are returned when the wrapped object isn't a container.<br/>
|
||||
In all cases, users aren't expected to _reflect_ containers explicitly. It's
|
||||
sufficient to assign a container for which a specialization of the traits
|
||||
classes exists to a `meta_any` object to be able to get its proxy object.
|
||||
@ -402,32 +375,18 @@ to case. In particular:
|
||||
* The `value_type` member function returns the meta type of the elements.
|
||||
|
||||
* The `size` member function returns the number of elements in the container as
|
||||
an unsigned integer value:
|
||||
|
||||
```cpp
|
||||
const auto size = view.size();
|
||||
```
|
||||
an unsigned integer value.
|
||||
|
||||
* The `resize` member function allows to resize the wrapped container and
|
||||
returns true in case of success:
|
||||
|
||||
```cpp
|
||||
const bool ok = view.resize(3u);
|
||||
```
|
||||
|
||||
returns true in case of success.<br/>
|
||||
For example, it's not possible to resize fixed size containers.
|
||||
|
||||
* The `clear` member function allows to clear the wrapped container and returns
|
||||
true in case of success:
|
||||
|
||||
```cpp
|
||||
const bool ok = view.clear();
|
||||
```
|
||||
|
||||
true in case of success.<br/>
|
||||
For example, it's not possible to clear fixed size containers.
|
||||
|
||||
* The `begin` and `end` member functions return opaque iterators that can be
|
||||
used to iterate the container directly:
|
||||
* The `begin` and `end` member functions return opaque iterators that is used to
|
||||
iterate the container directly:
|
||||
|
||||
```cpp
|
||||
for(entt::meta_any element: view) {
|
||||
@ -441,7 +400,7 @@ to case. In particular:
|
||||
All meta iterators are input iterators and don't offer an indirection operator
|
||||
on purpose.
|
||||
|
||||
* The `insert` member function can be used to add elements to the container. It
|
||||
* The `insert` member function is used to add elements to the container. It
|
||||
accepts a meta iterator and the element to insert:
|
||||
|
||||
```cpp
|
||||
@ -451,15 +410,15 @@ to case. In particular:
|
||||
```
|
||||
|
||||
This function returns a meta iterator pointing to the inserted element and a
|
||||
boolean value to indicate whether the operation was successful or not. Note
|
||||
that a call to `insert` may silently fail in case of fixed size containers or
|
||||
whether the arguments aren't at least convertible to the required types.<br/>
|
||||
Since the meta iterators are contextually convertible to bool, users can rely
|
||||
on them to know if the operation has failed on the actual container or
|
||||
upstream, for example for an argument conversion problem.
|
||||
boolean value to indicate whether the operation was successful or not. A call
|
||||
to `insert` may silently fail in case of fixed size containers or whether the
|
||||
arguments aren't at least convertible to the required types.<br/>
|
||||
Since meta iterators are contextually convertible to bool, users can rely on
|
||||
them to know if the operation failed on the actual container or upstream, for
|
||||
example due to an argument conversion problem.
|
||||
|
||||
* The `erase` member function can be used to remove elements from the container.
|
||||
It accepts a meta iterator to the element to remove:
|
||||
* The `erase` member function is used to remove elements from the container. It
|
||||
accepts a meta iterator to the element to remove:
|
||||
|
||||
```cpp
|
||||
auto first = view.begin();
|
||||
@ -468,11 +427,11 @@ to case. In particular:
|
||||
```
|
||||
|
||||
This function returns a meta iterator following the last removed element and a
|
||||
boolean value to indicate whether the operation was successful or not. Note
|
||||
that a call to `erase` may silently fail in case of fixed size containers.
|
||||
boolean value to indicate whether the operation was successful or not. A call
|
||||
to `erase` may silently fail in case of fixed size containers.
|
||||
|
||||
* The `operator[]` can be used to access elements in a container. It accepts a
|
||||
single argument, that is the position of the element to return:
|
||||
* The `operator[]` is used to access container elements. It accepts a single
|
||||
argument, the position of the element to return:
|
||||
|
||||
```cpp
|
||||
for(std::size_t pos{}, last = view.size(); pos < last; ++pos) {
|
||||
@ -482,8 +441,8 @@ to case. In particular:
|
||||
```
|
||||
|
||||
The function returns instances of `meta_any` that directly refer to the actual
|
||||
elements. Modifying the returned object will then directly modify the element
|
||||
inside the container.<br/>
|
||||
elements. Modifying the returned object directly modifies the element inside
|
||||
the container.<br/>
|
||||
Depending on the underlying sequence container, this operation may not be as
|
||||
efficient. For example, in the case of an `std::list`, a positional access
|
||||
translates to a linear visit of the list itself (probably not what the user
|
||||
@ -508,21 +467,13 @@ differences in behavior in the case of key-only containers. In particular:
|
||||
`std::map<int, char>`.
|
||||
|
||||
* The `size` member function returns the number of elements in the container as
|
||||
an unsigned integer value:
|
||||
|
||||
```cpp
|
||||
const auto size = view.size();
|
||||
```
|
||||
an unsigned integer value.
|
||||
|
||||
* The `clear` member function allows to clear the wrapped container and returns
|
||||
true in case of success:
|
||||
true in case of success.
|
||||
|
||||
```cpp
|
||||
const bool ok = view.clear();
|
||||
```
|
||||
|
||||
* The `begin` and `end` member functions return opaque iterators that can be
|
||||
used to iterate the container directly:
|
||||
* The `begin` and `end` member functions return opaque iterators that are used
|
||||
to iterate the container directly:
|
||||
|
||||
```cpp
|
||||
for(std::pair<entt::meta_any, entt::meta_any> element: view) {
|
||||
@ -539,11 +490,11 @@ differences in behavior in the case of key-only containers. In particular:
|
||||
|
||||
While the accessed key is usually constant in the associative containers and
|
||||
is therefore returned by copy, the value (if any) is wrapped by an instance of
|
||||
`meta_any` that directly refers to the actual element. Modifying it will then
|
||||
directly modify the element inside the container.
|
||||
`meta_any` that directly refers to the actual element. Modifying it directly
|
||||
modifies the element inside the container.
|
||||
|
||||
* The `insert` member function can be used to add elements to the container. It
|
||||
accepts two arguments, respectively the key and the value to be inserted:
|
||||
* The `insert` member function is used to add elements to a container. It gets
|
||||
two arguments, respectively the key and the value to insert:
|
||||
|
||||
```cpp
|
||||
auto last = view.end();
|
||||
@ -552,39 +503,39 @@ differences in behavior in the case of key-only containers. In particular:
|
||||
```
|
||||
|
||||
This function returns a boolean value to indicate whether the operation was
|
||||
successful or not. Note that a call to `insert` may fail when the arguments
|
||||
aren't at least convertible to the required types.
|
||||
successful or not. A call to `insert` may fail when the arguments aren't at
|
||||
least convertible to the required types.
|
||||
|
||||
* The `erase` member function can be used to remove elements from the container.
|
||||
It accepts a single argument, that is the key to be removed:
|
||||
* The `erase` member function is used to remove elements from a container. It
|
||||
gets a single argument, the key to remove:
|
||||
|
||||
```cpp
|
||||
view.erase(42);
|
||||
```
|
||||
|
||||
This function returns a boolean value to indicate whether the operation was
|
||||
successful or not. Note that a call to `erase` may fail when the argument
|
||||
isn't at least convertible to the required type.
|
||||
successful or not. A call to `erase` may fail when the argument isn't at least
|
||||
convertible to the required type.
|
||||
|
||||
* The `operator[]` can be used to access elements in a container. It accepts a
|
||||
single argument, that is the key of the element to return:
|
||||
* The `operator[]` is used to access elements in a container. It gets a single
|
||||
argument, the key of the element to return:
|
||||
|
||||
```cpp
|
||||
entt::meta_any value = view[42];
|
||||
```
|
||||
|
||||
The function returns instances of `meta_any` that directly refer to the actual
|
||||
elements. Modifying the returned object will then directly modify the element
|
||||
inside the container.
|
||||
elements. Modifying the returned object directly modifies the element inside
|
||||
the container.
|
||||
|
||||
Container support is minimal but likely sufficient to satisfy all needs.
|
||||
|
||||
## Pointer-like types
|
||||
|
||||
As with containers, it's also possible to communicate to the meta system which
|
||||
types to consider _pointers_. This will allow to dereference instances of
|
||||
`meta_any`, thus obtaining light _references_ to the pointed objects that are
|
||||
also correctly associated with their meta types.<br/>
|
||||
As with containers, it's also possible to _tell_ to the meta system which types
|
||||
are _pointers_. This makes it possible to dereference instances of `meta_any`,
|
||||
thus obtaining light _references_ to pointed objects that are also correctly
|
||||
associated with their meta types.<br/>
|
||||
To make the meta system recognize a type as _pointer-like_, users can specialize
|
||||
the `is_meta_pointer_like` class. `EnTT` already exports the specializations for
|
||||
some common classes. In particular:
|
||||
@ -614,13 +565,12 @@ if(any.type().is_pointer_like()) {
|
||||
}
|
||||
```
|
||||
|
||||
Of course, it's not necessary to perform a double check. Instead, it's enough to
|
||||
query the meta type or verify that the returned object is valid. For example,
|
||||
invalid instances are returned when the wrapped object isn't a pointer-like
|
||||
type.<br/>
|
||||
Note that dereferencing a pointer-like object returns an instance of `meta_any`
|
||||
which refers to the pointed object and allows users to modify it directly
|
||||
(unless the returned element is const, of course).
|
||||
It's not necessary to perform a double check. Instead, it's enough to query the
|
||||
meta type or verify that the returned object is valid. For example, invalid
|
||||
instances are returned when the wrapped object isn't a pointer-like type.<br/>
|
||||
Dereferencing a pointer-like object returns an instance of `meta_any` which
|
||||
_refers_ to the pointed object. Modifying it means modifying the pointed object
|
||||
directly (unless the returned element is const).
|
||||
|
||||
In general, _dereferencing_ a pointer-like type boils down to a `*ptr`. However,
|
||||
`EnTT` also supports classes that don't offer an `operator*`. In particular:
|
||||
@ -648,12 +598,12 @@ In general, _dereferencing_ a pointer-like type boils down to a `*ptr`. However,
|
||||
};
|
||||
```
|
||||
|
||||
In all other cases, that is, when dereferencing a pointer works as expected and
|
||||
regardless of the pointed type, no user intervention is required.
|
||||
In all other cases and when dereferencing a pointer works as expected regardless
|
||||
of the pointed type, no user intervention is required.
|
||||
|
||||
## Template information
|
||||
|
||||
Meta types also provide a minimal set of information about the nature of the
|
||||
Meta types also provide a minimal set of information about the _nature_ of the
|
||||
original type in case it's a class template.<br/>
|
||||
By default, this works out of the box and requires no user action. However, it's
|
||||
important to include the header file `template.hpp` to make this information
|
||||
@ -688,9 +638,9 @@ template<typename Ret, typename... Args>
|
||||
struct function_type<Ret(Args...)> {};
|
||||
```
|
||||
|
||||
In this case, rather than the function type, the user might want the return type
|
||||
and unpacked arguments as if they were different template parameters for the
|
||||
original class template.<br/>
|
||||
In this case, rather than the function type, it might be useful to provide the
|
||||
return type and unpacked arguments as if they were different template parameters
|
||||
for the original class template.<br/>
|
||||
To achieve this, users must enter the library internals and provide their own
|
||||
specialization for the class template `entt::meta_template_traits`, such as:
|
||||
|
||||
@ -704,8 +654,8 @@ struct entt::meta_template_traits<function_type<Ret(Args...)>> {
|
||||
|
||||
The reflection system doesn't verify the accuracy of the information nor infer a
|
||||
correspondence between real types and meta types.<br/>
|
||||
Therefore, the specialization will be used as is and the information it contains
|
||||
will be associated with the appropriate type when required.
|
||||
Therefore, the specialization is used as is and the information it contains is
|
||||
associated with the appropriate type when required.
|
||||
|
||||
## Automatic conversions
|
||||
|
||||
@ -752,29 +702,29 @@ any.allow_cast(type);
|
||||
int value = any.cast<int>();
|
||||
```
|
||||
|
||||
This should make working with arithmetic types and scoped or unscoped enums as
|
||||
easy as it is in C++.<br/>
|
||||
It's also worth noting that it's still possible to set up conversion functions
|
||||
manually and these will always be preferred over the automatic ones.
|
||||
This makes working with arithmetic types and scoped or unscoped enums as easy as
|
||||
it is in C++.<br/>
|
||||
It's still possible to set up conversion functions manually and these are always
|
||||
preferred over the automatic ones.
|
||||
|
||||
## Implicitly generated default constructor
|
||||
|
||||
In many cases, it's useful to be able to create objects of default constructible
|
||||
types through the reflection system, while not having to explicitly register the
|
||||
meta type or the default constructor.<br/>
|
||||
Creating objects of default constructible types through the reflection system
|
||||
while not having to explicitly register the meta type or its default constructor
|
||||
is also possible.<br/>
|
||||
For example, in the case of primitive types like `int` or `char`, but not just
|
||||
them.
|
||||
|
||||
For this reason and only for default constructible types, default constructors
|
||||
are automatically defined and associated with their meta types, whether they are
|
||||
explicitly or implicitly generated.<br/>
|
||||
For default constructible types only, default constructors are automatically
|
||||
defined and associated with their meta types, whether they are explicitly or
|
||||
implicitly generated.<br/>
|
||||
Therefore, this is all is needed to construct an integer from its meta type:
|
||||
|
||||
```cpp
|
||||
entt::resolve<int>().construct();
|
||||
```
|
||||
|
||||
Where the meta type can be for example the one returned from a meta container,
|
||||
Where the meta type is for example the one returned from a meta container,
|
||||
useful for building keys without knowing or having to register the actual types.
|
||||
|
||||
In all cases, when users register default constructors, they are preferred both
|
||||
@ -783,8 +733,8 @@ during searches and when the `construct` member function is invoked.
|
||||
## From void to any
|
||||
|
||||
Sometimes all a user has is an opaque pointer to an object of a known meta type.
|
||||
It would be handy in this case to be able to construct a `meta_any` object from
|
||||
them.<br/>
|
||||
It would be handy in this case to be able to construct a `meta_any` element from
|
||||
it.<br/>
|
||||
For this purpose, the `meta_type` class offers a `from_void` member function
|
||||
designed to convert an opaque pointer into a `meta_any`:
|
||||
|
||||
@ -792,9 +742,8 @@ designed to convert an opaque pointer into a `meta_any`:
|
||||
entt::meta_any any = entt::resolve(id).from_void(element);
|
||||
```
|
||||
|
||||
It goes without saying that it's not possible to do a check on the actual type.
|
||||
Therefore, this call can be considered as a _static cast_ with all the problems
|
||||
and undefined behaviors of the case following errors.<br/>
|
||||
Unfortunately, it's not possible to do a check on the actual type. Therefore,
|
||||
this call can be considered as a _static cast_ with all its _problems_.<br/>
|
||||
On the other hand, the ability to construct a `meta_any` from an opaque pointer
|
||||
opens the door to some pretty interesting uses that are worth exploring.
|
||||
|
||||
@ -826,17 +775,17 @@ There are a few alternatives available at the moment:
|
||||
entt::meta<my_type>().func<&my_type::member_function, entt::as_void_t>("member"_hs);
|
||||
```
|
||||
|
||||
If the use with functions is obvious, it must be said that it's also possible
|
||||
to use this policy with constructors and data members. In the first case, the
|
||||
constructor will be invoked but the returned wrapper will actually be empty.
|
||||
In the second case, instead, the property will not be accessible for reading.
|
||||
If the use with functions is obvious, perhaps less so is use with constructors
|
||||
and data members. In the first case, the returned wrapper is always empty even
|
||||
though the constructor is still invoked. In the second case, the property
|
||||
isn't accessible for reading instead.
|
||||
|
||||
* The _as-ref_ and _as-cref_ policies, associated with the types
|
||||
`entt::as_ref_t` and `entt::as_cref_t`.<br/>
|
||||
They allow to build wrappers that act as references to unmanaged objects.
|
||||
Accessing the object contained in the wrapper for which the _reference_ was
|
||||
requested will make it possible to directly access the instance used to
|
||||
initialize the wrapper itself:
|
||||
requested makes it possible to directly access the instance used to initialize
|
||||
the wrapper itself:
|
||||
|
||||
```cpp
|
||||
entt::meta<my_type>().data<&my_type::data_member, entt::as_ref_t>("member"_hs);
|
||||
@ -854,21 +803,16 @@ obvious corner cases that can in turn be solved with the use of policies.
|
||||
|
||||
## Named constants and enums
|
||||
|
||||
A special mention should be made for constant values and enums. It wouldn't be
|
||||
necessary, but it will help distracted readers.
|
||||
|
||||
As mentioned, the `data` member function can be used to reflect constants of any
|
||||
type among the other things.<br/>
|
||||
This allows users to create meta types for enums that will work exactly like any
|
||||
other meta type built from a class. Similarly, arithmetic types can be enriched
|
||||
As mentioned, the `data` member function is used to reflect constants of any
|
||||
type.<br/>
|
||||
This allows users to create meta types for enums that work exactly like any
|
||||
other meta type built from a class. Similarly, arithmetic types are _enriched_
|
||||
with constants of special meaning where required.<br/>
|
||||
Personally, I find it very useful not to export what is the difference between
|
||||
enums and classes in C++ directly in the space of the reflected types.
|
||||
All values thus exported appear to users as if they were constant data members
|
||||
of the reflected types. This avoids the need to _export_ what is the difference
|
||||
between enums and classes in C++ directly in the space of the reflected types.
|
||||
|
||||
All the values thus exported will appear to users as if they were constant data
|
||||
members of the reflected types.
|
||||
|
||||
Exporting constant values or elements from an enum is as simple as ever:
|
||||
Exposing constant values or elements from an enum is quite simple:
|
||||
|
||||
```cpp
|
||||
entt::meta<my_enum>()
|
||||
@ -878,28 +822,22 @@ entt::meta<my_enum>()
|
||||
entt::meta<int>().data<2048>("max_int"_hs);
|
||||
```
|
||||
|
||||
It goes without saying that accessing them is trivial as well. It's a matter of
|
||||
doing the following, as with any other data member of a meta type:
|
||||
Accessing them is trivial as well. It's a matter of doing the following, as with
|
||||
any other data member of a meta type:
|
||||
|
||||
```cpp
|
||||
auto value = entt::resolve<my_enum>().data("a_value"_hs).get({}).cast<my_enum>();
|
||||
auto max = entt::resolve<int>().data("max_int"_hs).get({}).cast<int>();
|
||||
```
|
||||
|
||||
As a side note, remember that all this happens behind the scenes without any
|
||||
allocation because of the small object optimization performed by the `meta_any`
|
||||
class.
|
||||
All this happens behind the scenes without any allocation because of the small
|
||||
object optimization performed by the `meta_any` class.
|
||||
|
||||
## Properties and meta objects
|
||||
|
||||
Sometimes (for example, when it comes to creating an editor) it might be useful
|
||||
to attach properties to the meta objects created. Fortunately, this is possible
|
||||
for most of them.<br/>
|
||||
For the meta objects that support properties, the member functions of the
|
||||
factory used for registering them will return an extended version of the factory
|
||||
itself. The latter can be used to attach properties to the last created meta
|
||||
object.<br/>
|
||||
Apparently, it's more difficult to say than to do:
|
||||
for most of them:
|
||||
|
||||
```cpp
|
||||
entt::meta<my_type>().type("reflected_type"_hs).prop("tooltip"_hs, "message");
|
||||
@ -914,10 +852,10 @@ Key only properties are also supported out of the box:
|
||||
entt::meta<my_type>().type("reflected_type"_hs).prop(my_enum::key_only);
|
||||
```
|
||||
|
||||
To attach multiple properties to a meta object, it's possible to invoke `prop`
|
||||
more than once.<br/>
|
||||
It's also possible to invoke `prop` at different times, as long as the factory
|
||||
is reset to the meta object of interest.
|
||||
To attach multiple properties to a meta object, just invoke `prop` more than
|
||||
once.<br/>
|
||||
It's also possible to call `prop` at different times, as long as the factory is
|
||||
reset to the meta object of interest.
|
||||
|
||||
The meta objects for which properties are supported are currently meta types,
|
||||
meta data and meta functions.<br/>
|
||||
@ -940,7 +878,7 @@ form of a `meta_any` object.
|
||||
|
||||
## Unregister types
|
||||
|
||||
A type registered with the reflection system can also be unregistered. This
|
||||
A type registered with the reflection system can also be _unregistered_. This
|
||||
means unregistering all its data members, member functions, conversion functions
|
||||
and so on. However, base classes aren't unregistered as well, since they don't
|
||||
necessarily depend on it.<br/>
|
||||
@ -969,7 +907,7 @@ A type can be re-registered later with a completely different name and form.
|
||||
## Meta context
|
||||
|
||||
All meta types and their parts are created at runtime and stored in a default
|
||||
_context_. This can be reached via a service locator as:
|
||||
_context_. This is obtained via a service locator as:
|
||||
|
||||
```cpp
|
||||
auto &&context = entt::locator<entt::meta_context>::value_or();
|
||||
@ -984,8 +922,8 @@ auto &&context = entt::locator<entt::meta_context>::value_or();
|
||||
std::swap(context, other);
|
||||
```
|
||||
|
||||
This can be useful for testing purposes or to define multiple contexts with
|
||||
different meta objects to be used as appropriate.
|
||||
This is useful for testing purposes or to define multiple context objects with
|
||||
different meta type to use as appropriate.
|
||||
|
||||
If _replacing_ the default context isn't enough, `EnTT` also offers the ability
|
||||
to use multiple and externally managed contexts with the runtime reflection
|
||||
@ -998,16 +936,16 @@ entt::meta_ctx context{};
|
||||
auto factory = entt::meta<my_type>(context).type("reflected_type"_hs);
|
||||
```
|
||||
|
||||
By doing so, the new meta type won't be available in the default context but
|
||||
will be usable by passing around the new context when needed, such as when
|
||||
creating a new `meta_any` object:
|
||||
By doing so, the new meta type isn't available in the default context but is
|
||||
usable by passing around the new context when needed, such as when creating a
|
||||
new `meta_any` object:
|
||||
|
||||
```cpp
|
||||
entt::meta_any any{context, std::in_place_type<my_type>};
|
||||
```
|
||||
|
||||
Similarly, to search for meta types in a context other than the default one, it
|
||||
will be necessary to pass it to the `resolve` function:
|
||||
Similarly, to search for meta types in a context other than the default one,
|
||||
it's necessary to pass it to the `resolve` function:
|
||||
|
||||
```cpp
|
||||
entt::meta_type type = entt::resolve(context, "reflected_type"_hs)
|
||||
|
@ -26,17 +26,16 @@ This module aims to make it simple and easy to use.
|
||||
|
||||
The library allows to define _concepts_ as interfaces to fulfill with concrete
|
||||
classes without having to inherit from a common base.<br/>
|
||||
This is, among others, one of the advantages of static polymorphism in general
|
||||
Among others, this is one of the advantages of static polymorphism in general
|
||||
and of a generic wrapper like that offered by the `poly` class template in
|
||||
particular.<br/>
|
||||
What users get is an object that can be passed around as such and not through a
|
||||
reference or a pointer, as happens when it comes to working with dynamic
|
||||
polymorphism.
|
||||
The result is an object to pass around as such and not through a reference or a
|
||||
pointer, as it happens when it comes to working with dynamic polymorphism.
|
||||
|
||||
Since the `poly` class template makes use of `entt::any` internally, it also
|
||||
supports most of its feature. Among the most important, the possibility to
|
||||
create aliases to existing and thus unmanaged objects. This allows users to
|
||||
exploit the static polymorphism while maintaining ownership of objects.<br/>
|
||||
supports most of its feature. For example, the possibility to create aliases to
|
||||
existing and thus unmanaged objects. This allows users to exploit the static
|
||||
polymorphism while maintaining ownership of objects.<br/>
|
||||
Likewise, the `poly` class template also benefits from the small buffer
|
||||
optimization offered by the `entt::any` class and therefore minimizes the number
|
||||
of allocations, avoiding them altogether where possible.
|
||||
@ -44,7 +43,7 @@ of allocations, avoiding them altogether where possible.
|
||||
## Other libraries
|
||||
|
||||
There are some very interesting libraries regarding static polymorphism.<br/>
|
||||
Among all, the two that I prefer are:
|
||||
The ones that I like more are:
|
||||
|
||||
* [`dyno`](https://github.com/ldionne/dyno): runtime polymorphism done right.
|
||||
* [`Poly`](https://github.com/facebook/folly/blob/master/folly/docs/Poly.md):
|
||||
@ -69,18 +68,18 @@ use the terminology introduced by Eric Niebler) is to define a _concept_ that
|
||||
types will have to adhere to.<br/>
|
||||
For this purpose, the library offers a single class that supports both deduced
|
||||
and fully defined interfaces. Although having interfaces deduced automatically
|
||||
is convenient and allows users to write less code in most cases, this has some
|
||||
is convenient and allows users to write less code in most cases, it has some
|
||||
limitations and it's therefore useful to be able to get around the deduction by
|
||||
providing a custom definition for the static virtual table.
|
||||
|
||||
Once the interface is defined, it will be sufficient to provide a generic
|
||||
implementation to fulfill the concept.<br/>
|
||||
Once the interface is defined, a generic implementation is needed to fulfill the
|
||||
concept itself.<br/>
|
||||
Also in this case, the library allows customizations based on types or families
|
||||
of types, so as to be able to go beyond the generic case where necessary.
|
||||
|
||||
## Deduced interface
|
||||
|
||||
This is how a concept with a deduced interface is introduced:
|
||||
This is how a concept with a deduced interface is defined:
|
||||
|
||||
```cpp
|
||||
struct Drawable: entt::type_list<> {
|
||||
@ -108,12 +107,12 @@ struct Drawable: entt::type_list<> {
|
||||
};
|
||||
```
|
||||
|
||||
In this case, all parameters must be passed to `invoke` after the reference to
|
||||
In this case, all parameters are passed to `invoke` after the reference to
|
||||
`this` and the return value is whatever the internal call returns.<br/>
|
||||
As for `invoke`, this is a name that is injected into the _concept_ through
|
||||
`Base`, from which one must necessarily inherit. Since it's also a dependent
|
||||
name, the `this-> template` form is unfortunately necessary due to the rules of
|
||||
the language. However, there exists also an alternative that goes through an
|
||||
the language. However, there also exists an alternative that goes through an
|
||||
external call:
|
||||
|
||||
```cpp
|
||||
@ -165,12 +164,12 @@ struct Drawable: entt::type_list<bool(int) const> {
|
||||
|
||||
Why should a user fully define a concept if the function types are the same as
|
||||
the deduced ones?<br>
|
||||
Because, in fact, this is exactly the limitation that can be worked around by
|
||||
manually defining the static virtual table.
|
||||
In fact, this is the limitation that can be worked around by manually defining
|
||||
the static virtual table.
|
||||
|
||||
When things are deduced, there is an implicit constraint.<br/>
|
||||
If the concept exposes a member function called `draw` with function type
|
||||
`void()`, a concept can be satisfied:
|
||||
`void()`, a concept is satisfied:
|
||||
|
||||
* Either by a class that exposes a member function with the same name and the
|
||||
same signature.
|
||||
@ -179,7 +178,7 @@ If the concept exposes a member function called `draw` with function type
|
||||
interface itself.
|
||||
|
||||
In other words, it's not possible to make use of functions not belonging to the
|
||||
interface, even if they are present in the types that fulfill the concept.<br/>
|
||||
interface, even if they're part of the types that fulfill the concept.<br/>
|
||||
Similarly, it's not possible to deduce a function in the static virtual table
|
||||
with a function type different from that of the associated member function in
|
||||
the interface itself.
|
||||
@ -200,8 +199,8 @@ struct Drawable: entt::type_list<> {
|
||||
};
|
||||
```
|
||||
|
||||
In this case, it's stated that the `draw` method of a generic type will be
|
||||
enough to satisfy the requirements of the `Drawable` concept.<br/>
|
||||
In this case, it's stated that the `draw` method of a generic type is enough to
|
||||
satisfy the requirements of the `Drawable` concept.<br/>
|
||||
Both member functions and free functions are supported to fulfill concepts:
|
||||
|
||||
```cpp
|
||||
@ -251,15 +250,15 @@ struct DrawableAndErasable: entt::type_list<> {
|
||||
```
|
||||
|
||||
The static virtual table is empty and must remain so.<br/>
|
||||
On the other hand, `type` no longer inherits from `Base` and instead forwards
|
||||
On the other hand, `type` no longer inherits from `Base`. Instead, it forwards
|
||||
its template parameter to the type exposed by the _base class_. Internally, the
|
||||
size of the static virtual table of the base class is used as an offset for the
|
||||
local indexes.<br/>
|
||||
_size_ of the static virtual table of the base class is used as an offset for
|
||||
the local indexes.<br/>
|
||||
Finally, by means of the `value_list_cat_t` utility, the implementation consists
|
||||
in appending the new functions to the previous list.
|
||||
|
||||
As for a defined concept instead, also the list of types must be extended, in a
|
||||
similar way to what is shown for the implementation of the above concept.<br/>
|
||||
As for a defined concept instead, the list of types is _extended_ in a similar
|
||||
way to what is shown for the implementation of the above concept.<br/>
|
||||
To do this, it's useful to declare a function that allows to convert a _concept_
|
||||
into its underlying `type_list` object:
|
||||
|
||||
@ -268,8 +267,8 @@ template<typename... Type>
|
||||
entt::type_list<Type...> as_type_list(const entt::type_list<Type...> &);
|
||||
```
|
||||
|
||||
The definition isn't strictly required, since the function will only be used
|
||||
through a `decltype` as it follows:
|
||||
The definition isn't strictly required, since the function is only used through
|
||||
a `decltype` as it follows:
|
||||
|
||||
```cpp
|
||||
struct DrawableAndErasable: entt::type_list_cat_t<
|
||||
@ -286,9 +285,8 @@ Everything else is the same as already shown instead.
|
||||
|
||||
# Static polymorphism in the wild
|
||||
|
||||
Once the _concept_ and implementation have been introduced, it will be possible
|
||||
to use the `poly` class template to contain instances that meet the
|
||||
requirements:
|
||||
Once the _concept_ and implementation are defined, it's possible to use the
|
||||
`poly` class template to _wrap_ instances that meet the requirements:
|
||||
|
||||
```cpp
|
||||
using drawable = entt::poly<Drawable>;
|
||||
@ -310,9 +308,9 @@ instance = square{};
|
||||
instance->draw();
|
||||
```
|
||||
|
||||
The `poly` class template offers a wide range of constructors, from the default
|
||||
one (which will return an uninitialized `poly` object) to the copy and move
|
||||
constructors, as well as the ability to create objects in-place.<br/>
|
||||
This class offers a wide range of constructors, from the default one (which
|
||||
returns an uninitialized `poly` object) to the copy and move constructors, as
|
||||
well as the ability to create objects in-place.<br/>
|
||||
Among others, there is also a constructor that allows users to wrap unmanaged
|
||||
objects in a `poly` instance (either const or non-const ones):
|
||||
|
||||
@ -329,14 +327,14 @@ drawable other = instance.as_ref();
|
||||
```
|
||||
|
||||
In both cases, although the interface of the `poly` object doesn't change, it
|
||||
won't construct any element or take care of destroying the referenced objects.
|
||||
doesn't construct any element or take care of destroying the referenced objects.
|
||||
|
||||
Note also how the underlying concept is accessed via a call to `operator->` and
|
||||
not directly as `instance.draw()`.<br/>
|
||||
This allows users to decouple the API of the wrapper from that of the concept.
|
||||
Therefore, where `instance.data()` will invoke the `data` member function of the
|
||||
poly object, `instance->data()` will map directly to the functionality exposed
|
||||
by the underlying concept.
|
||||
Therefore, where `instance.data()` invokes the `data` member function of the
|
||||
poly object, `instance->data()` maps directly to the functionality exposed by
|
||||
the underlying concept.
|
||||
|
||||
# Storage size and alignment requirement
|
||||
|
||||
@ -351,9 +349,9 @@ entt::basic_poly<Drawable, sizeof(double[4]), alignof(double[4])>
|
||||
|
||||
The default size is `sizeof(double[2])`, which seems like a good compromise
|
||||
between a buffer that is too large and one unable to hold anything larger than
|
||||
an integer. The alignment requirement is optional instead and by default such
|
||||
that it's the most stringent (the largest) for any object whose size is at most
|
||||
equal to the one provided.<br/>
|
||||
an integer. The alignment requirement is optional and by default such that it's
|
||||
the most stringent (the largest) for any object whose size is at most equal to
|
||||
the one provided.<br/>
|
||||
It's worth noting that providing a size of 0 (which is an accepted value in all
|
||||
respects) will force the system to dynamically allocate the contained objects in
|
||||
all cases.
|
||||
|
@ -15,18 +15,17 @@
|
||||
|
||||
# Introduction
|
||||
|
||||
Sometimes processes are a useful tool to work around the strict definition of a
|
||||
system and introduce logic in a different way, usually without resorting to the
|
||||
introduction of other components.
|
||||
|
||||
`EnTT` offers a minimal support to this paradigm by introducing a few classes
|
||||
that users can use to define and execute cooperative processes.
|
||||
Processes are a useful tool to work around the strict definition of a system and
|
||||
introduce logic in a different way, usually without resorting to other component
|
||||
types.<br/>
|
||||
`EnTT` offers minimal support to this paradigm by introducing a few classes used
|
||||
to define and execute cooperative processes.
|
||||
|
||||
# The process
|
||||
|
||||
A typical process must inherit from the `process` class template that stays true
|
||||
to the CRTP idiom. Moreover, derived classes must specify what's the intended
|
||||
type for elapsed times.
|
||||
A typical task inherits from the `process` class template that stays true to the
|
||||
CRTP idiom. Moreover, derived classes specify what the intended type for elapsed
|
||||
times is.
|
||||
|
||||
A process should expose publicly the following member functions whether needed
|
||||
(note that it isn't required to define a function unless the derived class wants
|
||||
@ -34,39 +33,38 @@ to _override_ the default behavior):
|
||||
|
||||
* `void update(Delta, void *);`
|
||||
|
||||
It's invoked once per tick until a process is explicitly aborted or it
|
||||
terminates either with or without errors. Even though it's not mandatory to
|
||||
declare this member function, as a rule of thumb each process should at
|
||||
least define it to work properly. The `void *` parameter is an opaque pointer
|
||||
to user data (if any) forwarded directly to the process during an update.
|
||||
This is invoked once per tick until a process is explicitly aborted or ends
|
||||
either with or without errors. Even though it's not mandatory to declare this
|
||||
member function, as a rule of thumb each process should at least define it to
|
||||
work _properly_. The `void *` parameter is an opaque pointer to user data (if
|
||||
any) forwarded directly to the process during an update.
|
||||
|
||||
* `void init();`
|
||||
|
||||
It's invoked when the process joins the running queue of a scheduler. This
|
||||
happens as soon as it's attached to the scheduler if the process is a top
|
||||
level one, otherwise when it replaces its parent if the process is a
|
||||
continuation.
|
||||
This is invoked when the process joins the running queue of a scheduler. It
|
||||
happens usually as soon as the process is attached to the scheduler if it's a
|
||||
top level one, otherwise when it replaces its parent if it's a _continuation_.
|
||||
|
||||
* `void succeeded();`
|
||||
|
||||
It's invoked in case of success, immediately after an update and during the
|
||||
This is invoked in case of success, immediately after an update and during the
|
||||
same tick.
|
||||
|
||||
* `void failed();`
|
||||
|
||||
It's invoked in case of errors, immediately after an update and during the
|
||||
This is invoked in case of errors, immediately after an update and during the
|
||||
same tick.
|
||||
|
||||
* `void aborted();`
|
||||
|
||||
It's invoked only if a process is explicitly aborted. There is no guarantee
|
||||
that it executes in the same tick, this depends solely on whether the
|
||||
process is aborted immediately or not.
|
||||
This is invoked only if a process is explicitly aborted. There is no guarantee
|
||||
that it executes in the same tick, it depends solely on whether the process is
|
||||
aborted immediately or not.
|
||||
|
||||
Derived classes can also change the internal state of a process by invoking
|
||||
`succeed` and `fail`, as well as `pause` and `unpause` the process itself. All
|
||||
these are protected member functions made available to be able to manage the
|
||||
life cycle of a process from a derived class.
|
||||
`succeed` and `fail`, as well as `pause` and `unpause` the process itself.<br/>
|
||||
All these are protected member functions made available to manage the life cycle
|
||||
of a process from a derived class.
|
||||
|
||||
Here is a minimal example for the sake of curiosity:
|
||||
|
||||
@ -95,14 +93,14 @@ private:
|
||||
|
||||
## Adaptor
|
||||
|
||||
Lambdas and functors can't be used directly with a scheduler for they are not
|
||||
Lambdas and functors can't be used directly with a scheduler because they aren't
|
||||
properly defined processes with managed life cycles.<br/>
|
||||
This class helps in filling the gap and turning lambdas and functors into
|
||||
full-featured processes usable by a scheduler.
|
||||
|
||||
The function call operator has a signature similar to the one of the `update`
|
||||
function of a process but for the fact that it receives two extra arguments to
|
||||
call whenever a process is terminated with success or with an error:
|
||||
function of a process but for the fact that it receives two extra callbacks to
|
||||
invoke whenever a process terminates with success or with an error:
|
||||
|
||||
```cpp
|
||||
void(Delta delta, void *data, auto succeed, auto fail);
|
||||
@ -127,9 +125,9 @@ A cooperative scheduler runs different processes and helps managing their life
|
||||
cycles.
|
||||
|
||||
Each process is invoked once per tick. If it terminates, it's removed
|
||||
automatically from the scheduler and it's never invoked again. Otherwise it's
|
||||
automatically from the scheduler and it's never invoked again. Otherwise, it's
|
||||
a good candidate to run one more time the next tick.<br/>
|
||||
A process can also have a child. In this case, the parent process is replaced
|
||||
A process can also have a _child_. In this case, the parent process is replaced
|
||||
with its child when it terminates and only if it returns with success. In case
|
||||
of errors, both the parent process and its child are discarded. This way, it's
|
||||
easy to create chain of processes to run sequentially.
|
||||
@ -138,18 +136,25 @@ Using a scheduler is straightforward. To create it, users must provide only the
|
||||
type for the elapsed times and no arguments at all:
|
||||
|
||||
```cpp
|
||||
entt::scheduler<std::uint32_t> scheduler;
|
||||
entt::basic_scheduler<std::uint64_t> scheduler;
|
||||
```
|
||||
|
||||
It has member functions to query its internal data structures, like `empty` or
|
||||
`size`, as well as a `clear` utility to reset it to a clean state:
|
||||
Otherwise, the `scheduler` alias is also available for the most common cases. It
|
||||
uses `std::uint32_t` as a default type:
|
||||
|
||||
```cpp
|
||||
entt::scheduler scheduler;
|
||||
```
|
||||
|
||||
The class has member functions to query its internal data structures, like
|
||||
`empty` or `size`, as well as a `clear` utility to reset it to a clean state:
|
||||
|
||||
```cpp
|
||||
// checks if there are processes still running
|
||||
const auto empty = scheduler.empty();
|
||||
|
||||
// gets the number of processes still running
|
||||
entt::scheduler<std::uint32_t>::size_type size = scheduler.size();
|
||||
entt::scheduler::size_type size = scheduler.size();
|
||||
|
||||
// resets the scheduler to its initial state and discards all the processes
|
||||
scheduler.clear();
|
||||
@ -173,7 +178,7 @@ To attach a process to a scheduler there are mainly two ways:
|
||||
```
|
||||
|
||||
In both cases, the return value is an opaque object that offers a `then` member
|
||||
function to use to create chains of processes to run sequentially.<br/>
|
||||
function used to create chains of processes to run sequentially.<br/>
|
||||
As a minimal example of use:
|
||||
|
||||
```cpp
|
||||
@ -201,7 +206,7 @@ scheduler.update(delta, &data);
|
||||
```
|
||||
|
||||
In addition to these functions, the scheduler offers an `abort` member function
|
||||
that can be used to discard all the running processes at once:
|
||||
that is used to discard all the running processes at once:
|
||||
|
||||
```cpp
|
||||
// aborts all the processes abruptly ...
|
||||
|
@ -1,18 +1,35 @@
|
||||
# Similar projects
|
||||
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
# Table of Contents
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [Similar projects](#similar-projects)
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
|
||||
# Introduction
|
||||
|
||||
There are many projects similar to `EnTT`, both open source and not.<br/>
|
||||
Some even borrowed some ideas from this library and expressed them in different
|
||||
languages.<br/>
|
||||
Others developed different architectures from scratch and therefore offer
|
||||
alternative solutions with their pros and cons.
|
||||
|
||||
Below an incomplete list of those that I've come across so far.<br/>
|
||||
If you know of other similar projects out there, feel free to open an issue or a
|
||||
PR and I'll be glad to add them to this page.<br/>
|
||||
I hope the following lists can grow much more in the future.
|
||||
|
||||
# Similar projects
|
||||
|
||||
Below an incomplete list of similar projects that I've come across so far.<br/>
|
||||
If some terms or designs aren't clear, I recommend referring to the
|
||||
[_ECS Back and Forth_](https://skypjack.github.io/tags/#ecs) series for all the
|
||||
details.
|
||||
|
||||
I hope this list can grow much more in the future:
|
||||
|
||||
* C:
|
||||
* [destral_ecs](https://github.com/roig/destral_ecs): a single-file ECS based
|
||||
on sparse sets.
|
||||
@ -34,6 +51,8 @@ I hope this list can grow much more in the future:
|
||||
solution between an ECS and dynamic mixins.
|
||||
|
||||
* C#
|
||||
* [Arch](https://github.com/genaray/Arch): a simple, fast and _unity entities_
|
||||
inspired archetype ECS with optional multithreading.
|
||||
* [Entitas](https://github.com/sschmid/Entitas-CSharp): the ECS framework for
|
||||
C# and Unity, where _reactive systems_ were invented.
|
||||
* [LeoECS](https://github.com/Leopotam/ecs): simple lightweight C# Entity
|
||||
@ -70,6 +89,3 @@ I hope this list can grow much more in the future:
|
||||
|
||||
* Zig
|
||||
* [zig-ecs](https://github.com/prime31/zig-ecs): a _zig-ification_ of `EnTT`.
|
||||
|
||||
If you know of other resources out there that can be of interest for the reader,
|
||||
feel free to open an issue or a PR and I'll be glad to add them to this page.
|
||||
|
@ -9,6 +9,7 @@
|
||||
* [Delegate](#delegate)
|
||||
* [Runtime arguments](#runtime-arguments)
|
||||
* [Lambda support](#lambda-support)
|
||||
* [Raw access](#raw-access)
|
||||
* [Signals](#signals)
|
||||
* [Event dispatcher](#event-dispatcher)
|
||||
* [Named queues](#named-queues)
|
||||
@ -38,7 +39,7 @@ lightweight classes to solve the same and many other problems.
|
||||
# Delegate
|
||||
|
||||
A delegate can be used as a general purpose invoker with no memory overhead for
|
||||
free functions and member functions provided along with an instance on which to
|
||||
free functions, lambdas and members provided along with an instance on which to
|
||||
invoke them.<br/>
|
||||
It doesn't claim to be a drop-in replacement for an `std::function`, so don't
|
||||
expect to use it whenever an `std::function` fits well. That said, it's most
|
||||
@ -92,9 +93,9 @@ delegate.connect<&g>(c);
|
||||
delegate(42);
|
||||
```
|
||||
|
||||
The function `g` is invoked with a reference to `c` and `42`. However, the
|
||||
function type of the delegate is still `void(int)`. This is also the signature
|
||||
of its function call operator.<br/>
|
||||
Function `g` is invoked with a reference to `c` and `42`. However, the function
|
||||
type of the delegate is still `void(int)`. This is also the signature of its
|
||||
function call operator.<br/>
|
||||
Another interesting aspect of the delegate class is that it accepts functions
|
||||
with a list of parameters that is shorter than that of its function type:
|
||||
|
||||
@ -105,9 +106,15 @@ delegate(42);
|
||||
```
|
||||
|
||||
Where the function type of the delegate is `void(int)` as above. It goes without
|
||||
saying that the extra arguments are silently discarded internally.<br/>
|
||||
This is a nice-to-have feature in a lot of cases, as an example when the
|
||||
`delegate` class is used as a building block of a signal-slot system.
|
||||
saying that the extra arguments are silently discarded internally. This is a
|
||||
nice-to-have feature in a lot of cases, as an example when the `delegate` class
|
||||
is used as a building block of a signal-slot system.<br/>
|
||||
In fact, this filtering works both ways. The class tries to pass its first
|
||||
_count_ arguments **first**, then the last _count_. Watch out for conversion
|
||||
rules if in doubt when connecting a listener!<br/>
|
||||
Arbitrary functions that pull random arguments from the delegate list aren't
|
||||
supported instead. Other feature were preferred, such as support for functions
|
||||
with compatible argument lists although not equal to those of the delegate.
|
||||
|
||||
To create and initialize a delegate at once, there are a few specialized
|
||||
constructors. Because of the rules of the language, the listener is provided by
|
||||
@ -231,6 +238,24 @@ As above, the first parameter (`const void *`) isn't part of the function type
|
||||
of the delegate and is used to dispatch arbitrary user data back and forth. In
|
||||
other terms, the function type of the delegate above is `int(int)`.
|
||||
|
||||
## Raw access
|
||||
|
||||
While not recommended, a delegate also allows direct access to the stored
|
||||
callable function target and underlying data, if any.<br/>
|
||||
This makes it possible to bypass the behavior of the delegate itself and force
|
||||
calls on different instances:
|
||||
|
||||
```cpp
|
||||
my_struct other;
|
||||
delegate.target(&other, 42);
|
||||
```
|
||||
|
||||
It goes without saying that this type of approach is **very** risky, especially
|
||||
since there is no way of knowing whether the contained function was originally a
|
||||
member function of some class, a free function or a lambda.<br/>
|
||||
Another possible (and meaningful) use of this feature is that of identifying a
|
||||
particular delegate through its descriptive _traits_ instead.
|
||||
|
||||
# Signals
|
||||
|
||||
Signal handlers work with references to classes, function pointers and pointers
|
||||
@ -290,7 +315,7 @@ sink.disconnect<&foo>();
|
||||
sink.disconnect<&listener::bar>(instance);
|
||||
|
||||
// disconnect all member functions of an instance, if any
|
||||
sink.disconnect(instance);
|
||||
sink.disconnect(&instance);
|
||||
|
||||
// discards all listeners at once
|
||||
sink.disconnect();
|
||||
@ -300,15 +325,6 @@ As shown above, listeners don't have to strictly follow the signature of the
|
||||
signal. As long as a listener can be invoked with the given arguments to yield a
|
||||
result that is convertible to the given return type, everything works just
|
||||
fine.<br/>
|
||||
It's also possible to connect a listener before other elements already contained
|
||||
by the signal. The `before` function returns a `sink` object that is correctly
|
||||
initialized for the purpose and can be used to connect one or more listeners in
|
||||
order and in the desired position:
|
||||
|
||||
```cpp
|
||||
sink.before<&foo>().connect<&listener::bar>(instance);
|
||||
```
|
||||
|
||||
In all cases, the `connect` member function returns by default a `connection`
|
||||
object to be used as an alternative to break a connection by means of its
|
||||
`release` member function.<br/>
|
||||
@ -409,7 +425,7 @@ of them at once:
|
||||
|
||||
```cpp
|
||||
dispatcher.sink<an_event>().disconnect<&listener::receive>(listener);
|
||||
dispatcher.sink<another_event>().disconnect(listener);
|
||||
dispatcher.sink<another_event>().disconnect(&listener);
|
||||
```
|
||||
|
||||
The `trigger` member function serves the purpose of sending an immediate event
|
||||
|
@ -1,62 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
|
||||
<Type Name="entt::basic_registry<*>">
|
||||
<Intrinsic Name="pools_size" Expression="pools.packed.first_base::value.size()"/>
|
||||
<Intrinsic Name="vars_size" Expression="vars.ctx.packed.first_base::value.size()"/>
|
||||
<Intrinsic Name="to_entity" Expression="*((entity_traits::entity_type *)&entity) & entity_traits::entity_mask">
|
||||
<Parameter Name="entity" Type="entity_traits::value_type &"/>
|
||||
<Intrinsic Name="to_entity" Expression="*((traits_type::entity_type *)&entity) & traits_type::entity_mask">
|
||||
<Parameter Name="entity" Type="traits_type::value_type &"/>
|
||||
</Intrinsic>
|
||||
<DisplayString>{{ size={ epool.size() } }}</DisplayString>
|
||||
<DisplayString>{{ pools={ pools.size() } }}</DisplayString>
|
||||
<Expand>
|
||||
<Item IncludeView="simple" Name="[epool]">epool,view(simple)nr</Item>
|
||||
<Synthetic Name="[epool]" ExcludeView="simple">
|
||||
<DisplayString>{ epool.size() }</DisplayString>
|
||||
<Expand>
|
||||
<CustomListItems>
|
||||
<Variable Name="pos" InitialValue="0" />
|
||||
<Variable Name="last" InitialValue="epool.size()"/>
|
||||
<Loop>
|
||||
<Break Condition="pos == last"/>
|
||||
<If Condition="to_entity(epool[pos]) == pos">
|
||||
<Item Name="[{ pos }]">epool[pos]</Item>
|
||||
</If>
|
||||
<Exec>++pos</Exec>
|
||||
</Loop>
|
||||
</CustomListItems>
|
||||
</Expand>
|
||||
</Synthetic>
|
||||
<Synthetic Name="[destroyed]" ExcludeView="simple">
|
||||
<DisplayString>{ to_entity(free_list) != entity_traits::entity_mask }</DisplayString>
|
||||
<Expand>
|
||||
<CustomListItems>
|
||||
<Variable Name="it" InitialValue="to_entity(free_list)" />
|
||||
<Loop>
|
||||
<Break Condition="it == entity_traits::entity_mask"/>
|
||||
<Item Name="[{ it }]">epool[it]</Item>
|
||||
<Exec>it = to_entity(epool[it])</Exec>
|
||||
</Loop>
|
||||
</CustomListItems>
|
||||
</Expand>
|
||||
</Synthetic>
|
||||
<Item Name="[entities]">entities</Item>
|
||||
<Synthetic Name="[pools]">
|
||||
<DisplayString>{ pools_size() }</DisplayString>
|
||||
<DisplayString>{ pools.size() }</DisplayString>
|
||||
<Expand>
|
||||
<IndexListItems ExcludeView="simple">
|
||||
<Size>pools_size()</Size>
|
||||
<Size>pools.size()</Size>
|
||||
<ValueNode>*pools.packed.first_base::value[$i].element.second</ValueNode>
|
||||
</IndexListItems>
|
||||
<IndexListItems IncludeView="simple">
|
||||
<Size>pools_size()</Size>
|
||||
<Size>pools.size()</Size>
|
||||
<ValueNode>*pools.packed.first_base::value[$i].element.second,view(simple)</ValueNode>
|
||||
</IndexListItems>
|
||||
</Expand>
|
||||
</Synthetic>
|
||||
<Item Name="[groups]" ExcludeView="simple">groups.size()</Item>
|
||||
<Synthetic Name="[vars]">
|
||||
<DisplayString>{ vars_size() }</DisplayString>
|
||||
<DisplayString>{ vars.ctx.size() }</DisplayString>
|
||||
<Expand>
|
||||
<IndexListItems>
|
||||
<Size>vars_size()</Size>
|
||||
<Size>vars.ctx.size()</Size>
|
||||
<ValueNode>vars.ctx.packed.first_base::value[$i].element.second</ValueNode>
|
||||
</IndexListItems>
|
||||
</Expand>
|
||||
@ -69,20 +38,20 @@
|
||||
<Item Name="[capacity]" ExcludeView="simple">packed.capacity()</Item>
|
||||
<Item Name="[policy]">mode,en</Item>
|
||||
<Synthetic Name="[sparse]">
|
||||
<DisplayString>{ sparse.size() * entity_traits::page_size }</DisplayString>
|
||||
<DisplayString>{ sparse.size() * traits_type::page_size }</DisplayString>
|
||||
<Expand>
|
||||
<ExpandedItem IncludeView="simple">sparse,view(simple)</ExpandedItem>
|
||||
<CustomListItems ExcludeView="simple">
|
||||
<Variable Name="pos" InitialValue="0"/>
|
||||
<Variable Name="page" InitialValue="0"/>
|
||||
<Variable Name="offset" InitialValue="0"/>
|
||||
<Variable Name="last" InitialValue="sparse.size() * entity_traits::page_size"/>
|
||||
<Variable Name="last" InitialValue="sparse.size() * traits_type::page_size"/>
|
||||
<Loop>
|
||||
<Break Condition="pos == last"/>
|
||||
<Exec>page = pos / entity_traits::page_size</Exec>
|
||||
<Exec>offset = pos & (entity_traits::page_size - 1)</Exec>
|
||||
<If Condition="sparse[page] && (*((entity_traits::entity_type *)&sparse[page][offset]) < ~entity_traits::entity_mask)">
|
||||
<Item Name="[{ pos }]">*((entity_traits::entity_type *)&sparse[page][offset]) & entity_traits::entity_mask</Item>
|
||||
<Exec>page = pos / traits_type::page_size</Exec>
|
||||
<Exec>offset = pos & (traits_type::page_size - 1)</Exec>
|
||||
<If Condition="sparse[page] && (*((traits_type::entity_type *)&sparse[page][offset]) < ~traits_type::entity_mask)">
|
||||
<Item Name="[{ pos }]">*((traits_type::entity_type *)&sparse[page][offset]) & traits_type::entity_mask</Item>
|
||||
</If>
|
||||
<Exec>++pos</Exec>
|
||||
</Loop>
|
||||
@ -98,7 +67,7 @@
|
||||
<Variable Name="last" InitialValue="packed.size()"/>
|
||||
<Loop>
|
||||
<Break Condition="pos == last"/>
|
||||
<If Condition="*((entity_traits::entity_type *)&packed[pos]) < ~entity_traits::entity_mask">
|
||||
<If Condition="*((traits_type::entity_type *)&packed[pos]) < ~traits_type::entity_mask">
|
||||
<Item Name="[{ pos }]">packed[pos]</Item>
|
||||
</If>
|
||||
<Exec>++pos</Exec>
|
||||
@ -111,18 +80,19 @@
|
||||
<Type Name="entt::basic_storage<*>">
|
||||
<DisplayString>{{ size={ base_type::packed.size() }, type={ base_type::info->alias,na } }}</DisplayString>
|
||||
<Expand>
|
||||
<Item Name="[capacity]" Optional="true" ExcludeView="simple">packed.first_base::value.capacity() * comp_traits::page_size</Item>
|
||||
<Item Name="[page size]" Optional="true" ExcludeView="simple">comp_traits::page_size</Item>
|
||||
<Item Name="[capacity]" Optional="true" ExcludeView="simple">payload.capacity() * traits_type::page_size</Item>
|
||||
<Item Name="[page size]" Optional="true" ExcludeView="simple">traits_type::page_size</Item>
|
||||
<Item Name="[length]" Optional="true" ExcludeView="simple">length</Item>
|
||||
<Item Name="[base]" ExcludeView="simple">(base_type*)this,nand</Item>
|
||||
<Item Name="[base]" IncludeView="simple">(base_type*)this,view(simple)nand</Item>
|
||||
<!-- having SFINAE-like techniques in natvis is priceless :) -->
|
||||
<CustomListItems Condition="packed.first_base::value.size() != 0" Optional="true">
|
||||
<CustomListItems Condition="payload.size() != 0" Optional="true">
|
||||
<Variable Name="pos" InitialValue="0" />
|
||||
<Variable Name="last" InitialValue="base_type::packed.size()"/>
|
||||
<Loop>
|
||||
<Break Condition="pos == last"/>
|
||||
<If Condition="*((base_type::entity_traits::entity_type *)&base_type::packed[pos]) < ~base_type::entity_traits::entity_mask">
|
||||
<Item Name="[{ pos }:{ base_type::packed[pos] }]">packed.first_base::value[pos / comp_traits::page_size][pos & (comp_traits::page_size - 1)]</Item>
|
||||
<If Condition="*((base_type::traits_type::entity_type *)&base_type::packed[pos]) < ~base_type::traits_type::entity_mask">
|
||||
<Item Name="[{ pos }:{ base_type::packed[pos] }]">payload[pos / traits_type::page_size][pos & (traits_type::page_size - 1)]</Item>
|
||||
</If>
|
||||
<Exec>++pos</Exec>
|
||||
</Loop>
|
||||
|
@ -8,7 +8,7 @@
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="entt::basic_dispatcher<*>">
|
||||
<Intrinsic Name="size" Expression="pools.first_base::value.packed.first_base::value.size()"/>
|
||||
<Intrinsic Name="size" Expression="pools.first_base::value.size()"/>
|
||||
<DisplayString>{{ size={ size() } }}</DisplayString>
|
||||
<Expand>
|
||||
<Synthetic Name="[pools]">
|
||||
@ -50,7 +50,6 @@
|
||||
<DisplayString>{{ type={ "$T1" } }}</DisplayString>
|
||||
<Expand>
|
||||
<Item Name="[signal]">signal,na</Item>
|
||||
<Item Name="[offset]">offset</Item>
|
||||
</Expand>
|
||||
</Type>
|
||||
</AutoVisualizer>
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -50,6 +50,8 @@
|
||||
# define ENTT_ASSERT_CONSTEXPR(condition, msg) ENTT_ASSERT(condition, msg)
|
||||
#endif
|
||||
|
||||
#define ENTT_FAIL(msg) ENTT_ASSERT(false, msg);
|
||||
|
||||
#ifdef ENTT_NO_ETO
|
||||
# define ENTT_ETO_TYPE(Type) void
|
||||
#else
|
||||
|
@ -4,8 +4,8 @@
|
||||
#include "macro.h"
|
||||
|
||||
#define ENTT_VERSION_MAJOR 3
|
||||
#define ENTT_VERSION_MINOR 11
|
||||
#define ENTT_VERSION_PATCH 1
|
||||
#define ENTT_VERSION_MINOR 12
|
||||
#define ENTT_VERSION_PATCH 2
|
||||
|
||||
#define ENTT_VERSION \
|
||||
ENTT_XSTR(ENTT_VERSION_MAJOR) \
|
||||
|
@ -128,51 +128,51 @@ public:
|
||||
return {it->element.first, it->element.second};
|
||||
}
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
friend constexpr std::ptrdiff_t operator-(const dense_map_iterator<ILhs> &, const dense_map_iterator<IRhs> &) noexcept;
|
||||
template<typename Lhs, typename Rhs>
|
||||
friend constexpr std::ptrdiff_t operator-(const dense_map_iterator<Lhs> &, const dense_map_iterator<Rhs> &) noexcept;
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
friend constexpr bool operator==(const dense_map_iterator<ILhs> &, const dense_map_iterator<IRhs> &) noexcept;
|
||||
template<typename Lhs, typename Rhs>
|
||||
friend constexpr bool operator==(const dense_map_iterator<Lhs> &, const dense_map_iterator<Rhs> &) noexcept;
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
friend constexpr bool operator<(const dense_map_iterator<ILhs> &, const dense_map_iterator<IRhs> &) noexcept;
|
||||
template<typename Lhs, typename Rhs>
|
||||
friend constexpr bool operator<(const dense_map_iterator<Lhs> &, const dense_map_iterator<Rhs> &) noexcept;
|
||||
|
||||
private:
|
||||
It it;
|
||||
};
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) noexcept {
|
||||
template<typename Lhs, typename Rhs>
|
||||
[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_map_iterator<Lhs> &lhs, const dense_map_iterator<Rhs> &rhs) noexcept {
|
||||
return lhs.it - rhs.it;
|
||||
}
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
[[nodiscard]] constexpr bool operator==(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) noexcept {
|
||||
template<typename Lhs, typename Rhs>
|
||||
[[nodiscard]] constexpr bool operator==(const dense_map_iterator<Lhs> &lhs, const dense_map_iterator<Rhs> &rhs) noexcept {
|
||||
return lhs.it == rhs.it;
|
||||
}
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
[[nodiscard]] constexpr bool operator!=(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) noexcept {
|
||||
template<typename Lhs, typename Rhs>
|
||||
[[nodiscard]] constexpr bool operator!=(const dense_map_iterator<Lhs> &lhs, const dense_map_iterator<Rhs> &rhs) noexcept {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
[[nodiscard]] constexpr bool operator<(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) noexcept {
|
||||
template<typename Lhs, typename Rhs>
|
||||
[[nodiscard]] constexpr bool operator<(const dense_map_iterator<Lhs> &lhs, const dense_map_iterator<Rhs> &rhs) noexcept {
|
||||
return lhs.it < rhs.it;
|
||||
}
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
[[nodiscard]] constexpr bool operator>(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) noexcept {
|
||||
template<typename Lhs, typename Rhs>
|
||||
[[nodiscard]] constexpr bool operator>(const dense_map_iterator<Lhs> &lhs, const dense_map_iterator<Rhs> &rhs) noexcept {
|
||||
return rhs < lhs;
|
||||
}
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
[[nodiscard]] constexpr bool operator<=(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) noexcept {
|
||||
template<typename Lhs, typename Rhs>
|
||||
[[nodiscard]] constexpr bool operator<=(const dense_map_iterator<Lhs> &lhs, const dense_map_iterator<Rhs> &rhs) noexcept {
|
||||
return !(lhs > rhs);
|
||||
}
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
[[nodiscard]] constexpr bool operator>=(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) noexcept {
|
||||
template<typename Lhs, typename Rhs>
|
||||
[[nodiscard]] constexpr bool operator>=(const dense_map_iterator<Lhs> &lhs, const dense_map_iterator<Rhs> &rhs) noexcept {
|
||||
return !(lhs < rhs);
|
||||
}
|
||||
|
||||
@ -230,13 +230,13 @@ private:
|
||||
std::size_t offset;
|
||||
};
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
[[nodiscard]] constexpr bool operator==(const dense_map_local_iterator<ILhs> &lhs, const dense_map_local_iterator<IRhs> &rhs) noexcept {
|
||||
template<typename Lhs, typename Rhs>
|
||||
[[nodiscard]] constexpr bool operator==(const dense_map_local_iterator<Lhs> &lhs, const dense_map_local_iterator<Rhs> &rhs) noexcept {
|
||||
return lhs.index() == rhs.index();
|
||||
}
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
[[nodiscard]] constexpr bool operator!=(const dense_map_local_iterator<ILhs> &lhs, const dense_map_local_iterator<IRhs> &rhs) noexcept {
|
||||
template<typename Lhs, typename Rhs>
|
||||
[[nodiscard]] constexpr bool operator!=(const dense_map_local_iterator<Lhs> &lhs, const dense_map_local_iterator<Rhs> &rhs) noexcept {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
@ -266,7 +266,7 @@ class dense_map {
|
||||
static constexpr std::size_t minimum_capacity = 8u;
|
||||
|
||||
using node_type = internal::dense_map_node<Key, Type>;
|
||||
using alloc_traits = typename std::allocator_traits<Allocator>;
|
||||
using alloc_traits = std::allocator_traits<Allocator>;
|
||||
static_assert(std::is_same_v<typename alloc_traits::value_type, std::pair<const Key, Type>>, "Invalid value type");
|
||||
using sparse_container_type = std::vector<std::size_t, typename alloc_traits::template rebind_alloc<std::size_t>>;
|
||||
using packed_container_type = std::vector<node_type, typename alloc_traits::template rebind_alloc<node_type>>;
|
||||
@ -464,7 +464,6 @@ public:
|
||||
/**
|
||||
* @brief Returns an iterator to the beginning.
|
||||
*
|
||||
* The returned iterator points to the first instance of the internal array.
|
||||
* If the array is empty, the returned iterator will be equal to `end()`.
|
||||
*
|
||||
* @return An iterator to the first instance of the internal array.
|
||||
@ -485,11 +484,6 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the end.
|
||||
*
|
||||
* The returned iterator points to the element following the last instance
|
||||
* of the internal array. Attempting to dereference the returned iterator
|
||||
* results in undefined behavior.
|
||||
*
|
||||
* @return An iterator to the element following the last instance of the
|
||||
* internal array.
|
||||
*/
|
||||
@ -838,7 +832,7 @@ public:
|
||||
}
|
||||
|
||||
/*! @copydoc equal_range */
|
||||
template<class Other>
|
||||
template<typename Other>
|
||||
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, std::pair<const_iterator, const_iterator>>>
|
||||
equal_range(const Other &key) const {
|
||||
const auto it = find(key);
|
||||
|
@ -96,51 +96,51 @@ public:
|
||||
return *operator->();
|
||||
}
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
friend constexpr std::ptrdiff_t operator-(const dense_set_iterator<ILhs> &, const dense_set_iterator<IRhs> &) noexcept;
|
||||
template<typename Lhs, typename Rhs>
|
||||
friend constexpr std::ptrdiff_t operator-(const dense_set_iterator<Lhs> &, const dense_set_iterator<Rhs> &) noexcept;
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
friend constexpr bool operator==(const dense_set_iterator<ILhs> &, const dense_set_iterator<IRhs> &) noexcept;
|
||||
template<typename Lhs, typename Rhs>
|
||||
friend constexpr bool operator==(const dense_set_iterator<Lhs> &, const dense_set_iterator<Rhs> &) noexcept;
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
friend constexpr bool operator<(const dense_set_iterator<ILhs> &, const dense_set_iterator<IRhs> &) noexcept;
|
||||
template<typename Lhs, typename Rhs>
|
||||
friend constexpr bool operator<(const dense_set_iterator<Lhs> &, const dense_set_iterator<Rhs> &) noexcept;
|
||||
|
||||
private:
|
||||
It it;
|
||||
};
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) noexcept {
|
||||
template<typename Lhs, typename Rhs>
|
||||
[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
|
||||
return lhs.it - rhs.it;
|
||||
}
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
[[nodiscard]] constexpr bool operator==(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) noexcept {
|
||||
template<typename Lhs, typename Rhs>
|
||||
[[nodiscard]] constexpr bool operator==(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
|
||||
return lhs.it == rhs.it;
|
||||
}
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
[[nodiscard]] constexpr bool operator!=(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) noexcept {
|
||||
template<typename Lhs, typename Rhs>
|
||||
[[nodiscard]] constexpr bool operator!=(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
[[nodiscard]] constexpr bool operator<(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) noexcept {
|
||||
template<typename Lhs, typename Rhs>
|
||||
[[nodiscard]] constexpr bool operator<(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
|
||||
return lhs.it < rhs.it;
|
||||
}
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
[[nodiscard]] constexpr bool operator>(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) noexcept {
|
||||
template<typename Lhs, typename Rhs>
|
||||
[[nodiscard]] constexpr bool operator>(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
|
||||
return rhs < lhs;
|
||||
}
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
[[nodiscard]] constexpr bool operator<=(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) noexcept {
|
||||
template<typename Lhs, typename Rhs>
|
||||
[[nodiscard]] constexpr bool operator<=(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
|
||||
return !(lhs > rhs);
|
||||
}
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
[[nodiscard]] constexpr bool operator>=(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) noexcept {
|
||||
template<typename Lhs, typename Rhs>
|
||||
[[nodiscard]] constexpr bool operator>=(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
|
||||
return !(lhs < rhs);
|
||||
}
|
||||
|
||||
@ -195,13 +195,13 @@ private:
|
||||
std::size_t offset;
|
||||
};
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
[[nodiscard]] constexpr bool operator==(const dense_set_local_iterator<ILhs> &lhs, const dense_set_local_iterator<IRhs> &rhs) noexcept {
|
||||
template<typename Lhs, typename Rhs>
|
||||
[[nodiscard]] constexpr bool operator==(const dense_set_local_iterator<Lhs> &lhs, const dense_set_local_iterator<Rhs> &rhs) noexcept {
|
||||
return lhs.index() == rhs.index();
|
||||
}
|
||||
|
||||
template<typename ILhs, typename IRhs>
|
||||
[[nodiscard]] constexpr bool operator!=(const dense_set_local_iterator<ILhs> &lhs, const dense_set_local_iterator<IRhs> &rhs) noexcept {
|
||||
template<typename Lhs, typename Rhs>
|
||||
[[nodiscard]] constexpr bool operator!=(const dense_set_local_iterator<Lhs> &lhs, const dense_set_local_iterator<Rhs> &rhs) noexcept {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
@ -410,7 +410,6 @@ public:
|
||||
/**
|
||||
* @brief Returns an iterator to the beginning.
|
||||
*
|
||||
* The returned iterator points to the first instance of the internal array.
|
||||
* If the array is empty, the returned iterator will be equal to `end()`.
|
||||
*
|
||||
* @return An iterator to the first instance of the internal array.
|
||||
@ -431,11 +430,6 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the end.
|
||||
*
|
||||
* The returned iterator points to the element following the last instance
|
||||
* of the internal array. Attempting to dereference the returned iterator
|
||||
* results in undefined behavior.
|
||||
*
|
||||
* @return An iterator to the element following the last instance of the
|
||||
* internal array.
|
||||
*/
|
||||
@ -691,7 +685,7 @@ public:
|
||||
}
|
||||
|
||||
/*! @copydoc equal_range */
|
||||
template<class Other>
|
||||
template<typename Other>
|
||||
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, std::pair<const_iterator, const_iterator>>>
|
||||
equal_range(const Other &value) const {
|
||||
const auto it = find(value);
|
||||
|
@ -95,14 +95,15 @@ struct radix_sort {
|
||||
template<typename It, typename Getter = identity>
|
||||
void operator()(It first, It last, Getter getter = Getter{}) const {
|
||||
if(first < last) {
|
||||
static constexpr auto mask = (1 << Bit) - 1;
|
||||
static constexpr auto buckets = 1 << Bit;
|
||||
static constexpr auto passes = N / Bit;
|
||||
constexpr auto passes = N / Bit;
|
||||
|
||||
using value_type = typename std::iterator_traits<It>::value_type;
|
||||
std::vector<value_type> aux(std::distance(first, last));
|
||||
|
||||
auto part = [getter = std::move(getter)](auto from, auto to, auto out, auto start) {
|
||||
constexpr auto mask = (1 << Bit) - 1;
|
||||
constexpr auto buckets = 1 << Bit;
|
||||
|
||||
std::size_t index[buckets]{};
|
||||
std::size_t count[buckets]{};
|
||||
|
||||
|
@ -59,7 +59,7 @@ class basic_any {
|
||||
};
|
||||
|
||||
template<typename Type>
|
||||
static constexpr bool in_situ = Len && alignof(Type) <= Align && sizeof(Type) <= Len &&std::is_nothrow_move_constructible_v<Type>;
|
||||
static constexpr bool in_situ = Len && alignof(Type) <= Align && sizeof(Type) <= Len && std::is_nothrow_move_constructible_v<Type>;
|
||||
|
||||
template<typename Type>
|
||||
static const void *basic_vtable(const operation op, const basic_any &value, const void *other) {
|
||||
@ -128,17 +128,17 @@ class basic_any {
|
||||
vtable = basic_vtable<std::remove_cv_t<std::remove_reference_t<Type>>>;
|
||||
|
||||
if constexpr(std::is_lvalue_reference_v<Type>) {
|
||||
static_assert(sizeof...(Args) == 1u && (std::is_lvalue_reference_v<Args> && ...), "Invalid arguments");
|
||||
static_assert((std::is_lvalue_reference_v<Args> && ...) && (sizeof...(Args) == 1u), "Invalid arguments");
|
||||
mode = std::is_const_v<std::remove_reference_t<Type>> ? policy::cref : policy::ref;
|
||||
instance = (std::addressof(args), ...);
|
||||
} else if constexpr(in_situ<std::remove_cv_t<std::remove_reference_t<Type>>>) {
|
||||
if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v<std::remove_cv_t<std::remove_reference_t<Type>>>) {
|
||||
if constexpr(std::is_aggregate_v<std::remove_cv_t<std::remove_reference_t<Type>>> && (sizeof...(Args) != 0u || !std::is_default_constructible_v<std::remove_cv_t<std::remove_reference_t<Type>>>)) {
|
||||
new(&storage) std::remove_cv_t<std::remove_reference_t<Type>>{std::forward<Args>(args)...};
|
||||
} else {
|
||||
new(&storage) std::remove_cv_t<std::remove_reference_t<Type>>(std::forward<Args>(args)...);
|
||||
}
|
||||
} else {
|
||||
if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v<std::remove_cv_t<std::remove_reference_t<Type>>>) {
|
||||
if constexpr(std::is_aggregate_v<std::remove_cv_t<std::remove_reference_t<Type>>> && (sizeof...(Args) != 0u || !std::is_default_constructible_v<std::remove_cv_t<std::remove_reference_t<Type>>>)) {
|
||||
instance = new std::remove_cv_t<std::remove_reference_t<Type>>{std::forward<Args>(args)...};
|
||||
} else {
|
||||
instance = new std::remove_cv_t<std::remove_reference_t<Type>>(std::forward<Args>(args)...);
|
||||
@ -428,7 +428,7 @@ private:
|
||||
* @return The element converted to the requested type.
|
||||
*/
|
||||
template<typename Type, std::size_t Len, std::size_t Align>
|
||||
Type any_cast(const basic_any<Len, Align> &data) noexcept {
|
||||
[[nodiscard]] Type any_cast(const basic_any<Len, Align> &data) noexcept {
|
||||
const auto *const instance = any_cast<std::remove_reference_t<Type>>(&data);
|
||||
ENTT_ASSERT(instance, "Invalid instance");
|
||||
return static_cast<Type>(*instance);
|
||||
@ -436,7 +436,7 @@ Type any_cast(const basic_any<Len, Align> &data) noexcept {
|
||||
|
||||
/*! @copydoc any_cast */
|
||||
template<typename Type, std::size_t Len, std::size_t Align>
|
||||
Type any_cast(basic_any<Len, Align> &data) noexcept {
|
||||
[[nodiscard]] Type any_cast(basic_any<Len, Align> &data) noexcept {
|
||||
// forces const on non-reference types to make them work also with wrappers for const references
|
||||
auto *const instance = any_cast<std::remove_reference_t<const Type>>(&data);
|
||||
ENTT_ASSERT(instance, "Invalid instance");
|
||||
@ -445,7 +445,7 @@ Type any_cast(basic_any<Len, Align> &data) noexcept {
|
||||
|
||||
/*! @copydoc any_cast */
|
||||
template<typename Type, std::size_t Len, std::size_t Align>
|
||||
Type any_cast(basic_any<Len, Align> &&data) noexcept {
|
||||
[[nodiscard]] Type any_cast(basic_any<Len, Align> &&data) noexcept {
|
||||
if constexpr(std::is_copy_constructible_v<std::remove_cv_t<std::remove_reference_t<Type>>>) {
|
||||
if(auto *const instance = any_cast<std::remove_reference_t<Type>>(&data); instance) {
|
||||
return static_cast<Type>(std::move(*instance));
|
||||
@ -461,14 +461,14 @@ Type any_cast(basic_any<Len, Align> &&data) noexcept {
|
||||
|
||||
/*! @copydoc any_cast */
|
||||
template<typename Type, std::size_t Len, std::size_t Align>
|
||||
const Type *any_cast(const basic_any<Len, Align> *data) noexcept {
|
||||
[[nodiscard]] const Type *any_cast(const basic_any<Len, Align> *data) noexcept {
|
||||
const auto &info = type_id<std::remove_cv_t<Type>>();
|
||||
return static_cast<const Type *>(data->data(info));
|
||||
}
|
||||
|
||||
/*! @copydoc any_cast */
|
||||
template<typename Type, std::size_t Len, std::size_t Align>
|
||||
Type *any_cast(basic_any<Len, Align> *data) noexcept {
|
||||
[[nodiscard]] Type *any_cast(basic_any<Len, Align> *data) noexcept {
|
||||
if constexpr(std::is_const_v<Type>) {
|
||||
// last attempt to make wrappers for const references return their values
|
||||
return any_cast<Type>(&std::as_const(*data));
|
||||
@ -488,7 +488,7 @@ Type *any_cast(basic_any<Len, Align> *data) noexcept {
|
||||
* @return A properly initialized wrapper for an object of the given type.
|
||||
*/
|
||||
template<typename Type, std::size_t Len = basic_any<>::length, std::size_t Align = basic_any<Len>::alignment, typename... Args>
|
||||
basic_any<Len, Align> make_any(Args &&...args) {
|
||||
[[nodiscard]] basic_any<Len, Align> make_any(Args &&...args) {
|
||||
return basic_any<Len, Align>{std::in_place_type<Type>, std::forward<Args>(args)...};
|
||||
}
|
||||
|
||||
@ -501,7 +501,7 @@ basic_any<Len, Align> make_any(Args &&...args) {
|
||||
* @return A properly initialized and not necessarily owning wrapper.
|
||||
*/
|
||||
template<std::size_t Len = basic_any<>::length, std::size_t Align = basic_any<Len>::alignment, typename Type>
|
||||
basic_any<Len, Align> forward_as_any(Type &&value) {
|
||||
[[nodiscard]] basic_any<Len, Align> forward_as_any(Type &&value) {
|
||||
return basic_any<Len, Align>{std::in_place_type<Type &&>, std::forward<Type>(value)};
|
||||
}
|
||||
|
||||
|
@ -67,7 +67,7 @@ struct basic_hashed_string {
|
||||
template<typename Char>
|
||||
class basic_hashed_string: internal::basic_hashed_string<Char> {
|
||||
using base_type = internal::basic_hashed_string<Char>;
|
||||
using hs_traits = internal::fnv1a_traits<id_type>;
|
||||
using traits_type = internal::fnv1a_traits<id_type>;
|
||||
|
||||
struct const_wrapper {
|
||||
// non-explicit constructor on purpose
|
||||
@ -79,10 +79,10 @@ class basic_hashed_string: internal::basic_hashed_string<Char> {
|
||||
|
||||
// Fowler–Noll–Vo hash function v. 1a - the good
|
||||
[[nodiscard]] static constexpr auto helper(const Char *str) noexcept {
|
||||
base_type base{str, 0u, hs_traits::offset};
|
||||
base_type base{str, 0u, traits_type::offset};
|
||||
|
||||
for(; str[base.length]; ++base.length) {
|
||||
base.hash = (base.hash ^ static_cast<hs_traits::type>(str[base.length])) * hs_traits::prime;
|
||||
base.hash = (base.hash ^ static_cast<traits_type::type>(str[base.length])) * traits_type::prime;
|
||||
}
|
||||
|
||||
return base;
|
||||
@ -90,10 +90,10 @@ class basic_hashed_string: internal::basic_hashed_string<Char> {
|
||||
|
||||
// Fowler–Noll–Vo hash function v. 1a - the good
|
||||
[[nodiscard]] static constexpr auto helper(const Char *str, const std::size_t len) noexcept {
|
||||
base_type base{str, len, hs_traits::offset};
|
||||
base_type base{str, len, traits_type::offset};
|
||||
|
||||
for(size_type pos{}; pos < len; ++pos) {
|
||||
base.hash = (base.hash ^ static_cast<hs_traits::type>(str[pos])) * hs_traits::prime;
|
||||
base.hash = (base.hash ^ static_cast<traits_type::type>(str[pos])) * traits_type::prime;
|
||||
}
|
||||
|
||||
return base;
|
||||
|
@ -106,7 +106,7 @@ constexpr void propagate_on_container_swap([[maybe_unused]] Allocator &lhs, [[ma
|
||||
|
||||
/**
|
||||
* @brief Deleter for allocator-aware unique pointers (waiting for C++20).
|
||||
* @tparam Args Types of arguments to use to construct the object.
|
||||
* @tparam Allocator Type of allocator used to manage memory and elements.
|
||||
*/
|
||||
template<typename Allocator>
|
||||
struct allocation_deleter: private Allocator {
|
||||
@ -127,7 +127,7 @@ struct allocation_deleter: private Allocator {
|
||||
* @param ptr A valid pointer to an object of the given type.
|
||||
*/
|
||||
constexpr void operator()(pointer ptr) noexcept(std::is_nothrow_destructible_v<typename allocator_type::value_type>) {
|
||||
using alloc_traits = typename std::allocator_traits<Allocator>;
|
||||
using alloc_traits = std::allocator_traits<Allocator>;
|
||||
alloc_traits::destroy(*this, to_address(ptr));
|
||||
alloc_traits::deallocate(*this, ptr, 1u);
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ struct forward_apply: private Func {
|
||||
* @tparam Args Types of arguments to use to construct the new instance.
|
||||
* @param args Parameters to use to construct the instance.
|
||||
*/
|
||||
template<class... Args>
|
||||
template<typename... Args>
|
||||
constexpr forward_apply(Args &&...args) noexcept(std::is_nothrow_constructible_v<Func, Args...>)
|
||||
: Func{std::forward<Args>(args)...} {}
|
||||
|
||||
@ -79,13 +79,13 @@ struct forward_apply: private Func {
|
||||
* @param args Parameters to forward to the underlying function.
|
||||
* @return Return value of the underlying function, if any.
|
||||
*/
|
||||
template<class Type>
|
||||
template<typename Type>
|
||||
constexpr decltype(auto) operator()(Type &&args) noexcept(noexcept(std::apply(std::declval<Func &>(), args))) {
|
||||
return std::apply(static_cast<Func &>(*this), std::forward<Type>(args));
|
||||
}
|
||||
|
||||
/*! @copydoc operator()() */
|
||||
template<class Type>
|
||||
template<typename Type>
|
||||
constexpr decltype(auto) operator()(Type &&args) const noexcept(noexcept(std::apply(std::declval<const Func &>(), args))) {
|
||||
return std::apply(static_cast<const Func &>(*this), std::forward<Type>(args));
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include "../config/config.h"
|
||||
@ -55,7 +56,6 @@ using type_identity_t = typename type_identity<Type>::type;
|
||||
/**
|
||||
* @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains.
|
||||
* @tparam Type The type of which to return the size.
|
||||
* @tparam The size of the type if `sizeof` accepts it, 0 otherwise.
|
||||
*/
|
||||
template<typename Type, typename = void>
|
||||
struct size_of: std::integral_constant<std::size_t, 0u> {};
|
||||
@ -297,7 +297,8 @@ struct type_list_contains;
|
||||
* @tparam Other Type to look for.
|
||||
*/
|
||||
template<typename... Type, typename Other>
|
||||
struct type_list_contains<type_list<Type...>, Other>: std::disjunction<std::is_same<Type, Other>...> {};
|
||||
struct type_list_contains<type_list<Type...>, Other>
|
||||
: std::bool_constant<(std::is_same_v<Type, Other> || ...)> {};
|
||||
|
||||
/**
|
||||
* @brief Helper variable template.
|
||||
@ -385,10 +386,20 @@ struct value_list_element<Index, value_list<Value, Other...>>
|
||||
*/
|
||||
template<auto Value, auto... Other>
|
||||
struct value_list_element<0u, value_list<Value, Other...>> {
|
||||
/*! @brief Searched type. */
|
||||
using type = decltype(Value);
|
||||
/*! @brief Searched value. */
|
||||
static constexpr auto value = Value;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Helper type.
|
||||
* @tparam Index Index of the type to return.
|
||||
* @tparam List Value list to search into.
|
||||
*/
|
||||
template<std::size_t Index, typename List>
|
||||
using value_list_element_t = typename value_list_element<Index, List>::type;
|
||||
|
||||
/**
|
||||
* @brief Helper type.
|
||||
* @tparam Index Index of the value to return.
|
||||
@ -397,6 +408,58 @@ struct value_list_element<0u, value_list<Value, Other...>> {
|
||||
template<std::size_t Index, typename List>
|
||||
inline constexpr auto value_list_element_v = value_list_element<Index, List>::value;
|
||||
|
||||
/*! @brief Primary template isn't defined on purpose. */
|
||||
template<auto, typename>
|
||||
struct value_list_index;
|
||||
|
||||
/**
|
||||
* @brief Provides compile-time type access to the values of a value list.
|
||||
* @tparam Value Value to look for and for which to return the index.
|
||||
* @tparam First First value provided by the value list.
|
||||
* @tparam Other Other values provided by the value list.
|
||||
*/
|
||||
template<auto Value, auto First, auto... Other>
|
||||
struct value_list_index<Value, value_list<First, Other...>> {
|
||||
/*! @brief Unsigned integer type. */
|
||||
using value_type = std::size_t;
|
||||
/*! @brief Compile-time position of the given value in the sublist. */
|
||||
static constexpr value_type value = 1u + value_list_index<Value, value_list<Other...>>::value;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Provides compile-time type access to the values of a value list.
|
||||
* @tparam Value Value to look for and for which to return the index.
|
||||
* @tparam Other Other values provided by the value list.
|
||||
*/
|
||||
template<auto Value, auto... Other>
|
||||
struct value_list_index<Value, value_list<Value, Other...>> {
|
||||
static_assert(value_list_index<Value, value_list<Other...>>::value == sizeof...(Other), "Non-unique type");
|
||||
/*! @brief Unsigned integer type. */
|
||||
using value_type = std::size_t;
|
||||
/*! @brief Compile-time position of the given value in the sublist. */
|
||||
static constexpr value_type value = 0u;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Provides compile-time type access to the values of a value list.
|
||||
* @tparam Value Value to look for and for which to return the index.
|
||||
*/
|
||||
template<auto Value>
|
||||
struct value_list_index<Value, value_list<>> {
|
||||
/*! @brief Unsigned integer type. */
|
||||
using value_type = std::size_t;
|
||||
/*! @brief Compile-time position of the given type in the sublist. */
|
||||
static constexpr value_type value = 0u;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Helper variable template.
|
||||
* @tparam List Value list.
|
||||
* @tparam Value Value to look for and for which to return the index.
|
||||
*/
|
||||
template<auto Value, typename List>
|
||||
inline constexpr std::size_t value_list_index_v = value_list_index<Value, List>::value;
|
||||
|
||||
/**
|
||||
* @brief Concatenates multiple value lists.
|
||||
* @tparam Value Values provided by the first value list.
|
||||
@ -448,6 +511,89 @@ struct value_list_cat<value_list<Value...>> {
|
||||
template<typename... List>
|
||||
using value_list_cat_t = typename value_list_cat<List...>::type;
|
||||
|
||||
/*! @brief Primary template isn't defined on purpose. */
|
||||
template<typename>
|
||||
struct value_list_unique;
|
||||
|
||||
/**
|
||||
* @brief Removes duplicates values from a value list.
|
||||
* @tparam Value One of the values provided by the given value list.
|
||||
* @tparam Other The other values provided by the given value list.
|
||||
*/
|
||||
template<auto Value, auto... Other>
|
||||
struct value_list_unique<value_list<Value, Other...>> {
|
||||
/*! @brief A value list without duplicate types. */
|
||||
using type = std::conditional_t<
|
||||
((Value == Other) || ...),
|
||||
typename value_list_unique<value_list<Other...>>::type,
|
||||
value_list_cat_t<value_list<Value>, typename value_list_unique<value_list<Other...>>::type>>;
|
||||
};
|
||||
|
||||
/*! @brief Removes duplicates values from a value list. */
|
||||
template<>
|
||||
struct value_list_unique<value_list<>> {
|
||||
/*! @brief A value list without duplicate types. */
|
||||
using type = value_list<>;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Helper type.
|
||||
* @tparam Type A value list.
|
||||
*/
|
||||
template<typename Type>
|
||||
using value_list_unique_t = typename value_list_unique<Type>::type;
|
||||
|
||||
/**
|
||||
* @brief Provides the member constant `value` to true if a value list contains
|
||||
* a given value, false otherwise.
|
||||
* @tparam List Value list.
|
||||
* @tparam Value Value to look for.
|
||||
*/
|
||||
template<typename List, auto Value>
|
||||
struct value_list_contains;
|
||||
|
||||
/**
|
||||
* @copybrief value_list_contains
|
||||
* @tparam Value Values provided by the value list.
|
||||
* @tparam Other Value to look for.
|
||||
*/
|
||||
template<auto... Value, auto Other>
|
||||
struct value_list_contains<value_list<Value...>, Other>
|
||||
: std::bool_constant<((Value == Other) || ...)> {};
|
||||
|
||||
/**
|
||||
* @brief Helper variable template.
|
||||
* @tparam List Value list.
|
||||
* @tparam Value Value to look for.
|
||||
*/
|
||||
template<typename List, auto Value>
|
||||
inline constexpr bool value_list_contains_v = value_list_contains<List, Value>::value;
|
||||
|
||||
/*! @brief Primary template isn't defined on purpose. */
|
||||
template<typename...>
|
||||
class value_list_diff;
|
||||
|
||||
/**
|
||||
* @brief Computes the difference between two value lists.
|
||||
* @tparam Value Values provided by the first value list.
|
||||
* @tparam Other Values provided by the second value list.
|
||||
*/
|
||||
template<auto... Value, auto... Other>
|
||||
class value_list_diff<value_list<Value...>, value_list<Other...>> {
|
||||
using v141_toolset_workaround = value_list<Other...>;
|
||||
|
||||
public:
|
||||
/*! @brief A value list that is the difference between the two value lists. */
|
||||
using type = value_list_cat_t<std::conditional_t<value_list_contains_v<v141_toolset_workaround, Value>, value_list<>, value_list<Value>>...>;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Helper type.
|
||||
* @tparam List Value lists between which to compute the difference.
|
||||
*/
|
||||
template<typename... List>
|
||||
using value_list_diff_t = typename value_list_diff<List...>::type;
|
||||
|
||||
/*! @brief Same as std::is_invocable, but with tuples. */
|
||||
template<typename, typename>
|
||||
struct is_applicable: std::false_type {};
|
||||
@ -568,7 +714,7 @@ inline constexpr bool is_iterator_v = is_iterator<Type>::value;
|
||||
*/
|
||||
template<typename Type>
|
||||
struct is_ebco_eligible
|
||||
: std::conjunction<std::is_empty<Type>, std::negation<std::is_final<Type>>> {};
|
||||
: std::bool_constant<std::is_empty_v<Type> && !std::is_final_v<Type>> {};
|
||||
|
||||
/**
|
||||
* @brief Helper variable template.
|
||||
@ -659,6 +805,10 @@ template<typename Type>
|
||||
struct is_equality_comparable<Type, std::void_t<decltype(std::declval<Type>() == std::declval<Type>())>>
|
||||
: std::bool_constant<internal::maybe_equality_comparable<Type>(choice<2>)> {};
|
||||
|
||||
/*! @copydoc is_equality_comparable */
|
||||
template<typename Type, auto N>
|
||||
struct is_equality_comparable<Type[N]>: std::false_type {};
|
||||
|
||||
/**
|
||||
* @brief Helper variable template.
|
||||
* @tparam Type The type to test.
|
||||
@ -755,4 +905,16 @@ using nth_argument_t = typename nth_argument<Index, Candidate>::type;
|
||||
|
||||
} // namespace entt
|
||||
|
||||
template<typename... Type>
|
||||
struct std::tuple_size<entt::type_list<Type...>>: std::integral_constant<std::size_t, entt::type_list<Type...>::size> {};
|
||||
|
||||
template<std::size_t Index, typename... Type>
|
||||
struct std::tuple_element<Index, entt::type_list<Type...>>: entt::type_list_element<Index, entt::type_list<Type...>> {};
|
||||
|
||||
template<auto... Value>
|
||||
struct std::tuple_size<entt::value_list<Value...>>: std::integral_constant<std::size_t, entt::value_list<Value...>::size> {};
|
||||
|
||||
template<std::size_t Index, auto... Value>
|
||||
struct std::tuple_element<Index, entt::value_list<Value...>>: entt::value_list_element<Index, entt::value_list<Value...>> {};
|
||||
|
||||
#endif
|
||||
|
@ -17,7 +17,7 @@ struct identity {
|
||||
* @param value The actual argument.
|
||||
* @return The submitted value as-is.
|
||||
*/
|
||||
template<class Type>
|
||||
template<typename Type>
|
||||
[[nodiscard]] constexpr Type &&operator()(Type &&value) const noexcept {
|
||||
return std::forward<Type>(value);
|
||||
}
|
||||
@ -50,7 +50,7 @@ template<typename Func>
|
||||
* @brief Helper type for visitors.
|
||||
* @tparam Func Types of function objects.
|
||||
*/
|
||||
template<class... Func>
|
||||
template<typename... Func>
|
||||
struct overloaded: Func... {
|
||||
using Func::operator()...;
|
||||
};
|
||||
@ -59,14 +59,14 @@ struct overloaded: Func... {
|
||||
* @brief Deduction guide.
|
||||
* @tparam Func Types of function objects.
|
||||
*/
|
||||
template<class... Func>
|
||||
template<typename... Func>
|
||||
overloaded(Func...) -> overloaded<Func...>;
|
||||
|
||||
/**
|
||||
* @brief Basic implementation of a y-combinator.
|
||||
* @tparam Func Type of a potentially recursive function.
|
||||
*/
|
||||
template<class Func>
|
||||
template<typename Func>
|
||||
struct y_combinator {
|
||||
/**
|
||||
* @brief Constructs a y-combinator from a given function.
|
||||
@ -81,13 +81,13 @@ struct y_combinator {
|
||||
* @param args Parameters to use to invoke the underlying function.
|
||||
* @return Return value of the underlying function, if any.
|
||||
*/
|
||||
template<class... Args>
|
||||
template<typename... Args>
|
||||
constexpr decltype(auto) operator()(Args &&...args) const noexcept(std::is_nothrow_invocable_v<Func, const y_combinator &, Args...>) {
|
||||
return func(*this, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/*! @copydoc operator()() */
|
||||
template<class... Args>
|
||||
template<typename... Args>
|
||||
constexpr decltype(auto) operator()(Args &&...args) noexcept(std::is_nothrow_invocable_v<Func, y_combinator &, Args...>) {
|
||||
return func(*this, std::forward<Args>(args)...);
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "fwd.hpp"
|
||||
|
||||
namespace entt {
|
||||
|
||||
@ -17,6 +18,9 @@ namespace internal {
|
||||
template<typename Type, typename = void>
|
||||
struct in_place_delete: std::bool_constant<!(std::is_move_constructible_v<Type> && std::is_move_assignable_v<Type>)> {};
|
||||
|
||||
template<>
|
||||
struct in_place_delete<void>: std::false_type {};
|
||||
|
||||
template<typename Type>
|
||||
struct in_place_delete<Type, std::enable_if_t<Type::in_place_delete>>
|
||||
: std::true_type {};
|
||||
@ -24,6 +28,9 @@ struct in_place_delete<Type, std::enable_if_t<Type::in_place_delete>>
|
||||
template<typename Type, typename = void>
|
||||
struct page_size: std::integral_constant<std::size_t, !std::is_empty_v<ENTT_ETO_TYPE(Type)> * ENTT_PACKED_PAGE> {};
|
||||
|
||||
template<>
|
||||
struct page_size<void>: std::integral_constant<std::size_t, 0u> {};
|
||||
|
||||
template<typename Type>
|
||||
struct page_size<Type, std::enable_if_t<std::is_convertible_v<decltype(Type::page_size), std::size_t>>>
|
||||
: std::integral_constant<std::size_t, Type::page_size> {};
|
||||
@ -52,13 +59,6 @@ struct component_traits {
|
||||
static constexpr std::size_t page_size = internal::page_size<Type>::value;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Helper variable template.
|
||||
* @tparam Type Type of component.
|
||||
*/
|
||||
template<class Type>
|
||||
inline constexpr bool ignore_as_empty_v = (std::is_void_v<Type> || component_traits<Type>::page_size == 0u);
|
||||
|
||||
} // namespace entt
|
||||
|
||||
#endif
|
||||
|
@ -16,35 +16,47 @@ namespace entt {
|
||||
|
||||
namespace internal {
|
||||
|
||||
// waiting for C++20 (and std::popcount)
|
||||
template<typename Type>
|
||||
static constexpr int popcount(Type value) noexcept {
|
||||
return value ? (int(value & 1) + popcount(value >> 1)) : 0;
|
||||
}
|
||||
|
||||
template<typename, typename = void>
|
||||
struct entt_traits;
|
||||
|
||||
template<typename Type>
|
||||
struct entt_traits<Type, std::enable_if_t<std::is_enum_v<Type>>>
|
||||
: entt_traits<std::underlying_type_t<Type>> {};
|
||||
: entt_traits<std::underlying_type_t<Type>> {
|
||||
using value_type = Type;
|
||||
};
|
||||
|
||||
template<typename Type>
|
||||
struct entt_traits<Type, std::enable_if_t<std::is_class_v<Type>>>
|
||||
: entt_traits<typename Type::entity_type> {};
|
||||
: entt_traits<typename Type::entity_type> {
|
||||
using value_type = Type;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct entt_traits<std::uint32_t> {
|
||||
using value_type = std::uint32_t;
|
||||
|
||||
using entity_type = std::uint32_t;
|
||||
using version_type = std::uint16_t;
|
||||
|
||||
static constexpr entity_type entity_mask = 0xFFFFF;
|
||||
static constexpr entity_type version_mask = 0xFFF;
|
||||
static constexpr std::size_t entity_shift = 20u;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct entt_traits<std::uint64_t> {
|
||||
using value_type = std::uint64_t;
|
||||
|
||||
using entity_type = std::uint64_t;
|
||||
using version_type = std::uint32_t;
|
||||
|
||||
static constexpr entity_type entity_mask = 0xFFFFFFFF;
|
||||
static constexpr entity_type version_mask = 0xFFFFFFFF;
|
||||
static constexpr std::size_t entity_shift = 32u;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
@ -55,24 +67,28 @@ struct entt_traits<std::uint64_t> {
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Entity traits.
|
||||
* @tparam Type Type of identifier.
|
||||
* @brief Common basic entity traits implementation.
|
||||
* @tparam Traits Actual entity traits to use.
|
||||
*/
|
||||
template<typename Type>
|
||||
class entt_traits: internal::entt_traits<Type> {
|
||||
using base_type = internal::entt_traits<Type>;
|
||||
template<typename Traits>
|
||||
class basic_entt_traits {
|
||||
static constexpr auto length = internal::popcount(Traits::entity_mask);
|
||||
|
||||
static_assert(Traits::entity_mask && ((typename Traits::entity_type{1} << length) == (Traits::entity_mask + 1)), "Invalid entity mask");
|
||||
static_assert((typename Traits::entity_type{1} << internal::popcount(Traits::version_mask)) == (Traits::version_mask + 1), "Invalid version mask");
|
||||
|
||||
public:
|
||||
/*! @brief Value type. */
|
||||
using value_type = Type;
|
||||
using value_type = typename Traits::value_type;
|
||||
/*! @brief Underlying entity type. */
|
||||
using entity_type = typename base_type::entity_type;
|
||||
using entity_type = typename Traits::entity_type;
|
||||
/*! @brief Underlying version type. */
|
||||
using version_type = typename base_type::version_type;
|
||||
/*! @brief Reserved identifier. */
|
||||
static constexpr entity_type reserved = base_type::entity_mask | (base_type::version_mask << base_type::entity_shift);
|
||||
/*! @brief Page size, default is `ENTT_SPARSE_PAGE`. */
|
||||
static constexpr auto page_size = ENTT_SPARSE_PAGE;
|
||||
using version_type = typename Traits::version_type;
|
||||
|
||||
/*! @brief Entity mask size. */
|
||||
static constexpr entity_type entity_mask = Traits::entity_mask;
|
||||
/*! @brief Version mask size */
|
||||
static constexpr entity_type version_mask = Traits::version_mask;
|
||||
|
||||
/**
|
||||
* @brief Converts an entity to its underlying type.
|
||||
@ -89,7 +105,7 @@ public:
|
||||
* @return The integral representation of the entity part.
|
||||
*/
|
||||
[[nodiscard]] static constexpr entity_type to_entity(const value_type value) noexcept {
|
||||
return (to_integral(value) & base_type::entity_mask);
|
||||
return (to_integral(value) & entity_mask);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -98,7 +114,17 @@ public:
|
||||
* @return The integral representation of the version part.
|
||||
*/
|
||||
[[nodiscard]] static constexpr version_type to_version(const value_type value) noexcept {
|
||||
return (to_integral(value) >> base_type::entity_shift);
|
||||
return static_cast<version_type>(to_integral(value) >> length);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the successor of a given identifier.
|
||||
* @param value The identifier of which to return the successor.
|
||||
* @return The successor of the given identifier.
|
||||
*/
|
||||
[[nodiscard]] static constexpr value_type next(const value_type value) noexcept {
|
||||
const auto vers = to_version(value) + 1;
|
||||
return construct(to_entity(value), static_cast<version_type>(vers + (vers == version_mask)));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,7 +138,7 @@ public:
|
||||
* @return A properly constructed identifier.
|
||||
*/
|
||||
[[nodiscard]] static constexpr value_type construct(const entity_type entity, const version_type version) noexcept {
|
||||
return value_type{(entity & base_type::entity_mask) | (static_cast<entity_type>(version) << base_type::entity_shift)};
|
||||
return value_type{(entity & entity_mask) | (static_cast<entity_type>(version) << length)};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -126,11 +152,23 @@ public:
|
||||
* @return A properly constructed identifier.
|
||||
*/
|
||||
[[nodiscard]] static constexpr value_type combine(const entity_type lhs, const entity_type rhs) noexcept {
|
||||
constexpr auto mask = (base_type::version_mask << base_type::entity_shift);
|
||||
return value_type{(lhs & base_type::entity_mask) | (rhs & mask)};
|
||||
constexpr auto mask = (version_mask << length);
|
||||
return value_type{(lhs & entity_mask) | (rhs & mask)};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Entity traits.
|
||||
* @tparam Type Type of identifier.
|
||||
*/
|
||||
template<typename Type>
|
||||
struct entt_traits: basic_entt_traits<internal::entt_traits<Type>> {
|
||||
/*! @brief Base type. */
|
||||
using base_type = basic_entt_traits<internal::entt_traits<Type>>;
|
||||
/*! @brief Page size, default is `ENTT_SPARSE_PAGE`. */
|
||||
static constexpr std::size_t page_size = ENTT_SPARSE_PAGE;
|
||||
};
|
||||
|
||||
/**
|
||||
* @copydoc entt_traits<Entity>::to_integral
|
||||
* @tparam Entity The value type.
|
||||
@ -167,8 +205,9 @@ struct null_t {
|
||||
*/
|
||||
template<typename Entity>
|
||||
[[nodiscard]] constexpr operator Entity() const noexcept {
|
||||
using entity_traits = entt_traits<Entity>;
|
||||
return entity_traits::combine(entity_traits::reserved, entity_traits::reserved);
|
||||
using traits_type = entt_traits<Entity>;
|
||||
constexpr auto value = traits_type::construct(traits_type::entity_mask, traits_type::version_mask);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -197,8 +236,8 @@ struct null_t {
|
||||
*/
|
||||
template<typename Entity>
|
||||
[[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept {
|
||||
using entity_traits = entt_traits<Entity>;
|
||||
return entity_traits::to_entity(entity) == entity_traits::to_entity(*this);
|
||||
using traits_type = entt_traits<Entity>;
|
||||
return traits_type::to_entity(entity) == traits_type::to_entity(*this);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -246,8 +285,9 @@ struct tombstone_t {
|
||||
*/
|
||||
template<typename Entity>
|
||||
[[nodiscard]] constexpr operator Entity() const noexcept {
|
||||
using entity_traits = entt_traits<Entity>;
|
||||
return entity_traits::combine(entity_traits::reserved, entity_traits::reserved);
|
||||
using traits_type = entt_traits<Entity>;
|
||||
constexpr auto value = traits_type::construct(traits_type::entity_mask, traits_type::version_mask);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -276,8 +316,8 @@ struct tombstone_t {
|
||||
*/
|
||||
template<typename Entity>
|
||||
[[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept {
|
||||
using entity_traits = entt_traits<Entity>;
|
||||
return entity_traits::to_version(entity) == entity_traits::to_version(*this);
|
||||
using traits_type = entt_traits<Entity>;
|
||||
return traits_type::to_version(entity) == traits_type::to_version(*this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,6 +1,7 @@
|
||||
#ifndef ENTT_ENTITY_FWD_HPP
|
||||
#define ENTT_ENTITY_FWD_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include "../core/fwd.hpp"
|
||||
@ -11,6 +12,14 @@ namespace entt {
|
||||
/*! @brief Default entity identifier. */
|
||||
enum class entity : id_type {};
|
||||
|
||||
/*! @brief Storage deletion policy. */
|
||||
enum class deletion_policy : std::uint8_t {
|
||||
/*! @brief Swap-and-pop deletion policy. */
|
||||
swap_and_pop = 0u,
|
||||
/*! @brief In-place deletion policy. */
|
||||
in_place = 1u
|
||||
};
|
||||
|
||||
template<typename Entity = entity, typename = std::allocator<Entity>>
|
||||
class basic_sparse_set;
|
||||
|
||||
@ -18,18 +27,18 @@ template<typename Type, typename = entity, typename = std::allocator<Type>, type
|
||||
class basic_storage;
|
||||
|
||||
template<typename Type>
|
||||
class sigh_storage_mixin;
|
||||
class sigh_mixin;
|
||||
|
||||
/**
|
||||
* @brief Provides a common way to define storage types.
|
||||
* @tparam Type Storage value type.
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Entity A valid entity type.
|
||||
* @tparam Allocator Type of allocator used to manage memory and elements.
|
||||
*/
|
||||
template<typename Type, typename Entity = entity, typename Allocator = std::allocator<Type>, typename = void>
|
||||
struct storage_type {
|
||||
/*! @brief Type-to-storage conversion result. */
|
||||
using type = sigh_storage_mixin<basic_storage<Type, Entity, Allocator>>;
|
||||
using type = sigh_mixin<basic_storage<Type, Entity, Allocator>>;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -42,7 +51,7 @@ using storage_type_t = typename storage_type<Args...>::type;
|
||||
/**
|
||||
* Type-to-storage conversion utility that preserves constness.
|
||||
* @tparam Type Storage value type, eventually const.
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Entity A valid entity type.
|
||||
* @tparam Allocator Type of allocator used to manage memory and elements.
|
||||
*/
|
||||
template<typename Type, typename Entity = entity, typename Allocator = std::allocator<std::remove_const_t<Type>>>
|
||||
@ -70,7 +79,7 @@ class basic_runtime_view;
|
||||
template<typename, typename, typename>
|
||||
class basic_group;
|
||||
|
||||
template<typename>
|
||||
template<typename, typename Mask = std::uint32_t, typename = std::allocator<Mask>>
|
||||
class basic_observer;
|
||||
|
||||
template<typename>
|
||||
@ -93,7 +102,10 @@ class basic_continuous_loader;
|
||||
* @tparam Type List of types.
|
||||
*/
|
||||
template<typename... Type>
|
||||
using exclude_t = type_list<Type...>;
|
||||
struct exclude_t final: type_list<Type...> {
|
||||
/*! @brief Default constructor. */
|
||||
explicit constexpr exclude_t() {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Variable template for exclusion lists.
|
||||
@ -107,7 +119,10 @@ inline constexpr exclude_t<Type...> exclude{};
|
||||
* @tparam Type List of types.
|
||||
*/
|
||||
template<typename... Type>
|
||||
using get_t = type_list<Type...>;
|
||||
struct get_t final: type_list<Type...> {
|
||||
/*! @brief Default constructor. */
|
||||
explicit constexpr get_t() {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Variable template for lists of observed components.
|
||||
@ -121,7 +136,10 @@ inline constexpr get_t<Type...> get{};
|
||||
* @tparam Type List of types.
|
||||
*/
|
||||
template<typename... Type>
|
||||
using owned_t = type_list<Type...>;
|
||||
struct owned_t final: type_list<Type...> {
|
||||
/*! @brief Default constructor. */
|
||||
explicit constexpr owned_t() {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Variable template for lists of owned components.
|
||||
@ -130,6 +148,39 @@ using owned_t = type_list<Type...>;
|
||||
template<typename... Type>
|
||||
inline constexpr owned_t<Type...> owned{};
|
||||
|
||||
/**
|
||||
* @brief Applies a given _function_ to a get list and generate a new list.
|
||||
* @tparam Type Types provided by the get list.
|
||||
* @tparam Op Unary operation as template class with a type member named `type`.
|
||||
*/
|
||||
template<typename... Type, template<typename...> class Op>
|
||||
struct type_list_transform<get_t<Type...>, Op> {
|
||||
/*! @brief Resulting get list after applying the transform function. */
|
||||
using type = get_t<typename Op<Type>::type...>;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Applies a given _function_ to an exclude list and generate a new list.
|
||||
* @tparam Type Types provided by the exclude list.
|
||||
* @tparam Op Unary operation as template class with a type member named `type`.
|
||||
*/
|
||||
template<typename... Type, template<typename...> class Op>
|
||||
struct type_list_transform<exclude_t<Type...>, Op> {
|
||||
/*! @brief Resulting exclude list after applying the transform function. */
|
||||
using type = exclude_t<typename Op<Type>::type...>;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Applies a given _function_ to an owned list and generate a new list.
|
||||
* @tparam Type Types provided by the owned list.
|
||||
* @tparam Op Unary operation as template class with a type member named `type`.
|
||||
*/
|
||||
template<typename... Type, template<typename...> class Op>
|
||||
struct type_list_transform<owned_t<Type...>, Op> {
|
||||
/*! @brief Resulting owned list after applying the transform function. */
|
||||
using type = owned_t<typename Op<Type>::type...>;
|
||||
};
|
||||
|
||||
/*! @brief Alias declaration for the most common use case. */
|
||||
using sparse_set = basic_sparse_set<>;
|
||||
|
||||
|
@ -5,9 +5,10 @@
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include "../config/config.h"
|
||||
#include "../core/fwd.hpp"
|
||||
#include "../core/iterator.hpp"
|
||||
#include "../core/type_info.hpp"
|
||||
#include "../core/type_traits.hpp"
|
||||
#include "component.hpp"
|
||||
#include "entity.hpp"
|
||||
#include "fwd.hpp"
|
||||
#include "sparse_set.hpp"
|
||||
@ -29,7 +30,7 @@ template<typename It, typename... Owned, typename... Get>
|
||||
class extended_group_iterator<It, owned_t<Owned...>, get_t<Get...>> {
|
||||
template<typename Type>
|
||||
auto index_to_element([[maybe_unused]] Type &cpool) const {
|
||||
if constexpr(ignore_as_empty_v<typename Type::value_type>) {
|
||||
if constexpr(Type::traits_type::page_size == 0u) {
|
||||
return std::make_tuple();
|
||||
} else {
|
||||
return std::forward_as_tuple(cpool.rbegin()[it.index()]);
|
||||
@ -37,6 +38,7 @@ class extended_group_iterator<It, owned_t<Owned...>, get_t<Get...>> {
|
||||
}
|
||||
|
||||
public:
|
||||
using iterator_type = It;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval<It>()), std::declval<Owned>().get_as_tuple({})..., std::declval<Get>().get_as_tuple({})...));
|
||||
using pointer = input_iterator_pointer<value_type>;
|
||||
@ -68,6 +70,10 @@ public:
|
||||
return operator*();
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr iterator_type base() const noexcept {
|
||||
return it;
|
||||
}
|
||||
|
||||
template<typename... Lhs, typename... Rhs>
|
||||
friend constexpr bool operator==(const extended_group_iterator<Lhs...> &, const extended_group_iterator<Rhs...> &) noexcept;
|
||||
|
||||
@ -86,6 +92,164 @@ template<typename... Lhs, typename... Rhs>
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
struct group_descriptor {
|
||||
using size_type = std::size_t;
|
||||
virtual ~group_descriptor() = default;
|
||||
virtual size_type owned(const id_type *, const size_type) const noexcept {
|
||||
return 0u;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename, typename, typename>
|
||||
class group_handler;
|
||||
|
||||
template<typename... Owned, typename... Get, typename... Exclude>
|
||||
class group_handler<owned_t<Owned...>, get_t<Get...>, exclude_t<Exclude...>> final: public group_descriptor {
|
||||
// nasty workaround for an issue with the toolset v141 that doesn't accept a fold expression here
|
||||
static_assert(!std::disjunction_v<std::bool_constant<Owned::traits_type::in_place_delete>...>, "Groups do not support in-place delete");
|
||||
static_assert(!std::disjunction_v<std::is_const<Owned>..., std::is_const<Get>..., std::is_const<Exclude>...>, "Const storage type not allowed");
|
||||
|
||||
using base_type = std::common_type_t<typename Owned::base_type..., typename Get::base_type..., typename Exclude::base_type...>;
|
||||
using entity_type = typename base_type::entity_type;
|
||||
|
||||
void swap_elements(const std::size_t pos, const entity_type entt) {
|
||||
std::apply([pos, entt](auto *...cpool) { (cpool->swap_elements(cpool->data()[pos], entt), ...); }, pools);
|
||||
}
|
||||
|
||||
void push_on_construct(const entity_type entt) {
|
||||
if(std::apply([entt, len = len](auto *cpool, auto *...other) { return cpool->contains(entt) && !(cpool->index(entt) < len) && (other->contains(entt) && ...); }, pools)
|
||||
&& std::apply([entt](auto *...cpool) { return (!cpool->contains(entt) && ...); }, filter)) {
|
||||
swap_elements(len++, entt);
|
||||
}
|
||||
}
|
||||
|
||||
void push_on_destroy(const entity_type entt) {
|
||||
if(std::apply([entt, len = len](auto *cpool, auto *...other) { return cpool->contains(entt) && !(cpool->index(entt) < len) && (other->contains(entt) && ...); }, pools)
|
||||
&& std::apply([entt](auto *...cpool) { return (0u + ... + cpool->contains(entt)) == 1u; }, filter)) {
|
||||
swap_elements(len++, entt);
|
||||
}
|
||||
}
|
||||
|
||||
void remove_if(const entity_type entt) {
|
||||
if(std::get<0>(pools)->contains(entt) && (std::get<0>(pools)->index(entt) < len)) {
|
||||
swap_elements(--len, entt);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
using size_type = typename base_type::size_type;
|
||||
|
||||
group_handler(Owned &...opool, Get &...gpool, Exclude &...epool)
|
||||
: pools{&opool..., &gpool...},
|
||||
filter{&epool...},
|
||||
len{} {
|
||||
std::apply([this](auto *...cpool) { ((cpool->on_construct().template connect<&group_handler::push_on_construct>(*this), cpool->on_destroy().template connect<&group_handler::remove_if>(*this)), ...); }, pools);
|
||||
std::apply([this](auto *...cpool) { ((cpool->on_construct().template connect<&group_handler::remove_if>(*this), cpool->on_destroy().template connect<&group_handler::push_on_destroy>(*this)), ...); }, filter);
|
||||
|
||||
// we cannot iterate backwards because we want to leave behind valid entities in case of owned types
|
||||
for(auto *first = std::get<0>(pools)->data(), *last = first + std::get<0>(pools)->size(); first != last; ++first) {
|
||||
push_on_construct(*first);
|
||||
}
|
||||
}
|
||||
|
||||
size_type owned(const id_type *elem, const size_type length) const noexcept final {
|
||||
size_type cnt = 0u;
|
||||
|
||||
for(auto pos = 0u; pos < length; ++pos) {
|
||||
cnt += ((elem[pos] == entt::type_hash<typename Owned::value_type>::value()) || ...);
|
||||
}
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
[[nodiscard]] size_type length() const noexcept {
|
||||
return len;
|
||||
}
|
||||
|
||||
template<typename Type>
|
||||
Type pools_as() const noexcept {
|
||||
return pools;
|
||||
}
|
||||
|
||||
template<typename Type>
|
||||
Type filter_as() const noexcept {
|
||||
return filter;
|
||||
}
|
||||
|
||||
private:
|
||||
std::tuple<Owned *..., Get *...> pools;
|
||||
std::tuple<Exclude *...> filter;
|
||||
std::size_t len;
|
||||
};
|
||||
|
||||
template<typename... Get, typename... Exclude>
|
||||
class group_handler<owned_t<>, get_t<Get...>, exclude_t<Exclude...>> final: public group_descriptor {
|
||||
// nasty workaround for an issue with the toolset v141 that doesn't accept a fold expression here
|
||||
static_assert(!std::disjunction_v<std::is_const<Get>..., std::is_const<Exclude>...>, "Const storage type not allowed");
|
||||
|
||||
using base_type = std::common_type_t<typename Get::base_type..., typename Exclude::base_type...>;
|
||||
using entity_type = typename base_type::entity_type;
|
||||
|
||||
void push_on_construct(const entity_type entt) {
|
||||
if(!elem.contains(entt)
|
||||
&& std::apply([entt](auto *...cpool) { return (cpool->contains(entt) && ...); }, pools)
|
||||
&& std::apply([entt](auto *...cpool) { return (!cpool->contains(entt) && ...); }, filter)) {
|
||||
elem.push(entt);
|
||||
}
|
||||
}
|
||||
|
||||
void push_on_destroy(const entity_type entt) {
|
||||
if(!elem.contains(entt)
|
||||
&& std::apply([entt](auto *...cpool) { return (cpool->contains(entt) && ...); }, pools)
|
||||
&& std::apply([entt](auto *...cpool) { return (0u + ... + cpool->contains(entt)) == 1u; }, filter)) {
|
||||
elem.push(entt);
|
||||
}
|
||||
}
|
||||
|
||||
void remove_if(const entity_type entt) {
|
||||
elem.remove(entt);
|
||||
}
|
||||
|
||||
public:
|
||||
using common_type = base_type;
|
||||
|
||||
template<typename Alloc>
|
||||
group_handler(const Alloc &alloc, Get &...gpool, Exclude &...epool)
|
||||
: pools{&gpool...},
|
||||
filter{&epool...},
|
||||
elem{alloc} {
|
||||
std::apply([this](auto *...cpool) { ((cpool->on_construct().template connect<&group_handler::push_on_construct>(*this), cpool->on_destroy().template connect<&group_handler::remove_if>(*this)), ...); }, pools);
|
||||
std::apply([this](auto *...cpool) { ((cpool->on_construct().template connect<&group_handler::remove_if>(*this), cpool->on_destroy().template connect<&group_handler::push_on_destroy>(*this)), ...); }, filter);
|
||||
|
||||
for(const auto entity: static_cast<base_type &>(*std::get<0>(pools))) {
|
||||
push_on_construct(entity);
|
||||
}
|
||||
}
|
||||
|
||||
common_type &handle() noexcept {
|
||||
return elem;
|
||||
}
|
||||
|
||||
const common_type &handle() const noexcept {
|
||||
return elem;
|
||||
}
|
||||
|
||||
template<typename Type>
|
||||
Type pools_as() const noexcept {
|
||||
return pools;
|
||||
}
|
||||
|
||||
template<typename Type>
|
||||
Type filter_as() const noexcept {
|
||||
return filter;
|
||||
}
|
||||
|
||||
private:
|
||||
std::tuple<Get *...> pools;
|
||||
std::tuple<Exclude *...> filter;
|
||||
base_type elem;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
/**
|
||||
@ -119,18 +283,28 @@ class basic_group;
|
||||
* * The entity currently pointed is destroyed.
|
||||
*
|
||||
* In all other cases, modifying the pools iterated by the group in any way
|
||||
* invalidates all the iterators and using them results in undefined behavior.
|
||||
* invalidates all the iterators.
|
||||
*
|
||||
* @tparam Get Types of storage _observed_ by the group.
|
||||
* @tparam Exclude Types of storage used to filter the group.
|
||||
*/
|
||||
template<typename... Get, typename... Exclude>
|
||||
class basic_group<owned_t<>, get_t<Get...>, exclude_t<Exclude...>> {
|
||||
using underlying_type = std::common_type_t<typename Get::entity_type..., typename Exclude::entity_type...>;
|
||||
using basic_common_type = std::common_type_t<typename Get::base_type..., typename Exclude::base_type...>;
|
||||
using base_type = std::common_type_t<typename Get::base_type..., typename Exclude::base_type...>;
|
||||
using underlying_type = typename base_type::entity_type;
|
||||
|
||||
template<typename Type>
|
||||
static constexpr std::size_t index_of = type_list_index_v<std::remove_const_t<Type>, type_list<typename Get::value_type...>>;
|
||||
static constexpr std::size_t index_of = type_list_index_v<std::remove_const_t<Type>, type_list<typename Get::value_type..., typename Exclude::value_type...>>;
|
||||
|
||||
auto pools() const noexcept {
|
||||
using return_type = std::tuple<Get *...>;
|
||||
return descriptor ? descriptor->template pools_as<return_type>() : return_type{};
|
||||
}
|
||||
|
||||
auto filter() const noexcept {
|
||||
using return_type = std::tuple<Exclude *...>;
|
||||
return descriptor ? descriptor->template filter_as<return_type>() : return_type{};
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Underlying entity identifier. */
|
||||
@ -138,53 +312,59 @@ public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Common type among all storage types. */
|
||||
using base_type = basic_common_type;
|
||||
using common_type = base_type;
|
||||
/*! @brief Random access iterator type. */
|
||||
using iterator = typename base_type::iterator;
|
||||
using iterator = typename common_type::iterator;
|
||||
/*! @brief Reversed iterator type. */
|
||||
using reverse_iterator = typename base_type::reverse_iterator;
|
||||
using reverse_iterator = typename common_type::reverse_iterator;
|
||||
/*! @brief Iterable group type. */
|
||||
using iterable = iterable_adaptor<internal::extended_group_iterator<iterator, owned_t<>, get_t<Get...>>>;
|
||||
/*! @brief Group handler type. */
|
||||
using handler = internal::group_handler<owned_t<>, get_t<std::remove_const_t<Get>...>, exclude_t<std::remove_const_t<Exclude>...>>;
|
||||
|
||||
/*! @brief Default constructor to use to create empty, invalid groups. */
|
||||
basic_group() noexcept
|
||||
: handler{} {}
|
||||
: descriptor{} {}
|
||||
|
||||
/**
|
||||
* @brief Constructs a group from a set of storage classes.
|
||||
* @param ref The actual entities to iterate.
|
||||
* @param gpool Storage types to iterate _observed_ by the group.
|
||||
* @param ref A reference to a group handler.
|
||||
*/
|
||||
basic_group(basic_common_type &ref, Get &...gpool) noexcept
|
||||
: handler{&ref},
|
||||
pools{&gpool...} {}
|
||||
basic_group(handler &ref) noexcept
|
||||
: descriptor{&ref} {}
|
||||
|
||||
/**
|
||||
* @brief Returns a const reference to the underlying handler.
|
||||
* @return A const reference to the underlying handler.
|
||||
* @brief Returns the leading storage of a group.
|
||||
* @return The leading storage of the group.
|
||||
*/
|
||||
[[nodiscard]] const base_type &handle() const noexcept {
|
||||
return *handler;
|
||||
[[nodiscard]] const common_type &handle() const noexcept {
|
||||
return descriptor->handle();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the storage for a given component type.
|
||||
* @brief Returns the storage for a given component type, if any.
|
||||
* @tparam Type Type of component of which to return the storage.
|
||||
* @return The storage for the given component type.
|
||||
*/
|
||||
template<typename Type>
|
||||
[[nodiscard]] decltype(auto) storage() const noexcept {
|
||||
[[nodiscard]] auto *storage() const noexcept {
|
||||
return storage<index_of<Type>>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the storage for a given index.
|
||||
* @brief Returns the storage for a given index, if any.
|
||||
* @tparam Index Index of the storage to return.
|
||||
* @return The storage for the given index.
|
||||
*/
|
||||
template<std::size_t Index>
|
||||
[[nodiscard]] decltype(auto) storage() const noexcept {
|
||||
return *std::get<Index>(pools);
|
||||
[[nodiscard]] auto *storage() const noexcept {
|
||||
constexpr auto offset = sizeof...(Get);
|
||||
|
||||
if constexpr(Index < offset) {
|
||||
return std::get<Index>(pools());
|
||||
} else {
|
||||
return std::get<Index - offset>(filter());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -192,7 +372,7 @@ public:
|
||||
* @return Number of entities that are part of the group.
|
||||
*/
|
||||
[[nodiscard]] size_type size() const noexcept {
|
||||
return *this ? handler->size() : size_type{};
|
||||
return *this ? handle().size() : size_type{};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -201,13 +381,13 @@ public:
|
||||
* @return Capacity of the group.
|
||||
*/
|
||||
[[nodiscard]] size_type capacity() const noexcept {
|
||||
return *this ? handler->capacity() : size_type{};
|
||||
return *this ? handle().capacity() : size_type{};
|
||||
}
|
||||
|
||||
/*! @brief Requests the removal of unused capacity. */
|
||||
void shrink_to_fit() {
|
||||
if(*this) {
|
||||
handler->shrink_to_fit();
|
||||
descriptor->handle().shrink_to_fit();
|
||||
}
|
||||
}
|
||||
|
||||
@ -216,60 +396,48 @@ public:
|
||||
* @return True if the group is empty, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool empty() const noexcept {
|
||||
return !*this || handler->empty();
|
||||
return !*this || handle().empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the first entity of the group.
|
||||
*
|
||||
* The returned iterator points to the first entity of the group. If the
|
||||
* group is empty, the returned iterator will be equal to `end()`.
|
||||
* If the group is empty, the returned iterator will be equal to `end()`.
|
||||
*
|
||||
* @return An iterator to the first entity of the group.
|
||||
*/
|
||||
[[nodiscard]] iterator begin() const noexcept {
|
||||
return *this ? handler->begin() : iterator{};
|
||||
return *this ? handle().begin() : iterator{};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator that is past the last entity of the group.
|
||||
*
|
||||
* The returned iterator points to the entity following the last entity of
|
||||
* the group. Attempting to dereference the returned iterator results in
|
||||
* undefined behavior.
|
||||
*
|
||||
* @return An iterator to the entity following the last entity of the
|
||||
* group.
|
||||
*/
|
||||
[[nodiscard]] iterator end() const noexcept {
|
||||
return *this ? handler->end() : iterator{};
|
||||
return *this ? handle().end() : iterator{};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the first entity of the reversed group.
|
||||
*
|
||||
* The returned iterator points to the first entity of the reversed group.
|
||||
* If the group is empty, the returned iterator will be equal to `rend()`.
|
||||
*
|
||||
* @return An iterator to the first entity of the reversed group.
|
||||
*/
|
||||
[[nodiscard]] reverse_iterator rbegin() const noexcept {
|
||||
return *this ? handler->rbegin() : reverse_iterator{};
|
||||
return *this ? handle().rbegin() : reverse_iterator{};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator that is past the last entity of the reversed
|
||||
* group.
|
||||
*
|
||||
* The returned iterator points to the entity following the last entity of
|
||||
* the reversed group. Attempting to dereference the returned iterator
|
||||
* results in undefined behavior.
|
||||
*
|
||||
* @return An iterator to the entity following the last entity of the
|
||||
* reversed group.
|
||||
*/
|
||||
[[nodiscard]] reverse_iterator rend() const noexcept {
|
||||
return *this ? handler->rend() : reverse_iterator{};
|
||||
return *this ? handle().rend() : reverse_iterator{};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -299,8 +467,7 @@ public:
|
||||
* iterator otherwise.
|
||||
*/
|
||||
[[nodiscard]] iterator find(const entity_type entt) const noexcept {
|
||||
const auto it = *this ? handler->find(entt) : iterator{};
|
||||
return it != end() && *it == entt ? it : end();
|
||||
return *this ? handle().find(entt) : iterator{};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -317,7 +484,7 @@ public:
|
||||
* @return True if the group is properly initialized, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] explicit operator bool() const noexcept {
|
||||
return handler != nullptr;
|
||||
return descriptor != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -326,32 +493,47 @@ public:
|
||||
* @return True if the group contains the given entity, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool contains(const entity_type entt) const noexcept {
|
||||
return *this && handler->contains(entt);
|
||||
return *this && handle().contains(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the components assigned to the given entity.
|
||||
*
|
||||
* Prefer this function instead of `registry::get` during iterations. It has
|
||||
* far better performance than its counterpart.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid component type results in a compilation
|
||||
* error. Attempting to use an entity that doesn't belong to the group
|
||||
* results in undefined behavior.
|
||||
* Attempting to use an entity that doesn't belong to the group results in
|
||||
* undefined behavior.
|
||||
*
|
||||
* @tparam Type Types of components to get.
|
||||
* @tparam Type Type of the component to get.
|
||||
* @tparam Other Other types of components to get.
|
||||
* @param entt A valid identifier.
|
||||
* @return The components assigned to the entity.
|
||||
*/
|
||||
template<typename... Type>
|
||||
template<typename Type, typename... Other>
|
||||
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
|
||||
if constexpr(sizeof...(Type) == 0) {
|
||||
return std::apply([entt](auto *...curr) { return std::tuple_cat(curr->get_as_tuple(entt)...); }, pools);
|
||||
} else if constexpr(sizeof...(Type) == 1) {
|
||||
return (std::get<index_of<Type>>(pools)->get(entt), ...);
|
||||
return get<index_of<Type>, index_of<Other>...>(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the components assigned to the given entity.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that doesn't belong to the groups results in
|
||||
* undefined behavior.
|
||||
*
|
||||
* @tparam Index Indexes of the components to get.
|
||||
* @param entt A valid identifier.
|
||||
* @return The components assigned to the entity.
|
||||
*/
|
||||
template<std::size_t... Index>
|
||||
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
|
||||
const auto cpools = pools();
|
||||
|
||||
if constexpr(sizeof...(Index) == 0) {
|
||||
return std::apply([entt](auto *...curr) { return std::tuple_cat(curr->get_as_tuple(entt)...); }, cpools);
|
||||
} else if constexpr(sizeof...(Index) == 1) {
|
||||
return (std::get<Index>(cpools)->get(entt), ...);
|
||||
} else {
|
||||
return std::tuple_cat(std::get<index_of<Type>>(pools)->get_as_tuple(entt)...);
|
||||
return std::tuple_cat(std::get<Index>(cpools)->get_as_tuple(entt)...);
|
||||
}
|
||||
}
|
||||
|
||||
@ -402,16 +584,13 @@ public:
|
||||
* @return An iterable object to use to _visit_ the group.
|
||||
*/
|
||||
[[nodiscard]] iterable each() const noexcept {
|
||||
return iterable{{begin(), pools}, {end(), pools}};
|
||||
const auto cpools = pools();
|
||||
return iterable{{begin(), cpools}, {end(), cpools}};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sort a group according to the given comparison function.
|
||||
*
|
||||
* Sort the group so that iterating it with a couple of iterators returns
|
||||
* entities and components in the expected order. See `begin` and `end` for
|
||||
* more details.
|
||||
*
|
||||
* The comparison function object must return `true` if the first element
|
||||
* is _less_ than the second one, `false` otherwise. The signature of the
|
||||
* comparison function should be equivalent to one of the following:
|
||||
@ -433,7 +612,8 @@ public:
|
||||
* * An iterator past the last element of the range to sort.
|
||||
* * A comparison function to use to compare the elements.
|
||||
*
|
||||
* @tparam Type Optional types of components to compare.
|
||||
* @tparam Type Optional type of component to compare.
|
||||
* @tparam Other Other optional types of components to compare.
|
||||
* @tparam Compare Type of comparison function object.
|
||||
* @tparam Sort Type of sort function object.
|
||||
* @tparam Args Types of arguments to forward to the sort function object.
|
||||
@ -441,52 +621,60 @@ public:
|
||||
* @param algo A valid sort function object.
|
||||
* @param args Arguments to forward to the sort function object, if any.
|
||||
*/
|
||||
template<typename... Type, typename Compare, typename Sort = std_sort, typename... Args>
|
||||
template<typename Type, typename... Other, typename Compare, typename Sort = std_sort, typename... Args>
|
||||
void sort(Compare compare, Sort algo = Sort{}, Args &&...args) {
|
||||
sort<index_of<Type>, index_of<Other>...>(std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sort a group according to the given comparison function.
|
||||
*
|
||||
* @sa sort
|
||||
*
|
||||
* @tparam Index Optional indexes of components to compare.
|
||||
* @tparam Compare Type of comparison function object.
|
||||
* @tparam Sort Type of sort function object.
|
||||
* @tparam Args Types of arguments to forward to the sort function object.
|
||||
* @param compare A valid comparison function object.
|
||||
* @param algo A valid sort function object.
|
||||
* @param args Arguments to forward to the sort function object, if any.
|
||||
*/
|
||||
template<std::size_t... Index, typename Compare, typename Sort = std_sort, typename... Args>
|
||||
void sort(Compare compare, Sort algo = Sort{}, Args &&...args) {
|
||||
if(*this) {
|
||||
if constexpr(sizeof...(Type) == 0) {
|
||||
if constexpr(sizeof...(Index) == 0) {
|
||||
static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>, "Invalid comparison function");
|
||||
handler->sort(std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||
descriptor->handle().sort(std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||
} else {
|
||||
auto comp = [this, &compare](const entity_type lhs, const entity_type rhs) {
|
||||
if constexpr(sizeof...(Type) == 1) {
|
||||
return compare((std::get<index_of<Type>>(pools)->get(lhs), ...), (std::get<index_of<Type>>(pools)->get(rhs), ...));
|
||||
auto comp = [&compare, cpools = pools()](const entity_type lhs, const entity_type rhs) {
|
||||
if constexpr(sizeof...(Index) == 1) {
|
||||
return compare((std::get<Index>(cpools)->get(lhs), ...), (std::get<Index>(cpools)->get(rhs), ...));
|
||||
} else {
|
||||
return compare(std::forward_as_tuple(std::get<index_of<Type>>(pools)->get(lhs)...), std::forward_as_tuple(std::get<index_of<Type>>(pools)->get(rhs)...));
|
||||
return compare(std::forward_as_tuple(std::get<Index>(cpools)->get(lhs)...), std::forward_as_tuple(std::get<Index>(cpools)->get(rhs)...));
|
||||
}
|
||||
};
|
||||
|
||||
handler->sort(std::move(comp), std::move(algo), std::forward<Args>(args)...);
|
||||
descriptor->handle().sort(std::move(comp), std::move(algo), std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sort the shared pool of entities according to the given component.
|
||||
* @brief Sort the shared pool of entities according to a given storage.
|
||||
*
|
||||
* Non-owning groups of the same type share with the registry a pool of
|
||||
* entities with its own order that doesn't depend on the order of any pool
|
||||
* of components. Users can order the underlying data structure so that it
|
||||
* respects the order of the pool of the given component.
|
||||
*
|
||||
* @note
|
||||
* The shared pool of entities and thus its order is affected by the changes
|
||||
* to each and every pool that it tracks. Therefore changes to those pools
|
||||
* can quickly ruin the order imposed to the pool of entities shared between
|
||||
* the non-owning groups.
|
||||
* to each and every pool that it tracks.
|
||||
*
|
||||
* @tparam Type Type of component to use to impose the order.
|
||||
* @param other The storage to use to impose the order.
|
||||
*/
|
||||
template<typename Type>
|
||||
void sort() const {
|
||||
void sort_as(const common_type &other) const {
|
||||
if(*this) {
|
||||
handler->respect(*std::get<index_of<Type>>(pools));
|
||||
descriptor->handle().sort_as(other);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
base_type *const handler;
|
||||
const std::tuple<Get *...> pools;
|
||||
handler *descriptor;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -514,7 +702,7 @@ private:
|
||||
* * The entity currently pointed is destroyed.
|
||||
*
|
||||
* In all other cases, modifying the pools iterated by the group in any way
|
||||
* invalidates all the iterators and using them results in undefined behavior.
|
||||
* invalidates all the iterators.
|
||||
*
|
||||
* @tparam Owned Types of storage _owned_ by the group.
|
||||
* @tparam Get Types of storage _observed_ by the group.
|
||||
@ -522,11 +710,21 @@ private:
|
||||
*/
|
||||
template<typename... Owned, typename... Get, typename... Exclude>
|
||||
class basic_group<owned_t<Owned...>, get_t<Get...>, exclude_t<Exclude...>> {
|
||||
using underlying_type = std::common_type_t<typename Owned::entity_type..., typename Get::entity_type..., typename Exclude::entity_type...>;
|
||||
using basic_common_type = std::common_type_t<typename Owned::base_type..., typename Get::base_type..., typename Exclude::base_type...>;
|
||||
using base_type = std::common_type_t<typename Owned::base_type..., typename Get::base_type..., typename Exclude::base_type...>;
|
||||
using underlying_type = typename base_type::entity_type;
|
||||
|
||||
template<typename Type>
|
||||
static constexpr std::size_t index_of = type_list_index_v<std::remove_const_t<Type>, type_list<typename Owned::value_type..., typename Get::value_type...>>;
|
||||
static constexpr std::size_t index_of = type_list_index_v<std::remove_const_t<Type>, type_list<typename Owned::value_type..., typename Get::value_type..., typename Exclude::value_type...>>;
|
||||
|
||||
auto pools() const noexcept {
|
||||
using return_type = std::tuple<Owned *..., Get *...>;
|
||||
return descriptor ? descriptor->template pools_as<return_type>() : return_type{};
|
||||
}
|
||||
|
||||
auto filter() const noexcept {
|
||||
using return_type = std::tuple<Exclude *...>;
|
||||
return descriptor ? descriptor->template filter_as<return_type>() : return_type{};
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Underlying entity identifier. */
|
||||
@ -534,46 +732,59 @@ public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Common type among all storage types. */
|
||||
using base_type = basic_common_type;
|
||||
using common_type = base_type;
|
||||
/*! @brief Random access iterator type. */
|
||||
using iterator = typename base_type::iterator;
|
||||
using iterator = typename common_type::iterator;
|
||||
/*! @brief Reversed iterator type. */
|
||||
using reverse_iterator = typename base_type::reverse_iterator;
|
||||
using reverse_iterator = typename common_type::reverse_iterator;
|
||||
/*! @brief Iterable group type. */
|
||||
using iterable = iterable_adaptor<internal::extended_group_iterator<iterator, owned_t<Owned...>, get_t<Get...>>>;
|
||||
/*! @brief Group handler type. */
|
||||
using handler = internal::group_handler<owned_t<std::remove_const_t<Owned>...>, get_t<std::remove_const_t<Get>...>, exclude_t<std::remove_const_t<Exclude>...>>;
|
||||
|
||||
/*! @brief Default constructor to use to create empty, invalid groups. */
|
||||
basic_group() noexcept
|
||||
: length{} {}
|
||||
: descriptor{} {}
|
||||
|
||||
/**
|
||||
* @brief Constructs a group from a set of storage classes.
|
||||
* @param extent The actual number of entities to iterate.
|
||||
* @param opool Storage types to iterate _owned_ by the group.
|
||||
* @param gpool Storage types to iterate _observed_ by the group.
|
||||
* @param ref A reference to a group handler.
|
||||
*/
|
||||
basic_group(const std::size_t &extent, Owned &...opool, Get &...gpool) noexcept
|
||||
: pools{&opool..., &gpool...},
|
||||
length{&extent} {}
|
||||
basic_group(handler &ref) noexcept
|
||||
: descriptor{&ref} {}
|
||||
|
||||
/**
|
||||
* @brief Returns the storage for a given component type.
|
||||
* @brief Returns the leading storage of a group.
|
||||
* @return The leading storage of the group.
|
||||
*/
|
||||
[[nodiscard]] const common_type &handle() const noexcept {
|
||||
return *storage<0>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the storage for a given component type, if any.
|
||||
* @tparam Type Type of component of which to return the storage.
|
||||
* @return The storage for the given component type.
|
||||
*/
|
||||
template<typename Type>
|
||||
[[nodiscard]] decltype(auto) storage() const noexcept {
|
||||
[[nodiscard]] auto *storage() const noexcept {
|
||||
return storage<index_of<Type>>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the storage for a given index.
|
||||
* @brief Returns the storage for a given index, if any.
|
||||
* @tparam Index Index of the storage to return.
|
||||
* @return The storage for the given index.
|
||||
*/
|
||||
template<std::size_t Index>
|
||||
[[nodiscard]] decltype(auto) storage() const noexcept {
|
||||
return *std::get<Index>(pools);
|
||||
[[nodiscard]] auto *storage() const noexcept {
|
||||
constexpr auto offset = sizeof...(Owned) + sizeof...(Get);
|
||||
|
||||
if constexpr(Index < offset) {
|
||||
return std::get<Index>(pools());
|
||||
} else {
|
||||
return std::get<Index - offset>(filter());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -581,7 +792,7 @@ public:
|
||||
* @return Number of entities that that are part of the group.
|
||||
*/
|
||||
[[nodiscard]] size_type size() const noexcept {
|
||||
return *this ? *length : size_type{};
|
||||
return *this ? descriptor->length() : size_type{};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -589,60 +800,48 @@ public:
|
||||
* @return True if the group is empty, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool empty() const noexcept {
|
||||
return !*this || !*length;
|
||||
return !*this || !descriptor->length();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the first entity of the group.
|
||||
*
|
||||
* The returned iterator points to the first entity of the group. If the
|
||||
* group is empty, the returned iterator will be equal to `end()`.
|
||||
* If the group is empty, the returned iterator will be equal to `end()`.
|
||||
*
|
||||
* @return An iterator to the first entity of the group.
|
||||
*/
|
||||
[[nodiscard]] iterator begin() const noexcept {
|
||||
return *this ? (std::get<0>(pools)->base_type::end() - *length) : iterator{};
|
||||
return *this ? (handle().end() - descriptor->length()) : iterator{};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator that is past the last entity of the group.
|
||||
*
|
||||
* The returned iterator points to the entity following the last entity of
|
||||
* the group. Attempting to dereference the returned iterator results in
|
||||
* undefined behavior.
|
||||
*
|
||||
* @return An iterator to the entity following the last entity of the
|
||||
* group.
|
||||
*/
|
||||
[[nodiscard]] iterator end() const noexcept {
|
||||
return *this ? std::get<0>(pools)->base_type::end() : iterator{};
|
||||
return *this ? handle().end() : iterator{};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the first entity of the reversed group.
|
||||
*
|
||||
* The returned iterator points to the first entity of the reversed group.
|
||||
* If the group is empty, the returned iterator will be equal to `rend()`.
|
||||
*
|
||||
* @return An iterator to the first entity of the reversed group.
|
||||
*/
|
||||
[[nodiscard]] reverse_iterator rbegin() const noexcept {
|
||||
return *this ? std::get<0>(pools)->base_type::rbegin() : reverse_iterator{};
|
||||
return *this ? handle().rbegin() : reverse_iterator{};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator that is past the last entity of the reversed
|
||||
* group.
|
||||
*
|
||||
* The returned iterator points to the entity following the last entity of
|
||||
* the reversed group. Attempting to dereference the returned iterator
|
||||
* results in undefined behavior.
|
||||
*
|
||||
* @return An iterator to the entity following the last entity of the
|
||||
* reversed group.
|
||||
*/
|
||||
[[nodiscard]] reverse_iterator rend() const noexcept {
|
||||
return *this ? (std::get<0>(pools)->base_type::rbegin() + *length) : reverse_iterator{};
|
||||
return *this ? (handle().rbegin() + descriptor->length()) : reverse_iterator{};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -672,8 +871,8 @@ public:
|
||||
* iterator otherwise.
|
||||
*/
|
||||
[[nodiscard]] iterator find(const entity_type entt) const noexcept {
|
||||
const auto it = *this ? std::get<0>(pools)->find(entt) : iterator{};
|
||||
return it != end() && it >= begin() && *it == entt ? it : end();
|
||||
const auto it = *this ? handle().find(entt) : iterator{};
|
||||
return it >= begin() ? it : iterator{};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -690,7 +889,7 @@ public:
|
||||
* @return True if the group is properly initialized, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] explicit operator bool() const noexcept {
|
||||
return length != nullptr;
|
||||
return descriptor != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -699,32 +898,47 @@ public:
|
||||
* @return True if the group contains the given entity, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool contains(const entity_type entt) const noexcept {
|
||||
return *this && std::get<0>(pools)->contains(entt) && (std::get<0>(pools)->index(entt) < (*length));
|
||||
return *this && handle().contains(entt) && (handle().index(entt) < (descriptor->length()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the components assigned to the given entity.
|
||||
*
|
||||
* Prefer this function instead of `registry::get` during iterations. It has
|
||||
* far better performance than its counterpart.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid component type results in a compilation
|
||||
* error. Attempting to use an entity that doesn't belong to the group
|
||||
* results in undefined behavior.
|
||||
* Attempting to use an entity that doesn't belong to the group results in
|
||||
* undefined behavior.
|
||||
*
|
||||
* @tparam Type Types of components to get.
|
||||
* @tparam Type Type of the component to get.
|
||||
* @tparam Other Other types of components to get.
|
||||
* @param entt A valid identifier.
|
||||
* @return The components assigned to the entity.
|
||||
*/
|
||||
template<typename... Type>
|
||||
template<typename Type, typename... Other>
|
||||
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
|
||||
if constexpr(sizeof...(Type) == 0) {
|
||||
return std::apply([entt](auto *...curr) { return std::tuple_cat(curr->get_as_tuple(entt)...); }, pools);
|
||||
} else if constexpr(sizeof...(Type) == 1) {
|
||||
return (std::get<index_of<Type>>(pools)->get(entt), ...);
|
||||
return get<index_of<Type>, index_of<Other>...>(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the components assigned to the given entity.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that doesn't belong to the groups results in
|
||||
* undefined behavior.
|
||||
*
|
||||
* @tparam Index Indexes of the components to get.
|
||||
* @param entt A valid identifier.
|
||||
* @return The components assigned to the entity.
|
||||
*/
|
||||
template<std::size_t... Index>
|
||||
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
|
||||
const auto cpools = pools();
|
||||
|
||||
if constexpr(sizeof...(Index) == 0) {
|
||||
return std::apply([entt](auto *...curr) { return std::tuple_cat(curr->get_as_tuple(entt)...); }, cpools);
|
||||
} else if constexpr(sizeof...(Index) == 1) {
|
||||
return (std::get<Index>(cpools)->get(entt), ...);
|
||||
} else {
|
||||
return std::tuple_cat(std::get<index_of<Type>>(pools)->get_as_tuple(entt)...);
|
||||
return std::tuple_cat(std::get<Index>(cpools)->get_as_tuple(entt)...);
|
||||
}
|
||||
}
|
||||
|
||||
@ -775,16 +989,13 @@ public:
|
||||
* @return An iterable object to use to _visit_ the group.
|
||||
*/
|
||||
[[nodiscard]] iterable each() const noexcept {
|
||||
return {{begin(), pools}, {end(), pools}};
|
||||
const auto cpools = pools();
|
||||
return {{begin(), cpools}, {end(), cpools}};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sort a group according to the given comparison function.
|
||||
*
|
||||
* Sort the group so that iterating it with a couple of iterators returns
|
||||
* entities and components in the expected order. See `begin` and `end` for
|
||||
* more details.
|
||||
*
|
||||
* The comparison function object must return `true` if the first element
|
||||
* is _less_ than the second one, `false` otherwise. The signature of the
|
||||
* comparison function should be equivalent to one of the following:
|
||||
@ -807,7 +1018,8 @@ public:
|
||||
* * An iterator past the last element of the range to sort.
|
||||
* * A comparison function to use to compare the elements.
|
||||
*
|
||||
* @tparam Type Optional types of components to compare.
|
||||
* @tparam Type Optional type of component to compare.
|
||||
* @tparam Other Other optional types of components to compare.
|
||||
* @tparam Compare Type of comparison function object.
|
||||
* @tparam Sort Type of sort function object.
|
||||
* @tparam Args Types of arguments to forward to the sort function object.
|
||||
@ -815,36 +1027,56 @@ public:
|
||||
* @param algo A valid sort function object.
|
||||
* @param args Arguments to forward to the sort function object, if any.
|
||||
*/
|
||||
template<typename... Type, typename Compare, typename Sort = std_sort, typename... Args>
|
||||
template<typename Type, typename... Other, typename Compare, typename Sort = std_sort, typename... Args>
|
||||
void sort(Compare compare, Sort algo = Sort{}, Args &&...args) const {
|
||||
if constexpr(sizeof...(Type) == 0) {
|
||||
sort<index_of<Type>, index_of<Other>...>(std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sort a group according to the given comparison function.
|
||||
*
|
||||
* @sa sort
|
||||
*
|
||||
* @tparam Index Optional indexes of components to compare.
|
||||
* @tparam Compare Type of comparison function object.
|
||||
* @tparam Sort Type of sort function object.
|
||||
* @tparam Args Types of arguments to forward to the sort function object.
|
||||
* @param compare A valid comparison function object.
|
||||
* @param algo A valid sort function object.
|
||||
* @param args Arguments to forward to the sort function object, if any.
|
||||
*/
|
||||
template<std::size_t... Index, typename Compare, typename Sort = std_sort, typename... Args>
|
||||
void sort(Compare compare, Sort algo = Sort{}, Args &&...args) const {
|
||||
const auto cpools = pools();
|
||||
|
||||
if constexpr(sizeof...(Index) == 0) {
|
||||
static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>, "Invalid comparison function");
|
||||
std::get<0>(pools)->sort_n(*length, std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||
storage<0>()->sort_n(descriptor->length(), std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||
} else {
|
||||
auto comp = [this, &compare](const entity_type lhs, const entity_type rhs) {
|
||||
if constexpr(sizeof...(Type) == 1) {
|
||||
return compare((std::get<index_of<Type>>(pools)->get(lhs), ...), (std::get<index_of<Type>>(pools)->get(rhs), ...));
|
||||
auto comp = [&compare, &cpools](const entity_type lhs, const entity_type rhs) {
|
||||
if constexpr(sizeof...(Index) == 1) {
|
||||
return compare((std::get<Index>(cpools)->get(lhs), ...), (std::get<Index>(cpools)->get(rhs), ...));
|
||||
} else {
|
||||
return compare(std::forward_as_tuple(std::get<index_of<Type>>(pools)->get(lhs)...), std::forward_as_tuple(std::get<index_of<Type>>(pools)->get(rhs)...));
|
||||
return compare(std::forward_as_tuple(std::get<Index>(cpools)->get(lhs)...), std::forward_as_tuple(std::get<Index>(cpools)->get(rhs)...));
|
||||
}
|
||||
};
|
||||
|
||||
std::get<0>(pools)->sort_n(*length, std::move(comp), std::move(algo), std::forward<Args>(args)...);
|
||||
storage<0>()->sort_n(descriptor->length(), std::move(comp), std::move(algo), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
std::apply([this](auto *head, auto *...other) {
|
||||
for(auto next = *length; next; --next) {
|
||||
auto cb = [this](auto *head, auto *...other) {
|
||||
for(auto next = descriptor->length(); next; --next) {
|
||||
const auto pos = next - 1;
|
||||
[[maybe_unused]] const auto entt = head->data()[pos];
|
||||
(other->swap_elements(other->data()[pos], entt), ...);
|
||||
}
|
||||
},
|
||||
pools);
|
||||
};
|
||||
|
||||
std::apply(cb, cpools);
|
||||
}
|
||||
|
||||
private:
|
||||
const std::tuple<Owned *..., Get *...> pools;
|
||||
const size_type *const length;
|
||||
handler *descriptor;
|
||||
};
|
||||
|
||||
} // namespace entt
|
||||
|
@ -43,7 +43,9 @@ public:
|
||||
: entt{value},
|
||||
it{from},
|
||||
last{to} {
|
||||
while(it != last && !it->second.contains(entt)) { ++it; }
|
||||
while(it != last && !it->second.contains(entt)) {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr handle_storage_iterator &operator++() noexcept {
|
||||
@ -141,7 +143,7 @@ struct basic_handle {
|
||||
|
||||
/**
|
||||
* @brief Constructs a const handle from a non-const one.
|
||||
* @tparam Other A valid entity type (see entt_traits for more details).
|
||||
* @tparam Other A valid entity type.
|
||||
* @tparam Args Scope of the handle to construct.
|
||||
* @return A const handle referring to the same registry and the same
|
||||
* entity.
|
||||
@ -196,7 +198,7 @@ struct basic_handle {
|
||||
|
||||
/*! @brief Destroys the entity associated with a handle. */
|
||||
void destroy() {
|
||||
reg->destroy(entt);
|
||||
reg->destroy(std::exchange(entt, null));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -204,7 +206,7 @@ struct basic_handle {
|
||||
* @param version A desired version upon destruction.
|
||||
*/
|
||||
void destroy(const version_type version) {
|
||||
reg->destroy(entt, version);
|
||||
reg->destroy(std::exchange(entt, null), version);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3,10 +3,10 @@
|
||||
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include "../core/fwd.hpp"
|
||||
#include "../core/type_traits.hpp"
|
||||
#include "../signal/delegate.hpp"
|
||||
#include "component.hpp"
|
||||
#include "fwd.hpp"
|
||||
#include "group.hpp"
|
||||
#include "view.hpp"
|
||||
@ -28,7 +28,7 @@ public:
|
||||
/*! @brief Type of registry to convert. */
|
||||
using registry_type = Registry;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = std::remove_const_t<typename registry_type::entity_type>;
|
||||
using entity_type = typename registry_type::entity_type;
|
||||
|
||||
/**
|
||||
* @brief Constructs a converter for a given registry.
|
||||
@ -71,7 +71,7 @@ public:
|
||||
/*! @brief Type of registry to convert. */
|
||||
using registry_type = Registry;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = std::remove_const_t<typename registry_type::entity_type>;
|
||||
using entity_type = typename registry_type::entity_type;
|
||||
|
||||
/**
|
||||
* @brief Constructs a converter for a given registry.
|
||||
@ -126,19 +126,133 @@ void invoke(Registry ®, const typename Registry::entity_type entt) {
|
||||
*/
|
||||
template<typename Registry, typename Component>
|
||||
typename Registry::entity_type to_entity(const Registry ®, const Component &instance) {
|
||||
const auto &storage = reg.template storage<Component>();
|
||||
const typename Registry::base_type &base = storage;
|
||||
const auto *addr = std::addressof(instance);
|
||||
if(const auto *storage = reg.template storage<Component>(); storage) {
|
||||
constexpr auto page_size = std::remove_const_t<std::remove_pointer_t<decltype(storage)>>::traits_type::page_size;
|
||||
const typename Registry::common_type &base = *storage;
|
||||
const auto *addr = std::addressof(instance);
|
||||
|
||||
for(auto it = base.rbegin(), last = base.rend(); it < last; it += component_traits<Component>::page_size) {
|
||||
if(const auto dist = (addr - std::addressof(storage.get(*it))); dist >= 0 && dist < static_cast<decltype(dist)>(component_traits<Component>::page_size)) {
|
||||
return *(it + dist);
|
||||
for(auto it = base.rbegin(), last = base.rend(); it < last; it += page_size) {
|
||||
if(const auto dist = (addr - std::addressof(storage->get(*it))); dist >= 0 && dist < static_cast<decltype(dist)>(page_size)) {
|
||||
return *(it + dist);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/*! @brief Primary template isn't defined on purpose. */
|
||||
template<typename...>
|
||||
struct sigh_helper;
|
||||
|
||||
/**
|
||||
* @brief Signal connection helper for registries.
|
||||
* @tparam Registry Basic registry type.
|
||||
*/
|
||||
template<typename Registry>
|
||||
struct sigh_helper<Registry> {
|
||||
/*! @brief Registry type. */
|
||||
using registry_type = Registry;
|
||||
|
||||
/**
|
||||
* @brief Constructs a helper for a given registry.
|
||||
* @param ref A valid reference to a registry.
|
||||
*/
|
||||
sigh_helper(registry_type &ref)
|
||||
: bucket{&ref} {}
|
||||
|
||||
/**
|
||||
* @brief Binds a properly initialized helper to a given signal type.
|
||||
* @tparam Type Type of signal to bind the helper to.
|
||||
* @param id Optional name for the underlying storage to use.
|
||||
* @return A helper for a given registry and signal type.
|
||||
*/
|
||||
template<typename Type>
|
||||
auto with(const id_type id = type_hash<Type>::value()) noexcept {
|
||||
return sigh_helper<registry_type, Type>{*bucket, id};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the underlying registry.
|
||||
* @return A reference to the underlying registry.
|
||||
*/
|
||||
[[nodiscard]] registry_type ®istry() noexcept {
|
||||
return *bucket;
|
||||
}
|
||||
|
||||
private:
|
||||
registry_type *bucket;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Signal connection helper for registries.
|
||||
* @tparam Registry Basic registry type.
|
||||
* @tparam Type Type of signal to connect listeners to.
|
||||
*/
|
||||
template<typename Registry, typename Type>
|
||||
struct sigh_helper<Registry, Type> final: sigh_helper<Registry> {
|
||||
/*! @brief Registry type. */
|
||||
using registry_type = Registry;
|
||||
|
||||
/**
|
||||
* @brief Constructs a helper for a given registry.
|
||||
* @param ref A valid reference to a registry.
|
||||
* @param id Optional name for the underlying storage to use.
|
||||
*/
|
||||
sigh_helper(registry_type &ref, const id_type id = type_hash<Type>::value())
|
||||
: sigh_helper<Registry>{ref},
|
||||
name{id} {}
|
||||
|
||||
/**
|
||||
* @brief Forwards the call to `on_construct` on the underlying storage.
|
||||
* @tparam Candidate Function or member to connect.
|
||||
* @tparam Args Type of class or type of payload, if any.
|
||||
* @param args A valid object that fits the purpose, if any.
|
||||
* @return This helper.
|
||||
*/
|
||||
template<auto Candidate, typename... Args>
|
||||
auto on_construct(Args &&...args) {
|
||||
this->registry().template on_construct<Type>(name).template connect<Candidate>(std::forward<Args>(args)...);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Forwards the call to `on_update` on the underlying storage.
|
||||
* @tparam Candidate Function or member to connect.
|
||||
* @tparam Args Type of class or type of payload, if any.
|
||||
* @param args A valid object that fits the purpose, if any.
|
||||
* @return This helper.
|
||||
*/
|
||||
template<auto Candidate, typename... Args>
|
||||
auto on_update(Args &&...args) {
|
||||
this->registry().template on_update<Type>(name).template connect<Candidate>(std::forward<Args>(args)...);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Forwards the call to `on_destroy` on the underlying storage.
|
||||
* @tparam Candidate Function or member to connect.
|
||||
* @tparam Args Type of class or type of payload, if any.
|
||||
* @param args A valid object that fits the purpose, if any.
|
||||
* @return This helper.
|
||||
*/
|
||||
template<auto Candidate, typename... Args>
|
||||
auto on_destroy(Args &&...args) {
|
||||
this->registry().template on_destroy<Type>(name).template connect<Candidate>(std::forward<Args>(args)...);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
id_type name;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Deduction guide.
|
||||
* @tparam Registry Basic registry type.
|
||||
*/
|
||||
template<typename Registry>
|
||||
sigh_helper(Registry &) -> sigh_helper<Registry>;
|
||||
|
||||
} // namespace entt
|
||||
|
||||
#endif
|
||||
|
293
src/entt/entity/mixin.hpp
Normal file
293
src/entt/entity/mixin.hpp
Normal file
@ -0,0 +1,293 @@
|
||||
#ifndef ENTT_ENTITY_MIXIN_HPP
|
||||
#define ENTT_ENTITY_MIXIN_HPP
|
||||
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include "../config/config.h"
|
||||
#include "../core/any.hpp"
|
||||
#include "../signal/sigh.hpp"
|
||||
#include "entity.hpp"
|
||||
#include "fwd.hpp"
|
||||
|
||||
namespace entt {
|
||||
|
||||
/**
|
||||
* @brief Mixin type used to add signal support to storage types.
|
||||
*
|
||||
* The function type of a listener is equivalent to:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(basic_registry<entity_type> &, entity_type);
|
||||
* @endcode
|
||||
*
|
||||
* This applies to all signals made available.
|
||||
*
|
||||
* @tparam Type The type of the underlying storage.
|
||||
*/
|
||||
template<typename Type>
|
||||
class sigh_mixin final: public Type {
|
||||
using underlying_type = Type;
|
||||
using basic_registry_type = basic_registry<typename underlying_type::entity_type, typename underlying_type::base_type::allocator_type>;
|
||||
using sigh_type = sigh<void(basic_registry_type &, const typename underlying_type::entity_type), typename underlying_type::allocator_type>;
|
||||
using underlying_iterator = typename underlying_type::base_type::basic_iterator;
|
||||
|
||||
basic_registry_type &owner_or_assert() const noexcept {
|
||||
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
|
||||
return *owner;
|
||||
}
|
||||
|
||||
void pop(underlying_iterator first, underlying_iterator last) final {
|
||||
if(auto ® = owner_or_assert(); destruction.empty()) {
|
||||
underlying_type::pop(first, last);
|
||||
} else {
|
||||
for(; first != last; ++first) {
|
||||
const auto entt = *first;
|
||||
destruction.publish(reg, entt);
|
||||
const auto it = underlying_type::find(entt);
|
||||
underlying_type::pop(it, it + 1u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pop_all() final {
|
||||
if(auto ® = owner_or_assert(); !destruction.empty()) {
|
||||
for(auto pos = underlying_type::each().begin().base().index(); !(pos < 0); --pos) {
|
||||
if constexpr(underlying_type::traits_type::in_place_delete) {
|
||||
if(const auto entt = underlying_type::operator[](static_cast<typename underlying_type::size_type>(pos)); entt != tombstone) {
|
||||
destruction.publish(reg, entt);
|
||||
}
|
||||
} else {
|
||||
destruction.publish(reg, underlying_type::operator[](static_cast<typename underlying_type::size_type>(pos)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
underlying_type::pop_all();
|
||||
}
|
||||
|
||||
underlying_iterator try_emplace(const typename underlying_type::entity_type entt, const bool force_back, const void *value) final {
|
||||
const auto it = underlying_type::try_emplace(entt, force_back, value);
|
||||
|
||||
if(auto ® = owner_or_assert(); it != underlying_type::base_type::end()) {
|
||||
construction.publish(reg, *it);
|
||||
}
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Allocator type. */
|
||||
using allocator_type = typename underlying_type::allocator_type;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = typename underlying_type::entity_type;
|
||||
/*! @brief Expected registry type. */
|
||||
using registry_type = basic_registry_type;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
sigh_mixin()
|
||||
: sigh_mixin{allocator_type{}} {}
|
||||
|
||||
/**
|
||||
* @brief Constructs an empty storage with a given allocator.
|
||||
* @param allocator The allocator to use.
|
||||
*/
|
||||
explicit sigh_mixin(const allocator_type &allocator)
|
||||
: underlying_type{allocator},
|
||||
owner{},
|
||||
construction{allocator},
|
||||
destruction{allocator},
|
||||
update{allocator} {}
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
* @param other The instance to move from.
|
||||
*/
|
||||
sigh_mixin(sigh_mixin &&other) noexcept
|
||||
: underlying_type{std::move(other)},
|
||||
owner{other.owner},
|
||||
construction{std::move(other.construction)},
|
||||
destruction{std::move(other.destruction)},
|
||||
update{std::move(other.update)} {}
|
||||
|
||||
/**
|
||||
* @brief Allocator-extended move constructor.
|
||||
* @param other The instance to move from.
|
||||
* @param allocator The allocator to use.
|
||||
*/
|
||||
sigh_mixin(sigh_mixin &&other, const allocator_type &allocator) noexcept
|
||||
: underlying_type{std::move(other), allocator},
|
||||
owner{other.owner},
|
||||
construction{std::move(other.construction), allocator},
|
||||
destruction{std::move(other.destruction), allocator},
|
||||
update{std::move(other.update), allocator} {}
|
||||
|
||||
/**
|
||||
* @brief Move assignment operator.
|
||||
* @param other The instance to move from.
|
||||
* @return This storage.
|
||||
*/
|
||||
sigh_mixin &operator=(sigh_mixin &&other) noexcept {
|
||||
underlying_type::operator=(std::move(other));
|
||||
owner = other.owner;
|
||||
construction = std::move(other.construction);
|
||||
destruction = std::move(other.destruction);
|
||||
update = std::move(other.update);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Exchanges the contents with those of a given storage.
|
||||
* @param other Storage to exchange the content with.
|
||||
*/
|
||||
void swap(sigh_mixin &other) {
|
||||
using std::swap;
|
||||
underlying_type::swap(other);
|
||||
swap(owner, other.owner);
|
||||
swap(construction, other.construction);
|
||||
swap(destruction, other.destruction);
|
||||
swap(update, other.update);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a sink object.
|
||||
*
|
||||
* The sink returned by this function can be used to receive notifications
|
||||
* whenever a new instance is created and assigned to an entity.<br/>
|
||||
* Listeners are invoked after the object has been assigned to the entity.
|
||||
*
|
||||
* @sa sink
|
||||
*
|
||||
* @return A temporary sink object.
|
||||
*/
|
||||
[[nodiscard]] auto on_construct() noexcept {
|
||||
return sink{construction};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a sink object.
|
||||
*
|
||||
* The sink returned by this function can be used to receive notifications
|
||||
* whenever an instance is explicitly updated.<br/>
|
||||
* Listeners are invoked after the object has been updated.
|
||||
*
|
||||
* @sa sink
|
||||
*
|
||||
* @return A temporary sink object.
|
||||
*/
|
||||
[[nodiscard]] auto on_update() noexcept {
|
||||
return sink{update};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a sink object.
|
||||
*
|
||||
* The sink returned by this function can be used to receive notifications
|
||||
* whenever an instance is removed from an entity and thus destroyed.<br/>
|
||||
* Listeners are invoked before the object has been removed from the entity.
|
||||
*
|
||||
* @sa sink
|
||||
*
|
||||
* @return A temporary sink object.
|
||||
*/
|
||||
[[nodiscard]] auto on_destroy() noexcept {
|
||||
return sink{destruction};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Emplace elements into a storage.
|
||||
*
|
||||
* The behavior of this operation depends on the underlying storage type
|
||||
* (for example, components vs entities).<br/>
|
||||
* Refer to the specific documentation for more details.
|
||||
*
|
||||
* @return A return value as returned by the underlying storage.
|
||||
*/
|
||||
auto emplace() {
|
||||
const auto entt = underlying_type::emplace();
|
||||
construction.publish(owner_or_assert(), entt);
|
||||
return entt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Emplace elements into a storage.
|
||||
*
|
||||
* The behavior of this operation depends on the underlying storage type
|
||||
* (for example, components vs entities).<br/>
|
||||
* Refer to the specific documentation for more details.
|
||||
*
|
||||
* @tparam Args Types of arguments to forward to the underlying storage.
|
||||
* @param hint A valid identifier.
|
||||
* @param args Parameters to forward to the underlying storage.
|
||||
* @return A return value as returned by the underlying storage.
|
||||
*/
|
||||
template<typename... Args>
|
||||
decltype(auto) emplace(const entity_type hint, Args &&...args) {
|
||||
if constexpr(std::is_same_v<typename underlying_type::value_type, typename underlying_type::entity_type>) {
|
||||
const auto entt = underlying_type::emplace(hint, std::forward<Args>(args)...);
|
||||
construction.publish(owner_or_assert(), entt);
|
||||
return entt;
|
||||
} else {
|
||||
underlying_type::emplace(hint, std::forward<Args>(args)...);
|
||||
construction.publish(owner_or_assert(), hint);
|
||||
return this->get(hint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Patches the given instance for an entity.
|
||||
* @tparam Func Types of the function objects to invoke.
|
||||
* @param entt A valid identifier.
|
||||
* @param func Valid function objects.
|
||||
* @return A reference to the patched instance.
|
||||
*/
|
||||
template<typename... Func>
|
||||
decltype(auto) patch(const entity_type entt, Func &&...func) {
|
||||
underlying_type::patch(entt, std::forward<Func>(func)...);
|
||||
update.publish(owner_or_assert(), entt);
|
||||
return this->get(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Emplace elements into a storage.
|
||||
*
|
||||
* The behavior of this operation depends on the underlying storage type
|
||||
* (for example, components vs entities).<br/>
|
||||
* Refer to the specific documentation for more details.
|
||||
*
|
||||
* @tparam It Iterator type (as required by the underlying storage type).
|
||||
* @tparam Args Types of arguments to forward to the underlying storage.
|
||||
* @param first An iterator to the first element of the range.
|
||||
* @param last An iterator past the last element of the range.
|
||||
* @param args Parameters to use to forward to the underlying storage.
|
||||
*/
|
||||
template<typename It, typename... Args>
|
||||
void insert(It first, It last, Args &&...args) {
|
||||
underlying_type::insert(first, last, std::forward<Args>(args)...);
|
||||
|
||||
if(auto ® = owner_or_assert(); !construction.empty()) {
|
||||
for(; first != last; ++first) {
|
||||
construction.publish(reg, *first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Forwards variables to derived classes, if any.
|
||||
* @param value A variable wrapped in an opaque container.
|
||||
*/
|
||||
void bind(any value) noexcept final {
|
||||
auto *reg = any_cast<basic_registry_type>(&value);
|
||||
owner = reg ? reg : owner;
|
||||
underlying_type::bind(std::move(value));
|
||||
}
|
||||
|
||||
private:
|
||||
basic_registry_type *owner;
|
||||
sigh_type construction;
|
||||
sigh_type destruction;
|
||||
sigh_type update;
|
||||
};
|
||||
|
||||
} // namespace entt
|
||||
|
||||
#endif
|
@ -43,7 +43,7 @@ struct basic_collector<> {
|
||||
* @return The updated collector.
|
||||
*/
|
||||
template<typename... AllOf, typename... NoneOf>
|
||||
static constexpr auto group(exclude_t<NoneOf...> = {}) noexcept {
|
||||
static constexpr auto group(exclude_t<NoneOf...> = exclude_t{}) noexcept {
|
||||
return basic_collector<matcher<type_list<>, type_list<>, type_list<NoneOf...>, AllOf...>>{};
|
||||
}
|
||||
|
||||
@ -78,7 +78,7 @@ struct basic_collector<matcher<type_list<Reject...>, type_list<Require...>, Rule
|
||||
* @return The updated collector.
|
||||
*/
|
||||
template<typename... AllOf, typename... NoneOf>
|
||||
static constexpr auto group(exclude_t<NoneOf...> = {}) noexcept {
|
||||
static constexpr auto group(exclude_t<NoneOf...> = exclude_t{}) noexcept {
|
||||
return basic_collector<matcher<type_list<>, type_list<>, type_list<NoneOf...>, AllOf...>, current_type, Other...>{};
|
||||
}
|
||||
|
||||
@ -99,7 +99,7 @@ struct basic_collector<matcher<type_list<Reject...>, type_list<Require...>, Rule
|
||||
* @return The updated collector.
|
||||
*/
|
||||
template<typename... AllOf, typename... NoneOf>
|
||||
static constexpr auto where(exclude_t<NoneOf...> = {}) noexcept {
|
||||
static constexpr auto where(exclude_t<NoneOf...> = exclude_t{}) noexcept {
|
||||
using extended_type = matcher<type_list<Reject..., NoneOf...>, type_list<Require..., AllOf...>, Rule...>;
|
||||
return basic_collector<extended_type, Other...>{};
|
||||
}
|
||||
@ -146,8 +146,7 @@ inline constexpr basic_collector<> collector{};
|
||||
* * The entity currently pointed is destroyed.
|
||||
*
|
||||
* In all the other cases, modifying the pools of the given components in any
|
||||
* way invalidates all the iterators and using them results in undefined
|
||||
* behavior.
|
||||
* way invalidates all the iterators.
|
||||
*
|
||||
* @warning
|
||||
* Lifetime of an observer doesn't necessarily have to overcome that of the
|
||||
@ -156,10 +155,12 @@ inline constexpr basic_collector<> collector{};
|
||||
* pointers.
|
||||
*
|
||||
* @tparam Registry Basic registry type.
|
||||
* @tparam Mask Mask type.
|
||||
* @tparam Allocator Type of allocator used to manage memory and elements.
|
||||
*/
|
||||
template<typename Registry>
|
||||
class basic_observer: private basic_storage<std::uint32_t, typename Registry::entity_type> {
|
||||
using base_type = basic_storage<std::uint32_t, typename Registry::entity_type>;
|
||||
template<typename Registry, typename Mask, typename Allocator>
|
||||
class basic_observer: private basic_storage<Mask, typename Registry::entity_type, Allocator> {
|
||||
using base_type = basic_storage<Mask, typename Registry::entity_type, Allocator>;
|
||||
|
||||
template<typename>
|
||||
struct matcher_handler;
|
||||
@ -193,10 +194,10 @@ class basic_observer: private basic_storage<std::uint32_t, typename Registry::en
|
||||
}
|
||||
|
||||
static void disconnect(basic_observer &obs, Registry ®) {
|
||||
(reg.template on_destroy<Require>().disconnect(obs), ...);
|
||||
(reg.template on_construct<Reject>().disconnect(obs), ...);
|
||||
reg.template on_update<AnyOf>().disconnect(obs);
|
||||
reg.template on_destroy<AnyOf>().disconnect(obs);
|
||||
(reg.template on_destroy<Require>().disconnect(&obs), ...);
|
||||
(reg.template on_construct<Reject>().disconnect(&obs), ...);
|
||||
reg.template on_update<AnyOf>().disconnect(&obs);
|
||||
reg.template on_destroy<AnyOf>().disconnect(&obs);
|
||||
}
|
||||
};
|
||||
|
||||
@ -239,12 +240,12 @@ class basic_observer: private basic_storage<std::uint32_t, typename Registry::en
|
||||
}
|
||||
|
||||
static void disconnect(basic_observer &obs, Registry ®) {
|
||||
(reg.template on_destroy<Require>().disconnect(obs), ...);
|
||||
(reg.template on_construct<Reject>().disconnect(obs), ...);
|
||||
(reg.template on_construct<AllOf>().disconnect(obs), ...);
|
||||
(reg.template on_destroy<NoneOf>().disconnect(obs), ...);
|
||||
(reg.template on_destroy<AllOf>().disconnect(obs), ...);
|
||||
(reg.template on_construct<NoneOf>().disconnect(obs), ...);
|
||||
(reg.template on_destroy<Require>().disconnect(&obs), ...);
|
||||
(reg.template on_construct<Reject>().disconnect(&obs), ...);
|
||||
(reg.template on_construct<AllOf>().disconnect(&obs), ...);
|
||||
(reg.template on_destroy<NoneOf>().disconnect(&obs), ...);
|
||||
(reg.template on_destroy<AllOf>().disconnect(&obs), ...);
|
||||
(reg.template on_construct<NoneOf>().disconnect(&obs), ...);
|
||||
}
|
||||
};
|
||||
|
||||
@ -267,15 +268,26 @@ public:
|
||||
using entity_type = typename registry_type::entity_type;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Allocator type. */
|
||||
using allocator_type = Allocator;
|
||||
/*! @brief Random access iterator type. */
|
||||
using iterator = typename registry_type::base_type::iterator;
|
||||
using iterator = typename registry_type::common_type::iterator;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
basic_observer()
|
||||
: release{} {}
|
||||
: basic_observer{allocator_type{}} {}
|
||||
|
||||
/**
|
||||
* @brief Constructs an empty storage with a given allocator.
|
||||
* @param allocator The allocator to use.
|
||||
*/
|
||||
explicit basic_observer(const allocator_type &allocator)
|
||||
: base_type{allocator},
|
||||
release{} {}
|
||||
|
||||
/*! @brief Default copy constructor, deleted on purpose. */
|
||||
basic_observer(const basic_observer &) = delete;
|
||||
|
||||
/*! @brief Default move constructor, deleted on purpose. */
|
||||
basic_observer(basic_observer &&) = delete;
|
||||
|
||||
@ -283,16 +295,14 @@ public:
|
||||
* @brief Creates an observer and connects it to a given registry.
|
||||
* @tparam Matcher Types of matchers to use to initialize the observer.
|
||||
* @param reg A valid reference to a registry.
|
||||
* @param allocator The allocator to use.
|
||||
*/
|
||||
template<typename... Matcher>
|
||||
basic_observer(registry_type ®, basic_collector<Matcher...>)
|
||||
: basic_observer{} {
|
||||
basic_observer(registry_type ®, basic_collector<Matcher...>, const allocator_type &allocator = allocator_type{})
|
||||
: basic_observer{allocator} {
|
||||
connect<Matcher...>(reg, std::index_sequence_for<Matcher...>{});
|
||||
}
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
~basic_observer() = default;
|
||||
|
||||
/**
|
||||
* @brief Default copy assignment operator, deleted on purpose.
|
||||
* @return This observer.
|
||||
@ -360,8 +370,7 @@ public:
|
||||
/**
|
||||
* @brief Returns an iterator to the first entity of the observer.
|
||||
*
|
||||
* The returned iterator points to the first entity of the observer. If the
|
||||
* container is empty, the returned iterator will be equal to `end()`.
|
||||
* If the observer is empty, the returned iterator will be equal to `end()`.
|
||||
*
|
||||
* @return An iterator to the first entity of the observer.
|
||||
*/
|
||||
@ -371,11 +380,6 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator that is past the last entity of the observer.
|
||||
*
|
||||
* The returned iterator points to the entity following the last entity of
|
||||
* the observer. Attempting to dereference the returned iterator results in
|
||||
* undefined behavior.
|
||||
*
|
||||
* @return An iterator to the entity following the last entity of the
|
||||
* observer.
|
||||
*/
|
||||
|
@ -126,7 +126,7 @@ class basic_organizer final {
|
||||
if constexpr(std::is_same_v<Type, Registry>) {
|
||||
return reg;
|
||||
} else if constexpr(internal::is_view_v<Type>) {
|
||||
return as_view{reg};
|
||||
return static_cast<Type>(as_view{reg});
|
||||
} else {
|
||||
return reg.ctx().template emplace<std::remove_reference_t<Type>>();
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -104,38 +104,22 @@ private:
|
||||
/**
|
||||
* @brief Generic runtime view.
|
||||
*
|
||||
* Runtime views iterate over those entities that have at least all the given
|
||||
* components in their bags. During initialization, a runtime view looks at the
|
||||
* number of entities available for each component and picks up a reference to
|
||||
* the smallest set of candidate entities in order to get a performance boost
|
||||
* when iterate.<br/>
|
||||
* Order of elements during iterations are highly dependent on the order of the
|
||||
* underlying data structures. See sparse_set and its specializations for more
|
||||
* details.
|
||||
* Runtime views iterate over those entities that are at least in the given
|
||||
* storage. During initialization, a runtime view looks at the number of
|
||||
* entities available for each component and uses the smallest set in order to
|
||||
* get a performance boost when iterating.
|
||||
*
|
||||
* @b Important
|
||||
*
|
||||
* Iterators aren't invalidated if:
|
||||
*
|
||||
* * New instances of the given components are created and assigned to entities.
|
||||
* * The entity currently pointed is modified (as an example, if one of the
|
||||
* given components is removed from the entity to which the iterator points).
|
||||
* * New elements are added to the storage.
|
||||
* * The entity currently pointed is modified (for example, components are added
|
||||
* or removed from it).
|
||||
* * The entity currently pointed is destroyed.
|
||||
*
|
||||
* In all the other cases, modifying the pools of the given components in any
|
||||
* way invalidates all the iterators and using them results in undefined
|
||||
* behavior.
|
||||
*
|
||||
* @note
|
||||
* Views share references to the underlying data structures of the registry that
|
||||
* generated them. Therefore any change to the entities and to the components
|
||||
* made by means of the registry are immediately reflected by the views, unless
|
||||
* a pool was missing when the view was built (in this case, the view won't
|
||||
* have a valid reference and won't be updated accordingly).
|
||||
*
|
||||
* @warning
|
||||
* Lifetime of a view must not overcome that of the registry that generated it.
|
||||
* In any other case, attempting to use a view results in undefined behavior.
|
||||
* In all other cases, modifying the storage iterated by the view in any way
|
||||
* invalidates all the iterators.
|
||||
*
|
||||
* @tparam Type Common base type.
|
||||
* @tparam Allocator Type of allocator used to manage memory and elements.
|
||||
@ -154,9 +138,9 @@ public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Common type among all storage types. */
|
||||
using base_type = Type;
|
||||
using common_type = Type;
|
||||
/*! @brief Bidirectional iterator type. */
|
||||
using iterator = internal::runtime_view_iterator<base_type>;
|
||||
using iterator = internal::runtime_view_iterator<common_type>;
|
||||
|
||||
/*! @brief Default constructor to use to create empty, invalid views. */
|
||||
basic_runtime_view() noexcept
|
||||
@ -235,7 +219,7 @@ public:
|
||||
* @param base An opaque reference to a storage object.
|
||||
* @return This runtime view.
|
||||
*/
|
||||
basic_runtime_view &iterate(base_type &base) {
|
||||
basic_runtime_view &iterate(common_type &base) {
|
||||
if(pools.empty() || !(base.size() < pools[0u]->size())) {
|
||||
pools.push_back(&base);
|
||||
} else {
|
||||
@ -250,7 +234,7 @@ public:
|
||||
* @param base An opaque reference to a storage object.
|
||||
* @return This runtime view.
|
||||
*/
|
||||
basic_runtime_view &exclude(base_type &base) {
|
||||
basic_runtime_view &exclude(common_type &base) {
|
||||
filter.push_back(&base);
|
||||
return *this;
|
||||
}
|
||||
@ -267,9 +251,7 @@ public:
|
||||
* @brief Returns an iterator to the first entity that has the given
|
||||
* components.
|
||||
*
|
||||
* The returned iterator points to the first entity that has the given
|
||||
* components. If the view is empty, the returned iterator will be equal to
|
||||
* `end()`.
|
||||
* If the view is empty, the returned iterator will be equal to `end()`.
|
||||
*
|
||||
* @return An iterator to the first entity that has the given components.
|
||||
*/
|
||||
@ -280,11 +262,6 @@ public:
|
||||
/**
|
||||
* @brief Returns an iterator that is past the last entity that has the
|
||||
* given components.
|
||||
*
|
||||
* The returned iterator points to the entity following the last entity that
|
||||
* has the given components. Attempting to dereference the returned iterator
|
||||
* results in undefined behavior.
|
||||
*
|
||||
* @return An iterator to the entity following the last entity that has the
|
||||
* given components.
|
||||
*/
|
||||
@ -307,8 +284,7 @@ public:
|
||||
* @brief Iterates entities and applies the given function object to them.
|
||||
*
|
||||
* The function object is invoked for each entity. It is provided only with
|
||||
* the entity itself. To get the components, users can use the registry with
|
||||
* which the view was built.<br/>
|
||||
* the entity itself.<br/>
|
||||
* The signature of the function should be equivalent to the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
|
@ -1,7 +1,6 @@
|
||||
#ifndef ENTT_ENTITY_SNAPSHOT_HPP
|
||||
#define ENTT_ENTITY_SNAPSHOT_HPP
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <tuple>
|
||||
@ -11,13 +10,37 @@
|
||||
#include "../config/config.h"
|
||||
#include "../container/dense_map.hpp"
|
||||
#include "../core/type_traits.hpp"
|
||||
#include "component.hpp"
|
||||
#include "entity.hpp"
|
||||
#include "fwd.hpp"
|
||||
#include "view.hpp"
|
||||
|
||||
namespace entt {
|
||||
|
||||
/**
|
||||
* @cond TURN_OFF_DOXYGEN
|
||||
* Internal details not to be documented.
|
||||
*/
|
||||
|
||||
namespace internal {
|
||||
|
||||
template<typename Registry>
|
||||
void orphans(Registry ®istry) {
|
||||
auto view = registry.template view<typename Registry::entity_type>();
|
||||
|
||||
for(auto entt: view) {
|
||||
if(registry.orphan(entt)) {
|
||||
view.storage()->erase(entt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
/**
|
||||
* Internal details not to be documented.
|
||||
* @endcond
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Utility class to create snapshots from a registry.
|
||||
*
|
||||
@ -30,34 +53,8 @@ namespace entt {
|
||||
*/
|
||||
template<typename Registry>
|
||||
class basic_snapshot {
|
||||
using entity_traits = entt_traits<typename Registry::entity_type>;
|
||||
|
||||
template<typename Component, typename Archive, typename It>
|
||||
void get(Archive &archive, std::size_t sz, It first, It last) const {
|
||||
const auto view = reg->template view<const Component>();
|
||||
archive(typename entity_traits::entity_type(sz));
|
||||
|
||||
while(first != last) {
|
||||
const auto entt = *(first++);
|
||||
|
||||
if(reg->template all_of<Component>(entt)) {
|
||||
std::apply(archive, std::tuple_cat(std::make_tuple(entt), view.get(entt)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... Component, typename Archive, typename It, std::size_t... Index>
|
||||
void component(Archive &archive, It first, It last, std::index_sequence<Index...>) const {
|
||||
std::array<std::size_t, sizeof...(Index)> size{};
|
||||
auto begin = first;
|
||||
|
||||
while(begin != last) {
|
||||
const auto entt = *(begin++);
|
||||
((reg->template all_of<Component>(entt) ? ++size[Index] : 0u), ...);
|
||||
}
|
||||
|
||||
(get<Component>(archive, size[Index], first, last), ...);
|
||||
}
|
||||
static_assert(!std::is_const_v<Registry>, "Non-const registry type required");
|
||||
using traits_type = typename Registry::traits_type;
|
||||
|
||||
public:
|
||||
/*! Basic registry type. */
|
||||
@ -79,58 +76,96 @@ public:
|
||||
basic_snapshot &operator=(basic_snapshot &&) noexcept = default;
|
||||
|
||||
/**
|
||||
* @brief Puts aside all the entities from the underlying registry.
|
||||
*
|
||||
* Entities are serialized along with their versions. Destroyed entities are
|
||||
* taken in consideration as well by this function.
|
||||
*
|
||||
* @brief Serializes all elements of a type with associated identifiers.
|
||||
* @tparam Type Type of elements to serialize.
|
||||
* @tparam Archive Type of output archive.
|
||||
* @param archive A valid reference to an output archive.
|
||||
* @param id Optional name used to map the storage within the registry.
|
||||
* @return An object of this type to continue creating the snapshot.
|
||||
*/
|
||||
template<typename Archive>
|
||||
const basic_snapshot &entities(Archive &archive) const {
|
||||
const auto sz = reg->size();
|
||||
template<typename Type, typename Archive>
|
||||
const basic_snapshot &get(Archive &archive, const id_type id = type_hash<Type>::value()) const {
|
||||
if(const auto *storage = reg->template storage<Type>(id); storage) {
|
||||
archive(static_cast<typename traits_type::entity_type>(storage->size()));
|
||||
|
||||
archive(typename entity_traits::entity_type(sz + 1u));
|
||||
archive(reg->released());
|
||||
if constexpr(std::is_same_v<Type, entity_type>) {
|
||||
archive(static_cast<typename traits_type::entity_type>(storage->in_use()));
|
||||
|
||||
for(auto first = reg->data(), last = first + sz; first != last; ++first) {
|
||||
archive(*first);
|
||||
for(auto first = storage->data(), last = first + storage->size(); first != last; ++first) {
|
||||
archive(*first);
|
||||
}
|
||||
} else {
|
||||
for(auto elem: storage->reach()) {
|
||||
std::apply([&archive](auto &&...args) { (archive(std::forward<decltype(args)>(args)), ...); }, elem);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
archive(typename traits_type::entity_type{});
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Puts aside the given components.
|
||||
*
|
||||
* Each instance is serialized together with the entity to which it belongs.
|
||||
* Entities are serialized along with their versions.
|
||||
*
|
||||
* @brief Serializes all elements of a type with associated identifiers for
|
||||
* the entities in a range.
|
||||
* @tparam Type Type of elements to serialize.
|
||||
* @tparam Archive Type of output archive.
|
||||
* @tparam It Type of input iterator.
|
||||
* @param archive A valid reference to an output archive.
|
||||
* @param first An iterator to the first element of the range to serialize.
|
||||
* @param last An iterator past the last element of the range to serialize.
|
||||
* @param id Optional name used to map the storage within the registry.
|
||||
* @return An object of this type to continue creating the snapshot.
|
||||
*/
|
||||
template<typename Type, typename Archive, typename It>
|
||||
const basic_snapshot &get(Archive &archive, It first, It last, const id_type id = type_hash<Type>::value()) const {
|
||||
static_assert(!std::is_same_v<Type, entity_type>, "Entity types not supported");
|
||||
|
||||
if(const auto *storage = reg->template storage<Type>(id); storage && !storage->empty()) {
|
||||
archive(static_cast<typename traits_type::entity_type>(std::distance(first, last)));
|
||||
|
||||
for(; first != last; ++first) {
|
||||
if(const auto entt = *first; storage->contains(entt)) {
|
||||
archive(entt);
|
||||
std::apply([&archive](auto &&...args) { (archive(std::forward<decltype(args)>(args)), ...); }, storage->get_as_tuple(entt));
|
||||
} else {
|
||||
archive(static_cast<entity_type>(null));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
archive(typename traits_type::entity_type{});
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Serializes all identifiers, including those to be recycled.
|
||||
* @tparam Archive Type of output archive.
|
||||
* @param archive A valid reference to an output archive.
|
||||
* @return An object of this type to continue creating the snapshot.
|
||||
*/
|
||||
template<typename Archive>
|
||||
[[deprecated("use .get<Entity>(archive) instead")]] const basic_snapshot &entities(Archive &archive) const {
|
||||
return get<entity_type>(archive);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Serializes all elements of a type with associated identifiers.
|
||||
* @tparam Component Types of components to serialize.
|
||||
* @tparam Archive Type of output archive.
|
||||
* @param archive A valid reference to an output archive.
|
||||
* @return An object of this type to continue creating the snapshot.
|
||||
*/
|
||||
template<typename... Component, typename Archive>
|
||||
const basic_snapshot &component(Archive &archive) const {
|
||||
if constexpr(sizeof...(Component) == 1u) {
|
||||
const auto view = reg->template view<const Component...>();
|
||||
(component<Component>(archive, view.rbegin(), view.rend()), ...);
|
||||
return *this;
|
||||
} else {
|
||||
(component<Component>(archive), ...);
|
||||
return *this;
|
||||
}
|
||||
[[deprecated("use .get<Type>(archive) instead")]] const basic_snapshot &component(Archive &archive) const {
|
||||
return (get<Component>(archive), ...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Puts aside the given components for the entities in a range.
|
||||
*
|
||||
* Each instance is serialized together with the entity to which it belongs.
|
||||
* Entities are serialized along with their versions.
|
||||
*
|
||||
* @brief Serializes all elements of a type with associated identifiers for
|
||||
* the entities in a range.
|
||||
* @tparam Component Types of components to serialize.
|
||||
* @tparam Archive Type of output archive.
|
||||
* @tparam It Type of input iterator.
|
||||
@ -140,9 +175,8 @@ public:
|
||||
* @return An object of this type to continue creating the snapshot.
|
||||
*/
|
||||
template<typename... Component, typename Archive, typename It>
|
||||
const basic_snapshot &component(Archive &archive, It first, It last) const {
|
||||
component<Component...>(archive, first, last, std::index_sequence_for<Component...>{});
|
||||
return *this;
|
||||
[[deprecated("use .get<Type>(archive, first, last) instead")]] const basic_snapshot &component(Archive &archive, It first, It last) const {
|
||||
return (get<Component>(archive, first, last), ...);
|
||||
}
|
||||
|
||||
private:
|
||||
@ -161,33 +195,8 @@ private:
|
||||
*/
|
||||
template<typename Registry>
|
||||
class basic_snapshot_loader {
|
||||
using entity_traits = entt_traits<typename Registry::entity_type>;
|
||||
|
||||
template<typename Component, typename Archive>
|
||||
void assign(Archive &archive) const {
|
||||
typename entity_traits::entity_type length{};
|
||||
entity_type entt;
|
||||
|
||||
archive(length);
|
||||
|
||||
if constexpr(ignore_as_empty_v<Component>) {
|
||||
while(length--) {
|
||||
archive(entt);
|
||||
const auto entity = reg->valid(entt) ? entt : reg->create(entt);
|
||||
ENTT_ASSERT(entity == entt, "Entity not available for use");
|
||||
reg->template emplace<Component>(entt);
|
||||
}
|
||||
} else {
|
||||
Component instance;
|
||||
|
||||
while(length--) {
|
||||
archive(entt, instance);
|
||||
const auto entity = reg->valid(entt) ? entt : reg->create(entt);
|
||||
ENTT_ASSERT(entity == entt, "Entity not available for use");
|
||||
reg->template emplace<Component>(entt, std::move(instance));
|
||||
}
|
||||
}
|
||||
}
|
||||
static_assert(!std::is_const_v<Registry>, "Non-const registry type required");
|
||||
using traits_type = typename Registry::traits_type;
|
||||
|
||||
public:
|
||||
/*! Basic registry type. */
|
||||
@ -202,7 +211,9 @@ public:
|
||||
basic_snapshot_loader(registry_type &source) noexcept
|
||||
: reg{&source} {
|
||||
// restoring a snapshot as a whole requires a clean registry
|
||||
ENTT_ASSERT(reg->empty(), "Registry must be empty");
|
||||
for([[maybe_unused]] auto elem: source.storage()) {
|
||||
ENTT_ASSERT(elem.second.empty(), "Registry must be empty");
|
||||
}
|
||||
}
|
||||
|
||||
/*! @brief Default move constructor. */
|
||||
@ -212,48 +223,80 @@ public:
|
||||
basic_snapshot_loader &operator=(basic_snapshot_loader &&) noexcept = default;
|
||||
|
||||
/**
|
||||
* @brief Restores entities that were in use during serialization.
|
||||
*
|
||||
* This function restores the entities that were in use during serialization
|
||||
* and gives them the versions they originally had.
|
||||
*
|
||||
* @brief Restores all elements of a type with associated identifiers.
|
||||
* @tparam Type Type of elements to restore.
|
||||
* @tparam Archive Type of input archive.
|
||||
* @param archive A valid reference to an input archive.
|
||||
* @param id Optional name used to map the storage within the registry.
|
||||
* @return A valid loader to continue restoring data.
|
||||
*/
|
||||
template<typename Archive>
|
||||
const basic_snapshot_loader &entities(Archive &archive) const {
|
||||
typename entity_traits::entity_type length{};
|
||||
template<typename Type, typename Archive>
|
||||
basic_snapshot_loader &get([[maybe_unused]] Archive &archive, const id_type id = type_hash<Type>::value()) {
|
||||
auto &storage = reg->template storage<Type>(id);
|
||||
typename traits_type::entity_type length{};
|
||||
|
||||
archive(length);
|
||||
std::vector<entity_type> all(length);
|
||||
|
||||
for(std::size_t pos{}; pos < length; ++pos) {
|
||||
archive(all[pos]);
|
||||
if constexpr(std::is_same_v<Type, entity_type>) {
|
||||
typename traits_type::entity_type in_use{};
|
||||
|
||||
storage.reserve(length);
|
||||
archive(in_use);
|
||||
|
||||
for(entity_type entity = null; length; --length) {
|
||||
archive(entity);
|
||||
storage.emplace(entity);
|
||||
}
|
||||
|
||||
storage.in_use(in_use);
|
||||
} else {
|
||||
auto &other = reg->template storage<entity_type>();
|
||||
entity_type entt{null};
|
||||
|
||||
while(length--) {
|
||||
if(archive(entt); entt != null) {
|
||||
const auto entity = other.contains(entt) ? entt : other.emplace(entt);
|
||||
ENTT_ASSERT(entity == entt, "Entity not available for use");
|
||||
|
||||
if constexpr(Registry::template storage_for_type<Type>::traits_type::page_size == 0u) {
|
||||
storage.emplace(entity);
|
||||
} else {
|
||||
Type elem{};
|
||||
archive(elem);
|
||||
storage.emplace(entity, std::move(elem));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reg->assign(++all.cbegin(), all.cend(), all[0u]);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Restores components and assigns them to the right entities.
|
||||
* @brief Restores all identifiers, including those to be recycled.
|
||||
* @tparam Archive Type of input archive.
|
||||
* @param archive A valid reference to an input archive.
|
||||
* @return A valid loader to continue restoring data.
|
||||
*/
|
||||
template<typename Archive>
|
||||
[[deprecated("use .get<Entity>(archive) instead")]] basic_snapshot_loader &entities(Archive &archive) {
|
||||
return get<entity_type>(archive);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Restores all elements of a type with associated identifiers.
|
||||
*
|
||||
* The template parameter list must be exactly the same used during
|
||||
* serialization. In the event that the entity to which the component is
|
||||
* assigned doesn't exist yet, the loader will take care to create it with
|
||||
* the version it originally had.
|
||||
* serialization.
|
||||
*
|
||||
* @tparam Component Types of components to restore.
|
||||
* @tparam Component Type of component to restore.
|
||||
* @tparam Archive Type of input archive.
|
||||
* @param archive A valid reference to an input archive.
|
||||
* @return A valid loader to continue restoring data.
|
||||
*/
|
||||
template<typename... Component, typename Archive>
|
||||
const basic_snapshot_loader &component(Archive &archive) const {
|
||||
(assign<Component>(archive), ...);
|
||||
return *this;
|
||||
[[deprecated("use .get<Type>(archive) instead")]] basic_snapshot_loader &component(Archive &archive) {
|
||||
return (get<Component>(archive), ...);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -262,17 +305,12 @@ public:
|
||||
* In case all the entities were serialized but only part of the components
|
||||
* was saved, it could happen that some of the entities have no components
|
||||
* once restored.<br/>
|
||||
* This functions helps to identify and destroy those entities.
|
||||
* This function helps to identify and destroy those entities.
|
||||
*
|
||||
* @return A valid loader to continue restoring data.
|
||||
*/
|
||||
const basic_snapshot_loader &orphans() const {
|
||||
reg->each([this](const auto entt) {
|
||||
if(reg->orphan(entt)) {
|
||||
reg->release(entt);
|
||||
}
|
||||
});
|
||||
|
||||
basic_snapshot_loader &orphans() {
|
||||
internal::orphans(*reg);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -290,7 +328,7 @@ private:
|
||||
* Identifiers that entities originally had are not transferred to the target.
|
||||
* Instead, the loader maps remote identifiers to local ones while restoring a
|
||||
* snapshot.<br/>
|
||||
* An example of use is the implementation of a client-server applications with
|
||||
* An example of use is the implementation of a client-server application with
|
||||
* the requirement of transferring somehow parts of the representation side to
|
||||
* side.
|
||||
*
|
||||
@ -298,29 +336,16 @@ private:
|
||||
*/
|
||||
template<typename Registry>
|
||||
class basic_continuous_loader {
|
||||
using entity_traits = entt_traits<typename Registry::entity_type>;
|
||||
|
||||
void destroy(typename Registry::entity_type entt) {
|
||||
if(const auto it = remloc.find(entt); it == remloc.cend()) {
|
||||
const auto local = reg->create();
|
||||
remloc.emplace(entt, std::make_pair(local, true));
|
||||
reg->destroy(local);
|
||||
}
|
||||
}
|
||||
static_assert(!std::is_const_v<Registry>, "Non-const registry type required");
|
||||
using traits_type = typename Registry::traits_type;
|
||||
|
||||
void restore(typename Registry::entity_type entt) {
|
||||
const auto it = remloc.find(entt);
|
||||
|
||||
if(it == remloc.cend()) {
|
||||
const auto local = reg->create();
|
||||
remloc.emplace(entt, std::make_pair(local, true));
|
||||
} else {
|
||||
if(!reg->valid(remloc[entt].first)) {
|
||||
remloc[entt].first = reg->create();
|
||||
if(const auto entity = to_entity(entt); remloc.contains(entity) && remloc[entity].first == entt) {
|
||||
if(!reg->valid(remloc[entity].second)) {
|
||||
remloc[entity].second = reg->create();
|
||||
}
|
||||
|
||||
// set the dirty flag
|
||||
remloc[entt].second = true;
|
||||
} else {
|
||||
remloc.insert_or_assign(entity, std::make_pair(entt, reg->create()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -369,42 +394,6 @@ class basic_continuous_loader {
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Component>
|
||||
void remove_if_exists() {
|
||||
for(auto &&ref: remloc) {
|
||||
const auto local = ref.second.first;
|
||||
|
||||
if(reg->valid(local)) {
|
||||
reg->template remove<Component>(local);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Component, typename Archive, typename... Other, typename... Member>
|
||||
void assign(Archive &archive, [[maybe_unused]] Member Other::*...member) {
|
||||
typename entity_traits::entity_type length{};
|
||||
entity_type entt;
|
||||
|
||||
archive(length);
|
||||
|
||||
if constexpr(ignore_as_empty_v<Component>) {
|
||||
while(length--) {
|
||||
archive(entt);
|
||||
restore(entt);
|
||||
reg->template emplace_or_replace<Component>(map(entt));
|
||||
}
|
||||
} else {
|
||||
Component instance;
|
||||
|
||||
while(length--) {
|
||||
archive(entt, instance);
|
||||
(update(instance, member), ...);
|
||||
restore(entt);
|
||||
reg->template emplace_or_replace<Component>(map(entt), std::move(instance));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/*! Basic registry type. */
|
||||
using registry_type = Registry;
|
||||
@ -416,7 +405,8 @@ public:
|
||||
* @param source A valid reference to a registry.
|
||||
*/
|
||||
basic_continuous_loader(registry_type &source) noexcept
|
||||
: reg{&source} {}
|
||||
: remloc{source.get_allocator()},
|
||||
reg{&source} {}
|
||||
|
||||
/*! @brief Default move constructor. */
|
||||
basic_continuous_loader(basic_continuous_loader &&) = default;
|
||||
@ -425,90 +415,142 @@ public:
|
||||
basic_continuous_loader &operator=(basic_continuous_loader &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Restores entities that were in use during serialization.
|
||||
* @brief Restores all elements of a type with associated identifiers.
|
||||
*
|
||||
* This function restores the entities that were in use during serialization
|
||||
* and creates local counterparts for them if required.
|
||||
* It creates local counterparts for remote elements as needed.<br/>
|
||||
* Members are either data members of type entity_type or containers of
|
||||
* entities. In both cases, a loader visits them and replaces entities with
|
||||
* their local counterpart.
|
||||
*
|
||||
* @tparam Type Type of elements to restore.
|
||||
* @tparam Archive Type of input archive.
|
||||
* @param archive A valid reference to an input archive.
|
||||
* @param id Optional name used to map the storage within the registry.
|
||||
* @return A valid loader to continue restoring data.
|
||||
*/
|
||||
template<typename Type, typename Archive>
|
||||
basic_continuous_loader &get([[maybe_unused]] Archive &archive, const id_type id = type_hash<Type>::value()) {
|
||||
auto &storage = reg->template storage<Type>(id);
|
||||
typename traits_type::entity_type length{};
|
||||
entity_type entt{null};
|
||||
|
||||
archive(length);
|
||||
|
||||
if constexpr(std::is_same_v<Type, entity_type>) {
|
||||
typename traits_type::entity_type in_use{};
|
||||
|
||||
storage.reserve(length);
|
||||
archive(in_use);
|
||||
|
||||
for(std::size_t pos{}; pos < in_use; ++pos) {
|
||||
archive(entt);
|
||||
restore(entt);
|
||||
}
|
||||
|
||||
for(std::size_t pos = in_use; pos < length; ++pos) {
|
||||
archive(entt);
|
||||
|
||||
if(const auto entity = to_entity(entt); remloc.contains(entity)) {
|
||||
if(reg->valid(remloc[entity].second)) {
|
||||
reg->destroy(remloc[entity].second);
|
||||
}
|
||||
|
||||
remloc.erase(entity);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(auto &&ref: remloc) {
|
||||
storage.remove(ref.second.second);
|
||||
}
|
||||
|
||||
while(length--) {
|
||||
if(archive(entt); entt != null) {
|
||||
restore(entt);
|
||||
|
||||
if constexpr(Registry::template storage_for_type<Type>::traits_type::page_size == 0u) {
|
||||
storage.emplace(map(entt));
|
||||
} else {
|
||||
Type elem{};
|
||||
archive(elem);
|
||||
storage.emplace(map(entt), std::move(elem));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Restores all identifiers, including those to be recycled.
|
||||
*
|
||||
* It creates local counterparts for remote elements as needed.
|
||||
*
|
||||
* @tparam Archive Type of input archive.
|
||||
* @param archive A valid reference to an input archive.
|
||||
* @return A non-const reference to this loader.
|
||||
*/
|
||||
template<typename Archive>
|
||||
basic_continuous_loader &entities(Archive &archive) {
|
||||
typename entity_traits::entity_type length{};
|
||||
entity_type entt{};
|
||||
|
||||
archive(length);
|
||||
// discards the head of the list of destroyed entities
|
||||
archive(entt);
|
||||
|
||||
for(std::size_t pos{}, last = length - 1u; pos < last; ++pos) {
|
||||
archive(entt);
|
||||
|
||||
if(const auto entity = entity_traits::to_entity(entt); entity == pos) {
|
||||
restore(entt);
|
||||
} else {
|
||||
destroy(entt);
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
[[deprecated("use .get<Entity>(archive) instead")]] basic_continuous_loader &entities(Archive &archive) {
|
||||
return get<entity_type>(archive);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Restores components and assigns them to the right entities.
|
||||
* @brief Serializes all elements of a type with associated identifiers.
|
||||
*
|
||||
* The template parameter list must be exactly the same used during
|
||||
* serialization. In the event that the entity to which the component is
|
||||
* assigned doesn't exist yet, the loader will take care to create a local
|
||||
* counterpart for it.<br/>
|
||||
* Members can be either data members of type entity_type or containers of
|
||||
* entities. In both cases, the loader will visit them and update the
|
||||
* entities by replacing each one with its local counterpart.
|
||||
* It creates local counterparts for remote elements as needed.<br/>
|
||||
* Members are either data members of type entity_type or containers of
|
||||
* entities. In both cases, a loader visits them and replaces entities with
|
||||
* their local counterpart.
|
||||
*
|
||||
* @tparam Component Type of component to restore.
|
||||
* @tparam Archive Type of input archive.
|
||||
* @tparam Other Types of components to update with local counterparts.
|
||||
* @tparam Member Types of members to update with their local counterparts.
|
||||
* @param archive A valid reference to an input archive.
|
||||
* @param member Members to update with their local counterparts.
|
||||
* @return A non-const reference to this loader.
|
||||
*/
|
||||
template<typename... Component, typename Archive, typename... Other, typename... Member>
|
||||
basic_continuous_loader &component(Archive &archive, Member Other::*...member) {
|
||||
(remove_if_exists<Component>(), ...);
|
||||
(assign<Component>(archive, member...), ...);
|
||||
template<typename... Component, typename Archive, typename... Member, typename... Clazz>
|
||||
[[deprecated("use .component<Type>(archive, members...) instead")]] basic_continuous_loader &component(Archive &archive, Member Clazz::*...member) {
|
||||
([&](auto &storage) {
|
||||
for(auto &&ref: remloc) {
|
||||
storage.remove(ref.second.second);
|
||||
}
|
||||
|
||||
typename traits_type::entity_type length{};
|
||||
entity_type entt{null};
|
||||
|
||||
archive(length);
|
||||
|
||||
while(length--) {
|
||||
if(archive(entt); entt != null) {
|
||||
restore(entt);
|
||||
|
||||
if constexpr(std::remove_reference_t<decltype(storage)>::traits_type::page_size == 0u) {
|
||||
storage.emplace(map(entt));
|
||||
} else {
|
||||
typename std::remove_reference_t<decltype(storage)>::value_type elem{};
|
||||
archive(elem);
|
||||
(update(elem, member), ...);
|
||||
storage.emplace(map(entt), std::move(elem));
|
||||
}
|
||||
}
|
||||
}
|
||||
}(reg->template storage<Component>()),
|
||||
...);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Helps to purge entities that no longer have a conterpart.
|
||||
* @brief Helps to purge entities that no longer have a counterpart.
|
||||
*
|
||||
* Users should invoke this member function after restoring each snapshot,
|
||||
* unless they know exactly what they are doing.
|
||||
*
|
||||
* @return A non-const reference to this loader.
|
||||
*/
|
||||
basic_continuous_loader &shrink() {
|
||||
auto it = remloc.begin();
|
||||
|
||||
while(it != remloc.cend()) {
|
||||
const auto local = it->second.first;
|
||||
bool &dirty = it->second.second;
|
||||
|
||||
if(dirty) {
|
||||
dirty = false;
|
||||
++it;
|
||||
} else {
|
||||
if(reg->valid(local)) {
|
||||
reg->destroy(local);
|
||||
}
|
||||
|
||||
it = remloc.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
[[deprecated("use .get<Entity>(archive) instead")]] basic_continuous_loader &shrink() {
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -518,17 +560,12 @@ public:
|
||||
* In case all the entities were serialized but only part of the components
|
||||
* was saved, it could happen that some of the entities have no components
|
||||
* once restored.<br/>
|
||||
* This functions helps to identify and destroy those entities.
|
||||
* This function helps to identify and destroy those entities.
|
||||
*
|
||||
* @return A non-const reference to this loader.
|
||||
*/
|
||||
basic_continuous_loader &orphans() {
|
||||
reg->each([this](const auto entt) {
|
||||
if(reg->orphan(entt)) {
|
||||
reg->release(entt);
|
||||
}
|
||||
});
|
||||
|
||||
internal::orphans(*reg);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -538,7 +575,8 @@ public:
|
||||
* @return True if `entity` is managed by the loader, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool contains(entity_type entt) const noexcept {
|
||||
return (remloc.find(entt) != remloc.cend());
|
||||
const auto it = remloc.find(to_entity(entt));
|
||||
return it != remloc.cend() && it->second.first == entt;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -547,18 +585,15 @@ public:
|
||||
* @return The local identifier if any, the null entity otherwise.
|
||||
*/
|
||||
[[nodiscard]] entity_type map(entity_type entt) const noexcept {
|
||||
const auto it = remloc.find(entt);
|
||||
entity_type other = null;
|
||||
|
||||
if(it != remloc.cend()) {
|
||||
other = it->second.first;
|
||||
if(const auto it = remloc.find(to_entity(entt)); it != remloc.cend() && it->second.first == entt) {
|
||||
return it->second.second;
|
||||
}
|
||||
|
||||
return other;
|
||||
return null;
|
||||
}
|
||||
|
||||
private:
|
||||
dense_map<entity_type, std::pair<entity_type, bool>> remloc;
|
||||
dense_map<typename traits_type::entity_type, std::pair<entity_type, entity_type>> remloc;
|
||||
registry_type *reg;
|
||||
};
|
||||
|
||||
|
@ -88,6 +88,10 @@ struct sparse_set_iterator final {
|
||||
return *operator->();
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr pointer data() const noexcept {
|
||||
return packed ? packed->data() : nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr difference_type index() const noexcept {
|
||||
return offset - 1;
|
||||
}
|
||||
@ -97,38 +101,38 @@ private:
|
||||
difference_type offset;
|
||||
};
|
||||
|
||||
template<typename Type, typename Other>
|
||||
[[nodiscard]] constexpr std::ptrdiff_t operator-(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) noexcept {
|
||||
template<typename Container>
|
||||
[[nodiscard]] constexpr std::ptrdiff_t operator-(const sparse_set_iterator<Container> &lhs, const sparse_set_iterator<Container> &rhs) noexcept {
|
||||
return rhs.index() - lhs.index();
|
||||
}
|
||||
|
||||
template<typename Type, typename Other>
|
||||
[[nodiscard]] constexpr bool operator==(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) noexcept {
|
||||
template<typename Container>
|
||||
[[nodiscard]] constexpr bool operator==(const sparse_set_iterator<Container> &lhs, const sparse_set_iterator<Container> &rhs) noexcept {
|
||||
return lhs.index() == rhs.index();
|
||||
}
|
||||
|
||||
template<typename Type, typename Other>
|
||||
[[nodiscard]] constexpr bool operator!=(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) noexcept {
|
||||
template<typename Container>
|
||||
[[nodiscard]] constexpr bool operator!=(const sparse_set_iterator<Container> &lhs, const sparse_set_iterator<Container> &rhs) noexcept {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
template<typename Type, typename Other>
|
||||
[[nodiscard]] constexpr bool operator<(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) noexcept {
|
||||
template<typename Container>
|
||||
[[nodiscard]] constexpr bool operator<(const sparse_set_iterator<Container> &lhs, const sparse_set_iterator<Container> &rhs) noexcept {
|
||||
return lhs.index() > rhs.index();
|
||||
}
|
||||
|
||||
template<typename Type, typename Other>
|
||||
[[nodiscard]] constexpr bool operator>(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) noexcept {
|
||||
return lhs.index() < rhs.index();
|
||||
template<typename Container>
|
||||
[[nodiscard]] constexpr bool operator>(const sparse_set_iterator<Container> &lhs, const sparse_set_iterator<Container> &rhs) noexcept {
|
||||
return rhs < lhs;
|
||||
}
|
||||
|
||||
template<typename Type, typename Other>
|
||||
[[nodiscard]] constexpr bool operator<=(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) noexcept {
|
||||
template<typename Container>
|
||||
[[nodiscard]] constexpr bool operator<=(const sparse_set_iterator<Container> &lhs, const sparse_set_iterator<Container> &rhs) noexcept {
|
||||
return !(lhs > rhs);
|
||||
}
|
||||
|
||||
template<typename Type, typename Other>
|
||||
[[nodiscard]] constexpr bool operator>=(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) noexcept {
|
||||
template<typename Container>
|
||||
[[nodiscard]] constexpr bool operator>=(const sparse_set_iterator<Container> &lhs, const sparse_set_iterator<Container> &rhs) noexcept {
|
||||
return !(lhs < rhs);
|
||||
}
|
||||
|
||||
@ -139,14 +143,6 @@ template<typename Type, typename Other>
|
||||
* @endcond
|
||||
*/
|
||||
|
||||
/*! @brief Sparse set deletion policy. */
|
||||
enum class deletion_policy : std::uint8_t {
|
||||
/*! @brief Swap-and-pop deletion policy. */
|
||||
swap_and_pop = 0u,
|
||||
/*! @brief In-place deletion policy. */
|
||||
in_place = 1u
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Basic sparse set implementation.
|
||||
*
|
||||
@ -154,10 +150,6 @@ enum class deletion_policy : std::uint8_t {
|
||||
* Two arrays: an _external_ one and an _internal_ one; a _sparse_ one and a
|
||||
* _packed_ one; one used for direct access through contiguous memory, the other
|
||||
* one used to get the data through an extra level of indirection.<br/>
|
||||
* This is largely used by the registry to offer users the fastest access ever
|
||||
* to the components. Views and groups in general are almost entirely designed
|
||||
* around sparse sets.
|
||||
*
|
||||
* This type of data structure is widely documented in the literature and on the
|
||||
* web. This is nothing more than a customized implementation suitable for the
|
||||
* purpose of the framework.
|
||||
@ -167,7 +159,7 @@ enum class deletion_policy : std::uint8_t {
|
||||
* no guarantees that entities are returned in the insertion order when iterate
|
||||
* a sparse set. Do not make assumption on the order in any case.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Entity A valid entity type.
|
||||
* @tparam Allocator Type of allocator used to manage memory and elements.
|
||||
*/
|
||||
template<typename Entity, typename Allocator>
|
||||
@ -176,23 +168,26 @@ class basic_sparse_set {
|
||||
static_assert(std::is_same_v<typename alloc_traits::value_type, Entity>, "Invalid value type");
|
||||
using sparse_container_type = std::vector<typename alloc_traits::pointer, typename alloc_traits::template rebind_alloc<typename alloc_traits::pointer>>;
|
||||
using packed_container_type = std::vector<Entity, Allocator>;
|
||||
using entity_traits = entt_traits<Entity>;
|
||||
|
||||
[[nodiscard]] auto sparse_ptr(const Entity entt) const {
|
||||
const auto pos = static_cast<size_type>(entity_traits::to_entity(entt));
|
||||
const auto page = pos / entity_traits::page_size;
|
||||
return (page < sparse.size() && sparse[page]) ? (sparse[page] + fast_mod(pos, entity_traits::page_size)) : nullptr;
|
||||
const auto pos = static_cast<size_type>(traits_type::to_entity(entt));
|
||||
const auto page = pos / traits_type::page_size;
|
||||
return (page < sparse.size() && sparse[page]) ? (sparse[page] + fast_mod(pos, traits_type::page_size)) : nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto &sparse_ref(const Entity entt) const {
|
||||
ENTT_ASSERT(sparse_ptr(entt), "Invalid element");
|
||||
const auto pos = static_cast<size_type>(entity_traits::to_entity(entt));
|
||||
return sparse[pos / entity_traits::page_size][fast_mod(pos, entity_traits::page_size)];
|
||||
const auto pos = static_cast<size_type>(traits_type::to_entity(entt));
|
||||
return sparse[pos / traits_type::page_size][fast_mod(pos, traits_type::page_size)];
|
||||
}
|
||||
|
||||
[[nodiscard]] auto to_iterator(const Entity entt) const {
|
||||
return --(end() - index(entt));
|
||||
}
|
||||
|
||||
[[nodiscard]] auto &assure_at_least(const Entity entt) {
|
||||
const auto pos = static_cast<size_type>(entity_traits::to_entity(entt));
|
||||
const auto page = pos / entity_traits::page_size;
|
||||
const auto pos = static_cast<size_type>(traits_type::to_entity(entt));
|
||||
const auto page = pos / traits_type::page_size;
|
||||
|
||||
if(!(page < sparse.size())) {
|
||||
sparse.resize(page + 1u, nullptr);
|
||||
@ -200,11 +195,11 @@ class basic_sparse_set {
|
||||
|
||||
if(!sparse[page]) {
|
||||
auto page_allocator{packed.get_allocator()};
|
||||
sparse[page] = alloc_traits::allocate(page_allocator, entity_traits::page_size);
|
||||
std::uninitialized_fill(sparse[page], sparse[page] + entity_traits::page_size, null);
|
||||
sparse[page] = alloc_traits::allocate(page_allocator, traits_type::page_size);
|
||||
std::uninitialized_fill(sparse[page], sparse[page] + traits_type::page_size, null);
|
||||
}
|
||||
|
||||
auto &elem = sparse[page][fast_mod(pos, entity_traits::page_size)];
|
||||
auto &elem = sparse[page][fast_mod(pos, traits_type::page_size)];
|
||||
ENTT_ASSERT(elem == null, "Slot not available");
|
||||
return elem;
|
||||
}
|
||||
@ -214,8 +209,8 @@ class basic_sparse_set {
|
||||
|
||||
for(auto &&page: sparse) {
|
||||
if(page != nullptr) {
|
||||
std::destroy(page, page + entity_traits::page_size);
|
||||
alloc_traits::deallocate(page_allocator, page, entity_traits::page_size);
|
||||
std::destroy(page, page + traits_type::page_size);
|
||||
alloc_traits::deallocate(page_allocator, page, traits_type::page_size);
|
||||
page = nullptr;
|
||||
}
|
||||
}
|
||||
@ -226,13 +221,28 @@ private:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual void swap_at(const std::size_t, const std::size_t) {}
|
||||
virtual void move_element(const std::size_t, const std::size_t) {}
|
||||
virtual void swap_or_move(const std::size_t, const std::size_t) {}
|
||||
|
||||
protected:
|
||||
/*! @brief Random access iterator type. */
|
||||
using basic_iterator = internal::sparse_set_iterator<packed_container_type>;
|
||||
|
||||
/**
|
||||
* @brief Swaps two items at specific locations.
|
||||
* @param lhs A position to move from.
|
||||
* @param rhs The other position to move from.
|
||||
*/
|
||||
void swap_at(const std::size_t lhs, const std::size_t rhs) {
|
||||
const auto entity = static_cast<typename traits_type::entity_type>(lhs);
|
||||
const auto other = static_cast<typename traits_type::entity_type>(rhs);
|
||||
|
||||
sparse_ref(packed[lhs]) = traits_type::combine(other, traits_type::to_integral(packed[lhs]));
|
||||
sparse_ref(packed[rhs]) = traits_type::combine(entity, traits_type::to_integral(packed[rhs]));
|
||||
|
||||
using std::swap;
|
||||
swap(packed[lhs], packed[rhs]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Erases an entity from a sparse set.
|
||||
* @param it An iterator to the element to pop.
|
||||
@ -240,8 +250,8 @@ protected:
|
||||
void swap_and_pop(const basic_iterator it) {
|
||||
ENTT_ASSERT(mode == deletion_policy::swap_and_pop, "Deletion policy mismatched");
|
||||
auto &self = sparse_ref(*it);
|
||||
const auto entt = entity_traits::to_entity(self);
|
||||
sparse_ref(packed.back()) = entity_traits::combine(entt, entity_traits::to_integral(packed.back()));
|
||||
const auto entt = traits_type::to_entity(self);
|
||||
sparse_ref(packed.back()) = traits_type::combine(entt, traits_type::to_integral(packed.back()));
|
||||
packed[static_cast<size_type>(entt)] = packed.back();
|
||||
// unnecessary but it helps to detect nasty bugs
|
||||
ENTT_ASSERT((packed.back() = null, true), "");
|
||||
@ -256,8 +266,8 @@ protected:
|
||||
*/
|
||||
void in_place_pop(const basic_iterator it) {
|
||||
ENTT_ASSERT(mode == deletion_policy::in_place, "Deletion policy mismatched");
|
||||
const auto entt = entity_traits::to_entity(std::exchange(sparse_ref(*it), null));
|
||||
packed[static_cast<size_type>(entt)] = std::exchange(free_list, entity_traits::combine(entt, entity_traits::reserved));
|
||||
const auto entt = traits_type::to_entity(std::exchange(sparse_ref(*it), null));
|
||||
packed[static_cast<size_type>(entt)] = std::exchange(free_list, traits_type::combine(entt, tombstone));
|
||||
}
|
||||
|
||||
protected:
|
||||
@ -278,6 +288,23 @@ protected:
|
||||
}
|
||||
}
|
||||
|
||||
/*! @brief Erases all entities of a sparse set. */
|
||||
virtual void pop_all() {
|
||||
if(const auto prev = std::exchange(free_list, tombstone); prev == null) {
|
||||
for(auto first = begin(); !(first.index() < 0); ++first) {
|
||||
sparse_ref(*first) = null;
|
||||
}
|
||||
} else {
|
||||
for(auto first = begin(); !(first.index() < 0); ++first) {
|
||||
if(*first != tombstone) {
|
||||
sparse_ref(*first) = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
packed.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns an entity to a sparse set.
|
||||
* @param entt A valid identifier.
|
||||
@ -289,25 +316,27 @@ protected:
|
||||
|
||||
if(auto &elem = assure_at_least(entt); free_list == null || force_back) {
|
||||
packed.push_back(entt);
|
||||
elem = entity_traits::combine(static_cast<typename entity_traits::entity_type>(packed.size() - 1u), entity_traits::to_integral(entt));
|
||||
elem = traits_type::combine(static_cast<typename traits_type::entity_type>(packed.size() - 1u), traits_type::to_integral(entt));
|
||||
return begin();
|
||||
} else {
|
||||
const auto pos = static_cast<size_type>(entity_traits::to_entity(free_list));
|
||||
elem = entity_traits::combine(entity_traits::to_integral(free_list), entity_traits::to_integral(entt));
|
||||
const auto pos = static_cast<size_type>(traits_type::to_entity(free_list));
|
||||
elem = traits_type::combine(traits_type::to_integral(free_list), traits_type::to_integral(entt));
|
||||
free_list = std::exchange(packed[pos], entt);
|
||||
return --(end() - pos);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Allocator type. */
|
||||
using allocator_type = Allocator;
|
||||
/*! @brief Entity traits. */
|
||||
using traits_type = entt_traits<Entity>;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = typename entity_traits::value_type;
|
||||
using entity_type = typename traits_type::value_type;
|
||||
/*! @brief Underlying version type. */
|
||||
using version_type = typename entity_traits::version_type;
|
||||
using version_type = typename traits_type::version_type;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Allocator type. */
|
||||
using allocator_type = Allocator;
|
||||
/*! @brief Pointer type to contained entities. */
|
||||
using pointer = typename packed_container_type::const_pointer;
|
||||
/*! @brief Random access iterator type. */
|
||||
@ -317,7 +346,7 @@ public:
|
||||
/*! @brief Reverse iterator type. */
|
||||
using reverse_iterator = std::reverse_iterator<iterator>;
|
||||
/*! @brief Constant reverse iterator type. */
|
||||
using const_reverse_iterator = reverse_iterator;
|
||||
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
basic_sparse_set()
|
||||
@ -341,14 +370,14 @@ public:
|
||||
/**
|
||||
* @brief Constructs an empty container with the given value type, policy
|
||||
* and allocator.
|
||||
* @param value Returned value type, if any.
|
||||
* @param elem Returned value type, if any.
|
||||
* @param pol Type of deletion policy.
|
||||
* @param allocator The allocator to use (possibly default-constructed).
|
||||
*/
|
||||
explicit basic_sparse_set(const type_info &value, deletion_policy pol = deletion_policy::swap_and_pop, const allocator_type &allocator = {})
|
||||
explicit basic_sparse_set(const type_info &elem, deletion_policy pol = deletion_policy::swap_and_pop, const allocator_type &allocator = {})
|
||||
: sparse{allocator},
|
||||
packed{allocator},
|
||||
info{&value},
|
||||
info{&elem},
|
||||
free_list{tombstone},
|
||||
mode{pol} {}
|
||||
|
||||
@ -465,7 +494,7 @@ public:
|
||||
* @return Extent of the sparse set.
|
||||
*/
|
||||
[[nodiscard]] size_type extent() const noexcept {
|
||||
return sparse.size() * entity_traits::page_size;
|
||||
return sparse.size() * traits_type::page_size;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -490,6 +519,14 @@ public:
|
||||
return packed.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks whether a sparse set is fully packed.
|
||||
* @return True if the sparse set is fully packed, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool contiguous() const noexcept {
|
||||
return (free_list == null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Direct access to the internal packed array.
|
||||
* @return A pointer to the internal packed array.
|
||||
@ -501,8 +538,7 @@ public:
|
||||
/**
|
||||
* @brief Returns an iterator to the beginning.
|
||||
*
|
||||
* The returned iterator points to the first entity of the internal packed
|
||||
* array. If the sparse set is empty, the returned iterator will be equal to
|
||||
* If the sparse set is empty, the returned iterator will be equal to
|
||||
* `end()`.
|
||||
*
|
||||
* @return An iterator to the first entity of the sparse set.
|
||||
@ -519,11 +555,6 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the end.
|
||||
*
|
||||
* The returned iterator points to the element following the last entity in
|
||||
* a sparse set. Attempting to dereference the returned iterator results in
|
||||
* undefined behavior.
|
||||
*
|
||||
* @return An iterator to the element following the last entity of a sparse
|
||||
* set.
|
||||
*/
|
||||
@ -539,9 +570,8 @@ public:
|
||||
/**
|
||||
* @brief Returns a reverse iterator to the beginning.
|
||||
*
|
||||
* The returned iterator points to the first entity of the reversed internal
|
||||
* packed array. If the sparse set is empty, the returned iterator will be
|
||||
* equal to `rend()`.
|
||||
* If the sparse set is empty, the returned iterator will be equal to
|
||||
* `rend()`.
|
||||
*
|
||||
* @return An iterator to the first entity of the reversed internal packed
|
||||
* array.
|
||||
@ -557,11 +587,6 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Returns a reverse iterator to the end.
|
||||
*
|
||||
* The returned iterator points to the element following the last entity in
|
||||
* the reversed sparse set. Attempting to dereference the returned iterator
|
||||
* results in undefined behavior.
|
||||
*
|
||||
* @return An iterator to the element following the last entity of the
|
||||
* reversed sparse set.
|
||||
*/
|
||||
@ -581,7 +606,7 @@ public:
|
||||
* iterator otherwise.
|
||||
*/
|
||||
[[nodiscard]] iterator find(const entity_type entt) const noexcept {
|
||||
return contains(entt) ? --(end() - index(entt)) : end();
|
||||
return contains(entt) ? to_iterator(entt) : end();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -591,9 +616,9 @@ public:
|
||||
*/
|
||||
[[nodiscard]] bool contains(const entity_type entt) const noexcept {
|
||||
const auto elem = sparse_ptr(entt);
|
||||
constexpr auto cap = entity_traits::to_entity(null);
|
||||
constexpr auto cap = traits_type::to_entity(null);
|
||||
// testing versions permits to avoid accessing the packed array
|
||||
return elem && (((~cap & entity_traits::to_integral(entt)) ^ entity_traits::to_integral(*elem)) < cap);
|
||||
return elem && (((~cap & traits_type::to_integral(entt)) ^ traits_type::to_integral(*elem)) < cap);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -604,8 +629,8 @@ public:
|
||||
*/
|
||||
[[nodiscard]] version_type current(const entity_type entt) const noexcept {
|
||||
const auto elem = sparse_ptr(entt);
|
||||
constexpr auto fallback = entity_traits::to_version(tombstone);
|
||||
return elem ? entity_traits::to_version(*elem) : fallback;
|
||||
constexpr auto fallback = traits_type::to_version(tombstone);
|
||||
return elem ? traits_type::to_version(*elem) : fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -620,7 +645,7 @@ public:
|
||||
*/
|
||||
[[nodiscard]] size_type index(const entity_type entt) const noexcept {
|
||||
ENTT_ASSERT(contains(entt), "Set does not contain entity");
|
||||
return static_cast<size_type>(entity_traits::to_entity(sparse_ref(entt)));
|
||||
return static_cast<size_type>(traits_type::to_entity(sparse_ref(entt)));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -652,13 +677,13 @@ public:
|
||||
* @param entt A valid identifier.
|
||||
* @return An opaque pointer to the element assigned to the entity, if any.
|
||||
*/
|
||||
[[nodiscard]] const void *get(const entity_type entt) const noexcept {
|
||||
[[nodiscard]] const void *value(const entity_type entt) const noexcept {
|
||||
return get_at(index(entt));
|
||||
}
|
||||
|
||||
/*! @copydoc get */
|
||||
[[nodiscard]] void *get(const entity_type entt) noexcept {
|
||||
return const_cast<void *>(std::as_const(*this).get(entt));
|
||||
/*! @copydoc value */
|
||||
[[nodiscard]] void *value(const entity_type entt) noexcept {
|
||||
return const_cast<void *>(std::as_const(*this).value(entt));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -669,28 +694,12 @@ public:
|
||||
* results in undefined behavior.
|
||||
*
|
||||
* @param entt A valid identifier.
|
||||
* @param value Optional opaque value to forward to mixins, if any.
|
||||
* @param elem Optional opaque element to forward to mixins, if any.
|
||||
* @return Iterator pointing to the emplaced element in case of success, the
|
||||
* `end()` iterator otherwise.
|
||||
*/
|
||||
iterator emplace(const entity_type entt, const void *value = nullptr) {
|
||||
return try_emplace(entt, false, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Bump the version number of an entity.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to bump the version of an entity that doesn't belong to the
|
||||
* sparse set results in undefined behavior.
|
||||
*
|
||||
* @param entt A valid identifier.
|
||||
*/
|
||||
void bump(const entity_type entt) {
|
||||
auto &entity = sparse_ref(entt);
|
||||
ENTT_ASSERT(entt != tombstone && entity != null, "Cannot set the required version");
|
||||
entity = entity_traits::combine(entity_traits::to_integral(entity), entity_traits::to_integral(entt));
|
||||
packed[static_cast<size_type>(entity_traits::to_entity(entity))] = entt;
|
||||
iterator push(const entity_type entt, const void *elem = nullptr) {
|
||||
return try_emplace(entt, false, elem);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -707,7 +716,7 @@ public:
|
||||
* success, the `end()` iterator otherwise.
|
||||
*/
|
||||
template<typename It>
|
||||
iterator insert(It first, It last) {
|
||||
iterator push(It first, It last) {
|
||||
for(auto it = first; it != last; ++it) {
|
||||
try_emplace(*it, true);
|
||||
}
|
||||
@ -715,6 +724,24 @@ public:
|
||||
return first == last ? end() : find(*first);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Bump the version number of an entity.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to bump the version of an entity that doesn't belong to the
|
||||
* sparse set results in undefined behavior.
|
||||
*
|
||||
* @param entt A valid identifier.
|
||||
* @return The version of the given identifier.
|
||||
*/
|
||||
version_type bump(const entity_type entt) {
|
||||
auto &entity = sparse_ref(entt);
|
||||
ENTT_ASSERT(entt != tombstone && entity != null, "Cannot set the required version");
|
||||
entity = traits_type::combine(traits_type::to_integral(entity), traits_type::to_integral(entt));
|
||||
packed[static_cast<size_type>(traits_type::to_entity(entity))] = entt;
|
||||
return traits_type::to_version(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Erases an entity from a sparse set.
|
||||
*
|
||||
@ -725,7 +752,7 @@ public:
|
||||
* @param entt A valid identifier.
|
||||
*/
|
||||
void erase(const entity_type entt) {
|
||||
const auto it = --(end() - index(entt));
|
||||
const auto it = to_iterator(entt);
|
||||
pop(it, it + 1u);
|
||||
}
|
||||
|
||||
@ -769,29 +796,45 @@ public:
|
||||
size_type remove(It first, It last) {
|
||||
size_type count{};
|
||||
|
||||
for(; first != last; ++first) {
|
||||
count += remove(*first);
|
||||
if constexpr(std::is_same_v<It, basic_iterator>) {
|
||||
while(first != last) {
|
||||
while(first != last && !contains(*first)) {
|
||||
++first;
|
||||
}
|
||||
|
||||
const auto it = first;
|
||||
|
||||
while(first != last && contains(*first)) {
|
||||
++first;
|
||||
}
|
||||
|
||||
count += std::distance(it, first);
|
||||
erase(it, first);
|
||||
}
|
||||
} else {
|
||||
for(; first != last; ++first) {
|
||||
count += remove(*first);
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/*! @brief Removes all tombstones from the packed array of a sparse set. */
|
||||
/*! @brief Removes all tombstones from a sparse set. */
|
||||
void compact() {
|
||||
size_type from = packed.size();
|
||||
for(; from && packed[from - 1u] == tombstone; --from) {}
|
||||
|
||||
for(auto *it = &free_list; *it != null && from; it = std::addressof(packed[entity_traits::to_entity(*it)])) {
|
||||
if(const size_type to = entity_traits::to_entity(*it); to < from) {
|
||||
for(auto *it = &free_list; *it != null && from; it = std::addressof(packed[traits_type::to_entity(*it)])) {
|
||||
if(const size_type to = traits_type::to_entity(*it); to < from) {
|
||||
--from;
|
||||
move_element(from, to);
|
||||
swap_or_move(from, to);
|
||||
|
||||
using std::swap;
|
||||
swap(packed[from], packed[to]);
|
||||
packed[to] = std::exchange(packed[from], tombstone);
|
||||
const auto entity = static_cast<typename traits_type::entity_type>(to);
|
||||
sparse_ref(packed[to]) = traits_type::combine(entity, traits_type::to_integral(packed[to]));
|
||||
|
||||
const auto entity = static_cast<typename entity_traits::entity_type>(to);
|
||||
sparse_ref(packed[to]) = entity_traits::combine(entity, entity_traits::to_integral(packed[to]));
|
||||
*it = entity_traits::combine(static_cast<typename entity_traits::entity_type>(from), entity_traits::reserved);
|
||||
*it = traits_type::combine(static_cast<typename traits_type::entity_type>(from), tombstone);
|
||||
for(; from && packed[from - 1u] == tombstone; --from) {}
|
||||
}
|
||||
}
|
||||
@ -814,21 +857,12 @@ public:
|
||||
* @param rhs A valid identifier.
|
||||
*/
|
||||
void swap_elements(const entity_type lhs, const entity_type rhs) {
|
||||
ENTT_ASSERT(contains(lhs) && contains(rhs), "Set does not contain entities");
|
||||
const auto from = index(lhs);
|
||||
const auto to = index(rhs);
|
||||
|
||||
auto &entt = sparse_ref(lhs);
|
||||
auto &other = sparse_ref(rhs);
|
||||
|
||||
const auto from = entity_traits::to_entity(entt);
|
||||
const auto to = entity_traits::to_entity(other);
|
||||
|
||||
// basic no-leak guarantee (with invalid state) if swapping throws
|
||||
swap_at(static_cast<size_type>(from), static_cast<size_type>(to));
|
||||
entt = entity_traits::combine(to, entity_traits::to_integral(packed[from]));
|
||||
other = entity_traits::combine(from, entity_traits::to_integral(packed[to]));
|
||||
|
||||
using std::swap;
|
||||
swap(packed[from], packed[to]);
|
||||
// basic no-leak guarantee if swapping throws
|
||||
swap_or_move(from, to);
|
||||
swap_at(from, to);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -876,9 +910,9 @@ public:
|
||||
const auto idx = index(packed[next]);
|
||||
const auto entt = packed[curr];
|
||||
|
||||
swap_at(next, idx);
|
||||
const auto entity = static_cast<typename entity_traits::entity_type>(curr);
|
||||
sparse_ref(entt) = entity_traits::combine(entity, entity_traits::to_integral(packed[curr]));
|
||||
swap_or_move(next, idx);
|
||||
const auto entity = static_cast<typename traits_type::entity_type>(curr);
|
||||
sparse_ref(entt) = traits_type::combine(entity, traits_type::to_integral(packed[curr]));
|
||||
curr = std::exchange(next, idx);
|
||||
}
|
||||
}
|
||||
@ -906,48 +940,35 @@ public:
|
||||
* @brief Sort entities according to their order in another sparse set.
|
||||
*
|
||||
* Entities that are part of both the sparse sets are ordered internally
|
||||
* according to the order they have in `other`. All the other entities goes
|
||||
* to the end of the list and there are no guarantees on their order.<br/>
|
||||
* In other terms, this function can be used to impose the same order on two
|
||||
* sets by using one of them as a master and the other one as a slave.
|
||||
*
|
||||
* Iterating the sparse set with a couple of iterators returns elements in
|
||||
* the expected order after a call to `respect`. See `begin` and `end` for
|
||||
* more details.
|
||||
* according to the order they have in `other`.<br/>
|
||||
* All the other entities goes to the end of the list and there are no
|
||||
* guarantees on their order.
|
||||
*
|
||||
* @param other The sparse sets that imposes the order of the entities.
|
||||
*/
|
||||
void respect(const basic_sparse_set &other) {
|
||||
void sort_as(const basic_sparse_set &other) {
|
||||
compact();
|
||||
|
||||
const auto to = other.end();
|
||||
auto from = other.begin();
|
||||
|
||||
for(size_type pos = packed.size() - 1; pos && from != to; ++from) {
|
||||
if(contains(*from)) {
|
||||
if(*from != packed[pos]) {
|
||||
for(auto it = begin(); it.index() && from != to; ++from) {
|
||||
if(const auto curr = *from; contains(curr)) {
|
||||
if(const auto entt = *it; entt != curr) {
|
||||
// basic no-leak guarantee (with invalid state) if swapping throws
|
||||
swap_elements(packed[pos], *from);
|
||||
swap_elements(entt, curr);
|
||||
}
|
||||
|
||||
--pos;
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*! @brief Clears a sparse set. */
|
||||
void clear() {
|
||||
if(const auto last = end(); free_list == null) {
|
||||
pop(begin(), last);
|
||||
} else {
|
||||
for(auto &&entity: *this) {
|
||||
// tombstone filter on itself
|
||||
if(const auto it = find(entity); it != last) {
|
||||
pop(it, it + 1u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pop_all();
|
||||
// sanity check to avoid subtle issues due to storage classes
|
||||
ENTT_ASSERT((compact(), size()) == 0u, "Non-empty set");
|
||||
free_list = tombstone;
|
||||
packed.clear();
|
||||
}
|
||||
|
@ -9,7 +9,6 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "../config/config.h"
|
||||
#include "../core/compressed_pair.hpp"
|
||||
#include "../core/iterator.hpp"
|
||||
#include "../core/memory.hpp"
|
||||
#include "../core/type_info.hpp"
|
||||
@ -17,7 +16,6 @@
|
||||
#include "entity.hpp"
|
||||
#include "fwd.hpp"
|
||||
#include "sparse_set.hpp"
|
||||
#include "storage_mixin.hpp"
|
||||
|
||||
namespace entt {
|
||||
|
||||
@ -28,13 +26,12 @@ namespace entt {
|
||||
|
||||
namespace internal {
|
||||
|
||||
template<typename Container>
|
||||
template<typename Container, std::size_t Size>
|
||||
class storage_iterator final {
|
||||
friend storage_iterator<const Container>;
|
||||
friend storage_iterator<const Container, Size>;
|
||||
|
||||
using container_type = std::remove_const_t<Container>;
|
||||
using alloc_traits = std::allocator_traits<typename container_type::allocator_type>;
|
||||
using comp_traits = component_traits<std::remove_pointer_t<typename container_type::value_type>>;
|
||||
|
||||
using iterator_traits = std::iterator_traits<std::conditional_t<
|
||||
std::is_const_v<Container>,
|
||||
@ -51,12 +48,12 @@ public:
|
||||
constexpr storage_iterator() noexcept = default;
|
||||
|
||||
constexpr storage_iterator(Container *ref, const difference_type idx) noexcept
|
||||
: packed{ref},
|
||||
: payload{ref},
|
||||
offset{idx} {}
|
||||
|
||||
template<bool Const = std::is_const_v<Container>, typename = std::enable_if_t<Const>>
|
||||
constexpr storage_iterator(const storage_iterator<std::remove_const_t<Container>> &other) noexcept
|
||||
: storage_iterator{other.packed, other.offset} {}
|
||||
constexpr storage_iterator(const storage_iterator<std::remove_const_t<Container>, Size> &other) noexcept
|
||||
: storage_iterator{other.payload, other.offset} {}
|
||||
|
||||
constexpr storage_iterator &operator++() noexcept {
|
||||
return --offset, *this;
|
||||
@ -96,12 +93,12 @@ public:
|
||||
|
||||
[[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept {
|
||||
const auto pos = index() - value;
|
||||
return (*packed)[pos / comp_traits::page_size][fast_mod(pos, comp_traits::page_size)];
|
||||
return (*payload)[pos / Size][fast_mod(pos, Size)];
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr pointer operator->() const noexcept {
|
||||
const auto pos = index();
|
||||
return (*packed)[pos / comp_traits::page_size] + fast_mod(pos, comp_traits::page_size);
|
||||
return (*payload)[pos / Size] + fast_mod(pos, Size);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr reference operator*() const noexcept {
|
||||
@ -113,42 +110,42 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
Container *packed;
|
||||
Container *payload;
|
||||
difference_type offset;
|
||||
};
|
||||
|
||||
template<typename CLhs, typename CRhs>
|
||||
[[nodiscard]] constexpr std::ptrdiff_t operator-(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) noexcept {
|
||||
template<typename Lhs, typename Rhs, std::size_t Size>
|
||||
[[nodiscard]] constexpr std::ptrdiff_t operator-(const storage_iterator<Lhs, Size> &lhs, const storage_iterator<Rhs, Size> &rhs) noexcept {
|
||||
return rhs.index() - lhs.index();
|
||||
}
|
||||
|
||||
template<typename CLhs, typename CRhs>
|
||||
[[nodiscard]] constexpr bool operator==(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) noexcept {
|
||||
template<typename Lhs, typename Rhs, std::size_t Size>
|
||||
[[nodiscard]] constexpr bool operator==(const storage_iterator<Lhs, Size> &lhs, const storage_iterator<Rhs, Size> &rhs) noexcept {
|
||||
return lhs.index() == rhs.index();
|
||||
}
|
||||
|
||||
template<typename CLhs, typename CRhs>
|
||||
[[nodiscard]] constexpr bool operator!=(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) noexcept {
|
||||
template<typename Lhs, typename Rhs, std::size_t Size>
|
||||
[[nodiscard]] constexpr bool operator!=(const storage_iterator<Lhs, Size> &lhs, const storage_iterator<Rhs, Size> &rhs) noexcept {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
template<typename CLhs, typename CRhs>
|
||||
[[nodiscard]] constexpr bool operator<(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) noexcept {
|
||||
template<typename Lhs, typename Rhs, std::size_t Size>
|
||||
[[nodiscard]] constexpr bool operator<(const storage_iterator<Lhs, Size> &lhs, const storage_iterator<Rhs, Size> &rhs) noexcept {
|
||||
return lhs.index() > rhs.index();
|
||||
}
|
||||
|
||||
template<typename CLhs, typename CRhs>
|
||||
[[nodiscard]] constexpr bool operator>(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) noexcept {
|
||||
return lhs.index() < rhs.index();
|
||||
template<typename Lhs, typename Rhs, std::size_t Size>
|
||||
[[nodiscard]] constexpr bool operator>(const storage_iterator<Lhs, Size> &lhs, const storage_iterator<Rhs, Size> &rhs) noexcept {
|
||||
return rhs < lhs;
|
||||
}
|
||||
|
||||
template<typename CLhs, typename CRhs>
|
||||
[[nodiscard]] constexpr bool operator<=(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) noexcept {
|
||||
template<typename Lhs, typename Rhs, std::size_t Size>
|
||||
[[nodiscard]] constexpr bool operator<=(const storage_iterator<Lhs, Size> &lhs, const storage_iterator<Rhs, Size> &rhs) noexcept {
|
||||
return !(lhs > rhs);
|
||||
}
|
||||
|
||||
template<typename CLhs, typename CRhs>
|
||||
[[nodiscard]] constexpr bool operator>=(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) noexcept {
|
||||
template<typename Lhs, typename Rhs, std::size_t Size>
|
||||
[[nodiscard]] constexpr bool operator>=(const storage_iterator<Lhs, Size> &lhs, const storage_iterator<Rhs, Size> &rhs) noexcept {
|
||||
return !(lhs < rhs);
|
||||
}
|
||||
|
||||
@ -158,10 +155,11 @@ class extended_storage_iterator final {
|
||||
friend class extended_storage_iterator;
|
||||
|
||||
public:
|
||||
using iterator_type = It;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval<It>()), std::forward_as_tuple(*std::declval<Other>()...)));
|
||||
using pointer = input_iterator_pointer<value_type>;
|
||||
using reference = value_type;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
|
||||
constexpr extended_storage_iterator()
|
||||
@ -191,20 +189,24 @@ public:
|
||||
return {*std::get<It>(it), *std::get<Other>(it)...};
|
||||
}
|
||||
|
||||
template<typename... CLhs, typename... CRhs>
|
||||
friend constexpr bool operator==(const extended_storage_iterator<CLhs...> &, const extended_storage_iterator<CRhs...> &) noexcept;
|
||||
[[nodiscard]] constexpr iterator_type base() const noexcept {
|
||||
return std::get<It>(it);
|
||||
}
|
||||
|
||||
template<typename... Lhs, typename... Rhs>
|
||||
friend constexpr bool operator==(const extended_storage_iterator<Lhs...> &, const extended_storage_iterator<Rhs...> &) noexcept;
|
||||
|
||||
private:
|
||||
std::tuple<It, Other...> it;
|
||||
};
|
||||
|
||||
template<typename... CLhs, typename... CRhs>
|
||||
[[nodiscard]] constexpr bool operator==(const extended_storage_iterator<CLhs...> &lhs, const extended_storage_iterator<CRhs...> &rhs) noexcept {
|
||||
template<typename... Lhs, typename... Rhs>
|
||||
[[nodiscard]] constexpr bool operator==(const extended_storage_iterator<Lhs...> &lhs, const extended_storage_iterator<Rhs...> &rhs) noexcept {
|
||||
return std::get<0>(lhs.it) == std::get<0>(rhs.it);
|
||||
}
|
||||
|
||||
template<typename... CLhs, typename... CRhs>
|
||||
[[nodiscard]] constexpr bool operator!=(const extended_storage_iterator<CLhs...> &lhs, const extended_storage_iterator<CRhs...> &rhs) noexcept {
|
||||
template<typename... Lhs, typename... Rhs>
|
||||
[[nodiscard]] constexpr bool operator!=(const extended_storage_iterator<Lhs...> &lhs, const extended_storage_iterator<Rhs...> &rhs) noexcept {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
@ -227,43 +229,43 @@ template<typename... CLhs, typename... CRhs>
|
||||
* normally available for non-empty types will not be available for empty ones.
|
||||
*
|
||||
* @tparam Type Type of objects assigned to the entities.
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Entity A valid entity type.
|
||||
* @tparam Allocator Type of allocator used to manage memory and elements.
|
||||
*/
|
||||
template<typename Type, typename Entity, typename Allocator, typename>
|
||||
class basic_storage: public basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>> {
|
||||
using alloc_traits = std::allocator_traits<Allocator>;
|
||||
static_assert(std::is_same_v<typename alloc_traits::value_type, Type>, "Invalid value type");
|
||||
using underlying_type = basic_sparse_set<Entity, typename alloc_traits::template rebind_alloc<Entity>>;
|
||||
using container_type = std::vector<typename alloc_traits::pointer, typename alloc_traits::template rebind_alloc<typename alloc_traits::pointer>>;
|
||||
using comp_traits = component_traits<Type>;
|
||||
using underlying_type = basic_sparse_set<Entity, typename alloc_traits::template rebind_alloc<Entity>>;
|
||||
using underlying_iterator = typename underlying_type::basic_iterator;
|
||||
|
||||
static constexpr bool is_pinned_type_v = !(std::is_move_constructible_v<Type> && std::is_move_assignable_v<Type>);
|
||||
|
||||
[[nodiscard]] auto &element_at(const std::size_t pos) const {
|
||||
return packed.first()[pos / comp_traits::page_size][fast_mod(pos, comp_traits::page_size)];
|
||||
return payload[pos / traits_type::page_size][fast_mod(pos, traits_type::page_size)];
|
||||
}
|
||||
|
||||
auto assure_at_least(const std::size_t pos) {
|
||||
auto &&container = packed.first();
|
||||
const auto idx = pos / comp_traits::page_size;
|
||||
const auto idx = pos / traits_type::page_size;
|
||||
|
||||
if(!(idx < container.size())) {
|
||||
auto curr = container.size();
|
||||
container.resize(idx + 1u, nullptr);
|
||||
if(!(idx < payload.size())) {
|
||||
auto curr = payload.size();
|
||||
allocator_type allocator{get_allocator()};
|
||||
payload.resize(idx + 1u, nullptr);
|
||||
|
||||
ENTT_TRY {
|
||||
for(const auto last = container.size(); curr < last; ++curr) {
|
||||
container[curr] = alloc_traits::allocate(packed.second(), comp_traits::page_size);
|
||||
for(const auto last = payload.size(); curr < last; ++curr) {
|
||||
payload[curr] = alloc_traits::allocate(allocator, traits_type::page_size);
|
||||
}
|
||||
}
|
||||
ENTT_CATCH {
|
||||
container.resize(curr);
|
||||
payload.resize(curr);
|
||||
ENTT_THROW;
|
||||
}
|
||||
}
|
||||
|
||||
return container[idx] + fast_mod(pos, comp_traits::page_size);
|
||||
return payload[idx] + fast_mod(pos, traits_type::page_size);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
@ -272,7 +274,7 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
|
||||
|
||||
ENTT_TRY {
|
||||
auto elem = assure_at_least(static_cast<size_type>(it.index()));
|
||||
entt::uninitialized_construct_using_allocator(to_address(elem), packed.second(), std::forward<Args>(args)...);
|
||||
entt::uninitialized_construct_using_allocator(to_address(elem), get_allocator(), std::forward<Args>(args)...);
|
||||
}
|
||||
ENTT_CATCH {
|
||||
base_type::pop(it, it + 1u);
|
||||
@ -283,25 +285,24 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
|
||||
}
|
||||
|
||||
void shrink_to_size(const std::size_t sz) {
|
||||
const auto from = (sz + traits_type::page_size - 1u) / traits_type::page_size;
|
||||
allocator_type allocator{get_allocator()};
|
||||
|
||||
for(auto pos = sz, length = base_type::size(); pos < length; ++pos) {
|
||||
if constexpr(comp_traits::in_place_delete) {
|
||||
if constexpr(traits_type::in_place_delete) {
|
||||
if(base_type::at(pos) != tombstone) {
|
||||
std::destroy_at(std::addressof(element_at(pos)));
|
||||
alloc_traits::destroy(allocator, std::addressof(element_at(pos)));
|
||||
}
|
||||
} else {
|
||||
std::destroy_at(std::addressof(element_at(pos)));
|
||||
alloc_traits::destroy(allocator, std::addressof(element_at(pos)));
|
||||
}
|
||||
}
|
||||
|
||||
auto &&container = packed.first();
|
||||
auto page_allocator{packed.second()};
|
||||
const auto from = (sz + comp_traits::page_size - 1u) / comp_traits::page_size;
|
||||
|
||||
for(auto pos = from, last = container.size(); pos < last; ++pos) {
|
||||
alloc_traits::deallocate(page_allocator, container[pos], comp_traits::page_size);
|
||||
for(auto pos = from, last = payload.size(); pos < last; ++pos) {
|
||||
alloc_traits::deallocate(allocator, payload[pos], traits_type::page_size);
|
||||
}
|
||||
|
||||
container.resize(from);
|
||||
payload.resize(from);
|
||||
}
|
||||
|
||||
private:
|
||||
@ -309,54 +310,68 @@ private:
|
||||
return std::addressof(element_at(pos));
|
||||
}
|
||||
|
||||
void swap_at([[maybe_unused]] const std::size_t lhs, [[maybe_unused]] const std::size_t rhs) final {
|
||||
// use a runtime value to avoid compile-time suppression that drives the code coverage tool crazy
|
||||
ENTT_ASSERT((lhs + 1u) && !is_pinned_type_v, "Pinned type");
|
||||
|
||||
if constexpr(!is_pinned_type_v) {
|
||||
using std::swap;
|
||||
swap(element_at(lhs), element_at(rhs));
|
||||
}
|
||||
}
|
||||
|
||||
void move_element([[maybe_unused]] const std::size_t from, [[maybe_unused]] const std::size_t to) final {
|
||||
void swap_or_move([[maybe_unused]] const std::size_t from, [[maybe_unused]] const std::size_t to) override {
|
||||
// use a runtime value to avoid compile-time suppression that drives the code coverage tool crazy
|
||||
ENTT_ASSERT((from + 1u) && !is_pinned_type_v, "Pinned type");
|
||||
|
||||
if constexpr(!is_pinned_type_v) {
|
||||
auto &elem = element_at(from);
|
||||
entt::uninitialized_construct_using_allocator(to_address(assure_at_least(to)), packed.second(), std::move(elem));
|
||||
std::destroy_at(std::addressof(elem));
|
||||
|
||||
if constexpr(traits_type::in_place_delete) {
|
||||
if(base_type::operator[](to) == tombstone) {
|
||||
allocator_type allocator{get_allocator()};
|
||||
entt::uninitialized_construct_using_allocator(to_address(assure_at_least(to)), allocator, std::move(elem));
|
||||
alloc_traits::destroy(allocator, std::addressof(elem));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
using std::swap;
|
||||
swap(elem, element_at(to));
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
/*! @brief Random access iterator type. */
|
||||
using basic_iterator = typename underlying_type::basic_iterator;
|
||||
|
||||
/**
|
||||
* @brief Erases entities from a sparse set.
|
||||
* @brief Erases entities from a storage.
|
||||
* @param first An iterator to the first element of the range of entities.
|
||||
* @param last An iterator past the last element of the range of entities.
|
||||
*/
|
||||
void pop(basic_iterator first, basic_iterator last) override {
|
||||
for(; first != last; ++first) {
|
||||
void pop(underlying_iterator first, underlying_iterator last) override {
|
||||
for(allocator_type allocator{get_allocator()}; first != last; ++first) {
|
||||
// cannot use first.index() because it would break with cross iterators
|
||||
auto &elem = element_at(base_type::index(*first));
|
||||
|
||||
if constexpr(comp_traits::in_place_delete) {
|
||||
if constexpr(traits_type::in_place_delete) {
|
||||
base_type::in_place_pop(first);
|
||||
std::destroy_at(std::addressof(elem));
|
||||
alloc_traits::destroy(allocator, std::addressof(elem));
|
||||
} else {
|
||||
auto &other = element_at(base_type::size() - 1u);
|
||||
// destroying on exit allows reentrant destructors
|
||||
[[maybe_unused]] auto unused = std::exchange(elem, std::move(other));
|
||||
std::destroy_at(std::addressof(other));
|
||||
alloc_traits::destroy(allocator, std::addressof(other));
|
||||
base_type::swap_and_pop(first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*! @brief Erases all entities of a storage. */
|
||||
void pop_all() override {
|
||||
allocator_type allocator{get_allocator()};
|
||||
|
||||
for(auto first = base_type::begin(); !(first.index() < 0); ++first) {
|
||||
if constexpr(traits_type::in_place_delete) {
|
||||
if(*first != tombstone) {
|
||||
base_type::in_place_pop(first);
|
||||
alloc_traits::destroy(allocator, std::addressof(element_at(static_cast<size_type>(first.index()))));
|
||||
}
|
||||
} else {
|
||||
base_type::swap_and_pop(first);
|
||||
alloc_traits::destroy(allocator, std::addressof(element_at(static_cast<size_type>(first.index()))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns an entity to a storage.
|
||||
* @param entt A valid identifier.
|
||||
@ -364,7 +379,7 @@ protected:
|
||||
* @param force_back Force back insertion.
|
||||
* @return Iterator pointing to the emplaced element.
|
||||
*/
|
||||
basic_iterator try_emplace([[maybe_unused]] const Entity entt, [[maybe_unused]] const bool force_back, const void *value) override {
|
||||
underlying_iterator try_emplace([[maybe_unused]] const Entity entt, [[maybe_unused]] const bool force_back, const void *value) override {
|
||||
if(value) {
|
||||
if constexpr(std::is_copy_constructible_v<value_type>) {
|
||||
return emplace_element(entt, force_back, *static_cast<const value_type *>(value));
|
||||
@ -383,22 +398,24 @@ protected:
|
||||
public:
|
||||
/*! @brief Base type. */
|
||||
using base_type = underlying_type;
|
||||
/*! @brief Allocator type. */
|
||||
using allocator_type = Allocator;
|
||||
/*! @brief Type of the objects assigned to entities. */
|
||||
using value_type = Type;
|
||||
/*! @brief Component traits. */
|
||||
using traits_type = component_traits<value_type>;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Allocator type. */
|
||||
using allocator_type = Allocator;
|
||||
/*! @brief Pointer type to contained elements. */
|
||||
using pointer = typename container_type::pointer;
|
||||
/*! @brief Constant pointer type to contained elements. */
|
||||
using const_pointer = typename alloc_traits::template rebind_traits<typename alloc_traits::const_pointer>::const_pointer;
|
||||
/*! @brief Random access iterator type. */
|
||||
using iterator = internal::storage_iterator<container_type>;
|
||||
using iterator = internal::storage_iterator<container_type, traits_type::page_size>;
|
||||
/*! @brief Constant random access iterator type. */
|
||||
using const_iterator = internal::storage_iterator<const container_type>;
|
||||
using const_iterator = internal::storage_iterator<const container_type, traits_type::page_size>;
|
||||
/*! @brief Reverse iterator type. */
|
||||
using reverse_iterator = std::reverse_iterator<iterator>;
|
||||
/*! @brief Constant reverse iterator type. */
|
||||
@ -407,6 +424,10 @@ public:
|
||||
using iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::iterator, iterator>>;
|
||||
/*! @brief Constant extended iterable storage proxy. */
|
||||
using const_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_iterator, const_iterator>>;
|
||||
/*! @brief Extended reverse iterable storage proxy. */
|
||||
using reverse_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::reverse_iterator, reverse_iterator>>;
|
||||
/*! @brief Constant extended reverse iterable storage proxy. */
|
||||
using const_reverse_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_reverse_iterator, const_reverse_iterator>>;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
basic_storage()
|
||||
@ -417,8 +438,8 @@ public:
|
||||
* @param allocator The allocator to use.
|
||||
*/
|
||||
explicit basic_storage(const allocator_type &allocator)
|
||||
: base_type{type_id<value_type>(), deletion_policy{comp_traits::in_place_delete}, allocator},
|
||||
packed{container_type{allocator}, allocator} {}
|
||||
: base_type{type_id<value_type>(), deletion_policy{traits_type::in_place_delete}, allocator},
|
||||
payload{allocator} {}
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
@ -426,7 +447,7 @@ public:
|
||||
*/
|
||||
basic_storage(basic_storage &&other) noexcept
|
||||
: base_type{std::move(other)},
|
||||
packed{std::move(other.packed)} {}
|
||||
payload{std::move(other.payload)} {}
|
||||
|
||||
/**
|
||||
* @brief Allocator-extended move constructor.
|
||||
@ -435,8 +456,8 @@ public:
|
||||
*/
|
||||
basic_storage(basic_storage &&other, const allocator_type &allocator) noexcept
|
||||
: base_type{std::move(other), allocator},
|
||||
packed{container_type{std::move(other.packed.first()), allocator}, allocator} {
|
||||
ENTT_ASSERT(alloc_traits::is_always_equal::value || packed.second() == other.packed.second(), "Copying a storage is not allowed");
|
||||
payload{std::move(other.payload), allocator} {
|
||||
ENTT_ASSERT(alloc_traits::is_always_equal::value || payload.get_allocator() == other.payload.get_allocator(), "Copying a storage is not allowed");
|
||||
}
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
@ -450,12 +471,11 @@ public:
|
||||
* @return This storage.
|
||||
*/
|
||||
basic_storage &operator=(basic_storage &&other) noexcept {
|
||||
ENTT_ASSERT(alloc_traits::is_always_equal::value || packed.second() == other.packed.second(), "Copying a storage is not allowed");
|
||||
ENTT_ASSERT(alloc_traits::is_always_equal::value || payload.get_allocator() == other.payload.get_allocator(), "Copying a storage is not allowed");
|
||||
|
||||
shrink_to_size(0u);
|
||||
base_type::operator=(std::move(other));
|
||||
packed.first() = std::move(other.packed.first());
|
||||
propagate_on_container_move_assignment(packed.second(), other.packed.second());
|
||||
payload = std::move(other.payload);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -465,9 +485,8 @@ public:
|
||||
*/
|
||||
void swap(basic_storage &other) {
|
||||
using std::swap;
|
||||
underlying_type::swap(other);
|
||||
propagate_on_container_swap(packed.second(), other.packed.second());
|
||||
swap(packed.first(), other.packed.first());
|
||||
base_type::swap(other);
|
||||
swap(payload, other.payload);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -475,7 +494,7 @@ public:
|
||||
* @return The associated allocator.
|
||||
*/
|
||||
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
|
||||
return allocator_type{packed.second()};
|
||||
return payload.get_allocator();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -499,7 +518,7 @@ public:
|
||||
* @return Capacity of the storage.
|
||||
*/
|
||||
[[nodiscard]] size_type capacity() const noexcept override {
|
||||
return packed.first().size() * comp_traits::page_size;
|
||||
return payload.size() * traits_type::page_size;
|
||||
}
|
||||
|
||||
/*! @brief Requests the removal of unused capacity. */
|
||||
@ -513,25 +532,24 @@ public:
|
||||
* @return A pointer to the array of objects.
|
||||
*/
|
||||
[[nodiscard]] const_pointer raw() const noexcept {
|
||||
return packed.first().data();
|
||||
return payload.data();
|
||||
}
|
||||
|
||||
/*! @copydoc raw */
|
||||
[[nodiscard]] pointer raw() noexcept {
|
||||
return packed.first().data();
|
||||
return payload.data();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the beginning.
|
||||
*
|
||||
* The returned iterator points to the first instance of the internal array.
|
||||
* If the storage is empty, the returned iterator will be equal to `end()`.
|
||||
*
|
||||
* @return An iterator to the first instance of the internal array.
|
||||
*/
|
||||
[[nodiscard]] const_iterator cbegin() const noexcept {
|
||||
const auto pos = static_cast<typename iterator::difference_type>(base_type::size());
|
||||
return const_iterator{&packed.first(), pos};
|
||||
return const_iterator{&payload, pos};
|
||||
}
|
||||
|
||||
/*! @copydoc cbegin */
|
||||
@ -542,21 +560,16 @@ public:
|
||||
/*! @copydoc begin */
|
||||
[[nodiscard]] iterator begin() noexcept {
|
||||
const auto pos = static_cast<typename iterator::difference_type>(base_type::size());
|
||||
return iterator{&packed.first(), pos};
|
||||
return iterator{&payload, pos};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the end.
|
||||
*
|
||||
* The returned iterator points to the element following the last instance
|
||||
* of the internal array. Attempting to dereference the returned iterator
|
||||
* results in undefined behavior.
|
||||
*
|
||||
* @return An iterator to the element following the last instance of the
|
||||
* internal array.
|
||||
*/
|
||||
[[nodiscard]] const_iterator cend() const noexcept {
|
||||
return const_iterator{&packed.first(), {}};
|
||||
return const_iterator{&payload, {}};
|
||||
}
|
||||
|
||||
/*! @copydoc cend */
|
||||
@ -566,15 +579,13 @@ public:
|
||||
|
||||
/*! @copydoc end */
|
||||
[[nodiscard]] iterator end() noexcept {
|
||||
return iterator{&packed.first(), {}};
|
||||
return iterator{&payload, {}};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reverse iterator to the beginning.
|
||||
*
|
||||
* The returned iterator points to the first instance of the reversed
|
||||
* internal array. If the storage is empty, the returned iterator will be
|
||||
* equal to `rend()`.
|
||||
* If the storage is empty, the returned iterator will be equal to `rend()`.
|
||||
*
|
||||
* @return An iterator to the first instance of the reversed internal array.
|
||||
*/
|
||||
@ -594,11 +605,6 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Returns a reverse iterator to the end.
|
||||
*
|
||||
* The returned iterator points to the element following the last instance
|
||||
* of the reversed internal array. Attempting to dereference the returned
|
||||
* iterator results in undefined behavior.
|
||||
*
|
||||
* @return An iterator to the element following the last instance of the
|
||||
* reversed internal array.
|
||||
*/
|
||||
@ -663,7 +669,7 @@ public:
|
||||
*/
|
||||
template<typename... Args>
|
||||
value_type &emplace(const entity_type entt, Args &&...args) {
|
||||
if constexpr(std::is_aggregate_v<value_type>) {
|
||||
if constexpr(std::is_aggregate_v<value_type> && (sizeof...(Args) != 0u || !std::is_default_constructible_v<value_type>)) {
|
||||
const auto it = emplace_element(entt, false, Type{std::forward<Args>(args)...});
|
||||
return element_at(static_cast<size_type>(it.index()));
|
||||
} else {
|
||||
@ -699,12 +705,15 @@ public:
|
||||
* @param first An iterator to the first element of the range of entities.
|
||||
* @param last An iterator past the last element of the range of entities.
|
||||
* @param value An instance of the object to construct.
|
||||
* @return Iterator pointing to the last element inserted, if any.
|
||||
*/
|
||||
template<typename It>
|
||||
void insert(It first, It last, const value_type &value = {}) {
|
||||
iterator insert(It first, It last, const value_type &value = {}) {
|
||||
for(; first != last; ++first) {
|
||||
emplace_element(*first, true, value);
|
||||
}
|
||||
|
||||
return begin();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -718,12 +727,15 @@ public:
|
||||
* @param first An iterator to the first element of the range of entities.
|
||||
* @param last An iterator past the last element of the range of entities.
|
||||
* @param from An iterator to the first element of the range of objects.
|
||||
* @return Iterator pointing to the first element inserted, if any.
|
||||
*/
|
||||
template<typename EIt, typename CIt, typename = std::enable_if_t<std::is_same_v<typename std::iterator_traits<CIt>::value_type, value_type>>>
|
||||
void insert(EIt first, EIt last, CIt from) {
|
||||
iterator insert(EIt first, EIt last, CIt from) {
|
||||
for(; first != last; ++first, ++from) {
|
||||
emplace_element(*first, true, *from);
|
||||
}
|
||||
|
||||
return begin();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -743,34 +755,54 @@ public:
|
||||
return {internal::extended_storage_iterator{base_type::cbegin(), cbegin()}, internal::extended_storage_iterator{base_type::cend(), cend()}};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reverse iterable object to use to _visit_ a storage.
|
||||
*
|
||||
* @sa each
|
||||
*
|
||||
* @return A reverse iterable object to use to _visit_ the storage.
|
||||
*/
|
||||
[[nodiscard]] reverse_iterable reach() noexcept {
|
||||
return {internal::extended_storage_iterator{base_type::rbegin(), rbegin()}, internal::extended_storage_iterator{base_type::rend(), rend()}};
|
||||
}
|
||||
|
||||
/*! @copydoc reach */
|
||||
[[nodiscard]] const_reverse_iterable reach() const noexcept {
|
||||
return {internal::extended_storage_iterator{base_type::crbegin(), crbegin()}, internal::extended_storage_iterator{base_type::crend(), crend()}};
|
||||
}
|
||||
|
||||
private:
|
||||
compressed_pair<container_type, allocator_type> packed;
|
||||
container_type payload;
|
||||
};
|
||||
|
||||
/*! @copydoc basic_storage */
|
||||
template<typename Type, typename Entity, typename Allocator>
|
||||
class basic_storage<Type, Entity, Allocator, std::enable_if_t<ignore_as_empty_v<Type>>>
|
||||
class basic_storage<Type, Entity, Allocator, std::enable_if_t<component_traits<Type>::page_size == 0u>>
|
||||
: public basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>> {
|
||||
using alloc_traits = std::allocator_traits<Allocator>;
|
||||
static_assert(std::is_same_v<typename alloc_traits::value_type, Type>, "Invalid value type");
|
||||
using underlying_type = basic_sparse_set<Entity, typename alloc_traits::template rebind_alloc<Entity>>;
|
||||
using comp_traits = component_traits<Type>;
|
||||
|
||||
public:
|
||||
/*! @brief Base type. */
|
||||
using base_type = underlying_type;
|
||||
/*! @brief Allocator type. */
|
||||
using allocator_type = Allocator;
|
||||
using base_type = basic_sparse_set<Entity, typename alloc_traits::template rebind_alloc<Entity>>;
|
||||
/*! @brief Type of the objects assigned to entities. */
|
||||
using value_type = Type;
|
||||
/*! @brief Component traits. */
|
||||
using traits_type = component_traits<value_type>;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Allocator type. */
|
||||
using allocator_type = Allocator;
|
||||
/*! @brief Extended iterable storage proxy. */
|
||||
using iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::iterator>>;
|
||||
/*! @brief Constant extended iterable storage proxy. */
|
||||
using const_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_iterator>>;
|
||||
/*! @brief Extended reverse iterable storage proxy. */
|
||||
using reverse_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::reverse_iterator>>;
|
||||
/*! @brief Constant extended reverse iterable storage proxy. */
|
||||
using const_reverse_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_reverse_iterator>>;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
basic_storage()
|
||||
@ -781,7 +813,7 @@ public:
|
||||
* @param allocator The allocator to use.
|
||||
*/
|
||||
explicit basic_storage(const allocator_type &allocator)
|
||||
: base_type{type_id<value_type>(), deletion_policy{comp_traits::in_place_delete}, allocator} {}
|
||||
: base_type{type_id<value_type>(), deletion_policy{traits_type::in_place_delete}, allocator} {}
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
@ -896,6 +928,325 @@ public:
|
||||
[[nodiscard]] const_iterable each() const noexcept {
|
||||
return {internal::extended_storage_iterator{base_type::cbegin()}, internal::extended_storage_iterator{base_type::cend()}};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reverse iterable object to use to _visit_ a storage.
|
||||
*
|
||||
* @sa each
|
||||
*
|
||||
* @return A reverse iterable object to use to _visit_ the storage.
|
||||
*/
|
||||
[[nodiscard]] reverse_iterable reach() noexcept {
|
||||
return {internal::extended_storage_iterator{base_type::rbegin()}, internal::extended_storage_iterator{base_type::rend()}};
|
||||
}
|
||||
|
||||
/*! @copydoc reach */
|
||||
[[nodiscard]] const_reverse_iterable reach() const noexcept {
|
||||
return {internal::extended_storage_iterator{base_type::crbegin()}, internal::extended_storage_iterator{base_type::crend()}};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Swap-only entity storage specialization.
|
||||
* @tparam Entity A valid entity type.
|
||||
* @tparam Allocator Type of allocator used to manage memory and elements.
|
||||
*/
|
||||
template<typename Entity, typename Allocator>
|
||||
class basic_storage<Entity, Entity, Allocator>
|
||||
: public basic_sparse_set<Entity, Allocator> {
|
||||
using alloc_traits = std::allocator_traits<Allocator>;
|
||||
static_assert(std::is_same_v<typename alloc_traits::value_type, Entity>, "Invalid value type");
|
||||
using underlying_type = basic_sparse_set<Entity, typename alloc_traits::template rebind_alloc<Entity>>;
|
||||
using underlying_iterator = typename underlying_type::basic_iterator;
|
||||
using local_traits_type = entt_traits<Entity>;
|
||||
|
||||
auto entity_at(const std::size_t pos) const noexcept {
|
||||
ENTT_ASSERT(pos < local_traits_type::to_entity(null), "Invalid element");
|
||||
return local_traits_type::combine(static_cast<typename local_traits_type::entity_type>(pos), {});
|
||||
}
|
||||
|
||||
private:
|
||||
void swap_or_move([[maybe_unused]] const std::size_t lhs, [[maybe_unused]] const std::size_t rhs) override {
|
||||
ENTT_ASSERT(((lhs < length) + (rhs < length)) != 1u, "Cross swapping is not supported");
|
||||
}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Erases entities from a storage.
|
||||
* @param first An iterator to the first element of the range of entities.
|
||||
* @param last An iterator past the last element of the range of entities.
|
||||
*/
|
||||
void pop(underlying_iterator first, underlying_iterator last) override {
|
||||
for(; first != last; ++first) {
|
||||
if(const auto pos = base_type::index(*first); pos < length) {
|
||||
base_type::bump(local_traits_type::next(*first));
|
||||
|
||||
if(pos != --length) {
|
||||
base_type::swap_at(pos, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*! @brief Erases all entities of a sparse set. */
|
||||
void pop_all() override {
|
||||
length = 0u;
|
||||
base_type::pop_all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns an entity to a storage.
|
||||
* @param hint A valid identifier.
|
||||
* @return Iterator pointing to the emplaced element.
|
||||
*/
|
||||
underlying_iterator try_emplace(const Entity hint, const bool, const void *) override {
|
||||
return base_type::find(emplace(hint));
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Base type. */
|
||||
using base_type = basic_sparse_set<Entity, Allocator>;
|
||||
/*! @brief Type of the objects assigned to entities. */
|
||||
using value_type = Entity;
|
||||
/*! @brief Component traits. */
|
||||
using traits_type = component_traits<value_type>;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Allocator type. */
|
||||
using allocator_type = Allocator;
|
||||
/*! @brief Extended iterable storage proxy. */
|
||||
using iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::iterator>>;
|
||||
/*! @brief Constant extended iterable storage proxy. */
|
||||
using const_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_iterator>>;
|
||||
/*! @brief Extended reverse iterable storage proxy. */
|
||||
using reverse_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::reverse_iterator>>;
|
||||
/*! @brief Constant extended reverse iterable storage proxy. */
|
||||
using const_reverse_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_reverse_iterator>>;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
basic_storage()
|
||||
: basic_storage{allocator_type{}} {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Constructs an empty container with a given allocator.
|
||||
* @param allocator The allocator to use.
|
||||
*/
|
||||
explicit basic_storage(const allocator_type &allocator)
|
||||
: base_type{type_id<value_type>(), deletion_policy::swap_and_pop, allocator},
|
||||
length{} {}
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
* @param other The instance to move from.
|
||||
*/
|
||||
basic_storage(basic_storage &&other) noexcept
|
||||
: base_type{std::move(other)},
|
||||
length{std::exchange(other.length, size_type{})} {}
|
||||
|
||||
/**
|
||||
* @brief Allocator-extended move constructor.
|
||||
* @param other The instance to move from.
|
||||
* @param allocator The allocator to use.
|
||||
*/
|
||||
basic_storage(basic_storage &&other, const allocator_type &allocator) noexcept
|
||||
: base_type{std::move(other), allocator},
|
||||
length{std::exchange(other.length, size_type{})} {}
|
||||
|
||||
/**
|
||||
* @brief Move assignment operator.
|
||||
* @param other The instance to move from.
|
||||
* @return This storage.
|
||||
*/
|
||||
basic_storage &operator=(basic_storage &&other) noexcept {
|
||||
base_type::operator=(std::move(other));
|
||||
length = std::exchange(other.length, size_type{});
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the object assigned to an entity, that is `void`.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that doesn't belong to the storage results in
|
||||
* undefined behavior.
|
||||
*
|
||||
* @param entt A valid identifier.
|
||||
*/
|
||||
void get([[maybe_unused]] const entity_type entt) const noexcept {
|
||||
ENTT_ASSERT(base_type::index(entt) < length, "The requested entity is not a live one");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an empty tuple.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that doesn't belong to the storage results in
|
||||
* undefined behavior.
|
||||
*
|
||||
* @param entt A valid identifier.
|
||||
* @return Returns an empty tuple.
|
||||
*/
|
||||
[[nodiscard]] std::tuple<> get_as_tuple([[maybe_unused]] const entity_type entt) const noexcept {
|
||||
ENTT_ASSERT(base_type::index(entt) < length, "The requested entity is not a live one");
|
||||
return std::tuple{};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Exchanges the contents with those of a given storage.
|
||||
* @param other Storage to exchange the content with.
|
||||
*/
|
||||
void swap(basic_storage &other) {
|
||||
using std::swap;
|
||||
base_type::swap(other);
|
||||
swap(length, other.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a new identifier or recycles a destroyed one.
|
||||
* @return A valid identifier.
|
||||
*/
|
||||
entity_type emplace() {
|
||||
if(length == base_type::size()) {
|
||||
return *base_type::try_emplace(entity_at(length++), true);
|
||||
}
|
||||
|
||||
return base_type::operator[](length++);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a new identifier or recycles a destroyed one.
|
||||
*
|
||||
* If the requested identifier isn't in use, the suggested one is used.
|
||||
* Otherwise, a new identifier is returned.
|
||||
*
|
||||
* @param hint Required identifier.
|
||||
* @return A valid identifier.
|
||||
*/
|
||||
entity_type emplace(const entity_type hint) {
|
||||
if(hint == null || hint == tombstone) {
|
||||
return emplace();
|
||||
} else if(const auto curr = local_traits_type::construct(local_traits_type::to_entity(hint), base_type::current(hint)); curr == tombstone) {
|
||||
const auto pos = static_cast<size_type>(local_traits_type::to_entity(hint));
|
||||
|
||||
while(!(pos < base_type::size())) {
|
||||
base_type::try_emplace(entity_at(base_type::size()), true);
|
||||
}
|
||||
|
||||
base_type::swap_at(pos, length++);
|
||||
} else if(const auto idx = base_type::index(curr); idx < length) {
|
||||
return emplace();
|
||||
} else {
|
||||
base_type::swap_at(idx, length++);
|
||||
}
|
||||
|
||||
base_type::bump(hint);
|
||||
|
||||
return hint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates a given identifier.
|
||||
* @tparam Func Types of the function objects to invoke.
|
||||
* @param entt A valid identifier.
|
||||
* @param func Valid function objects.
|
||||
*/
|
||||
template<typename... Func>
|
||||
void patch([[maybe_unused]] const entity_type entt, Func &&...func) {
|
||||
ENTT_ASSERT(base_type::contains(entt), "Storage does not contain entity");
|
||||
(std::forward<Func>(func)(), ...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns each element in a range an identifier.
|
||||
* @tparam It Type of mutable forward iterator.
|
||||
* @param first An iterator to the first element of the range to generate.
|
||||
* @param last An iterator past the last element of the range to generate.
|
||||
*/
|
||||
template<typename It>
|
||||
void insert(It first, It last) {
|
||||
for(const auto sz = base_type::size(); first != last && length != sz; ++first, ++length) {
|
||||
*first = base_type::operator[](length);
|
||||
}
|
||||
|
||||
for(; first != last; ++first) {
|
||||
*first = *base_type::try_emplace(entity_at(length++), true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Makes all elements in a range contiguous.
|
||||
* @tparam It Type of forward iterator.
|
||||
* @param first An iterator to the first element of the range to pack.
|
||||
* @param last An iterator past the last element of the range to pack.
|
||||
* @return The number of elements within the newly created range.
|
||||
*/
|
||||
template<typename It>
|
||||
size_type pack(It first, It last) {
|
||||
size_type len = length;
|
||||
|
||||
for(; first != last; ++first, --len) {
|
||||
const auto pos = base_type::index(*first);
|
||||
ENTT_ASSERT(pos < length, "Invalid element");
|
||||
base_type::swap_at(pos, static_cast<size_type>(len - 1u));
|
||||
}
|
||||
|
||||
return (length - len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the number of elements considered still in use.
|
||||
* @return The number of elements considered still in use.
|
||||
*/
|
||||
[[nodiscard]] size_type in_use() const noexcept {
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the number of elements considered still in use.
|
||||
* @param len The number of elements considered still in use.
|
||||
*/
|
||||
void in_use(const size_type len) noexcept {
|
||||
ENTT_ASSERT(!(len > base_type::size()), "Invalid length");
|
||||
length = len;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterable object to use to _visit_ a storage.
|
||||
*
|
||||
* The iterable object returns a tuple that contains the current entity.
|
||||
*
|
||||
* @return An iterable object to use to _visit_ the storage.
|
||||
*/
|
||||
[[nodiscard]] iterable each() noexcept {
|
||||
return {internal::extended_storage_iterator{base_type::end() - length}, internal::extended_storage_iterator{base_type::end()}};
|
||||
}
|
||||
|
||||
/*! @copydoc each */
|
||||
[[nodiscard]] const_iterable each() const noexcept {
|
||||
return {internal::extended_storage_iterator{base_type::cend() - length}, internal::extended_storage_iterator{base_type::cend()}};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reverse iterable object to use to _visit_ a storage.
|
||||
*
|
||||
* @sa each
|
||||
*
|
||||
* @return A reverse iterable object to use to _visit_ the storage.
|
||||
*/
|
||||
[[nodiscard]] reverse_iterable reach() noexcept {
|
||||
return {internal::extended_storage_iterator{base_type::rbegin()}, internal::extended_storage_iterator{base_type::rbegin() + length}};
|
||||
}
|
||||
|
||||
/*! @copydoc reach */
|
||||
[[nodiscard]] const_reverse_iterable reach() const noexcept {
|
||||
return {internal::extended_storage_iterator{base_type::crbegin()}, internal::extended_storage_iterator{base_type::crbegin() + length}};
|
||||
}
|
||||
|
||||
private:
|
||||
size_type length;
|
||||
};
|
||||
|
||||
} // namespace entt
|
||||
|
@ -1,236 +0,0 @@
|
||||
#ifndef ENTT_ENTITY_SIGH_STORAGE_MIXIN_HPP
|
||||
#define ENTT_ENTITY_SIGH_STORAGE_MIXIN_HPP
|
||||
|
||||
#include <utility>
|
||||
#include "../config/config.h"
|
||||
#include "../core/any.hpp"
|
||||
#include "../signal/sigh.hpp"
|
||||
#include "fwd.hpp"
|
||||
|
||||
namespace entt {
|
||||
|
||||
/**
|
||||
* @brief Mixin type used to add signal support to storage types.
|
||||
*
|
||||
* The function type of a listener is equivalent to:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(basic_registry<entity_type> &, entity_type);
|
||||
* @endcode
|
||||
*
|
||||
* This applies to all signals made available.
|
||||
*
|
||||
* @tparam Type The type of the underlying storage.
|
||||
*/
|
||||
template<typename Type>
|
||||
class sigh_storage_mixin final: public Type {
|
||||
using basic_registry_type = basic_registry<typename Type::entity_type, typename Type::base_type::allocator_type>;
|
||||
using sigh_type = sigh<void(basic_registry_type &, const typename Type::entity_type), typename Type::allocator_type>;
|
||||
using basic_iterator = typename Type::basic_iterator;
|
||||
|
||||
void pop(basic_iterator first, basic_iterator last) override {
|
||||
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
|
||||
|
||||
for(; first != last; ++first) {
|
||||
const auto entt = *first;
|
||||
destruction.publish(*owner, entt);
|
||||
const auto it = Type::find(entt);
|
||||
Type::pop(it, it + 1u);
|
||||
}
|
||||
}
|
||||
|
||||
basic_iterator try_emplace(const typename basic_registry_type::entity_type entt, const bool force_back, const void *value) final {
|
||||
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
|
||||
Type::try_emplace(entt, force_back, value);
|
||||
construction.publish(*owner, entt);
|
||||
return Type::find(entt);
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Allocator type. */
|
||||
using allocator_type = typename Type::allocator_type;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = typename Type::entity_type;
|
||||
/*! @brief Expected registry type. */
|
||||
using registry_type = basic_registry_type;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
sigh_storage_mixin()
|
||||
: sigh_storage_mixin{allocator_type{}} {}
|
||||
|
||||
/**
|
||||
* @brief Constructs an empty storage with a given allocator.
|
||||
* @param allocator The allocator to use.
|
||||
*/
|
||||
explicit sigh_storage_mixin(const allocator_type &allocator)
|
||||
: Type{allocator},
|
||||
owner{},
|
||||
construction{allocator},
|
||||
destruction{allocator},
|
||||
update{allocator} {}
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
* @param other The instance to move from.
|
||||
*/
|
||||
sigh_storage_mixin(sigh_storage_mixin &&other) noexcept
|
||||
: Type{std::move(other)},
|
||||
owner{other.owner},
|
||||
construction{std::move(other.construction)},
|
||||
destruction{std::move(other.destruction)},
|
||||
update{std::move(other.update)} {}
|
||||
|
||||
/**
|
||||
* @brief Allocator-extended move constructor.
|
||||
* @param other The instance to move from.
|
||||
* @param allocator The allocator to use.
|
||||
*/
|
||||
sigh_storage_mixin(sigh_storage_mixin &&other, const allocator_type &allocator) noexcept
|
||||
: Type{std::move(other), allocator},
|
||||
owner{other.owner},
|
||||
construction{std::move(other.construction), allocator},
|
||||
destruction{std::move(other.destruction), allocator},
|
||||
update{std::move(other.update), allocator} {}
|
||||
|
||||
/**
|
||||
* @brief Move assignment operator.
|
||||
* @param other The instance to move from.
|
||||
* @return This storage.
|
||||
*/
|
||||
sigh_storage_mixin &operator=(sigh_storage_mixin &&other) noexcept {
|
||||
Type::operator=(std::move(other));
|
||||
owner = other.owner;
|
||||
construction = std::move(other.construction);
|
||||
destruction = std::move(other.destruction);
|
||||
update = std::move(other.update);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Exchanges the contents with those of a given storage.
|
||||
* @param other Storage to exchange the content with.
|
||||
*/
|
||||
void swap(sigh_storage_mixin &other) {
|
||||
using std::swap;
|
||||
Type::swap(other);
|
||||
swap(owner, other.owner);
|
||||
swap(construction, other.construction);
|
||||
swap(destruction, other.destruction);
|
||||
swap(update, other.update);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a sink object.
|
||||
*
|
||||
* The sink returned by this function can be used to receive notifications
|
||||
* whenever a new instance is created and assigned to an entity.<br/>
|
||||
* Listeners are invoked after the object has been assigned to the entity.
|
||||
*
|
||||
* @sa sink
|
||||
*
|
||||
* @return A temporary sink object.
|
||||
*/
|
||||
[[nodiscard]] auto on_construct() noexcept {
|
||||
return sink{construction};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a sink object.
|
||||
*
|
||||
* The sink returned by this function can be used to receive notifications
|
||||
* whenever an instance is explicitly updated.<br/>
|
||||
* Listeners are invoked after the object has been updated.
|
||||
*
|
||||
* @sa sink
|
||||
*
|
||||
* @return A temporary sink object.
|
||||
*/
|
||||
[[nodiscard]] auto on_update() noexcept {
|
||||
return sink{update};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a sink object.
|
||||
*
|
||||
* The sink returned by this function can be used to receive notifications
|
||||
* whenever an instance is removed from an entity and thus destroyed.<br/>
|
||||
* Listeners are invoked before the object has been removed from the entity.
|
||||
*
|
||||
* @sa sink
|
||||
*
|
||||
* @return A temporary sink object.
|
||||
*/
|
||||
[[nodiscard]] auto on_destroy() noexcept {
|
||||
return sink{destruction};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns entities to a storage.
|
||||
* @tparam Args Types of arguments to use to construct the object.
|
||||
* @param entt A valid identifier.
|
||||
* @param args Parameters to use to initialize the object.
|
||||
* @return A reference to the newly created object.
|
||||
*/
|
||||
template<typename... Args>
|
||||
decltype(auto) emplace(const entity_type entt, Args &&...args) {
|
||||
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
|
||||
Type::emplace(entt, std::forward<Args>(args)...);
|
||||
construction.publish(*owner, entt);
|
||||
return this->get(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Patches the given instance for an entity.
|
||||
* @tparam Func Types of the function objects to invoke.
|
||||
* @param entt A valid identifier.
|
||||
* @param func Valid function objects.
|
||||
* @return A reference to the patched instance.
|
||||
*/
|
||||
template<typename... Func>
|
||||
decltype(auto) patch(const entity_type entt, Func &&...func) {
|
||||
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
|
||||
Type::patch(entt, std::forward<Func>(func)...);
|
||||
update.publish(*owner, entt);
|
||||
return this->get(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns entities to a storage.
|
||||
* @tparam It Type of input iterator.
|
||||
* @tparam Args Types of arguments to use to construct the objects assigned
|
||||
* to the entities.
|
||||
* @param first An iterator to the first element of the range of entities.
|
||||
* @param last An iterator past the last element of the range of entities.
|
||||
* @param args Parameters to use to initialize the objects assigned to the
|
||||
* entities.
|
||||
*/
|
||||
template<typename It, typename... Args>
|
||||
void insert(It first, It last, Args &&...args) {
|
||||
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
|
||||
Type::insert(first, last, std::forward<Args>(args)...);
|
||||
|
||||
for(auto it = construction.empty() ? last : first; it != last; ++it) {
|
||||
construction.publish(*owner, *it);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Forwards variables to derived classes, if any.
|
||||
* @param value A variable wrapped in an opaque container.
|
||||
*/
|
||||
void bind(any value) noexcept final {
|
||||
auto *reg = any_cast<basic_registry_type>(&value);
|
||||
owner = reg ? reg : owner;
|
||||
Type::bind(std::move(value));
|
||||
}
|
||||
|
||||
private:
|
||||
basic_registry_type *owner;
|
||||
sigh_type construction;
|
||||
sigh_type destruction;
|
||||
sigh_type update;
|
||||
};
|
||||
|
||||
} // namespace entt
|
||||
|
||||
#endif
|
@ -9,11 +9,8 @@
|
||||
#include "../config/config.h"
|
||||
#include "../core/iterator.hpp"
|
||||
#include "../core/type_traits.hpp"
|
||||
#include "component.hpp"
|
||||
#include "entity.hpp"
|
||||
#include "fwd.hpp"
|
||||
#include "sparse_set.hpp"
|
||||
#include "storage.hpp"
|
||||
|
||||
namespace entt {
|
||||
|
||||
@ -24,6 +21,24 @@ namespace entt {
|
||||
|
||||
namespace internal {
|
||||
|
||||
template<typename... Args, typename Type, std::size_t N>
|
||||
[[nodiscard]] auto filter_as_tuple(const std::array<const Type *, N> &filter) noexcept {
|
||||
return std::apply([](const auto *...curr) { return std::make_tuple(static_cast<Args *>(const_cast<constness_as_t<Type, Args> *>(curr))...); }, filter);
|
||||
}
|
||||
|
||||
template<typename Type, std::size_t N>
|
||||
[[nodiscard]] auto none_of(const std::array<const Type *, N> &filter, const typename Type::entity_type entt) noexcept {
|
||||
return std::apply([entt](const auto *...curr) { return (!(curr && curr->contains(entt)) && ...); }, filter);
|
||||
}
|
||||
|
||||
template<typename... Get, typename... Exclude, std::size_t... Index>
|
||||
[[nodiscard]] auto view_pack(const std::tuple<Get *...> value, const std::tuple<Exclude *...> excl, std::index_sequence<Index...>) {
|
||||
const auto pools = std::tuple_cat(value, excl);
|
||||
basic_view<get_t<Get...>, exclude_t<Exclude...>> elem{};
|
||||
(((std::get<Index>(pools) != nullptr) ? elem.template storage<Index>(*std::get<Index>(pools)) : void()), ...);
|
||||
return elem;
|
||||
}
|
||||
|
||||
template<typename Type, std::size_t Get, std::size_t Exclude>
|
||||
class view_iterator final {
|
||||
using iterator_type = typename Type::const_iterator;
|
||||
@ -31,7 +46,7 @@ class view_iterator final {
|
||||
[[nodiscard]] bool valid() const noexcept {
|
||||
return ((Get != 0u) || (*it != tombstone))
|
||||
&& std::apply([entt = *it](const auto *...curr) { return (curr->contains(entt) && ...); }, pools)
|
||||
&& std::apply([entt = *it](const auto *...curr) { return (!curr->contains(entt) && ...); }, filter);
|
||||
&& none_of(filter, *it);
|
||||
}
|
||||
|
||||
public:
|
||||
@ -47,11 +62,11 @@ public:
|
||||
pools{},
|
||||
filter{} {}
|
||||
|
||||
view_iterator(iterator_type curr, iterator_type to, std::array<const Type *, Get> all_of, std::array<const Type *, Exclude> none_of) noexcept
|
||||
view_iterator(iterator_type curr, iterator_type to, std::array<const Type *, Get> value, std::array<const Type *, Exclude> excl) noexcept
|
||||
: it{curr},
|
||||
last{to},
|
||||
pools{all_of},
|
||||
filter{none_of} {
|
||||
pools{value},
|
||||
filter{excl} {
|
||||
while(it != last && !valid()) {
|
||||
++it;
|
||||
}
|
||||
@ -97,6 +112,7 @@ template<typename LhsType, auto... LhsArgs, typename RhsType, auto... RhsArgs>
|
||||
|
||||
template<typename It, typename... Type>
|
||||
struct extended_view_iterator final {
|
||||
using iterator_type = It;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval<It>()), std::declval<Type>().get_as_tuple({})...));
|
||||
using pointer = input_iterator_pointer<value_type>;
|
||||
@ -107,9 +123,9 @@ struct extended_view_iterator final {
|
||||
: it{},
|
||||
pools{} {}
|
||||
|
||||
extended_view_iterator(It from, std::tuple<Type *...> storage)
|
||||
extended_view_iterator(It from, std::tuple<Type *...> value)
|
||||
: it{from},
|
||||
pools{storage} {}
|
||||
pools{value} {}
|
||||
|
||||
extended_view_iterator &operator++() noexcept {
|
||||
return ++it, *this;
|
||||
@ -128,6 +144,10 @@ struct extended_view_iterator final {
|
||||
return operator*();
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr iterator_type base() const noexcept {
|
||||
return it;
|
||||
}
|
||||
|
||||
template<typename... Lhs, typename... Rhs>
|
||||
friend bool constexpr operator==(const extended_view_iterator<Lhs...> &, const extended_view_iterator<Rhs...> &) noexcept;
|
||||
|
||||
@ -179,31 +199,33 @@ class basic_view;
|
||||
* or removed from it).
|
||||
* * The entity currently pointed is destroyed.
|
||||
*
|
||||
* In all other cases, modifying the pools iterated by the view in any way
|
||||
* invalidates all the iterators and using them results in undefined behavior.
|
||||
* In all other cases, modifying the storage iterated by the view in any way
|
||||
* invalidates all the iterators.
|
||||
*
|
||||
* @tparam Get Types of storage iterated by the view.
|
||||
* @tparam Exclude Types of storage used to filter the view.
|
||||
*/
|
||||
template<typename... Get, typename... Exclude>
|
||||
class basic_view<get_t<Get...>, exclude_t<Exclude...>> {
|
||||
using underlying_type = std::common_type_t<typename Get::entity_type..., typename Exclude::entity_type...>;
|
||||
using basic_common_type = std::common_type_t<typename Get::base_type..., typename Exclude::base_type...>;
|
||||
static constexpr auto offset = sizeof...(Get);
|
||||
using base_type = std::common_type_t<typename Get::base_type..., typename Exclude::base_type...>;
|
||||
using underlying_type = typename base_type::entity_type;
|
||||
|
||||
template<typename, typename, typename>
|
||||
friend class basic_view;
|
||||
|
||||
template<typename Type>
|
||||
static constexpr std::size_t index_of = type_list_index_v<std::remove_const_t<Type>, type_list<typename Get::value_type...>>;
|
||||
static constexpr std::size_t index_of = type_list_index_v<std::remove_const_t<Type>, type_list<typename Get::value_type..., typename Exclude::value_type...>>;
|
||||
|
||||
[[nodiscard]] auto opaque_check_set() const noexcept {
|
||||
std::array<const base_type *, sizeof...(Get) - 1u> other{};
|
||||
std::array<const common_type *, sizeof...(Get) - 1u> other{};
|
||||
std::apply([&other, pos = 0u, view = view](const auto *...curr) mutable { ((curr == view ? void() : void(other[pos++] = curr)), ...); }, pools);
|
||||
return other;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto filter_as_array() const noexcept {
|
||||
return std::apply([](const auto *...curr) { return std::array<const base_type *, sizeof...(Exclude)>{curr...}; }, filter);
|
||||
void unchecked_refresh() noexcept {
|
||||
view = std::get<0>(pools);
|
||||
std::apply([this](auto *, auto *...other) { ((this->view = other->size() < this->view->size() ? other : this->view), ...); }, pools);
|
||||
}
|
||||
|
||||
template<std::size_t Curr, std::size_t Other, typename... Args>
|
||||
@ -211,18 +233,14 @@ class basic_view<get_t<Get...>, exclude_t<Exclude...>> {
|
||||
if constexpr(Curr == Other) {
|
||||
return std::forward_as_tuple(std::get<Args>(curr)...);
|
||||
} else {
|
||||
return storage<Other>().get_as_tuple(std::get<0>(curr));
|
||||
return std::get<Other>(pools)->get_as_tuple(std::get<0>(curr));
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] auto reject(const underlying_type entt) const noexcept {
|
||||
return std::apply([entt](const auto *...curr) { return (curr->contains(entt) || ...); }, filter);
|
||||
}
|
||||
|
||||
template<std::size_t Curr, typename Func, std::size_t... Index>
|
||||
void each(Func &func, std::index_sequence<Index...>) const {
|
||||
for(const auto curr: storage<Curr>().each()) {
|
||||
if(const auto entt = std::get<0>(curr); ((sizeof...(Get) != 1u) || (entt != tombstone)) && ((Curr == Index || storage<Index>().contains(entt)) && ...) && !reject(entt)) {
|
||||
for(const auto curr: std::get<Curr>(pools)->each()) {
|
||||
if(const auto entt = std::get<0>(curr); ((sizeof...(Get) != 1u) || (entt != tombstone)) && ((Curr == Index || std::get<Index>(pools)->contains(entt)) && ...) && internal::none_of(filter, entt)) {
|
||||
if constexpr(is_applicable_v<Func, decltype(std::tuple_cat(std::tuple<entity_type>{}, std::declval<basic_view>().get({})))>) {
|
||||
std::apply(func, std::tuple_cat(std::make_tuple(entt), dispatch_get<Curr, Index>(curr)...));
|
||||
} else {
|
||||
@ -234,7 +252,7 @@ class basic_view<get_t<Get...>, exclude_t<Exclude...>> {
|
||||
|
||||
template<typename Func, std::size_t... Index>
|
||||
void pick_and_each(Func &func, std::index_sequence<Index...> seq) const {
|
||||
((&storage<Index>() == view ? each<Index>(func, seq) : void()), ...);
|
||||
((std::get<Index>(pools) == view ? each<Index>(func, seq) : void()), ...);
|
||||
}
|
||||
|
||||
public:
|
||||
@ -243,9 +261,9 @@ public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Common type among all storage types. */
|
||||
using base_type = basic_common_type;
|
||||
using common_type = base_type;
|
||||
/*! @brief Bidirectional iterator type. */
|
||||
using iterator = internal::view_iterator<base_type, sizeof...(Get) - 1u, sizeof...(Exclude)>;
|
||||
using iterator = internal::view_iterator<common_type, sizeof...(Get) - 1u, sizeof...(Exclude)>;
|
||||
/*! @brief Iterable view type. */
|
||||
using iterable = iterable_adaptor<internal::extended_view_iterator<iterator, Get...>>;
|
||||
|
||||
@ -258,12 +276,14 @@ public:
|
||||
/**
|
||||
* @brief Constructs a multi-type view from a set of storage classes.
|
||||
* @param value The storage for the types to iterate.
|
||||
* @param exclude The storage for the types used to filter the view.
|
||||
* @param excl The storage for the types used to filter the view.
|
||||
*/
|
||||
basic_view(Get &...value, Exclude &...exclude) noexcept
|
||||
basic_view(Get &...value, Exclude &...excl) noexcept
|
||||
: pools{&value...},
|
||||
filter{&exclude...},
|
||||
view{[](const base_type *first, const auto *...other) { ((first = other->size() < first->size() ? other : first), ...); return first; }(&value...)} {}
|
||||
filter{&excl...},
|
||||
view{} {
|
||||
unchecked_refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Constructs a multi-type view from a set of storage classes.
|
||||
@ -271,66 +291,91 @@ public:
|
||||
* @param excl The storage for the types used to filter the view.
|
||||
*/
|
||||
basic_view(std::tuple<Get &...> value, std::tuple<Exclude &...> excl = {}) noexcept
|
||||
: pools{std::apply([](auto &...curr) { return std::make_tuple(&curr...); }, value)},
|
||||
filter{std::apply([](auto &...curr) { return std::make_tuple(&curr...); }, excl)},
|
||||
view{std::apply([](const base_type *first, const auto *...other) { ((first = other->size() < first->size() ? other : first), ...); return first; }, pools)} {}
|
||||
: basic_view{std::make_from_tuple<basic_view>(std::tuple_cat(value, excl))} {}
|
||||
|
||||
/**
|
||||
* @brief Creates a new view driven by a given component in its iterations.
|
||||
* @tparam Type Type of component used to drive the iteration.
|
||||
* @return A new view driven by the given component in its iterations.
|
||||
* @brief Forces a view to use a given component to drive iterations
|
||||
* @tparam Type Type of component to use to drive iterations.
|
||||
*/
|
||||
template<typename Type>
|
||||
[[nodiscard]] basic_view use() const noexcept {
|
||||
return use<index_of<Type>>();
|
||||
void use() noexcept {
|
||||
use<index_of<Type>>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a new view driven by a given component in its iterations.
|
||||
* @tparam Index Index of the component used to drive the iteration.
|
||||
* @return A new view driven by the given component in its iterations.
|
||||
* @brief Forces a view to use a given component to drive iterations
|
||||
* @tparam Index Index of the component to use to drive iterations.
|
||||
*/
|
||||
template<std::size_t Index>
|
||||
[[nodiscard]] basic_view use() const noexcept {
|
||||
basic_view other{*this};
|
||||
other.view = &storage<Index>();
|
||||
return other;
|
||||
void use() noexcept {
|
||||
if(view) {
|
||||
view = std::get<Index>(pools);
|
||||
}
|
||||
}
|
||||
|
||||
/*! @brief Updates the internal leading view if required. */
|
||||
void refresh() noexcept {
|
||||
if(view || std::apply([](const auto *...curr) { return ((curr != nullptr) && ...); }, pools)) {
|
||||
unchecked_refresh();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates the internal leading view if required.
|
||||
* @return A newly created and internally optimized view.
|
||||
*/
|
||||
[[nodiscard]] basic_view refresh() const noexcept {
|
||||
return std::apply([](auto *...elem) { return basic_view{*elem...}; }, std::tuple_cat(pools, filter));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the leading storage of a view.
|
||||
* @brief Returns the leading storage of a view, if any.
|
||||
* @return The leading storage of the view.
|
||||
*/
|
||||
[[nodiscard]] const base_type &handle() const noexcept {
|
||||
return *view;
|
||||
[[nodiscard]] const common_type *handle() const noexcept {
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the storage for a given component type.
|
||||
* @tparam Comp Type of component of which to return the storage.
|
||||
* @brief Returns the storage for a given component type, if any.
|
||||
* @tparam Type Type of component of which to return the storage.
|
||||
* @return The storage for the given component type.
|
||||
*/
|
||||
template<typename Type>
|
||||
[[nodiscard]] decltype(auto) storage() const noexcept {
|
||||
[[nodiscard]] auto *storage() const noexcept {
|
||||
return storage<index_of<Type>>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the storage for a given index.
|
||||
* @brief Returns the storage for a given index, if any.
|
||||
* @tparam Index Index of the storage to return.
|
||||
* @return The storage for the given index.
|
||||
*/
|
||||
template<std::size_t Index>
|
||||
[[nodiscard]] decltype(auto) storage() const noexcept {
|
||||
return *std::get<Index>(pools);
|
||||
[[nodiscard]] auto *storage() const noexcept {
|
||||
if constexpr(Index < offset) {
|
||||
return std::get<Index>(pools);
|
||||
} else {
|
||||
return std::get<Index - offset>(internal::filter_as_tuple<Exclude...>(filter));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns a storage to a view.
|
||||
* @tparam Type Type of storage to assign to the view.
|
||||
* @param elem A storage to assign to the view.
|
||||
*/
|
||||
template<typename Type>
|
||||
void storage(Type &elem) noexcept {
|
||||
storage<index_of<typename Type::value_type>>(elem);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns a storage to a view.
|
||||
* @tparam Index Index of the storage to assign to the view.
|
||||
* @tparam Type Type of storage to assign to the view.
|
||||
* @param elem A storage to assign to the view.
|
||||
*/
|
||||
template<std::size_t Index, typename Type>
|
||||
void storage(Type &elem) noexcept {
|
||||
if constexpr(Index < offset) {
|
||||
std::get<Index>(pools) = &elem;
|
||||
refresh();
|
||||
} else {
|
||||
std::get<Index - offset>(filter) = &elem;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -338,32 +383,26 @@ public:
|
||||
* @return Estimated number of entities iterated by the view.
|
||||
*/
|
||||
[[nodiscard]] size_type size_hint() const noexcept {
|
||||
return view->size();
|
||||
return view ? view->size() : size_type{};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the first entity of the view.
|
||||
*
|
||||
* The returned iterator points to the first entity of the view. If the view
|
||||
* is empty, the returned iterator will be equal to `end()`.
|
||||
* If the view is empty, the returned iterator will be equal to `end()`.
|
||||
*
|
||||
* @return An iterator to the first entity of the view.
|
||||
*/
|
||||
[[nodiscard]] iterator begin() const noexcept {
|
||||
return iterator{view->begin(), view->end(), opaque_check_set(), filter_as_array()};
|
||||
return view ? iterator{view->begin(), view->end(), opaque_check_set(), filter} : iterator{};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator that is past the last entity of the view.
|
||||
*
|
||||
* The returned iterator points to the entity following the last entity of
|
||||
* the view. Attempting to dereference the returned iterator results in
|
||||
* undefined behavior.
|
||||
*
|
||||
* @return An iterator to the entity following the last entity of the view.
|
||||
*/
|
||||
[[nodiscard]] iterator end() const noexcept {
|
||||
return iterator{view->end(), view->end(), opaque_check_set(), filter_as_array()};
|
||||
return view ? iterator{view->end(), view->end(), opaque_check_set(), filter} : iterator{};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -382,9 +421,13 @@ public:
|
||||
* otherwise.
|
||||
*/
|
||||
[[nodiscard]] entity_type back() const noexcept {
|
||||
auto it = view->rbegin();
|
||||
for(const auto last = view->rend(); it != last && !contains(*it); ++it) {}
|
||||
return it == view->rend() ? null : *it;
|
||||
if(view) {
|
||||
auto it = view->rbegin();
|
||||
for(const auto last = view->rend(); it != last && !contains(*it); ++it) {}
|
||||
return it == view->rend() ? null : *it;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -394,7 +437,7 @@ public:
|
||||
* iterator otherwise.
|
||||
*/
|
||||
[[nodiscard]] iterator find(const entity_type entt) const noexcept {
|
||||
return contains(entt) ? iterator{view->find(entt), view->end(), opaque_check_set(), filter_as_array()} : end();
|
||||
return contains(entt) ? iterator{view->find(entt), view->end(), opaque_check_set(), filter} : end();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -407,11 +450,12 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a view is properly initialized.
|
||||
* @return True if the view is properly initialized, false otherwise.
|
||||
* @brief Checks if a view is fully initialized.
|
||||
* @return True if the view is fully initialized, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] explicit operator bool() const noexcept {
|
||||
return view != nullptr;
|
||||
return std::apply([](const auto *...curr) { return ((curr != nullptr) && ...); }, pools)
|
||||
&& std::apply([](const auto *...curr) { return ((curr != nullptr) && ...); }, filter);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -420,8 +464,7 @@ public:
|
||||
* @return True if the view contains the given entity, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool contains(const entity_type entt) const noexcept {
|
||||
return std::apply([entt](const auto *...curr) { return (curr->contains(entt) && ...); }, pools)
|
||||
&& std::apply([entt](const auto *...curr) { return (!curr->contains(entt) && ...); }, filter);
|
||||
return view && std::apply([entt](const auto *...curr) { return (curr->contains(entt) && ...); }, pools) && internal::none_of(filter, entt);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -431,39 +474,33 @@ public:
|
||||
* Attempting to use an entity that doesn't belong to the view results in
|
||||
* undefined behavior.
|
||||
*
|
||||
* @tparam Type Types of components to get.
|
||||
* @tparam Type Type of the component to get.
|
||||
* @tparam Other Other types of components to get.
|
||||
* @param entt A valid identifier.
|
||||
* @return The components assigned to the entity.
|
||||
*/
|
||||
template<typename... Type>
|
||||
template<typename Type, typename... Other>
|
||||
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
|
||||
if constexpr(sizeof...(Type) == 0) {
|
||||
return get<index_of<Type>, index_of<Other>...>(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the components assigned to the given entity.
|
||||
*
|
||||
* @sa get
|
||||
*
|
||||
* @tparam Index Indexes of the components to get.
|
||||
* @param entt A valid identifier.
|
||||
* @return The components assigned to the entity.
|
||||
*/
|
||||
template<std::size_t... Index>
|
||||
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
|
||||
if constexpr(sizeof...(Index) == 0) {
|
||||
return std::apply([entt](auto *...curr) { return std::tuple_cat(curr->get_as_tuple(entt)...); }, pools);
|
||||
} else if constexpr(sizeof...(Type) == 1) {
|
||||
return (storage<index_of<Type>>().get(entt), ...);
|
||||
} else if constexpr(sizeof...(Index) == 1) {
|
||||
return (std::get<Index>(pools)->get(entt), ...);
|
||||
} else {
|
||||
return std::tuple_cat(storage<index_of<Type>>().get_as_tuple(entt)...);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the components assigned to the given entity.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that doesn't belong to the view results in
|
||||
* undefined behavior.
|
||||
*
|
||||
* @tparam First Index of a component to get.
|
||||
* @tparam Other Indexes of other components to get.
|
||||
* @param entt A valid identifier.
|
||||
* @return The components assigned to the entity.
|
||||
*/
|
||||
template<std::size_t First, std::size_t... Other>
|
||||
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
|
||||
if constexpr(sizeof...(Other) == 0) {
|
||||
return storage<First>().get(entt);
|
||||
} else {
|
||||
return std::tuple_cat(storage<First>().get_as_tuple(entt), storage<Other>().get_as_tuple(entt)...);
|
||||
return std::tuple_cat(std::get<Index>(pools)->get_as_tuple(entt)...);
|
||||
}
|
||||
}
|
||||
|
||||
@ -487,7 +524,7 @@ public:
|
||||
*/
|
||||
template<typename Func>
|
||||
void each(Func func) const {
|
||||
pick_and_each(func, std::index_sequence_for<Get...>{});
|
||||
view ? pick_and_each(func, std::index_sequence_for<Get...>{}) : void();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -512,14 +549,16 @@ public:
|
||||
*/
|
||||
template<typename... OGet, typename... OExclude>
|
||||
[[nodiscard]] auto operator|(const basic_view<get_t<OGet...>, exclude_t<OExclude...>> &other) const noexcept {
|
||||
return std::make_from_tuple<basic_view<get_t<Get..., OGet...>, exclude_t<Exclude..., OExclude...>>>(
|
||||
std::apply([](auto *...curr) { return std::forward_as_tuple(*curr...); }, std::tuple_cat(pools, other.pools, filter, other.filter)));
|
||||
return internal::view_pack(
|
||||
std::tuple_cat(pools, other.pools),
|
||||
std::tuple_cat(internal::filter_as_tuple<Exclude...>(filter), internal::filter_as_tuple<OExclude...>(other.filter)),
|
||||
std::index_sequence_for<Get..., OGet..., Exclude..., OExclude...>{});
|
||||
}
|
||||
|
||||
private:
|
||||
std::tuple<Get *...> pools;
|
||||
std::tuple<Exclude *...> filter;
|
||||
const base_type *view;
|
||||
std::array<const common_type *, sizeof...(Exclude)> filter;
|
||||
const common_type *view;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -538,13 +577,13 @@ private:
|
||||
* or removed from it).
|
||||
* * The entity currently pointed is destroyed.
|
||||
*
|
||||
* In all other cases, modifying the pool iterated by the view in any way
|
||||
* invalidates all the iterators and using them results in undefined behavior.
|
||||
* In all other cases, modifying the storage iterated by the view in any way
|
||||
* invalidates all the iterators.
|
||||
*
|
||||
* @tparam Get Type of storage iterated by the view.
|
||||
*/
|
||||
template<typename Get>
|
||||
class basic_view<get_t<Get>, exclude_t<>, std::void_t<std::enable_if_t<!component_traits<typename Get::value_type>::in_place_delete>>> {
|
||||
class basic_view<get_t<Get>, exclude_t<>, std::void_t<std::enable_if_t<!Get::traits_type::in_place_delete>>> {
|
||||
template<typename, typename, typename>
|
||||
friend class basic_view;
|
||||
|
||||
@ -554,62 +593,81 @@ public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Common type among all storage types. */
|
||||
using base_type = typename Get::base_type;
|
||||
using common_type = typename Get::base_type;
|
||||
/*! @brief Random access iterator type. */
|
||||
using iterator = typename base_type::iterator;
|
||||
using iterator = typename common_type::iterator;
|
||||
/*! @brief Reversed iterator type. */
|
||||
using reverse_iterator = typename base_type::reverse_iterator;
|
||||
using reverse_iterator = typename common_type::reverse_iterator;
|
||||
/*! @brief Iterable view type. */
|
||||
using iterable = decltype(std::declval<Get>().each());
|
||||
|
||||
/*! @brief Default constructor to use to create empty, invalid views. */
|
||||
basic_view() noexcept
|
||||
: pools{},
|
||||
filter{} {}
|
||||
filter{},
|
||||
view{} {}
|
||||
|
||||
/**
|
||||
* @brief Constructs a single-type view from a storage class.
|
||||
* @param ref The storage for the type to iterate.
|
||||
* @param value The storage for the type to iterate.
|
||||
*/
|
||||
basic_view(Get &ref) noexcept
|
||||
: pools{&ref},
|
||||
filter{} {}
|
||||
basic_view(Get &value) noexcept
|
||||
: pools{&value},
|
||||
filter{},
|
||||
view{&value} {}
|
||||
|
||||
/**
|
||||
* @brief Constructs a single-type view from a storage class.
|
||||
* @param ref The storage for the type to iterate.
|
||||
* @param value The storage for the type to iterate.
|
||||
*/
|
||||
basic_view(std::tuple<Get &> ref, std::tuple<> = {}) noexcept
|
||||
: pools{&std::get<0>(ref)},
|
||||
filter{} {}
|
||||
basic_view(std::tuple<Get &> value, std::tuple<> = {}) noexcept
|
||||
: basic_view{std::get<0>(value)} {}
|
||||
|
||||
/**
|
||||
* @brief Returns the leading storage of a view.
|
||||
* @brief Returns the leading storage of a view, if any.
|
||||
* @return The leading storage of the view.
|
||||
*/
|
||||
[[nodiscard]] const base_type &handle() const noexcept {
|
||||
return storage();
|
||||
[[nodiscard]] const common_type *handle() const noexcept {
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the storage for a given component type.
|
||||
* @brief Returns the storage for a given component type, if any.
|
||||
* @tparam Type Type of component of which to return the storage.
|
||||
* @return The storage for the given component type.
|
||||
*/
|
||||
template<typename Type = typename Get::value_type>
|
||||
[[nodiscard]] decltype(auto) storage() const noexcept {
|
||||
[[nodiscard]] auto *storage() const noexcept {
|
||||
static_assert(std::is_same_v<std::remove_const_t<Type>, typename Get::value_type>, "Invalid component type");
|
||||
return storage<0>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the storage for a given index.
|
||||
* @brief Returns the storage for a given index, if any.
|
||||
* @tparam Index Index of the storage to return.
|
||||
* @return The storage for the given index.
|
||||
*/
|
||||
template<std::size_t Index>
|
||||
[[nodiscard]] decltype(auto) storage() const noexcept {
|
||||
return *std::get<Index>(pools);
|
||||
[[nodiscard]] auto *storage() const noexcept {
|
||||
return std::get<Index>(pools);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns a storage to a view.
|
||||
* @param elem A storage to assign to the view.
|
||||
*/
|
||||
void storage(Get &elem) noexcept {
|
||||
storage<0>(elem);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns a storage to a view.
|
||||
* @tparam Index Index of the storage to assign to the view.
|
||||
* @param elem A storage to assign to the view.
|
||||
*/
|
||||
template<std::size_t Index>
|
||||
void storage(Get &elem) noexcept {
|
||||
view = std::get<Index>(pools) = &elem;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -617,7 +675,7 @@ public:
|
||||
* @return Number of entities that have the given component.
|
||||
*/
|
||||
[[nodiscard]] size_type size() const noexcept {
|
||||
return handle().size();
|
||||
return view ? view->size() : size_type{};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -625,59 +683,47 @@ public:
|
||||
* @return True if the view is empty, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool empty() const noexcept {
|
||||
return handle().empty();
|
||||
return !view || view->empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the first entity of the view.
|
||||
*
|
||||
* The returned iterator points to the first entity of the view. If the view
|
||||
* is empty, the returned iterator will be equal to `end()`.
|
||||
* If the view is empty, the returned iterator will be equal to `end()`.
|
||||
*
|
||||
* @return An iterator to the first entity of the view.
|
||||
*/
|
||||
[[nodiscard]] iterator begin() const noexcept {
|
||||
return handle().begin();
|
||||
return view ? view->begin() : iterator{};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator that is past the last entity of the view.
|
||||
*
|
||||
* The returned iterator points to the entity following the last entity of
|
||||
* the view. Attempting to dereference the returned iterator results in
|
||||
* undefined behavior.
|
||||
*
|
||||
* @return An iterator to the entity following the last entity of the view.
|
||||
*/
|
||||
[[nodiscard]] iterator end() const noexcept {
|
||||
return handle().end();
|
||||
return view ? view->end() : iterator{};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the first entity of the reversed view.
|
||||
*
|
||||
* The returned iterator points to the first entity of the reversed view. If
|
||||
* the view is empty, the returned iterator will be equal to `rend()`.
|
||||
* If the view is empty, the returned iterator will be equal to `rend()`.
|
||||
*
|
||||
* @return An iterator to the first entity of the reversed view.
|
||||
*/
|
||||
[[nodiscard]] reverse_iterator rbegin() const noexcept {
|
||||
return handle().rbegin();
|
||||
return view ? view->rbegin() : reverse_iterator{};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator that is past the last entity of the reversed
|
||||
* view.
|
||||
*
|
||||
* The returned iterator points to the entity following the last entity of
|
||||
* the reversed view. Attempting to dereference the returned iterator
|
||||
* results in undefined behavior.
|
||||
*
|
||||
* @return An iterator to the entity following the last entity of the
|
||||
* reversed view.
|
||||
*/
|
||||
[[nodiscard]] reverse_iterator rend() const noexcept {
|
||||
return handle().rend();
|
||||
return view ? view->rend() : reverse_iterator{};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -686,7 +732,7 @@ public:
|
||||
* otherwise.
|
||||
*/
|
||||
[[nodiscard]] entity_type front() const noexcept {
|
||||
return empty() ? null : *begin();
|
||||
return (!view || view->empty()) ? null : *view->begin();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -695,7 +741,7 @@ public:
|
||||
* otherwise.
|
||||
*/
|
||||
[[nodiscard]] entity_type back() const noexcept {
|
||||
return empty() ? null : *rbegin();
|
||||
return (!view || view->empty()) ? null : *view->rbegin();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -705,7 +751,7 @@ public:
|
||||
* iterator otherwise.
|
||||
*/
|
||||
[[nodiscard]] iterator find(const entity_type entt) const noexcept {
|
||||
return contains(entt) ? handle().find(entt) : end();
|
||||
return view ? view->find(entt) : iterator{};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -723,15 +769,15 @@ public:
|
||||
* @return The component assigned to the given entity.
|
||||
*/
|
||||
[[nodiscard]] decltype(auto) operator[](const entity_type entt) const {
|
||||
return storage().get(entt);
|
||||
return std::get<0>(pools)->get(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a view is properly initialized.
|
||||
* @return True if the view is properly initialized, false otherwise.
|
||||
* @brief Checks if a view is fully initialized.
|
||||
* @return True if the view is fully initialized, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] explicit operator bool() const noexcept {
|
||||
return std::get<0>(pools) != nullptr;
|
||||
return (std::get<0>(pools) != nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -740,7 +786,7 @@ public:
|
||||
* @return True if the view contains the given entity, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool contains(const entity_type entt) const noexcept {
|
||||
return handle().contains(entt);
|
||||
return view && view->contains(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -750,24 +796,24 @@ public:
|
||||
* Attempting to use an entity that doesn't belong to the view results in
|
||||
* undefined behavior.
|
||||
*
|
||||
* @tparam Type Type or index of the component to get.
|
||||
* @tparam Elem Type or index of the component to get.
|
||||
* @param entt A valid identifier.
|
||||
* @return The component assigned to the entity.
|
||||
*/
|
||||
template<typename... Type>
|
||||
template<typename Elem>
|
||||
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
|
||||
if constexpr(sizeof...(Type) == 0) {
|
||||
return storage().get_as_tuple(entt);
|
||||
} else {
|
||||
static_assert((std::is_same_v<std::remove_const_t<Type>, typename Get::value_type> && ...), "Invalid component type");
|
||||
return storage().get(entt);
|
||||
}
|
||||
static_assert(std::is_same_v<std::remove_const_t<Elem>, typename Get::value_type>, "Invalid component type");
|
||||
return get<0>(entt);
|
||||
}
|
||||
|
||||
/*! @copydoc get */
|
||||
template<std::size_t Index>
|
||||
template<std::size_t... Elem>
|
||||
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
|
||||
return storage().get(entt);
|
||||
if constexpr(sizeof...(Elem) == 0) {
|
||||
return std::get<0>(pools)->get_as_tuple(entt);
|
||||
} else {
|
||||
return std::get<Elem...>(pools)->get(entt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -794,17 +840,19 @@ public:
|
||||
*/
|
||||
template<typename Func>
|
||||
void each(Func func) const {
|
||||
if constexpr(is_applicable_v<Func, decltype(*each().begin())>) {
|
||||
for(const auto pack: each()) {
|
||||
std::apply(func, pack);
|
||||
}
|
||||
} else if constexpr(ignore_as_empty_v<typename Get::value_type>) {
|
||||
for(size_type pos{}, last = size(); pos < last; ++pos) {
|
||||
func();
|
||||
}
|
||||
} else {
|
||||
for(auto &&component: storage()) {
|
||||
func(component);
|
||||
if(view) {
|
||||
if constexpr(is_applicable_v<Func, decltype(*each().begin())>) {
|
||||
for(const auto pack: each()) {
|
||||
std::apply(func, pack);
|
||||
}
|
||||
} else if constexpr(Get::traits_type::page_size == 0u) {
|
||||
for(size_type pos{}, last = size(); pos < last; ++pos) {
|
||||
func();
|
||||
}
|
||||
} else {
|
||||
for(auto &&component: *std::get<0>(pools)) {
|
||||
func(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -819,7 +867,7 @@ public:
|
||||
* @return An iterable object to use to _visit_ the view.
|
||||
*/
|
||||
[[nodiscard]] iterable each() const noexcept {
|
||||
return storage().each();
|
||||
return view ? std::get<0>(pools)->each() : iterable{};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -831,13 +879,16 @@ public:
|
||||
*/
|
||||
template<typename... OGet, typename... OExclude>
|
||||
[[nodiscard]] auto operator|(const basic_view<get_t<OGet...>, exclude_t<OExclude...>> &other) const noexcept {
|
||||
return std::make_from_tuple<basic_view<get_t<Get, OGet...>, exclude_t<OExclude...>>>(
|
||||
std::apply([](auto *...curr) { return std::forward_as_tuple(*curr...); }, std::tuple_cat(pools, other.pools, other.filter)));
|
||||
return internal::view_pack(
|
||||
std::tuple_cat(pools, other.pools),
|
||||
internal::filter_as_tuple<OExclude...>(other.filter),
|
||||
std::index_sequence_for<Get, OGet..., OExclude...>{});
|
||||
}
|
||||
|
||||
private:
|
||||
std::tuple<Get *> pools;
|
||||
std::tuple<> filter;
|
||||
std::array<const common_type *, 0u> filter;
|
||||
const common_type *view;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "entity/group.hpp"
|
||||
#include "entity/handle.hpp"
|
||||
#include "entity/helper.hpp"
|
||||
#include "entity/mixin.hpp"
|
||||
#include "entity/observer.hpp"
|
||||
#include "entity/organizer.hpp"
|
||||
#include "entity/registry.hpp"
|
||||
@ -31,7 +32,6 @@
|
||||
#include "entity/snapshot.hpp"
|
||||
#include "entity/sparse_set.hpp"
|
||||
#include "entity/storage.hpp"
|
||||
#include "entity/storage_mixin.hpp"
|
||||
#include "entity/view.hpp"
|
||||
#include "graph/adjacency_matrix.hpp"
|
||||
#include "graph/dot.hpp"
|
||||
|
@ -258,7 +258,7 @@ public:
|
||||
[[nodiscard]] iterable_adaptor<out_edge_iterator> out_edges(const vertex_type vertex) const noexcept {
|
||||
const auto it = matrix.cbegin();
|
||||
const auto from = vertex * vert;
|
||||
const auto to = vertex * vert + vert;
|
||||
const auto to = from + vert;
|
||||
return {{it, vert, from, to, 1u}, {it, vert, to, to, 1u}};
|
||||
}
|
||||
|
||||
@ -270,7 +270,7 @@ public:
|
||||
[[nodiscard]] iterable_adaptor<in_edge_iterator> in_edges(const vertex_type vertex) const noexcept {
|
||||
const auto it = matrix.cbegin();
|
||||
const auto from = vertex;
|
||||
const auto to = vert * (vert - 1u) + vertex;
|
||||
const auto to = vert * vert + from;
|
||||
return {{it, vert, from, to, vert}, {it, vert, to, to, vert}};
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,7 @@ class basic_flow {
|
||||
using task_container_type = dense_set<id_type, identity, std::equal_to<id_type>, typename alloc_traits::template rebind_alloc<id_type>>;
|
||||
using ro_rw_container_type = std::vector<std::pair<std::size_t, bool>, typename alloc_traits::template rebind_alloc<std::pair<std::size_t, bool>>>;
|
||||
using deps_container_type = dense_map<id_type, ro_rw_container_type, identity, std::equal_to<id_type>, typename alloc_traits::template rebind_alloc<std::pair<const id_type, ro_rw_container_type>>>;
|
||||
using adjacency_matrix_type = adjacency_matrix<directed_tag, typename alloc_traits::template rebind_alloc<std::size_t>>;
|
||||
|
||||
void emplace(const id_type res, const bool is_rw) {
|
||||
ENTT_ASSERT(index.first() < vertices.size(), "Invalid node");
|
||||
@ -43,6 +44,76 @@ class basic_flow {
|
||||
deps[res].emplace_back(index.first(), is_rw);
|
||||
}
|
||||
|
||||
void setup_graph(adjacency_matrix_type &matrix) const {
|
||||
for(const auto &elem: deps) {
|
||||
const auto last = elem.second.cend();
|
||||
auto it = elem.second.cbegin();
|
||||
|
||||
while(it != last) {
|
||||
if(it->second) {
|
||||
// rw item
|
||||
if(auto curr = it++; it != last) {
|
||||
if(it->second) {
|
||||
matrix.insert(curr->first, it->first);
|
||||
} else if(const auto next = std::find_if(it, last, [](const auto &value) { return value.second; }); next != last) {
|
||||
for(; it != next; ++it) {
|
||||
matrix.insert(curr->first, it->first);
|
||||
matrix.insert(it->first, next->first);
|
||||
}
|
||||
} else {
|
||||
for(; it != next; ++it) {
|
||||
matrix.insert(curr->first, it->first);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// ro item (first iteration only)
|
||||
if(const auto next = std::find_if(it, last, [](const auto &value) { return value.second; }); next != last) {
|
||||
for(; it != next; ++it) {
|
||||
matrix.insert(it->first, next->first);
|
||||
}
|
||||
} else {
|
||||
it = last;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void transitive_closure(adjacency_matrix_type &matrix) const {
|
||||
const auto length = matrix.size();
|
||||
|
||||
for(std::size_t vk{}; vk < length; ++vk) {
|
||||
for(std::size_t vi{}; vi < length; ++vi) {
|
||||
for(std::size_t vj{}; vj < length; ++vj) {
|
||||
if(matrix.contains(vi, vk) && matrix.contains(vk, vj)) {
|
||||
matrix.insert(vi, vj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void transitive_reduction(adjacency_matrix_type &matrix) const {
|
||||
const auto length = matrix.size();
|
||||
|
||||
for(std::size_t vert{}; vert < length; ++vert) {
|
||||
matrix.erase(vert, vert);
|
||||
}
|
||||
|
||||
for(std::size_t vj{}; vj < length; ++vj) {
|
||||
for(std::size_t vi{}; vi < length; ++vi) {
|
||||
if(matrix.contains(vi, vj)) {
|
||||
for(std::size_t vk{}; vk < length; ++vk) {
|
||||
if(matrix.contains(vj, vk)) {
|
||||
matrix.erase(vi, vk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Allocator type. */
|
||||
using allocator_type = Allocator;
|
||||
@ -50,6 +121,8 @@ public:
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Iterable task list. */
|
||||
using iterable = iterable_adaptor<typename task_container_type::const_iterator>;
|
||||
/*! @brief Adjacency matrix type. */
|
||||
using graph_type = adjacency_matrix_type;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
basic_flow()
|
||||
@ -124,9 +197,10 @@ public:
|
||||
|
||||
/*! @brief Clears the flow builder. */
|
||||
void clear() noexcept {
|
||||
index.first() = sync_on = {};
|
||||
index.first() = {};
|
||||
vertices.clear();
|
||||
deps.clear();
|
||||
sync_on = {};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -245,72 +319,12 @@ public:
|
||||
* @brief Generates a task graph for the current content.
|
||||
* @return The adjacency matrix of the task graph.
|
||||
*/
|
||||
[[nodiscard]] adjacency_matrix<directed_tag> graph() const {
|
||||
const auto length = vertices.size();
|
||||
adjacency_matrix<directed_tag> matrix{length};
|
||||
[[nodiscard]] graph_type graph() const {
|
||||
graph_type matrix{vertices.size(), get_allocator()};
|
||||
|
||||
// creates the adjacency matrix
|
||||
for(const auto &elem: deps) {
|
||||
const auto last = elem.second.cend();
|
||||
auto it = elem.second.cbegin();
|
||||
|
||||
while(it != last) {
|
||||
if(it->second) {
|
||||
// rw item
|
||||
if(auto curr = it++; it != last) {
|
||||
if(it->second) {
|
||||
matrix.insert(curr->first, it->first);
|
||||
} else if(const auto next = std::find_if(it, last, [](const auto &value) { return value.second; }); next != last) {
|
||||
for(; it != next; ++it) {
|
||||
matrix.insert(curr->first, it->first);
|
||||
matrix.insert(it->first, next->first);
|
||||
}
|
||||
} else {
|
||||
for(; it != next; ++it) {
|
||||
matrix.insert(curr->first, it->first);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// ro item (first iteration only)
|
||||
if(const auto next = std::find_if(it, last, [](const auto &value) { return value.second; }); next != last) {
|
||||
for(; it != next; ++it) {
|
||||
matrix.insert(it->first, next->first);
|
||||
}
|
||||
} else {
|
||||
it = last;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// computes the transitive closure
|
||||
for(std::size_t vk{}; vk < length; ++vk) {
|
||||
for(std::size_t vi{}; vi < length; ++vi) {
|
||||
for(std::size_t vj{}; vj < length; ++vj) {
|
||||
if(matrix.contains(vi, vk) && matrix.contains(vk, vj)) {
|
||||
matrix.insert(vi, vj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// applies the transitive reduction
|
||||
for(std::size_t vert{}; vert < length; ++vert) {
|
||||
matrix.erase(vert, vert);
|
||||
}
|
||||
|
||||
for(std::size_t vj{}; vj < length; ++vj) {
|
||||
for(std::size_t vi{}; vi < length; ++vi) {
|
||||
if(matrix.contains(vi, vj)) {
|
||||
for(std::size_t vk{}; vk < length; ++vk) {
|
||||
if(matrix.contains(vj, vk)) {
|
||||
matrix.erase(vi, vk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
setup_graph(matrix);
|
||||
transitive_closure(matrix);
|
||||
transitive_reduction(matrix);
|
||||
|
||||
return matrix;
|
||||
}
|
||||
|
@ -70,40 +70,40 @@ public:
|
||||
* cases, they are discarded.
|
||||
*
|
||||
* @tparam Args Types of arguments to use to construct the fallback service.
|
||||
* @tparam Impl Fallback service type.
|
||||
* @tparam Type Fallback service type.
|
||||
* @param args Parameters to use to construct the fallback service.
|
||||
* @return A reference to a valid service.
|
||||
*/
|
||||
template<typename Impl = Service, typename... Args>
|
||||
template<typename Type = Service, typename... Args>
|
||||
[[nodiscard]] static Service &value_or(Args &&...args) {
|
||||
return service ? *service : emplace<Impl>(std::forward<Args>(args)...);
|
||||
return service ? *service : emplace<Type>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets or replaces a service.
|
||||
* @tparam Impl Service type.
|
||||
* @tparam Type Service type.
|
||||
* @tparam Args Types of arguments to use to construct the service.
|
||||
* @param args Parameters to use to construct the service.
|
||||
* @return A reference to a valid service.
|
||||
*/
|
||||
template<typename Impl = Service, typename... Args>
|
||||
template<typename Type = Service, typename... Args>
|
||||
static Service &emplace(Args &&...args) {
|
||||
service = std::make_shared<Impl>(std::forward<Args>(args)...);
|
||||
service = std::make_shared<Type>(std::forward<Args>(args)...);
|
||||
return *service;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets or replaces a service using a given allocator.
|
||||
* @tparam Impl Service type.
|
||||
* @tparam Type Service type.
|
||||
* @tparam Allocator Type of allocator used to manage memory and elements.
|
||||
* @tparam Args Types of arguments to use to construct the service.
|
||||
* @param alloc The allocator to use.
|
||||
* @param args Parameters to use to construct the service.
|
||||
* @return A reference to a valid service.
|
||||
*/
|
||||
template<typename Impl = Service, typename Allocator, typename... Args>
|
||||
static Service &allocate_emplace(Allocator alloc, Args &&...args) {
|
||||
service = std::allocate_shared<Impl>(alloc, std::forward<Args>(args)...);
|
||||
template<typename Type = Service, typename Allocator, typename... Args>
|
||||
static Service &emplace(std::allocator_arg_t, Allocator alloc, Args &&...args) {
|
||||
service = std::allocate_shared<Type>(alloc, std::forward<Args>(args)...);
|
||||
return *service;
|
||||
}
|
||||
|
||||
@ -125,6 +125,18 @@ public:
|
||||
service = other.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resets or replaces a service.
|
||||
* @tparam Type Service type.
|
||||
* @tparam Deleter Deleter type.
|
||||
* @param elem A pointer to a service to manage.
|
||||
* @param deleter A deleter to use to destroy the service.
|
||||
*/
|
||||
template<typename Type, typename Deleter = std::default_delete<Type>>
|
||||
static void reset(Type *elem, Deleter deleter = {}) {
|
||||
service = std::shared_ptr<Service>{elem, std::move(deleter)};
|
||||
}
|
||||
|
||||
private:
|
||||
// std::shared_ptr because of its type erased allocator which is useful here
|
||||
inline static std::shared_ptr<Service> service{};
|
||||
|
@ -21,8 +21,8 @@ struct meta_type_node;
|
||||
struct meta_context {
|
||||
dense_map<id_type, meta_type_node, identity> value{};
|
||||
|
||||
static inline meta_context &from(meta_ctx &ctx);
|
||||
static inline const meta_context &from(const meta_ctx &ctx);
|
||||
[[nodiscard]] static inline meta_context &from(meta_ctx &ctx);
|
||||
[[nodiscard]] static inline const meta_context &from(const meta_ctx &ctx);
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
@ -49,11 +49,11 @@ class meta_ctx: private internal::meta_context {
|
||||
* Internal details not to be documented.
|
||||
*/
|
||||
|
||||
inline internal::meta_context &internal::meta_context::from(meta_ctx &ctx) {
|
||||
[[nodiscard]] inline internal::meta_context &internal::meta_context::from(meta_ctx &ctx) {
|
||||
return ctx;
|
||||
}
|
||||
|
||||
inline const internal::meta_context &internal::meta_context::from(const meta_ctx &ctx) {
|
||||
[[nodiscard]] inline const internal::meta_context &internal::meta_context::from(const meta_ctx &ctx) {
|
||||
return ctx;
|
||||
}
|
||||
|
||||
|
@ -29,28 +29,12 @@ namespace entt {
|
||||
|
||||
namespace internal {
|
||||
|
||||
inline decltype(auto) owner(meta_ctx &ctx, const type_info &info) {
|
||||
[[nodiscard]] inline decltype(auto) owner(meta_ctx &ctx, const type_info &info) {
|
||||
auto &&context = internal::meta_context::from(ctx);
|
||||
ENTT_ASSERT(context.value.contains(info.hash()), "Type not available");
|
||||
return context.value[info.hash()];
|
||||
}
|
||||
|
||||
inline meta_base_node &meta_extend(internal::meta_type_node &parent, const id_type id, meta_base_node node) {
|
||||
return parent.details->base.insert_or_assign(id, std::move(node)).first->second;
|
||||
}
|
||||
|
||||
inline meta_conv_node &meta_extend(internal::meta_type_node &parent, const id_type id, meta_conv_node node) {
|
||||
return parent.details->conv.insert_or_assign(id, std::move(node)).first->second;
|
||||
}
|
||||
|
||||
inline meta_ctor_node &meta_extend(internal::meta_type_node &parent, const id_type id, meta_ctor_node node) {
|
||||
return parent.details->ctor.insert_or_assign(id, std::move(node)).first->second;
|
||||
}
|
||||
|
||||
inline meta_dtor_node &meta_extend(internal::meta_type_node &parent, meta_dtor_node node) {
|
||||
return (parent.dtor = std::move(node));
|
||||
}
|
||||
|
||||
inline meta_data_node &meta_extend(internal::meta_type_node &parent, const id_type id, meta_data_node node) {
|
||||
return parent.details->data.insert_or_assign(id, std::move(node)).first->second;
|
||||
}
|
||||
@ -72,10 +56,6 @@ inline meta_func_node &meta_extend(internal::meta_type_node &parent, const id_ty
|
||||
return parent.details->func.insert_or_assign(id, std::move(node)).first->second;
|
||||
}
|
||||
|
||||
inline meta_prop_node &meta_extend(dense_map<id_type, meta_prop_node, identity> &prop, const id_type id, meta_prop_node node) {
|
||||
return (prop[id] = std::move(node));
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
/**
|
||||
@ -156,16 +136,8 @@ public:
|
||||
template<typename Base>
|
||||
auto base() noexcept {
|
||||
static_assert(!std::is_same_v<Type, Base> && std::is_base_of_v<Base, Type>, "Invalid base type");
|
||||
|
||||
internal::meta_extend(
|
||||
internal::owner(*ctx, *info),
|
||||
type_id<Base>().hash(),
|
||||
internal::meta_base_node{
|
||||
&internal::resolve<Base>,
|
||||
+[](const void *instance) noexcept {
|
||||
return static_cast<const void *>(static_cast<const Base *>(static_cast<const Type *>(instance)));
|
||||
}});
|
||||
|
||||
auto *const op = +[](const void *instance) noexcept { return static_cast<const void *>(static_cast<const Base *>(static_cast<const Type *>(instance))); };
|
||||
internal::owner(*ctx, *info).details->base.insert_or_assign(type_id<Base>().hash(), internal::meta_base_node{&internal::resolve<Base>, op});
|
||||
bucket = nullptr;
|
||||
return *this;
|
||||
}
|
||||
@ -185,15 +157,8 @@ public:
|
||||
template<auto Candidate>
|
||||
auto conv() noexcept {
|
||||
using conv_type = std::remove_cv_t<std::remove_reference_t<std::invoke_result_t<decltype(Candidate), Type &>>>;
|
||||
|
||||
internal::meta_extend(
|
||||
internal::owner(*ctx, *info),
|
||||
type_id<conv_type>().hash(),
|
||||
internal::meta_conv_node{
|
||||
+[](const meta_ctx &area, const void *instance) {
|
||||
return forward_as_meta(area, std::invoke(Candidate, *static_cast<const Type *>(instance)));
|
||||
}});
|
||||
|
||||
auto *const op = +[](const meta_ctx &area, const void *instance) { return forward_as_meta(area, std::invoke(Candidate, *static_cast<const Type *>(instance))); };
|
||||
internal::owner(*ctx, *info).details->conv.insert_or_assign(type_id<conv_type>().hash(), internal::meta_conv_node{op});
|
||||
bucket = nullptr;
|
||||
return *this;
|
||||
}
|
||||
@ -210,15 +175,8 @@ public:
|
||||
template<typename To>
|
||||
auto conv() noexcept {
|
||||
using conv_type = std::remove_cv_t<std::remove_reference_t<To>>;
|
||||
|
||||
internal::meta_extend(
|
||||
internal::owner(*ctx, *info),
|
||||
type_id<conv_type>().hash(),
|
||||
internal::meta_conv_node{
|
||||
+[](const meta_ctx &area, const void *instance) {
|
||||
return forward_as_meta(area, static_cast<To>(*static_cast<const Type *>(instance)));
|
||||
}});
|
||||
|
||||
auto *const op = +[](const meta_ctx &area, const void *instance) { return forward_as_meta(area, static_cast<To>(*static_cast<const Type *>(instance))); };
|
||||
internal::owner(*ctx, *info).details->conv.insert_or_assign(type_id<conv_type>().hash(), internal::meta_conv_node{op});
|
||||
bucket = nullptr;
|
||||
return *this;
|
||||
}
|
||||
@ -241,15 +199,7 @@ public:
|
||||
using descriptor = meta_function_helper_t<Type, decltype(Candidate)>;
|
||||
static_assert(Policy::template value<typename descriptor::return_type>, "Invalid return type for the given policy");
|
||||
static_assert(std::is_same_v<std::remove_cv_t<std::remove_reference_t<typename descriptor::return_type>>, Type>, "The function doesn't return an object of the required type");
|
||||
|
||||
internal::meta_extend(
|
||||
internal::owner(*ctx, *info),
|
||||
type_id<typename descriptor::args_type>().hash(),
|
||||
internal::meta_ctor_node{
|
||||
descriptor::args_type::size,
|
||||
&meta_arg<typename descriptor::args_type>,
|
||||
&meta_construct<Type, Candidate, Policy>});
|
||||
|
||||
internal::owner(*ctx, *info).details->ctor.insert_or_assign(type_id<typename descriptor::args_type>().hash(), internal::meta_ctor_node{descriptor::args_type::size, &meta_arg<typename descriptor::args_type>, &meta_construct<Type, Candidate, Policy>});
|
||||
bucket = nullptr;
|
||||
return *this;
|
||||
}
|
||||
@ -269,14 +219,7 @@ public:
|
||||
// default constructor is already implicitly generated, no need for redundancy
|
||||
if constexpr(sizeof...(Args) != 0u) {
|
||||
using descriptor = meta_function_helper_t<Type, Type (*)(Args...)>;
|
||||
|
||||
internal::meta_extend(
|
||||
internal::owner(*ctx, *info),
|
||||
type_id<typename descriptor::args_type>().hash(),
|
||||
internal::meta_ctor_node{
|
||||
descriptor::args_type::size,
|
||||
&meta_arg<typename descriptor::args_type>,
|
||||
&meta_construct<Type, Args...>});
|
||||
internal::owner(*ctx, *info).details->ctor.insert_or_assign(type_id<typename descriptor::args_type>().hash(), internal::meta_ctor_node{descriptor::args_type::size, &meta_arg<typename descriptor::args_type>, &meta_construct<Type, Args...>});
|
||||
}
|
||||
|
||||
bucket = nullptr;
|
||||
@ -304,12 +247,8 @@ public:
|
||||
template<auto Func>
|
||||
auto dtor() noexcept {
|
||||
static_assert(std::is_invocable_v<decltype(Func), Type &>, "The function doesn't accept an object of the type provided");
|
||||
|
||||
internal::meta_extend(
|
||||
internal::owner(*ctx, *info),
|
||||
internal::meta_dtor_node{
|
||||
+[](void *instance) { std::invoke(Func, *static_cast<Type *>(instance)); }});
|
||||
|
||||
auto *const op = +[](void *instance) { std::invoke(Func, *static_cast<Type *>(instance)); };
|
||||
internal::owner(*ctx, *info).dtor = internal::meta_dtor_node{op};
|
||||
bucket = nullptr;
|
||||
return *this;
|
||||
}
|
||||
@ -330,32 +269,39 @@ public:
|
||||
template<auto Data, typename Policy = as_is_t>
|
||||
auto data(const id_type id) noexcept {
|
||||
if constexpr(std::is_member_object_pointer_v<decltype(Data)>) {
|
||||
using data_type = std::remove_reference_t<std::invoke_result_t<decltype(Data), Type &>>;
|
||||
using data_type = std::invoke_result_t<decltype(Data), Type &>;
|
||||
static_assert(Policy::template value<data_type>, "Invalid return type for the given policy");
|
||||
|
||||
auto &&elem = internal::meta_extend(
|
||||
internal::owner(*ctx, *info),
|
||||
id,
|
||||
internal::meta_data_node{
|
||||
/* this is never static */
|
||||
std::is_const_v<data_type> ? internal::meta_traits::is_const : internal::meta_traits::is_none,
|
||||
std::is_const_v<std::remove_reference_t<data_type>> ? internal::meta_traits::is_const : internal::meta_traits::is_none,
|
||||
1u,
|
||||
&internal::resolve<std::remove_cv_t<data_type>>,
|
||||
&meta_arg<type_list<std::remove_cv_t<data_type>>>,
|
||||
&internal::resolve<std::remove_cv_t<std::remove_reference_t<data_type>>>,
|
||||
&meta_arg<type_list<std::remove_cv_t<std::remove_reference_t<data_type>>>>,
|
||||
&meta_setter<Type, Data>,
|
||||
&meta_getter<Type, Data, Policy>});
|
||||
|
||||
bucket = &elem.prop;
|
||||
} else {
|
||||
using data_type = std::remove_reference_t<std::remove_pointer_t<decltype(Data)>>;
|
||||
using data_type = std::remove_pointer_t<decltype(Data)>;
|
||||
|
||||
if constexpr(std::is_pointer_v<decltype(Data)>) {
|
||||
static_assert(Policy::template value<decltype(*Data)>, "Invalid return type for the given policy");
|
||||
} else {
|
||||
static_assert(Policy::template value<data_type>, "Invalid return type for the given policy");
|
||||
}
|
||||
|
||||
auto &&elem = internal::meta_extend(
|
||||
internal::owner(*ctx, *info),
|
||||
id,
|
||||
internal::meta_data_node{
|
||||
((std::is_same_v<Type, std::remove_cv_t<data_type>> || std::is_const_v<data_type>) ? internal::meta_traits::is_const : internal::meta_traits::is_none) | internal::meta_traits::is_static,
|
||||
((std::is_same_v<Type, std::remove_cv_t<std::remove_reference_t<data_type>>> || std::is_const_v<std::remove_reference_t<data_type>>) ? internal::meta_traits::is_const : internal::meta_traits::is_none) | internal::meta_traits::is_static,
|
||||
1u,
|
||||
&internal::resolve<std::remove_cv_t<data_type>>,
|
||||
&meta_arg<type_list<std::remove_cv_t<data_type>>>,
|
||||
&internal::resolve<std::remove_cv_t<std::remove_reference_t<data_type>>>,
|
||||
&meta_arg<type_list<std::remove_cv_t<std::remove_reference_t<data_type>>>>,
|
||||
&meta_setter<Type, Data>,
|
||||
&meta_getter<Type, Data, Policy>});
|
||||
|
||||
@ -495,18 +441,11 @@ public:
|
||||
ENTT_ASSERT(bucket != nullptr, "Meta object does not support properties");
|
||||
|
||||
if constexpr(sizeof...(Value) == 0u) {
|
||||
internal::meta_extend(
|
||||
*bucket,
|
||||
id,
|
||||
internal::meta_prop_node{
|
||||
&internal::resolve<void>});
|
||||
(*bucket)[id] = internal::meta_prop_node{&internal::resolve<void>};
|
||||
} else {
|
||||
internal::meta_extend(
|
||||
*bucket,
|
||||
id,
|
||||
internal::meta_prop_node{
|
||||
&internal::resolve<std::decay_t<Value>>...,
|
||||
std::make_shared<std::decay_t<Value>>(std::forward<Value>(value))...});
|
||||
(*bucket)[id] = internal::meta_prop_node{
|
||||
&internal::resolve<std::decay_t<Value>>...,
|
||||
std::make_shared<std::decay_t<Value>>(std::forward<Value>(value))...};
|
||||
}
|
||||
|
||||
return *this;
|
||||
|
@ -613,7 +613,7 @@ private:
|
||||
* @return A properly initialized and not necessarily owning wrapper.
|
||||
*/
|
||||
template<typename Type>
|
||||
meta_any forward_as_meta(const meta_ctx &ctx, Type &&value) {
|
||||
[[nodiscard]] meta_any forward_as_meta(const meta_ctx &ctx, Type &&value) {
|
||||
return meta_any{ctx, std::in_place_type<Type &&>, std::forward<Type>(value)};
|
||||
}
|
||||
|
||||
@ -624,7 +624,7 @@ meta_any forward_as_meta(const meta_ctx &ctx, Type &&value) {
|
||||
* @return A properly initialized and not necessarily owning wrapper.
|
||||
*/
|
||||
template<typename Type>
|
||||
meta_any forward_as_meta(Type &&value) {
|
||||
[[nodiscard]] meta_any forward_as_meta(Type &&value) {
|
||||
return forward_as_meta(locator<meta_ctx>::value_or(), std::forward<Type>(value));
|
||||
}
|
||||
|
||||
@ -722,6 +722,16 @@ struct meta_handle {
|
||||
return static_cast<bool>(any);
|
||||
}
|
||||
|
||||
/*! @copydoc meta_any::operator== */
|
||||
[[nodiscard]] bool operator==(const meta_handle &other) const noexcept {
|
||||
return (any == other.any);
|
||||
}
|
||||
|
||||
/*! @copydoc meta_any::operator!= */
|
||||
[[nodiscard]] bool operator!=(const meta_handle &other) const noexcept {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Access operator for accessing the contained opaque object.
|
||||
* @return A wrapper that shares a reference to an unmanaged object.
|
||||
@ -756,13 +766,21 @@ struct meta_prop {
|
||||
ctx{&area} {}
|
||||
|
||||
/**
|
||||
* @brief Returns the stored value by copy.
|
||||
* @brief Returns the stored value by const reference.
|
||||
* @return A wrapper containing the value stored with the property.
|
||||
*/
|
||||
[[nodiscard]] meta_any value() const {
|
||||
return node->value ? node->type(internal::meta_context::from(*ctx)).from_void(*ctx, nullptr, node->value.get()) : meta_any{meta_ctx_arg, *ctx};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the stored value by reference.
|
||||
* @return A wrapper containing the value stored with the property.
|
||||
*/
|
||||
[[nodiscard]] meta_any value() {
|
||||
return node->value ? node->type(internal::meta_context::from(*ctx)).from_void(*ctx, node->value.get(), nullptr) : meta_any{meta_ctx_arg, *ctx};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if an object is valid, false otherwise.
|
||||
* @return True if the object is valid, false otherwise.
|
||||
@ -771,11 +789,30 @@ struct meta_prop {
|
||||
return (node != nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if two objects refer to the same type.
|
||||
* @param other The object with which to compare.
|
||||
* @return True if the objects refer to the same type, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool operator==(const meta_prop &other) const noexcept {
|
||||
return (ctx == other.ctx && node == other.node);
|
||||
}
|
||||
|
||||
private:
|
||||
const internal::meta_prop_node *node;
|
||||
const meta_ctx *ctx;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Checks if two objects refer to the same type.
|
||||
* @param lhs An object, either valid or not.
|
||||
* @param rhs An object, either valid or not.
|
||||
* @return False if the objects refer to the same node, true otherwise.
|
||||
*/
|
||||
[[nodiscard]] inline bool operator!=(const meta_prop &lhs, const meta_prop &rhs) noexcept {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
/*! @brief Opaque wrapper for data members. */
|
||||
struct meta_data {
|
||||
/*! @brief Unsigned integer type. */
|
||||
@ -876,11 +913,26 @@ struct meta_data {
|
||||
return (node != nullptr);
|
||||
}
|
||||
|
||||
/*! @copydoc meta_prop::operator== */
|
||||
[[nodiscard]] bool operator==(const meta_data &other) const noexcept {
|
||||
return (ctx == other.ctx && node == other.node);
|
||||
}
|
||||
|
||||
private:
|
||||
const internal::meta_data_node *node;
|
||||
const meta_ctx *ctx;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Checks if two objects refer to the same type.
|
||||
* @param lhs An object, either valid or not.
|
||||
* @param rhs An object, either valid or not.
|
||||
* @return False if the objects refer to the same node, true otherwise.
|
||||
*/
|
||||
[[nodiscard]] inline bool operator!=(const meta_data &lhs, const meta_data &rhs) noexcept {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
/*! @brief Opaque wrapper for member functions. */
|
||||
struct meta_func {
|
||||
/*! @brief Unsigned integer type. */
|
||||
@ -962,8 +1014,12 @@ struct meta_func {
|
||||
*/
|
||||
template<typename... Args>
|
||||
meta_any invoke(meta_handle instance, Args &&...args) const {
|
||||
meta_any arguments[sizeof...(Args) + 1u]{{*ctx, std::forward<Args>(args)}...};
|
||||
return invoke(meta_handle{*ctx, std::move(instance)}, arguments, sizeof...(Args));
|
||||
if constexpr(sizeof...(Args) == 0u) {
|
||||
return invoke(std::move(instance), static_cast<meta_any *>(nullptr), size_type{});
|
||||
} else {
|
||||
meta_any arguments[sizeof...(Args)]{{*ctx, std::forward<Args>(args)}...};
|
||||
return invoke(std::move(instance), arguments, sizeof...(Args));
|
||||
}
|
||||
}
|
||||
|
||||
/*! @copydoc meta_data::prop */
|
||||
@ -997,11 +1053,26 @@ struct meta_func {
|
||||
return (node != nullptr);
|
||||
}
|
||||
|
||||
/*! @copydoc meta_prop::operator== */
|
||||
[[nodiscard]] bool operator==(const meta_func &other) const noexcept {
|
||||
return (ctx == other.ctx && node == other.node);
|
||||
}
|
||||
|
||||
private:
|
||||
const internal::meta_func_node *node;
|
||||
const meta_ctx *ctx;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Checks if two objects refer to the same type.
|
||||
* @param lhs An object, either valid or not.
|
||||
* @param rhs An object, either valid or not.
|
||||
* @return False if the objects refer to the same node, true otherwise.
|
||||
*/
|
||||
[[nodiscard]] inline bool operator!=(const meta_func &lhs, const meta_func &rhs) noexcept {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
/*! @brief Opaque wrapper for types. */
|
||||
class meta_type {
|
||||
template<typename Func>
|
||||
@ -1339,8 +1410,12 @@ public:
|
||||
*/
|
||||
template<typename... Args>
|
||||
[[nodiscard]] meta_any construct(Args &&...args) const {
|
||||
meta_any arguments[sizeof...(Args) + 1u]{{*ctx, std::forward<Args>(args)}...};
|
||||
return construct(arguments, sizeof...(Args));
|
||||
if constexpr(sizeof...(Args) == 0u) {
|
||||
return construct(static_cast<meta_any *>(nullptr), size_type{});
|
||||
} else {
|
||||
meta_any arguments[sizeof...(Args)]{{*ctx, std::forward<Args>(args)}...};
|
||||
return construct(arguments, sizeof...(Args));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1348,12 +1423,12 @@ public:
|
||||
* @param element A valid pointer to an element of the underlying type.
|
||||
* @return A wrapper that references the given instance.
|
||||
*/
|
||||
meta_any from_void(void *element) const {
|
||||
[[nodiscard]] meta_any from_void(void *element) const {
|
||||
return (element && node.from_void) ? node.from_void(*ctx, element, nullptr) : meta_any{meta_ctx_arg, *ctx};
|
||||
}
|
||||
|
||||
/*! @copydoc from_void */
|
||||
meta_any from_void(const void *element) const {
|
||||
[[nodiscard]] meta_any from_void(const void *element) const {
|
||||
return (element && node.from_void) ? node.from_void(*ctx, nullptr, element) : meta_any{meta_ctx_arg, *ctx};
|
||||
}
|
||||
|
||||
@ -1373,7 +1448,7 @@ public:
|
||||
meta_any invoke(const id_type id, meta_handle instance, meta_any *const args, const size_type sz) const {
|
||||
if(node.details) {
|
||||
if(auto it = node.details->func.find(id); it != node.details->func.cend()) {
|
||||
if(const auto *candidate = lookup(args, sz, (instance->data() == nullptr), [curr = &it->second]() mutable { return curr ? std::exchange(curr, curr->next.get()) : nullptr; }); candidate) {
|
||||
if(const auto *candidate = lookup(args, sz, instance && (instance->data() == nullptr), [curr = &it->second]() mutable { return curr ? std::exchange(curr, curr->next.get()) : nullptr; }); candidate) {
|
||||
return candidate->invoke(*ctx, meta_handle{*ctx, std::move(instance)}, args);
|
||||
}
|
||||
}
|
||||
@ -1399,8 +1474,12 @@ public:
|
||||
*/
|
||||
template<typename... Args>
|
||||
meta_any invoke(const id_type id, meta_handle instance, Args &&...args) const {
|
||||
meta_any arguments[sizeof...(Args) + 1u]{{*ctx, std::forward<Args>(args)}...};
|
||||
return invoke(id, meta_handle{*ctx, std::move(instance)}, arguments, sizeof...(Args));
|
||||
if constexpr(sizeof...(Args) == 0u) {
|
||||
return invoke(id, std::move(instance), static_cast<meta_any *>(nullptr), size_type{});
|
||||
} else {
|
||||
meta_any arguments[sizeof...(Args)]{{*ctx, std::forward<Args>(args)}...};
|
||||
return invoke(id, std::move(instance), arguments, sizeof...(Args));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1466,11 +1545,7 @@ public:
|
||||
return !(ctx == nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if two objects refer to the same type.
|
||||
* @param other The object with which to compare.
|
||||
* @return True if the objects refer to the same type, false otherwise.
|
||||
*/
|
||||
/*! @copydoc meta_prop::operator== */
|
||||
[[nodiscard]] bool operator==(const meta_type &other) const noexcept {
|
||||
return (ctx == other.ctx) && ((!node.info && !other.node.info) || (node.info && other.node.info && *node.info == *other.node.info));
|
||||
}
|
||||
@ -1622,7 +1697,7 @@ public:
|
||||
explicit meta_iterator(const meta_ctx &area, It iter) noexcept
|
||||
: ctx{&area},
|
||||
vtable{&basic_vtable<It>},
|
||||
handle{std::move(iter)} {}
|
||||
handle{iter} {}
|
||||
|
||||
meta_iterator &operator++() noexcept {
|
||||
vtable(operation::incr, handle, 1, nullptr);
|
||||
@ -1716,7 +1791,7 @@ public:
|
||||
meta_iterator(const meta_ctx &area, std::integral_constant<bool, KeyOnly>, It iter) noexcept
|
||||
: ctx{&area},
|
||||
vtable{&basic_vtable<KeyOnly, It>},
|
||||
handle{std::move(iter)} {}
|
||||
handle{iter} {}
|
||||
|
||||
meta_iterator &operator++() noexcept {
|
||||
vtable(operation::incr, handle, nullptr);
|
||||
@ -1826,7 +1901,7 @@ inline meta_sequence_container::iterator meta_sequence_container::insert(iterato
|
||||
* @return A possibly invalid iterator following the last removed element.
|
||||
*/
|
||||
inline meta_sequence_container::iterator meta_sequence_container::erase(iterator it) {
|
||||
return insert(std::move(it), {});
|
||||
return insert(it, {});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -68,11 +68,7 @@ struct as_void_t final {
|
||||
*/
|
||||
template<typename Type>
|
||||
struct is_meta_policy
|
||||
: std::disjunction<
|
||||
std::is_same<Type, as_ref_t>,
|
||||
std::is_same<Type, as_cref_t>,
|
||||
std::is_same<Type, as_is_t>,
|
||||
std::is_same<Type, as_void_t>> {};
|
||||
: std::bool_constant<std::is_same_v<Type, as_ref_t> || std::is_same_v<Type, as_cref_t> || std::is_same_v<Type, as_is_t> || std::is_same_v<Type, as_void_t>> {};
|
||||
|
||||
/**
|
||||
* @brief Helper variable template.
|
||||
|
@ -25,19 +25,19 @@ struct meta_range_iterator final {
|
||||
using reference = value_type;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
|
||||
meta_range_iterator() noexcept
|
||||
constexpr meta_range_iterator() noexcept
|
||||
: it{},
|
||||
ctx{} {}
|
||||
|
||||
meta_range_iterator(const meta_ctx &area, const It iter) noexcept
|
||||
constexpr meta_range_iterator(const meta_ctx &area, const It iter) noexcept
|
||||
: it{iter},
|
||||
ctx{&area} {}
|
||||
|
||||
meta_range_iterator &operator++() noexcept {
|
||||
constexpr meta_range_iterator &operator++() noexcept {
|
||||
return ++it, *this;
|
||||
}
|
||||
|
||||
meta_range_iterator operator++(int) noexcept {
|
||||
constexpr meta_range_iterator operator++(int) noexcept {
|
||||
meta_range_iterator orig = *this;
|
||||
return ++(*this), orig;
|
||||
}
|
||||
|
@ -30,7 +30,6 @@ struct meta_associative_container_traits;
|
||||
/**
|
||||
* @brief Provides the member constant `value` to true if a given type is a
|
||||
* pointer-like type from the point of view of the meta system, false otherwise.
|
||||
* @tparam Type Potentially pointer-like type.
|
||||
*/
|
||||
template<typename>
|
||||
struct is_meta_pointer_like: std::false_type {};
|
||||
|
@ -92,9 +92,12 @@ template<typename Type, typename Ret, typename MaybeType, typename... Args>
|
||||
struct meta_function_descriptor<Type, Ret (*)(MaybeType, Args...)>
|
||||
: meta_function_descriptor_traits<
|
||||
Ret,
|
||||
std::conditional_t<std::is_base_of_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type>, type_list<Args...>, type_list<MaybeType, Args...>>,
|
||||
!std::is_base_of_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type>,
|
||||
std::is_base_of_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type> && std::is_const_v<std::remove_reference_t<MaybeType>>> {};
|
||||
std::conditional_t<
|
||||
std::is_same_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type> || std::is_base_of_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type>,
|
||||
type_list<Args...>,
|
||||
type_list<MaybeType, Args...>>,
|
||||
!(std::is_same_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type> || std::is_base_of_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type>),
|
||||
std::is_const_v<std::remove_reference_t<MaybeType>> && (std::is_same_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type> || std::is_base_of_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type>)> {};
|
||||
|
||||
/**
|
||||
* @brief Meta function descriptor.
|
||||
@ -162,7 +165,7 @@ using meta_function_helper_t = typename meta_function_helper<Type, Candidate>::t
|
||||
* @return A meta any containing the returned value, if any.
|
||||
*/
|
||||
template<typename Policy = as_is_t, typename Type>
|
||||
std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_dispatch(const meta_ctx &ctx, [[maybe_unused]] Type &&value) {
|
||||
[[nodiscard]] std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_dispatch(const meta_ctx &ctx, [[maybe_unused]] Type &&value) {
|
||||
if constexpr(std::is_same_v<Policy, as_void_t>) {
|
||||
return meta_any{ctx, std::in_place_type<void>};
|
||||
} else if constexpr(std::is_same_v<Policy, as_ref_t>) {
|
||||
@ -183,7 +186,7 @@ std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_dispatch(const meta_ct
|
||||
* @return A meta any containing the returned value, if any.
|
||||
*/
|
||||
template<typename Policy = as_is_t, typename Type>
|
||||
std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_dispatch(Type &&value) {
|
||||
[[nodiscard]] std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_dispatch(Type &&value) {
|
||||
return meta_dispatch<Policy, Type>(locator<meta_ctx>::value_or(), std::forward<Type>(value));
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@ struct poly_inspector {
|
||||
* @brief Generic conversion operator (definition only).
|
||||
* @tparam Type Type to which conversion is requested.
|
||||
*/
|
||||
template<class Type>
|
||||
template<typename Type>
|
||||
operator Type &&() const;
|
||||
|
||||
/**
|
||||
|
@ -1,13 +1,18 @@
|
||||
#ifndef ENTT_PROCESS_FWD_HPP
|
||||
#define ENTT_PROCESS_FWD_HPP
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace entt {
|
||||
|
||||
template<typename, typename>
|
||||
class process;
|
||||
|
||||
template<typename>
|
||||
class scheduler;
|
||||
template<typename = std::uint32_t>
|
||||
class basic_scheduler;
|
||||
|
||||
/*! @brief Alias declaration for the most common use case. */
|
||||
using scheduler = basic_scheduler<>;
|
||||
|
||||
} // namespace entt
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "fwd.hpp"
|
||||
#include "process.hpp"
|
||||
|
||||
namespace entt {
|
||||
@ -38,11 +39,11 @@ namespace entt {
|
||||
* @tparam Delta Type to use to provide elapsed time.
|
||||
*/
|
||||
template<typename Delta>
|
||||
class scheduler {
|
||||
class basic_scheduler {
|
||||
struct process_handler {
|
||||
using instance_type = std::unique_ptr<void, void (*)(void *)>;
|
||||
using update_fn_type = bool(scheduler &, std::size_t, Delta, void *);
|
||||
using abort_fn_type = void(scheduler &, std::size_t, bool);
|
||||
using update_fn_type = bool(basic_scheduler &, std::size_t, Delta, void *);
|
||||
using abort_fn_type = void(basic_scheduler &, std::size_t, bool);
|
||||
using next_type = std::unique_ptr<process_handler>;
|
||||
|
||||
instance_type instance;
|
||||
@ -58,8 +59,8 @@ class scheduler {
|
||||
template<typename Proc, typename... Args>
|
||||
continuation then(Args &&...args) {
|
||||
static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>, "Invalid process type");
|
||||
auto proc = typename process_handler::instance_type{new Proc{std::forward<Args>(args)...}, &scheduler::deleter<Proc>};
|
||||
handler->next.reset(new process_handler{std::move(proc), &scheduler::update<Proc>, &scheduler::abort<Proc>, nullptr});
|
||||
auto proc = typename process_handler::instance_type{new Proc{std::forward<Args>(args)...}, &basic_scheduler::deleter<Proc>};
|
||||
handler->next.reset(new process_handler{std::move(proc), &basic_scheduler::update<Proc>, &basic_scheduler::abort<Proc>, nullptr});
|
||||
handler = handler->next.get();
|
||||
return *this;
|
||||
}
|
||||
@ -74,7 +75,7 @@ class scheduler {
|
||||
};
|
||||
|
||||
template<typename Proc>
|
||||
[[nodiscard]] static bool update(scheduler &owner, std::size_t pos, const Delta delta, void *data) {
|
||||
[[nodiscard]] static bool update(basic_scheduler &owner, std::size_t pos, const Delta delta, void *data) {
|
||||
auto *process = static_cast<Proc *>(owner.handlers[pos].instance.get());
|
||||
process->tick(delta, data);
|
||||
|
||||
@ -94,7 +95,7 @@ class scheduler {
|
||||
}
|
||||
|
||||
template<typename Proc>
|
||||
static void abort(scheduler &owner, std::size_t pos, const bool immediately) {
|
||||
static void abort(basic_scheduler &owner, std::size_t pos, const bool immediately) {
|
||||
static_cast<Proc *>(owner.handlers[pos].instance.get())->abort(immediately);
|
||||
}
|
||||
|
||||
@ -104,18 +105,20 @@ class scheduler {
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using delta_type = Delta;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
scheduler()
|
||||
basic_scheduler()
|
||||
: handlers{} {}
|
||||
|
||||
/*! @brief Default move constructor. */
|
||||
scheduler(scheduler &&) = default;
|
||||
basic_scheduler(basic_scheduler &&) = default;
|
||||
|
||||
/*! @brief Default move assignment operator. @return This scheduler. */
|
||||
scheduler &operator=(scheduler &&) = default;
|
||||
basic_scheduler &operator=(basic_scheduler &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Number of processes currently scheduled.
|
||||
@ -171,8 +174,8 @@ public:
|
||||
template<typename Proc, typename... Args>
|
||||
auto attach(Args &&...args) {
|
||||
static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>, "Invalid process type");
|
||||
auto proc = typename process_handler::instance_type{new Proc{std::forward<Args>(args)...}, &scheduler::deleter<Proc>};
|
||||
auto &&ref = handlers.emplace_back(process_handler{std::move(proc), &scheduler::update<Proc>, &scheduler::abort<Proc>, nullptr});
|
||||
auto proc = typename process_handler::instance_type{new Proc{std::forward<Args>(args)...}, &basic_scheduler::deleter<Proc>};
|
||||
auto &&ref = handlers.emplace_back(process_handler{std::move(proc), &basic_scheduler::update<Proc>, &basic_scheduler::abort<Proc>, nullptr});
|
||||
// forces the process to exit the uninitialized state
|
||||
ref.update(*this, handlers.size() - 1u, {}, nullptr);
|
||||
return continuation{&handlers.back()};
|
||||
@ -246,7 +249,7 @@ public:
|
||||
* @param delta Elapsed time.
|
||||
* @param data Optional data.
|
||||
*/
|
||||
void update(const Delta delta, void *data = nullptr) {
|
||||
void update(const delta_type delta, void *data = nullptr) {
|
||||
for(auto pos = handlers.size(); pos; --pos) {
|
||||
const auto curr = pos - 1u;
|
||||
|
||||
|
@ -95,51 +95,51 @@ public:
|
||||
return operator*();
|
||||
}
|
||||
|
||||
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
|
||||
friend constexpr std::ptrdiff_t operator-(const resource_cache_iterator<TLhs, ILhs> &, const resource_cache_iterator<TRhs, IRhs> &) noexcept;
|
||||
template<typename... Lhs, typename... Rhs>
|
||||
friend constexpr std::ptrdiff_t operator-(const resource_cache_iterator<Lhs...> &, const resource_cache_iterator<Rhs...> &) noexcept;
|
||||
|
||||
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
|
||||
friend constexpr bool operator==(const resource_cache_iterator<TLhs, ILhs> &, const resource_cache_iterator<TRhs, IRhs> &) noexcept;
|
||||
template<typename... Lhs, typename... Rhs>
|
||||
friend constexpr bool operator==(const resource_cache_iterator<Lhs...> &, const resource_cache_iterator<Rhs...> &) noexcept;
|
||||
|
||||
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
|
||||
friend constexpr bool operator<(const resource_cache_iterator<TLhs, ILhs> &, const resource_cache_iterator<TRhs, IRhs> &) noexcept;
|
||||
template<typename... Lhs, typename... Rhs>
|
||||
friend constexpr bool operator<(const resource_cache_iterator<Lhs...> &, const resource_cache_iterator<Rhs...> &) noexcept;
|
||||
|
||||
private:
|
||||
It it;
|
||||
};
|
||||
|
||||
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
|
||||
[[nodiscard]] constexpr std::ptrdiff_t operator-(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) noexcept {
|
||||
template<typename... Lhs, typename... Rhs>
|
||||
[[nodiscard]] constexpr std::ptrdiff_t operator-(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
|
||||
return lhs.it - rhs.it;
|
||||
}
|
||||
|
||||
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
|
||||
[[nodiscard]] constexpr bool operator==(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) noexcept {
|
||||
template<typename... Lhs, typename... Rhs>
|
||||
[[nodiscard]] constexpr bool operator==(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
|
||||
return lhs.it == rhs.it;
|
||||
}
|
||||
|
||||
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
|
||||
[[nodiscard]] constexpr bool operator!=(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) noexcept {
|
||||
template<typename... Lhs, typename... Rhs>
|
||||
[[nodiscard]] constexpr bool operator!=(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
|
||||
[[nodiscard]] constexpr bool operator<(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) noexcept {
|
||||
template<typename... Lhs, typename... Rhs>
|
||||
[[nodiscard]] constexpr bool operator<(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
|
||||
return lhs.it < rhs.it;
|
||||
}
|
||||
|
||||
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
|
||||
[[nodiscard]] constexpr bool operator>(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) noexcept {
|
||||
template<typename... Lhs, typename... Rhs>
|
||||
[[nodiscard]] constexpr bool operator>(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
|
||||
return rhs < lhs;
|
||||
}
|
||||
|
||||
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
|
||||
[[nodiscard]] constexpr bool operator<=(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) noexcept {
|
||||
template<typename... Lhs, typename... Rhs>
|
||||
[[nodiscard]] constexpr bool operator<=(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
|
||||
return !(lhs > rhs);
|
||||
}
|
||||
|
||||
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
|
||||
[[nodiscard]] constexpr bool operator>=(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) noexcept {
|
||||
template<typename... Lhs, typename... Rhs>
|
||||
[[nodiscard]] constexpr bool operator>=(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
|
||||
return !(lhs < rhs);
|
||||
}
|
||||
|
||||
@ -158,7 +158,7 @@ template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
|
||||
*/
|
||||
template<typename Type, typename Loader, typename Allocator>
|
||||
class resource_cache {
|
||||
using alloc_traits = typename std::allocator_traits<Allocator>;
|
||||
using alloc_traits = std::allocator_traits<Allocator>;
|
||||
static_assert(std::is_same_v<typename alloc_traits::value_type, Type>, "Invalid value type");
|
||||
using container_allocator = typename alloc_traits::template rebind_alloc<std::pair<const id_type, typename Loader::result_type>>;
|
||||
using container_type = dense_map<id_type, typename Loader::result_type, identity, std::equal_to<id_type>, container_allocator>;
|
||||
@ -241,8 +241,7 @@ public:
|
||||
/**
|
||||
* @brief Returns an iterator to the beginning.
|
||||
*
|
||||
* The returned iterator points to the first instance of the cache. If the
|
||||
* cache is empty, the returned iterator will be equal to `end()`.
|
||||
* If the cache is empty, the returned iterator will be equal to `end()`.
|
||||
*
|
||||
* @return An iterator to the first instance of the internal cache.
|
||||
*/
|
||||
@ -262,11 +261,6 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the end.
|
||||
*
|
||||
* The returned iterator points to the element following the last instance
|
||||
* of the cache. Attempting to dereference the returned iterator results in
|
||||
* undefined behavior.
|
||||
*
|
||||
* @return An iterator to the element following the last instance of the
|
||||
* internal cache.
|
||||
*/
|
||||
|
@ -163,81 +163,81 @@ private:
|
||||
|
||||
/**
|
||||
* @brief Compares two handles.
|
||||
* @tparam Res Type of resource managed by the first handle.
|
||||
* @tparam Other Type of resource managed by the second handle.
|
||||
* @tparam Lhs Type of resource managed by the first handle.
|
||||
* @tparam Rhs Type of resource managed by the second handle.
|
||||
* @param lhs A valid handle.
|
||||
* @param rhs A valid handle.
|
||||
* @return True if both handles refer to the same resource, false otherwise.
|
||||
*/
|
||||
template<typename Res, typename Other>
|
||||
[[nodiscard]] bool operator==(const resource<Res> &lhs, const resource<Other> &rhs) noexcept {
|
||||
template<typename Lhs, typename Rhs>
|
||||
[[nodiscard]] bool operator==(const resource<Lhs> &lhs, const resource<Rhs> &rhs) noexcept {
|
||||
return (std::addressof(*lhs) == std::addressof(*rhs));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compares two handles.
|
||||
* @tparam Res Type of resource managed by the first handle.
|
||||
* @tparam Other Type of resource managed by the second handle.
|
||||
* @tparam Lhs Type of resource managed by the first handle.
|
||||
* @tparam Rhs Type of resource managed by the second handle.
|
||||
* @param lhs A valid handle.
|
||||
* @param rhs A valid handle.
|
||||
* @return False if both handles refer to the same registry, true otherwise.
|
||||
* @return False if both handles refer to the same resource, true otherwise.
|
||||
*/
|
||||
template<typename Res, typename Other>
|
||||
[[nodiscard]] bool operator!=(const resource<Res> &lhs, const resource<Other> &rhs) noexcept {
|
||||
template<typename Lhs, typename Rhs>
|
||||
[[nodiscard]] bool operator!=(const resource<Lhs> &lhs, const resource<Rhs> &rhs) noexcept {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compares two handles.
|
||||
* @tparam Res Type of resource managed by the first handle.
|
||||
* @tparam Other Type of resource managed by the second handle.
|
||||
* @tparam Lhs Type of resource managed by the first handle.
|
||||
* @tparam Rhs Type of resource managed by the second handle.
|
||||
* @param lhs A valid handle.
|
||||
* @param rhs A valid handle.
|
||||
* @return True if the first handle is less than the second, false otherwise.
|
||||
*/
|
||||
template<typename Res, typename Other>
|
||||
[[nodiscard]] bool operator<(const resource<Res> &lhs, const resource<Other> &rhs) noexcept {
|
||||
template<typename Lhs, typename Rhs>
|
||||
[[nodiscard]] bool operator<(const resource<Lhs> &lhs, const resource<Rhs> &rhs) noexcept {
|
||||
return (std::addressof(*lhs) < std::addressof(*rhs));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compares two handles.
|
||||
* @tparam Res Type of resource managed by the first handle.
|
||||
* @tparam Other Type of resource managed by the second handle.
|
||||
* @tparam Lhs Type of resource managed by the first handle.
|
||||
* @tparam Rhs Type of resource managed by the second handle.
|
||||
* @param lhs A valid handle.
|
||||
* @param rhs A valid handle.
|
||||
* @return True if the first handle is greater than the second, false otherwise.
|
||||
*/
|
||||
template<typename Res, typename Other>
|
||||
[[nodiscard]] bool operator>(const resource<Res> &lhs, const resource<Other> &rhs) noexcept {
|
||||
return (std::addressof(*lhs) > std::addressof(*rhs));
|
||||
template<typename Lhs, typename Rhs>
|
||||
[[nodiscard]] bool operator>(const resource<Lhs> &lhs, const resource<Rhs> &rhs) noexcept {
|
||||
return rhs < lhs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compares two handles.
|
||||
* @tparam Res Type of resource managed by the first handle.
|
||||
* @tparam Other Type of resource managed by the second handle.
|
||||
* @tparam Lhs Type of resource managed by the first handle.
|
||||
* @tparam Rhs Type of resource managed by the second handle.
|
||||
* @param lhs A valid handle.
|
||||
* @param rhs A valid handle.
|
||||
* @return True if the first handle is less than or equal to the second, false
|
||||
* otherwise.
|
||||
*/
|
||||
template<typename Res, typename Other>
|
||||
[[nodiscard]] bool operator<=(const resource<Res> &lhs, const resource<Other> &rhs) noexcept {
|
||||
template<typename Lhs, typename Rhs>
|
||||
[[nodiscard]] bool operator<=(const resource<Lhs> &lhs, const resource<Rhs> &rhs) noexcept {
|
||||
return !(lhs > rhs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compares two handles.
|
||||
* @tparam Res Type of resource managed by the first handle.
|
||||
* @tparam Other Type of resource managed by the second handle.
|
||||
* @tparam Lhs Type of resource managed by the first handle.
|
||||
* @tparam Rhs Type of resource managed by the second handle.
|
||||
* @param lhs A valid handle.
|
||||
* @param rhs A valid handle.
|
||||
* @return True if the first handle is greater than or equal to the second,
|
||||
* false otherwise.
|
||||
*/
|
||||
template<typename Res, typename Other>
|
||||
[[nodiscard]] bool operator>=(const resource<Res> &lhs, const resource<Other> &rhs) noexcept {
|
||||
template<typename Lhs, typename Rhs>
|
||||
[[nodiscard]] bool operator>=(const resource<Lhs> &lhs, const resource<Rhs> &rhs) noexcept {
|
||||
return !(lhs < rhs);
|
||||
}
|
||||
|
||||
|
@ -76,7 +76,13 @@ class delegate<Ret(Args...)> {
|
||||
[[nodiscard]] auto wrap(std::index_sequence<Index...>) noexcept {
|
||||
return [](const void *, Args... args) -> Ret {
|
||||
[[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward<Args>(args)...);
|
||||
return static_cast<Ret>(std::invoke(Candidate, std::forward<type_list_element_t<Index, type_list<Args...>>>(std::get<Index>(arguments))...));
|
||||
|
||||
if constexpr(std::is_invocable_r_v<Ret, decltype(Candidate), type_list_element_t<Index, type_list<Args...>>...>) {
|
||||
return static_cast<Ret>(std::invoke(Candidate, std::forward<type_list_element_t<Index, type_list<Args...>>>(std::get<Index>(arguments))...));
|
||||
} else {
|
||||
constexpr auto offset = sizeof...(Args) - sizeof...(Index);
|
||||
return static_cast<Ret>(std::invoke(Candidate, std::forward<type_list_element_t<Index + offset, type_list<Args...>>>(std::get<Index + offset>(arguments))...));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -85,7 +91,13 @@ class delegate<Ret(Args...)> {
|
||||
return [](const void *payload, Args... args) -> Ret {
|
||||
[[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward<Args>(args)...);
|
||||
Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload));
|
||||
return static_cast<Ret>(std::invoke(Candidate, *curr, std::forward<type_list_element_t<Index, type_list<Args...>>>(std::get<Index>(arguments))...));
|
||||
|
||||
if constexpr(std::is_invocable_r_v<Ret, decltype(Candidate), Type &, type_list_element_t<Index, type_list<Args...>>...>) {
|
||||
return static_cast<Ret>(std::invoke(Candidate, *curr, std::forward<type_list_element_t<Index, type_list<Args...>>>(std::get<Index>(arguments))...));
|
||||
} else {
|
||||
constexpr auto offset = sizeof...(Args) - sizeof...(Index);
|
||||
return static_cast<Ret>(std::invoke(Candidate, *curr, std::forward<type_list_element_t<Index + offset, type_list<Args...>>>(std::get<Index + offset>(arguments))...));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -94,7 +106,13 @@ class delegate<Ret(Args...)> {
|
||||
return [](const void *payload, Args... args) -> Ret {
|
||||
[[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward<Args>(args)...);
|
||||
Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload));
|
||||
return static_cast<Ret>(std::invoke(Candidate, curr, std::forward<type_list_element_t<Index, type_list<Args...>>>(std::get<Index>(arguments))...));
|
||||
|
||||
if constexpr(std::is_invocable_r_v<Ret, decltype(Candidate), Type *, type_list_element_t<Index, type_list<Args...>>...>) {
|
||||
return static_cast<Ret>(std::invoke(Candidate, curr, std::forward<type_list_element_t<Index, type_list<Args...>>>(std::get<Index>(arguments))...));
|
||||
} else {
|
||||
constexpr auto offset = sizeof...(Args) - sizeof...(Index);
|
||||
return static_cast<Ret>(std::invoke(Candidate, curr, std::forward<type_list_element_t<Index + offset, type_list<Args...>>>(std::get<Index + offset>(arguments))...));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -233,6 +251,14 @@ public:
|
||||
fn = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a pointer to the stored callable function target, if any.
|
||||
* @return An opaque pointer to the stored callable function target.
|
||||
*/
|
||||
[[nodiscard]] function_type *target() const noexcept {
|
||||
return fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the instance or the payload linked to a delegate, if any.
|
||||
* @return An opaque pointer to the underlying data.
|
||||
|
@ -75,7 +75,7 @@ public:
|
||||
|
||||
template<typename... Args>
|
||||
void enqueue(Args &&...args) {
|
||||
if constexpr(std::is_aggregate_v<Type>) {
|
||||
if constexpr(std::is_aggregate_v<Type> && (sizeof...(Args) != 0u || !std::is_default_constructible_v<Type>)) {
|
||||
events.push_back(Type{std::forward<Args>(args)...});
|
||||
} else {
|
||||
events.emplace_back(std::forward<Args>(args)...);
|
||||
@ -179,7 +179,9 @@ public:
|
||||
* @param allocator The allocator to use.
|
||||
*/
|
||||
basic_dispatcher(basic_dispatcher &&other, const allocator_type &allocator) noexcept
|
||||
: pools{container_type{std::move(other.pools.first()), allocator}, allocator} {}
|
||||
: pools{container_type{std::move(other.pools.first()), allocator}, allocator} {
|
||||
ENTT_ASSERT(alloc_traits::is_always_equal::value || pools.second() == other.pools.second(), "Copying a dispatcher is not allowed");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move assignment operator.
|
||||
@ -187,6 +189,8 @@ public:
|
||||
* @return This dispatcher.
|
||||
*/
|
||||
basic_dispatcher &operator=(basic_dispatcher &&other) noexcept {
|
||||
ENTT_ASSERT(alloc_traits::is_always_equal::value || pools.second() == other.pools.second(), "Copying a dispatcher is not allowed");
|
||||
|
||||
pools = std::move(other.pools);
|
||||
return *this;
|
||||
}
|
||||
|
@ -76,7 +76,9 @@ public:
|
||||
* @param allocator The allocator to use.
|
||||
*/
|
||||
emitter(emitter &&other, const allocator_type &allocator) noexcept
|
||||
: handlers{container_type{std::move(other.handlers.first()), allocator}, allocator} {}
|
||||
: handlers{container_type{std::move(other.handlers.first()), allocator}, allocator} {
|
||||
ENTT_ASSERT(alloc_traits::is_always_equal::value || handlers.second() == other.handlers.second(), "Copying an emitter is not allowed");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move assignment operator.
|
||||
@ -84,6 +86,8 @@ public:
|
||||
* @return This dispatcher.
|
||||
*/
|
||||
emitter &operator=(emitter &&other) noexcept {
|
||||
ENTT_ASSERT(alloc_traits::is_always_equal::value || handlers.second() == other.handlers.second(), "Copying an emitter is not allowed");
|
||||
|
||||
handlers = std::move(other.handlers);
|
||||
return *this;
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
#ifndef ENTT_SIGNAL_SIGH_HPP
|
||||
#define ENTT_SIGNAL_SIGH_HPP
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@ -56,7 +56,8 @@ class sigh<Ret(Args...), Allocator> {
|
||||
friend class sink<sigh<Ret(Args...), Allocator>>;
|
||||
|
||||
using alloc_traits = std::allocator_traits<Allocator>;
|
||||
using container_type = std::vector<delegate<Ret(Args...)>, typename alloc_traits::template rebind_alloc<delegate<Ret(Args...)>>>;
|
||||
using delegate_type = delegate<Ret(Args...)>;
|
||||
using container_type = std::vector<delegate_type, typename alloc_traits::template rebind_alloc<delegate_type>>;
|
||||
|
||||
public:
|
||||
/*! @brief Allocator type. */
|
||||
@ -168,8 +169,8 @@ public:
|
||||
* @param args Arguments to use to invoke listeners.
|
||||
*/
|
||||
void publish(Args... args) const {
|
||||
for(auto &&call: std::as_const(calls)) {
|
||||
call(args...);
|
||||
for(auto pos = calls.size(); pos; --pos) {
|
||||
calls[pos - 1u](args...);
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,24 +190,24 @@ public:
|
||||
*/
|
||||
template<typename Func>
|
||||
void collect(Func func, Args... args) const {
|
||||
for(auto &&call: calls) {
|
||||
if constexpr(std::is_void_v<Ret>) {
|
||||
for(auto pos = calls.size(); pos; --pos) {
|
||||
if constexpr(std::is_void_v<Ret> || !std::is_invocable_v<Func, Ret>) {
|
||||
calls[pos - 1u](args...);
|
||||
|
||||
if constexpr(std::is_invocable_r_v<bool, Func>) {
|
||||
call(args...);
|
||||
if(func()) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
call(args...);
|
||||
func();
|
||||
}
|
||||
} else {
|
||||
if constexpr(std::is_invocable_r_v<bool, Func, Ret>) {
|
||||
if(func(call(args...))) {
|
||||
if(func(calls[pos - 1u](args...))) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
func(call(args...));
|
||||
func(calls[pos - 1u](args...));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -358,6 +359,7 @@ private:
|
||||
template<typename Ret, typename... Args, typename Allocator>
|
||||
class sink<sigh<Ret(Args...), Allocator>> {
|
||||
using signal_type = sigh<Ret(Args...), Allocator>;
|
||||
using delegate_type = typename signal_type::delegate_type;
|
||||
using difference_type = typename signal_type::container_type::difference_type;
|
||||
|
||||
template<auto Candidate, typename Type>
|
||||
@ -370,13 +372,14 @@ class sink<sigh<Ret(Args...), Allocator>> {
|
||||
sink{*static_cast<signal_type *>(signal)}.disconnect<Candidate>();
|
||||
}
|
||||
|
||||
auto before(delegate<Ret(Args...)> call) {
|
||||
const auto &calls = signal->calls;
|
||||
const auto it = std::find(calls.cbegin(), calls.cend(), std::move(call));
|
||||
|
||||
sink other{*this};
|
||||
other.offset = calls.cend() - it;
|
||||
return other;
|
||||
template<typename Func>
|
||||
void disconnect_if(Func callback) {
|
||||
for(auto pos = signal->calls.size(); pos; --pos) {
|
||||
if(auto &elem = signal->calls[pos - 1u]; callback(elem)) {
|
||||
elem = std::move(signal->calls.back());
|
||||
signal->calls.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
@ -385,8 +388,7 @@ public:
|
||||
* @param ref A valid reference to a signal object.
|
||||
*/
|
||||
sink(sigh<Ret(Args...), Allocator> &ref) noexcept
|
||||
: offset{},
|
||||
signal{&ref} {}
|
||||
: signal{&ref} {}
|
||||
|
||||
/**
|
||||
* @brief Returns false if at least a listener is connected to the sink.
|
||||
@ -396,89 +398,9 @@ public:
|
||||
return signal->calls.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a sink that connects before a given free function or an
|
||||
* unbound member.
|
||||
* @tparam Function A valid free function pointer.
|
||||
* @return A properly initialized sink object.
|
||||
*/
|
||||
template<auto Function>
|
||||
[[nodiscard]] sink before() {
|
||||
delegate<Ret(Args...)> call{};
|
||||
call.template connect<Function>();
|
||||
return before(std::move(call));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a sink that connects before a free function with payload
|
||||
* or a bound member.
|
||||
* @tparam Candidate Member or free function to look for.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
* @param value_or_instance A valid object that fits the purpose.
|
||||
* @return A properly initialized sink object.
|
||||
*/
|
||||
template<auto Candidate, typename Type>
|
||||
[[nodiscard]] sink before(Type &&value_or_instance) {
|
||||
delegate<Ret(Args...)> call{};
|
||||
call.template connect<Candidate>(value_or_instance);
|
||||
return before(std::move(call));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a sink that connects before a given instance or specific
|
||||
* payload.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
* @param value_or_instance A valid object that fits the purpose.
|
||||
* @return A properly initialized sink object.
|
||||
*/
|
||||
template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::decay_t<std::remove_pointer_t<Type>>, void>, sink>>
|
||||
[[nodiscard]] sink before(Type &value_or_instance) {
|
||||
return before(&value_or_instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a sink that connects before a given instance or specific
|
||||
* payload.
|
||||
* @param value_or_instance A valid pointer that fits the purpose.
|
||||
* @return A properly initialized sink object.
|
||||
*/
|
||||
[[nodiscard]] sink before(const void *value_or_instance) {
|
||||
sink other{*this};
|
||||
|
||||
if(value_or_instance) {
|
||||
const auto &calls = signal->calls;
|
||||
const auto it = std::find_if(calls.cbegin(), calls.cend(), [value_or_instance](const auto &delegate) {
|
||||
return delegate.data() == value_or_instance;
|
||||
});
|
||||
|
||||
other.offset = calls.cend() - it;
|
||||
}
|
||||
|
||||
return other;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a sink that connects before anything else.
|
||||
* @return A properly initialized sink object.
|
||||
*/
|
||||
[[nodiscard]] sink before() {
|
||||
sink other{*this};
|
||||
other.offset = signal->calls.size();
|
||||
return other;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function (with or without payload), a bound or an
|
||||
* unbound member to a signal.
|
||||
*
|
||||
* The signal isn't responsible for the connected object or the payload, if
|
||||
* any. Users must guarantee that the lifetime of the instance overcomes the
|
||||
* one of the signal. On the other side, the signal handler performs
|
||||
* checks to avoid multiple connections for the same function.<br/>
|
||||
* When used to connect a free function with payload, its signature must be
|
||||
* such that the instance is the first argument before the ones used to
|
||||
* define the signal itself.
|
||||
*
|
||||
* @tparam Candidate Function or member to connect to the signal.
|
||||
* @tparam Type Type of class or type of payload, if any.
|
||||
* @param value_or_instance A valid object that fits the purpose, if any.
|
||||
@ -488,9 +410,9 @@ public:
|
||||
connection connect(Type &&...value_or_instance) {
|
||||
disconnect<Candidate>(value_or_instance...);
|
||||
|
||||
delegate<Ret(Args...)> call{};
|
||||
delegate_type call{};
|
||||
call.template connect<Candidate>(value_or_instance...);
|
||||
signal->calls.insert(signal->calls.end() - offset, std::move(call));
|
||||
signal->calls.push_back(std::move(call));
|
||||
|
||||
delegate<void(void *)> conn{};
|
||||
conn.template connect<&release<Candidate, Type...>>(value_or_instance...);
|
||||
@ -506,21 +428,9 @@ public:
|
||||
*/
|
||||
template<auto Candidate, typename... Type>
|
||||
void disconnect(Type &&...value_or_instance) {
|
||||
auto &calls = signal->calls;
|
||||
delegate<Ret(Args...)> call{};
|
||||
delegate_type call{};
|
||||
call.template connect<Candidate>(value_or_instance...);
|
||||
calls.erase(std::remove(calls.begin(), calls.end(), std::move(call)), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects free functions with payload or bound members from a
|
||||
* signal.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
* @param value_or_instance A valid object that fits the purpose.
|
||||
*/
|
||||
template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::decay_t<std::remove_pointer_t<Type>>, void>>>
|
||||
void disconnect(Type &value_or_instance) {
|
||||
disconnect(&value_or_instance);
|
||||
disconnect_if([&call](const auto &elem) { return elem == call; });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -530,9 +440,7 @@ public:
|
||||
*/
|
||||
void disconnect(const void *value_or_instance) {
|
||||
if(value_or_instance) {
|
||||
auto &calls = signal->calls;
|
||||
auto predicate = [value_or_instance](const auto &delegate) { return delegate.data() == value_or_instance; };
|
||||
calls.erase(std::remove_if(calls.begin(), calls.end(), std::move(predicate)), calls.end());
|
||||
disconnect_if([value_or_instance](const auto &elem) { return elem.data() == value_or_instance; });
|
||||
}
|
||||
}
|
||||
|
||||
@ -542,7 +450,6 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
difference_type offset;
|
||||
signal_type *signal;
|
||||
};
|
||||
|
||||
|
@ -51,7 +51,20 @@ function(SETUP_TARGET TARGET_NAME)
|
||||
target_compile_options(
|
||||
${TARGET_NAME}
|
||||
PRIVATE
|
||||
/EHsc /W1 /wd4996 /w14800
|
||||
# vs2017 emits too many false positives for my tastes
|
||||
$<IF:$<EQUAL:${MSVC_TOOLSET_VERSION},141>, /W1, /W4>
|
||||
# clang-cl goes a little wrong with some warnings instead
|
||||
$<$<STREQUAL:"${CMAKE_CXX_COMPILER_ID}","Clang">:
|
||||
-Wno-deprecated-declarations
|
||||
-Wno-ignored-qualifiers
|
||||
-Wno-unknown-warning-option
|
||||
-Wno-exceptions
|
||||
-Wno-unused-local-typedef
|
||||
-Wno-unused-private-field
|
||||
>
|
||||
# documentation diagnostic turned on for clang-cl only
|
||||
$<$<STREQUAL:"${CMAKE_CXX_COMPILER_ID}","Clang">:-Wdocumentation>
|
||||
/EHsc /wd4324 /wd4996
|
||||
$<$<CONFIG:Debug>:/Od>
|
||||
$<$<CONFIG:Release>:/O2>
|
||||
)
|
||||
@ -94,21 +107,23 @@ function(SETUP_BASIC_TEST TEST_NAME TEST_SOURCES)
|
||||
add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME})
|
||||
endfunction()
|
||||
|
||||
function(SETUP_LIB_TEST TEST_NAME)
|
||||
add_library(_${TEST_NAME} SHARED $<TARGET_OBJECTS:odr> lib/${TEST_NAME}/lib.cpp)
|
||||
SETUP_TARGET(_${TEST_NAME} ENTT_API_EXPORT)
|
||||
SETUP_BASIC_TEST(lib_${TEST_NAME} lib/${TEST_NAME}/main.cpp ENTT_API_IMPORT)
|
||||
target_link_libraries(lib_${TEST_NAME} PRIVATE _${TEST_NAME})
|
||||
function(SETUP_LIB_SHARED_TEST TEST_NAME SUB_PATH)
|
||||
set(TARGET_NAME ${TEST_NAME}_${SUB_PATH})
|
||||
add_library(_${TARGET_NAME} SHARED $<TARGET_OBJECTS:odr> lib/${TEST_NAME}/${SUB_PATH}/lib.cpp)
|
||||
SETUP_TARGET(_${TARGET_NAME} ENTT_API_EXPORT)
|
||||
SETUP_BASIC_TEST(lib_${TARGET_NAME} lib/${TEST_NAME}/${SUB_PATH}/main.cpp ENTT_API_IMPORT)
|
||||
target_link_libraries(lib_${TARGET_NAME} PRIVATE _${TARGET_NAME})
|
||||
endfunction()
|
||||
|
||||
function(SETUP_PLUGIN_TEST TEST_NAME)
|
||||
add_library(_${TEST_NAME} MODULE $<TARGET_OBJECTS:odr> lib/${TEST_NAME}/plugin.cpp)
|
||||
SETUP_TARGET(_${TEST_NAME} ${ARGVN})
|
||||
SETUP_BASIC_TEST(lib_${TEST_NAME} lib/${TEST_NAME}/main.cpp PLUGIN="$<TARGET_FILE:_${TEST_NAME}>" ${ARGVN})
|
||||
target_include_directories(_${TEST_NAME} PRIVATE ${cr_INCLUDE_DIR})
|
||||
target_include_directories(lib_${TEST_NAME} PRIVATE ${cr_INCLUDE_DIR})
|
||||
target_link_libraries(lib_${TEST_NAME} PRIVATE ${CMAKE_DL_LIBS})
|
||||
add_dependencies(lib_${TEST_NAME} _${TEST_NAME})
|
||||
function(SETUP_LIB_PLUGIN_TEST TEST_NAME SUB_PATH)
|
||||
set(TARGET_NAME ${TEST_NAME}_${SUB_PATH})
|
||||
add_library(_${TARGET_NAME} MODULE $<TARGET_OBJECTS:odr> lib/${TEST_NAME}/${SUB_PATH}/plugin.cpp)
|
||||
SETUP_TARGET(_${TARGET_NAME} ${ARGVN})
|
||||
SETUP_BASIC_TEST(lib_${TARGET_NAME} lib/${TEST_NAME}/${SUB_PATH}/main.cpp PLUGIN="$<TARGET_FILE:_${TARGET_NAME}>" ${ARGVN})
|
||||
target_include_directories(_${TARGET_NAME} PRIVATE ${cr_INCLUDE_DIR})
|
||||
target_include_directories(lib_${TARGET_NAME} PRIVATE ${cr_INCLUDE_DIR})
|
||||
target_link_libraries(lib_${TARGET_NAME} PRIVATE ${CMAKE_DL_LIBS})
|
||||
add_dependencies(lib_${TARGET_NAME} _${TARGET_NAME})
|
||||
endfunction()
|
||||
|
||||
# Test benchmark
|
||||
@ -142,19 +157,21 @@ if(ENTT_BUILD_LIB)
|
||||
set(cr_INCLUDE_DIR ${cr_SOURCE_DIR})
|
||||
endif()
|
||||
|
||||
SETUP_LIB_TEST(dispatcher)
|
||||
SETUP_LIB_TEST(emitter)
|
||||
SETUP_LIB_TEST(locator)
|
||||
SETUP_LIB_TEST(meta)
|
||||
SETUP_LIB_TEST(registry)
|
||||
SETUP_LIB_SHARED_TEST(dispatcher shared)
|
||||
SETUP_LIB_PLUGIN_TEST(dispatcher plugin)
|
||||
|
||||
SETUP_PLUGIN_TEST(dispatcher_plugin)
|
||||
SETUP_PLUGIN_TEST(emitter_plugin)
|
||||
SETUP_PLUGIN_TEST(locator_plugin)
|
||||
SETUP_PLUGIN_TEST(meta_plugin)
|
||||
SETUP_PLUGIN_TEST(registry_plugin)
|
||||
SETUP_LIB_SHARED_TEST(emitter shared)
|
||||
SETUP_LIB_PLUGIN_TEST(emitter plugin)
|
||||
|
||||
SETUP_PLUGIN_TEST(meta_plugin_std ENTT_STANDARD_CPP)
|
||||
SETUP_LIB_SHARED_TEST(locator shared)
|
||||
SETUP_LIB_PLUGIN_TEST(locator plugin)
|
||||
|
||||
SETUP_LIB_SHARED_TEST(meta shared)
|
||||
SETUP_LIB_PLUGIN_TEST(meta plugin)
|
||||
SETUP_LIB_PLUGIN_TEST(meta plugin_std ENTT_STANDARD_CPP)
|
||||
|
||||
SETUP_LIB_SHARED_TEST(registry shared)
|
||||
SETUP_LIB_PLUGIN_TEST(registry plugin)
|
||||
endif()
|
||||
|
||||
# Test snapshot
|
||||
@ -215,10 +232,11 @@ SETUP_BASIC_TEST(observer entt/entity/observer.cpp)
|
||||
SETUP_BASIC_TEST(organizer entt/entity/organizer.cpp)
|
||||
SETUP_BASIC_TEST(registry entt/entity/registry.cpp)
|
||||
SETUP_BASIC_TEST(runtime_view entt/entity/runtime_view.cpp)
|
||||
SETUP_BASIC_TEST(sigh_mixin entt/entity/sigh_mixin.cpp)
|
||||
SETUP_BASIC_TEST(snapshot entt/entity/snapshot.cpp)
|
||||
SETUP_BASIC_TEST(sparse_set entt/entity/sparse_set.cpp)
|
||||
SETUP_BASIC_TEST(storage entt/entity/storage.cpp)
|
||||
SETUP_BASIC_TEST(storage_mixin entt/entity/storage_mixin.cpp)
|
||||
SETUP_BASIC_TEST(storage_entity entt/entity/storage_entity.cpp)
|
||||
SETUP_BASIC_TEST(view entt/entity/view.cpp)
|
||||
|
||||
# Test graph
|
||||
|
@ -19,7 +19,9 @@ struct stable_position: position {
|
||||
};
|
||||
|
||||
template<auto>
|
||||
struct comp { int x; };
|
||||
struct comp {
|
||||
int x;
|
||||
};
|
||||
|
||||
struct timer final {
|
||||
timer()
|
||||
@ -34,16 +36,24 @@ private:
|
||||
std::chrono::time_point<std::chrono::system_clock> start;
|
||||
};
|
||||
|
||||
template<typename Func, typename... Args>
|
||||
void generic_with(Func func) {
|
||||
timer timer;
|
||||
func();
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
template<typename Iterable, typename Func>
|
||||
void generic(Iterable &&iterable, Func func) {
|
||||
void iterate_with(Iterable &&iterable, Func func) {
|
||||
timer timer;
|
||||
std::forward<Iterable>(iterable).each(func);
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
template<typename Func>
|
||||
void pathological(Func func) {
|
||||
void pathological_with(Func func) {
|
||||
entt::registry registry;
|
||||
auto view = func(registry);
|
||||
|
||||
for(std::uint64_t i = 0; i < 500000L; i++) {
|
||||
const auto entity = registry.create();
|
||||
@ -54,10 +64,21 @@ void pathological(Func func) {
|
||||
|
||||
for(auto i = 0; i < 10; ++i) {
|
||||
registry.each([i = 0, ®istry](const auto entity) mutable {
|
||||
if(!(++i % 7)) { registry.remove<position>(entity); }
|
||||
if(!(++i % 11)) { registry.remove<velocity>(entity); }
|
||||
if(!(++i % 13)) { registry.remove<comp<0>>(entity); }
|
||||
if(!(++i % 17)) { registry.destroy(entity); }
|
||||
if(!(++i % 7)) {
|
||||
registry.remove<position>(entity);
|
||||
}
|
||||
|
||||
if(!(++i % 11)) {
|
||||
registry.remove<velocity>(entity);
|
||||
}
|
||||
|
||||
if(!(++i % 13)) {
|
||||
registry.remove<comp<0>>(entity);
|
||||
}
|
||||
|
||||
if(!(++i % 17)) {
|
||||
registry.destroy(entity);
|
||||
}
|
||||
});
|
||||
|
||||
for(std::uint64_t j = 0; j < 50000L; j++) {
|
||||
@ -69,7 +90,7 @@ void pathological(Func func) {
|
||||
}
|
||||
|
||||
timer timer;
|
||||
func(registry).each([](auto &...comp) { ((comp.x = {}), ...); });
|
||||
view.each([](auto &...comp) { ((comp.x = {}), ...); });
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
@ -78,13 +99,11 @@ TEST(Benchmark, Create) {
|
||||
|
||||
std::cout << "Creating 1000000 entities" << std::endl;
|
||||
|
||||
timer timer;
|
||||
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
static_cast<void>(registry.create());
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
generic_with([&]() {
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
static_cast<void>(registry.create());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, CreateMany) {
|
||||
@ -93,9 +112,9 @@ TEST(Benchmark, CreateMany) {
|
||||
|
||||
std::cout << "Creating 1000000 entities at once" << std::endl;
|
||||
|
||||
timer timer;
|
||||
registry.create(entities.begin(), entities.end());
|
||||
timer.elapsed();
|
||||
generic_with([&]() {
|
||||
registry.create(entities.begin(), entities.end());
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, CreateManyAndEmplaceComponents) {
|
||||
@ -104,15 +123,14 @@ TEST(Benchmark, CreateManyAndEmplaceComponents) {
|
||||
|
||||
std::cout << "Creating 1000000 entities at once and emplace components" << std::endl;
|
||||
|
||||
timer timer;
|
||||
registry.create(entities.begin(), entities.end());
|
||||
generic_with([&]() {
|
||||
registry.create(entities.begin(), entities.end());
|
||||
|
||||
for(const auto entity: entities) {
|
||||
registry.emplace<position>(entity);
|
||||
registry.emplace<velocity>(entity);
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
for(const auto entity: entities) {
|
||||
registry.emplace<position>(entity);
|
||||
registry.emplace<velocity>(entity);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, CreateManyWithComponents) {
|
||||
@ -121,79 +139,107 @@ TEST(Benchmark, CreateManyWithComponents) {
|
||||
|
||||
std::cout << "Creating 1000000 entities at once with components" << std::endl;
|
||||
|
||||
timer timer;
|
||||
registry.create(entities.begin(), entities.end());
|
||||
registry.insert<position>(entities.begin(), entities.end());
|
||||
registry.insert<velocity>(entities.begin(), entities.end());
|
||||
timer.elapsed();
|
||||
generic_with([&]() {
|
||||
registry.create(entities.begin(), entities.end());
|
||||
registry.insert<position>(entities.begin(), entities.end());
|
||||
registry.insert<velocity>(entities.begin(), entities.end());
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, Erase) {
|
||||
entt::registry registry;
|
||||
std::vector<entt::entity> entities(1000000);
|
||||
auto view = registry.view<int>();
|
||||
auto view = registry.view<position>();
|
||||
|
||||
std::cout << "Erasing 1000000 components from their entities" << std::endl;
|
||||
|
||||
registry.create(entities.begin(), entities.end());
|
||||
registry.insert<int>(entities.begin(), entities.end());
|
||||
registry.insert<position>(entities.begin(), entities.end());
|
||||
|
||||
timer timer;
|
||||
|
||||
for(auto entity: view) {
|
||||
registry.erase<int>(entity);
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
generic_with([&]() {
|
||||
for(auto entity: view) {
|
||||
registry.erase<position>(entity);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, EraseMany) {
|
||||
entt::registry registry;
|
||||
std::vector<entt::entity> entities(1000000);
|
||||
auto view = registry.view<int>();
|
||||
auto view = registry.view<position>();
|
||||
|
||||
std::cout << "Erasing 1000000 components from their entities at once" << std::endl;
|
||||
|
||||
registry.create(entities.begin(), entities.end());
|
||||
registry.insert<int>(entities.begin(), entities.end());
|
||||
registry.insert<position>(entities.begin(), entities.end());
|
||||
|
||||
timer timer;
|
||||
registry.erase<int>(view.begin(), view.end());
|
||||
timer.elapsed();
|
||||
generic_with([&]() {
|
||||
registry.erase<position>(view.begin(), view.end());
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, EraseManyMulti) {
|
||||
entt::registry registry;
|
||||
std::vector<entt::entity> entities(1000000);
|
||||
auto view = registry.view<position>();
|
||||
|
||||
std::cout << "Erasing 1000000 components per type from their entities at once" << std::endl;
|
||||
|
||||
registry.create(entities.begin(), entities.end());
|
||||
registry.insert<position>(entities.begin(), entities.end());
|
||||
registry.insert<velocity>(entities.begin(), entities.end());
|
||||
|
||||
generic_with([&]() {
|
||||
registry.erase<position, velocity>(view.begin(), view.end());
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, Remove) {
|
||||
entt::registry registry;
|
||||
std::vector<entt::entity> entities(1000000);
|
||||
auto view = registry.view<int>();
|
||||
auto view = registry.view<position>();
|
||||
|
||||
std::cout << "Removing 1000000 components from their entities" << std::endl;
|
||||
|
||||
registry.create(entities.begin(), entities.end());
|
||||
registry.insert<int>(entities.begin(), entities.end());
|
||||
registry.insert<position>(entities.begin(), entities.end());
|
||||
|
||||
timer timer;
|
||||
|
||||
for(auto entity: view) {
|
||||
registry.remove<int>(entity);
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
generic_with([&]() {
|
||||
for(auto entity: view) {
|
||||
registry.remove<position>(entity);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, RemoveMany) {
|
||||
entt::registry registry;
|
||||
std::vector<entt::entity> entities(1000000);
|
||||
auto view = registry.view<int>();
|
||||
auto view = registry.view<position>();
|
||||
|
||||
std::cout << "Removing 1000000 components from their entities at once" << std::endl;
|
||||
|
||||
registry.create(entities.begin(), entities.end());
|
||||
registry.insert<int>(entities.begin(), entities.end());
|
||||
registry.insert<position>(entities.begin(), entities.end());
|
||||
|
||||
timer timer;
|
||||
registry.remove<int>(view.begin(), view.end());
|
||||
timer.elapsed();
|
||||
generic_with([&]() {
|
||||
registry.remove<position>(view.begin(), view.end());
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, RemoveManyMulti) {
|
||||
entt::registry registry;
|
||||
std::vector<entt::entity> entities(1000000);
|
||||
auto view = registry.view<position>();
|
||||
|
||||
std::cout << "Removing 1000000 components per type from their entities at once" << std::endl;
|
||||
|
||||
registry.create(entities.begin(), entities.end());
|
||||
registry.insert<position>(entities.begin(), entities.end());
|
||||
registry.insert<velocity>(entities.begin(), entities.end());
|
||||
|
||||
generic_with([&]() {
|
||||
registry.remove<position, velocity>(view.begin(), view.end());
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, Clear) {
|
||||
@ -203,11 +249,40 @@ TEST(Benchmark, Clear) {
|
||||
std::cout << "Clearing 1000000 components from their entities" << std::endl;
|
||||
|
||||
registry.create(entities.begin(), entities.end());
|
||||
registry.insert<int>(entities.begin(), entities.end());
|
||||
registry.insert<position>(entities.begin(), entities.end());
|
||||
|
||||
timer timer;
|
||||
registry.clear<int>();
|
||||
timer.elapsed();
|
||||
generic_with([&]() {
|
||||
registry.clear<position>();
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, ClearMulti) {
|
||||
entt::registry registry;
|
||||
std::vector<entt::entity> entities(1000000);
|
||||
|
||||
std::cout << "Clearing 1000000 components per type from their entities" << std::endl;
|
||||
|
||||
registry.create(entities.begin(), entities.end());
|
||||
registry.insert<position>(entities.begin(), entities.end());
|
||||
registry.insert<velocity>(entities.begin(), entities.end());
|
||||
|
||||
generic_with([&]() {
|
||||
registry.clear<position, velocity>();
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, ClearStable) {
|
||||
entt::registry registry;
|
||||
std::vector<entt::entity> entities(1000000);
|
||||
|
||||
std::cout << "Clearing 1000000 stable components from their entities" << std::endl;
|
||||
|
||||
registry.create(entities.begin(), entities.end());
|
||||
registry.insert<stable_position>(entities.begin(), entities.end());
|
||||
|
||||
generic_with([&]() {
|
||||
registry.clear<stable_position>();
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, Recycle) {
|
||||
@ -217,18 +292,13 @@ TEST(Benchmark, Recycle) {
|
||||
std::cout << "Recycling 1000000 entities" << std::endl;
|
||||
|
||||
registry.create(entities.begin(), entities.end());
|
||||
registry.destroy(entities.begin(), entities.end());
|
||||
|
||||
registry.each([®istry](auto entity) {
|
||||
registry.destroy(entity);
|
||||
generic_with([&]() {
|
||||
for(auto next = entities.size(); next; --next) {
|
||||
entities[next] = registry.create();
|
||||
}
|
||||
});
|
||||
|
||||
timer timer;
|
||||
|
||||
for(auto next = entities.size(); next; --next) {
|
||||
static_cast<void>(registry.create());
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, RecycleMany) {
|
||||
@ -238,62 +308,121 @@ TEST(Benchmark, RecycleMany) {
|
||||
std::cout << "Recycling 1000000 entities" << std::endl;
|
||||
|
||||
registry.create(entities.begin(), entities.end());
|
||||
registry.destroy(entities.begin(), entities.end());
|
||||
|
||||
registry.each([®istry](auto entity) {
|
||||
registry.destroy(entity);
|
||||
generic_with([&]() {
|
||||
registry.create(entities.begin(), entities.end());
|
||||
});
|
||||
|
||||
timer timer;
|
||||
registry.create(entities.begin(), entities.end());
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, Destroy) {
|
||||
entt::registry registry;
|
||||
std::vector<entt::entity> entities(1000000);
|
||||
auto view = registry.view<int>();
|
||||
auto view = registry.view<position>();
|
||||
|
||||
std::cout << "Destroying 1000000 entities" << std::endl;
|
||||
|
||||
registry.create(entities.begin(), entities.end());
|
||||
registry.insert<int>(entities.begin(), entities.end());
|
||||
registry.insert<position>(entities.begin(), entities.end());
|
||||
|
||||
timer timer;
|
||||
|
||||
for(auto entity: view) {
|
||||
registry.destroy(entity);
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
generic_with([&]() {
|
||||
for(auto entity: view) {
|
||||
registry.destroy(entity);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, DestroyMany) {
|
||||
entt::registry registry;
|
||||
std::vector<entt::entity> entities(1000000);
|
||||
auto view = registry.view<int>();
|
||||
auto view = registry.view<position>();
|
||||
|
||||
std::cout << "Destroying 1000000 entities at once" << std::endl;
|
||||
|
||||
registry.create(entities.begin(), entities.end());
|
||||
registry.insert<int>(entities.begin(), entities.end());
|
||||
registry.insert<position>(entities.begin(), entities.end());
|
||||
|
||||
timer timer;
|
||||
registry.destroy(view.begin(), view.end());
|
||||
timer.elapsed();
|
||||
generic_with([&]() {
|
||||
registry.destroy(view.begin(), view.end());
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, DestroyManyFastPath) {
|
||||
TEST(Benchmark, DestroyManyMulti) {
|
||||
entt::registry registry;
|
||||
std::vector<entt::entity> entities(1000000);
|
||||
auto view = registry.view<position>();
|
||||
|
||||
std::cout << "Destroying 1000000 entities at once, multiple components" << std::endl;
|
||||
|
||||
registry.create(entities.begin(), entities.end());
|
||||
registry.insert<position>(entities.begin(), entities.end());
|
||||
registry.insert<velocity>(entities.begin(), entities.end());
|
||||
|
||||
generic_with([&]() {
|
||||
registry.destroy(view.begin(), view.end());
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, GetFromRegistry) {
|
||||
entt::registry registry;
|
||||
std::vector<entt::entity> entities(1000000);
|
||||
|
||||
std::cout << "Destroying 1000000 entities at once, fast path" << std::endl;
|
||||
registry.create(entities.begin(), entities.end());
|
||||
registry.insert<position>(entities.begin(), entities.end());
|
||||
|
||||
generic_with([&]() {
|
||||
for(auto entity: entities) {
|
||||
registry.get<position>(entity).x = 0u;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, GetFromRegistryMulti) {
|
||||
entt::registry registry;
|
||||
std::vector<entt::entity> entities(1000000);
|
||||
|
||||
registry.create(entities.begin(), entities.end());
|
||||
registry.insert<int>(entities.begin(), entities.end());
|
||||
registry.insert<position>(entities.begin(), entities.end());
|
||||
registry.insert<velocity>(entities.begin(), entities.end());
|
||||
|
||||
timer timer;
|
||||
registry.destroy(entities.begin(), entities.end());
|
||||
timer.elapsed();
|
||||
generic_with([&]() {
|
||||
for(auto entity: entities) {
|
||||
registry.get<position>(entity).x = 0u;
|
||||
registry.get<velocity>(entity).y = 0u;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, GetFromView) {
|
||||
entt::registry registry;
|
||||
std::vector<entt::entity> entities(1000000);
|
||||
auto view = registry.view<position>();
|
||||
|
||||
registry.create(entities.begin(), entities.end());
|
||||
registry.insert<position>(entities.begin(), entities.end());
|
||||
|
||||
generic_with([&]() {
|
||||
for(auto entity: entities) {
|
||||
view.get<position>(entity).x = 0u;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, GetFromViewMulti) {
|
||||
entt::registry registry;
|
||||
std::vector<entt::entity> entities(1000000);
|
||||
auto view = registry.view<position, velocity>();
|
||||
|
||||
registry.create(entities.begin(), entities.end());
|
||||
registry.insert<position>(entities.begin(), entities.end());
|
||||
registry.insert<velocity>(entities.begin(), entities.end());
|
||||
|
||||
generic_with([&]() {
|
||||
for(auto entity: entities) {
|
||||
view.get<position>(entity).x = 0u;
|
||||
view.get<velocity>(entity).y = 0u;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateSingleComponent1M) {
|
||||
@ -306,22 +435,22 @@ TEST(Benchmark, IterateSingleComponent1M) {
|
||||
registry.emplace<position>(entity);
|
||||
}
|
||||
|
||||
generic(registry.view<position>(), [](auto &...comp) {
|
||||
iterate_with(registry.view<position>(), [](auto &...comp) {
|
||||
((comp.x = {}), ...);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateSingleComponentTombstonePolicy1M) {
|
||||
TEST(Benchmark, IterateSingleStableComponent1M) {
|
||||
entt::registry registry;
|
||||
|
||||
std::cout << "Iterating over 1000000 entities, one component, tombstone policy" << std::endl;
|
||||
std::cout << "Iterating over 1000000 entities, one stable component" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
const auto entity = registry.create();
|
||||
registry.emplace<stable_position>(entity);
|
||||
}
|
||||
|
||||
generic(registry.view<stable_position>(), [](auto &...comp) {
|
||||
iterate_with(registry.view<stable_position>(), [](auto &...comp) {
|
||||
((comp.x = {}), ...);
|
||||
});
|
||||
}
|
||||
@ -339,7 +468,7 @@ TEST(Benchmark, IterateSingleComponentRuntime1M) {
|
||||
entt::runtime_view view{};
|
||||
view.iterate(registry.storage<position>());
|
||||
|
||||
generic(view, [®istry](auto entity) {
|
||||
iterate_with(view, [&](auto entity) {
|
||||
registry.get<position>(entity).x = {};
|
||||
});
|
||||
}
|
||||
@ -355,15 +484,15 @@ TEST(Benchmark, IterateTwoComponents1M) {
|
||||
registry.emplace<velocity>(entity);
|
||||
}
|
||||
|
||||
generic(registry.view<position, velocity>(), [](auto &...comp) {
|
||||
iterate_with(registry.view<position, velocity>(), [](auto &...comp) {
|
||||
((comp.x = {}), ...);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTombstonePolicyTwoComponentsTombstonePolicy1M) {
|
||||
TEST(Benchmark, IterateTwoStableComponents1M) {
|
||||
entt::registry registry;
|
||||
|
||||
std::cout << "Iterating over 1000000 entities, two components, tombstone policy" << std::endl;
|
||||
std::cout << "Iterating over 1000000 entities, two stable components" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
const auto entity = registry.create();
|
||||
@ -371,7 +500,7 @@ TEST(Benchmark, IterateTombstonePolicyTwoComponentsTombstonePolicy1M) {
|
||||
registry.emplace<velocity>(entity);
|
||||
}
|
||||
|
||||
generic(registry.view<stable_position, velocity>(), [](auto &...comp) {
|
||||
iterate_with(registry.view<stable_position, velocity>(), [](auto &...comp) {
|
||||
((comp.x = {}), ...);
|
||||
});
|
||||
}
|
||||
@ -390,7 +519,7 @@ TEST(Benchmark, IterateTwoComponents1MHalf) {
|
||||
}
|
||||
}
|
||||
|
||||
generic(registry.view<position, velocity>(), [](auto &...comp) {
|
||||
iterate_with(registry.view<position, velocity>(), [](auto &...comp) {
|
||||
((comp.x = {}), ...);
|
||||
});
|
||||
}
|
||||
@ -409,7 +538,7 @@ TEST(Benchmark, IterateTwoComponents1MOne) {
|
||||
}
|
||||
}
|
||||
|
||||
generic(registry.view<position, velocity>(), [](auto &...comp) {
|
||||
iterate_with(registry.view<position, velocity>(), [](auto &...comp) {
|
||||
((comp.x = {}), ...);
|
||||
});
|
||||
}
|
||||
@ -425,7 +554,7 @@ TEST(Benchmark, IterateTwoComponentsNonOwningGroup1M) {
|
||||
registry.emplace<velocity>(entity);
|
||||
}
|
||||
|
||||
generic(registry.group<>(entt::get<position, velocity>), [](auto &...comp) {
|
||||
iterate_with(registry.group<>(entt::get<position, velocity>), [](auto &...comp) {
|
||||
((comp.x = {}), ...);
|
||||
});
|
||||
}
|
||||
@ -441,7 +570,7 @@ TEST(Benchmark, IterateTwoComponentsFullOwningGroup1M) {
|
||||
registry.emplace<velocity>(entity);
|
||||
}
|
||||
|
||||
generic(registry.group<position, velocity>(), [](auto &...comp) {
|
||||
iterate_with(registry.group<position, velocity>(), [](auto &...comp) {
|
||||
((comp.x = {}), ...);
|
||||
});
|
||||
}
|
||||
@ -457,7 +586,7 @@ TEST(Benchmark, IterateTwoComponentsPartialOwningGroup1M) {
|
||||
registry.emplace<velocity>(entity);
|
||||
}
|
||||
|
||||
generic(registry.group<position>(entt::get<velocity>), [](auto &...comp) {
|
||||
iterate_with(registry.group<position>(entt::get<velocity>), [](auto &...comp) {
|
||||
((comp.x = {}), ...);
|
||||
});
|
||||
}
|
||||
@ -477,7 +606,7 @@ TEST(Benchmark, IterateTwoComponentsRuntime1M) {
|
||||
view.iterate(registry.storage<position>())
|
||||
.iterate(registry.storage<velocity>());
|
||||
|
||||
generic(view, [®istry](auto entity) {
|
||||
iterate_with(view, [&](auto entity) {
|
||||
registry.get<position>(entity).x = {};
|
||||
registry.get<velocity>(entity).x = {};
|
||||
});
|
||||
@ -501,7 +630,7 @@ TEST(Benchmark, IterateTwoComponentsRuntime1MHalf) {
|
||||
view.iterate(registry.storage<position>())
|
||||
.iterate(registry.storage<velocity>());
|
||||
|
||||
generic(view, [®istry](auto entity) {
|
||||
iterate_with(view, [&](auto entity) {
|
||||
registry.get<position>(entity).x = {};
|
||||
registry.get<velocity>(entity).x = {};
|
||||
});
|
||||
@ -525,7 +654,7 @@ TEST(Benchmark, IterateTwoComponentsRuntime1MOne) {
|
||||
view.iterate(registry.storage<position>())
|
||||
.iterate(registry.storage<velocity>());
|
||||
|
||||
generic(view, [®istry](auto entity) {
|
||||
iterate_with(view, [&](auto entity) {
|
||||
registry.get<position>(entity).x = {};
|
||||
registry.get<velocity>(entity).x = {};
|
||||
});
|
||||
@ -543,15 +672,15 @@ TEST(Benchmark, IterateThreeComponents1M) {
|
||||
registry.emplace<comp<0>>(entity);
|
||||
}
|
||||
|
||||
generic(registry.view<position, velocity, comp<0>>(), [](auto &...comp) {
|
||||
iterate_with(registry.view<position, velocity, comp<0>>(), [](auto &...comp) {
|
||||
((comp.x = {}), ...);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateThreeComponentsTombstonePolicy1M) {
|
||||
TEST(Benchmark, IterateThreeStableComponents1M) {
|
||||
entt::registry registry;
|
||||
|
||||
std::cout << "Iterating over 1000000 entities, three components, tombstone policy" << std::endl;
|
||||
std::cout << "Iterating over 1000000 entities, three stable components" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
const auto entity = registry.create();
|
||||
@ -560,7 +689,7 @@ TEST(Benchmark, IterateThreeComponentsTombstonePolicy1M) {
|
||||
registry.emplace<comp<0>>(entity);
|
||||
}
|
||||
|
||||
generic(registry.view<stable_position, velocity, comp<0>>(), [](auto &...comp) {
|
||||
iterate_with(registry.view<stable_position, velocity, comp<0>>(), [](auto &...comp) {
|
||||
((comp.x = {}), ...);
|
||||
});
|
||||
}
|
||||
@ -580,7 +709,7 @@ TEST(Benchmark, IterateThreeComponents1MHalf) {
|
||||
}
|
||||
}
|
||||
|
||||
generic(registry.view<position, velocity, comp<0>>(), [](auto &...comp) {
|
||||
iterate_with(registry.view<position, velocity, comp<0>>(), [](auto &...comp) {
|
||||
((comp.x = {}), ...);
|
||||
});
|
||||
}
|
||||
@ -600,7 +729,7 @@ TEST(Benchmark, IterateThreeComponents1MOne) {
|
||||
}
|
||||
}
|
||||
|
||||
generic(registry.view<position, velocity, comp<0>>(), [](auto &...comp) {
|
||||
iterate_with(registry.view<position, velocity, comp<0>>(), [](auto &...comp) {
|
||||
((comp.x = {}), ...);
|
||||
});
|
||||
}
|
||||
@ -617,7 +746,7 @@ TEST(Benchmark, IterateThreeComponentsNonOwningGroup1M) {
|
||||
registry.emplace<comp<0>>(entity);
|
||||
}
|
||||
|
||||
generic(registry.group<>(entt::get<position, velocity, comp<0>>), [](auto &...comp) {
|
||||
iterate_with(registry.group<>(entt::get<position, velocity, comp<0>>), [](auto &...comp) {
|
||||
((comp.x = {}), ...);
|
||||
});
|
||||
}
|
||||
@ -634,7 +763,7 @@ TEST(Benchmark, IterateThreeComponentsFullOwningGroup1M) {
|
||||
registry.emplace<comp<0>>(entity);
|
||||
}
|
||||
|
||||
generic(registry.group<position, velocity, comp<0>>(), [](auto &...comp) {
|
||||
iterate_with(registry.group<position, velocity, comp<0>>(), [](auto &...comp) {
|
||||
((comp.x = {}), ...);
|
||||
});
|
||||
}
|
||||
@ -651,7 +780,7 @@ TEST(Benchmark, IterateThreeComponentsPartialOwningGroup1M) {
|
||||
registry.emplace<comp<0>>(entity);
|
||||
}
|
||||
|
||||
generic(registry.group<position, velocity>(entt::get<comp<0>>), [](auto &...comp) {
|
||||
iterate_with(registry.group<position, velocity>(entt::get<comp<0>>), [](auto &...comp) {
|
||||
((comp.x = {}), ...);
|
||||
});
|
||||
}
|
||||
@ -673,7 +802,7 @@ TEST(Benchmark, IterateThreeComponentsRuntime1M) {
|
||||
.iterate(registry.storage<velocity>())
|
||||
.iterate(registry.storage<comp<0>>());
|
||||
|
||||
generic(view, [®istry](auto entity) {
|
||||
iterate_with(view, [&](auto entity) {
|
||||
registry.get<position>(entity).x = {};
|
||||
registry.get<velocity>(entity).x = {};
|
||||
registry.get<comp<0>>(entity).x = {};
|
||||
@ -700,7 +829,7 @@ TEST(Benchmark, IterateThreeComponentsRuntime1MHalf) {
|
||||
.iterate(registry.storage<velocity>())
|
||||
.iterate(registry.storage<comp<0>>());
|
||||
|
||||
generic(view, [®istry](auto entity) {
|
||||
iterate_with(view, [&](auto entity) {
|
||||
registry.get<position>(entity).x = {};
|
||||
registry.get<velocity>(entity).x = {};
|
||||
registry.get<comp<0>>(entity).x = {};
|
||||
@ -727,7 +856,7 @@ TEST(Benchmark, IterateThreeComponentsRuntime1MOne) {
|
||||
.iterate(registry.storage<velocity>())
|
||||
.iterate(registry.storage<comp<0>>());
|
||||
|
||||
generic(view, [®istry](auto entity) {
|
||||
iterate_with(view, [&](auto entity) {
|
||||
registry.get<position>(entity).x = {};
|
||||
registry.get<velocity>(entity).x = {};
|
||||
registry.get<comp<0>>(entity).x = {};
|
||||
@ -748,15 +877,15 @@ TEST(Benchmark, IterateFiveComponents1M) {
|
||||
registry.emplace<comp<2>>(entity);
|
||||
}
|
||||
|
||||
generic(registry.view<position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) {
|
||||
iterate_with(registry.view<position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) {
|
||||
((comp.x = {}), ...);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateFiveComponentsTombstonePolicy1M) {
|
||||
TEST(Benchmark, IterateFiveStableComponents1M) {
|
||||
entt::registry registry;
|
||||
|
||||
std::cout << "Iterating over 1000000 entities, five components, tombstone policy" << std::endl;
|
||||
std::cout << "Iterating over 1000000 entities, five stable components" << std::endl;
|
||||
|
||||
for(std::uint64_t i = 0; i < 1000000L; i++) {
|
||||
const auto entity = registry.create();
|
||||
@ -767,7 +896,7 @@ TEST(Benchmark, IterateFiveComponentsTombstonePolicy1M) {
|
||||
registry.emplace<comp<2>>(entity);
|
||||
}
|
||||
|
||||
generic(registry.view<stable_position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) {
|
||||
iterate_with(registry.view<stable_position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) {
|
||||
((comp.x = {}), ...);
|
||||
});
|
||||
}
|
||||
@ -789,7 +918,7 @@ TEST(Benchmark, IterateFiveComponents1MHalf) {
|
||||
}
|
||||
}
|
||||
|
||||
generic(registry.view<position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) {
|
||||
iterate_with(registry.view<position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) {
|
||||
((comp.x = {}), ...);
|
||||
});
|
||||
}
|
||||
@ -811,7 +940,7 @@ TEST(Benchmark, IterateFiveComponents1MOne) {
|
||||
}
|
||||
}
|
||||
|
||||
generic(registry.view<position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) {
|
||||
iterate_with(registry.view<position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) {
|
||||
((comp.x = {}), ...);
|
||||
});
|
||||
}
|
||||
@ -830,7 +959,7 @@ TEST(Benchmark, IterateFiveComponentsNonOwningGroup1M) {
|
||||
registry.emplace<comp<2>>(entity);
|
||||
}
|
||||
|
||||
generic(registry.group<>(entt::get<position, velocity, comp<0>, comp<1>, comp<2>>), [](auto &...comp) {
|
||||
iterate_with(registry.group<>(entt::get<position, velocity, comp<0>, comp<1>, comp<2>>), [](auto &...comp) {
|
||||
((comp.x = {}), ...);
|
||||
});
|
||||
}
|
||||
@ -849,7 +978,7 @@ TEST(Benchmark, IterateFiveComponentsFullOwningGroup1M) {
|
||||
registry.emplace<comp<2>>(entity);
|
||||
}
|
||||
|
||||
generic(registry.group<position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) {
|
||||
iterate_with(registry.group<position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) {
|
||||
((comp.x = {}), ...);
|
||||
});
|
||||
}
|
||||
@ -868,7 +997,7 @@ TEST(Benchmark, IterateFiveComponentsPartialFourOfFiveOwningGroup1M) {
|
||||
registry.emplace<comp<2>>(entity);
|
||||
}
|
||||
|
||||
generic(registry.group<position, velocity, comp<0>, comp<1>>(entt::get<comp<2>>), [](auto &...comp) {
|
||||
iterate_with(registry.group<position, velocity, comp<0>, comp<1>>(entt::get<comp<2>>), [](auto &...comp) {
|
||||
((comp.x = {}), ...);
|
||||
});
|
||||
}
|
||||
@ -887,7 +1016,7 @@ TEST(Benchmark, IterateFiveComponentsPartialThreeOfFiveOwningGroup1M) {
|
||||
registry.emplace<comp<2>>(entity);
|
||||
}
|
||||
|
||||
generic(registry.group<position, velocity, comp<0>>(entt::get<comp<1>, comp<2>>), [](auto &...comp) {
|
||||
iterate_with(registry.group<position, velocity, comp<0>>(entt::get<comp<1>, comp<2>>), [](auto &...comp) {
|
||||
((comp.x = {}), ...);
|
||||
});
|
||||
}
|
||||
@ -913,7 +1042,7 @@ TEST(Benchmark, IterateFiveComponentsRuntime1M) {
|
||||
.iterate(registry.storage<comp<1>>())
|
||||
.iterate(registry.storage<comp<2>>());
|
||||
|
||||
generic(view, [®istry](auto entity) {
|
||||
iterate_with(view, [&](auto entity) {
|
||||
registry.get<position>(entity).x = {};
|
||||
registry.get<velocity>(entity).x = {};
|
||||
registry.get<comp<0>>(entity).x = {};
|
||||
@ -946,7 +1075,7 @@ TEST(Benchmark, IterateFiveComponentsRuntime1MHalf) {
|
||||
.iterate(registry.storage<comp<1>>())
|
||||
.iterate(registry.storage<comp<2>>());
|
||||
|
||||
generic(view, [®istry](auto entity) {
|
||||
iterate_with(view, [&](auto entity) {
|
||||
registry.get<position>(entity).x = {};
|
||||
registry.get<velocity>(entity).x = {};
|
||||
registry.get<comp<0>>(entity).x = {};
|
||||
@ -979,7 +1108,7 @@ TEST(Benchmark, IterateFiveComponentsRuntime1MOne) {
|
||||
.iterate(registry.storage<comp<1>>())
|
||||
.iterate(registry.storage<comp<2>>());
|
||||
|
||||
generic(view, [®istry](auto entity) {
|
||||
iterate_with(view, [&](auto entity) {
|
||||
registry.get<position>(entity).x = {};
|
||||
registry.get<velocity>(entity).x = {};
|
||||
registry.get<comp<0>>(entity).x = {};
|
||||
@ -990,22 +1119,22 @@ TEST(Benchmark, IterateFiveComponentsRuntime1MOne) {
|
||||
|
||||
TEST(Benchmark, IteratePathological) {
|
||||
std::cout << "Pathological case" << std::endl;
|
||||
pathological([](auto ®istry) { return registry.template view<position, velocity, comp<0>>(); });
|
||||
pathological_with([](auto ®istry) { return registry.template view<position, velocity, comp<0>>(); });
|
||||
}
|
||||
|
||||
TEST(Benchmark, IteratePathologicalNonOwningGroup) {
|
||||
std::cout << "Pathological case (non-owning group)" << std::endl;
|
||||
pathological([](auto ®istry) { return registry.template group<>(entt::get<position, velocity, comp<0>>); });
|
||||
pathological_with([](auto ®istry) { return registry.template group<>(entt::get<position, velocity, comp<0>>); });
|
||||
}
|
||||
|
||||
TEST(Benchmark, IteratePathologicalFullOwningGroup) {
|
||||
std::cout << "Pathological case (full-owning group)" << std::endl;
|
||||
pathological([](auto ®istry) { return registry.template group<position, velocity, comp<0>>(); });
|
||||
pathological_with([](auto ®istry) { return registry.template group<position, velocity, comp<0>>(); });
|
||||
}
|
||||
|
||||
TEST(Benchmark, IteratePathologicalPartialOwningGroup) {
|
||||
std::cout << "Pathological case (partial-owning group)" << std::endl;
|
||||
pathological([](auto ®istry) { return registry.template group<position, velocity>(entt::get<comp<0>>); });
|
||||
pathological_with([](auto ®istry) { return registry.template group<position, velocity>(entt::get<comp<0>>); });
|
||||
}
|
||||
|
||||
TEST(Benchmark, SortSingle) {
|
||||
@ -1018,9 +1147,9 @@ TEST(Benchmark, SortSingle) {
|
||||
registry.emplace<position>(entity, i, i);
|
||||
}
|
||||
|
||||
timer timer;
|
||||
registry.sort<position>([](const auto &lhs, const auto &rhs) { return lhs.x < rhs.x && lhs.y < rhs.y; });
|
||||
timer.elapsed();
|
||||
generic_with([&]() {
|
||||
registry.sort<position>([](const auto &lhs, const auto &rhs) { return lhs.x < rhs.x && lhs.y < rhs.y; });
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, SortMulti) {
|
||||
@ -1036,9 +1165,9 @@ TEST(Benchmark, SortMulti) {
|
||||
|
||||
registry.sort<position>([](const auto &lhs, const auto &rhs) { return lhs.x < rhs.x && lhs.y < rhs.y; });
|
||||
|
||||
timer timer;
|
||||
registry.sort<velocity, position>();
|
||||
timer.elapsed();
|
||||
generic_with([&]() {
|
||||
registry.sort<velocity, position>();
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, AlmostSortedStdSort) {
|
||||
@ -1062,9 +1191,9 @@ TEST(Benchmark, AlmostSortedStdSort) {
|
||||
registry.emplace<position>(entity, 50000 * i, 50000 * i);
|
||||
}
|
||||
|
||||
timer timer;
|
||||
registry.sort<position>([](const auto &lhs, const auto &rhs) { return lhs.x > rhs.x && lhs.y > rhs.y; });
|
||||
timer.elapsed();
|
||||
generic_with([&]() {
|
||||
registry.sort<position>([](const auto &lhs, const auto &rhs) { return lhs.x > rhs.x && lhs.y > rhs.y; });
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Benchmark, AlmostSortedInsertionSort) {
|
||||
@ -1088,7 +1217,7 @@ TEST(Benchmark, AlmostSortedInsertionSort) {
|
||||
registry.emplace<position>(entity, 50000 * i, 50000 * i);
|
||||
}
|
||||
|
||||
timer timer;
|
||||
registry.sort<position>([](const auto &lhs, const auto &rhs) { return lhs.x > rhs.x && lhs.y > rhs.y; }, entt::insertion_sort{});
|
||||
timer.elapsed();
|
||||
generic_with([&]() {
|
||||
registry.sort<position>([](const auto &lhs, const auto &rhs) { return lhs.x > rhs.x && lhs.y > rhs.y; }, entt::insertion_sort{});
|
||||
});
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ struct basic_test_allocator: std::allocator<Type> {
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const basic_test_allocator &other) {
|
||||
bool operator==(const basic_test_allocator &other) const {
|
||||
return (this == &other);
|
||||
}
|
||||
};
|
||||
|
@ -25,14 +25,14 @@ public:
|
||||
using propagate_on_container_swap = std::true_type;
|
||||
using exception_type = test_exception;
|
||||
|
||||
template<class Other>
|
||||
template<typename Other>
|
||||
struct rebind {
|
||||
using other = throwing_allocator<Other>;
|
||||
};
|
||||
|
||||
throwing_allocator() = default;
|
||||
|
||||
template<class Other>
|
||||
template<typename Other>
|
||||
throwing_allocator(const throwing_allocator<Other> &other)
|
||||
: base{other} {}
|
||||
|
||||
|
@ -100,7 +100,7 @@ TEST(DenseMap, Functionalities) {
|
||||
}
|
||||
|
||||
TEST(DenseMap, Constructors) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_map<int, int> map;
|
||||
|
||||
ASSERT_EQ(map.bucket_count(), minimum_bucket_count);
|
||||
@ -395,7 +395,7 @@ TEST(DenseMap, Insert) {
|
||||
}
|
||||
|
||||
TEST(DenseMap, InsertRehash) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_map<std::size_t, std::size_t, entt::identity> map;
|
||||
|
||||
ASSERT_EQ(map.size(), 0u);
|
||||
@ -429,7 +429,7 @@ TEST(DenseMap, InsertRehash) {
|
||||
}
|
||||
|
||||
TEST(DenseMap, InsertSameBucket) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_map<std::size_t, std::size_t, entt::identity> map;
|
||||
|
||||
for(std::size_t next{}; next < minimum_bucket_count; ++next) {
|
||||
@ -598,7 +598,7 @@ TEST(DenseMap, Emplace) {
|
||||
}
|
||||
|
||||
TEST(DenseMap, EmplaceRehash) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_map<std::size_t, std::size_t, entt::identity> map;
|
||||
|
||||
ASSERT_EQ(map.size(), 0u);
|
||||
@ -633,7 +633,7 @@ TEST(DenseMap, EmplaceRehash) {
|
||||
}
|
||||
|
||||
TEST(DenseMap, EmplaceSameBucket) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_map<std::size_t, std::size_t, entt::identity> map;
|
||||
|
||||
for(std::size_t next{}; next < minimum_bucket_count; ++next) {
|
||||
@ -681,7 +681,7 @@ TEST(DenseMap, TryEmplace) {
|
||||
}
|
||||
|
||||
TEST(DenseMap, TryEmplaceRehash) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_map<std::size_t, std::size_t, entt::identity> map;
|
||||
|
||||
ASSERT_EQ(map.size(), 0u);
|
||||
@ -715,7 +715,7 @@ TEST(DenseMap, TryEmplaceRehash) {
|
||||
}
|
||||
|
||||
TEST(DenseMap, TryEmplaceSameBucket) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_map<std::size_t, std::size_t, entt::identity> map;
|
||||
|
||||
for(std::size_t next{}; next < minimum_bucket_count; ++next) {
|
||||
@ -749,7 +749,7 @@ TEST(DenseMap, TryEmplaceMovableType) {
|
||||
}
|
||||
|
||||
TEST(DenseMap, Erase) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_map<std::size_t, std::size_t, entt::identity> map;
|
||||
|
||||
for(std::size_t next{}, last = minimum_bucket_count + 1u; next < last; ++next) {
|
||||
@ -799,7 +799,7 @@ TEST(DenseMap, Erase) {
|
||||
}
|
||||
|
||||
TEST(DenseMap, EraseWithMovableKeyValue) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_map<std::string, std::size_t> map;
|
||||
|
||||
map.emplace("0", 0u);
|
||||
@ -817,7 +817,7 @@ TEST(DenseMap, EraseWithMovableKeyValue) {
|
||||
}
|
||||
|
||||
TEST(DenseMap, EraseFromBucket) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_map<std::size_t, std::size_t, entt::identity> map;
|
||||
|
||||
ASSERT_EQ(map.bucket_count(), minimum_bucket_count);
|
||||
@ -995,7 +995,7 @@ TEST(DenseMap, LocalIterator) {
|
||||
static_assert(std::is_same_v<iterator::pointer, entt::input_iterator_pointer<std::pair<const std::size_t &, std::size_t &>>>);
|
||||
static_assert(std::is_same_v<iterator::reference, std::pair<const std::size_t &, std::size_t &>>);
|
||||
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_map<std::size_t, std::size_t, entt::identity> map;
|
||||
map.emplace(3u, 42u);
|
||||
map.emplace(3u + minimum_bucket_count, 99u);
|
||||
@ -1023,7 +1023,7 @@ TEST(DenseMap, ConstLocalIterator) {
|
||||
static_assert(std::is_same_v<iterator::pointer, entt::input_iterator_pointer<std::pair<const std::size_t &, const std::size_t &>>>);
|
||||
static_assert(std::is_same_v<iterator::reference, std::pair<const std::size_t &, const std::size_t &>>);
|
||||
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_map<std::size_t, std::size_t, entt::identity> map;
|
||||
map.emplace(3u, 42u);
|
||||
map.emplace(3u + minimum_bucket_count, 99u);
|
||||
@ -1064,7 +1064,7 @@ TEST(DenseMap, LocalIteratorConversion) {
|
||||
}
|
||||
|
||||
TEST(DenseMap, Rehash) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_map<std::size_t, std::size_t, entt::identity> map;
|
||||
map[32u] = 99u;
|
||||
|
||||
@ -1147,7 +1147,7 @@ TEST(DenseMap, Rehash) {
|
||||
}
|
||||
|
||||
TEST(DenseMap, Reserve) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_map<int, int> map;
|
||||
|
||||
ASSERT_EQ(map.bucket_count(), minimum_bucket_count);
|
||||
@ -1159,7 +1159,7 @@ TEST(DenseMap, Reserve) {
|
||||
map.reserve(minimum_bucket_count);
|
||||
|
||||
ASSERT_EQ(map.bucket_count(), 2 * minimum_bucket_count);
|
||||
ASSERT_EQ(map.bucket_count(), entt::next_power_of_two(std::ceil(minimum_bucket_count / map.max_load_factor())));
|
||||
ASSERT_EQ(map.bucket_count(), entt::next_power_of_two(static_cast<std::size_t>(std::ceil(minimum_bucket_count / map.max_load_factor()))));
|
||||
}
|
||||
|
||||
TEST(DenseMap, ThrowingAllocator) {
|
||||
@ -1167,7 +1167,7 @@ TEST(DenseMap, ThrowingAllocator) {
|
||||
using packed_allocator = test::throwing_allocator<entt::internal::dense_map_node<std::size_t, std::size_t>>;
|
||||
using packed_exception = typename packed_allocator::exception_type;
|
||||
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_map<std::size_t, std::size_t, std::hash<std::size_t>, std::equal_to<std::size_t>, allocator> map{};
|
||||
|
||||
packed_allocator::trigger_on_allocate = true;
|
||||
|
@ -98,7 +98,7 @@ TEST(DenseSet, Functionalities) {
|
||||
}
|
||||
|
||||
TEST(DenseSet, Constructors) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_set<int> set;
|
||||
|
||||
ASSERT_EQ(set.bucket_count(), minimum_bucket_count);
|
||||
@ -357,7 +357,7 @@ TEST(DenseSet, Insert) {
|
||||
}
|
||||
|
||||
TEST(DenseSet, InsertRehash) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_set<std::size_t, entt::identity> set;
|
||||
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
@ -388,7 +388,7 @@ TEST(DenseSet, InsertRehash) {
|
||||
}
|
||||
|
||||
TEST(DenseSet, InsertSameBucket) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_set<std::size_t, entt::identity> set;
|
||||
|
||||
for(std::size_t next{}; next < minimum_bucket_count; ++next) {
|
||||
@ -451,7 +451,7 @@ TEST(DenseSet, Emplace) {
|
||||
}
|
||||
|
||||
TEST(DenseSet, EmplaceRehash) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_set<std::size_t, entt::identity> set;
|
||||
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
@ -483,7 +483,7 @@ TEST(DenseSet, EmplaceRehash) {
|
||||
}
|
||||
|
||||
TEST(DenseSet, EmplaceSameBucket) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_set<std::size_t, entt::identity> set;
|
||||
|
||||
for(std::size_t next{}; next < minimum_bucket_count; ++next) {
|
||||
@ -503,7 +503,7 @@ TEST(DenseSet, EmplaceSameBucket) {
|
||||
}
|
||||
|
||||
TEST(DenseSet, Erase) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_set<std::size_t, entt::identity> set;
|
||||
|
||||
for(std::size_t next{}, last = minimum_bucket_count + 1u; next < last; ++next) {
|
||||
@ -553,7 +553,7 @@ TEST(DenseSet, Erase) {
|
||||
}
|
||||
|
||||
TEST(DenseSet, EraseWithMovableKeyValue) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_set<std::string> set;
|
||||
|
||||
set.emplace("0");
|
||||
@ -570,7 +570,7 @@ TEST(DenseSet, EraseWithMovableKeyValue) {
|
||||
}
|
||||
|
||||
TEST(DenseSet, EraseFromBucket) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_set<std::size_t, entt::identity> set;
|
||||
|
||||
ASSERT_EQ(set.bucket_count(), minimum_bucket_count);
|
||||
@ -721,7 +721,7 @@ TEST(DenseSet, LocalIterator) {
|
||||
static_assert(std::is_same_v<iterator::pointer, const std::size_t *>);
|
||||
static_assert(std::is_same_v<iterator::reference, const std::size_t &>);
|
||||
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_set<std::size_t, entt::identity> set;
|
||||
set.emplace(3u);
|
||||
set.emplace(3u + minimum_bucket_count);
|
||||
@ -749,7 +749,7 @@ TEST(DenseSet, ConstLocalIterator) {
|
||||
static_assert(std::is_same_v<iterator::pointer, const std::size_t *>);
|
||||
static_assert(std::is_same_v<iterator::reference, const std::size_t &>);
|
||||
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_set<std::size_t, entt::identity> set;
|
||||
set.emplace(3u);
|
||||
set.emplace(3u + minimum_bucket_count);
|
||||
@ -790,7 +790,7 @@ TEST(DenseSet, LocalIteratorConversion) {
|
||||
}
|
||||
|
||||
TEST(DenseSet, Rehash) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_set<std::size_t, entt::identity> set;
|
||||
set.emplace(32u);
|
||||
|
||||
@ -865,7 +865,7 @@ TEST(DenseSet, Rehash) {
|
||||
}
|
||||
|
||||
TEST(DenseSet, Reserve) {
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_set<int> set;
|
||||
|
||||
ASSERT_EQ(set.bucket_count(), minimum_bucket_count);
|
||||
@ -877,7 +877,7 @@ TEST(DenseSet, Reserve) {
|
||||
set.reserve(minimum_bucket_count);
|
||||
|
||||
ASSERT_EQ(set.bucket_count(), 2 * minimum_bucket_count);
|
||||
ASSERT_EQ(set.bucket_count(), entt::next_power_of_two(std::ceil(minimum_bucket_count / set.max_load_factor())));
|
||||
ASSERT_EQ(set.bucket_count(), entt::next_power_of_two(static_cast<std::size_t>(std::ceil(minimum_bucket_count / set.max_load_factor()))));
|
||||
}
|
||||
|
||||
TEST(DenseSet, ThrowingAllocator) {
|
||||
@ -885,7 +885,7 @@ TEST(DenseSet, ThrowingAllocator) {
|
||||
using packed_allocator = test::throwing_allocator<std::pair<std::size_t, std::size_t>>;
|
||||
using packed_exception = typename packed_allocator::exception_type;
|
||||
|
||||
static constexpr std::size_t minimum_bucket_count = 8u;
|
||||
constexpr std::size_t minimum_bucket_count = 8u;
|
||||
entt::dense_set<std::size_t, std::hash<std::size_t>, std::equal_to<std::size_t>, allocator> set{};
|
||||
|
||||
packed_allocator::trigger_on_allocate = true;
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
@ -39,21 +40,9 @@ struct not_comparable {
|
||||
bool operator==(const not_comparable &) const = delete;
|
||||
};
|
||||
|
||||
struct not_copyable {
|
||||
not_copyable()
|
||||
: payload{} {}
|
||||
|
||||
not_copyable(const not_copyable &) = delete;
|
||||
not_copyable(not_copyable &&) = default;
|
||||
|
||||
not_copyable &operator=(const not_copyable &) = delete;
|
||||
not_copyable &operator=(not_copyable &&) = default;
|
||||
|
||||
double payload;
|
||||
};
|
||||
|
||||
struct not_movable {
|
||||
not_movable() = default;
|
||||
|
||||
not_movable(const not_movable &) = default;
|
||||
not_movable(not_movable &&) = delete;
|
||||
|
||||
@ -1183,13 +1172,12 @@ TEST_F(Any, AnyCast) {
|
||||
ASSERT_EQ(entt::any_cast<int &>(any), 42);
|
||||
ASSERT_EQ(entt::any_cast<const int &>(cany), 42);
|
||||
|
||||
not_copyable instance{};
|
||||
instance.payload = 42.;
|
||||
auto instance = std::make_unique<double>(42.);
|
||||
entt::any ref{entt::forward_as_any(instance)};
|
||||
entt::any cref{entt::forward_as_any(std::as_const(instance).payload)};
|
||||
entt::any cref{entt::forward_as_any(std::as_const(*instance))};
|
||||
|
||||
ASSERT_EQ(entt::any_cast<not_copyable>(std::move(ref)).payload, 42.);
|
||||
ASSERT_EQ(entt::any_cast<double>(std::move(cref)), 42.);
|
||||
ASSERT_EQ(*entt::any_cast<std::unique_ptr<double>>(std::move(ref)), 42.);
|
||||
ASSERT_EQ(entt::any_cast<int>(entt::any{42}), 42);
|
||||
}
|
||||
|
||||
@ -1197,16 +1185,15 @@ ENTT_DEBUG_TEST_F(AnyDeathTest, AnyCast) {
|
||||
entt::any any{42};
|
||||
const auto &cany = any;
|
||||
|
||||
ASSERT_DEATH(entt::any_cast<double &>(any), "");
|
||||
ASSERT_DEATH(entt::any_cast<const double &>(cany), "");
|
||||
ASSERT_DEATH([[maybe_unused]] auto &elem = entt::any_cast<double &>(any), "");
|
||||
ASSERT_DEATH([[maybe_unused]] const auto &elem = entt::any_cast<const double &>(cany), "");
|
||||
|
||||
not_copyable instance{};
|
||||
instance.payload = 42.;
|
||||
auto instance = std::make_unique<double>(42.);
|
||||
entt::any ref{entt::forward_as_any(instance)};
|
||||
entt::any cref{entt::forward_as_any(std::as_const(instance).payload)};
|
||||
entt::any cref{entt::forward_as_any(std::as_const(*instance))};
|
||||
|
||||
ASSERT_DEATH(entt::any_cast<not_copyable>(std::as_const(ref).as_ref()), "");
|
||||
ASSERT_DEATH(entt::any_cast<double>(entt::any{42}), "");
|
||||
ASSERT_DEATH([[maybe_unused]] auto elem = entt::any_cast<std::unique_ptr<double>>(std::as_const(ref).as_ref()), "");
|
||||
ASSERT_DEATH([[maybe_unused]] auto elem = entt::any_cast<double>(entt::any{42}), "");
|
||||
}
|
||||
|
||||
TEST_F(Any, MakeAny) {
|
||||
@ -1263,8 +1250,8 @@ TEST_F(Any, ForwardAsAny) {
|
||||
}
|
||||
|
||||
TEST_F(Any, NotCopyableType) {
|
||||
const not_copyable value{};
|
||||
entt::any any{std::in_place_type<not_copyable>};
|
||||
const std::unique_ptr<int> value{};
|
||||
entt::any any{std::in_place_type<std::unique_ptr<int>>};
|
||||
entt::any other = entt::forward_as_any(value);
|
||||
|
||||
ASSERT_TRUE(any);
|
||||
@ -1296,7 +1283,7 @@ TEST_F(Any, NotCopyableType) {
|
||||
|
||||
TEST_F(Any, NotCopyableValueType) {
|
||||
std::vector<entt::any> vec{};
|
||||
vec.emplace_back(std::in_place_type<not_copyable>);
|
||||
vec.emplace_back(std::in_place_type<std::unique_ptr<int>>);
|
||||
vec.shrink_to_fit();
|
||||
|
||||
ASSERT_EQ(vec.size(), 1u);
|
||||
@ -1304,7 +1291,7 @@ TEST_F(Any, NotCopyableValueType) {
|
||||
ASSERT_TRUE(vec[0u]);
|
||||
|
||||
// strong exception guarantee due to noexcept move ctor
|
||||
vec.emplace_back(std::in_place_type<not_copyable>);
|
||||
vec.emplace_back(std::in_place_type<std::unique_ptr<int>>);
|
||||
|
||||
ASSERT_EQ(vec.size(), 2u);
|
||||
ASSERT_TRUE(vec[0u]);
|
||||
@ -1411,7 +1398,7 @@ TEST_F(Any, SBOVsZeroedSBOSize) {
|
||||
}
|
||||
|
||||
TEST_F(Any, SboAlignment) {
|
||||
static constexpr auto alignment = alignof(over_aligned);
|
||||
constexpr auto alignment = alignof(over_aligned);
|
||||
entt::basic_any<alignment, alignment> sbo[2] = {over_aligned{}, over_aligned{}};
|
||||
const auto *data = sbo[0].data();
|
||||
|
||||
@ -1427,7 +1414,7 @@ TEST_F(Any, SboAlignment) {
|
||||
}
|
||||
|
||||
TEST_F(Any, NoSboAlignment) {
|
||||
static constexpr auto alignment = alignof(over_aligned);
|
||||
constexpr auto alignment = alignof(over_aligned);
|
||||
entt::basic_any<alignment> nosbo[2] = {over_aligned{}, over_aligned{}};
|
||||
const auto *data = nosbo[0].data();
|
||||
|
||||
|
@ -6,19 +6,19 @@
|
||||
#include <entt/core/hashed_string.hpp>
|
||||
|
||||
template<typename>
|
||||
struct foobar_t;
|
||||
struct expected;
|
||||
|
||||
template<>
|
||||
struct foobar_t<std::uint32_t> {
|
||||
struct expected<std::uint32_t> {
|
||||
static constexpr auto value = 0xbf9cf968;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct foobar_t<std::uint64_t> {
|
||||
struct expected<std::uint64_t> {
|
||||
static constexpr auto value = 0x85944171f73967e8;
|
||||
};
|
||||
|
||||
inline constexpr auto foobar_v = foobar_t<entt::id_type>::value;
|
||||
inline constexpr auto expected_v = expected<entt::id_type>::value;
|
||||
|
||||
TEST(BasicHashedString, DeductionGuide) {
|
||||
static_assert(std::is_same_v<decltype(entt::basic_hashed_string{"foo"}), entt::hashed_string>);
|
||||
@ -47,8 +47,8 @@ TEST(HashedString, Functionalities) {
|
||||
|
||||
entt::hashed_string hs{"foobar"};
|
||||
|
||||
ASSERT_EQ(static_cast<hash_type>(hs), foobar_v);
|
||||
ASSERT_EQ(hs.value(), foobar_v);
|
||||
ASSERT_EQ(static_cast<hash_type>(hs), expected_v);
|
||||
ASSERT_EQ(hs.value(), expected_v);
|
||||
|
||||
ASSERT_EQ(foo_hs, "foo"_hs);
|
||||
ASSERT_NE(bar_hs, "foo"_hs);
|
||||
@ -78,13 +78,13 @@ TEST(HashedString, Correctness) {
|
||||
const char *foobar = "foobar";
|
||||
std::string_view view{"foobar__", 6};
|
||||
|
||||
ASSERT_EQ(entt::hashed_string{foobar}, foobar_v);
|
||||
ASSERT_EQ((entt::hashed_string{view.data(), view.size()}), foobar_v);
|
||||
ASSERT_EQ(entt::hashed_string{"foobar"}, foobar_v);
|
||||
ASSERT_EQ(entt::hashed_string{foobar}, expected_v);
|
||||
ASSERT_EQ((entt::hashed_string{view.data(), view.size()}), expected_v);
|
||||
ASSERT_EQ(entt::hashed_string{"foobar"}, expected_v);
|
||||
|
||||
ASSERT_EQ(entt::hashed_string::value(foobar), foobar_v);
|
||||
ASSERT_EQ(entt::hashed_string::value(view.data(), view.size()), foobar_v);
|
||||
ASSERT_EQ(entt::hashed_string::value("foobar"), foobar_v);
|
||||
ASSERT_EQ(entt::hashed_string::value(foobar), expected_v);
|
||||
ASSERT_EQ(entt::hashed_string::value(view.data(), view.size()), expected_v);
|
||||
ASSERT_EQ(entt::hashed_string::value("foobar"), expected_v);
|
||||
|
||||
ASSERT_EQ(entt::hashed_string{foobar}.size(), 6u);
|
||||
ASSERT_EQ((entt::hashed_string{view.data(), view.size()}).size(), 6u);
|
||||
@ -111,16 +111,16 @@ TEST(HashedString, Constexprness) {
|
||||
constexpr std::string_view view{"foobar__", 6};
|
||||
|
||||
static_assert(entt::hashed_string{"quux"} == "quux"_hs);
|
||||
static_assert(entt::hashed_string{"foobar"} == foobar_v);
|
||||
static_assert(entt::hashed_string{"foobar"} == expected_v);
|
||||
|
||||
static_assert(entt::hashed_string::value("quux") == "quux"_hs);
|
||||
static_assert(entt::hashed_string::value("foobar") == foobar_v);
|
||||
static_assert(entt::hashed_string::value("foobar") == expected_v);
|
||||
|
||||
static_assert(entt::hashed_string{"quux", 4} == "quux"_hs);
|
||||
static_assert(entt::hashed_string{view.data(), view.size()} == foobar_v);
|
||||
static_assert(entt::hashed_string{view.data(), view.size()} == expected_v);
|
||||
|
||||
static_assert(entt::hashed_string::value("quux", 4) == "quux"_hs);
|
||||
static_assert(entt::hashed_string::value(view.data(), view.size()) == foobar_v);
|
||||
static_assert(entt::hashed_string::value(view.data(), view.size()) == expected_v);
|
||||
|
||||
static_assert(entt::hashed_string{"bar"} < "foo"_hs);
|
||||
static_assert(entt::hashed_string{"bar"} <= "bar"_hs);
|
||||
@ -151,8 +151,8 @@ TEST(HashedWString, Functionalities) {
|
||||
|
||||
entt::hashed_wstring hws{L"foobar"};
|
||||
|
||||
ASSERT_EQ(static_cast<hash_type>(hws), foobar_v);
|
||||
ASSERT_EQ(hws.value(), foobar_v);
|
||||
ASSERT_EQ(static_cast<hash_type>(hws), expected_v);
|
||||
ASSERT_EQ(hws.value(), expected_v);
|
||||
|
||||
ASSERT_EQ(foo_hws, L"foo"_hws);
|
||||
ASSERT_NE(bar_hws, L"foo"_hws);
|
||||
@ -172,13 +172,13 @@ TEST(HashedWString, Correctness) {
|
||||
const wchar_t *foobar = L"foobar";
|
||||
std::wstring_view view{L"foobar__", 6};
|
||||
|
||||
ASSERT_EQ(entt::hashed_wstring{foobar}, foobar_v);
|
||||
ASSERT_EQ((entt::hashed_wstring{view.data(), view.size()}), foobar_v);
|
||||
ASSERT_EQ(entt::hashed_wstring{L"foobar"}, foobar_v);
|
||||
ASSERT_EQ(entt::hashed_wstring{foobar}, expected_v);
|
||||
ASSERT_EQ((entt::hashed_wstring{view.data(), view.size()}), expected_v);
|
||||
ASSERT_EQ(entt::hashed_wstring{L"foobar"}, expected_v);
|
||||
|
||||
ASSERT_EQ(entt::hashed_wstring::value(foobar), foobar_v);
|
||||
ASSERT_EQ(entt::hashed_wstring::value(view.data(), view.size()), foobar_v);
|
||||
ASSERT_EQ(entt::hashed_wstring::value(L"foobar"), foobar_v);
|
||||
ASSERT_EQ(entt::hashed_wstring::value(foobar), expected_v);
|
||||
ASSERT_EQ(entt::hashed_wstring::value(view.data(), view.size()), expected_v);
|
||||
ASSERT_EQ(entt::hashed_wstring::value(L"foobar"), expected_v);
|
||||
|
||||
ASSERT_EQ(entt::hashed_wstring{foobar}.size(), 6u);
|
||||
ASSERT_EQ((entt::hashed_wstring{view.data(), view.size()}).size(), 6u);
|
||||
@ -205,16 +205,16 @@ TEST(HashedWString, Constexprness) {
|
||||
constexpr std::wstring_view view{L"foobar__", 6};
|
||||
|
||||
static_assert(entt::hashed_wstring{L"quux"} == L"quux"_hws);
|
||||
static_assert(entt::hashed_wstring{L"foobar"} == foobar_v);
|
||||
static_assert(entt::hashed_wstring{L"foobar"} == expected_v);
|
||||
|
||||
static_assert(entt::hashed_wstring::value(L"quux") == L"quux"_hws);
|
||||
static_assert(entt::hashed_wstring::value(L"foobar") == foobar_v);
|
||||
static_assert(entt::hashed_wstring::value(L"foobar") == expected_v);
|
||||
|
||||
static_assert(entt::hashed_wstring{L"quux", 4} == L"quux"_hws);
|
||||
static_assert(entt::hashed_wstring{view.data(), view.size()} == foobar_v);
|
||||
static_assert(entt::hashed_wstring{view.data(), view.size()} == expected_v);
|
||||
|
||||
static_assert(entt::hashed_wstring::value(L"quux", 4) == L"quux"_hws);
|
||||
static_assert(entt::hashed_wstring::value(view.data(), view.size()) == foobar_v);
|
||||
static_assert(entt::hashed_wstring::value(view.data(), view.size()) == expected_v);
|
||||
|
||||
static_assert(entt::hashed_wstring{L"bar"} < L"foo"_hws);
|
||||
static_assert(entt::hashed_wstring{L"bar"} <= L"bar"_hws);
|
||||
|
@ -24,6 +24,12 @@ TEST(ToAddress, Functionalities) {
|
||||
|
||||
TEST(PoccaPocmaAndPocs, Functionalities) {
|
||||
test::basic_test_allocator<int> lhs, rhs;
|
||||
test::basic_test_allocator<int, std::false_type> no_pocs;
|
||||
|
||||
// code coverage purposes
|
||||
ASSERT_FALSE(lhs == rhs);
|
||||
ASSERT_NO_FATAL_FAILURE(entt::propagate_on_container_swap(no_pocs, no_pocs));
|
||||
|
||||
// honestly, I don't even know how one is supposed to test such a thing :)
|
||||
entt::propagate_on_container_copy_assignment(lhs, rhs);
|
||||
entt::propagate_on_container_move_assignment(lhs, rhs);
|
||||
@ -31,8 +37,8 @@ TEST(PoccaPocmaAndPocs, Functionalities) {
|
||||
}
|
||||
|
||||
ENTT_DEBUG_TEST(PoccaPocmaAndPocsDeathTest, Functionalities) {
|
||||
using pocs = std::false_type;
|
||||
test::basic_test_allocator<int, pocs> lhs, rhs;
|
||||
test::basic_test_allocator<int, std::false_type> lhs, rhs;
|
||||
|
||||
ASSERT_DEATH(entt::propagate_on_container_swap(lhs, rhs), "");
|
||||
}
|
||||
|
||||
@ -60,8 +66,8 @@ TEST(NextPowerOfTwo, Functionalities) {
|
||||
ASSERT_EQ(entt::next_power_of_two(17u), 32u);
|
||||
ASSERT_EQ(entt::next_power_of_two(32u), 32u);
|
||||
ASSERT_EQ(entt::next_power_of_two(33u), 64u);
|
||||
ASSERT_EQ(entt::next_power_of_two(std::pow(2, 16)), std::pow(2, 16));
|
||||
ASSERT_EQ(entt::next_power_of_two(std::pow(2, 16) + 1u), std::pow(2, 17));
|
||||
ASSERT_EQ(entt::next_power_of_two(static_cast<std::size_t>(std::pow(2, 16))), static_cast<std::size_t>(std::pow(2, 16)));
|
||||
ASSERT_EQ(entt::next_power_of_two(static_cast<std::size_t>(std::pow(2, 16) + 1u)), static_cast<std::size_t>(std::pow(2, 17)));
|
||||
}
|
||||
|
||||
ENTT_DEBUG_TEST(NextPowerOfTwoDeathTest, Functionalities) {
|
||||
|
@ -34,5 +34,5 @@ TEST(Tuple, UnwrapTuple) {
|
||||
TEST(Tuple, ForwardApply) {
|
||||
ASSERT_EQ(entt::forward_apply{[](auto &&...args) { return sizeof...(args); }}(std::make_tuple()), 0u);
|
||||
ASSERT_EQ(entt::forward_apply{[](int i) { return i; }}(std::make_tuple(42)), 42);
|
||||
ASSERT_EQ(entt::forward_apply{[](auto... args) { return (args + ...); }}(std::make_tuple('a', 1u)), 'b');
|
||||
ASSERT_EQ(entt::forward_apply{[](auto... args) { return (args + ...); }}(std::make_tuple('a', 1)), 'b');
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ TEST(TypeList, Functionalities) {
|
||||
static_assert(std::is_same_v<entt::type_list_cat_t<type, other, type, other>, entt::type_list<int, char, double, int, char, double>>);
|
||||
static_assert(std::is_same_v<entt::type_list_cat_t<type, other>, entt::type_list<int, char, double>>);
|
||||
static_assert(std::is_same_v<entt::type_list_cat_t<type, type>, entt::type_list<int, char, int, char>>);
|
||||
static_assert(std::is_same_v<entt::type_list_unique_t<entt::type_list_cat_t<type, type>>, entt::type_list<int, char>>);
|
||||
static_assert(std::is_same_v<entt::type_list_unique_t<entt::type_list_cat_t<type, type>>, type>);
|
||||
|
||||
static_assert(entt::type_list_contains_v<type, int>);
|
||||
static_assert(entt::type_list_contains_v<type, char>);
|
||||
@ -112,6 +112,14 @@ TEST(TypeList, Functionalities) {
|
||||
static_assert(std::is_same_v<entt::type_list_transform_t<entt::type_list<int, char>, entt::type_identity>, entt::type_list<int, char>>);
|
||||
static_assert(std::is_same_v<entt::type_list_transform_t<entt::type_list<int, char>, std::add_const>, entt::type_list<const int, const char>>);
|
||||
static_assert(std::is_same_v<entt::type_list_transform_t<entt::type_list<int, char>, multi_argument_operation>, entt::type_list<void, void>>);
|
||||
|
||||
static_assert(std::tuple_size_v<entt::type_list<>> == 0u);
|
||||
static_assert(std::tuple_size_v<entt::type_list<int>> == 1u);
|
||||
static_assert(std::tuple_size_v<entt::type_list<int, float>> == 2u);
|
||||
|
||||
static_assert(std::is_same_v<int, std::tuple_element_t<0, entt::type_list<int>>>);
|
||||
static_assert(std::is_same_v<int, std::tuple_element_t<0, entt::type_list<int, float>>>);
|
||||
static_assert(std::is_same_v<float, std::tuple_element_t<1, entt::type_list<int, float>>>);
|
||||
}
|
||||
|
||||
TEST(ValueList, Functionalities) {
|
||||
@ -125,10 +133,33 @@ TEST(ValueList, Functionalities) {
|
||||
static_assert(std::is_same_v<entt::value_list_cat_t<value, other, value, other>, entt::value_list<0, 2, 1, 0, 2, 1>>);
|
||||
static_assert(std::is_same_v<entt::value_list_cat_t<value, other>, entt::value_list<0, 2, 1>>);
|
||||
static_assert(std::is_same_v<entt::value_list_cat_t<value, value>, entt::value_list<0, 2, 0, 2>>);
|
||||
static_assert(std::is_same_v<entt::value_list_unique_t<entt::value_list_cat_t<value, value>>, value>);
|
||||
|
||||
static_assert(entt::value_list_contains_v<value, 0>);
|
||||
static_assert(entt::value_list_contains_v<value, 2>);
|
||||
static_assert(!entt::value_list_contains_v<value, 1>);
|
||||
|
||||
static_assert(entt::value_list_element_v<0u, value> == 0);
|
||||
static_assert(entt::value_list_element_v<1u, value> == 2);
|
||||
static_assert(entt::value_list_element_v<0u, other> == 1);
|
||||
|
||||
static_assert(entt::value_list_index_v<0, value> == 0u);
|
||||
static_assert(entt::value_list_index_v<2, value> == 1u);
|
||||
static_assert(entt::value_list_index_v<1, other> == 0u);
|
||||
|
||||
static_assert(std::is_same_v<entt::value_list_diff_t<entt::value_list<0, 1, 2>, entt::value_list<3, 4>>, entt::value_list<0, 1, 2>>);
|
||||
static_assert(std::is_same_v<entt::value_list_diff_t<entt::value_list<0, 1, 2>, entt::value_list<0, 1, 2>>, entt::value_list<>>);
|
||||
static_assert(std::is_same_v<entt::value_list_diff_t<entt::value_list<0, 1, 2>, entt::value_list<0, 1>>, entt::value_list<2>>);
|
||||
static_assert(std::is_same_v<entt::value_list_diff_t<entt::value_list<0, 1, 2>, entt::value_list<1, 2>>, entt::value_list<0>>);
|
||||
static_assert(std::is_same_v<entt::value_list_diff_t<entt::value_list<0, 1, 2>, entt::value_list<1>>, entt::value_list<0, 2>>);
|
||||
|
||||
static_assert(std::tuple_size_v<entt::value_list<>> == 0u);
|
||||
static_assert(std::tuple_size_v<entt::value_list<42>> == 1u);
|
||||
static_assert(std::tuple_size_v<entt::value_list<42, 'a'>> == 2u);
|
||||
|
||||
static_assert(std::is_same_v<int, std::tuple_element_t<0, entt::value_list<42>>>);
|
||||
static_assert(std::is_same_v<int, std::tuple_element_t<0, entt::value_list<42, 'a'>>>);
|
||||
static_assert(std::is_same_v<char, std::tuple_element_t<1, entt::value_list<42, 'a'>>>);
|
||||
}
|
||||
|
||||
TEST(IsApplicable, Functionalities) {
|
||||
@ -183,6 +214,7 @@ TEST(IsEqualityComparable, Functionalities) {
|
||||
static_assert(entt::is_equality_comparable_v<std::vector<not_comparable>::iterator>);
|
||||
static_assert(entt::is_equality_comparable_v<nlohmann_json_like>);
|
||||
|
||||
static_assert(!entt::is_equality_comparable_v<int[3u]>);
|
||||
static_assert(!entt::is_equality_comparable_v<not_comparable>);
|
||||
static_assert(!entt::is_equality_comparable_v<const not_comparable>);
|
||||
static_assert(!entt::is_equality_comparable_v<std::vector<not_comparable>>);
|
||||
|
@ -30,50 +30,43 @@ struct entt::component_traits<traits_based> {
|
||||
};
|
||||
|
||||
TEST(Component, VoidType) {
|
||||
using traits = entt::component_traits<void>;
|
||||
using traits_type = entt::component_traits<void>;
|
||||
|
||||
static_assert(traits::in_place_delete);
|
||||
static_assert(entt::ignore_as_empty_v<typename traits::type>);
|
||||
// we don't really care about this thanks to ignore_as_empty_v
|
||||
static_assert(traits::page_size != 0u);
|
||||
static_assert(!traits_type::in_place_delete);
|
||||
static_assert(traits_type::page_size == 0u);
|
||||
}
|
||||
|
||||
TEST(Component, Empty) {
|
||||
using traits = entt::component_traits<empty>;
|
||||
using traits_type = entt::component_traits<empty>;
|
||||
|
||||
static_assert(!traits::in_place_delete);
|
||||
static_assert(entt::ignore_as_empty_v<typename traits::type>);
|
||||
static_assert(traits::page_size == 0u);
|
||||
static_assert(!traits_type::in_place_delete);
|
||||
static_assert(traits_type::page_size == 0u);
|
||||
}
|
||||
|
||||
TEST(Component, NonEmpty) {
|
||||
using traits = entt::component_traits<non_empty>;
|
||||
using traits_type = entt::component_traits<non_empty>;
|
||||
|
||||
static_assert(!traits::in_place_delete);
|
||||
static_assert(!entt::ignore_as_empty_v<typename traits::type>);
|
||||
static_assert(traits::page_size == ENTT_PACKED_PAGE);
|
||||
static_assert(!traits_type::in_place_delete);
|
||||
static_assert(traits_type::page_size == ENTT_PACKED_PAGE);
|
||||
}
|
||||
|
||||
TEST(Component, NonMovable) {
|
||||
using traits = entt::component_traits<non_movable>;
|
||||
using traits_type = entt::component_traits<non_movable>;
|
||||
|
||||
static_assert(traits::in_place_delete);
|
||||
static_assert(!entt::ignore_as_empty_v<typename traits::type>);
|
||||
static_assert(traits::page_size == ENTT_PACKED_PAGE);
|
||||
static_assert(traits_type::in_place_delete);
|
||||
static_assert(traits_type::page_size == ENTT_PACKED_PAGE);
|
||||
}
|
||||
|
||||
TEST(Component, SelfContained) {
|
||||
using traits = entt::component_traits<self_contained>;
|
||||
using traits_type = entt::component_traits<self_contained>;
|
||||
|
||||
static_assert(traits::in_place_delete);
|
||||
static_assert(!entt::ignore_as_empty_v<typename traits::type>);
|
||||
static_assert(traits::page_size == 4u);
|
||||
static_assert(traits_type::in_place_delete);
|
||||
static_assert(traits_type::page_size == 4u);
|
||||
}
|
||||
|
||||
TEST(Component, TraitsBased) {
|
||||
using traits = entt::component_traits<traits_based>;
|
||||
using traits_type = entt::component_traits<traits_based>;
|
||||
|
||||
static_assert(!traits::in_place_delete);
|
||||
static_assert(!entt::ignore_as_empty_v<typename traits::type>);
|
||||
static_assert(traits::page_size == 8u);
|
||||
static_assert(!traits_type::in_place_delete);
|
||||
static_assert(traits_type::page_size == 8u);
|
||||
}
|
||||
|
@ -29,6 +29,12 @@ TEST(Entity, Traits) {
|
||||
|
||||
ASSERT_EQ(traits_type::combine(entt::tombstone, entt::null), tombstone);
|
||||
ASSERT_EQ(traits_type::combine(entt::null, entt::tombstone), null);
|
||||
|
||||
ASSERT_EQ(traits_type::next(entity), traits_type::construct(entt::to_integral(entity), entt::to_version(entity) + 1u));
|
||||
ASSERT_EQ(traits_type::next(other), traits_type::construct(entt::to_integral(other), entt::to_version(other) + 1u));
|
||||
|
||||
ASSERT_EQ(traits_type::next(entt::tombstone), traits_type::construct(entt::null, {}));
|
||||
ASSERT_EQ(traits_type::next(entt::null), traits_type::construct(entt::null, {}));
|
||||
}
|
||||
|
||||
TEST(Entity, Null) {
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/group.hpp>
|
||||
#include <entt/entity/registry.hpp>
|
||||
#include "../common/config.h"
|
||||
|
||||
struct empty_type {};
|
||||
|
||||
@ -56,7 +57,7 @@ TEST(NonOwningGroup, Functionalities) {
|
||||
for(auto entity: group) {
|
||||
ASSERT_EQ(std::get<0>(cgroup.get<const int, const char>(entity)), 42);
|
||||
ASSERT_EQ(std::get<1>(group.get<int, char>(entity)), '2');
|
||||
ASSERT_EQ(cgroup.get<const char>(entity), '2');
|
||||
ASSERT_EQ(cgroup.get<1>(entity), '2');
|
||||
}
|
||||
|
||||
ASSERT_EQ(group.handle().data()[0u], e1);
|
||||
@ -93,6 +94,7 @@ TEST(NonOwningGroup, Handle) {
|
||||
ASSERT_TRUE(handle.empty());
|
||||
ASSERT_FALSE(handle.contains(entity));
|
||||
ASSERT_EQ(&handle, &group.handle());
|
||||
ASSERT_NE(&handle, group.storage<int>());
|
||||
|
||||
registry.emplace<int>(entity);
|
||||
registry.emplace<char>(entity);
|
||||
@ -187,10 +189,10 @@ TEST(NonOwningGroup, Each) {
|
||||
auto cgroup = std::as_const(registry).group_if_exists(entt::get<const int, const char>);
|
||||
|
||||
registry.emplace<int>(entity[0u], 0);
|
||||
registry.emplace<char>(entity[0u], 0);
|
||||
registry.emplace<char>(entity[0u], static_cast<char>(0));
|
||||
|
||||
registry.emplace<int>(entity[1u], 1);
|
||||
registry.emplace<char>(entity[1u], 1);
|
||||
registry.emplace<char>(entity[1u], static_cast<char>(1));
|
||||
|
||||
auto iterable = group.each();
|
||||
auto citerable = cgroup.each();
|
||||
@ -201,16 +203,18 @@ TEST(NonOwningGroup, Each) {
|
||||
|
||||
auto it = iterable.begin();
|
||||
|
||||
ASSERT_EQ(it.base(), group.begin());
|
||||
ASSERT_EQ((it++, ++it), iterable.end());
|
||||
ASSERT_EQ(it.base(), group.end());
|
||||
|
||||
group.each([expected = 1u](auto entt, int &ivalue, char &cvalue) mutable {
|
||||
ASSERT_EQ(entt::to_integral(entt), expected);
|
||||
group.each([expected = 1](auto entt, int &ivalue, char &cvalue) mutable {
|
||||
ASSERT_EQ(static_cast<int>(entt::to_integral(entt)), expected);
|
||||
ASSERT_EQ(ivalue, expected);
|
||||
ASSERT_EQ(cvalue, expected);
|
||||
--expected;
|
||||
});
|
||||
|
||||
cgroup.each([expected = 1u](const int &ivalue, const char &cvalue) mutable {
|
||||
cgroup.each([expected = 1](const int &ivalue, const char &cvalue) mutable {
|
||||
ASSERT_EQ(ivalue, expected);
|
||||
ASSERT_EQ(cvalue, expected);
|
||||
--expected;
|
||||
@ -224,8 +228,8 @@ TEST(NonOwningGroup, Each) {
|
||||
|
||||
// do not use iterable, make sure an iterable group works when created from a temporary
|
||||
for(auto [entt, ivalue, cvalue]: registry.group(entt::get<int, char>).each()) {
|
||||
ASSERT_EQ(entt::to_integral(entt), ivalue);
|
||||
ASSERT_EQ(entt::to_integral(entt), cvalue);
|
||||
ASSERT_EQ(static_cast<int>(entt::to_integral(entt)), ivalue);
|
||||
ASSERT_EQ(static_cast<char>(entt::to_integral(entt)), cvalue);
|
||||
}
|
||||
}
|
||||
|
||||
@ -273,9 +277,9 @@ TEST(NonOwningGroup, Sort) {
|
||||
ASSERT_EQ(group.handle().data()[1u], e1);
|
||||
ASSERT_EQ(group.handle().data()[2u], e2);
|
||||
|
||||
ASSERT_EQ((group.get<const int, unsigned int>(e0)), (std::make_tuple(0, 0u)));
|
||||
ASSERT_EQ((group.get<const int, unsigned int>(e1)), (std::make_tuple(1, 1u)));
|
||||
ASSERT_EQ((group.get<const int, unsigned int>(e2)), (std::make_tuple(2, 2u)));
|
||||
ASSERT_EQ((group.get<0, 1>(e0)), (std::make_tuple(0, 0u)));
|
||||
ASSERT_EQ((group.get<0, 1>(e1)), (std::make_tuple(1, 1u)));
|
||||
ASSERT_EQ((group.get<0, 1>(e2)), (std::make_tuple(2, 2u)));
|
||||
|
||||
ASSERT_FALSE(group.contains(e3));
|
||||
|
||||
@ -323,10 +327,10 @@ TEST(NonOwningGroup, SortAsAPool) {
|
||||
}
|
||||
|
||||
registry.sort<unsigned int>(std::less<unsigned int>{});
|
||||
group.sort<unsigned int>();
|
||||
group.sort_as(*group.storage<unsigned int>());
|
||||
|
||||
ASSERT_EQ((group.get<const int, unsigned int>(e0)), (std::make_tuple(0, 0u)));
|
||||
ASSERT_EQ((group.get<const int, unsigned int>(e1)), (std::make_tuple(1, 1u)));
|
||||
ASSERT_EQ((group.get<0, 1>(e1)), (std::make_tuple(1, 1u)));
|
||||
ASSERT_EQ((group.get<const int, unsigned int>(e2)), (std::make_tuple(2, 2u)));
|
||||
|
||||
ASSERT_FALSE(group.contains(e3));
|
||||
@ -384,9 +388,18 @@ TEST(NonOwningGroup, ConstNonConstAndAllInBetween) {
|
||||
|
||||
ASSERT_EQ(group.size(), 1u);
|
||||
|
||||
static_assert(std::is_same_v<decltype(group.get<0>({})), int &>);
|
||||
static_assert(std::is_same_v<decltype(group.get<int>({})), int &>);
|
||||
|
||||
static_assert(std::is_same_v<decltype(group.get<1>({})), void>);
|
||||
static_assert(std::is_same_v<decltype(group.get<empty_type>({})), void>);
|
||||
|
||||
static_assert(std::is_same_v<decltype(group.get<2>({})), const char &>);
|
||||
static_assert(std::is_same_v<decltype(group.get<const char>({})), const char &>);
|
||||
static_assert(std::is_same_v<decltype(group.get<int, const char>({})), std::tuple<int &, const char &>>);
|
||||
|
||||
static_assert(std::is_same_v<decltype(group.get<int, empty_type, const char>({})), std::tuple<int &, const char &>>);
|
||||
static_assert(std::is_same_v<decltype(group.get<0, 1, 2>({})), std::tuple<int &, const char &>>);
|
||||
|
||||
static_assert(std::is_same_v<decltype(group.get({})), std::tuple<int &, const char &>>);
|
||||
|
||||
static_assert(std::is_same_v<decltype(std::as_const(registry).group_if_exists(entt::get<int, char>)), decltype(std::as_const(registry).group_if_exists(entt::get<const int, const char>))>);
|
||||
@ -475,7 +488,7 @@ TEST(NonOwningGroup, ExcludedComponents) {
|
||||
if(entity == e0) {
|
||||
ASSERT_EQ(group.get<int>(e0), 0);
|
||||
} else if(entity == e2) {
|
||||
ASSERT_EQ(group.get<int>(e2), 2);
|
||||
ASSERT_EQ(group.get<0>(e2), 2);
|
||||
}
|
||||
}
|
||||
|
||||
@ -493,7 +506,7 @@ TEST(NonOwningGroup, ExcludedComponents) {
|
||||
if(entity == e1) {
|
||||
ASSERT_EQ(group.get<int>(e1), 1);
|
||||
} else if(entity == e3) {
|
||||
ASSERT_EQ(group.get<int>(e3), 3);
|
||||
ASSERT_EQ(group.get<0>(e3), 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -663,28 +676,100 @@ TEST(NonOwningGroup, IterableGroupAlgorithmCompatibility) {
|
||||
TEST(NonOwningGroup, Storage) {
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
const auto group = registry.group(entt::get<int, const char>);
|
||||
auto group = registry.group(entt::get<int, const char>, entt::exclude<double, const float>);
|
||||
|
||||
static_assert(std::is_same_v<decltype(group.storage<0u>()), entt::storage_type_t<int> &>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<int>()), entt::storage_type_t<int> &>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<1u>()), const entt::storage_type_t<char> &>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<const char>()), const entt::storage_type_t<char> &>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<0u>()), entt::storage_type_t<int> *>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<int>()), entt::storage_type_t<int> *>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<const int>()), entt::storage_type_t<int> *>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<1u>()), const entt::storage_type_t<char> *>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<char>()), const entt::storage_type_t<char> *>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<const char>()), const entt::storage_type_t<char> *>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<2u>()), entt::storage_type_t<double> *>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<double>()), entt::storage_type_t<double> *>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<const double>()), entt::storage_type_t<double> *>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<3u>()), const entt::storage_type_t<float> *>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<float>()), const entt::storage_type_t<float> *>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<const float>()), const entt::storage_type_t<float> *>);
|
||||
|
||||
ASSERT_TRUE(group);
|
||||
|
||||
ASSERT_NE(group.storage<int>(), nullptr);
|
||||
ASSERT_NE(group.storage<1u>(), nullptr);
|
||||
ASSERT_NE(group.storage<double>(), nullptr);
|
||||
ASSERT_NE(group.storage<3u>(), nullptr);
|
||||
|
||||
ASSERT_EQ(group.size(), 0u);
|
||||
|
||||
group.storage<int>().emplace(entity);
|
||||
group.storage<int>()->emplace(entity);
|
||||
group.storage<double>()->emplace(entity);
|
||||
registry.emplace<char>(entity);
|
||||
registry.emplace<float>(entity);
|
||||
|
||||
ASSERT_EQ(group.size(), 0u);
|
||||
ASSERT_EQ(group.begin(), group.end());
|
||||
ASSERT_TRUE(group.storage<int>()->contains(entity));
|
||||
ASSERT_TRUE(group.storage<const char>()->contains(entity));
|
||||
ASSERT_TRUE(group.storage<double>()->contains(entity));
|
||||
ASSERT_TRUE(group.storage<const float>()->contains(entity));
|
||||
ASSERT_TRUE((registry.all_of<int, char, double, float>(entity)));
|
||||
|
||||
group.storage<double>()->erase(entity);
|
||||
registry.erase<float>(entity);
|
||||
|
||||
ASSERT_EQ(group.size(), 1u);
|
||||
ASSERT_TRUE(group.storage<int>().contains(entity));
|
||||
ASSERT_TRUE(group.storage<const char>().contains(entity));
|
||||
ASSERT_NE(group.begin(), group.end());
|
||||
ASSERT_TRUE(group.storage<const int>()->contains(entity));
|
||||
ASSERT_TRUE(group.storage<char>()->contains(entity));
|
||||
ASSERT_FALSE(group.storage<const double>()->contains(entity));
|
||||
ASSERT_FALSE(group.storage<float>()->contains(entity));
|
||||
ASSERT_TRUE((registry.all_of<int, char>(entity)));
|
||||
ASSERT_FALSE((registry.any_of<double, float>(entity)));
|
||||
|
||||
group.storage<0u>().erase(entity);
|
||||
group.storage<0u>()->erase(entity);
|
||||
|
||||
ASSERT_EQ(group.size(), 0u);
|
||||
ASSERT_TRUE(group.storage<1u>().contains(entity));
|
||||
ASSERT_FALSE((registry.all_of<int, char>(entity)));
|
||||
ASSERT_EQ(group.begin(), group.end());
|
||||
ASSERT_FALSE(group.storage<0u>()->contains(entity));
|
||||
ASSERT_TRUE(group.storage<1u>()->contains(entity));
|
||||
ASSERT_FALSE(group.storage<2u>()->contains(entity));
|
||||
ASSERT_FALSE(group.storage<3u>()->contains(entity));
|
||||
ASSERT_TRUE((registry.all_of<char>(entity)));
|
||||
ASSERT_FALSE((registry.any_of<int, double, float>(entity)));
|
||||
|
||||
group = {};
|
||||
|
||||
ASSERT_FALSE(group);
|
||||
|
||||
ASSERT_EQ(group.storage<0u>(), nullptr);
|
||||
ASSERT_EQ(group.storage<const char>(), nullptr);
|
||||
ASSERT_EQ(group.storage<2u>(), nullptr);
|
||||
ASSERT_EQ(group.storage<const float>(), nullptr);
|
||||
}
|
||||
|
||||
TEST(NonOwningGroup, Overlapping) {
|
||||
entt::registry registry;
|
||||
|
||||
auto group = registry.group(entt::get<char>, entt::exclude<double>);
|
||||
auto other = registry.group<int>(entt::get<char>, entt::exclude<double>);
|
||||
|
||||
ASSERT_TRUE(group.empty());
|
||||
ASSERT_TRUE(other.empty());
|
||||
|
||||
const auto entity = registry.create();
|
||||
registry.emplace<char>(entity, '1');
|
||||
|
||||
ASSERT_FALSE(group.empty());
|
||||
ASSERT_TRUE(other.empty());
|
||||
|
||||
registry.emplace<int>(entity, 42);
|
||||
|
||||
ASSERT_FALSE(group.empty());
|
||||
ASSERT_FALSE(other.empty());
|
||||
|
||||
registry.emplace<double>(entity, 3.);
|
||||
|
||||
ASSERT_TRUE(group.empty());
|
||||
ASSERT_TRUE(other.empty());
|
||||
}
|
||||
|
||||
TEST(OwningGroup, Functionalities) {
|
||||
@ -721,16 +806,17 @@ TEST(OwningGroup, Functionalities) {
|
||||
|
||||
ASSERT_EQ(group.size(), 1u);
|
||||
|
||||
ASSERT_EQ(cgroup.storage<const int>().raw()[0u][0u], 42);
|
||||
ASSERT_EQ(group.storage<int>().raw()[0u][0u], 42);
|
||||
ASSERT_EQ(cgroup.storage<const int>()->raw()[0u][0u], 42);
|
||||
ASSERT_EQ(group.storage<int>()->raw()[0u][0u], 42);
|
||||
|
||||
for(auto entity: group) {
|
||||
ASSERT_EQ(std::get<0>(cgroup.get<const int, const char>(entity)), 42);
|
||||
ASSERT_EQ(std::get<1>(group.get<int, char>(entity)), '2');
|
||||
ASSERT_EQ(cgroup.get<const char>(entity), '2');
|
||||
ASSERT_EQ(cgroup.get<1>(entity), '2');
|
||||
}
|
||||
|
||||
ASSERT_EQ(group.storage<int>().raw()[0u][0u], 42);
|
||||
ASSERT_EQ(group.handle().data()[0u], e1);
|
||||
ASSERT_EQ(group.storage<int>()->raw()[0u][0u], 42);
|
||||
|
||||
registry.erase<char>(e0);
|
||||
registry.erase<char>(e1);
|
||||
@ -748,6 +834,26 @@ TEST(OwningGroup, Functionalities) {
|
||||
ASSERT_FALSE(invalid);
|
||||
}
|
||||
|
||||
TEST(OwningGroup, Handle) {
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
|
||||
auto group = registry.group<int>(entt::get<char>);
|
||||
auto &&handle = group.handle();
|
||||
|
||||
ASSERT_TRUE(handle.empty());
|
||||
ASSERT_FALSE(handle.contains(entity));
|
||||
ASSERT_EQ(&handle, &group.handle());
|
||||
ASSERT_EQ(&handle, group.storage<int>());
|
||||
|
||||
registry.emplace<int>(entity);
|
||||
registry.emplace<char>(entity);
|
||||
|
||||
ASSERT_FALSE(handle.empty());
|
||||
ASSERT_TRUE(handle.contains(entity));
|
||||
ASSERT_EQ(&handle, &group.handle());
|
||||
}
|
||||
|
||||
TEST(OwningGroup, Invalid) {
|
||||
entt::registry registry{};
|
||||
auto group = std::as_const(registry).group_if_exists<const int>(entt::get<const empty_type>);
|
||||
@ -831,10 +937,10 @@ TEST(OwningGroup, Each) {
|
||||
auto cgroup = std::as_const(registry).group_if_exists<const int>(entt::get<const char>);
|
||||
|
||||
registry.emplace<int>(entity[0u], 0);
|
||||
registry.emplace<char>(entity[0u], 0);
|
||||
registry.emplace<char>(entity[0u], static_cast<char>(0));
|
||||
|
||||
registry.emplace<int>(entity[1u], 1);
|
||||
registry.emplace<char>(entity[1u], 1);
|
||||
registry.emplace<char>(entity[1u], static_cast<char>(1));
|
||||
|
||||
auto iterable = group.each();
|
||||
auto citerable = cgroup.each();
|
||||
@ -845,16 +951,18 @@ TEST(OwningGroup, Each) {
|
||||
|
||||
auto it = iterable.begin();
|
||||
|
||||
ASSERT_EQ(it.base(), group.begin());
|
||||
ASSERT_EQ((it++, ++it), iterable.end());
|
||||
ASSERT_EQ(it.base(), group.end());
|
||||
|
||||
group.each([expected = 1u](auto entt, int &ivalue, char &cvalue) mutable {
|
||||
ASSERT_EQ(entt::to_integral(entt), expected);
|
||||
group.each([expected = 1](auto entt, int &ivalue, char &cvalue) mutable {
|
||||
ASSERT_EQ(static_cast<int>(entt::to_integral(entt)), expected);
|
||||
ASSERT_EQ(ivalue, expected);
|
||||
ASSERT_EQ(cvalue, expected);
|
||||
--expected;
|
||||
});
|
||||
|
||||
cgroup.each([expected = 1u](const int &ivalue, const char &cvalue) mutable {
|
||||
cgroup.each([expected = 1](const int &ivalue, const char &cvalue) mutable {
|
||||
ASSERT_EQ(ivalue, expected);
|
||||
ASSERT_EQ(cvalue, expected);
|
||||
--expected;
|
||||
@ -868,8 +976,8 @@ TEST(OwningGroup, Each) {
|
||||
|
||||
// do not use iterable, make sure an iterable group works when created from a temporary
|
||||
for(auto [entt, ivalue, cvalue]: registry.group<int>(entt::get<char>).each()) {
|
||||
ASSERT_EQ(entt::to_integral(entt), ivalue);
|
||||
ASSERT_EQ(entt::to_integral(entt), cvalue);
|
||||
ASSERT_EQ(static_cast<int>(entt::to_integral(entt)), ivalue);
|
||||
ASSERT_EQ(static_cast<char>(entt::to_integral(entt)), cvalue);
|
||||
}
|
||||
}
|
||||
|
||||
@ -893,27 +1001,27 @@ TEST(OwningGroup, SortOrdered) {
|
||||
registry.emplace<boxed_int>(entities[4], 2);
|
||||
|
||||
group.sort([&group](const entt::entity lhs, const entt::entity rhs) {
|
||||
return group.get<boxed_int>(lhs).value < group.get<boxed_int>(rhs).value;
|
||||
return group.get<boxed_int>(lhs).value < group.get<0>(rhs).value;
|
||||
});
|
||||
|
||||
ASSERT_EQ(group.storage<boxed_int>().data()[0u], entities[0]);
|
||||
ASSERT_EQ(group.storage<boxed_int>().data()[1u], entities[1]);
|
||||
ASSERT_EQ(group.storage<boxed_int>().data()[2u], entities[2]);
|
||||
ASSERT_EQ(group.storage<boxed_int>().data()[3u], entities[3]);
|
||||
ASSERT_EQ(group.storage<boxed_int>().data()[4u], entities[4]);
|
||||
ASSERT_EQ(group.handle().data()[0u], entities[0]);
|
||||
ASSERT_EQ(group.handle().data()[1u], entities[1]);
|
||||
ASSERT_EQ(group.handle().data()[2u], entities[2]);
|
||||
ASSERT_EQ(group.handle().data()[3u], entities[3]);
|
||||
ASSERT_EQ(group.handle().data()[4u], entities[4]);
|
||||
|
||||
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][0u].value, 12);
|
||||
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][1u].value, 9);
|
||||
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][2u].value, 6);
|
||||
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][3u].value, 1);
|
||||
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][4u].value, 2);
|
||||
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][0u].value, 12);
|
||||
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][1u].value, 9);
|
||||
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][2u].value, 6);
|
||||
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][3u].value, 1);
|
||||
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][4u].value, 2);
|
||||
|
||||
ASSERT_EQ(group.storage<char>().raw()[0u][0u], 'a');
|
||||
ASSERT_EQ(group.storage<char>().raw()[0u][1u], 'b');
|
||||
ASSERT_EQ(group.storage<char>().raw()[0u][2u], 'c');
|
||||
ASSERT_EQ(group.storage<char>()->raw()[0u][0u], 'a');
|
||||
ASSERT_EQ(group.storage<char>()->raw()[0u][1u], 'b');
|
||||
ASSERT_EQ(group.storage<char>()->raw()[0u][2u], 'c');
|
||||
|
||||
ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{12}, 'a')));
|
||||
ASSERT_EQ((group.get<boxed_int, char>(entities[1])), (std::make_tuple(boxed_int{9}, 'b')));
|
||||
ASSERT_EQ((group.get<0, 1>(entities[1])), (std::make_tuple(boxed_int{9}, 'b')));
|
||||
ASSERT_EQ((group.get<boxed_int, char>(entities[2])), (std::make_tuple(boxed_int{6}, 'c')));
|
||||
|
||||
ASSERT_FALSE(group.contains(entities[3]));
|
||||
@ -943,24 +1051,24 @@ TEST(OwningGroup, SortReverse) {
|
||||
return lhs.value < rhs.value;
|
||||
});
|
||||
|
||||
ASSERT_EQ(group.storage<boxed_int>().data()[0u], entities[2]);
|
||||
ASSERT_EQ(group.storage<boxed_int>().data()[1u], entities[1]);
|
||||
ASSERT_EQ(group.storage<boxed_int>().data()[2u], entities[0]);
|
||||
ASSERT_EQ(group.storage<boxed_int>().data()[3u], entities[3]);
|
||||
ASSERT_EQ(group.storage<boxed_int>().data()[4u], entities[4]);
|
||||
ASSERT_EQ(group.handle().data()[0u], entities[2]);
|
||||
ASSERT_EQ(group.handle().data()[1u], entities[1]);
|
||||
ASSERT_EQ(group.handle().data()[2u], entities[0]);
|
||||
ASSERT_EQ(group.handle().data()[3u], entities[3]);
|
||||
ASSERT_EQ(group.handle().data()[4u], entities[4]);
|
||||
|
||||
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][0u].value, 12);
|
||||
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][1u].value, 9);
|
||||
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][2u].value, 6);
|
||||
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][3u].value, 1);
|
||||
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][4u].value, 2);
|
||||
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][0u].value, 12);
|
||||
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][1u].value, 9);
|
||||
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][2u].value, 6);
|
||||
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][3u].value, 1);
|
||||
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][4u].value, 2);
|
||||
|
||||
ASSERT_EQ(group.storage<char>().raw()[0u][0u], 'c');
|
||||
ASSERT_EQ(group.storage<char>().raw()[0u][1u], 'b');
|
||||
ASSERT_EQ(group.storage<char>().raw()[0u][2u], 'a');
|
||||
ASSERT_EQ(group.storage<char>()->raw()[0u][0u], 'c');
|
||||
ASSERT_EQ(group.storage<char>()->raw()[0u][1u], 'b');
|
||||
ASSERT_EQ(group.storage<char>()->raw()[0u][2u], 'a');
|
||||
|
||||
ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{6}, 'a')));
|
||||
ASSERT_EQ((group.get<boxed_int, char>(entities[1])), (std::make_tuple(boxed_int{9}, 'b')));
|
||||
ASSERT_EQ((group.get<0, 1>(entities[1])), (std::make_tuple(boxed_int{9}, 'b')));
|
||||
ASSERT_EQ((group.get<boxed_int, char>(entities[2])), (std::make_tuple(boxed_int{12}, 'c')));
|
||||
|
||||
ASSERT_FALSE(group.contains(entities[3]));
|
||||
@ -998,27 +1106,27 @@ TEST(OwningGroup, SortUnordered) {
|
||||
return std::get<1>(lhs) < std::get<1>(rhs);
|
||||
});
|
||||
|
||||
ASSERT_EQ(group.storage<boxed_int>().data()[0u], entities[4]);
|
||||
ASSERT_EQ(group.storage<boxed_int>().data()[1u], entities[3]);
|
||||
ASSERT_EQ(group.storage<boxed_int>().data()[2u], entities[0]);
|
||||
ASSERT_EQ(group.storage<boxed_int>().data()[3u], entities[1]);
|
||||
ASSERT_EQ(group.storage<boxed_int>().data()[4u], entities[2]);
|
||||
ASSERT_EQ(group.storage<boxed_int>().data()[5u], entities[5]);
|
||||
ASSERT_EQ(group.storage<boxed_int>().data()[6u], entities[6]);
|
||||
ASSERT_EQ(group.handle().data()[0u], entities[4]);
|
||||
ASSERT_EQ(group.handle().data()[1u], entities[3]);
|
||||
ASSERT_EQ(group.handle().data()[2u], entities[0]);
|
||||
ASSERT_EQ(group.handle().data()[3u], entities[1]);
|
||||
ASSERT_EQ(group.handle().data()[4u], entities[2]);
|
||||
ASSERT_EQ(group.handle().data()[5u], entities[5]);
|
||||
ASSERT_EQ(group.handle().data()[6u], entities[6]);
|
||||
|
||||
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][0u].value, 12);
|
||||
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][1u].value, 9);
|
||||
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][2u].value, 6);
|
||||
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][3u].value, 3);
|
||||
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][4u].value, 1);
|
||||
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][5u].value, 4);
|
||||
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][6u].value, 5);
|
||||
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][0u].value, 12);
|
||||
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][1u].value, 9);
|
||||
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][2u].value, 6);
|
||||
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][3u].value, 3);
|
||||
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][4u].value, 1);
|
||||
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][5u].value, 4);
|
||||
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][6u].value, 5);
|
||||
|
||||
ASSERT_EQ(group.get<char>(group.storage<boxed_int>().data()[0u]), 'e');
|
||||
ASSERT_EQ(group.get<char>(group.storage<boxed_int>().data()[1u]), 'd');
|
||||
ASSERT_EQ(group.get<char>(group.storage<boxed_int>().data()[2u]), 'c');
|
||||
ASSERT_EQ(group.get<char>(group.storage<boxed_int>().data()[3u]), 'b');
|
||||
ASSERT_EQ(group.get<char>(group.storage<boxed_int>().data()[4u]), 'a');
|
||||
ASSERT_EQ(group.get<char>(group.handle().data()[0u]), 'e');
|
||||
ASSERT_EQ(group.get<1>(group.handle().data()[1u]), 'd');
|
||||
ASSERT_EQ(group.get<char>(group.handle().data()[2u]), 'c');
|
||||
ASSERT_EQ(group.get<1>(group.handle().data()[3u]), 'b');
|
||||
ASSERT_EQ(group.get<char>(group.handle().data()[4u]), 'a');
|
||||
|
||||
ASSERT_FALSE(group.contains(entities[5]));
|
||||
ASSERT_FALSE(group.contains(entities[6]));
|
||||
@ -1026,7 +1134,7 @@ TEST(OwningGroup, SortUnordered) {
|
||||
|
||||
TEST(OwningGroup, SortWithExclusionList) {
|
||||
entt::registry registry;
|
||||
auto group = registry.group<boxed_int>({}, entt::exclude<char>);
|
||||
auto group = registry.group<boxed_int>(entt::get<>, entt::exclude<char>);
|
||||
|
||||
entt::entity entities[5]{};
|
||||
registry.create(std::begin(entities), std::end(entities));
|
||||
@ -1043,20 +1151,20 @@ TEST(OwningGroup, SortWithExclusionList) {
|
||||
return lhs < rhs;
|
||||
});
|
||||
|
||||
ASSERT_EQ(group.storage<boxed_int>().data()[0u], entities[4]);
|
||||
ASSERT_EQ(group.storage<boxed_int>().data()[1u], entities[3]);
|
||||
ASSERT_EQ(group.storage<boxed_int>().data()[2u], entities[1]);
|
||||
ASSERT_EQ(group.storage<boxed_int>().data()[3u], entities[0]);
|
||||
ASSERT_EQ(group.handle().data()[0u], entities[4]);
|
||||
ASSERT_EQ(group.handle().data()[1u], entities[3]);
|
||||
ASSERT_EQ(group.handle().data()[2u], entities[1]);
|
||||
ASSERT_EQ(group.handle().data()[3u], entities[0]);
|
||||
|
||||
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][0u].value, 4);
|
||||
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][1u].value, 3);
|
||||
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][2u].value, 1);
|
||||
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][3u].value, 0);
|
||||
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][0u].value, 4);
|
||||
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][1u].value, 3);
|
||||
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][2u].value, 1);
|
||||
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][3u].value, 0);
|
||||
|
||||
ASSERT_EQ(group.get<boxed_int>(entities[0]).value, 0);
|
||||
ASSERT_EQ(group.get<boxed_int>(entities[1]).value, 1);
|
||||
ASSERT_EQ(group.get<0>(entities[1]).value, 1);
|
||||
ASSERT_EQ(group.get<boxed_int>(entities[3]).value, 3);
|
||||
ASSERT_EQ(group.get<boxed_int>(entities[4]).value, 4);
|
||||
ASSERT_EQ(group.get<0>(entities[4]).value, 4);
|
||||
|
||||
ASSERT_FALSE(group.contains(entities[2]));
|
||||
}
|
||||
@ -1110,11 +1218,24 @@ TEST(OwningGroup, ConstNonConstAndAllInBetween) {
|
||||
|
||||
ASSERT_EQ(group.size(), 1u);
|
||||
|
||||
static_assert(std::is_same_v<decltype(group.get<0>({})), int &>);
|
||||
static_assert(std::is_same_v<decltype(group.get<int>({})), int &>);
|
||||
|
||||
static_assert(std::is_same_v<decltype(group.get<1>({})), const char &>);
|
||||
static_assert(std::is_same_v<decltype(group.get<const char>({})), const char &>);
|
||||
|
||||
static_assert(std::is_same_v<decltype(group.get<2>({})), void>);
|
||||
static_assert(std::is_same_v<decltype(group.get<empty_type>({})), void>);
|
||||
|
||||
static_assert(std::is_same_v<decltype(group.get<3>({})), double &>);
|
||||
static_assert(std::is_same_v<decltype(group.get<double>({})), double &>);
|
||||
|
||||
static_assert(std::is_same_v<decltype(group.get<4>({})), const float &>);
|
||||
static_assert(std::is_same_v<decltype(group.get<const float>({})), const float &>);
|
||||
static_assert(std::is_same_v<decltype(group.get<int, const char, double, const float>({})), std::tuple<int &, const char &, double &, const float &>>);
|
||||
|
||||
static_assert(std::is_same_v<decltype(group.get<int, const char, empty_type, double, const float>({})), std::tuple<int &, const char &, double &, const float &>>);
|
||||
static_assert(std::is_same_v<decltype(group.get<0, 1, 2, 3, 4>({})), std::tuple<int &, const char &, double &, const float &>>);
|
||||
|
||||
static_assert(std::is_same_v<decltype(group.get({})), std::tuple<int &, const char &, double &, const float &>>);
|
||||
|
||||
static_assert(std::is_same_v<decltype(std::as_const(registry).group_if_exists<int>(entt::get<char>)), decltype(std::as_const(registry).group_if_exists<const int>(entt::get<const char>))>);
|
||||
@ -1192,7 +1313,7 @@ TEST(OwningGroup, ExcludedComponents) {
|
||||
registry.emplace<int>(e1, 1);
|
||||
registry.emplace<char>(e1);
|
||||
|
||||
const auto group = registry.group<int>({}, entt::exclude<char, double>);
|
||||
const auto group = registry.group<int>(entt::get<>, entt::exclude<char, double>);
|
||||
|
||||
const auto e2 = registry.create();
|
||||
registry.emplace<int>(e2, 2);
|
||||
@ -1207,7 +1328,7 @@ TEST(OwningGroup, ExcludedComponents) {
|
||||
if(entity == e0) {
|
||||
ASSERT_EQ(group.get<int>(e0), 0);
|
||||
} else if(entity == e2) {
|
||||
ASSERT_EQ(group.get<int>(e2), 2);
|
||||
ASSERT_EQ(group.get<0>(e2), 2);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1225,7 +1346,7 @@ TEST(OwningGroup, ExcludedComponents) {
|
||||
if(entity == e1) {
|
||||
ASSERT_EQ(group.get<int>(e1), 1);
|
||||
} else if(entity == e3) {
|
||||
ASSERT_EQ(group.get<int>(e3), 3);
|
||||
ASSERT_EQ(group.get<0>(e3), 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1263,8 +1384,8 @@ TEST(OwningGroup, EmptyAndNonEmptyTypes) {
|
||||
|
||||
TEST(OwningGroup, TrackEntitiesOnComponentDestruction) {
|
||||
entt::registry registry;
|
||||
const auto group = registry.group<int>({}, entt::exclude<char>);
|
||||
const auto cgroup = std::as_const(registry).group_if_exists<const int>({}, entt::exclude<char>);
|
||||
const auto group = registry.group<int>(entt::get<>, entt::exclude<char>);
|
||||
const auto cgroup = std::as_const(registry).group_if_exists<const int>(entt::get<>, entt::exclude<char>);
|
||||
|
||||
const auto entity = registry.create();
|
||||
registry.emplace<int>(entity);
|
||||
@ -1403,7 +1524,7 @@ TEST(OwningGroup, SwappingValuesIsAllowed) {
|
||||
|
||||
// thanks to @andranik3949 for pointing out this missing test
|
||||
registry.view<const boxed_int>().each([](const auto entity, const auto &value) {
|
||||
ASSERT_EQ(entt::to_integral(entity), value.value);
|
||||
ASSERT_EQ(static_cast<int>(entt::to_integral(entity)), value.value);
|
||||
});
|
||||
}
|
||||
|
||||
@ -1443,26 +1564,81 @@ TEST(OwningGroup, IterableGroupAlgorithmCompatibility) {
|
||||
TEST(OwningGroup, Storage) {
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
const auto group = registry.group<int>(entt::get<const char>);
|
||||
auto group = registry.group<int>(entt::get<const char>, entt::exclude<double, const float>);
|
||||
|
||||
static_assert(std::is_same_v<decltype(group.storage<0u>()), entt::storage_type_t<int> &>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<int>()), entt::storage_type_t<int> &>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<1u>()), const entt::storage_type_t<char> &>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<const char>()), const entt::storage_type_t<char> &>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<0u>()), entt::storage_type_t<int> *>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<int>()), entt::storage_type_t<int> *>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<const int>()), entt::storage_type_t<int> *>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<1u>()), const entt::storage_type_t<char> *>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<char>()), const entt::storage_type_t<char> *>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<const char>()), const entt::storage_type_t<char> *>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<2u>()), entt::storage_type_t<double> *>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<double>()), entt::storage_type_t<double> *>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<const double>()), entt::storage_type_t<double> *>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<3u>()), const entt::storage_type_t<float> *>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<float>()), const entt::storage_type_t<float> *>);
|
||||
static_assert(std::is_same_v<decltype(group.storage<const float>()), const entt::storage_type_t<float> *>);
|
||||
|
||||
ASSERT_TRUE(group);
|
||||
|
||||
ASSERT_NE(group.storage<int>(), nullptr);
|
||||
ASSERT_NE(group.storage<1u>(), nullptr);
|
||||
ASSERT_NE(group.storage<double>(), nullptr);
|
||||
ASSERT_NE(group.storage<3u>(), nullptr);
|
||||
|
||||
ASSERT_EQ(group.size(), 0u);
|
||||
|
||||
group.storage<int>().emplace(entity);
|
||||
group.storage<int>()->emplace(entity);
|
||||
group.storage<double>()->emplace(entity);
|
||||
registry.emplace<char>(entity);
|
||||
registry.emplace<float>(entity);
|
||||
|
||||
ASSERT_EQ(group.size(), 0u);
|
||||
ASSERT_EQ(group.begin(), group.end());
|
||||
ASSERT_TRUE(group.storage<int>()->contains(entity));
|
||||
ASSERT_TRUE(group.storage<const char>()->contains(entity));
|
||||
ASSERT_TRUE(group.storage<double>()->contains(entity));
|
||||
ASSERT_TRUE(group.storage<const float>()->contains(entity));
|
||||
ASSERT_TRUE((registry.all_of<int, char, double, float>(entity)));
|
||||
|
||||
group.storage<double>()->erase(entity);
|
||||
registry.erase<float>(entity);
|
||||
|
||||
ASSERT_EQ(group.size(), 1u);
|
||||
ASSERT_TRUE(group.storage<int>().contains(entity));
|
||||
ASSERT_TRUE(group.storage<const char>().contains(entity));
|
||||
ASSERT_NE(group.begin(), group.end());
|
||||
ASSERT_TRUE(group.storage<const int>()->contains(entity));
|
||||
ASSERT_TRUE(group.storage<char>()->contains(entity));
|
||||
ASSERT_FALSE(group.storage<const double>()->contains(entity));
|
||||
ASSERT_FALSE(group.storage<float>()->contains(entity));
|
||||
ASSERT_TRUE((registry.all_of<int, char>(entity)));
|
||||
ASSERT_FALSE((registry.any_of<double, float>(entity)));
|
||||
|
||||
group.storage<0u>().erase(entity);
|
||||
group.storage<0u>()->erase(entity);
|
||||
|
||||
ASSERT_EQ(group.size(), 0u);
|
||||
ASSERT_TRUE(group.storage<1u>().contains(entity));
|
||||
ASSERT_FALSE((registry.all_of<int, char>(entity)));
|
||||
ASSERT_EQ(group.begin(), group.end());
|
||||
ASSERT_FALSE(group.storage<0u>()->contains(entity));
|
||||
ASSERT_TRUE(group.storage<1u>()->contains(entity));
|
||||
ASSERT_FALSE(group.storage<2u>()->contains(entity));
|
||||
ASSERT_FALSE(group.storage<3u>()->contains(entity));
|
||||
ASSERT_TRUE((registry.all_of<char>(entity)));
|
||||
ASSERT_FALSE((registry.any_of<int, double, float>(entity)));
|
||||
|
||||
group = {};
|
||||
|
||||
ASSERT_FALSE(group);
|
||||
|
||||
ASSERT_EQ(group.storage<0u>(), nullptr);
|
||||
ASSERT_EQ(group.storage<const char>(), nullptr);
|
||||
ASSERT_EQ(group.storage<2u>(), nullptr);
|
||||
ASSERT_EQ(group.storage<const float>(), nullptr);
|
||||
}
|
||||
|
||||
ENTT_DEBUG_TEST(OwningGroupDeathTest, Overlapping) {
|
||||
entt::registry registry;
|
||||
registry.group<char>(entt::get<int>, entt::exclude<double>);
|
||||
|
||||
ASSERT_DEATH((registry.group<char, float>(entt::get<float>, entt::exclude<double>)), "");
|
||||
ASSERT_DEATH(registry.group<char>(entt::get<int, float>, entt::exclude<double>), "");
|
||||
ASSERT_DEATH(registry.group<char>(entt::get<int>, entt::exclude<double, float>), "");
|
||||
}
|
||||
|
@ -83,8 +83,8 @@ TEST(BasicHandle, Destruction) {
|
||||
ASSERT_FALSE(handle);
|
||||
ASSERT_FALSE(handle.valid());
|
||||
ASSERT_NE(handle.registry(), nullptr);
|
||||
ASSERT_EQ(handle.entity(), entity);
|
||||
ASSERT_EQ(registry.current(entity), typename entt::registry::version_type{});
|
||||
ASSERT_EQ(handle.entity(), entt::entity{entt::null});
|
||||
|
||||
handle = entt::handle{registry, registry.create()};
|
||||
|
||||
@ -98,8 +98,8 @@ TEST(BasicHandle, Destruction) {
|
||||
ASSERT_FALSE(handle);
|
||||
ASSERT_FALSE(handle.valid());
|
||||
ASSERT_NE(handle.registry(), nullptr);
|
||||
ASSERT_EQ(handle.entity(), entity);
|
||||
ASSERT_NE(registry.current(entity), typename entt::registry::version_type{});
|
||||
ASSERT_EQ(handle.entity(), entt::entity{entt::null});
|
||||
}
|
||||
|
||||
TEST(BasicHandle, Comparison) {
|
||||
@ -275,6 +275,8 @@ TEST(BasicHandle, HandleStorageIterator) {
|
||||
|
||||
registry.emplace<int>(entity);
|
||||
registry.emplace<double>(entity);
|
||||
// required to test the find-first initialization step
|
||||
registry.storage<entt::entity>().erase(entity);
|
||||
|
||||
auto test = [](auto iterable) {
|
||||
auto end{iterable.begin()};
|
||||
@ -290,6 +292,13 @@ TEST(BasicHandle, HandleStorageIterator) {
|
||||
ASSERT_EQ(++begin, iterable.end());
|
||||
};
|
||||
|
||||
test(entt::handle{registry, entity}.storage());
|
||||
test(entt::const_handle{std::as_const(registry), entity}.storage());
|
||||
const auto handle = entt::handle{registry, entity};
|
||||
const auto chandle = entt::const_handle{std::as_const(registry), entity};
|
||||
|
||||
ASSERT_FALSE(registry.valid(entity));
|
||||
ASSERT_FALSE(handle);
|
||||
ASSERT_FALSE(chandle);
|
||||
|
||||
test(handle.storage());
|
||||
test(chandle.storage());
|
||||
}
|
||||
|
@ -16,6 +16,10 @@ struct stable_type {
|
||||
int value;
|
||||
};
|
||||
|
||||
void sigh_callback(int &value) {
|
||||
++value;
|
||||
}
|
||||
|
||||
TEST(Helper, AsView) {
|
||||
entt::registry registry;
|
||||
const entt::registry cregistry;
|
||||
@ -48,7 +52,7 @@ TEST(Helper, Invoke) {
|
||||
TEST(Helper, ToEntity) {
|
||||
entt::registry registry;
|
||||
const entt::entity null = entt::null;
|
||||
constexpr auto page_size = entt::component_traits<int>::page_size;
|
||||
constexpr auto page_size = entt::storage_type_t<int>::traits_type::page_size;
|
||||
const int value = 42;
|
||||
|
||||
ASSERT_EQ(entt::to_entity(registry, 42), null);
|
||||
@ -88,7 +92,7 @@ TEST(Helper, ToEntity) {
|
||||
TEST(Helper, ToEntityStableType) {
|
||||
entt::registry registry;
|
||||
const entt::entity null = entt::null;
|
||||
constexpr auto page_size = entt::component_traits<stable_type>::page_size;
|
||||
constexpr auto page_size = entt::storage_type_t<stable_type>::traits_type::page_size;
|
||||
const stable_type value{42};
|
||||
|
||||
ASSERT_EQ(entt::to_entity(registry, stable_type{42}), null);
|
||||
@ -124,3 +128,44 @@ TEST(Helper, ToEntityStableType) {
|
||||
ASSERT_EQ(entt::to_entity(registry, stable_type{42}), null);
|
||||
ASSERT_EQ(entt::to_entity(registry, value), null);
|
||||
}
|
||||
|
||||
TEST(Helper, SighHelper) {
|
||||
using namespace entt::literals;
|
||||
|
||||
entt::registry registry{};
|
||||
const auto entt = registry.create();
|
||||
entt::sigh_helper helper{registry};
|
||||
int counter{};
|
||||
|
||||
ASSERT_EQ(&helper.registry(), ®istry);
|
||||
|
||||
helper.with<int>()
|
||||
.on_construct<&sigh_callback>(counter)
|
||||
.on_update<&sigh_callback>(counter)
|
||||
.on_destroy<&sigh_callback>(counter);
|
||||
|
||||
ASSERT_EQ(counter, 0);
|
||||
|
||||
registry.emplace<int>(entt);
|
||||
registry.replace<int>(entt);
|
||||
registry.erase<int>(entt);
|
||||
|
||||
ASSERT_EQ(counter, 3);
|
||||
|
||||
helper.with<double>("other"_hs)
|
||||
.on_construct<&sigh_callback>(counter)
|
||||
.on_update<&sigh_callback>(counter)
|
||||
.on_destroy<&sigh_callback>(counter);
|
||||
|
||||
registry.emplace<double>(entt);
|
||||
registry.replace<double>(entt);
|
||||
registry.erase<double>(entt);
|
||||
|
||||
ASSERT_EQ(counter, 3);
|
||||
|
||||
registry.storage<double>("other"_hs).emplace(entt);
|
||||
registry.storage<double>("other"_hs).patch(entt);
|
||||
registry.storage<double>("other"_hs).erase(entt);
|
||||
|
||||
ASSERT_EQ(counter, 6);
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/type_info.hpp>
|
||||
#include <entt/entity/organizer.hpp>
|
||||
#include <entt/entity/registry.hpp>
|
||||
|
||||
@ -20,7 +22,7 @@ struct clazz {
|
||||
static void ro_int_char_with_payload(clazz &, entt::view<entt::get_t<const int, const char>>) {}
|
||||
};
|
||||
|
||||
void to_args_integrity(entt::view<entt::get_t<int>> view, std::size_t &value, entt::registry ®istry) {
|
||||
void to_args_integrity(entt::view<entt::get_t<int>> view, std::size_t &value, entt::registry &) {
|
||||
value = view.size();
|
||||
}
|
||||
|
||||
@ -363,6 +365,10 @@ TEST(Organizer, Prepare) {
|
||||
ASSERT_FALSE(registry.ctx().contains<char>());
|
||||
ASSERT_FALSE(registry.ctx().contains<double>());
|
||||
|
||||
ASSERT_EQ(std::as_const(registry).storage<int>(), nullptr);
|
||||
ASSERT_EQ(std::as_const(registry).storage<char>(), nullptr);
|
||||
ASSERT_EQ(std::as_const(registry).storage<double>(), nullptr);
|
||||
|
||||
for(auto &&vertex: graph) {
|
||||
vertex.prepare(registry);
|
||||
}
|
||||
@ -370,6 +376,10 @@ TEST(Organizer, Prepare) {
|
||||
ASSERT_FALSE(registry.ctx().contains<int>());
|
||||
ASSERT_FALSE(registry.ctx().contains<char>());
|
||||
ASSERT_TRUE(registry.ctx().contains<double>());
|
||||
|
||||
ASSERT_NE(std::as_const(registry).storage<int>(), nullptr);
|
||||
ASSERT_NE(std::as_const(registry).storage<char>(), nullptr);
|
||||
ASSERT_EQ(std::as_const(registry).storage<double>(), nullptr);
|
||||
}
|
||||
|
||||
TEST(Organizer, Dependencies) {
|
||||
@ -428,5 +438,5 @@ TEST(Organizer, ToArgsIntegrity) {
|
||||
auto graph = organizer.graph();
|
||||
graph[0u].callback()(graph[0u].data(), registry);
|
||||
|
||||
ASSERT_EQ(registry.ctx().at<std::size_t>(), 0u);
|
||||
ASSERT_EQ(registry.ctx().get<std::size_t>(), 0u);
|
||||
}
|
||||
|
@ -40,18 +40,16 @@ struct aggregate {
|
||||
};
|
||||
|
||||
struct listener {
|
||||
template<typename Component>
|
||||
template<typename Type>
|
||||
static void sort(entt::registry ®istry) {
|
||||
registry.sort<Component>([](auto lhs, auto rhs) { return lhs < rhs; });
|
||||
registry.sort<Type>([](auto lhs, auto rhs) { return lhs < rhs; });
|
||||
}
|
||||
|
||||
template<typename Component>
|
||||
void incr(const entt::registry &, entt::entity entity) {
|
||||
last = entity;
|
||||
++counter;
|
||||
}
|
||||
|
||||
template<typename Component>
|
||||
void decr(const entt::registry &, entt::entity entity) {
|
||||
last = entity;
|
||||
--counter;
|
||||
@ -75,11 +73,11 @@ struct destruction_order {
|
||||
destruction_order(const entt::registry &ref, bool &ctx)
|
||||
: registry{&ref},
|
||||
ctx_check{&ctx} {
|
||||
*ctx_check = (registry->ctx().find<int>() != nullptr);
|
||||
*ctx_check = (registry->ctx().find<ctx_check_type>() != nullptr);
|
||||
}
|
||||
|
||||
~destruction_order() {
|
||||
*ctx_check = *ctx_check && (registry->ctx().find<int>() != nullptr);
|
||||
*ctx_check = *ctx_check && (registry->ctx().find<ctx_check_type>() != nullptr);
|
||||
}
|
||||
|
||||
private:
|
||||
@ -87,6 +85,22 @@ private:
|
||||
bool *ctx_check{};
|
||||
};
|
||||
|
||||
enum class small_entity : std::uint32_t {};
|
||||
|
||||
struct small_entity_traits {
|
||||
using value_type = small_entity;
|
||||
using entity_type = uint32_t;
|
||||
using version_type = uint16_t;
|
||||
static constexpr entity_type entity_mask = 0xFF;
|
||||
static constexpr entity_type version_mask = 0x00;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct entt::entt_traits<small_entity>: entt::basic_entt_traits<small_entity_traits> {
|
||||
using base_type = entt::basic_entt_traits<small_entity_traits>;
|
||||
static constexpr auto page_size = ENTT_SPARSE_PAGE;
|
||||
};
|
||||
|
||||
TEST(Registry, Context) {
|
||||
entt::registry registry;
|
||||
auto &ctx = registry.ctx();
|
||||
@ -126,8 +140,8 @@ TEST(Registry, Context) {
|
||||
|
||||
ASSERT_EQ(ctx.emplace<const int>(0), 42);
|
||||
ASSERT_EQ(ctx.find<const int>(), cctx.find<int>());
|
||||
ASSERT_EQ(ctx.at<int>(), cctx.at<const int>());
|
||||
ASSERT_EQ(ctx.at<int>(), 42);
|
||||
ASSERT_EQ(ctx.get<int>(), cctx.get<const int>());
|
||||
ASSERT_EQ(ctx.get<int>(), 42);
|
||||
|
||||
ASSERT_EQ(ctx.find<double>(), nullptr);
|
||||
ASSERT_EQ(cctx.find<double>(), nullptr);
|
||||
@ -139,8 +153,8 @@ TEST(Registry, Context) {
|
||||
|
||||
ASSERT_EQ(ctx.insert_or_assign<const int>(0), 0);
|
||||
ASSERT_EQ(ctx.find<const int>(), cctx.find<int>());
|
||||
ASSERT_EQ(ctx.at<int>(), cctx.at<const int>());
|
||||
ASSERT_EQ(ctx.at<int>(), 0);
|
||||
ASSERT_EQ(ctx.get<int>(), cctx.get<const int>());
|
||||
ASSERT_EQ(ctx.get<int>(), 0);
|
||||
}
|
||||
|
||||
TEST(Registry, ContextHint) {
|
||||
@ -376,13 +390,12 @@ TEST(Registry, Functionalities) {
|
||||
TEST(Registry, Constructors) {
|
||||
entt::registry registry;
|
||||
entt::registry other{42};
|
||||
const entt::entity entity = entt::tombstone;
|
||||
|
||||
ASSERT_TRUE(registry.empty());
|
||||
ASSERT_TRUE(other.empty());
|
||||
|
||||
ASSERT_EQ(registry.released(), entity);
|
||||
ASSERT_EQ(other.released(), entity);
|
||||
ASSERT_EQ(registry.released(), 0u);
|
||||
ASSERT_EQ(other.released(), 0u);
|
||||
}
|
||||
|
||||
TEST(Registry, Move) {
|
||||
@ -486,6 +499,8 @@ TEST(Registry, Identifiers) {
|
||||
}
|
||||
|
||||
TEST(Registry, Data) {
|
||||
using traits_type = entt::entt_traits<entt::entity>;
|
||||
|
||||
entt::registry registry;
|
||||
|
||||
ASSERT_EQ(std::as_const(registry).data(), nullptr);
|
||||
@ -497,8 +512,8 @@ TEST(Registry, Data) {
|
||||
const auto other = registry.create();
|
||||
registry.release(entity);
|
||||
|
||||
ASSERT_NE(*std::as_const(registry).data(), entity);
|
||||
ASSERT_EQ(*(std::as_const(registry).data() + 1u), other);
|
||||
ASSERT_EQ(*std::as_const(registry).data(), other);
|
||||
ASSERT_EQ(*(std::as_const(registry).data() + 1u), traits_type::next(entity));
|
||||
}
|
||||
|
||||
TEST(Registry, CreateManyEntitiesAtOnce) {
|
||||
@ -533,7 +548,7 @@ TEST(Registry, CreateManyEntitiesAtOnceWithListener) {
|
||||
entt::entity entities[3];
|
||||
listener listener;
|
||||
|
||||
registry.on_construct<int>().connect<&listener::incr<int>>(listener);
|
||||
registry.on_construct<int>().connect<&listener::incr>(listener);
|
||||
registry.create(std::begin(entities), std::end(entities));
|
||||
registry.insert(std::begin(entities), std::end(entities), 42);
|
||||
registry.insert(std::begin(entities), std::end(entities), 'c');
|
||||
@ -542,8 +557,8 @@ TEST(Registry, CreateManyEntitiesAtOnceWithListener) {
|
||||
ASSERT_EQ(registry.get<char>(entities[1]), 'c');
|
||||
ASSERT_EQ(listener.counter, 3);
|
||||
|
||||
registry.on_construct<int>().disconnect<&listener::incr<int>>(listener);
|
||||
registry.on_construct<empty_type>().connect<&listener::incr<empty_type>>(listener);
|
||||
registry.on_construct<int>().disconnect<&listener::incr>(listener);
|
||||
registry.on_construct<empty_type>().connect<&listener::incr>(listener);
|
||||
registry.create(std::begin(entities), std::end(entities));
|
||||
registry.insert(std::begin(entities), std::end(entities), 'a');
|
||||
registry.insert<empty_type>(std::begin(entities), std::end(entities));
|
||||
@ -560,8 +575,9 @@ TEST(Registry, CreateWithHint) {
|
||||
auto e3 = registry.create(entt::entity{3});
|
||||
auto e2 = registry.create(entt::entity{3});
|
||||
|
||||
ASSERT_EQ(e2, entt::entity{2});
|
||||
ASSERT_FALSE(registry.valid(entt::entity{1}));
|
||||
ASSERT_EQ(e2, entt::entity{1});
|
||||
ASSERT_FALSE(registry.valid(entt::entity{0}));
|
||||
ASSERT_FALSE(registry.valid(entt::entity{2}));
|
||||
ASSERT_EQ(e3, entt::entity{3});
|
||||
|
||||
registry.release(e2);
|
||||
@ -572,10 +588,10 @@ TEST(Registry, CreateWithHint) {
|
||||
e2 = registry.create();
|
||||
auto e1 = registry.create(entt::entity{2});
|
||||
|
||||
ASSERT_EQ(traits_type::to_entity(e2), 2u);
|
||||
ASSERT_EQ(traits_type::to_entity(e2), 1u);
|
||||
ASSERT_EQ(traits_type::to_version(e2), 1u);
|
||||
|
||||
ASSERT_EQ(traits_type::to_entity(e1), 1u);
|
||||
ASSERT_EQ(traits_type::to_entity(e1), 2u);
|
||||
ASSERT_EQ(traits_type::to_version(e1), 0u);
|
||||
|
||||
registry.release(e1);
|
||||
@ -640,6 +656,14 @@ TEST(Registry, CreateDestroyReleaseCornerCase) {
|
||||
ASSERT_EQ(registry.current(e1), 1u);
|
||||
}
|
||||
|
||||
ENTT_DEBUG_TEST(RegistryDeathTest, CreateTooManyEntities) {
|
||||
entt::basic_registry<small_entity> registry;
|
||||
std::vector<small_entity> entities(entt::entt_traits<small_entity>::to_entity(entt::null));
|
||||
registry.create(entities.begin(), entities.end());
|
||||
|
||||
ASSERT_DEATH([[maybe_unused]] const auto entity = registry.create(), "");
|
||||
}
|
||||
|
||||
TEST(Registry, DestroyVersion) {
|
||||
entt::registry registry;
|
||||
|
||||
@ -666,7 +690,7 @@ ENTT_DEBUG_TEST(RegistryDeathTest, DestroyVersion) {
|
||||
ASSERT_DEATH(registry.destroy(entity, 3), "");
|
||||
}
|
||||
|
||||
TEST(Registry, RangeDestroy) {
|
||||
TEST(Registry, DestroyRange) {
|
||||
entt::registry registry;
|
||||
const auto iview = registry.view<int>();
|
||||
const auto icview = registry.view<int, char>();
|
||||
@ -722,6 +746,24 @@ TEST(Registry, RangeDestroy) {
|
||||
ASSERT_FALSE(registry.valid(entities[1u]));
|
||||
ASSERT_FALSE(registry.valid(entities[2u]));
|
||||
ASSERT_EQ(registry.storage<int>().size(), 0u);
|
||||
|
||||
entt::sparse_set managed{};
|
||||
|
||||
registry.create(std::begin(entities), std::end(entities));
|
||||
managed.push(std::begin(entities), std::end(entities));
|
||||
registry.insert<int>(managed.begin(), managed.end());
|
||||
|
||||
ASSERT_TRUE(registry.valid(managed[0u]));
|
||||
ASSERT_TRUE(registry.valid(managed[1u]));
|
||||
ASSERT_TRUE(registry.valid(managed[2u]));
|
||||
ASSERT_EQ(registry.storage<int>().size(), 3u);
|
||||
|
||||
registry.destroy(managed.begin(), managed.end());
|
||||
|
||||
ASSERT_FALSE(registry.valid(managed[0u]));
|
||||
ASSERT_FALSE(registry.valid(managed[1u]));
|
||||
ASSERT_FALSE(registry.valid(managed[2u]));
|
||||
ASSERT_EQ(registry.storage<int>().size(), 0u);
|
||||
}
|
||||
|
||||
TEST(Registry, StableDestroy) {
|
||||
@ -792,7 +834,7 @@ ENTT_DEBUG_TEST(RegistryDeathTest, ReleaseVersion) {
|
||||
ASSERT_DEATH(registry.release(entity, 3), "");
|
||||
}
|
||||
|
||||
TEST(Registry, RangeRelease) {
|
||||
TEST(Registry, ReleaseRange) {
|
||||
entt::registry registry;
|
||||
entt::entity entities[3u];
|
||||
|
||||
@ -930,10 +972,12 @@ TEST(Registry, Orphans) {
|
||||
|
||||
TEST(Registry, View) {
|
||||
entt::registry registry;
|
||||
auto mview = registry.view<int, char>();
|
||||
entt::entity entities[3u];
|
||||
|
||||
auto iview = registry.view<int>();
|
||||
auto cview = registry.view<char>();
|
||||
entt::entity entities[3u];
|
||||
auto mview = registry.view<int, char>();
|
||||
auto fview = registry.view<int>(entt::exclude<char>);
|
||||
|
||||
registry.create(std::begin(entities), std::end(entities));
|
||||
|
||||
@ -948,10 +992,55 @@ TEST(Registry, View) {
|
||||
ASSERT_EQ(iview.size(), 3u);
|
||||
ASSERT_EQ(cview.size(), 2u);
|
||||
|
||||
std::size_t cnt{};
|
||||
mview.each([&cnt](auto...) { ++cnt; });
|
||||
ASSERT_EQ(mview.size_hint(), 3u);
|
||||
ASSERT_EQ(fview.size_hint(), 3u);
|
||||
|
||||
ASSERT_EQ(cnt, 2u);
|
||||
mview.refresh();
|
||||
fview.refresh();
|
||||
|
||||
ASSERT_EQ(mview.size_hint(), 2u);
|
||||
ASSERT_EQ(fview.size_hint(), 3u);
|
||||
|
||||
ASSERT_NE(mview.begin(), mview.end());
|
||||
ASSERT_NE(fview.begin(), fview.end());
|
||||
|
||||
ASSERT_EQ(std::distance(mview.begin(), mview.end()), 2);
|
||||
ASSERT_EQ(std::distance(fview.begin(), fview.end()), 1);
|
||||
|
||||
mview.each([&entities, first = true](auto entity, auto &&...) mutable {
|
||||
ASSERT_EQ(entity, entities[2u * first]);
|
||||
first = false;
|
||||
});
|
||||
|
||||
fview.each([&entities](auto entity, auto &&...) {
|
||||
ASSERT_EQ(entity, entities[1u]);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Registry, ExcludeOnlyView) {
|
||||
entt::registry registry;
|
||||
entt::entity entities[4u];
|
||||
|
||||
auto view = registry.view<entt::entity>(entt::exclude<int>);
|
||||
|
||||
registry.create(std::begin(entities), std::end(entities));
|
||||
|
||||
registry.emplace<int>(entities[0u], 0);
|
||||
registry.emplace<int>(entities[2u], 0);
|
||||
registry.emplace<int>(entities[3u], 0);
|
||||
|
||||
registry.destroy(entities[3u]);
|
||||
|
||||
ASSERT_EQ(view.size_hint(), 4u);
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
|
||||
// returns all matching identifiers, both in-use and available ones
|
||||
ASSERT_EQ(std::distance(view.begin(), view.end()), 2);
|
||||
|
||||
// skips available identifiers automatically, only returns in-use elements
|
||||
view.each([&entities](auto entity, auto &&...) {
|
||||
ASSERT_EQ(entity, entities[1u]);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Registry, NonOwningGroupInitOnFirstUse) {
|
||||
@ -964,7 +1053,7 @@ TEST(Registry, NonOwningGroupInitOnFirstUse) {
|
||||
registry.emplace<char>(entities[2u], 'c');
|
||||
|
||||
std::size_t cnt{};
|
||||
auto group = registry.group<>(entt::get<int, char>);
|
||||
auto group = registry.group(entt::get<int, char>);
|
||||
group.each([&cnt](auto...) { ++cnt; });
|
||||
|
||||
ASSERT_FALSE((registry.owned<int, char>()));
|
||||
@ -974,7 +1063,7 @@ TEST(Registry, NonOwningGroupInitOnFirstUse) {
|
||||
TEST(Registry, NonOwningGroupInitOnEmplace) {
|
||||
entt::registry registry;
|
||||
entt::entity entities[3u];
|
||||
auto group = registry.group<>(entt::get<int, char>);
|
||||
auto group = registry.group(entt::get<int, char>);
|
||||
|
||||
registry.create(std::begin(entities), std::end(entities));
|
||||
registry.insert<int>(std::begin(entities), std::end(entities), 0);
|
||||
@ -1097,7 +1186,7 @@ TEST(Registry, CleanViewAfterRemoveAndClear) {
|
||||
|
||||
TEST(Registry, CleanNonOwningGroupViewAfterRemoveAndClear) {
|
||||
entt::registry registry;
|
||||
auto group = registry.group<>(entt::get<int, char>);
|
||||
auto group = registry.group(entt::get<int, char>);
|
||||
|
||||
const auto entity = registry.create();
|
||||
registry.emplace<int>(entity, 0);
|
||||
@ -1188,107 +1277,21 @@ TEST(Registry, CleanPartialOwningGroupViewAfterRemoveAndClear) {
|
||||
ASSERT_EQ(group.size(), 0u);
|
||||
}
|
||||
|
||||
TEST(Registry, NestedGroups) {
|
||||
ENTT_DEBUG_TEST(RegistryDeathTest, NestedGroups) {
|
||||
entt::registry registry;
|
||||
entt::entity entities[10];
|
||||
registry.group<int, double>(entt::get<char>);
|
||||
|
||||
registry.create(std::begin(entities), std::end(entities));
|
||||
registry.insert<int>(std::begin(entities), std::end(entities));
|
||||
registry.insert<char>(std::begin(entities), std::end(entities));
|
||||
const auto g1 = registry.group<int>(entt::get<char>, entt::exclude<double>);
|
||||
ASSERT_DEATH(registry.group<int>(entt::get<char>), "");
|
||||
ASSERT_DEATH(registry.group<int>(entt::get<char, double>), "");
|
||||
ASSERT_DEATH(registry.group<int>(entt::get<char>, entt::exclude<double>), "");
|
||||
ASSERT_DEATH((registry.group<int, double>()), "");
|
||||
}
|
||||
|
||||
ASSERT_TRUE(registry.sortable(g1));
|
||||
ASSERT_EQ(g1.size(), 10u);
|
||||
ENTT_DEBUG_TEST(RegistryDeathTest, ConflictingGroups) {
|
||||
entt::registry registry;
|
||||
|
||||
const auto g2 = registry.group<int>(entt::get<char>);
|
||||
|
||||
ASSERT_TRUE(registry.sortable(g1));
|
||||
ASSERT_FALSE(registry.sortable(g2));
|
||||
ASSERT_EQ(g1.size(), 10u);
|
||||
ASSERT_EQ(g2.size(), 10u);
|
||||
|
||||
for(auto i = 0u; i < 5u; ++i) {
|
||||
ASSERT_TRUE(g1.contains(entities[i * 2 + 1]));
|
||||
ASSERT_TRUE(g1.contains(entities[i * 2]));
|
||||
ASSERT_TRUE(g2.contains(entities[i * 2 + 1]));
|
||||
ASSERT_TRUE(g2.contains(entities[i * 2]));
|
||||
registry.emplace<double>(entities[i * 2]);
|
||||
}
|
||||
|
||||
ASSERT_EQ(g1.size(), 5u);
|
||||
ASSERT_EQ(g2.size(), 10u);
|
||||
|
||||
for(auto i = 0u; i < 5u; ++i) {
|
||||
ASSERT_TRUE(g1.contains(entities[i * 2 + 1]));
|
||||
ASSERT_FALSE(g1.contains(entities[i * 2]));
|
||||
ASSERT_TRUE(g2.contains(entities[i * 2 + 1]));
|
||||
ASSERT_TRUE(g2.contains(entities[i * 2]));
|
||||
registry.erase<int>(entities[i * 2 + 1]);
|
||||
}
|
||||
|
||||
ASSERT_EQ(g1.size(), 0u);
|
||||
ASSERT_EQ(g2.size(), 5u);
|
||||
|
||||
const auto g3 = registry.group<int, float>(entt::get<char>, entt::exclude<double>);
|
||||
|
||||
ASSERT_FALSE(registry.sortable(g1));
|
||||
ASSERT_FALSE(registry.sortable(g2));
|
||||
ASSERT_TRUE(registry.sortable(g3));
|
||||
|
||||
ASSERT_EQ(g1.size(), 0u);
|
||||
ASSERT_EQ(g2.size(), 5u);
|
||||
ASSERT_EQ(g3.size(), 0u);
|
||||
|
||||
for(auto i = 0u; i < 5u; ++i) {
|
||||
ASSERT_FALSE(g1.contains(entities[i * 2 + 1]));
|
||||
ASSERT_FALSE(g1.contains(entities[i * 2]));
|
||||
ASSERT_FALSE(g2.contains(entities[i * 2 + 1]));
|
||||
ASSERT_TRUE(g2.contains(entities[i * 2]));
|
||||
ASSERT_FALSE(g3.contains(entities[i * 2 + 1]));
|
||||
ASSERT_FALSE(g3.contains(entities[i * 2]));
|
||||
registry.emplace<int>(entities[i * 2 + 1]);
|
||||
}
|
||||
|
||||
ASSERT_EQ(g1.size(), 5u);
|
||||
ASSERT_EQ(g2.size(), 10u);
|
||||
ASSERT_EQ(g3.size(), 0u);
|
||||
|
||||
for(auto i = 0u; i < 5u; ++i) {
|
||||
ASSERT_TRUE(g1.contains(entities[i * 2 + 1]));
|
||||
ASSERT_FALSE(g1.contains(entities[i * 2]));
|
||||
ASSERT_TRUE(g2.contains(entities[i * 2 + 1]));
|
||||
ASSERT_TRUE(g2.contains(entities[i * 2]));
|
||||
ASSERT_FALSE(g3.contains(entities[i * 2 + 1]));
|
||||
ASSERT_FALSE(g3.contains(entities[i * 2]));
|
||||
registry.emplace<float>(entities[i * 2]);
|
||||
}
|
||||
|
||||
ASSERT_EQ(g1.size(), 5u);
|
||||
ASSERT_EQ(g2.size(), 10u);
|
||||
ASSERT_EQ(g3.size(), 0u);
|
||||
|
||||
for(auto i = 0u; i < 5u; ++i) {
|
||||
registry.erase<double>(entities[i * 2]);
|
||||
}
|
||||
|
||||
ASSERT_EQ(g1.size(), 10u);
|
||||
ASSERT_EQ(g2.size(), 10u);
|
||||
ASSERT_EQ(g3.size(), 5u);
|
||||
|
||||
for(auto i = 0u; i < 5u; ++i) {
|
||||
ASSERT_TRUE(g1.contains(entities[i * 2 + 1]));
|
||||
ASSERT_TRUE(g1.contains(entities[i * 2]));
|
||||
ASSERT_TRUE(g2.contains(entities[i * 2 + 1]));
|
||||
ASSERT_TRUE(g2.contains(entities[i * 2]));
|
||||
ASSERT_FALSE(g3.contains(entities[i * 2 + 1]));
|
||||
ASSERT_TRUE(g3.contains(entities[i * 2]));
|
||||
registry.erase<int>(entities[i * 2 + 1]);
|
||||
registry.erase<int>(entities[i * 2]);
|
||||
}
|
||||
|
||||
ASSERT_EQ(g1.size(), 0u);
|
||||
ASSERT_EQ(g2.size(), 0u);
|
||||
ASSERT_EQ(g3.size(), 0u);
|
||||
registry.group<char>(entt::get<int>, entt::exclude<double>);
|
||||
ASSERT_DEATH(registry.group<char>(entt::get<float>, entt::exclude<double>), "");
|
||||
}
|
||||
|
||||
TEST(Registry, SortSingle) {
|
||||
@ -1379,10 +1382,10 @@ TEST(Registry, Signals) {
|
||||
entt::entity entities[2u];
|
||||
listener listener;
|
||||
|
||||
registry.on_construct<empty_type>().connect<&listener::incr<empty_type>>(listener);
|
||||
registry.on_destroy<empty_type>().connect<&listener::decr<empty_type>>(listener);
|
||||
registry.on_construct<int>().connect<&listener::incr<int>>(listener);
|
||||
registry.on_destroy<int>().connect<&listener::decr<int>>(listener);
|
||||
registry.on_construct<empty_type>().connect<&listener::incr>(listener);
|
||||
registry.on_destroy<empty_type>().connect<&listener::decr>(listener);
|
||||
registry.on_construct<int>().connect<&listener::incr>(listener);
|
||||
registry.on_destroy<int>().connect<&listener::decr>(listener);
|
||||
|
||||
registry.create(std::begin(entities), std::end(entities));
|
||||
registry.insert<empty_type>(std::begin(entities), std::end(entities));
|
||||
@ -1400,16 +1403,16 @@ TEST(Registry, Signals) {
|
||||
ASSERT_EQ(listener.counter, 2);
|
||||
ASSERT_EQ(listener.last, entities[0u]);
|
||||
|
||||
registry.on_destroy<empty_type>().disconnect<&listener::decr<empty_type>>(listener);
|
||||
registry.on_destroy<int>().disconnect<&listener::decr<int>>(listener);
|
||||
registry.on_destroy<empty_type>().disconnect<&listener::decr>(listener);
|
||||
registry.on_destroy<int>().disconnect<&listener::decr>(listener);
|
||||
|
||||
registry.erase<empty_type, int>(entities[1u]);
|
||||
|
||||
ASSERT_EQ(listener.counter, 2);
|
||||
ASSERT_EQ(listener.last, entities[0u]);
|
||||
|
||||
registry.on_construct<empty_type>().disconnect<&listener::incr<empty_type>>(listener);
|
||||
registry.on_construct<int>().disconnect<&listener::incr<int>>(listener);
|
||||
registry.on_construct<empty_type>().disconnect<&listener::incr>(listener);
|
||||
registry.on_construct<int>().disconnect<&listener::incr>(listener);
|
||||
|
||||
registry.emplace<empty_type>(entities[1u]);
|
||||
registry.emplace<int>(entities[1u]);
|
||||
@ -1417,8 +1420,8 @@ TEST(Registry, Signals) {
|
||||
ASSERT_EQ(listener.counter, 2);
|
||||
ASSERT_EQ(listener.last, entities[0u]);
|
||||
|
||||
registry.on_construct<int>().connect<&listener::incr<int>>(listener);
|
||||
registry.on_destroy<int>().connect<&listener::decr<int>>(listener);
|
||||
registry.on_construct<int>().connect<&listener::incr>(listener);
|
||||
registry.on_destroy<int>().connect<&listener::decr>(listener);
|
||||
|
||||
registry.emplace<int>(entities[0u]);
|
||||
registry.erase<int>(entities[1u]);
|
||||
@ -1426,8 +1429,8 @@ TEST(Registry, Signals) {
|
||||
ASSERT_EQ(listener.counter, 2);
|
||||
ASSERT_EQ(listener.last, entities[1u]);
|
||||
|
||||
registry.on_construct<empty_type>().connect<&listener::incr<empty_type>>(listener);
|
||||
registry.on_destroy<empty_type>().connect<&listener::decr<empty_type>>(listener);
|
||||
registry.on_construct<empty_type>().connect<&listener::incr>(listener);
|
||||
registry.on_destroy<empty_type>().connect<&listener::decr>(listener);
|
||||
|
||||
registry.erase<empty_type>(entities[1u]);
|
||||
registry.emplace<empty_type>(entities[0u]);
|
||||
@ -1454,8 +1457,8 @@ TEST(Registry, Signals) {
|
||||
ASSERT_EQ(listener.counter, 2);
|
||||
ASSERT_EQ(listener.last, entities[0u]);
|
||||
|
||||
registry.on_destroy<empty_type>().disconnect<&listener::decr<empty_type>>(listener);
|
||||
registry.on_destroy<int>().disconnect<&listener::decr<int>>(listener);
|
||||
registry.on_destroy<empty_type>().disconnect<&listener::decr>(listener);
|
||||
registry.on_destroy<int>().disconnect<&listener::decr>(listener);
|
||||
|
||||
registry.emplace_or_replace<empty_type>(entities[0u]);
|
||||
registry.emplace_or_replace<int>(entities[0u]);
|
||||
@ -1463,8 +1466,8 @@ TEST(Registry, Signals) {
|
||||
ASSERT_EQ(listener.counter, 2);
|
||||
ASSERT_EQ(listener.last, entities[0u]);
|
||||
|
||||
registry.on_update<empty_type>().connect<&listener::incr<empty_type>>(listener);
|
||||
registry.on_update<int>().connect<&listener::incr<int>>(listener);
|
||||
registry.on_update<empty_type>().connect<&listener::incr>(listener);
|
||||
registry.on_update<int>().connect<&listener::incr>(listener);
|
||||
|
||||
registry.emplace_or_replace<empty_type>(entities[0u]);
|
||||
registry.emplace_or_replace<int>(entities[0u]);
|
||||
@ -1479,6 +1482,84 @@ TEST(Registry, Signals) {
|
||||
ASSERT_EQ(listener.last, entities[0u]);
|
||||
}
|
||||
|
||||
TEST(Registry, SignalsOnRuntimePool) {
|
||||
using namespace entt::literals;
|
||||
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
listener listener;
|
||||
|
||||
registry.on_construct<int>("custom"_hs).connect<&listener::incr>(listener);
|
||||
registry.on_update<int>("custom"_hs).connect<&listener::incr>(listener);
|
||||
registry.on_destroy<int>("custom"_hs).connect<&listener::incr>(listener);
|
||||
|
||||
ASSERT_EQ(listener.counter, 0);
|
||||
|
||||
registry.emplace<int>(entity);
|
||||
registry.patch<int>(entity);
|
||||
registry.erase<int>(entity);
|
||||
|
||||
ASSERT_EQ(listener.counter, 0);
|
||||
|
||||
registry.storage<int>("custom"_hs).emplace(entity);
|
||||
registry.storage<int>("custom"_hs).patch(entity);
|
||||
registry.storage<int>("custom"_hs).erase(entity);
|
||||
|
||||
ASSERT_EQ(listener.counter, 3);
|
||||
}
|
||||
|
||||
TEST(Registry, SignalsOnEntity) {
|
||||
entt::registry registry;
|
||||
listener listener;
|
||||
|
||||
registry.on_construct<entt::entity>().connect<&listener::incr>(listener);
|
||||
|
||||
entt::entity entity = registry.create();
|
||||
entt::entity other = registry.create();
|
||||
|
||||
ASSERT_EQ(listener.counter, 2);
|
||||
ASSERT_EQ(listener.last, other);
|
||||
|
||||
registry.destroy(other);
|
||||
registry.destroy(entity);
|
||||
|
||||
ASSERT_EQ(listener.counter, 2);
|
||||
ASSERT_EQ(listener.last, other);
|
||||
|
||||
registry.on_construct<entt::entity>().disconnect(&listener);
|
||||
|
||||
other = registry.create();
|
||||
entity = registry.create();
|
||||
|
||||
ASSERT_EQ(listener.counter, 2);
|
||||
ASSERT_NE(listener.last, entity);
|
||||
ASSERT_NE(listener.last, other);
|
||||
|
||||
registry.on_update<entt::entity>().connect<&listener::decr>(listener);
|
||||
registry.patch<entt::entity>(entity);
|
||||
|
||||
ASSERT_EQ(listener.counter, 1);
|
||||
ASSERT_EQ(listener.last, entity);
|
||||
|
||||
registry.on_update<entt::entity>().disconnect(&listener);
|
||||
registry.patch<entt::entity>(other);
|
||||
|
||||
ASSERT_EQ(listener.counter, 1);
|
||||
ASSERT_NE(listener.last, other);
|
||||
|
||||
registry.on_destroy<entt::entity>().connect<&listener::decr>(listener);
|
||||
registry.destroy(entity);
|
||||
|
||||
ASSERT_EQ(listener.counter, 0);
|
||||
ASSERT_EQ(listener.last, entity);
|
||||
|
||||
registry.on_destroy<entt::entity>().disconnect(&listener);
|
||||
registry.destroy(other);
|
||||
|
||||
ASSERT_EQ(listener.counter, 0);
|
||||
ASSERT_NE(listener.last, other);
|
||||
}
|
||||
|
||||
TEST(Registry, SignalWhenDestroying) {
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
@ -1576,6 +1657,17 @@ TEST(Registry, Erase) {
|
||||
ASSERT_EQ(registry.storage<char>().size(), 0u);
|
||||
ASSERT_EQ(registry.storage<double>().size(), 1u);
|
||||
|
||||
registry.insert<int>(std::begin(entities) + 1, std::end(entities) - 1u);
|
||||
registry.insert<char>(std::begin(entities) + 1, std::end(entities) - 1u);
|
||||
|
||||
ASSERT_EQ(registry.storage<int>().size(), 1u);
|
||||
ASSERT_EQ(registry.storage<char>().size(), 1u);
|
||||
|
||||
registry.erase<int, char>(iview.begin(), iview.end());
|
||||
|
||||
ASSERT_EQ(registry.storage<int>().size(), 0u);
|
||||
ASSERT_EQ(registry.storage<char>().size(), 0u);
|
||||
|
||||
registry.insert<int>(std::begin(entities), std::end(entities));
|
||||
registry.insert<char>(std::begin(entities), std::end(entities));
|
||||
|
||||
@ -1643,6 +1735,39 @@ TEST(Registry, StableErase) {
|
||||
ASSERT_EQ(registry.storage<double>().size(), 1u);
|
||||
}
|
||||
|
||||
TEST(Registry, EraseIf) {
|
||||
using namespace entt::literals;
|
||||
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
|
||||
registry.emplace<int>(entity);
|
||||
registry.storage<int>("other"_hs).emplace(entity);
|
||||
registry.emplace<char>(entity);
|
||||
|
||||
ASSERT_TRUE(registry.storage<int>().contains(entity));
|
||||
ASSERT_TRUE(registry.storage<int>("other"_hs).contains(entity));
|
||||
ASSERT_TRUE(registry.storage<char>().contains(entity));
|
||||
|
||||
registry.erase_if(entity, [](auto &&...) { return false; });
|
||||
|
||||
ASSERT_TRUE(registry.storage<int>().contains(entity));
|
||||
ASSERT_TRUE(registry.storage<int>("other"_hs).contains(entity));
|
||||
ASSERT_TRUE(registry.storage<char>().contains(entity));
|
||||
|
||||
registry.erase_if(entity, [](entt::id_type id, auto &&...) { return id == "other"_hs; });
|
||||
|
||||
ASSERT_TRUE(registry.storage<int>().contains(entity));
|
||||
ASSERT_FALSE(registry.storage<int>("other"_hs).contains(entity));
|
||||
ASSERT_TRUE(registry.storage<char>().contains(entity));
|
||||
|
||||
registry.erase_if(entity, [](auto, const auto &storage) { return storage.type() == entt::type_id<char>(); });
|
||||
|
||||
ASSERT_TRUE(registry.storage<int>().contains(entity));
|
||||
ASSERT_FALSE(registry.storage<int>("other"_hs).contains(entity));
|
||||
ASSERT_FALSE(registry.storage<char>().contains(entity));
|
||||
}
|
||||
|
||||
TEST(Registry, Remove) {
|
||||
entt::registry registry;
|
||||
const auto iview = registry.view<int>();
|
||||
@ -1689,6 +1814,18 @@ TEST(Registry, Remove) {
|
||||
ASSERT_EQ(registry.storage<char>().size(), 0u);
|
||||
ASSERT_EQ(registry.storage<double>().size(), 1u);
|
||||
|
||||
registry.insert<int>(std::begin(entities) + 1, std::end(entities) - 1u);
|
||||
registry.insert<char>(std::begin(entities) + 1, std::end(entities) - 1u);
|
||||
|
||||
ASSERT_EQ(registry.storage<int>().size(), 1u);
|
||||
ASSERT_EQ(registry.storage<char>().size(), 1u);
|
||||
|
||||
registry.remove<int, char>(iview.begin(), iview.end());
|
||||
registry.remove<int, char>(iview.begin(), iview.end());
|
||||
|
||||
ASSERT_EQ(registry.storage<int>().size(), 0u);
|
||||
ASSERT_EQ(registry.storage<char>().size(), 0u);
|
||||
|
||||
registry.insert<int>(std::begin(entities), std::end(entities));
|
||||
registry.insert<char>(std::begin(entities), std::end(entities));
|
||||
|
||||
@ -1791,7 +1928,7 @@ TEST(Registry, NonOwningGroupInterleaved) {
|
||||
registry.emplace<int>(entity);
|
||||
registry.emplace<char>(entity);
|
||||
|
||||
const auto group = registry.group<>(entt::get<int, char>);
|
||||
const auto group = registry.group(entt::get<int, char>);
|
||||
|
||||
entity = registry.create();
|
||||
registry.emplace<int>(entity);
|
||||
@ -1845,7 +1982,7 @@ TEST(Registry, PartialOwningGroupInterleaved) {
|
||||
|
||||
TEST(Registry, NonOwningGroupSortInterleaved) {
|
||||
entt::registry registry;
|
||||
const auto group = registry.group<>(entt::get<int, char>);
|
||||
const auto group = registry.group(entt::get<int, char>);
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.emplace<int>(e0, 0);
|
||||
@ -2009,7 +2146,7 @@ TEST(Registry, ScramblingPoolsIsAllowed) {
|
||||
|
||||
// thanks to @andranik3949 for pointing out this missing test
|
||||
registry.view<const int>().each([](const auto entity, const auto &value) {
|
||||
ASSERT_EQ(entt::to_integral(entity), value);
|
||||
ASSERT_EQ(static_cast<int>(entt::to_integral(entity)), value);
|
||||
});
|
||||
}
|
||||
|
||||
@ -2021,7 +2158,7 @@ TEST(Registry, RuntimePools) {
|
||||
const auto entity = registry.create();
|
||||
|
||||
static_assert(std::is_same_v<decltype(registry.storage<empty_type>()), entt::storage_type_t<empty_type> &>);
|
||||
static_assert(std::is_same_v<decltype(std::as_const(registry).storage<empty_type>()), const entt::storage_type_t<empty_type> &>);
|
||||
static_assert(std::is_same_v<decltype(std::as_const(registry).storage<empty_type>()), const entt::storage_type_t<empty_type> *>);
|
||||
|
||||
static_assert(std::is_same_v<decltype(registry.storage("other"_hs)), entt::storage_type_t<empty_type>::base_type *>);
|
||||
static_assert(std::is_same_v<decltype(std::as_const(registry).storage("other"_hs)), const entt::storage_type_t<empty_type>::base_type *>);
|
||||
@ -2030,7 +2167,7 @@ TEST(Registry, RuntimePools) {
|
||||
ASSERT_EQ(std::as_const(registry).storage("rehto"_hs), nullptr);
|
||||
|
||||
ASSERT_EQ(®istry.storage<empty_type>("other"_hs), &storage);
|
||||
ASSERT_NE(&std::as_const(registry).storage<empty_type>(), &storage);
|
||||
ASSERT_NE(std::as_const(registry).storage<empty_type>(), &storage);
|
||||
|
||||
ASSERT_FALSE(registry.any_of<empty_type>(entity));
|
||||
ASSERT_FALSE(storage.contains(entity));
|
||||
@ -2070,6 +2207,7 @@ TEST(Registry, Storage) {
|
||||
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
|
||||
auto &storage = registry.storage<int>("int"_hs);
|
||||
storage.emplace(entity);
|
||||
|
||||
@ -2149,8 +2287,7 @@ TEST(Registry, RegistryStorageIterator) {
|
||||
|
||||
TEST(Registry, RegistryStorageIteratorConversion) {
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
registry.emplace<int>(entity);
|
||||
registry.storage<int>();
|
||||
|
||||
auto proxy = registry.storage();
|
||||
auto cproxy = std::as_const(registry).storage();
|
||||
@ -2176,6 +2313,22 @@ TEST(Registry, RegistryStorageIteratorConversion) {
|
||||
ASSERT_NE(++cit, it);
|
||||
}
|
||||
|
||||
TEST(Registry, VoidType) {
|
||||
using namespace entt::literals;
|
||||
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
auto &storage = registry.storage<void>("custom"_hs);
|
||||
storage.emplace(entity);
|
||||
|
||||
ASSERT_TRUE(registry.storage<void>().empty());
|
||||
ASSERT_FALSE(registry.storage<void>("custom"_hs).empty());
|
||||
ASSERT_TRUE(registry.storage<void>("custom"_hs).contains(entity));
|
||||
|
||||
ASSERT_EQ(registry.storage<void>().type(), entt::type_id<void>());
|
||||
ASSERT_EQ(registry.storage<void>("custom"_hs).type(), entt::type_id<void>());
|
||||
}
|
||||
|
||||
TEST(Registry, NoEtoType) {
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include <iterator>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/mixin.hpp>
|
||||
#include <entt/entity/registry.hpp>
|
||||
#include <entt/entity/storage.hpp>
|
||||
#include "../common/throwing_allocator.hpp"
|
||||
@ -30,9 +31,20 @@ void listener(counter &counter, Registry &, typename Registry::entity_type) {
|
||||
++counter.value;
|
||||
}
|
||||
|
||||
TEST(SighStorageMixin, GenericType) {
|
||||
struct empty_each_tag final {};
|
||||
|
||||
template<>
|
||||
struct entt::basic_storage<empty_each_tag, entt::entity, std::allocator<empty_each_tag>>: entt::basic_storage<void, entt::entity, std::allocator<void>> {
|
||||
basic_storage(const std::allocator<empty_each_tag> &) {}
|
||||
|
||||
[[nodiscard]] iterable each() noexcept {
|
||||
return {internal::extended_storage_iterator{base_type::end()}, internal::extended_storage_iterator{base_type::end()}};
|
||||
}
|
||||
};
|
||||
|
||||
TEST(SighMixin, GenericType) {
|
||||
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
|
||||
entt::sigh_storage_mixin<entt::storage<int>> pool;
|
||||
entt::sigh_mixin<entt::storage<int>> pool;
|
||||
entt::sparse_set &base = pool;
|
||||
entt::registry registry;
|
||||
|
||||
@ -40,10 +52,21 @@ TEST(SighStorageMixin, GenericType) {
|
||||
counter on_destroy{};
|
||||
|
||||
pool.bind(entt::forward_as_any(registry));
|
||||
|
||||
ASSERT_TRUE(pool.empty());
|
||||
|
||||
pool.insert(entities, entities + 1u);
|
||||
pool.erase(entities[0u]);
|
||||
|
||||
ASSERT_TRUE(pool.empty());
|
||||
|
||||
ASSERT_EQ(on_construct.value, 0);
|
||||
ASSERT_EQ(on_destroy.value, 0);
|
||||
|
||||
pool.on_construct().connect<&listener<entt::registry>>(on_construct);
|
||||
pool.on_destroy().connect<&listener<entt::registry>>(on_destroy);
|
||||
|
||||
ASSERT_NE(base.emplace(entities[0u]), base.end());
|
||||
ASSERT_NE(base.push(entities[0u]), base.end());
|
||||
|
||||
pool.emplace(entities[1u]);
|
||||
|
||||
@ -61,7 +84,7 @@ TEST(SighStorageMixin, GenericType) {
|
||||
ASSERT_EQ(on_destroy.value, 2);
|
||||
ASSERT_TRUE(pool.empty());
|
||||
|
||||
ASSERT_NE(base.insert(std::begin(entities), std::end(entities)), base.end());
|
||||
ASSERT_NE(base.push(std::begin(entities), std::end(entities)), base.end());
|
||||
|
||||
ASSERT_EQ(pool.get(entities[0u]), 0);
|
||||
ASSERT_EQ(pool.get(entities[1u]), 0);
|
||||
@ -95,9 +118,9 @@ TEST(SighStorageMixin, GenericType) {
|
||||
ASSERT_TRUE(pool.empty());
|
||||
}
|
||||
|
||||
TEST(SighStorageMixin, StableType) {
|
||||
TEST(SighMixin, StableType) {
|
||||
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
|
||||
entt::sigh_storage_mixin<entt::storage<stable_type>> pool;
|
||||
entt::sigh_mixin<entt::storage<stable_type>> pool;
|
||||
entt::sparse_set &base = pool;
|
||||
entt::registry registry;
|
||||
|
||||
@ -108,7 +131,7 @@ TEST(SighStorageMixin, StableType) {
|
||||
pool.on_construct().connect<&listener<entt::registry>>(on_construct);
|
||||
pool.on_destroy().connect<&listener<entt::registry>>(on_destroy);
|
||||
|
||||
ASSERT_NE(base.emplace(entities[0u]), base.end());
|
||||
ASSERT_NE(base.push(entities[0u]), base.end());
|
||||
|
||||
pool.emplace(entities[1u]);
|
||||
|
||||
@ -126,7 +149,7 @@ TEST(SighStorageMixin, StableType) {
|
||||
ASSERT_EQ(on_destroy.value, 2);
|
||||
ASSERT_FALSE(pool.empty());
|
||||
|
||||
ASSERT_NE(base.insert(std::begin(entities), std::end(entities)), base.end());
|
||||
ASSERT_NE(base.push(std::begin(entities), std::end(entities)), base.end());
|
||||
|
||||
ASSERT_EQ(pool.get(entities[0u]).value, 0);
|
||||
ASSERT_EQ(pool.get(entities[1u]).value, 0);
|
||||
@ -160,9 +183,9 @@ TEST(SighStorageMixin, StableType) {
|
||||
ASSERT_FALSE(pool.empty());
|
||||
}
|
||||
|
||||
TEST(SighStorageMixin, EmptyType) {
|
||||
TEST(SighMixin, EmptyType) {
|
||||
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
|
||||
entt::sigh_storage_mixin<entt::storage<empty_type>> pool;
|
||||
entt::sigh_mixin<entt::storage<empty_type>> pool;
|
||||
entt::sparse_set &base = pool;
|
||||
entt::registry registry;
|
||||
|
||||
@ -173,7 +196,7 @@ TEST(SighStorageMixin, EmptyType) {
|
||||
pool.on_construct().connect<&listener<entt::registry>>(on_construct);
|
||||
pool.on_destroy().connect<&listener<entt::registry>>(on_destroy);
|
||||
|
||||
ASSERT_NE(base.emplace(entities[0u]), base.end());
|
||||
ASSERT_NE(base.push(entities[0u]), base.end());
|
||||
|
||||
pool.emplace(entities[1u]);
|
||||
|
||||
@ -191,7 +214,7 @@ TEST(SighStorageMixin, EmptyType) {
|
||||
ASSERT_EQ(on_destroy.value, 2);
|
||||
ASSERT_TRUE(pool.empty());
|
||||
|
||||
ASSERT_NE(base.insert(std::begin(entities), std::end(entities)), base.end());
|
||||
ASSERT_NE(base.push(std::begin(entities), std::end(entities)), base.end());
|
||||
|
||||
ASSERT_TRUE(pool.contains(entities[0u]));
|
||||
ASSERT_TRUE(pool.contains(entities[1u]));
|
||||
@ -225,9 +248,9 @@ TEST(SighStorageMixin, EmptyType) {
|
||||
ASSERT_TRUE(pool.empty());
|
||||
}
|
||||
|
||||
TEST(SighStorageMixin, NonDefaultConstructibleType) {
|
||||
TEST(SighMixin, NonDefaultConstructibleType) {
|
||||
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
|
||||
entt::sigh_storage_mixin<entt::storage<non_default_constructible>> pool;
|
||||
entt::sigh_mixin<entt::storage<non_default_constructible>> pool;
|
||||
entt::sparse_set &base = pool;
|
||||
entt::registry registry;
|
||||
|
||||
@ -238,12 +261,12 @@ TEST(SighStorageMixin, NonDefaultConstructibleType) {
|
||||
pool.on_construct().connect<&listener<entt::registry>>(on_construct);
|
||||
pool.on_destroy().connect<&listener<entt::registry>>(on_destroy);
|
||||
|
||||
ASSERT_EQ(base.emplace(entities[0u]), base.end());
|
||||
ASSERT_EQ(base.push(entities[0u]), base.end());
|
||||
|
||||
pool.emplace(entities[1u], 3);
|
||||
|
||||
ASSERT_EQ(pool.size(), 1u);
|
||||
ASSERT_EQ(on_construct.value, 2);
|
||||
ASSERT_EQ(on_construct.value, 1);
|
||||
ASSERT_EQ(on_destroy.value, 0);
|
||||
ASSERT_FALSE(pool.empty());
|
||||
|
||||
@ -253,11 +276,11 @@ TEST(SighStorageMixin, NonDefaultConstructibleType) {
|
||||
base.erase(entities[1u]);
|
||||
|
||||
ASSERT_EQ(pool.size(), 0u);
|
||||
ASSERT_EQ(on_construct.value, 2);
|
||||
ASSERT_EQ(on_construct.value, 1);
|
||||
ASSERT_EQ(on_destroy.value, 1);
|
||||
ASSERT_TRUE(pool.empty());
|
||||
|
||||
ASSERT_EQ(base.insert(std::begin(entities), std::end(entities)), base.end());
|
||||
ASSERT_EQ(base.push(std::begin(entities), std::end(entities)), base.end());
|
||||
|
||||
ASSERT_FALSE(pool.contains(entities[0u]));
|
||||
ASSERT_FALSE(pool.contains(entities[1u]));
|
||||
@ -266,7 +289,7 @@ TEST(SighStorageMixin, NonDefaultConstructibleType) {
|
||||
pool.insert(std::begin(entities), std::end(entities), 3);
|
||||
|
||||
ASSERT_EQ(pool.size(), 2u);
|
||||
ASSERT_EQ(on_construct.value, 6);
|
||||
ASSERT_EQ(on_construct.value, 3);
|
||||
ASSERT_EQ(on_destroy.value, 1);
|
||||
ASSERT_FALSE(pool.empty());
|
||||
|
||||
@ -276,13 +299,13 @@ TEST(SighStorageMixin, NonDefaultConstructibleType) {
|
||||
pool.erase(std::begin(entities), std::end(entities));
|
||||
|
||||
ASSERT_EQ(pool.size(), 0u);
|
||||
ASSERT_EQ(on_construct.value, 6);
|
||||
ASSERT_EQ(on_construct.value, 3);
|
||||
ASSERT_EQ(on_destroy.value, 3);
|
||||
ASSERT_TRUE(pool.empty());
|
||||
}
|
||||
|
||||
TEST(SighStorageMixin, VoidType) {
|
||||
entt::sigh_storage_mixin<entt::storage<void>> pool;
|
||||
TEST(SighMixin, VoidType) {
|
||||
entt::sigh_mixin<entt::storage<void>> pool;
|
||||
entt::registry registry;
|
||||
|
||||
counter on_construct{};
|
||||
@ -297,7 +320,7 @@ TEST(SighStorageMixin, VoidType) {
|
||||
ASSERT_EQ(pool.type(), entt::type_id<void>());
|
||||
ASSERT_TRUE(pool.contains(entt::entity{99}));
|
||||
|
||||
entt::sigh_storage_mixin<entt::storage<void>> other{std::move(pool)};
|
||||
entt::sigh_mixin<entt::storage<void>> other{std::move(pool)};
|
||||
|
||||
ASSERT_FALSE(pool.contains(entt::entity{99}));
|
||||
ASSERT_TRUE(other.contains(entt::entity{99}));
|
||||
@ -313,8 +336,8 @@ TEST(SighStorageMixin, VoidType) {
|
||||
ASSERT_EQ(on_destroy.value, 1);
|
||||
}
|
||||
|
||||
TEST(SighStorageMixin, Move) {
|
||||
entt::sigh_storage_mixin<entt::storage<int>> pool;
|
||||
TEST(SighMixin, Move) {
|
||||
entt::sigh_mixin<entt::storage<int>> pool;
|
||||
entt::registry registry;
|
||||
|
||||
counter on_construct{};
|
||||
@ -330,7 +353,7 @@ TEST(SighStorageMixin, Move) {
|
||||
ASSERT_TRUE(std::is_move_assignable_v<decltype(pool)>);
|
||||
ASSERT_EQ(pool.type(), entt::type_id<int>());
|
||||
|
||||
entt::sigh_storage_mixin<entt::storage<int>> other{std::move(pool)};
|
||||
entt::sigh_mixin<entt::storage<int>> other{std::move(pool)};
|
||||
|
||||
ASSERT_TRUE(pool.empty());
|
||||
ASSERT_FALSE(other.empty());
|
||||
@ -347,7 +370,7 @@ TEST(SighStorageMixin, Move) {
|
||||
ASSERT_EQ(pool.get(entt::entity{3}), 3);
|
||||
ASSERT_EQ(other.at(0u), static_cast<entt::entity>(entt::null));
|
||||
|
||||
other = entt::sigh_storage_mixin<entt::storage<int>>{};
|
||||
other = entt::sigh_mixin<entt::storage<int>>{};
|
||||
other.bind(entt::forward_as_any(registry));
|
||||
|
||||
other.emplace(entt::entity{42}, 42);
|
||||
@ -365,9 +388,9 @@ TEST(SighStorageMixin, Move) {
|
||||
ASSERT_EQ(on_destroy.value, 1);
|
||||
}
|
||||
|
||||
TEST(SighStorageMixin, Swap) {
|
||||
entt::sigh_storage_mixin<entt::storage<int>> pool;
|
||||
entt::sigh_storage_mixin<entt::storage<int>> other;
|
||||
TEST(SighMixin, Swap) {
|
||||
entt::sigh_mixin<entt::storage<int>> pool;
|
||||
entt::sigh_mixin<entt::storage<int>> other;
|
||||
entt::registry registry;
|
||||
|
||||
counter on_construct{};
|
||||
@ -411,7 +434,100 @@ TEST(SighStorageMixin, Swap) {
|
||||
ASSERT_EQ(on_destroy.value, 3);
|
||||
}
|
||||
|
||||
TEST(SighStorageMixin, CustomAllocator) {
|
||||
TEST(SighMixin, EmptyEachStorage) {
|
||||
entt::sigh_mixin<entt::storage<empty_each_tag>> pool;
|
||||
entt::registry registry;
|
||||
|
||||
counter on_destroy{};
|
||||
|
||||
pool.bind(entt::forward_as_any(registry));
|
||||
pool.on_destroy().connect<&listener<entt::registry>>(on_destroy);
|
||||
|
||||
ASSERT_TRUE(pool.empty());
|
||||
ASSERT_EQ(on_destroy.value, 0);
|
||||
|
||||
pool.push(entt::entity{42});
|
||||
|
||||
ASSERT_FALSE(pool.empty());
|
||||
ASSERT_EQ(on_destroy.value, 0);
|
||||
|
||||
ASSERT_NE(pool.begin(), pool.end());
|
||||
ASSERT_EQ(pool.each().begin(), pool.each().end());
|
||||
ASSERT_EQ(on_destroy.value, 0);
|
||||
|
||||
pool.clear();
|
||||
|
||||
ASSERT_EQ(pool.begin(), pool.end());
|
||||
ASSERT_EQ(pool.each().begin(), pool.each().end());
|
||||
// no signal at all because of the (fake) empty iterable
|
||||
ASSERT_EQ(on_destroy.value, 0);
|
||||
}
|
||||
|
||||
TEST(SighMixin, StorageEntity) {
|
||||
using traits_type = entt::entt_traits<entt::entity>;
|
||||
|
||||
entt::sigh_mixin<entt::storage<entt::entity>> pool;
|
||||
entt::registry registry;
|
||||
|
||||
counter on_construct{};
|
||||
counter on_destroy{};
|
||||
|
||||
pool.bind(entt::forward_as_any(registry));
|
||||
pool.on_construct().connect<&listener<entt::registry>>(on_construct);
|
||||
pool.on_destroy().connect<&listener<entt::registry>>(on_destroy);
|
||||
|
||||
pool.push(entt::entity{1});
|
||||
|
||||
ASSERT_EQ(on_construct.value, 1);
|
||||
ASSERT_EQ(on_destroy.value, 0);
|
||||
ASSERT_EQ(pool.size(), 2u);
|
||||
ASSERT_EQ(pool.in_use(), 1u);
|
||||
|
||||
pool.erase(entt::entity{1});
|
||||
|
||||
ASSERT_EQ(on_construct.value, 1);
|
||||
ASSERT_EQ(on_destroy.value, 1);
|
||||
ASSERT_EQ(pool.size(), 2u);
|
||||
ASSERT_EQ(pool.in_use(), 0u);
|
||||
|
||||
pool.push(traits_type::construct(0, 2));
|
||||
pool.push(traits_type::construct(2, 1));
|
||||
|
||||
ASSERT_TRUE(pool.contains(traits_type::construct(0, 2)));
|
||||
ASSERT_TRUE(pool.contains(traits_type::construct(1, 1)));
|
||||
ASSERT_TRUE(pool.contains(traits_type::construct(2, 1)));
|
||||
|
||||
ASSERT_EQ(on_construct.value, 3);
|
||||
ASSERT_EQ(on_destroy.value, 1);
|
||||
ASSERT_EQ(pool.size(), 3u);
|
||||
ASSERT_EQ(pool.in_use(), 2u);
|
||||
|
||||
pool.clear();
|
||||
|
||||
ASSERT_EQ(pool.size(), 0u);
|
||||
ASSERT_EQ(pool.in_use(), 0u);
|
||||
|
||||
ASSERT_EQ(on_construct.value, 3);
|
||||
ASSERT_EQ(on_destroy.value, 3);
|
||||
|
||||
pool.emplace();
|
||||
pool.emplace(entt::entity{0});
|
||||
|
||||
entt::entity entities[1u]{};
|
||||
pool.insert(entities, entities + 1u);
|
||||
|
||||
ASSERT_EQ(on_construct.value, 6);
|
||||
ASSERT_EQ(on_destroy.value, 3);
|
||||
ASSERT_EQ(pool.size(), 3u);
|
||||
ASSERT_EQ(pool.in_use(), 3u);
|
||||
|
||||
pool.clear();
|
||||
|
||||
ASSERT_EQ(pool.size(), 0u);
|
||||
ASSERT_EQ(pool.in_use(), 0u);
|
||||
}
|
||||
|
||||
TEST(SighMixin, CustomAllocator) {
|
||||
auto test = [](auto pool, auto alloc) {
|
||||
using registry_type = typename decltype(pool)::registry_type;
|
||||
registry_type registry;
|
||||
@ -466,12 +582,12 @@ TEST(SighStorageMixin, CustomAllocator) {
|
||||
|
||||
test::throwing_allocator<entt::entity> allocator{};
|
||||
|
||||
test(entt::sigh_storage_mixin<entt::basic_storage<int, entt::entity, test::throwing_allocator<int>>>{allocator}, allocator);
|
||||
test(entt::sigh_storage_mixin<entt::basic_storage<std::true_type, entt::entity, test::throwing_allocator<std::true_type>>>{allocator}, allocator);
|
||||
test(entt::sigh_storage_mixin<entt::basic_storage<stable_type, entt::entity, test::throwing_allocator<stable_type>>>{allocator}, allocator);
|
||||
test(entt::sigh_mixin<entt::basic_storage<int, entt::entity, test::throwing_allocator<int>>>{allocator}, allocator);
|
||||
test(entt::sigh_mixin<entt::basic_storage<std::true_type, entt::entity, test::throwing_allocator<std::true_type>>>{allocator}, allocator);
|
||||
test(entt::sigh_mixin<entt::basic_storage<stable_type, entt::entity, test::throwing_allocator<stable_type>>>{allocator}, allocator);
|
||||
}
|
||||
|
||||
TEST(SighStorageMixin, ThrowingAllocator) {
|
||||
TEST(SighMixin, ThrowingAllocator) {
|
||||
auto test = [](auto pool) {
|
||||
using pool_allocator_type = typename decltype(pool)::allocator_type;
|
||||
using value_type = typename decltype(pool)::value_type;
|
||||
@ -511,7 +627,7 @@ TEST(SighStorageMixin, ThrowingAllocator) {
|
||||
|
||||
test::throwing_allocator<entt::entity>::trigger_on_allocate = true;
|
||||
|
||||
ASSERT_THROW(base.emplace(entt::entity{0}), test::throwing_allocator<entt::entity>::exception_type);
|
||||
ASSERT_THROW(base.push(entt::entity{0}), test::throwing_allocator<entt::entity>::exception_type);
|
||||
ASSERT_FALSE(base.contains(entt::entity{0}));
|
||||
ASSERT_TRUE(base.empty());
|
||||
|
||||
@ -543,12 +659,12 @@ TEST(SighStorageMixin, ThrowingAllocator) {
|
||||
ASSERT_EQ(on_destroy.value, 1);
|
||||
};
|
||||
|
||||
test(entt::sigh_storage_mixin<entt::basic_storage<int, entt::entity, test::throwing_allocator<int>>>{});
|
||||
test(entt::sigh_storage_mixin<entt::basic_storage<stable_type, entt::entity, test::throwing_allocator<stable_type>>>{});
|
||||
test(entt::sigh_mixin<entt::basic_storage<int, entt::entity, test::throwing_allocator<int>>>{});
|
||||
test(entt::sigh_mixin<entt::basic_storage<stable_type, entt::entity, test::throwing_allocator<stable_type>>>{});
|
||||
}
|
||||
|
||||
TEST(SighStorageMixin, ThrowingComponent) {
|
||||
entt::sigh_storage_mixin<entt::storage<test::throwing_type>> pool;
|
||||
TEST(SighMixin, ThrowingComponent) {
|
||||
entt::sigh_mixin<entt::storage<test::throwing_type>> pool;
|
||||
using registry_type = typename decltype(pool)::registry_type;
|
||||
registry_type registry;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -26,6 +26,7 @@ TEST(SparseSet, Functionalities) {
|
||||
ASSERT_EQ(set.capacity(), 42u);
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_TRUE(set.contiguous());
|
||||
ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end());
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.contains(entt::entity{0}));
|
||||
@ -36,10 +37,11 @@ TEST(SparseSet, Functionalities) {
|
||||
ASSERT_EQ(set.capacity(), 42u);
|
||||
ASSERT_TRUE(set.empty());
|
||||
|
||||
set.emplace(entt::entity{42});
|
||||
set.push(entt::entity{42});
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 1u);
|
||||
ASSERT_TRUE(set.contiguous());
|
||||
ASSERT_NE(std::as_const(set).begin(), std::as_const(set).end());
|
||||
ASSERT_NE(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.contains(entt::entity{0}));
|
||||
@ -48,12 +50,13 @@ TEST(SparseSet, Functionalities) {
|
||||
ASSERT_EQ(set.at(0u), entt::entity{42});
|
||||
ASSERT_EQ(set.at(1u), static_cast<entt::entity>(entt::null));
|
||||
ASSERT_EQ(set[0u], entt::entity{42});
|
||||
ASSERT_EQ(set.get(entt::entity{42}), nullptr);
|
||||
ASSERT_EQ(set.value(entt::entity{42}), nullptr);
|
||||
|
||||
set.erase(entt::entity{42});
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_TRUE(set.contiguous());
|
||||
ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end());
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.contains(entt::entity{0}));
|
||||
@ -61,7 +64,7 @@ TEST(SparseSet, Functionalities) {
|
||||
ASSERT_EQ(set.at(0u), static_cast<entt::entity>(entt::null));
|
||||
ASSERT_EQ(set.at(1u), static_cast<entt::entity>(entt::null));
|
||||
|
||||
set.emplace(entt::entity{42});
|
||||
set.push(entt::entity{42});
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.index(entt::entity{42}), 0u);
|
||||
@ -73,6 +76,7 @@ TEST(SparseSet, Functionalities) {
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_TRUE(set.contiguous());
|
||||
ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end());
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.contains(entt::entity{0}));
|
||||
@ -86,11 +90,11 @@ TEST(SparseSet, Contains) {
|
||||
|
||||
entt::sparse_set set{entt::deletion_policy::in_place};
|
||||
|
||||
set.emplace(entt::entity{0});
|
||||
set.emplace(entt::entity{3});
|
||||
set.emplace(entt::entity{42});
|
||||
set.emplace(entt::entity{99});
|
||||
set.emplace(traits_type::construct(1, 5));
|
||||
set.push(entt::entity{0});
|
||||
set.push(entt::entity{3});
|
||||
set.push(entt::entity{42});
|
||||
set.push(entt::entity{99});
|
||||
set.push(traits_type::construct(1, 5));
|
||||
|
||||
ASSERT_FALSE(set.contains(entt::null));
|
||||
ASSERT_FALSE(set.contains(entt::tombstone));
|
||||
@ -133,8 +137,8 @@ TEST(SparseSet, Current) {
|
||||
ASSERT_EQ(set.current(traits_type::construct(0, 0)), traits_type::to_version(entt::tombstone));
|
||||
ASSERT_EQ(set.current(traits_type::construct(3, 3)), traits_type::to_version(entt::tombstone));
|
||||
|
||||
set.emplace(traits_type::construct(0, 0));
|
||||
set.emplace(traits_type::construct(3, 3));
|
||||
set.push(traits_type::construct(0, 0));
|
||||
set.push(traits_type::construct(3, 3));
|
||||
|
||||
ASSERT_NE(set.current(traits_type::construct(0, 0)), traits_type::to_version(entt::tombstone));
|
||||
ASSERT_NE(set.current(traits_type::construct(3, 3)), traits_type::to_version(entt::tombstone));
|
||||
@ -153,8 +157,8 @@ TEST(SparseSet, Index) {
|
||||
|
||||
entt::sparse_set set{};
|
||||
|
||||
set.emplace(traits_type::construct(0, 0));
|
||||
set.emplace(traits_type::construct(3, 3));
|
||||
set.push(traits_type::construct(0, 0));
|
||||
set.push(traits_type::construct(3, 3));
|
||||
|
||||
ASSERT_EQ(set.index(traits_type::construct(0, 0)), 0u);
|
||||
ASSERT_EQ(set.index(traits_type::construct(3, 3)), 1u);
|
||||
@ -175,7 +179,7 @@ ENTT_DEBUG_TEST(SparseSetDeathTest, Index) {
|
||||
|
||||
TEST(SparseSet, Move) {
|
||||
entt::sparse_set set;
|
||||
set.emplace(entt::entity{42});
|
||||
set.push(entt::entity{42});
|
||||
|
||||
ASSERT_TRUE(std::is_move_constructible_v<decltype(set)>);
|
||||
ASSERT_TRUE(std::is_move_assignable_v<decltype(set)>);
|
||||
@ -195,7 +199,7 @@ TEST(SparseSet, Move) {
|
||||
ASSERT_EQ(other.at(0u), static_cast<entt::entity>(entt::null));
|
||||
|
||||
other = entt::sparse_set{};
|
||||
other.emplace(entt::entity{3});
|
||||
other.push(entt::entity{3});
|
||||
other = std::move(set);
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
@ -208,10 +212,10 @@ TEST(SparseSet, Swap) {
|
||||
entt::sparse_set set;
|
||||
entt::sparse_set other{entt::deletion_policy::in_place};
|
||||
|
||||
set.emplace(entt::entity{42});
|
||||
set.push(entt::entity{42});
|
||||
|
||||
other.emplace(entt::entity{9});
|
||||
other.emplace(entt::entity{3});
|
||||
other.push(entt::entity{9});
|
||||
other.push(entt::entity{3});
|
||||
other.erase(entt::entity{9});
|
||||
|
||||
ASSERT_EQ(set.size(), 1u);
|
||||
@ -233,12 +237,12 @@ TEST(SparseSet, Pagination) {
|
||||
|
||||
ASSERT_EQ(set.extent(), 0u);
|
||||
|
||||
set.emplace(entt::entity{traits_type::page_size - 1u});
|
||||
set.push(entt::entity{traits_type::page_size - 1u});
|
||||
|
||||
ASSERT_EQ(set.extent(), traits_type::page_size);
|
||||
ASSERT_TRUE(set.contains(entt::entity{traits_type::page_size - 1u}));
|
||||
|
||||
set.emplace(entt::entity{traits_type::page_size});
|
||||
set.push(entt::entity{traits_type::page_size});
|
||||
|
||||
ASSERT_EQ(set.extent(), 2 * traits_type::page_size);
|
||||
ASSERT_TRUE(set.contains(entt::entity{traits_type::page_size - 1u}));
|
||||
@ -263,17 +267,17 @@ TEST(SparseSet, Pagination) {
|
||||
ASSERT_EQ(set.extent(), 2 * traits_type::page_size);
|
||||
}
|
||||
|
||||
TEST(SparseSet, Emplace) {
|
||||
TEST(SparseSet, Push) {
|
||||
entt::sparse_set set{entt::deletion_policy::in_place};
|
||||
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_NE(set.emplace(entities[0u]), set.end());
|
||||
ASSERT_NE(set.push(entities[0u]), set.end());
|
||||
|
||||
set.erase(entities[0u]);
|
||||
|
||||
ASSERT_NE(set.emplace(entities[1u]), set.end());
|
||||
ASSERT_NE(set.emplace(entities[0u]), set.end());
|
||||
ASSERT_NE(set.push(entities[1u]), set.end());
|
||||
ASSERT_NE(set.push(entities[0u]), set.end());
|
||||
|
||||
ASSERT_EQ(set.at(0u), entities[1u]);
|
||||
ASSERT_EQ(set.at(1u), entities[0u]);
|
||||
@ -282,8 +286,8 @@ TEST(SparseSet, Emplace) {
|
||||
|
||||
set.erase(std::begin(entities), std::end(entities));
|
||||
|
||||
ASSERT_NE(set.emplace(entities[1u]), set.end());
|
||||
ASSERT_NE(set.emplace(entities[0u]), set.end());
|
||||
ASSERT_NE(set.push(entities[1u]), set.end());
|
||||
ASSERT_NE(set.push(entities[0u]), set.end());
|
||||
|
||||
ASSERT_EQ(set.at(0u), entities[1u]);
|
||||
ASSERT_EQ(set.at(1u), entities[0u]);
|
||||
@ -291,72 +295,16 @@ TEST(SparseSet, Emplace) {
|
||||
ASSERT_EQ(set.index(entities[1u]), 0u);
|
||||
}
|
||||
|
||||
ENTT_DEBUG_TEST(SparseSetDeathTest, Emplace) {
|
||||
entt::sparse_set set{entt::deletion_policy::in_place};
|
||||
set.emplace(entt::entity{42});
|
||||
|
||||
ASSERT_DEATH(set.emplace(entt::entity{42}), "");
|
||||
}
|
||||
|
||||
TEST(SparseSet, EmplaceOutOfBounds) {
|
||||
using traits_type = entt::entt_traits<entt::entity>;
|
||||
|
||||
entt::sparse_set set{entt::deletion_policy::in_place};
|
||||
entt::entity entities[2u]{entt::entity{0}, entt::entity{traits_type::page_size}};
|
||||
|
||||
ASSERT_NE(set.emplace(entities[0u]), set.end());
|
||||
ASSERT_EQ(set.extent(), traits_type::page_size);
|
||||
ASSERT_EQ(set.index(entities[0u]), 0u);
|
||||
|
||||
set.erase(entities[0u]);
|
||||
|
||||
ASSERT_NE(set.emplace(entities[1u]), set.end());
|
||||
ASSERT_EQ(set.extent(), 2u * traits_type::page_size);
|
||||
ASSERT_EQ(set.index(entities[1u]), 0u);
|
||||
}
|
||||
|
||||
TEST(SparseSet, Bump) {
|
||||
using traits_type = entt::entt_traits<entt::entity>;
|
||||
|
||||
entt::sparse_set set;
|
||||
entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, traits_type::construct(9, 3)};
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
|
||||
ASSERT_EQ(set.current(entities[0u]), 0u);
|
||||
ASSERT_EQ(set.current(entities[1u]), 0u);
|
||||
ASSERT_EQ(set.current(entities[2u]), 3u);
|
||||
|
||||
set.bump(entities[0u]);
|
||||
set.bump(traits_type::construct(traits_type::to_entity(entities[1u]), 1));
|
||||
set.bump(traits_type::construct(traits_type::to_entity(entities[2u]), 0));
|
||||
|
||||
ASSERT_EQ(set.current(entities[0u]), 0u);
|
||||
ASSERT_EQ(set.current(entities[1u]), 1u);
|
||||
ASSERT_EQ(set.current(entities[2u]), 0u);
|
||||
}
|
||||
|
||||
ENTT_DEBUG_TEST(SparseSetDeathTest, Bump) {
|
||||
using traits_type = entt::entt_traits<entt::entity>;
|
||||
|
||||
entt::sparse_set set{entt::deletion_policy::in_place};
|
||||
set.emplace(entt::entity{3});
|
||||
|
||||
ASSERT_DEATH(set.bump(entt::null), "");
|
||||
ASSERT_DEATH(set.bump(entt::tombstone), "");
|
||||
ASSERT_DEATH(set.bump(entt::entity{42}), "");
|
||||
ASSERT_DEATH(set.bump(traits_type::construct(traits_type::to_entity(entt::entity{3}), traits_type::to_version(entt::tombstone))), "");
|
||||
}
|
||||
|
||||
TEST(SparseSet, Insert) {
|
||||
TEST(SparseSet, PushRange) {
|
||||
entt::sparse_set set{entt::deletion_policy::in_place};
|
||||
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
|
||||
|
||||
set.emplace(entt::entity{12});
|
||||
set.push(entt::entity{12});
|
||||
|
||||
ASSERT_EQ(set.insert(std::end(entities), std::end(entities)), set.end());
|
||||
ASSERT_NE(set.insert(std::begin(entities), std::end(entities)), set.end());
|
||||
ASSERT_EQ(set.push(std::end(entities), std::end(entities)), set.end());
|
||||
ASSERT_NE(set.push(std::begin(entities), std::end(entities)), set.end());
|
||||
|
||||
set.emplace(entt::entity{24});
|
||||
set.push(entt::entity{24});
|
||||
|
||||
ASSERT_TRUE(set.contains(entities[0u]));
|
||||
ASSERT_TRUE(set.contains(entities[1u]));
|
||||
@ -378,7 +326,7 @@ TEST(SparseSet, Insert) {
|
||||
|
||||
set.erase(std::begin(entities), std::end(entities));
|
||||
|
||||
ASSERT_NE(set.insert(std::rbegin(entities), std::rend(entities)), set.end());
|
||||
ASSERT_NE(set.push(std::rbegin(entities), std::rend(entities)), set.end());
|
||||
|
||||
ASSERT_EQ(set.size(), 6u);
|
||||
ASSERT_EQ(set.at(4u), entities[1u]);
|
||||
@ -387,6 +335,64 @@ TEST(SparseSet, Insert) {
|
||||
ASSERT_EQ(set.index(entities[1u]), 4u);
|
||||
}
|
||||
|
||||
ENTT_DEBUG_TEST(SparseSetDeathTest, Push) {
|
||||
entt::sparse_set set{entt::deletion_policy::in_place};
|
||||
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
|
||||
set.push(entt::entity{42});
|
||||
|
||||
ASSERT_DEATH(set.push(entt::entity{42}), "");
|
||||
ASSERT_DEATH(set.push(std::begin(entities), std::end(entities)), "");
|
||||
}
|
||||
|
||||
TEST(SparseSet, PushOutOfBounds) {
|
||||
using traits_type = entt::entt_traits<entt::entity>;
|
||||
|
||||
entt::sparse_set set{entt::deletion_policy::in_place};
|
||||
entt::entity entities[2u]{entt::entity{0}, entt::entity{traits_type::page_size}};
|
||||
|
||||
ASSERT_NE(set.push(entities[0u]), set.end());
|
||||
ASSERT_EQ(set.extent(), traits_type::page_size);
|
||||
ASSERT_EQ(set.index(entities[0u]), 0u);
|
||||
|
||||
set.erase(entities[0u]);
|
||||
|
||||
ASSERT_NE(set.push(entities[1u]), set.end());
|
||||
ASSERT_EQ(set.extent(), 2u * traits_type::page_size);
|
||||
ASSERT_EQ(set.index(entities[1u]), 0u);
|
||||
}
|
||||
|
||||
TEST(SparseSet, Bump) {
|
||||
using traits_type = entt::entt_traits<entt::entity>;
|
||||
|
||||
entt::sparse_set set;
|
||||
entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, traits_type::construct(9, 3)};
|
||||
set.push(std::begin(entities), std::end(entities));
|
||||
|
||||
ASSERT_EQ(set.current(entities[0u]), 0u);
|
||||
ASSERT_EQ(set.current(entities[1u]), 0u);
|
||||
ASSERT_EQ(set.current(entities[2u]), 3u);
|
||||
|
||||
ASSERT_EQ(set.bump(entities[0u]), 0u);
|
||||
ASSERT_EQ(set.bump(traits_type::construct(traits_type::to_entity(entities[1u]), 1)), 1u);
|
||||
ASSERT_EQ(set.bump(traits_type::construct(traits_type::to_entity(entities[2u]), 0)), 0u);
|
||||
|
||||
ASSERT_EQ(set.current(entities[0u]), 0u);
|
||||
ASSERT_EQ(set.current(entities[1u]), 1u);
|
||||
ASSERT_EQ(set.current(entities[2u]), 0u);
|
||||
}
|
||||
|
||||
ENTT_DEBUG_TEST(SparseSetDeathTest, Bump) {
|
||||
using traits_type = entt::entt_traits<entt::entity>;
|
||||
|
||||
entt::sparse_set set{entt::deletion_policy::in_place};
|
||||
set.push(entt::entity{3});
|
||||
|
||||
ASSERT_DEATH(set.bump(entt::null), "");
|
||||
ASSERT_DEATH(set.bump(entt::tombstone), "");
|
||||
ASSERT_DEATH(set.bump(entt::entity{42}), "");
|
||||
ASSERT_DEATH(set.bump(traits_type::construct(traits_type::to_entity(entt::entity{3}), traits_type::to_version(entt::tombstone))), "");
|
||||
}
|
||||
|
||||
TEST(SparseSet, Erase) {
|
||||
using traits_type = entt::entt_traits<entt::entity>;
|
||||
|
||||
@ -396,7 +402,7 @@ TEST(SparseSet, Erase) {
|
||||
ASSERT_EQ(set.policy(), entt::deletion_policy::swap_and_pop);
|
||||
ASSERT_TRUE(set.empty());
|
||||
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
set.push(std::begin(entities), std::end(entities));
|
||||
set.erase(set.begin(), set.end());
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
@ -404,7 +410,7 @@ TEST(SparseSet, Erase) {
|
||||
ASSERT_EQ(set.current(entities[1u]), traits_type::to_version(entt::tombstone));
|
||||
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
|
||||
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
set.push(std::begin(entities), std::end(entities));
|
||||
set.erase(entities, entities + 2u);
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
@ -418,7 +424,7 @@ TEST(SparseSet, Erase) {
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
|
||||
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
set.push(std::begin(entities), std::end(entities));
|
||||
std::swap(entities[1u], entities[2u]);
|
||||
set.erase(entities, entities + 2u);
|
||||
|
||||
@ -442,8 +448,8 @@ TEST(SparseSet, CrossErase) {
|
||||
entt::sparse_set other;
|
||||
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
|
||||
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
other.emplace(entities[1u]);
|
||||
set.push(std::begin(entities), std::end(entities));
|
||||
other.push(entities[1u]);
|
||||
set.erase(other.begin(), other.end());
|
||||
|
||||
ASSERT_TRUE(set.contains(entities[0u]));
|
||||
@ -461,9 +467,9 @@ TEST(SparseSet, StableErase) {
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
|
||||
set.emplace(entities[0u]);
|
||||
set.emplace(entities[1u]);
|
||||
set.emplace(entities[2u]);
|
||||
set.push(entities[0u]);
|
||||
set.push(entities[1u]);
|
||||
set.push(entities[2u]);
|
||||
|
||||
set.erase(set.begin(), set.end());
|
||||
|
||||
@ -476,9 +482,9 @@ TEST(SparseSet, StableErase) {
|
||||
ASSERT_TRUE(set.at(1u) == entt::tombstone);
|
||||
ASSERT_TRUE(set.at(2u) == entt::tombstone);
|
||||
|
||||
set.emplace(entities[0u]);
|
||||
set.emplace(entities[1u]);
|
||||
set.emplace(entities[2u]);
|
||||
set.push(entities[0u]);
|
||||
set.push(entities[1u]);
|
||||
set.push(entities[2u]);
|
||||
|
||||
set.erase(entities, entities + 2u);
|
||||
|
||||
@ -497,9 +503,9 @@ TEST(SparseSet, StableErase) {
|
||||
ASSERT_EQ(set.size(), 3u);
|
||||
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
|
||||
|
||||
set.emplace(entities[0u]);
|
||||
set.emplace(entities[1u]);
|
||||
set.emplace(entities[2u]);
|
||||
set.push(entities[0u]);
|
||||
set.push(entities[1u]);
|
||||
set.push(entities[2u]);
|
||||
|
||||
std::swap(entities[1u], entities[2u]);
|
||||
set.erase(entities, entities + 2u);
|
||||
@ -527,9 +533,9 @@ TEST(SparseSet, StableErase) {
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
|
||||
|
||||
set.emplace(entities[0u]);
|
||||
set.emplace(entities[1u]);
|
||||
set.emplace(entities[2u]);
|
||||
set.push(entities[0u]);
|
||||
set.push(entities[1u]);
|
||||
set.push(entities[2u]);
|
||||
|
||||
set.erase(entities[2u]);
|
||||
|
||||
@ -546,13 +552,13 @@ TEST(SparseSet, StableErase) {
|
||||
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
|
||||
ASSERT_TRUE(*set.begin() == entt::tombstone);
|
||||
|
||||
set.emplace(entities[0u]);
|
||||
set.push(entities[0u]);
|
||||
|
||||
ASSERT_EQ(*++set.begin(), entities[0u]);
|
||||
|
||||
set.emplace(entities[1u]);
|
||||
set.emplace(entities[2u]);
|
||||
set.emplace(entt::entity{0});
|
||||
set.push(entities[1u]);
|
||||
set.push(entities[2u]);
|
||||
set.push(entt::entity{0});
|
||||
|
||||
ASSERT_EQ(set.size(), 4u);
|
||||
ASSERT_EQ(*set.begin(), entt::entity{0});
|
||||
@ -580,8 +586,8 @@ TEST(SparseSet, CrossStableErase) {
|
||||
entt::sparse_set other{entt::deletion_policy::in_place};
|
||||
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
|
||||
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
other.emplace(entities[1u]);
|
||||
set.push(std::begin(entities), std::end(entities));
|
||||
other.push(entities[1u]);
|
||||
set.erase(other.begin(), other.end());
|
||||
|
||||
ASSERT_TRUE(set.contains(entities[0u]));
|
||||
@ -599,11 +605,11 @@ TEST(SparseSet, Remove) {
|
||||
ASSERT_TRUE(set.empty());
|
||||
|
||||
ASSERT_EQ(set.remove(std::begin(entities), std::end(entities)), 0u);
|
||||
ASSERT_EQ(set.remove(entities[1u]), 0u);
|
||||
ASSERT_FALSE(set.remove(entities[1u]));
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
set.push(std::begin(entities), std::end(entities));
|
||||
|
||||
ASSERT_EQ(set.remove(set.begin(), set.end()), 3u);
|
||||
ASSERT_TRUE(set.empty());
|
||||
@ -611,7 +617,7 @@ TEST(SparseSet, Remove) {
|
||||
ASSERT_EQ(set.current(entities[1u]), traits_type::to_version(entt::tombstone));
|
||||
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
|
||||
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
set.push(std::begin(entities), std::end(entities));
|
||||
|
||||
ASSERT_EQ(set.remove(entities, entities + 2u), 2u);
|
||||
ASSERT_FALSE(set.empty());
|
||||
@ -620,12 +626,12 @@ TEST(SparseSet, Remove) {
|
||||
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entities[2u]));
|
||||
ASSERT_EQ(*set.begin(), entities[2u]);
|
||||
|
||||
ASSERT_EQ(set.remove(entities[2u]), 1u);
|
||||
ASSERT_EQ(set.remove(entities[2u]), 0u);
|
||||
ASSERT_TRUE(set.remove(entities[2u]));
|
||||
ASSERT_FALSE(set.remove(entities[2u]));
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
|
||||
|
||||
set.insert(entities, entities + 2u);
|
||||
set.push(entities, entities + 2u);
|
||||
|
||||
ASSERT_EQ(set.remove(std::begin(entities), std::end(entities)), 2u);
|
||||
ASSERT_EQ(set.current(entities[0u]), traits_type::to_version(entt::tombstone));
|
||||
@ -633,7 +639,7 @@ TEST(SparseSet, Remove) {
|
||||
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
|
||||
ASSERT_TRUE(set.empty());
|
||||
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
set.push(std::begin(entities), std::end(entities));
|
||||
std::swap(entities[1u], entities[2u]);
|
||||
|
||||
ASSERT_EQ(set.remove(entities, entities + 2u), 2u);
|
||||
@ -641,9 +647,9 @@ TEST(SparseSet, Remove) {
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(*set.begin(), entities[2u]);
|
||||
|
||||
ASSERT_EQ(set.remove(traits_type::construct(9, 0)), 0u);
|
||||
ASSERT_EQ(set.remove(entt::tombstone), 0u);
|
||||
ASSERT_EQ(set.remove(entt::null), 0u);
|
||||
ASSERT_FALSE(set.remove(traits_type::construct(9, 0)));
|
||||
ASSERT_FALSE(set.remove(entt::tombstone));
|
||||
ASSERT_FALSE(set.remove(entt::null));
|
||||
}
|
||||
|
||||
TEST(SparseSet, CrossRemove) {
|
||||
@ -651,8 +657,8 @@ TEST(SparseSet, CrossRemove) {
|
||||
entt::sparse_set other;
|
||||
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
|
||||
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
other.emplace(entities[1u]);
|
||||
set.push(std::begin(entities), std::end(entities));
|
||||
other.push(entities[1u]);
|
||||
set.remove(other.begin(), other.end());
|
||||
|
||||
ASSERT_TRUE(set.contains(entities[0u]));
|
||||
@ -671,14 +677,14 @@ TEST(SparseSet, StableRemove) {
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
|
||||
ASSERT_EQ(set.remove(std::begin(entities), std::end(entities)), 0u);
|
||||
ASSERT_EQ(set.remove(entities[1u]), 0u);
|
||||
ASSERT_FALSE(set.remove(entities[1u]));
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
|
||||
set.emplace(entities[0u]);
|
||||
set.emplace(entities[1u]);
|
||||
set.emplace(entities[2u]);
|
||||
set.push(entities[0u]);
|
||||
set.push(entities[1u]);
|
||||
set.push(entities[2u]);
|
||||
|
||||
ASSERT_EQ(set.remove(set.begin(), set.end()), 3u);
|
||||
ASSERT_EQ(set.remove(set.begin(), set.end()), 0u);
|
||||
@ -692,9 +698,9 @@ TEST(SparseSet, StableRemove) {
|
||||
ASSERT_TRUE(set.at(1u) == entt::tombstone);
|
||||
ASSERT_TRUE(set.at(2u) == entt::tombstone);
|
||||
|
||||
set.emplace(entities[0u]);
|
||||
set.emplace(entities[1u]);
|
||||
set.emplace(entities[2u]);
|
||||
set.push(entities[0u]);
|
||||
set.push(entities[1u]);
|
||||
set.push(entities[2u]);
|
||||
|
||||
ASSERT_EQ(set.remove(entities, entities + 2u), 2u);
|
||||
ASSERT_EQ(set.remove(entities, entities + 2u), 0u);
|
||||
@ -708,16 +714,16 @@ TEST(SparseSet, StableRemove) {
|
||||
ASSERT_TRUE(set.at(0u) == entt::tombstone);
|
||||
ASSERT_TRUE(set.at(1u) == entt::tombstone);
|
||||
|
||||
ASSERT_EQ(set.remove(entities[2u]), 1u);
|
||||
ASSERT_EQ(set.remove(entities[2u]), 0u);
|
||||
ASSERT_TRUE(set.remove(entities[2u]));
|
||||
ASSERT_FALSE(set.remove(entities[2u]));
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 3u);
|
||||
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
|
||||
|
||||
set.emplace(entities[0u]);
|
||||
set.emplace(entities[1u]);
|
||||
set.emplace(entities[2u]);
|
||||
set.push(entities[0u]);
|
||||
set.push(entities[1u]);
|
||||
set.push(entities[2u]);
|
||||
|
||||
std::swap(entities[1u], entities[2u]);
|
||||
|
||||
@ -747,19 +753,19 @@ TEST(SparseSet, StableRemove) {
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
|
||||
|
||||
set.emplace(entities[0u]);
|
||||
set.emplace(entities[1u]);
|
||||
set.emplace(entities[2u]);
|
||||
set.push(entities[0u]);
|
||||
set.push(entities[1u]);
|
||||
set.push(entities[2u]);
|
||||
|
||||
ASSERT_EQ(set.remove(entities[2u]), 1u);
|
||||
ASSERT_EQ(set.remove(entities[2u]), 0u);
|
||||
ASSERT_TRUE(set.remove(entities[2u]));
|
||||
ASSERT_FALSE(set.remove(entities[2u]));
|
||||
|
||||
ASSERT_NE(set.current(entities[0u]), traits_type::to_version(entt::tombstone));
|
||||
ASSERT_NE(set.current(entities[1u]), traits_type::to_version(entt::tombstone));
|
||||
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
|
||||
|
||||
ASSERT_EQ(set.remove(entities[0u]), 1u);
|
||||
ASSERT_EQ(set.remove(entities[1u]), 1u);
|
||||
ASSERT_TRUE(set.remove(entities[0u]));
|
||||
ASSERT_TRUE(set.remove(entities[1u]));
|
||||
ASSERT_EQ(set.remove(entities, entities + 2u), 0u);
|
||||
|
||||
ASSERT_EQ(set.size(), 3u);
|
||||
@ -768,13 +774,13 @@ TEST(SparseSet, StableRemove) {
|
||||
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
|
||||
ASSERT_TRUE(*set.begin() == entt::tombstone);
|
||||
|
||||
set.emplace(entities[0u]);
|
||||
set.push(entities[0u]);
|
||||
|
||||
ASSERT_EQ(*++set.begin(), entities[0u]);
|
||||
|
||||
set.emplace(entities[1u]);
|
||||
set.emplace(entities[2u]);
|
||||
set.emplace(entt::entity{0});
|
||||
set.push(entities[1u]);
|
||||
set.push(entities[2u]);
|
||||
set.push(entt::entity{0});
|
||||
|
||||
ASSERT_EQ(set.size(), 4u);
|
||||
ASSERT_EQ(*set.begin(), entt::entity{0});
|
||||
@ -786,8 +792,8 @@ TEST(SparseSet, StableRemove) {
|
||||
ASSERT_NE(set.current(entities[1u]), traits_type::to_version(entt::tombstone));
|
||||
ASSERT_NE(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
|
||||
|
||||
ASSERT_EQ(set.remove(traits_type::construct(9, 0)), 0u);
|
||||
ASSERT_EQ(set.remove(entt::null), 0u);
|
||||
ASSERT_FALSE(set.remove(traits_type::construct(9, 0)));
|
||||
ASSERT_FALSE(set.remove(entt::null));
|
||||
}
|
||||
|
||||
TEST(SparseSet, CrossStableRemove) {
|
||||
@ -795,8 +801,8 @@ TEST(SparseSet, CrossStableRemove) {
|
||||
entt::sparse_set other{entt::deletion_policy::in_place};
|
||||
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
|
||||
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
other.emplace(entities[1u]);
|
||||
set.push(std::begin(entities), std::end(entities));
|
||||
other.push(entities[1u]);
|
||||
set.remove(other.begin(), other.end());
|
||||
|
||||
ASSERT_TRUE(set.contains(entities[0u]));
|
||||
@ -815,13 +821,13 @@ TEST(SparseSet, Compact) {
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
|
||||
set.emplace(entt::entity{0});
|
||||
set.push(entt::entity{0});
|
||||
set.compact();
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 1u);
|
||||
|
||||
set.emplace(entt::entity{42});
|
||||
set.push(entt::entity{42});
|
||||
set.erase(entt::entity{0});
|
||||
|
||||
ASSERT_EQ(set.size(), 2u);
|
||||
@ -832,7 +838,7 @@ TEST(SparseSet, Compact) {
|
||||
ASSERT_EQ(set.size(), 1u);
|
||||
ASSERT_EQ(set.index(entt::entity{42}), 0u);
|
||||
|
||||
set.emplace(entt::entity{0});
|
||||
set.push(entt::entity{0});
|
||||
set.compact();
|
||||
|
||||
ASSERT_EQ(set.size(), 2u);
|
||||
@ -851,8 +857,8 @@ TEST(SparseSet, SwapEntity) {
|
||||
|
||||
entt::sparse_set set;
|
||||
|
||||
set.emplace(traits_type::construct(3, 5));
|
||||
set.emplace(traits_type::construct(42, 99));
|
||||
set.push(traits_type::construct(3, 5));
|
||||
set.push(traits_type::construct(42, 99));
|
||||
|
||||
ASSERT_EQ(set.index(traits_type::construct(3, 5)), 0u);
|
||||
ASSERT_EQ(set.index(traits_type::construct(42, 99)), 1u);
|
||||
@ -878,9 +884,9 @@ ENTT_DEBUG_TEST(SparseSetDeathTest, SwapEntity) {
|
||||
TEST(SparseSet, Clear) {
|
||||
entt::sparse_set set{entt::deletion_policy::in_place};
|
||||
|
||||
set.emplace(entt::entity{3});
|
||||
set.emplace(entt::entity{42});
|
||||
set.emplace(entt::entity{9});
|
||||
set.push(entt::entity{3});
|
||||
set.push(entt::entity{42});
|
||||
set.push(entt::entity{9});
|
||||
set.erase(entt::entity{42});
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
@ -895,9 +901,9 @@ TEST(SparseSet, Clear) {
|
||||
ASSERT_EQ(set.find(entt::entity{3}), set.end());
|
||||
ASSERT_EQ(set.find(entt::entity{9}), set.end());
|
||||
|
||||
set.emplace(entt::entity{3});
|
||||
set.emplace(entt::entity{42});
|
||||
set.emplace(entt::entity{9});
|
||||
set.push(entt::entity{3});
|
||||
set.push(entt::entity{42});
|
||||
set.push(entt::entity{9});
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 3u);
|
||||
@ -913,6 +919,64 @@ TEST(SparseSet, Clear) {
|
||||
ASSERT_EQ(set.find(entt::entity{9}), set.end());
|
||||
}
|
||||
|
||||
TEST(SparseSet, Contiguous) {
|
||||
entt::sparse_set swap_and_pop{entt::deletion_policy::swap_and_pop};
|
||||
entt::sparse_set in_place{entt::deletion_policy::in_place};
|
||||
|
||||
const entt::entity entity{42};
|
||||
const entt::entity other{3};
|
||||
|
||||
ASSERT_TRUE(swap_and_pop.contiguous());
|
||||
ASSERT_TRUE(in_place.contiguous());
|
||||
|
||||
swap_and_pop.push(entity);
|
||||
in_place.push(entity);
|
||||
|
||||
swap_and_pop.push(other);
|
||||
in_place.push(other);
|
||||
|
||||
ASSERT_TRUE(swap_and_pop.contiguous());
|
||||
ASSERT_TRUE(in_place.contiguous());
|
||||
|
||||
swap_and_pop.erase(entity);
|
||||
in_place.erase(entity);
|
||||
|
||||
ASSERT_TRUE(swap_and_pop.contiguous());
|
||||
ASSERT_FALSE(in_place.contiguous());
|
||||
|
||||
swap_and_pop.push(entity);
|
||||
in_place.push(entity);
|
||||
|
||||
ASSERT_TRUE(swap_and_pop.contiguous());
|
||||
ASSERT_TRUE(in_place.contiguous());
|
||||
|
||||
swap_and_pop.erase(other);
|
||||
in_place.erase(other);
|
||||
|
||||
ASSERT_TRUE(swap_and_pop.contiguous());
|
||||
ASSERT_FALSE(in_place.contiguous());
|
||||
|
||||
in_place.compact();
|
||||
|
||||
ASSERT_TRUE(swap_and_pop.contiguous());
|
||||
ASSERT_TRUE(in_place.contiguous());
|
||||
|
||||
swap_and_pop.push(other);
|
||||
in_place.push(other);
|
||||
|
||||
swap_and_pop.erase(entity);
|
||||
in_place.erase(entity);
|
||||
|
||||
ASSERT_TRUE(swap_and_pop.contiguous());
|
||||
ASSERT_FALSE(in_place.contiguous());
|
||||
|
||||
swap_and_pop.clear();
|
||||
in_place.clear();
|
||||
|
||||
ASSERT_TRUE(swap_and_pop.contiguous());
|
||||
ASSERT_TRUE(in_place.contiguous());
|
||||
}
|
||||
|
||||
TEST(SparseSet, Iterator) {
|
||||
using iterator = typename entt::sparse_set::iterator;
|
||||
|
||||
@ -921,13 +985,20 @@ TEST(SparseSet, Iterator) {
|
||||
static_assert(std::is_same_v<iterator::reference, const entt::entity &>);
|
||||
|
||||
entt::sparse_set set;
|
||||
set.emplace(entt::entity{3});
|
||||
set.push(entt::entity{3});
|
||||
|
||||
iterator end{set.begin()};
|
||||
iterator begin{};
|
||||
|
||||
ASSERT_EQ(end.data(), set.data());
|
||||
ASSERT_EQ(begin.data(), nullptr);
|
||||
|
||||
begin = set.end();
|
||||
std::swap(begin, end);
|
||||
|
||||
ASSERT_EQ(end.data(), set.data());
|
||||
ASSERT_EQ(begin.data(), set.data());
|
||||
|
||||
ASSERT_EQ(begin, set.cbegin());
|
||||
ASSERT_EQ(end, set.cend());
|
||||
ASSERT_NE(begin, end);
|
||||
@ -967,7 +1038,7 @@ TEST(SparseSet, Iterator) {
|
||||
ASSERT_EQ(begin.index(), 0);
|
||||
ASSERT_EQ(end.index(), -1);
|
||||
|
||||
set.emplace(entt::entity{42});
|
||||
set.push(entt::entity{42});
|
||||
begin = set.begin();
|
||||
|
||||
ASSERT_EQ(begin.index(), 1);
|
||||
@ -985,7 +1056,7 @@ TEST(SparseSet, ReverseIterator) {
|
||||
static_assert(std::is_same_v<reverse_iterator::reference, const entt::entity &>);
|
||||
|
||||
entt::sparse_set set;
|
||||
set.emplace(entt::entity{3});
|
||||
set.push(entt::entity{3});
|
||||
|
||||
reverse_iterator end{set.rbegin()};
|
||||
reverse_iterator begin{};
|
||||
@ -1031,7 +1102,7 @@ TEST(SparseSet, ReverseIterator) {
|
||||
ASSERT_EQ(begin.base().index(), -1);
|
||||
ASSERT_EQ(end.base().index(), 0);
|
||||
|
||||
set.emplace(entt::entity{42});
|
||||
set.push(entt::entity{42});
|
||||
end = set.rend();
|
||||
|
||||
ASSERT_EQ(begin.base().index(), -1);
|
||||
@ -1045,9 +1116,9 @@ TEST(SparseSet, Find) {
|
||||
using traits_type = entt::entt_traits<entt::entity>;
|
||||
|
||||
entt::sparse_set set;
|
||||
set.emplace(entt::entity{3});
|
||||
set.emplace(entt::entity{42});
|
||||
set.emplace(traits_type::construct(99, 1));
|
||||
set.push(entt::entity{3});
|
||||
set.push(entt::entity{42});
|
||||
set.push(traits_type::construct(99, 1));
|
||||
|
||||
ASSERT_NE(set.find(entt::entity{3}), set.end());
|
||||
ASSERT_NE(set.find(entt::entity{42}), set.end());
|
||||
@ -1069,9 +1140,9 @@ TEST(SparseSet, Find) {
|
||||
TEST(SparseSet, Data) {
|
||||
entt::sparse_set set;
|
||||
|
||||
set.emplace(entt::entity{3});
|
||||
set.emplace(entt::entity{12});
|
||||
set.emplace(entt::entity{42});
|
||||
set.push(entt::entity{3});
|
||||
set.push(entt::entity{12});
|
||||
set.push(entt::entity{42});
|
||||
|
||||
ASSERT_EQ(set.index(entt::entity{3}), 0u);
|
||||
ASSERT_EQ(set.index(entt::entity{12}), 1u);
|
||||
@ -1086,7 +1157,7 @@ TEST(SparseSet, SortOrdered) {
|
||||
entt::sparse_set set;
|
||||
entt::entity entities[5u]{entt::entity{42}, entt::entity{12}, entt::entity{9}, entt::entity{7}, entt::entity{3}};
|
||||
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
set.push(std::begin(entities), std::end(entities));
|
||||
set.sort(std::less{});
|
||||
|
||||
ASSERT_TRUE(std::equal(std::rbegin(entities), std::rend(entities), set.begin(), set.end()));
|
||||
@ -1096,7 +1167,7 @@ TEST(SparseSet, SortReverse) {
|
||||
entt::sparse_set set;
|
||||
entt::entity entities[5u]{entt::entity{3}, entt::entity{7}, entt::entity{9}, entt::entity{12}, entt::entity{42}};
|
||||
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
set.push(std::begin(entities), std::end(entities));
|
||||
set.sort(std::less{});
|
||||
|
||||
ASSERT_TRUE(std::equal(std::begin(entities), std::end(entities), set.begin(), set.end()));
|
||||
@ -1106,7 +1177,7 @@ TEST(SparseSet, SortUnordered) {
|
||||
entt::sparse_set set;
|
||||
entt::entity entities[5u]{entt::entity{9}, entt::entity{7}, entt::entity{3}, entt::entity{12}, entt::entity{42}};
|
||||
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
set.push(std::begin(entities), std::end(entities));
|
||||
set.sort(std::less{});
|
||||
|
||||
auto begin = set.begin();
|
||||
@ -1124,7 +1195,7 @@ TEST(SparseSet, SortRange) {
|
||||
entt::sparse_set set{entt::deletion_policy::in_place};
|
||||
entt::entity entities[5u]{entt::entity{7}, entt::entity{9}, entt::entity{3}, entt::entity{12}, entt::entity{42}};
|
||||
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
set.push(std::begin(entities), std::end(entities));
|
||||
set.erase(entities[0u]);
|
||||
|
||||
ASSERT_EQ(set.size(), 5u);
|
||||
@ -1139,7 +1210,7 @@ TEST(SparseSet, SortRange) {
|
||||
|
||||
set.clear();
|
||||
set.compact();
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
set.push(std::begin(entities), std::end(entities));
|
||||
set.sort_n(0u, std::less{});
|
||||
|
||||
ASSERT_TRUE(std::equal(std::rbegin(entities), std::rend(entities), set.begin(), set.end()));
|
||||
@ -1167,7 +1238,7 @@ ENTT_DEBUG_TEST(SparseSetDeathTest, SortRange) {
|
||||
entt::sparse_set set{entt::deletion_policy::in_place};
|
||||
entt::entity entity{42};
|
||||
|
||||
set.emplace(entity);
|
||||
set.push(entity);
|
||||
set.erase(entity);
|
||||
|
||||
ASSERT_DEATH(set.sort_n(0u, std::less{});, "");
|
||||
@ -1179,11 +1250,11 @@ TEST(SparseSet, RespectDisjoint) {
|
||||
entt::sparse_set rhs;
|
||||
|
||||
entt::entity lhs_entities[3u]{entt::entity{3}, entt::entity{12}, entt::entity{42}};
|
||||
lhs.insert(std::begin(lhs_entities), std::end(lhs_entities));
|
||||
lhs.push(std::begin(lhs_entities), std::end(lhs_entities));
|
||||
|
||||
ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.begin(), lhs.end()));
|
||||
|
||||
lhs.respect(rhs);
|
||||
lhs.sort_as(rhs);
|
||||
|
||||
ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.begin(), lhs.end()));
|
||||
}
|
||||
@ -1193,15 +1264,15 @@ TEST(SparseSet, RespectOverlap) {
|
||||
entt::sparse_set rhs;
|
||||
|
||||
entt::entity lhs_entities[3u]{entt::entity{3}, entt::entity{12}, entt::entity{42}};
|
||||
lhs.insert(std::begin(lhs_entities), std::end(lhs_entities));
|
||||
lhs.push(std::begin(lhs_entities), std::end(lhs_entities));
|
||||
|
||||
entt::entity rhs_entities[1u]{entt::entity{12}};
|
||||
rhs.insert(std::begin(rhs_entities), std::end(rhs_entities));
|
||||
rhs.push(std::begin(rhs_entities), std::end(rhs_entities));
|
||||
|
||||
ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.begin(), lhs.end()));
|
||||
ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.begin(), rhs.end()));
|
||||
|
||||
lhs.respect(rhs);
|
||||
lhs.sort_as(rhs);
|
||||
|
||||
auto begin = lhs.begin();
|
||||
auto end = lhs.end();
|
||||
@ -1217,15 +1288,15 @@ TEST(SparseSet, RespectOrdered) {
|
||||
entt::sparse_set rhs;
|
||||
|
||||
entt::entity lhs_entities[5u]{entt::entity{1}, entt::entity{2}, entt::entity{3}, entt::entity{4}, entt::entity{5}};
|
||||
lhs.insert(std::begin(lhs_entities), std::end(lhs_entities));
|
||||
lhs.push(std::begin(lhs_entities), std::end(lhs_entities));
|
||||
|
||||
entt::entity rhs_entities[6u]{entt::entity{6}, entt::entity{1}, entt::entity{2}, entt::entity{3}, entt::entity{4}, entt::entity{5}};
|
||||
rhs.insert(std::begin(rhs_entities), std::end(rhs_entities));
|
||||
rhs.push(std::begin(rhs_entities), std::end(rhs_entities));
|
||||
|
||||
ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.begin(), lhs.end()));
|
||||
ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.begin(), rhs.end()));
|
||||
|
||||
rhs.respect(lhs);
|
||||
rhs.sort_as(lhs);
|
||||
|
||||
ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.begin(), rhs.end()));
|
||||
}
|
||||
@ -1235,15 +1306,15 @@ TEST(SparseSet, RespectReverse) {
|
||||
entt::sparse_set rhs;
|
||||
|
||||
entt::entity lhs_entities[5u]{entt::entity{1}, entt::entity{2}, entt::entity{3}, entt::entity{4}, entt::entity{5}};
|
||||
lhs.insert(std::begin(lhs_entities), std::end(lhs_entities));
|
||||
lhs.push(std::begin(lhs_entities), std::end(lhs_entities));
|
||||
|
||||
entt::entity rhs_entities[6u]{entt::entity{5}, entt::entity{4}, entt::entity{3}, entt::entity{2}, entt::entity{1}, entt::entity{6}};
|
||||
rhs.insert(std::begin(rhs_entities), std::end(rhs_entities));
|
||||
rhs.push(std::begin(rhs_entities), std::end(rhs_entities));
|
||||
|
||||
ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.begin(), lhs.end()));
|
||||
ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.begin(), rhs.end()));
|
||||
|
||||
rhs.respect(lhs);
|
||||
rhs.sort_as(lhs);
|
||||
|
||||
auto begin = rhs.begin();
|
||||
auto end = rhs.end();
|
||||
@ -1262,15 +1333,15 @@ TEST(SparseSet, RespectUnordered) {
|
||||
entt::sparse_set rhs;
|
||||
|
||||
entt::entity lhs_entities[5u]{entt::entity{1}, entt::entity{2}, entt::entity{3}, entt::entity{4}, entt::entity{5}};
|
||||
lhs.insert(std::begin(lhs_entities), std::end(lhs_entities));
|
||||
lhs.push(std::begin(lhs_entities), std::end(lhs_entities));
|
||||
|
||||
entt::entity rhs_entities[6u]{entt::entity{3}, entt::entity{2}, entt::entity{6}, entt::entity{1}, entt::entity{4}, entt::entity{5}};
|
||||
rhs.insert(std::begin(rhs_entities), std::end(rhs_entities));
|
||||
rhs.push(std::begin(rhs_entities), std::end(rhs_entities));
|
||||
|
||||
ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.begin(), lhs.end()));
|
||||
ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.begin(), rhs.end()));
|
||||
|
||||
rhs.respect(lhs);
|
||||
rhs.sort_as(lhs);
|
||||
|
||||
auto begin = rhs.begin();
|
||||
auto end = rhs.end();
|
||||
@ -1291,15 +1362,15 @@ TEST(SparseSet, RespectInvalid) {
|
||||
entt::sparse_set rhs;
|
||||
|
||||
entt::entity lhs_entities[3u]{entt::entity{1}, entt::entity{2}, traits_type::construct(3, 1)};
|
||||
lhs.insert(std::begin(lhs_entities), std::end(lhs_entities));
|
||||
lhs.push(std::begin(lhs_entities), std::end(lhs_entities));
|
||||
|
||||
entt::entity rhs_entities[3u]{entt::entity{2}, entt::entity{1}, traits_type::construct(3, 2)};
|
||||
rhs.insert(std::begin(rhs_entities), std::end(rhs_entities));
|
||||
rhs.push(std::begin(rhs_entities), std::end(rhs_entities));
|
||||
|
||||
ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.begin(), lhs.end()));
|
||||
ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.begin(), rhs.end()));
|
||||
|
||||
rhs.respect(lhs);
|
||||
rhs.sort_as(lhs);
|
||||
|
||||
auto begin = rhs.begin();
|
||||
auto end = rhs.end();
|
||||
@ -1315,7 +1386,7 @@ TEST(SparseSet, RespectInvalid) {
|
||||
|
||||
TEST(SparseSet, CanModifyDuringIteration) {
|
||||
entt::sparse_set set;
|
||||
set.emplace(entt::entity{0});
|
||||
set.push(entt::entity{0});
|
||||
|
||||
ASSERT_EQ(set.capacity(), 1u);
|
||||
|
||||
@ -1338,8 +1409,8 @@ TEST(SparseSet, CustomAllocator) {
|
||||
|
||||
ASSERT_EQ(set.capacity(), 1u);
|
||||
|
||||
set.emplace(entt::entity{0});
|
||||
set.emplace(entt::entity{1});
|
||||
set.push(entt::entity{0});
|
||||
set.push(entt::entity{1});
|
||||
|
||||
entt::basic_sparse_set<entt::entity, test::throwing_allocator<entt::entity>> other{std::move(set), allocator};
|
||||
|
||||
@ -1389,11 +1460,11 @@ TEST(SparseSet, ThrowingAllocator) {
|
||||
|
||||
test::throwing_allocator<entt::entity>::trigger_on_allocate = true;
|
||||
|
||||
ASSERT_THROW(set.emplace(entt::entity{0}), test::throwing_allocator<entt::entity>::exception_type);
|
||||
ASSERT_THROW(set.push(entt::entity{0}), test::throwing_allocator<entt::entity>::exception_type);
|
||||
ASSERT_EQ(set.extent(), traits_type::page_size);
|
||||
ASSERT_EQ(set.capacity(), 0u);
|
||||
|
||||
set.emplace(entt::entity{0});
|
||||
set.push(entt::entity{0});
|
||||
test::throwing_allocator<entt::entity>::trigger_on_allocate = true;
|
||||
|
||||
ASSERT_THROW(set.reserve(2u), test::throwing_allocator<entt::entity>::exception_type);
|
||||
@ -1403,7 +1474,7 @@ TEST(SparseSet, ThrowingAllocator) {
|
||||
|
||||
test::throwing_allocator<entt::entity>::trigger_on_allocate = true;
|
||||
|
||||
ASSERT_THROW(set.emplace(entt::entity{1}), test::throwing_allocator<entt::entity>::exception_type);
|
||||
ASSERT_THROW(set.push(entt::entity{1}), test::throwing_allocator<entt::entity>::exception_type);
|
||||
ASSERT_EQ(set.extent(), traits_type::page_size);
|
||||
ASSERT_TRUE(set.contains(entt::entity{0}));
|
||||
ASSERT_FALSE(set.contains(entt::entity{1}));
|
||||
@ -1412,7 +1483,7 @@ TEST(SparseSet, ThrowingAllocator) {
|
||||
entt::entity entities[2u]{entt::entity{1}, entt::entity{traits_type::page_size}};
|
||||
test::throwing_allocator<entt::entity>::trigger_after_allocate = true;
|
||||
|
||||
ASSERT_THROW(set.insert(std::begin(entities), std::end(entities)), test::throwing_allocator<entt::entity>::exception_type);
|
||||
ASSERT_THROW(set.push(std::begin(entities), std::end(entities)), test::throwing_allocator<entt::entity>::exception_type);
|
||||
ASSERT_EQ(set.extent(), 2 * traits_type::page_size);
|
||||
ASSERT_TRUE(set.contains(entt::entity{0}));
|
||||
ASSERT_TRUE(set.contains(entt::entity{1}));
|
||||
@ -1420,7 +1491,7 @@ TEST(SparseSet, ThrowingAllocator) {
|
||||
ASSERT_EQ(set.capacity(), 2u);
|
||||
ASSERT_EQ(set.size(), 2u);
|
||||
|
||||
set.emplace(entities[1u]);
|
||||
set.push(entities[1u]);
|
||||
|
||||
ASSERT_TRUE(set.contains(entt::entity{traits_type::page_size}));
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
628
test/entt/entity/storage_entity.cpp
Normal file
628
test/entt/entity/storage_entity.cpp
Normal file
@ -0,0 +1,628 @@
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/storage.hpp>
|
||||
#include "../common/config.h"
|
||||
#include "../common/throwing_allocator.hpp"
|
||||
|
||||
TEST(StorageEntity, TypeAndPolicy) {
|
||||
entt::storage<entt::entity> pool;
|
||||
|
||||
ASSERT_EQ(pool.type(), entt::type_id<entt::entity>());
|
||||
ASSERT_EQ(pool.policy(), entt::deletion_policy::swap_and_pop);
|
||||
}
|
||||
|
||||
TEST(StorageEntity, Functionalities) {
|
||||
entt::entity entities[2u]{entt::entity{0}, entt::entity{1}};
|
||||
entt::storage<entt::entity> pool;
|
||||
|
||||
ASSERT_TRUE(pool.empty());
|
||||
ASSERT_EQ(pool.size(), 0u);
|
||||
ASSERT_EQ(pool.in_use(), 0u);
|
||||
|
||||
ASSERT_EQ(*pool.push(entt::null), entities[0u]);
|
||||
ASSERT_EQ(*pool.push(entt::tombstone), entities[1u]);
|
||||
|
||||
ASSERT_FALSE(pool.empty());
|
||||
ASSERT_EQ(pool.size(), 2u);
|
||||
ASSERT_EQ(pool.in_use(), 2u);
|
||||
|
||||
pool.in_use(1u);
|
||||
|
||||
ASSERT_FALSE(pool.empty());
|
||||
ASSERT_EQ(pool.size(), 2u);
|
||||
ASSERT_EQ(pool.in_use(), 1u);
|
||||
|
||||
ASSERT_NO_THROW(pool.get(entities[0u]));
|
||||
ASSERT_EQ(pool.get_as_tuple(entities[0u]), std::tuple<>{});
|
||||
|
||||
pool.erase(entities[0u]);
|
||||
|
||||
ASSERT_FALSE(pool.empty());
|
||||
ASSERT_EQ(pool.size(), 2u);
|
||||
ASSERT_EQ(pool.in_use(), 0u);
|
||||
}
|
||||
|
||||
ENTT_DEBUG_TEST(StorageEntityDeathTest, Get) {
|
||||
entt::storage<entt::entity> pool;
|
||||
pool.emplace(entt::entity{99});
|
||||
|
||||
ASSERT_DEATH(pool.get(entt::entity{3}), "");
|
||||
ASSERT_DEATH([[maybe_unused]] auto tup = pool.get_as_tuple(entt::entity{3}), "");
|
||||
|
||||
ASSERT_NO_THROW(pool.get(entt::entity{99}));
|
||||
ASSERT_NO_THROW([[maybe_unused]] auto tup = pool.get_as_tuple(entt::entity{99}));
|
||||
|
||||
pool.erase(entt::entity{99});
|
||||
|
||||
ASSERT_DEATH(pool.get(entt::entity{99}), "");
|
||||
ASSERT_DEATH([[maybe_unused]] auto tup = pool.get_as_tuple(entt::entity{99}), "");
|
||||
}
|
||||
|
||||
TEST(StorageEntity, Move) {
|
||||
entt::storage<entt::entity> pool;
|
||||
|
||||
pool.push(entt::entity{1});
|
||||
|
||||
ASSERT_EQ(pool.size(), 2u);
|
||||
ASSERT_EQ(pool.in_use(), 1u);
|
||||
|
||||
ASSERT_TRUE(std::is_move_constructible_v<decltype(pool)>);
|
||||
ASSERT_TRUE(std::is_move_assignable_v<decltype(pool)>);
|
||||
|
||||
entt::storage<entt::entity> other{std::move(pool)};
|
||||
|
||||
ASSERT_EQ(pool.size(), 0u);
|
||||
ASSERT_EQ(other.size(), 2u);
|
||||
ASSERT_EQ(pool.in_use(), 0u);
|
||||
ASSERT_EQ(other.in_use(), 1u);
|
||||
ASSERT_EQ(pool.at(0u), static_cast<entt::entity>(entt::null));
|
||||
ASSERT_EQ(other.at(0u), entt::entity{1});
|
||||
|
||||
pool = std::move(other);
|
||||
|
||||
ASSERT_EQ(pool.size(), 2u);
|
||||
ASSERT_EQ(other.size(), 0u);
|
||||
ASSERT_EQ(pool.in_use(), 1u);
|
||||
ASSERT_EQ(other.in_use(), 0u);
|
||||
ASSERT_EQ(pool.at(0u), entt::entity{1});
|
||||
ASSERT_EQ(other.at(0u), static_cast<entt::entity>(entt::null));
|
||||
|
||||
other = entt::storage<entt::entity>{};
|
||||
|
||||
other.push(entt::entity{3});
|
||||
other = std::move(pool);
|
||||
|
||||
ASSERT_EQ(pool.size(), 0u);
|
||||
ASSERT_EQ(other.size(), 2u);
|
||||
ASSERT_EQ(pool.in_use(), 0u);
|
||||
ASSERT_EQ(other.in_use(), 1u);
|
||||
ASSERT_EQ(pool.at(0u), static_cast<entt::entity>(entt::null));
|
||||
ASSERT_EQ(other.at(0u), entt::entity{1});
|
||||
|
||||
other.clear();
|
||||
|
||||
ASSERT_EQ(other.size(), 0u);
|
||||
ASSERT_EQ(other.in_use(), 0u);
|
||||
|
||||
ASSERT_EQ(*other.push(entt::null), entt::entity{0});
|
||||
}
|
||||
|
||||
TEST(StorageEntity, Swap) {
|
||||
entt::storage<entt::entity> pool;
|
||||
entt::storage<entt::entity> other;
|
||||
|
||||
pool.push(entt::entity{1});
|
||||
|
||||
other.push(entt::entity{2});
|
||||
other.push(entt::entity{0});
|
||||
other.erase(entt::entity{2});
|
||||
|
||||
ASSERT_EQ(pool.size(), 2u);
|
||||
ASSERT_EQ(other.size(), 3u);
|
||||
ASSERT_EQ(pool.in_use(), 1u);
|
||||
ASSERT_EQ(other.in_use(), 1u);
|
||||
|
||||
pool.swap(other);
|
||||
|
||||
ASSERT_EQ(pool.size(), 3u);
|
||||
ASSERT_EQ(other.size(), 2u);
|
||||
ASSERT_EQ(pool.in_use(), 1u);
|
||||
ASSERT_EQ(other.in_use(), 1u);
|
||||
|
||||
ASSERT_EQ(pool.at(0u), entt::entity{0});
|
||||
ASSERT_EQ(other.at(0u), entt::entity{1});
|
||||
|
||||
pool.clear();
|
||||
other.clear();
|
||||
|
||||
ASSERT_EQ(pool.size(), 0u);
|
||||
ASSERT_EQ(other.size(), 0u);
|
||||
ASSERT_EQ(pool.in_use(), 0u);
|
||||
ASSERT_EQ(other.in_use(), 0u);
|
||||
|
||||
ASSERT_EQ(*other.push(entt::null), entt::entity{0});
|
||||
}
|
||||
|
||||
TEST(StorageEntity, Push) {
|
||||
using traits_type = entt::entt_traits<entt::entity>;
|
||||
|
||||
entt::storage<entt::entity> pool;
|
||||
|
||||
ASSERT_EQ(*pool.push(entt::null), entt::entity{0});
|
||||
ASSERT_EQ(*pool.push(entt::tombstone), entt::entity{1});
|
||||
ASSERT_EQ(*pool.push(entt::entity{0}), entt::entity{2});
|
||||
ASSERT_EQ(*pool.push(traits_type::construct(1, 1)), entt::entity{3});
|
||||
ASSERT_EQ(*pool.push(traits_type::construct(5, 3)), traits_type::construct(5, 3));
|
||||
|
||||
ASSERT_LT(pool.index(entt::entity{0}), pool.in_use());
|
||||
ASSERT_LT(pool.index(entt::entity{1}), pool.in_use());
|
||||
ASSERT_LT(pool.index(entt::entity{2}), pool.in_use());
|
||||
ASSERT_LT(pool.index(entt::entity{3}), pool.in_use());
|
||||
ASSERT_GE(pool.index(entt::entity{4}), pool.in_use());
|
||||
ASSERT_LT(pool.index(traits_type::construct(5, 3)), pool.in_use());
|
||||
|
||||
ASSERT_EQ(*pool.push(traits_type::construct(4, 42)), traits_type::construct(4, 42));
|
||||
ASSERT_EQ(*pool.push(traits_type::construct(4, 43)), entt::entity{6});
|
||||
|
||||
entt::entity entities[2u]{entt::entity{1}, traits_type::construct(5, 3)};
|
||||
|
||||
pool.erase(entities, entities + 2u);
|
||||
pool.erase(entt::entity{2});
|
||||
|
||||
ASSERT_EQ(pool.current(entities[0u]), 1);
|
||||
ASSERT_EQ(pool.current(entities[1u]), 4);
|
||||
ASSERT_EQ(pool.current(entt::entity{2}), 1);
|
||||
|
||||
ASSERT_LT(pool.index(entt::entity{0}), pool.in_use());
|
||||
ASSERT_GE(pool.index(traits_type::construct(1, 1)), pool.in_use());
|
||||
ASSERT_GE(pool.index(traits_type::construct(2, 1)), pool.in_use());
|
||||
ASSERT_LT(pool.index(entt::entity{3}), pool.in_use());
|
||||
ASSERT_LT(pool.index(traits_type::construct(4, 42)), pool.in_use());
|
||||
ASSERT_GE(pool.index(traits_type::construct(5, 4)), pool.in_use());
|
||||
|
||||
ASSERT_EQ(*pool.push(entt::null), traits_type::construct(2, 1));
|
||||
ASSERT_EQ(*pool.push(traits_type::construct(1, 3)), traits_type::construct(1, 3));
|
||||
ASSERT_EQ(*pool.push(entt::null), traits_type::construct(5, 4));
|
||||
ASSERT_EQ(*pool.push(entt::null), entt::entity{7});
|
||||
}
|
||||
|
||||
TEST(StorageEntity, Emplace) {
|
||||
using traits_type = entt::entt_traits<entt::entity>;
|
||||
|
||||
entt::storage<entt::entity> pool;
|
||||
entt::entity entities[2u]{};
|
||||
|
||||
ASSERT_EQ(pool.emplace(), entt::entity{0});
|
||||
ASSERT_EQ(pool.emplace(entt::null), entt::entity{1});
|
||||
ASSERT_EQ(pool.emplace(entt::tombstone), entt::entity{2});
|
||||
ASSERT_EQ(pool.emplace(entt::entity{0}), entt::entity{3});
|
||||
ASSERT_EQ(pool.emplace(traits_type::construct(1, 1)), entt::entity{4});
|
||||
ASSERT_EQ(pool.emplace(traits_type::construct(6, 3)), traits_type::construct(6, 3));
|
||||
|
||||
ASSERT_LT(pool.index(entt::entity{0}), pool.in_use());
|
||||
ASSERT_LT(pool.index(entt::entity{1}), pool.in_use());
|
||||
ASSERT_LT(pool.index(entt::entity{2}), pool.in_use());
|
||||
ASSERT_LT(pool.index(entt::entity{3}), pool.in_use());
|
||||
ASSERT_LT(pool.index(entt::entity{4}), pool.in_use());
|
||||
ASSERT_GE(pool.index(entt::entity{5}), pool.in_use());
|
||||
ASSERT_LT(pool.index(traits_type::construct(6, 3)), pool.in_use());
|
||||
|
||||
ASSERT_EQ(pool.emplace(traits_type::construct(5, 42)), traits_type::construct(5, 42));
|
||||
ASSERT_EQ(pool.emplace(traits_type::construct(5, 43)), entt::entity{7});
|
||||
|
||||
pool.erase(entt::entity{2});
|
||||
|
||||
ASSERT_EQ(pool.emplace(), traits_type::construct(2, 1));
|
||||
|
||||
pool.erase(traits_type::construct(2, 1));
|
||||
pool.insert(entities, entities + 2u);
|
||||
|
||||
ASSERT_EQ(entities[0u], traits_type::construct(2, 2));
|
||||
ASSERT_EQ(entities[1u], entt::entity{8});
|
||||
}
|
||||
|
||||
TEST(StorageEntity, Patch) {
|
||||
entt::storage<entt::entity> pool;
|
||||
const auto entity = pool.emplace();
|
||||
|
||||
int counter = 0;
|
||||
auto callback = [&counter]() { ++counter; };
|
||||
|
||||
ASSERT_EQ(counter, 0);
|
||||
|
||||
pool.patch(entity);
|
||||
pool.patch(entity, callback);
|
||||
pool.patch(entity, callback, callback);
|
||||
|
||||
ASSERT_EQ(counter, 3);
|
||||
}
|
||||
|
||||
ENTT_DEBUG_TEST(StorageEntityDeathTest, Patch) {
|
||||
entt::storage<entt::entity> pool;
|
||||
|
||||
ASSERT_DEATH(pool.patch(entt::null), "");
|
||||
}
|
||||
|
||||
TEST(StorageEntity, Insert) {
|
||||
entt::storage<entt::entity> pool;
|
||||
entt::entity entities[2u]{};
|
||||
|
||||
pool.insert(std::begin(entities), std::end(entities));
|
||||
|
||||
ASSERT_TRUE(pool.contains(entities[0u]));
|
||||
ASSERT_TRUE(pool.contains(entities[1u]));
|
||||
|
||||
ASSERT_FALSE(pool.empty());
|
||||
ASSERT_EQ(pool.size(), 2u);
|
||||
ASSERT_EQ(pool.in_use(), 2u);
|
||||
|
||||
pool.erase(std::begin(entities), std::end(entities));
|
||||
|
||||
ASSERT_FALSE(pool.empty());
|
||||
ASSERT_EQ(pool.size(), 2u);
|
||||
ASSERT_EQ(pool.in_use(), 0u);
|
||||
|
||||
pool.insert(entities, entities + 1u);
|
||||
|
||||
ASSERT_TRUE(pool.contains(entities[0u]));
|
||||
ASSERT_FALSE(pool.contains(entities[1u]));
|
||||
|
||||
ASSERT_FALSE(pool.empty());
|
||||
ASSERT_EQ(pool.size(), 2u);
|
||||
ASSERT_EQ(pool.in_use(), 1u);
|
||||
}
|
||||
|
||||
TEST(StorageEntity, Pack) {
|
||||
entt::storage<entt::entity> pool;
|
||||
entt::entity entities[3u]{entt::entity{1}, entt::entity{3}, entt::entity{42}};
|
||||
|
||||
pool.push(entities, entities + 3u);
|
||||
std::swap(entities[0u], entities[1u]);
|
||||
|
||||
const auto len = pool.pack(entities + 1u, entities + 3u);
|
||||
auto it = pool.each().cbegin().base();
|
||||
|
||||
ASSERT_NE(it, pool.cbegin());
|
||||
ASSERT_NE(it, pool.cend());
|
||||
|
||||
ASSERT_EQ(len, 2u);
|
||||
ASSERT_NE(it + len, pool.cend());
|
||||
ASSERT_EQ(it + len + 1u, pool.cend());
|
||||
|
||||
ASSERT_EQ(*it++, entities[1u]);
|
||||
ASSERT_EQ(*it++, entities[2u]);
|
||||
|
||||
ASSERT_NE(it, pool.cend());
|
||||
ASSERT_EQ(*it++, entities[0u]);
|
||||
ASSERT_EQ(it, pool.cend());
|
||||
}
|
||||
|
||||
TEST(StorageEntity, Iterable) {
|
||||
using iterator = typename entt::storage<entt::entity>::iterable::iterator;
|
||||
|
||||
static_assert(std::is_same_v<iterator::value_type, std::tuple<entt::entity>>);
|
||||
static_assert(std::is_same_v<typename iterator::pointer, entt::input_iterator_pointer<std::tuple<entt::entity>>>);
|
||||
static_assert(std::is_same_v<typename iterator::reference, typename iterator::value_type>);
|
||||
|
||||
entt::storage<entt::entity> pool;
|
||||
|
||||
pool.emplace(entt::entity{1});
|
||||
pool.emplace(entt::entity{3});
|
||||
pool.emplace(entt::entity{42});
|
||||
|
||||
pool.erase(entt::entity{3});
|
||||
|
||||
auto iterable = pool.each();
|
||||
|
||||
iterator end{iterable.begin()};
|
||||
iterator begin{};
|
||||
begin = iterable.end();
|
||||
std::swap(begin, end);
|
||||
|
||||
ASSERT_EQ(begin, iterable.begin());
|
||||
ASSERT_EQ(end, iterable.end());
|
||||
ASSERT_NE(begin, end);
|
||||
|
||||
ASSERT_NE(begin.base(), pool.begin());
|
||||
ASSERT_EQ(begin.base(), pool.end() - pool.in_use());
|
||||
ASSERT_EQ(end.base(), pool.end());
|
||||
|
||||
ASSERT_EQ(std::get<0>(*begin.operator->().operator->()), entt::entity{42});
|
||||
ASSERT_EQ(std::get<0>(*begin), entt::entity{42});
|
||||
|
||||
ASSERT_EQ(begin++, iterable.begin());
|
||||
ASSERT_EQ(begin.base(), pool.end() - 1);
|
||||
ASSERT_EQ(++begin, iterable.end());
|
||||
ASSERT_EQ(begin.base(), pool.end());
|
||||
|
||||
for(auto [entity]: iterable) {
|
||||
static_assert(std::is_same_v<decltype(entity), entt::entity>);
|
||||
ASSERT_TRUE(entity != entt::entity{3});
|
||||
}
|
||||
}
|
||||
|
||||
TEST(StorageEntity, ConstIterable) {
|
||||
using iterator = typename entt::storage<entt::entity>::const_iterable::iterator;
|
||||
|
||||
static_assert(std::is_same_v<iterator::value_type, std::tuple<entt::entity>>);
|
||||
static_assert(std::is_same_v<typename iterator::pointer, entt::input_iterator_pointer<std::tuple<entt::entity>>>);
|
||||
static_assert(std::is_same_v<typename iterator::reference, typename iterator::value_type>);
|
||||
|
||||
entt::storage<entt::entity> pool;
|
||||
|
||||
pool.emplace(entt::entity{1});
|
||||
pool.emplace(entt::entity{3});
|
||||
pool.emplace(entt::entity{42});
|
||||
|
||||
pool.erase(entt::entity{3});
|
||||
|
||||
auto iterable = std::as_const(pool).each();
|
||||
|
||||
iterator end{iterable.cbegin()};
|
||||
iterator begin{};
|
||||
begin = iterable.cend();
|
||||
std::swap(begin, end);
|
||||
|
||||
ASSERT_EQ(begin, iterable.cbegin());
|
||||
ASSERT_EQ(end, iterable.cend());
|
||||
ASSERT_NE(begin, end);
|
||||
|
||||
ASSERT_NE(begin.base(), pool.begin());
|
||||
ASSERT_EQ(begin.base(), pool.end() - pool.in_use());
|
||||
ASSERT_EQ(end.base(), pool.end());
|
||||
|
||||
ASSERT_EQ(std::get<0>(*begin.operator->().operator->()), entt::entity{42});
|
||||
ASSERT_EQ(std::get<0>(*begin), entt::entity{42});
|
||||
|
||||
ASSERT_EQ(begin++, iterable.begin());
|
||||
ASSERT_EQ(begin.base(), pool.end() - 1);
|
||||
ASSERT_EQ(++begin, iterable.end());
|
||||
ASSERT_EQ(begin.base(), pool.end());
|
||||
|
||||
for(auto [entity]: iterable) {
|
||||
static_assert(std::is_same_v<decltype(entity), entt::entity>);
|
||||
ASSERT_TRUE(entity != entt::entity{3});
|
||||
}
|
||||
}
|
||||
|
||||
TEST(StorageEntity, IterableIteratorConversion) {
|
||||
entt::storage<entt::entity> pool;
|
||||
pool.emplace(entt::entity{3});
|
||||
|
||||
typename entt::storage<entt::entity>::iterable::iterator it = pool.each().begin();
|
||||
typename entt::storage<entt::entity>::const_iterable::iterator cit = it;
|
||||
|
||||
static_assert(std::is_same_v<decltype(*it), std::tuple<entt::entity>>);
|
||||
static_assert(std::is_same_v<decltype(*cit), std::tuple<entt::entity>>);
|
||||
|
||||
ASSERT_EQ(it, cit);
|
||||
ASSERT_NE(++cit, it);
|
||||
}
|
||||
|
||||
TEST(StorageEntity, IterableAlgorithmCompatibility) {
|
||||
entt::storage<entt::entity> pool;
|
||||
pool.emplace(entt::entity{3});
|
||||
|
||||
const auto iterable = pool.each();
|
||||
const auto it = std::find_if(iterable.begin(), iterable.end(), [](auto args) { return std::get<0>(args) == entt::entity{3}; });
|
||||
|
||||
ASSERT_EQ(std::get<0>(*it), entt::entity{3});
|
||||
}
|
||||
|
||||
TEST(StorageEntity, ReverseIterable) {
|
||||
using iterator = typename entt::storage<entt::entity>::reverse_iterable::iterator;
|
||||
|
||||
static_assert(std::is_same_v<iterator::value_type, std::tuple<entt::entity>>);
|
||||
static_assert(std::is_same_v<typename iterator::pointer, entt::input_iterator_pointer<std::tuple<entt::entity>>>);
|
||||
static_assert(std::is_same_v<typename iterator::reference, typename iterator::value_type>);
|
||||
|
||||
entt::storage<entt::entity> pool;
|
||||
|
||||
pool.emplace(entt::entity{1});
|
||||
pool.emplace(entt::entity{3});
|
||||
pool.emplace(entt::entity{42});
|
||||
|
||||
pool.erase(entt::entity{3});
|
||||
|
||||
auto iterable = pool.reach();
|
||||
|
||||
iterator end{iterable.begin()};
|
||||
iterator begin{};
|
||||
begin = iterable.end();
|
||||
std::swap(begin, end);
|
||||
|
||||
ASSERT_EQ(begin, iterable.begin());
|
||||
ASSERT_EQ(end, iterable.end());
|
||||
ASSERT_NE(begin, end);
|
||||
|
||||
ASSERT_EQ(begin.base(), pool.rbegin());
|
||||
ASSERT_EQ(end.base(), pool.rbegin() + pool.in_use());
|
||||
ASSERT_NE(end.base(), pool.rend());
|
||||
|
||||
ASSERT_EQ(std::get<0>(*begin.operator->().operator->()), entt::entity{1});
|
||||
ASSERT_EQ(std::get<0>(*begin), entt::entity{1});
|
||||
|
||||
ASSERT_EQ(begin++, iterable.begin());
|
||||
ASSERT_EQ(begin.base(), pool.rbegin() + 1);
|
||||
ASSERT_EQ(++begin, iterable.end());
|
||||
ASSERT_EQ(begin.base(), pool.rbegin() + 2);
|
||||
|
||||
for(auto [entity]: iterable) {
|
||||
static_assert(std::is_same_v<decltype(entity), entt::entity>);
|
||||
ASSERT_TRUE(entity != entt::entity{3});
|
||||
}
|
||||
}
|
||||
|
||||
TEST(StorageEntity, ReverseConstIterable) {
|
||||
using iterator = typename entt::storage<entt::entity>::const_reverse_iterable::iterator;
|
||||
|
||||
static_assert(std::is_same_v<iterator::value_type, std::tuple<entt::entity>>);
|
||||
static_assert(std::is_same_v<typename iterator::pointer, entt::input_iterator_pointer<std::tuple<entt::entity>>>);
|
||||
static_assert(std::is_same_v<typename iterator::reference, typename iterator::value_type>);
|
||||
|
||||
entt::storage<entt::entity> pool;
|
||||
|
||||
pool.emplace(entt::entity{1});
|
||||
pool.emplace(entt::entity{3});
|
||||
pool.emplace(entt::entity{42});
|
||||
|
||||
pool.erase(entt::entity{3});
|
||||
|
||||
auto iterable = std::as_const(pool).reach();
|
||||
|
||||
iterator end{iterable.cbegin()};
|
||||
iterator begin{};
|
||||
begin = iterable.cend();
|
||||
std::swap(begin, end);
|
||||
|
||||
ASSERT_EQ(begin, iterable.cbegin());
|
||||
ASSERT_EQ(end, iterable.cend());
|
||||
ASSERT_NE(begin, end);
|
||||
|
||||
ASSERT_EQ(begin.base(), pool.rbegin());
|
||||
ASSERT_EQ(end.base(), pool.rbegin() + pool.in_use());
|
||||
ASSERT_NE(end.base(), pool.rend());
|
||||
|
||||
ASSERT_EQ(std::get<0>(*begin.operator->().operator->()), entt::entity{1});
|
||||
ASSERT_EQ(std::get<0>(*begin), entt::entity{1});
|
||||
|
||||
ASSERT_EQ(begin++, iterable.begin());
|
||||
ASSERT_EQ(begin.base(), pool.rbegin() + 1);
|
||||
ASSERT_EQ(++begin, iterable.end());
|
||||
ASSERT_EQ(begin.base(), pool.rbegin() + 2);
|
||||
|
||||
for(auto [entity]: iterable) {
|
||||
static_assert(std::is_same_v<decltype(entity), entt::entity>);
|
||||
ASSERT_TRUE(entity != entt::entity{3});
|
||||
}
|
||||
}
|
||||
|
||||
TEST(StorageEntity, ReverseIterableIteratorConversion) {
|
||||
entt::storage<entt::entity> pool;
|
||||
pool.emplace(entt::entity{3});
|
||||
|
||||
typename entt::storage<entt::entity>::reverse_iterable::iterator it = pool.reach().begin();
|
||||
typename entt::storage<entt::entity>::const_reverse_iterable::iterator cit = it;
|
||||
|
||||
static_assert(std::is_same_v<decltype(*it), std::tuple<entt::entity>>);
|
||||
static_assert(std::is_same_v<decltype(*cit), std::tuple<entt::entity>>);
|
||||
|
||||
ASSERT_EQ(it, cit);
|
||||
ASSERT_NE(++cit, it);
|
||||
}
|
||||
|
||||
TEST(StorageEntity, ReverseIterableAlgorithmCompatibility) {
|
||||
entt::storage<entt::entity> pool;
|
||||
pool.emplace(entt::entity{3});
|
||||
|
||||
const auto iterable = pool.reach();
|
||||
const auto it = std::find_if(iterable.begin(), iterable.end(), [](auto args) { return std::get<0>(args) == entt::entity{3}; });
|
||||
|
||||
ASSERT_EQ(std::get<0>(*it), entt::entity{3});
|
||||
}
|
||||
|
||||
TEST(StorageEntity, SwapElements) {
|
||||
entt::storage<entt::entity> pool;
|
||||
|
||||
pool.push(entt::entity{0});
|
||||
pool.push(entt::entity{1});
|
||||
|
||||
ASSERT_EQ(pool.size(), 2u);
|
||||
ASSERT_EQ(pool.in_use(), 2u);
|
||||
ASSERT_TRUE(pool.contains(entt::entity{0}));
|
||||
ASSERT_TRUE(pool.contains(entt::entity{1}));
|
||||
|
||||
ASSERT_EQ(*pool.begin(), entt::entity{1});
|
||||
ASSERT_EQ(*++pool.begin(), entt::entity{0});
|
||||
|
||||
pool.swap_elements(entt::entity{0}, entt::entity{1});
|
||||
|
||||
ASSERT_EQ(*pool.begin(), entt::entity{0});
|
||||
ASSERT_EQ(*++pool.begin(), entt::entity{1});
|
||||
}
|
||||
|
||||
ENTT_DEBUG_TEST(StorageEntityDeathTest, SwapElements) {
|
||||
entt::storage<entt::entity> pool;
|
||||
|
||||
pool.push(entt::entity{1});
|
||||
|
||||
ASSERT_EQ(pool.size(), 2u);
|
||||
ASSERT_EQ(pool.in_use(), 1u);
|
||||
ASSERT_TRUE(pool.contains(entt::entity{0}));
|
||||
ASSERT_TRUE(pool.contains(entt::entity{1}));
|
||||
|
||||
ASSERT_DEATH(pool.swap_elements(entt::entity{0}, entt::entity{1}), "");
|
||||
}
|
||||
|
||||
ENTT_DEBUG_TEST(StorageEntityDeathTest, InUse) {
|
||||
entt::storage<entt::entity> pool;
|
||||
|
||||
pool.push(entt::entity{0});
|
||||
pool.push(entt::entity{1});
|
||||
|
||||
ASSERT_DEATH(pool.in_use(3u), "");
|
||||
}
|
||||
|
||||
ENTT_DEBUG_TEST(StorageEntityDeathTest, SortAndRespect) {
|
||||
entt::storage<entt::entity> pool;
|
||||
entt::storage<entt::entity> other;
|
||||
|
||||
pool.push(entt::entity{1});
|
||||
pool.push(entt::entity{2});
|
||||
pool.erase(entt::entity{2});
|
||||
|
||||
other.push(entt::entity{2});
|
||||
|
||||
ASSERT_DEATH(pool.sort([](auto...) { return true; }), "");
|
||||
ASSERT_DEATH(pool.sort_as(other), "");
|
||||
}
|
||||
|
||||
TEST(StorageEntity, CustomAllocator) {
|
||||
test::throwing_allocator<entt::entity> allocator{};
|
||||
entt::basic_storage<entt::entity, entt::entity, test::throwing_allocator<entt::entity>> pool{allocator};
|
||||
|
||||
pool.reserve(1u);
|
||||
|
||||
ASSERT_EQ(pool.size(), 0u);
|
||||
ASSERT_EQ(pool.in_use(), 0u);
|
||||
|
||||
pool.push(entt::entity{0});
|
||||
pool.push(entt::entity{1});
|
||||
|
||||
ASSERT_EQ(pool.size(), 2u);
|
||||
ASSERT_EQ(pool.in_use(), 2u);
|
||||
|
||||
decltype(pool) other{std::move(pool), allocator};
|
||||
|
||||
ASSERT_TRUE(pool.empty());
|
||||
ASSERT_FALSE(other.empty());
|
||||
ASSERT_EQ(pool.size(), 0u);
|
||||
ASSERT_EQ(other.size(), 2u);
|
||||
ASSERT_EQ(pool.in_use(), 0u);
|
||||
ASSERT_EQ(other.in_use(), 2u);
|
||||
|
||||
pool = std::move(other);
|
||||
|
||||
ASSERT_FALSE(pool.empty());
|
||||
ASSERT_TRUE(other.empty());
|
||||
ASSERT_EQ(pool.size(), 2u);
|
||||
ASSERT_EQ(other.size(), 0u);
|
||||
ASSERT_EQ(pool.in_use(), 2u);
|
||||
ASSERT_EQ(other.in_use(), 0u);
|
||||
|
||||
pool.swap(other);
|
||||
pool = std::move(other);
|
||||
|
||||
ASSERT_FALSE(pool.empty());
|
||||
ASSERT_TRUE(other.empty());
|
||||
ASSERT_EQ(pool.size(), 2u);
|
||||
ASSERT_EQ(other.size(), 0u);
|
||||
ASSERT_EQ(pool.in_use(), 2u);
|
||||
ASSERT_EQ(other.in_use(), 0u);
|
||||
|
||||
pool.clear();
|
||||
|
||||
ASSERT_EQ(pool.size(), 0u);
|
||||
ASSERT_EQ(pool.in_use(), 0u);
|
||||
}
|
@ -24,7 +24,6 @@ TEST(SingleComponentView, Functionalities) {
|
||||
|
||||
ASSERT_TRUE(view.empty());
|
||||
|
||||
registry.emplace<int>(e1);
|
||||
registry.emplace<char>(e1);
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(view.begin()++);
|
||||
@ -70,6 +69,42 @@ TEST(SingleComponentView, Functionalities) {
|
||||
ASSERT_FALSE(invalid);
|
||||
}
|
||||
|
||||
TEST(SingleComponentView, InvalidView) {
|
||||
entt::basic_view<entt::get_t<entt::storage<int>>, entt::exclude_t<>> view{};
|
||||
|
||||
ASSERT_FALSE(view);
|
||||
|
||||
ASSERT_EQ(view.size(), 0u);
|
||||
ASSERT_TRUE(view.empty());
|
||||
ASSERT_FALSE(view.contains(entt::null));
|
||||
ASSERT_EQ(view.find(entt::null), view.end());
|
||||
|
||||
ASSERT_EQ(view.front(), static_cast<entt::entity>(entt::null));
|
||||
ASSERT_EQ(view.back(), static_cast<entt::entity>(entt::null));
|
||||
|
||||
ASSERT_EQ(view.begin(), typename decltype(view)::iterator{});
|
||||
ASSERT_EQ(view.begin(), view.end());
|
||||
|
||||
ASSERT_EQ(view.rbegin(), typename decltype(view)::reverse_iterator{});
|
||||
ASSERT_EQ(view.rbegin(), view.rend());
|
||||
|
||||
auto iterable = view.each();
|
||||
|
||||
ASSERT_EQ(iterable.begin(), iterable.end());
|
||||
ASSERT_EQ(iterable.cbegin(), iterable.cend());
|
||||
|
||||
view.each([](const int &) { FAIL(); });
|
||||
view.each([](const entt::entity, const int &) { FAIL(); });
|
||||
|
||||
entt::storage<int> storage;
|
||||
view.storage(storage);
|
||||
|
||||
ASSERT_TRUE(view);
|
||||
|
||||
view.each([](const int &) { FAIL(); });
|
||||
view.each([](const entt::entity, const int &) { FAIL(); });
|
||||
}
|
||||
|
||||
TEST(SingleComponentView, Constructors) {
|
||||
entt::storage<int> storage{};
|
||||
|
||||
@ -81,7 +116,8 @@ TEST(SingleComponentView, Constructors) {
|
||||
ASSERT_TRUE(from_storage);
|
||||
ASSERT_TRUE(from_tuple);
|
||||
|
||||
ASSERT_EQ(&from_storage.handle(), &from_tuple.handle());
|
||||
ASSERT_NE(from_storage.handle(), nullptr);
|
||||
ASSERT_EQ(from_storage.handle(), from_tuple.handle());
|
||||
}
|
||||
|
||||
TEST(SingleComponentView, Handle) {
|
||||
@ -89,17 +125,19 @@ TEST(SingleComponentView, Handle) {
|
||||
const auto entity = registry.create();
|
||||
|
||||
auto view = registry.view<int>();
|
||||
auto &&handle = view.handle();
|
||||
auto *handle = view.handle();
|
||||
|
||||
ASSERT_TRUE(handle.empty());
|
||||
ASSERT_FALSE(handle.contains(entity));
|
||||
ASSERT_EQ(&handle, &view.handle());
|
||||
ASSERT_NE(handle, nullptr);
|
||||
|
||||
ASSERT_TRUE(handle->empty());
|
||||
ASSERT_FALSE(handle->contains(entity));
|
||||
ASSERT_EQ(handle, view.handle());
|
||||
|
||||
registry.emplace<int>(entity);
|
||||
|
||||
ASSERT_FALSE(handle.empty());
|
||||
ASSERT_TRUE(handle.contains(entity));
|
||||
ASSERT_EQ(&handle, &view.handle());
|
||||
ASSERT_FALSE(handle->empty());
|
||||
ASSERT_TRUE(handle->contains(entity));
|
||||
ASSERT_EQ(handle, view.handle());
|
||||
}
|
||||
|
||||
TEST(SingleComponentView, LazyTypeFromConstRegistry) {
|
||||
@ -111,8 +149,8 @@ TEST(SingleComponentView, LazyTypeFromConstRegistry) {
|
||||
registry.emplace<empty_type>(entity);
|
||||
registry.emplace<int>(entity);
|
||||
|
||||
ASSERT_TRUE(cview);
|
||||
ASSERT_TRUE(eview);
|
||||
ASSERT_FALSE(cview);
|
||||
ASSERT_FALSE(eview);
|
||||
|
||||
ASSERT_TRUE(cview.empty());
|
||||
ASSERT_EQ(eview.size(), 0u);
|
||||
@ -164,14 +202,6 @@ TEST(SingleComponentView, Contains) {
|
||||
|
||||
TEST(SingleComponentView, Empty) {
|
||||
entt::registry registry;
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.emplace<char>(e0);
|
||||
registry.emplace<double>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.emplace<char>(e1);
|
||||
|
||||
auto view = registry.view<int>();
|
||||
|
||||
ASSERT_EQ(view.size(), 0u);
|
||||
@ -198,15 +228,17 @@ TEST(SingleComponentView, Each) {
|
||||
|
||||
auto it = iterable.begin();
|
||||
|
||||
ASSERT_EQ(it.base(), view.begin());
|
||||
ASSERT_EQ((it++, ++it), iterable.end());
|
||||
ASSERT_EQ(it.base(), view.end());
|
||||
|
||||
view.each([expected = 1u](auto entt, int &value) mutable {
|
||||
ASSERT_EQ(entt::to_integral(entt), expected);
|
||||
view.each([expected = 1](auto entt, int &value) mutable {
|
||||
ASSERT_EQ(static_cast<int>(entt::to_integral(entt)), expected);
|
||||
ASSERT_EQ(value, expected);
|
||||
--expected;
|
||||
});
|
||||
|
||||
cview.each([expected = 1u](const int &value) mutable {
|
||||
cview.each([expected = 1](const int &value) mutable {
|
||||
ASSERT_EQ(value, expected);
|
||||
--expected;
|
||||
});
|
||||
@ -219,7 +251,7 @@ TEST(SingleComponentView, Each) {
|
||||
|
||||
// do not use iterable, make sure an iterable view works when created from a temporary
|
||||
for(auto [entt, value]: view.each()) {
|
||||
ASSERT_EQ(entt::to_integral(entt), value);
|
||||
ASSERT_EQ(static_cast<int>(entt::to_integral(entt)), value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -334,15 +366,13 @@ TEST(SingleComponentView, Find) {
|
||||
|
||||
TEST(SingleComponentView, EmptyTypes) {
|
||||
entt::registry registry;
|
||||
entt::entity entities[2u];
|
||||
entt::entity entity = registry.create();
|
||||
|
||||
registry.create(std::begin(entities), std::end(entities));
|
||||
registry.emplace<int>(entities[0u], 0);
|
||||
registry.emplace<empty_type>(entities[0u]);
|
||||
registry.emplace<char>(entities[1u], 'c');
|
||||
registry.emplace<int>(entity, 0);
|
||||
registry.emplace<empty_type>(entity);
|
||||
|
||||
registry.view<empty_type>().each([&](const auto entt) {
|
||||
ASSERT_EQ(entities[0u], entt);
|
||||
ASSERT_EQ(entity, entt);
|
||||
});
|
||||
|
||||
registry.view<empty_type>().each([check = true]() mutable {
|
||||
@ -352,11 +382,11 @@ TEST(SingleComponentView, EmptyTypes) {
|
||||
|
||||
for(auto [entt]: registry.view<empty_type>().each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
ASSERT_EQ(entities[0u], entt);
|
||||
ASSERT_EQ(entity, entt);
|
||||
}
|
||||
|
||||
registry.view<int>().each([&](const auto entt, int) {
|
||||
ASSERT_EQ(entities[0u], entt);
|
||||
ASSERT_EQ(entity, entt);
|
||||
});
|
||||
|
||||
registry.view<int>().each([check = true](int) mutable {
|
||||
@ -367,7 +397,7 @@ TEST(SingleComponentView, EmptyTypes) {
|
||||
for(auto [entt, iv]: registry.view<int>().each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
ASSERT_EQ(entities[0u], entt);
|
||||
ASSERT_EQ(entity, entt);
|
||||
}
|
||||
}
|
||||
|
||||
@ -459,35 +489,87 @@ TEST(SingleComponentView, StableType) {
|
||||
TEST(SingleComponentView, Storage) {
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
const auto view = registry.view<int>();
|
||||
const auto cview = registry.view<const char>();
|
||||
auto view = registry.view<int>();
|
||||
auto cview = registry.view<const char>();
|
||||
|
||||
static_assert(std::is_same_v<decltype(view.storage()), entt::storage_type_t<int> &>);
|
||||
static_assert(std::is_same_v<decltype(view.storage<0u>()), entt::storage_type_t<int> &>);
|
||||
static_assert(std::is_same_v<decltype(view.storage<int>()), entt::storage_type_t<int> &>);
|
||||
static_assert(std::is_same_v<decltype(cview.storage()), const entt::storage_type_t<char> &>);
|
||||
static_assert(std::is_same_v<decltype(cview.storage<0u>()), const entt::storage_type_t<char> &>);
|
||||
static_assert(std::is_same_v<decltype(cview.storage<const char>()), const entt::storage_type_t<char> &>);
|
||||
static_assert(std::is_same_v<decltype(view.storage()), entt::storage_type_t<int> *>);
|
||||
static_assert(std::is_same_v<decltype(view.storage<0u>()), entt::storage_type_t<int> *>);
|
||||
static_assert(std::is_same_v<decltype(view.storage<int>()), entt::storage_type_t<int> *>);
|
||||
static_assert(std::is_same_v<decltype(view.storage<const int>()), entt::storage_type_t<int> *>);
|
||||
static_assert(std::is_same_v<decltype(cview.storage()), const entt::storage_type_t<char> *>);
|
||||
static_assert(std::is_same_v<decltype(cview.storage<0u>()), const entt::storage_type_t<char> *>);
|
||||
static_assert(std::is_same_v<decltype(cview.storage<char>()), const entt::storage_type_t<char> *>);
|
||||
static_assert(std::is_same_v<decltype(cview.storage<const char>()), const entt::storage_type_t<char> *>);
|
||||
|
||||
ASSERT_TRUE(view);
|
||||
ASSERT_TRUE(cview);
|
||||
|
||||
ASSERT_NE(view.storage<int>(), nullptr);
|
||||
ASSERT_NE(cview.storage<0u>(), nullptr);
|
||||
|
||||
ASSERT_EQ(view.size(), 0u);
|
||||
ASSERT_EQ(cview.size(), 0u);
|
||||
|
||||
view.storage().emplace(entity);
|
||||
view.storage()->emplace(entity);
|
||||
registry.emplace<char>(entity);
|
||||
|
||||
ASSERT_EQ(view.size(), 1u);
|
||||
ASSERT_EQ(cview.size(), 1u);
|
||||
ASSERT_TRUE(view.storage<int>().contains(entity));
|
||||
ASSERT_TRUE(cview.storage<0u>().contains(entity));
|
||||
ASSERT_TRUE(view.storage<int>()->contains(entity));
|
||||
ASSERT_TRUE(cview.storage<0u>()->contains(entity));
|
||||
ASSERT_TRUE((registry.all_of<int, char>(entity)));
|
||||
|
||||
view.storage().erase(entity);
|
||||
view.storage()->erase(entity);
|
||||
|
||||
ASSERT_EQ(view.size(), 0u);
|
||||
ASSERT_EQ(cview.size(), 1u);
|
||||
ASSERT_FALSE(view.storage<0u>().contains(entity));
|
||||
ASSERT_TRUE(cview.storage<const char>().contains(entity));
|
||||
ASSERT_FALSE(view.storage<0u>()->contains(entity));
|
||||
ASSERT_TRUE(cview.storage<const char>()->contains(entity));
|
||||
ASSERT_FALSE((registry.all_of<int, char>(entity)));
|
||||
|
||||
view = {};
|
||||
cview = {};
|
||||
|
||||
ASSERT_FALSE(view);
|
||||
ASSERT_FALSE(cview);
|
||||
|
||||
ASSERT_EQ(view.storage<0u>(), nullptr);
|
||||
ASSERT_EQ(cview.storage<const char>(), nullptr);
|
||||
}
|
||||
|
||||
TEST(SingleComponentView, SwapStorage) {
|
||||
using namespace entt::literals;
|
||||
|
||||
entt::registry registry;
|
||||
entt::basic_view<entt::get_t<entt::storage<int>>, entt::exclude_t<>> view;
|
||||
entt::basic_view<entt::get_t<const entt::storage<int>>, entt::exclude_t<>> cview;
|
||||
|
||||
ASSERT_FALSE(view);
|
||||
ASSERT_FALSE(cview);
|
||||
ASSERT_EQ(view.storage<0u>(), nullptr);
|
||||
ASSERT_EQ(cview.storage<const int>(), nullptr);
|
||||
|
||||
const entt::entity entity{42u};
|
||||
registry.emplace<int>(entity);
|
||||
|
||||
view.storage(registry.storage<int>());
|
||||
cview.storage(registry.storage<int>());
|
||||
|
||||
ASSERT_TRUE(view);
|
||||
ASSERT_TRUE(cview);
|
||||
ASSERT_NE(view.storage<0u>(), nullptr);
|
||||
ASSERT_NE(cview.storage<const int>(), nullptr);
|
||||
|
||||
ASSERT_EQ(view.size(), 1u);
|
||||
ASSERT_EQ(cview.size(), 1u);
|
||||
ASSERT_TRUE(view.contains(entity));
|
||||
ASSERT_TRUE(cview.contains(entity));
|
||||
|
||||
view.storage(registry.storage<int>("other"_hs));
|
||||
cview.storage(registry.storage<int>("other"_hs));
|
||||
|
||||
ASSERT_TRUE(view.empty());
|
||||
ASSERT_TRUE(cview.empty());
|
||||
}
|
||||
|
||||
TEST(MultiComponentView, Functionalities) {
|
||||
@ -532,6 +614,46 @@ TEST(MultiComponentView, Functionalities) {
|
||||
ASSERT_FALSE(invalid);
|
||||
}
|
||||
|
||||
TEST(MultiComponentView, InvalidView) {
|
||||
entt::basic_view<entt::get_t<entt::storage<int>>, entt::exclude_t<entt::storage<char>>> view{};
|
||||
|
||||
ASSERT_FALSE(view);
|
||||
|
||||
ASSERT_EQ(view.size_hint(), 0u);
|
||||
ASSERT_FALSE(view.contains(entt::null));
|
||||
ASSERT_EQ(view.find(entt::null), view.end());
|
||||
|
||||
ASSERT_EQ(view.front(), static_cast<entt::entity>(entt::null));
|
||||
ASSERT_EQ(view.back(), static_cast<entt::entity>(entt::null));
|
||||
|
||||
ASSERT_EQ(view.begin(), typename decltype(view)::iterator{});
|
||||
ASSERT_EQ(view.begin(), view.end());
|
||||
|
||||
auto iterable = view.each();
|
||||
|
||||
ASSERT_EQ(iterable.begin(), iterable.end());
|
||||
ASSERT_EQ(iterable.cbegin(), iterable.cend());
|
||||
|
||||
view.each([](const int &) { FAIL(); });
|
||||
view.each([](const entt::entity, const int &) { FAIL(); });
|
||||
|
||||
entt::storage<int> storage;
|
||||
view.storage(storage);
|
||||
|
||||
ASSERT_FALSE(view);
|
||||
|
||||
view.each([](const int &) { FAIL(); });
|
||||
view.each([](const entt::entity, const int &) { FAIL(); });
|
||||
|
||||
entt::storage<char> other;
|
||||
view.storage(other);
|
||||
|
||||
ASSERT_TRUE(view);
|
||||
|
||||
view.each([](const int &) { FAIL(); });
|
||||
view.each([](const entt::entity, const int &) { FAIL(); });
|
||||
}
|
||||
|
||||
TEST(MultiComponentView, Constructors) {
|
||||
entt::storage<int> storage{};
|
||||
|
||||
@ -543,7 +665,8 @@ TEST(MultiComponentView, Constructors) {
|
||||
ASSERT_TRUE(from_storage);
|
||||
ASSERT_TRUE(from_tuple);
|
||||
|
||||
ASSERT_EQ(&from_storage.handle(), &from_tuple.handle());
|
||||
ASSERT_NE(from_storage.handle(), nullptr);
|
||||
ASSERT_EQ(from_storage.handle(), from_tuple.handle());
|
||||
}
|
||||
|
||||
TEST(MultiComponentView, Handle) {
|
||||
@ -551,30 +674,34 @@ TEST(MultiComponentView, Handle) {
|
||||
const auto entity = registry.create();
|
||||
|
||||
auto view = registry.view<int, char>();
|
||||
auto &&handle = view.handle();
|
||||
auto *handle = view.handle();
|
||||
|
||||
ASSERT_TRUE(handle.empty());
|
||||
ASSERT_FALSE(handle.contains(entity));
|
||||
ASSERT_EQ(&handle, &view.handle());
|
||||
ASSERT_NE(handle, nullptr);
|
||||
|
||||
ASSERT_TRUE(handle->empty());
|
||||
ASSERT_FALSE(handle->contains(entity));
|
||||
ASSERT_EQ(handle, view.handle());
|
||||
|
||||
registry.emplace<int>(entity);
|
||||
|
||||
ASSERT_FALSE(handle.empty());
|
||||
ASSERT_TRUE(handle.contains(entity));
|
||||
ASSERT_EQ(&handle, &view.handle());
|
||||
ASSERT_FALSE(handle->empty());
|
||||
ASSERT_TRUE(handle->contains(entity));
|
||||
ASSERT_EQ(handle, view.handle());
|
||||
|
||||
view = view.refresh();
|
||||
auto &&other = view.handle();
|
||||
view.refresh();
|
||||
auto *other = view.handle();
|
||||
|
||||
ASSERT_TRUE(other.empty());
|
||||
ASSERT_FALSE(other.contains(entity));
|
||||
ASSERT_EQ(&other, &view.handle());
|
||||
ASSERT_NE(&handle, &other);
|
||||
ASSERT_NE(other, nullptr);
|
||||
|
||||
view = view.use<int>();
|
||||
ASSERT_TRUE(other->empty());
|
||||
ASSERT_FALSE(other->contains(entity));
|
||||
ASSERT_EQ(other, view.handle());
|
||||
ASSERT_NE(handle, other);
|
||||
|
||||
ASSERT_NE(&other, &view.handle());
|
||||
ASSERT_EQ(&handle, &view.handle());
|
||||
view.use<int>();
|
||||
|
||||
ASSERT_NE(other, view.handle());
|
||||
ASSERT_EQ(handle, view.handle());
|
||||
}
|
||||
|
||||
TEST(MultiComponentView, LazyTypesFromConstRegistry) {
|
||||
@ -585,7 +712,7 @@ TEST(MultiComponentView, LazyTypesFromConstRegistry) {
|
||||
registry.emplace<empty_type>(entity);
|
||||
registry.emplace<int>(entity);
|
||||
|
||||
ASSERT_TRUE(view);
|
||||
ASSERT_FALSE(view);
|
||||
|
||||
ASSERT_EQ(view.size_hint(), 0u);
|
||||
ASSERT_FALSE(view.contains(entity));
|
||||
@ -604,7 +731,7 @@ TEST(MultiComponentView, LazyExcludedTypeFromConstRegistry) {
|
||||
|
||||
auto view = std::as_const(registry).view<const int>(entt::exclude<char>);
|
||||
|
||||
ASSERT_TRUE(view);
|
||||
ASSERT_FALSE(view);
|
||||
|
||||
ASSERT_EQ(view.size_hint(), 1u);
|
||||
ASSERT_TRUE(view.contains(entity));
|
||||
@ -683,7 +810,6 @@ TEST(MultiComponentView, SizeHint) {
|
||||
entt::registry registry;
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.emplace<double>(e0);
|
||||
registry.emplace<int>(e0);
|
||||
registry.emplace<float>(e0);
|
||||
|
||||
@ -705,10 +831,10 @@ TEST(MultiComponentView, Each) {
|
||||
auto cview = std::as_const(registry).view<const int, const char>();
|
||||
|
||||
registry.emplace<int>(entity[0u], 0);
|
||||
registry.emplace<char>(entity[0u], 0);
|
||||
registry.emplace<char>(entity[0u], static_cast<char>(0));
|
||||
|
||||
registry.emplace<int>(entity[1u], 1);
|
||||
registry.emplace<char>(entity[1u], 1);
|
||||
registry.emplace<char>(entity[1u], static_cast<char>(1));
|
||||
|
||||
auto iterable = view.each();
|
||||
auto citerable = cview.each();
|
||||
@ -719,16 +845,18 @@ TEST(MultiComponentView, Each) {
|
||||
|
||||
auto it = iterable.begin();
|
||||
|
||||
ASSERT_EQ(it.base(), view.begin());
|
||||
ASSERT_EQ((it++, ++it), iterable.end());
|
||||
ASSERT_EQ(it.base(), view.end());
|
||||
|
||||
view.each([expected = 1u](auto entt, int &ivalue, char &cvalue) mutable {
|
||||
ASSERT_EQ(entt::to_integral(entt), expected);
|
||||
view.each([expected = 1](auto entt, int &ivalue, char &cvalue) mutable {
|
||||
ASSERT_EQ(static_cast<int>(entt::to_integral(entt)), expected);
|
||||
ASSERT_EQ(ivalue, expected);
|
||||
ASSERT_EQ(cvalue, expected);
|
||||
--expected;
|
||||
});
|
||||
|
||||
cview.each([expected = 1u](const int &ivalue, const char &cvalue) mutable {
|
||||
cview.each([expected = 1](const int &ivalue, const char &cvalue) mutable {
|
||||
ASSERT_EQ(ivalue, expected);
|
||||
ASSERT_EQ(cvalue, expected);
|
||||
--expected;
|
||||
@ -742,13 +870,14 @@ TEST(MultiComponentView, Each) {
|
||||
|
||||
// do not use iterable, make sure an iterable view works when created from a temporary
|
||||
for(auto [entt, ivalue, cvalue]: registry.view<int, char>().each()) {
|
||||
ASSERT_EQ(entt::to_integral(entt), ivalue);
|
||||
ASSERT_EQ(entt::to_integral(entt), cvalue);
|
||||
ASSERT_EQ(static_cast<int>(entt::to_integral(entt)), ivalue);
|
||||
ASSERT_EQ(static_cast<char>(entt::to_integral(entt)), cvalue);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(MultiComponentView, EachWithSuggestedType) {
|
||||
entt::registry registry;
|
||||
auto view = registry.view<int, char>();
|
||||
|
||||
for(auto i = 0; i < 3; ++i) {
|
||||
const auto entity = registry.create();
|
||||
@ -760,7 +889,8 @@ TEST(MultiComponentView, EachWithSuggestedType) {
|
||||
const auto entity = registry.create();
|
||||
registry.emplace<int>(entity, 99);
|
||||
|
||||
registry.view<int, char>().use<int>().each([value = 2](const auto curr, const auto) mutable {
|
||||
view.use<int>();
|
||||
view.each([value = 2](const auto curr, const auto) mutable {
|
||||
ASSERT_EQ(curr, value--);
|
||||
});
|
||||
|
||||
@ -768,7 +898,8 @@ TEST(MultiComponentView, EachWithSuggestedType) {
|
||||
return lhs < rhs;
|
||||
});
|
||||
|
||||
registry.view<int, char>().use<0u>().each([value = 0](const auto curr, const auto) mutable {
|
||||
view.use<0u>();
|
||||
view.each([value = 0](const auto curr, const auto) mutable {
|
||||
ASSERT_EQ(curr, value++);
|
||||
});
|
||||
|
||||
@ -787,8 +918,9 @@ TEST(MultiComponentView, EachWithSuggestedType) {
|
||||
});
|
||||
|
||||
value = {};
|
||||
view.use<int>();
|
||||
|
||||
for(auto &&curr: registry.view<int, char>().use<int>().each()) {
|
||||
for(auto &&curr: view.each()) {
|
||||
ASSERT_EQ(std::get<1>(curr), static_cast<int>(value++));
|
||||
}
|
||||
}
|
||||
@ -955,6 +1087,10 @@ TEST(MultiComponentView, ExcludedComponents) {
|
||||
TEST(MultiComponentView, EmptyTypes) {
|
||||
entt::registry registry;
|
||||
|
||||
auto v1 = registry.view<int, char, empty_type>(entt::exclude<double>);
|
||||
auto v2 = registry.view<int, empty_type, char>(entt::exclude<double>);
|
||||
auto v3 = registry.view<empty_type, int, char>(entt::exclude<double>);
|
||||
|
||||
const auto entity = registry.create();
|
||||
registry.emplace<int>(entity);
|
||||
registry.emplace<char>(entity);
|
||||
@ -970,57 +1106,61 @@ TEST(MultiComponentView, EmptyTypes) {
|
||||
registry.emplace<int>(ignored);
|
||||
registry.emplace<char>(ignored);
|
||||
|
||||
registry.view<int, char, empty_type>(entt::exclude<double>).each([entity](const auto entt, int, char) {
|
||||
v1.each([entity](const auto entt, int, char) {
|
||||
ASSERT_EQ(entity, entt);
|
||||
});
|
||||
|
||||
for(auto [entt, iv, cv]: registry.view<int, char, empty_type>(entt::exclude<double>).each()) {
|
||||
for(auto [entt, iv, cv]: v1.each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
static_assert(std::is_same_v<decltype(cv), char &>);
|
||||
ASSERT_EQ(entity, entt);
|
||||
}
|
||||
|
||||
registry.view<int, empty_type, char>(entt::exclude<double>).each([check = true](int, char) mutable {
|
||||
v2.each([check = true](int, char) mutable {
|
||||
ASSERT_TRUE(check);
|
||||
check = false;
|
||||
});
|
||||
|
||||
for(auto [entt, iv, cv]: registry.view<int, empty_type, char>(entt::exclude<double>).each()) {
|
||||
for(auto [entt, iv, cv]: v2.each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
static_assert(std::is_same_v<decltype(cv), char &>);
|
||||
ASSERT_EQ(entity, entt);
|
||||
}
|
||||
|
||||
registry.view<empty_type, int, char>(entt::exclude<double>).each([entity](const auto entt, int, char) {
|
||||
v3.each([entity](const auto entt, int, char) {
|
||||
ASSERT_EQ(entity, entt);
|
||||
});
|
||||
|
||||
for(auto [entt, iv, cv]: registry.view<empty_type, int, char>(entt::exclude<double>).each()) {
|
||||
for(auto [entt, iv, cv]: v3.each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
static_assert(std::is_same_v<decltype(cv), char &>);
|
||||
ASSERT_EQ(entity, entt);
|
||||
}
|
||||
|
||||
registry.view<empty_type, int, char>(entt::exclude<double>).use<empty_type>().each([entity](const auto entt, int, char) {
|
||||
v3.use<empty_type>();
|
||||
v3.each([entity](const auto entt, int, char) {
|
||||
ASSERT_EQ(entity, entt);
|
||||
});
|
||||
|
||||
for(auto [entt, iv, cv]: registry.view<empty_type, int, char>(entt::exclude<double>).use<0u>().each()) {
|
||||
v3.use<0u>();
|
||||
for(auto [entt, iv, cv]: v3.each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
static_assert(std::is_same_v<decltype(cv), char &>);
|
||||
ASSERT_EQ(entity, entt);
|
||||
}
|
||||
|
||||
registry.view<int, empty_type, char>(entt::exclude<double>).use<1u>().each([check = true](int, char) mutable {
|
||||
v2.use<1u>();
|
||||
v2.each([check = true](int, char) mutable {
|
||||
ASSERT_TRUE(check);
|
||||
check = false;
|
||||
});
|
||||
|
||||
for(auto [entt, iv, cv]: registry.view<int, empty_type, char>(entt::exclude<double>).use<empty_type>().each()) {
|
||||
v2.use<empty_type>();
|
||||
for(auto [entt, iv, cv]: v2.each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
static_assert(std::is_same_v<decltype(cv), char &>);
|
||||
@ -1111,7 +1251,7 @@ TEST(MultiComponentView, StableType) {
|
||||
|
||||
ASSERT_EQ(view.size_hint(), 1u);
|
||||
|
||||
view = view.use<stable_type>();
|
||||
view.use<stable_type>();
|
||||
|
||||
ASSERT_EQ(view.size_hint(), 2u);
|
||||
ASSERT_FALSE(view.contains(entity));
|
||||
@ -1146,7 +1286,9 @@ TEST(MultiComponentView, StableType) {
|
||||
|
||||
TEST(MultiComponentView, StableTypeWithExcludedComponent) {
|
||||
entt::registry registry;
|
||||
auto view = registry.view<stable_type>(entt::exclude<int>).use<stable_type>();
|
||||
auto view = registry.view<stable_type>(entt::exclude<int>);
|
||||
|
||||
view.use<stable_type>();
|
||||
|
||||
const auto entity = registry.create();
|
||||
const auto other = registry.create();
|
||||
@ -1223,8 +1365,118 @@ TEST(MultiComponentView, SameComponentTypes) {
|
||||
ASSERT_EQ(second, 9);
|
||||
}
|
||||
|
||||
ASSERT_EQ(&view.handle(), &storage);
|
||||
ASSERT_EQ(&view.use<1u>().handle(), &other);
|
||||
ASSERT_EQ(view.handle(), &storage);
|
||||
|
||||
view.use<1u>();
|
||||
|
||||
ASSERT_EQ(view.handle(), &other);
|
||||
}
|
||||
|
||||
TEST(MultiComponentView, Storage) {
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
auto view = registry.view<int, const char>(entt::exclude<double, const float>);
|
||||
|
||||
static_assert(std::is_same_v<decltype(view.storage<0u>()), entt::storage_type_t<int> *>);
|
||||
static_assert(std::is_same_v<decltype(view.storage<int>()), entt::storage_type_t<int> *>);
|
||||
static_assert(std::is_same_v<decltype(view.storage<const int>()), entt::storage_type_t<int> *>);
|
||||
static_assert(std::is_same_v<decltype(view.storage<1u>()), const entt::storage_type_t<char> *>);
|
||||
static_assert(std::is_same_v<decltype(view.storage<char>()), const entt::storage_type_t<char> *>);
|
||||
static_assert(std::is_same_v<decltype(view.storage<const char>()), const entt::storage_type_t<char> *>);
|
||||
static_assert(std::is_same_v<decltype(view.storage<2u>()), entt::storage_type_t<double> *>);
|
||||
static_assert(std::is_same_v<decltype(view.storage<double>()), entt::storage_type_t<double> *>);
|
||||
static_assert(std::is_same_v<decltype(view.storage<const double>()), entt::storage_type_t<double> *>);
|
||||
static_assert(std::is_same_v<decltype(view.storage<3u>()), const entt::storage_type_t<float> *>);
|
||||
static_assert(std::is_same_v<decltype(view.storage<float>()), const entt::storage_type_t<float> *>);
|
||||
static_assert(std::is_same_v<decltype(view.storage<const float>()), const entt::storage_type_t<float> *>);
|
||||
|
||||
ASSERT_TRUE(view);
|
||||
|
||||
ASSERT_NE(view.storage<int>(), nullptr);
|
||||
ASSERT_NE(view.storage<1u>(), nullptr);
|
||||
ASSERT_NE(view.storage<double>(), nullptr);
|
||||
ASSERT_NE(view.storage<3u>(), nullptr);
|
||||
|
||||
ASSERT_EQ(view.size_hint(), 0u);
|
||||
|
||||
view.storage<int>()->emplace(entity);
|
||||
view.storage<double>()->emplace(entity);
|
||||
registry.emplace<char>(entity);
|
||||
registry.emplace<float>(entity);
|
||||
|
||||
ASSERT_EQ(view.size_hint(), 1u);
|
||||
ASSERT_EQ(view.begin(), view.end());
|
||||
ASSERT_TRUE(view.storage<int>()->contains(entity));
|
||||
ASSERT_TRUE(view.storage<const char>()->contains(entity));
|
||||
ASSERT_TRUE(view.storage<double>()->contains(entity));
|
||||
ASSERT_TRUE(view.storage<const float>()->contains(entity));
|
||||
ASSERT_TRUE((registry.all_of<int, char, double, float>(entity)));
|
||||
|
||||
view.storage<double>()->erase(entity);
|
||||
registry.erase<float>(entity);
|
||||
|
||||
ASSERT_EQ(view.size_hint(), 1u);
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
ASSERT_TRUE(view.storage<const int>()->contains(entity));
|
||||
ASSERT_TRUE(view.storage<char>()->contains(entity));
|
||||
ASSERT_FALSE(view.storage<const double>()->contains(entity));
|
||||
ASSERT_FALSE(view.storage<float>()->contains(entity));
|
||||
ASSERT_TRUE((registry.all_of<int, char>(entity)));
|
||||
ASSERT_FALSE((registry.any_of<double, float>(entity)));
|
||||
|
||||
view.storage<0u>()->erase(entity);
|
||||
|
||||
ASSERT_EQ(view.size_hint(), 0u);
|
||||
ASSERT_EQ(view.begin(), view.end());
|
||||
ASSERT_FALSE(view.storage<0u>()->contains(entity));
|
||||
ASSERT_TRUE(view.storage<1u>()->contains(entity));
|
||||
ASSERT_FALSE(view.storage<2u>()->contains(entity));
|
||||
ASSERT_FALSE(view.storage<3u>()->contains(entity));
|
||||
ASSERT_TRUE((registry.all_of<char>(entity)));
|
||||
ASSERT_FALSE((registry.any_of<int, double, float>(entity)));
|
||||
|
||||
view = {};
|
||||
|
||||
ASSERT_FALSE(view);
|
||||
|
||||
ASSERT_EQ(view.storage<0u>(), nullptr);
|
||||
ASSERT_EQ(view.storage<const char>(), nullptr);
|
||||
ASSERT_EQ(view.storage<2u>(), nullptr);
|
||||
ASSERT_EQ(view.storage<const float>(), nullptr);
|
||||
}
|
||||
|
||||
TEST(MultiComponentView, SwapStorage) {
|
||||
using namespace entt::literals;
|
||||
|
||||
entt::registry registry;
|
||||
entt::basic_view<entt::get_t<entt::storage<int>>, entt::exclude_t<const entt::storage<char>>> view;
|
||||
|
||||
ASSERT_FALSE(view);
|
||||
ASSERT_EQ(view.storage<0u>(), nullptr);
|
||||
ASSERT_EQ(view.storage<const char>(), nullptr);
|
||||
|
||||
const entt::entity entity{42u};
|
||||
registry.emplace<int>(entity);
|
||||
registry.emplace<char>(entity);
|
||||
|
||||
view.storage(registry.storage<int>());
|
||||
view.storage<1u>(registry.storage<char>());
|
||||
|
||||
ASSERT_TRUE(view);
|
||||
ASSERT_NE(view.storage<int>(), nullptr);
|
||||
ASSERT_NE(view.storage<1u>(), nullptr);
|
||||
|
||||
ASSERT_EQ(view.size_hint(), 1u);
|
||||
ASSERT_FALSE(view.contains(entity));
|
||||
|
||||
view.storage(registry.storage<char>("other"_hs));
|
||||
|
||||
ASSERT_EQ(view.size_hint(), 1u);
|
||||
ASSERT_TRUE(view.contains(entity));
|
||||
|
||||
view.storage(registry.storage<int>("empty"_hs));
|
||||
|
||||
ASSERT_EQ(view.size_hint(), 0u);
|
||||
}
|
||||
|
||||
TEST(View, Pipe) {
|
||||
@ -1241,10 +1493,10 @@ TEST(View, Pipe) {
|
||||
registry.emplace<char>(other);
|
||||
registry.emplace<stable_type>(other);
|
||||
|
||||
const auto view1 = registry.view<int>(entt::exclude<const double>);
|
||||
const auto view2 = registry.view<const char>(entt::exclude<float>);
|
||||
const auto view3 = registry.view<empty_type>();
|
||||
const auto view4 = registry.view<stable_type>();
|
||||
auto view1 = registry.view<int>(entt::exclude<const double>);
|
||||
auto view2 = registry.view<const char>(entt::exclude<float>);
|
||||
auto view3 = registry.view<empty_type>();
|
||||
auto view4 = registry.view<stable_type>();
|
||||
|
||||
static_assert(std::is_same_v<entt::basic_view<entt::get_t<entt::storage_type_t<int>, const entt::storage_type_t<char>>, entt::exclude_t<const entt::storage_type_t<double>, entt::storage_type_t<float>>>, decltype(view1 | view2)>);
|
||||
static_assert(std::is_same_v<entt::basic_view<entt::get_t<const entt::storage_type_t<char>, entt::storage_type_t<int>>, entt::exclude_t<entt::storage_type_t<float>, const entt::storage_type_t<double>>>, decltype(view2 | view1)>);
|
||||
@ -1261,30 +1513,26 @@ TEST(View, Pipe) {
|
||||
|
||||
ASSERT_FALSE((view1 | view4 | view2).contains(entity));
|
||||
ASSERT_TRUE((view1 | view4 | view2).contains(other));
|
||||
}
|
||||
|
||||
TEST(MultiComponentView, Storage) {
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
const auto view = registry.view<int, const char>();
|
||||
|
||||
static_assert(std::is_same_v<decltype(view.storage<0u>()), entt::storage_type_t<int> &>);
|
||||
static_assert(std::is_same_v<decltype(view.storage<int>()), entt::storage_type_t<int> &>);
|
||||
static_assert(std::is_same_v<decltype(view.storage<1u>()), const entt::storage_type_t<char> &>);
|
||||
static_assert(std::is_same_v<decltype(view.storage<const char>()), const entt::storage_type_t<char> &>);
|
||||
|
||||
ASSERT_EQ(view.size_hint(), 0u);
|
||||
|
||||
view.storage<int>().emplace(entity);
|
||||
registry.emplace<char>(entity);
|
||||
|
||||
ASSERT_EQ(view.size_hint(), 1u);
|
||||
ASSERT_TRUE(view.storage<const char>().contains(entity));
|
||||
ASSERT_TRUE((registry.all_of<int, char>(entity)));
|
||||
|
||||
view.storage<0u>().erase(entity);
|
||||
|
||||
ASSERT_EQ(view.size_hint(), 0u);
|
||||
ASSERT_TRUE(view.storage<1u>().contains(entity));
|
||||
ASSERT_FALSE((registry.all_of<int, char>(entity)));
|
||||
|
||||
view1 = {};
|
||||
view3 = {};
|
||||
|
||||
ASSERT_FALSE(view1);
|
||||
ASSERT_TRUE(view2);
|
||||
ASSERT_FALSE(view3);
|
||||
ASSERT_TRUE(view4);
|
||||
|
||||
auto pack14 = view1 | view4;
|
||||
auto pack32 = view3 | view2;
|
||||
|
||||
ASSERT_FALSE(pack14);
|
||||
ASSERT_FALSE(pack32);
|
||||
|
||||
ASSERT_EQ(pack14.storage<int>(), nullptr);
|
||||
ASSERT_EQ(pack14.storage<const double>(), nullptr);
|
||||
ASSERT_NE(pack14.storage<stable_type>(), nullptr);
|
||||
|
||||
ASSERT_EQ(pack32.storage<empty_type>(), nullptr);
|
||||
ASSERT_NE(pack32.storage<const char>(), nullptr);
|
||||
ASSERT_NE(pack32.storage<float>(), nullptr);
|
||||
}
|
||||
|
@ -295,6 +295,23 @@ TEST(AdjacencyMatrix, EdgesDirected) {
|
||||
ASSERT_EQ(++it, iterable.end());
|
||||
}
|
||||
|
||||
TEST(AdjacencyMatrix, EdgesBackwardOnlyDirected) {
|
||||
entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{2u};
|
||||
auto iterable = adjacency_matrix.edges();
|
||||
|
||||
ASSERT_EQ(iterable.begin(), iterable.end());
|
||||
|
||||
adjacency_matrix.insert(1u, 0u);
|
||||
iterable = adjacency_matrix.edges();
|
||||
|
||||
ASSERT_NE(iterable.begin(), iterable.end());
|
||||
|
||||
auto it = iterable.begin();
|
||||
|
||||
ASSERT_EQ(*it++, std::make_pair(std::size_t{1u}, std::size_t{0u}));
|
||||
ASSERT_EQ(it, iterable.end());
|
||||
}
|
||||
|
||||
TEST(AdjacencyMatrix, EdgesUndirected) {
|
||||
entt::adjacency_matrix<entt::undirected_tag> adjacency_matrix{3u};
|
||||
auto iterable = adjacency_matrix.edges();
|
||||
@ -317,6 +334,24 @@ TEST(AdjacencyMatrix, EdgesUndirected) {
|
||||
ASSERT_EQ(++it, iterable.end());
|
||||
}
|
||||
|
||||
TEST(AdjacencyMatrix, EdgesBackwardOnlyUndirected) {
|
||||
entt::adjacency_matrix<entt::undirected_tag> adjacency_matrix{2u};
|
||||
auto iterable = adjacency_matrix.edges();
|
||||
|
||||
ASSERT_EQ(iterable.begin(), iterable.end());
|
||||
|
||||
adjacency_matrix.insert(1u, 0u);
|
||||
iterable = adjacency_matrix.edges();
|
||||
|
||||
ASSERT_NE(iterable.begin(), iterable.end());
|
||||
|
||||
auto it = iterable.begin();
|
||||
|
||||
ASSERT_EQ(*it++, std::make_pair(std::size_t{0u}, std::size_t{1u}));
|
||||
ASSERT_EQ(*it++, std::make_pair(std::size_t{1u}, std::size_t{0u}));
|
||||
ASSERT_EQ(it, iterable.end());
|
||||
}
|
||||
|
||||
TEST(AdjacencyMatrix, OutEdgesDirected) {
|
||||
entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{3u};
|
||||
auto iterable = adjacency_matrix.out_edges(0u);
|
||||
@ -340,6 +375,28 @@ TEST(AdjacencyMatrix, OutEdgesDirected) {
|
||||
ASSERT_EQ(it, iterable.cend());
|
||||
}
|
||||
|
||||
TEST(AdjacencyMatrix, OutEdgesBackwardOnlyDirected) {
|
||||
entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{2u};
|
||||
auto iterable = adjacency_matrix.out_edges(1u);
|
||||
|
||||
ASSERT_EQ(iterable.begin(), iterable.end());
|
||||
|
||||
adjacency_matrix.insert(1u, 0u);
|
||||
iterable = adjacency_matrix.out_edges(1u);
|
||||
|
||||
ASSERT_NE(iterable.begin(), iterable.end());
|
||||
|
||||
auto it = iterable.begin();
|
||||
|
||||
ASSERT_EQ(*it++, std::make_pair(std::size_t{1u}, std::size_t{0u}));
|
||||
ASSERT_EQ(it, iterable.end());
|
||||
|
||||
iterable = adjacency_matrix.out_edges(0u);
|
||||
it = iterable.cbegin();
|
||||
|
||||
ASSERT_EQ(it, iterable.cend());
|
||||
}
|
||||
|
||||
TEST(AdjacencyMatrix, OutEdgesUndirected) {
|
||||
entt::adjacency_matrix<entt::undirected_tag> adjacency_matrix{3u};
|
||||
auto iterable = adjacency_matrix.out_edges(0u);
|
||||
@ -365,6 +422,30 @@ TEST(AdjacencyMatrix, OutEdgesUndirected) {
|
||||
ASSERT_EQ(it, iterable.cend());
|
||||
}
|
||||
|
||||
TEST(AdjacencyMatrix, OutEdgesBackwardOnlyUndirected) {
|
||||
entt::adjacency_matrix<entt::undirected_tag> adjacency_matrix{2u};
|
||||
auto iterable = adjacency_matrix.out_edges(1u);
|
||||
|
||||
ASSERT_EQ(iterable.begin(), iterable.end());
|
||||
|
||||
adjacency_matrix.insert(1u, 0u);
|
||||
iterable = adjacency_matrix.out_edges(1u);
|
||||
|
||||
ASSERT_NE(iterable.begin(), iterable.end());
|
||||
|
||||
auto it = iterable.begin();
|
||||
|
||||
ASSERT_EQ(*it++, std::make_pair(std::size_t{1u}, std::size_t{0u}));
|
||||
ASSERT_EQ(it, iterable.end());
|
||||
|
||||
iterable = adjacency_matrix.out_edges(0u);
|
||||
it = iterable.cbegin();
|
||||
|
||||
ASSERT_NE(it, iterable.cend());
|
||||
ASSERT_EQ(*it++, std::make_pair(std::size_t{0u}, std::size_t{1u}));
|
||||
ASSERT_EQ(it, iterable.cend());
|
||||
}
|
||||
|
||||
TEST(AdjacencyMatrix, InEdgesDirected) {
|
||||
entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{3u};
|
||||
auto iterable = adjacency_matrix.in_edges(1u);
|
||||
@ -388,6 +469,28 @@ TEST(AdjacencyMatrix, InEdgesDirected) {
|
||||
ASSERT_EQ(it, iterable.cend());
|
||||
}
|
||||
|
||||
TEST(AdjacencyMatrix, InEdgesBackwardOnlyDirected) {
|
||||
entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{2u};
|
||||
auto iterable = adjacency_matrix.in_edges(0u);
|
||||
|
||||
ASSERT_EQ(iterable.begin(), iterable.end());
|
||||
|
||||
adjacency_matrix.insert(1u, 0u);
|
||||
iterable = adjacency_matrix.in_edges(0u);
|
||||
|
||||
ASSERT_NE(iterable.begin(), iterable.end());
|
||||
|
||||
auto it = iterable.begin();
|
||||
|
||||
ASSERT_EQ(*it++, std::make_pair(std::size_t{1u}, std::size_t{0u}));
|
||||
ASSERT_EQ(it, iterable.end());
|
||||
|
||||
iterable = adjacency_matrix.in_edges(1u);
|
||||
it = iterable.cbegin();
|
||||
|
||||
ASSERT_EQ(it, iterable.cend());
|
||||
}
|
||||
|
||||
TEST(AdjacencyMatrix, InEdgesUndirected) {
|
||||
entt::adjacency_matrix<entt::undirected_tag> adjacency_matrix{3u};
|
||||
auto iterable = adjacency_matrix.in_edges(1u);
|
||||
@ -403,6 +506,7 @@ TEST(AdjacencyMatrix, InEdgesUndirected) {
|
||||
auto it = iterable.begin();
|
||||
|
||||
ASSERT_EQ(*it++, std::make_pair(std::size_t{0u}, std::size_t{1u}));
|
||||
ASSERT_EQ(*it++, std::make_pair(std::size_t{2u}, std::size_t{1u}));
|
||||
ASSERT_EQ(it, iterable.end());
|
||||
|
||||
iterable = adjacency_matrix.in_edges(0u);
|
||||
@ -413,6 +517,30 @@ TEST(AdjacencyMatrix, InEdgesUndirected) {
|
||||
ASSERT_EQ(it, iterable.cend());
|
||||
}
|
||||
|
||||
TEST(AdjacencyMatrix, InEdgesBackwardOnlyUndirected) {
|
||||
entt::adjacency_matrix<entt::undirected_tag> adjacency_matrix{2u};
|
||||
auto iterable = adjacency_matrix.in_edges(0u);
|
||||
|
||||
ASSERT_EQ(iterable.begin(), iterable.end());
|
||||
|
||||
adjacency_matrix.insert(1u, 0u);
|
||||
iterable = adjacency_matrix.in_edges(0u);
|
||||
|
||||
ASSERT_NE(iterable.begin(), iterable.end());
|
||||
|
||||
auto it = iterable.begin();
|
||||
|
||||
ASSERT_EQ(*it++, std::make_pair(std::size_t{1u}, std::size_t{0u}));
|
||||
ASSERT_EQ(it, iterable.end());
|
||||
|
||||
iterable = adjacency_matrix.in_edges(1u);
|
||||
it = iterable.cbegin();
|
||||
|
||||
ASSERT_NE(it, iterable.cend());
|
||||
ASSERT_EQ(*it++, std::make_pair(std::size_t{0u}, std::size_t{1u}));
|
||||
ASSERT_EQ(it, iterable.cend());
|
||||
}
|
||||
|
||||
TEST(AdjacencyMatrix, ThrowingAllocator) {
|
||||
using allocator = test::throwing_allocator<std::size_t>;
|
||||
using exception = typename allocator::exception_type;
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/hashed_string.hpp>
|
||||
#include <entt/graph/flow.hpp>
|
||||
#include "../common/config.h"
|
||||
#include "../common/throwing_allocator.hpp"
|
||||
|
||||
TEST(Flow, Constructors) {
|
||||
@ -24,9 +25,9 @@ TEST(Flow, Constructors) {
|
||||
ASSERT_EQ(flow.size(), 0u);
|
||||
ASSERT_EQ(other.size(), 3u);
|
||||
|
||||
ASSERT_EQ(other[0u], 0);
|
||||
ASSERT_EQ(other[1u], 3);
|
||||
ASSERT_EQ(other[2u], 99);
|
||||
ASSERT_EQ(other[0u], 0u);
|
||||
ASSERT_EQ(other[1u], 3u);
|
||||
ASSERT_EQ(other[2u], 99u);
|
||||
}
|
||||
|
||||
TEST(Flow, Copy) {
|
||||
@ -41,9 +42,9 @@ TEST(Flow, Copy) {
|
||||
ASSERT_EQ(flow.size(), 3u);
|
||||
ASSERT_EQ(other.size(), 3u);
|
||||
|
||||
ASSERT_EQ(other[0u], 0);
|
||||
ASSERT_EQ(other[1u], 3);
|
||||
ASSERT_EQ(other[2u], 99);
|
||||
ASSERT_EQ(other[0u], 0u);
|
||||
ASSERT_EQ(other[1u], 3u);
|
||||
ASSERT_EQ(other[2u], 99u);
|
||||
|
||||
flow.bind(1);
|
||||
other.bind(2);
|
||||
@ -53,10 +54,10 @@ TEST(Flow, Copy) {
|
||||
ASSERT_EQ(other.size(), 4u);
|
||||
ASSERT_EQ(flow.size(), 4u);
|
||||
|
||||
ASSERT_EQ(other[0u], 0);
|
||||
ASSERT_EQ(other[1u], 3);
|
||||
ASSERT_EQ(other[2u], 99);
|
||||
ASSERT_EQ(other[3u], 1);
|
||||
ASSERT_EQ(other[0u], 0u);
|
||||
ASSERT_EQ(other[1u], 3u);
|
||||
ASSERT_EQ(other[2u], 99u);
|
||||
ASSERT_EQ(other[3u], 1u);
|
||||
}
|
||||
|
||||
TEST(Flow, Move) {
|
||||
@ -71,9 +72,9 @@ TEST(Flow, Move) {
|
||||
ASSERT_EQ(flow.size(), 0u);
|
||||
ASSERT_EQ(other.size(), 3u);
|
||||
|
||||
ASSERT_EQ(other[0u], 0);
|
||||
ASSERT_EQ(other[1u], 3);
|
||||
ASSERT_EQ(other[2u], 99);
|
||||
ASSERT_EQ(other[0u], 0u);
|
||||
ASSERT_EQ(other[1u], 3u);
|
||||
ASSERT_EQ(other[2u], 99u);
|
||||
|
||||
flow = {};
|
||||
flow.bind(1);
|
||||
@ -84,7 +85,7 @@ TEST(Flow, Move) {
|
||||
ASSERT_EQ(other.size(), 1u);
|
||||
ASSERT_EQ(flow.size(), 0u);
|
||||
|
||||
ASSERT_EQ(other[0u], 1);
|
||||
ASSERT_EQ(other[0u], 1u);
|
||||
}
|
||||
|
||||
TEST(Flow, Swap) {
|
||||
@ -95,13 +96,13 @@ TEST(Flow, Swap) {
|
||||
|
||||
ASSERT_EQ(other.size(), 0u);
|
||||
ASSERT_EQ(flow.size(), 1u);
|
||||
ASSERT_EQ(flow[0u], 7);
|
||||
ASSERT_EQ(flow[0u], 7u);
|
||||
|
||||
flow.swap(other);
|
||||
|
||||
ASSERT_EQ(other.size(), 1u);
|
||||
ASSERT_EQ(flow.size(), 0u);
|
||||
ASSERT_EQ(other[0u], 7);
|
||||
ASSERT_EQ(other[0u], 7u);
|
||||
}
|
||||
|
||||
TEST(Flow, Clear) {
|
||||
@ -111,8 +112,8 @@ TEST(Flow, Clear) {
|
||||
flow.bind(99);
|
||||
|
||||
ASSERT_EQ(flow.size(), 2u);
|
||||
ASSERT_EQ(flow[0u], 0);
|
||||
ASSERT_EQ(flow[1u], 99);
|
||||
ASSERT_EQ(flow[0u], 0u);
|
||||
ASSERT_EQ(flow[1u], 99u);
|
||||
|
||||
flow.clear();
|
||||
|
||||
@ -270,6 +271,57 @@ TEST(Flow, Sync) {
|
||||
ASSERT_EQ(it, last);
|
||||
}
|
||||
|
||||
ENTT_DEBUG_TEST(FlowDeathTest, NoBind) {
|
||||
entt::flow flow{};
|
||||
|
||||
ASSERT_DEATH(flow.ro(42), "");
|
||||
ASSERT_DEATH(flow.rw(42), "");
|
||||
|
||||
flow.bind(0);
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(flow.ro(1));
|
||||
ASSERT_NO_FATAL_FAILURE(flow.rw(2));
|
||||
}
|
||||
|
||||
TEST(Flow, DirectRebind) {
|
||||
entt::flow flow{};
|
||||
flow.bind(0).ro(10).rw(10).bind(1).ro(10);
|
||||
auto graph = flow.graph();
|
||||
|
||||
ASSERT_EQ(flow.size(), 2u);
|
||||
ASSERT_EQ(flow.size(), graph.size());
|
||||
ASSERT_NE(graph.edges().cbegin(), graph.edges().cend());
|
||||
|
||||
ASSERT_TRUE(graph.contains(0u, 1u));
|
||||
ASSERT_FALSE(graph.contains(1u, 0u));
|
||||
}
|
||||
|
||||
TEST(Flow, DeferredRebind) {
|
||||
entt::flow flow{};
|
||||
flow.bind(0).ro(10).bind(1).ro(10).bind(0).rw(10);
|
||||
auto graph = flow.graph();
|
||||
|
||||
ASSERT_EQ(flow.size(), 2u);
|
||||
ASSERT_EQ(flow.size(), graph.size());
|
||||
ASSERT_NE(graph.edges().cbegin(), graph.edges().cend());
|
||||
|
||||
ASSERT_FALSE(graph.contains(0u, 1u));
|
||||
ASSERT_TRUE(graph.contains(1u, 0u));
|
||||
}
|
||||
|
||||
TEST(Flow, Loop) {
|
||||
entt::flow flow{};
|
||||
flow.bind(0).rw(10).bind(1).ro(10).bind(0).rw(10);
|
||||
auto graph = flow.graph();
|
||||
|
||||
ASSERT_EQ(flow.size(), 2u);
|
||||
ASSERT_EQ(flow.size(), graph.size());
|
||||
ASSERT_NE(graph.edges().cbegin(), graph.edges().cend());
|
||||
|
||||
ASSERT_TRUE(graph.contains(0u, 1u));
|
||||
ASSERT_TRUE(graph.contains(1u, 0u));
|
||||
}
|
||||
|
||||
TEST(Flow, ThrowingAllocator) {
|
||||
using allocator = test::throwing_allocator<entt::id_type>;
|
||||
using task_allocator = test::throwing_allocator<std::pair<std::size_t, entt::id_type>>;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user