From 90ce4bda4e1dc23508bbd6b6923156cd5a370c18 Mon Sep 17 00:00:00 2001 From: Green Sky Date: Mon, 2 Oct 2023 15:30:10 +0200 Subject: [PATCH] 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(...) 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 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 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 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 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 --- .github/workflows/analyzer.yml | 18 +- .github/workflows/build.yml | 129 +- .github/workflows/coverage.yml | 6 +- .github/workflows/deploy.yml | 2 +- .github/workflows/sanitizer.yml | 2 +- CMakeLists.txt | 6 +- LICENSE | 2 +- README.md | 19 +- TODO | 21 +- docs/CMakeLists.txt | 16 +- docs/doxy.in | 142 +- docs/md/config.md | 1 - docs/md/container.md | 7 +- docs/md/core.md | 279 +- docs/md/entity.md | 1512 +- docs/md/faq.md | 2 +- docs/md/graph.md | 175 +- docs/md/lib.md | 18 +- docs/md/links.md | 60 +- docs/md/meta.md | 420 +- docs/md/poly.md | 78 +- docs/md/process.md | 79 +- docs/md/reference.md | 28 +- docs/md/signal.md | 52 +- natvis/entt/entity.natvis | 74 +- natvis/entt/signal.natvis | 3 +- single_include/entt/entt.hpp | 23485 ++++++++++------ src/entt/config/config.h | 2 + src/entt/config/version.h | 4 +- src/entt/container/dense_map.hpp | 58 +- src/entt/container/dense_set.hpp | 56 +- src/entt/core/algorithm.hpp | 7 +- src/entt/core/any.hpp | 22 +- src/entt/core/hashed_string.hpp | 10 +- src/entt/core/memory.hpp | 4 +- src/entt/core/tuple.hpp | 6 +- src/entt/core/type_traits.hpp | 168 +- src/entt/core/utility.hpp | 12 +- src/entt/entity/component.hpp | 14 +- src/entt/entity/entity.hpp | 98 +- src/entt/entity/fwd.hpp | 67 +- src/entt/entity/group.hpp | 566 +- src/entt/entity/handle.hpp | 10 +- src/entt/entity/helper.hpp | 132 +- src/entt/entity/mixin.hpp | 293 + src/entt/entity/observer.hpp | 68 +- src/entt/entity/organizer.hpp | 2 +- src/entt/entity/registry.hpp | 742 +- src/entt/entity/runtime_view.hpp | 54 +- src/entt/entity/snapshot.hpp | 533 +- src/entt/entity/sparse_set.hpp | 337 +- src/entt/entity/storage.hpp | 607 +- src/entt/entity/storage_mixin.hpp | 236 - src/entt/entity/view.hpp | 431 +- src/entt/entt.hpp | 2 +- src/entt/graph/adjacency_matrix.hpp | 4 +- src/entt/graph/flow.hpp | 146 +- src/entt/locator/locator.hpp | 32 +- src/entt/meta/context.hpp | 8 +- src/entt/meta/factory.hpp | 121 +- src/entt/meta/meta.hpp | 115 +- src/entt/meta/policy.hpp | 6 +- src/entt/meta/range.hpp | 8 +- src/entt/meta/type_traits.hpp | 1 - src/entt/meta/utility.hpp | 13 +- src/entt/poly/poly.hpp | 2 +- src/entt/process/fwd.hpp | 9 +- src/entt/process/scheduler.hpp | 29 +- src/entt/resource/cache.hpp | 50 +- src/entt/resource/resource.hpp | 52 +- src/entt/signal/delegate.hpp | 32 +- src/entt/signal/dispatcher.hpp | 8 +- src/entt/signal/emitter.hpp | 6 +- src/entt/signal/sigh.hpp | 147 +- test/CMakeLists.txt | 70 +- test/benchmark/benchmark.cpp | 443 +- test/entt/common/basic_test_allocator.hpp | 2 +- test/entt/common/throwing_allocator.hpp | 4 +- test/entt/container/dense_map.cpp | 32 +- test/entt/container/dense_set.cpp | 28 +- test/entt/core/any.cpp | 47 +- test/entt/core/hashed_string.cpp | 56 +- test/entt/core/memory.cpp | 14 +- test/entt/core/tuple.cpp | 2 +- test/entt/core/type_traits.cpp | 34 +- test/entt/entity/component.cpp | 43 +- test/entt/entity/entity.cpp | 6 + test/entt/entity/group.cpp | 406 +- test/entt/entity/handle.cpp | 17 +- test/entt/entity/helper.cpp | 49 +- test/entt/entity/organizer.cpp | 14 +- test/entt/entity/registry.cpp | 457 +- .../{storage_mixin.cpp => sigh_mixin.cpp} | 196 +- test/entt/entity/snapshot.cpp | 1256 +- test/entt/entity/sparse_set.cpp | 473 +- test/entt/entity/storage.cpp | 641 +- test/entt/entity/storage_entity.cpp | 628 + test/entt/entity/view.cpp | 488 +- test/entt/graph/adjacency_matrix.cpp | 128 + test/entt/graph/flow.cpp | 88 +- test/entt/locator/locator.cpp | 87 +- test/entt/meta/meta_any.cpp | 3 - test/entt/meta/meta_container.cpp | 66 +- test/entt/meta/meta_data.cpp | 11 +- test/entt/meta/meta_func.cpp | 37 +- test/entt/meta/meta_handle.cpp | 10 + test/entt/meta/meta_pointer.cpp | 16 +- test/entt/meta/meta_prop.cpp | 18 +- test/entt/meta/meta_type.cpp | 5 +- test/entt/poly/poly.cpp | 10 +- test/entt/process/scheduler.cpp | 14 +- test/entt/resource/resource.cpp | 12 +- test/entt/resource/resource_cache.cpp | 12 +- test/entt/signal/delegate.cpp | 122 +- test/entt/signal/sigh.cpp | 170 +- test/example/entity_copy.cpp | 101 +- test/example/signal_less.cpp | 2 +- test/lib/dispatcher/common/types.h | 10 + .../plugin}/main.cpp | 2 +- .../plugin}/plugin.cpp | 2 +- test/lib/dispatcher/{ => shared}/lib.cpp | 2 +- test/lib/dispatcher/{ => shared}/main.cpp | 2 +- test/lib/dispatcher/types.h | 12 - test/lib/dispatcher_plugin/types.h | 10 - .../common}/types.h | 4 +- .../plugin}/main.cpp | 2 +- .../plugin}/plugin.cpp | 2 +- test/lib/emitter/{ => shared}/lib.cpp | 2 +- test/lib/emitter/{ => shared}/main.cpp | 2 +- test/lib/emitter/types.h | 16 - test/lib/locator/common/types.h | 8 + .../plugin}/main.cpp | 5 +- .../plugin}/plugin.cpp | 1 + .../plugin}/types.h | 7 +- test/lib/locator/{ => shared}/lib.cpp | 2 +- test/lib/locator/{ => shared}/main.cpp | 2 +- test/lib/locator/types.h | 8 - test/lib/meta/{ => common}/types.h | 4 +- .../lib/{meta_plugin => meta/plugin}/main.cpp | 3 +- .../{meta_plugin => meta/plugin}/plugin.cpp | 1 + test/lib/{meta_plugin => meta/plugin}/types.h | 10 - .../plugin_std}/main.cpp | 0 .../plugin_std}/plugin.cpp | 1 + .../plugin_std}/types.h | 15 +- test/lib/meta/{ => shared}/lib.cpp | 2 +- test/lib/meta/{ => shared}/main.cpp | 2 +- .../common}/types.h | 4 +- .../plugin}/main.cpp | 2 +- .../plugin}/plugin.cpp | 2 +- test/lib/registry/{ => shared}/lib.cpp | 4 +- test/lib/registry/{ => shared}/main.cpp | 2 +- test/lib/registry/types.h | 16 - test/snapshot/snapshot.cpp | 30 +- 153 files changed, 24263 insertions(+), 14220 deletions(-) create mode 100644 src/entt/entity/mixin.hpp delete mode 100644 src/entt/entity/storage_mixin.hpp rename test/entt/entity/{storage_mixin.cpp => sigh_mixin.cpp} (75%) create mode 100644 test/entt/entity/storage_entity.cpp create mode 100644 test/lib/dispatcher/common/types.h rename test/lib/{dispatcher_plugin => dispatcher/plugin}/main.cpp (95%) rename test/lib/{dispatcher_plugin => dispatcher/plugin}/plugin.cpp (94%) rename test/lib/dispatcher/{ => shared}/lib.cpp (87%) rename test/lib/dispatcher/{ => shared}/main.cpp (95%) delete mode 100644 test/lib/dispatcher/types.h delete mode 100644 test/lib/dispatcher_plugin/types.h rename test/lib/{emitter_plugin => emitter/common}/types.h (66%) rename test/lib/{emitter_plugin => emitter/plugin}/main.cpp (94%) rename test/lib/{emitter_plugin => emitter/plugin}/plugin.cpp (94%) rename test/lib/emitter/{ => shared}/lib.cpp (85%) rename test/lib/emitter/{ => shared}/main.cpp (92%) delete mode 100644 test/lib/emitter/types.h create mode 100644 test/lib/locator/common/types.h rename test/lib/{locator_plugin => locator/plugin}/main.cpp (88%) rename test/lib/{locator_plugin => locator/plugin}/plugin.cpp (94%) rename test/lib/{locator_plugin => locator/plugin}/types.h (56%) rename test/lib/locator/{ => shared}/lib.cpp (90%) rename test/lib/locator/{ => shared}/main.cpp (95%) delete mode 100644 test/lib/locator/types.h rename test/lib/meta/{ => common}/types.h (58%) rename test/lib/{meta_plugin => meta/plugin}/main.cpp (95%) rename test/lib/{meta_plugin => meta/plugin}/plugin.cpp (97%) rename test/lib/{meta_plugin => meta/plugin}/types.h (71%) rename test/lib/{meta_plugin_std => meta/plugin_std}/main.cpp (100%) rename test/lib/{meta_plugin_std => meta/plugin_std}/plugin.cpp (97%) rename test/lib/{meta_plugin_std => meta/plugin_std}/types.h (80%) rename test/lib/meta/{ => shared}/lib.cpp (96%) rename test/lib/meta/{ => shared}/main.cpp (98%) rename test/lib/{registry_plugin => registry/common}/types.h (55%) rename test/lib/{registry_plugin => registry/plugin}/main.cpp (96%) rename test/lib/{registry_plugin => registry/plugin}/plugin.cpp (96%) rename test/lib/registry/{ => shared}/lib.cpp (88%) rename test/lib/registry/{ => shared}/main.cpp (96%) delete mode 100644 test/lib/registry/types.h diff --git a/.github/workflows/analyzer.yml b/.github/workflows/analyzer.yml index 179d668f..68b32d77 100644 --- a/.github/workflows/analyzer.yml +++ b/.github/workflows/analyzer.yml @@ -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 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2248787b..2c575a47 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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: diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 74b2e68a..150c78aa 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -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 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c89f379f..1d58b67f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -15,7 +15,7 @@ jobs: FORMULA: entt.rb steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Clone repository working-directory: build env: diff --git a/.github/workflows/sanitizer.yml b/.github/workflows/sanitizer.yml index c67ed1d4..a41ab6c3 100644 --- a/.github/workflows/sanitizer.yml +++ b/.github/workflows/sanitizer.yml @@ -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: diff --git a/CMakeLists.txt b/CMakeLists.txt index d9e26ee7..63240678 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 ") +message(VERBOSE "* Copyright (c) 2017-2023 Michele Caini ") message(VERBOSE "*") # @@ -143,6 +143,7 @@ if(ENTT_INCLUDE_HEADERS) $ $ $ + $ $ $ $ @@ -151,7 +152,6 @@ if(ENTT_INCLUDE_HEADERS) $ $ $ - $ $ $ $ diff --git a/LICENSE b/LICENSE index 770161ed..c6595f00 100644 --- a/LICENSE +++ b/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 diff --git a/README.md b/README.md index dc7885ec..c4c136c1 100644 --- a/README.md +++ b/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.
+for the latest release, that is the last stable tag.
Moreover, there exists a [wiki](https://github.com/skypjack/entt/wiki) dedicated to the project where users can find all related documentation pages. diff --git a/docs/md/container.md b/docs/md/container.md index 173e729e..d8361b38 100644 --- a/docs/md/container.md +++ b/docs/md/container.md @@ -9,7 +9,6 @@ * [Containers](#containers) * [Dense map](#dense-map) * [Dense set](#dense-set) - @@ -21,7 +20,7 @@ difficult to do better (although it's very easy to do worse, as many examples available online demonstrate).
`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.
-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`.
+the `std::unordered_map` class.
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.
@@ -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`.
+library, that is, the `std::unordered_set` class.
Therefore, there is no need to go into the API description. diff --git a/docs/md/core.md b/docs/md/core.md index e4ac8b50..2b172ce2 100644 --- a/docs/md/core.md +++ b/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.
-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.
+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).
+`EnTT` offers its own `any` type. It may seem redundant considering that C++17 +introduced `std::any`, but it is not (hopefully).
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.
-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.
-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.
+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.
-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.
+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()` 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()` if the container is empty.
+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.
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.
-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.
+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.
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.
-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; 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.
-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.
-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; ``` -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.
-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.
+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.
-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.
+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.
+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.
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 ``` 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.
-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.
+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.
+Hashed strings are human-readable identifiers in the codebase that turn into +numeric values at runtime, thus without affecting performance.
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.
-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.
+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`.
-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`.
+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`. For those who want to use the C++ type for wide -character representation, there exists also the alias `hashed_wstring` for -`basic_hashed_string`.
+The `hashed_string` class is an alias for `basic_hashed_string`. To use +the C++ type for wide character representations, there exists also the alias +`hashed_wstring` for `basic_hashed_string`.
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.
+The hashed string class uses FNV-1a internally to hash strings. Because of the +_pigeonhole principle_, conflicts are possible. This is a fact.
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.
+hashing functions. In this case, the best solution is likely to give up. That's +all.
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.
+Writing and working with iterators isn't always easy. More often than not it +also leads to duplicated code.
`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.
+their const counterparts) for iteration.
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.
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.
-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).
-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.
+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.
+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::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.
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.
- 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.
+ Indexes **must** be sequentially generated in this case.
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.
- 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::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.
+ 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.
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.
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.
+have the same fully qualified name. In this case, `type_name` returns the same +value for the two types.
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.
+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.
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; ``` -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(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.
+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.
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.
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 diff --git a/docs/md/entity.md b/docs/md/entity.md index e0713db9..c6fb7da4 100644 --- a/docs/md/entity.md +++ b/docs/md/entity.md @@ -12,9 +12,10 @@ * [Pay per use](#pay-per-use) * [All or nothing](#all-or-nothing) * [Vademecum](#vademecum) -* [Pools](#pools) * [The Registry, the Entity and the Component](#the-registry-the-entity-and-the-component) * [Observe changes](#observe-changes) + * [Entity lifecycle](#entity-lifecycle) + * [Listeners disconnection](#listeners-disconnection) * [They call me Reactive System](#they-call-me-reactive-system) * [Sorting: is it possible?](#sorting-is-it-possible) * [Helpers](#helpers) @@ -23,37 +24,43 @@ * [To entity](#to-entity) * [Dependencies](#dependencies) * [Invoke](#invoke) + * [Connection helper](#connection-helper) * [Handle](#handle) * [Organizer](#organizer) * [Context variables](#context-variables) * [Aliased properties](#aliased-properties) - * [Component traits](#component-traits) - * [Pointer stability](#pointer-stability) - * [In-place delete](#in-place-delete) - * [Hierarchies and the like](#hierarchies-and-the-like) - * [Meet the runtime](#meet-the-runtime) - * [A base class to rule them all](#a-base-class-to-rule-them-all) - * [Beam me up, registry](#beam-me-up-registry) * [Snapshot: complete vs continuous](#snapshot-complete-vs-continuous) * [Snapshot loader](#snapshot-loader) * [Continuous loader](#continuous-loader) * [Archives](#archives) * [One example to rule them all](#one-example-to-rule-them-all) +* [Storage](#storage) + * [Component traits](#component-traits) + * [Empty type optimization](#empty-type-optimization) + * [Void storage](#void-storage) + * [Entity storage](#entity-storage) + * [One of a kind to the registry](#one-of-a-kind-to-the-registry) + * [Pointer stability](#pointer-stability) + * [In-place delete](#in-place-delete) + * [Hierarchies and the like](#hierarchies-and-the-like) +* [Meet the runtime](#meet-the-runtime) + * [A base class to rule them all](#a-base-class-to-rule-them-all) + * [Beam me up, registry](#beam-me-up-registry) * [Views and Groups](#views-and-groups) * [Views](#views) - * [Iteration order](#iteration-order) + * [Create once, reuse many times](#create-once-reuse-many-times) + * [Exclude-only](#exclude-only) * [View pack](#view-pack) + * [Iteration order](#iteration-order) * [Runtime views](#runtime-views) * [Groups](#groups) * [Full-owning groups](#full-owning-groups) * [Partial-owning groups](#partial-owning-groups) * [Non-owning groups](#non-owning-groups) - * [Nested groups](#nested-groups) * [Types: const, non-const and all in between](#types-const-non-const-and-all-in-between) * [Give me everything](#give-me-everything) * [What is allowed and what is not](#what-is-allowed-and-what-is-not) * [More performance, more constraints](#more-performance-more-constraints) -* [Empty type optimization](#empty-type-optimization) * [Multithreading](#multithreading) * [Iterators](#iterators) * [Const registry](#const-registry) @@ -64,8 +71,8 @@ # Introduction -`EnTT` is a header-only, tiny and easy to use entity-component system (and much -more) written in modern C++.
+`EnTT` offers a header-only, tiny and easy to use entity-component system module +written in modern C++.
The entity-component-system (also known as _ECS_) is an architectural pattern used mostly in game development. @@ -73,8 +80,8 @@ used mostly in game development. ## Type-less and bitset-free -`EnTT` offers a sparse set based model that doesn't require users to specify the -set of components neither at compile-time nor at runtime.
+The library implements a sparse set based model that doesn't require users to +specify the set of components neither at compile-time nor at runtime.
This is why users can instantiate the core class simply like: ```cpp @@ -92,19 +99,20 @@ When the time comes, just use it and that's all. ## Build your own -`EnTT` is designed as a container that can be used at any time, just like a -vector or any other container. It doesn't attempt in any way to take over on the -user codebase, nor to control its main loop or process scheduling.
+The ECS module (as well as the rest of the library) is designed as a set of +containers that are used as needed, just like a vector or any other container. +It doesn't attempt in any way to take over on the user codebase, nor to control +its main loop or process scheduling.
Unlike other more or less well known models, it also makes use of independent -pools that can be extended via _static mixins_. The built-in signal support is -an example of this flexible model: defined as a mixin, it's easily disabled if -not needed. Similarly, the storage class has a specialization that shows how +pools that are extended via _static mixins_. The built-in signal support is an +example of this flexible design: defined as a mixin, it's easily disabled if not +needed. Similarly, the storage class has a specialization that shows how everything is customizable down to the smallest detail. ## Pay per use -`EnTT` is entirely designed around the principle that users have to pay only for -what they want. +Everything is designed around the principle that users only have to pay for what +they want. When it comes to using an entity-component system, the tradeoff is usually between performance and memory usage. The faster it is, the more memory it uses. @@ -117,57 +125,36 @@ performance.
basic data structures and gives users the possibility to pay more for higher performance where needed. -So far, this choice has proven to be a good one and I really hope it can be for -many others besides me. - ## All or nothing -`EnTT` is such that a `T**` pointer (or whatever a custom pool returns) is +As a rule of thumb, a `T **` pointer (or whatever a custom pool returns) is always available to directly access all the instances of a given component type `T`.
-I cannot say whether it will be useful or not to the reader, but it's worth to -mention it since it's one of the corner stones of this library. - -Many of the tools described below give the possibility to get this information -and have been designed around this need.
-The rest is experimentation and the desire to invent something new, hoping to -have succeeded. +This is one of the corner stones of the library. Many of the tools offered are +designed around this need and give the possibility to get this information. # Vademecum -The registry to store, the views and the groups to iterate. That's all. - The `entt::entity` type implements the concept of _entity identifier_. An entity (the _E_ of an _ECS_) is an opaque element to use as-is. Inspecting it isn't recommended since its format can change in future.
Components (the _C_ of an _ECS_) are of any type, without any constraints, not even that of being movable. No need to register them nor their types.
-Systems (the _S_ of an _ECS_) can be plain functions, functors, lambdas and so -on. It's not required to announce them in any case and have no requirements. +Systems (the _S_ of an _ECS_) are plain functions, functors, lambdas and so on. +It's not required to announce them in any case and have no requirements. The next sections go into detail on how to use the entity-component system part of the `EnTT` library.
-The project is composed of many other classes in addition to those described -below. For more details, please refer to the inline documentation. - -# Pools - -Pools of components are a sort of _specialized version_ of a sparse set. Each -pool contains all the instances of a single component type and all the entities -to which it's assigned.
-Sparse arrays are _paged_ to avoid wasting memory. Packed arrays of components -are also paged to have pointer stability upon additions. Packed arrays of -entities are not instead.
-All pools rearranges their items in order to keep the internal arrays tightly -packed and maximize performance, unless pointer stability is enabled. +This module is likely larger than what is described below. For more details, +please refer to the inline documentation. # The Registry, the Entity and the Component -A registry stores and manages entities (or better, identifiers) and pools.
-The class template `basic_registry` lets users decide what's the preferred type -to represent an entity. Because `std::uint32_t` is large enough for almost all -the cases, there also exists the enum class `entt::entity` that _wraps_ it and -the alias `entt::registry` for `entt::basic_registry`. +A registry stores and manages entities (or _identifiers_) and components.
+The class template `basic_registry` lets users decide what the preferred type to +represent an entity is. Because `std::uint32_t` is large enough for almost any +case, there also exists the enum class `entt::entity` that _wraps_ it and the +alias `entt::registry` for `entt::basic_registry`. Entities are represented by _entity identifiers_. An entity identifier contains information about the entity itself and its version.
@@ -184,8 +171,8 @@ auto entity = registry.create(); registry.destroy(entity); ``` -The `create` member function also accepts a hint and has an overload that gets -two iterators and can be used to generate multiple entities at once efficiently. +The `create` member function also accepts a hint. Moreover, it has an overload +that gets two iterators to use to generate many entities at once efficiently. Similarly, the `destroy` member function also works with a range of entities: ```cpp @@ -194,10 +181,10 @@ auto view = registry.view(); registry.destroy(view.begin(), view.end()); ``` -In addition to offering an overload to force the version upon destruction. Note -that this function removes all components from an entity before releasing its -identifier. There also exists a _lighter_ alternative that only releases the -elements without poking in any pool, for use with orphaned entities: +In addition to offering an overload to force the version upon destruction.
+This function removes all components from an entity before releasing it. There +also exists a _lighter_ alternative that doesn't query component pools, for use +with orphaned entities: ```cpp // releases an orphaned identifier @@ -205,30 +192,33 @@ registry.release(entity); ``` As with the `destroy` function, also in this case entity ranges are supported -and it's possible to force the version during release. +and it's possible to force a _version_. In both cases, when an identifier is released, the registry can freely reuse it internally. In particular, the version of an entity is increased (unless the overload that forces a version is used instead of the default one).
-Users can probe an identifier to know the information it carries: +Users can then _test_ identifiers by means of a registry: ```cpp // returns true if the entity is still valid, false otherwise bool b = registry.valid(entity); -// gets the version contained in the entity identifier -auto version = registry.version(entity); - // gets the actual version for the given entity auto curr = registry.current(entity); ``` -Components can be assigned to or removed from entities at any time. As for the -entities, the registry offers a set of functions to use to work with components. +Or _inspect_ them using some functions meant for parsing an identifier as-is, +such as: +```cpp +// gets the version contained in the entity identifier +auto version = entt::to_version(entity); +``` + +Components are assigned to or removed from entities at any time.
The `emplace` member function template creates, initializes and assigns to an entity the given component. It accepts a variable number of arguments to use to -construct the component itself if present: +construct the component itself: ```cpp registry.emplace(entity, 0., 0.); @@ -242,10 +232,9 @@ vel.dy = 0.; The default storage _detects_ aggregate types internally and exploits aggregate initialization when possible.
-Therefore, it's not strictly necessary to define a constructor for each type, in -accordance with the rules of the language. +Therefore, it's not strictly necessary to define a constructor for each type. -On the other hand, `insert` works with _ranges_ and can be used to: +The `insert` member function works with _ranges_ and is used to: * Assign the same component to all entities at once when a type is specified as a template parameter or an instance is passed as an argument: @@ -259,7 +248,7 @@ On the other hand, `insert` works with _ranges_ and can be used to: ``` * Assign a set of components to the entities when a range is provided (the - length of the range of components must be the same of that of entities): + length of the range of components **must** be the same of that of entities): ```cpp // first and last specify the range of entities, instances points to the first element of the range of components @@ -267,7 +256,7 @@ On the other hand, `insert` works with _ranges_ and can be used to: ``` If an entity already has the given component, the `replace` and `patch` member -function templates can be used to update it: +function templates are used to update it: ```cpp // replaces the component in-place @@ -284,7 +273,7 @@ When it's unknown whether an entity already owns an instance of a component, registry.emplace_or_replace(entity, 0., 0.); ``` -This is a slightly faster alternative for the following snippet: +This is a slightly faster alternative to the following snippet: ```cpp if(registry.all_of(entity)) { @@ -313,14 +302,14 @@ registry.erase(entity); ``` When in doubt whether the entity owns the component, use the `remove` member -function instead. It behaves similarly to `erase` but it erases the component -if and only if it exists, otherwise it returns safely to the caller: +function instead. It behaves similarly to `erase` but it drops the component if +and only if it exists, otherwise it returns safely to the caller: ```cpp registry.remove(entity); ``` -The `clear` member function works similarly and can be used to either: +The `clear` member function works similarly and is used to either: * Erases all instances of the given components from the entities that own them: @@ -334,7 +323,7 @@ The `clear` member function works similarly and can be used to either: registry.clear(); ``` -Finally, references to components can be retrieved simply as: +Finally, references to components are obtained simply as: ```cpp const auto &cregistry = registry; @@ -348,10 +337,8 @@ const auto [cpos, cvel] = cregistry.get(entity); auto [pos, vel] = registry.get(entity); ``` -The `get` member function template gives direct access to the component of an -entity stored in the underlying data structures of the registry. There exists -also an alternative member function named `try_get` that returns a pointer to -the component owned by an entity if any, a null pointer otherwise. +If the existence of the component isn't certain, `try_get` is the more suitable +function instead. ## Observe changes @@ -381,7 +368,15 @@ the destruction and update of an instance, respectively.
Because of how C++ works, listeners attached to `on_update` are only invoked following a call to `replace`, `emplace_or_replace` or `patch`. -The function type of a listener is equivalent to the following: +Runtime pools are also supported by providing an identifier to the functions +above: + +```cpp +registry.on_construct("other"_hs).connect<&my_free_function>(); +``` + +Refer to the following sections for more information about runtime pools.
+In all cases, the function type of a listener is equivalent to the following: ```cpp void(entt::registry &, entt::entity); @@ -421,6 +416,49 @@ There are many useful but less known functionalities that aren't described here, such as the connection objects or the possibility to attach listeners with a list of parameters that is shorter than that of the signal itself. +### Entity lifecycle + +Observing entities is also possible. In this case, the user must use the entity +type instead of the component type: + +```cpp +registry.on_construct().connect<&my_listener>(); +``` + +Since entity storage is unique within a registry, if a _name_ is provided it's +ignored and therefore discarded.
+As for the function signature, this is exactly the same as the components. + +Entities support all types of signals: construct, destroy and update. The latter +is perhaps ambiguous as an entity is not truly _updated_. Rather, its identifier +is created and finally released.
+Indeed, the update signal is meant to send _general notifications_ regarding an +entity. It can be triggered via the `patch` function, as is the case with +components: + +```cpp +registry.patch(entity); +``` + +Destroying an entity and then updating the version of an identifier **does not** +give rise to these types of signals under any circumstances instead. + +### Listeners disconnection + +The destruction order of the storage classes and therefore the disconnection of +the listeners is completely random.
+There are no guarantees today and while a logic is easily discerned, it's not +guaranteed that it will remain so in the future. + +For example, a listener getting disconnected after a component is discarded as a +result of pool destruction is most likely a recipe for problems.
+Rather, it's advisable to invoke the `clear` function of the registry before +destroying it. This forces the deletion of all components and entities without +ever discarding the pools.
+As a result, a listener that wants to access components, entities, or pools can +safely do so against a still valid registry, while checking for the existence of +the various elements as appropriate. + ### They call me Reactive System Signals are the basic tools to construct reactive systems, even if they aren't @@ -430,28 +468,28 @@ In order to explain what reactive systems are, this is a slightly revised quote from the documentation of the library that first introduced this tool, [Entitas](https://github.com/sschmid/Entitas-CSharp): ->Imagine you have 100 fighting units on the battlefield but only 10 of them ->changed their positions. Instead of using a normal system and updating all 100 ->entities depending on the position, you can use a reactive system which will ->only update the 10 changed units. So efficient. +> Imagine you have 100 fighting units on the battlefield but only 10 of them +> changed their positions. Instead of using a normal system and updating all 100 +> entities depending on the position, you can use a reactive system which will +> only update the 10 changed units. So efficient. -In `EnTT`, this means to iterating over a reduced set of entities and components -with respect to what would otherwise be returned from a view or a group.
+In `EnTT`, this means iterating over a reduced set of entities and components +than what would otherwise be returned from a view or group.
On these words, however, the similarities with the proposal of `Entitas` also end. The rules of the language and the design of the library obviously impose and allow different things. -An `observer` is initialized with an instance of a registry and a set of rules +An `observer` is initialized with an instance of a registry and a set of _rules_ that describes what are the entities to intercept. As an example: ```cpp entt::observer observer{registry, entt::collector.update()}; ``` -The class is default constructible and can be reconfigured at any time by means -of the `connect` member function. Moreover, instances can be disconnected from -the underlying registries through the `disconnect` member function.
-The `observer` offers also what is needed to query the internal state and to +The class is default constructible and is reconfigured at any time by means of +the `connect` member function. Moreover, an observer is disconnected from the +underlying registry through the `disconnect` member function.
+The `observer` offers also what is needed to query its _internal state_ and to know if it's empty or how many entities it contains. Moreover, it can return a raw pointer to the list of entities it contains. @@ -487,25 +525,23 @@ At least as long as the `observer` isn't const. This means that the non-const overload of `each` does also reset the underlying data structure before to return to the caller, while the const overload does not for obvious reasons. -The `collector` is a utility aimed to generate a list of `matcher`s (the actual -rules) to use with an `observer` instead.
+A `collector` is a utility aimed to generate a list of `matcher`s (the actual +rules) to use with an `observer`.
There are two types of `matcher`s: -* Observing matcher: an observer will return at least all the living entities - for which one or more of the given components have been updated and not yet - destroyed. +* Observing matcher: an observer returns at least the entities for which one or + more of the given components have been updated and not yet destroyed. ```cpp entt::collector.update(); ``` - _Updated_ in this case means that all listeners attached to `on_update` are - invoked. In order for this to happen, specific functions such as `patch` must - be used. Refer to the specific documentation for more details. + Where _updated_ means that all listeners attached to `on_update` are invoked. + In order for this to happen, specific functions such as `patch` must be used. + Refer to the specific documentation for more details. -* Grouping matcher: an observer will return at least all the living entities - that would have entered the given group if it existed and that would have - not yet left it. +* Grouping matcher: an observer returns at least the entities that would have + entered the given group if it existed and that would have not yet left it. ```cpp entt::collector.group(entt::exclude); @@ -519,7 +555,7 @@ have assigned the given components since the last time one asked.
If an entity already has all the components except one and the missing type is assigned to it, the entity is intercepted by a grouping matcher. -In addition, a matcher can be filtered with a `where` clause: +In addition, matchers support filtering by means of a `where` clause: ```cpp entt::collector.update().where(entt::exclude); @@ -529,9 +565,9 @@ This clause introduces a way to intercept entities if and only if they are already part of a hypothetical group. If they are not, they aren't returned by the observer, no matter if they matched the given rule.
In the example above, whenever the component `sprite` of an entity is updated, -the observer probes the entity itself to verify that it has at least `position` -and has not `velocity` before to store it aside. If one of the two conditions of -the filter isn't respected, the entity is discarded, no matter what. +the observer checks the entity itself to verify that it has at least `position` +and has not `velocity`. If one of the two conditions isn't satisfied, the entity +is discarded, no matter what. A `where` clause accepts a theoretically unlimited number of types as well as multiple elements in the exclusion list. Moreover, every matcher can have its @@ -540,12 +576,11 @@ one. ## Sorting: is it possible? -Sorting entities and components is possible with `EnTT`. In particular, it uses -an in-place algorithm that doesn't require memory allocations nor anything else -and is therefore particularly convenient.
+Sorting entities and components is possible using an in-place algorithm that +doesn't require memory allocations and is therefore quite convenient.
There are two functions that respond to slightly different needs: -* Components can be sorted either directly: +* Components are sorted either directly: ```cpp registry.sort([](const auto &lhs, const auto &rhs) { @@ -562,10 +597,9 @@ There are two functions that respond to slightly different needs: ``` There exists also the possibility to use a custom sort function object for - when the usage pattern is known. As an example, in case of an almost sorted - pool, quick sort could be much slower than insertion sort. + when the usage pattern is known. -* Components can be sorted according to the order imposed by another component: +* Components are sorted according to the order imposed by another component: ```cpp registry.sort(); @@ -584,7 +618,7 @@ built-in support for the most basic functionalities. ### Null entity -The `entt::null` variable models the concept of _null entity_.
+The `entt::null` variable models the concept of a _null entity_.
The library guarantees that the following expression always returns false: ```cpp @@ -601,7 +635,7 @@ conversions from the null entity to identifiers of any allowed type: entt::entity null = entt::null; ``` -Similarly, the null entity can be compared to any other identifier: +Similarly, the null entity compares to any other identifier: ```cpp const auto entity = registry.create(); @@ -613,13 +647,13 @@ identifier and is instead completely transparent to its version. Be aware that `entt::null` and entity 0 aren't the same thing. Likewise, a zero initialized entity isn't the same as `entt::null`. Therefore, although -`entt::entity{}` is in some sense an alias for entity 0, none of them can be -used to create a null entity. +`entt::entity{}` is in some sense an alias for entity 0, none of them are used +to create a null entity. ### Tombstone Similar to the null entity, the `entt::tombstone` variable models the concept of -_tombstone_.
+a _tombstone_.
Once created, the integral form of the two values is the same, although they affect different parts of an identifier. In fact, the tombstone only uses the version part of it and is completely transparent to the entity part. @@ -644,7 +678,7 @@ exist implicit conversions from a tombstone to identifiers of any allowed type: entt::entity null = entt::tombstone; ``` -Similarly, the tombstone can be compared to any other identifier: +Similarly, the tombstone compares to any other identifier: ```cpp const auto entity = registry.create(); @@ -653,14 +687,13 @@ const bool tombstone = (entity == entt::tombstone); Be aware that `entt::tombstone` and entity 0 aren't the same thing. Likewise, a zero initialized entity isn't the same as `entt::tombstone`. Therefore, although -`entt::entity{}` is in some sense an alias for entity 0, none of them can be -used to create tombstones. +`entt::entity{}` is in some sense an alias for entity 0, none of them are used +to create tombstones. ### To entity -Sometimes it's useful to get the entity from a component instance.
-This is what the `entt::to_entity` helper does. It accepts a registry and an -instance of a component and returns the entity associated with the latter: +This function accepts a registry and an instance of a component and returns the +entity associated with the latter: ```cpp const auto entity = entt::to_entity(registry, position); @@ -670,9 +703,8 @@ A null entity is returned in case the component doesn't belong to the registry. ### Dependencies -The `registry` class is designed to be able to create short circuits between its -functions. This simplifies the definition of _dependencies_ between different -operations.
+The `registry` class is designed to create short circuits between its member +functions. This greatly simplifies the definition of a _dependency_.
For example, the following adds (or replaces) the component `a_type` whenever `my_type` is assigned to an entity: @@ -680,29 +712,27 @@ For example, the following adds (or replaces) the component `a_type` whenever registry.on_construct().connect<&entt::registry::emplace_or_replace>(); ``` -Similarly, the code shown below removes `a_type` from an entity whenever -`my_type` is assigned to it: +Similarly, the code below removes `a_type` from an entity whenever `my_type` is +assigned to it: ```cpp registry.on_construct().connect<&entt::registry::remove>(); ``` -A dependency can also be easily broken as follows: +A dependency is easily _broken_ as follows: ```cpp registry.on_construct().disconnect<&entt::registry::emplace_or_replace>(); ``` -There are many other types of dependencies. In general, most of the functions -that accept an entity as the first argument are good candidates for this +There are many other types of _dependencies_. In general, most of the functions +that accept an entity as their first argument are good candidates for this purpose. ### Invoke -Sometimes it's useful to directly invoke a member function of a component as a -callback. It's already possible in practice but requires users to _extend_ their -classes and this may not always be possible.
-The `invoke` helper allows to _propagate_ the signal in these cases: +The `invoke` helper allows to _propagate_ a signal to a member function of a +component without having to _extend_ it: ```cpp registry.on_construct().connect>(); @@ -711,21 +741,38 @@ registry.on_construct().connect>(); All it does is pick up the _right_ component for the received entity and invoke the requested method, passing on the arguments if necessary. +### Connection helper + +Connecting signals can quickly become cumbersome.
+This utility aims to simplify the process by grouping the calls: + +```cpp +entt::sigh_helper{registry} + .with() + .on_construct<&a_listener>() + .on_destroy<&another_listener>() + .with("other"_hs) + .on_update(); +``` + +Runtime pools are also supported by providing an identifier when calling `with`, +as shown in the previous snippet. Refer to the following sections for more +information about runtime pools.
+Obviously, this helper doesn't make the code disappear but it should at least +reduce the boilerplate in the most complex cases. + ### Handle -A handle is a thin wrapper around an entity and a registry. It provides the same -functions that the registry offers for working with components, such as -`emplace`, `get`, `patch`, `remove` and so on. The difference being that the -entity is implicitly passed to the registry.
+A handle is a thin wrapper around an entity and a registry. It _replicates_ the +API of a registry by offering functions such as `get` or `emplace`. The +difference being that the entity is implicitly passed to the registry.
It's default constructible as an invalid handle that contains a null registry and a null entity. When it contains a null registry, calling functions that -delegate execution to the registry will cause an undefined behavior, so it's -recommended to check the validity of the handle with implicit cast to `bool` -when in doubt.
-A handle is also non-owning, meaning that it can be freely copied and moved -around without affecting its entity (in fact, handles happen to be trivially -copyable). An implication of this is that mutability becomes part of the -type. +delegate execution to the registry causes undefined behavior. It's recommended +to test for validity with its implicit cast to `bool` if in doubt.
+A handle is also non-owning, meaning that it's freely copied and moved around +without affecting its entity (in fact, handles happen to be trivially copyable). +An implication of this is that mutability becomes part of the type. There are two aliases that use `entt::entity` as their default entity: `entt::handle` and `entt::const_handle`.
@@ -736,12 +783,8 @@ using my_handle = entt::basic_handle>; using my_const_handle = entt::basic_handle>; ``` -Handles are also implicitly convertible to const handles out of the box but not -the other way around.
-A handle stores a non-const pointer to a registry and therefore it can do all -the things that can be done with a non-const registry. On the other hand, const -handles store const pointers to registries and offer a restricted set of -functionalities. +Non-const handles are also implicitly convertible to const handles out of the +box but not the other way around. This class is intended to simplify function signatures. In case of functions that take a registry and an entity and do most of their work on that entity, @@ -793,15 +836,15 @@ it. For example: organizer.emplace<&free_function>("func"); ``` -When a function of any type is registered with the organizer, everything it -accesses is considered a _resource_ (views are _unpacked_ and their types are -treated as resources). The _constness_ of the type also dictates its access mode -(RO/RW). In turn, this affects the resulting graph, since it influences the -possibility of launching tasks in parallel.
+When a function is registered with the organizer, everything it accesses is +considered a _resource_ (views are _unpacked_ and their types are treated as +resources). The _constness_ of a type also dictates its access mode (RO/RW). In +turn, this affects the resulting graph, since it influences the possibility of +launching tasks in parallel.
As for the registry, if a function doesn't explicitly request it or requires a constant reference to it, it's considered a read-only access. Otherwise, it's -considered as read-write access. All functions will still have the registry -among their resources. +considered as read-write access. All functions have the registry among their +resources. When registering a function, users can also require resources that aren't in the list of parameters of the function itself. These are declared as template @@ -819,8 +862,8 @@ organizer.emplace<&free_function, const renderable>("func"); ``` In this case, even if `renderable` appears among the parameters of the function -as not constant, it will be treated as constant as regards the generation of the -task graph. +as not constant, it's treated as constant as regards the generation of the task +graph. To generate the task graph, the organizer offers the `graph` member function: @@ -828,22 +871,22 @@ To generate the task graph, the organizer offers the `graph` member function: std::vector graph = organizer.graph(); ``` -The graph is returned in the form of an adjacency list. Each vertex offers the +A graph is returned in the form of an adjacency list. Each vertex offers the following features: -* `ro_count` and `rw_count`: they return the number of resources accessed in - read-only or read-write mode. +* `ro_count` and `rw_count`: the number of resources accessed in read-only or + read-write mode. -* `ro_dependency` and `rw_dependency`: useful for retrieving the type info - objects associated with the parameters of the underlying function. +* `ro_dependency` and `rw_dependency`: type info objects associated with the + parameters of the underlying function. -* `top_level`: indicates whether a node is a top level one, that is, it has no - entering edges. +* `top_level`: true if a node is a top level one (it has no entering edges), + false otherwise. -* `info`: returns the type info object associated with the underlying function. +* `info`: type info object associated with the underlying function. -* `name`: returns the name associated with the given vertex if any, a null - pointer otherwise. +* `name`: the name associated with the given vertex if any, a null pointer + otherwise. * `callback`: a pointer to the function to execute and whose function type is `void(const void *, entt::registry &)`. @@ -854,8 +897,8 @@ following features: within the adjacency list. Since the creation of pools and resources within the registry isn't necessarily -thread safe, each vertex also offers a `prepare` function which can be called to -setup a registry for execution with the created graph: +thread safe, each vertex also offers a `prepare` function which is used to setup +a registry for execution with the created graph: ```cpp auto graph = organizer.graph(); @@ -873,8 +916,8 @@ use the preferred tool. Each registry has a _context_ associated with it, which is an `any` object map accessible by both type and _name_ for convenience. The _name_ isn't really a -name though. In fact, it's a numeric id of type `id_type` to be used as a key -for the variable. Any value is accepted, even runtime ones.
+name though. In fact, it's a numeric id of type `id_type` used as a key for the +variable. Any value is accepted, even runtime ones.
The context is returned via the `ctx` functions and offers a minimal set of feature including the following: @@ -904,9 +947,9 @@ registry.ctx().erase(); registry.ctx().erase("my_variable"_hs); ``` -The type of a context variable must be such that it's default constructible and -can be moved. If the supplied type doesn't match that of the variable when using -a _name_, the operation will fail.
+Context variable must be both default constructible and movable. If the supplied +type doesn't match that of the variable when using a _name_, the operation +fails.
For all users who want to use the context but don't want to create elements, the `contains` and `find` functions are also available: @@ -920,9 +963,9 @@ the variable to look up, as does `at`. ### Aliased properties -Context variables can also be used to create aliases for existing variables that -aren't directly managed by the registry. In this case, it's also possible to -make them read-only.
+A context also supports creating _aliases_ for existing variables that aren't +directly managed by the registry. Const and therefore read-only variables are +also accepted.
To do that, the type used upon construction must be a reference type and an lvalue is necessarily provided as an argument: @@ -952,12 +995,245 @@ const my_type *ptr = registry.ctx().find(); const my_type &var = registry.ctx().get(); ``` -Aliased properties can be erased as it happens with any other variable. -Similarly, they can also be associated with user-generated _names_ (or ids). +Aliased properties are erased as it happens with any other variable. Similarly, +it's also possible to assign them a _name_. + +## Snapshot: complete vs continuous + +This module comes with bare minimum support to serialization.
+It doesn't convert components to bytes directly, there wasn't the need of +another tool for serialization out there. Instead, it accepts an opaque object +with a suitable interface (namely an _archive_) to serialize its internal data +structures and restore them later. The way types and instances are converted to +a bunch of bytes is completely in charge to the archive and thus to final users. + +The goal of the serialization part is to allow users to make both a dump of the +entire registry or a narrower snapshot, that is to select only the components in +which they are interested.
+Intuitively, the use cases are different. As an example, the first approach is +suitable for local save/restore functionalities while the latter is suitable for +creating client-server applications and for transferring somehow parts of the +representation side to side. + +To take a snapshot of a registry, use the `snapshot` class: + +```cpp +output_archive output; + +entt::snapshot{registry} + .get(output) + .get(output) + .get(output); +``` + +It isn't necessary to invoke all functions each and every time. What functions +to use in which case mostly depends on the goal. + +When _getting_ an entity type, the snapshot class serializes all entities along +with their versions.
+In all other case, entities and components from a given storage are passed to +the archive. Named pools are also supported: + +```cpp +entt::snapshot{registry}.get(output, "other"_hs); +``` + +There exists another version of the `get` member function that accepts a range +of entities to serialize. It can be used to _filter_ out those entities that +shouldn't be serialized for some reasons: + +```cpp +const auto view = registry.view(); +output_archive output; + +entt::snapshot{registry} + .get(output, view.begin(), view.end()) + .get(output, view.begin(), view.end()); +``` + +Once a snapshot is created, there exist mainly two _ways_ to load it: as a whole +and in a kind of _continuous mode_.
+The following sections describe both loaders and archives in details. + +### Snapshot loader + +A snapshot loader requires that the destination registry be empty. It loads all +the data at once while keeping intact the identifiers that the entities +originally had: + +```cpp +input_archive input; + +entt::snapshot_loader{registry} + .get(input) + .get(input) + .get(input) + .orphans(); +``` + +It isn't necessary to invoke all functions each and every time. What functions +to use in which case mostly depends on the goal.
+For obvious reasons, what is important is that the data are restored in exactly +the same order in which they were serialized. + +When _getting_ an entity type, a snapshot loader restores all entities with the +versions that they originally had at the source.
+In all other cases, entities and components are restored in a given storage. If +the registry doesn't contain the entity, it's also created accordingly. As for +the snapshot class, named pools are supported too: + +```cpp +entt::snapshot_loader{registry}.get(input, "other"_hs); +``` + +Finally, the `orphans` member function releases the entities that have no +components after a restore, if any. + +### Continuous loader + +A continuous loader is designed to load data from a source registry to a +(possibly) non-empty destination. The loader accommodates in a registry more +than one snapshot in a sort of _continuous loading_ that updates the destination +one step at a time.
+Identifiers that entities originally had are not transferred to the target. +Instead, the loader maps remote identifiers to local ones while restoring a +snapshot. Wrapping the archive is a conveninent way of updating identifiers that +are part of components automatically (see the example below).
+Another difference with the snapshot loader is that the continuous loader has an +internal state that must persist over time. Therefore, there is no reason to +limit its lifetime to that of a temporary object: + +```cpp +entt::continuous_loader loader{registry}; +input_archive input; + +auto archive = [&loader, &input](auto &value) { + input(value); + + if constexpr(std::is_same_v, dirty_component>) { + value.parent = loader.map(value.parent); + value.child = loader.map(value.child); + } +}; + +loader + .get(input) + .get(input) + .get(input) + .get(input) + .orphans(); +``` + +It isn't necessary to invoke all functions each and every time. What functions +to use in which case mostly depends on the goal.
+For obvious reasons, what is important is that the data are restored in exactly +the same order in which they were serialized. + +When _getting_ an entity type, a loader restores groups of entities and maps +each entity to a local counterpart when required. For each remote identifier not +yet registered by the loader, a local identifier is created so as to keep the +local entity in sync with the remote one.
+In all other cases, entities and components are restored in a given storage. If +the registry doesn't contain the entity, it's also tracked accordingly. As for +the snapshot class, named pools are supported too: + +```cpp +loader.get(input, "other"_hs); +``` + +Finally, the `orphans` member function releases the entities that have no +components after a restore, if any. + +### Archives + +Archives must publicly expose a predefined set of member functions. The API is +straightforward and consists only of a group of function call operators that +are invoked by the snapshot class and the loaders. + +In particular: + +* An output archive (the one used when creating a snapshot) exposes a function + call operator with the following signature to store entities: + + ```cpp + void operator()(entt::entity); + ``` + + Where `entt::entity` is the type of the entities used by the registry.
+ Note that all member functions of the snapshot class also make an initial call + to store aside the _size_ of the set they are going to store. In this case, + the expected function type for the function call operator is: + + ```cpp + void operator()(std::underlying_type_t); + ``` + + In addition, an archive accepts (const) references to the types of component + to serialize. Therefore, given a type `T`, the archive offers a function call + operator with the following signature: + + ```cpp + void operator()(const T &); + ``` + + The output archive can freely decide how to serialize the data. The registry + isn't affected at all by the decision. + +* An input archive (the one used when restoring a snapshot) exposes a function + call operator with the following signature to load entities: + + ```cpp + void operator()(entt::entity &); + ``` + + Where `entt::entity` is the type of the entities used by the registry. Each + time the function is invoked, the archive reads the next element from the + underlying storage and copies it in the given variable.
+ All member functions of a loader class also make an initial call to read the + _size_ of the set they are going to load. In this case, the expected function + type for the function call operator is: + + ```cpp + void operator()(std::underlying_type_t &); + ``` + + In addition, an archive accepts references to the types of component to + restore. Therefore, given a type `T`, the archive contains a function call + operator with the following signature: + + ```cpp + void operator()(T &); + ``` + + Every time this operator is invoked, the archive reads the next element from + the underlying storage and copies it in the given variable. + +### One example to rule them all + +`EnTT` comes with some examples (actually some tests) that show how to integrate +a well known library for serialization as an archive. It uses +[`Cereal C++`](https://uscilab.github.io/cereal/) under the hood, mainly +because I wanted to learn how it works at the time I was writing the code. + +The code **isn't** production-ready and it isn't neither the only nor (probably) +the best way to do it. However, feel free to use it at your own risk.
+The basic idea is to store everything in a group of queues in memory, then bring +everything back to the registry with different loaders. + +# Storage + +Pools of components are _specialized versions_ of the sparse set class. Each +pool contains all the instances of a single component type and all the entities +to which it's assigned.
+Sparse arrays are _paged_ to avoid wasting memory. Packed arrays of components +are also paged to have pointer stability upon additions. Packed arrays of +entities are not instead.
+All pools rearranges their items in order to keep the internal arrays tightly +packed and maximize performance, unless full pointer stability is enabled. ## Component traits -In `EnTT`, almost everything is customizable. Components are no exception.
+In `EnTT`, almost everything is customizable. Pools are no exception.
In this case, the _standardized_ way to access all component properties is the `component_traits` class. @@ -972,9 +1248,9 @@ The non-specialized version of this class contains the following members: * `page_size`: `Type::page_size` if present, `ENTT_PACKED_PAGE` for non-empty types and 0 otherwise. -Where `Type` is any type of component. All properties can be customized by -specializing the above class and defining all its members, or by adding only -those of interest to a component definition: +Where `Type` is any type of component. Properties are customized by specializing +the above class and defining its members, or by adding only those of interest to +a component definition: ```cpp struct transform { @@ -983,10 +1259,127 @@ struct transform { }; ``` -The `component_traits` class template will take care of correctly extracting the -properties from the supplied type to pass them to the rest of the library.
-In the case of a direct specialization, the class is also _sfinae-friendly_. It -supports single and multi type specializations as well as feature-based ones. +The `component_traits` class template takes care of _extracting_ the properties +from the supplied type.
+Plus, it's _sfinae-friendly_ and also supports feature-based specializations. + +## Empty type optimization + +An empty type `T` is such that `std::is_empty_v` returns true. They also are +the same types for which _empty base optimization_ (EBO) is possible.
+`EnTT` handles these types in a special way, optimizing both in terms of +performance and memory usage. However, this also has consequences that are worth +mentioning. + +When an empty type is detected, it's not instantiated by default. Therefore, +only the entities to which it's assigned are made available. There doesn't exist +a way to _get_ empty types from a storage or a registry. Views and groups never +return their instances too (for example, during a call to `each`).
+On the other hand, iterations are faster because only the entities to which the +type is assigned are considered. Moreover, less memory is used, mainly because +there doesn't exist any instance of the component, no matter how many entities +it is assigned to. + +More in general, none of the feature offered by the library is affected, but for +the ones that require to return actual instances.
+This optimization is disabled by defining the `ENTT_NO_ETO` macro. In this case, +empty types are treated like all other types. Setting a page size at component +level via the `component_traits` class template is another way to disable this +optimization selectively rather than globally. + +## Void storage + +A void storage (or `entt::storage` or `entt::basic_storage`), +is a fully functional storage type used to create pools not associated with a +particular component type.
+From a technical point of view, it's in all respects similar to a storage for +empty types when their optimization is enabled. Pagination is disabled as well +as pointer stability (as not necessary).
+However, this should be preferred to using a simple sparse set. In particular, +a void storage offers all those feature normally offered by other storage types. +Therefore, it's a perfectly valid pool for use with views and groups or within a +registry. + +## Entity storage + +This storage is such that the component type is the same as the entity type, for +example `entt::storage` or `entt::basic_storage`.
+For this type of pools, there is a specific specialization within `EnTT`. In +fact, entities are subject to different rules with respect to components +(although still customizable by the user if needed). In particular: + +* Entities are never truly _deleted_. They are moved out of the list of entities + _in use_ and their versions are updated automatically. + +* `emplace` as well as `insert` have a slightly different meaning than their + counterparts for components. In the case of an entity storage, these functions + _generate_ or _recycle_ identifiers rather than allowing them to be _assigned_ + to existing entities. + +* The `each` function iterates only the entities _in use_, that is, those not + marked as _ready for reuse_. To iterate all the entities it's necessary to + iterate the underlying sparse set instead. + +Moreover, the entity storage offers a couple of additional utilities such as: + +* The `in_use` function which is used to know how many entities are still + _in use_. When combined with `size`, it also makes it possible to know how + many entities are available for recycling. + +* The `pack` function which is used to make a given set of entities contiguous. + This is particularly useful to pass valid lists of entities via iterators + (with access usually optimized within the library). + +This kind of storage is designed to be used where any other storage is fine and +can therefore be combined with views, groups and so on. + +### One of a kind to the registry + +Within the registry, an entity storage is treated in all respects like any other +storage.
+Therefore, it's possible to add mixins to it as well as retrieve it via the +`storage` function. It can also be used as storage in a view (for exclude-only +views for example): + +```cpp +auto view = registry.view(entt::exclude); +``` + +However, it's also subject to a couple of exceptions, partly out of necessity +and partly for ease of use. + +In particular, it's not possible to create multiple elements of this type.
+This means that the _name_ used to retrieve this kind of storage is ignored and +the registry will only ever return the same element to the caller. For example: + +```cpp +auto &other = registry.storage("other"_hs); +``` + +In this case, the identifier is discarded as is. The call is in all respects +equivalent to the following: + +```cpp +auto &storage = registry.storage(); +``` + +Because entity storage doesn't have a name, it can't be retrieved via the opaque +`storage` function either.
+It would make no sense to try anyway, given that the type of the registry and +therefore its entity type are known regardless. + +Finally, when the user asks the registry for an iterable object to visit all the +storage elements inside it as follows: + +```cpp +for(auto [id, storage]: registry.each()) { + // ... +} +``` + +Entity storage is never returned. This simplifies many tasks (such as copying an +entity) and fits perfectly with the fact that this type of storage doesn't have +an identifier inside the registry. ## Pointer stability @@ -1014,7 +1407,7 @@ definition when needed.
Views and groups adapt accordingly when they detect a storage with a different deletion policy than the default. In particular: -* Groups are incompatible with stable storage and will even refuse to compile. +* Groups are incompatible with stable storage and even refuse to compile. * Multi type and runtime views are completely transparent to storage policies. * Single type views for stable storage types offer the same interface of multi type views. For example, only `size_hint` is available. @@ -1059,28 +1452,28 @@ struct transform { Furthermore, it's quite common for a group of elements to be created close in time and therefore fallback into adjacent positions, thus favoring locality even -on random accesses. Locality that won't be sacrificed over time given the -stability of storage positions, with undoubted performance advantages. +on random accesses. Locality that isn't sacrificed over time given the stability +of storage positions, with undoubted performance advantages. -## Meet the runtime +# Meet the runtime `EnTT` takes advantage of what the language offers at compile-time. However, this can have its downsides (well known to those familiar with type erasure techniques).
To fill the gap, the library also provides a bunch of utilities and feature that -can be very useful to handle types and pools at runtime. +are very useful to handle types and pools at runtime. -### A base class to rule them all +## A base class to rule them all -Storage classes are fully self-contained types. These can be extended via mixins +Storage classes are fully self-contained types. They are _extended_ via mixins to add more functionalities (generic or type specific). In addition, they offer a basic set of functions that already allow users to go very far.
The aim is to limit the need for customizations as much as possible, offering what is usually necessary for the vast majority of cases. -When a storage is used through its base class (i.e. when its actual type isn't -known), there is always the possibility of receiving a `type_info` describing -the type of the objects associated with the entities (if any): +When a storage is used through its base class (for example, when its actual type +isn't known), there is always the possibility of receiving a `type_info` object +for the type of elements associated with the entities (if any): ```cpp if(entt::type_id() == base.type()) { @@ -1089,8 +1482,8 @@ if(entt::type_id() == base.type()) { ``` Furthermore, all features rely on internal functions that forward the calls to -the mixins. The latter can then make use of any information, which can be set -via `bind`: +the mixins. The latter can then make use of any information, which is set via +`bind`: ```cpp base.bind(entt::forward_as_any(registry)); @@ -1105,13 +1498,13 @@ passed to it every time. Alongside these more specific things, there are also a couple of functions designed to address some common requirements such as copying an entity.
In particular, the base class behind a storage offers the possibility to _take_ -the object associated with an entity through an opaque pointer: +the value associated with an entity through an opaque pointer: ```cpp -const void *instance = base.get(entity); +const void *instance = base.value(entity); ``` -Similarly, the non-specialized `emplace` function accepts an optional opaque +Similarly, the non-specialized `push` function accepts an optional opaque pointer and behaves differently depending on the case: * When the pointer is null, the function tries to default-construct an instance @@ -1128,7 +1521,7 @@ them by copy if needed: // create a copy of an entity component by component for(auto &&curr: registry.storage()) { if(auto &storage = curr.second; storage.contains(src)) { - storage.emplace(dst, storage.get(src)); + storage.push(dst, storage.value(src)); } } ``` @@ -1137,16 +1530,10 @@ This is particularly useful to clone entities in an opaque way. In addition, the decoupling of features allows for filtering or use of different copying policies depending on the type. -### Beam me up, registry +## Beam me up, registry -`EnTT` is strongly based on types and has always allowed to create only one -storage of a certain type within a registry.
-However, this doesn't work well for users who want to create multiple storage of -the same type associated with different _names_, such as for interacting with a -scripting system. - -Nowadays, the library has solved this problem and offers the possibility of -associating a type with a _name_ (or rather, a numeric identifier): +`EnTT` allows the user to assign a _name_ (or rather, a numeric identifier) to a +type and then create multiple pools of the same type: ```cpp using namespace entt::literals; @@ -1155,30 +1542,28 @@ auto &&storage = registry.storage("second pool"_hs); If a name isn't provided, the default storage associated with the given type is always returned.
-Since the storage are also self-contained, the registry doesn't try in any way -to _duplicate_ its API and offer parallel functionalities for storage discovered -by name.
-However, there is still no limit to the possibilities of use. For example: +Since the storage are also self-contained, the registry doesn't _duplicate_ its +own API for them. However, there is still no limit to the possibilities of use: ```cpp auto &&other = registry.storage("other"_hs); registry.emplace(entity); -storage.emplace(entity); +storage.push(entity); ``` -In other words, anything that can be done via the registry interface can also be -done directly on the reference storage.
+Anything that can be done via the registry interface can also be done directly +on the reference storage.
On the other hand, those calls involving all storage are guaranteed to also _reach_ manually created ones: ```cpp -// will remove the entity from both storage +// removes the entity from both storage registry.destroy(entity); ``` -Finally, a storage of this type can be used with any view (which also accepts -multiple storages of the same type, if necessary): +Finally, a storage of this type works with any view (which also accepts multiple +storages of the same type, if necessary): ```cpp // direct initialization @@ -1193,266 +1578,21 @@ auto join = registry.view() | entt::basic_view{registry.storage -Sure the basic design remains very type-bound, but finally it's no longer bound -to this one option alone. - -## Snapshot: complete vs continuous - -The `registry` class offers basic support to serialization.
-It doesn't convert components to bytes directly, there wasn't the need of -another tool for serialization out there. Instead, it accepts an opaque object -with a suitable interface (namely an _archive_) to serialize its internal data -structures and restore them later. The way types and instances are converted to -a bunch of bytes is completely in charge to the archive and thus to final users. - -The goal of the serialization part is to allow users to make both a dump of the -entire registry or a narrower snapshot, that is to select only the components in -which they are interested.
-Intuitively, the use cases are different. As an example, the first approach is -suitable for local save/restore functionalities while the latter is suitable for -creating client-server applications and for transferring somehow parts of the -representation side to side. - -To take a snapshot of a registry, use the `snapshot` class: - -```cpp -output_archive output; - -entt::snapshot{registry} - .entities(output) - .component(output); -``` - -It isn't necessary to invoke all functions each and every time. What functions -to use in which case mostly depends on the goal and there is not a golden rule -for that. - -The `entities` member function makes the snapshot serialize all entities (both -those still alive and those released) along with their versions.
-On the other hand, the `component` member function is a function template the -aim of which is to store aside components. The presence of a template parameter -list is a consequence of a couple of design choices from the past and in the -present: - -* First of all, there is no reason to force a user to serialize all the - components at once and most of the time it isn't desiderable. As an example, - in case the stuff for the HUD in a game is put into the registry for some - reasons, its components can be freely discarded during a serialization step - because probably the software already knows how to reconstruct them correctly. - -* Furthermore, the registry makes heavy use of _type-erasure_ techniques - internally and doesn't know at any time what component types it contains. - Therefore being explicit at the call site is mandatory. - -There exists also another version of the `component` member function that -accepts a range of entities to serialize. This version is a bit slower than the -other one, mainly because it iterates the range of entities more than once for -internal purposes. However, it can be used to filter out those entities that -shouldn't be serialized for some reasons.
-As an example: - -```cpp -const auto view = registry.view(); -output_archive output; - -entt::snapshot{registry}.component(output, view.begin(), view.end()); -``` - -Note that `component` stores items along with entities. It means that it works -properly without a call to the `entities` member function. - -Once a snapshot is created, there exist mainly two _ways_ to load it: as a whole -and in a kind of _continuous mode_.
-The following sections describe both loaders and archives in details. - -### Snapshot loader - -A snapshot loader requires that the destination registry be empty and loads all -the data at once while keeping intact the identifiers that the entities -originally had.
-To use it, just pass to the constructor a valid registry: - -```cpp -input_archive input; - -entt::snapshot_loader{registry} - .entities(input) - .component(input) - .orphans(); -``` - -It isn't necessary to invoke all functions each and every time. What functions -to use in which case mostly depends on the goal and there is not a golden rule -for that. For obvious reasons, what is important is that the data are restored -in exactly the same order in which they were serialized. - -The `entities` member function restores the sets of entities and the versions -that they originally had at the source. - -The `component` member function restores all and only the components specified -and assigns them to the right entities. Note that the template parameter list -must be exactly the same used during the serialization. - -The `orphans` member function literally releases those entities that have no -components attached. It's usually useless if the snapshot is a full dump of the -source. However, in case all the entities are serialized but only few components -are saved, it could happen that some of the entities have no components once -restored. The best the users can do to deal with them is to release those -entities and thus update their versions. - -### Continuous loader - -A continuous loader is designed to load data from a source registry to a -(possibly) non-empty destination. The loader can accommodate in a registry more -than one snapshot in a sort of _continuous loading_ that updates the destination -one step at a time.
-Identifiers that entities originally had are not transferred to the target. -Instead, the loader maps remote identifiers to local ones while restoring a -snapshot. Because of that, this kind of loader offers a way to update -automatically identifiers that are part of components (as an example, as data -members or gathered in a container).
-Another difference with the snapshot loader is that the continuous loader has an -internal state that must persist over time. Therefore, there is no reason to -limit its lifetime to that of a temporary object. - -Example of use: - -```cpp -entt::continuous_loader loader{registry}; -input_archive input; - -loader.entities(input) - .component(input, &dirty_component::parent, &dirty_component::child) - .orphans() - .shrink(); -``` - -It isn't necessary to invoke all functions each and every time. What functions -to use in which case mostly depends on the goal and there is not a golden rule -for that. For obvious reasons, what is important is that the data are restored -in exactly the same order in which they were serialized. - -The `entities` member function restores groups of entities and maps each entity -to a local counterpart when required. In other terms, for each remote entity -identifier not yet registered by the loader, it creates a local identifier so -that it can keep the local entity in sync with the remote one. - -The `component` member function restores all and only the components specified -and assigns them to the right entities.
-In case the component contains entities itself (either as data members of type -`entt::entity` or as containers of entities), the loader can update them -automatically. To do that, it's enough to specify the data members to update as -shown in the example. - -The `orphans` member function literally releases those entities that have no -components after a restore. It has exactly the same purpose described in the -previous section and works the same way. - -Finally, `shrink` helps to purge local entities that no longer have a remote -conterpart. Users should invoke this member function after restoring each -snapshot, unless they know exactly what they are doing. - -### Archives - -Archives must publicly expose a predefined set of member functions. The API is -straightforward and consists only of a group of function call operators that -are invoked by the snapshot class and the loaders. - -In particular: - -* An output archive, the one used when creating a snapshot, must expose a - function call operator with the following signature to store entities: - - ```cpp - void operator()(entt::entity); - ``` - - Where `entt::entity` is the type of the entities used by the registry.
- Note that all member functions of the snapshot class make also an initial call - to store aside the _size_ of the set they are going to store. In this case, - the expected function type for the function call operator is: - - ```cpp - void operator()(std::underlying_type_t); - ``` - - In addition, an archive must accept a pair of entity and component for each - type to be serialized. Therefore, given a type `T`, the archive must contain a - function call operator with the following signature: - - ```cpp - void operator()(entt::entity, const T &); - ``` - - The output archive can freely decide how to serialize the data. The registry - is not affected at all by the decision. - -* An input archive, the one used when restoring a snapshot, must expose a - function call operator with the following signature to load entities: - - ```cpp - void operator()(entt::entity &); - ``` - - Where `entt::entity` is the type of the entities used by the registry. Each - time the function is invoked, the archive must read the next element from the - underlying storage and copy it in the given variable.
- Note that all member functions of a loader class make also an initial call to - read the _size_ of the set they are going to load. In this case, the expected - function type for the function call operator is: - - ```cpp - void operator()(std::underlying_type_t &); - ``` - - In addition, the archive must accept a pair of references to an entity and its - component for each type to be restored. Therefore, given a type `T`, the - archive must contain a function call operator with the following signature: - - ```cpp - void operator()(entt::entity &, T &); - ``` - - Every time such an operator is invoked, the archive must read the next - elements from the underlying storage and copy them in the given variables. - -### One example to rule them all - -`EnTT` comes with some examples (actually some tests) that show how to integrate -a well known library for serialization as an archive. It uses -[`Cereal C++`](https://uscilab.github.io/cereal/) under the hood, mainly -because I wanted to learn how it works at the time I was writing the code. - -The code is not production-ready and it isn't neither the only nor (probably) -the best way to do it. However, feel free to use it at your own risk. - -The basic idea is to store everything in a group of queues in memory, then bring -everything back to the registry with different loaders. +`EnTT` _at runtime_, which was previously quite limited. # Views and Groups -First of all, it's worth answering a question: why views and groups?
-Briefly, they're a good tool to enforce single responsibility. A system that has -access to a registry can create and destroy entities, as well as assign and -remove components. On the other side, a system that has access to a view or a -group can only iterate, read and update entities and components.
-It is a subtle difference that can help designing a better software sometimes. - -More in details: - -* Views are a non-intrusive tool to access entities and components without - affecting other functionalities or increasing the memory consumption. - -* Groups are an intrusive tool that allows to reach higher performance along - critical paths but has also a price to pay for that. +Views are a non-intrusive tool for working with entities and components without +affecting other functionalities or increasing memory consumption.
+Groups are an intrusive tool to use to improve performance along critical paths +but which also has a price to pay for that. There are mainly two kinds of views: _compile-time_ (also known as `view`) and runtime (also known as `runtime_view`).
-The former requires a compile-time list of component types and can make several -optimizations because of that. The latter can be constructed at runtime instead -using numerical type identifiers and are a bit slower to iterate.
-In both cases, creating and destroying a view isn't expensive at all since they +The former requires a compile-time list of component (or storage) types and can +make several optimizations because of that. The latter is constructed at runtime +using numerical type identifiers instead and is a bit slower to iterate.
+In both cases, creating and destroying views isn't expensive at all since they don't have any type of initialization. Groups come in three different flavors: _full-owning groups_, _partial-owning @@ -1460,42 +1600,32 @@ groups_ and _non-owning groups_. The main difference between them is in terms of performance.
Groups can literally _own_ one or more component types. They are allowed to rearrange pools so as to speed up iterations. Roughly speaking: the more -components a group owns, the faster it is to iterate them.
-A given component can belong to multiple groups only if they are _nested_, so -users have to define groups carefully to get the best out of them. +components a group owns, the faster it is to iterate them. ## Views -A view behaves differently if it's constructed for a single component or if it -has been created to iterate multiple components. Even the API is slightly -different in the two cases. +Single type views and multi type views behave differently and also have slightly +different APIs. -Single type views are specialized to give a boost in terms of performance in all -the situations. This kind of views can access the underlying data structures -directly and avoid superfluous checks. There is nothing as fast as a single type -view. In fact, they walk through a packed (actually paged) array of components -and return them one at a time.
-Views also offer a bunch of functionalities to get the number of entities and -components they are going to return. It's also possible to ask a view if it -contains a given entity.
+Single type views are specialized to give a performance boost in all cases. +There is nothing as fast as a single type view. They just walk through packed +(actually paged) arrays of elements and return them directly.
+This kind of views also allow to get the exact number of elements they are going +to return.
Refer to the inline documentation for all the details. -Multi type views iterate entities that have at least all the given components in -their bags. During construction, these views look at the number of entities -available for each component and pick up a reference to the smallest set of -candidates in order to speed up iterations.
-They offer fewer functionalities than single type views. In particular, a multi -type view exposes utility functions to get the estimated number of entities it -is going to return and to know if it contains a given entity.
+Multi type views iterate entities that have at least all the given components. +During construction, they look at the number of elements available in each pool +and use the smallest set in order to speed up iterations.
+This kind of views only allow to get the estimated number of elements they are +going to return.
Refer to the inline documentation for all the details. -There is no need to store views aside as they are extremely cheap to construct. -In fact, this is even discouraged when creating a view from a const registry. -Since all storage are lazily initialized, they may not exist when the view is -built. Therefore, the view itself will refer to an empty _placeholder_ and will -never be re-assigned the actual storage.
-In all cases, views return newly created and correctly initialized iterators for -the storage they refer to when `begin` or `end` are invoked. +Storing aside views isn't required as they are extremely cheap to construct. In +fact, this is even discouraged when creating a view from a const registry. Since +all storage are lazily initialized, they may not exist when the view is created. +Thus, while perfectly usable, the view may contain pending references that are +never reinitialized with the actual storage. Views share the way they are created by means of a registry: @@ -1556,8 +1686,8 @@ any case. As a side note, in the case of single type views, `get` accepts but doesn't strictly require a template parameter, since the type is implicitly defined. -However, when the type isn't specified, for consistency with the multi type -view, the instance will be returned using a tuple: +However, when the type isn't specified, the instance is returned using a tuple +for consistency with multi type views: ```cpp auto view = registry.view(); @@ -1571,10 +1701,106 @@ for(auto entity: view) { **Note**: prefer the `get` member function of a view instead of that of a registry during iterations to get the types iterated by the view itself. +### Create once, reuse many times + +Views support lazy initialization as well as _storage swapping_.
+An empty (or partially initialized) view is such that it returns false when +converted to bool (to let the user know that it isn't fully initialized) but it +also works as-is like any other view. + +In order to initialize a view one piece at a time, it allows users to inject +storage classes when available: + +```cpp +entt::storage_for_t storage{}; +entt::view> view{}; + +view.storage(storage); +``` + +If there are multiple storages of the same type, it's possible to disambiguate +using the _index_ of the element to be replaced: + +```cpp +view.storage<1>(storage); +``` + +The ability to literally _replace_ a storage in a view also opens up its reuse +with different sets of entities.
+For example, to _filter_ a view based on two groups of entities with different +characteristics, there will be no need to reinitialize anything: + +```cpp +entt::view> view{registry.storage>()}; + +entt::storage_for_t the_good{}; +entt::storage_for_t the_bad{}; + +// initialize the sets above as needed + +view.storage(the_good); + +for(auto [entt, elem]: view) { + // the good entities with their components here +} + +view.storage(the_bad); + +for(auto [entt, elem]: view) { + // the bad entities with their components here +} +``` + +Finally, it should be noted that the lack of a storage is treated to all intents +and purposes as if it were an _empty_ element.
+Thus, a _get_ storage (as in `entt::get_t`) makes the view empty automatically +while an _exclude_ storage (as in `entt::exclude_t`) is ignored as if that part +of the filter didn't exist. + +### Exclude-only + +_Exclude-only_ views aren't really a thing in `EnTT`.
+However, the same result can be achieved by combining the right storage into a +simple view. + +If one gets to the root of the problem, the purpose of an exclude-only view is +to return entities that don't meet certain requirements.
+Since entity storage, unlike exclude-only views, **is** a thing in `EnTT`, users +can leverage it for these kinds of queries. It's also guaranteed to be unique +within a registry and is always accessible when creating a view: + +```cpp +auto view = registry.view(entt::exclude); +``` + +The returned view is such that it will return only the entities that don't have +the `my_type` component, regardless of what other components they have. + +### View pack + +Views are combined with each other to create new and more specific queries.
+The type returned when combining multiple views together is itself a view, more +in general a multi component one. + +Combining different views tries to mimic C++20 ranges: + +```cpp +auto view = registry.view(); +auto other = registry.view(); + +auto pack = view | other; +``` + +The constness of the types is preserved and their order depends on the order in +which the views are combined. For example, the pack above returns an instance of +`position` first and then one of `velocity`.
+Since combining views generates views, a chain can be of arbitrary length and +the above type order rules apply sequentially. + ### Iteration order By default, a view is iterated along the pool that contains the smallest number -of components.
+of elements.
For example, if the registry contains fewer `velocity`s than it contains `position`s, then the order of the elements returned by the following view depends on how the `velocity` components are arranged in their pool: @@ -1611,39 +1837,14 @@ Unfortunately, multi type views don't offer reverse iterators. Therefore, in this case it's a must to implement this functionality manually or to use single type views to lead the iteration. -### View pack - -Views are combined with each other to create new and more specific types.
-The type returned when combining multiple views together is itself a view, more -in general a multi component one. - -Combining different views tries to mimic C++20 ranges: - -```cpp -auto view = registry.view(); -auto other = registry.view(); - -auto pack = view | other; -``` - -The constness of the types is preserved and their order depends on the order in -which the views are combined. Therefore, the pack in the example above will -return an instance of `position` first and then one of `velocity`.
-Since combining views generates views, a chain can be of arbitrary length and -the above type order rules apply sequentially. - ### Runtime views -Runtime views iterate entities that have at least all the given components in -their bags. During construction, these views look at the number of entities -available for each component and pick up a reference to the smallest set of -candidates in order to speed up iterations.
+Multi type views iterate entities that have at least all the given components. +During construction, they look at the number of elements available in each pool +and use the smallest set in order to speed up iterations.
They offer more or less the same functionalities of a multi type view. However, they don't expose a `get` member function and users should refer to the registry -that generated the view to access components. In particular, a runtime view -exposes utility functions to get the estimated number of entities it is going to -return and to know whether it's empty or not. It's also possible to ask a -runtime view if it contains a given entity.
+that generated the view to access components.
Refer to the inline documentation for all the details. Runtime views are pretty cheap to construct and should not be stored aside in @@ -1688,7 +1889,7 @@ useful in this regard. Groups are meant to iterate multiple components at once and to offer a faster alternative to multi type views.
Groups overcome the performance of the other tools available but require to get -the ownership of components. This sets some constraints on the pools. On the +the ownership of components. This sets some constraints on their pools. On the other hand, groups aren't an automatism that increases memory consumption, affects functionalities and tries to optimize iterations for all the possible combinations of components. Users can decide when to pay for groups and to what @@ -1708,24 +1909,18 @@ avoided as long as possible. All groups affect to an extent the creation and destruction of their components. This is due to the fact that they must _observe_ changes in the pools of interest and arrange data _correctly_ when needed for the types they own.
-That being said, the way groups operate is beyond the scope of this document. -However, it's unlikely that users will be able to appreciate the impact of -groups on the other functionalities of a registry. - -Groups offer a bunch of functionalities to get the number of entities and -components they are going to return. It's also possible to ask a group if it -contains a given entity.
+In all cases, a group allows to get the exact number of elements it's going to +return.
Refer to the inline documentation for all the details. -There is no need to store groups aside for they are extremely cheap to create, -even though valid groups can be copied without problems and reused freely.
+Storing aside groups isn't required as they are extremely cheap to create, even +though valid groups can be copied without problems and reused freely.
A group performs an initialization step the very first time it's requested and this could be quite costly. To avoid it, consider creating the group when no components have been assigned yet. If the registry is empty, preparation is -extremely fast. Groups also return newly created and correctly initialized -iterators whenever `begin` or `end` are invoked. +extremely fast. -To iterate groups, either use them in a range-for loop: +To iterate a group, either use it in a range-for loop: ```cpp auto group = registry.group(entt::get); @@ -1773,9 +1968,10 @@ registry during iterations to get the types iterated by the group itself. A full-owning group is the fastest tool a user can expect to use to iterate multiple components at once. It iterates all the components directly, no -indirection required. This type of groups performs more or less as if users are -accessing sequentially a bunch of packed arrays of components all sorted -identically, with no jumps nor branches. +indirection required.
+This type of groups performs more or less as if users are accessing sequentially +a bunch of packed arrays of components all sorted identically, with no jumps nor +branches. A full-owning group is created as: @@ -1793,15 +1989,15 @@ Once created, the group gets the ownership of all the components specified in the template parameter list and arranges their pools as needed. Sorting owned components is no longer allowed once the group has been created. -However, full-owning groups can be sorted by means of their `sort` member -functions. Sorting a full-owning group affects all its instances. +However, full-owning groups are sorted using their `sort` member functions. +Sorting a full-owning group affects all its instances. ### Partial-owning groups A partial-owning group works similarly to a full-owning group for the components -it owns, but relies on indirection to get components owned by other groups. This -isn't as fast as a full-owning group, but it's already much faster than views -when there are only one or two free components to retrieve (the most common +it owns, but relies on indirection to get components owned by other groups.
+This isn't as fast as a full-owning group, but it's already much faster than a +view when there are only one or two free components to retrieve (the most common cases likely). In the worst case, it's not slower than views anyway. A partial-owning group is created as: @@ -1821,15 +2017,15 @@ the template parameter list and arranges their pools as needed. The ownership of the types provided via `entt::get` doesn't pass to the group instead. Sorting owned components is no longer allowed once the group has been created. -However, partial-owning groups can be sorted by means of their `sort` member -functions. Sorting a partial-owning group affects all its instances. +However, partial-owning groups are sorted using their `sort` member functions. +Sorting a partial-owning group affects all its instances. ### Non-owning groups Non-owning groups are usually fast enough, for sure faster than views and well suited for most of the cases. However, they require custom data structures to -work properly and they increase memory consumption. As a rule of thumb, users -should avoid using non-owning groups, if possible. +work properly and they increase memory consumption.
+As a rule of thumb, users should avoid using non-owning groups, if possible. A non-owning group is created as: @@ -1847,88 +2043,8 @@ The group doesn't receive the ownership of any type of component in this case. This type of groups is therefore the least performing in general, but also the only one that can be used in any situation to slightly improve performance. -Non-owning groups can be sorted by means of their `sort` member functions. -Sorting a non-owning group affects all its instances. - -### Nested groups - -A type of component cannot be owned by two or more conflicting groups such as: - -* `registry.group()`. -* `registry.group()`. - -However, the same type can be owned by groups belonging to the same _family_, -also called _nested groups_, such as: - -* `registry.group()`. -* `registry.group()`. - -Fortunately, these are also very common cases if not the most common ones.
-It allows to increase performance on a greater number of component combinations. - -Two nested groups are such that they own at least one component type and the list -of component types involved by one of them is contained entirely in that of the -other. More specifically, this applies independently to all component lists used -to define a group.
-Therefore, the rules for defining whether two or more groups are nested can be -summarized as: - -* One of the groups involves one or more additional component types with respect - to the other, whether they are owned, observed or excluded. - -* The list of component types owned by the most restrictive group is the same or - contains entirely that of the others. This also applies to the list of - observed and excluded components. - -It means that nested groups _extend_ their parents by adding more conditions in -the form of new components. - -As mentioned, the components don't necessarily have to be all _owned_ so that -two groups can be considered nested. The following definitions are fully valid: - -* `registry.group(entt::get)`. -* `registry.group(entt::get)`. -* `registry.group(entt::get)`. - -Exclusion lists also play their part in this respect. When it comes to defining -nested groups, an excluded component type `T` is treated as being an observed -type `not_T`. Therefore, consider these two definitions: - -* `registry.group()`. -* `registry.group({}, entt::exclude)`. - -They are treated as if users were defining the following groups: - -* `group()`. -* `group(entt::get)`. - -Where `not_rotation` is an empty tag present only when `rotation` is not. - -Because of this, to define a new group that is more restrictive than an existing -one, it's enough to take the list of component types of the latter and extend it -by adding new component types either owned, observed or excluded, without any -precautions depending on the case.
-The opposite is also true. To define a _larger_ group, it will be enough to take -an existing one and remove _constraints_ from it, in whatever form they are -expressed.
-Note that the greater the number of component types involved by a group, the -more restrictive it is. - -Despite the extreme flexibility of nested groups which allow to independently -use component types either owned, observed or excluded, the real strength of -this tool lies in the possibility of defining a greater number of groups that -**own** the same components, thus offering the best performance in more -cases.
-In fact, given a list of component types involved by a group, the greater the -number of those owned, the greater the performance of the group itself. - -As a side note, it's no longer possible to sort all groups when defining nested -ones. This is because the most restrictive group shares its elements with the -less restrictive ones and ordering the latter would invalidate the former.
-However, given a family of nested groups, it's still possible to sort the most -restrictive of them. To prevent users from having to remember which of their -groups is the most restrictive, the registry class offers the `sortable` member -function to know if a group can be sorted or not. +Non-owning groups are sorted using their `sort` member functions. Sorting a +non-owning group affects all its instances. ## Types: const, non-const and all in between @@ -1936,8 +2052,8 @@ The `registry` class offers two overloads when it comes to constructing views and groups: a const version and a non-const one. The former accepts only const types as template parameters, the latter accepts both const and non-const types instead.
-It means that views and groups can be constructed from a const registry and they -propagate the constness of the registry to the types involved. As an example: +It means that views and groups generated by a const registry also propagate the +constness to the types involved. As an example: ```cpp entt::view view = std::as_const(registry).view(); @@ -1949,7 +2065,7 @@ Consider the following definition for a non-const view instead: entt::view view = registry.view(); ``` -In the example above, `view` can be used to access either read-only or writable +In the example above, `view` is used to access either read-only or writable `position` components while `velocity` components are read-only in all cases.
Similarly, these statements are all valid: @@ -1963,7 +2079,7 @@ std::tuple ctup = view.get(entity); @@ -2015,7 +2131,7 @@ registry.each([®istry](auto entity) { In general, iterating all entities can result in poor performance. It should not be done frequently to avoid the risk of a performance hit.
-However, it can be convenient when initializing an editor or to reclaim pending +However, it's convenient when initializing an editor or to reclaim pending identifiers. ## What is allowed and what is not @@ -2037,6 +2153,9 @@ and components during iterations, nor to have pointer stability.
returned. Destroying entities and components is always allowed instead, even if not currently iterated, without the risk of invalidating any references. +* In case of reverse iterations, adding or removing elements is not allowed + under any circumstances. It could quickly lead to undefined behaviors. + In other terms, iterators are rarely invalidated. Also, component references aren't invalidated when a new element is added while they could be invalidated upon destruction due to the _swap-and-pop_ policy, unless the type leading the @@ -2082,16 +2201,16 @@ nature and the scope of the groups, it isn't something in which it will happen to come across probably, but it's good to know it anyway. First of all, it must be said that creating components while iterating a group -isn't a problem at all and can be done freely as it happens with the views. The -same applies to the destruction of components and entities, for which the rules +isn't a problem at all and is done freely as it happens with the views. The same +applies to the destruction of components and entities, for which the rules mentioned above apply. -The additional limitation pops out instead when a given component that is owned -by a group is iterated outside of it. In this case, adding components that are -part of the group itself may invalidate the iterators. There are no further +The additional limitation arises instead when a given component that is owned by +a group is iterated outside of it. In this case, adding components that are part +of the group itself may invalidate the iterators. There are no further limitations to the destruction of components and entities.
-Fortunately, this isn't always true. In fact, it almost never is and this -happens only under certain conditions. In particular: +Fortunately, this isn't always true. In fact, it almost never is and only +happens under certain conditions. In particular: * Iterating a type of component that is part of a group with a single type view and adding to an entity all the components required to get it into the group @@ -2112,30 +2231,6 @@ data internally to maximize performance. Because of that, full consistency for owned components is guaranteed only when they are iterated as part of their groups or as free types with multi type views and groups in general. -# Empty type optimization - -An empty type `T` is such that `std::is_empty_v` returns true. They also are -the same types for which _empty base optimization_ (EBO) is possible.
-`EnTT` handles these types in a special way, optimizing both in terms of -performance and memory usage. However, this also has consequences that are worth -mentioning. - -When an empty type is detected, it's not instantiated by default. Therefore, -only the entities to which it's assigned are made available. There doesn't exist -a way to _get_ empty types from a registry. Views and groups will never return -their instances (for example, during a call to `each`).
-On the other hand, iterations are faster because only the entities to which the -type is assigned are considered. Moreover, less memory is used, mainly because -there doesn't exist any instance of the component, no matter how many entities -it is assigned to. - -More in general, none of the feature offered by the library is affected, but for -the ones that require to return actual instances.
-This optimization is disabled by defining the `ENTT_NO_ETO` macro. In this case, -empty types are treated like all other types. Setting a page size at component -level via the `component_traits` class template is another way to disable this -optimization selectively rather than globally. - # Multithreading In general, the entire registry isn't thread safe as it is. Thread safety isn't @@ -2148,8 +2243,8 @@ a set of components and modify the same set concurrently. However: * As long as a thread iterates the entities that have the component `X` or assign and removes that component from a set of entities, another thread can - safely do the same with components `Y` and `Z` and everything will work like a - charm. As a trivial example, users can freely execute the rendering system and + safely do the same with components `Y` and `Z` and everything work like just + fine. As a trivial example, users can freely execute the rendering system and iterate the renderable entities while updating a physic component concurrently on a separate thread. @@ -2170,9 +2265,9 @@ are completely responsible for synchronization whether required. On the other hand, they could get away with it without having to resort to particular expedients. -Finally, `EnTT` can be configured via a few compile-time definitions to make -some of its parts implicitly thread-safe, roughly speaking only the ones that -really make sense and can't be turned around.
+Finally, `EnTT` is configured via a few compile-time definitions to make some of +its parts implicitly thread-safe, roughly speaking only the ones that really +make sense and can't be turned around.
In particular, when multiple instances of objects referencing the type index generator (such as the `registry` class) are used in different threads, then it might be useful to define `ENTT_USE_ATOMIC`.
@@ -2186,7 +2281,7 @@ they meet at least the requirements of forward iterators.
In other terms, they are suitable for use with the parallel algorithms of the standard library. If it's not clear, this is a great thing. -As an example, this kind of iterators can be used in combination with +As an example, this kind of iterators are used in combination with `std::for_each` and `std::execution::par` to parallelize the visit and therefore the update of the components returned by a view or a group, as long as the constraints previously discussed are respected: @@ -2213,32 +2308,29 @@ should even benefit from it further. ## Const registry -A const registry is also fully thread safe. This means that it won't be able to +A const registry is also fully thread safe. This means that it's not able to lazily initialize a missing storage when a view is generated.
The reason for this is easy to explain. To avoid requiring types to be _announced_ in advance, a registry lazily creates the storage objects for the different components. However, this isn't possible for a thread safe const -registry.
-On the other side, all pools must necessarily _exist_ when creating a view. -Therefore, static _placeholders_ for missing storage are used to fill the gap. +registry. -Note that returned views are always valid and behave as expected in the context -of the caller. The only difference is that static _placeholders_ (if any) are -never renewed.
-As a result, a view created from a const registry may behave incorrectly over -time if it's kept for a second use.
+Returned views are always valid and behave as expected in the context of the +caller. However, they may contain dangling references to non-existing storage +when created from a const registry.
+As a result, such a view may misbehave over time if it's kept aside for a second +use.
Therefore, if the general advice is to create views when necessary and discard them immediately afterwards, this becomes almost a rule when it comes to views generated from a const registry. Fortunately, there is also a way to instantiate storage classes early when in doubt or when there are special requirements.
-Calling the `storage` method is equivalent to _announcing_ the existence of a -particular storage, to avoid running into problems. For those interested, there -are also alternative approaches, such as a single threaded tick for the registry -warm-up, but these are not always applicable.
-In this case, no placeholders will be used since all storage exist. In other -words, views never risk becoming _invalid_. +Calling the `storage` method is equivalent to _announcing_ a particular storage, +so as to avoid running into problems. For those interested, there are also +alternative approaches, such as a single threaded tick for the registry warm-up, +but these are not always applicable.
+In this case, views never risk becoming _invalid_. # Beyond this document diff --git a/docs/md/faq.md b/docs/md/faq.md index 1cbc795f..d270062f 100644 --- a/docs/md/faq.md +++ b/docs/md/faq.md @@ -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.
If this isn't clear, below you can find a _vademecum_ for this purpose: diff --git a/docs/md/graph.md b/docs/md/graph.md index 62bcf077..d7152999 100644 --- a/docs/md/graph.md +++ b/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) - @@ -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.
-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.
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.
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.
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 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.
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.
+Both the functions expect the vertex to visit (that is, to return the in- or +out-edges for) as an argument.
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.
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.
+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.
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.
This class works mainly with _identifiers_, that is, values of type `id_type`. -That is, both tasks and resources are identified by integral values.
+In other terms, both tasks and resources are identified by integral values.
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.
-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.
+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.
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.
-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.
+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.
+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`.
+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.
+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.
+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.
+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.
+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.
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.
+However, there is a way to _force_ the execution order of two processes.
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.
-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.
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 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. diff --git a/docs/md/lib.md b/docs/md/lib.md index 451d7139..ac1ab6cb 100644 --- a/docs/md/lib.md +++ b/docs/md/lib.md @@ -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.
-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.
+This raises the need to identify objects whose type has been erased.
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.
+`ENTT_API_IMPORT` are to import or export symbols, so as to make everything work +nicely across boundaries.
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).
+For those who need more details, the test suite contains many examples covering +the most common cases (see the `lib` directory for all details).
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::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.
-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 diff --git a/docs/md/links.md b/docs/md/links.md index fe087ddc..f1b6ce48 100644 --- a/docs/md/links.md +++ b/docs/md/links.md @@ -1,5 +1,22 @@ # EnTT in Action + +# 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) + + +# 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.
+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.
+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. diff --git a/docs/md/meta.md b/docs/md/meta.md index ef337ea9..23bc5cf9 100644 --- a/docs/md/meta.md +++ b/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).
-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.
+The `meta` function is where it all starts: ```cpp auto factory = entt::meta(); ``` -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().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.
-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.
+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.
- 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().ctor().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.
- 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().dtor<&destroy>(); ``` + The purpose is to offer the possibility to free up resources that require + _special treatment_ before an object is actually destroyed.
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.
- 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() @@ -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_.
- 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.
+ 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().data("member"_hs); @@ -153,13 +146,10 @@ decorated version of it. This object can be used to add the following: entt::meta().data, &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.
- 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() @@ -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_.
+ The `func` function requires the identifier to use for the meta data function. + Users can then access it by _name_ at runtime.
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.
- Use the `base` member function for this purpose: + derived from it: ```cpp entt::meta().base(); ``` - 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.
- 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().conv(); ``` -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.
-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.
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.
-Similar to `any`, this class can also be used to create _aliases_ for unmanaged +pointer-like types, while `any` doesn't.
+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` 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.
+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}; 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.
+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.
+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.
-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.
+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()); ``` 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.
-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().func("member"_hs); func) { @@ -319,26 +296,23 @@ for(auto &&[id, type]: entt::resolve().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.
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.
-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.
+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.
-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.
`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.
-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.
+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.
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.
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.
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.
- 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.
+ 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.
+ elements. Modifying the returned object directly modifies the element inside + the container.
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`. * 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 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.
+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.
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.
-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.
+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.
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 struct function_type {}; ``` -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.
+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.
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> { The reflection system doesn't verify the accuracy of the information nor infer a correspondence between real types and meta types.
-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(); ``` -This should make working with arithmetic types and scoped or unscoped enums as -easy as it is in C++.
-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++.
+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.
+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.
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.
+For default constructible types only, default constructors are automatically +defined and associated with their meta types, whether they are explicitly or +implicitly generated.
Therefore, this is all is needed to construct an integer from its meta type: ```cpp entt::resolve().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.
+It would be handy in this case to be able to construct a `meta_any` element from +it.
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.
+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_.
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().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`.
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().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.
-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.
+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.
-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() @@ -878,28 +822,22 @@ entt::meta() entt::meta().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().data("a_value"_hs).get({}).cast(); auto max = entt::resolve().data("max_int"_hs).get({}).cast(); ``` -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.
-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.
-Apparently, it's more difficult to say than to do: +for most of them: ```cpp entt::meta().type("reflected_type"_hs).prop("tooltip"_hs, "message"); @@ -914,10 +852,10 @@ Key only properties are also supported out of the box: entt::meta().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.
-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.
+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.
@@ -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.
@@ -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::value_or(); @@ -984,8 +922,8 @@ auto &&context = entt::locator::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(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}; ``` -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) diff --git a/docs/md/poly.md b/docs/md/poly.md index c87804e1..1c37be2c 100644 --- a/docs/md/poly.md +++ b/docs/md/poly.md @@ -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.
-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.
-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.
+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.
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.
-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.
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.
+Once the interface is defined, a generic implementation is needed to fulfill the +concept itself.
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.
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 { Why should a user fully define a concept if the function types are the same as the deduced ones?
-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.
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.
+interface, even if they're part of the types that fulfill the concept.
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.
+In this case, it's stated that the `draw` method of a generic type is enough to +satisfy the requirements of the `Drawable` concept.
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.
-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.
+_size_ of the static virtual table of the base class is used as an offset for +the local indexes.
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.
+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.
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 entt::type_list as_type_list(const entt::type_list &); ``` -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; @@ -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.
+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.
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()`.
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 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.
+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.
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. diff --git a/docs/md/process.md b/docs/md/process.md index 67219adf..25e41430 100644 --- a/docs/md/process.md +++ b/docs/md/process.md @@ -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.
+`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.
+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.
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.
-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 scheduler; +entt::basic_scheduler 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::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.
+function used to create chains of processes to run sequentially.
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 ... diff --git a/docs/md/reference.md b/docs/md/reference.md index e41b3d9b..a6741eae 100644 --- a/docs/md/reference.md +++ b/docs/md/reference.md @@ -1,18 +1,35 @@ # Similar projects + +# Table of Contents + +* [Introduction](#introduction) +* [Similar projects](#similar-projects) + + +# Introduction + There are many projects similar to `EnTT`, both open source and not.
Some even borrowed some ideas from this library and expressed them in different languages.
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.
+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.
+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.
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. diff --git a/docs/md/signal.md b/docs/md/signal.md index b2fd5590..b879697a 100644 --- a/docs/md/signal.md +++ b/docs/md/signal.md @@ -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.
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.
+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.
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.
-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.
+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!
+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.
+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.
+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.
-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.
@@ -409,7 +425,7 @@ of them at once: ```cpp dispatcher.sink().disconnect<&listener::receive>(listener); -dispatcher.sink().disconnect(listener); +dispatcher.sink().disconnect(&listener); ``` The `trigger` member function serves the purpose of sending an immediate event diff --git a/natvis/entt/entity.natvis b/natvis/entt/entity.natvis index 4925cd7c..8a741ab5 100644 --- a/natvis/entt/entity.natvis +++ b/natvis/entt/entity.natvis @@ -1,62 +1,31 @@ - - - - + + - {{ size={ epool.size() } }} + {{ pools={ pools.size() } }} - epool,view(simple)nr - - { epool.size() } - - - - - - - - epool[pos] - - ++pos - - - - - - { to_entity(free_list) != entity_traits::entity_mask } - - - - - - epool[it] - it = to_entity(epool[it]) - - - - + entities - { pools_size() } + { pools.size() } - pools_size() + pools.size() *pools.packed.first_base::value[$i].element.second - pools_size() + pools.size() *pools.packed.first_base::value[$i].element.second,view(simple) groups.size() - { vars_size() } + { vars.ctx.size() } - vars_size() + vars.ctx.size() vars.ctx.packed.first_base::value[$i].element.second @@ -69,20 +38,20 @@ packed.capacity() mode,en - { sparse.size() * entity_traits::page_size } + { sparse.size() * traits_type::page_size } sparse,view(simple) - + - page = pos / entity_traits::page_size - offset = pos & (entity_traits::page_size - 1) - - *((entity_traits::entity_type *)&sparse[page][offset]) & entity_traits::entity_mask + page = pos / traits_type::page_size + offset = pos & (traits_type::page_size - 1) + + *((traits_type::entity_type *)&sparse[page][offset]) & traits_type::entity_mask ++pos @@ -98,7 +67,7 @@ - + packed[pos] ++pos @@ -111,18 +80,19 @@ {{ size={ base_type::packed.size() }, type={ base_type::info->alias,na } }} - packed.first_base::value.capacity() * comp_traits::page_size - comp_traits::page_size + payload.capacity() * traits_type::page_size + traits_type::page_size + length (base_type*)this,nand (base_type*)this,view(simple)nand - + - - packed.first_base::value[pos / comp_traits::page_size][pos & (comp_traits::page_size - 1)] + + payload[pos / traits_type::page_size][pos & (traits_type::page_size - 1)] ++pos diff --git a/natvis/entt/signal.natvis b/natvis/entt/signal.natvis index 3e6e3ca7..4eb618c4 100644 --- a/natvis/entt/signal.natvis +++ b/natvis/entt/signal.natvis @@ -8,7 +8,7 @@ - + {{ size={ size() } }} @@ -50,7 +50,6 @@ {{ type={ "$T1" } }} signal,na - offset diff --git a/single_include/entt/entt.hpp b/single_include/entt/entt.hpp index d54519a2..3354ba40 100644 --- a/single_include/entt/entt.hpp +++ b/single_include/entt/entt.hpp @@ -18,8 +18,8 @@ #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) \ @@ -75,6 +75,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 @@ -122,8 +124,8 @@ #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) \ @@ -164,8 +166,8 @@ #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) \ @@ -221,6 +223,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 @@ -265,6 +269,7 @@ #include #include +#include #include #include // #include "../config/config.h" @@ -286,8 +291,8 @@ #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) \ @@ -343,6 +348,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 @@ -444,7 +451,6 @@ using type_identity_t = typename type_identity::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 struct size_of: std::integral_constant {}; @@ -686,7 +692,8 @@ struct type_list_contains; * @tparam Other Type to look for. */ template -struct type_list_contains, Other>: std::disjunction...> {}; +struct type_list_contains, Other> + : std::bool_constant<(std::is_same_v || ...)> {}; /** * @brief Helper variable template. @@ -774,10 +781,20 @@ struct value_list_element> */ template struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = typename value_list_element::type; + /** * @brief Helper type. * @tparam Index Index of the value to return. @@ -786,6 +803,58 @@ struct value_list_element<0u, value_list> { template inline constexpr auto value_list_element_v = value_list_element::value; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +struct value_list_index> { + static_assert(value_list_index>::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 +struct value_list_index> { + /*! @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 +inline constexpr std::size_t value_list_index_v = value_list_index::value; + /** * @brief Concatenates multiple value lists. * @tparam Value Values provided by the first value list. @@ -837,6 +906,89 @@ struct value_list_cat> { template using value_list_cat_t = typename value_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = std::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = typename value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : std::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +class value_list_diff, value_list> { + using v141_toolset_workaround = value_list; + +public: + /*! @brief A value list that is the difference between the two value lists. */ + using type = value_list_cat_t, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = typename value_list_diff::type; + /*! @brief Same as std::is_invocable, but with tuples. */ template struct is_applicable: std::false_type {}; @@ -957,7 +1109,7 @@ inline constexpr bool is_iterator_v = is_iterator::value; */ template struct is_ebco_eligible - : std::conjunction, std::negation>> {}; + : std::bool_constant && !std::is_final_v> {}; /** * @brief Helper variable template. @@ -1048,6 +1200,10 @@ template struct is_equality_comparable() == std::declval())>> : std::bool_constant(choice<2>)> {}; +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: std::false_type {}; + /** * @brief Helper variable template. * @tparam Type The type to test. @@ -1144,6 +1300,18 @@ using nth_argument_t = typename nth_argument::type; } // namespace entt +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::type_list_element> {}; + +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::value_list_element> {}; + #endif @@ -1727,7 +1895,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 struct allocation_deleter: private Allocator { @@ -1748,7 +1916,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) { - using alloc_traits = typename std::allocator_traits; + using alloc_traits = std::allocator_traits; alloc_traits::destroy(*this, to_address(ptr)); alloc_traits::deallocate(*this, ptr, 1u); } @@ -1915,6 +2083,7 @@ constexpr Type *uninitialized_construct_using_allocator(Type *value, const Alloc #include #include +#include #include #include // #include "../config/config.h" @@ -1969,7 +2138,6 @@ using type_identity_t = typename type_identity::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 struct size_of: std::integral_constant {}; @@ -2211,7 +2379,8 @@ struct type_list_contains; * @tparam Other Type to look for. */ template -struct type_list_contains, Other>: std::disjunction...> {}; +struct type_list_contains, Other> + : std::bool_constant<(std::is_same_v || ...)> {}; /** * @brief Helper variable template. @@ -2299,10 +2468,20 @@ struct value_list_element> */ template struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = typename value_list_element::type; + /** * @brief Helper type. * @tparam Index Index of the value to return. @@ -2311,6 +2490,58 @@ struct value_list_element<0u, value_list> { template inline constexpr auto value_list_element_v = value_list_element::value; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +struct value_list_index> { + static_assert(value_list_index>::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 +struct value_list_index> { + /*! @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 +inline constexpr std::size_t value_list_index_v = value_list_index::value; + /** * @brief Concatenates multiple value lists. * @tparam Value Values provided by the first value list. @@ -2362,6 +2593,89 @@ struct value_list_cat> { template using value_list_cat_t = typename value_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = std::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = typename value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : std::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +class value_list_diff, value_list> { + using v141_toolset_workaround = value_list; + +public: + /*! @brief A value list that is the difference between the two value lists. */ + using type = value_list_cat_t, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = typename value_list_diff::type; + /*! @brief Same as std::is_invocable, but with tuples. */ template struct is_applicable: std::false_type {}; @@ -2482,7 +2796,7 @@ inline constexpr bool is_iterator_v = is_iterator::value; */ template struct is_ebco_eligible - : std::conjunction, std::negation>> {}; + : std::bool_constant && !std::is_final_v> {}; /** * @brief Helper variable template. @@ -2573,6 +2887,10 @@ template struct is_equality_comparable() == std::declval())>> : std::bool_constant(choice<2>)> {}; +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: std::false_type {}; + /** * @brief Helper variable template. * @tparam Type The type to test. @@ -2669,6 +2987,18 @@ using nth_argument_t = typename nth_argument::type; } // namespace entt +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::type_list_element> {}; + +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::value_list_element> {}; + #endif // #include "fwd.hpp" @@ -2810,51 +3140,51 @@ public: return {it->element.first, it->element.second}; } - template - friend constexpr std::ptrdiff_t operator-(const dense_map_iterator &, const dense_map_iterator &) noexcept; + template + friend constexpr std::ptrdiff_t operator-(const dense_map_iterator &, const dense_map_iterator &) noexcept; - template - friend constexpr bool operator==(const dense_map_iterator &, const dense_map_iterator &) noexcept; + template + friend constexpr bool operator==(const dense_map_iterator &, const dense_map_iterator &) noexcept; - template - friend constexpr bool operator<(const dense_map_iterator &, const dense_map_iterator &) noexcept; + template + friend constexpr bool operator<(const dense_map_iterator &, const dense_map_iterator &) noexcept; private: It it; }; -template -[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return lhs.it - rhs.it; } -template -[[nodiscard]] constexpr bool operator==(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return lhs.it == rhs.it; } -template -[[nodiscard]] constexpr bool operator!=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return !(lhs == rhs); } -template -[[nodiscard]] constexpr bool operator<(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return lhs.it < rhs.it; } -template -[[nodiscard]] constexpr bool operator>(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return rhs < lhs; } -template -[[nodiscard]] constexpr bool operator<=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return !(lhs > rhs); } -template -[[nodiscard]] constexpr bool operator>=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return !(lhs < rhs); } @@ -2912,13 +3242,13 @@ private: std::size_t offset; }; -template -[[nodiscard]] constexpr bool operator==(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { return lhs.index() == rhs.index(); } -template -[[nodiscard]] constexpr bool operator!=(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { return !(lhs == rhs); } @@ -2948,7 +3278,7 @@ class dense_map { static constexpr std::size_t minimum_capacity = 8u; using node_type = internal::dense_map_node; - using alloc_traits = typename std::allocator_traits; + using alloc_traits = std::allocator_traits; static_assert(std::is_same_v>, "Invalid value type"); using sparse_container_type = std::vector>; using packed_container_type = std::vector>; @@ -3146,7 +3476,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. @@ -3167,11 +3496,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. */ @@ -3520,7 +3844,7 @@ public: } /*! @copydoc equal_range */ - template + template [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t>> equal_range(const Other &key) const { const auto it = find(key); @@ -3845,51 +4169,51 @@ public: return *operator->(); } - template - friend constexpr std::ptrdiff_t operator-(const dense_set_iterator &, const dense_set_iterator &) noexcept; + template + friend constexpr std::ptrdiff_t operator-(const dense_set_iterator &, const dense_set_iterator &) noexcept; - template - friend constexpr bool operator==(const dense_set_iterator &, const dense_set_iterator &) noexcept; + template + friend constexpr bool operator==(const dense_set_iterator &, const dense_set_iterator &) noexcept; - template - friend constexpr bool operator<(const dense_set_iterator &, const dense_set_iterator &) noexcept; + template + friend constexpr bool operator<(const dense_set_iterator &, const dense_set_iterator &) noexcept; private: It it; }; -template -[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return lhs.it - rhs.it; } -template -[[nodiscard]] constexpr bool operator==(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return lhs.it == rhs.it; } -template -[[nodiscard]] constexpr bool operator!=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return !(lhs == rhs); } -template -[[nodiscard]] constexpr bool operator<(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return lhs.it < rhs.it; } -template -[[nodiscard]] constexpr bool operator>(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return rhs < lhs; } -template -[[nodiscard]] constexpr bool operator<=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return !(lhs > rhs); } -template -[[nodiscard]] constexpr bool operator>=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return !(lhs < rhs); } @@ -3944,13 +4268,13 @@ private: std::size_t offset; }; -template -[[nodiscard]] constexpr bool operator==(const dense_set_local_iterator &lhs, const dense_set_local_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const dense_set_local_iterator &lhs, const dense_set_local_iterator &rhs) noexcept { return lhs.index() == rhs.index(); } -template -[[nodiscard]] constexpr bool operator!=(const dense_set_local_iterator &lhs, const dense_set_local_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const dense_set_local_iterator &lhs, const dense_set_local_iterator &rhs) noexcept { return !(lhs == rhs); } @@ -4159,7 +4483,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. @@ -4180,11 +4503,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. */ @@ -4440,7 +4758,7 @@ public: } /*! @copydoc equal_range */ - template + template [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t>> equal_range(const Other &value) const { const auto it = find(value); @@ -4672,7 +4990,7 @@ struct identity { * @param value The actual argument. * @return The submitted value as-is. */ - template + template [[nodiscard]] constexpr Type &&operator()(Type &&value) const noexcept { return std::forward(value); } @@ -4705,7 +5023,7 @@ template * @brief Helper type for visitors. * @tparam Func Types of function objects. */ -template +template struct overloaded: Func... { using Func::operator()...; }; @@ -4714,14 +5032,14 @@ struct overloaded: Func... { * @brief Deduction guide. * @tparam Func Types of function objects. */ -template +template overloaded(Func...) -> overloaded; /** * @brief Basic implementation of a y-combinator. * @tparam Func Type of a potentially recursive function. */ -template +template struct y_combinator { /** * @brief Constructs a y-combinator from a given function. @@ -4736,13 +5054,13 @@ struct y_combinator { * @param args Parameters to use to invoke the underlying function. * @return Return value of the underlying function, if any. */ - template + template constexpr decltype(auto) operator()(Args &&...args) const noexcept(std::is_nothrow_invocable_v) { return func(*this, std::forward(args)...); } /*! @copydoc operator()() */ - template + template constexpr decltype(auto) operator()(Args &&...args) noexcept(std::is_nothrow_invocable_v) { return func(*this, std::forward(args)...); } @@ -4843,14 +5161,15 @@ struct radix_sort { template 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::value_type; std::vector 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]{}; @@ -4911,8 +5230,8 @@ struct radix_sort { #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) \ @@ -4968,6 +5287,8 @@ struct radix_sort { # 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 @@ -5018,7 +5339,7 @@ struct identity { * @param value The actual argument. * @return The submitted value as-is. */ - template + template [[nodiscard]] constexpr Type &&operator()(Type &&value) const noexcept { return std::forward(value); } @@ -5051,7 +5372,7 @@ template * @brief Helper type for visitors. * @tparam Func Types of function objects. */ -template +template struct overloaded: Func... { using Func::operator()...; }; @@ -5060,14 +5381,14 @@ struct overloaded: Func... { * @brief Deduction guide. * @tparam Func Types of function objects. */ -template +template overloaded(Func...) -> overloaded; /** * @brief Basic implementation of a y-combinator. * @tparam Func Type of a potentially recursive function. */ -template +template struct y_combinator { /** * @brief Constructs a y-combinator from a given function. @@ -5082,13 +5403,13 @@ struct y_combinator { * @param args Parameters to use to invoke the underlying function. * @return Return value of the underlying function, if any. */ - template + template constexpr decltype(auto) operator()(Args &&...args) const noexcept(std::is_nothrow_invocable_v) { return func(*this, std::forward(args)...); } /*! @copydoc operator()() */ - template + template constexpr decltype(auto) operator()(Args &&...args) noexcept(std::is_nothrow_invocable_v) { return func(*this, std::forward(args)...); } @@ -5238,7 +5559,7 @@ struct basic_hashed_string { template class basic_hashed_string: internal::basic_hashed_string { using base_type = internal::basic_hashed_string; - using hs_traits = internal::fnv1a_traits; + using traits_type = internal::fnv1a_traits; struct const_wrapper { // non-explicit constructor on purpose @@ -5250,10 +5571,10 @@ class basic_hashed_string: internal::basic_hashed_string { // 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(str[base.length])) * hs_traits::prime; + base.hash = (base.hash ^ static_cast(str[base.length])) * traits_type::prime; } return base; @@ -5261,10 +5582,10 @@ class basic_hashed_string: internal::basic_hashed_string { // 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(str[pos])) * hs_traits::prime; + base.hash = (base.hash ^ static_cast(str[pos])) * traits_type::prime; } return base; @@ -5775,6 +6096,7 @@ template #include #include +#include #include #include // #include "../config/config.h" @@ -5829,7 +6151,6 @@ using type_identity_t = typename type_identity::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 struct size_of: std::integral_constant {}; @@ -6071,7 +6392,8 @@ struct type_list_contains; * @tparam Other Type to look for. */ template -struct type_list_contains, Other>: std::disjunction...> {}; +struct type_list_contains, Other> + : std::bool_constant<(std::is_same_v || ...)> {}; /** * @brief Helper variable template. @@ -6159,10 +6481,20 @@ struct value_list_element> */ template struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = typename value_list_element::type; + /** * @brief Helper type. * @tparam Index Index of the value to return. @@ -6171,6 +6503,58 @@ struct value_list_element<0u, value_list> { template inline constexpr auto value_list_element_v = value_list_element::value; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +struct value_list_index> { + static_assert(value_list_index>::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 +struct value_list_index> { + /*! @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 +inline constexpr std::size_t value_list_index_v = value_list_index::value; + /** * @brief Concatenates multiple value lists. * @tparam Value Values provided by the first value list. @@ -6222,6 +6606,89 @@ struct value_list_cat> { template using value_list_cat_t = typename value_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = std::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = typename value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : std::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +class value_list_diff, value_list> { + using v141_toolset_workaround = value_list; + +public: + /*! @brief A value list that is the difference between the two value lists. */ + using type = value_list_cat_t, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = typename value_list_diff::type; + /*! @brief Same as std::is_invocable, but with tuples. */ template struct is_applicable: std::false_type {}; @@ -6342,7 +6809,7 @@ inline constexpr bool is_iterator_v = is_iterator::value; */ template struct is_ebco_eligible - : std::conjunction, std::negation>> {}; + : std::bool_constant && !std::is_final_v> {}; /** * @brief Helper variable template. @@ -6433,6 +6900,10 @@ template struct is_equality_comparable() == std::declval())>> : std::bool_constant(choice<2>)> {}; +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: std::false_type {}; + /** * @brief Helper variable template. * @tparam Type The type to test. @@ -6529,6 +7000,18 @@ using nth_argument_t = typename nth_argument::type; } // namespace entt +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::type_list_element> {}; + +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::value_list_element> {}; + #endif @@ -6580,7 +7063,7 @@ class basic_any { }; template - static constexpr bool in_situ = Len && alignof(Type) <= Align && sizeof(Type) <= Len &&std::is_nothrow_move_constructible_v; + static constexpr bool in_situ = Len && alignof(Type) <= Align && sizeof(Type) <= Len && std::is_nothrow_move_constructible_v; template static const void *basic_vtable(const operation op, const basic_any &value, const void *other) { @@ -6649,17 +7132,17 @@ class basic_any { vtable = basic_vtable>>; if constexpr(std::is_lvalue_reference_v) { - static_assert(sizeof...(Args) == 1u && (std::is_lvalue_reference_v && ...), "Invalid arguments"); + static_assert((std::is_lvalue_reference_v && ...) && (sizeof...(Args) == 1u), "Invalid arguments"); mode = std::is_const_v> ? policy::cref : policy::ref; instance = (std::addressof(args), ...); } else if constexpr(in_situ>>) { - if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v>>) { + if constexpr(std::is_aggregate_v>> && (sizeof...(Args) != 0u || !std::is_default_constructible_v>>)) { new(&storage) std::remove_cv_t>{std::forward(args)...}; } else { new(&storage) std::remove_cv_t>(std::forward(args)...); } } else { - if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v>>) { + if constexpr(std::is_aggregate_v>> && (sizeof...(Args) != 0u || !std::is_default_constructible_v>>)) { instance = new std::remove_cv_t>{std::forward(args)...}; } else { instance = new std::remove_cv_t>(std::forward(args)...); @@ -6949,7 +7432,7 @@ private: * @return The element converted to the requested type. */ template -Type any_cast(const basic_any &data) noexcept { +[[nodiscard]] Type any_cast(const basic_any &data) noexcept { const auto *const instance = any_cast>(&data); ENTT_ASSERT(instance, "Invalid instance"); return static_cast(*instance); @@ -6957,7 +7440,7 @@ Type any_cast(const basic_any &data) noexcept { /*! @copydoc any_cast */ template -Type any_cast(basic_any &data) noexcept { +[[nodiscard]] Type any_cast(basic_any &data) noexcept { // forces const on non-reference types to make them work also with wrappers for const references auto *const instance = any_cast>(&data); ENTT_ASSERT(instance, "Invalid instance"); @@ -6966,7 +7449,7 @@ Type any_cast(basic_any &data) noexcept { /*! @copydoc any_cast */ template -Type any_cast(basic_any &&data) noexcept { +[[nodiscard]] Type any_cast(basic_any &&data) noexcept { if constexpr(std::is_copy_constructible_v>>) { if(auto *const instance = any_cast>(&data); instance) { return static_cast(std::move(*instance)); @@ -6982,14 +7465,14 @@ Type any_cast(basic_any &&data) noexcept { /*! @copydoc any_cast */ template -const Type *any_cast(const basic_any *data) noexcept { +[[nodiscard]] const Type *any_cast(const basic_any *data) noexcept { const auto &info = type_id>(); return static_cast(data->data(info)); } /*! @copydoc any_cast */ template -Type *any_cast(basic_any *data) noexcept { +[[nodiscard]] Type *any_cast(basic_any *data) noexcept { if constexpr(std::is_const_v) { // last attempt to make wrappers for const references return their values return any_cast(&std::as_const(*data)); @@ -7009,7 +7492,7 @@ Type *any_cast(basic_any *data) noexcept { * @return A properly initialized wrapper for an object of the given type. */ template::length, std::size_t Align = basic_any::alignment, typename... Args> -basic_any make_any(Args &&...args) { +[[nodiscard]] basic_any make_any(Args &&...args) { return basic_any{std::in_place_type, std::forward(args)...}; } @@ -7022,7 +7505,7 @@ basic_any make_any(Args &&...args) { * @return A properly initialized and not necessarily owning wrapper. */ template::length, std::size_t Align = basic_any::alignment, typename Type> -basic_any forward_as_any(Type &&value) { +[[nodiscard]] basic_any forward_as_any(Type &&value) { return basic_any{std::in_place_type, std::forward(value)}; } @@ -7550,7 +8033,7 @@ struct basic_hashed_string { template class basic_hashed_string: internal::basic_hashed_string { using base_type = internal::basic_hashed_string; - using hs_traits = internal::fnv1a_traits; + using traits_type = internal::fnv1a_traits; struct const_wrapper { // non-explicit constructor on purpose @@ -7562,10 +8045,10 @@ class basic_hashed_string: internal::basic_hashed_string { // 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(str[base.length])) * hs_traits::prime; + base.hash = (base.hash ^ static_cast(str[base.length])) * traits_type::prime; } return base; @@ -7573,10 +8056,10 @@ class basic_hashed_string: internal::basic_hashed_string { // 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(str[pos])) * hs_traits::prime; + base.hash = (base.hash ^ static_cast(str[pos])) * traits_type::prime; } return base; @@ -8164,7 +8647,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 struct allocation_deleter: private Allocator { @@ -8185,7 +8668,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) { - using alloc_traits = typename std::allocator_traits; + using alloc_traits = std::allocator_traits; alloc_traits::destroy(*this, to_address(ptr)); alloc_traits::deallocate(*this, ptr, 1u); } @@ -8478,7 +8961,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 + template constexpr forward_apply(Args &&...args) noexcept(std::is_nothrow_constructible_v) : Func{std::forward(args)...} {} @@ -8488,13 +8971,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 + template constexpr decltype(auto) operator()(Type &&args) noexcept(noexcept(std::apply(std::declval(), args))) { return std::apply(static_cast(*this), std::forward(args)); } /*! @copydoc operator()() */ - template + template constexpr decltype(auto) operator()(Type &&args) const noexcept(noexcept(std::apply(std::declval(), args))) { return std::apply(static_cast(*this), std::forward(args)); } @@ -8797,6 +9280,7 @@ template #include #include +#include #include #include // #include "../config/config.h" @@ -8851,7 +9335,6 @@ using type_identity_t = typename type_identity::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 struct size_of: std::integral_constant {}; @@ -9093,7 +9576,8 @@ struct type_list_contains; * @tparam Other Type to look for. */ template -struct type_list_contains, Other>: std::disjunction...> {}; +struct type_list_contains, Other> + : std::bool_constant<(std::is_same_v || ...)> {}; /** * @brief Helper variable template. @@ -9181,10 +9665,20 @@ struct value_list_element> */ template struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = typename value_list_element::type; + /** * @brief Helper type. * @tparam Index Index of the value to return. @@ -9193,6 +9687,58 @@ struct value_list_element<0u, value_list> { template inline constexpr auto value_list_element_v = value_list_element::value; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +struct value_list_index> { + static_assert(value_list_index>::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 +struct value_list_index> { + /*! @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 +inline constexpr std::size_t value_list_index_v = value_list_index::value; + /** * @brief Concatenates multiple value lists. * @tparam Value Values provided by the first value list. @@ -9244,6 +9790,89 @@ struct value_list_cat> { template using value_list_cat_t = typename value_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = std::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = typename value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : std::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +class value_list_diff, value_list> { + using v141_toolset_workaround = value_list; + +public: + /*! @brief A value list that is the difference between the two value lists. */ + using type = value_list_cat_t, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = typename value_list_diff::type; + /*! @brief Same as std::is_invocable, but with tuples. */ template struct is_applicable: std::false_type {}; @@ -9364,7 +9993,7 @@ inline constexpr bool is_iterator_v = is_iterator::value; */ template struct is_ebco_eligible - : std::conjunction, std::negation>> {}; + : std::bool_constant && !std::is_final_v> {}; /** * @brief Helper variable template. @@ -9455,6 +10084,10 @@ template struct is_equality_comparable() == std::declval())>> : std::bool_constant(choice<2>)> {}; +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: std::false_type {}; + /** * @brief Helper variable template. * @tparam Type The type to test. @@ -9551,6 +10184,18 @@ using nth_argument_t = typename nth_argument::type; } // namespace entt +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::type_list_element> {}; + +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::value_list_element> {}; + #endif // #include "core/utility.hpp" @@ -9573,7 +10218,7 @@ struct identity { * @param value The actual argument. * @return The submitted value as-is. */ - template + template [[nodiscard]] constexpr Type &&operator()(Type &&value) const noexcept { return std::forward(value); } @@ -9606,7 +10251,7 @@ template * @brief Helper type for visitors. * @tparam Func Types of function objects. */ -template +template struct overloaded: Func... { using Func::operator()...; }; @@ -9615,14 +10260,14 @@ struct overloaded: Func... { * @brief Deduction guide. * @tparam Func Types of function objects. */ -template +template overloaded(Func...) -> overloaded; /** * @brief Basic implementation of a y-combinator. * @tparam Func Type of a potentially recursive function. */ -template +template struct y_combinator { /** * @brief Constructs a y-combinator from a given function. @@ -9637,13 +10282,13 @@ struct y_combinator { * @param args Parameters to use to invoke the underlying function. * @return Return value of the underlying function, if any. */ - template + template constexpr decltype(auto) operator()(Args &&...args) const noexcept(std::is_nothrow_invocable_v) { return func(*this, std::forward(args)...); } /*! @copydoc operator()() */ - template + template constexpr decltype(auto) operator()(Args &&...args) noexcept(std::is_nothrow_invocable_v) { return func(*this, std::forward(args)...); } @@ -9681,8 +10326,8 @@ private: #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) \ @@ -9738,6 +10383,8 @@ private: # 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 @@ -9768,78 +10415,11 @@ private: #endif - -namespace entt { - -/** - * @cond TURN_OFF_DOXYGEN - * Internal details not to be documented. - */ - -namespace internal { - -template -struct in_place_delete: std::bool_constant && std::is_move_assignable_v)> {}; - -template -struct in_place_delete> - : std::true_type {}; - -template -struct page_size: std::integral_constant * ENTT_PACKED_PAGE> {}; - -template -struct page_size>> - : std::integral_constant {}; - -} // namespace internal - -/** - * Internal details not to be documented. - * @endcond - */ - -/** - * @brief Common way to access various properties of components. - * @tparam Type Type of component. - */ -template -struct component_traits { - static_assert(std::is_same_v, Type>, "Unsupported type"); - - /*! @brief Component type. */ - using type = Type; - - /*! @brief Pointer stability, default is `false`. */ - static constexpr bool in_place_delete = internal::in_place_delete::value; - /*! @brief Page size, default is `ENTT_PACKED_PAGE` for non-empty types. */ - static constexpr std::size_t page_size = internal::page_size::value; -}; - -/** - * @brief Helper variable template. - * @tparam Type Type of component. - */ -template -inline constexpr bool ignore_as_empty_v = (std::is_void_v || component_traits::page_size == 0u); - -} // namespace entt - -#endif - -// #include "entity/entity.hpp" -#ifndef ENTT_ENTITY_ENTITY_HPP -#define ENTT_ENTITY_ENTITY_HPP - -#include -#include -#include -// #include "../config/config.h" - // #include "fwd.hpp" #ifndef ENTT_ENTITY_FWD_HPP #define ENTT_ENTITY_FWD_HPP +#include #include #include // #include "../core/fwd.hpp" @@ -9866,8 +10446,8 @@ inline constexpr bool ignore_as_empty_v = (std::is_void_v || component_tra #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) \ @@ -9923,6 +10503,8 @@ inline constexpr bool ignore_as_empty_v = (std::is_void_v || component_tra # 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 @@ -9975,6 +10557,7 @@ using any = basic_any<>; #include #include +#include #include #include // #include "../config/config.h" @@ -10050,7 +10633,6 @@ using type_identity_t = typename type_identity::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 struct size_of: std::integral_constant {}; @@ -10292,7 +10874,8 @@ struct type_list_contains; * @tparam Other Type to look for. */ template -struct type_list_contains, Other>: std::disjunction...> {}; +struct type_list_contains, Other> + : std::bool_constant<(std::is_same_v || ...)> {}; /** * @brief Helper variable template. @@ -10380,10 +10963,20 @@ struct value_list_element> */ template struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = typename value_list_element::type; + /** * @brief Helper type. * @tparam Index Index of the value to return. @@ -10392,6 +10985,58 @@ struct value_list_element<0u, value_list> { template inline constexpr auto value_list_element_v = value_list_element::value; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +struct value_list_index> { + static_assert(value_list_index>::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 +struct value_list_index> { + /*! @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 +inline constexpr std::size_t value_list_index_v = value_list_index::value; + /** * @brief Concatenates multiple value lists. * @tparam Value Values provided by the first value list. @@ -10443,6 +11088,89 @@ struct value_list_cat> { template using value_list_cat_t = typename value_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = std::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = typename value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : std::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +class value_list_diff, value_list> { + using v141_toolset_workaround = value_list; + +public: + /*! @brief A value list that is the difference between the two value lists. */ + using type = value_list_cat_t, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = typename value_list_diff::type; + /*! @brief Same as std::is_invocable, but with tuples. */ template struct is_applicable: std::false_type {}; @@ -10563,7 +11291,7 @@ inline constexpr bool is_iterator_v = is_iterator::value; */ template struct is_ebco_eligible - : std::conjunction, std::negation>> {}; + : std::bool_constant && !std::is_final_v> {}; /** * @brief Helper variable template. @@ -10654,6 +11382,10 @@ template struct is_equality_comparable() == std::declval())>> : std::bool_constant(choice<2>)> {}; +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: std::false_type {}; + /** * @brief Helper variable template. * @tparam Type The type to test. @@ -10750,6 +11482,18 @@ using nth_argument_t = typename nth_argument::type; } // namespace entt +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::type_list_element> {}; + +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::value_list_element> {}; + #endif @@ -10758,6 +11502,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> class basic_sparse_set; @@ -10765,18 +11517,18 @@ template, type class basic_storage; template -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 = void> struct storage_type { /*! @brief Type-to-storage conversion result. */ - using type = sigh_storage_mixin>; + using type = sigh_mixin>; }; /** @@ -10789,7 +11541,7 @@ using storage_type_t = typename storage_type::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>> @@ -10817,7 +11569,7 @@ class basic_runtime_view; template class basic_group; -template +template> class basic_observer; template @@ -10840,7 +11592,10 @@ class basic_continuous_loader; * @tparam Type List of types. */ template -using exclude_t = type_list; +struct exclude_t final: type_list { + /*! @brief Default constructor. */ + explicit constexpr exclude_t() {} +}; /** * @brief Variable template for exclusion lists. @@ -10854,7 +11609,10 @@ inline constexpr exclude_t exclude{}; * @tparam Type List of types. */ template -using get_t = type_list; +struct get_t final: type_list { + /*! @brief Default constructor. */ + explicit constexpr get_t() {} +}; /** * @brief Variable template for lists of observed components. @@ -10868,7 +11626,10 @@ inline constexpr get_t get{}; * @tparam Type List of types. */ template -using owned_t = type_list; +struct owned_t final: type_list { + /*! @brief Default constructor. */ + explicit constexpr owned_t() {} +}; /** * @brief Variable template for lists of owned components. @@ -10877,6 +11638,39 @@ using owned_t = type_list; template inline constexpr owned_t 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 class Op> +struct type_list_transform, Op> { + /*! @brief Resulting get list after applying the transform function. */ + using type = get_t::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 class Op> +struct type_list_transform, Op> { + /*! @brief Resulting exclude list after applying the transform function. */ + using type = exclude_t::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 class Op> +struct type_list_transform, Op> { + /*! @brief Resulting owned list after applying the transform function. */ + using type = owned_t::type...>; +}; + /*! @brief Alias declaration for the most common use case. */ using sparse_set = basic_sparse_set<>; @@ -10962,35 +11756,116 @@ namespace entt { namespace internal { +template +struct in_place_delete: std::bool_constant && std::is_move_assignable_v)> {}; + +template<> +struct in_place_delete: std::false_type {}; + +template +struct in_place_delete> + : std::true_type {}; + +template +struct page_size: std::integral_constant * ENTT_PACKED_PAGE> {}; + +template<> +struct page_size: std::integral_constant {}; + +template +struct page_size>> + : std::integral_constant {}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/** + * @brief Common way to access various properties of components. + * @tparam Type Type of component. + */ +template +struct component_traits { + static_assert(std::is_same_v, Type>, "Unsupported type"); + + /*! @brief Component type. */ + using type = Type; + + /*! @brief Pointer stability, default is `false`. */ + static constexpr bool in_place_delete = internal::in_place_delete::value; + /*! @brief Page size, default is `ENTT_PACKED_PAGE` for non-empty types. */ + static constexpr std::size_t page_size = internal::page_size::value; +}; + +} // namespace entt + +#endif + +// #include "entity/entity.hpp" +#ifndef ENTT_ENTITY_ENTITY_HPP +#define ENTT_ENTITY_ENTITY_HPP + +#include +#include +#include +// #include "../config/config.h" + +// #include "fwd.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +// waiting for C++20 (and std::popcount) +template +static constexpr int popcount(Type value) noexcept { + return value ? (int(value & 1) + popcount(value >> 1)) : 0; +} + template struct entt_traits; template struct entt_traits>> - : entt_traits> {}; + : entt_traits> { + using value_type = Type; +}; template struct entt_traits>> - : entt_traits {}; + : entt_traits { + using value_type = Type; +}; template<> struct entt_traits { + 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 { + 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 @@ -11001,24 +11876,28 @@ struct entt_traits { */ /** - * @brief Entity traits. - * @tparam Type Type of identifier. + * @brief Common basic entity traits implementation. + * @tparam Traits Actual entity traits to use. */ -template -class entt_traits: internal::entt_traits { - using base_type = internal::entt_traits; +template +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. @@ -11035,7 +11914,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); } /** @@ -11044,7 +11923,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(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(vers + (vers == version_mask))); } /** @@ -11058,7 +11947,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(version) << base_type::entity_shift)}; + return value_type{(entity & entity_mask) | (static_cast(version) << length)}; } /** @@ -11072,11 +11961,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 +struct entt_traits: basic_entt_traits> { + /*! @brief Base type. */ + using base_type = basic_entt_traits>; + /*! @brief Page size, default is `ENTT_SPARSE_PAGE`. */ + static constexpr std::size_t page_size = ENTT_SPARSE_PAGE; +}; + /** * @copydoc entt_traits::to_integral * @tparam Entity The value type. @@ -11113,8 +12014,9 @@ struct null_t { */ template [[nodiscard]] constexpr operator Entity() const noexcept { - using entity_traits = entt_traits; - return entity_traits::combine(entity_traits::reserved, entity_traits::reserved); + using traits_type = entt_traits; + constexpr auto value = traits_type::construct(traits_type::entity_mask, traits_type::version_mask); + return value; } /** @@ -11143,8 +12045,8 @@ struct null_t { */ template [[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept { - using entity_traits = entt_traits; - return entity_traits::to_entity(entity) == entity_traits::to_entity(*this); + using traits_type = entt_traits; + return traits_type::to_entity(entity) == traits_type::to_entity(*this); } /** @@ -11192,8 +12094,9 @@ struct tombstone_t { */ template [[nodiscard]] constexpr operator Entity() const noexcept { - using entity_traits = entt_traits; - return entity_traits::combine(entity_traits::reserved, entity_traits::reserved); + using traits_type = entt_traits; + constexpr auto value = traits_type::construct(traits_type::entity_mask, traits_type::version_mask); + return value; } /** @@ -11222,8 +12125,8 @@ struct tombstone_t { */ template [[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept { - using entity_traits = entt_traits; - return entity_traits::to_version(entity) == entity_traits::to_version(*this); + using traits_type = entt_traits; + return traits_type::to_version(entity) == traits_type::to_version(*this); } /** @@ -11293,6 +12196,8 @@ inline constexpr tombstone_t tombstone{}; #include // #include "../config/config.h" +// #include "../core/fwd.hpp" + // #include "../core/iterator.hpp" #ifndef ENTT_CORE_ITERATOR_HPP #define ENTT_CORE_ITERATOR_HPP @@ -11492,789 +12397,7 @@ private: #endif -// #include "../core/type_traits.hpp" - -// #include "component.hpp" -#ifndef ENTT_ENTITY_COMPONENT_HPP -#define ENTT_ENTITY_COMPONENT_HPP - -#include -#include -// #include "../config/config.h" - - -namespace entt { - -/** - * @cond TURN_OFF_DOXYGEN - * Internal details not to be documented. - */ - -namespace internal { - -template -struct in_place_delete: std::bool_constant && std::is_move_assignable_v)> {}; - -template -struct in_place_delete> - : std::true_type {}; - -template -struct page_size: std::integral_constant * ENTT_PACKED_PAGE> {}; - -template -struct page_size>> - : std::integral_constant {}; - -} // namespace internal - -/** - * Internal details not to be documented. - * @endcond - */ - -/** - * @brief Common way to access various properties of components. - * @tparam Type Type of component. - */ -template -struct component_traits { - static_assert(std::is_same_v, Type>, "Unsupported type"); - - /*! @brief Component type. */ - using type = Type; - - /*! @brief Pointer stability, default is `false`. */ - static constexpr bool in_place_delete = internal::in_place_delete::value; - /*! @brief Page size, default is `ENTT_PACKED_PAGE` for non-empty types. */ - static constexpr std::size_t page_size = internal::page_size::value; -}; - -/** - * @brief Helper variable template. - * @tparam Type Type of component. - */ -template -inline constexpr bool ignore_as_empty_v = (std::is_void_v || component_traits::page_size == 0u); - -} // namespace entt - -#endif - -// #include "entity.hpp" -#ifndef ENTT_ENTITY_ENTITY_HPP -#define ENTT_ENTITY_ENTITY_HPP - -#include -#include -#include -// #include "../config/config.h" - -// #include "fwd.hpp" - - -namespace entt { - -/** - * @cond TURN_OFF_DOXYGEN - * Internal details not to be documented. - */ - -namespace internal { - -template -struct entt_traits; - -template -struct entt_traits>> - : entt_traits> {}; - -template -struct entt_traits>> - : entt_traits {}; - -template<> -struct entt_traits { - 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 { - 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 - -/** - * Internal details not to be documented. - * @endcond - */ - -/** - * @brief Entity traits. - * @tparam Type Type of identifier. - */ -template -class entt_traits: internal::entt_traits { - using base_type = internal::entt_traits; - -public: - /*! @brief Value type. */ - using value_type = Type; - /*! @brief Underlying entity type. */ - using entity_type = typename base_type::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; - - /** - * @brief Converts an entity to its underlying type. - * @param value The value to convert. - * @return The integral representation of the given value. - */ - [[nodiscard]] static constexpr entity_type to_integral(const value_type value) noexcept { - return static_cast(value); - } - - /** - * @brief Returns the entity part once converted to the underlying type. - * @param value The value to convert. - * @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); - } - - /** - * @brief Returns the version part once converted to the underlying type. - * @param value The value to convert. - * @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); - } - - /** - * @brief Constructs an identifier from its parts. - * - * If the version part is not provided, a tombstone is returned.
- * If the entity part is not provided, a null identifier is returned. - * - * @param entity The entity part of the identifier. - * @param version The version part of the identifier. - * @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(version) << base_type::entity_shift)}; - } - - /** - * @brief Combines two identifiers in a single one. - * - * The returned identifier is a copy of the first element except for its - * version, which is taken from the second element. - * - * @param lhs The identifier from which to take the entity part. - * @param rhs The identifier from which to take the version part. - * @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)}; - } -}; - -/** - * @copydoc entt_traits::to_integral - * @tparam Entity The value type. - */ -template -[[nodiscard]] constexpr typename entt_traits::entity_type to_integral(const Entity value) noexcept { - return entt_traits::to_integral(value); -} - -/** - * @copydoc entt_traits::to_entity - * @tparam Entity The value type. - */ -template -[[nodiscard]] constexpr typename entt_traits::entity_type to_entity(const Entity value) noexcept { - return entt_traits::to_entity(value); -} - -/** - * @copydoc entt_traits::to_version - * @tparam Entity The value type. - */ -template -[[nodiscard]] constexpr typename entt_traits::version_type to_version(const Entity value) noexcept { - return entt_traits::to_version(value); -} - -/*! @brief Null object for all identifiers. */ -struct null_t { - /** - * @brief Converts the null object to identifiers of any type. - * @tparam Entity Type of identifier. - * @return The null representation for the given type. - */ - template - [[nodiscard]] constexpr operator Entity() const noexcept { - using entity_traits = entt_traits; - return entity_traits::combine(entity_traits::reserved, entity_traits::reserved); - } - - /** - * @brief Compares two null objects. - * @param other A null object. - * @return True in all cases. - */ - [[nodiscard]] constexpr bool operator==([[maybe_unused]] const null_t other) const noexcept { - return true; - } - - /** - * @brief Compares two null objects. - * @param other A null object. - * @return False in all cases. - */ - [[nodiscard]] constexpr bool operator!=([[maybe_unused]] const null_t other) const noexcept { - return false; - } - - /** - * @brief Compares a null object and an identifier of any type. - * @tparam Entity Type of identifier. - * @param entity Identifier with which to compare. - * @return False if the two elements differ, true otherwise. - */ - template - [[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept { - using entity_traits = entt_traits; - return entity_traits::to_entity(entity) == entity_traits::to_entity(*this); - } - - /** - * @brief Compares a null object and an identifier of any type. - * @tparam Entity Type of identifier. - * @param entity Identifier with which to compare. - * @return True if the two elements differ, false otherwise. - */ - template - [[nodiscard]] constexpr bool operator!=(const Entity entity) const noexcept { - return !(entity == *this); - } -}; - -/** - * @brief Compares a null object and an identifier of any type. - * @tparam Entity Type of identifier. - * @param entity Identifier with which to compare. - * @param other A null object yet to be converted. - * @return False if the two elements differ, true otherwise. - */ -template -[[nodiscard]] constexpr bool operator==(const Entity entity, const null_t other) noexcept { - return other.operator==(entity); -} - -/** - * @brief Compares a null object and an identifier of any type. - * @tparam Entity Type of identifier. - * @param entity Identifier with which to compare. - * @param other A null object yet to be converted. - * @return True if the two elements differ, false otherwise. - */ -template -[[nodiscard]] constexpr bool operator!=(const Entity entity, const null_t other) noexcept { - return !(other == entity); -} - -/*! @brief Tombstone object for all identifiers. */ -struct tombstone_t { - /** - * @brief Converts the tombstone object to identifiers of any type. - * @tparam Entity Type of identifier. - * @return The tombstone representation for the given type. - */ - template - [[nodiscard]] constexpr operator Entity() const noexcept { - using entity_traits = entt_traits; - return entity_traits::combine(entity_traits::reserved, entity_traits::reserved); - } - - /** - * @brief Compares two tombstone objects. - * @param other A tombstone object. - * @return True in all cases. - */ - [[nodiscard]] constexpr bool operator==([[maybe_unused]] const tombstone_t other) const noexcept { - return true; - } - - /** - * @brief Compares two tombstone objects. - * @param other A tombstone object. - * @return False in all cases. - */ - [[nodiscard]] constexpr bool operator!=([[maybe_unused]] const tombstone_t other) const noexcept { - return false; - } - - /** - * @brief Compares a tombstone object and an identifier of any type. - * @tparam Entity Type of identifier. - * @param entity Identifier with which to compare. - * @return False if the two elements differ, true otherwise. - */ - template - [[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept { - using entity_traits = entt_traits; - return entity_traits::to_version(entity) == entity_traits::to_version(*this); - } - - /** - * @brief Compares a tombstone object and an identifier of any type. - * @tparam Entity Type of identifier. - * @param entity Identifier with which to compare. - * @return True if the two elements differ, false otherwise. - */ - template - [[nodiscard]] constexpr bool operator!=(const Entity entity) const noexcept { - return !(entity == *this); - } -}; - -/** - * @brief Compares a tombstone object and an identifier of any type. - * @tparam Entity Type of identifier. - * @param entity Identifier with which to compare. - * @param other A tombstone object yet to be converted. - * @return False if the two elements differ, true otherwise. - */ -template -[[nodiscard]] constexpr bool operator==(const Entity entity, const tombstone_t other) noexcept { - return other.operator==(entity); -} - -/** - * @brief Compares a tombstone object and an identifier of any type. - * @tparam Entity Type of identifier. - * @param entity Identifier with which to compare. - * @param other A tombstone object yet to be converted. - * @return True if the two elements differ, false otherwise. - */ -template -[[nodiscard]] constexpr bool operator!=(const Entity entity, const tombstone_t other) noexcept { - return !(other == entity); -} - -/** - * @brief Compile-time constant for null entities. - * - * There exist implicit conversions from this variable to identifiers of any - * allowed type. Similarly, there exist comparison operators between the null - * entity and any other identifier. - */ -inline constexpr null_t null{}; - -/** - * @brief Compile-time constant for tombstone entities. - * - * There exist implicit conversions from this variable to identifiers of any - * allowed type. Similarly, there exist comparison operators between the - * tombstone entity and any other identifier. - */ -inline constexpr tombstone_t tombstone{}; - -} // namespace entt - -#endif - -// #include "fwd.hpp" - -// #include "sparse_set.hpp" -#ifndef ENTT_ENTITY_SPARSE_SET_HPP -#define ENTT_ENTITY_SPARSE_SET_HPP - -#include -#include -#include -#include -#include -#include -// #include "../config/config.h" - -// #include "../core/algorithm.hpp" -#ifndef ENTT_CORE_ALGORITHM_HPP -#define ENTT_CORE_ALGORITHM_HPP - -#include -#include -#include -#include -#include -// #include "utility.hpp" -#ifndef ENTT_CORE_UTILITY_HPP -#define ENTT_CORE_UTILITY_HPP - -#include -#include - -namespace entt { - -/*! @brief Identity function object (waiting for C++20). */ -struct identity { - /*! @brief Indicates that this is a transparent function object. */ - using is_transparent = void; - - /** - * @brief Returns its argument unchanged. - * @tparam Type Type of the argument. - * @param value The actual argument. - * @return The submitted value as-is. - */ - template - [[nodiscard]] constexpr Type &&operator()(Type &&value) const noexcept { - return std::forward(value); - } -}; - -/** - * @brief Constant utility to disambiguate overloaded members of a class. - * @tparam Type Type of the desired overload. - * @tparam Class Type of class to which the member belongs. - * @param member A valid pointer to a member. - * @return Pointer to the member. - */ -template -[[nodiscard]] constexpr auto overload(Type Class::*member) noexcept { - return member; -} - -/** - * @brief Constant utility to disambiguate overloaded functions. - * @tparam Func Function type of the desired overload. - * @param func A valid pointer to a function. - * @return Pointer to the function. - */ -template -[[nodiscard]] constexpr auto overload(Func *func) noexcept { - return func; -} - -/** - * @brief Helper type for visitors. - * @tparam Func Types of function objects. - */ -template -struct overloaded: Func... { - using Func::operator()...; -}; - -/** - * @brief Deduction guide. - * @tparam Func Types of function objects. - */ -template -overloaded(Func...) -> overloaded; - -/** - * @brief Basic implementation of a y-combinator. - * @tparam Func Type of a potentially recursive function. - */ -template -struct y_combinator { - /** - * @brief Constructs a y-combinator from a given function. - * @param recursive A potentially recursive function. - */ - constexpr y_combinator(Func recursive) noexcept(std::is_nothrow_move_constructible_v) - : func{std::move(recursive)} {} - - /** - * @brief Invokes a y-combinator and therefore its underlying function. - * @tparam Args Types of arguments to use to invoke the underlying function. - * @param args Parameters to use to invoke the underlying function. - * @return Return value of the underlying function, if any. - */ - template - constexpr decltype(auto) operator()(Args &&...args) const noexcept(std::is_nothrow_invocable_v) { - return func(*this, std::forward(args)...); - } - - /*! @copydoc operator()() */ - template - constexpr decltype(auto) operator()(Args &&...args) noexcept(std::is_nothrow_invocable_v) { - return func(*this, std::forward(args)...); - } - -private: - Func func; -}; - -} // namespace entt - -#endif - - -namespace entt { - -/** - * @brief Function object to wrap `std::sort` in a class type. - * - * Unfortunately, `std::sort` cannot be passed as template argument to a class - * template or a function template.
- * This class fills the gap by wrapping some flavors of `std::sort` in a - * function object. - */ -struct std_sort { - /** - * @brief Sorts the elements in a range. - * - * Sorts the elements in a range using the given binary comparison function. - * - * @tparam It Type of random access iterator. - * @tparam Compare Type of comparison function object. - * @tparam Args Types of arguments to forward to the sort function. - * @param first An iterator to the first element of the range to sort. - * @param last An iterator past the last element of the range to sort. - * @param compare A valid comparison function object. - * @param args Arguments to forward to the sort function, if any. - */ - template, typename... Args> - void operator()(It first, It last, Compare compare = Compare{}, Args &&...args) const { - std::sort(std::forward(args)..., std::move(first), std::move(last), std::move(compare)); - } -}; - -/*! @brief Function object for performing insertion sort. */ -struct insertion_sort { - /** - * @brief Sorts the elements in a range. - * - * Sorts the elements in a range using the given binary comparison function. - * - * @tparam It Type of random access iterator. - * @tparam Compare Type of comparison function object. - * @param first An iterator to the first element of the range to sort. - * @param last An iterator past the last element of the range to sort. - * @param compare A valid comparison function object. - */ - template> - void operator()(It first, It last, Compare compare = Compare{}) const { - if(first < last) { - for(auto it = first + 1; it < last; ++it) { - auto value = std::move(*it); - auto pre = it; - - for(; pre > first && compare(value, *(pre - 1)); --pre) { - *pre = std::move(*(pre - 1)); - } - - *pre = std::move(value); - } - } - } -}; - -/** - * @brief Function object for performing LSD radix sort. - * @tparam Bit Number of bits processed per pass. - * @tparam N Maximum number of bits to sort. - */ -template -struct radix_sort { - static_assert((N % Bit) == 0, "The maximum number of bits to sort must be a multiple of the number of bits processed per pass"); - - /** - * @brief Sorts the elements in a range. - * - * Sorts the elements in a range using the given _getter_ to access the - * actual data to be sorted. - * - * This implementation is inspired by the online book - * [Physically Based Rendering](http://www.pbr-book.org/3ed-2018/Primitives_and_Intersection_Acceleration/Bounding_Volume_Hierarchies.html#RadixSort). - * - * @tparam It Type of random access iterator. - * @tparam Getter Type of _getter_ function object. - * @param first An iterator to the first element of the range to sort. - * @param last An iterator past the last element of the range to sort. - * @param getter A valid _getter_ function object. - */ - template - 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; - - using value_type = typename std::iterator_traits::value_type; - std::vector aux(std::distance(first, last)); - - auto part = [getter = std::move(getter)](auto from, auto to, auto out, auto start) { - std::size_t index[buckets]{}; - std::size_t count[buckets]{}; - - for(auto it = from; it != to; ++it) { - ++count[(getter(*it) >> start) & mask]; - } - - for(std::size_t pos{}, end = buckets - 1u; pos < end; ++pos) { - index[pos + 1u] = index[pos] + count[pos]; - } - - for(auto it = from; it != to; ++it) { - out[index[(getter(*it) >> start) & mask]++] = std::move(*it); - } - }; - - for(std::size_t pass = 0; pass < (passes & ~1); pass += 2) { - part(first, last, aux.begin(), pass * Bit); - part(aux.begin(), aux.end(), first, (pass + 1) * Bit); - } - - if constexpr(passes & 1) { - part(first, last, aux.begin(), (passes - 1) * Bit); - std::move(aux.begin(), aux.end(), first); - } - } - } -}; - -} // namespace entt - -#endif - -// #include "../core/any.hpp" -#ifndef ENTT_CORE_ANY_HPP -#define ENTT_CORE_ANY_HPP - -#include -#include -#include -#include -// #include "../config/config.h" - -// #include "../core/utility.hpp" -#ifndef ENTT_CORE_UTILITY_HPP -#define ENTT_CORE_UTILITY_HPP - -#include -#include - -namespace entt { - -/*! @brief Identity function object (waiting for C++20). */ -struct identity { - /*! @brief Indicates that this is a transparent function object. */ - using is_transparent = void; - - /** - * @brief Returns its argument unchanged. - * @tparam Type Type of the argument. - * @param value The actual argument. - * @return The submitted value as-is. - */ - template - [[nodiscard]] constexpr Type &&operator()(Type &&value) const noexcept { - return std::forward(value); - } -}; - -/** - * @brief Constant utility to disambiguate overloaded members of a class. - * @tparam Type Type of the desired overload. - * @tparam Class Type of class to which the member belongs. - * @param member A valid pointer to a member. - * @return Pointer to the member. - */ -template -[[nodiscard]] constexpr auto overload(Type Class::*member) noexcept { - return member; -} - -/** - * @brief Constant utility to disambiguate overloaded functions. - * @tparam Func Function type of the desired overload. - * @param func A valid pointer to a function. - * @return Pointer to the function. - */ -template -[[nodiscard]] constexpr auto overload(Func *func) noexcept { - return func; -} - -/** - * @brief Helper type for visitors. - * @tparam Func Types of function objects. - */ -template -struct overloaded: Func... { - using Func::operator()...; -}; - -/** - * @brief Deduction guide. - * @tparam Func Types of function objects. - */ -template -overloaded(Func...) -> overloaded; - -/** - * @brief Basic implementation of a y-combinator. - * @tparam Func Type of a potentially recursive function. - */ -template -struct y_combinator { - /** - * @brief Constructs a y-combinator from a given function. - * @param recursive A potentially recursive function. - */ - constexpr y_combinator(Func recursive) noexcept(std::is_nothrow_move_constructible_v) - : func{std::move(recursive)} {} - - /** - * @brief Invokes a y-combinator and therefore its underlying function. - * @tparam Args Types of arguments to use to invoke the underlying function. - * @param args Parameters to use to invoke the underlying function. - * @return Return value of the underlying function, if any. - */ - template - constexpr decltype(auto) operator()(Args &&...args) const noexcept(std::is_nothrow_invocable_v) { - return func(*this, std::forward(args)...); - } - - /*! @copydoc operator()() */ - template - constexpr decltype(auto) operator()(Args &&...args) noexcept(std::is_nothrow_invocable_v) { - return func(*this, std::forward(args)...); - } - -private: - Func func; -}; - -} // namespace entt - -#endif - -// #include "fwd.hpp" - -// #include "type_info.hpp" +// #include "../core/type_info.hpp" #ifndef ENTT_CORE_TYPE_INFO_HPP #define ENTT_CORE_TYPE_INFO_HPP @@ -12388,7 +12511,7 @@ struct basic_hashed_string { template class basic_hashed_string: internal::basic_hashed_string { using base_type = internal::basic_hashed_string; - using hs_traits = internal::fnv1a_traits; + using traits_type = internal::fnv1a_traits; struct const_wrapper { // non-explicit constructor on purpose @@ -12400,10 +12523,10 @@ class basic_hashed_string: internal::basic_hashed_string { // 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(str[base.length])) * hs_traits::prime; + base.hash = (base.hash ^ static_cast(str[base.length])) * traits_type::prime; } return base; @@ -12411,10 +12534,10 @@ class basic_hashed_string: internal::basic_hashed_string { // 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(str[pos])) * hs_traits::prime; + base.hash = (base.hash ^ static_cast(str[pos])) * traits_type::prime; } return base; @@ -12919,12 +13042,1049 @@ template #endif +// #include "../core/type_traits.hpp" + +// #include "entity.hpp" +#ifndef ENTT_ENTITY_ENTITY_HPP +#define ENTT_ENTITY_ENTITY_HPP + +#include +#include +#include +// #include "../config/config.h" + +// #include "fwd.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +// waiting for C++20 (and std::popcount) +template +static constexpr int popcount(Type value) noexcept { + return value ? (int(value & 1) + popcount(value >> 1)) : 0; +} + +template +struct entt_traits; + +template +struct entt_traits>> + : entt_traits> { + using value_type = Type; +}; + +template +struct entt_traits>> + : entt_traits { + using value_type = Type; +}; + +template<> +struct entt_traits { + 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; +}; + +template<> +struct entt_traits { + 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; +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/** + * @brief Common basic entity traits implementation. + * @tparam Traits Actual entity traits to use. + */ +template +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 = typename Traits::value_type; + /*! @brief Underlying entity type. */ + using entity_type = typename Traits::entity_type; + /*! @brief Underlying version type. */ + 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. + * @param value The value to convert. + * @return The integral representation of the given value. + */ + [[nodiscard]] static constexpr entity_type to_integral(const value_type value) noexcept { + return static_cast(value); + } + + /** + * @brief Returns the entity part once converted to the underlying type. + * @param value The value to convert. + * @return The integral representation of the entity part. + */ + [[nodiscard]] static constexpr entity_type to_entity(const value_type value) noexcept { + return (to_integral(value) & entity_mask); + } + + /** + * @brief Returns the version part once converted to the underlying type. + * @param value The value to convert. + * @return The integral representation of the version part. + */ + [[nodiscard]] static constexpr version_type to_version(const value_type value) noexcept { + return static_cast(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(vers + (vers == version_mask))); + } + + /** + * @brief Constructs an identifier from its parts. + * + * If the version part is not provided, a tombstone is returned.
+ * If the entity part is not provided, a null identifier is returned. + * + * @param entity The entity part of the identifier. + * @param version The version part of the identifier. + * @return A properly constructed identifier. + */ + [[nodiscard]] static constexpr value_type construct(const entity_type entity, const version_type version) noexcept { + return value_type{(entity & entity_mask) | (static_cast(version) << length)}; + } + + /** + * @brief Combines two identifiers in a single one. + * + * The returned identifier is a copy of the first element except for its + * version, which is taken from the second element. + * + * @param lhs The identifier from which to take the entity part. + * @param rhs The identifier from which to take the version part. + * @return A properly constructed identifier. + */ + [[nodiscard]] static constexpr value_type combine(const entity_type lhs, const entity_type rhs) noexcept { + constexpr auto mask = (version_mask << length); + return value_type{(lhs & entity_mask) | (rhs & mask)}; + } +}; + +/** + * @brief Entity traits. + * @tparam Type Type of identifier. + */ +template +struct entt_traits: basic_entt_traits> { + /*! @brief Base type. */ + using base_type = basic_entt_traits>; + /*! @brief Page size, default is `ENTT_SPARSE_PAGE`. */ + static constexpr std::size_t page_size = ENTT_SPARSE_PAGE; +}; + +/** + * @copydoc entt_traits::to_integral + * @tparam Entity The value type. + */ +template +[[nodiscard]] constexpr typename entt_traits::entity_type to_integral(const Entity value) noexcept { + return entt_traits::to_integral(value); +} + +/** + * @copydoc entt_traits::to_entity + * @tparam Entity The value type. + */ +template +[[nodiscard]] constexpr typename entt_traits::entity_type to_entity(const Entity value) noexcept { + return entt_traits::to_entity(value); +} + +/** + * @copydoc entt_traits::to_version + * @tparam Entity The value type. + */ +template +[[nodiscard]] constexpr typename entt_traits::version_type to_version(const Entity value) noexcept { + return entt_traits::to_version(value); +} + +/*! @brief Null object for all identifiers. */ +struct null_t { + /** + * @brief Converts the null object to identifiers of any type. + * @tparam Entity Type of identifier. + * @return The null representation for the given type. + */ + template + [[nodiscard]] constexpr operator Entity() const noexcept { + using traits_type = entt_traits; + constexpr auto value = traits_type::construct(traits_type::entity_mask, traits_type::version_mask); + return value; + } + + /** + * @brief Compares two null objects. + * @param other A null object. + * @return True in all cases. + */ + [[nodiscard]] constexpr bool operator==([[maybe_unused]] const null_t other) const noexcept { + return true; + } + + /** + * @brief Compares two null objects. + * @param other A null object. + * @return False in all cases. + */ + [[nodiscard]] constexpr bool operator!=([[maybe_unused]] const null_t other) const noexcept { + return false; + } + + /** + * @brief Compares a null object and an identifier of any type. + * @tparam Entity Type of identifier. + * @param entity Identifier with which to compare. + * @return False if the two elements differ, true otherwise. + */ + template + [[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept { + using traits_type = entt_traits; + return traits_type::to_entity(entity) == traits_type::to_entity(*this); + } + + /** + * @brief Compares a null object and an identifier of any type. + * @tparam Entity Type of identifier. + * @param entity Identifier with which to compare. + * @return True if the two elements differ, false otherwise. + */ + template + [[nodiscard]] constexpr bool operator!=(const Entity entity) const noexcept { + return !(entity == *this); + } +}; + +/** + * @brief Compares a null object and an identifier of any type. + * @tparam Entity Type of identifier. + * @param entity Identifier with which to compare. + * @param other A null object yet to be converted. + * @return False if the two elements differ, true otherwise. + */ +template +[[nodiscard]] constexpr bool operator==(const Entity entity, const null_t other) noexcept { + return other.operator==(entity); +} + +/** + * @brief Compares a null object and an identifier of any type. + * @tparam Entity Type of identifier. + * @param entity Identifier with which to compare. + * @param other A null object yet to be converted. + * @return True if the two elements differ, false otherwise. + */ +template +[[nodiscard]] constexpr bool operator!=(const Entity entity, const null_t other) noexcept { + return !(other == entity); +} + +/*! @brief Tombstone object for all identifiers. */ +struct tombstone_t { + /** + * @brief Converts the tombstone object to identifiers of any type. + * @tparam Entity Type of identifier. + * @return The tombstone representation for the given type. + */ + template + [[nodiscard]] constexpr operator Entity() const noexcept { + using traits_type = entt_traits; + constexpr auto value = traits_type::construct(traits_type::entity_mask, traits_type::version_mask); + return value; + } + + /** + * @brief Compares two tombstone objects. + * @param other A tombstone object. + * @return True in all cases. + */ + [[nodiscard]] constexpr bool operator==([[maybe_unused]] const tombstone_t other) const noexcept { + return true; + } + + /** + * @brief Compares two tombstone objects. + * @param other A tombstone object. + * @return False in all cases. + */ + [[nodiscard]] constexpr bool operator!=([[maybe_unused]] const tombstone_t other) const noexcept { + return false; + } + + /** + * @brief Compares a tombstone object and an identifier of any type. + * @tparam Entity Type of identifier. + * @param entity Identifier with which to compare. + * @return False if the two elements differ, true otherwise. + */ + template + [[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept { + using traits_type = entt_traits; + return traits_type::to_version(entity) == traits_type::to_version(*this); + } + + /** + * @brief Compares a tombstone object and an identifier of any type. + * @tparam Entity Type of identifier. + * @param entity Identifier with which to compare. + * @return True if the two elements differ, false otherwise. + */ + template + [[nodiscard]] constexpr bool operator!=(const Entity entity) const noexcept { + return !(entity == *this); + } +}; + +/** + * @brief Compares a tombstone object and an identifier of any type. + * @tparam Entity Type of identifier. + * @param entity Identifier with which to compare. + * @param other A tombstone object yet to be converted. + * @return False if the two elements differ, true otherwise. + */ +template +[[nodiscard]] constexpr bool operator==(const Entity entity, const tombstone_t other) noexcept { + return other.operator==(entity); +} + +/** + * @brief Compares a tombstone object and an identifier of any type. + * @tparam Entity Type of identifier. + * @param entity Identifier with which to compare. + * @param other A tombstone object yet to be converted. + * @return True if the two elements differ, false otherwise. + */ +template +[[nodiscard]] constexpr bool operator!=(const Entity entity, const tombstone_t other) noexcept { + return !(other == entity); +} + +/** + * @brief Compile-time constant for null entities. + * + * There exist implicit conversions from this variable to identifiers of any + * allowed type. Similarly, there exist comparison operators between the null + * entity and any other identifier. + */ +inline constexpr null_t null{}; + +/** + * @brief Compile-time constant for tombstone entities. + * + * There exist implicit conversions from this variable to identifiers of any + * allowed type. Similarly, there exist comparison operators between the + * tombstone entity and any other identifier. + */ +inline constexpr tombstone_t tombstone{}; + +} // namespace entt + +#endif + +// #include "fwd.hpp" + +// #include "sparse_set.hpp" +#ifndef ENTT_ENTITY_SPARSE_SET_HPP +#define ENTT_ENTITY_SPARSE_SET_HPP + +#include +#include +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/algorithm.hpp" +#ifndef ENTT_CORE_ALGORITHM_HPP +#define ENTT_CORE_ALGORITHM_HPP + +#include +#include +#include +#include +#include +// #include "utility.hpp" +#ifndef ENTT_CORE_UTILITY_HPP +#define ENTT_CORE_UTILITY_HPP + +#include +#include + +namespace entt { + +/*! @brief Identity function object (waiting for C++20). */ +struct identity { + /*! @brief Indicates that this is a transparent function object. */ + using is_transparent = void; + + /** + * @brief Returns its argument unchanged. + * @tparam Type Type of the argument. + * @param value The actual argument. + * @return The submitted value as-is. + */ + template + [[nodiscard]] constexpr Type &&operator()(Type &&value) const noexcept { + return std::forward(value); + } +}; + +/** + * @brief Constant utility to disambiguate overloaded members of a class. + * @tparam Type Type of the desired overload. + * @tparam Class Type of class to which the member belongs. + * @param member A valid pointer to a member. + * @return Pointer to the member. + */ +template +[[nodiscard]] constexpr auto overload(Type Class::*member) noexcept { + return member; +} + +/** + * @brief Constant utility to disambiguate overloaded functions. + * @tparam Func Function type of the desired overload. + * @param func A valid pointer to a function. + * @return Pointer to the function. + */ +template +[[nodiscard]] constexpr auto overload(Func *func) noexcept { + return func; +} + +/** + * @brief Helper type for visitors. + * @tparam Func Types of function objects. + */ +template +struct overloaded: Func... { + using Func::operator()...; +}; + +/** + * @brief Deduction guide. + * @tparam Func Types of function objects. + */ +template +overloaded(Func...) -> overloaded; + +/** + * @brief Basic implementation of a y-combinator. + * @tparam Func Type of a potentially recursive function. + */ +template +struct y_combinator { + /** + * @brief Constructs a y-combinator from a given function. + * @param recursive A potentially recursive function. + */ + constexpr y_combinator(Func recursive) noexcept(std::is_nothrow_move_constructible_v) + : func{std::move(recursive)} {} + + /** + * @brief Invokes a y-combinator and therefore its underlying function. + * @tparam Args Types of arguments to use to invoke the underlying function. + * @param args Parameters to use to invoke the underlying function. + * @return Return value of the underlying function, if any. + */ + template + constexpr decltype(auto) operator()(Args &&...args) const noexcept(std::is_nothrow_invocable_v) { + return func(*this, std::forward(args)...); + } + + /*! @copydoc operator()() */ + template + constexpr decltype(auto) operator()(Args &&...args) noexcept(std::is_nothrow_invocable_v) { + return func(*this, std::forward(args)...); + } + +private: + Func func; +}; + +} // namespace entt + +#endif + + +namespace entt { + +/** + * @brief Function object to wrap `std::sort` in a class type. + * + * Unfortunately, `std::sort` cannot be passed as template argument to a class + * template or a function template.
+ * This class fills the gap by wrapping some flavors of `std::sort` in a + * function object. + */ +struct std_sort { + /** + * @brief Sorts the elements in a range. + * + * Sorts the elements in a range using the given binary comparison function. + * + * @tparam It Type of random access iterator. + * @tparam Compare Type of comparison function object. + * @tparam Args Types of arguments to forward to the sort function. + * @param first An iterator to the first element of the range to sort. + * @param last An iterator past the last element of the range to sort. + * @param compare A valid comparison function object. + * @param args Arguments to forward to the sort function, if any. + */ + template, typename... Args> + void operator()(It first, It last, Compare compare = Compare{}, Args &&...args) const { + std::sort(std::forward(args)..., std::move(first), std::move(last), std::move(compare)); + } +}; + +/*! @brief Function object for performing insertion sort. */ +struct insertion_sort { + /** + * @brief Sorts the elements in a range. + * + * Sorts the elements in a range using the given binary comparison function. + * + * @tparam It Type of random access iterator. + * @tparam Compare Type of comparison function object. + * @param first An iterator to the first element of the range to sort. + * @param last An iterator past the last element of the range to sort. + * @param compare A valid comparison function object. + */ + template> + void operator()(It first, It last, Compare compare = Compare{}) const { + if(first < last) { + for(auto it = first + 1; it < last; ++it) { + auto value = std::move(*it); + auto pre = it; + + for(; pre > first && compare(value, *(pre - 1)); --pre) { + *pre = std::move(*(pre - 1)); + } + + *pre = std::move(value); + } + } + } +}; + +/** + * @brief Function object for performing LSD radix sort. + * @tparam Bit Number of bits processed per pass. + * @tparam N Maximum number of bits to sort. + */ +template +struct radix_sort { + static_assert((N % Bit) == 0, "The maximum number of bits to sort must be a multiple of the number of bits processed per pass"); + + /** + * @brief Sorts the elements in a range. + * + * Sorts the elements in a range using the given _getter_ to access the + * actual data to be sorted. + * + * This implementation is inspired by the online book + * [Physically Based Rendering](http://www.pbr-book.org/3ed-2018/Primitives_and_Intersection_Acceleration/Bounding_Volume_Hierarchies.html#RadixSort). + * + * @tparam It Type of random access iterator. + * @tparam Getter Type of _getter_ function object. + * @param first An iterator to the first element of the range to sort. + * @param last An iterator past the last element of the range to sort. + * @param getter A valid _getter_ function object. + */ + template + void operator()(It first, It last, Getter getter = Getter{}) const { + if(first < last) { + constexpr auto passes = N / Bit; + + using value_type = typename std::iterator_traits::value_type; + std::vector 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]{}; + + for(auto it = from; it != to; ++it) { + ++count[(getter(*it) >> start) & mask]; + } + + for(std::size_t pos{}, end = buckets - 1u; pos < end; ++pos) { + index[pos + 1u] = index[pos] + count[pos]; + } + + for(auto it = from; it != to; ++it) { + out[index[(getter(*it) >> start) & mask]++] = std::move(*it); + } + }; + + for(std::size_t pass = 0; pass < (passes & ~1); pass += 2) { + part(first, last, aux.begin(), pass * Bit); + part(aux.begin(), aux.end(), first, (pass + 1) * Bit); + } + + if constexpr(passes & 1) { + part(first, last, aux.begin(), (passes - 1) * Bit); + std::move(aux.begin(), aux.end(), first); + } + } + } +}; + +} // namespace entt + +#endif + +// #include "../core/any.hpp" +#ifndef ENTT_CORE_ANY_HPP +#define ENTT_CORE_ANY_HPP + +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/utility.hpp" +#ifndef ENTT_CORE_UTILITY_HPP +#define ENTT_CORE_UTILITY_HPP + +#include +#include + +namespace entt { + +/*! @brief Identity function object (waiting for C++20). */ +struct identity { + /*! @brief Indicates that this is a transparent function object. */ + using is_transparent = void; + + /** + * @brief Returns its argument unchanged. + * @tparam Type Type of the argument. + * @param value The actual argument. + * @return The submitted value as-is. + */ + template + [[nodiscard]] constexpr Type &&operator()(Type &&value) const noexcept { + return std::forward(value); + } +}; + +/** + * @brief Constant utility to disambiguate overloaded members of a class. + * @tparam Type Type of the desired overload. + * @tparam Class Type of class to which the member belongs. + * @param member A valid pointer to a member. + * @return Pointer to the member. + */ +template +[[nodiscard]] constexpr auto overload(Type Class::*member) noexcept { + return member; +} + +/** + * @brief Constant utility to disambiguate overloaded functions. + * @tparam Func Function type of the desired overload. + * @param func A valid pointer to a function. + * @return Pointer to the function. + */ +template +[[nodiscard]] constexpr auto overload(Func *func) noexcept { + return func; +} + +/** + * @brief Helper type for visitors. + * @tparam Func Types of function objects. + */ +template +struct overloaded: Func... { + using Func::operator()...; +}; + +/** + * @brief Deduction guide. + * @tparam Func Types of function objects. + */ +template +overloaded(Func...) -> overloaded; + +/** + * @brief Basic implementation of a y-combinator. + * @tparam Func Type of a potentially recursive function. + */ +template +struct y_combinator { + /** + * @brief Constructs a y-combinator from a given function. + * @param recursive A potentially recursive function. + */ + constexpr y_combinator(Func recursive) noexcept(std::is_nothrow_move_constructible_v) + : func{std::move(recursive)} {} + + /** + * @brief Invokes a y-combinator and therefore its underlying function. + * @tparam Args Types of arguments to use to invoke the underlying function. + * @param args Parameters to use to invoke the underlying function. + * @return Return value of the underlying function, if any. + */ + template + constexpr decltype(auto) operator()(Args &&...args) const noexcept(std::is_nothrow_invocable_v) { + return func(*this, std::forward(args)...); + } + + /*! @copydoc operator()() */ + template + constexpr decltype(auto) operator()(Args &&...args) noexcept(std::is_nothrow_invocable_v) { + return func(*this, std::forward(args)...); + } + +private: + Func func; +}; + +} // namespace entt + +#endif + +// #include "fwd.hpp" + +// #include "type_info.hpp" +#ifndef ENTT_CORE_TYPE_INFO_HPP +#define ENTT_CORE_TYPE_INFO_HPP + +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/attribute.h" + +// #include "fwd.hpp" + +// #include "hashed_string.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +struct ENTT_API type_index final { + [[nodiscard]] static id_type next() noexcept { + static ENTT_MAYBE_ATOMIC(id_type) value{}; + return value++; + } +}; + +template +[[nodiscard]] constexpr auto stripped_type_name() noexcept { +#if defined ENTT_PRETTY_FUNCTION + std::string_view pretty_function{ENTT_PRETTY_FUNCTION}; + auto first = pretty_function.find_first_not_of(' ', pretty_function.find_first_of(ENTT_PRETTY_FUNCTION_PREFIX) + 1); + auto value = pretty_function.substr(first, pretty_function.find_last_of(ENTT_PRETTY_FUNCTION_SUFFIX) - first); + return value; +#else + return std::string_view{""}; +#endif +} + +template().find_first_of('.')> +[[nodiscard]] static constexpr std::string_view type_name(int) noexcept { + constexpr auto value = stripped_type_name(); + return value; +} + +template +[[nodiscard]] static std::string_view type_name(char) noexcept { + static const auto value = stripped_type_name(); + return value; +} + +template().find_first_of('.')> +[[nodiscard]] static constexpr id_type type_hash(int) noexcept { + constexpr auto stripped = stripped_type_name(); + constexpr auto value = hashed_string::value(stripped.data(), stripped.size()); + return value; +} + +template +[[nodiscard]] static id_type type_hash(char) noexcept { + static const auto value = [](const auto stripped) { + return hashed_string::value(stripped.data(), stripped.size()); + }(stripped_type_name()); + return value; +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/** + * @brief Type sequential identifier. + * @tparam Type Type for which to generate a sequential identifier. + */ +template +struct ENTT_API type_index final { + /** + * @brief Returns the sequential identifier of a given type. + * @return The sequential identifier of a given type. + */ + [[nodiscard]] static id_type value() noexcept { + static const id_type value = internal::type_index::next(); + return value; + } + + /*! @copydoc value */ + [[nodiscard]] constexpr operator id_type() const noexcept { + return value(); + } +}; + +/** + * @brief Type hash. + * @tparam Type Type for which to generate a hash value. + */ +template +struct type_hash final { + /** + * @brief Returns the numeric representation of a given type. + * @return The numeric representation of the given type. + */ +#if defined ENTT_PRETTY_FUNCTION + [[nodiscard]] static constexpr id_type value() noexcept { + return internal::type_hash(0); +#else + [[nodiscard]] static constexpr id_type value() noexcept { + return type_index::value(); +#endif + } + + /*! @copydoc value */ + [[nodiscard]] constexpr operator id_type() const noexcept { + return value(); + } +}; + +/** + * @brief Type name. + * @tparam Type Type for which to generate a name. + */ +template +struct type_name final { + /** + * @brief Returns the name of a given type. + * @return The name of the given type. + */ + [[nodiscard]] static constexpr std::string_view value() noexcept { + return internal::type_name(0); + } + + /*! @copydoc value */ + [[nodiscard]] constexpr operator std::string_view() const noexcept { + return value(); + } +}; + +/*! @brief Implementation specific information about a type. */ +struct type_info final { + /** + * @brief Constructs a type info object for a given type. + * @tparam Type Type for which to construct a type info object. + */ + template + constexpr type_info(std::in_place_type_t) noexcept + : seq{type_index>>::value()}, + identifier{type_hash>>::value()}, + alias{type_name>>::value()} {} + + /** + * @brief Type index. + * @return Type index. + */ + [[nodiscard]] constexpr id_type index() const noexcept { + return seq; + } + + /** + * @brief Type hash. + * @return Type hash. + */ + [[nodiscard]] constexpr id_type hash() const noexcept { + return identifier; + } + + /** + * @brief Type name. + * @return Type name. + */ + [[nodiscard]] constexpr std::string_view name() const noexcept { + return alias; + } + +private: + id_type seq; + id_type identifier; + std::string_view alias; +}; + +/** + * @brief Compares the contents of two type info objects. + * @param lhs A type info object. + * @param rhs A type info object. + * @return True if the two type info objects are identical, false otherwise. + */ +[[nodiscard]] inline constexpr bool operator==(const type_info &lhs, const type_info &rhs) noexcept { + return lhs.hash() == rhs.hash(); +} + +/** + * @brief Compares the contents of two type info objects. + * @param lhs A type info object. + * @param rhs A type info object. + * @return True if the two type info objects differ, false otherwise. + */ +[[nodiscard]] inline constexpr bool operator!=(const type_info &lhs, const type_info &rhs) noexcept { + return !(lhs == rhs); +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is less than the second, false otherwise. + */ +[[nodiscard]] constexpr bool operator<(const type_info &lhs, const type_info &rhs) noexcept { + return lhs.index() < rhs.index(); +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is less than or equal to the second, false + * otherwise. + */ +[[nodiscard]] constexpr bool operator<=(const type_info &lhs, const type_info &rhs) noexcept { + return !(rhs < lhs); +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is greater than the second, false + * otherwise. + */ +[[nodiscard]] constexpr bool operator>(const type_info &lhs, const type_info &rhs) noexcept { + return rhs < lhs; +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is greater than or equal to the second, + * false otherwise. + */ +[[nodiscard]] constexpr bool operator>=(const type_info &lhs, const type_info &rhs) noexcept { + return !(lhs < rhs); +} + +/** + * @brief Returns the type info object associated to a given type. + * + * The returned element refers to an object with static storage duration.
+ * The type doesn't need to be a complete type. If the type is a reference, the + * result refers to the referenced type. In all cases, top-level cv-qualifiers + * are ignored. + * + * @tparam Type Type for which to generate a type info object. + * @return A reference to a properly initialized type info object. + */ +template +[[nodiscard]] const type_info &type_id() noexcept { + if constexpr(std::is_same_v>>) { + static type_info instance{std::in_place_type}; + return instance; + } else { + return type_id>>(); + } +} + +/*! @copydoc type_id */ +template +[[nodiscard]] const type_info &type_id(Type &&) noexcept { + return type_id>>(); +} + +} // namespace entt + +#endif + // #include "type_traits.hpp" #ifndef ENTT_CORE_TYPE_TRAITS_HPP #define ENTT_CORE_TYPE_TRAITS_HPP #include #include +#include #include #include // #include "../config/config.h" @@ -12979,7 +14139,6 @@ using type_identity_t = typename type_identity::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 struct size_of: std::integral_constant {}; @@ -13221,7 +14380,8 @@ struct type_list_contains; * @tparam Other Type to look for. */ template -struct type_list_contains, Other>: std::disjunction...> {}; +struct type_list_contains, Other> + : std::bool_constant<(std::is_same_v || ...)> {}; /** * @brief Helper variable template. @@ -13309,10 +14469,20 @@ struct value_list_element> */ template struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = typename value_list_element::type; + /** * @brief Helper type. * @tparam Index Index of the value to return. @@ -13321,6 +14491,58 @@ struct value_list_element<0u, value_list> { template inline constexpr auto value_list_element_v = value_list_element::value; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +struct value_list_index> { + static_assert(value_list_index>::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 +struct value_list_index> { + /*! @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 +inline constexpr std::size_t value_list_index_v = value_list_index::value; + /** * @brief Concatenates multiple value lists. * @tparam Value Values provided by the first value list. @@ -13372,6 +14594,89 @@ struct value_list_cat> { template using value_list_cat_t = typename value_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = std::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = typename value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : std::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +class value_list_diff, value_list> { + using v141_toolset_workaround = value_list; + +public: + /*! @brief A value list that is the difference between the two value lists. */ + using type = value_list_cat_t, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = typename value_list_diff::type; + /*! @brief Same as std::is_invocable, but with tuples. */ template struct is_applicable: std::false_type {}; @@ -13492,7 +14797,7 @@ inline constexpr bool is_iterator_v = is_iterator::value; */ template struct is_ebco_eligible - : std::conjunction, std::negation>> {}; + : std::bool_constant && !std::is_final_v> {}; /** * @brief Helper variable template. @@ -13583,6 +14888,10 @@ template struct is_equality_comparable() == std::declval())>> : std::bool_constant(choice<2>)> {}; +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: std::false_type {}; + /** * @brief Helper variable template. * @tparam Type The type to test. @@ -13679,6 +14988,18 @@ using nth_argument_t = typename nth_argument::type; } // namespace entt +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::type_list_element> {}; + +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::value_list_element> {}; + #endif @@ -13730,7 +15051,7 @@ class basic_any { }; template - static constexpr bool in_situ = Len && alignof(Type) <= Align && sizeof(Type) <= Len &&std::is_nothrow_move_constructible_v; + static constexpr bool in_situ = Len && alignof(Type) <= Align && sizeof(Type) <= Len && std::is_nothrow_move_constructible_v; template static const void *basic_vtable(const operation op, const basic_any &value, const void *other) { @@ -13799,17 +15120,17 @@ class basic_any { vtable = basic_vtable>>; if constexpr(std::is_lvalue_reference_v) { - static_assert(sizeof...(Args) == 1u && (std::is_lvalue_reference_v && ...), "Invalid arguments"); + static_assert((std::is_lvalue_reference_v && ...) && (sizeof...(Args) == 1u), "Invalid arguments"); mode = std::is_const_v> ? policy::cref : policy::ref; instance = (std::addressof(args), ...); } else if constexpr(in_situ>>) { - if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v>>) { + if constexpr(std::is_aggregate_v>> && (sizeof...(Args) != 0u || !std::is_default_constructible_v>>)) { new(&storage) std::remove_cv_t>{std::forward(args)...}; } else { new(&storage) std::remove_cv_t>(std::forward(args)...); } } else { - if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v>>) { + if constexpr(std::is_aggregate_v>> && (sizeof...(Args) != 0u || !std::is_default_constructible_v>>)) { instance = new std::remove_cv_t>{std::forward(args)...}; } else { instance = new std::remove_cv_t>(std::forward(args)...); @@ -14099,7 +15420,7 @@ private: * @return The element converted to the requested type. */ template -Type any_cast(const basic_any &data) noexcept { +[[nodiscard]] Type any_cast(const basic_any &data) noexcept { const auto *const instance = any_cast>(&data); ENTT_ASSERT(instance, "Invalid instance"); return static_cast(*instance); @@ -14107,7 +15428,7 @@ Type any_cast(const basic_any &data) noexcept { /*! @copydoc any_cast */ template -Type any_cast(basic_any &data) noexcept { +[[nodiscard]] Type any_cast(basic_any &data) noexcept { // forces const on non-reference types to make them work also with wrappers for const references auto *const instance = any_cast>(&data); ENTT_ASSERT(instance, "Invalid instance"); @@ -14116,7 +15437,7 @@ Type any_cast(basic_any &data) noexcept { /*! @copydoc any_cast */ template -Type any_cast(basic_any &&data) noexcept { +[[nodiscard]] Type any_cast(basic_any &&data) noexcept { if constexpr(std::is_copy_constructible_v>>) { if(auto *const instance = any_cast>(&data); instance) { return static_cast(std::move(*instance)); @@ -14132,14 +15453,14 @@ Type any_cast(basic_any &&data) noexcept { /*! @copydoc any_cast */ template -const Type *any_cast(const basic_any *data) noexcept { +[[nodiscard]] const Type *any_cast(const basic_any *data) noexcept { const auto &info = type_id>(); return static_cast(data->data(info)); } /*! @copydoc any_cast */ template -Type *any_cast(basic_any *data) noexcept { +[[nodiscard]] Type *any_cast(basic_any *data) noexcept { if constexpr(std::is_const_v) { // last attempt to make wrappers for const references return their values return any_cast(&std::as_const(*data)); @@ -14159,7 +15480,7 @@ Type *any_cast(basic_any *data) noexcept { * @return A properly initialized wrapper for an object of the given type. */ template::length, std::size_t Align = basic_any::alignment, typename... Args> -basic_any make_any(Args &&...args) { +[[nodiscard]] basic_any make_any(Args &&...args) { return basic_any{std::in_place_type, std::forward(args)...}; } @@ -14172,7 +15493,7 @@ basic_any make_any(Args &&...args) { * @return A properly initialized and not necessarily owning wrapper. */ template::length, std::size_t Align = basic_any::alignment, typename Type> -basic_any forward_as_any(Type &&value) { +[[nodiscard]] basic_any forward_as_any(Type &&value) { return basic_any{std::in_place_type, std::forward(value)}; } @@ -14290,7 +15611,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 struct allocation_deleter: private Allocator { @@ -14311,7 +15632,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) { - using alloc_traits = typename std::allocator_traits; + using alloc_traits = std::allocator_traits; alloc_traits::destroy(*this, to_address(ptr)); alloc_traits::deallocate(*this, ptr, 1u); } @@ -14473,284 +15794,6 @@ constexpr Type *uninitialized_construct_using_allocator(Type *value, const Alloc #endif // #include "../core/type_info.hpp" -#ifndef ENTT_CORE_TYPE_INFO_HPP -#define ENTT_CORE_TYPE_INFO_HPP - -#include -#include -#include -// #include "../config/config.h" - -// #include "../core/attribute.h" - -// #include "fwd.hpp" - -// #include "hashed_string.hpp" - - -namespace entt { - -/** - * @cond TURN_OFF_DOXYGEN - * Internal details not to be documented. - */ - -namespace internal { - -struct ENTT_API type_index final { - [[nodiscard]] static id_type next() noexcept { - static ENTT_MAYBE_ATOMIC(id_type) value{}; - return value++; - } -}; - -template -[[nodiscard]] constexpr auto stripped_type_name() noexcept { -#if defined ENTT_PRETTY_FUNCTION - std::string_view pretty_function{ENTT_PRETTY_FUNCTION}; - auto first = pretty_function.find_first_not_of(' ', pretty_function.find_first_of(ENTT_PRETTY_FUNCTION_PREFIX) + 1); - auto value = pretty_function.substr(first, pretty_function.find_last_of(ENTT_PRETTY_FUNCTION_SUFFIX) - first); - return value; -#else - return std::string_view{""}; -#endif -} - -template().find_first_of('.')> -[[nodiscard]] static constexpr std::string_view type_name(int) noexcept { - constexpr auto value = stripped_type_name(); - return value; -} - -template -[[nodiscard]] static std::string_view type_name(char) noexcept { - static const auto value = stripped_type_name(); - return value; -} - -template().find_first_of('.')> -[[nodiscard]] static constexpr id_type type_hash(int) noexcept { - constexpr auto stripped = stripped_type_name(); - constexpr auto value = hashed_string::value(stripped.data(), stripped.size()); - return value; -} - -template -[[nodiscard]] static id_type type_hash(char) noexcept { - static const auto value = [](const auto stripped) { - return hashed_string::value(stripped.data(), stripped.size()); - }(stripped_type_name()); - return value; -} - -} // namespace internal - -/** - * Internal details not to be documented. - * @endcond - */ - -/** - * @brief Type sequential identifier. - * @tparam Type Type for which to generate a sequential identifier. - */ -template -struct ENTT_API type_index final { - /** - * @brief Returns the sequential identifier of a given type. - * @return The sequential identifier of a given type. - */ - [[nodiscard]] static id_type value() noexcept { - static const id_type value = internal::type_index::next(); - return value; - } - - /*! @copydoc value */ - [[nodiscard]] constexpr operator id_type() const noexcept { - return value(); - } -}; - -/** - * @brief Type hash. - * @tparam Type Type for which to generate a hash value. - */ -template -struct type_hash final { - /** - * @brief Returns the numeric representation of a given type. - * @return The numeric representation of the given type. - */ -#if defined ENTT_PRETTY_FUNCTION - [[nodiscard]] static constexpr id_type value() noexcept { - return internal::type_hash(0); -#else - [[nodiscard]] static constexpr id_type value() noexcept { - return type_index::value(); -#endif - } - - /*! @copydoc value */ - [[nodiscard]] constexpr operator id_type() const noexcept { - return value(); - } -}; - -/** - * @brief Type name. - * @tparam Type Type for which to generate a name. - */ -template -struct type_name final { - /** - * @brief Returns the name of a given type. - * @return The name of the given type. - */ - [[nodiscard]] static constexpr std::string_view value() noexcept { - return internal::type_name(0); - } - - /*! @copydoc value */ - [[nodiscard]] constexpr operator std::string_view() const noexcept { - return value(); - } -}; - -/*! @brief Implementation specific information about a type. */ -struct type_info final { - /** - * @brief Constructs a type info object for a given type. - * @tparam Type Type for which to construct a type info object. - */ - template - constexpr type_info(std::in_place_type_t) noexcept - : seq{type_index>>::value()}, - identifier{type_hash>>::value()}, - alias{type_name>>::value()} {} - - /** - * @brief Type index. - * @return Type index. - */ - [[nodiscard]] constexpr id_type index() const noexcept { - return seq; - } - - /** - * @brief Type hash. - * @return Type hash. - */ - [[nodiscard]] constexpr id_type hash() const noexcept { - return identifier; - } - - /** - * @brief Type name. - * @return Type name. - */ - [[nodiscard]] constexpr std::string_view name() const noexcept { - return alias; - } - -private: - id_type seq; - id_type identifier; - std::string_view alias; -}; - -/** - * @brief Compares the contents of two type info objects. - * @param lhs A type info object. - * @param rhs A type info object. - * @return True if the two type info objects are identical, false otherwise. - */ -[[nodiscard]] inline constexpr bool operator==(const type_info &lhs, const type_info &rhs) noexcept { - return lhs.hash() == rhs.hash(); -} - -/** - * @brief Compares the contents of two type info objects. - * @param lhs A type info object. - * @param rhs A type info object. - * @return True if the two type info objects differ, false otherwise. - */ -[[nodiscard]] inline constexpr bool operator!=(const type_info &lhs, const type_info &rhs) noexcept { - return !(lhs == rhs); -} - -/** - * @brief Compares two type info objects. - * @param lhs A valid type info object. - * @param rhs A valid type info object. - * @return True if the first element is less than the second, false otherwise. - */ -[[nodiscard]] constexpr bool operator<(const type_info &lhs, const type_info &rhs) noexcept { - return lhs.index() < rhs.index(); -} - -/** - * @brief Compares two type info objects. - * @param lhs A valid type info object. - * @param rhs A valid type info object. - * @return True if the first element is less than or equal to the second, false - * otherwise. - */ -[[nodiscard]] constexpr bool operator<=(const type_info &lhs, const type_info &rhs) noexcept { - return !(rhs < lhs); -} - -/** - * @brief Compares two type info objects. - * @param lhs A valid type info object. - * @param rhs A valid type info object. - * @return True if the first element is greater than the second, false - * otherwise. - */ -[[nodiscard]] constexpr bool operator>(const type_info &lhs, const type_info &rhs) noexcept { - return rhs < lhs; -} - -/** - * @brief Compares two type info objects. - * @param lhs A valid type info object. - * @param rhs A valid type info object. - * @return True if the first element is greater than or equal to the second, - * false otherwise. - */ -[[nodiscard]] constexpr bool operator>=(const type_info &lhs, const type_info &rhs) noexcept { - return !(lhs < rhs); -} - -/** - * @brief Returns the type info object associated to a given type. - * - * The returned element refers to an object with static storage duration.
- * The type doesn't need to be a complete type. If the type is a reference, the - * result refers to the referenced type. In all cases, top-level cv-qualifiers - * are ignored. - * - * @tparam Type Type for which to generate a type info object. - * @return A reference to a properly initialized type info object. - */ -template -[[nodiscard]] const type_info &type_id() noexcept { - if constexpr(std::is_same_v>>) { - static type_info instance{std::in_place_type}; - return instance; - } else { - return type_id>>(); - } -} - -/*! @copydoc type_id */ -template -[[nodiscard]] const type_info &type_id(Type &&) noexcept { - return type_id>>(); -} - -} // namespace entt - -#endif // #include "entity.hpp" @@ -14830,6 +15873,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; } @@ -14839,38 +15886,38 @@ private: difference_type offset; }; -template -[[nodiscard]] constexpr std::ptrdiff_t operator-(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr std::ptrdiff_t operator-(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { return rhs.index() - lhs.index(); } -template -[[nodiscard]] constexpr bool operator==(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { return lhs.index() == rhs.index(); } -template -[[nodiscard]] constexpr bool operator!=(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { return !(lhs == rhs); } -template -[[nodiscard]] constexpr bool operator<(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { return lhs.index() > rhs.index(); } -template -[[nodiscard]] constexpr bool operator>(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { - return lhs.index() < rhs.index(); +template +[[nodiscard]] constexpr bool operator>(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { + return rhs < lhs; } -template -[[nodiscard]] constexpr bool operator<=(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<=(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { return !(lhs > rhs); } -template -[[nodiscard]] constexpr bool operator>=(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>=(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { return !(lhs < rhs); } @@ -14881,14 +15928,6 @@ template * @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. * @@ -14896,10 +15935,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.
- * 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. @@ -14909,7 +15944,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 @@ -14918,23 +15953,26 @@ class basic_sparse_set { static_assert(std::is_same_v, "Invalid value type"); using sparse_container_type = std::vector>; using packed_container_type = std::vector; - using entity_traits = entt_traits; [[nodiscard]] auto sparse_ptr(const Entity entt) const { - const auto pos = static_cast(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(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(entity_traits::to_entity(entt)); - return sparse[pos / entity_traits::page_size][fast_mod(pos, entity_traits::page_size)]; + const auto pos = static_cast(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(entity_traits::to_entity(entt)); - const auto page = pos / entity_traits::page_size; + const auto pos = static_cast(traits_type::to_entity(entt)); + const auto page = pos / traits_type::page_size; if(!(page < sparse.size())) { sparse.resize(page + 1u, nullptr); @@ -14942,11 +15980,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; } @@ -14956,8 +15994,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; } } @@ -14968,13 +16006,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; + /** + * @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(lhs); + const auto other = static_cast(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. @@ -14982,8 +16035,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(entt)] = packed.back(); // unnecessary but it helps to detect nasty bugs ENTT_ASSERT((packed.back() = null, true), ""); @@ -14998,8 +16051,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(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(entt)] = std::exchange(free_list, traits_type::combine(entt, tombstone)); } protected: @@ -15020,6 +16073,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. @@ -15031,25 +16101,27 @@ protected: if(auto &elem = assure_at_least(entt); free_list == null || force_back) { packed.push_back(entt); - elem = entity_traits::combine(static_cast(packed.size() - 1u), entity_traits::to_integral(entt)); + elem = traits_type::combine(static_cast(packed.size() - 1u), traits_type::to_integral(entt)); return begin(); } else { - const auto pos = static_cast(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(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; /*! @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. */ @@ -15059,7 +16131,7 @@ public: /*! @brief Reverse iterator type. */ using reverse_iterator = std::reverse_iterator; /*! @brief Constant reverse iterator type. */ - using const_reverse_iterator = reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; /*! @brief Default constructor. */ basic_sparse_set() @@ -15083,14 +16155,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} {} @@ -15207,7 +16279,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; } /** @@ -15232,6 +16304,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. @@ -15243,8 +16323,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. @@ -15261,11 +16340,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. */ @@ -15281,9 +16355,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. @@ -15299,11 +16372,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. */ @@ -15323,7 +16391,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(); } /** @@ -15333,9 +16401,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); } /** @@ -15346,8 +16414,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; } /** @@ -15362,7 +16430,7 @@ public: */ [[nodiscard]] size_type index(const entity_type entt) const noexcept { ENTT_ASSERT(contains(entt), "Set does not contain entity"); - return static_cast(entity_traits::to_entity(sparse_ref(entt))); + return static_cast(traits_type::to_entity(sparse_ref(entt))); } /** @@ -15394,13 +16462,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(std::as_const(*this).get(entt)); + /*! @copydoc value */ + [[nodiscard]] void *value(const entity_type entt) noexcept { + return const_cast(std::as_const(*this).value(entt)); } /** @@ -15411,28 +16479,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(entity_traits::to_entity(entity))] = entt; + iterator push(const entity_type entt, const void *elem = nullptr) { + return try_emplace(entt, false, elem); } /** @@ -15449,7 +16501,7 @@ public: * success, the `end()` iterator otherwise. */ template - iterator insert(It first, It last) { + iterator push(It first, It last) { for(auto it = first; it != last; ++it) { try_emplace(*it, true); } @@ -15457,6 +16509,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(traits_type::to_entity(entity))] = entt; + return traits_type::to_version(entt); + } + /** * @brief Erases an entity from a sparse set. * @@ -15467,7 +16537,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); } @@ -15511,29 +16581,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) { + 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(to); + sparse_ref(packed[to]) = traits_type::combine(entity, traits_type::to_integral(packed[to])); - const auto entity = static_cast(to); - sparse_ref(packed[to]) = entity_traits::combine(entity, entity_traits::to_integral(packed[to])); - *it = entity_traits::combine(static_cast(from), entity_traits::reserved); + *it = traits_type::combine(static_cast(from), tombstone); for(; from && packed[from - 1u] == tombstone; --from) {} } } @@ -15556,21 +16642,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(from), static_cast(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); } /** @@ -15618,9 +16695,9 @@ public: const auto idx = index(packed[next]); const auto entt = packed[curr]; - swap_at(next, idx); - const auto entity = static_cast(curr); - sparse_ref(entt) = entity_traits::combine(entity, entity_traits::to_integral(packed[curr])); + swap_or_move(next, idx); + const auto entity = static_cast(curr); + sparse_ref(entt) = traits_type::combine(entity, traits_type::to_integral(packed[curr])); curr = std::exchange(next, idx); } } @@ -15648,48 +16725,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.
- * 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`.
+ * 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(); } @@ -15730,15 +16794,21 @@ private: #include // #include "../config/config.h" -// #include "../core/compressed_pair.hpp" -#ifndef ENTT_CORE_COMPRESSED_PAIR_HPP -#define ENTT_CORE_COMPRESSED_PAIR_HPP +// #include "../core/iterator.hpp" + +// #include "../core/memory.hpp" + +// #include "../core/type_info.hpp" + +// #include "component.hpp" +#ifndef ENTT_ENTITY_COMPONENT_HPP +#define ENTT_ENTITY_COMPONENT_HPP #include -#include #include -#include -// #include "type_traits.hpp" +// #include "../config/config.h" + +// #include "fwd.hpp" namespace entt { @@ -15750,60 +16820,1532 @@ namespace entt { namespace internal { -template -struct compressed_pair_element { - using reference = Type &; - using const_reference = const Type &; +template +struct in_place_delete: std::bool_constant && std::is_move_assignable_v)> {}; - template>> - constexpr compressed_pair_element() noexcept(std::is_nothrow_default_constructible_v) - : value{} {} +template<> +struct in_place_delete: std::false_type {}; - template>, compressed_pair_element>>> - constexpr compressed_pair_element(Arg &&arg) noexcept(std::is_nothrow_constructible_v) - : value{std::forward(arg)} {} +template +struct in_place_delete> + : std::true_type {}; - template - constexpr compressed_pair_element(std::tuple args, std::index_sequence) noexcept(std::is_nothrow_constructible_v) - : value{std::forward(std::get(args))...} {} +template +struct page_size: std::integral_constant * ENTT_PACKED_PAGE> {}; - [[nodiscard]] constexpr reference get() noexcept { - return value; +template<> +struct page_size: std::integral_constant {}; + +template +struct page_size>> + : std::integral_constant {}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/** + * @brief Common way to access various properties of components. + * @tparam Type Type of component. + */ +template +struct component_traits { + static_assert(std::is_same_v, Type>, "Unsupported type"); + + /*! @brief Component type. */ + using type = Type; + + /*! @brief Pointer stability, default is `false`. */ + static constexpr bool in_place_delete = internal::in_place_delete::value; + /*! @brief Page size, default is `ENTT_PACKED_PAGE` for non-empty types. */ + static constexpr std::size_t page_size = internal::page_size::value; +}; + +} // namespace entt + +#endif + +// #include "entity.hpp" + +// #include "fwd.hpp" + +// #include "sparse_set.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +class storage_iterator final { + friend storage_iterator; + + using container_type = std::remove_const_t; + using alloc_traits = std::allocator_traits; + + using iterator_traits = std::iterator_traits, + typename alloc_traits::template rebind_traits::element_type>::const_pointer, + typename alloc_traits::template rebind_traits::element_type>::pointer>>; + +public: + using value_type = typename iterator_traits::value_type; + using pointer = typename iterator_traits::pointer; + using reference = typename iterator_traits::reference; + using difference_type = typename iterator_traits::difference_type; + using iterator_category = std::random_access_iterator_tag; + + constexpr storage_iterator() noexcept = default; + + constexpr storage_iterator(Container *ref, const difference_type idx) noexcept + : payload{ref}, + offset{idx} {} + + template, typename = std::enable_if_t> + constexpr storage_iterator(const storage_iterator, Size> &other) noexcept + : storage_iterator{other.payload, other.offset} {} + + constexpr storage_iterator &operator++() noexcept { + return --offset, *this; } - [[nodiscard]] constexpr const_reference get() const noexcept { - return value; + constexpr storage_iterator operator++(int) noexcept { + storage_iterator orig = *this; + return ++(*this), orig; + } + + constexpr storage_iterator &operator--() noexcept { + return ++offset, *this; + } + + constexpr storage_iterator operator--(int) noexcept { + storage_iterator orig = *this; + return operator--(), orig; + } + + constexpr storage_iterator &operator+=(const difference_type value) noexcept { + offset -= value; + return *this; + } + + constexpr storage_iterator operator+(const difference_type value) const noexcept { + storage_iterator copy = *this; + return (copy += value); + } + + constexpr storage_iterator &operator-=(const difference_type value) noexcept { + return (*this += -value); + } + + constexpr storage_iterator operator-(const difference_type value) const noexcept { + return (*this + -value); + } + + [[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept { + const auto pos = index() - value; + return (*payload)[pos / Size][fast_mod(pos, Size)]; + } + + [[nodiscard]] constexpr pointer operator->() const noexcept { + const auto pos = index(); + return (*payload)[pos / Size] + fast_mod(pos, Size); + } + + [[nodiscard]] constexpr reference operator*() const noexcept { + return *operator->(); + } + + [[nodiscard]] constexpr difference_type index() const noexcept { + return offset - 1; } private: - Type value; + Container *payload; + difference_type offset; }; -template -struct compressed_pair_element>>: Type { - using reference = Type &; - using const_reference = const Type &; - using base_type = Type; +template +[[nodiscard]] constexpr std::ptrdiff_t operator-(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { + return rhs.index() - lhs.index(); +} - template>> - constexpr compressed_pair_element() noexcept(std::is_nothrow_default_constructible_v) - : base_type{} {} +template +[[nodiscard]] constexpr bool operator==(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { + return lhs.index() == rhs.index(); +} - template>, compressed_pair_element>>> - constexpr compressed_pair_element(Arg &&arg) noexcept(std::is_nothrow_constructible_v) - : base_type{std::forward(arg)} {} +template +[[nodiscard]] constexpr bool operator!=(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { + return !(lhs == rhs); +} - template - constexpr compressed_pair_element(std::tuple args, std::index_sequence) noexcept(std::is_nothrow_constructible_v) - : base_type{std::forward(std::get(args))...} {} +template +[[nodiscard]] constexpr bool operator<(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { + return lhs.index() > rhs.index(); +} - [[nodiscard]] constexpr reference get() noexcept { +template +[[nodiscard]] constexpr bool operator>(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { + return rhs < lhs; +} + +template +[[nodiscard]] constexpr bool operator<=(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { + return !(lhs > rhs); +} + +template +[[nodiscard]] constexpr bool operator>=(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { + return !(lhs < rhs); +} + +template +class extended_storage_iterator final { + template + 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()), std::forward_as_tuple(*std::declval()...))); + using pointer = input_iterator_pointer; + using reference = value_type; + using iterator_category = std::input_iterator_tag; + + constexpr extended_storage_iterator() + : it{} {} + + constexpr extended_storage_iterator(It base, Other... other) + : it{base, other...} {} + + template && ...) && (std::is_constructible_v && ...)>> + constexpr extended_storage_iterator(const extended_storage_iterator &other) + : it{other.it} {} + + constexpr extended_storage_iterator &operator++() noexcept { + return ++std::get(it), (++std::get(it), ...), *this; + } + + constexpr extended_storage_iterator operator++(int) noexcept { + extended_storage_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] constexpr pointer operator->() const noexcept { + return operator*(); + } + + [[nodiscard]] constexpr reference operator*() const noexcept { + return {*std::get(it), *std::get(it)...}; + } + + [[nodiscard]] constexpr iterator_type base() const noexcept { + return std::get(it); + } + + template + friend constexpr bool operator==(const extended_storage_iterator &, const extended_storage_iterator &) noexcept; + +private: + std::tuple it; +}; + +template +[[nodiscard]] constexpr bool operator==(const extended_storage_iterator &lhs, const extended_storage_iterator &rhs) noexcept { + return std::get<0>(lhs.it) == std::get<0>(rhs.it); +} + +template +[[nodiscard]] constexpr bool operator!=(const extended_storage_iterator &lhs, const extended_storage_iterator &rhs) noexcept { + return !(lhs == rhs); +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/** + * @brief Basic storage implementation. + * + * Internal data structures arrange elements to maximize performance. There are + * no guarantees that objects are returned in the insertion order when iterate + * a storage. Do not make assumption on the order in any case. + * + * @warning + * Empty types aren't explicitly instantiated. Therefore, many of the functions + * 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. + * @tparam Allocator Type of allocator used to manage memory and elements. + */ +template +class basic_storage: public basic_sparse_set::template rebind_alloc> { + using alloc_traits = std::allocator_traits; + static_assert(std::is_same_v, "Invalid value type"); + using container_type = std::vector>; + using underlying_type = basic_sparse_set>; + using underlying_iterator = typename underlying_type::basic_iterator; + + static constexpr bool is_pinned_type_v = !(std::is_move_constructible_v && std::is_move_assignable_v); + + [[nodiscard]] auto &element_at(const std::size_t pos) const { + return payload[pos / traits_type::page_size][fast_mod(pos, traits_type::page_size)]; + } + + auto assure_at_least(const std::size_t pos) { + const auto idx = pos / traits_type::page_size; + + if(!(idx < payload.size())) { + auto curr = payload.size(); + allocator_type allocator{get_allocator()}; + payload.resize(idx + 1u, nullptr); + + ENTT_TRY { + for(const auto last = payload.size(); curr < last; ++curr) { + payload[curr] = alloc_traits::allocate(allocator, traits_type::page_size); + } + } + ENTT_CATCH { + payload.resize(curr); + ENTT_THROW; + } + } + + return payload[idx] + fast_mod(pos, traits_type::page_size); + } + + template + auto emplace_element(const Entity entt, const bool force_back, Args &&...args) { + const auto it = base_type::try_emplace(entt, force_back); + + ENTT_TRY { + auto elem = assure_at_least(static_cast(it.index())); + entt::uninitialized_construct_using_allocator(to_address(elem), get_allocator(), std::forward(args)...); + } + ENTT_CATCH { + base_type::pop(it, it + 1u); + ENTT_THROW; + } + + return it; + } + + 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(traits_type::in_place_delete) { + if(base_type::at(pos) != tombstone) { + alloc_traits::destroy(allocator, std::addressof(element_at(pos))); + } + } else { + alloc_traits::destroy(allocator, std::addressof(element_at(pos))); + } + } + + for(auto pos = from, last = payload.size(); pos < last; ++pos) { + alloc_traits::deallocate(allocator, payload[pos], traits_type::page_size); + } + + payload.resize(from); + } + +private: + const void *get_at(const std::size_t pos) const final { + return std::addressof(element_at(pos)); + } + + 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); + + 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 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(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(traits_type::in_place_delete) { + base_type::in_place_pop(first); + 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)); + 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(first.index())))); + } + } else { + base_type::swap_and_pop(first); + alloc_traits::destroy(allocator, std::addressof(element_at(static_cast(first.index())))); + } + } + } + + /** + * @brief Assigns an entity to a storage. + * @param entt A valid identifier. + * @param value Optional opaque value. + * @param force_back Force back insertion. + * @return Iterator pointing to the emplaced element. + */ + 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) { + return emplace_element(entt, force_back, *static_cast(value)); + } else { + return base_type::end(); + } + } else { + if constexpr(std::is_default_constructible_v) { + return emplace_element(entt, force_back); + } else { + return base_type::end(); + } + } + } + +public: + /*! @brief Base type. */ + using base_type = underlying_type; + /*! @brief Type of the objects assigned to entities. */ + using value_type = Type; + /*! @brief Component traits. */ + using traits_type = component_traits; + /*! @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::const_pointer; + /*! @brief Random access iterator type. */ + using iterator = internal::storage_iterator; + /*! @brief Constant random access iterator type. */ + using const_iterator = internal::storage_iterator; + /*! @brief Reverse iterator type. */ + using reverse_iterator = std::reverse_iterator; + /*! @brief Constant reverse iterator type. */ + using const_reverse_iterator = std::reverse_iterator; + /*! @brief Extended iterable storage proxy. */ + using iterable = iterable_adaptor>; + /*! @brief Constant extended iterable storage proxy. */ + using const_iterable = iterable_adaptor>; + /*! @brief Extended reverse iterable storage proxy. */ + using reverse_iterable = iterable_adaptor>; + /*! @brief Constant extended reverse iterable storage proxy. */ + using const_reverse_iterable = iterable_adaptor>; + + /*! @brief Default constructor. */ + basic_storage() + : basic_storage{allocator_type{}} {} + + /** + * @brief Constructs an empty storage with a given allocator. + * @param allocator The allocator to use. + */ + explicit basic_storage(const allocator_type &allocator) + : base_type{type_id(), deletion_policy{traits_type::in_place_delete}, allocator}, + payload{allocator} {} + + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + basic_storage(basic_storage &&other) noexcept + : base_type{std::move(other)}, + payload{std::move(other.payload)} {} + + /** + * @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}, + 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. */ + ~basic_storage() override { + shrink_to_size(0u); + } + + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This storage. + */ + basic_storage &operator=(basic_storage &&other) noexcept { + 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)); + payload = std::move(other.payload); return *this; } - [[nodiscard]] constexpr const_reference get() const noexcept { + /** + * @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(payload, other.payload); + } + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const noexcept { + return payload.get_allocator(); + } + + /** + * @brief Increases the capacity of a storage. + * + * If the new capacity is greater than the current capacity, new storage is + * allocated, otherwise the method does nothing. + * + * @param cap Desired capacity. + */ + void reserve(const size_type cap) override { + if(cap != 0u) { + base_type::reserve(cap); + assure_at_least(cap - 1u); + } + } + + /** + * @brief Returns the number of elements that a storage has currently + * allocated space for. + * @return Capacity of the storage. + */ + [[nodiscard]] size_type capacity() const noexcept override { + return payload.size() * traits_type::page_size; + } + + /*! @brief Requests the removal of unused capacity. */ + void shrink_to_fit() override { + base_type::shrink_to_fit(); + shrink_to_size(base_type::size()); + } + + /** + * @brief Direct access to the array of objects. + * @return A pointer to the array of objects. + */ + [[nodiscard]] const_pointer raw() const noexcept { + return payload.data(); + } + + /*! @copydoc raw */ + [[nodiscard]] pointer raw() noexcept { + return payload.data(); + } + + /** + * @brief Returns an iterator to the beginning. + * + * 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(base_type::size()); + return const_iterator{&payload, pos}; + } + + /*! @copydoc cbegin */ + [[nodiscard]] const_iterator begin() const noexcept { + return cbegin(); + } + + /*! @copydoc begin */ + [[nodiscard]] iterator begin() noexcept { + const auto pos = static_cast(base_type::size()); + return iterator{&payload, pos}; + } + + /** + * @brief Returns an iterator to the end. + * @return An iterator to the element following the last instance of the + * internal array. + */ + [[nodiscard]] const_iterator cend() const noexcept { + return const_iterator{&payload, {}}; + } + + /*! @copydoc cend */ + [[nodiscard]] const_iterator end() const noexcept { + return cend(); + } + + /*! @copydoc end */ + [[nodiscard]] iterator end() noexcept { + return iterator{&payload, {}}; + } + + /** + * @brief Returns a reverse iterator to the beginning. + * + * 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. + */ + [[nodiscard]] const_reverse_iterator crbegin() const noexcept { + return std::make_reverse_iterator(cend()); + } + + /*! @copydoc crbegin */ + [[nodiscard]] const_reverse_iterator rbegin() const noexcept { + return crbegin(); + } + + /*! @copydoc rbegin */ + [[nodiscard]] reverse_iterator rbegin() noexcept { + return std::make_reverse_iterator(end()); + } + + /** + * @brief Returns a reverse iterator to the end. + * @return An iterator to the element following the last instance of the + * reversed internal array. + */ + [[nodiscard]] const_reverse_iterator crend() const noexcept { + return std::make_reverse_iterator(cbegin()); + } + + /*! @copydoc crend */ + [[nodiscard]] const_reverse_iterator rend() const noexcept { + return crend(); + } + + /*! @copydoc rend */ + [[nodiscard]] reverse_iterator rend() noexcept { + return std::make_reverse_iterator(begin()); + } + + /** + * @brief Returns the object assigned to an entity. + * + * @warning + * Attempting to use an entity that doesn't belong to the storage results in + * undefined behavior. + * + * @param entt A valid identifier. + * @return The object assigned to the entity. + */ + [[nodiscard]] const value_type &get(const entity_type entt) const noexcept { + return element_at(base_type::index(entt)); + } + + /*! @copydoc get */ + [[nodiscard]] value_type &get(const entity_type entt) noexcept { + return const_cast(std::as_const(*this).get(entt)); + } + + /** + * @brief Returns the object assigned to an entity as a tuple. + * @param entt A valid identifier. + * @return The object assigned to the entity as a tuple. + */ + [[nodiscard]] std::tuple get_as_tuple(const entity_type entt) const noexcept { + return std::forward_as_tuple(get(entt)); + } + + /*! @copydoc get_as_tuple */ + [[nodiscard]] std::tuple get_as_tuple(const entity_type entt) noexcept { + return std::forward_as_tuple(get(entt)); + } + + /** + * @brief Assigns an entity to a storage and constructs its object. + * + * @warning + * Attempting to use an entity that already belongs to the storage results + * in undefined behavior. + * + * @tparam Args Types of arguments to use to construct the object. + * @param entt A valid identifier. + * @param args Parameters to use to construct an object for the entity. + * @return A reference to the newly created object. + */ + template + value_type &emplace(const entity_type entt, Args &&...args) { + if constexpr(std::is_aggregate_v && (sizeof...(Args) != 0u || !std::is_default_constructible_v)) { + const auto it = emplace_element(entt, false, Type{std::forward(args)...}); + return element_at(static_cast(it.index())); + } else { + const auto it = emplace_element(entt, false, std::forward(args)...); + return element_at(static_cast(it.index())); + } + } + + /** + * @brief Updates the instance assigned to a given entity in-place. + * @tparam Func Types of the function objects to invoke. + * @param entt A valid identifier. + * @param func Valid function objects. + * @return A reference to the updated instance. + */ + template + value_type &patch(const entity_type entt, Func &&...func) { + const auto idx = base_type::index(entt); + auto &elem = element_at(idx); + (std::forward(func)(elem), ...); + return elem; + } + + /** + * @brief Assigns one or more entities to a storage and constructs their + * objects from a given instance. + * + * @warning + * Attempting to assign an entity that already belongs to the storage + * results in undefined behavior. + * + * @tparam It Type of input iterator. + * @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 + iterator insert(It first, It last, const value_type &value = {}) { + for(; first != last; ++first) { + emplace_element(*first, true, value); + } + + return begin(); + } + + /** + * @brief Assigns one or more entities to a storage and constructs their + * objects from a given range. + * + * @sa construct + * + * @tparam EIt Type of input iterator. + * @tparam CIt Type of input iterator. + * @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::value_type, value_type>>> + iterator insert(EIt first, EIt last, CIt from) { + for(; first != last; ++first, ++from) { + emplace_element(*first, true, *from); + } + + return begin(); + } + + /** + * @brief Returns an iterable object to use to _visit_ a storage. + * + * The iterable object returns a tuple that contains the current entity and + * a reference to its component. + * + * @return An iterable object to use to _visit_ the storage. + */ + [[nodiscard]] iterable each() noexcept { + return {internal::extended_storage_iterator{base_type::begin(), begin()}, internal::extended_storage_iterator{base_type::end(), end()}}; + } + + /*! @copydoc each */ + [[nodiscard]] const_iterable each() const noexcept { + 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: + container_type payload; +}; + +/*! @copydoc basic_storage */ +template +class basic_storage::page_size == 0u>> + : public basic_sparse_set::template rebind_alloc> { + using alloc_traits = std::allocator_traits; + static_assert(std::is_same_v, "Invalid value type"); + +public: + /*! @brief Base type. */ + using base_type = basic_sparse_set>; + /*! @brief Type of the objects assigned to entities. */ + using value_type = Type; + /*! @brief Component traits. */ + using traits_type = component_traits; + /*! @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>; + /*! @brief Constant extended iterable storage proxy. */ + using const_iterable = iterable_adaptor>; + /*! @brief Extended reverse iterable storage proxy. */ + using reverse_iterable = iterable_adaptor>; + /*! @brief Constant extended reverse iterable storage proxy. */ + using const_reverse_iterable = iterable_adaptor>; + + /*! @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(), deletion_policy{traits_type::in_place_delete}, allocator} {} + + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + basic_storage(basic_storage &&other) noexcept = default; + + /** + * @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} {} + + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This storage. + */ + basic_storage &operator=(basic_storage &&other) noexcept = default; + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const noexcept { + return allocator_type{base_type::get_allocator()}; + } + + /** + * @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::contains(entt), "Storage does not contain entity"); + } + + /** + * @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::contains(entt), "Storage does not contain entity"); + return std::tuple{}; + } + + /** + * @brief Assigns an entity to a storage and constructs its object. + * + * @warning + * Attempting to use an entity that already belongs to the storage results + * in undefined behavior. + * + * @tparam Args Types of arguments to use to construct the object. + * @param entt A valid identifier. + */ + template + void emplace(const entity_type entt, Args &&...) { + base_type::try_emplace(entt, false); + } + + /** + * @brief Updates the instance assigned to a given entity in-place. + * @tparam Func Types of the function objects to invoke. + * @param entt A valid identifier. + * @param func Valid function objects. + */ + template + void patch([[maybe_unused]] const entity_type entt, Func &&...func) { + ENTT_ASSERT(base_type::contains(entt), "Storage does not contain entity"); + (std::forward(func)(), ...); + } + + /** + * @brief Assigns entities to a storage. + * @tparam It Type of input iterator. + * @tparam Args Types of optional arguments. + * @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. + */ + template + void insert(It first, It last, Args &&...) { + for(; first != last; ++first) { + base_type::try_emplace(*first, true); + } + } + + /** + * @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::begin()}, internal::extended_storage_iterator{base_type::end()}}; + } + + /*! @copydoc each */ + [[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 +class basic_storage + : public basic_sparse_set { + using alloc_traits = std::allocator_traits; + static_assert(std::is_same_v, "Invalid value type"); + using underlying_type = basic_sparse_set>; + using underlying_iterator = typename underlying_type::basic_iterator; + using local_traits_type = entt_traits; + + 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(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; + /*! @brief Type of the objects assigned to entities. */ + using value_type = Entity; + /*! @brief Component traits. */ + using traits_type = component_traits; + /*! @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>; + /*! @brief Constant extended iterable storage proxy. */ + using const_iterable = iterable_adaptor>; + /*! @brief Extended reverse iterable storage proxy. */ + using reverse_iterable = iterable_adaptor>; + /*! @brief Constant extended reverse iterable storage proxy. */ + using const_reverse_iterable = iterable_adaptor>; + + /*! @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(), 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(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 + void patch([[maybe_unused]] const entity_type entt, Func &&...func) { + ENTT_ASSERT(base_type::contains(entt), "Storage does not contain entity"); + (std::forward(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 + 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 + 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(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 + +#endif + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +class extended_group_iterator; + +template +class extended_group_iterator, get_t> { + template + auto index_to_element([[maybe_unused]] Type &cpool) const { + if constexpr(Type::traits_type::page_size == 0u) { + return std::make_tuple(); + } else { + return std::forward_as_tuple(cpool.rbegin()[it.index()]); + } + } + +public: + using iterator_type = It; + using difference_type = std::ptrdiff_t; + using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval()), std::declval().get_as_tuple({})..., std::declval().get_as_tuple({})...)); + using pointer = input_iterator_pointer; + using reference = value_type; + using iterator_category = std::input_iterator_tag; + + constexpr extended_group_iterator() + : it{}, + pools{} {} + + extended_group_iterator(It from, const std::tuple &cpools) + : it{from}, + pools{cpools} {} + + extended_group_iterator &operator++() noexcept { + return ++it, *this; + } + + extended_group_iterator operator++(int) noexcept { + extended_group_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] reference operator*() const noexcept { + return std::tuple_cat(std::make_tuple(*it), index_to_element(*std::get(pools))..., std::get(pools)->get_as_tuple(*it)...); + } + + [[nodiscard]] pointer operator->() const noexcept { + return operator*(); + } + + [[nodiscard]] constexpr iterator_type base() const noexcept { + return it; + } + + template + friend constexpr bool operator==(const extended_group_iterator &, const extended_group_iterator &) noexcept; + +private: + It it; + std::tuple pools; +}; + +template +[[nodiscard]] constexpr bool operator==(const extended_group_iterator &lhs, const extended_group_iterator &rhs) noexcept { + return lhs.it == rhs.it; +} + +template +[[nodiscard]] constexpr bool operator!=(const extended_group_iterator &lhs, const extended_group_iterator &rhs) noexcept { + 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 +class group_handler; + +template +class group_handler, get_t, exclude_t> 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...>, "Groups do not support in-place delete"); + static_assert(!std::disjunction_v..., std::is_const..., std::is_const...>, "Const storage type not allowed"); + + using base_type = std::common_type_t; + 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::value()) || ...); + } + + return cnt; + } + + [[nodiscard]] size_type length() const noexcept { + return len; + } + + template + Type pools_as() const noexcept { + return pools; + } + + template + Type filter_as() const noexcept { + return filter; + } + +private: + std::tuple pools; + std::tuple filter; + std::size_t len; +}; + +template +class group_handler, get_t, exclude_t> 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...>, "Const storage type not allowed"); + + using base_type = std::common_type_t; + 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 + 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(*std::get<0>(pools))) { + push_on_construct(entity); + } + } + + common_type &handle() noexcept { + return elem; + } + + const common_type &handle() const noexcept { + return elem; + } + + template + Type pools_as() const noexcept { + return pools; + } + + template + Type filter_as() const noexcept { + return filter; + } + +private: + std::tuple pools; + std::tuple filter; + base_type elem; }; } // namespace internal @@ -15814,237 +18356,1233 @@ struct compressed_pair_element -class compressed_pair final - : internal::compressed_pair_element, - internal::compressed_pair_element { - using first_base = internal::compressed_pair_element; - using second_base = internal::compressed_pair_element; +template +class basic_group; + +/** + * @brief Non-owning group. + * + * A non-owning group returns all entities and only the entities that are at + * least in the given storage. Moreover, it's guaranteed that the entity list is + * tightly packed in memory for fast iterations. + * + * @b Important + * + * Iterators aren't invalidated if: + * + * * 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 other cases, modifying the pools iterated by the group in any way + * invalidates all the iterators. + * + * @tparam Get Types of storage _observed_ by the group. + * @tparam Exclude Types of storage used to filter the group. + */ +template +class basic_group, get_t, exclude_t> { + using base_type = std::common_type_t; + using underlying_type = typename base_type::entity_type; + + template + static constexpr std::size_t index_of = type_list_index_v, type_list>; + + auto pools() const noexcept { + using return_type = std::tuple; + return descriptor ? descriptor->template pools_as() : return_type{}; + } + + auto filter() const noexcept { + using return_type = std::tuple; + return descriptor ? descriptor->template filter_as() : return_type{}; + } public: - /*! @brief The type of the first element that the pair stores. */ - using first_type = First; - /*! @brief The type of the second element that the pair stores. */ - using second_type = Second; + /*! @brief Underlying entity identifier. */ + using entity_type = underlying_type; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Common type among all storage types. */ + using common_type = base_type; + /*! @brief Random access iterator type. */ + using iterator = typename common_type::iterator; + /*! @brief Reversed iterator type. */ + using reverse_iterator = typename common_type::reverse_iterator; + /*! @brief Iterable group type. */ + using iterable = iterable_adaptor, get_t>>; + /*! @brief Group handler type. */ + using handler = internal::group_handler, get_t...>, exclude_t...>>; + + /*! @brief Default constructor to use to create empty, invalid groups. */ + basic_group() noexcept + : descriptor{} {} /** - * @brief Default constructor, conditionally enabled. - * - * This constructor is only available when the types that the pair stores - * are both at least default constructible. - * - * @tparam Dummy Dummy template parameter used for internal purposes. + * @brief Constructs a group from a set of storage classes. + * @param ref A reference to a group handler. */ - template && std::is_default_constructible_v>> - constexpr compressed_pair() noexcept(std::is_nothrow_default_constructible_v &&std::is_nothrow_default_constructible_v) - : first_base{}, - second_base{} {} + basic_group(handler &ref) noexcept + : descriptor{&ref} {} /** - * @brief Copy constructor. - * @param other The instance to copy from. + * @brief Returns the leading storage of a group. + * @return The leading storage of the group. */ - constexpr compressed_pair(const compressed_pair &other) noexcept(std::is_nothrow_copy_constructible_v &&std::is_nothrow_copy_constructible_v) = default; - - /** - * @brief Move constructor. - * @param other The instance to move from. - */ - constexpr compressed_pair(compressed_pair &&other) noexcept(std::is_nothrow_move_constructible_v &&std::is_nothrow_move_constructible_v) = default; - - /** - * @brief Constructs a pair from its values. - * @tparam Arg Type of value to use to initialize the first element. - * @tparam Other Type of value to use to initialize the second element. - * @param arg Value to use to initialize the first element. - * @param other Value to use to initialize the second element. - */ - template - constexpr compressed_pair(Arg &&arg, Other &&other) noexcept(std::is_nothrow_constructible_v &&std::is_nothrow_constructible_v) - : first_base{std::forward(arg)}, - second_base{std::forward(other)} {} - - /** - * @brief Constructs a pair by forwarding the arguments to its parts. - * @tparam Args Types of arguments to use to initialize the first element. - * @tparam Other Types of arguments to use to initialize the second element. - * @param args Arguments to use to initialize the first element. - * @param other Arguments to use to initialize the second element. - */ - template - constexpr compressed_pair(std::piecewise_construct_t, std::tuple args, std::tuple other) noexcept(std::is_nothrow_constructible_v &&std::is_nothrow_constructible_v) - : first_base{std::move(args), std::index_sequence_for{}}, - second_base{std::move(other), std::index_sequence_for{}} {} - - /** - * @brief Copy assignment operator. - * @param other The instance to copy from. - * @return This compressed pair object. - */ - constexpr compressed_pair &operator=(const compressed_pair &other) noexcept(std::is_nothrow_copy_assignable_v &&std::is_nothrow_copy_assignable_v) = default; - - /** - * @brief Move assignment operator. - * @param other The instance to move from. - * @return This compressed pair object. - */ - constexpr compressed_pair &operator=(compressed_pair &&other) noexcept(std::is_nothrow_move_assignable_v &&std::is_nothrow_move_assignable_v) = default; - - /** - * @brief Returns the first element that a pair stores. - * @return The first element that a pair stores. - */ - [[nodiscard]] constexpr first_type &first() noexcept { - return static_cast(*this).get(); - } - - /*! @copydoc first */ - [[nodiscard]] constexpr const first_type &first() const noexcept { - return static_cast(*this).get(); + [[nodiscard]] const common_type &handle() const noexcept { + return descriptor->handle(); } /** - * @brief Returns the second element that a pair stores. - * @return The second element that a pair stores. + * @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. */ - [[nodiscard]] constexpr second_type &second() noexcept { - return static_cast(*this).get(); - } - - /*! @copydoc second */ - [[nodiscard]] constexpr const second_type &second() const noexcept { - return static_cast(*this).get(); + template + [[nodiscard]] auto *storage() const noexcept { + return storage>(); } /** - * @brief Swaps two compressed pair objects. - * @param other The compressed pair to swap with. - */ - constexpr void swap(compressed_pair &other) noexcept(std::is_nothrow_swappable_v &&std::is_nothrow_swappable_v) { - using std::swap; - swap(first(), other.first()); - swap(second(), other.second()); - } - - /** - * @brief Extracts an element from the compressed pair. - * @tparam Index An integer value that is either 0 or 1. - * @return Returns a reference to the first element if `Index` is 0 and a - * reference to the second element if `Index` is 1. + * @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 - constexpr decltype(auto) get() noexcept { - if constexpr(Index == 0u) { - return first(); + [[nodiscard]] auto *storage() const noexcept { + constexpr auto offset = sizeof...(Get); + + if constexpr(Index < offset) { + return std::get(pools()); } else { - static_assert(Index == 1u, "Index out of bounds"); - return second(); + return std::get(filter()); } } - /*! @copydoc get */ - template - constexpr decltype(auto) get() const noexcept { - if constexpr(Index == 0u) { - return first(); - } else { - static_assert(Index == 1u, "Index out of bounds"); - return second(); + /** + * @brief Returns the number of entities that are part of the group. + * @return Number of entities that are part of the group. + */ + [[nodiscard]] size_type size() const noexcept { + return *this ? handle().size() : size_type{}; + } + + /** + * @brief Returns the number of elements that a group has currently + * allocated space for. + * @return Capacity of the group. + */ + [[nodiscard]] size_type capacity() const noexcept { + return *this ? handle().capacity() : size_type{}; + } + + /*! @brief Requests the removal of unused capacity. */ + void shrink_to_fit() { + if(*this) { + descriptor->handle().shrink_to_fit(); } } + + /** + * @brief Checks whether a group is empty. + * @return True if the group is empty, false otherwise. + */ + [[nodiscard]] bool empty() const noexcept { + return !*this || handle().empty(); + } + + /** + * @brief Returns an iterator to the first entity of the group. + * + * 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 ? handle().begin() : iterator{}; + } + + /** + * @brief Returns an iterator that is past the last entity of the group. + * @return An iterator to the entity following the last entity of the + * group. + */ + [[nodiscard]] iterator end() const noexcept { + return *this ? handle().end() : iterator{}; + } + + /** + * @brief Returns an iterator 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 ? handle().rbegin() : reverse_iterator{}; + } + + /** + * @brief Returns an iterator that is past the last entity of the reversed + * group. + * @return An iterator to the entity following the last entity of the + * reversed group. + */ + [[nodiscard]] reverse_iterator rend() const noexcept { + return *this ? handle().rend() : reverse_iterator{}; + } + + /** + * @brief Returns the first entity of the group, if any. + * @return The first entity of the group if one exists, the null entity + * otherwise. + */ + [[nodiscard]] entity_type front() const noexcept { + const auto it = begin(); + return it != end() ? *it : null; + } + + /** + * @brief Returns the last entity of the group, if any. + * @return The last entity of the group if one exists, the null entity + * otherwise. + */ + [[nodiscard]] entity_type back() const noexcept { + const auto it = rbegin(); + return it != rend() ? *it : null; + } + + /** + * @brief Finds an entity. + * @param entt A valid identifier. + * @return An iterator to the given entity if it's found, past the end + * iterator otherwise. + */ + [[nodiscard]] iterator find(const entity_type entt) const noexcept { + return *this ? handle().find(entt) : iterator{}; + } + + /** + * @brief Returns the identifier that occupies the given position. + * @param pos Position of the element to return. + * @return The identifier that occupies the given position. + */ + [[nodiscard]] entity_type operator[](const size_type pos) const { + return begin()[pos]; + } + + /** + * @brief Checks if a group is properly initialized. + * @return True if the group is properly initialized, false otherwise. + */ + [[nodiscard]] explicit operator bool() const noexcept { + return descriptor != nullptr; + } + + /** + * @brief Checks if a group contains an entity. + * @param entt A valid identifier. + * @return True if the group contains the given entity, false otherwise. + */ + [[nodiscard]] bool contains(const entity_type entt) const noexcept { + return *this && handle().contains(entt); + } + + /** + * @brief Returns the components assigned to the given entity. + * + * @warning + * Attempting to use an entity that doesn't belong to the group results in + * undefined behavior. + * + * @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 + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + return get, index_of...>(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 + [[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(cpools)->get(entt), ...); + } else { + return std::tuple_cat(std::get(cpools)->get_as_tuple(entt)...); + } + } + + /** + * @brief Iterates entities and components and applies the given function + * object to them. + * + * The function object is invoked for each entity. It is provided with the + * entity itself and a set of references to non-empty components. The + * _constness_ of the components is as requested.
+ * The signature of the function must be equivalent to one of the following + * forms: + * + * @code{.cpp} + * void(const entity_type, Type &...); + * void(Type &...); + * @endcode + * + * @note + * Empty types aren't explicitly instantiated and therefore they are never + * returned during iterations. + * + * @tparam Func Type of the function object to invoke. + * @param func A valid function object. + */ + template + void each(Func func) const { + for(const auto entt: *this) { + if constexpr(is_applicable_v{}, std::declval().get({})))>) { + std::apply(func, std::tuple_cat(std::make_tuple(entt), get(entt))); + } else { + std::apply(func, get(entt)); + } + } + } + + /** + * @brief Returns an iterable object to use to _visit_ a group. + * + * The iterable object returns tuples that contain the current entity and a + * set of references to its non-empty components. The _constness_ of the + * components is as requested. + * + * @note + * Empty types aren't explicitly instantiated and therefore they are never + * returned during iterations. + * + * @return An iterable object to use to _visit_ the group. + */ + [[nodiscard]] iterable each() const noexcept { + const auto cpools = pools(); + return iterable{{begin(), cpools}, {end(), cpools}}; + } + + /** + * @brief Sort a group according to the given comparison function. + * + * 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: + * + * @code{.cpp} + * bool(std::tuple, std::tuple); + * bool(const Type &..., const Type &...); + * bool(const Entity, const Entity); + * @endcode + * + * Where `Type` are such that they are iterated by the group.
+ * Moreover, the comparison function object shall induce a + * _strict weak ordering_ on the values. + * + * The sort function object must offer a member function template + * `operator()` that accepts three arguments: + * + * * An iterator to the first element of the range to sort. + * * An iterator past the last element of the range to sort. + * * A comparison function to use to compare the elements. + * + * @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. + * @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 + void sort(Compare compare, Sort algo = Sort{}, Args &&...args) { + sort, index_of...>(std::move(compare), std::move(algo), std::forward(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 + void sort(Compare compare, Sort algo = Sort{}, Args &&...args) { + if(*this) { + if constexpr(sizeof...(Index) == 0) { + static_assert(std::is_invocable_v, "Invalid comparison function"); + descriptor->handle().sort(std::move(compare), std::move(algo), std::forward(args)...); + } else { + auto comp = [&compare, cpools = pools()](const entity_type lhs, const entity_type rhs) { + if constexpr(sizeof...(Index) == 1) { + return compare((std::get(cpools)->get(lhs), ...), (std::get(cpools)->get(rhs), ...)); + } else { + return compare(std::forward_as_tuple(std::get(cpools)->get(lhs)...), std::forward_as_tuple(std::get(cpools)->get(rhs)...)); + } + }; + + descriptor->handle().sort(std::move(comp), std::move(algo), std::forward(args)...); + } + } + } + + /** + * @brief Sort the shared pool of entities according to a given storage. + * + * The shared pool of entities and thus its order is affected by the changes + * to each and every pool that it tracks. + * + * @param other The storage to use to impose the order. + */ + void sort_as(const common_type &other) const { + if(*this) { + descriptor->handle().sort_as(other); + } + } + +private: + handler *descriptor; }; /** - * @brief Deduction guide. - * @tparam Type Type of value to use to initialize the first element. - * @tparam Other Type of value to use to initialize the second element. + * @brief Owning group. + * + * Owning groups returns all entities and only the entities that are at + * least in the given storage. Moreover: + * + * * It's guaranteed that the entity list is tightly packed in memory for fast + * iterations. + * * It's guaranteed that all components in the owned storage are tightly packed + * in memory for even faster iterations and to allow direct access. + * * They stay true to the order of the owned storage and all instances have the + * same order in memory. + * + * The more types of storage are owned, the faster it is to iterate a group. + * + * @b Important + * + * Iterators aren't invalidated if: + * + * * 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 other cases, modifying the pools iterated by the group in any way + * invalidates all the iterators. + * + * @tparam Owned Types of storage _owned_ by the group. + * @tparam Get Types of storage _observed_ by the group. + * @tparam Exclude Types of storage used to filter the group. */ -template -compressed_pair(Type &&, Other &&) -> compressed_pair, std::decay_t>; +template +class basic_group, get_t, exclude_t> { + using base_type = std::common_type_t; + using underlying_type = typename base_type::entity_type; -/** - * @brief Swaps two compressed pair objects. - * @tparam First The type of the first element that the pairs store. - * @tparam Second The type of the second element that the pairs store. - * @param lhs A valid compressed pair object. - * @param rhs A valid compressed pair object. - */ -template -inline constexpr void swap(compressed_pair &lhs, compressed_pair &rhs) { - lhs.swap(rhs); -} + template + static constexpr std::size_t index_of = type_list_index_v, type_list>; + + auto pools() const noexcept { + using return_type = std::tuple; + return descriptor ? descriptor->template pools_as() : return_type{}; + } + + auto filter() const noexcept { + using return_type = std::tuple; + return descriptor ? descriptor->template filter_as() : return_type{}; + } + +public: + /*! @brief Underlying entity identifier. */ + using entity_type = underlying_type; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Common type among all storage types. */ + using common_type = base_type; + /*! @brief Random access iterator type. */ + using iterator = typename common_type::iterator; + /*! @brief Reversed iterator type. */ + using reverse_iterator = typename common_type::reverse_iterator; + /*! @brief Iterable group type. */ + using iterable = iterable_adaptor, get_t>>; + /*! @brief Group handler type. */ + using handler = internal::group_handler...>, get_t...>, exclude_t...>>; + + /*! @brief Default constructor to use to create empty, invalid groups. */ + basic_group() noexcept + : descriptor{} {} + + /** + * @brief Constructs a group from a set of storage classes. + * @param ref A reference to a group handler. + */ + basic_group(handler &ref) noexcept + : descriptor{&ref} {} + + /** + * @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 + [[nodiscard]] auto *storage() const noexcept { + return storage>(); + } + + /** + * @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 + [[nodiscard]] auto *storage() const noexcept { + constexpr auto offset = sizeof...(Owned) + sizeof...(Get); + + if constexpr(Index < offset) { + return std::get(pools()); + } else { + return std::get(filter()); + } + } + + /** + * @brief Returns the number of entities that that are part of the group. + * @return Number of entities that that are part of the group. + */ + [[nodiscard]] size_type size() const noexcept { + return *this ? descriptor->length() : size_type{}; + } + + /** + * @brief Checks whether a group is empty. + * @return True if the group is empty, false otherwise. + */ + [[nodiscard]] bool empty() const noexcept { + return !*this || !descriptor->length(); + } + + /** + * @brief Returns an iterator to the first entity of the group. + * + * 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 ? (handle().end() - descriptor->length()) : iterator{}; + } + + /** + * @brief Returns an iterator that is past the last entity of the group. + * @return An iterator to the entity following the last entity of the + * group. + */ + [[nodiscard]] iterator end() const noexcept { + return *this ? handle().end() : iterator{}; + } + + /** + * @brief Returns an iterator 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 ? handle().rbegin() : reverse_iterator{}; + } + + /** + * @brief Returns an iterator that is past the last entity of the reversed + * group. + * @return An iterator to the entity following the last entity of the + * reversed group. + */ + [[nodiscard]] reverse_iterator rend() const noexcept { + return *this ? (handle().rbegin() + descriptor->length()) : reverse_iterator{}; + } + + /** + * @brief Returns the first entity of the group, if any. + * @return The first entity of the group if one exists, the null entity + * otherwise. + */ + [[nodiscard]] entity_type front() const noexcept { + const auto it = begin(); + return it != end() ? *it : null; + } + + /** + * @brief Returns the last entity of the group, if any. + * @return The last entity of the group if one exists, the null entity + * otherwise. + */ + [[nodiscard]] entity_type back() const noexcept { + const auto it = rbegin(); + return it != rend() ? *it : null; + } + + /** + * @brief Finds an entity. + * @param entt A valid identifier. + * @return An iterator to the given entity if it's found, past the end + * iterator otherwise. + */ + [[nodiscard]] iterator find(const entity_type entt) const noexcept { + const auto it = *this ? handle().find(entt) : iterator{}; + return it >= begin() ? it : iterator{}; + } + + /** + * @brief Returns the identifier that occupies the given position. + * @param pos Position of the element to return. + * @return The identifier that occupies the given position. + */ + [[nodiscard]] entity_type operator[](const size_type pos) const { + return begin()[pos]; + } + + /** + * @brief Checks if a group is properly initialized. + * @return True if the group is properly initialized, false otherwise. + */ + [[nodiscard]] explicit operator bool() const noexcept { + return descriptor != nullptr; + } + + /** + * @brief Checks if a group contains an entity. + * @param entt A valid identifier. + * @return True if the group contains the given entity, false otherwise. + */ + [[nodiscard]] bool contains(const entity_type entt) const noexcept { + return *this && handle().contains(entt) && (handle().index(entt) < (descriptor->length())); + } + + /** + * @brief Returns the components assigned to the given entity. + * + * @warning + * Attempting to use an entity that doesn't belong to the group results in + * undefined behavior. + * + * @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 + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + return get, index_of...>(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 + [[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(cpools)->get(entt), ...); + } else { + return std::tuple_cat(std::get(cpools)->get_as_tuple(entt)...); + } + } + + /** + * @brief Iterates entities and components and applies the given function + * object to them. + * + * The function object is invoked for each entity. It is provided with the + * entity itself and a set of references to non-empty components. The + * _constness_ of the components is as requested.
+ * The signature of the function must be equivalent to one of the following + * forms: + * + * @code{.cpp} + * void(const entity_type, Type &...); + * void(Type &...); + * @endcode + * + * @note + * Empty types aren't explicitly instantiated and therefore they are never + * returned during iterations. + * + * @tparam Func Type of the function object to invoke. + * @param func A valid function object. + */ + template + void each(Func func) const { + for(auto args: each()) { + if constexpr(is_applicable_v{}, std::declval().get({})))>) { + std::apply(func, args); + } else { + std::apply([&func](auto, auto &&...less) { func(std::forward(less)...); }, args); + } + } + } + + /** + * @brief Returns an iterable object to use to _visit_ a group. + * + * The iterable object returns tuples that contain the current entity and a + * set of references to its non-empty components. The _constness_ of the + * components is as requested. + * + * @note + * Empty types aren't explicitly instantiated and therefore they are never + * returned during iterations. + * + * @return An iterable object to use to _visit_ the group. + */ + [[nodiscard]] iterable each() const noexcept { + const auto cpools = pools(); + return {{begin(), cpools}, {end(), cpools}}; + } + + /** + * @brief Sort a group according to the given comparison function. + * + * 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: + * + * @code{.cpp} + * bool(std::tuple, std::tuple); + * bool(const Type &, const Type &); + * bool(const Entity, const Entity); + * @endcode + * + * Where `Type` are either owned types or not but still such that they are + * iterated by the group.
+ * Moreover, the comparison function object shall induce a + * _strict weak ordering_ on the values. + * + * The sort function object must offer a member function template + * `operator()` that accepts three arguments: + * + * * An iterator to the first element of the range to sort. + * * An iterator past the last element of the range to sort. + * * A comparison function to use to compare the elements. + * + * @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. + * @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 + void sort(Compare compare, Sort algo = Sort{}, Args &&...args) const { + sort, index_of...>(std::move(compare), std::move(algo), std::forward(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 + 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, "Invalid comparison function"); + storage<0>()->sort_n(descriptor->length(), std::move(compare), std::move(algo), std::forward(args)...); + } else { + auto comp = [&compare, &cpools](const entity_type lhs, const entity_type rhs) { + if constexpr(sizeof...(Index) == 1) { + return compare((std::get(cpools)->get(lhs), ...), (std::get(cpools)->get(rhs), ...)); + } else { + return compare(std::forward_as_tuple(std::get(cpools)->get(lhs)...), std::forward_as_tuple(std::get(cpools)->get(rhs)...)); + } + }; + + storage<0>()->sort_n(descriptor->length(), std::move(comp), std::move(algo), std::forward(args)...); + } + + 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), ...); + } + }; + + std::apply(cb, cpools); + } + +private: + handler *descriptor; +}; } // namespace entt -// disable structured binding support for clang 6, it messes when specializing tuple_size -#if !defined __clang_major__ || __clang_major__ > 6 -namespace std { - -/** - * @brief `std::tuple_size` specialization for `compressed_pair`s. - * @tparam First The type of the first element that the pair stores. - * @tparam Second The type of the second element that the pair stores. - */ -template -struct tuple_size>: integral_constant {}; - -/** - * @brief `std::tuple_element` specialization for `compressed_pair`s. - * @tparam Index The index of the type to return. - * @tparam First The type of the first element that the pair stores. - * @tparam Second The type of the second element that the pair stores. - */ -template -struct tuple_element>: conditional { - static_assert(Index < 2u, "Index out of bounds"); -}; - -} // namespace std #endif -#endif +// #include "entity/handle.hpp" +#ifndef ENTT_ENTITY_HANDLE_HPP +#define ENTT_ENTITY_HANDLE_HPP +#include +#include +#include +#include // #include "../core/iterator.hpp" -// #include "../core/memory.hpp" - -// #include "../core/type_info.hpp" - -// #include "component.hpp" +// #include "../core/type_traits.hpp" // #include "entity.hpp" // #include "fwd.hpp" -// #include "sparse_set.hpp" -// #include "storage_mixin.hpp" -#ifndef ENTT_ENTITY_SIGH_STORAGE_MIXIN_HPP -#define ENTT_ENTITY_SIGH_STORAGE_MIXIN_HPP +namespace entt { -#include -// #include "../config/config.h" +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ -// #include "../core/any.hpp" +namespace internal { -// #include "../signal/sigh.hpp" -#ifndef ENTT_SIGNAL_SIGH_HPP -#define ENTT_SIGNAL_SIGH_HPP +template +class handle_storage_iterator final { + template + friend class handle_storage_iterator; -#include -#include + using underlying_type = std::remove_reference_t; + using entity_type = typename underlying_type::entity_type; + +public: + using value_type = typename std::iterator_traits::value_type; + using pointer = input_iterator_pointer; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = std::input_iterator_tag; + + constexpr handle_storage_iterator() noexcept + : entt{null}, + it{}, + last{} {} + + constexpr handle_storage_iterator(entity_type value, It from, It to) noexcept + : entt{value}, + it{from}, + last{to} { + while(it != last && !it->second.contains(entt)) { + ++it; + } + } + + constexpr handle_storage_iterator &operator++() noexcept { + while(++it != last && !it->second.contains(entt)) {} + return *this; + } + + constexpr handle_storage_iterator operator++(int) noexcept { + handle_storage_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] constexpr reference operator*() const noexcept { + return *it; + } + + [[nodiscard]] constexpr pointer operator->() const noexcept { + return operator*(); + } + + template + friend constexpr bool operator==(const handle_storage_iterator &, const handle_storage_iterator &) noexcept; + +private: + entity_type entt; + It it; + It last; +}; + +template +[[nodiscard]] constexpr bool operator==(const handle_storage_iterator &lhs, const handle_storage_iterator &rhs) noexcept { + return lhs.it == rhs.it; +} + +template +[[nodiscard]] constexpr bool operator!=(const handle_storage_iterator &lhs, const handle_storage_iterator &rhs) noexcept { + return !(lhs == rhs); +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/** + * @brief Non-owning handle to an entity. + * + * Tiny wrapper around a registry and an entity. + * + * @tparam Registry Basic registry type. + * @tparam Scope Types to which to restrict the scope of a handle. + */ +template +struct basic_handle { + /*! @brief Type of registry accepted by the handle. */ + using registry_type = Registry; + /*! @brief Underlying entity identifier. */ + using entity_type = typename registry_type::entity_type; + /*! @brief Underlying version type. */ + using version_type = typename registry_type::version_type; + /*! @brief Unsigned integer type. */ + using size_type = typename registry_type::size_type; + + /*! @brief Constructs an invalid handle. */ + basic_handle() noexcept + : reg{}, + entt{null} {} + + /** + * @brief Constructs a handle from a given registry and entity. + * @param ref An instance of the registry class. + * @param value A valid identifier. + */ + basic_handle(registry_type &ref, entity_type value) noexcept + : reg{&ref}, + entt{value} {} + + /** + * @brief Returns an iterable object to use to _visit_ a handle. + * + * The iterable object returns a pair that contains the name and a reference + * to the current storage.
+ * Returned storage are those that contain the entity associated with the + * handle. + * + * @return An iterable object to use to _visit_ the handle. + */ + [[nodiscard]] auto storage() const noexcept { + auto iterable = reg->storage(); + using iterator_type = internal::handle_storage_iterator; + return iterable_adaptor{iterator_type{entt, iterable.begin(), iterable.end()}, iterator_type{entt, iterable.end(), iterable.end()}}; + } + + /** + * @brief Constructs a const handle from a non-const one. + * @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. + */ + template + operator basic_handle() const noexcept { + static_assert(std::is_same_v || std::is_same_v, Registry>, "Invalid conversion between different handles"); + static_assert((sizeof...(Scope) == 0 || ((sizeof...(Args) != 0 && sizeof...(Args) <= sizeof...(Scope)) && ... && (type_list_contains_v, Args>))), "Invalid conversion between different handles"); + + return reg ? basic_handle{*reg, entt} : basic_handle{}; + } + + /** + * @brief Converts a handle to its underlying entity. + * @return The contained identifier. + */ + [[nodiscard]] operator entity_type() const noexcept { + return entity(); + } + + /** + * @brief Checks if a handle refers to non-null registry pointer and entity. + * @return True if the handle refers to non-null registry and entity, false otherwise. + */ + [[nodiscard]] explicit operator bool() const noexcept { + return reg && reg->valid(entt); + } + + /** + * @brief Checks if a handle refers to a valid entity or not. + * @return True if the handle refers to a valid entity, false otherwise. + */ + [[nodiscard]] bool valid() const { + return reg->valid(entt); + } + + /** + * @brief Returns a pointer to the underlying registry, if any. + * @return A pointer to the underlying registry, if any. + */ + [[nodiscard]] registry_type *registry() const noexcept { + return reg; + } + + /** + * @brief Returns the entity associated with a handle. + * @return The entity associated with the handle. + */ + [[nodiscard]] entity_type entity() const noexcept { + return entt; + } + + /*! @brief Destroys the entity associated with a handle. */ + void destroy() { + reg->destroy(std::exchange(entt, null)); + } + + /** + * @brief Destroys the entity associated with a handle. + * @param version A desired version upon destruction. + */ + void destroy(const version_type version) { + reg->destroy(std::exchange(entt, null), version); + } + + /** + * @brief Assigns the given component to a handle. + * @tparam Component Type of component to create. + * @tparam Args Types of arguments to use to construct the component. + * @param args Parameters to use to initialize the component. + * @return A reference to the newly created component. + */ + template + decltype(auto) emplace(Args &&...args) const { + static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v), "Invalid type"); + return reg->template emplace(entt, std::forward(args)...); + } + + /** + * @brief Assigns or replaces the given component for a handle. + * @tparam Component Type of component to assign or replace. + * @tparam Args Types of arguments to use to construct the component. + * @param args Parameters to use to initialize the component. + * @return A reference to the newly created component. + */ + template + decltype(auto) emplace_or_replace(Args &&...args) const { + static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v), "Invalid type"); + return reg->template emplace_or_replace(entt, std::forward(args)...); + } + + /** + * @brief Patches the given component for a handle. + * @tparam Component Type of component to patch. + * @tparam Func Types of the function objects to invoke. + * @param func Valid function objects. + * @return A reference to the patched component. + */ + template + decltype(auto) patch(Func &&...func) const { + static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v), "Invalid type"); + return reg->template patch(entt, std::forward(func)...); + } + + /** + * @brief Replaces the given component for a handle. + * @tparam Component Type of component to replace. + * @tparam Args Types of arguments to use to construct the component. + * @param args Parameters to use to initialize the component. + * @return A reference to the component being replaced. + */ + template + decltype(auto) replace(Args &&...args) const { + static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v), "Invalid type"); + return reg->template replace(entt, std::forward(args)...); + } + + /** + * @brief Removes the given components from a handle. + * @tparam Component Types of components to remove. + * @return The number of components actually removed. + */ + template + size_type remove() const { + static_assert(sizeof...(Scope) == 0 || (type_list_contains_v, Component> && ...), "Invalid type"); + return reg->template remove(entt); + } + + /** + * @brief Erases the given components from a handle. + * @tparam Component Types of components to erase. + */ + template + void erase() const { + static_assert(sizeof...(Scope) == 0 || (type_list_contains_v, Component> && ...), "Invalid type"); + reg->template erase(entt); + } + + /** + * @brief Checks if a handle has all the given components. + * @tparam Component Components for which to perform the check. + * @return True if the handle has all the components, false otherwise. + */ + template + [[nodiscard]] decltype(auto) all_of() const { + return reg->template all_of(entt); + } + + /** + * @brief Checks if a handle has at least one of the given components. + * @tparam Component Components for which to perform the check. + * @return True if the handle has at least one of the given components, + * false otherwise. + */ + template + [[nodiscard]] decltype(auto) any_of() const { + return reg->template any_of(entt); + } + + /** + * @brief Returns references to the given components for a handle. + * @tparam Component Types of components to get. + * @return References to the components owned by the handle. + */ + template + [[nodiscard]] decltype(auto) get() const { + static_assert(sizeof...(Scope) == 0 || (type_list_contains_v, Component> && ...), "Invalid type"); + return reg->template get(entt); + } + + /** + * @brief Returns a reference to the given component for a handle. + * @tparam Component Type of component to get. + * @tparam Args Types of arguments to use to construct the component. + * @param args Parameters to use to initialize the component. + * @return Reference to the component owned by the handle. + */ + template + [[nodiscard]] decltype(auto) get_or_emplace(Args &&...args) const { + static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v), "Invalid type"); + return reg->template get_or_emplace(entt, std::forward(args)...); + } + + /** + * @brief Returns pointers to the given components for a handle. + * @tparam Component Types of components to get. + * @return Pointers to the components owned by the handle. + */ + template + [[nodiscard]] auto try_get() const { + static_assert(sizeof...(Scope) == 0 || (type_list_contains_v, Component> && ...), "Invalid type"); + return reg->template try_get(entt); + } + + /** + * @brief Checks if a handle has components assigned. + * @return True if the handle has no components assigned, false otherwise. + */ + [[nodiscard]] bool orphan() const { + return reg->orphan(entt); + } + +private: + registry_type *reg; + entity_type entt; +}; + +/** + * @brief Compares two handles. + * @tparam Args Scope of the first handle. + * @tparam Other Scope of the second handle. + * @param lhs A valid handle. + * @param rhs A valid handle. + * @return True if both handles refer to the same registry and the same + * entity, false otherwise. + */ +template +[[nodiscard]] bool operator==(const basic_handle &lhs, const basic_handle &rhs) noexcept { + return lhs.registry() == rhs.registry() && lhs.entity() == rhs.entity(); +} + +/** + * @brief Compares two handles. + * @tparam Args Scope of the first handle. + * @tparam Other Scope of the second handle. + * @param lhs A valid handle. + * @param rhs A valid handle. + * @return False if both handles refer to the same registry and the same + * entity, true otherwise. + */ +template +[[nodiscard]] bool operator!=(const basic_handle &lhs, const basic_handle &rhs) noexcept { + return !(lhs == rhs); +} + +} // namespace entt + +#endif + +// #include "entity/helper.hpp" +#ifndef ENTT_ENTITY_HELPER_HPP +#define ENTT_ENTITY_HELPER_HPP + +#include #include #include -#include -// #include "delegate.hpp" +// #include "../core/fwd.hpp" + +// #include "../core/type_traits.hpp" + +// #include "../signal/delegate.hpp" #ifndef ENTT_SIGNAL_DELEGATE_HPP #define ENTT_SIGNAL_DELEGATE_HPP @@ -16072,8 +19610,8 @@ struct tuple_element>: conditional>: conditional>: conditional #include +#include #include #include // #include "../config/config.h" @@ -16186,8 +19727,8 @@ struct tuple_element>: conditional>: conditional::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 struct size_of: std::integral_constant {}; @@ -16586,7 +20128,8 @@ struct type_list_contains; * @tparam Other Type to look for. */ template -struct type_list_contains, Other>: std::disjunction...> {}; +struct type_list_contains, Other> + : std::bool_constant<(std::is_same_v || ...)> {}; /** * @brief Helper variable template. @@ -16674,10 +20217,20 @@ struct value_list_element> */ template struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = typename value_list_element::type; + /** * @brief Helper type. * @tparam Index Index of the value to return. @@ -16686,6 +20239,58 @@ struct value_list_element<0u, value_list> { template inline constexpr auto value_list_element_v = value_list_element::value; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +struct value_list_index> { + static_assert(value_list_index>::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 +struct value_list_index> { + /*! @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 +inline constexpr std::size_t value_list_index_v = value_list_index::value; + /** * @brief Concatenates multiple value lists. * @tparam Value Values provided by the first value list. @@ -16737,6 +20342,89 @@ struct value_list_cat> { template using value_list_cat_t = typename value_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = std::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = typename value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : std::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +class value_list_diff, value_list> { + using v141_toolset_workaround = value_list; + +public: + /*! @brief A value list that is the difference between the two value lists. */ + using type = value_list_cat_t, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = typename value_list_diff::type; + /*! @brief Same as std::is_invocable, but with tuples. */ template struct is_applicable: std::false_type {}; @@ -16857,7 +20545,7 @@ inline constexpr bool is_iterator_v = is_iterator::value; */ template struct is_ebco_eligible - : std::conjunction, std::negation>> {}; + : std::bool_constant && !std::is_final_v> {}; /** * @brief Helper variable template. @@ -16948,6 +20636,10 @@ template struct is_equality_comparable() == std::declval())>> : std::bool_constant(choice<2>)> {}; +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: std::false_type {}; + /** * @brief Helper variable template. * @tparam Type The type to test. @@ -17044,6 +20736,18 @@ using nth_argument_t = typename nth_argument::type; } // namespace entt +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::type_list_element> {}; + +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::value_list_element> {}; + #endif // #include "fwd.hpp" @@ -17161,7 +20865,13 @@ class delegate { [[nodiscard]] auto wrap(std::index_sequence) noexcept { return [](const void *, Args... args) -> Ret { [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); - return static_cast(std::invoke(Candidate, std::forward>>(std::get(arguments))...)); + + if constexpr(std::is_invocable_r_v>...>) { + return static_cast(std::invoke(Candidate, std::forward>>(std::get(arguments))...)); + } else { + constexpr auto offset = sizeof...(Args) - sizeof...(Index); + return static_cast(std::invoke(Candidate, std::forward>>(std::get(arguments))...)); + } }; } @@ -17170,7 +20880,13 @@ class delegate { return [](const void *payload, Args... args) -> Ret { [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); Type *curr = static_cast(const_cast *>(payload)); - return static_cast(std::invoke(Candidate, *curr, std::forward>>(std::get(arguments))...)); + + if constexpr(std::is_invocable_r_v>...>) { + return static_cast(std::invoke(Candidate, *curr, std::forward>>(std::get(arguments))...)); + } else { + constexpr auto offset = sizeof...(Args) - sizeof...(Index); + return static_cast(std::invoke(Candidate, *curr, std::forward>>(std::get(arguments))...)); + } }; } @@ -17179,7 +20895,13 @@ class delegate { return [](const void *payload, Args... args) -> Ret { [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); Type *curr = static_cast(const_cast *>(payload)); - return static_cast(std::invoke(Candidate, curr, std::forward>>(std::get(arguments))...)); + + if constexpr(std::is_invocable_r_v>...>) { + return static_cast(std::invoke(Candidate, curr, std::forward>>(std::get(arguments))...)); + } else { + constexpr auto offset = sizeof...(Args) - sizeof...(Index); + return static_cast(std::invoke(Candidate, curr, std::forward>>(std::get(arguments))...)); + } }; } @@ -17318,6 +21040,2649 @@ 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. + */ + [[nodiscard]] const void *data() const noexcept { + return instance; + } + + /** + * @brief Triggers a delegate. + * + * The delegate invokes the underlying function and returns the result. + * + * @warning + * Attempting to trigger an invalid delegate results in undefined + * behavior. + * + * @param args Arguments to use to invoke the underlying function. + * @return The value returned by the underlying function. + */ + Ret operator()(Args... args) const { + ENTT_ASSERT(static_cast(*this), "Uninitialized delegate"); + return fn(instance, std::forward(args)...); + } + + /** + * @brief Checks whether a delegate actually stores a listener. + * @return False if the delegate is empty, true otherwise. + */ + [[nodiscard]] explicit operator bool() const noexcept { + // no need to also test instance + return !(fn == nullptr); + } + + /** + * @brief Compares the contents of two delegates. + * @param other Delegate with which to compare. + * @return False if the two contents differ, true otherwise. + */ + [[nodiscard]] bool operator==(const delegate &other) const noexcept { + return fn == other.fn && instance == other.instance; + } + +private: + const void *instance; + function_type *fn; +}; + +/** + * @brief Compares the contents of two delegates. + * @tparam Ret Return type of a function type. + * @tparam Args Types of arguments of a function type. + * @param lhs A valid delegate object. + * @param rhs A valid delegate object. + * @return True if the two contents differ, false otherwise. + */ +template +[[nodiscard]] bool operator!=(const delegate &lhs, const delegate &rhs) noexcept { + return !(lhs == rhs); +} + +/** + * @brief Deduction guide. + * @tparam Candidate Function or member to connect to the delegate. + */ +template +delegate(connect_arg_t) -> delegate>>; + +/** + * @brief Deduction guide. + * @tparam Candidate Function or member to connect to the delegate. + * @tparam Type Type of class or type of payload. + */ +template +delegate(connect_arg_t, Type &&) -> delegate>>; + +/** + * @brief Deduction guide. + * @tparam Ret Return type of a function type. + * @tparam Args Types of arguments of a function type. + */ +template +delegate(Ret (*)(const void *, Args...), const void * = nullptr) -> delegate; + +} // namespace entt + +#endif + +// #include "fwd.hpp" + +// #include "group.hpp" +#ifndef ENTT_ENTITY_GROUP_HPP +#define ENTT_ENTITY_GROUP_HPP + +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/fwd.hpp" + +// #include "../core/iterator.hpp" + +// #include "../core/type_info.hpp" + +// #include "../core/type_traits.hpp" + +// #include "entity.hpp" + +// #include "fwd.hpp" + +// #include "sparse_set.hpp" + +// #include "storage.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +class extended_group_iterator; + +template +class extended_group_iterator, get_t> { + template + auto index_to_element([[maybe_unused]] Type &cpool) const { + if constexpr(Type::traits_type::page_size == 0u) { + return std::make_tuple(); + } else { + return std::forward_as_tuple(cpool.rbegin()[it.index()]); + } + } + +public: + using iterator_type = It; + using difference_type = std::ptrdiff_t; + using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval()), std::declval().get_as_tuple({})..., std::declval().get_as_tuple({})...)); + using pointer = input_iterator_pointer; + using reference = value_type; + using iterator_category = std::input_iterator_tag; + + constexpr extended_group_iterator() + : it{}, + pools{} {} + + extended_group_iterator(It from, const std::tuple &cpools) + : it{from}, + pools{cpools} {} + + extended_group_iterator &operator++() noexcept { + return ++it, *this; + } + + extended_group_iterator operator++(int) noexcept { + extended_group_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] reference operator*() const noexcept { + return std::tuple_cat(std::make_tuple(*it), index_to_element(*std::get(pools))..., std::get(pools)->get_as_tuple(*it)...); + } + + [[nodiscard]] pointer operator->() const noexcept { + return operator*(); + } + + [[nodiscard]] constexpr iterator_type base() const noexcept { + return it; + } + + template + friend constexpr bool operator==(const extended_group_iterator &, const extended_group_iterator &) noexcept; + +private: + It it; + std::tuple pools; +}; + +template +[[nodiscard]] constexpr bool operator==(const extended_group_iterator &lhs, const extended_group_iterator &rhs) noexcept { + return lhs.it == rhs.it; +} + +template +[[nodiscard]] constexpr bool operator!=(const extended_group_iterator &lhs, const extended_group_iterator &rhs) noexcept { + 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 +class group_handler; + +template +class group_handler, get_t, exclude_t> 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...>, "Groups do not support in-place delete"); + static_assert(!std::disjunction_v..., std::is_const..., std::is_const...>, "Const storage type not allowed"); + + using base_type = std::common_type_t; + 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::value()) || ...); + } + + return cnt; + } + + [[nodiscard]] size_type length() const noexcept { + return len; + } + + template + Type pools_as() const noexcept { + return pools; + } + + template + Type filter_as() const noexcept { + return filter; + } + +private: + std::tuple pools; + std::tuple filter; + std::size_t len; +}; + +template +class group_handler, get_t, exclude_t> 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...>, "Const storage type not allowed"); + + using base_type = std::common_type_t; + 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 + 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(*std::get<0>(pools))) { + push_on_construct(entity); + } + } + + common_type &handle() noexcept { + return elem; + } + + const common_type &handle() const noexcept { + return elem; + } + + template + Type pools_as() const noexcept { + return pools; + } + + template + Type filter_as() const noexcept { + return filter; + } + +private: + std::tuple pools; + std::tuple filter; + base_type elem; +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/** + * @brief Group. + * + * Primary template isn't defined on purpose. All the specializations give a + * compile-time error, but for a few reasonable cases. + */ +template +class basic_group; + +/** + * @brief Non-owning group. + * + * A non-owning group returns all entities and only the entities that are at + * least in the given storage. Moreover, it's guaranteed that the entity list is + * tightly packed in memory for fast iterations. + * + * @b Important + * + * Iterators aren't invalidated if: + * + * * 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 other cases, modifying the pools iterated by the group in any way + * invalidates all the iterators. + * + * @tparam Get Types of storage _observed_ by the group. + * @tparam Exclude Types of storage used to filter the group. + */ +template +class basic_group, get_t, exclude_t> { + using base_type = std::common_type_t; + using underlying_type = typename base_type::entity_type; + + template + static constexpr std::size_t index_of = type_list_index_v, type_list>; + + auto pools() const noexcept { + using return_type = std::tuple; + return descriptor ? descriptor->template pools_as() : return_type{}; + } + + auto filter() const noexcept { + using return_type = std::tuple; + return descriptor ? descriptor->template filter_as() : return_type{}; + } + +public: + /*! @brief Underlying entity identifier. */ + using entity_type = underlying_type; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Common type among all storage types. */ + using common_type = base_type; + /*! @brief Random access iterator type. */ + using iterator = typename common_type::iterator; + /*! @brief Reversed iterator type. */ + using reverse_iterator = typename common_type::reverse_iterator; + /*! @brief Iterable group type. */ + using iterable = iterable_adaptor, get_t>>; + /*! @brief Group handler type. */ + using handler = internal::group_handler, get_t...>, exclude_t...>>; + + /*! @brief Default constructor to use to create empty, invalid groups. */ + basic_group() noexcept + : descriptor{} {} + + /** + * @brief Constructs a group from a set of storage classes. + * @param ref A reference to a group handler. + */ + basic_group(handler &ref) noexcept + : descriptor{&ref} {} + + /** + * @brief Returns the leading storage of a group. + * @return The leading storage of the group. + */ + [[nodiscard]] const common_type &handle() const noexcept { + return descriptor->handle(); + } + + /** + * @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 + [[nodiscard]] auto *storage() const noexcept { + return storage>(); + } + + /** + * @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 + [[nodiscard]] auto *storage() const noexcept { + constexpr auto offset = sizeof...(Get); + + if constexpr(Index < offset) { + return std::get(pools()); + } else { + return std::get(filter()); + } + } + + /** + * @brief Returns the number of entities that are part of the group. + * @return Number of entities that are part of the group. + */ + [[nodiscard]] size_type size() const noexcept { + return *this ? handle().size() : size_type{}; + } + + /** + * @brief Returns the number of elements that a group has currently + * allocated space for. + * @return Capacity of the group. + */ + [[nodiscard]] size_type capacity() const noexcept { + return *this ? handle().capacity() : size_type{}; + } + + /*! @brief Requests the removal of unused capacity. */ + void shrink_to_fit() { + if(*this) { + descriptor->handle().shrink_to_fit(); + } + } + + /** + * @brief Checks whether a group is empty. + * @return True if the group is empty, false otherwise. + */ + [[nodiscard]] bool empty() const noexcept { + return !*this || handle().empty(); + } + + /** + * @brief Returns an iterator to the first entity of the group. + * + * 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 ? handle().begin() : iterator{}; + } + + /** + * @brief Returns an iterator that is past the last entity of the group. + * @return An iterator to the entity following the last entity of the + * group. + */ + [[nodiscard]] iterator end() const noexcept { + return *this ? handle().end() : iterator{}; + } + + /** + * @brief Returns an iterator 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 ? handle().rbegin() : reverse_iterator{}; + } + + /** + * @brief Returns an iterator that is past the last entity of the reversed + * group. + * @return An iterator to the entity following the last entity of the + * reversed group. + */ + [[nodiscard]] reverse_iterator rend() const noexcept { + return *this ? handle().rend() : reverse_iterator{}; + } + + /** + * @brief Returns the first entity of the group, if any. + * @return The first entity of the group if one exists, the null entity + * otherwise. + */ + [[nodiscard]] entity_type front() const noexcept { + const auto it = begin(); + return it != end() ? *it : null; + } + + /** + * @brief Returns the last entity of the group, if any. + * @return The last entity of the group if one exists, the null entity + * otherwise. + */ + [[nodiscard]] entity_type back() const noexcept { + const auto it = rbegin(); + return it != rend() ? *it : null; + } + + /** + * @brief Finds an entity. + * @param entt A valid identifier. + * @return An iterator to the given entity if it's found, past the end + * iterator otherwise. + */ + [[nodiscard]] iterator find(const entity_type entt) const noexcept { + return *this ? handle().find(entt) : iterator{}; + } + + /** + * @brief Returns the identifier that occupies the given position. + * @param pos Position of the element to return. + * @return The identifier that occupies the given position. + */ + [[nodiscard]] entity_type operator[](const size_type pos) const { + return begin()[pos]; + } + + /** + * @brief Checks if a group is properly initialized. + * @return True if the group is properly initialized, false otherwise. + */ + [[nodiscard]] explicit operator bool() const noexcept { + return descriptor != nullptr; + } + + /** + * @brief Checks if a group contains an entity. + * @param entt A valid identifier. + * @return True if the group contains the given entity, false otherwise. + */ + [[nodiscard]] bool contains(const entity_type entt) const noexcept { + return *this && handle().contains(entt); + } + + /** + * @brief Returns the components assigned to the given entity. + * + * @warning + * Attempting to use an entity that doesn't belong to the group results in + * undefined behavior. + * + * @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 + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + return get, index_of...>(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 + [[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(cpools)->get(entt), ...); + } else { + return std::tuple_cat(std::get(cpools)->get_as_tuple(entt)...); + } + } + + /** + * @brief Iterates entities and components and applies the given function + * object to them. + * + * The function object is invoked for each entity. It is provided with the + * entity itself and a set of references to non-empty components. The + * _constness_ of the components is as requested.
+ * The signature of the function must be equivalent to one of the following + * forms: + * + * @code{.cpp} + * void(const entity_type, Type &...); + * void(Type &...); + * @endcode + * + * @note + * Empty types aren't explicitly instantiated and therefore they are never + * returned during iterations. + * + * @tparam Func Type of the function object to invoke. + * @param func A valid function object. + */ + template + void each(Func func) const { + for(const auto entt: *this) { + if constexpr(is_applicable_v{}, std::declval().get({})))>) { + std::apply(func, std::tuple_cat(std::make_tuple(entt), get(entt))); + } else { + std::apply(func, get(entt)); + } + } + } + + /** + * @brief Returns an iterable object to use to _visit_ a group. + * + * The iterable object returns tuples that contain the current entity and a + * set of references to its non-empty components. The _constness_ of the + * components is as requested. + * + * @note + * Empty types aren't explicitly instantiated and therefore they are never + * returned during iterations. + * + * @return An iterable object to use to _visit_ the group. + */ + [[nodiscard]] iterable each() const noexcept { + const auto cpools = pools(); + return iterable{{begin(), cpools}, {end(), cpools}}; + } + + /** + * @brief Sort a group according to the given comparison function. + * + * 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: + * + * @code{.cpp} + * bool(std::tuple, std::tuple); + * bool(const Type &..., const Type &...); + * bool(const Entity, const Entity); + * @endcode + * + * Where `Type` are such that they are iterated by the group.
+ * Moreover, the comparison function object shall induce a + * _strict weak ordering_ on the values. + * + * The sort function object must offer a member function template + * `operator()` that accepts three arguments: + * + * * An iterator to the first element of the range to sort. + * * An iterator past the last element of the range to sort. + * * A comparison function to use to compare the elements. + * + * @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. + * @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 + void sort(Compare compare, Sort algo = Sort{}, Args &&...args) { + sort, index_of...>(std::move(compare), std::move(algo), std::forward(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 + void sort(Compare compare, Sort algo = Sort{}, Args &&...args) { + if(*this) { + if constexpr(sizeof...(Index) == 0) { + static_assert(std::is_invocable_v, "Invalid comparison function"); + descriptor->handle().sort(std::move(compare), std::move(algo), std::forward(args)...); + } else { + auto comp = [&compare, cpools = pools()](const entity_type lhs, const entity_type rhs) { + if constexpr(sizeof...(Index) == 1) { + return compare((std::get(cpools)->get(lhs), ...), (std::get(cpools)->get(rhs), ...)); + } else { + return compare(std::forward_as_tuple(std::get(cpools)->get(lhs)...), std::forward_as_tuple(std::get(cpools)->get(rhs)...)); + } + }; + + descriptor->handle().sort(std::move(comp), std::move(algo), std::forward(args)...); + } + } + } + + /** + * @brief Sort the shared pool of entities according to a given storage. + * + * The shared pool of entities and thus its order is affected by the changes + * to each and every pool that it tracks. + * + * @param other The storage to use to impose the order. + */ + void sort_as(const common_type &other) const { + if(*this) { + descriptor->handle().sort_as(other); + } + } + +private: + handler *descriptor; +}; + +/** + * @brief Owning group. + * + * Owning groups returns all entities and only the entities that are at + * least in the given storage. Moreover: + * + * * It's guaranteed that the entity list is tightly packed in memory for fast + * iterations. + * * It's guaranteed that all components in the owned storage are tightly packed + * in memory for even faster iterations and to allow direct access. + * * They stay true to the order of the owned storage and all instances have the + * same order in memory. + * + * The more types of storage are owned, the faster it is to iterate a group. + * + * @b Important + * + * Iterators aren't invalidated if: + * + * * 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 other cases, modifying the pools iterated by the group in any way + * invalidates all the iterators. + * + * @tparam Owned Types of storage _owned_ by the group. + * @tparam Get Types of storage _observed_ by the group. + * @tparam Exclude Types of storage used to filter the group. + */ +template +class basic_group, get_t, exclude_t> { + using base_type = std::common_type_t; + using underlying_type = typename base_type::entity_type; + + template + static constexpr std::size_t index_of = type_list_index_v, type_list>; + + auto pools() const noexcept { + using return_type = std::tuple; + return descriptor ? descriptor->template pools_as() : return_type{}; + } + + auto filter() const noexcept { + using return_type = std::tuple; + return descriptor ? descriptor->template filter_as() : return_type{}; + } + +public: + /*! @brief Underlying entity identifier. */ + using entity_type = underlying_type; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Common type among all storage types. */ + using common_type = base_type; + /*! @brief Random access iterator type. */ + using iterator = typename common_type::iterator; + /*! @brief Reversed iterator type. */ + using reverse_iterator = typename common_type::reverse_iterator; + /*! @brief Iterable group type. */ + using iterable = iterable_adaptor, get_t>>; + /*! @brief Group handler type. */ + using handler = internal::group_handler...>, get_t...>, exclude_t...>>; + + /*! @brief Default constructor to use to create empty, invalid groups. */ + basic_group() noexcept + : descriptor{} {} + + /** + * @brief Constructs a group from a set of storage classes. + * @param ref A reference to a group handler. + */ + basic_group(handler &ref) noexcept + : descriptor{&ref} {} + + /** + * @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 + [[nodiscard]] auto *storage() const noexcept { + return storage>(); + } + + /** + * @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 + [[nodiscard]] auto *storage() const noexcept { + constexpr auto offset = sizeof...(Owned) + sizeof...(Get); + + if constexpr(Index < offset) { + return std::get(pools()); + } else { + return std::get(filter()); + } + } + + /** + * @brief Returns the number of entities that that are part of the group. + * @return Number of entities that that are part of the group. + */ + [[nodiscard]] size_type size() const noexcept { + return *this ? descriptor->length() : size_type{}; + } + + /** + * @brief Checks whether a group is empty. + * @return True if the group is empty, false otherwise. + */ + [[nodiscard]] bool empty() const noexcept { + return !*this || !descriptor->length(); + } + + /** + * @brief Returns an iterator to the first entity of the group. + * + * 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 ? (handle().end() - descriptor->length()) : iterator{}; + } + + /** + * @brief Returns an iterator that is past the last entity of the group. + * @return An iterator to the entity following the last entity of the + * group. + */ + [[nodiscard]] iterator end() const noexcept { + return *this ? handle().end() : iterator{}; + } + + /** + * @brief Returns an iterator 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 ? handle().rbegin() : reverse_iterator{}; + } + + /** + * @brief Returns an iterator that is past the last entity of the reversed + * group. + * @return An iterator to the entity following the last entity of the + * reversed group. + */ + [[nodiscard]] reverse_iterator rend() const noexcept { + return *this ? (handle().rbegin() + descriptor->length()) : reverse_iterator{}; + } + + /** + * @brief Returns the first entity of the group, if any. + * @return The first entity of the group if one exists, the null entity + * otherwise. + */ + [[nodiscard]] entity_type front() const noexcept { + const auto it = begin(); + return it != end() ? *it : null; + } + + /** + * @brief Returns the last entity of the group, if any. + * @return The last entity of the group if one exists, the null entity + * otherwise. + */ + [[nodiscard]] entity_type back() const noexcept { + const auto it = rbegin(); + return it != rend() ? *it : null; + } + + /** + * @brief Finds an entity. + * @param entt A valid identifier. + * @return An iterator to the given entity if it's found, past the end + * iterator otherwise. + */ + [[nodiscard]] iterator find(const entity_type entt) const noexcept { + const auto it = *this ? handle().find(entt) : iterator{}; + return it >= begin() ? it : iterator{}; + } + + /** + * @brief Returns the identifier that occupies the given position. + * @param pos Position of the element to return. + * @return The identifier that occupies the given position. + */ + [[nodiscard]] entity_type operator[](const size_type pos) const { + return begin()[pos]; + } + + /** + * @brief Checks if a group is properly initialized. + * @return True if the group is properly initialized, false otherwise. + */ + [[nodiscard]] explicit operator bool() const noexcept { + return descriptor != nullptr; + } + + /** + * @brief Checks if a group contains an entity. + * @param entt A valid identifier. + * @return True if the group contains the given entity, false otherwise. + */ + [[nodiscard]] bool contains(const entity_type entt) const noexcept { + return *this && handle().contains(entt) && (handle().index(entt) < (descriptor->length())); + } + + /** + * @brief Returns the components assigned to the given entity. + * + * @warning + * Attempting to use an entity that doesn't belong to the group results in + * undefined behavior. + * + * @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 + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + return get, index_of...>(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 + [[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(cpools)->get(entt), ...); + } else { + return std::tuple_cat(std::get(cpools)->get_as_tuple(entt)...); + } + } + + /** + * @brief Iterates entities and components and applies the given function + * object to them. + * + * The function object is invoked for each entity. It is provided with the + * entity itself and a set of references to non-empty components. The + * _constness_ of the components is as requested.
+ * The signature of the function must be equivalent to one of the following + * forms: + * + * @code{.cpp} + * void(const entity_type, Type &...); + * void(Type &...); + * @endcode + * + * @note + * Empty types aren't explicitly instantiated and therefore they are never + * returned during iterations. + * + * @tparam Func Type of the function object to invoke. + * @param func A valid function object. + */ + template + void each(Func func) const { + for(auto args: each()) { + if constexpr(is_applicable_v{}, std::declval().get({})))>) { + std::apply(func, args); + } else { + std::apply([&func](auto, auto &&...less) { func(std::forward(less)...); }, args); + } + } + } + + /** + * @brief Returns an iterable object to use to _visit_ a group. + * + * The iterable object returns tuples that contain the current entity and a + * set of references to its non-empty components. The _constness_ of the + * components is as requested. + * + * @note + * Empty types aren't explicitly instantiated and therefore they are never + * returned during iterations. + * + * @return An iterable object to use to _visit_ the group. + */ + [[nodiscard]] iterable each() const noexcept { + const auto cpools = pools(); + return {{begin(), cpools}, {end(), cpools}}; + } + + /** + * @brief Sort a group according to the given comparison function. + * + * 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: + * + * @code{.cpp} + * bool(std::tuple, std::tuple); + * bool(const Type &, const Type &); + * bool(const Entity, const Entity); + * @endcode + * + * Where `Type` are either owned types or not but still such that they are + * iterated by the group.
+ * Moreover, the comparison function object shall induce a + * _strict weak ordering_ on the values. + * + * The sort function object must offer a member function template + * `operator()` that accepts three arguments: + * + * * An iterator to the first element of the range to sort. + * * An iterator past the last element of the range to sort. + * * A comparison function to use to compare the elements. + * + * @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. + * @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 + void sort(Compare compare, Sort algo = Sort{}, Args &&...args) const { + sort, index_of...>(std::move(compare), std::move(algo), std::forward(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 + 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, "Invalid comparison function"); + storage<0>()->sort_n(descriptor->length(), std::move(compare), std::move(algo), std::forward(args)...); + } else { + auto comp = [&compare, &cpools](const entity_type lhs, const entity_type rhs) { + if constexpr(sizeof...(Index) == 1) { + return compare((std::get(cpools)->get(lhs), ...), (std::get(cpools)->get(rhs), ...)); + } else { + return compare(std::forward_as_tuple(std::get(cpools)->get(lhs)...), std::forward_as_tuple(std::get(cpools)->get(rhs)...)); + } + }; + + storage<0>()->sort_n(descriptor->length(), std::move(comp), std::move(algo), std::forward(args)...); + } + + 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), ...); + } + }; + + std::apply(cb, cpools); + } + +private: + handler *descriptor; +}; + +} // namespace entt + +#endif + +// #include "view.hpp" +#ifndef ENTT_ENTITY_VIEW_HPP +#define ENTT_ENTITY_VIEW_HPP + +#include +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/iterator.hpp" + +// #include "../core/type_traits.hpp" + +// #include "entity.hpp" + +// #include "fwd.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +[[nodiscard]] auto filter_as_tuple(const std::array &filter) noexcept { + return std::apply([](const auto *...curr) { return std::make_tuple(static_cast(const_cast *>(curr))...); }, filter); +} + +template +[[nodiscard]] auto none_of(const std::array &filter, const typename Type::entity_type entt) noexcept { + return std::apply([entt](const auto *...curr) { return (!(curr && curr->contains(entt)) && ...); }, filter); +} + +template +[[nodiscard]] auto view_pack(const std::tuple value, const std::tuple excl, std::index_sequence) { + const auto pools = std::tuple_cat(value, excl); + basic_view, exclude_t> elem{}; + (((std::get(pools) != nullptr) ? elem.template storage(*std::get(pools)) : void()), ...); + return elem; +} + +template +class view_iterator final { + using iterator_type = typename Type::const_iterator; + + [[nodiscard]] bool valid() const noexcept { + return ((Get != 0u) || (*it != tombstone)) + && std::apply([entt = *it](const auto *...curr) { return (curr->contains(entt) && ...); }, pools) + && none_of(filter, *it); + } + +public: + using value_type = typename iterator_type::value_type; + using pointer = typename iterator_type::pointer; + using reference = typename iterator_type::reference; + using difference_type = typename iterator_type::difference_type; + using iterator_category = std::forward_iterator_tag; + + constexpr view_iterator() noexcept + : it{}, + last{}, + pools{}, + filter{} {} + + view_iterator(iterator_type curr, iterator_type to, std::array value, std::array excl) noexcept + : it{curr}, + last{to}, + pools{value}, + filter{excl} { + while(it != last && !valid()) { + ++it; + } + } + + view_iterator &operator++() noexcept { + while(++it != last && !valid()) {} + return *this; + } + + view_iterator operator++(int) noexcept { + view_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] pointer operator->() const noexcept { + return &*it; + } + + [[nodiscard]] reference operator*() const noexcept { + return *operator->(); + } + + template + friend constexpr bool operator==(const view_iterator &, const view_iterator &) noexcept; + +private: + iterator_type it; + iterator_type last; + std::array pools; + std::array filter; +}; + +template +[[nodiscard]] constexpr bool operator==(const view_iterator &lhs, const view_iterator &rhs) noexcept { + return lhs.it == rhs.it; +} + +template +[[nodiscard]] constexpr bool operator!=(const view_iterator &lhs, const view_iterator &rhs) noexcept { + return !(lhs == rhs); +} + +template +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()), std::declval().get_as_tuple({})...)); + using pointer = input_iterator_pointer; + using reference = value_type; + using iterator_category = std::input_iterator_tag; + + constexpr extended_view_iterator() + : it{}, + pools{} {} + + extended_view_iterator(It from, std::tuple value) + : it{from}, + pools{value} {} + + extended_view_iterator &operator++() noexcept { + return ++it, *this; + } + + extended_view_iterator operator++(int) noexcept { + extended_view_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] reference operator*() const noexcept { + return std::apply([entt = *it](auto *...curr) { return std::tuple_cat(std::make_tuple(entt), curr->get_as_tuple(entt)...); }, pools); + } + + [[nodiscard]] pointer operator->() const noexcept { + return operator*(); + } + + [[nodiscard]] constexpr iterator_type base() const noexcept { + return it; + } + + template + friend bool constexpr operator==(const extended_view_iterator &, const extended_view_iterator &) noexcept; + +private: + It it; + std::tuple pools; +}; + +template +[[nodiscard]] constexpr bool operator==(const extended_view_iterator &lhs, const extended_view_iterator &rhs) noexcept { + return lhs.it == rhs.it; +} + +template +[[nodiscard]] constexpr bool operator!=(const extended_view_iterator &lhs, const extended_view_iterator &rhs) noexcept { + return !(lhs == rhs); +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/** + * @brief View implementation. + * + * Primary template isn't defined on purpose. All the specializations give a + * compile-time error, but for a few reasonable cases. + */ +template +class basic_view; + +/** + * @brief Multi component view. + * + * Multi component views iterate over those entities that are at least in the + * given storage. During initialization, a multi component 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 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 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 +class basic_view, exclude_t> { + static constexpr auto offset = sizeof...(Get); + using base_type = std::common_type_t; + using underlying_type = typename base_type::entity_type; + + template + friend class basic_view; + + template + static constexpr std::size_t index_of = type_list_index_v, type_list>; + + [[nodiscard]] auto opaque_check_set() const noexcept { + std::array other{}; + std::apply([&other, pos = 0u, view = view](const auto *...curr) mutable { ((curr == view ? void() : void(other[pos++] = curr)), ...); }, pools); + return other; + } + + 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 + [[nodiscard]] auto dispatch_get(const std::tuple &curr) const { + if constexpr(Curr == Other) { + return std::forward_as_tuple(std::get(curr)...); + } else { + return std::get(pools)->get_as_tuple(std::get<0>(curr)); + } + } + + template + void each(Func &func, std::index_sequence) const { + for(const auto curr: std::get(pools)->each()) { + if(const auto entt = std::get<0>(curr); ((sizeof...(Get) != 1u) || (entt != tombstone)) && ((Curr == Index || std::get(pools)->contains(entt)) && ...) && internal::none_of(filter, entt)) { + if constexpr(is_applicable_v{}, std::declval().get({})))>) { + std::apply(func, std::tuple_cat(std::make_tuple(entt), dispatch_get(curr)...)); + } else { + std::apply(func, std::tuple_cat(dispatch_get(curr)...)); + } + } + } + } + + template + void pick_and_each(Func &func, std::index_sequence seq) const { + ((std::get(pools) == view ? each(func, seq) : void()), ...); + } + +public: + /*! @brief Underlying entity identifier. */ + using entity_type = underlying_type; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Common type among all storage types. */ + using common_type = base_type; + /*! @brief Bidirectional iterator type. */ + using iterator = internal::view_iterator; + /*! @brief Iterable view type. */ + using iterable = iterable_adaptor>; + + /*! @brief Default constructor to use to create empty, invalid views. */ + basic_view() noexcept + : pools{}, + filter{}, + view{} {} + + /** + * @brief Constructs a multi-type view from a set of storage classes. + * @param value The storage for the types to iterate. + * @param excl The storage for the types used to filter the view. + */ + basic_view(Get &...value, Exclude &...excl) noexcept + : pools{&value...}, + filter{&excl...}, + view{} { + unchecked_refresh(); + } + + /** + * @brief Constructs a multi-type view from a set of storage classes. + * @param value The storage for the types to iterate. + * @param excl The storage for the types used to filter the view. + */ + basic_view(std::tuple value, std::tuple excl = {}) noexcept + : basic_view{std::make_from_tuple(std::tuple_cat(value, excl))} {} + + /** + * @brief Forces a view to use a given component to drive iterations + * @tparam Type Type of component to use to drive iterations. + */ + template + void use() noexcept { + use>(); + } + + /** + * @brief Forces a view to use a given component to drive iterations + * @tparam Index Index of the component to use to drive iterations. + */ + template + void use() noexcept { + if(view) { + view = std::get(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 Returns the leading storage of a view, if any. + * @return The leading storage of the view. + */ + [[nodiscard]] const common_type *handle() const noexcept { + return view; + } + + /** + * @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 + [[nodiscard]] auto *storage() const noexcept { + return storage>(); + } + + /** + * @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 + [[nodiscard]] auto *storage() const noexcept { + if constexpr(Index < offset) { + return std::get(pools); + } else { + return std::get(internal::filter_as_tuple(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 + void storage(Type &elem) noexcept { + storage>(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 + void storage(Type &elem) noexcept { + if constexpr(Index < offset) { + std::get(pools) = &elem; + refresh(); + } else { + std::get(filter) = &elem; + } + } + + /** + * @brief Estimates the number of entities iterated by the view. + * @return Estimated number of entities iterated by the view. + */ + [[nodiscard]] size_type size_hint() const noexcept { + return view ? view->size() : size_type{}; + } + + /** + * @brief Returns an iterator to the first entity of the view. + * + * 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 view ? iterator{view->begin(), view->end(), opaque_check_set(), filter} : iterator{}; + } + + /** + * @brief Returns an iterator that is past the last entity of the view. + * @return An iterator to the entity following the last entity of the view. + */ + [[nodiscard]] iterator end() const noexcept { + return view ? iterator{view->end(), view->end(), opaque_check_set(), filter} : iterator{}; + } + + /** + * @brief Returns the first entity of the view, if any. + * @return The first entity of the view if one exists, the null entity + * otherwise. + */ + [[nodiscard]] entity_type front() const noexcept { + const auto it = begin(); + return it != end() ? *it : null; + } + + /** + * @brief Returns the last entity of the view, if any. + * @return The last entity of the view if one exists, the null entity + * otherwise. + */ + [[nodiscard]] entity_type back() const noexcept { + 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; + } + + /** + * @brief Finds an entity. + * @param entt A valid identifier. + * @return An iterator to the given entity if it's found, past the end + * iterator otherwise. + */ + [[nodiscard]] iterator find(const entity_type entt) const noexcept { + return contains(entt) ? iterator{view->find(entt), view->end(), opaque_check_set(), filter} : end(); + } + + /** + * @brief Returns the components assigned to the given entity. + * @param entt A valid identifier. + * @return The components assigned to the given entity. + */ + [[nodiscard]] decltype(auto) operator[](const entity_type entt) const { + return get(entt); + } + + /** + * @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::apply([](const auto *...curr) { return ((curr != nullptr) && ...); }, pools) + && std::apply([](const auto *...curr) { return ((curr != nullptr) && ...); }, filter); + } + + /** + * @brief Checks if a view contains an entity. + * @param entt A valid identifier. + * @return True if the view contains the given entity, false otherwise. + */ + [[nodiscard]] bool contains(const entity_type entt) const noexcept { + return view && std::apply([entt](const auto *...curr) { return (curr->contains(entt) && ...); }, pools) && internal::none_of(filter, 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 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 + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + return get, index_of...>(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 + [[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...(Index) == 1) { + return (std::get(pools)->get(entt), ...); + } else { + return std::tuple_cat(std::get(pools)->get_as_tuple(entt)...); + } + } + + /** + * @brief Iterates entities and components and applies the given function + * object to them. + * + * The function object is invoked for each entity. It is provided with the + * entity itself and a set of references to non-empty components. The + * _constness_ of the components is as requested.
+ * The signature of the function must be equivalent to one of the following + * forms: + * + * @code{.cpp} + * void(const entity_type, Type &...); + * void(Type &...); + * @endcode + * + * @tparam Func Type of the function object to invoke. + * @param func A valid function object. + */ + template + void each(Func func) const { + view ? pick_and_each(func, std::index_sequence_for{}) : void(); + } + + /** + * @brief Returns an iterable object to use to _visit_ a view. + * + * The iterable object returns a tuple that contains the current entity and + * a set of references to its non-empty components. The _constness_ of the + * components is as requested. + * + * @return An iterable object to use to _visit_ the view. + */ + [[nodiscard]] iterable each() const noexcept { + return {internal::extended_view_iterator{begin(), pools}, internal::extended_view_iterator{end(), pools}}; + } + + /** + * @brief Combines two views in a _more specific_ one (friend function). + * @tparam OGet Component list of the view to combine with. + * @tparam OExclude Filter list of the view to combine with. + * @param other The view to combine with. + * @return A more specific view. + */ + template + [[nodiscard]] auto operator|(const basic_view, exclude_t> &other) const noexcept { + return internal::view_pack( + std::tuple_cat(pools, other.pools), + std::tuple_cat(internal::filter_as_tuple(filter), internal::filter_as_tuple(other.filter)), + std::index_sequence_for{}); + } + +private: + std::tuple pools; + std::array filter; + const common_type *view; +}; + +/** + * @brief Single component view specialization. + * + * Single component views are specialized in order to get a boost in terms of + * performance. This kind of views can access the underlying data structure + * directly and avoid superfluous checks. + * + * @b Important + * + * Iterators aren't invalidated if: + * + * * 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 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 +class basic_view, exclude_t<>, std::void_t>> { + template + friend class basic_view; + +public: + /*! @brief Underlying entity identifier. */ + using entity_type = typename Get::entity_type; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Common type among all storage types. */ + using common_type = typename Get::base_type; + /*! @brief Random access iterator type. */ + using iterator = typename common_type::iterator; + /*! @brief Reversed iterator type. */ + using reverse_iterator = typename common_type::reverse_iterator; + /*! @brief Iterable view type. */ + using iterable = decltype(std::declval().each()); + + /*! @brief Default constructor to use to create empty, invalid views. */ + basic_view() noexcept + : pools{}, + filter{}, + view{} {} + + /** + * @brief Constructs a single-type view from a storage class. + * @param value The storage for the type to iterate. + */ + basic_view(Get &value) noexcept + : pools{&value}, + filter{}, + view{&value} {} + + /** + * @brief Constructs a single-type view from a storage class. + * @param value The storage for the type to iterate. + */ + basic_view(std::tuple value, std::tuple<> = {}) noexcept + : basic_view{std::get<0>(value)} {} + + /** + * @brief Returns the leading storage of a view, if any. + * @return The leading storage of the view. + */ + [[nodiscard]] const common_type *handle() const noexcept { + return view; + } + + /** + * @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 + [[nodiscard]] auto *storage() const noexcept { + static_assert(std::is_same_v, typename Get::value_type>, "Invalid component type"); + return storage<0>(); + } + + /** + * @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 + [[nodiscard]] auto *storage() const noexcept { + return std::get(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 + void storage(Get &elem) noexcept { + view = std::get(pools) = &elem; + } + + /** + * @brief Returns the number of entities that have the given component. + * @return Number of entities that have the given component. + */ + [[nodiscard]] size_type size() const noexcept { + return view ? view->size() : size_type{}; + } + + /** + * @brief Checks whether a view is empty. + * @return True if the view is empty, false otherwise. + */ + [[nodiscard]] bool empty() const noexcept { + return !view || view->empty(); + } + + /** + * @brief Returns an iterator to the first entity of the view. + * + * 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 view ? view->begin() : iterator{}; + } + + /** + * @brief Returns an iterator that is past the last entity of the view. + * @return An iterator to the entity following the last entity of the view. + */ + [[nodiscard]] iterator end() const noexcept { + return view ? view->end() : iterator{}; + } + + /** + * @brief Returns an iterator to the first entity of the reversed view. + * + * 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 view ? view->rbegin() : reverse_iterator{}; + } + + /** + * @brief Returns an iterator that is past the last entity of the reversed + * view. + * @return An iterator to the entity following the last entity of the + * reversed view. + */ + [[nodiscard]] reverse_iterator rend() const noexcept { + return view ? view->rend() : reverse_iterator{}; + } + + /** + * @brief Returns the first entity of the view, if any. + * @return The first entity of the view if one exists, the null entity + * otherwise. + */ + [[nodiscard]] entity_type front() const noexcept { + return (!view || view->empty()) ? null : *view->begin(); + } + + /** + * @brief Returns the last entity of the view, if any. + * @return The last entity of the view if one exists, the null entity + * otherwise. + */ + [[nodiscard]] entity_type back() const noexcept { + return (!view || view->empty()) ? null : *view->rbegin(); + } + + /** + * @brief Finds an entity. + * @param entt A valid identifier. + * @return An iterator to the given entity if it's found, past the end + * iterator otherwise. + */ + [[nodiscard]] iterator find(const entity_type entt) const noexcept { + return view ? view->find(entt) : iterator{}; + } + + /** + * @brief Returns the identifier that occupies the given position. + * @param pos Position of the element to return. + * @return The identifier that occupies the given position. + */ + [[nodiscard]] entity_type operator[](const size_type pos) const { + return begin()[pos]; + } + + /** + * @brief Returns the component assigned to the given entity. + * @param entt A valid identifier. + * @return The component assigned to the given entity. + */ + [[nodiscard]] decltype(auto) operator[](const entity_type entt) const { + return std::get<0>(pools)->get(entt); + } + + /** + * @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); + } + + /** + * @brief Checks if a view contains an entity. + * @param entt A valid identifier. + * @return True if the view contains the given entity, false otherwise. + */ + [[nodiscard]] bool contains(const entity_type entt) const noexcept { + return view && view->contains(entt); + } + + /** + * @brief Returns the component assigned to the given entity. + * + * @warning + * Attempting to use an entity that doesn't belong to the view results in + * undefined behavior. + * + * @tparam Elem Type or index of the component to get. + * @param entt A valid identifier. + * @return The component assigned to the entity. + */ + template + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + static_assert(std::is_same_v, typename Get::value_type>, "Invalid component type"); + return get<0>(entt); + } + + /*! @copydoc get */ + template + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + if constexpr(sizeof...(Elem) == 0) { + return std::get<0>(pools)->get_as_tuple(entt); + } else { + return std::get(pools)->get(entt); + } + } + + /** + * @brief Iterates entities and components and applies the given function + * object to them. + * + * The function object is invoked for each entity. It is provided with the + * entity itself and a reference to the component if it's a non-empty one. + * The _constness_ of the component is as requested.
+ * The signature of the function must be equivalent to one of the following + * forms: + * + * @code{.cpp} + * void(const entity_type, Type &); + * void(typename Type &); + * @endcode + * + * @note + * Empty types aren't explicitly instantiated and therefore they are never + * returned during iterations. + * + * @tparam Func Type of the function object to invoke. + * @param func A valid function object. + */ + template + void each(Func func) const { + if(view) { + if constexpr(is_applicable_v) { + 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); + } + } + } + } + + /** + * @brief Returns an iterable object to use to _visit_ a view. + * + * The iterable object returns a tuple that contains the current entity and + * a reference to its component if it's a non-empty one. The _constness_ of + * the component is as requested. + * + * @return An iterable object to use to _visit_ the view. + */ + [[nodiscard]] iterable each() const noexcept { + return view ? std::get<0>(pools)->each() : iterable{}; + } + + /** + * @brief Combines two views in a _more specific_ one (friend function). + * @tparam OGet Component list of the view to combine with. + * @tparam OExclude Filter list of the view to combine with. + * @param other The view to combine with. + * @return A more specific view. + */ + template + [[nodiscard]] auto operator|(const basic_view, exclude_t> &other) const noexcept { + return internal::view_pack( + std::tuple_cat(pools, other.pools), + internal::filter_as_tuple(other.filter), + std::index_sequence_for{}); + } + +private: + std::tuple pools; + std::array filter; + const common_type *view; +}; + +/** + * @brief Deduction guide. + * @tparam Type Type of storage classes used to create the view. + * @param storage The storage for the types to iterate. + */ +template +basic_view(Type &...storage) -> basic_view, exclude_t<>>; + +/** + * @brief Deduction guide. + * @tparam Get Types of components iterated by the view. + * @tparam Exclude Types of components used to filter the view. + */ +template +basic_view(std::tuple, std::tuple = {}) -> basic_view, exclude_t>; + +} // namespace entt + +#endif + + +namespace entt { + +/** + * @brief Converts a registry to a view. + * @tparam Registry Basic registry type. + */ +template +class as_view { + template + auto dispatch(get_t, exclude_t) const { + return reg.template view...>(exclude_t...>{}); + } + +public: + /*! @brief Type of registry to convert. */ + using registry_type = Registry; + /*! @brief Underlying entity identifier. */ + using entity_type = typename registry_type::entity_type; + + /** + * @brief Constructs a converter for a given registry. + * @param source A valid reference to a registry. + */ + as_view(registry_type &source) noexcept + : reg{source} {} + + /** + * @brief Conversion function from a registry to a view. + * @tparam Get Type of storage used to construct the view. + * @tparam Exclude Types of storage used to filter the view. + * @return A newly created view. + */ + template + operator basic_view() const { + return dispatch(Get{}, Exclude{}); + } + +private: + registry_type ® +}; + +/** + * @brief Converts a registry to a group. + * @tparam Registry Basic registry type. + */ +template +class as_group { + template + auto dispatch(owned_t, get_t, exclude_t) const { + if constexpr(std::is_const_v) { + return reg.template group_if_exists(get_t{}, exclude_t{}); + } else { + return reg.template group...>(get_t...>{}, exclude_t...>{}); + } + } + +public: + /*! @brief Type of registry to convert. */ + using registry_type = Registry; + /*! @brief Underlying entity identifier. */ + using entity_type = typename registry_type::entity_type; + + /** + * @brief Constructs a converter for a given registry. + * @param source A valid reference to a registry. + */ + as_group(registry_type &source) noexcept + : reg{source} {} + + /** + * @brief Conversion function from a registry to a group. + * @tparam Owned Types of _owned_ by the group. + * @tparam Get Types of storage _observed_ by the group. + * @tparam Exclude Types of storage used to filter the group. + * @return A newly created group. + */ + template + operator basic_group() const { + return dispatch(Owned{}, Get{}, Exclude{}); + } + +private: + registry_type ® +}; + +/** + * @brief Helper to create a listener that directly invokes a member function. + * @tparam Member Member function to invoke on a component of the given type. + * @tparam Registry Basic registry type. + * @param reg A registry that contains the given entity and its components. + * @param entt Entity from which to get the component. + */ +template>> +void invoke(Registry ®, const typename Registry::entity_type entt) { + static_assert(std::is_member_function_pointer_v, "Invalid pointer to non-static member function"); + delegate func; + func.template connect(reg.template get>(entt)); + func(reg, entt); +} + +/** + * @brief Returns the entity associated with a given component. + * + * @warning + * Currently, this function only works correctly with the default pool as it + * makes assumptions about how the components are laid out. + * + * @tparam Registry Basic registry type. + * @tparam Component Type of component. + * @param reg A registry that contains the given entity and its components. + * @param instance A valid component instance. + * @return The entity associated with the given component. + */ +template +typename Registry::entity_type to_entity(const Registry ®, const Component &instance) { + if(const auto *storage = reg.template storage(); storage) { + constexpr auto page_size = std::remove_const_t>::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 += page_size) { + if(const auto dist = (addr - std::addressof(storage->get(*it))); dist >= 0 && dist < static_cast(page_size)) { + return *(it + dist); + } + } + } + + return null; +} + +/*! @brief Primary template isn't defined on purpose. */ +template +struct sigh_helper; + +/** + * @brief Signal connection helper for registries. + * @tparam Registry Basic registry type. + */ +template +struct sigh_helper { + /*! @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 + auto with(const id_type id = type_hash::value()) noexcept { + return sigh_helper{*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 +struct sigh_helper final: sigh_helper { + /*! @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::value()) + : sigh_helper{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 on_construct(Args &&...args) { + this->registry().template on_construct(name).template connect(std::forward(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 on_update(Args &&...args) { + this->registry().template on_update(name).template connect(std::forward(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 on_destroy(Args &&...args) { + this->registry().template on_destroy(name).template connect(std::forward(args)...); + return *this; + } + +private: + id_type name; +}; + +/** + * @brief Deduction guide. + * @tparam Registry Basic registry type. + */ +template +sigh_helper(Registry &) -> sigh_helper; + +} // namespace entt + +#endif + +// #include "entity/mixin.hpp" +#ifndef ENTT_ENTITY_MIXIN_HPP +#define ENTT_ENTITY_MIXIN_HPP + +#include +#include +// #include "../config/config.h" + +// #include "../core/any.hpp" + +// #include "../signal/sigh.hpp" +#ifndef ENTT_SIGNAL_SIGH_HPP +#define ENTT_SIGNAL_SIGH_HPP + +#include +#include +#include +#include +#include +// #include "delegate.hpp" +#ifndef ENTT_SIGNAL_DELEGATE_HPP +#define ENTT_SIGNAL_DELEGATE_HPP + +#include +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/type_traits.hpp" + +// #include "fwd.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +constexpr auto function_pointer(Ret (*)(Args...)) -> Ret (*)(Args...); + +template +constexpr auto function_pointer(Ret (*)(Type, Args...), Other &&) -> Ret (*)(Args...); + +template +constexpr auto function_pointer(Ret (Class::*)(Args...), Other &&...) -> Ret (*)(Args...); + +template +constexpr auto function_pointer(Ret (Class::*)(Args...) const, Other &&...) -> Ret (*)(Args...); + +template +constexpr auto function_pointer(Type Class::*, Other &&...) -> Type (*)(); + +template +using function_pointer_t = decltype(function_pointer(std::declval()...)); + +template +[[nodiscard]] constexpr auto index_sequence_for(Ret (*)(Args...)) { + return std::index_sequence_for{}; +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/** + * @brief Basic delegate implementation. + * + * Primary template isn't defined on purpose. All the specializations give a + * compile-time error unless the template parameter is a function type. + */ +template +class delegate; + +/** + * @brief Utility class to use to send around functions and members. + * + * Unmanaged delegate for function pointers and members. Users of this class are + * in charge of disconnecting instances before deleting them. + * + * A delegate can be used as a general purpose invoker without memory overhead + * for free functions possibly with payloads and bound or unbound members. + * + * @tparam Ret Return type of a function type. + * @tparam Args Types of arguments of a function type. + */ +template +class delegate { + template + [[nodiscard]] auto wrap(std::index_sequence) noexcept { + return [](const void *, Args... args) -> Ret { + [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); + + if constexpr(std::is_invocable_r_v>...>) { + return static_cast(std::invoke(Candidate, std::forward>>(std::get(arguments))...)); + } else { + constexpr auto offset = sizeof...(Args) - sizeof...(Index); + return static_cast(std::invoke(Candidate, std::forward>>(std::get(arguments))...)); + } + }; + } + + template + [[nodiscard]] auto wrap(Type &, std::index_sequence) noexcept { + return [](const void *payload, Args... args) -> Ret { + [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); + Type *curr = static_cast(const_cast *>(payload)); + + if constexpr(std::is_invocable_r_v>...>) { + return static_cast(std::invoke(Candidate, *curr, std::forward>>(std::get(arguments))...)); + } else { + constexpr auto offset = sizeof...(Args) - sizeof...(Index); + return static_cast(std::invoke(Candidate, *curr, std::forward>>(std::get(arguments))...)); + } + }; + } + + template + [[nodiscard]] auto wrap(Type *, std::index_sequence) noexcept { + return [](const void *payload, Args... args) -> Ret { + [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); + Type *curr = static_cast(const_cast *>(payload)); + + if constexpr(std::is_invocable_r_v>...>) { + return static_cast(std::invoke(Candidate, curr, std::forward>>(std::get(arguments))...)); + } else { + constexpr auto offset = sizeof...(Args) - sizeof...(Index); + return static_cast(std::invoke(Candidate, curr, std::forward>>(std::get(arguments))...)); + } + }; + } + +public: + /*! @brief Function type of the contained target. */ + using function_type = Ret(const void *, Args...); + /*! @brief Function type of the delegate. */ + using type = Ret(Args...); + /*! @brief Return type of the delegate. */ + using result_type = Ret; + + /*! @brief Default constructor. */ + delegate() noexcept + : instance{nullptr}, + fn{nullptr} {} + + /** + * @brief Constructs a delegate with a given object or payload, if any. + * @tparam Candidate Function or member to connect to the delegate. + * @tparam Type Type of class or type of payload, if any. + * @param value_or_instance Optional valid object that fits the purpose. + */ + template + delegate(connect_arg_t, Type &&...value_or_instance) noexcept { + connect(std::forward(value_or_instance)...); + } + + /** + * @brief Constructs a delegate and connects an user defined function with + * optional payload. + * @param function Function to connect to the delegate. + * @param payload User defined arbitrary data. + */ + delegate(function_type *function, const void *payload = nullptr) noexcept { + connect(function, payload); + } + + /** + * @brief Connects a free function or an unbound member to a delegate. + * @tparam Candidate Function or member to connect to the delegate. + */ + template + void connect() noexcept { + instance = nullptr; + + if constexpr(std::is_invocable_r_v) { + fn = [](const void *, Args... args) -> Ret { + return Ret(std::invoke(Candidate, std::forward(args)...)); + }; + } else if constexpr(std::is_member_pointer_v) { + fn = wrap(internal::index_sequence_for>>(internal::function_pointer_t{})); + } else { + fn = wrap(internal::index_sequence_for(internal::function_pointer_t{})); + } + } + + /** + * @brief Connects a free function with payload or a bound member to a + * delegate. + * + * The delegate isn't responsible for the connected object or the payload. + * Users must always guarantee that the lifetime of the instance overcomes + * the one of the delegate.
+ * 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 delegate itself. + * + * @tparam Candidate Function or member to connect to the delegate. + * @tparam Type Type of class or type of payload. + * @param value_or_instance A valid reference that fits the purpose. + */ + template + void connect(Type &value_or_instance) noexcept { + instance = &value_or_instance; + + if constexpr(std::is_invocable_r_v) { + fn = [](const void *payload, Args... args) -> Ret { + Type *curr = static_cast(const_cast *>(payload)); + return Ret(std::invoke(Candidate, *curr, std::forward(args)...)); + }; + } else { + fn = wrap(value_or_instance, internal::index_sequence_for(internal::function_pointer_t{})); + } + } + + /** + * @brief Connects a free function with payload or a bound member to a + * delegate. + * + * @sa connect(Type &) + * + * @tparam Candidate Function or member to connect to the delegate. + * @tparam Type Type of class or type of payload. + * @param value_or_instance A valid pointer that fits the purpose. + */ + template + void connect(Type *value_or_instance) noexcept { + instance = value_or_instance; + + if constexpr(std::is_invocable_r_v) { + fn = [](const void *payload, Args... args) -> Ret { + Type *curr = static_cast(const_cast *>(payload)); + return Ret(std::invoke(Candidate, curr, std::forward(args)...)); + }; + } else { + fn = wrap(value_or_instance, internal::index_sequence_for(internal::function_pointer_t{})); + } + } + + /** + * @brief Connects an user defined function with optional payload to a + * delegate. + * + * The delegate isn't responsible for the connected object or the payload. + * Users must always guarantee that the lifetime of an instance overcomes + * the one of the delegate.
+ * The payload is returned as the first argument to the target function in + * all cases. + * + * @param function Function to connect to the delegate. + * @param payload User defined arbitrary data. + */ + void connect(function_type *function, const void *payload = nullptr) noexcept { + ENTT_ASSERT(function != nullptr, "Uninitialized function pointer"); + instance = payload; + fn = function; + } + + /** + * @brief Resets a delegate. + * + * After a reset, a delegate cannot be invoked anymore. + */ + void reset() noexcept { + instance = nullptr; + 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. @@ -17456,7 +23821,8 @@ class sigh { friend class sink>; using alloc_traits = std::allocator_traits; - using container_type = std::vector, typename alloc_traits::template rebind_alloc>>; + using delegate_type = delegate; + using container_type = std::vector>; public: /*! @brief Allocator type. */ @@ -17568,8 +23934,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...); } } @@ -17589,24 +23955,24 @@ public: */ template void collect(Func func, Args... args) const { - for(auto &&call: calls) { - if constexpr(std::is_void_v) { + for(auto pos = calls.size(); pos; --pos) { + if constexpr(std::is_void_v || !std::is_invocable_v) { + calls[pos - 1u](args...); + if constexpr(std::is_invocable_r_v) { - call(args...); if(func()) { break; } } else { - call(args...); func(); } } else { if constexpr(std::is_invocable_r_v) { - if(func(call(args...))) { + if(func(calls[pos - 1u](args...))) { break; } } else { - func(call(args...)); + func(calls[pos - 1u](args...)); } } } @@ -17758,6 +24124,7 @@ private: template class sink> { using signal_type = sigh; + using delegate_type = typename signal_type::delegate_type; using difference_type = typename signal_type::container_type::difference_type; template @@ -17770,13 +24137,14 @@ class sink> { sink{*static_cast(signal)}.disconnect(); } - auto before(delegate 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 + 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: @@ -17785,8 +24153,7 @@ public: * @param ref A valid reference to a signal object. */ sink(sigh &ref) noexcept - : offset{}, - signal{&ref} {} + : signal{&ref} {} /** * @brief Returns false if at least a listener is connected to the sink. @@ -17796,89 +24163,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 - [[nodiscard]] sink before() { - delegate call{}; - call.template connect(); - 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 - [[nodiscard]] sink before(Type &&value_or_instance) { - delegate call{}; - call.template connect(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>, 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.
- * 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. @@ -17888,9 +24175,9 @@ public: connection connect(Type &&...value_or_instance) { disconnect(value_or_instance...); - delegate call{}; + delegate_type call{}; call.template connect(value_or_instance...); - signal->calls.insert(signal->calls.end() - offset, std::move(call)); + signal->calls.push_back(std::move(call)); delegate conn{}; conn.template connect<&release>(value_or_instance...); @@ -17906,21 +24193,9 @@ public: */ template void disconnect(Type &&...value_or_instance) { - auto &calls = signal->calls; - delegate call{}; + delegate_type call{}; call.template connect(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>, void>>> - void disconnect(Type &value_or_instance) { - disconnect(&value_or_instance); + disconnect_if([&call](const auto &elem) { return elem == call; }); } /** @@ -17930,9 +24205,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; }); } } @@ -17942,7 +24215,6 @@ public: } private: - difference_type offset; signal_type *signal; }; @@ -17963,6 +24235,8 @@ sink(sigh &) -> sink>; #endif +// #include "entity.hpp" + // #include "fwd.hpp" @@ -17982,47 +24256,74 @@ namespace entt { * @tparam Type The type of the underlying storage. */ template -class sigh_storage_mixin final: public Type { - using basic_registry_type = basic_registry; - using sigh_type = sigh; - using basic_iterator = typename Type::basic_iterator; +class sigh_mixin final: public Type { + using underlying_type = Type; + using basic_registry_type = basic_registry; + using sigh_type = sigh; + using underlying_iterator = typename underlying_type::base_type::basic_iterator; - void pop(basic_iterator first, basic_iterator last) override { + basic_registry_type &owner_or_assert() const noexcept { ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); + return *owner; + } - for(; first != last; ++first) { - const auto entt = *first; - destruction.publish(*owner, entt); - const auto it = Type::find(entt); - Type::pop(it, it + 1u); + 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); + } } } - 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); + 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(pos)); entt != tombstone) { + destruction.publish(reg, entt); + } + } else { + destruction.publish(reg, underlying_type::operator[](static_cast(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 Type::allocator_type; + using allocator_type = typename underlying_type::allocator_type; /*! @brief Underlying entity identifier. */ - using entity_type = typename Type::entity_type; + using entity_type = typename underlying_type::entity_type; /*! @brief Expected registry type. */ using registry_type = basic_registry_type; /*! @brief Default constructor. */ - sigh_storage_mixin() - : sigh_storage_mixin{allocator_type{}} {} + sigh_mixin() + : sigh_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}, + explicit sigh_mixin(const allocator_type &allocator) + : underlying_type{allocator}, owner{}, construction{allocator}, destruction{allocator}, @@ -18032,8 +24333,8 @@ public: * @brief Move constructor. * @param other The instance to move from. */ - sigh_storage_mixin(sigh_storage_mixin &&other) noexcept - : Type{std::move(other)}, + sigh_mixin(sigh_mixin &&other) noexcept + : underlying_type{std::move(other)}, owner{other.owner}, construction{std::move(other.construction)}, destruction{std::move(other.destruction)}, @@ -18044,8 +24345,8 @@ public: * @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}, + 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}, @@ -18056,8 +24357,8 @@ public: * @param other The instance to move from. * @return This storage. */ - sigh_storage_mixin &operator=(sigh_storage_mixin &&other) noexcept { - Type::operator=(std::move(other)); + 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); @@ -18069,9 +24370,9 @@ public: * @brief Exchanges the contents with those of a given storage. * @param other Storage to exchange the content with. */ - void swap(sigh_storage_mixin &other) { + void swap(sigh_mixin &other) { using std::swap; - Type::swap(other); + underlying_type::swap(other); swap(owner, other.owner); swap(construction, other.construction); swap(destruction, other.destruction); @@ -18124,18 +24425,43 @@ public: } /** - * @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. + * @brief Emplace elements into a storage. + * + * The behavior of this operation depends on the underlying storage type + * (for example, components vs entities).
+ * 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).
+ * 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 - decltype(auto) emplace(const entity_type entt, Args &&...args) { - ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); - Type::emplace(entt, std::forward(args)...); - construction.publish(*owner, entt); - return this->get(entt); + decltype(auto) emplace(const entity_type hint, Args &&...args) { + if constexpr(std::is_same_v) { + const auto entt = underlying_type::emplace(hint, std::forward(args)...); + construction.publish(owner_or_assert(), entt); + return entt; + } else { + underlying_type::emplace(hint, std::forward(args)...); + construction.publish(owner_or_assert(), hint); + return this->get(hint); + } } /** @@ -18147,29 +24473,32 @@ public: */ template decltype(auto) patch(const entity_type entt, Func &&...func) { - ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); - Type::patch(entt, std::forward(func)...); - update.publish(*owner, entt); + underlying_type::patch(entt, std::forward(func)...); + update.publish(owner_or_assert(), 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. + * @brief Emplace elements into a storage. + * + * The behavior of this operation depends on the underlying storage type + * (for example, components vs entities).
+ * 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 void insert(It first, It last, Args &&...args) { - ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); - Type::insert(first, last, std::forward(args)...); + underlying_type::insert(first, last, std::forward(args)...); - for(auto it = construction.empty() ? last : first; it != last; ++it) { - construction.publish(*owner, *it); + if(auto ® = owner_or_assert(); !construction.empty()) { + for(; first != last; ++first) { + construction.publish(reg, *first); + } } } @@ -18180,7 +24509,7 @@ public: void bind(any value) noexcept final { auto *reg = any_cast(&value); owner = reg ? reg : owner; - Type::bind(std::move(value)); + underlying_type::bind(std::move(value)); } private: @@ -18194,4324 +24523,6 @@ private: #endif - -namespace entt { - -/** - * @cond TURN_OFF_DOXYGEN - * Internal details not to be documented. - */ - -namespace internal { - -template -class storage_iterator final { - friend storage_iterator; - - using container_type = std::remove_const_t; - using alloc_traits = std::allocator_traits; - using comp_traits = component_traits>; - - using iterator_traits = std::iterator_traits, - typename alloc_traits::template rebind_traits::element_type>::const_pointer, - typename alloc_traits::template rebind_traits::element_type>::pointer>>; - -public: - using value_type = typename iterator_traits::value_type; - using pointer = typename iterator_traits::pointer; - using reference = typename iterator_traits::reference; - using difference_type = typename iterator_traits::difference_type; - using iterator_category = std::random_access_iterator_tag; - - constexpr storage_iterator() noexcept = default; - - constexpr storage_iterator(Container *ref, const difference_type idx) noexcept - : packed{ref}, - offset{idx} {} - - template, typename = std::enable_if_t> - constexpr storage_iterator(const storage_iterator> &other) noexcept - : storage_iterator{other.packed, other.offset} {} - - constexpr storage_iterator &operator++() noexcept { - return --offset, *this; - } - - constexpr storage_iterator operator++(int) noexcept { - storage_iterator orig = *this; - return ++(*this), orig; - } - - constexpr storage_iterator &operator--() noexcept { - return ++offset, *this; - } - - constexpr storage_iterator operator--(int) noexcept { - storage_iterator orig = *this; - return operator--(), orig; - } - - constexpr storage_iterator &operator+=(const difference_type value) noexcept { - offset -= value; - return *this; - } - - constexpr storage_iterator operator+(const difference_type value) const noexcept { - storage_iterator copy = *this; - return (copy += value); - } - - constexpr storage_iterator &operator-=(const difference_type value) noexcept { - return (*this += -value); - } - - constexpr storage_iterator operator-(const difference_type value) const noexcept { - return (*this + -value); - } - - [[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)]; - } - - [[nodiscard]] constexpr pointer operator->() const noexcept { - const auto pos = index(); - return (*packed)[pos / comp_traits::page_size] + fast_mod(pos, comp_traits::page_size); - } - - [[nodiscard]] constexpr reference operator*() const noexcept { - return *operator->(); - } - - [[nodiscard]] constexpr difference_type index() const noexcept { - return offset - 1; - } - -private: - Container *packed; - difference_type offset; -}; - -template -[[nodiscard]] constexpr std::ptrdiff_t operator-(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { - return rhs.index() - lhs.index(); -} - -template -[[nodiscard]] constexpr bool operator==(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { - return lhs.index() == rhs.index(); -} - -template -[[nodiscard]] constexpr bool operator!=(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { - return !(lhs == rhs); -} - -template -[[nodiscard]] constexpr bool operator<(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { - return lhs.index() > rhs.index(); -} - -template -[[nodiscard]] constexpr bool operator>(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { - return lhs.index() < rhs.index(); -} - -template -[[nodiscard]] constexpr bool operator<=(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { - return !(lhs > rhs); -} - -template -[[nodiscard]] constexpr bool operator>=(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { - return !(lhs < rhs); -} - -template -class extended_storage_iterator final { - template - friend class extended_storage_iterator; - -public: - using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval()), std::forward_as_tuple(*std::declval()...))); - using pointer = input_iterator_pointer; - using reference = value_type; - using difference_type = std::ptrdiff_t; - using iterator_category = std::input_iterator_tag; - - constexpr extended_storage_iterator() - : it{} {} - - constexpr extended_storage_iterator(It base, Other... other) - : it{base, other...} {} - - template && ...) && (std::is_constructible_v && ...)>> - constexpr extended_storage_iterator(const extended_storage_iterator &other) - : it{other.it} {} - - constexpr extended_storage_iterator &operator++() noexcept { - return ++std::get(it), (++std::get(it), ...), *this; - } - - constexpr extended_storage_iterator operator++(int) noexcept { - extended_storage_iterator orig = *this; - return ++(*this), orig; - } - - [[nodiscard]] constexpr pointer operator->() const noexcept { - return operator*(); - } - - [[nodiscard]] constexpr reference operator*() const noexcept { - return {*std::get(it), *std::get(it)...}; - } - - template - friend constexpr bool operator==(const extended_storage_iterator &, const extended_storage_iterator &) noexcept; - -private: - std::tuple it; -}; - -template -[[nodiscard]] constexpr bool operator==(const extended_storage_iterator &lhs, const extended_storage_iterator &rhs) noexcept { - return std::get<0>(lhs.it) == std::get<0>(rhs.it); -} - -template -[[nodiscard]] constexpr bool operator!=(const extended_storage_iterator &lhs, const extended_storage_iterator &rhs) noexcept { - return !(lhs == rhs); -} - -} // namespace internal - -/** - * Internal details not to be documented. - * @endcond - */ - -/** - * @brief Basic storage implementation. - * - * Internal data structures arrange elements to maximize performance. There are - * no guarantees that objects are returned in the insertion order when iterate - * a storage. Do not make assumption on the order in any case. - * - * @warning - * Empty types aren't explicitly instantiated. Therefore, many of the functions - * 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 Allocator Type of allocator used to manage memory and elements. - */ -template -class basic_storage: public basic_sparse_set::template rebind_alloc> { - using alloc_traits = std::allocator_traits; - static_assert(std::is_same_v, "Invalid value type"); - using underlying_type = basic_sparse_set>; - using container_type = std::vector>; - using comp_traits = component_traits; - - static constexpr bool is_pinned_type_v = !(std::is_move_constructible_v && std::is_move_assignable_v); - - [[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)]; - } - - auto assure_at_least(const std::size_t pos) { - auto &&container = packed.first(); - const auto idx = pos / comp_traits::page_size; - - if(!(idx < container.size())) { - auto curr = container.size(); - container.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); - } - } - ENTT_CATCH { - container.resize(curr); - ENTT_THROW; - } - } - - return container[idx] + fast_mod(pos, comp_traits::page_size); - } - - template - auto emplace_element(const Entity entt, const bool force_back, Args &&...args) { - const auto it = base_type::try_emplace(entt, force_back); - - ENTT_TRY { - auto elem = assure_at_least(static_cast(it.index())); - entt::uninitialized_construct_using_allocator(to_address(elem), packed.second(), std::forward(args)...); - } - ENTT_CATCH { - base_type::pop(it, it + 1u); - ENTT_THROW; - } - - return it; - } - - void shrink_to_size(const std::size_t sz) { - for(auto pos = sz, length = base_type::size(); pos < length; ++pos) { - if constexpr(comp_traits::in_place_delete) { - if(base_type::at(pos) != tombstone) { - std::destroy_at(std::addressof(element_at(pos))); - } - } else { - std::destroy_at(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); - } - - container.resize(from); - } - -private: - const void *get_at(const std::size_t pos) const final { - 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 { - // 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)); - } - } - -protected: - /*! @brief Random access iterator type. */ - using basic_iterator = typename underlying_type::basic_iterator; - - /** - * @brief Erases entities from a sparse set. - * @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) { - // 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) { - base_type::in_place_pop(first); - std::destroy_at(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)); - base_type::swap_and_pop(first); - } - } - } - - /** - * @brief Assigns an entity to a storage. - * @param entt A valid identifier. - * @param value Optional opaque value. - * @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 { - if(value) { - if constexpr(std::is_copy_constructible_v) { - return emplace_element(entt, force_back, *static_cast(value)); - } else { - return base_type::end(); - } - } else { - if constexpr(std::is_default_constructible_v) { - return emplace_element(entt, force_back); - } else { - return base_type::end(); - } - } - } - -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 Underlying entity identifier. */ - using entity_type = Entity; - /*! @brief Unsigned integer type. */ - using size_type = std::size_t; - /*! @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::const_pointer; - /*! @brief Random access iterator type. */ - using iterator = internal::storage_iterator; - /*! @brief Constant random access iterator type. */ - using const_iterator = internal::storage_iterator; - /*! @brief Reverse iterator type. */ - using reverse_iterator = std::reverse_iterator; - /*! @brief Constant reverse iterator type. */ - using const_reverse_iterator = std::reverse_iterator; - /*! @brief Extended iterable storage proxy. */ - using iterable = iterable_adaptor>; - /*! @brief Constant extended iterable storage proxy. */ - using const_iterable = iterable_adaptor>; - - /*! @brief Default constructor. */ - basic_storage() - : basic_storage{allocator_type{}} {} - - /** - * @brief Constructs an empty storage with a given allocator. - * @param allocator The allocator to use. - */ - explicit basic_storage(const allocator_type &allocator) - : base_type{type_id(), deletion_policy{comp_traits::in_place_delete}, allocator}, - packed{container_type{allocator}, allocator} {} - - /** - * @brief Move constructor. - * @param other The instance to move from. - */ - basic_storage(basic_storage &&other) noexcept - : base_type{std::move(other)}, - packed{std::move(other.packed)} {} - - /** - * @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}, - 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"); - } - - /*! @brief Default destructor. */ - ~basic_storage() override { - shrink_to_size(0u); - } - - /** - * @brief Move assignment operator. - * @param other The instance to move from. - * @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"); - - 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()); - return *this; - } - - /** - * @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; - underlying_type::swap(other); - propagate_on_container_swap(packed.second(), other.packed.second()); - swap(packed.first(), other.packed.first()); - } - - /** - * @brief Returns the associated allocator. - * @return The associated allocator. - */ - [[nodiscard]] constexpr allocator_type get_allocator() const noexcept { - return allocator_type{packed.second()}; - } - - /** - * @brief Increases the capacity of a storage. - * - * If the new capacity is greater than the current capacity, new storage is - * allocated, otherwise the method does nothing. - * - * @param cap Desired capacity. - */ - void reserve(const size_type cap) override { - if(cap != 0u) { - base_type::reserve(cap); - assure_at_least(cap - 1u); - } - } - - /** - * @brief Returns the number of elements that a storage has currently - * allocated space for. - * @return Capacity of the storage. - */ - [[nodiscard]] size_type capacity() const noexcept override { - return packed.first().size() * comp_traits::page_size; - } - - /*! @brief Requests the removal of unused capacity. */ - void shrink_to_fit() override { - base_type::shrink_to_fit(); - shrink_to_size(base_type::size()); - } - - /** - * @brief Direct access to the array of objects. - * @return A pointer to the array of objects. - */ - [[nodiscard]] const_pointer raw() const noexcept { - return packed.first().data(); - } - - /*! @copydoc raw */ - [[nodiscard]] pointer raw() noexcept { - return packed.first().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(base_type::size()); - return const_iterator{&packed.first(), pos}; - } - - /*! @copydoc cbegin */ - [[nodiscard]] const_iterator begin() const noexcept { - return cbegin(); - } - - /*! @copydoc begin */ - [[nodiscard]] iterator begin() noexcept { - const auto pos = static_cast(base_type::size()); - return iterator{&packed.first(), 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(), {}}; - } - - /*! @copydoc cend */ - [[nodiscard]] const_iterator end() const noexcept { - return cend(); - } - - /*! @copydoc end */ - [[nodiscard]] iterator end() noexcept { - return iterator{&packed.first(), {}}; - } - - /** - * @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()`. - * - * @return An iterator to the first instance of the reversed internal array. - */ - [[nodiscard]] const_reverse_iterator crbegin() const noexcept { - return std::make_reverse_iterator(cend()); - } - - /*! @copydoc crbegin */ - [[nodiscard]] const_reverse_iterator rbegin() const noexcept { - return crbegin(); - } - - /*! @copydoc rbegin */ - [[nodiscard]] reverse_iterator rbegin() noexcept { - return std::make_reverse_iterator(end()); - } - - /** - * @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. - */ - [[nodiscard]] const_reverse_iterator crend() const noexcept { - return std::make_reverse_iterator(cbegin()); - } - - /*! @copydoc crend */ - [[nodiscard]] const_reverse_iterator rend() const noexcept { - return crend(); - } - - /*! @copydoc rend */ - [[nodiscard]] reverse_iterator rend() noexcept { - return std::make_reverse_iterator(begin()); - } - - /** - * @brief Returns the object assigned to an entity. - * - * @warning - * Attempting to use an entity that doesn't belong to the storage results in - * undefined behavior. - * - * @param entt A valid identifier. - * @return The object assigned to the entity. - */ - [[nodiscard]] const value_type &get(const entity_type entt) const noexcept { - return element_at(base_type::index(entt)); - } - - /*! @copydoc get */ - [[nodiscard]] value_type &get(const entity_type entt) noexcept { - return const_cast(std::as_const(*this).get(entt)); - } - - /** - * @brief Returns the object assigned to an entity as a tuple. - * @param entt A valid identifier. - * @return The object assigned to the entity as a tuple. - */ - [[nodiscard]] std::tuple get_as_tuple(const entity_type entt) const noexcept { - return std::forward_as_tuple(get(entt)); - } - - /*! @copydoc get_as_tuple */ - [[nodiscard]] std::tuple get_as_tuple(const entity_type entt) noexcept { - return std::forward_as_tuple(get(entt)); - } - - /** - * @brief Assigns an entity to a storage and constructs its object. - * - * @warning - * Attempting to use an entity that already belongs to the storage results - * in undefined behavior. - * - * @tparam Args Types of arguments to use to construct the object. - * @param entt A valid identifier. - * @param args Parameters to use to construct an object for the entity. - * @return A reference to the newly created object. - */ - template - value_type &emplace(const entity_type entt, Args &&...args) { - if constexpr(std::is_aggregate_v) { - const auto it = emplace_element(entt, false, Type{std::forward(args)...}); - return element_at(static_cast(it.index())); - } else { - const auto it = emplace_element(entt, false, std::forward(args)...); - return element_at(static_cast(it.index())); - } - } - - /** - * @brief Updates the instance assigned to a given entity in-place. - * @tparam Func Types of the function objects to invoke. - * @param entt A valid identifier. - * @param func Valid function objects. - * @return A reference to the updated instance. - */ - template - value_type &patch(const entity_type entt, Func &&...func) { - const auto idx = base_type::index(entt); - auto &elem = element_at(idx); - (std::forward(func)(elem), ...); - return elem; - } - - /** - * @brief Assigns one or more entities to a storage and constructs their - * objects from a given instance. - * - * @warning - * Attempting to assign an entity that already belongs to the storage - * results in undefined behavior. - * - * @tparam It Type of input iterator. - * @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. - */ - template - void insert(It first, It last, const value_type &value = {}) { - for(; first != last; ++first) { - emplace_element(*first, true, value); - } - } - - /** - * @brief Assigns one or more entities to a storage and constructs their - * objects from a given range. - * - * @sa construct - * - * @tparam EIt Type of input iterator. - * @tparam CIt Type of input iterator. - * @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. - */ - template::value_type, value_type>>> - void insert(EIt first, EIt last, CIt from) { - for(; first != last; ++first, ++from) { - emplace_element(*first, true, *from); - } - } - - /** - * @brief Returns an iterable object to use to _visit_ a storage. - * - * The iterable object returns a tuple that contains the current entity and - * a reference to its component. - * - * @return An iterable object to use to _visit_ the storage. - */ - [[nodiscard]] iterable each() noexcept { - return {internal::extended_storage_iterator{base_type::begin(), begin()}, internal::extended_storage_iterator{base_type::end(), end()}}; - } - - /*! @copydoc each */ - [[nodiscard]] const_iterable each() const noexcept { - return {internal::extended_storage_iterator{base_type::cbegin(), cbegin()}, internal::extended_storage_iterator{base_type::cend(), cend()}}; - } - -private: - compressed_pair packed; -}; - -/*! @copydoc basic_storage */ -template -class basic_storage>> - : public basic_sparse_set::template rebind_alloc> { - using alloc_traits = std::allocator_traits; - static_assert(std::is_same_v, "Invalid value type"); - using underlying_type = basic_sparse_set>; - using comp_traits = component_traits; - -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 Underlying entity identifier. */ - using entity_type = Entity; - /*! @brief Unsigned integer type. */ - using size_type = std::size_t; - /*! @brief Extended iterable storage proxy. */ - using iterable = iterable_adaptor>; - /*! @brief Constant extended iterable storage proxy. */ - using const_iterable = iterable_adaptor>; - - /*! @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(), deletion_policy{comp_traits::in_place_delete}, allocator} {} - - /** - * @brief Move constructor. - * @param other The instance to move from. - */ - basic_storage(basic_storage &&other) noexcept = default; - - /** - * @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} {} - - /** - * @brief Move assignment operator. - * @param other The instance to move from. - * @return This storage. - */ - basic_storage &operator=(basic_storage &&other) noexcept = default; - - /** - * @brief Returns the associated allocator. - * @return The associated allocator. - */ - [[nodiscard]] constexpr allocator_type get_allocator() const noexcept { - return allocator_type{base_type::get_allocator()}; - } - - /** - * @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::contains(entt), "Storage does not contain entity"); - } - - /** - * @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::contains(entt), "Storage does not contain entity"); - return std::tuple{}; - } - - /** - * @brief Assigns an entity to a storage and constructs its object. - * - * @warning - * Attempting to use an entity that already belongs to the storage results - * in undefined behavior. - * - * @tparam Args Types of arguments to use to construct the object. - * @param entt A valid identifier. - */ - template - void emplace(const entity_type entt, Args &&...) { - base_type::try_emplace(entt, false); - } - - /** - * @brief Updates the instance assigned to a given entity in-place. - * @tparam Func Types of the function objects to invoke. - * @param entt A valid identifier. - * @param func Valid function objects. - */ - template - void patch([[maybe_unused]] const entity_type entt, Func &&...func) { - ENTT_ASSERT(base_type::contains(entt), "Storage does not contain entity"); - (std::forward(func)(), ...); - } - - /** - * @brief Assigns entities to a storage. - * @tparam It Type of input iterator. - * @tparam Args Types of optional arguments. - * @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. - */ - template - void insert(It first, It last, Args &&...) { - for(; first != last; ++first) { - base_type::try_emplace(*first, true); - } - } - - /** - * @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::begin()}, internal::extended_storage_iterator{base_type::end()}}; - } - - /*! @copydoc each */ - [[nodiscard]] const_iterable each() const noexcept { - return {internal::extended_storage_iterator{base_type::cbegin()}, internal::extended_storage_iterator{base_type::cend()}}; - } -}; - -} // namespace entt - -#endif - - -namespace entt { - -/** - * @cond TURN_OFF_DOXYGEN - * Internal details not to be documented. - */ - -namespace internal { - -template -class extended_group_iterator; - -template -class extended_group_iterator, get_t> { - template - auto index_to_element([[maybe_unused]] Type &cpool) const { - if constexpr(ignore_as_empty_v) { - return std::make_tuple(); - } else { - return std::forward_as_tuple(cpool.rbegin()[it.index()]); - } - } - -public: - using difference_type = std::ptrdiff_t; - using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval()), std::declval().get_as_tuple({})..., std::declval().get_as_tuple({})...)); - using pointer = input_iterator_pointer; - using reference = value_type; - using iterator_category = std::input_iterator_tag; - - constexpr extended_group_iterator() - : it{}, - pools{} {} - - extended_group_iterator(It from, const std::tuple &cpools) - : it{from}, - pools{cpools} {} - - extended_group_iterator &operator++() noexcept { - return ++it, *this; - } - - extended_group_iterator operator++(int) noexcept { - extended_group_iterator orig = *this; - return ++(*this), orig; - } - - [[nodiscard]] reference operator*() const noexcept { - return std::tuple_cat(std::make_tuple(*it), index_to_element(*std::get(pools))..., std::get(pools)->get_as_tuple(*it)...); - } - - [[nodiscard]] pointer operator->() const noexcept { - return operator*(); - } - - template - friend constexpr bool operator==(const extended_group_iterator &, const extended_group_iterator &) noexcept; - -private: - It it; - std::tuple pools; -}; - -template -[[nodiscard]] constexpr bool operator==(const extended_group_iterator &lhs, const extended_group_iterator &rhs) noexcept { - return lhs.it == rhs.it; -} - -template -[[nodiscard]] constexpr bool operator!=(const extended_group_iterator &lhs, const extended_group_iterator &rhs) noexcept { - return !(lhs == rhs); -} - -} // namespace internal - -/** - * Internal details not to be documented. - * @endcond - */ - -/** - * @brief Group. - * - * Primary template isn't defined on purpose. All the specializations give a - * compile-time error, but for a few reasonable cases. - */ -template -class basic_group; - -/** - * @brief Non-owning group. - * - * A non-owning group returns all entities and only the entities that are at - * least in the given storage. Moreover, it's guaranteed that the entity list is - * tightly packed in memory for fast iterations. - * - * @b Important - * - * Iterators aren't invalidated if: - * - * * 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 other cases, modifying the pools iterated by the group in any way - * invalidates all the iterators and using them results in undefined behavior. - * - * @tparam Get Types of storage _observed_ by the group. - * @tparam Exclude Types of storage used to filter the group. - */ -template -class basic_group, get_t, exclude_t> { - using underlying_type = std::common_type_t; - using basic_common_type = std::common_type_t; - - template - static constexpr std::size_t index_of = type_list_index_v, type_list>; - -public: - /*! @brief Underlying entity identifier. */ - using entity_type = underlying_type; - /*! @brief Unsigned integer type. */ - using size_type = std::size_t; - /*! @brief Common type among all storage types. */ - using base_type = basic_common_type; - /*! @brief Random access iterator type. */ - using iterator = typename base_type::iterator; - /*! @brief Reversed iterator type. */ - using reverse_iterator = typename base_type::reverse_iterator; - /*! @brief Iterable group type. */ - using iterable = iterable_adaptor, get_t>>; - - /*! @brief Default constructor to use to create empty, invalid groups. */ - basic_group() noexcept - : handler{} {} - - /** - * @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. - */ - basic_group(basic_common_type &ref, Get &...gpool) noexcept - : handler{&ref}, - pools{&gpool...} {} - - /** - * @brief Returns a const reference to the underlying handler. - * @return A const reference to the underlying handler. - */ - [[nodiscard]] const base_type &handle() const noexcept { - return *handler; - } - - /** - * @brief Returns the storage for a given component type. - * @tparam Type Type of component of which to return the storage. - * @return The storage for the given component type. - */ - template - [[nodiscard]] decltype(auto) storage() const noexcept { - return storage>(); - } - - /** - * @brief Returns the storage for a given index. - * @tparam Index Index of the storage to return. - * @return The storage for the given index. - */ - template - [[nodiscard]] decltype(auto) storage() const noexcept { - return *std::get(pools); - } - - /** - * @brief Returns the number of entities that are part of the group. - * @return Number of entities that are part of the group. - */ - [[nodiscard]] size_type size() const noexcept { - return *this ? handler->size() : size_type{}; - } - - /** - * @brief Returns the number of elements that a group has currently - * allocated space for. - * @return Capacity of the group. - */ - [[nodiscard]] size_type capacity() const noexcept { - return *this ? handler->capacity() : size_type{}; - } - - /*! @brief Requests the removal of unused capacity. */ - void shrink_to_fit() { - if(*this) { - handler->shrink_to_fit(); - } - } - - /** - * @brief Checks whether a group is empty. - * @return True if the group is empty, false otherwise. - */ - [[nodiscard]] bool empty() const noexcept { - return !*this || handler->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()`. - * - * @return An iterator to the first entity of the group. - */ - [[nodiscard]] iterator begin() const noexcept { - return *this ? handler->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{}; - } - - /** - * @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{}; - } - - /** - * @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{}; - } - - /** - * @brief Returns the first entity of the group, if any. - * @return The first entity of the group if one exists, the null entity - * otherwise. - */ - [[nodiscard]] entity_type front() const noexcept { - const auto it = begin(); - return it != end() ? *it : null; - } - - /** - * @brief Returns the last entity of the group, if any. - * @return The last entity of the group if one exists, the null entity - * otherwise. - */ - [[nodiscard]] entity_type back() const noexcept { - const auto it = rbegin(); - return it != rend() ? *it : null; - } - - /** - * @brief Finds an entity. - * @param entt A valid identifier. - * @return An iterator to the given entity if it's found, past the end - * 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(); - } - - /** - * @brief Returns the identifier that occupies the given position. - * @param pos Position of the element to return. - * @return The identifier that occupies the given position. - */ - [[nodiscard]] entity_type operator[](const size_type pos) const { - return begin()[pos]; - } - - /** - * @brief Checks if a group is properly initialized. - * @return True if the group is properly initialized, false otherwise. - */ - [[nodiscard]] explicit operator bool() const noexcept { - return handler != nullptr; - } - - /** - * @brief Checks if a group contains an entity. - * @param entt A valid identifier. - * @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); - } - - /** - * @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. - * - * @tparam Type Types of components to get. - * @param entt A valid identifier. - * @return The components assigned to the entity. - */ - template - [[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>(pools)->get(entt), ...); - } else { - return std::tuple_cat(std::get>(pools)->get_as_tuple(entt)...); - } - } - - /** - * @brief Iterates entities and components and applies the given function - * object to them. - * - * The function object is invoked for each entity. It is provided with the - * entity itself and a set of references to non-empty components. The - * _constness_ of the components is as requested.
- * The signature of the function must be equivalent to one of the following - * forms: - * - * @code{.cpp} - * void(const entity_type, Type &...); - * void(Type &...); - * @endcode - * - * @note - * Empty types aren't explicitly instantiated and therefore they are never - * returned during iterations. - * - * @tparam Func Type of the function object to invoke. - * @param func A valid function object. - */ - template - void each(Func func) const { - for(const auto entt: *this) { - if constexpr(is_applicable_v{}, std::declval().get({})))>) { - std::apply(func, std::tuple_cat(std::make_tuple(entt), get(entt))); - } else { - std::apply(func, get(entt)); - } - } - } - - /** - * @brief Returns an iterable object to use to _visit_ a group. - * - * The iterable object returns tuples that contain the current entity and a - * set of references to its non-empty components. The _constness_ of the - * components is as requested. - * - * @note - * Empty types aren't explicitly instantiated and therefore they are never - * returned during iterations. - * - * @return An iterable object to use to _visit_ the group. - */ - [[nodiscard]] iterable each() const noexcept { - return iterable{{begin(), pools}, {end(), pools}}; - } - - /** - * @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: - * - * @code{.cpp} - * bool(std::tuple, std::tuple); - * bool(const Type &..., const Type &...); - * bool(const Entity, const Entity); - * @endcode - * - * Where `Type` are such that they are iterated by the group.
- * Moreover, the comparison function object shall induce a - * _strict weak ordering_ on the values. - * - * The sort function object must offer a member function template - * `operator()` that accepts three arguments: - * - * * An iterator to the first element of the range to sort. - * * 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 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 - void sort(Compare compare, Sort algo = Sort{}, Args &&...args) { - if(*this) { - if constexpr(sizeof...(Type) == 0) { - static_assert(std::is_invocable_v, "Invalid comparison function"); - handler->sort(std::move(compare), std::move(algo), std::forward(args)...); - } else { - auto comp = [this, &compare](const entity_type lhs, const entity_type rhs) { - if constexpr(sizeof...(Type) == 1) { - return compare((std::get>(pools)->get(lhs), ...), (std::get>(pools)->get(rhs), ...)); - } else { - return compare(std::forward_as_tuple(std::get>(pools)->get(lhs)...), std::forward_as_tuple(std::get>(pools)->get(rhs)...)); - } - }; - - handler->sort(std::move(comp), std::move(algo), std::forward(args)...); - } - } - } - - /** - * @brief Sort the shared pool of entities according to the given component. - * - * 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. - * - * @tparam Type Type of component to use to impose the order. - */ - template - void sort() const { - if(*this) { - handler->respect(*std::get>(pools)); - } - } - -private: - base_type *const handler; - const std::tuple pools; -}; - -/** - * @brief Owning group. - * - * Owning groups returns all entities and only the entities that are at - * least in the given storage. Moreover: - * - * * It's guaranteed that the entity list is tightly packed in memory for fast - * iterations. - * * It's guaranteed that all components in the owned storage are tightly packed - * in memory for even faster iterations and to allow direct access. - * * They stay true to the order of the owned storage and all instances have the - * same order in memory. - * - * The more types of storage are owned, the faster it is to iterate a group. - * - * @b Important - * - * Iterators aren't invalidated if: - * - * * 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 other cases, modifying the pools iterated by the group in any way - * invalidates all the iterators and using them results in undefined behavior. - * - * @tparam Owned Types of storage _owned_ by the group. - * @tparam Get Types of storage _observed_ by the group. - * @tparam Exclude Types of storage used to filter the group. - */ -template -class basic_group, get_t, exclude_t> { - using underlying_type = std::common_type_t; - using basic_common_type = std::common_type_t; - - template - static constexpr std::size_t index_of = type_list_index_v, type_list>; - -public: - /*! @brief Underlying entity identifier. */ - using entity_type = underlying_type; - /*! @brief Unsigned integer type. */ - using size_type = std::size_t; - /*! @brief Common type among all storage types. */ - using base_type = basic_common_type; - /*! @brief Random access iterator type. */ - using iterator = typename base_type::iterator; - /*! @brief Reversed iterator type. */ - using reverse_iterator = typename base_type::reverse_iterator; - /*! @brief Iterable group type. */ - using iterable = iterable_adaptor, get_t>>; - - /*! @brief Default constructor to use to create empty, invalid groups. */ - basic_group() noexcept - : length{} {} - - /** - * @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. - */ - basic_group(const std::size_t &extent, Owned &...opool, Get &...gpool) noexcept - : pools{&opool..., &gpool...}, - length{&extent} {} - - /** - * @brief Returns the storage for a given component type. - * @tparam Type Type of component of which to return the storage. - * @return The storage for the given component type. - */ - template - [[nodiscard]] decltype(auto) storage() const noexcept { - return storage>(); - } - - /** - * @brief Returns the storage for a given index. - * @tparam Index Index of the storage to return. - * @return The storage for the given index. - */ - template - [[nodiscard]] decltype(auto) storage() const noexcept { - return *std::get(pools); - } - - /** - * @brief Returns the number of entities that that are part of the group. - * @return Number of entities that that are part of the group. - */ - [[nodiscard]] size_type size() const noexcept { - return *this ? *length : size_type{}; - } - - /** - * @brief Checks whether a group is empty. - * @return True if the group is empty, false otherwise. - */ - [[nodiscard]] bool empty() const noexcept { - return !*this || !*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()`. - * - * @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{}; - } - - /** - * @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{}; - } - - /** - * @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{}; - } - - /** - * @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{}; - } - - /** - * @brief Returns the first entity of the group, if any. - * @return The first entity of the group if one exists, the null entity - * otherwise. - */ - [[nodiscard]] entity_type front() const noexcept { - const auto it = begin(); - return it != end() ? *it : null; - } - - /** - * @brief Returns the last entity of the group, if any. - * @return The last entity of the group if one exists, the null entity - * otherwise. - */ - [[nodiscard]] entity_type back() const noexcept { - const auto it = rbegin(); - return it != rend() ? *it : null; - } - - /** - * @brief Finds an entity. - * @param entt A valid identifier. - * @return An iterator to the given entity if it's found, past the end - * 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(); - } - - /** - * @brief Returns the identifier that occupies the given position. - * @param pos Position of the element to return. - * @return The identifier that occupies the given position. - */ - [[nodiscard]] entity_type operator[](const size_type pos) const { - return begin()[pos]; - } - - /** - * @brief Checks if a group is properly initialized. - * @return True if the group is properly initialized, false otherwise. - */ - [[nodiscard]] explicit operator bool() const noexcept { - return length != nullptr; - } - - /** - * @brief Checks if a group contains an entity. - * @param entt A valid identifier. - * @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)); - } - - /** - * @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. - * - * @tparam Type Types of components to get. - * @param entt A valid identifier. - * @return The components assigned to the entity. - */ - template - [[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>(pools)->get(entt), ...); - } else { - return std::tuple_cat(std::get>(pools)->get_as_tuple(entt)...); - } - } - - /** - * @brief Iterates entities and components and applies the given function - * object to them. - * - * The function object is invoked for each entity. It is provided with the - * entity itself and a set of references to non-empty components. The - * _constness_ of the components is as requested.
- * The signature of the function must be equivalent to one of the following - * forms: - * - * @code{.cpp} - * void(const entity_type, Type &...); - * void(Type &...); - * @endcode - * - * @note - * Empty types aren't explicitly instantiated and therefore they are never - * returned during iterations. - * - * @tparam Func Type of the function object to invoke. - * @param func A valid function object. - */ - template - void each(Func func) const { - for(auto args: each()) { - if constexpr(is_applicable_v{}, std::declval().get({})))>) { - std::apply(func, args); - } else { - std::apply([&func](auto, auto &&...less) { func(std::forward(less)...); }, args); - } - } - } - - /** - * @brief Returns an iterable object to use to _visit_ a group. - * - * The iterable object returns tuples that contain the current entity and a - * set of references to its non-empty components. The _constness_ of the - * components is as requested. - * - * @note - * Empty types aren't explicitly instantiated and therefore they are never - * returned during iterations. - * - * @return An iterable object to use to _visit_ the group. - */ - [[nodiscard]] iterable each() const noexcept { - return {{begin(), pools}, {end(), pools}}; - } - - /** - * @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: - * - * @code{.cpp} - * bool(std::tuple, std::tuple); - * bool(const Type &, const Type &); - * bool(const Entity, const Entity); - * @endcode - * - * Where `Type` are either owned types or not but still such that they are - * iterated by the group.
- * Moreover, the comparison function object shall induce a - * _strict weak ordering_ on the values. - * - * The sort function object must offer a member function template - * `operator()` that accepts three arguments: - * - * * An iterator to the first element of the range to sort. - * * 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 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 - void sort(Compare compare, Sort algo = Sort{}, Args &&...args) const { - if constexpr(sizeof...(Type) == 0) { - static_assert(std::is_invocable_v, "Invalid comparison function"); - std::get<0>(pools)->sort_n(*length, std::move(compare), std::move(algo), std::forward(args)...); - } else { - auto comp = [this, &compare](const entity_type lhs, const entity_type rhs) { - if constexpr(sizeof...(Type) == 1) { - return compare((std::get>(pools)->get(lhs), ...), (std::get>(pools)->get(rhs), ...)); - } else { - return compare(std::forward_as_tuple(std::get>(pools)->get(lhs)...), std::forward_as_tuple(std::get>(pools)->get(rhs)...)); - } - }; - - std::get<0>(pools)->sort_n(*length, std::move(comp), std::move(algo), std::forward(args)...); - } - - std::apply([this](auto *head, auto *...other) { - for(auto next = *length; next; --next) { - const auto pos = next - 1; - [[maybe_unused]] const auto entt = head->data()[pos]; - (other->swap_elements(other->data()[pos], entt), ...); - } - }, - pools); - } - -private: - const std::tuple pools; - const size_type *const length; -}; - -} // namespace entt - -#endif - -// #include "entity/handle.hpp" -#ifndef ENTT_ENTITY_HANDLE_HPP -#define ENTT_ENTITY_HANDLE_HPP - -#include -#include -#include -#include -// #include "../core/iterator.hpp" - -// #include "../core/type_traits.hpp" - -// #include "entity.hpp" - -// #include "fwd.hpp" - - -namespace entt { - -/** - * @cond TURN_OFF_DOXYGEN - * Internal details not to be documented. - */ - -namespace internal { - -template -class handle_storage_iterator final { - template - friend class handle_storage_iterator; - - using underlying_type = std::remove_reference_t; - using entity_type = typename underlying_type::entity_type; - -public: - using value_type = typename std::iterator_traits::value_type; - using pointer = input_iterator_pointer; - using reference = value_type; - using difference_type = std::ptrdiff_t; - using iterator_category = std::input_iterator_tag; - - constexpr handle_storage_iterator() noexcept - : entt{null}, - it{}, - last{} {} - - constexpr handle_storage_iterator(entity_type value, It from, It to) noexcept - : entt{value}, - it{from}, - last{to} { - while(it != last && !it->second.contains(entt)) { ++it; } - } - - constexpr handle_storage_iterator &operator++() noexcept { - while(++it != last && !it->second.contains(entt)) {} - return *this; - } - - constexpr handle_storage_iterator operator++(int) noexcept { - handle_storage_iterator orig = *this; - return ++(*this), orig; - } - - [[nodiscard]] constexpr reference operator*() const noexcept { - return *it; - } - - [[nodiscard]] constexpr pointer operator->() const noexcept { - return operator*(); - } - - template - friend constexpr bool operator==(const handle_storage_iterator &, const handle_storage_iterator &) noexcept; - -private: - entity_type entt; - It it; - It last; -}; - -template -[[nodiscard]] constexpr bool operator==(const handle_storage_iterator &lhs, const handle_storage_iterator &rhs) noexcept { - return lhs.it == rhs.it; -} - -template -[[nodiscard]] constexpr bool operator!=(const handle_storage_iterator &lhs, const handle_storage_iterator &rhs) noexcept { - return !(lhs == rhs); -} - -} // namespace internal - -/** - * Internal details not to be documented. - * @endcond - */ - -/** - * @brief Non-owning handle to an entity. - * - * Tiny wrapper around a registry and an entity. - * - * @tparam Registry Basic registry type. - * @tparam Scope Types to which to restrict the scope of a handle. - */ -template -struct basic_handle { - /*! @brief Type of registry accepted by the handle. */ - using registry_type = Registry; - /*! @brief Underlying entity identifier. */ - using entity_type = typename registry_type::entity_type; - /*! @brief Underlying version type. */ - using version_type = typename registry_type::version_type; - /*! @brief Unsigned integer type. */ - using size_type = typename registry_type::size_type; - - /*! @brief Constructs an invalid handle. */ - basic_handle() noexcept - : reg{}, - entt{null} {} - - /** - * @brief Constructs a handle from a given registry and entity. - * @param ref An instance of the registry class. - * @param value A valid identifier. - */ - basic_handle(registry_type &ref, entity_type value) noexcept - : reg{&ref}, - entt{value} {} - - /** - * @brief Returns an iterable object to use to _visit_ a handle. - * - * The iterable object returns a pair that contains the name and a reference - * to the current storage.
- * Returned storage are those that contain the entity associated with the - * handle. - * - * @return An iterable object to use to _visit_ the handle. - */ - [[nodiscard]] auto storage() const noexcept { - auto iterable = reg->storage(); - using iterator_type = internal::handle_storage_iterator; - return iterable_adaptor{iterator_type{entt, iterable.begin(), iterable.end()}, iterator_type{entt, iterable.end(), iterable.end()}}; - } - - /** - * @brief Constructs a const handle from a non-const one. - * @tparam Other A valid entity type (see entt_traits for more details). - * @tparam Args Scope of the handle to construct. - * @return A const handle referring to the same registry and the same - * entity. - */ - template - operator basic_handle() const noexcept { - static_assert(std::is_same_v || std::is_same_v, Registry>, "Invalid conversion between different handles"); - static_assert((sizeof...(Scope) == 0 || ((sizeof...(Args) != 0 && sizeof...(Args) <= sizeof...(Scope)) && ... && (type_list_contains_v, Args>))), "Invalid conversion between different handles"); - - return reg ? basic_handle{*reg, entt} : basic_handle{}; - } - - /** - * @brief Converts a handle to its underlying entity. - * @return The contained identifier. - */ - [[nodiscard]] operator entity_type() const noexcept { - return entity(); - } - - /** - * @brief Checks if a handle refers to non-null registry pointer and entity. - * @return True if the handle refers to non-null registry and entity, false otherwise. - */ - [[nodiscard]] explicit operator bool() const noexcept { - return reg && reg->valid(entt); - } - - /** - * @brief Checks if a handle refers to a valid entity or not. - * @return True if the handle refers to a valid entity, false otherwise. - */ - [[nodiscard]] bool valid() const { - return reg->valid(entt); - } - - /** - * @brief Returns a pointer to the underlying registry, if any. - * @return A pointer to the underlying registry, if any. - */ - [[nodiscard]] registry_type *registry() const noexcept { - return reg; - } - - /** - * @brief Returns the entity associated with a handle. - * @return The entity associated with the handle. - */ - [[nodiscard]] entity_type entity() const noexcept { - return entt; - } - - /*! @brief Destroys the entity associated with a handle. */ - void destroy() { - reg->destroy(entt); - } - - /** - * @brief Destroys the entity associated with a handle. - * @param version A desired version upon destruction. - */ - void destroy(const version_type version) { - reg->destroy(entt, version); - } - - /** - * @brief Assigns the given component to a handle. - * @tparam Component Type of component to create. - * @tparam Args Types of arguments to use to construct the component. - * @param args Parameters to use to initialize the component. - * @return A reference to the newly created component. - */ - template - decltype(auto) emplace(Args &&...args) const { - static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v), "Invalid type"); - return reg->template emplace(entt, std::forward(args)...); - } - - /** - * @brief Assigns or replaces the given component for a handle. - * @tparam Component Type of component to assign or replace. - * @tparam Args Types of arguments to use to construct the component. - * @param args Parameters to use to initialize the component. - * @return A reference to the newly created component. - */ - template - decltype(auto) emplace_or_replace(Args &&...args) const { - static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v), "Invalid type"); - return reg->template emplace_or_replace(entt, std::forward(args)...); - } - - /** - * @brief Patches the given component for a handle. - * @tparam Component Type of component to patch. - * @tparam Func Types of the function objects to invoke. - * @param func Valid function objects. - * @return A reference to the patched component. - */ - template - decltype(auto) patch(Func &&...func) const { - static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v), "Invalid type"); - return reg->template patch(entt, std::forward(func)...); - } - - /** - * @brief Replaces the given component for a handle. - * @tparam Component Type of component to replace. - * @tparam Args Types of arguments to use to construct the component. - * @param args Parameters to use to initialize the component. - * @return A reference to the component being replaced. - */ - template - decltype(auto) replace(Args &&...args) const { - static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v), "Invalid type"); - return reg->template replace(entt, std::forward(args)...); - } - - /** - * @brief Removes the given components from a handle. - * @tparam Component Types of components to remove. - * @return The number of components actually removed. - */ - template - size_type remove() const { - static_assert(sizeof...(Scope) == 0 || (type_list_contains_v, Component> && ...), "Invalid type"); - return reg->template remove(entt); - } - - /** - * @brief Erases the given components from a handle. - * @tparam Component Types of components to erase. - */ - template - void erase() const { - static_assert(sizeof...(Scope) == 0 || (type_list_contains_v, Component> && ...), "Invalid type"); - reg->template erase(entt); - } - - /** - * @brief Checks if a handle has all the given components. - * @tparam Component Components for which to perform the check. - * @return True if the handle has all the components, false otherwise. - */ - template - [[nodiscard]] decltype(auto) all_of() const { - return reg->template all_of(entt); - } - - /** - * @brief Checks if a handle has at least one of the given components. - * @tparam Component Components for which to perform the check. - * @return True if the handle has at least one of the given components, - * false otherwise. - */ - template - [[nodiscard]] decltype(auto) any_of() const { - return reg->template any_of(entt); - } - - /** - * @brief Returns references to the given components for a handle. - * @tparam Component Types of components to get. - * @return References to the components owned by the handle. - */ - template - [[nodiscard]] decltype(auto) get() const { - static_assert(sizeof...(Scope) == 0 || (type_list_contains_v, Component> && ...), "Invalid type"); - return reg->template get(entt); - } - - /** - * @brief Returns a reference to the given component for a handle. - * @tparam Component Type of component to get. - * @tparam Args Types of arguments to use to construct the component. - * @param args Parameters to use to initialize the component. - * @return Reference to the component owned by the handle. - */ - template - [[nodiscard]] decltype(auto) get_or_emplace(Args &&...args) const { - static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v), "Invalid type"); - return reg->template get_or_emplace(entt, std::forward(args)...); - } - - /** - * @brief Returns pointers to the given components for a handle. - * @tparam Component Types of components to get. - * @return Pointers to the components owned by the handle. - */ - template - [[nodiscard]] auto try_get() const { - static_assert(sizeof...(Scope) == 0 || (type_list_contains_v, Component> && ...), "Invalid type"); - return reg->template try_get(entt); - } - - /** - * @brief Checks if a handle has components assigned. - * @return True if the handle has no components assigned, false otherwise. - */ - [[nodiscard]] bool orphan() const { - return reg->orphan(entt); - } - -private: - registry_type *reg; - entity_type entt; -}; - -/** - * @brief Compares two handles. - * @tparam Args Scope of the first handle. - * @tparam Other Scope of the second handle. - * @param lhs A valid handle. - * @param rhs A valid handle. - * @return True if both handles refer to the same registry and the same - * entity, false otherwise. - */ -template -[[nodiscard]] bool operator==(const basic_handle &lhs, const basic_handle &rhs) noexcept { - return lhs.registry() == rhs.registry() && lhs.entity() == rhs.entity(); -} - -/** - * @brief Compares two handles. - * @tparam Args Scope of the first handle. - * @tparam Other Scope of the second handle. - * @param lhs A valid handle. - * @param rhs A valid handle. - * @return False if both handles refer to the same registry and the same - * entity, true otherwise. - */ -template -[[nodiscard]] bool operator!=(const basic_handle &lhs, const basic_handle &rhs) noexcept { - return !(lhs == rhs); -} - -} // namespace entt - -#endif - -// #include "entity/helper.hpp" -#ifndef ENTT_ENTITY_HELPER_HPP -#define ENTT_ENTITY_HELPER_HPP - -#include -#include -// #include "../core/fwd.hpp" - -// #include "../core/type_traits.hpp" - -// #include "../signal/delegate.hpp" -#ifndef ENTT_SIGNAL_DELEGATE_HPP -#define ENTT_SIGNAL_DELEGATE_HPP - -#include -#include -#include -#include -#include -// #include "../config/config.h" - -// #include "../core/type_traits.hpp" - -// #include "fwd.hpp" - - -namespace entt { - -/** - * @cond TURN_OFF_DOXYGEN - * Internal details not to be documented. - */ - -namespace internal { - -template -constexpr auto function_pointer(Ret (*)(Args...)) -> Ret (*)(Args...); - -template -constexpr auto function_pointer(Ret (*)(Type, Args...), Other &&) -> Ret (*)(Args...); - -template -constexpr auto function_pointer(Ret (Class::*)(Args...), Other &&...) -> Ret (*)(Args...); - -template -constexpr auto function_pointer(Ret (Class::*)(Args...) const, Other &&...) -> Ret (*)(Args...); - -template -constexpr auto function_pointer(Type Class::*, Other &&...) -> Type (*)(); - -template -using function_pointer_t = decltype(function_pointer(std::declval()...)); - -template -[[nodiscard]] constexpr auto index_sequence_for(Ret (*)(Args...)) { - return std::index_sequence_for{}; -} - -} // namespace internal - -/** - * Internal details not to be documented. - * @endcond - */ - -/** - * @brief Basic delegate implementation. - * - * Primary template isn't defined on purpose. All the specializations give a - * compile-time error unless the template parameter is a function type. - */ -template -class delegate; - -/** - * @brief Utility class to use to send around functions and members. - * - * Unmanaged delegate for function pointers and members. Users of this class are - * in charge of disconnecting instances before deleting them. - * - * A delegate can be used as a general purpose invoker without memory overhead - * for free functions possibly with payloads and bound or unbound members. - * - * @tparam Ret Return type of a function type. - * @tparam Args Types of arguments of a function type. - */ -template -class delegate { - template - [[nodiscard]] auto wrap(std::index_sequence) noexcept { - return [](const void *, Args... args) -> Ret { - [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); - return static_cast(std::invoke(Candidate, std::forward>>(std::get(arguments))...)); - }; - } - - template - [[nodiscard]] auto wrap(Type &, std::index_sequence) noexcept { - return [](const void *payload, Args... args) -> Ret { - [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); - Type *curr = static_cast(const_cast *>(payload)); - return static_cast(std::invoke(Candidate, *curr, std::forward>>(std::get(arguments))...)); - }; - } - - template - [[nodiscard]] auto wrap(Type *, std::index_sequence) noexcept { - return [](const void *payload, Args... args) -> Ret { - [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); - Type *curr = static_cast(const_cast *>(payload)); - return static_cast(std::invoke(Candidate, curr, std::forward>>(std::get(arguments))...)); - }; - } - -public: - /*! @brief Function type of the contained target. */ - using function_type = Ret(const void *, Args...); - /*! @brief Function type of the delegate. */ - using type = Ret(Args...); - /*! @brief Return type of the delegate. */ - using result_type = Ret; - - /*! @brief Default constructor. */ - delegate() noexcept - : instance{nullptr}, - fn{nullptr} {} - - /** - * @brief Constructs a delegate with a given object or payload, if any. - * @tparam Candidate Function or member to connect to the delegate. - * @tparam Type Type of class or type of payload, if any. - * @param value_or_instance Optional valid object that fits the purpose. - */ - template - delegate(connect_arg_t, Type &&...value_or_instance) noexcept { - connect(std::forward(value_or_instance)...); - } - - /** - * @brief Constructs a delegate and connects an user defined function with - * optional payload. - * @param function Function to connect to the delegate. - * @param payload User defined arbitrary data. - */ - delegate(function_type *function, const void *payload = nullptr) noexcept { - connect(function, payload); - } - - /** - * @brief Connects a free function or an unbound member to a delegate. - * @tparam Candidate Function or member to connect to the delegate. - */ - template - void connect() noexcept { - instance = nullptr; - - if constexpr(std::is_invocable_r_v) { - fn = [](const void *, Args... args) -> Ret { - return Ret(std::invoke(Candidate, std::forward(args)...)); - }; - } else if constexpr(std::is_member_pointer_v) { - fn = wrap(internal::index_sequence_for>>(internal::function_pointer_t{})); - } else { - fn = wrap(internal::index_sequence_for(internal::function_pointer_t{})); - } - } - - /** - * @brief Connects a free function with payload or a bound member to a - * delegate. - * - * The delegate isn't responsible for the connected object or the payload. - * Users must always guarantee that the lifetime of the instance overcomes - * the one of the delegate.
- * 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 delegate itself. - * - * @tparam Candidate Function or member to connect to the delegate. - * @tparam Type Type of class or type of payload. - * @param value_or_instance A valid reference that fits the purpose. - */ - template - void connect(Type &value_or_instance) noexcept { - instance = &value_or_instance; - - if constexpr(std::is_invocable_r_v) { - fn = [](const void *payload, Args... args) -> Ret { - Type *curr = static_cast(const_cast *>(payload)); - return Ret(std::invoke(Candidate, *curr, std::forward(args)...)); - }; - } else { - fn = wrap(value_or_instance, internal::index_sequence_for(internal::function_pointer_t{})); - } - } - - /** - * @brief Connects a free function with payload or a bound member to a - * delegate. - * - * @sa connect(Type &) - * - * @tparam Candidate Function or member to connect to the delegate. - * @tparam Type Type of class or type of payload. - * @param value_or_instance A valid pointer that fits the purpose. - */ - template - void connect(Type *value_or_instance) noexcept { - instance = value_or_instance; - - if constexpr(std::is_invocable_r_v) { - fn = [](const void *payload, Args... args) -> Ret { - Type *curr = static_cast(const_cast *>(payload)); - return Ret(std::invoke(Candidate, curr, std::forward(args)...)); - }; - } else { - fn = wrap(value_or_instance, internal::index_sequence_for(internal::function_pointer_t{})); - } - } - - /** - * @brief Connects an user defined function with optional payload to a - * delegate. - * - * The delegate isn't responsible for the connected object or the payload. - * Users must always guarantee that the lifetime of an instance overcomes - * the one of the delegate.
- * The payload is returned as the first argument to the target function in - * all cases. - * - * @param function Function to connect to the delegate. - * @param payload User defined arbitrary data. - */ - void connect(function_type *function, const void *payload = nullptr) noexcept { - ENTT_ASSERT(function != nullptr, "Uninitialized function pointer"); - instance = payload; - fn = function; - } - - /** - * @brief Resets a delegate. - * - * After a reset, a delegate cannot be invoked anymore. - */ - void reset() noexcept { - instance = nullptr; - fn = nullptr; - } - - /** - * @brief Returns the instance or the payload linked to a delegate, if any. - * @return An opaque pointer to the underlying data. - */ - [[nodiscard]] const void *data() const noexcept { - return instance; - } - - /** - * @brief Triggers a delegate. - * - * The delegate invokes the underlying function and returns the result. - * - * @warning - * Attempting to trigger an invalid delegate results in undefined - * behavior. - * - * @param args Arguments to use to invoke the underlying function. - * @return The value returned by the underlying function. - */ - Ret operator()(Args... args) const { - ENTT_ASSERT(static_cast(*this), "Uninitialized delegate"); - return fn(instance, std::forward(args)...); - } - - /** - * @brief Checks whether a delegate actually stores a listener. - * @return False if the delegate is empty, true otherwise. - */ - [[nodiscard]] explicit operator bool() const noexcept { - // no need to also test instance - return !(fn == nullptr); - } - - /** - * @brief Compares the contents of two delegates. - * @param other Delegate with which to compare. - * @return False if the two contents differ, true otherwise. - */ - [[nodiscard]] bool operator==(const delegate &other) const noexcept { - return fn == other.fn && instance == other.instance; - } - -private: - const void *instance; - function_type *fn; -}; - -/** - * @brief Compares the contents of two delegates. - * @tparam Ret Return type of a function type. - * @tparam Args Types of arguments of a function type. - * @param lhs A valid delegate object. - * @param rhs A valid delegate object. - * @return True if the two contents differ, false otherwise. - */ -template -[[nodiscard]] bool operator!=(const delegate &lhs, const delegate &rhs) noexcept { - return !(lhs == rhs); -} - -/** - * @brief Deduction guide. - * @tparam Candidate Function or member to connect to the delegate. - */ -template -delegate(connect_arg_t) -> delegate>>; - -/** - * @brief Deduction guide. - * @tparam Candidate Function or member to connect to the delegate. - * @tparam Type Type of class or type of payload. - */ -template -delegate(connect_arg_t, Type &&) -> delegate>>; - -/** - * @brief Deduction guide. - * @tparam Ret Return type of a function type. - * @tparam Args Types of arguments of a function type. - */ -template -delegate(Ret (*)(const void *, Args...), const void * = nullptr) -> delegate; - -} // namespace entt - -#endif - -// #include "component.hpp" - -// #include "fwd.hpp" - -// #include "group.hpp" -#ifndef ENTT_ENTITY_GROUP_HPP -#define ENTT_ENTITY_GROUP_HPP - -#include -#include -#include -// #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 { - -/** - * @cond TURN_OFF_DOXYGEN - * Internal details not to be documented. - */ - -namespace internal { - -template -class extended_group_iterator; - -template -class extended_group_iterator, get_t> { - template - auto index_to_element([[maybe_unused]] Type &cpool) const { - if constexpr(ignore_as_empty_v) { - return std::make_tuple(); - } else { - return std::forward_as_tuple(cpool.rbegin()[it.index()]); - } - } - -public: - using difference_type = std::ptrdiff_t; - using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval()), std::declval().get_as_tuple({})..., std::declval().get_as_tuple({})...)); - using pointer = input_iterator_pointer; - using reference = value_type; - using iterator_category = std::input_iterator_tag; - - constexpr extended_group_iterator() - : it{}, - pools{} {} - - extended_group_iterator(It from, const std::tuple &cpools) - : it{from}, - pools{cpools} {} - - extended_group_iterator &operator++() noexcept { - return ++it, *this; - } - - extended_group_iterator operator++(int) noexcept { - extended_group_iterator orig = *this; - return ++(*this), orig; - } - - [[nodiscard]] reference operator*() const noexcept { - return std::tuple_cat(std::make_tuple(*it), index_to_element(*std::get(pools))..., std::get(pools)->get_as_tuple(*it)...); - } - - [[nodiscard]] pointer operator->() const noexcept { - return operator*(); - } - - template - friend constexpr bool operator==(const extended_group_iterator &, const extended_group_iterator &) noexcept; - -private: - It it; - std::tuple pools; -}; - -template -[[nodiscard]] constexpr bool operator==(const extended_group_iterator &lhs, const extended_group_iterator &rhs) noexcept { - return lhs.it == rhs.it; -} - -template -[[nodiscard]] constexpr bool operator!=(const extended_group_iterator &lhs, const extended_group_iterator &rhs) noexcept { - return !(lhs == rhs); -} - -} // namespace internal - -/** - * Internal details not to be documented. - * @endcond - */ - -/** - * @brief Group. - * - * Primary template isn't defined on purpose. All the specializations give a - * compile-time error, but for a few reasonable cases. - */ -template -class basic_group; - -/** - * @brief Non-owning group. - * - * A non-owning group returns all entities and only the entities that are at - * least in the given storage. Moreover, it's guaranteed that the entity list is - * tightly packed in memory for fast iterations. - * - * @b Important - * - * Iterators aren't invalidated if: - * - * * 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 other cases, modifying the pools iterated by the group in any way - * invalidates all the iterators and using them results in undefined behavior. - * - * @tparam Get Types of storage _observed_ by the group. - * @tparam Exclude Types of storage used to filter the group. - */ -template -class basic_group, get_t, exclude_t> { - using underlying_type = std::common_type_t; - using basic_common_type = std::common_type_t; - - template - static constexpr std::size_t index_of = type_list_index_v, type_list>; - -public: - /*! @brief Underlying entity identifier. */ - using entity_type = underlying_type; - /*! @brief Unsigned integer type. */ - using size_type = std::size_t; - /*! @brief Common type among all storage types. */ - using base_type = basic_common_type; - /*! @brief Random access iterator type. */ - using iterator = typename base_type::iterator; - /*! @brief Reversed iterator type. */ - using reverse_iterator = typename base_type::reverse_iterator; - /*! @brief Iterable group type. */ - using iterable = iterable_adaptor, get_t>>; - - /*! @brief Default constructor to use to create empty, invalid groups. */ - basic_group() noexcept - : handler{} {} - - /** - * @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. - */ - basic_group(basic_common_type &ref, Get &...gpool) noexcept - : handler{&ref}, - pools{&gpool...} {} - - /** - * @brief Returns a const reference to the underlying handler. - * @return A const reference to the underlying handler. - */ - [[nodiscard]] const base_type &handle() const noexcept { - return *handler; - } - - /** - * @brief Returns the storage for a given component type. - * @tparam Type Type of component of which to return the storage. - * @return The storage for the given component type. - */ - template - [[nodiscard]] decltype(auto) storage() const noexcept { - return storage>(); - } - - /** - * @brief Returns the storage for a given index. - * @tparam Index Index of the storage to return. - * @return The storage for the given index. - */ - template - [[nodiscard]] decltype(auto) storage() const noexcept { - return *std::get(pools); - } - - /** - * @brief Returns the number of entities that are part of the group. - * @return Number of entities that are part of the group. - */ - [[nodiscard]] size_type size() const noexcept { - return *this ? handler->size() : size_type{}; - } - - /** - * @brief Returns the number of elements that a group has currently - * allocated space for. - * @return Capacity of the group. - */ - [[nodiscard]] size_type capacity() const noexcept { - return *this ? handler->capacity() : size_type{}; - } - - /*! @brief Requests the removal of unused capacity. */ - void shrink_to_fit() { - if(*this) { - handler->shrink_to_fit(); - } - } - - /** - * @brief Checks whether a group is empty. - * @return True if the group is empty, false otherwise. - */ - [[nodiscard]] bool empty() const noexcept { - return !*this || handler->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()`. - * - * @return An iterator to the first entity of the group. - */ - [[nodiscard]] iterator begin() const noexcept { - return *this ? handler->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{}; - } - - /** - * @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{}; - } - - /** - * @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{}; - } - - /** - * @brief Returns the first entity of the group, if any. - * @return The first entity of the group if one exists, the null entity - * otherwise. - */ - [[nodiscard]] entity_type front() const noexcept { - const auto it = begin(); - return it != end() ? *it : null; - } - - /** - * @brief Returns the last entity of the group, if any. - * @return The last entity of the group if one exists, the null entity - * otherwise. - */ - [[nodiscard]] entity_type back() const noexcept { - const auto it = rbegin(); - return it != rend() ? *it : null; - } - - /** - * @brief Finds an entity. - * @param entt A valid identifier. - * @return An iterator to the given entity if it's found, past the end - * 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(); - } - - /** - * @brief Returns the identifier that occupies the given position. - * @param pos Position of the element to return. - * @return The identifier that occupies the given position. - */ - [[nodiscard]] entity_type operator[](const size_type pos) const { - return begin()[pos]; - } - - /** - * @brief Checks if a group is properly initialized. - * @return True if the group is properly initialized, false otherwise. - */ - [[nodiscard]] explicit operator bool() const noexcept { - return handler != nullptr; - } - - /** - * @brief Checks if a group contains an entity. - * @param entt A valid identifier. - * @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); - } - - /** - * @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. - * - * @tparam Type Types of components to get. - * @param entt A valid identifier. - * @return The components assigned to the entity. - */ - template - [[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>(pools)->get(entt), ...); - } else { - return std::tuple_cat(std::get>(pools)->get_as_tuple(entt)...); - } - } - - /** - * @brief Iterates entities and components and applies the given function - * object to them. - * - * The function object is invoked for each entity. It is provided with the - * entity itself and a set of references to non-empty components. The - * _constness_ of the components is as requested.
- * The signature of the function must be equivalent to one of the following - * forms: - * - * @code{.cpp} - * void(const entity_type, Type &...); - * void(Type &...); - * @endcode - * - * @note - * Empty types aren't explicitly instantiated and therefore they are never - * returned during iterations. - * - * @tparam Func Type of the function object to invoke. - * @param func A valid function object. - */ - template - void each(Func func) const { - for(const auto entt: *this) { - if constexpr(is_applicable_v{}, std::declval().get({})))>) { - std::apply(func, std::tuple_cat(std::make_tuple(entt), get(entt))); - } else { - std::apply(func, get(entt)); - } - } - } - - /** - * @brief Returns an iterable object to use to _visit_ a group. - * - * The iterable object returns tuples that contain the current entity and a - * set of references to its non-empty components. The _constness_ of the - * components is as requested. - * - * @note - * Empty types aren't explicitly instantiated and therefore they are never - * returned during iterations. - * - * @return An iterable object to use to _visit_ the group. - */ - [[nodiscard]] iterable each() const noexcept { - return iterable{{begin(), pools}, {end(), pools}}; - } - - /** - * @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: - * - * @code{.cpp} - * bool(std::tuple, std::tuple); - * bool(const Type &..., const Type &...); - * bool(const Entity, const Entity); - * @endcode - * - * Where `Type` are such that they are iterated by the group.
- * Moreover, the comparison function object shall induce a - * _strict weak ordering_ on the values. - * - * The sort function object must offer a member function template - * `operator()` that accepts three arguments: - * - * * An iterator to the first element of the range to sort. - * * 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 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 - void sort(Compare compare, Sort algo = Sort{}, Args &&...args) { - if(*this) { - if constexpr(sizeof...(Type) == 0) { - static_assert(std::is_invocable_v, "Invalid comparison function"); - handler->sort(std::move(compare), std::move(algo), std::forward(args)...); - } else { - auto comp = [this, &compare](const entity_type lhs, const entity_type rhs) { - if constexpr(sizeof...(Type) == 1) { - return compare((std::get>(pools)->get(lhs), ...), (std::get>(pools)->get(rhs), ...)); - } else { - return compare(std::forward_as_tuple(std::get>(pools)->get(lhs)...), std::forward_as_tuple(std::get>(pools)->get(rhs)...)); - } - }; - - handler->sort(std::move(comp), std::move(algo), std::forward(args)...); - } - } - } - - /** - * @brief Sort the shared pool of entities according to the given component. - * - * 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. - * - * @tparam Type Type of component to use to impose the order. - */ - template - void sort() const { - if(*this) { - handler->respect(*std::get>(pools)); - } - } - -private: - base_type *const handler; - const std::tuple pools; -}; - -/** - * @brief Owning group. - * - * Owning groups returns all entities and only the entities that are at - * least in the given storage. Moreover: - * - * * It's guaranteed that the entity list is tightly packed in memory for fast - * iterations. - * * It's guaranteed that all components in the owned storage are tightly packed - * in memory for even faster iterations and to allow direct access. - * * They stay true to the order of the owned storage and all instances have the - * same order in memory. - * - * The more types of storage are owned, the faster it is to iterate a group. - * - * @b Important - * - * Iterators aren't invalidated if: - * - * * 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 other cases, modifying the pools iterated by the group in any way - * invalidates all the iterators and using them results in undefined behavior. - * - * @tparam Owned Types of storage _owned_ by the group. - * @tparam Get Types of storage _observed_ by the group. - * @tparam Exclude Types of storage used to filter the group. - */ -template -class basic_group, get_t, exclude_t> { - using underlying_type = std::common_type_t; - using basic_common_type = std::common_type_t; - - template - static constexpr std::size_t index_of = type_list_index_v, type_list>; - -public: - /*! @brief Underlying entity identifier. */ - using entity_type = underlying_type; - /*! @brief Unsigned integer type. */ - using size_type = std::size_t; - /*! @brief Common type among all storage types. */ - using base_type = basic_common_type; - /*! @brief Random access iterator type. */ - using iterator = typename base_type::iterator; - /*! @brief Reversed iterator type. */ - using reverse_iterator = typename base_type::reverse_iterator; - /*! @brief Iterable group type. */ - using iterable = iterable_adaptor, get_t>>; - - /*! @brief Default constructor to use to create empty, invalid groups. */ - basic_group() noexcept - : length{} {} - - /** - * @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. - */ - basic_group(const std::size_t &extent, Owned &...opool, Get &...gpool) noexcept - : pools{&opool..., &gpool...}, - length{&extent} {} - - /** - * @brief Returns the storage for a given component type. - * @tparam Type Type of component of which to return the storage. - * @return The storage for the given component type. - */ - template - [[nodiscard]] decltype(auto) storage() const noexcept { - return storage>(); - } - - /** - * @brief Returns the storage for a given index. - * @tparam Index Index of the storage to return. - * @return The storage for the given index. - */ - template - [[nodiscard]] decltype(auto) storage() const noexcept { - return *std::get(pools); - } - - /** - * @brief Returns the number of entities that that are part of the group. - * @return Number of entities that that are part of the group. - */ - [[nodiscard]] size_type size() const noexcept { - return *this ? *length : size_type{}; - } - - /** - * @brief Checks whether a group is empty. - * @return True if the group is empty, false otherwise. - */ - [[nodiscard]] bool empty() const noexcept { - return !*this || !*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()`. - * - * @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{}; - } - - /** - * @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{}; - } - - /** - * @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{}; - } - - /** - * @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{}; - } - - /** - * @brief Returns the first entity of the group, if any. - * @return The first entity of the group if one exists, the null entity - * otherwise. - */ - [[nodiscard]] entity_type front() const noexcept { - const auto it = begin(); - return it != end() ? *it : null; - } - - /** - * @brief Returns the last entity of the group, if any. - * @return The last entity of the group if one exists, the null entity - * otherwise. - */ - [[nodiscard]] entity_type back() const noexcept { - const auto it = rbegin(); - return it != rend() ? *it : null; - } - - /** - * @brief Finds an entity. - * @param entt A valid identifier. - * @return An iterator to the given entity if it's found, past the end - * 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(); - } - - /** - * @brief Returns the identifier that occupies the given position. - * @param pos Position of the element to return. - * @return The identifier that occupies the given position. - */ - [[nodiscard]] entity_type operator[](const size_type pos) const { - return begin()[pos]; - } - - /** - * @brief Checks if a group is properly initialized. - * @return True if the group is properly initialized, false otherwise. - */ - [[nodiscard]] explicit operator bool() const noexcept { - return length != nullptr; - } - - /** - * @brief Checks if a group contains an entity. - * @param entt A valid identifier. - * @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)); - } - - /** - * @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. - * - * @tparam Type Types of components to get. - * @param entt A valid identifier. - * @return The components assigned to the entity. - */ - template - [[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>(pools)->get(entt), ...); - } else { - return std::tuple_cat(std::get>(pools)->get_as_tuple(entt)...); - } - } - - /** - * @brief Iterates entities and components and applies the given function - * object to them. - * - * The function object is invoked for each entity. It is provided with the - * entity itself and a set of references to non-empty components. The - * _constness_ of the components is as requested.
- * The signature of the function must be equivalent to one of the following - * forms: - * - * @code{.cpp} - * void(const entity_type, Type &...); - * void(Type &...); - * @endcode - * - * @note - * Empty types aren't explicitly instantiated and therefore they are never - * returned during iterations. - * - * @tparam Func Type of the function object to invoke. - * @param func A valid function object. - */ - template - void each(Func func) const { - for(auto args: each()) { - if constexpr(is_applicable_v{}, std::declval().get({})))>) { - std::apply(func, args); - } else { - std::apply([&func](auto, auto &&...less) { func(std::forward(less)...); }, args); - } - } - } - - /** - * @brief Returns an iterable object to use to _visit_ a group. - * - * The iterable object returns tuples that contain the current entity and a - * set of references to its non-empty components. The _constness_ of the - * components is as requested. - * - * @note - * Empty types aren't explicitly instantiated and therefore they are never - * returned during iterations. - * - * @return An iterable object to use to _visit_ the group. - */ - [[nodiscard]] iterable each() const noexcept { - return {{begin(), pools}, {end(), pools}}; - } - - /** - * @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: - * - * @code{.cpp} - * bool(std::tuple, std::tuple); - * bool(const Type &, const Type &); - * bool(const Entity, const Entity); - * @endcode - * - * Where `Type` are either owned types or not but still such that they are - * iterated by the group.
- * Moreover, the comparison function object shall induce a - * _strict weak ordering_ on the values. - * - * The sort function object must offer a member function template - * `operator()` that accepts three arguments: - * - * * An iterator to the first element of the range to sort. - * * 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 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 - void sort(Compare compare, Sort algo = Sort{}, Args &&...args) const { - if constexpr(sizeof...(Type) == 0) { - static_assert(std::is_invocable_v, "Invalid comparison function"); - std::get<0>(pools)->sort_n(*length, std::move(compare), std::move(algo), std::forward(args)...); - } else { - auto comp = [this, &compare](const entity_type lhs, const entity_type rhs) { - if constexpr(sizeof...(Type) == 1) { - return compare((std::get>(pools)->get(lhs), ...), (std::get>(pools)->get(rhs), ...)); - } else { - return compare(std::forward_as_tuple(std::get>(pools)->get(lhs)...), std::forward_as_tuple(std::get>(pools)->get(rhs)...)); - } - }; - - std::get<0>(pools)->sort_n(*length, std::move(comp), std::move(algo), std::forward(args)...); - } - - std::apply([this](auto *head, auto *...other) { - for(auto next = *length; next; --next) { - const auto pos = next - 1; - [[maybe_unused]] const auto entt = head->data()[pos]; - (other->swap_elements(other->data()[pos], entt), ...); - } - }, - pools); - } - -private: - const std::tuple pools; - const size_type *const length; -}; - -} // namespace entt - -#endif - -// #include "view.hpp" -#ifndef ENTT_ENTITY_VIEW_HPP -#define ENTT_ENTITY_VIEW_HPP - -#include -#include -#include -#include -#include -// #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 { - -/** - * @cond TURN_OFF_DOXYGEN - * Internal details not to be documented. - */ - -namespace internal { - -template -class view_iterator final { - using iterator_type = typename Type::const_iterator; - - [[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); - } - -public: - using value_type = typename iterator_type::value_type; - using pointer = typename iterator_type::pointer; - using reference = typename iterator_type::reference; - using difference_type = typename iterator_type::difference_type; - using iterator_category = std::forward_iterator_tag; - - constexpr view_iterator() noexcept - : it{}, - last{}, - pools{}, - filter{} {} - - view_iterator(iterator_type curr, iterator_type to, std::array all_of, std::array none_of) noexcept - : it{curr}, - last{to}, - pools{all_of}, - filter{none_of} { - while(it != last && !valid()) { - ++it; - } - } - - view_iterator &operator++() noexcept { - while(++it != last && !valid()) {} - return *this; - } - - view_iterator operator++(int) noexcept { - view_iterator orig = *this; - return ++(*this), orig; - } - - [[nodiscard]] pointer operator->() const noexcept { - return &*it; - } - - [[nodiscard]] reference operator*() const noexcept { - return *operator->(); - } - - template - friend constexpr bool operator==(const view_iterator &, const view_iterator &) noexcept; - -private: - iterator_type it; - iterator_type last; - std::array pools; - std::array filter; -}; - -template -[[nodiscard]] constexpr bool operator==(const view_iterator &lhs, const view_iterator &rhs) noexcept { - return lhs.it == rhs.it; -} - -template -[[nodiscard]] constexpr bool operator!=(const view_iterator &lhs, const view_iterator &rhs) noexcept { - return !(lhs == rhs); -} - -template -struct extended_view_iterator final { - using difference_type = std::ptrdiff_t; - using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval()), std::declval().get_as_tuple({})...)); - using pointer = input_iterator_pointer; - using reference = value_type; - using iterator_category = std::input_iterator_tag; - - constexpr extended_view_iterator() - : it{}, - pools{} {} - - extended_view_iterator(It from, std::tuple storage) - : it{from}, - pools{storage} {} - - extended_view_iterator &operator++() noexcept { - return ++it, *this; - } - - extended_view_iterator operator++(int) noexcept { - extended_view_iterator orig = *this; - return ++(*this), orig; - } - - [[nodiscard]] reference operator*() const noexcept { - return std::apply([entt = *it](auto *...curr) { return std::tuple_cat(std::make_tuple(entt), curr->get_as_tuple(entt)...); }, pools); - } - - [[nodiscard]] pointer operator->() const noexcept { - return operator*(); - } - - template - friend bool constexpr operator==(const extended_view_iterator &, const extended_view_iterator &) noexcept; - -private: - It it; - std::tuple pools; -}; - -template -[[nodiscard]] constexpr bool operator==(const extended_view_iterator &lhs, const extended_view_iterator &rhs) noexcept { - return lhs.it == rhs.it; -} - -template -[[nodiscard]] constexpr bool operator!=(const extended_view_iterator &lhs, const extended_view_iterator &rhs) noexcept { - return !(lhs == rhs); -} - -} // namespace internal - -/** - * Internal details not to be documented. - * @endcond - */ - -/** - * @brief View implementation. - * - * Primary template isn't defined on purpose. All the specializations give a - * compile-time error, but for a few reasonable cases. - */ -template -class basic_view; - -/** - * @brief Multi component view. - * - * Multi component views iterate over those entities that are at least in the - * given storage. During initialization, a multi component 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 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 other cases, modifying the pools iterated by the view in any way - * invalidates all the iterators and using them results in undefined behavior. - * - * @tparam Get Types of storage iterated by the view. - * @tparam Exclude Types of storage used to filter the view. - */ -template -class basic_view, exclude_t> { - using underlying_type = std::common_type_t; - using basic_common_type = std::common_type_t; - - template - friend class basic_view; - - template - static constexpr std::size_t index_of = type_list_index_v, type_list>; - - [[nodiscard]] auto opaque_check_set() const noexcept { - std::array 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{curr...}; }, filter); - } - - template - [[nodiscard]] auto dispatch_get(const std::tuple &curr) const { - if constexpr(Curr == Other) { - return std::forward_as_tuple(std::get(curr)...); - } else { - return storage().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 - void each(Func &func, std::index_sequence) const { - for(const auto curr: storage().each()) { - if(const auto entt = std::get<0>(curr); ((sizeof...(Get) != 1u) || (entt != tombstone)) && ((Curr == Index || storage().contains(entt)) && ...) && !reject(entt)) { - if constexpr(is_applicable_v{}, std::declval().get({})))>) { - std::apply(func, std::tuple_cat(std::make_tuple(entt), dispatch_get(curr)...)); - } else { - std::apply(func, std::tuple_cat(dispatch_get(curr)...)); - } - } - } - } - - template - void pick_and_each(Func &func, std::index_sequence seq) const { - ((&storage() == view ? each(func, seq) : void()), ...); - } - -public: - /*! @brief Underlying entity identifier. */ - using entity_type = underlying_type; - /*! @brief Unsigned integer type. */ - using size_type = std::size_t; - /*! @brief Common type among all storage types. */ - using base_type = basic_common_type; - /*! @brief Bidirectional iterator type. */ - using iterator = internal::view_iterator; - /*! @brief Iterable view type. */ - using iterable = iterable_adaptor>; - - /*! @brief Default constructor to use to create empty, invalid views. */ - basic_view() noexcept - : pools{}, - filter{}, - view{} {} - - /** - * @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. - */ - basic_view(Get &...value, Exclude &...exclude) noexcept - : pools{&value...}, - filter{&exclude...}, - view{[](const base_type *first, const auto *...other) { ((first = other->size() < first->size() ? other : first), ...); return first; }(&value...)} {} - - /** - * @brief Constructs a multi-type view from a set of storage classes. - * @param value The storage for the types to iterate. - * @param excl The storage for the types used to filter the view. - */ - basic_view(std::tuple value, std::tuple 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)} {} - - /** - * @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. - */ - template - [[nodiscard]] basic_view use() const noexcept { - return use>(); - } - - /** - * @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. - */ - template - [[nodiscard]] basic_view use() const noexcept { - basic_view other{*this}; - other.view = &storage(); - return other; - } - - /** - * @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. - * @return The leading storage of the view. - */ - [[nodiscard]] const base_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. - * @return The storage for the given component type. - */ - template - [[nodiscard]] decltype(auto) storage() const noexcept { - return storage>(); - } - - /** - * @brief Returns the storage for a given index. - * @tparam Index Index of the storage to return. - * @return The storage for the given index. - */ - template - [[nodiscard]] decltype(auto) storage() const noexcept { - return *std::get(pools); - } - - /** - * @brief Estimates the number of entities iterated by the view. - * @return Estimated number of entities iterated by the view. - */ - [[nodiscard]] size_type size_hint() const noexcept { - return view->size(); - } - - /** - * @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()`. - * - * @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()}; - } - - /** - * @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()}; - } - - /** - * @brief Returns the first entity of the view, if any. - * @return The first entity of the view if one exists, the null entity - * otherwise. - */ - [[nodiscard]] entity_type front() const noexcept { - const auto it = begin(); - return it != end() ? *it : null; - } - - /** - * @brief Returns the last entity of the view, if any. - * @return The last entity of the view if one exists, the null entity - * 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; - } - - /** - * @brief Finds an entity. - * @param entt A valid identifier. - * @return An iterator to the given entity if it's found, past the end - * 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(); - } - - /** - * @brief Returns the components assigned to the given entity. - * @param entt A valid identifier. - * @return The components assigned to the given entity. - */ - [[nodiscard]] decltype(auto) operator[](const entity_type entt) const { - return get(entt); - } - - /** - * @brief Checks if a view is properly initialized. - * @return True if the view is properly initialized, false otherwise. - */ - [[nodiscard]] explicit operator bool() const noexcept { - return view != nullptr; - } - - /** - * @brief Checks if a view contains an entity. - * @param entt A valid identifier. - * @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); - } - - /** - * @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 Type Types of components to get. - * @param entt A valid identifier. - * @return The components assigned to the entity. - */ - template - [[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 (storage>().get(entt), ...); - } else { - return std::tuple_cat(storage>().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 - [[nodiscard]] decltype(auto) get(const entity_type entt) const { - if constexpr(sizeof...(Other) == 0) { - return storage().get(entt); - } else { - return std::tuple_cat(storage().get_as_tuple(entt), storage().get_as_tuple(entt)...); - } - } - - /** - * @brief Iterates entities and components and applies the given function - * object to them. - * - * The function object is invoked for each entity. It is provided with the - * entity itself and a set of references to non-empty components. The - * _constness_ of the components is as requested.
- * The signature of the function must be equivalent to one of the following - * forms: - * - * @code{.cpp} - * void(const entity_type, Type &...); - * void(Type &...); - * @endcode - * - * @tparam Func Type of the function object to invoke. - * @param func A valid function object. - */ - template - void each(Func func) const { - pick_and_each(func, std::index_sequence_for{}); - } - - /** - * @brief Returns an iterable object to use to _visit_ a view. - * - * The iterable object returns a tuple that contains the current entity and - * a set of references to its non-empty components. The _constness_ of the - * components is as requested. - * - * @return An iterable object to use to _visit_ the view. - */ - [[nodiscard]] iterable each() const noexcept { - return {internal::extended_view_iterator{begin(), pools}, internal::extended_view_iterator{end(), pools}}; - } - - /** - * @brief Combines two views in a _more specific_ one (friend function). - * @tparam OGet Component list of the view to combine with. - * @tparam OExclude Filter list of the view to combine with. - * @param other The view to combine with. - * @return A more specific view. - */ - template - [[nodiscard]] auto operator|(const basic_view, exclude_t> &other) const noexcept { - return std::make_from_tuple, exclude_t>>( - std::apply([](auto *...curr) { return std::forward_as_tuple(*curr...); }, std::tuple_cat(pools, other.pools, filter, other.filter))); - } - -private: - std::tuple pools; - std::tuple filter; - const base_type *view; -}; - -/** - * @brief Single component view specialization. - * - * Single component views are specialized in order to get a boost in terms of - * performance. This kind of views can access the underlying data structure - * directly and avoid superfluous checks. - * - * @b Important - * - * Iterators aren't invalidated if: - * - * * 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 other cases, modifying the pool iterated by the view in any way - * invalidates all the iterators and using them results in undefined behavior. - * - * @tparam Get Type of storage iterated by the view. - */ -template -class basic_view, exclude_t<>, std::void_t::in_place_delete>>> { - template - friend class basic_view; - -public: - /*! @brief Underlying entity identifier. */ - using entity_type = typename Get::entity_type; - /*! @brief Unsigned integer type. */ - using size_type = std::size_t; - /*! @brief Common type among all storage types. */ - using base_type = typename Get::base_type; - /*! @brief Random access iterator type. */ - using iterator = typename base_type::iterator; - /*! @brief Reversed iterator type. */ - using reverse_iterator = typename base_type::reverse_iterator; - /*! @brief Iterable view type. */ - using iterable = decltype(std::declval().each()); - - /*! @brief Default constructor to use to create empty, invalid views. */ - basic_view() noexcept - : pools{}, - filter{} {} - - /** - * @brief Constructs a single-type view from a storage class. - * @param ref The storage for the type to iterate. - */ - basic_view(Get &ref) noexcept - : pools{&ref}, - filter{} {} - - /** - * @brief Constructs a single-type view from a storage class. - * @param ref The storage for the type to iterate. - */ - basic_view(std::tuple ref, std::tuple<> = {}) noexcept - : pools{&std::get<0>(ref)}, - filter{} {} - - /** - * @brief Returns the leading storage of a view. - * @return The leading storage of the view. - */ - [[nodiscard]] const base_type &handle() const noexcept { - return storage(); - } - - /** - * @brief Returns the storage for a given component type. - * @tparam Type Type of component of which to return the storage. - * @return The storage for the given component type. - */ - template - [[nodiscard]] decltype(auto) storage() const noexcept { - static_assert(std::is_same_v, typename Get::value_type>, "Invalid component type"); - return storage<0>(); - } - - /** - * @brief Returns the storage for a given index. - * @tparam Index Index of the storage to return. - * @return The storage for the given index. - */ - template - [[nodiscard]] decltype(auto) storage() const noexcept { - return *std::get(pools); - } - - /** - * @brief Returns the number of entities that have the given component. - * @return Number of entities that have the given component. - */ - [[nodiscard]] size_type size() const noexcept { - return handle().size(); - } - - /** - * @brief Checks whether a view is empty. - * @return True if the view is empty, false otherwise. - */ - [[nodiscard]] bool empty() const noexcept { - return handle().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()`. - * - * @return An iterator to the first entity of the view. - */ - [[nodiscard]] iterator begin() const noexcept { - return handle().begin(); - } - - /** - * @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(); - } - - /** - * @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()`. - * - * @return An iterator to the first entity of the reversed view. - */ - [[nodiscard]] reverse_iterator rbegin() const noexcept { - return handle().rbegin(); - } - - /** - * @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(); - } - - /** - * @brief Returns the first entity of the view, if any. - * @return The first entity of the view if one exists, the null entity - * otherwise. - */ - [[nodiscard]] entity_type front() const noexcept { - return empty() ? null : *begin(); - } - - /** - * @brief Returns the last entity of the view, if any. - * @return The last entity of the view if one exists, the null entity - * otherwise. - */ - [[nodiscard]] entity_type back() const noexcept { - return empty() ? null : *rbegin(); - } - - /** - * @brief Finds an entity. - * @param entt A valid identifier. - * @return An iterator to the given entity if it's found, past the end - * iterator otherwise. - */ - [[nodiscard]] iterator find(const entity_type entt) const noexcept { - return contains(entt) ? handle().find(entt) : end(); - } - - /** - * @brief Returns the identifier that occupies the given position. - * @param pos Position of the element to return. - * @return The identifier that occupies the given position. - */ - [[nodiscard]] entity_type operator[](const size_type pos) const { - return begin()[pos]; - } - - /** - * @brief Returns the component assigned to the given entity. - * @param entt A valid identifier. - * @return The component assigned to the given entity. - */ - [[nodiscard]] decltype(auto) operator[](const entity_type entt) const { - return storage().get(entt); - } - - /** - * @brief Checks if a view is properly initialized. - * @return True if the view is properly initialized, false otherwise. - */ - [[nodiscard]] explicit operator bool() const noexcept { - return std::get<0>(pools) != nullptr; - } - - /** - * @brief Checks if a view contains an entity. - * @param entt A valid identifier. - * @return True if the view contains the given entity, false otherwise. - */ - [[nodiscard]] bool contains(const entity_type entt) const noexcept { - return handle().contains(entt); - } - - /** - * @brief Returns the component assigned to the given entity. - * - * @warning - * 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. - * @param entt A valid identifier. - * @return The component assigned to the entity. - */ - template - [[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, typename Get::value_type> && ...), "Invalid component type"); - return storage().get(entt); - } - } - - /*! @copydoc get */ - template - [[nodiscard]] decltype(auto) get(const entity_type entt) const { - return storage().get(entt); - } - - /** - * @brief Iterates entities and components and applies the given function - * object to them. - * - * The function object is invoked for each entity. It is provided with the - * entity itself and a reference to the component if it's a non-empty one. - * The _constness_ of the component is as requested.
- * The signature of the function must be equivalent to one of the following - * forms: - * - * @code{.cpp} - * void(const entity_type, Type &); - * void(typename Type &); - * @endcode - * - * @note - * Empty types aren't explicitly instantiated and therefore they are never - * returned during iterations. - * - * @tparam Func Type of the function object to invoke. - * @param func A valid function object. - */ - template - void each(Func func) const { - if constexpr(is_applicable_v) { - for(const auto pack: each()) { - std::apply(func, pack); - } - } else if constexpr(ignore_as_empty_v) { - for(size_type pos{}, last = size(); pos < last; ++pos) { - func(); - } - } else { - for(auto &&component: storage()) { - func(component); - } - } - } - - /** - * @brief Returns an iterable object to use to _visit_ a view. - * - * The iterable object returns a tuple that contains the current entity and - * a reference to its component if it's a non-empty one. The _constness_ of - * the component is as requested. - * - * @return An iterable object to use to _visit_ the view. - */ - [[nodiscard]] iterable each() const noexcept { - return storage().each(); - } - - /** - * @brief Combines two views in a _more specific_ one (friend function). - * @tparam OGet Component list of the view to combine with. - * @tparam OExclude Filter list of the view to combine with. - * @param other The view to combine with. - * @return A more specific view. - */ - template - [[nodiscard]] auto operator|(const basic_view, exclude_t> &other) const noexcept { - return std::make_from_tuple, exclude_t>>( - std::apply([](auto *...curr) { return std::forward_as_tuple(*curr...); }, std::tuple_cat(pools, other.pools, other.filter))); - } - -private: - std::tuple pools; - std::tuple<> filter; -}; - -/** - * @brief Deduction guide. - * @tparam Type Type of storage classes used to create the view. - * @param storage The storage for the types to iterate. - */ -template -basic_view(Type &...storage) -> basic_view, exclude_t<>>; - -/** - * @brief Deduction guide. - * @tparam Get Types of components iterated by the view. - * @tparam Exclude Types of components used to filter the view. - */ -template -basic_view(std::tuple, std::tuple = {}) -> basic_view, exclude_t>; - -} // namespace entt - -#endif - - -namespace entt { - -/** - * @brief Converts a registry to a view. - * @tparam Registry Basic registry type. - */ -template -class as_view { - template - auto dispatch(get_t, exclude_t) const { - return reg.template view...>(exclude_t...>{}); - } - -public: - /*! @brief Type of registry to convert. */ - using registry_type = Registry; - /*! @brief Underlying entity identifier. */ - using entity_type = std::remove_const_t; - - /** - * @brief Constructs a converter for a given registry. - * @param source A valid reference to a registry. - */ - as_view(registry_type &source) noexcept - : reg{source} {} - - /** - * @brief Conversion function from a registry to a view. - * @tparam Get Type of storage used to construct the view. - * @tparam Exclude Types of storage used to filter the view. - * @return A newly created view. - */ - template - operator basic_view() const { - return dispatch(Get{}, Exclude{}); - } - -private: - registry_type ® -}; - -/** - * @brief Converts a registry to a group. - * @tparam Registry Basic registry type. - */ -template -class as_group { - template - auto dispatch(owned_t, get_t, exclude_t) const { - if constexpr(std::is_const_v) { - return reg.template group_if_exists(get_t{}, exclude_t{}); - } else { - return reg.template group...>(get_t...>{}, exclude_t...>{}); - } - } - -public: - /*! @brief Type of registry to convert. */ - using registry_type = Registry; - /*! @brief Underlying entity identifier. */ - using entity_type = std::remove_const_t; - - /** - * @brief Constructs a converter for a given registry. - * @param source A valid reference to a registry. - */ - as_group(registry_type &source) noexcept - : reg{source} {} - - /** - * @brief Conversion function from a registry to a group. - * @tparam Owned Types of _owned_ by the group. - * @tparam Get Types of storage _observed_ by the group. - * @tparam Exclude Types of storage used to filter the group. - * @return A newly created group. - */ - template - operator basic_group() const { - return dispatch(Owned{}, Get{}, Exclude{}); - } - -private: - registry_type ® -}; - -/** - * @brief Helper to create a listener that directly invokes a member function. - * @tparam Member Member function to invoke on a component of the given type. - * @tparam Registry Basic registry type. - * @param reg A registry that contains the given entity and its components. - * @param entt Entity from which to get the component. - */ -template>> -void invoke(Registry ®, const typename Registry::entity_type entt) { - static_assert(std::is_member_function_pointer_v, "Invalid pointer to non-static member function"); - delegate func; - func.template connect(reg.template get>(entt)); - func(reg, entt); -} - -/** - * @brief Returns the entity associated with a given component. - * - * @warning - * Currently, this function only works correctly with the default pool as it - * makes assumptions about how the components are laid out. - * - * @tparam Registry Basic registry type. - * @tparam Component Type of component. - * @param reg A registry that contains the given entity and its components. - * @param instance A valid component instance. - * @return The entity associated with the given component. - */ -template -typename Registry::entity_type to_entity(const Registry ®, const Component &instance) { - const auto &storage = reg.template storage(); - const typename Registry::base_type &base = storage; - const auto *addr = std::addressof(instance); - - for(auto it = base.rbegin(), last = base.rend(); it < last; it += component_traits::page_size) { - if(const auto dist = (addr - std::addressof(storage.get(*it))); dist >= 0 && dist < static_cast(component_traits::page_size)) { - return *(it + dist); - } - } - - return null; -} - -} // namespace entt - -#endif - // #include "entity/observer.hpp" #ifndef ENTT_ENTITY_OBSERVER_HPP #define ENTT_ENTITY_OBSERVER_HPP @@ -22562,7 +24573,7 @@ struct basic_collector<> { * @return The updated collector. */ template - static constexpr auto group(exclude_t = {}) noexcept { + static constexpr auto group(exclude_t = exclude_t{}) noexcept { return basic_collector, type_list<>, type_list, AllOf...>>{}; } @@ -22597,7 +24608,7 @@ struct basic_collector, type_list, Rule * @return The updated collector. */ template - static constexpr auto group(exclude_t = {}) noexcept { + static constexpr auto group(exclude_t = exclude_t{}) noexcept { return basic_collector, type_list<>, type_list, AllOf...>, current_type, Other...>{}; } @@ -22618,7 +24629,7 @@ struct basic_collector, type_list, Rule * @return The updated collector. */ template - static constexpr auto where(exclude_t = {}) noexcept { + static constexpr auto where(exclude_t = exclude_t{}) noexcept { using extended_type = matcher, type_list, Rule...>; return basic_collector{}; } @@ -22665,8 +24676,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 @@ -22675,10 +24685,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 -class basic_observer: private basic_storage { - using base_type = basic_storage; +template +class basic_observer: private basic_storage { + using base_type = basic_storage; template struct matcher_handler; @@ -22712,10 +24724,10 @@ class basic_observer: private basic_storage().disconnect(obs), ...); - (reg.template on_construct().disconnect(obs), ...); - reg.template on_update().disconnect(obs); - reg.template on_destroy().disconnect(obs); + (reg.template on_destroy().disconnect(&obs), ...); + (reg.template on_construct().disconnect(&obs), ...); + reg.template on_update().disconnect(&obs); + reg.template on_destroy().disconnect(&obs); } }; @@ -22758,12 +24770,12 @@ class basic_observer: private basic_storage().disconnect(obs), ...); - (reg.template on_construct().disconnect(obs), ...); - (reg.template on_construct().disconnect(obs), ...); - (reg.template on_destroy().disconnect(obs), ...); - (reg.template on_destroy().disconnect(obs), ...); - (reg.template on_construct().disconnect(obs), ...); + (reg.template on_destroy().disconnect(&obs), ...); + (reg.template on_construct().disconnect(&obs), ...); + (reg.template on_construct().disconnect(&obs), ...); + (reg.template on_destroy().disconnect(&obs), ...); + (reg.template on_destroy().disconnect(&obs), ...); + (reg.template on_construct().disconnect(&obs), ...); } }; @@ -22786,15 +24798,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; @@ -22802,16 +24825,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 - basic_observer(registry_type ®, basic_collector) - : basic_observer{} { + basic_observer(registry_type ®, basic_collector, const allocator_type &allocator = allocator_type{}) + : basic_observer{allocator} { connect(reg, std::index_sequence_for{}); } - /*! @brief Default destructor. */ - ~basic_observer() = default; - /** * @brief Default copy assignment operator, deleted on purpose. * @return This observer. @@ -22879,8 +24900,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. */ @@ -22890,11 +24910,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. */ @@ -22982,7 +24997,7 @@ struct identity { * @param value The actual argument. * @return The submitted value as-is. */ - template + template [[nodiscard]] constexpr Type &&operator()(Type &&value) const noexcept { return std::forward(value); } @@ -23015,7 +25030,7 @@ template * @brief Helper type for visitors. * @tparam Func Types of function objects. */ -template +template struct overloaded: Func... { using Func::operator()...; }; @@ -23024,14 +25039,14 @@ struct overloaded: Func... { * @brief Deduction guide. * @tparam Func Types of function objects. */ -template +template overloaded(Func...) -> overloaded; /** * @brief Basic implementation of a y-combinator. * @tparam Func Type of a potentially recursive function. */ -template +template struct y_combinator { /** * @brief Constructs a y-combinator from a given function. @@ -23046,13 +25061,13 @@ struct y_combinator { * @param args Parameters to use to invoke the underlying function. * @return Return value of the underlying function, if any. */ - template + template constexpr decltype(auto) operator()(Args &&...args) const noexcept(std::is_nothrow_invocable_v) { return func(*this, std::forward(args)...); } /*! @copydoc operator()() */ - template + template constexpr decltype(auto) operator()(Args &&...args) noexcept(std::is_nothrow_invocable_v) { return func(*this, std::forward(args)...); } @@ -23094,8 +25109,8 @@ private: #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) \ @@ -23151,6 +25166,8 @@ private: # 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 @@ -23410,8 +25427,8 @@ private: #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) \ @@ -23467,6 +25484,8 @@ private: # 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 @@ -23783,7 +25802,7 @@ public: [[nodiscard]] iterable_adaptor 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}}; } @@ -23795,7 +25814,7 @@ public: [[nodiscard]] iterable_adaptor 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}}; } @@ -23919,8 +25938,8 @@ private: #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) \ @@ -23976,6 +25995,8 @@ private: # 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 @@ -24020,6 +26041,7 @@ private: #include #include +#include #include #include // #include "../config/config.h" @@ -24041,8 +26063,8 @@ private: #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) \ @@ -24098,6 +26120,8 @@ private: # 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 @@ -24199,7 +26223,6 @@ using type_identity_t = typename type_identity::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 struct size_of: std::integral_constant {}; @@ -24441,7 +26464,8 @@ struct type_list_contains; * @tparam Other Type to look for. */ template -struct type_list_contains, Other>: std::disjunction...> {}; +struct type_list_contains, Other> + : std::bool_constant<(std::is_same_v || ...)> {}; /** * @brief Helper variable template. @@ -24529,10 +26553,20 @@ struct value_list_element> */ template struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = typename value_list_element::type; + /** * @brief Helper type. * @tparam Index Index of the value to return. @@ -24541,6 +26575,58 @@ struct value_list_element<0u, value_list> { template inline constexpr auto value_list_element_v = value_list_element::value; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +struct value_list_index> { + static_assert(value_list_index>::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 +struct value_list_index> { + /*! @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 +inline constexpr std::size_t value_list_index_v = value_list_index::value; + /** * @brief Concatenates multiple value lists. * @tparam Value Values provided by the first value list. @@ -24592,6 +26678,89 @@ struct value_list_cat> { template using value_list_cat_t = typename value_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = std::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = typename value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : std::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +class value_list_diff, value_list> { + using v141_toolset_workaround = value_list; + +public: + /*! @brief A value list that is the difference between the two value lists. */ + using type = value_list_cat_t, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = typename value_list_diff::type; + /*! @brief Same as std::is_invocable, but with tuples. */ template struct is_applicable: std::false_type {}; @@ -24712,7 +26881,7 @@ inline constexpr bool is_iterator_v = is_iterator::value; */ template struct is_ebco_eligible - : std::conjunction, std::negation>> {}; + : std::bool_constant && !std::is_final_v> {}; /** * @brief Helper variable template. @@ -24803,6 +26972,10 @@ template struct is_equality_comparable() == std::declval())>> : std::bool_constant(choice<2>)> {}; +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: std::false_type {}; + /** * @brief Helper variable template. * @tparam Type The type to test. @@ -24899,6 +27072,18 @@ using nth_argument_t = typename nth_argument::type; } // namespace entt +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::type_list_element> {}; + +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::value_list_element> {}; + #endif @@ -25482,7 +27667,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 struct allocation_deleter: private Allocator { @@ -25503,7 +27688,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) { - using alloc_traits = typename std::allocator_traits; + using alloc_traits = std::allocator_traits; alloc_traits::destroy(*this, to_address(ptr)); alloc_traits::deallocate(*this, ptr, 1u); } @@ -25670,6 +27855,7 @@ constexpr Type *uninitialized_construct_using_allocator(Type *value, const Alloc #include #include +#include #include #include // #include "../config/config.h" @@ -25724,7 +27910,6 @@ using type_identity_t = typename type_identity::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 struct size_of: std::integral_constant {}; @@ -25966,7 +28151,8 @@ struct type_list_contains; * @tparam Other Type to look for. */ template -struct type_list_contains, Other>: std::disjunction...> {}; +struct type_list_contains, Other> + : std::bool_constant<(std::is_same_v || ...)> {}; /** * @brief Helper variable template. @@ -26054,10 +28240,20 @@ struct value_list_element> */ template struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = typename value_list_element::type; + /** * @brief Helper type. * @tparam Index Index of the value to return. @@ -26066,6 +28262,58 @@ struct value_list_element<0u, value_list> { template inline constexpr auto value_list_element_v = value_list_element::value; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +struct value_list_index> { + static_assert(value_list_index>::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 +struct value_list_index> { + /*! @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 +inline constexpr std::size_t value_list_index_v = value_list_index::value; + /** * @brief Concatenates multiple value lists. * @tparam Value Values provided by the first value list. @@ -26117,6 +28365,89 @@ struct value_list_cat> { template using value_list_cat_t = typename value_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = std::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = typename value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : std::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +class value_list_diff, value_list> { + using v141_toolset_workaround = value_list; + +public: + /*! @brief A value list that is the difference between the two value lists. */ + using type = value_list_cat_t, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = typename value_list_diff::type; + /*! @brief Same as std::is_invocable, but with tuples. */ template struct is_applicable: std::false_type {}; @@ -26237,7 +28568,7 @@ inline constexpr bool is_iterator_v = is_iterator::value; */ template struct is_ebco_eligible - : std::conjunction, std::negation>> {}; + : std::bool_constant && !std::is_final_v> {}; /** * @brief Helper variable template. @@ -26328,6 +28659,10 @@ template struct is_equality_comparable() == std::declval())>> : std::bool_constant(choice<2>)> {}; +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: std::false_type {}; + /** * @brief Helper variable template. * @tparam Type The type to test. @@ -26424,6 +28759,18 @@ using nth_argument_t = typename nth_argument::type; } // namespace entt +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::type_list_element> {}; + +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::value_list_element> {}; + #endif // #include "fwd.hpp" @@ -26565,51 +28912,51 @@ public: return {it->element.first, it->element.second}; } - template - friend constexpr std::ptrdiff_t operator-(const dense_map_iterator &, const dense_map_iterator &) noexcept; + template + friend constexpr std::ptrdiff_t operator-(const dense_map_iterator &, const dense_map_iterator &) noexcept; - template - friend constexpr bool operator==(const dense_map_iterator &, const dense_map_iterator &) noexcept; + template + friend constexpr bool operator==(const dense_map_iterator &, const dense_map_iterator &) noexcept; - template - friend constexpr bool operator<(const dense_map_iterator &, const dense_map_iterator &) noexcept; + template + friend constexpr bool operator<(const dense_map_iterator &, const dense_map_iterator &) noexcept; private: It it; }; -template -[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return lhs.it - rhs.it; } -template -[[nodiscard]] constexpr bool operator==(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return lhs.it == rhs.it; } -template -[[nodiscard]] constexpr bool operator!=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return !(lhs == rhs); } -template -[[nodiscard]] constexpr bool operator<(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return lhs.it < rhs.it; } -template -[[nodiscard]] constexpr bool operator>(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return rhs < lhs; } -template -[[nodiscard]] constexpr bool operator<=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return !(lhs > rhs); } -template -[[nodiscard]] constexpr bool operator>=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return !(lhs < rhs); } @@ -26667,13 +29014,13 @@ private: std::size_t offset; }; -template -[[nodiscard]] constexpr bool operator==(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { return lhs.index() == rhs.index(); } -template -[[nodiscard]] constexpr bool operator!=(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { return !(lhs == rhs); } @@ -26703,7 +29050,7 @@ class dense_map { static constexpr std::size_t minimum_capacity = 8u; using node_type = internal::dense_map_node; - using alloc_traits = typename std::allocator_traits; + using alloc_traits = std::allocator_traits; static_assert(std::is_same_v>, "Invalid value type"); using sparse_container_type = std::vector>; using packed_container_type = std::vector>; @@ -26901,7 +29248,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. @@ -26922,11 +29268,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. */ @@ -27275,7 +29616,7 @@ public: } /*! @copydoc equal_range */ - template + template [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t>> equal_range(const Other &key) const { const auto it = find(key); @@ -27600,51 +29941,51 @@ public: return *operator->(); } - template - friend constexpr std::ptrdiff_t operator-(const dense_set_iterator &, const dense_set_iterator &) noexcept; + template + friend constexpr std::ptrdiff_t operator-(const dense_set_iterator &, const dense_set_iterator &) noexcept; - template - friend constexpr bool operator==(const dense_set_iterator &, const dense_set_iterator &) noexcept; + template + friend constexpr bool operator==(const dense_set_iterator &, const dense_set_iterator &) noexcept; - template - friend constexpr bool operator<(const dense_set_iterator &, const dense_set_iterator &) noexcept; + template + friend constexpr bool operator<(const dense_set_iterator &, const dense_set_iterator &) noexcept; private: It it; }; -template -[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return lhs.it - rhs.it; } -template -[[nodiscard]] constexpr bool operator==(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return lhs.it == rhs.it; } -template -[[nodiscard]] constexpr bool operator!=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return !(lhs == rhs); } -template -[[nodiscard]] constexpr bool operator<(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return lhs.it < rhs.it; } -template -[[nodiscard]] constexpr bool operator>(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return rhs < lhs; } -template -[[nodiscard]] constexpr bool operator<=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return !(lhs > rhs); } -template -[[nodiscard]] constexpr bool operator>=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return !(lhs < rhs); } @@ -27699,13 +30040,13 @@ private: std::size_t offset; }; -template -[[nodiscard]] constexpr bool operator==(const dense_set_local_iterator &lhs, const dense_set_local_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const dense_set_local_iterator &lhs, const dense_set_local_iterator &rhs) noexcept { return lhs.index() == rhs.index(); } -template -[[nodiscard]] constexpr bool operator!=(const dense_set_local_iterator &lhs, const dense_set_local_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const dense_set_local_iterator &lhs, const dense_set_local_iterator &rhs) noexcept { return !(lhs == rhs); } @@ -27914,7 +30255,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. @@ -27935,11 +30275,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. */ @@ -28195,7 +30530,7 @@ public: } /*! @copydoc equal_range */ - template + template [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t>> equal_range(const Other &value) const { const auto it = find(value); @@ -28412,6 +30747,7 @@ private: #include #include +#include #include #include // #include "../config/config.h" @@ -28487,7 +30823,6 @@ using type_identity_t = typename type_identity::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 struct size_of: std::integral_constant {}; @@ -28729,7 +31064,8 @@ struct type_list_contains; * @tparam Other Type to look for. */ template -struct type_list_contains, Other>: std::disjunction...> {}; +struct type_list_contains, Other> + : std::bool_constant<(std::is_same_v || ...)> {}; /** * @brief Helper variable template. @@ -28817,10 +31153,20 @@ struct value_list_element> */ template struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = typename value_list_element::type; + /** * @brief Helper type. * @tparam Index Index of the value to return. @@ -28829,6 +31175,58 @@ struct value_list_element<0u, value_list> { template inline constexpr auto value_list_element_v = value_list_element::value; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +struct value_list_index> { + static_assert(value_list_index>::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 +struct value_list_index> { + /*! @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 +inline constexpr std::size_t value_list_index_v = value_list_index::value; + /** * @brief Concatenates multiple value lists. * @tparam Value Values provided by the first value list. @@ -28880,6 +31278,89 @@ struct value_list_cat> { template using value_list_cat_t = typename value_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = std::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = typename value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : std::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +class value_list_diff, value_list> { + using v141_toolset_workaround = value_list; + +public: + /*! @brief A value list that is the difference between the two value lists. */ + using type = value_list_cat_t, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = typename value_list_diff::type; + /*! @brief Same as std::is_invocable, but with tuples. */ template struct is_applicable: std::false_type {}; @@ -29000,7 +31481,7 @@ inline constexpr bool is_iterator_v = is_iterator::value; */ template struct is_ebco_eligible - : std::conjunction, std::negation>> {}; + : std::bool_constant && !std::is_final_v> {}; /** * @brief Helper variable template. @@ -29091,6 +31572,10 @@ template struct is_equality_comparable() == std::declval())>> : std::bool_constant(choice<2>)> {}; +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: std::false_type {}; + /** * @brief Helper variable template. * @tparam Type The type to test. @@ -29187,6 +31672,18 @@ using nth_argument_t = typename nth_argument::type; } // namespace entt +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::type_list_element> {}; + +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::value_list_element> {}; + #endif @@ -29485,7 +31982,7 @@ struct identity { * @param value The actual argument. * @return The submitted value as-is. */ - template + template [[nodiscard]] constexpr Type &&operator()(Type &&value) const noexcept { return std::forward(value); } @@ -29518,7 +32015,7 @@ template * @brief Helper type for visitors. * @tparam Func Types of function objects. */ -template +template struct overloaded: Func... { using Func::operator()...; }; @@ -29527,14 +32024,14 @@ struct overloaded: Func... { * @brief Deduction guide. * @tparam Func Types of function objects. */ -template +template overloaded(Func...) -> overloaded; /** * @brief Basic implementation of a y-combinator. * @tparam Func Type of a potentially recursive function. */ -template +template struct y_combinator { /** * @brief Constructs a y-combinator from a given function. @@ -29549,13 +32046,13 @@ struct y_combinator { * @param args Parameters to use to invoke the underlying function. * @return Return value of the underlying function, if any. */ - template + template constexpr decltype(auto) operator()(Args &&...args) const noexcept(std::is_nothrow_invocable_v) { return func(*this, std::forward(args)...); } /*! @copydoc operator()() */ - template + template constexpr decltype(auto) operator()(Args &&...args) noexcept(std::is_nothrow_invocable_v) { return func(*this, std::forward(args)...); } @@ -29832,7 +32329,7 @@ public: [[nodiscard]] iterable_adaptor 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}}; } @@ -29844,7 +32341,7 @@ public: [[nodiscard]] iterable_adaptor 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}}; } @@ -29937,6 +32434,7 @@ class basic_flow { using task_container_type = dense_set, typename alloc_traits::template rebind_alloc>; using ro_rw_container_type = std::vector, typename alloc_traits::template rebind_alloc>>; using deps_container_type = dense_map, typename alloc_traits::template rebind_alloc>>; + using adjacency_matrix_type = adjacency_matrix>; void emplace(const id_type res, const bool is_rw) { ENTT_ASSERT(index.first() < vertices.size(), "Invalid node"); @@ -29948,6 +32446,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; @@ -29955,6 +32523,8 @@ public: using size_type = std::size_t; /*! @brief Iterable task list. */ using iterable = iterable_adaptor; + /*! @brief Adjacency matrix type. */ + using graph_type = adjacency_matrix_type; /*! @brief Default constructor. */ basic_flow() @@ -30029,9 +32599,10 @@ public: /*! @brief Clears the flow builder. */ void clear() noexcept { - index.first() = sync_on = {}; + index.first() = {}; vertices.clear(); deps.clear(); + sync_on = {}; } /** @@ -30150,72 +32721,12 @@ public: * @brief Generates a task graph for the current content. * @return The adjacency matrix of the task graph. */ - [[nodiscard]] adjacency_matrix graph() const { - const auto length = vertices.size(); - adjacency_matrix 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; } @@ -30239,14 +32750,13 @@ private: #include #include +#include // #include "../core/fwd.hpp" // #include "../core/type_traits.hpp" // #include "../signal/delegate.hpp" -// #include "component.hpp" - // #include "fwd.hpp" // #include "group.hpp" @@ -30271,7 +32781,7 @@ public: /*! @brief Type of registry to convert. */ using registry_type = Registry; /*! @brief Underlying entity identifier. */ - using entity_type = std::remove_const_t; + using entity_type = typename registry_type::entity_type; /** * @brief Constructs a converter for a given registry. @@ -30314,7 +32824,7 @@ public: /*! @brief Type of registry to convert. */ using registry_type = Registry; /*! @brief Underlying entity identifier. */ - using entity_type = std::remove_const_t; + using entity_type = typename registry_type::entity_type; /** * @brief Constructs a converter for a given registry. @@ -30369,19 +32879,133 @@ void invoke(Registry ®, const typename Registry::entity_type entt) { */ template typename Registry::entity_type to_entity(const Registry ®, const Component &instance) { - const auto &storage = reg.template storage(); - const typename Registry::base_type &base = storage; - const auto *addr = std::addressof(instance); + if(const auto *storage = reg.template storage(); storage) { + constexpr auto page_size = std::remove_const_t>::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::page_size) { - if(const auto dist = (addr - std::addressof(storage.get(*it))); dist >= 0 && dist < static_cast(component_traits::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(page_size)) { + return *(it + dist); + } } } return null; } +/*! @brief Primary template isn't defined on purpose. */ +template +struct sigh_helper; + +/** + * @brief Signal connection helper for registries. + * @tparam Registry Basic registry type. + */ +template +struct sigh_helper { + /*! @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 + auto with(const id_type id = type_hash::value()) noexcept { + return sigh_helper{*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 +struct sigh_helper final: sigh_helper { + /*! @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::value()) + : sigh_helper{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 on_construct(Args &&...args) { + this->registry().template on_construct(name).template connect(std::forward(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 on_update(Args &&...args) { + this->registry().template on_update(name).template connect(std::forward(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 on_destroy(Args &&...args) { + this->registry().template on_destroy(name).template connect(std::forward(args)...); + return *this; + } + +private: + id_type name; +}; + +/** + * @brief Deduction guide. + * @tparam Registry Basic registry type. + */ +template +sigh_helper(Registry &) -> sigh_helper; + } // namespace entt #endif @@ -30500,7 +33124,7 @@ class basic_organizer final { if constexpr(std::is_same_v) { return reg; } else if constexpr(internal::is_view_v) { - return as_view{reg}; + return static_cast(as_view{reg}); } else { return reg.ctx().template emplace>(); } @@ -30833,8 +33457,8 @@ private: #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) \ @@ -30890,6 +33514,8 @@ private: # 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 @@ -30934,6 +33560,7 @@ private: #include #include +#include #include #include // #include "../config/config.h" @@ -30955,8 +33582,8 @@ private: #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) \ @@ -31012,6 +33639,8 @@ private: # 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 @@ -31113,7 +33742,6 @@ using type_identity_t = typename type_identity::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 struct size_of: std::integral_constant {}; @@ -31355,7 +33983,8 @@ struct type_list_contains; * @tparam Other Type to look for. */ template -struct type_list_contains, Other>: std::disjunction...> {}; +struct type_list_contains, Other> + : std::bool_constant<(std::is_same_v || ...)> {}; /** * @brief Helper variable template. @@ -31443,10 +34072,20 @@ struct value_list_element> */ template struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = typename value_list_element::type; + /** * @brief Helper type. * @tparam Index Index of the value to return. @@ -31455,6 +34094,58 @@ struct value_list_element<0u, value_list> { template inline constexpr auto value_list_element_v = value_list_element::value; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +struct value_list_index> { + static_assert(value_list_index>::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 +struct value_list_index> { + /*! @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 +inline constexpr std::size_t value_list_index_v = value_list_index::value; + /** * @brief Concatenates multiple value lists. * @tparam Value Values provided by the first value list. @@ -31506,6 +34197,89 @@ struct value_list_cat> { template using value_list_cat_t = typename value_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = std::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = typename value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : std::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +class value_list_diff, value_list> { + using v141_toolset_workaround = value_list; + +public: + /*! @brief A value list that is the difference between the two value lists. */ + using type = value_list_cat_t, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = typename value_list_diff::type; + /*! @brief Same as std::is_invocable, but with tuples. */ template struct is_applicable: std::false_type {}; @@ -31626,7 +34400,7 @@ inline constexpr bool is_iterator_v = is_iterator::value; */ template struct is_ebco_eligible - : std::conjunction, std::negation>> {}; + : std::bool_constant && !std::is_final_v> {}; /** * @brief Helper variable template. @@ -31717,6 +34491,10 @@ template struct is_equality_comparable() == std::declval())>> : std::bool_constant(choice<2>)> {}; +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: std::false_type {}; + /** * @brief Helper variable template. * @tparam Type The type to test. @@ -31813,6 +34591,18 @@ using nth_argument_t = typename nth_argument::type; } // namespace entt +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::type_list_element> {}; + +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::value_list_element> {}; + #endif @@ -32396,7 +35186,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 struct allocation_deleter: private Allocator { @@ -32417,7 +35207,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) { - using alloc_traits = typename std::allocator_traits; + using alloc_traits = std::allocator_traits; alloc_traits::destroy(*this, to_address(ptr)); alloc_traits::deallocate(*this, ptr, 1u); } @@ -32584,6 +35374,7 @@ constexpr Type *uninitialized_construct_using_allocator(Type *value, const Alloc #include #include +#include #include #include // #include "../config/config.h" @@ -32638,7 +35429,6 @@ using type_identity_t = typename type_identity::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 struct size_of: std::integral_constant {}; @@ -32880,7 +35670,8 @@ struct type_list_contains; * @tparam Other Type to look for. */ template -struct type_list_contains, Other>: std::disjunction...> {}; +struct type_list_contains, Other> + : std::bool_constant<(std::is_same_v || ...)> {}; /** * @brief Helper variable template. @@ -32968,10 +35759,20 @@ struct value_list_element> */ template struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = typename value_list_element::type; + /** * @brief Helper type. * @tparam Index Index of the value to return. @@ -32980,6 +35781,58 @@ struct value_list_element<0u, value_list> { template inline constexpr auto value_list_element_v = value_list_element::value; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +struct value_list_index> { + static_assert(value_list_index>::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 +struct value_list_index> { + /*! @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 +inline constexpr std::size_t value_list_index_v = value_list_index::value; + /** * @brief Concatenates multiple value lists. * @tparam Value Values provided by the first value list. @@ -33031,6 +35884,89 @@ struct value_list_cat> { template using value_list_cat_t = typename value_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = std::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = typename value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : std::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +class value_list_diff, value_list> { + using v141_toolset_workaround = value_list; + +public: + /*! @brief A value list that is the difference between the two value lists. */ + using type = value_list_cat_t, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = typename value_list_diff::type; + /*! @brief Same as std::is_invocable, but with tuples. */ template struct is_applicable: std::false_type {}; @@ -33151,7 +36087,7 @@ inline constexpr bool is_iterator_v = is_iterator::value; */ template struct is_ebco_eligible - : std::conjunction, std::negation>> {}; + : std::bool_constant && !std::is_final_v> {}; /** * @brief Helper variable template. @@ -33242,6 +36178,10 @@ template struct is_equality_comparable() == std::declval())>> : std::bool_constant(choice<2>)> {}; +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: std::false_type {}; + /** * @brief Helper variable template. * @tparam Type The type to test. @@ -33338,6 +36278,18 @@ using nth_argument_t = typename nth_argument::type; } // namespace entt +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::type_list_element> {}; + +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::value_list_element> {}; + #endif // #include "fwd.hpp" @@ -33479,51 +36431,51 @@ public: return {it->element.first, it->element.second}; } - template - friend constexpr std::ptrdiff_t operator-(const dense_map_iterator &, const dense_map_iterator &) noexcept; + template + friend constexpr std::ptrdiff_t operator-(const dense_map_iterator &, const dense_map_iterator &) noexcept; - template - friend constexpr bool operator==(const dense_map_iterator &, const dense_map_iterator &) noexcept; + template + friend constexpr bool operator==(const dense_map_iterator &, const dense_map_iterator &) noexcept; - template - friend constexpr bool operator<(const dense_map_iterator &, const dense_map_iterator &) noexcept; + template + friend constexpr bool operator<(const dense_map_iterator &, const dense_map_iterator &) noexcept; private: It it; }; -template -[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return lhs.it - rhs.it; } -template -[[nodiscard]] constexpr bool operator==(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return lhs.it == rhs.it; } -template -[[nodiscard]] constexpr bool operator!=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return !(lhs == rhs); } -template -[[nodiscard]] constexpr bool operator<(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return lhs.it < rhs.it; } -template -[[nodiscard]] constexpr bool operator>(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return rhs < lhs; } -template -[[nodiscard]] constexpr bool operator<=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return !(lhs > rhs); } -template -[[nodiscard]] constexpr bool operator>=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return !(lhs < rhs); } @@ -33581,13 +36533,13 @@ private: std::size_t offset; }; -template -[[nodiscard]] constexpr bool operator==(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { return lhs.index() == rhs.index(); } -template -[[nodiscard]] constexpr bool operator!=(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { return !(lhs == rhs); } @@ -33617,7 +36569,7 @@ class dense_map { static constexpr std::size_t minimum_capacity = 8u; using node_type = internal::dense_map_node; - using alloc_traits = typename std::allocator_traits; + using alloc_traits = std::allocator_traits; static_assert(std::is_same_v>, "Invalid value type"); using sparse_container_type = std::vector>; using packed_container_type = std::vector>; @@ -33815,7 +36767,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. @@ -33836,11 +36787,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. */ @@ -34189,7 +37135,7 @@ public: } /*! @copydoc equal_range */ - template + template [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t>> equal_range(const Other &key) const { const auto it = find(key); @@ -34414,8 +37360,6 @@ struct uses_allocator, Allocator> // #include "../core/any.hpp" -// #include "../core/compressed_pair.hpp" - // #include "../core/fwd.hpp" // #include "../core/iterator.hpp" @@ -34428,14 +37372,312 @@ struct uses_allocator, Allocator> // #include "../core/utility.hpp" -// #include "component.hpp" - // #include "entity.hpp" // #include "fwd.hpp" // #include "group.hpp" +// #include "mixin.hpp" +#ifndef ENTT_ENTITY_MIXIN_HPP +#define ENTT_ENTITY_MIXIN_HPP + +#include +#include +// #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); + * @endcode + * + * This applies to all signals made available. + * + * @tparam Type The type of the underlying storage. + */ +template +class sigh_mixin final: public Type { + using underlying_type = Type; + using basic_registry_type = basic_registry; + using sigh_type = sigh; + 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(pos)); entt != tombstone) { + destruction.publish(reg, entt); + } + } else { + destruction.publish(reg, underlying_type::operator[](static_cast(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.
+ * 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.
+ * 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.
+ * 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).
+ * 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).
+ * 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 + decltype(auto) emplace(const entity_type hint, Args &&...args) { + if constexpr(std::is_same_v) { + const auto entt = underlying_type::emplace(hint, std::forward(args)...); + construction.publish(owner_or_assert(), entt); + return entt; + } else { + underlying_type::emplace(hint, std::forward(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 + decltype(auto) patch(const entity_type entt, Func &&...func) { + underlying_type::patch(entt, std::forward(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).
+ * 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 + void insert(It first, It last, Args &&...args) { + underlying_type::insert(first, last, std::forward(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(&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 + // #include "sparse_set.hpp" // #include "storage.hpp" @@ -34524,64 +37766,62 @@ public: return operator*(); } - template - friend constexpr std::ptrdiff_t operator-(const registry_storage_iterator &, const registry_storage_iterator &) noexcept; + template + friend constexpr std::ptrdiff_t operator-(const registry_storage_iterator &, const registry_storage_iterator &) noexcept; - template - friend constexpr bool operator==(const registry_storage_iterator &, const registry_storage_iterator &) noexcept; + template + friend constexpr bool operator==(const registry_storage_iterator &, const registry_storage_iterator &) noexcept; - template - friend constexpr bool operator<(const registry_storage_iterator &, const registry_storage_iterator &) noexcept; + template + friend constexpr bool operator<(const registry_storage_iterator &, const registry_storage_iterator &) noexcept; private: It it; }; -template -[[nodiscard]] constexpr std::ptrdiff_t operator-(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr std::ptrdiff_t operator-(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { return lhs.it - rhs.it; } -template -[[nodiscard]] constexpr bool operator==(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { return lhs.it == rhs.it; } -template -[[nodiscard]] constexpr bool operator!=(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { return !(lhs == rhs); } -template -[[nodiscard]] constexpr bool operator<(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { return lhs.it < rhs.it; } -template -[[nodiscard]] constexpr bool operator>(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { return rhs < lhs; } -template -[[nodiscard]] constexpr bool operator<=(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<=(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { return !(lhs > rhs); } -template -[[nodiscard]] constexpr bool operator>=(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>=(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { return !(lhs < rhs); } +template class registry_context { - using key_type = id_type; - using mapped_type = basic_any<0u>; - using container_type = dense_map; + using alloc_traits = std::allocator_traits; + using allocator_type = typename alloc_traits::template rebind_alloc>>; public: - template - [[deprecated("Use ::emplace_as instead")]] Type &emplace_hint(const id_type id, Args &&...args) { - return emplace_as(id, std::forward(args)...); - } + explicit registry_context(const allocator_type &allocator) + : ctx{allocator} {} template Type &emplace_as(const id_type id, Args &&...args) { @@ -34609,16 +37849,6 @@ public: return it != ctx.end() && it->second.type() == type_id() ? (ctx.erase(it), true) : false; } - template - [[deprecated("Use ::get instead")]] [[nodiscard]] const Type &at(const id_type id = type_id().hash()) const { - return get(id); - } - - template - [[deprecated("Use ::get instead")]] [[nodiscard]] Type &at(const id_type id = type_id().hash()) { - return get(id); - } - template [[nodiscard]] const Type &get(const id_type id = type_id().hash()) const { return any_cast(ctx.at(id)); @@ -34648,7 +37878,7 @@ public: } private: - container_type ctx; + dense_map, identity, std::equal_to, allocator_type> ctx; }; } // namespace internal @@ -34660,138 +37890,93 @@ private: /** * @brief Fast and reliable entity-component system. - * @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 class basic_registry { - using alloc_traits = typename std::allocator_traits; + using base_type = basic_sparse_set; + + using alloc_traits = std::allocator_traits; static_assert(std::is_same_v, "Invalid value type"); - using basic_common_type = basic_sparse_set; - using entity_traits = entt_traits; + + // std::shared_ptr because of its type erased allocator which is useful here + using pool_container_type = dense_map, identity, std::equal_to, typename alloc_traits::template rebind_alloc>>>; + using group_container_type = dense_map, identity, std::equal_to, typename alloc_traits::template rebind_alloc>>>; template - using storage_for_type = typename storage_for>>::type; + [[nodiscard]] auto &assure([[maybe_unused]] const id_type id = type_hash::value()) { + if constexpr(std::is_same_v) { + return entities; + } else { + static_assert(std::is_same_v>, "Non-decayed types not allowed"); + auto &cpool = pools[id]; - template - struct group_handler; + if(!cpool) { + using storage_type = storage_for_type; + using alloc_type = typename storage_type::allocator_type; - template - struct group_handler, get_t, Owned...> { - // nasty workaround for an issue with the toolset v141 that doesn't accept a fold expression here - static_assert(!std::disjunction_v::in_place_delete>...>, "Groups do not support in-place delete"); - using value_type = std::conditional_t; - value_type current{}; - - template - group_handler(Args &&...args) - : current{std::forward(args)...} {} - - template - void maybe_valid_if(basic_registry &owner, const Entity entt) { - [[maybe_unused]] const auto cpools = std::forward_as_tuple(owner.assure()...); - - const auto is_valid = ((std::is_same_v || std::get &>(cpools).contains(entt)) && ...) - && ((std::is_same_v || owner.assure().contains(entt)) && ...) - && ((std::is_same_v || !owner.assure().contains(entt)) && ...); - - if constexpr(sizeof...(Owned) == 0) { - if(is_valid && !current.contains(entt)) { - current.emplace(entt); - } - } else { - if(is_valid && !(std::get<0>(cpools).index(entt) < current)) { - const auto pos = current++; - (std::get &>(cpools).swap_elements(std::get &>(cpools).data()[pos], entt), ...); + if constexpr(std::is_same_v && !std::is_constructible_v) { + // std::allocator has no cross constructors (waiting for C++20) + cpool = std::allocate_shared(get_allocator(), alloc_type{}); + } else { + cpool = std::allocate_shared(get_allocator(), get_allocator()); } + + cpool->bind(forward_as_any(*this)); } - } - void discard_if([[maybe_unused]] basic_registry &owner, const Entity entt) { - if constexpr(sizeof...(Owned) == 0) { - current.remove(entt); - } else { - if(const auto cpools = std::forward_as_tuple(owner.assure()...); std::get<0>(cpools).contains(entt) && (std::get<0>(cpools).index(entt) < current)) { - const auto pos = --current; - (std::get &>(cpools).swap_elements(std::get &>(cpools).data()[pos], entt), ...); - } + ENTT_ASSERT(cpool->type() == type_id(), "Unexpected type"); + return static_cast &>(*cpool); + } + } + + template + [[nodiscard]] const auto *assure([[maybe_unused]] const id_type id = type_hash::value()) const { + if constexpr(std::is_same_v) { + return &entities; + } else { + static_assert(std::is_same_v>, "Non-decayed types not allowed"); + + if(const auto it = pools.find(id); it != pools.cend()) { + ENTT_ASSERT(it->second->type() == type_id(), "Unexpected type"); + return static_cast *>(it->second.get()); } + + return static_cast *>(nullptr); } - }; - - struct group_data { - std::size_t size; - std::shared_ptr group; - bool (*owned)(const id_type) noexcept; - bool (*get)(const id_type) noexcept; - bool (*exclude)(const id_type) noexcept; - }; - - template - [[nodiscard]] auto &assure(const id_type id = type_hash::value()) { - static_assert(std::is_same_v>, "Non-decayed types not allowed"); - auto &cpool = pools[id]; - - if(!cpool) { - cpool = std::allocate_shared>>(get_allocator(), get_allocator()); - cpool->bind(forward_as_any(*this)); - } - - ENTT_ASSERT(cpool->type() == type_id(), "Unexpected type"); - return static_cast &>(*cpool); - } - - template - [[nodiscard]] const auto &assure(const id_type id = type_hash::value()) const { - static_assert(std::is_same_v>, "Non-decayed types not allowed"); - - if(const auto it = pools.find(id); it != pools.cend()) { - ENTT_ASSERT(it->second->type() == type_id(), "Unexpected type"); - return static_cast &>(*it->second); - } - - static storage_for_type placeholder{}; - return placeholder; - } - - auto generate_identifier(const std::size_t pos) noexcept { - ENTT_ASSERT(pos < entity_traits::to_entity(null), "No entities available"); - return entity_traits::combine(static_cast(pos), {}); - } - - auto recycle_identifier() noexcept { - ENTT_ASSERT(free_list != null, "No entities available"); - const auto curr = entity_traits::to_entity(free_list); - free_list = entity_traits::combine(entity_traits::to_integral(epool[curr]), tombstone); - return (epool[curr] = entity_traits::combine(curr, entity_traits::to_integral(epool[curr]))); - } - - auto release_entity(const Entity entt, const typename entity_traits::version_type version) { - const typename entity_traits::version_type vers = version + (version == entity_traits::to_version(tombstone)); - epool[entity_traits::to_entity(entt)] = entity_traits::construct(entity_traits::to_integral(free_list), vers); - free_list = entity_traits::combine(entity_traits::to_integral(entt), tombstone); - return vers; } void rebind() { + entities.bind(forward_as_any(*this)); + for(auto &&curr: pools) { curr.second->bind(forward_as_any(*this)); } } public: + /*! @brief Entity traits. */ + using traits_type = typename base_type::traits_type; /*! @brief Allocator type. */ using allocator_type = Allocator; /*! @brief Underlying entity identifier. */ - using entity_type = Entity; + 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 Common type among all storage types. */ - using base_type = basic_common_type; + using common_type = base_type; /*! @brief Context type. */ - using context = internal::registry_context; + using context = internal::registry_context; + + /** + * @copybrief storage_for + * @tparam Type Storage value type, eventually const. + */ + template + using storage_for_type = typename storage_for>>::type; /*! @brief Default constructor. */ basic_registry() @@ -34810,12 +37995,12 @@ public: * @param allocator The allocator to use. */ basic_registry(const size_type count, const allocator_type &allocator = allocator_type{}) - : vars{}, - free_list{tombstone}, - epool{allocator}, + : vars{allocator}, pools{allocator}, - groups{allocator} { + groups{allocator}, + entities{allocator} { pools.reserve(count); + rebind(); } /** @@ -34824,10 +38009,9 @@ public: */ basic_registry(basic_registry &&other) noexcept : vars{std::move(other.vars)}, - free_list{std::move(other.free_list)}, - epool{std::move(other.epool)}, pools{std::move(other.pools)}, - groups{std::move(other.groups)} { + groups{std::move(other.groups)}, + entities{std::move(other.entities)} { rebind(); } @@ -34838,10 +38022,9 @@ public: */ basic_registry &operator=(basic_registry &&other) noexcept { vars = std::move(other.vars); - free_list = std::move(other.free_list); - epool = std::move(other.epool); pools = std::move(other.pools); groups = std::move(other.groups); + entities = std::move(other.entities); rebind(); @@ -34854,11 +38037,11 @@ public: */ void swap(basic_registry &other) { using std::swap; + swap(vars, other.vars); - swap(free_list, other.free_list); - swap(epool, other.epool); swap(pools, other.pools); swap(groups, other.groups); + swap(entities, other.entities); rebind(); other.rebind(); @@ -34869,7 +38052,7 @@ public: * @return The associated allocator. */ [[nodiscard]] constexpr allocator_type get_allocator() const noexcept { - return epool.get_allocator(); + return pools.get_allocator(); } /** @@ -34894,8 +38077,8 @@ public: * @param id Name used to map the storage within the registry. * @return A pointer to the storage if it exists, a null pointer otherwise. */ - [[nodiscard]] base_type *storage(const id_type id) { - return const_cast(std::as_const(*this).storage(id)); + [[nodiscard]] common_type *storage(const id_type id) { + return const_cast(std::as_const(*this).storage(id)); } /** @@ -34903,7 +38086,7 @@ public: * @param id Name used to map the storage within the registry. * @return A pointer to the storage if it exists, a null pointer otherwise. */ - [[nodiscard]] const base_type *storage(const id_type id) const { + [[nodiscard]] const common_type *storage(const id_type id) const { const auto it = pools.find(id); return it == pools.cend() ? nullptr : it->second.get(); } @@ -34915,23 +38098,18 @@ public: * @return The storage for the given component type. */ template - decltype(auto) storage(const id_type id = type_hash::value()) { + storage_for_type &storage(const id_type id = type_hash::value()) { return assure(id); } /** - * @brief Returns the storage for a given component type. - * - * @warning - * If a storage for the given component doesn't exist yet, a temporary - * placeholder is returned instead. - * + * @brief Returns the storage for a given component type, if any. * @tparam Type Type of component of which to return the storage. * @param id Optional name used to map the storage within the registry. * @return The storage for the given component type. */ template - decltype(auto) storage(const id_type id = type_hash::value()) const { + const storage_for_type *storage(const id_type id = type_hash::value()) const { return assure(id); } @@ -34939,30 +38117,24 @@ public: * @brief Returns the number of entities created so far. * @return Number of entities created so far. */ - [[nodiscard]] size_type size() const noexcept { - return epool.size(); + [[deprecated("use .storage().size() instead")]] [[nodiscard]] size_type size() const noexcept { + return entities.size(); } /** * @brief Returns the number of entities still in use. * @return Number of entities still in use. */ - [[nodiscard]] size_type alive() const { - auto sz = epool.size(); - - for(auto curr = free_list; curr != null; --sz) { - curr = epool[entity_traits::to_entity(curr)]; - } - - return sz; + [[deprecated("use .storage().in_use() instead")]] [[nodiscard]] size_type alive() const { + return entities.in_use(); } /** * @brief Increases the capacity (number of entities) of the registry. * @param cap Desired capacity. */ - void reserve(const size_type cap) { - epool.reserve(cap); + [[deprecated("use .storage().reserve(cap) instead")]] void reserve(const size_type cap) { + entities.reserve(cap); } /** @@ -34970,15 +38142,15 @@ public: * allocated space for. * @return Capacity of the registry. */ - [[nodiscard]] size_type capacity() const noexcept { - return epool.capacity(); + [[deprecated("use .storage().capacity() instead")]] [[nodiscard]] size_type capacity() const noexcept { + return entities.capacity(); } /** * @brief Checks whether the registry is empty (no entities still in use). * @return True if the registry is empty, false otherwise. */ - [[nodiscard]] bool empty() const { + [[deprecated("use .storage().in_use() instead")]] [[nodiscard]] bool empty() const { return !alive(); } @@ -34994,20 +38166,16 @@ public: * * @return A pointer to the array of entities. */ - [[nodiscard]] const entity_type *data() const noexcept { - return epool.data(); + [[deprecated("use .storage().data() instead")]] [[nodiscard]] const entity_type *data() const noexcept { + return entities.data(); } /** - * @brief Returns the head of the list of released entities. - * - * This function is intended for use in conjunction with `assign`.
- * The returned entity has an invalid identifier in all cases. - * - * @return The head of the list of released entities. + * @brief Returns the number of released entities. + * @return The number of released entities. */ - [[nodiscard]] entity_type released() const noexcept { - return free_list; + [[deprecated("use .storage().size() and .storage().in_use() instead")]] [[nodiscard]] size_type released() const noexcept { + return (entities.size() - entities.in_use()); } /** @@ -35016,8 +38184,7 @@ public: * @return True if the identifier is valid, false otherwise. */ [[nodiscard]] bool valid(const entity_type entt) const { - const auto pos = size_type(entity_traits::to_entity(entt)); - return (pos < epool.size() && epool[pos] == entt); + return entities.contains(entt) && (entities.index(entt) < entities.in_use()); } /** @@ -35027,8 +38194,7 @@ public: * version otherwise. */ [[nodiscard]] version_type current(const entity_type entt) const { - const auto pos = size_type(entity_traits::to_entity(entt)); - return entity_traits::to_version(pos < epool.size() ? epool[pos] : tombstone); + return entities.current(entt); } /** @@ -35036,7 +38202,7 @@ public: * @return A valid identifier. */ [[nodiscard]] entity_type create() { - return (free_list == null) ? epool.emplace_back(generate_identifier(epool.size())) : recycle_identifier(); + return entities.emplace(); } /** @@ -35049,26 +38215,7 @@ public: * @return A valid identifier. */ [[nodiscard]] entity_type create(const entity_type hint) { - const auto length = epool.size(); - - if(hint == null || hint == tombstone) { - return create(); - } else if(const auto req = entity_traits::to_entity(hint); !(req < length)) { - epool.resize(size_type(req) + 1u, null); - - for(auto pos = length; pos < req; ++pos) { - release_entity(generate_identifier(pos), {}); - } - - return (epool[req] = hint); - } else if(const auto curr = entity_traits::to_entity(epool[req]); req == curr) { - return create(); - } else { - auto *it = &free_list; - for(; entity_traits::to_entity(*it) != req; it = &epool[entity_traits::to_entity(*it)]) {} - *it = entity_traits::combine(curr, entity_traits::to_integral(*it)); - return (epool[req] = hint); - } + return entities.emplace(hint); } /** @@ -35082,16 +38229,7 @@ public: */ template void create(It first, It last) { - for(; free_list != null && first != last; ++first) { - *first = recycle_identifier(); - } - - const auto length = epool.size(); - epool.resize(length + std::distance(first, last), null); - - for(auto pos = length; first != last; ++first, ++pos) { - *first = epool[pos] = generate_identifier(pos); - } + entities.insert(std::move(first), std::move(last)); } /** @@ -35109,13 +38247,13 @@ public: * @tparam It Type of input iterator. * @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 destroyed The head of the list of destroyed entities. + * @param destroyed The number of released entities. */ template - void assign(It first, It last, const entity_type destroyed) { - ENTT_ASSERT(!alive(), "Entities still alive"); - epool.assign(first, last); - free_list = destroyed; + [[deprecated("use .storage().push(first, last) and .storage().in_use(len) instead")]] void assign(It first, It last, const size_type destroyed) { + ENTT_ASSERT(!entities.in_use(), "Non-empty registry"); + entities.push(first, last); + entities.in_use(entities.size() - destroyed); } /** @@ -35123,14 +38261,13 @@ public: * * The version is updated and the identifier can be recycled at any time. * - * @warning - * Attempting to use an invalid entity results in undefined behavior. - * * @param entt A valid identifier. * @return The version of the recycled entity. */ - version_type release(const entity_type entt) { - return release(entt, static_cast(entity_traits::to_version(entt) + 1u)); + [[deprecated("use .orphan(entt) and .storage().erase(entt) instead")]] version_type release(const entity_type entt) { + ENTT_ASSERT(orphan(entt), "Non-orphan entity"); + entities.erase(entt); + return entities.current(entt); } /** @@ -35139,49 +38276,47 @@ public: * The suggested version or the valid version closest to the suggested one * is used instead of the implicitly generated version. * - * @sa release - * * @param entt A valid identifier. * @param version A desired version upon destruction. * @return The version actually assigned to the entity. */ - version_type release(const entity_type entt, const version_type version) { - ENTT_ASSERT(valid(entt), "Invalid identifier"); - ENTT_ASSERT(std::all_of(pools.cbegin(), pools.cend(), [entt](auto &&curr) { return (curr.second->current(entt) == entity_traits::to_version(tombstone)); }), "Non-orphan entity"); - return release_entity(entt, version); + [[deprecated("use .orphan(entt), then .storage().erase(entt)/.bump(next) instead")]] version_type release(const entity_type entt, const version_type version) { + ENTT_ASSERT(orphan(entt), "Non-orphan entity"); + entities.erase(entt); + const auto elem = traits_type::construct(traits_type::to_entity(entt), version); + return entities.bump((elem == tombstone) ? traits_type::next(elem) : elem); } /** * @brief Releases all identifiers in a range. * - * @sa release - * * @tparam It Type of input iterator. * @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. */ template - void release(It first, It last) { - for(; first != last; ++first) { - release(*first); - } + [[deprecated("use .orphan(entt) and .storage().erase(first, last) instead")]] void release(It first, It last) { + ENTT_ASSERT(std::all_of(first, last, [this](const auto entt) { return orphan(entt); }), "Non-orphan entity"); + entities.erase(std::move(first), std::move(last)); } /** * @brief Destroys an entity and releases its identifier. * - * @sa release - * * @warning * Adding or removing components to an entity that is being destroyed can - * result in undefined behavior. Attempting to use an invalid entity results - * in undefined behavior. + * result in undefined behavior. * * @param entt A valid identifier. * @return The version of the recycled entity. */ version_type destroy(const entity_type entt) { - return destroy(entt, static_cast(entity_traits::to_version(entt) + 1u)); + for(size_type pos = pools.size(); pos; --pos) { + pools.begin()[pos - 1u].second->remove(entt); + } + + entities.erase(entt); + return entities.current(entt); } /** @@ -35197,11 +38332,9 @@ public: * @return The version actually assigned to the entity. */ version_type destroy(const entity_type entt, const version_type version) { - for(size_type pos = pools.size(); pos; --pos) { - pools.begin()[pos - 1u].second->remove(entt); - } - - return release(entt, version); + destroy(entt); + const auto elem = traits_type::construct(traits_type::to_entity(entt), version); + return entities.bump((elem == tombstone) ? traits_type::next(elem) : elem); } /** @@ -35215,9 +38348,14 @@ public: */ template void destroy(It first, It last) { - for(; first != last; ++first) { - destroy(*first); + const auto from = entities.each().cbegin().base(); + const auto to = from + entities.pack(first, last); + + for(size_type pos = pools.size(); pos; --pos) { + pools.begin()[pos - 1u].second->remove(from, to); } + + entities.erase(from, to); } /** @@ -35253,7 +38391,7 @@ public: */ template void insert(It first, It last, const Type &value = {}) { - assure().insert(first, last, value); + assure().insert(std::move(first), std::move(last), value); } /** @@ -35303,13 +38441,8 @@ public: * void(Type &); * @endcode * - * @note - * Empty types aren't explicitly instantiated and therefore they are never - * returned. However, this function can be used to trigger an update signal - * for them. - * * @warning - * Attempting to to patch a component of an entity that doesn't own it + * Attempting to patch a component of an entity that doesn't own it * results in undefined behavior. * * @tparam Type Type of component to patch. @@ -35345,7 +38478,6 @@ public: /** * @brief Removes the given components from an entity. - * * @tparam Type Type of component to remove. * @tparam Other Other types of components to remove. * @param entt A valid identifier. @@ -35370,17 +38502,27 @@ public: */ template size_type remove(It first, It last) { - if constexpr(sizeof...(Other) == 0u) { - return assure().remove(std::move(first), std::move(last)); - } else { - size_type count{}; + size_type count{}; + if constexpr(std::is_same_v) { + common_type *cpools[sizeof...(Other) + 1u]{&assure(), &assure()...}; + + for(size_type pos{}, len = sizeof...(Other) + 1u; pos < len; ++pos) { + if constexpr(sizeof...(Other) != 0u) { + if(cpools[pos]->data() == first.data()) { + std::swap(cpools[pos], cpools[sizeof...(Other)]); + } + } + + count += cpools[pos]->remove(first, last); + } + } else { for(auto cpools = std::forward_as_tuple(assure(), assure()...); first != last; ++first) { count += std::apply([entt = *first](auto &...curr) { return (curr.remove(entt) + ... + 0u); }, cpools); } - - return count; } + + return count; } /** @@ -35412,8 +38554,18 @@ public: */ template void erase(It first, It last) { - if constexpr(sizeof...(Other) == 0u) { - assure().erase(std::move(first), std::move(last)); + if constexpr(std::is_same_v) { + common_type *cpools[sizeof...(Other) + 1u]{&assure(), &assure()...}; + + for(size_type pos{}, len = sizeof...(Other) + 1u; pos < len; ++pos) { + if constexpr(sizeof...(Other) != 0u) { + if(cpools[pos]->data() == first.data()) { + std::swap(cpools[pos], cpools[sizeof...(Other)]); + } + } + + cpools[pos]->erase(first, last); + } } else { for(auto cpools = std::forward_as_tuple(assure(), assure()...); first != last; ++first) { std::apply([entt = *first](auto &...curr) { (curr.erase(entt), ...); }, cpools); @@ -35421,6 +38573,30 @@ public: } } + /** + * @brief Erases components satisfying specific criteria from an entity. + * + * The function type is equivalent to: + * + * @code{.cpp} + * void(const id_type, typename basic_registry::base_type &); + * @endcode + * + * Only storage where the entity exists are passed to the function. + * + * @tparam Func Type of the function object to invoke. + * @param entt A valid identifier. + * @param func A valid function object. + */ + template + void erase_if(const entity_type entt, Func func) { + for(auto [id, cpool]: storage()) { + if(cpool.contains(entt) && func(id, std::as_const(cpool))) { + cpool.erase(entt); + } + } + } + /** * @brief Removes all tombstones from a registry or only the pools for the * given components. @@ -35428,7 +38604,7 @@ public: */ template void compact() { - if constexpr(sizeof...(Type) == 0) { + if constexpr(sizeof...(Type) == 0u) { for(auto &&curr: pools) { curr.second->compact(); } @@ -35445,7 +38621,12 @@ public: */ template [[nodiscard]] bool all_of(const entity_type entt) const { - return (assure>().contains(entt) && ...); + if constexpr(sizeof...(Type) == 1u) { + auto *cpool = assure...>(); + return cpool && cpool->contains(entt); + } else { + return (all_of(entt) && ...); + } } /** @@ -35457,7 +38638,7 @@ public: */ template [[nodiscard]] bool any_of(const entity_type entt) const { - return (assure>().contains(entt) || ...); + return (all_of(entt) || ...); } /** @@ -35474,7 +38655,7 @@ public: template [[nodiscard]] decltype(auto) get([[maybe_unused]] const entity_type entt) const { if constexpr(sizeof...(Type) == 1u) { - return (assure>().get(entt), ...); + return (assure>()->get(entt), ...); } else { return std::forward_as_tuple(get(entt)...); } @@ -35484,7 +38665,7 @@ public: template [[nodiscard]] decltype(auto) get([[maybe_unused]] const entity_type entt) { if constexpr(sizeof...(Type) == 1u) { - return (const_cast(std::as_const(*this).template get(entt)), ...); + return (static_cast &>(assure>()).get(entt), ...); } else { return std::forward_as_tuple(get(entt)...); } @@ -35526,9 +38707,9 @@ public: */ template [[nodiscard]] auto try_get([[maybe_unused]] const entity_type entt) const { - if constexpr(sizeof...(Type) == 1) { - const auto &cpool = assure...>(); - return cpool.contains(entt) ? std::addressof(cpool.get(entt)) : nullptr; + if constexpr(sizeof...(Type) == 1u) { + const auto *cpool = assure...>(); + return (cpool && cpool->contains(entt)) ? std::addressof(cpool->get(entt)) : nullptr; } else { return std::make_tuple(try_get(entt)...); } @@ -35537,8 +38718,9 @@ public: /*! @copydoc try_get */ template [[nodiscard]] auto try_get([[maybe_unused]] const entity_type entt) { - if constexpr(sizeof...(Type) == 1) { - return (const_cast(std::as_const(*this).template try_get(entt)), ...); + if constexpr(sizeof...(Type) == 1u) { + auto &cpool = assure...>(); + return (static_cast(cpool.contains(entt) ? std::addressof(cpool.get(entt)) : nullptr), ...); } else { return std::make_tuple(try_get(entt)...); } @@ -35550,12 +38732,13 @@ public: */ template void clear() { - if constexpr(sizeof...(Type) == 0) { - for(auto &&curr: pools) { - curr.second->clear(); + if constexpr(sizeof...(Type) == 0u) { + for(size_type pos = pools.size(); pos; --pos) { + pools.begin()[pos - 1u].second->clear(); } - each([this](const auto entity) { this->release(entity); }); + const auto iterable = entities.each(); + entities.erase(iterable.begin().base(), iterable.end().base()); } else { (assure().clear(), ...); } @@ -35576,17 +38759,9 @@ public: * @param func A valid function object. */ template - void each(Func func) const { - if(free_list == null) { - for(auto pos = epool.size(); pos; --pos) { - func(epool[pos - 1]); - } - } else { - for(auto pos = epool.size(); pos; --pos) { - if(const auto entity = epool[pos - 1]; entity_traits::to_entity(entity) == (pos - 1)) { - func(entity); - } - } + [[deprecated("use .storage().each() instead")]] void each(Func func) const { + for(auto [entt]: entities.each()) { + func(entt); } } @@ -35615,11 +38790,12 @@ public: * @sa sink * * @tparam Type Type of component of which to get the sink. + * @param id Optional name used to map the storage within the registry. * @return A temporary sink object. */ template - [[nodiscard]] auto on_construct() { - return assure().on_construct(); + [[nodiscard]] auto on_construct(const id_type id = type_hash::value()) { + return assure(id).on_construct(); } /** @@ -35638,11 +38814,12 @@ public: * @sa sink * * @tparam Type Type of component of which to get the sink. + * @param id Optional name used to map the storage within the registry. * @return A temporary sink object. */ template - [[nodiscard]] auto on_update() { - return assure().on_update(); + [[nodiscard]] auto on_update(const id_type id = type_hash::value()) { + return assure(id).on_update(); } /** @@ -35661,21 +38838,16 @@ public: * @sa sink * * @tparam Type Type of component of which to get the sink. + * @param id Optional name used to map the storage within the registry. * @return A temporary sink object. */ template - [[nodiscard]] auto on_destroy() { - return assure().on_destroy(); + [[nodiscard]] auto on_destroy(const id_type id = type_hash::value()) { + return assure(id).on_destroy(); } /** * @brief Returns a view for the given components. - * - * Views are created on the fly and share with the registry its internal - * data structures. Feel free to discard them after the use.
- * Creating and destroying a view is an incredibly cheap operation. As a - * rule of thumb, storing a view should never be an option. - * * @tparam Type Type of component used to construct the view. * @tparam Other Other types of components used to construct the view. * @tparam Exclude Types of components used to filter the view. @@ -35683,172 +38855,79 @@ public: */ template [[nodiscard]] basic_view, storage_for_type...>, exclude_t...>> - view(exclude_t = {}) const { - return {assure>(), assure>()..., assure>()...}; + view(exclude_t = exclude_t{}) const { + const auto cpools = std::make_tuple(assure>(), assure>()..., assure>()...); + basic_view, storage_for_type...>, exclude_t...>> elem{}; + std::apply([&elem](const auto *...curr) { ((curr ? elem.storage(*curr) : void()), ...); }, cpools); + return elem; } /*! @copydoc view */ template [[nodiscard]] basic_view, storage_for_type...>, exclude_t...>> - view(exclude_t = {}) { + view(exclude_t = exclude_t{}) { return {assure>(), assure>()..., assure>()...}; } /** * @brief Returns a group for the given components. - * - * Groups are created on the fly and share with the registry its internal - * data structures. Feel free to discard them after the use.
- * Creating and destroying a group is an incredibly cheap operation. As a - * rule of thumb, storing a group should never be an option. - * - * Groups support exclusion lists and can own types of components. The more - * types are owned by a group, the faster it is to iterate entities and - * components.
- * However, groups also affect some features of the registry such as the - * creation and destruction of components. - * - * @note - * Pools of components that are owned by a group cannot be sorted anymore. - * The group takes the ownership of the pools and arrange components so as - * to iterate them as fast as possible. - * - * @tparam Owned Type of storage _owned_ by the group. - * @tparam Get Type of storage _observed_ by the group. - * @tparam Exclude Type of storage used to filter the group. + * @tparam Owned Types of storage _owned_ by the group. + * @tparam Get Types of storage _observed_ by the group, if any. + * @tparam Exclude Types of storage used to filter the group, if any. * @return A newly created group. */ template - [[nodiscard]] basic_group...>, get_t...>, exclude_t...>> - group(get_t = {}, exclude_t = {}) { - static_assert(sizeof...(Owned) + sizeof...(Get) > 0, "Exclusion-only groups are not supported"); - static_assert(sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude) > 1, "Single component groups are not allowed"); + basic_group...>, get_t...>, exclude_t...>> + group(get_t = get_t{}, exclude_t = exclude_t{}) { + using handler_type = typename basic_group...>, get_t...>, exclude_t...>>::handler; - using handler_type = group_handler...>, get_t...>, std::remove_const_t...>; - - const auto cpools = std::forward_as_tuple(assure>()..., assure>()...); - constexpr auto size = sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude); - handler_type *handler = nullptr; - - auto it = std::find_if(groups.cbegin(), groups.cend(), [size](const auto &gdata) { - return gdata.size == size - && (gdata.owned(type_hash>::value()) && ...) - && (gdata.get(type_hash>::value()) && ...) - && (gdata.exclude(type_hash>::value()) && ...); - }); - - if(it != groups.cend()) { - handler = static_cast(it->group.get()); - } else { - group_data candidate = { - size, - std::apply([this](auto &&...args) { return std::allocate_shared(get_allocator(), std::forward(args)...); }, entt::uses_allocator_construction_args(get_allocator())), - []([[maybe_unused]] const id_type ctype) noexcept { return ((ctype == type_hash>::value()) || ...); }, - []([[maybe_unused]] const id_type ctype) noexcept { return ((ctype == type_hash>::value()) || ...); }, - []([[maybe_unused]] const id_type ctype) noexcept { return ((ctype == type_hash>::value()) || ...); }, - }; - - handler = static_cast(candidate.group.get()); - - const void *maybe_valid_if = nullptr; - const void *discard_if = nullptr; - - if constexpr(sizeof...(Owned) == 0) { - groups.push_back(std::move(candidate)); - } else { - [[maybe_unused]] auto has_conflict = [size](const auto &gdata) { - const auto overlapping = (0u + ... + gdata.owned(type_hash>::value())); - const auto sz = overlapping + (0u + ... + gdata.get(type_hash>::value())) + (0u + ... + gdata.exclude(type_hash>::value())); - return !overlapping || ((sz == size) || (sz == gdata.size)); - }; - - ENTT_ASSERT(std::all_of(groups.cbegin(), groups.cend(), std::move(has_conflict)), "Conflicting groups"); - - const auto next = std::find_if_not(groups.cbegin(), groups.cend(), [size](const auto &gdata) { - return !(0u + ... + gdata.owned(type_hash>::value())) || (size > gdata.size); - }); - - const auto prev = std::find_if(std::make_reverse_iterator(next), groups.crend(), [](const auto &gdata) { - return (0u + ... + gdata.owned(type_hash>::value())); - }); - - maybe_valid_if = (next == groups.cend() ? maybe_valid_if : next->group.get()); - discard_if = (prev == groups.crend() ? discard_if : prev->group.get()); - groups.insert(next, std::move(candidate)); - } - - (on_construct>().before(maybe_valid_if).template connect<&handler_type::template maybe_valid_if>>(*handler), ...); - (on_construct>().before(maybe_valid_if).template connect<&handler_type::template maybe_valid_if>>(*handler), ...); - (on_destroy>().before(maybe_valid_if).template connect<&handler_type::template maybe_valid_if>>(*handler), ...); - - (on_destroy>().before(discard_if).template connect<&handler_type::discard_if>(*handler), ...); - (on_destroy>().before(discard_if).template connect<&handler_type::discard_if>(*handler), ...); - (on_construct>().before(discard_if).template connect<&handler_type::discard_if>(*handler), ...); - - if constexpr(sizeof...(Owned) == 0) { - for(const auto entity: view(exclude)) { - handler->current.emplace(entity); - } - } else { - // we cannot iterate backwards because we want to leave behind valid entities in case of owned types - for(auto *first = std::get<0>(cpools).data(), *last = first + std::get<0>(cpools).size(); first != last; ++first) { - handler->template maybe_valid_if...>>>(*this, *first); - } - } + if(auto it = groups.find(type_hash::value()); it != groups.cend()) { + return {*std::static_pointer_cast(it->second)}; } - return {handler->current, std::get> &>(cpools)..., std::get> &>(cpools)...}; + std::shared_ptr handler{}; + + if constexpr(sizeof...(Owned) == 0u) { + handler = std::allocate_shared(get_allocator(), get_allocator(), assure>()..., assure>()...); + } else { + handler = std::allocate_shared(get_allocator(), assure>()..., assure>()..., assure>()...); + [[maybe_unused]] const id_type elem[]{type_hash>::value()..., type_hash>::value()..., type_hash>::value()...}; + ENTT_ASSERT(std::all_of(groups.cbegin(), groups.cend(), [&elem](const auto &data) { return data.second->owned(elem, sizeof...(Owned)) == 0u; }), "Conflicting groups"); + } + + groups.emplace(type_hash::value(), handler); + return {*handler}; } /*! @copydoc group */ template - [[nodiscard]] basic_group...>, get_t...>, exclude_t...>> - group_if_exists(get_t = {}, exclude_t = {}) const { - auto it = std::find_if(groups.cbegin(), groups.cend(), [](const auto &gdata) { - return gdata.size == (sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude)) - && (gdata.owned(type_hash>::value()) && ...) - && (gdata.get(type_hash>::value()) && ...) - && (gdata.exclude(type_hash>::value()) && ...); - }); + basic_group...>, get_t...>, exclude_t...>> + group_if_exists(get_t = get_t{}, exclude_t = exclude_t{}) const { + using handler_type = typename basic_group...>, get_t...>, exclude_t...>>::handler; - if(it == groups.cend()) { - return {}; - } else { - using handler_type = group_handler...>, get_t...>, std::remove_const_t...>; - return {static_cast(it->group.get())->current, assure>()..., assure>()...}; + if(auto it = groups.find(type_hash::value()); it != groups.cend()) { + return {*std::static_pointer_cast(it->second)}; } + + return {}; } /** * @brief Checks whether the given components belong to any group. - * @tparam Component Types of components in which one is interested. + * @tparam Type Type of component in which one is interested. + * @tparam Other Other types of components in which one is interested. * @return True if the pools of the given components are _free_, false * otherwise. */ - template + template [[nodiscard]] bool owned() const { - return std::any_of(groups.cbegin(), groups.cend(), [](auto &&gdata) { return (gdata.owned(type_hash>::value()) || ...); }); - } - - /** - * @brief Checks whether a group can be sorted. - * @tparam Owned Type of storage _owned_ by the group. - * @tparam Get Type of storage _observed_ by the group. - * @tparam Exclude Type of storage used to filter the group. - * @return True if the group can be sorted, false otherwise. - */ - template - [[nodiscard]] bool sortable(const basic_group, get_t, exclude_t> &) noexcept { - constexpr auto size = sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude); - auto pred = [size](const auto &gdata) { return (0u + ... + gdata.owned(type_hash::value())) && (size < gdata.size); }; - return std::find_if(groups.cbegin(), groups.cend(), std::move(pred)) == groups.cend(); + const id_type elem[]{type_hash>::value(), type_hash>::value()...}; + return std::any_of(groups.cbegin(), groups.cend(), [&elem](auto &&data) { return data.second->owned(elem, 1u + sizeof...(Other)); }); } /** * @brief Sorts the elements of a given component. * - * The order remains valid until a component of the given type is assigned - * to or removed from an entity.
* The comparison function object returns `true` if the first element is * _less_ than the second one, `false` otherwise. Its signature is also * equivalent to one of the following: @@ -35895,15 +38974,9 @@ public: /** * @brief Sorts two pools of components in the same way. * - * Being `To` and `From` the two sets, after invoking this function an - * iterator for `To` returns elements according to the following rules: - * - * * All entities in `To` that are also in `From` are returned first - * according to the order they have in `From`. - * * All entities in `To` that are not in `From` are returned in no - * particular order after all the other entities. - * - * Any subsequent change to `From` won't affect the order in `To`. + * Entities and components in `To` which are part of both storage are sorted + * internally with the order they have in `From`. The others follow in no + * particular order. * * @warning * Pools of components owned by a group cannot be sorted. @@ -35914,7 +38987,7 @@ public: template void sort() { ENTT_ASSERT(!owned(), "Cannot sort owned storage"); - assure().respect(assure()); + assure().sort_as(assure()); } /** @@ -35932,11 +39005,9 @@ public: private: context vars; - entity_type free_list; - std::vector epool; - // std::shared_ptr because of its type erased allocator which is useful here - dense_map, identity, std::equal_to, typename alloc_traits::template rebind_alloc>>> pools; - std::vector> groups; + pool_container_type pools; + group_container_type groups; + storage_for_type entities; }; } // namespace entt @@ -36052,38 +39123,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.
- * 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. @@ -36102,9 +39157,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; + using iterator = internal::runtime_view_iterator; /*! @brief Default constructor to use to create empty, invalid views. */ basic_runtime_view() noexcept @@ -36183,7 +39238,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 { @@ -36198,7 +39253,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; } @@ -36215,9 +39270,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. */ @@ -36228,11 +39281,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. */ @@ -36255,8 +39303,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.
+ * the entity itself.
* The signature of the function should be equivalent to the following: * * @code{.cpp} @@ -36286,7 +39333,6 @@ private: #ifndef ENTT_ENTITY_SNAPSHOT_HPP #define ENTT_ENTITY_SNAPSHOT_HPP -#include #include #include #include @@ -36299,8 +39345,6 @@ private: // #include "../core/type_traits.hpp" -// #include "component.hpp" - // #include "entity.hpp" // #include "fwd.hpp" @@ -36310,6 +39354,31 @@ private: namespace entt { +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +void orphans(Registry ®istry) { + auto view = registry.template view(); + + 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. * @@ -36322,34 +39391,8 @@ namespace entt { */ template class basic_snapshot { - using entity_traits = entt_traits; - - template - void get(Archive &archive, std::size_t sz, It first, It last) const { - const auto view = reg->template view(); - archive(typename entity_traits::entity_type(sz)); - - while(first != last) { - const auto entt = *(first++); - - if(reg->template all_of(entt)) { - std::apply(archive, std::tuple_cat(std::make_tuple(entt), view.get(entt))); - } - } - } - - template - void component(Archive &archive, It first, It last, std::index_sequence) const { - std::array size{}; - auto begin = first; - - while(begin != last) { - const auto entt = *(begin++); - ((reg->template all_of(entt) ? ++size[Index] : 0u), ...); - } - - (get(archive, size[Index], first, last), ...); - } + static_assert(!std::is_const_v, "Non-const registry type required"); + using traits_type = typename Registry::traits_type; public: /*! Basic registry type. */ @@ -36371,58 +39414,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 - const basic_snapshot &entities(Archive &archive) const { - const auto sz = reg->size(); + template + const basic_snapshot &get(Archive &archive, const id_type id = type_hash::value()) const { + if(const auto *storage = reg->template storage(id); storage) { + archive(static_cast(storage->size())); - archive(typename entity_traits::entity_type(sz + 1u)); - archive(reg->released()); + if constexpr(std::is_same_v) { + archive(static_cast(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(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 + const basic_snapshot &get(Archive &archive, It first, It last, const id_type id = type_hash::value()) const { + static_assert(!std::is_same_v, "Entity types not supported"); + + if(const auto *storage = reg->template storage(id); storage && !storage->empty()) { + archive(static_cast(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(args)), ...); }, storage->get_as_tuple(entt)); + } else { + archive(static_cast(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 + [[deprecated("use .get(archive) instead")]] const basic_snapshot &entities(Archive &archive) const { + return get(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 - const basic_snapshot &component(Archive &archive) const { - if constexpr(sizeof...(Component) == 1u) { - const auto view = reg->template view(); - (component(archive, view.rbegin(), view.rend()), ...); - return *this; - } else { - (component(archive), ...); - return *this; - } + [[deprecated("use .get(archive) instead")]] const basic_snapshot &component(Archive &archive) const { + return (get(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. @@ -36432,9 +39513,8 @@ public: * @return An object of this type to continue creating the snapshot. */ template - const basic_snapshot &component(Archive &archive, It first, It last) const { - component(archive, first, last, std::index_sequence_for{}); - return *this; + [[deprecated("use .get(archive, first, last) instead")]] const basic_snapshot &component(Archive &archive, It first, It last) const { + return (get(archive, first, last), ...); } private: @@ -36453,33 +39533,8 @@ private: */ template class basic_snapshot_loader { - using entity_traits = entt_traits; - - template - void assign(Archive &archive) const { - typename entity_traits::entity_type length{}; - entity_type entt; - - archive(length); - - if constexpr(ignore_as_empty_v) { - 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(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(entt, std::move(instance)); - } - } - } + static_assert(!std::is_const_v, "Non-const registry type required"); + using traits_type = typename Registry::traits_type; public: /*! Basic registry type. */ @@ -36494,7 +39549,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. */ @@ -36504,48 +39561,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 - const basic_snapshot_loader &entities(Archive &archive) const { - typename entity_traits::entity_type length{}; + template + basic_snapshot_loader &get([[maybe_unused]] Archive &archive, const id_type id = type_hash::value()) { + auto &storage = reg->template storage(id); + typename traits_type::entity_type length{}; archive(length); - std::vector all(length); - for(std::size_t pos{}; pos < length; ++pos) { - archive(all[pos]); + if constexpr(std::is_same_v) { + 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 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::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 + [[deprecated("use .get(archive) instead")]] basic_snapshot_loader &entities(Archive &archive) { + return get(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 - const basic_snapshot_loader &component(Archive &archive) const { - (assign(archive), ...); - return *this; + [[deprecated("use .get(archive) instead")]] basic_snapshot_loader &component(Archive &archive) { + return (get(archive), ...); } /** @@ -36554,17 +39643,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.
- * 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; } @@ -36582,7 +39666,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.
- * 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. * @@ -36590,29 +39674,16 @@ private: */ template class basic_continuous_loader { - using entity_traits = entt_traits; - - 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, "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())); } } @@ -36661,42 +39732,6 @@ class basic_continuous_loader { } } - template - void remove_if_exists() { - for(auto &&ref: remloc) { - const auto local = ref.second.first; - - if(reg->valid(local)) { - reg->template remove(local); - } - } - } - - template - 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) { - while(length--) { - archive(entt); - restore(entt); - reg->template emplace_or_replace(map(entt)); - } - } else { - Component instance; - - while(length--) { - archive(entt, instance); - (update(instance, member), ...); - restore(entt); - reg->template emplace_or_replace(map(entt), std::move(instance)); - } - } - } - public: /*! Basic registry type. */ using registry_type = Registry; @@ -36708,7 +39743,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; @@ -36717,90 +39753,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.
+ * 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 + basic_continuous_loader &get([[maybe_unused]] Archive &archive, const id_type id = type_hash::value()) { + auto &storage = reg->template storage(id); + typename traits_type::entity_type length{}; + entity_type entt{null}; + + archive(length); + + if constexpr(std::is_same_v) { + 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::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 - 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(archive) instead")]] basic_continuous_loader &entities(Archive &archive) { + return get(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.
- * 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.
+ * 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 - basic_continuous_loader &component(Archive &archive, Member Other::*...member) { - (remove_if_exists(), ...); - (assign(archive, member...), ...); + template + [[deprecated("use .component(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::traits_type::page_size == 0u) { + storage.emplace(map(entt)); + } else { + typename std::remove_reference_t::value_type elem{}; + archive(elem); + (update(elem, member), ...); + storage.emplace(map(entt), std::move(elem)); + } + } + } + }(reg->template storage()), + ...); + 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(archive) instead")]] basic_continuous_loader &shrink() { return *this; } @@ -36810,17 +39898,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.
- * 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; } @@ -36830,7 +39913,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; } /** @@ -36839,18 +39923,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> remloc; + dense_map> remloc; registry_type *reg; }; @@ -36956,6 +40037,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; } @@ -36965,38 +40050,38 @@ private: difference_type offset; }; -template -[[nodiscard]] constexpr std::ptrdiff_t operator-(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr std::ptrdiff_t operator-(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { return rhs.index() - lhs.index(); } -template -[[nodiscard]] constexpr bool operator==(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { return lhs.index() == rhs.index(); } -template -[[nodiscard]] constexpr bool operator!=(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { return !(lhs == rhs); } -template -[[nodiscard]] constexpr bool operator<(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { return lhs.index() > rhs.index(); } -template -[[nodiscard]] constexpr bool operator>(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { - return lhs.index() < rhs.index(); +template +[[nodiscard]] constexpr bool operator>(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { + return rhs < lhs; } -template -[[nodiscard]] constexpr bool operator<=(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<=(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { return !(lhs > rhs); } -template -[[nodiscard]] constexpr bool operator>=(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>=(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { return !(lhs < rhs); } @@ -37007,14 +40092,6 @@ template * @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. * @@ -37022,10 +40099,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.
- * 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. @@ -37035,7 +40108,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 @@ -37044,23 +40117,26 @@ class basic_sparse_set { static_assert(std::is_same_v, "Invalid value type"); using sparse_container_type = std::vector>; using packed_container_type = std::vector; - using entity_traits = entt_traits; [[nodiscard]] auto sparse_ptr(const Entity entt) const { - const auto pos = static_cast(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(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(entity_traits::to_entity(entt)); - return sparse[pos / entity_traits::page_size][fast_mod(pos, entity_traits::page_size)]; + const auto pos = static_cast(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(entity_traits::to_entity(entt)); - const auto page = pos / entity_traits::page_size; + const auto pos = static_cast(traits_type::to_entity(entt)); + const auto page = pos / traits_type::page_size; if(!(page < sparse.size())) { sparse.resize(page + 1u, nullptr); @@ -37068,11 +40144,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; } @@ -37082,8 +40158,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; } } @@ -37094,13 +40170,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; + /** + * @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(lhs); + const auto other = static_cast(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. @@ -37108,8 +40199,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(entt)] = packed.back(); // unnecessary but it helps to detect nasty bugs ENTT_ASSERT((packed.back() = null, true), ""); @@ -37124,8 +40215,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(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(entt)] = std::exchange(free_list, traits_type::combine(entt, tombstone)); } protected: @@ -37146,6 +40237,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. @@ -37157,25 +40265,27 @@ protected: if(auto &elem = assure_at_least(entt); free_list == null || force_back) { packed.push_back(entt); - elem = entity_traits::combine(static_cast(packed.size() - 1u), entity_traits::to_integral(entt)); + elem = traits_type::combine(static_cast(packed.size() - 1u), traits_type::to_integral(entt)); return begin(); } else { - const auto pos = static_cast(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(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; /*! @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. */ @@ -37185,7 +40295,7 @@ public: /*! @brief Reverse iterator type. */ using reverse_iterator = std::reverse_iterator; /*! @brief Constant reverse iterator type. */ - using const_reverse_iterator = reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; /*! @brief Default constructor. */ basic_sparse_set() @@ -37209,14 +40319,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} {} @@ -37333,7 +40443,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; } /** @@ -37358,6 +40468,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. @@ -37369,8 +40487,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. @@ -37387,11 +40504,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. */ @@ -37407,9 +40519,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. @@ -37425,11 +40536,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. */ @@ -37449,7 +40555,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(); } /** @@ -37459,9 +40565,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); } /** @@ -37472,8 +40578,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; } /** @@ -37488,7 +40594,7 @@ public: */ [[nodiscard]] size_type index(const entity_type entt) const noexcept { ENTT_ASSERT(contains(entt), "Set does not contain entity"); - return static_cast(entity_traits::to_entity(sparse_ref(entt))); + return static_cast(traits_type::to_entity(sparse_ref(entt))); } /** @@ -37520,13 +40626,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(std::as_const(*this).get(entt)); + /*! @copydoc value */ + [[nodiscard]] void *value(const entity_type entt) noexcept { + return const_cast(std::as_const(*this).value(entt)); } /** @@ -37537,28 +40643,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(entity_traits::to_entity(entity))] = entt; + iterator push(const entity_type entt, const void *elem = nullptr) { + return try_emplace(entt, false, elem); } /** @@ -37575,7 +40665,7 @@ public: * success, the `end()` iterator otherwise. */ template - iterator insert(It first, It last) { + iterator push(It first, It last) { for(auto it = first; it != last; ++it) { try_emplace(*it, true); } @@ -37583,6 +40673,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(traits_type::to_entity(entity))] = entt; + return traits_type::to_version(entt); + } + /** * @brief Erases an entity from a sparse set. * @@ -37593,7 +40701,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); } @@ -37637,29 +40745,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) { + 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(to); + sparse_ref(packed[to]) = traits_type::combine(entity, traits_type::to_integral(packed[to])); - const auto entity = static_cast(to); - sparse_ref(packed[to]) = entity_traits::combine(entity, entity_traits::to_integral(packed[to])); - *it = entity_traits::combine(static_cast(from), entity_traits::reserved); + *it = traits_type::combine(static_cast(from), tombstone); for(; from && packed[from - 1u] == tombstone; --from) {} } } @@ -37682,21 +40806,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(from), static_cast(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); } /** @@ -37744,9 +40859,9 @@ public: const auto idx = index(packed[next]); const auto entt = packed[curr]; - swap_at(next, idx); - const auto entity = static_cast(curr); - sparse_ref(entt) = entity_traits::combine(entity, entity_traits::to_integral(packed[curr])); + swap_or_move(next, idx); + const auto entity = static_cast(curr); + sparse_ref(entt) = traits_type::combine(entity, traits_type::to_integral(packed[curr])); curr = std::exchange(next, idx); } } @@ -37774,48 +40889,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.
- * 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`.
+ * 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(); } @@ -37856,8 +40958,6 @@ private: #include // #include "../config/config.h" -// #include "../core/compressed_pair.hpp" - // #include "../core/iterator.hpp" // #include "../core/memory.hpp" @@ -37872,8 +40972,6 @@ private: // #include "sparse_set.hpp" -// #include "storage_mixin.hpp" - namespace entt { @@ -37884,13 +40982,12 @@ namespace entt { namespace internal { -template +template class storage_iterator final { - friend storage_iterator; + friend storage_iterator; using container_type = std::remove_const_t; using alloc_traits = std::allocator_traits; - using comp_traits = component_traits>; using iterator_traits = std::iterator_traits, @@ -37907,12 +41004,12 @@ public: constexpr storage_iterator() noexcept = default; constexpr storage_iterator(Container *ref, const difference_type idx) noexcept - : packed{ref}, + : payload{ref}, offset{idx} {} template, typename = std::enable_if_t> - constexpr storage_iterator(const storage_iterator> &other) noexcept - : storage_iterator{other.packed, other.offset} {} + constexpr storage_iterator(const storage_iterator, Size> &other) noexcept + : storage_iterator{other.payload, other.offset} {} constexpr storage_iterator &operator++() noexcept { return --offset, *this; @@ -37952,12 +41049,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 { @@ -37969,42 +41066,42 @@ public: } private: - Container *packed; + Container *payload; difference_type offset; }; -template -[[nodiscard]] constexpr std::ptrdiff_t operator-(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr std::ptrdiff_t operator-(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { return rhs.index() - lhs.index(); } -template -[[nodiscard]] constexpr bool operator==(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { return lhs.index() == rhs.index(); } -template -[[nodiscard]] constexpr bool operator!=(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { return !(lhs == rhs); } -template -[[nodiscard]] constexpr bool operator<(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { return lhs.index() > rhs.index(); } -template -[[nodiscard]] constexpr bool operator>(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { - return lhs.index() < rhs.index(); +template +[[nodiscard]] constexpr bool operator>(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { + return rhs < lhs; } -template -[[nodiscard]] constexpr bool operator<=(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<=(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { return !(lhs > rhs); } -template -[[nodiscard]] constexpr bool operator>=(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>=(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { return !(lhs < rhs); } @@ -38014,10 +41111,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()), std::forward_as_tuple(*std::declval()...))); using pointer = input_iterator_pointer; using reference = value_type; - using difference_type = std::ptrdiff_t; using iterator_category = std::input_iterator_tag; constexpr extended_storage_iterator() @@ -38047,20 +41145,24 @@ public: return {*std::get(it), *std::get(it)...}; } - template - friend constexpr bool operator==(const extended_storage_iterator &, const extended_storage_iterator &) noexcept; + [[nodiscard]] constexpr iterator_type base() const noexcept { + return std::get(it); + } + + template + friend constexpr bool operator==(const extended_storage_iterator &, const extended_storage_iterator &) noexcept; private: std::tuple it; }; -template -[[nodiscard]] constexpr bool operator==(const extended_storage_iterator &lhs, const extended_storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const extended_storage_iterator &lhs, const extended_storage_iterator &rhs) noexcept { return std::get<0>(lhs.it) == std::get<0>(rhs.it); } -template -[[nodiscard]] constexpr bool operator!=(const extended_storage_iterator &lhs, const extended_storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const extended_storage_iterator &lhs, const extended_storage_iterator &rhs) noexcept { return !(lhs == rhs); } @@ -38083,43 +41185,43 @@ template * 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 class basic_storage: public basic_sparse_set::template rebind_alloc> { using alloc_traits = std::allocator_traits; static_assert(std::is_same_v, "Invalid value type"); - using underlying_type = basic_sparse_set>; using container_type = std::vector>; - using comp_traits = component_traits; + using underlying_type = basic_sparse_set>; + using underlying_iterator = typename underlying_type::basic_iterator; static constexpr bool is_pinned_type_v = !(std::is_move_constructible_v && std::is_move_assignable_v); [[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 @@ -38128,7 +41230,7 @@ class basic_storage: public basic_sparse_set(it.index())); - entt::uninitialized_construct_using_allocator(to_address(elem), packed.second(), std::forward(args)...); + entt::uninitialized_construct_using_allocator(to_address(elem), get_allocator(), std::forward(args)...); } ENTT_CATCH { base_type::pop(it, it + 1u); @@ -38139,25 +41241,24 @@ class basic_storage: public basic_sparse_set(first.index())))); + } + } else { + base_type::swap_and_pop(first); + alloc_traits::destroy(allocator, std::addressof(element_at(static_cast(first.index())))); + } + } + } + /** * @brief Assigns an entity to a storage. * @param entt A valid identifier. @@ -38220,7 +41335,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) { return emplace_element(entt, force_back, *static_cast(value)); @@ -38239,22 +41354,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; /*! @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::const_pointer; /*! @brief Random access iterator type. */ - using iterator = internal::storage_iterator; + using iterator = internal::storage_iterator; /*! @brief Constant random access iterator type. */ - using const_iterator = internal::storage_iterator; + using const_iterator = internal::storage_iterator; /*! @brief Reverse iterator type. */ using reverse_iterator = std::reverse_iterator; /*! @brief Constant reverse iterator type. */ @@ -38263,6 +41380,10 @@ public: using iterable = iterable_adaptor>; /*! @brief Constant extended iterable storage proxy. */ using const_iterable = iterable_adaptor>; + /*! @brief Extended reverse iterable storage proxy. */ + using reverse_iterable = iterable_adaptor>; + /*! @brief Constant extended reverse iterable storage proxy. */ + using const_reverse_iterable = iterable_adaptor>; /*! @brief Default constructor. */ basic_storage() @@ -38273,8 +41394,8 @@ public: * @param allocator The allocator to use. */ explicit basic_storage(const allocator_type &allocator) - : base_type{type_id(), deletion_policy{comp_traits::in_place_delete}, allocator}, - packed{container_type{allocator}, allocator} {} + : base_type{type_id(), deletion_policy{traits_type::in_place_delete}, allocator}, + payload{allocator} {} /** * @brief Move constructor. @@ -38282,7 +41403,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. @@ -38291,8 +41412,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. */ @@ -38306,12 +41427,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; } @@ -38321,9 +41441,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); } /** @@ -38331,7 +41450,7 @@ public: * @return The associated allocator. */ [[nodiscard]] constexpr allocator_type get_allocator() const noexcept { - return allocator_type{packed.second()}; + return payload.get_allocator(); } /** @@ -38355,7 +41474,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. */ @@ -38369,25 +41488,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(base_type::size()); - return const_iterator{&packed.first(), pos}; + return const_iterator{&payload, pos}; } /*! @copydoc cbegin */ @@ -38398,21 +41516,16 @@ public: /*! @copydoc begin */ [[nodiscard]] iterator begin() noexcept { const auto pos = static_cast(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 */ @@ -38422,15 +41535,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. */ @@ -38450,11 +41561,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. */ @@ -38519,7 +41625,7 @@ public: */ template value_type &emplace(const entity_type entt, Args &&...args) { - if constexpr(std::is_aggregate_v) { + if constexpr(std::is_aggregate_v && (sizeof...(Args) != 0u || !std::is_default_constructible_v)) { const auto it = emplace_element(entt, false, Type{std::forward(args)...}); return element_at(static_cast(it.index())); } else { @@ -38555,12 +41661,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 - 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(); } /** @@ -38574,12 +41683,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::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(); } /** @@ -38599,34 +41711,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 packed; + container_type payload; }; /*! @copydoc basic_storage */ template -class basic_storage>> +class basic_storage::page_size == 0u>> : public basic_sparse_set::template rebind_alloc> { using alloc_traits = std::allocator_traits; static_assert(std::is_same_v, "Invalid value type"); - using underlying_type = basic_sparse_set>; - using comp_traits = component_traits; public: /*! @brief Base type. */ - using base_type = underlying_type; - /*! @brief Allocator type. */ - using allocator_type = Allocator; + using base_type = basic_sparse_set>; /*! @brief Type of the objects assigned to entities. */ using value_type = Type; + /*! @brief Component traits. */ + using traits_type = component_traits; /*! @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>; /*! @brief Constant extended iterable storage proxy. */ using const_iterable = iterable_adaptor>; + /*! @brief Extended reverse iterable storage proxy. */ + using reverse_iterable = iterable_adaptor>; + /*! @brief Constant extended reverse iterable storage proxy. */ + using const_reverse_iterable = iterable_adaptor>; /*! @brief Default constructor. */ basic_storage() @@ -38637,7 +41769,7 @@ public: * @param allocator The allocator to use. */ explicit basic_storage(const allocator_type &allocator) - : base_type{type_id(), deletion_policy{comp_traits::in_place_delete}, allocator} {} + : base_type{type_id(), deletion_policy{traits_type::in_place_delete}, allocator} {} /** * @brief Move constructor. @@ -38752,248 +41884,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()}}; + } }; -} // namespace entt - -#endif - -// #include "entity/storage_mixin.hpp" -#ifndef ENTT_ENTITY_SIGH_STORAGE_MIXIN_HPP -#define ENTT_ENTITY_SIGH_STORAGE_MIXIN_HPP - -#include -// #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); - * @endcode - * - * This applies to all signals made available. - * - * @tparam Type The type of the underlying storage. + * @brief Swap-only entity storage specialization. + * @tparam Entity A valid entity type. + * @tparam Allocator Type of allocator used to manage memory and elements. */ -template -class sigh_storage_mixin final: public Type { - using basic_registry_type = basic_registry; - using sigh_type = sigh; - using basic_iterator = typename Type::basic_iterator; +template +class basic_storage + : public basic_sparse_set { + using alloc_traits = std::allocator_traits; + static_assert(std::is_same_v, "Invalid value type"); + using underlying_type = basic_sparse_set>; + using underlying_iterator = typename underlying_type::basic_iterator; + using local_traits_type = entt_traits; - void pop(basic_iterator first, basic_iterator last) override { - ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); + 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(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) { - const auto entt = *first; - destruction.publish(*owner, entt); - const auto it = Type::find(entt); - Type::pop(it, it + 1u); + 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); + } + } } } - 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); + /*! @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 Allocator type. */ - using allocator_type = typename Type::allocator_type; + /*! @brief Base type. */ + using base_type = basic_sparse_set; + /*! @brief Type of the objects assigned to entities. */ + using value_type = Entity; + /*! @brief Component traits. */ + using traits_type = component_traits; /*! @brief Underlying entity identifier. */ - using entity_type = typename Type::entity_type; - /*! @brief Expected registry type. */ - using registry_type = basic_registry_type; + 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>; + /*! @brief Constant extended iterable storage proxy. */ + using const_iterable = iterable_adaptor>; + /*! @brief Extended reverse iterable storage proxy. */ + using reverse_iterable = iterable_adaptor>; + /*! @brief Constant extended reverse iterable storage proxy. */ + using const_reverse_iterable = iterable_adaptor>; /*! @brief Default constructor. */ - sigh_storage_mixin() - : sigh_storage_mixin{allocator_type{}} {} + basic_storage() + : basic_storage{allocator_type{}} { + } /** - * @brief Constructs an empty storage with a given allocator. + * @brief Constructs an empty container 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} {} + explicit basic_storage(const allocator_type &allocator) + : base_type{type_id(), deletion_policy::swap_and_pop, allocator}, + length{} {} /** * @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)} {} + 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. */ - 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} {} + 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. */ - 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); + 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(sigh_storage_mixin &other) { + void swap(basic_storage &other) { using std::swap; - Type::swap(other); - swap(owner, other.owner); - swap(construction, other.construction); - swap(destruction, other.destruction); - swap(update, other.update); + base_type::swap(other); + swap(length, other.length); } /** - * @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.
- * Listeners are invoked after the object has been assigned to the entity. - * - * @sa sink - * - * @return A temporary sink object. + * @brief Creates a new identifier or recycles a destroyed one. + * @return A valid identifier. */ - [[nodiscard]] auto on_construct() noexcept { - return sink{construction}; + entity_type emplace() { + if(length == base_type::size()) { + return *base_type::try_emplace(entity_at(length++), true); + } + + return base_type::operator[](length++); } /** - * @brief Returns a sink object. + * @brief Creates a new identifier or recycles a destroyed one. * - * The sink returned by this function can be used to receive notifications - * whenever an instance is explicitly updated.
- * Listeners are invoked after the object has been updated. + * If the requested identifier isn't in use, the suggested one is used. + * Otherwise, a new identifier is returned. * - * @sa sink - * - * @return A temporary sink object. + * @param hint Required identifier. + * @return A valid identifier. */ - [[nodiscard]] auto on_update() noexcept { - return sink{update}; + 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(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 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.
- * 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 - decltype(auto) emplace(const entity_type entt, Args &&...args) { - ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); - Type::emplace(entt, std::forward(args)...); - construction.publish(*owner, entt); - return this->get(entt); - } - - /** - * @brief Patches the given instance for an entity. + * @brief Updates a given identifier. * @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 - decltype(auto) patch(const entity_type entt, Func &&...func) { - ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); - Type::patch(entt, std::forward(func)...); - update.publish(*owner, entt); - return this->get(entt); + void patch([[maybe_unused]] const entity_type entt, Func &&...func) { + ENTT_ASSERT(base_type::contains(entt), "Storage does not contain entity"); + (std::forward(func)(), ...); } /** - * @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. + * @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 - void insert(It first, It last, Args &&...args) { - ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); - Type::insert(first, last, std::forward(args)...); + template + 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(auto it = construction.empty() ? last : first; it != last; ++it) { - construction.publish(*owner, *it); + for(; first != last; ++first) { + *first = *base_type::try_emplace(entity_at(length++), true); } } /** - * @brief Forwards variables to derived classes, if any. - * @param value A variable wrapped in an opaque container. + * @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. */ - void bind(any value) noexcept final { - auto *reg = any_cast(&value); - owner = reg ? reg : owner; - Type::bind(std::move(value)); + template + 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(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: - basic_registry_type *owner; - sigh_type construction; - sigh_type destruction; - sigh_type update; + size_type length; }; } // namespace entt @@ -39015,16 +42224,10 @@ private: // #include "../core/type_traits.hpp" -// #include "component.hpp" - // #include "entity.hpp" // #include "fwd.hpp" -// #include "sparse_set.hpp" - -// #include "storage.hpp" - namespace entt { @@ -39035,6 +42238,24 @@ namespace entt { namespace internal { +template +[[nodiscard]] auto filter_as_tuple(const std::array &filter) noexcept { + return std::apply([](const auto *...curr) { return std::make_tuple(static_cast(const_cast *>(curr))...); }, filter); +} + +template +[[nodiscard]] auto none_of(const std::array &filter, const typename Type::entity_type entt) noexcept { + return std::apply([entt](const auto *...curr) { return (!(curr && curr->contains(entt)) && ...); }, filter); +} + +template +[[nodiscard]] auto view_pack(const std::tuple value, const std::tuple excl, std::index_sequence) { + const auto pools = std::tuple_cat(value, excl); + basic_view, exclude_t> elem{}; + (((std::get(pools) != nullptr) ? elem.template storage(*std::get(pools)) : void()), ...); + return elem; +} + template class view_iterator final { using iterator_type = typename Type::const_iterator; @@ -39042,7 +42263,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: @@ -39058,11 +42279,11 @@ public: pools{}, filter{} {} - view_iterator(iterator_type curr, iterator_type to, std::array all_of, std::array none_of) noexcept + view_iterator(iterator_type curr, iterator_type to, std::array value, std::array excl) noexcept : it{curr}, last{to}, - pools{all_of}, - filter{none_of} { + pools{value}, + filter{excl} { while(it != last && !valid()) { ++it; } @@ -39108,6 +42329,7 @@ template template 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()), std::declval().get_as_tuple({})...)); using pointer = input_iterator_pointer; @@ -39118,9 +42340,9 @@ struct extended_view_iterator final { : it{}, pools{} {} - extended_view_iterator(It from, std::tuple storage) + extended_view_iterator(It from, std::tuple value) : it{from}, - pools{storage} {} + pools{value} {} extended_view_iterator &operator++() noexcept { return ++it, *this; @@ -39139,6 +42361,10 @@ struct extended_view_iterator final { return operator*(); } + [[nodiscard]] constexpr iterator_type base() const noexcept { + return it; + } + template friend bool constexpr operator==(const extended_view_iterator &, const extended_view_iterator &) noexcept; @@ -39190,31 +42416,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 class basic_view, exclude_t> { - using underlying_type = std::common_type_t; - using basic_common_type = std::common_type_t; + static constexpr auto offset = sizeof...(Get); + using base_type = std::common_type_t; + using underlying_type = typename base_type::entity_type; template friend class basic_view; template - static constexpr std::size_t index_of = type_list_index_v, type_list>; + static constexpr std::size_t index_of = type_list_index_v, type_list>; [[nodiscard]] auto opaque_check_set() const noexcept { - std::array other{}; + std::array 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{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 @@ -39222,18 +42450,14 @@ class basic_view, exclude_t> { if constexpr(Curr == Other) { return std::forward_as_tuple(std::get(curr)...); } else { - return storage().get_as_tuple(std::get<0>(curr)); + return std::get(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 void each(Func &func, std::index_sequence) const { - for(const auto curr: storage().each()) { - if(const auto entt = std::get<0>(curr); ((sizeof...(Get) != 1u) || (entt != tombstone)) && ((Curr == Index || storage().contains(entt)) && ...) && !reject(entt)) { + for(const auto curr: std::get(pools)->each()) { + if(const auto entt = std::get<0>(curr); ((sizeof...(Get) != 1u) || (entt != tombstone)) && ((Curr == Index || std::get(pools)->contains(entt)) && ...) && internal::none_of(filter, entt)) { if constexpr(is_applicable_v{}, std::declval().get({})))>) { std::apply(func, std::tuple_cat(std::make_tuple(entt), dispatch_get(curr)...)); } else { @@ -39245,7 +42469,7 @@ class basic_view, exclude_t> { template void pick_and_each(Func &func, std::index_sequence seq) const { - ((&storage() == view ? each(func, seq) : void()), ...); + ((std::get(pools) == view ? each(func, seq) : void()), ...); } public: @@ -39254,9 +42478,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; + using iterator = internal::view_iterator; /*! @brief Iterable view type. */ using iterable = iterable_adaptor>; @@ -39269,12 +42493,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. @@ -39282,66 +42508,91 @@ public: * @param excl The storage for the types used to filter the view. */ basic_view(std::tuple value, std::tuple 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(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 - [[nodiscard]] basic_view use() const noexcept { - return use>(); + void use() noexcept { + use>(); } /** - * @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 - [[nodiscard]] basic_view use() const noexcept { - basic_view other{*this}; - other.view = &storage(); - return other; + void use() noexcept { + if(view) { + view = std::get(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 - [[nodiscard]] decltype(auto) storage() const noexcept { + [[nodiscard]] auto *storage() const noexcept { return storage>(); } /** - * @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 - [[nodiscard]] decltype(auto) storage() const noexcept { - return *std::get(pools); + [[nodiscard]] auto *storage() const noexcept { + if constexpr(Index < offset) { + return std::get(pools); + } else { + return std::get(internal::filter_as_tuple(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 + void storage(Type &elem) noexcept { + storage>(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 + void storage(Type &elem) noexcept { + if constexpr(Index < offset) { + std::get(pools) = &elem; + refresh(); + } else { + std::get(filter) = &elem; + } } /** @@ -39349,32 +42600,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{}; } /** @@ -39393,9 +42638,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; } /** @@ -39405,7 +42654,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(); } /** @@ -39418,11 +42667,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); } /** @@ -39431,8 +42681,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); } /** @@ -39442,39 +42691,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 + template [[nodiscard]] decltype(auto) get(const entity_type entt) const { - if constexpr(sizeof...(Type) == 0) { + return get, index_of...>(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 + [[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>().get(entt), ...); + } else if constexpr(sizeof...(Index) == 1) { + return (std::get(pools)->get(entt), ...); } else { - return std::tuple_cat(storage>().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 - [[nodiscard]] decltype(auto) get(const entity_type entt) const { - if constexpr(sizeof...(Other) == 0) { - return storage().get(entt); - } else { - return std::tuple_cat(storage().get_as_tuple(entt), storage().get_as_tuple(entt)...); + return std::tuple_cat(std::get(pools)->get_as_tuple(entt)...); } } @@ -39498,7 +42741,7 @@ public: */ template void each(Func func) const { - pick_and_each(func, std::index_sequence_for{}); + view ? pick_and_each(func, std::index_sequence_for{}) : void(); } /** @@ -39523,14 +42766,16 @@ public: */ template [[nodiscard]] auto operator|(const basic_view, exclude_t> &other) const noexcept { - return std::make_from_tuple, exclude_t>>( - 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(filter), internal::filter_as_tuple(other.filter)), + std::index_sequence_for{}); } private: std::tuple pools; - std::tuple filter; - const base_type *view; + std::array filter; + const common_type *view; }; /** @@ -39549,13 +42794,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 -class basic_view, exclude_t<>, std::void_t::in_place_delete>>> { +class basic_view, exclude_t<>, std::void_t>> { template friend class basic_view; @@ -39565,62 +42810,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().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 ref, std::tuple<> = {}) noexcept - : pools{&std::get<0>(ref)}, - filter{} {} + basic_view(std::tuple 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 - [[nodiscard]] decltype(auto) storage() const noexcept { + [[nodiscard]] auto *storage() const noexcept { static_assert(std::is_same_v, 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 - [[nodiscard]] decltype(auto) storage() const noexcept { - return *std::get(pools); + [[nodiscard]] auto *storage() const noexcept { + return std::get(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 + void storage(Get &elem) noexcept { + view = std::get(pools) = &elem; } /** @@ -39628,7 +42892,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{}; } /** @@ -39636,59 +42900,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{}; } /** @@ -39697,7 +42949,7 @@ public: * otherwise. */ [[nodiscard]] entity_type front() const noexcept { - return empty() ? null : *begin(); + return (!view || view->empty()) ? null : *view->begin(); } /** @@ -39706,7 +42958,7 @@ public: * otherwise. */ [[nodiscard]] entity_type back() const noexcept { - return empty() ? null : *rbegin(); + return (!view || view->empty()) ? null : *view->rbegin(); } /** @@ -39716,7 +42968,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{}; } /** @@ -39734,15 +42986,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); } /** @@ -39751,7 +43003,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); } /** @@ -39761,24 +43013,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 + template [[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, typename Get::value_type> && ...), "Invalid component type"); - return storage().get(entt); - } + static_assert(std::is_same_v, typename Get::value_type>, "Invalid component type"); + return get<0>(entt); } /*! @copydoc get */ - template + template [[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(pools)->get(entt); + } } /** @@ -39805,17 +43057,19 @@ public: */ template void each(Func func) const { - if constexpr(is_applicable_v) { - for(const auto pack: each()) { - std::apply(func, pack); - } - } else if constexpr(ignore_as_empty_v) { - for(size_type pos{}, last = size(); pos < last; ++pos) { - func(); - } - } else { - for(auto &&component: storage()) { - func(component); + if(view) { + if constexpr(is_applicable_v) { + 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); + } } } } @@ -39830,7 +43084,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{}; } /** @@ -39842,13 +43096,16 @@ public: */ template [[nodiscard]] auto operator|(const basic_view, exclude_t> &other) const noexcept { - return std::make_from_tuple, exclude_t>>( - 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(other.filter), + std::index_sequence_for{}); } private: std::tuple pools; - std::tuple<> filter; + std::array filter; + const common_type *view; }; /** @@ -39900,8 +43157,8 @@ basic_view(std::tuple, std::tuple = {}) -> basic_view, std::tuple = {}) -> basic_view 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}}; } @@ -40601,7 +43862,7 @@ public: [[nodiscard]] iterable_adaptor 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}}; } @@ -40786,8 +44047,8 @@ void dot(std::ostream &out, const Graph &graph) { #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) \ @@ -40843,6 +44104,8 @@ void dot(std::ostream &out, const Graph &graph) { # 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 @@ -40887,6 +44150,7 @@ void dot(std::ostream &out, const Graph &graph) { #include #include +#include #include #include // #include "../config/config.h" @@ -40908,8 +44172,8 @@ void dot(std::ostream &out, const Graph &graph) { #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) \ @@ -40965,6 +44229,8 @@ void dot(std::ostream &out, const Graph &graph) { # 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 @@ -41066,7 +44332,6 @@ using type_identity_t = typename type_identity::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 struct size_of: std::integral_constant {}; @@ -41308,7 +44573,8 @@ struct type_list_contains; * @tparam Other Type to look for. */ template -struct type_list_contains, Other>: std::disjunction...> {}; +struct type_list_contains, Other> + : std::bool_constant<(std::is_same_v || ...)> {}; /** * @brief Helper variable template. @@ -41396,10 +44662,20 @@ struct value_list_element> */ template struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = typename value_list_element::type; + /** * @brief Helper type. * @tparam Index Index of the value to return. @@ -41408,6 +44684,58 @@ struct value_list_element<0u, value_list> { template inline constexpr auto value_list_element_v = value_list_element::value; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +struct value_list_index> { + static_assert(value_list_index>::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 +struct value_list_index> { + /*! @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 +inline constexpr std::size_t value_list_index_v = value_list_index::value; + /** * @brief Concatenates multiple value lists. * @tparam Value Values provided by the first value list. @@ -41459,6 +44787,89 @@ struct value_list_cat> { template using value_list_cat_t = typename value_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = std::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = typename value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : std::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +class value_list_diff, value_list> { + using v141_toolset_workaround = value_list; + +public: + /*! @brief A value list that is the difference between the two value lists. */ + using type = value_list_cat_t, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = typename value_list_diff::type; + /*! @brief Same as std::is_invocable, but with tuples. */ template struct is_applicable: std::false_type {}; @@ -41579,7 +44990,7 @@ inline constexpr bool is_iterator_v = is_iterator::value; */ template struct is_ebco_eligible - : std::conjunction, std::negation>> {}; + : std::bool_constant && !std::is_final_v> {}; /** * @brief Helper variable template. @@ -41670,6 +45081,10 @@ template struct is_equality_comparable() == std::declval())>> : std::bool_constant(choice<2>)> {}; +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: std::false_type {}; + /** * @brief Helper variable template. * @tparam Type The type to test. @@ -41766,6 +45181,18 @@ using nth_argument_t = typename nth_argument::type; } // namespace entt +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::type_list_element> {}; + +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::value_list_element> {}; + #endif @@ -42349,7 +45776,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 struct allocation_deleter: private Allocator { @@ -42370,7 +45797,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) { - using alloc_traits = typename std::allocator_traits; + using alloc_traits = std::allocator_traits; alloc_traits::destroy(*this, to_address(ptr)); alloc_traits::deallocate(*this, ptr, 1u); } @@ -42537,6 +45964,7 @@ constexpr Type *uninitialized_construct_using_allocator(Type *value, const Alloc #include #include +#include #include #include // #include "../config/config.h" @@ -42591,7 +46019,6 @@ using type_identity_t = typename type_identity::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 struct size_of: std::integral_constant {}; @@ -42833,7 +46260,8 @@ struct type_list_contains; * @tparam Other Type to look for. */ template -struct type_list_contains, Other>: std::disjunction...> {}; +struct type_list_contains, Other> + : std::bool_constant<(std::is_same_v || ...)> {}; /** * @brief Helper variable template. @@ -42921,10 +46349,20 @@ struct value_list_element> */ template struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = typename value_list_element::type; + /** * @brief Helper type. * @tparam Index Index of the value to return. @@ -42933,6 +46371,58 @@ struct value_list_element<0u, value_list> { template inline constexpr auto value_list_element_v = value_list_element::value; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +struct value_list_index> { + static_assert(value_list_index>::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 +struct value_list_index> { + /*! @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 +inline constexpr std::size_t value_list_index_v = value_list_index::value; + /** * @brief Concatenates multiple value lists. * @tparam Value Values provided by the first value list. @@ -42984,6 +46474,89 @@ struct value_list_cat> { template using value_list_cat_t = typename value_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = std::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = typename value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : std::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +class value_list_diff, value_list> { + using v141_toolset_workaround = value_list; + +public: + /*! @brief A value list that is the difference between the two value lists. */ + using type = value_list_cat_t, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = typename value_list_diff::type; + /*! @brief Same as std::is_invocable, but with tuples. */ template struct is_applicable: std::false_type {}; @@ -43104,7 +46677,7 @@ inline constexpr bool is_iterator_v = is_iterator::value; */ template struct is_ebco_eligible - : std::conjunction, std::negation>> {}; + : std::bool_constant && !std::is_final_v> {}; /** * @brief Helper variable template. @@ -43195,6 +46768,10 @@ template struct is_equality_comparable() == std::declval())>> : std::bool_constant(choice<2>)> {}; +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: std::false_type {}; + /** * @brief Helper variable template. * @tparam Type The type to test. @@ -43291,6 +46868,18 @@ using nth_argument_t = typename nth_argument::type; } // namespace entt +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::type_list_element> {}; + +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::value_list_element> {}; + #endif // #include "fwd.hpp" @@ -43432,51 +47021,51 @@ public: return {it->element.first, it->element.second}; } - template - friend constexpr std::ptrdiff_t operator-(const dense_map_iterator &, const dense_map_iterator &) noexcept; + template + friend constexpr std::ptrdiff_t operator-(const dense_map_iterator &, const dense_map_iterator &) noexcept; - template - friend constexpr bool operator==(const dense_map_iterator &, const dense_map_iterator &) noexcept; + template + friend constexpr bool operator==(const dense_map_iterator &, const dense_map_iterator &) noexcept; - template - friend constexpr bool operator<(const dense_map_iterator &, const dense_map_iterator &) noexcept; + template + friend constexpr bool operator<(const dense_map_iterator &, const dense_map_iterator &) noexcept; private: It it; }; -template -[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return lhs.it - rhs.it; } -template -[[nodiscard]] constexpr bool operator==(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return lhs.it == rhs.it; } -template -[[nodiscard]] constexpr bool operator!=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return !(lhs == rhs); } -template -[[nodiscard]] constexpr bool operator<(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return lhs.it < rhs.it; } -template -[[nodiscard]] constexpr bool operator>(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return rhs < lhs; } -template -[[nodiscard]] constexpr bool operator<=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return !(lhs > rhs); } -template -[[nodiscard]] constexpr bool operator>=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return !(lhs < rhs); } @@ -43534,13 +47123,13 @@ private: std::size_t offset; }; -template -[[nodiscard]] constexpr bool operator==(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { return lhs.index() == rhs.index(); } -template -[[nodiscard]] constexpr bool operator!=(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { return !(lhs == rhs); } @@ -43570,7 +47159,7 @@ class dense_map { static constexpr std::size_t minimum_capacity = 8u; using node_type = internal::dense_map_node; - using alloc_traits = typename std::allocator_traits; + using alloc_traits = std::allocator_traits; static_assert(std::is_same_v>, "Invalid value type"); using sparse_container_type = std::vector>; using packed_container_type = std::vector>; @@ -43768,7 +47357,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. @@ -43789,11 +47377,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. */ @@ -44142,7 +47725,7 @@ public: } /*! @copydoc equal_range */ - template + template [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t>> equal_range(const Other &key) const { const auto it = find(key); @@ -44467,51 +48050,51 @@ public: return *operator->(); } - template - friend constexpr std::ptrdiff_t operator-(const dense_set_iterator &, const dense_set_iterator &) noexcept; + template + friend constexpr std::ptrdiff_t operator-(const dense_set_iterator &, const dense_set_iterator &) noexcept; - template - friend constexpr bool operator==(const dense_set_iterator &, const dense_set_iterator &) noexcept; + template + friend constexpr bool operator==(const dense_set_iterator &, const dense_set_iterator &) noexcept; - template - friend constexpr bool operator<(const dense_set_iterator &, const dense_set_iterator &) noexcept; + template + friend constexpr bool operator<(const dense_set_iterator &, const dense_set_iterator &) noexcept; private: It it; }; -template -[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return lhs.it - rhs.it; } -template -[[nodiscard]] constexpr bool operator==(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return lhs.it == rhs.it; } -template -[[nodiscard]] constexpr bool operator!=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return !(lhs == rhs); } -template -[[nodiscard]] constexpr bool operator<(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return lhs.it < rhs.it; } -template -[[nodiscard]] constexpr bool operator>(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return rhs < lhs; } -template -[[nodiscard]] constexpr bool operator<=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return !(lhs > rhs); } -template -[[nodiscard]] constexpr bool operator>=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return !(lhs < rhs); } @@ -44566,13 +48149,13 @@ private: std::size_t offset; }; -template -[[nodiscard]] constexpr bool operator==(const dense_set_local_iterator &lhs, const dense_set_local_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const dense_set_local_iterator &lhs, const dense_set_local_iterator &rhs) noexcept { return lhs.index() == rhs.index(); } -template -[[nodiscard]] constexpr bool operator!=(const dense_set_local_iterator &lhs, const dense_set_local_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const dense_set_local_iterator &lhs, const dense_set_local_iterator &rhs) noexcept { return !(lhs == rhs); } @@ -44781,7 +48364,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. @@ -44802,11 +48384,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. */ @@ -45062,7 +48639,7 @@ public: } /*! @copydoc equal_range */ - template + template [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t>> equal_range(const Other &value) const { const auto it = find(value); @@ -45279,6 +48856,7 @@ private: #include #include +#include #include #include // #include "../config/config.h" @@ -45354,7 +48932,6 @@ using type_identity_t = typename type_identity::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 struct size_of: std::integral_constant {}; @@ -45596,7 +49173,8 @@ struct type_list_contains; * @tparam Other Type to look for. */ template -struct type_list_contains, Other>: std::disjunction...> {}; +struct type_list_contains, Other> + : std::bool_constant<(std::is_same_v || ...)> {}; /** * @brief Helper variable template. @@ -45684,10 +49262,20 @@ struct value_list_element> */ template struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = typename value_list_element::type; + /** * @brief Helper type. * @tparam Index Index of the value to return. @@ -45696,6 +49284,58 @@ struct value_list_element<0u, value_list> { template inline constexpr auto value_list_element_v = value_list_element::value; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +struct value_list_index> { + static_assert(value_list_index>::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 +struct value_list_index> { + /*! @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 +inline constexpr std::size_t value_list_index_v = value_list_index::value; + /** * @brief Concatenates multiple value lists. * @tparam Value Values provided by the first value list. @@ -45747,6 +49387,89 @@ struct value_list_cat> { template using value_list_cat_t = typename value_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = std::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = typename value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : std::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +class value_list_diff, value_list> { + using v141_toolset_workaround = value_list; + +public: + /*! @brief A value list that is the difference between the two value lists. */ + using type = value_list_cat_t, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = typename value_list_diff::type; + /*! @brief Same as std::is_invocable, but with tuples. */ template struct is_applicable: std::false_type {}; @@ -45867,7 +49590,7 @@ inline constexpr bool is_iterator_v = is_iterator::value; */ template struct is_ebco_eligible - : std::conjunction, std::negation>> {}; + : std::bool_constant && !std::is_final_v> {}; /** * @brief Helper variable template. @@ -45958,6 +49681,10 @@ template struct is_equality_comparable() == std::declval())>> : std::bool_constant(choice<2>)> {}; +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: std::false_type {}; + /** * @brief Helper variable template. * @tparam Type The type to test. @@ -46054,6 +49781,18 @@ using nth_argument_t = typename nth_argument::type; } // namespace entt +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::type_list_element> {}; + +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::value_list_element> {}; + #endif @@ -46352,7 +50091,7 @@ struct identity { * @param value The actual argument. * @return The submitted value as-is. */ - template + template [[nodiscard]] constexpr Type &&operator()(Type &&value) const noexcept { return std::forward(value); } @@ -46385,7 +50124,7 @@ template * @brief Helper type for visitors. * @tparam Func Types of function objects. */ -template +template struct overloaded: Func... { using Func::operator()...; }; @@ -46394,14 +50133,14 @@ struct overloaded: Func... { * @brief Deduction guide. * @tparam Func Types of function objects. */ -template +template overloaded(Func...) -> overloaded; /** * @brief Basic implementation of a y-combinator. * @tparam Func Type of a potentially recursive function. */ -template +template struct y_combinator { /** * @brief Constructs a y-combinator from a given function. @@ -46416,13 +50155,13 @@ struct y_combinator { * @param args Parameters to use to invoke the underlying function. * @return Return value of the underlying function, if any. */ - template + template constexpr decltype(auto) operator()(Args &&...args) const noexcept(std::is_nothrow_invocable_v) { return func(*this, std::forward(args)...); } /*! @copydoc operator()() */ - template + template constexpr decltype(auto) operator()(Args &&...args) noexcept(std::is_nothrow_invocable_v) { return func(*this, std::forward(args)...); } @@ -46699,7 +50438,7 @@ public: [[nodiscard]] iterable_adaptor 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}}; } @@ -46711,7 +50450,7 @@ public: [[nodiscard]] iterable_adaptor 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}}; } @@ -46804,6 +50543,7 @@ class basic_flow { using task_container_type = dense_set, typename alloc_traits::template rebind_alloc>; using ro_rw_container_type = std::vector, typename alloc_traits::template rebind_alloc>>; using deps_container_type = dense_map, typename alloc_traits::template rebind_alloc>>; + using adjacency_matrix_type = adjacency_matrix>; void emplace(const id_type res, const bool is_rw) { ENTT_ASSERT(index.first() < vertices.size(), "Invalid node"); @@ -46815,6 +50555,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; @@ -46822,6 +50632,8 @@ public: using size_type = std::size_t; /*! @brief Iterable task list. */ using iterable = iterable_adaptor; + /*! @brief Adjacency matrix type. */ + using graph_type = adjacency_matrix_type; /*! @brief Default constructor. */ basic_flow() @@ -46896,9 +50708,10 @@ public: /*! @brief Clears the flow builder. */ void clear() noexcept { - index.first() = sync_on = {}; + index.first() = {}; vertices.clear(); deps.clear(); + sync_on = {}; } /** @@ -47017,72 +50830,12 @@ public: * @brief Generates a task graph for the current content. * @return The adjacency matrix of the task graph. */ - [[nodiscard]] adjacency_matrix graph() const { - const auto length = vertices.size(); - adjacency_matrix 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; } @@ -47123,8 +50876,8 @@ private: #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) \ @@ -47180,6 +50933,8 @@ private: # 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 @@ -47276,40 +51031,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 + template [[nodiscard]] static Service &value_or(Args &&...args) { - return service ? *service : emplace(std::forward(args)...); + return service ? *service : emplace(std::forward(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 + template static Service &emplace(Args &&...args) { - service = std::make_shared(std::forward(args)...); + service = std::make_shared(std::forward(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 - static Service &allocate_emplace(Allocator alloc, Args &&...args) { - service = std::allocate_shared(alloc, std::forward(args)...); + template + static Service &emplace(std::allocator_arg_t, Allocator alloc, Args &&...args) { + service = std::allocate_shared(alloc, std::forward(args)...); return *service; } @@ -47331,6 +51086,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> + static void reset(Type *elem, Deleter deleter = {}) { + service = std::shared_ptr{elem, std::move(deleter)}; + } + private: // std::shared_ptr because of its type erased allocator which is useful here inline static std::shared_ptr service{}; @@ -47424,8 +51191,8 @@ struct adl_meta_pointer_like { #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) \ @@ -47481,6 +51248,8 @@ struct adl_meta_pointer_like { # 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 @@ -47525,6 +51294,7 @@ struct adl_meta_pointer_like { #include #include +#include #include #include // #include "../config/config.h" @@ -47546,8 +51316,8 @@ struct adl_meta_pointer_like { #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) \ @@ -47603,6 +51373,8 @@ struct adl_meta_pointer_like { # 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 @@ -47704,7 +51476,6 @@ using type_identity_t = typename type_identity::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 struct size_of: std::integral_constant {}; @@ -47946,7 +51717,8 @@ struct type_list_contains; * @tparam Other Type to look for. */ template -struct type_list_contains, Other>: std::disjunction...> {}; +struct type_list_contains, Other> + : std::bool_constant<(std::is_same_v || ...)> {}; /** * @brief Helper variable template. @@ -48034,10 +51806,20 @@ struct value_list_element> */ template struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = typename value_list_element::type; + /** * @brief Helper type. * @tparam Index Index of the value to return. @@ -48046,6 +51828,58 @@ struct value_list_element<0u, value_list> { template inline constexpr auto value_list_element_v = value_list_element::value; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +struct value_list_index> { + static_assert(value_list_index>::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 +struct value_list_index> { + /*! @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 +inline constexpr std::size_t value_list_index_v = value_list_index::value; + /** * @brief Concatenates multiple value lists. * @tparam Value Values provided by the first value list. @@ -48097,6 +51931,89 @@ struct value_list_cat> { template using value_list_cat_t = typename value_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = std::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = typename value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : std::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +class value_list_diff, value_list> { + using v141_toolset_workaround = value_list; + +public: + /*! @brief A value list that is the difference between the two value lists. */ + using type = value_list_cat_t, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = typename value_list_diff::type; + /*! @brief Same as std::is_invocable, but with tuples. */ template struct is_applicable: std::false_type {}; @@ -48217,7 +52134,7 @@ inline constexpr bool is_iterator_v = is_iterator::value; */ template struct is_ebco_eligible - : std::conjunction, std::negation>> {}; + : std::bool_constant && !std::is_final_v> {}; /** * @brief Helper variable template. @@ -48308,6 +52225,10 @@ template struct is_equality_comparable() == std::declval())>> : std::bool_constant(choice<2>)> {}; +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: std::false_type {}; + /** * @brief Helper variable template. * @tparam Type The type to test. @@ -48404,6 +52325,18 @@ using nth_argument_t = typename nth_argument::type; } // namespace entt +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::type_list_element> {}; + +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::value_list_element> {}; + #endif @@ -48987,7 +52920,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 struct allocation_deleter: private Allocator { @@ -49008,7 +52941,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) { - using alloc_traits = typename std::allocator_traits; + using alloc_traits = std::allocator_traits; alloc_traits::destroy(*this, to_address(ptr)); alloc_traits::deallocate(*this, ptr, 1u); } @@ -49175,6 +53108,7 @@ constexpr Type *uninitialized_construct_using_allocator(Type *value, const Alloc #include #include +#include #include #include // #include "../config/config.h" @@ -49229,7 +53163,6 @@ using type_identity_t = typename type_identity::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 struct size_of: std::integral_constant {}; @@ -49471,7 +53404,8 @@ struct type_list_contains; * @tparam Other Type to look for. */ template -struct type_list_contains, Other>: std::disjunction...> {}; +struct type_list_contains, Other> + : std::bool_constant<(std::is_same_v || ...)> {}; /** * @brief Helper variable template. @@ -49559,10 +53493,20 @@ struct value_list_element> */ template struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = typename value_list_element::type; + /** * @brief Helper type. * @tparam Index Index of the value to return. @@ -49571,6 +53515,58 @@ struct value_list_element<0u, value_list> { template inline constexpr auto value_list_element_v = value_list_element::value; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +struct value_list_index> { + static_assert(value_list_index>::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 +struct value_list_index> { + /*! @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 +inline constexpr std::size_t value_list_index_v = value_list_index::value; + /** * @brief Concatenates multiple value lists. * @tparam Value Values provided by the first value list. @@ -49622,6 +53618,89 @@ struct value_list_cat> { template using value_list_cat_t = typename value_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = std::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = typename value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : std::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +class value_list_diff, value_list> { + using v141_toolset_workaround = value_list; + +public: + /*! @brief A value list that is the difference between the two value lists. */ + using type = value_list_cat_t, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = typename value_list_diff::type; + /*! @brief Same as std::is_invocable, but with tuples. */ template struct is_applicable: std::false_type {}; @@ -49742,7 +53821,7 @@ inline constexpr bool is_iterator_v = is_iterator::value; */ template struct is_ebco_eligible - : std::conjunction, std::negation>> {}; + : std::bool_constant && !std::is_final_v> {}; /** * @brief Helper variable template. @@ -49833,6 +53912,10 @@ template struct is_equality_comparable() == std::declval())>> : std::bool_constant(choice<2>)> {}; +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: std::false_type {}; + /** * @brief Helper variable template. * @tparam Type The type to test. @@ -49929,6 +54012,18 @@ using nth_argument_t = typename nth_argument::type; } // namespace entt +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::type_list_element> {}; + +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::value_list_element> {}; + #endif // #include "fwd.hpp" @@ -50070,51 +54165,51 @@ public: return {it->element.first, it->element.second}; } - template - friend constexpr std::ptrdiff_t operator-(const dense_map_iterator &, const dense_map_iterator &) noexcept; + template + friend constexpr std::ptrdiff_t operator-(const dense_map_iterator &, const dense_map_iterator &) noexcept; - template - friend constexpr bool operator==(const dense_map_iterator &, const dense_map_iterator &) noexcept; + template + friend constexpr bool operator==(const dense_map_iterator &, const dense_map_iterator &) noexcept; - template - friend constexpr bool operator<(const dense_map_iterator &, const dense_map_iterator &) noexcept; + template + friend constexpr bool operator<(const dense_map_iterator &, const dense_map_iterator &) noexcept; private: It it; }; -template -[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return lhs.it - rhs.it; } -template -[[nodiscard]] constexpr bool operator==(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return lhs.it == rhs.it; } -template -[[nodiscard]] constexpr bool operator!=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return !(lhs == rhs); } -template -[[nodiscard]] constexpr bool operator<(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return lhs.it < rhs.it; } -template -[[nodiscard]] constexpr bool operator>(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return rhs < lhs; } -template -[[nodiscard]] constexpr bool operator<=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return !(lhs > rhs); } -template -[[nodiscard]] constexpr bool operator>=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return !(lhs < rhs); } @@ -50172,13 +54267,13 @@ private: std::size_t offset; }; -template -[[nodiscard]] constexpr bool operator==(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { return lhs.index() == rhs.index(); } -template -[[nodiscard]] constexpr bool operator!=(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { return !(lhs == rhs); } @@ -50208,7 +54303,7 @@ class dense_map { static constexpr std::size_t minimum_capacity = 8u; using node_type = internal::dense_map_node; - using alloc_traits = typename std::allocator_traits; + using alloc_traits = std::allocator_traits; static_assert(std::is_same_v>, "Invalid value type"); using sparse_container_type = std::vector>; using packed_container_type = std::vector>; @@ -50406,7 +54501,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. @@ -50427,11 +54521,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. */ @@ -50780,7 +54869,7 @@ public: } /*! @copydoc equal_range */ - template + template [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t>> equal_range(const Other &key) const { const auto it = find(key); @@ -51105,51 +55194,51 @@ public: return *operator->(); } - template - friend constexpr std::ptrdiff_t operator-(const dense_set_iterator &, const dense_set_iterator &) noexcept; + template + friend constexpr std::ptrdiff_t operator-(const dense_set_iterator &, const dense_set_iterator &) noexcept; - template - friend constexpr bool operator==(const dense_set_iterator &, const dense_set_iterator &) noexcept; + template + friend constexpr bool operator==(const dense_set_iterator &, const dense_set_iterator &) noexcept; - template - friend constexpr bool operator<(const dense_set_iterator &, const dense_set_iterator &) noexcept; + template + friend constexpr bool operator<(const dense_set_iterator &, const dense_set_iterator &) noexcept; private: It it; }; -template -[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return lhs.it - rhs.it; } -template -[[nodiscard]] constexpr bool operator==(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return lhs.it == rhs.it; } -template -[[nodiscard]] constexpr bool operator!=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return !(lhs == rhs); } -template -[[nodiscard]] constexpr bool operator<(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return lhs.it < rhs.it; } -template -[[nodiscard]] constexpr bool operator>(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return rhs < lhs; } -template -[[nodiscard]] constexpr bool operator<=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return !(lhs > rhs); } -template -[[nodiscard]] constexpr bool operator>=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return !(lhs < rhs); } @@ -51204,13 +55293,13 @@ private: std::size_t offset; }; -template -[[nodiscard]] constexpr bool operator==(const dense_set_local_iterator &lhs, const dense_set_local_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const dense_set_local_iterator &lhs, const dense_set_local_iterator &rhs) noexcept { return lhs.index() == rhs.index(); } -template -[[nodiscard]] constexpr bool operator!=(const dense_set_local_iterator &lhs, const dense_set_local_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const dense_set_local_iterator &lhs, const dense_set_local_iterator &rhs) noexcept { return !(lhs == rhs); } @@ -51419,7 +55508,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. @@ -51440,11 +55528,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. */ @@ -51700,7 +55783,7 @@ public: } /*! @copydoc equal_range */ - template + template [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t>> equal_range(const Other &value) const { const auto it = find(value); @@ -51933,8 +56016,8 @@ private: #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) \ @@ -51990,6 +56073,8 @@ private: # 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 @@ -52056,7 +56141,7 @@ struct identity { * @param value The actual argument. * @return The submitted value as-is. */ - template + template [[nodiscard]] constexpr Type &&operator()(Type &&value) const noexcept { return std::forward(value); } @@ -52089,7 +56174,7 @@ template * @brief Helper type for visitors. * @tparam Func Types of function objects. */ -template +template struct overloaded: Func... { using Func::operator()...; }; @@ -52098,14 +56183,14 @@ struct overloaded: Func... { * @brief Deduction guide. * @tparam Func Types of function objects. */ -template +template overloaded(Func...) -> overloaded; /** * @brief Basic implementation of a y-combinator. * @tparam Func Type of a potentially recursive function. */ -template +template struct y_combinator { /** * @brief Constructs a y-combinator from a given function. @@ -52120,13 +56205,13 @@ struct y_combinator { * @param args Parameters to use to invoke the underlying function. * @return Return value of the underlying function, if any. */ - template + template constexpr decltype(auto) operator()(Args &&...args) const noexcept(std::is_nothrow_invocable_v) { return func(*this, std::forward(args)...); } /*! @copydoc operator()() */ - template + template constexpr decltype(auto) operator()(Args &&...args) noexcept(std::is_nothrow_invocable_v) { return func(*this, std::forward(args)...); } @@ -52156,8 +56241,8 @@ struct meta_type_node; struct meta_context { dense_map 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 @@ -52184,11 +56269,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; } @@ -52229,8 +56314,8 @@ inline const internal::meta_context &internal::meta_context::from(const meta_ctx #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) \ @@ -52286,6 +56371,8 @@ inline const internal::meta_context &internal::meta_context::from(const meta_ctx # 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 @@ -52346,7 +56433,7 @@ struct identity { * @param value The actual argument. * @return The submitted value as-is. */ - template + template [[nodiscard]] constexpr Type &&operator()(Type &&value) const noexcept { return std::forward(value); } @@ -52379,7 +56466,7 @@ template * @brief Helper type for visitors. * @tparam Func Types of function objects. */ -template +template struct overloaded: Func... { using Func::operator()...; }; @@ -52388,14 +56475,14 @@ struct overloaded: Func... { * @brief Deduction guide. * @tparam Func Types of function objects. */ -template +template overloaded(Func...) -> overloaded; /** * @brief Basic implementation of a y-combinator. * @tparam Func Type of a potentially recursive function. */ -template +template struct y_combinator { /** * @brief Constructs a y-combinator from a given function. @@ -52410,13 +56497,13 @@ struct y_combinator { * @param args Parameters to use to invoke the underlying function. * @return Return value of the underlying function, if any. */ - template + template constexpr decltype(auto) operator()(Args &&...args) const noexcept(std::is_nothrow_invocable_v) { return func(*this, std::forward(args)...); } /*! @copydoc operator()() */ - template + template constexpr decltype(auto) operator()(Args &&...args) noexcept(std::is_nothrow_invocable_v) { return func(*this, std::forward(args)...); } @@ -52566,7 +56653,7 @@ struct basic_hashed_string { template class basic_hashed_string: internal::basic_hashed_string { using base_type = internal::basic_hashed_string; - using hs_traits = internal::fnv1a_traits; + using traits_type = internal::fnv1a_traits; struct const_wrapper { // non-explicit constructor on purpose @@ -52578,10 +56665,10 @@ class basic_hashed_string: internal::basic_hashed_string { // 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(str[base.length])) * hs_traits::prime; + base.hash = (base.hash ^ static_cast(str[base.length])) * traits_type::prime; } return base; @@ -52589,10 +56676,10 @@ class basic_hashed_string: internal::basic_hashed_string { // 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(str[pos])) * hs_traits::prime; + base.hash = (base.hash ^ static_cast(str[pos])) * traits_type::prime; } return base; @@ -53103,6 +57190,7 @@ template #include #include +#include #include #include // #include "../config/config.h" @@ -53157,7 +57245,6 @@ using type_identity_t = typename type_identity::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 struct size_of: std::integral_constant {}; @@ -53399,7 +57486,8 @@ struct type_list_contains; * @tparam Other Type to look for. */ template -struct type_list_contains, Other>: std::disjunction...> {}; +struct type_list_contains, Other> + : std::bool_constant<(std::is_same_v || ...)> {}; /** * @brief Helper variable template. @@ -53487,10 +57575,20 @@ struct value_list_element> */ template struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = typename value_list_element::type; + /** * @brief Helper type. * @tparam Index Index of the value to return. @@ -53499,6 +57597,58 @@ struct value_list_element<0u, value_list> { template inline constexpr auto value_list_element_v = value_list_element::value; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +struct value_list_index> { + static_assert(value_list_index>::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 +struct value_list_index> { + /*! @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 +inline constexpr std::size_t value_list_index_v = value_list_index::value; + /** * @brief Concatenates multiple value lists. * @tparam Value Values provided by the first value list. @@ -53550,6 +57700,89 @@ struct value_list_cat> { template using value_list_cat_t = typename value_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = std::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = typename value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : std::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +class value_list_diff, value_list> { + using v141_toolset_workaround = value_list; + +public: + /*! @brief A value list that is the difference between the two value lists. */ + using type = value_list_cat_t, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = typename value_list_diff::type; + /*! @brief Same as std::is_invocable, but with tuples. */ template struct is_applicable: std::false_type {}; @@ -53670,7 +57903,7 @@ inline constexpr bool is_iterator_v = is_iterator::value; */ template struct is_ebco_eligible - : std::conjunction, std::negation>> {}; + : std::bool_constant && !std::is_final_v> {}; /** * @brief Helper variable template. @@ -53761,6 +57994,10 @@ template struct is_equality_comparable() == std::declval())>> : std::bool_constant(choice<2>)> {}; +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: std::false_type {}; + /** * @brief Helper variable template. * @tparam Type The type to test. @@ -53857,6 +58094,18 @@ using nth_argument_t = typename nth_argument::type; } // namespace entt +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::type_list_element> {}; + +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::value_list_element> {}; + #endif @@ -53908,7 +58157,7 @@ class basic_any { }; template - static constexpr bool in_situ = Len && alignof(Type) <= Align && sizeof(Type) <= Len &&std::is_nothrow_move_constructible_v; + static constexpr bool in_situ = Len && alignof(Type) <= Align && sizeof(Type) <= Len && std::is_nothrow_move_constructible_v; template static const void *basic_vtable(const operation op, const basic_any &value, const void *other) { @@ -53977,17 +58226,17 @@ class basic_any { vtable = basic_vtable>>; if constexpr(std::is_lvalue_reference_v) { - static_assert(sizeof...(Args) == 1u && (std::is_lvalue_reference_v && ...), "Invalid arguments"); + static_assert((std::is_lvalue_reference_v && ...) && (sizeof...(Args) == 1u), "Invalid arguments"); mode = std::is_const_v> ? policy::cref : policy::ref; instance = (std::addressof(args), ...); } else if constexpr(in_situ>>) { - if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v>>) { + if constexpr(std::is_aggregate_v>> && (sizeof...(Args) != 0u || !std::is_default_constructible_v>>)) { new(&storage) std::remove_cv_t>{std::forward(args)...}; } else { new(&storage) std::remove_cv_t>(std::forward(args)...); } } else { - if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v>>) { + if constexpr(std::is_aggregate_v>> && (sizeof...(Args) != 0u || !std::is_default_constructible_v>>)) { instance = new std::remove_cv_t>{std::forward(args)...}; } else { instance = new std::remove_cv_t>(std::forward(args)...); @@ -54277,7 +58526,7 @@ private: * @return The element converted to the requested type. */ template -Type any_cast(const basic_any &data) noexcept { +[[nodiscard]] Type any_cast(const basic_any &data) noexcept { const auto *const instance = any_cast>(&data); ENTT_ASSERT(instance, "Invalid instance"); return static_cast(*instance); @@ -54285,7 +58534,7 @@ Type any_cast(const basic_any &data) noexcept { /*! @copydoc any_cast */ template -Type any_cast(basic_any &data) noexcept { +[[nodiscard]] Type any_cast(basic_any &data) noexcept { // forces const on non-reference types to make them work also with wrappers for const references auto *const instance = any_cast>(&data); ENTT_ASSERT(instance, "Invalid instance"); @@ -54294,7 +58543,7 @@ Type any_cast(basic_any &data) noexcept { /*! @copydoc any_cast */ template -Type any_cast(basic_any &&data) noexcept { +[[nodiscard]] Type any_cast(basic_any &&data) noexcept { if constexpr(std::is_copy_constructible_v>>) { if(auto *const instance = any_cast>(&data); instance) { return static_cast(std::move(*instance)); @@ -54310,14 +58559,14 @@ Type any_cast(basic_any &&data) noexcept { /*! @copydoc any_cast */ template -const Type *any_cast(const basic_any *data) noexcept { +[[nodiscard]] const Type *any_cast(const basic_any *data) noexcept { const auto &info = type_id>(); return static_cast(data->data(info)); } /*! @copydoc any_cast */ template -Type *any_cast(basic_any *data) noexcept { +[[nodiscard]] Type *any_cast(basic_any *data) noexcept { if constexpr(std::is_const_v) { // last attempt to make wrappers for const references return their values return any_cast(&std::as_const(*data)); @@ -54337,7 +58586,7 @@ Type *any_cast(basic_any *data) noexcept { * @return A properly initialized wrapper for an object of the given type. */ template::length, std::size_t Align = basic_any::alignment, typename... Args> -basic_any make_any(Args &&...args) { +[[nodiscard]] basic_any make_any(Args &&...args) { return basic_any{std::in_place_type, std::forward(args)...}; } @@ -54350,7 +58599,7 @@ basic_any make_any(Args &&...args) { * @return A properly initialized and not necessarily owning wrapper. */ template::length, std::size_t Align = basic_any::alignment, typename Type> -basic_any forward_as_any(Type &&value) { +[[nodiscard]] basic_any forward_as_any(Type &&value) { return basic_any{std::in_place_type, std::forward(value)}; } @@ -54845,6 +59094,7 @@ template #include #include +#include #include #include // #include "../config/config.h" @@ -54899,7 +59149,6 @@ using type_identity_t = typename type_identity::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 struct size_of: std::integral_constant {}; @@ -55141,7 +59390,8 @@ struct type_list_contains; * @tparam Other Type to look for. */ template -struct type_list_contains, Other>: std::disjunction...> {}; +struct type_list_contains, Other> + : std::bool_constant<(std::is_same_v || ...)> {}; /** * @brief Helper variable template. @@ -55229,10 +59479,20 @@ struct value_list_element> */ template struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = typename value_list_element::type; + /** * @brief Helper type. * @tparam Index Index of the value to return. @@ -55241,6 +59501,58 @@ struct value_list_element<0u, value_list> { template inline constexpr auto value_list_element_v = value_list_element::value; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +struct value_list_index> { + static_assert(value_list_index>::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 +struct value_list_index> { + /*! @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 +inline constexpr std::size_t value_list_index_v = value_list_index::value; + /** * @brief Concatenates multiple value lists. * @tparam Value Values provided by the first value list. @@ -55292,6 +59604,89 @@ struct value_list_cat> { template using value_list_cat_t = typename value_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = std::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = typename value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : std::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +class value_list_diff, value_list> { + using v141_toolset_workaround = value_list; + +public: + /*! @brief A value list that is the difference between the two value lists. */ + using type = value_list_cat_t, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = typename value_list_diff::type; + /*! @brief Same as std::is_invocable, but with tuples. */ template struct is_applicable: std::false_type {}; @@ -55412,7 +59807,7 @@ inline constexpr bool is_iterator_v = is_iterator::value; */ template struct is_ebco_eligible - : std::conjunction, std::negation>> {}; + : std::bool_constant && !std::is_final_v> {}; /** * @brief Helper variable template. @@ -55503,6 +59898,10 @@ template struct is_equality_comparable() == std::declval())>> : std::bool_constant(choice<2>)> {}; +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: std::false_type {}; + /** * @brief Helper variable template. * @tparam Type The type to test. @@ -55599,6 +59998,18 @@ using nth_argument_t = typename nth_argument::type; } // namespace entt +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::type_list_element> {}; + +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::value_list_element> {}; + #endif // #include "../core/utility.hpp" @@ -55628,8 +60039,8 @@ using nth_argument_t = typename nth_argument::type; #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) \ @@ -55685,6 +60096,8 @@ using nth_argument_t = typename nth_argument::type; # 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 @@ -55781,40 +60194,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 + template [[nodiscard]] static Service &value_or(Args &&...args) { - return service ? *service : emplace(std::forward(args)...); + return service ? *service : emplace(std::forward(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 + template static Service &emplace(Args &&...args) { - service = std::make_shared(std::forward(args)...); + service = std::make_shared(std::forward(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 - static Service &allocate_emplace(Allocator alloc, Args &&...args) { - service = std::allocate_shared(alloc, std::forward(args)...); + template + static Service &emplace(std::allocator_arg_t, Allocator alloc, Args &&...args) { + service = std::allocate_shared(alloc, std::forward(args)...); return *service; } @@ -55836,6 +60249,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> + static void reset(Type *elem, Deleter deleter = {}) { + service = std::shared_ptr{elem, std::move(deleter)}; + } + private: // std::shared_ptr because of its type erased allocator which is useful here inline static std::shared_ptr service{}; @@ -56096,7 +60521,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 struct is_meta_pointer_like: std::false_type {}; @@ -56371,19 +60795,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; } @@ -57090,7 +61514,7 @@ private: * @return A properly initialized and not necessarily owning wrapper. */ template -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, std::forward(value)}; } @@ -57101,7 +61525,7 @@ meta_any forward_as_meta(const meta_ctx &ctx, Type &&value) { * @return A properly initialized and not necessarily owning wrapper. */ template -meta_any forward_as_meta(Type &&value) { +[[nodiscard]] meta_any forward_as_meta(Type &&value) { return forward_as_meta(locator::value_or(), std::forward(value)); } @@ -57199,6 +61623,16 @@ struct meta_handle { return static_cast(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. @@ -57233,13 +61667,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. @@ -57248,11 +61690,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. */ @@ -57353,11 +61814,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. */ @@ -57439,8 +61915,12 @@ struct meta_func { */ template meta_any invoke(meta_handle instance, Args &&...args) const { - meta_any arguments[sizeof...(Args) + 1u]{{*ctx, std::forward(args)}...}; - return invoke(meta_handle{*ctx, std::move(instance)}, arguments, sizeof...(Args)); + if constexpr(sizeof...(Args) == 0u) { + return invoke(std::move(instance), static_cast(nullptr), size_type{}); + } else { + meta_any arguments[sizeof...(Args)]{{*ctx, std::forward(args)}...}; + return invoke(std::move(instance), arguments, sizeof...(Args)); + } } /*! @copydoc meta_data::prop */ @@ -57474,11 +61954,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 @@ -57816,8 +62311,12 @@ public: */ template [[nodiscard]] meta_any construct(Args &&...args) const { - meta_any arguments[sizeof...(Args) + 1u]{{*ctx, std::forward(args)}...}; - return construct(arguments, sizeof...(Args)); + if constexpr(sizeof...(Args) == 0u) { + return construct(static_cast(nullptr), size_type{}); + } else { + meta_any arguments[sizeof...(Args)]{{*ctx, std::forward(args)}...}; + return construct(arguments, sizeof...(Args)); + } } /** @@ -57825,12 +62324,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}; } @@ -57850,7 +62349,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); } } @@ -57876,8 +62375,12 @@ public: */ template meta_any invoke(const id_type id, meta_handle instance, Args &&...args) const { - meta_any arguments[sizeof...(Args) + 1u]{{*ctx, std::forward(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(nullptr), size_type{}); + } else { + meta_any arguments[sizeof...(Args)]{{*ctx, std::forward(args)}...}; + return invoke(id, std::move(instance), arguments, sizeof...(Args)); + } } /** @@ -57943,11 +62446,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)); } @@ -58099,7 +62598,7 @@ public: explicit meta_iterator(const meta_ctx &area, It iter) noexcept : ctx{&area}, vtable{&basic_vtable}, - handle{std::move(iter)} {} + handle{iter} {} meta_iterator &operator++() noexcept { vtable(operation::incr, handle, 1, nullptr); @@ -58193,7 +62692,7 @@ public: meta_iterator(const meta_ctx &area, std::integral_constant, It iter) noexcept : ctx{&area}, vtable{&basic_vtable}, - handle{std::move(iter)} {} + handle{iter} {} meta_iterator &operator++() noexcept { vtable(operation::incr, handle, nullptr); @@ -58303,7 +62802,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, {}); } /** @@ -58684,8 +63183,8 @@ struct meta_type_node; struct meta_context { dense_map 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 @@ -58712,11 +63211,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; } @@ -58826,11 +63325,7 @@ struct as_void_t final { */ template struct is_meta_policy - : std::disjunction< - std::is_same, - std::is_same, - std::is_same, - std::is_same> {}; + : std::bool_constant || std::is_same_v || std::is_same_v || std::is_same_v> {}; /** * @brief Helper variable template. @@ -59055,9 +63550,12 @@ template struct meta_function_descriptor : meta_function_descriptor_traits< Ret, - std::conditional_t>, Type>, type_list, type_list>, - !std::is_base_of_v>, Type>, - std::is_base_of_v>, Type> && std::is_const_v>> {}; + std::conditional_t< + std::is_same_v>, Type> || std::is_base_of_v>, Type>, + type_list, + type_list>, + !(std::is_same_v>, Type> || std::is_base_of_v>, Type>), + std::is_const_v> && (std::is_same_v>, Type> || std::is_base_of_v>, Type>)> {}; /** * @brief Meta function descriptor. @@ -59125,7 +63623,7 @@ using meta_function_helper_t = typename meta_function_helper::t * @return A meta any containing the returned value, if any. */ template -std::enable_if_t, meta_any> meta_dispatch(const meta_ctx &ctx, [[maybe_unused]] Type &&value) { +[[nodiscard]] std::enable_if_t, meta_any> meta_dispatch(const meta_ctx &ctx, [[maybe_unused]] Type &&value) { if constexpr(std::is_same_v) { return meta_any{ctx, std::in_place_type}; } else if constexpr(std::is_same_v) { @@ -59146,7 +63644,7 @@ std::enable_if_t, meta_any> meta_dispatch(const meta_ct * @return A meta any containing the returned value, if any. */ template -std::enable_if_t, meta_any> meta_dispatch(Type &&value) { +[[nodiscard]] std::enable_if_t, meta_any> meta_dispatch(Type &&value) { return meta_dispatch(locator::value_or(), std::forward(value)); } @@ -59511,28 +64009,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; } @@ -59554,10 +64036,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 &prop, const id_type id, meta_prop_node node) { - return (prop[id] = std::move(node)); -} - } // namespace internal /** @@ -59638,16 +64116,8 @@ public: template auto base() noexcept { static_assert(!std::is_same_v && std::is_base_of_v, "Invalid base type"); - - internal::meta_extend( - internal::owner(*ctx, *info), - type_id().hash(), - internal::meta_base_node{ - &internal::resolve, - +[](const void *instance) noexcept { - return static_cast(static_cast(static_cast(instance))); - }}); - + auto *const op = +[](const void *instance) noexcept { return static_cast(static_cast(static_cast(instance))); }; + internal::owner(*ctx, *info).details->base.insert_or_assign(type_id().hash(), internal::meta_base_node{&internal::resolve, op}); bucket = nullptr; return *this; } @@ -59667,15 +64137,8 @@ public: template auto conv() noexcept { using conv_type = std::remove_cv_t>>; - - internal::meta_extend( - internal::owner(*ctx, *info), - type_id().hash(), - internal::meta_conv_node{ - +[](const meta_ctx &area, const void *instance) { - return forward_as_meta(area, std::invoke(Candidate, *static_cast(instance))); - }}); - + auto *const op = +[](const meta_ctx &area, const void *instance) { return forward_as_meta(area, std::invoke(Candidate, *static_cast(instance))); }; + internal::owner(*ctx, *info).details->conv.insert_or_assign(type_id().hash(), internal::meta_conv_node{op}); bucket = nullptr; return *this; } @@ -59692,15 +64155,8 @@ public: template auto conv() noexcept { using conv_type = std::remove_cv_t>; - - internal::meta_extend( - internal::owner(*ctx, *info), - type_id().hash(), - internal::meta_conv_node{ - +[](const meta_ctx &area, const void *instance) { - return forward_as_meta(area, static_cast(*static_cast(instance))); - }}); - + auto *const op = +[](const meta_ctx &area, const void *instance) { return forward_as_meta(area, static_cast(*static_cast(instance))); }; + internal::owner(*ctx, *info).details->conv.insert_or_assign(type_id().hash(), internal::meta_conv_node{op}); bucket = nullptr; return *this; } @@ -59723,15 +64179,7 @@ public: using descriptor = meta_function_helper_t; static_assert(Policy::template value, "Invalid return type for the given policy"); static_assert(std::is_same_v>, Type>, "The function doesn't return an object of the required type"); - - internal::meta_extend( - internal::owner(*ctx, *info), - type_id().hash(), - internal::meta_ctor_node{ - descriptor::args_type::size, - &meta_arg, - &meta_construct}); - + internal::owner(*ctx, *info).details->ctor.insert_or_assign(type_id().hash(), internal::meta_ctor_node{descriptor::args_type::size, &meta_arg, &meta_construct}); bucket = nullptr; return *this; } @@ -59751,14 +64199,7 @@ public: // default constructor is already implicitly generated, no need for redundancy if constexpr(sizeof...(Args) != 0u) { using descriptor = meta_function_helper_t; - - internal::meta_extend( - internal::owner(*ctx, *info), - type_id().hash(), - internal::meta_ctor_node{ - descriptor::args_type::size, - &meta_arg, - &meta_construct}); + internal::owner(*ctx, *info).details->ctor.insert_or_assign(type_id().hash(), internal::meta_ctor_node{descriptor::args_type::size, &meta_arg, &meta_construct}); } bucket = nullptr; @@ -59786,12 +64227,8 @@ public: template auto dtor() noexcept { static_assert(std::is_invocable_v, "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(instance)); }}); - + auto *const op = +[](void *instance) { std::invoke(Func, *static_cast(instance)); }; + internal::owner(*ctx, *info).dtor = internal::meta_dtor_node{op}; bucket = nullptr; return *this; } @@ -59812,32 +64249,39 @@ public: template auto data(const id_type id) noexcept { if constexpr(std::is_member_object_pointer_v) { - using data_type = std::remove_reference_t>; + using data_type = std::invoke_result_t; + static_assert(Policy::template value, "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 ? internal::meta_traits::is_const : internal::meta_traits::is_none, + std::is_const_v> ? internal::meta_traits::is_const : internal::meta_traits::is_none, 1u, - &internal::resolve>, - &meta_arg>>, + &internal::resolve>>, + &meta_arg>>>, &meta_setter, &meta_getter}); bucket = &elem.prop; } else { - using data_type = std::remove_reference_t>; + using data_type = std::remove_pointer_t; + + if constexpr(std::is_pointer_v) { + static_assert(Policy::template value, "Invalid return type for the given policy"); + } else { + static_assert(Policy::template value, "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> || std::is_const_v) ? internal::meta_traits::is_const : internal::meta_traits::is_none) | internal::meta_traits::is_static, + ((std::is_same_v>> || std::is_const_v>) ? internal::meta_traits::is_const : internal::meta_traits::is_none) | internal::meta_traits::is_static, 1u, - &internal::resolve>, - &meta_arg>>, + &internal::resolve>>, + &meta_arg>>>, &meta_setter, &meta_getter}); @@ -59977,18 +64421,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}); + (*bucket)[id] = internal::meta_prop_node{&internal::resolve}; } else { - internal::meta_extend( - *bucket, - id, - internal::meta_prop_node{ - &internal::resolve>..., - std::make_shared>(std::forward(value))...}); + (*bucket)[id] = internal::meta_prop_node{ + &internal::resolve>..., + std::make_shared>(std::forward(value))...}; } return *this; @@ -60754,7 +65191,7 @@ private: * @return A properly initialized and not necessarily owning wrapper. */ template -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, std::forward(value)}; } @@ -60765,7 +65202,7 @@ meta_any forward_as_meta(const meta_ctx &ctx, Type &&value) { * @return A properly initialized and not necessarily owning wrapper. */ template -meta_any forward_as_meta(Type &&value) { +[[nodiscard]] meta_any forward_as_meta(Type &&value) { return forward_as_meta(locator::value_or(), std::forward(value)); } @@ -60863,6 +65300,16 @@ struct meta_handle { return static_cast(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. @@ -60897,13 +65344,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. @@ -60912,11 +65367,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. */ @@ -61017,11 +65491,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. */ @@ -61103,8 +65592,12 @@ struct meta_func { */ template meta_any invoke(meta_handle instance, Args &&...args) const { - meta_any arguments[sizeof...(Args) + 1u]{{*ctx, std::forward(args)}...}; - return invoke(meta_handle{*ctx, std::move(instance)}, arguments, sizeof...(Args)); + if constexpr(sizeof...(Args) == 0u) { + return invoke(std::move(instance), static_cast(nullptr), size_type{}); + } else { + meta_any arguments[sizeof...(Args)]{{*ctx, std::forward(args)}...}; + return invoke(std::move(instance), arguments, sizeof...(Args)); + } } /*! @copydoc meta_data::prop */ @@ -61138,11 +65631,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 @@ -61480,8 +65988,12 @@ public: */ template [[nodiscard]] meta_any construct(Args &&...args) const { - meta_any arguments[sizeof...(Args) + 1u]{{*ctx, std::forward(args)}...}; - return construct(arguments, sizeof...(Args)); + if constexpr(sizeof...(Args) == 0u) { + return construct(static_cast(nullptr), size_type{}); + } else { + meta_any arguments[sizeof...(Args)]{{*ctx, std::forward(args)}...}; + return construct(arguments, sizeof...(Args)); + } } /** @@ -61489,12 +66001,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}; } @@ -61514,7 +66026,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); } } @@ -61540,8 +66052,12 @@ public: */ template meta_any invoke(const id_type id, meta_handle instance, Args &&...args) const { - meta_any arguments[sizeof...(Args) + 1u]{{*ctx, std::forward(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(nullptr), size_type{}); + } else { + meta_any arguments[sizeof...(Args)]{{*ctx, std::forward(args)}...}; + return invoke(id, std::move(instance), arguments, sizeof...(Args)); + } } /** @@ -61607,11 +66123,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)); } @@ -61763,7 +66275,7 @@ public: explicit meta_iterator(const meta_ctx &area, It iter) noexcept : ctx{&area}, vtable{&basic_vtable}, - handle{std::move(iter)} {} + handle{iter} {} meta_iterator &operator++() noexcept { vtable(operation::incr, handle, 1, nullptr); @@ -61857,7 +66369,7 @@ public: meta_iterator(const meta_ctx &area, std::integral_constant, It iter) noexcept : ctx{&area}, vtable{&basic_vtable}, - handle{std::move(iter)} {} + handle{iter} {} meta_iterator &operator++() noexcept { vtable(operation::incr, handle, nullptr); @@ -61967,7 +66479,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, {}); } /** @@ -62459,11 +66971,7 @@ struct as_void_t final { */ template struct is_meta_policy - : std::disjunction< - std::is_same, - std::is_same, - std::is_same, - std::is_same> {}; + : std::bool_constant || std::is_same_v || std::is_same_v || std::is_same_v> {}; /** * @brief Helper variable template. @@ -62507,19 +67015,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; } @@ -62804,7 +67312,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 struct is_meta_pointer_like: std::false_type {}; @@ -62928,9 +67435,12 @@ template struct meta_function_descriptor : meta_function_descriptor_traits< Ret, - std::conditional_t>, Type>, type_list, type_list>, - !std::is_base_of_v>, Type>, - std::is_base_of_v>, Type> && std::is_const_v>> {}; + std::conditional_t< + std::is_same_v>, Type> || std::is_base_of_v>, Type>, + type_list, + type_list>, + !(std::is_same_v>, Type> || std::is_base_of_v>, Type>), + std::is_const_v> && (std::is_same_v>, Type> || std::is_base_of_v>, Type>)> {}; /** * @brief Meta function descriptor. @@ -62998,7 +67508,7 @@ using meta_function_helper_t = typename meta_function_helper::t * @return A meta any containing the returned value, if any. */ template -std::enable_if_t, meta_any> meta_dispatch(const meta_ctx &ctx, [[maybe_unused]] Type &&value) { +[[nodiscard]] std::enable_if_t, meta_any> meta_dispatch(const meta_ctx &ctx, [[maybe_unused]] Type &&value) { if constexpr(std::is_same_v) { return meta_any{ctx, std::in_place_type}; } else if constexpr(std::is_same_v) { @@ -63019,7 +67529,7 @@ std::enable_if_t, meta_any> meta_dispatch(const meta_ct * @return A meta any containing the returned value, if any. */ template -std::enable_if_t, meta_any> meta_dispatch(Type &&value) { +[[nodiscard]] std::enable_if_t, meta_any> meta_dispatch(Type &&value) { return meta_dispatch(locator::value_or(), std::forward(value)); } @@ -63479,8 +67989,8 @@ using invoke_result_t = typename std::invoke_result::type; #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) \ @@ -63536,6 +68046,8 @@ using invoke_result_t = typename std::invoke_result::type; # 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 @@ -63586,7 +68098,7 @@ struct identity { * @param value The actual argument. * @return The submitted value as-is. */ - template + template [[nodiscard]] constexpr Type &&operator()(Type &&value) const noexcept { return std::forward(value); } @@ -63619,7 +68131,7 @@ template * @brief Helper type for visitors. * @tparam Func Types of function objects. */ -template +template struct overloaded: Func... { using Func::operator()...; }; @@ -63628,14 +68140,14 @@ struct overloaded: Func... { * @brief Deduction guide. * @tparam Func Types of function objects. */ -template +template overloaded(Func...) -> overloaded; /** * @brief Basic implementation of a y-combinator. * @tparam Func Type of a potentially recursive function. */ -template +template struct y_combinator { /** * @brief Constructs a y-combinator from a given function. @@ -63650,13 +68162,13 @@ struct y_combinator { * @param args Parameters to use to invoke the underlying function. * @return Return value of the underlying function, if any. */ - template + template constexpr decltype(auto) operator()(Args &&...args) const noexcept(std::is_nothrow_invocable_v) { return func(*this, std::forward(args)...); } /*! @copydoc operator()() */ - template + template constexpr decltype(auto) operator()(Args &&...args) noexcept(std::is_nothrow_invocable_v) { return func(*this, std::forward(args)...); } @@ -63806,7 +68318,7 @@ struct basic_hashed_string { template class basic_hashed_string: internal::basic_hashed_string { using base_type = internal::basic_hashed_string; - using hs_traits = internal::fnv1a_traits; + using traits_type = internal::fnv1a_traits; struct const_wrapper { // non-explicit constructor on purpose @@ -63818,10 +68330,10 @@ class basic_hashed_string: internal::basic_hashed_string { // 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(str[base.length])) * hs_traits::prime; + base.hash = (base.hash ^ static_cast(str[base.length])) * traits_type::prime; } return base; @@ -63829,10 +68341,10 @@ class basic_hashed_string: internal::basic_hashed_string { // 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(str[pos])) * hs_traits::prime; + base.hash = (base.hash ^ static_cast(str[pos])) * traits_type::prime; } return base; @@ -64343,6 +68855,7 @@ template #include #include +#include #include #include // #include "../config/config.h" @@ -64397,7 +68910,6 @@ using type_identity_t = typename type_identity::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 struct size_of: std::integral_constant {}; @@ -64639,7 +69151,8 @@ struct type_list_contains; * @tparam Other Type to look for. */ template -struct type_list_contains, Other>: std::disjunction...> {}; +struct type_list_contains, Other> + : std::bool_constant<(std::is_same_v || ...)> {}; /** * @brief Helper variable template. @@ -64727,10 +69240,20 @@ struct value_list_element> */ template struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = typename value_list_element::type; + /** * @brief Helper type. * @tparam Index Index of the value to return. @@ -64739,6 +69262,58 @@ struct value_list_element<0u, value_list> { template inline constexpr auto value_list_element_v = value_list_element::value; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +struct value_list_index> { + static_assert(value_list_index>::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 +struct value_list_index> { + /*! @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 +inline constexpr std::size_t value_list_index_v = value_list_index::value; + /** * @brief Concatenates multiple value lists. * @tparam Value Values provided by the first value list. @@ -64790,6 +69365,89 @@ struct value_list_cat> { template using value_list_cat_t = typename value_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = std::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = typename value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : std::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +class value_list_diff, value_list> { + using v141_toolset_workaround = value_list; + +public: + /*! @brief A value list that is the difference between the two value lists. */ + using type = value_list_cat_t, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = typename value_list_diff::type; + /*! @brief Same as std::is_invocable, but with tuples. */ template struct is_applicable: std::false_type {}; @@ -64910,7 +69568,7 @@ inline constexpr bool is_iterator_v = is_iterator::value; */ template struct is_ebco_eligible - : std::conjunction, std::negation>> {}; + : std::bool_constant && !std::is_final_v> {}; /** * @brief Helper variable template. @@ -65001,6 +69659,10 @@ template struct is_equality_comparable() == std::declval())>> : std::bool_constant(choice<2>)> {}; +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: std::false_type {}; + /** * @brief Helper variable template. * @tparam Type The type to test. @@ -65097,6 +69759,18 @@ using nth_argument_t = typename nth_argument::type; } // namespace entt +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::type_list_element> {}; + +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::value_list_element> {}; + #endif @@ -65148,7 +69822,7 @@ class basic_any { }; template - static constexpr bool in_situ = Len && alignof(Type) <= Align && sizeof(Type) <= Len &&std::is_nothrow_move_constructible_v; + static constexpr bool in_situ = Len && alignof(Type) <= Align && sizeof(Type) <= Len && std::is_nothrow_move_constructible_v; template static const void *basic_vtable(const operation op, const basic_any &value, const void *other) { @@ -65217,17 +69891,17 @@ class basic_any { vtable = basic_vtable>>; if constexpr(std::is_lvalue_reference_v) { - static_assert(sizeof...(Args) == 1u && (std::is_lvalue_reference_v && ...), "Invalid arguments"); + static_assert((std::is_lvalue_reference_v && ...) && (sizeof...(Args) == 1u), "Invalid arguments"); mode = std::is_const_v> ? policy::cref : policy::ref; instance = (std::addressof(args), ...); } else if constexpr(in_situ>>) { - if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v>>) { + if constexpr(std::is_aggregate_v>> && (sizeof...(Args) != 0u || !std::is_default_constructible_v>>)) { new(&storage) std::remove_cv_t>{std::forward(args)...}; } else { new(&storage) std::remove_cv_t>(std::forward(args)...); } } else { - if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v>>) { + if constexpr(std::is_aggregate_v>> && (sizeof...(Args) != 0u || !std::is_default_constructible_v>>)) { instance = new std::remove_cv_t>{std::forward(args)...}; } else { instance = new std::remove_cv_t>(std::forward(args)...); @@ -65517,7 +70191,7 @@ private: * @return The element converted to the requested type. */ template -Type any_cast(const basic_any &data) noexcept { +[[nodiscard]] Type any_cast(const basic_any &data) noexcept { const auto *const instance = any_cast>(&data); ENTT_ASSERT(instance, "Invalid instance"); return static_cast(*instance); @@ -65525,7 +70199,7 @@ Type any_cast(const basic_any &data) noexcept { /*! @copydoc any_cast */ template -Type any_cast(basic_any &data) noexcept { +[[nodiscard]] Type any_cast(basic_any &data) noexcept { // forces const on non-reference types to make them work also with wrappers for const references auto *const instance = any_cast>(&data); ENTT_ASSERT(instance, "Invalid instance"); @@ -65534,7 +70208,7 @@ Type any_cast(basic_any &data) noexcept { /*! @copydoc any_cast */ template -Type any_cast(basic_any &&data) noexcept { +[[nodiscard]] Type any_cast(basic_any &&data) noexcept { if constexpr(std::is_copy_constructible_v>>) { if(auto *const instance = any_cast>(&data); instance) { return static_cast(std::move(*instance)); @@ -65550,14 +70224,14 @@ Type any_cast(basic_any &&data) noexcept { /*! @copydoc any_cast */ template -const Type *any_cast(const basic_any *data) noexcept { +[[nodiscard]] const Type *any_cast(const basic_any *data) noexcept { const auto &info = type_id>(); return static_cast(data->data(info)); } /*! @copydoc any_cast */ template -Type *any_cast(basic_any *data) noexcept { +[[nodiscard]] Type *any_cast(basic_any *data) noexcept { if constexpr(std::is_const_v) { // last attempt to make wrappers for const references return their values return any_cast(&std::as_const(*data)); @@ -65577,7 +70251,7 @@ Type *any_cast(basic_any *data) noexcept { * @return A properly initialized wrapper for an object of the given type. */ template::length, std::size_t Align = basic_any::alignment, typename... Args> -basic_any make_any(Args &&...args) { +[[nodiscard]] basic_any make_any(Args &&...args) { return basic_any{std::in_place_type, std::forward(args)...}; } @@ -65590,7 +70264,7 @@ basic_any make_any(Args &&...args) { * @return A properly initialized and not necessarily owning wrapper. */ template::length, std::size_t Align = basic_any::alignment, typename Type> -basic_any forward_as_any(Type &&value) { +[[nodiscard]] basic_any forward_as_any(Type &&value) { return basic_any{std::in_place_type, std::forward(value)}; } @@ -65884,6 +70558,7 @@ template #include #include +#include #include #include // #include "../config/config.h" @@ -65938,7 +70613,6 @@ using type_identity_t = typename type_identity::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 struct size_of: std::integral_constant {}; @@ -66180,7 +70854,8 @@ struct type_list_contains; * @tparam Other Type to look for. */ template -struct type_list_contains, Other>: std::disjunction...> {}; +struct type_list_contains, Other> + : std::bool_constant<(std::is_same_v || ...)> {}; /** * @brief Helper variable template. @@ -66268,10 +70943,20 @@ struct value_list_element> */ template struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = typename value_list_element::type; + /** * @brief Helper type. * @tparam Index Index of the value to return. @@ -66280,6 +70965,58 @@ struct value_list_element<0u, value_list> { template inline constexpr auto value_list_element_v = value_list_element::value; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +struct value_list_index> { + static_assert(value_list_index>::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 +struct value_list_index> { + /*! @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 +inline constexpr std::size_t value_list_index_v = value_list_index::value; + /** * @brief Concatenates multiple value lists. * @tparam Value Values provided by the first value list. @@ -66331,6 +71068,89 @@ struct value_list_cat> { template using value_list_cat_t = typename value_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = std::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = typename value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : std::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +class value_list_diff, value_list> { + using v141_toolset_workaround = value_list; + +public: + /*! @brief A value list that is the difference between the two value lists. */ + using type = value_list_cat_t, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = typename value_list_diff::type; + /*! @brief Same as std::is_invocable, but with tuples. */ template struct is_applicable: std::false_type {}; @@ -66451,7 +71271,7 @@ inline constexpr bool is_iterator_v = is_iterator::value; */ template struct is_ebco_eligible - : std::conjunction, std::negation>> {}; + : std::bool_constant && !std::is_final_v> {}; /** * @brief Helper variable template. @@ -66542,6 +71362,10 @@ template struct is_equality_comparable() == std::declval())>> : std::bool_constant(choice<2>)> {}; +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: std::false_type {}; + /** * @brief Helper variable template. * @tparam Type The type to test. @@ -66638,6 +71462,18 @@ using nth_argument_t = typename nth_argument::type; } // namespace entt +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::type_list_element> {}; + +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::value_list_element> {}; + #endif // #include "fwd.hpp" @@ -66671,7 +71507,7 @@ struct poly_inspector { * @brief Generic conversion operator (definition only). * @tparam Type Type to which conversion is requested. */ - template + template operator Type &&() const; /** @@ -67307,6 +72143,27 @@ struct process_adaptor: process, Delta>, private Fu #include #include #include +// #include "fwd.hpp" +#ifndef ENTT_PROCESS_FWD_HPP +#define ENTT_PROCESS_FWD_HPP + +#include + +namespace entt { + +template +class process; + +template +class basic_scheduler; + +/*! @brief Alias declaration for the most common use case. */ +using scheduler = basic_scheduler<>; + +} // namespace entt + +#endif + // #include "process.hpp" #ifndef ENTT_PROCESS_PROCESS_HPP #define ENTT_PROCESS_PROCESS_HPP @@ -67670,11 +72527,11 @@ namespace entt { * @tparam Delta Type to use to provide elapsed time. */ template -class scheduler { +class basic_scheduler { struct process_handler { using instance_type = std::unique_ptr; - 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; instance_type instance; @@ -67690,8 +72547,8 @@ class scheduler { template continuation then(Args &&...args) { static_assert(std::is_base_of_v, Proc>, "Invalid process type"); - auto proc = typename process_handler::instance_type{new Proc{std::forward(args)...}, &scheduler::deleter}; - handler->next.reset(new process_handler{std::move(proc), &scheduler::update, &scheduler::abort, nullptr}); + auto proc = typename process_handler::instance_type{new Proc{std::forward(args)...}, &basic_scheduler::deleter}; + handler->next.reset(new process_handler{std::move(proc), &basic_scheduler::update, &basic_scheduler::abort, nullptr}); handler = handler->next.get(); return *this; } @@ -67706,7 +72563,7 @@ class scheduler { }; template - [[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(owner.handlers[pos].instance.get()); process->tick(delta, data); @@ -67726,7 +72583,7 @@ class scheduler { } template - 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(owner.handlers[pos].instance.get())->abort(immediately); } @@ -67736,18 +72593,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. @@ -67803,8 +72662,8 @@ public: template auto attach(Args &&...args) { static_assert(std::is_base_of_v, Proc>, "Invalid process type"); - auto proc = typename process_handler::instance_type{new Proc{std::forward(args)...}, &scheduler::deleter}; - auto &&ref = handlers.emplace_back(process_handler{std::move(proc), &scheduler::update, &scheduler::abort, nullptr}); + auto proc = typename process_handler::instance_type{new Proc{std::forward(args)...}, &basic_scheduler::deleter}; + auto &&ref = handlers.emplace_back(process_handler{std::move(proc), &basic_scheduler::update, &basic_scheduler::abort, nullptr}); // forces the process to exit the uninitialized state ref.update(*this, handlers.size() - 1u, {}, nullptr); return continuation{&handlers.back()}; @@ -67878,7 +72737,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; @@ -67958,8 +72817,8 @@ private: #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) \ @@ -68015,6 +72874,8 @@ private: # 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 @@ -68059,6 +72920,7 @@ private: #include #include +#include #include #include // #include "../config/config.h" @@ -68080,8 +72942,8 @@ private: #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) \ @@ -68137,6 +72999,8 @@ private: # 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 @@ -68238,7 +73102,6 @@ using type_identity_t = typename type_identity::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 struct size_of: std::integral_constant {}; @@ -68480,7 +73343,8 @@ struct type_list_contains; * @tparam Other Type to look for. */ template -struct type_list_contains, Other>: std::disjunction...> {}; +struct type_list_contains, Other> + : std::bool_constant<(std::is_same_v || ...)> {}; /** * @brief Helper variable template. @@ -68568,10 +73432,20 @@ struct value_list_element> */ template struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = typename value_list_element::type; + /** * @brief Helper type. * @tparam Index Index of the value to return. @@ -68580,6 +73454,58 @@ struct value_list_element<0u, value_list> { template inline constexpr auto value_list_element_v = value_list_element::value; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +struct value_list_index> { + static_assert(value_list_index>::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 +struct value_list_index> { + /*! @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 +inline constexpr std::size_t value_list_index_v = value_list_index::value; + /** * @brief Concatenates multiple value lists. * @tparam Value Values provided by the first value list. @@ -68631,6 +73557,89 @@ struct value_list_cat> { template using value_list_cat_t = typename value_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = std::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = typename value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : std::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +class value_list_diff, value_list> { + using v141_toolset_workaround = value_list; + +public: + /*! @brief A value list that is the difference between the two value lists. */ + using type = value_list_cat_t, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = typename value_list_diff::type; + /*! @brief Same as std::is_invocable, but with tuples. */ template struct is_applicable: std::false_type {}; @@ -68751,7 +73760,7 @@ inline constexpr bool is_iterator_v = is_iterator::value; */ template struct is_ebco_eligible - : std::conjunction, std::negation>> {}; + : std::bool_constant && !std::is_final_v> {}; /** * @brief Helper variable template. @@ -68842,6 +73851,10 @@ template struct is_equality_comparable() == std::declval())>> : std::bool_constant(choice<2>)> {}; +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: std::false_type {}; + /** * @brief Helper variable template. * @tparam Type The type to test. @@ -68938,6 +73951,18 @@ using nth_argument_t = typename nth_argument::type; } // namespace entt +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::type_list_element> {}; + +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::value_list_element> {}; + #endif @@ -69521,7 +74546,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 struct allocation_deleter: private Allocator { @@ -69542,7 +74567,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) { - using alloc_traits = typename std::allocator_traits; + using alloc_traits = std::allocator_traits; alloc_traits::destroy(*this, to_address(ptr)); alloc_traits::deallocate(*this, ptr, 1u); } @@ -69709,6 +74734,7 @@ constexpr Type *uninitialized_construct_using_allocator(Type *value, const Alloc #include #include +#include #include #include // #include "../config/config.h" @@ -69763,7 +74789,6 @@ using type_identity_t = typename type_identity::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 struct size_of: std::integral_constant {}; @@ -70005,7 +75030,8 @@ struct type_list_contains; * @tparam Other Type to look for. */ template -struct type_list_contains, Other>: std::disjunction...> {}; +struct type_list_contains, Other> + : std::bool_constant<(std::is_same_v || ...)> {}; /** * @brief Helper variable template. @@ -70093,10 +75119,20 @@ struct value_list_element> */ template struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = typename value_list_element::type; + /** * @brief Helper type. * @tparam Index Index of the value to return. @@ -70105,6 +75141,58 @@ struct value_list_element<0u, value_list> { template inline constexpr auto value_list_element_v = value_list_element::value; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +struct value_list_index> { + static_assert(value_list_index>::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 +struct value_list_index> { + /*! @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 +inline constexpr std::size_t value_list_index_v = value_list_index::value; + /** * @brief Concatenates multiple value lists. * @tparam Value Values provided by the first value list. @@ -70156,6 +75244,89 @@ struct value_list_cat> { template using value_list_cat_t = typename value_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = std::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = typename value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : std::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +class value_list_diff, value_list> { + using v141_toolset_workaround = value_list; + +public: + /*! @brief A value list that is the difference between the two value lists. */ + using type = value_list_cat_t, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = typename value_list_diff::type; + /*! @brief Same as std::is_invocable, but with tuples. */ template struct is_applicable: std::false_type {}; @@ -70276,7 +75447,7 @@ inline constexpr bool is_iterator_v = is_iterator::value; */ template struct is_ebco_eligible - : std::conjunction, std::negation>> {}; + : std::bool_constant && !std::is_final_v> {}; /** * @brief Helper variable template. @@ -70367,6 +75538,10 @@ template struct is_equality_comparable() == std::declval())>> : std::bool_constant(choice<2>)> {}; +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: std::false_type {}; + /** * @brief Helper variable template. * @tparam Type The type to test. @@ -70463,6 +75638,18 @@ using nth_argument_t = typename nth_argument::type; } // namespace entt +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::type_list_element> {}; + +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::value_list_element> {}; + #endif // #include "fwd.hpp" @@ -70604,51 +75791,51 @@ public: return {it->element.first, it->element.second}; } - template - friend constexpr std::ptrdiff_t operator-(const dense_map_iterator &, const dense_map_iterator &) noexcept; + template + friend constexpr std::ptrdiff_t operator-(const dense_map_iterator &, const dense_map_iterator &) noexcept; - template - friend constexpr bool operator==(const dense_map_iterator &, const dense_map_iterator &) noexcept; + template + friend constexpr bool operator==(const dense_map_iterator &, const dense_map_iterator &) noexcept; - template - friend constexpr bool operator<(const dense_map_iterator &, const dense_map_iterator &) noexcept; + template + friend constexpr bool operator<(const dense_map_iterator &, const dense_map_iterator &) noexcept; private: It it; }; -template -[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return lhs.it - rhs.it; } -template -[[nodiscard]] constexpr bool operator==(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return lhs.it == rhs.it; } -template -[[nodiscard]] constexpr bool operator!=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return !(lhs == rhs); } -template -[[nodiscard]] constexpr bool operator<(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return lhs.it < rhs.it; } -template -[[nodiscard]] constexpr bool operator>(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return rhs < lhs; } -template -[[nodiscard]] constexpr bool operator<=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return !(lhs > rhs); } -template -[[nodiscard]] constexpr bool operator>=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return !(lhs < rhs); } @@ -70706,13 +75893,13 @@ private: std::size_t offset; }; -template -[[nodiscard]] constexpr bool operator==(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { return lhs.index() == rhs.index(); } -template -[[nodiscard]] constexpr bool operator!=(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { return !(lhs == rhs); } @@ -70742,7 +75929,7 @@ class dense_map { static constexpr std::size_t minimum_capacity = 8u; using node_type = internal::dense_map_node; - using alloc_traits = typename std::allocator_traits; + using alloc_traits = std::allocator_traits; static_assert(std::is_same_v>, "Invalid value type"); using sparse_container_type = std::vector>; using packed_container_type = std::vector>; @@ -70940,7 +76127,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. @@ -70961,11 +76147,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. */ @@ -71314,7 +76495,7 @@ public: } /*! @copydoc equal_range */ - template + template [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t>> equal_range(const Other &key) const { const auto it = find(key); @@ -71549,6 +76730,7 @@ struct uses_allocator, Allocator> #include #include +#include #include #include // #include "../config/config.h" @@ -71570,8 +76752,8 @@ struct uses_allocator, Allocator> #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) \ @@ -71627,6 +76809,8 @@ struct uses_allocator, Allocator> # 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 @@ -71728,7 +76912,6 @@ using type_identity_t = typename type_identity::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 struct size_of: std::integral_constant {}; @@ -71970,7 +77153,8 @@ struct type_list_contains; * @tparam Other Type to look for. */ template -struct type_list_contains, Other>: std::disjunction...> {}; +struct type_list_contains, Other> + : std::bool_constant<(std::is_same_v || ...)> {}; /** * @brief Helper variable template. @@ -72058,10 +77242,20 @@ struct value_list_element> */ template struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = typename value_list_element::type; + /** * @brief Helper type. * @tparam Index Index of the value to return. @@ -72070,6 +77264,58 @@ struct value_list_element<0u, value_list> { template inline constexpr auto value_list_element_v = value_list_element::value; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +struct value_list_index> { + static_assert(value_list_index>::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 +struct value_list_index> { + /*! @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 +inline constexpr std::size_t value_list_index_v = value_list_index::value; + /** * @brief Concatenates multiple value lists. * @tparam Value Values provided by the first value list. @@ -72121,6 +77367,89 @@ struct value_list_cat> { template using value_list_cat_t = typename value_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = std::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = typename value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : std::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +class value_list_diff, value_list> { + using v141_toolset_workaround = value_list; + +public: + /*! @brief A value list that is the difference between the two value lists. */ + using type = value_list_cat_t, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = typename value_list_diff::type; + /*! @brief Same as std::is_invocable, but with tuples. */ template struct is_applicable: std::false_type {}; @@ -72241,7 +77570,7 @@ inline constexpr bool is_iterator_v = is_iterator::value; */ template struct is_ebco_eligible - : std::conjunction, std::negation>> {}; + : std::bool_constant && !std::is_final_v> {}; /** * @brief Helper variable template. @@ -72332,6 +77661,10 @@ template struct is_equality_comparable() == std::declval())>> : std::bool_constant(choice<2>)> {}; +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: std::false_type {}; + /** * @brief Helper variable template. * @tparam Type The type to test. @@ -72428,6 +77761,18 @@ using nth_argument_t = typename nth_argument::type; } // namespace entt +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::type_list_element> {}; + +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::value_list_element> {}; + #endif @@ -72944,7 +78289,7 @@ struct identity { * @param value The actual argument. * @return The submitted value as-is. */ - template + template [[nodiscard]] constexpr Type &&operator()(Type &&value) const noexcept { return std::forward(value); } @@ -72977,7 +78322,7 @@ template * @brief Helper type for visitors. * @tparam Func Types of function objects. */ -template +template struct overloaded: Func... { using Func::operator()...; }; @@ -72986,14 +78331,14 @@ struct overloaded: Func... { * @brief Deduction guide. * @tparam Func Types of function objects. */ -template +template overloaded(Func...) -> overloaded; /** * @brief Basic implementation of a y-combinator. * @tparam Func Type of a potentially recursive function. */ -template +template struct y_combinator { /** * @brief Constructs a y-combinator from a given function. @@ -73008,13 +78353,13 @@ struct y_combinator { * @param args Parameters to use to invoke the underlying function. * @return Return value of the underlying function, if any. */ - template + template constexpr decltype(auto) operator()(Args &&...args) const noexcept(std::is_nothrow_invocable_v) { return func(*this, std::forward(args)...); } /*! @copydoc operator()() */ - template + template constexpr decltype(auto) operator()(Args &&...args) noexcept(std::is_nothrow_invocable_v) { return func(*this, std::forward(args)...); } @@ -73251,81 +78596,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 -[[nodiscard]] bool operator==(const resource &lhs, const resource &rhs) noexcept { +template +[[nodiscard]] bool operator==(const resource &lhs, const resource &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 -[[nodiscard]] bool operator!=(const resource &lhs, const resource &rhs) noexcept { +template +[[nodiscard]] bool operator!=(const resource &lhs, const resource &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 -[[nodiscard]] bool operator<(const resource &lhs, const resource &rhs) noexcept { +template +[[nodiscard]] bool operator<(const resource &lhs, const resource &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 -[[nodiscard]] bool operator>(const resource &lhs, const resource &rhs) noexcept { - return (std::addressof(*lhs) > std::addressof(*rhs)); +template +[[nodiscard]] bool operator>(const resource &lhs, const resource &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 -[[nodiscard]] bool operator<=(const resource &lhs, const resource &rhs) noexcept { +template +[[nodiscard]] bool operator<=(const resource &lhs, const resource &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 -[[nodiscard]] bool operator>=(const resource &lhs, const resource &rhs) noexcept { +template +[[nodiscard]] bool operator>=(const resource &lhs, const resource &rhs) noexcept { return !(lhs < rhs); } @@ -73412,51 +78757,51 @@ public: return operator*(); } - template - friend constexpr std::ptrdiff_t operator-(const resource_cache_iterator &, const resource_cache_iterator &) noexcept; + template + friend constexpr std::ptrdiff_t operator-(const resource_cache_iterator &, const resource_cache_iterator &) noexcept; - template - friend constexpr bool operator==(const resource_cache_iterator &, const resource_cache_iterator &) noexcept; + template + friend constexpr bool operator==(const resource_cache_iterator &, const resource_cache_iterator &) noexcept; - template - friend constexpr bool operator<(const resource_cache_iterator &, const resource_cache_iterator &) noexcept; + template + friend constexpr bool operator<(const resource_cache_iterator &, const resource_cache_iterator &) noexcept; private: It it; }; -template -[[nodiscard]] constexpr std::ptrdiff_t operator-(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr std::ptrdiff_t operator-(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) noexcept { return lhs.it - rhs.it; } -template -[[nodiscard]] constexpr bool operator==(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) noexcept { return lhs.it == rhs.it; } -template -[[nodiscard]] constexpr bool operator!=(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) noexcept { return !(lhs == rhs); } -template -[[nodiscard]] constexpr bool operator<(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) noexcept { return lhs.it < rhs.it; } -template -[[nodiscard]] constexpr bool operator>(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) noexcept { return rhs < lhs; } -template -[[nodiscard]] constexpr bool operator<=(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<=(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) noexcept { return !(lhs > rhs); } -template -[[nodiscard]] constexpr bool operator>=(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>=(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) noexcept { return !(lhs < rhs); } @@ -73475,7 +78820,7 @@ template */ template class resource_cache { - using alloc_traits = typename std::allocator_traits; + using alloc_traits = std::allocator_traits; static_assert(std::is_same_v, "Invalid value type"); using container_allocator = typename alloc_traits::template rebind_alloc>; using container_type = dense_map, container_allocator>; @@ -73558,8 +78903,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. */ @@ -73579,11 +78923,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. */ @@ -73942,81 +79281,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 -[[nodiscard]] bool operator==(const resource &lhs, const resource &rhs) noexcept { +template +[[nodiscard]] bool operator==(const resource &lhs, const resource &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 -[[nodiscard]] bool operator!=(const resource &lhs, const resource &rhs) noexcept { +template +[[nodiscard]] bool operator!=(const resource &lhs, const resource &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 -[[nodiscard]] bool operator<(const resource &lhs, const resource &rhs) noexcept { +template +[[nodiscard]] bool operator<(const resource &lhs, const resource &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 -[[nodiscard]] bool operator>(const resource &lhs, const resource &rhs) noexcept { - return (std::addressof(*lhs) > std::addressof(*rhs)); +template +[[nodiscard]] bool operator>(const resource &lhs, const resource &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 -[[nodiscard]] bool operator<=(const resource &lhs, const resource &rhs) noexcept { +template +[[nodiscard]] bool operator<=(const resource &lhs, const resource &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 -[[nodiscard]] bool operator>=(const resource &lhs, const resource &rhs) noexcept { +template +[[nodiscard]] bool operator>=(const resource &lhs, const resource &rhs) noexcept { return !(lhs < rhs); } @@ -74052,8 +79391,8 @@ template #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) \ @@ -74109,6 +79448,8 @@ template # 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 @@ -74145,6 +79486,7 @@ template #include #include +#include #include #include // #include "../config/config.h" @@ -74166,8 +79508,8 @@ template #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) \ @@ -74223,6 +79565,8 @@ template # 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 @@ -74324,7 +79668,6 @@ using type_identity_t = typename type_identity::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 struct size_of: std::integral_constant {}; @@ -74566,7 +79909,8 @@ struct type_list_contains; * @tparam Other Type to look for. */ template -struct type_list_contains, Other>: std::disjunction...> {}; +struct type_list_contains, Other> + : std::bool_constant<(std::is_same_v || ...)> {}; /** * @brief Helper variable template. @@ -74654,10 +79998,20 @@ struct value_list_element> */ template struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = typename value_list_element::type; + /** * @brief Helper type. * @tparam Index Index of the value to return. @@ -74666,6 +80020,58 @@ struct value_list_element<0u, value_list> { template inline constexpr auto value_list_element_v = value_list_element::value; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +struct value_list_index> { + static_assert(value_list_index>::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 +struct value_list_index> { + /*! @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 +inline constexpr std::size_t value_list_index_v = value_list_index::value; + /** * @brief Concatenates multiple value lists. * @tparam Value Values provided by the first value list. @@ -74717,6 +80123,89 @@ struct value_list_cat> { template using value_list_cat_t = typename value_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = std::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = typename value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : std::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +class value_list_diff, value_list> { + using v141_toolset_workaround = value_list; + +public: + /*! @brief A value list that is the difference between the two value lists. */ + using type = value_list_cat_t, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = typename value_list_diff::type; + /*! @brief Same as std::is_invocable, but with tuples. */ template struct is_applicable: std::false_type {}; @@ -74837,7 +80326,7 @@ inline constexpr bool is_iterator_v = is_iterator::value; */ template struct is_ebco_eligible - : std::conjunction, std::negation>> {}; + : std::bool_constant && !std::is_final_v> {}; /** * @brief Helper variable template. @@ -74928,6 +80417,10 @@ template struct is_equality_comparable() == std::declval())>> : std::bool_constant(choice<2>)> {}; +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: std::false_type {}; + /** * @brief Helper variable template. * @tparam Type The type to test. @@ -75024,6 +80517,18 @@ using nth_argument_t = typename nth_argument::type; } // namespace entt +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::type_list_element> {}; + +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::value_list_element> {}; + #endif // #include "fwd.hpp" @@ -75141,7 +80646,13 @@ class delegate { [[nodiscard]] auto wrap(std::index_sequence) noexcept { return [](const void *, Args... args) -> Ret { [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); - return static_cast(std::invoke(Candidate, std::forward>>(std::get(arguments))...)); + + if constexpr(std::is_invocable_r_v>...>) { + return static_cast(std::invoke(Candidate, std::forward>>(std::get(arguments))...)); + } else { + constexpr auto offset = sizeof...(Args) - sizeof...(Index); + return static_cast(std::invoke(Candidate, std::forward>>(std::get(arguments))...)); + } }; } @@ -75150,7 +80661,13 @@ class delegate { return [](const void *payload, Args... args) -> Ret { [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); Type *curr = static_cast(const_cast *>(payload)); - return static_cast(std::invoke(Candidate, *curr, std::forward>>(std::get(arguments))...)); + + if constexpr(std::is_invocable_r_v>...>) { + return static_cast(std::invoke(Candidate, *curr, std::forward>>(std::get(arguments))...)); + } else { + constexpr auto offset = sizeof...(Args) - sizeof...(Index); + return static_cast(std::invoke(Candidate, *curr, std::forward>>(std::get(arguments))...)); + } }; } @@ -75159,7 +80676,13 @@ class delegate { return [](const void *payload, Args... args) -> Ret { [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); Type *curr = static_cast(const_cast *>(payload)); - return static_cast(std::invoke(Candidate, curr, std::forward>>(std::get(arguments))...)); + + if constexpr(std::is_invocable_r_v>...>) { + return static_cast(std::invoke(Candidate, curr, std::forward>>(std::get(arguments))...)); + } else { + constexpr auto offset = sizeof...(Args) - sizeof...(Index); + return static_cast(std::invoke(Candidate, curr, std::forward>>(std::get(arguments))...)); + } }; } @@ -75298,6 +80821,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. @@ -75429,8 +80960,8 @@ delegate(Ret (*)(const void *, Args...), const void * = nullptr) -> delegate delegate delegate #include +#include #include #include // #include "../config/config.h" @@ -75551,8 +81085,8 @@ delegate(Ret (*)(const void *, Args...), const void * = nullptr) -> delegate delegate::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 struct size_of: std::integral_constant {}; @@ -75951,7 +81486,8 @@ struct type_list_contains; * @tparam Other Type to look for. */ template -struct type_list_contains, Other>: std::disjunction...> {}; +struct type_list_contains, Other> + : std::bool_constant<(std::is_same_v || ...)> {}; /** * @brief Helper variable template. @@ -76039,10 +81575,20 @@ struct value_list_element> */ template struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = typename value_list_element::type; + /** * @brief Helper type. * @tparam Index Index of the value to return. @@ -76051,6 +81597,58 @@ struct value_list_element<0u, value_list> { template inline constexpr auto value_list_element_v = value_list_element::value; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +struct value_list_index> { + static_assert(value_list_index>::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 +struct value_list_index> { + /*! @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 +inline constexpr std::size_t value_list_index_v = value_list_index::value; + /** * @brief Concatenates multiple value lists. * @tparam Value Values provided by the first value list. @@ -76102,6 +81700,89 @@ struct value_list_cat> { template using value_list_cat_t = typename value_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = std::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = typename value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : std::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +class value_list_diff, value_list> { + using v141_toolset_workaround = value_list; + +public: + /*! @brief A value list that is the difference between the two value lists. */ + using type = value_list_cat_t, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = typename value_list_diff::type; + /*! @brief Same as std::is_invocable, but with tuples. */ template struct is_applicable: std::false_type {}; @@ -76222,7 +81903,7 @@ inline constexpr bool is_iterator_v = is_iterator::value; */ template struct is_ebco_eligible - : std::conjunction, std::negation>> {}; + : std::bool_constant && !std::is_final_v> {}; /** * @brief Helper variable template. @@ -76313,6 +81994,10 @@ template struct is_equality_comparable() == std::declval())>> : std::bool_constant(choice<2>)> {}; +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: std::false_type {}; + /** * @brief Helper variable template. * @tparam Type The type to test. @@ -76409,6 +82094,18 @@ using nth_argument_t = typename nth_argument::type; } // namespace entt +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::type_list_element> {}; + +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::value_list_element> {}; + #endif @@ -76992,7 +82689,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 struct allocation_deleter: private Allocator { @@ -77013,7 +82710,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) { - using alloc_traits = typename std::allocator_traits; + using alloc_traits = std::allocator_traits; alloc_traits::destroy(*this, to_address(ptr)); alloc_traits::deallocate(*this, ptr, 1u); } @@ -77180,6 +82877,7 @@ constexpr Type *uninitialized_construct_using_allocator(Type *value, const Alloc #include #include +#include #include #include // #include "../config/config.h" @@ -77234,7 +82932,6 @@ using type_identity_t = typename type_identity::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 struct size_of: std::integral_constant {}; @@ -77476,7 +83173,8 @@ struct type_list_contains; * @tparam Other Type to look for. */ template -struct type_list_contains, Other>: std::disjunction...> {}; +struct type_list_contains, Other> + : std::bool_constant<(std::is_same_v || ...)> {}; /** * @brief Helper variable template. @@ -77564,10 +83262,20 @@ struct value_list_element> */ template struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = typename value_list_element::type; + /** * @brief Helper type. * @tparam Index Index of the value to return. @@ -77576,6 +83284,58 @@ struct value_list_element<0u, value_list> { template inline constexpr auto value_list_element_v = value_list_element::value; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +struct value_list_index> { + static_assert(value_list_index>::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 +struct value_list_index> { + /*! @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 +inline constexpr std::size_t value_list_index_v = value_list_index::value; + /** * @brief Concatenates multiple value lists. * @tparam Value Values provided by the first value list. @@ -77627,6 +83387,89 @@ struct value_list_cat> { template using value_list_cat_t = typename value_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = std::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = typename value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : std::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +class value_list_diff, value_list> { + using v141_toolset_workaround = value_list; + +public: + /*! @brief A value list that is the difference between the two value lists. */ + using type = value_list_cat_t, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = typename value_list_diff::type; + /*! @brief Same as std::is_invocable, but with tuples. */ template struct is_applicable: std::false_type {}; @@ -77747,7 +83590,7 @@ inline constexpr bool is_iterator_v = is_iterator::value; */ template struct is_ebco_eligible - : std::conjunction, std::negation>> {}; + : std::bool_constant && !std::is_final_v> {}; /** * @brief Helper variable template. @@ -77838,6 +83681,10 @@ template struct is_equality_comparable() == std::declval())>> : std::bool_constant(choice<2>)> {}; +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: std::false_type {}; + /** * @brief Helper variable template. * @tparam Type The type to test. @@ -77934,6 +83781,18 @@ using nth_argument_t = typename nth_argument::type; } // namespace entt +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::type_list_element> {}; + +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::value_list_element> {}; + #endif // #include "fwd.hpp" @@ -78075,51 +83934,51 @@ public: return {it->element.first, it->element.second}; } - template - friend constexpr std::ptrdiff_t operator-(const dense_map_iterator &, const dense_map_iterator &) noexcept; + template + friend constexpr std::ptrdiff_t operator-(const dense_map_iterator &, const dense_map_iterator &) noexcept; - template - friend constexpr bool operator==(const dense_map_iterator &, const dense_map_iterator &) noexcept; + template + friend constexpr bool operator==(const dense_map_iterator &, const dense_map_iterator &) noexcept; - template - friend constexpr bool operator<(const dense_map_iterator &, const dense_map_iterator &) noexcept; + template + friend constexpr bool operator<(const dense_map_iterator &, const dense_map_iterator &) noexcept; private: It it; }; -template -[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return lhs.it - rhs.it; } -template -[[nodiscard]] constexpr bool operator==(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return lhs.it == rhs.it; } -template -[[nodiscard]] constexpr bool operator!=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return !(lhs == rhs); } -template -[[nodiscard]] constexpr bool operator<(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return lhs.it < rhs.it; } -template -[[nodiscard]] constexpr bool operator>(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return rhs < lhs; } -template -[[nodiscard]] constexpr bool operator<=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return !(lhs > rhs); } -template -[[nodiscard]] constexpr bool operator>=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return !(lhs < rhs); } @@ -78177,13 +84036,13 @@ private: std::size_t offset; }; -template -[[nodiscard]] constexpr bool operator==(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { return lhs.index() == rhs.index(); } -template -[[nodiscard]] constexpr bool operator!=(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { return !(lhs == rhs); } @@ -78213,7 +84072,7 @@ class dense_map { static constexpr std::size_t minimum_capacity = 8u; using node_type = internal::dense_map_node; - using alloc_traits = typename std::allocator_traits; + using alloc_traits = std::allocator_traits; static_assert(std::is_same_v>, "Invalid value type"); using sparse_container_type = std::vector>; using packed_container_type = std::vector>; @@ -78411,7 +84270,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. @@ -78432,11 +84290,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. */ @@ -78785,7 +84638,7 @@ public: } /*! @copydoc equal_range */ - template + template [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t>> equal_range(const Other &key) const { const auto it = find(key); @@ -79020,6 +84873,7 @@ struct uses_allocator, Allocator> #include #include +#include #include #include // #include "../config/config.h" @@ -79074,7 +84928,6 @@ using type_identity_t = typename type_identity::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 struct size_of: std::integral_constant {}; @@ -79316,7 +85169,8 @@ struct type_list_contains; * @tparam Other Type to look for. */ template -struct type_list_contains, Other>: std::disjunction...> {}; +struct type_list_contains, Other> + : std::bool_constant<(std::is_same_v || ...)> {}; /** * @brief Helper variable template. @@ -79404,10 +85258,20 @@ struct value_list_element> */ template struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = typename value_list_element::type; + /** * @brief Helper type. * @tparam Index Index of the value to return. @@ -79416,6 +85280,58 @@ struct value_list_element<0u, value_list> { template inline constexpr auto value_list_element_v = value_list_element::value; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +struct value_list_index> { + static_assert(value_list_index>::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 +struct value_list_index> { + /*! @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 +inline constexpr std::size_t value_list_index_v = value_list_index::value; + /** * @brief Concatenates multiple value lists. * @tparam Value Values provided by the first value list. @@ -79467,6 +85383,89 @@ struct value_list_cat> { template using value_list_cat_t = typename value_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = std::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = typename value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : std::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +class value_list_diff, value_list> { + using v141_toolset_workaround = value_list; + +public: + /*! @brief A value list that is the difference between the two value lists. */ + using type = value_list_cat_t, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = typename value_list_diff::type; + /*! @brief Same as std::is_invocable, but with tuples. */ template struct is_applicable: std::false_type {}; @@ -79587,7 +85586,7 @@ inline constexpr bool is_iterator_v = is_iterator::value; */ template struct is_ebco_eligible - : std::conjunction, std::negation>> {}; + : std::bool_constant && !std::is_final_v> {}; /** * @brief Helper variable template. @@ -79678,6 +85677,10 @@ template struct is_equality_comparable() == std::declval())>> : std::bool_constant(choice<2>)> {}; +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: std::false_type {}; + /** * @brief Helper variable template. * @tparam Type The type to test. @@ -79774,6 +85777,18 @@ using nth_argument_t = typename nth_argument::type; } // namespace entt +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::type_list_element> {}; + +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::value_list_element> {}; + #endif @@ -80185,7 +86200,7 @@ struct basic_hashed_string { template class basic_hashed_string: internal::basic_hashed_string { using base_type = internal::basic_hashed_string; - using hs_traits = internal::fnv1a_traits; + using traits_type = internal::fnv1a_traits; struct const_wrapper { // non-explicit constructor on purpose @@ -80197,10 +86212,10 @@ class basic_hashed_string: internal::basic_hashed_string { // 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(str[base.length])) * hs_traits::prime; + base.hash = (base.hash ^ static_cast(str[base.length])) * traits_type::prime; } return base; @@ -80208,10 +86223,10 @@ class basic_hashed_string: internal::basic_hashed_string { // 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(str[pos])) * hs_traits::prime; + base.hash = (base.hash ^ static_cast(str[pos])) * traits_type::prime; } return base; @@ -80736,7 +86751,7 @@ struct identity { * @param value The actual argument. * @return The submitted value as-is. */ - template + template [[nodiscard]] constexpr Type &&operator()(Type &&value) const noexcept { return std::forward(value); } @@ -80769,7 +86784,7 @@ template * @brief Helper type for visitors. * @tparam Func Types of function objects. */ -template +template struct overloaded: Func... { using Func::operator()...; }; @@ -80778,14 +86793,14 @@ struct overloaded: Func... { * @brief Deduction guide. * @tparam Func Types of function objects. */ -template +template overloaded(Func...) -> overloaded; /** * @brief Basic implementation of a y-combinator. * @tparam Func Type of a potentially recursive function. */ -template +template struct y_combinator { /** * @brief Constructs a y-combinator from a given function. @@ -80800,13 +86815,13 @@ struct y_combinator { * @param args Parameters to use to invoke the underlying function. * @return Return value of the underlying function, if any. */ - template + template constexpr decltype(auto) operator()(Args &&...args) const noexcept(std::is_nothrow_invocable_v) { return func(*this, std::forward(args)...); } /*! @copydoc operator()() */ - template + template constexpr decltype(auto) operator()(Args &&...args) noexcept(std::is_nothrow_invocable_v) { return func(*this, std::forward(args)...); } @@ -80825,8 +86840,8 @@ private: #ifndef ENTT_SIGNAL_SIGH_HPP #define ENTT_SIGNAL_SIGH_HPP -#include -#include +#include +#include #include #include #include @@ -80912,7 +86927,13 @@ class delegate { [[nodiscard]] auto wrap(std::index_sequence) noexcept { return [](const void *, Args... args) -> Ret { [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); - return static_cast(std::invoke(Candidate, std::forward>>(std::get(arguments))...)); + + if constexpr(std::is_invocable_r_v>...>) { + return static_cast(std::invoke(Candidate, std::forward>>(std::get(arguments))...)); + } else { + constexpr auto offset = sizeof...(Args) - sizeof...(Index); + return static_cast(std::invoke(Candidate, std::forward>>(std::get(arguments))...)); + } }; } @@ -80921,7 +86942,13 @@ class delegate { return [](const void *payload, Args... args) -> Ret { [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); Type *curr = static_cast(const_cast *>(payload)); - return static_cast(std::invoke(Candidate, *curr, std::forward>>(std::get(arguments))...)); + + if constexpr(std::is_invocable_r_v>...>) { + return static_cast(std::invoke(Candidate, *curr, std::forward>>(std::get(arguments))...)); + } else { + constexpr auto offset = sizeof...(Args) - sizeof...(Index); + return static_cast(std::invoke(Candidate, *curr, std::forward>>(std::get(arguments))...)); + } }; } @@ -80930,7 +86957,13 @@ class delegate { return [](const void *payload, Args... args) -> Ret { [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); Type *curr = static_cast(const_cast *>(payload)); - return static_cast(std::invoke(Candidate, curr, std::forward>>(std::get(arguments))...)); + + if constexpr(std::is_invocable_r_v>...>) { + return static_cast(std::invoke(Candidate, curr, std::forward>>(std::get(arguments))...)); + } else { + constexpr auto offset = sizeof...(Args) - sizeof...(Index); + return static_cast(std::invoke(Candidate, curr, std::forward>>(std::get(arguments))...)); + } }; } @@ -81069,6 +87102,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. @@ -81207,7 +87248,8 @@ class sigh { friend class sink>; using alloc_traits = std::allocator_traits; - using container_type = std::vector, typename alloc_traits::template rebind_alloc>>; + using delegate_type = delegate; + using container_type = std::vector>; public: /*! @brief Allocator type. */ @@ -81319,8 +87361,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...); } } @@ -81340,24 +87382,24 @@ public: */ template void collect(Func func, Args... args) const { - for(auto &&call: calls) { - if constexpr(std::is_void_v) { + for(auto pos = calls.size(); pos; --pos) { + if constexpr(std::is_void_v || !std::is_invocable_v) { + calls[pos - 1u](args...); + if constexpr(std::is_invocable_r_v) { - call(args...); if(func()) { break; } } else { - call(args...); func(); } } else { if constexpr(std::is_invocable_r_v) { - if(func(call(args...))) { + if(func(calls[pos - 1u](args...))) { break; } } else { - func(call(args...)); + func(calls[pos - 1u](args...)); } } } @@ -81509,6 +87551,7 @@ private: template class sink> { using signal_type = sigh; + using delegate_type = typename signal_type::delegate_type; using difference_type = typename signal_type::container_type::difference_type; template @@ -81521,13 +87564,14 @@ class sink> { sink{*static_cast(signal)}.disconnect(); } - auto before(delegate 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 + 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: @@ -81536,8 +87580,7 @@ public: * @param ref A valid reference to a signal object. */ sink(sigh &ref) noexcept - : offset{}, - signal{&ref} {} + : signal{&ref} {} /** * @brief Returns false if at least a listener is connected to the sink. @@ -81547,89 +87590,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 - [[nodiscard]] sink before() { - delegate call{}; - call.template connect(); - 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 - [[nodiscard]] sink before(Type &&value_or_instance) { - delegate call{}; - call.template connect(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>, 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.
- * 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. @@ -81639,9 +87602,9 @@ public: connection connect(Type &&...value_or_instance) { disconnect(value_or_instance...); - delegate call{}; + delegate_type call{}; call.template connect(value_or_instance...); - signal->calls.insert(signal->calls.end() - offset, std::move(call)); + signal->calls.push_back(std::move(call)); delegate conn{}; conn.template connect<&release>(value_or_instance...); @@ -81657,21 +87620,9 @@ public: */ template void disconnect(Type &&...value_or_instance) { - auto &calls = signal->calls; - delegate call{}; + delegate_type call{}; call.template connect(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>, void>>> - void disconnect(Type &value_or_instance) { - disconnect(&value_or_instance); + disconnect_if([&call](const auto &elem) { return elem == call; }); } /** @@ -81681,9 +87632,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; }); } } @@ -81693,7 +87642,6 @@ public: } private: - difference_type offset; signal_type *signal; }; @@ -81775,7 +87723,7 @@ public: template void enqueue(Args &&...args) { - if constexpr(std::is_aggregate_v) { + if constexpr(std::is_aggregate_v && (sizeof...(Args) != 0u || !std::is_default_constructible_v)) { events.push_back(Type{std::forward(args)...}); } else { events.emplace_back(std::forward(args)...); @@ -81879,7 +87827,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. @@ -81887,6 +87837,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; } @@ -82174,7 +88126,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. @@ -82182,6 +88136,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; } @@ -82271,8 +88227,8 @@ private: #ifndef ENTT_SIGNAL_SIGH_HPP #define ENTT_SIGNAL_SIGH_HPP -#include -#include +#include +#include #include #include #include @@ -82328,7 +88284,8 @@ class sigh { friend class sink>; using alloc_traits = std::allocator_traits; - using container_type = std::vector, typename alloc_traits::template rebind_alloc>>; + using delegate_type = delegate; + using container_type = std::vector>; public: /*! @brief Allocator type. */ @@ -82440,8 +88397,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...); } } @@ -82461,24 +88418,24 @@ public: */ template void collect(Func func, Args... args) const { - for(auto &&call: calls) { - if constexpr(std::is_void_v) { + for(auto pos = calls.size(); pos; --pos) { + if constexpr(std::is_void_v || !std::is_invocable_v) { + calls[pos - 1u](args...); + if constexpr(std::is_invocable_r_v) { - call(args...); if(func()) { break; } } else { - call(args...); func(); } } else { if constexpr(std::is_invocable_r_v) { - if(func(call(args...))) { + if(func(calls[pos - 1u](args...))) { break; } } else { - func(call(args...)); + func(calls[pos - 1u](args...)); } } } @@ -82630,6 +88587,7 @@ private: template class sink> { using signal_type = sigh; + using delegate_type = typename signal_type::delegate_type; using difference_type = typename signal_type::container_type::difference_type; template @@ -82642,13 +88600,14 @@ class sink> { sink{*static_cast(signal)}.disconnect(); } - auto before(delegate 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 + 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: @@ -82657,8 +88616,7 @@ public: * @param ref A valid reference to a signal object. */ sink(sigh &ref) noexcept - : offset{}, - signal{&ref} {} + : signal{&ref} {} /** * @brief Returns false if at least a listener is connected to the sink. @@ -82668,89 +88626,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 - [[nodiscard]] sink before() { - delegate call{}; - call.template connect(); - 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 - [[nodiscard]] sink before(Type &&value_or_instance) { - delegate call{}; - call.template connect(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>, 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.
- * 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. @@ -82760,9 +88638,9 @@ public: connection connect(Type &&...value_or_instance) { disconnect(value_or_instance...); - delegate call{}; + delegate_type call{}; call.template connect(value_or_instance...); - signal->calls.insert(signal->calls.end() - offset, std::move(call)); + signal->calls.push_back(std::move(call)); delegate conn{}; conn.template connect<&release>(value_or_instance...); @@ -82778,21 +88656,9 @@ public: */ template void disconnect(Type &&...value_or_instance) { - auto &calls = signal->calls; - delegate call{}; + delegate_type call{}; call.template connect(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>, void>>> - void disconnect(Type &value_or_instance) { - disconnect(&value_or_instance); + disconnect_if([&call](const auto &elem) { return elem == call; }); } /** @@ -82802,9 +88668,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; }); } } @@ -82814,7 +88678,6 @@ public: } private: - difference_type offset; signal_type *signal; }; diff --git a/src/entt/config/config.h b/src/entt/config/config.h index 06c42e83..8a4a860d 100644 --- a/src/entt/config/config.h +++ b/src/entt/config/config.h @@ -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 diff --git a/src/entt/config/version.h b/src/entt/config/version.h index 10850757..5bd80672 100644 --- a/src/entt/config/version.h +++ b/src/entt/config/version.h @@ -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) \ diff --git a/src/entt/container/dense_map.hpp b/src/entt/container/dense_map.hpp index a2285005..50e355e9 100644 --- a/src/entt/container/dense_map.hpp +++ b/src/entt/container/dense_map.hpp @@ -128,51 +128,51 @@ public: return {it->element.first, it->element.second}; } - template - friend constexpr std::ptrdiff_t operator-(const dense_map_iterator &, const dense_map_iterator &) noexcept; + template + friend constexpr std::ptrdiff_t operator-(const dense_map_iterator &, const dense_map_iterator &) noexcept; - template - friend constexpr bool operator==(const dense_map_iterator &, const dense_map_iterator &) noexcept; + template + friend constexpr bool operator==(const dense_map_iterator &, const dense_map_iterator &) noexcept; - template - friend constexpr bool operator<(const dense_map_iterator &, const dense_map_iterator &) noexcept; + template + friend constexpr bool operator<(const dense_map_iterator &, const dense_map_iterator &) noexcept; private: It it; }; -template -[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return lhs.it - rhs.it; } -template -[[nodiscard]] constexpr bool operator==(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return lhs.it == rhs.it; } -template -[[nodiscard]] constexpr bool operator!=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return !(lhs == rhs); } -template -[[nodiscard]] constexpr bool operator<(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return lhs.it < rhs.it; } -template -[[nodiscard]] constexpr bool operator>(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return rhs < lhs; } -template -[[nodiscard]] constexpr bool operator<=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return !(lhs > rhs); } -template -[[nodiscard]] constexpr bool operator>=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) noexcept { return !(lhs < rhs); } @@ -230,13 +230,13 @@ private: std::size_t offset; }; -template -[[nodiscard]] constexpr bool operator==(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { return lhs.index() == rhs.index(); } -template -[[nodiscard]] constexpr bool operator!=(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const dense_map_local_iterator &lhs, const dense_map_local_iterator &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; - using alloc_traits = typename std::allocator_traits; + using alloc_traits = std::allocator_traits; static_assert(std::is_same_v>, "Invalid value type"); using sparse_container_type = std::vector>; using packed_container_type = std::vector>; @@ -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 + template [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t>> equal_range(const Other &key) const { const auto it = find(key); diff --git a/src/entt/container/dense_set.hpp b/src/entt/container/dense_set.hpp index d14b04d9..43a96a6a 100644 --- a/src/entt/container/dense_set.hpp +++ b/src/entt/container/dense_set.hpp @@ -96,51 +96,51 @@ public: return *operator->(); } - template - friend constexpr std::ptrdiff_t operator-(const dense_set_iterator &, const dense_set_iterator &) noexcept; + template + friend constexpr std::ptrdiff_t operator-(const dense_set_iterator &, const dense_set_iterator &) noexcept; - template - friend constexpr bool operator==(const dense_set_iterator &, const dense_set_iterator &) noexcept; + template + friend constexpr bool operator==(const dense_set_iterator &, const dense_set_iterator &) noexcept; - template - friend constexpr bool operator<(const dense_set_iterator &, const dense_set_iterator &) noexcept; + template + friend constexpr bool operator<(const dense_set_iterator &, const dense_set_iterator &) noexcept; private: It it; }; -template -[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return lhs.it - rhs.it; } -template -[[nodiscard]] constexpr bool operator==(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return lhs.it == rhs.it; } -template -[[nodiscard]] constexpr bool operator!=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return !(lhs == rhs); } -template -[[nodiscard]] constexpr bool operator<(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return lhs.it < rhs.it; } -template -[[nodiscard]] constexpr bool operator>(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return rhs < lhs; } -template -[[nodiscard]] constexpr bool operator<=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return !(lhs > rhs); } -template -[[nodiscard]] constexpr bool operator>=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) noexcept { return !(lhs < rhs); } @@ -195,13 +195,13 @@ private: std::size_t offset; }; -template -[[nodiscard]] constexpr bool operator==(const dense_set_local_iterator &lhs, const dense_set_local_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const dense_set_local_iterator &lhs, const dense_set_local_iterator &rhs) noexcept { return lhs.index() == rhs.index(); } -template -[[nodiscard]] constexpr bool operator!=(const dense_set_local_iterator &lhs, const dense_set_local_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const dense_set_local_iterator &lhs, const dense_set_local_iterator &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 + template [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t>> equal_range(const Other &value) const { const auto it = find(value); diff --git a/src/entt/core/algorithm.hpp b/src/entt/core/algorithm.hpp index 44c47ef4..55251231 100644 --- a/src/entt/core/algorithm.hpp +++ b/src/entt/core/algorithm.hpp @@ -95,14 +95,15 @@ struct radix_sort { template 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::value_type; std::vector 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]{}; diff --git a/src/entt/core/any.hpp b/src/entt/core/any.hpp index 0095a403..cf1eeff5 100644 --- a/src/entt/core/any.hpp +++ b/src/entt/core/any.hpp @@ -59,7 +59,7 @@ class basic_any { }; template - static constexpr bool in_situ = Len && alignof(Type) <= Align && sizeof(Type) <= Len &&std::is_nothrow_move_constructible_v; + static constexpr bool in_situ = Len && alignof(Type) <= Align && sizeof(Type) <= Len && std::is_nothrow_move_constructible_v; template 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>>; if constexpr(std::is_lvalue_reference_v) { - static_assert(sizeof...(Args) == 1u && (std::is_lvalue_reference_v && ...), "Invalid arguments"); + static_assert((std::is_lvalue_reference_v && ...) && (sizeof...(Args) == 1u), "Invalid arguments"); mode = std::is_const_v> ? policy::cref : policy::ref; instance = (std::addressof(args), ...); } else if constexpr(in_situ>>) { - if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v>>) { + if constexpr(std::is_aggregate_v>> && (sizeof...(Args) != 0u || !std::is_default_constructible_v>>)) { new(&storage) std::remove_cv_t>{std::forward(args)...}; } else { new(&storage) std::remove_cv_t>(std::forward(args)...); } } else { - if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v>>) { + if constexpr(std::is_aggregate_v>> && (sizeof...(Args) != 0u || !std::is_default_constructible_v>>)) { instance = new std::remove_cv_t>{std::forward(args)...}; } else { instance = new std::remove_cv_t>(std::forward(args)...); @@ -428,7 +428,7 @@ private: * @return The element converted to the requested type. */ template -Type any_cast(const basic_any &data) noexcept { +[[nodiscard]] Type any_cast(const basic_any &data) noexcept { const auto *const instance = any_cast>(&data); ENTT_ASSERT(instance, "Invalid instance"); return static_cast(*instance); @@ -436,7 +436,7 @@ Type any_cast(const basic_any &data) noexcept { /*! @copydoc any_cast */ template -Type any_cast(basic_any &data) noexcept { +[[nodiscard]] Type any_cast(basic_any &data) noexcept { // forces const on non-reference types to make them work also with wrappers for const references auto *const instance = any_cast>(&data); ENTT_ASSERT(instance, "Invalid instance"); @@ -445,7 +445,7 @@ Type any_cast(basic_any &data) noexcept { /*! @copydoc any_cast */ template -Type any_cast(basic_any &&data) noexcept { +[[nodiscard]] Type any_cast(basic_any &&data) noexcept { if constexpr(std::is_copy_constructible_v>>) { if(auto *const instance = any_cast>(&data); instance) { return static_cast(std::move(*instance)); @@ -461,14 +461,14 @@ Type any_cast(basic_any &&data) noexcept { /*! @copydoc any_cast */ template -const Type *any_cast(const basic_any *data) noexcept { +[[nodiscard]] const Type *any_cast(const basic_any *data) noexcept { const auto &info = type_id>(); return static_cast(data->data(info)); } /*! @copydoc any_cast */ template -Type *any_cast(basic_any *data) noexcept { +[[nodiscard]] Type *any_cast(basic_any *data) noexcept { if constexpr(std::is_const_v) { // last attempt to make wrappers for const references return their values return any_cast(&std::as_const(*data)); @@ -488,7 +488,7 @@ Type *any_cast(basic_any *data) noexcept { * @return A properly initialized wrapper for an object of the given type. */ template::length, std::size_t Align = basic_any::alignment, typename... Args> -basic_any make_any(Args &&...args) { +[[nodiscard]] basic_any make_any(Args &&...args) { return basic_any{std::in_place_type, std::forward(args)...}; } @@ -501,7 +501,7 @@ basic_any make_any(Args &&...args) { * @return A properly initialized and not necessarily owning wrapper. */ template::length, std::size_t Align = basic_any::alignment, typename Type> -basic_any forward_as_any(Type &&value) { +[[nodiscard]] basic_any forward_as_any(Type &&value) { return basic_any{std::in_place_type, std::forward(value)}; } diff --git a/src/entt/core/hashed_string.hpp b/src/entt/core/hashed_string.hpp index 02a50a99..b9c0d178 100644 --- a/src/entt/core/hashed_string.hpp +++ b/src/entt/core/hashed_string.hpp @@ -67,7 +67,7 @@ struct basic_hashed_string { template class basic_hashed_string: internal::basic_hashed_string { using base_type = internal::basic_hashed_string; - using hs_traits = internal::fnv1a_traits; + using traits_type = internal::fnv1a_traits; struct const_wrapper { // non-explicit constructor on purpose @@ -79,10 +79,10 @@ class basic_hashed_string: internal::basic_hashed_string { // 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(str[base.length])) * hs_traits::prime; + base.hash = (base.hash ^ static_cast(str[base.length])) * traits_type::prime; } return base; @@ -90,10 +90,10 @@ class basic_hashed_string: internal::basic_hashed_string { // 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(str[pos])) * hs_traits::prime; + base.hash = (base.hash ^ static_cast(str[pos])) * traits_type::prime; } return base; diff --git a/src/entt/core/memory.hpp b/src/entt/core/memory.hpp index d4e44f4b..685fbef6 100644 --- a/src/entt/core/memory.hpp +++ b/src/entt/core/memory.hpp @@ -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 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) { - using alloc_traits = typename std::allocator_traits; + using alloc_traits = std::allocator_traits; alloc_traits::destroy(*this, to_address(ptr)); alloc_traits::deallocate(*this, ptr, 1u); } diff --git a/src/entt/core/tuple.hpp b/src/entt/core/tuple.hpp index fa2147c0..1842f366 100644 --- a/src/entt/core/tuple.hpp +++ b/src/entt/core/tuple.hpp @@ -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 + template constexpr forward_apply(Args &&...args) noexcept(std::is_nothrow_constructible_v) : Func{std::forward(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 + template constexpr decltype(auto) operator()(Type &&args) noexcept(noexcept(std::apply(std::declval(), args))) { return std::apply(static_cast(*this), std::forward(args)); } /*! @copydoc operator()() */ - template + template constexpr decltype(auto) operator()(Type &&args) const noexcept(noexcept(std::apply(std::declval(), args))) { return std::apply(static_cast(*this), std::forward(args)); } diff --git a/src/entt/core/type_traits.hpp b/src/entt/core/type_traits.hpp index 4352e2fb..1304f5aa 100644 --- a/src/entt/core/type_traits.hpp +++ b/src/entt/core/type_traits.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include "../config/config.h" @@ -55,7 +56,6 @@ using type_identity_t = typename type_identity::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 struct size_of: std::integral_constant {}; @@ -297,7 +297,8 @@ struct type_list_contains; * @tparam Other Type to look for. */ template -struct type_list_contains, Other>: std::disjunction...> {}; +struct type_list_contains, Other> + : std::bool_constant<(std::is_same_v || ...)> {}; /** * @brief Helper variable template. @@ -385,10 +386,20 @@ struct value_list_element> */ template struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = typename value_list_element::type; + /** * @brief Helper type. * @tparam Index Index of the value to return. @@ -397,6 +408,58 @@ struct value_list_element<0u, value_list> { template inline constexpr auto value_list_element_v = value_list_element::value; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +struct value_list_index> { + static_assert(value_list_index>::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 +struct value_list_index> { + /*! @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 +inline constexpr std::size_t value_list_index_v = value_list_index::value; + /** * @brief Concatenates multiple value lists. * @tparam Value Values provided by the first value list. @@ -448,6 +511,89 @@ struct value_list_cat> { template using value_list_cat_t = typename value_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = std::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = typename value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : std::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +class value_list_diff, value_list> { + using v141_toolset_workaround = value_list; + +public: + /*! @brief A value list that is the difference between the two value lists. */ + using type = value_list_cat_t, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = typename value_list_diff::type; + /*! @brief Same as std::is_invocable, but with tuples. */ template struct is_applicable: std::false_type {}; @@ -568,7 +714,7 @@ inline constexpr bool is_iterator_v = is_iterator::value; */ template struct is_ebco_eligible - : std::conjunction, std::negation>> {}; + : std::bool_constant && !std::is_final_v> {}; /** * @brief Helper variable template. @@ -659,6 +805,10 @@ template struct is_equality_comparable() == std::declval())>> : std::bool_constant(choice<2>)> {}; +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: std::false_type {}; + /** * @brief Helper variable template. * @tparam Type The type to test. @@ -755,4 +905,16 @@ using nth_argument_t = typename nth_argument::type; } // namespace entt +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::type_list_element> {}; + +template +struct std::tuple_size>: std::integral_constant::size> {}; + +template +struct std::tuple_element>: entt::value_list_element> {}; + #endif diff --git a/src/entt/core/utility.hpp b/src/entt/core/utility.hpp index e38f6894..af8b5bd9 100644 --- a/src/entt/core/utility.hpp +++ b/src/entt/core/utility.hpp @@ -17,7 +17,7 @@ struct identity { * @param value The actual argument. * @return The submitted value as-is. */ - template + template [[nodiscard]] constexpr Type &&operator()(Type &&value) const noexcept { return std::forward(value); } @@ -50,7 +50,7 @@ template * @brief Helper type for visitors. * @tparam Func Types of function objects. */ -template +template struct overloaded: Func... { using Func::operator()...; }; @@ -59,14 +59,14 @@ struct overloaded: Func... { * @brief Deduction guide. * @tparam Func Types of function objects. */ -template +template overloaded(Func...) -> overloaded; /** * @brief Basic implementation of a y-combinator. * @tparam Func Type of a potentially recursive function. */ -template +template 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 + template constexpr decltype(auto) operator()(Args &&...args) const noexcept(std::is_nothrow_invocable_v) { return func(*this, std::forward(args)...); } /*! @copydoc operator()() */ - template + template constexpr decltype(auto) operator()(Args &&...args) noexcept(std::is_nothrow_invocable_v) { return func(*this, std::forward(args)...); } diff --git a/src/entt/entity/component.hpp b/src/entt/entity/component.hpp index 07fc9f43..51cbb963 100644 --- a/src/entt/entity/component.hpp +++ b/src/entt/entity/component.hpp @@ -4,6 +4,7 @@ #include #include #include "../config/config.h" +#include "fwd.hpp" namespace entt { @@ -17,6 +18,9 @@ namespace internal { template struct in_place_delete: std::bool_constant && std::is_move_assignable_v)> {}; +template<> +struct in_place_delete: std::false_type {}; + template struct in_place_delete> : std::true_type {}; @@ -24,6 +28,9 @@ struct in_place_delete> template struct page_size: std::integral_constant * ENTT_PACKED_PAGE> {}; +template<> +struct page_size: std::integral_constant {}; + template struct page_size>> : std::integral_constant {}; @@ -52,13 +59,6 @@ struct component_traits { static constexpr std::size_t page_size = internal::page_size::value; }; -/** - * @brief Helper variable template. - * @tparam Type Type of component. - */ -template -inline constexpr bool ignore_as_empty_v = (std::is_void_v || component_traits::page_size == 0u); - } // namespace entt #endif diff --git a/src/entt/entity/entity.hpp b/src/entt/entity/entity.hpp index fd7c96b9..ff226060 100644 --- a/src/entt/entity/entity.hpp +++ b/src/entt/entity/entity.hpp @@ -16,35 +16,47 @@ namespace entt { namespace internal { +// waiting for C++20 (and std::popcount) +template +static constexpr int popcount(Type value) noexcept { + return value ? (int(value & 1) + popcount(value >> 1)) : 0; +} + template struct entt_traits; template struct entt_traits>> - : entt_traits> {}; + : entt_traits> { + using value_type = Type; +}; template struct entt_traits>> - : entt_traits {}; + : entt_traits { + using value_type = Type; +}; template<> struct entt_traits { + 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 { + 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 { */ /** - * @brief Entity traits. - * @tparam Type Type of identifier. + * @brief Common basic entity traits implementation. + * @tparam Traits Actual entity traits to use. */ -template -class entt_traits: internal::entt_traits { - using base_type = internal::entt_traits; +template +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(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(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(version) << base_type::entity_shift)}; + return value_type{(entity & entity_mask) | (static_cast(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 +struct entt_traits: basic_entt_traits> { + /*! @brief Base type. */ + using base_type = basic_entt_traits>; + /*! @brief Page size, default is `ENTT_SPARSE_PAGE`. */ + static constexpr std::size_t page_size = ENTT_SPARSE_PAGE; +}; + /** * @copydoc entt_traits::to_integral * @tparam Entity The value type. @@ -167,8 +205,9 @@ struct null_t { */ template [[nodiscard]] constexpr operator Entity() const noexcept { - using entity_traits = entt_traits; - return entity_traits::combine(entity_traits::reserved, entity_traits::reserved); + using traits_type = entt_traits; + constexpr auto value = traits_type::construct(traits_type::entity_mask, traits_type::version_mask); + return value; } /** @@ -197,8 +236,8 @@ struct null_t { */ template [[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept { - using entity_traits = entt_traits; - return entity_traits::to_entity(entity) == entity_traits::to_entity(*this); + using traits_type = entt_traits; + return traits_type::to_entity(entity) == traits_type::to_entity(*this); } /** @@ -246,8 +285,9 @@ struct tombstone_t { */ template [[nodiscard]] constexpr operator Entity() const noexcept { - using entity_traits = entt_traits; - return entity_traits::combine(entity_traits::reserved, entity_traits::reserved); + using traits_type = entt_traits; + constexpr auto value = traits_type::construct(traits_type::entity_mask, traits_type::version_mask); + return value; } /** @@ -276,8 +316,8 @@ struct tombstone_t { */ template [[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept { - using entity_traits = entt_traits; - return entity_traits::to_version(entity) == entity_traits::to_version(*this); + using traits_type = entt_traits; + return traits_type::to_version(entity) == traits_type::to_version(*this); } /** diff --git a/src/entt/entity/fwd.hpp b/src/entt/entity/fwd.hpp index e0245ca9..0b054683 100644 --- a/src/entt/entity/fwd.hpp +++ b/src/entt/entity/fwd.hpp @@ -1,6 +1,7 @@ #ifndef ENTT_ENTITY_FWD_HPP #define ENTT_ENTITY_FWD_HPP +#include #include #include #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> class basic_sparse_set; @@ -18,18 +27,18 @@ template, type class basic_storage; template -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 = void> struct storage_type { /*! @brief Type-to-storage conversion result. */ - using type = sigh_storage_mixin>; + using type = sigh_mixin>; }; /** @@ -42,7 +51,7 @@ using storage_type_t = typename storage_type::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>> @@ -70,7 +79,7 @@ class basic_runtime_view; template class basic_group; -template +template> class basic_observer; template @@ -93,7 +102,10 @@ class basic_continuous_loader; * @tparam Type List of types. */ template -using exclude_t = type_list; +struct exclude_t final: type_list { + /*! @brief Default constructor. */ + explicit constexpr exclude_t() {} +}; /** * @brief Variable template for exclusion lists. @@ -107,7 +119,10 @@ inline constexpr exclude_t exclude{}; * @tparam Type List of types. */ template -using get_t = type_list; +struct get_t final: type_list { + /*! @brief Default constructor. */ + explicit constexpr get_t() {} +}; /** * @brief Variable template for lists of observed components. @@ -121,7 +136,10 @@ inline constexpr get_t get{}; * @tparam Type List of types. */ template -using owned_t = type_list; +struct owned_t final: type_list { + /*! @brief Default constructor. */ + explicit constexpr owned_t() {} +}; /** * @brief Variable template for lists of owned components. @@ -130,6 +148,39 @@ using owned_t = type_list; template inline constexpr owned_t 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 class Op> +struct type_list_transform, Op> { + /*! @brief Resulting get list after applying the transform function. */ + using type = get_t::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 class Op> +struct type_list_transform, Op> { + /*! @brief Resulting exclude list after applying the transform function. */ + using type = exclude_t::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 class Op> +struct type_list_transform, Op> { + /*! @brief Resulting owned list after applying the transform function. */ + using type = owned_t::type...>; +}; + /*! @brief Alias declaration for the most common use case. */ using sparse_set = basic_sparse_set<>; diff --git a/src/entt/entity/group.hpp b/src/entt/entity/group.hpp index 2f6376ff..71537e57 100644 --- a/src/entt/entity/group.hpp +++ b/src/entt/entity/group.hpp @@ -5,9 +5,10 @@ #include #include #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 class extended_group_iterator, get_t> { template auto index_to_element([[maybe_unused]] Type &cpool) const { - if constexpr(ignore_as_empty_v) { + 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, get_t> { } public: + using iterator_type = It; using difference_type = std::ptrdiff_t; using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval()), std::declval().get_as_tuple({})..., std::declval().get_as_tuple({})...)); using pointer = input_iterator_pointer; @@ -68,6 +70,10 @@ public: return operator*(); } + [[nodiscard]] constexpr iterator_type base() const noexcept { + return it; + } + template friend constexpr bool operator==(const extended_group_iterator &, const extended_group_iterator &) noexcept; @@ -86,6 +92,164 @@ template 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 +class group_handler; + +template +class group_handler, get_t, exclude_t> 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...>, "Groups do not support in-place delete"); + static_assert(!std::disjunction_v..., std::is_const..., std::is_const...>, "Const storage type not allowed"); + + using base_type = std::common_type_t; + 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::value()) || ...); + } + + return cnt; + } + + [[nodiscard]] size_type length() const noexcept { + return len; + } + + template + Type pools_as() const noexcept { + return pools; + } + + template + Type filter_as() const noexcept { + return filter; + } + +private: + std::tuple pools; + std::tuple filter; + std::size_t len; +}; + +template +class group_handler, get_t, exclude_t> 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...>, "Const storage type not allowed"); + + using base_type = std::common_type_t; + 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 + 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(*std::get<0>(pools))) { + push_on_construct(entity); + } + } + + common_type &handle() noexcept { + return elem; + } + + const common_type &handle() const noexcept { + return elem; + } + + template + Type pools_as() const noexcept { + return pools; + } + + template + Type filter_as() const noexcept { + return filter; + } + +private: + std::tuple pools; + std::tuple 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 class basic_group, get_t, exclude_t> { - using underlying_type = std::common_type_t; - using basic_common_type = std::common_type_t; + using base_type = std::common_type_t; + using underlying_type = typename base_type::entity_type; template - static constexpr std::size_t index_of = type_list_index_v, type_list>; + static constexpr std::size_t index_of = type_list_index_v, type_list>; + + auto pools() const noexcept { + using return_type = std::tuple; + return descriptor ? descriptor->template pools_as() : return_type{}; + } + + auto filter() const noexcept { + using return_type = std::tuple; + return descriptor ? descriptor->template filter_as() : 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, get_t>>; + /*! @brief Group handler type. */ + using handler = internal::group_handler, get_t...>, exclude_t...>>; /*! @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 - [[nodiscard]] decltype(auto) storage() const noexcept { + [[nodiscard]] auto *storage() const noexcept { return storage>(); } /** - * @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 - [[nodiscard]] decltype(auto) storage() const noexcept { - return *std::get(pools); + [[nodiscard]] auto *storage() const noexcept { + constexpr auto offset = sizeof...(Get); + + if constexpr(Index < offset) { + return std::get(pools()); + } else { + return std::get(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 + template [[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>(pools)->get(entt), ...); + return get, index_of...>(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 + [[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(cpools)->get(entt), ...); } else { - return std::tuple_cat(std::get>(pools)->get_as_tuple(entt)...); + return std::tuple_cat(std::get(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 + template + void sort(Compare compare, Sort algo = Sort{}, Args &&...args) { + sort, index_of...>(std::move(compare), std::move(algo), std::forward(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 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, "Invalid comparison function"); - handler->sort(std::move(compare), std::move(algo), std::forward(args)...); + descriptor->handle().sort(std::move(compare), std::move(algo), std::forward(args)...); } else { - auto comp = [this, &compare](const entity_type lhs, const entity_type rhs) { - if constexpr(sizeof...(Type) == 1) { - return compare((std::get>(pools)->get(lhs), ...), (std::get>(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(cpools)->get(lhs), ...), (std::get(cpools)->get(rhs), ...)); } else { - return compare(std::forward_as_tuple(std::get>(pools)->get(lhs)...), std::forward_as_tuple(std::get>(pools)->get(rhs)...)); + return compare(std::forward_as_tuple(std::get(cpools)->get(lhs)...), std::forward_as_tuple(std::get(cpools)->get(rhs)...)); } }; - handler->sort(std::move(comp), std::move(algo), std::forward(args)...); + descriptor->handle().sort(std::move(comp), std::move(algo), std::forward(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 - void sort() const { + void sort_as(const common_type &other) const { if(*this) { - handler->respect(*std::get>(pools)); + descriptor->handle().sort_as(other); } } private: - base_type *const handler; - const std::tuple 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 class basic_group, get_t, exclude_t> { - using underlying_type = std::common_type_t; - using basic_common_type = std::common_type_t; + using base_type = std::common_type_t; + using underlying_type = typename base_type::entity_type; template - static constexpr std::size_t index_of = type_list_index_v, type_list>; + static constexpr std::size_t index_of = type_list_index_v, type_list>; + + auto pools() const noexcept { + using return_type = std::tuple; + return descriptor ? descriptor->template pools_as() : return_type{}; + } + + auto filter() const noexcept { + using return_type = std::tuple; + return descriptor ? descriptor->template filter_as() : 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, get_t>>; + /*! @brief Group handler type. */ + using handler = internal::group_handler...>, get_t...>, exclude_t...>>; /*! @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 - [[nodiscard]] decltype(auto) storage() const noexcept { + [[nodiscard]] auto *storage() const noexcept { return storage>(); } /** - * @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 - [[nodiscard]] decltype(auto) storage() const noexcept { - return *std::get(pools); + [[nodiscard]] auto *storage() const noexcept { + constexpr auto offset = sizeof...(Owned) + sizeof...(Get); + + if constexpr(Index < offset) { + return std::get(pools()); + } else { + return std::get(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 + template [[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>(pools)->get(entt), ...); + return get, index_of...>(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 + [[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(cpools)->get(entt), ...); } else { - return std::tuple_cat(std::get>(pools)->get_as_tuple(entt)...); + return std::tuple_cat(std::get(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 + template void sort(Compare compare, Sort algo = Sort{}, Args &&...args) const { - if constexpr(sizeof...(Type) == 0) { + sort, index_of...>(std::move(compare), std::move(algo), std::forward(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 + 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, "Invalid comparison function"); - std::get<0>(pools)->sort_n(*length, std::move(compare), std::move(algo), std::forward(args)...); + storage<0>()->sort_n(descriptor->length(), std::move(compare), std::move(algo), std::forward(args)...); } else { - auto comp = [this, &compare](const entity_type lhs, const entity_type rhs) { - if constexpr(sizeof...(Type) == 1) { - return compare((std::get>(pools)->get(lhs), ...), (std::get>(pools)->get(rhs), ...)); + auto comp = [&compare, &cpools](const entity_type lhs, const entity_type rhs) { + if constexpr(sizeof...(Index) == 1) { + return compare((std::get(cpools)->get(lhs), ...), (std::get(cpools)->get(rhs), ...)); } else { - return compare(std::forward_as_tuple(std::get>(pools)->get(lhs)...), std::forward_as_tuple(std::get>(pools)->get(rhs)...)); + return compare(std::forward_as_tuple(std::get(cpools)->get(lhs)...), std::forward_as_tuple(std::get(cpools)->get(rhs)...)); } }; - std::get<0>(pools)->sort_n(*length, std::move(comp), std::move(algo), std::forward(args)...); + storage<0>()->sort_n(descriptor->length(), std::move(comp), std::move(algo), std::forward(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 pools; - const size_type *const length; + handler *descriptor; }; } // namespace entt diff --git a/src/entt/entity/handle.hpp b/src/entt/entity/handle.hpp index a42aeb08..210d9545 100644 --- a/src/entt/entity/handle.hpp +++ b/src/entt/entity/handle.hpp @@ -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); } /** diff --git a/src/entt/entity/helper.hpp b/src/entt/entity/helper.hpp index 7440937e..9cf25706 100644 --- a/src/entt/entity/helper.hpp +++ b/src/entt/entity/helper.hpp @@ -3,10 +3,10 @@ #include #include +#include #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; + 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; + 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::entity_type to_entity(const Registry ®, const Component &instance) { - const auto &storage = reg.template storage(); - const typename Registry::base_type &base = storage; - const auto *addr = std::addressof(instance); + if(const auto *storage = reg.template storage(); storage) { + constexpr auto page_size = std::remove_const_t>::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::page_size) { - if(const auto dist = (addr - std::addressof(storage.get(*it))); dist >= 0 && dist < static_cast(component_traits::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(page_size)) { + return *(it + dist); + } } } return null; } +/*! @brief Primary template isn't defined on purpose. */ +template +struct sigh_helper; + +/** + * @brief Signal connection helper for registries. + * @tparam Registry Basic registry type. + */ +template +struct sigh_helper { + /*! @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 + auto with(const id_type id = type_hash::value()) noexcept { + return sigh_helper{*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 +struct sigh_helper final: sigh_helper { + /*! @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::value()) + : sigh_helper{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 on_construct(Args &&...args) { + this->registry().template on_construct(name).template connect(std::forward(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 on_update(Args &&...args) { + this->registry().template on_update(name).template connect(std::forward(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 on_destroy(Args &&...args) { + this->registry().template on_destroy(name).template connect(std::forward(args)...); + return *this; + } + +private: + id_type name; +}; + +/** + * @brief Deduction guide. + * @tparam Registry Basic registry type. + */ +template +sigh_helper(Registry &) -> sigh_helper; + } // namespace entt #endif diff --git a/src/entt/entity/mixin.hpp b/src/entt/entity/mixin.hpp new file mode 100644 index 00000000..2f95d392 --- /dev/null +++ b/src/entt/entity/mixin.hpp @@ -0,0 +1,293 @@ +#ifndef ENTT_ENTITY_MIXIN_HPP +#define ENTT_ENTITY_MIXIN_HPP + +#include +#include +#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); + * @endcode + * + * This applies to all signals made available. + * + * @tparam Type The type of the underlying storage. + */ +template +class sigh_mixin final: public Type { + using underlying_type = Type; + using basic_registry_type = basic_registry; + using sigh_type = sigh; + 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(pos)); entt != tombstone) { + destruction.publish(reg, entt); + } + } else { + destruction.publish(reg, underlying_type::operator[](static_cast(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.
+ * 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.
+ * 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.
+ * 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).
+ * 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).
+ * 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 + decltype(auto) emplace(const entity_type hint, Args &&...args) { + if constexpr(std::is_same_v) { + const auto entt = underlying_type::emplace(hint, std::forward(args)...); + construction.publish(owner_or_assert(), entt); + return entt; + } else { + underlying_type::emplace(hint, std::forward(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 + decltype(auto) patch(const entity_type entt, Func &&...func) { + underlying_type::patch(entt, std::forward(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).
+ * 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 + void insert(It first, It last, Args &&...args) { + underlying_type::insert(first, last, std::forward(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(&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 diff --git a/src/entt/entity/observer.hpp b/src/entt/entity/observer.hpp index 5c1f503c..d9f53e03 100644 --- a/src/entt/entity/observer.hpp +++ b/src/entt/entity/observer.hpp @@ -43,7 +43,7 @@ struct basic_collector<> { * @return The updated collector. */ template - static constexpr auto group(exclude_t = {}) noexcept { + static constexpr auto group(exclude_t = exclude_t{}) noexcept { return basic_collector, type_list<>, type_list, AllOf...>>{}; } @@ -78,7 +78,7 @@ struct basic_collector, type_list, Rule * @return The updated collector. */ template - static constexpr auto group(exclude_t = {}) noexcept { + static constexpr auto group(exclude_t = exclude_t{}) noexcept { return basic_collector, type_list<>, type_list, AllOf...>, current_type, Other...>{}; } @@ -99,7 +99,7 @@ struct basic_collector, type_list, Rule * @return The updated collector. */ template - static constexpr auto where(exclude_t = {}) noexcept { + static constexpr auto where(exclude_t = exclude_t{}) noexcept { using extended_type = matcher, type_list, Rule...>; return basic_collector{}; } @@ -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 -class basic_observer: private basic_storage { - using base_type = basic_storage; +template +class basic_observer: private basic_storage { + using base_type = basic_storage; template struct matcher_handler; @@ -193,10 +194,10 @@ class basic_observer: private basic_storage().disconnect(obs), ...); - (reg.template on_construct().disconnect(obs), ...); - reg.template on_update().disconnect(obs); - reg.template on_destroy().disconnect(obs); + (reg.template on_destroy().disconnect(&obs), ...); + (reg.template on_construct().disconnect(&obs), ...); + reg.template on_update().disconnect(&obs); + reg.template on_destroy().disconnect(&obs); } }; @@ -239,12 +240,12 @@ class basic_observer: private basic_storage().disconnect(obs), ...); - (reg.template on_construct().disconnect(obs), ...); - (reg.template on_construct().disconnect(obs), ...); - (reg.template on_destroy().disconnect(obs), ...); - (reg.template on_destroy().disconnect(obs), ...); - (reg.template on_construct().disconnect(obs), ...); + (reg.template on_destroy().disconnect(&obs), ...); + (reg.template on_construct().disconnect(&obs), ...); + (reg.template on_construct().disconnect(&obs), ...); + (reg.template on_destroy().disconnect(&obs), ...); + (reg.template on_destroy().disconnect(&obs), ...); + (reg.template on_construct().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 - basic_observer(registry_type ®, basic_collector) - : basic_observer{} { + basic_observer(registry_type ®, basic_collector, const allocator_type &allocator = allocator_type{}) + : basic_observer{allocator} { connect(reg, std::index_sequence_for{}); } - /*! @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. */ diff --git a/src/entt/entity/organizer.hpp b/src/entt/entity/organizer.hpp index ebda20b9..67438e6f 100644 --- a/src/entt/entity/organizer.hpp +++ b/src/entt/entity/organizer.hpp @@ -126,7 +126,7 @@ class basic_organizer final { if constexpr(std::is_same_v) { return reg; } else if constexpr(internal::is_view_v) { - return as_view{reg}; + return static_cast(as_view{reg}); } else { return reg.ctx().template emplace>(); } diff --git a/src/entt/entity/registry.hpp b/src/entt/entity/registry.hpp index 48416407..39bea062 100644 --- a/src/entt/entity/registry.hpp +++ b/src/entt/entity/registry.hpp @@ -14,17 +14,16 @@ #include "../container/dense_map.hpp" #include "../core/algorithm.hpp" #include "../core/any.hpp" -#include "../core/compressed_pair.hpp" #include "../core/fwd.hpp" #include "../core/iterator.hpp" #include "../core/memory.hpp" #include "../core/type_info.hpp" #include "../core/type_traits.hpp" #include "../core/utility.hpp" -#include "component.hpp" #include "entity.hpp" #include "fwd.hpp" #include "group.hpp" +#include "mixin.hpp" #include "sparse_set.hpp" #include "storage.hpp" #include "view.hpp" @@ -110,64 +109,62 @@ public: return operator*(); } - template - friend constexpr std::ptrdiff_t operator-(const registry_storage_iterator &, const registry_storage_iterator &) noexcept; + template + friend constexpr std::ptrdiff_t operator-(const registry_storage_iterator &, const registry_storage_iterator &) noexcept; - template - friend constexpr bool operator==(const registry_storage_iterator &, const registry_storage_iterator &) noexcept; + template + friend constexpr bool operator==(const registry_storage_iterator &, const registry_storage_iterator &) noexcept; - template - friend constexpr bool operator<(const registry_storage_iterator &, const registry_storage_iterator &) noexcept; + template + friend constexpr bool operator<(const registry_storage_iterator &, const registry_storage_iterator &) noexcept; private: It it; }; -template -[[nodiscard]] constexpr std::ptrdiff_t operator-(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr std::ptrdiff_t operator-(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { return lhs.it - rhs.it; } -template -[[nodiscard]] constexpr bool operator==(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { return lhs.it == rhs.it; } -template -[[nodiscard]] constexpr bool operator!=(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { return !(lhs == rhs); } -template -[[nodiscard]] constexpr bool operator<(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { return lhs.it < rhs.it; } -template -[[nodiscard]] constexpr bool operator>(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { return rhs < lhs; } -template -[[nodiscard]] constexpr bool operator<=(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<=(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { return !(lhs > rhs); } -template -[[nodiscard]] constexpr bool operator>=(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>=(const registry_storage_iterator &lhs, const registry_storage_iterator &rhs) noexcept { return !(lhs < rhs); } +template class registry_context { - using key_type = id_type; - using mapped_type = basic_any<0u>; - using container_type = dense_map; + using alloc_traits = std::allocator_traits; + using allocator_type = typename alloc_traits::template rebind_alloc>>; public: - template - [[deprecated("Use ::emplace_as instead")]] Type &emplace_hint(const id_type id, Args &&...args) { - return emplace_as(id, std::forward(args)...); - } + explicit registry_context(const allocator_type &allocator) + : ctx{allocator} {} template Type &emplace_as(const id_type id, Args &&...args) { @@ -195,16 +192,6 @@ public: return it != ctx.end() && it->second.type() == type_id() ? (ctx.erase(it), true) : false; } - template - [[deprecated("Use ::get instead")]] [[nodiscard]] const Type &at(const id_type id = type_id().hash()) const { - return get(id); - } - - template - [[deprecated("Use ::get instead")]] [[nodiscard]] Type &at(const id_type id = type_id().hash()) { - return get(id); - } - template [[nodiscard]] const Type &get(const id_type id = type_id().hash()) const { return any_cast(ctx.at(id)); @@ -234,7 +221,7 @@ public: } private: - container_type ctx; + dense_map, identity, std::equal_to, allocator_type> ctx; }; } // namespace internal @@ -246,138 +233,93 @@ private: /** * @brief Fast and reliable entity-component system. - * @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 class basic_registry { - using alloc_traits = typename std::allocator_traits; + using base_type = basic_sparse_set; + + using alloc_traits = std::allocator_traits; static_assert(std::is_same_v, "Invalid value type"); - using basic_common_type = basic_sparse_set; - using entity_traits = entt_traits; + + // std::shared_ptr because of its type erased allocator which is useful here + using pool_container_type = dense_map, identity, std::equal_to, typename alloc_traits::template rebind_alloc>>>; + using group_container_type = dense_map, identity, std::equal_to, typename alloc_traits::template rebind_alloc>>>; template - using storage_for_type = typename storage_for>>::type; + [[nodiscard]] auto &assure([[maybe_unused]] const id_type id = type_hash::value()) { + if constexpr(std::is_same_v) { + return entities; + } else { + static_assert(std::is_same_v>, "Non-decayed types not allowed"); + auto &cpool = pools[id]; - template - struct group_handler; + if(!cpool) { + using storage_type = storage_for_type; + using alloc_type = typename storage_type::allocator_type; - template - struct group_handler, get_t, Owned...> { - // nasty workaround for an issue with the toolset v141 that doesn't accept a fold expression here - static_assert(!std::disjunction_v::in_place_delete>...>, "Groups do not support in-place delete"); - using value_type = std::conditional_t; - value_type current{}; - - template - group_handler(Args &&...args) - : current{std::forward(args)...} {} - - template - void maybe_valid_if(basic_registry &owner, const Entity entt) { - [[maybe_unused]] const auto cpools = std::forward_as_tuple(owner.assure()...); - - const auto is_valid = ((std::is_same_v || std::get &>(cpools).contains(entt)) && ...) - && ((std::is_same_v || owner.assure().contains(entt)) && ...) - && ((std::is_same_v || !owner.assure().contains(entt)) && ...); - - if constexpr(sizeof...(Owned) == 0) { - if(is_valid && !current.contains(entt)) { - current.emplace(entt); - } - } else { - if(is_valid && !(std::get<0>(cpools).index(entt) < current)) { - const auto pos = current++; - (std::get &>(cpools).swap_elements(std::get &>(cpools).data()[pos], entt), ...); + if constexpr(std::is_same_v && !std::is_constructible_v) { + // std::allocator has no cross constructors (waiting for C++20) + cpool = std::allocate_shared(get_allocator(), alloc_type{}); + } else { + cpool = std::allocate_shared(get_allocator(), get_allocator()); } + + cpool->bind(forward_as_any(*this)); } - } - void discard_if([[maybe_unused]] basic_registry &owner, const Entity entt) { - if constexpr(sizeof...(Owned) == 0) { - current.remove(entt); - } else { - if(const auto cpools = std::forward_as_tuple(owner.assure()...); std::get<0>(cpools).contains(entt) && (std::get<0>(cpools).index(entt) < current)) { - const auto pos = --current; - (std::get &>(cpools).swap_elements(std::get &>(cpools).data()[pos], entt), ...); - } + ENTT_ASSERT(cpool->type() == type_id(), "Unexpected type"); + return static_cast &>(*cpool); + } + } + + template + [[nodiscard]] const auto *assure([[maybe_unused]] const id_type id = type_hash::value()) const { + if constexpr(std::is_same_v) { + return &entities; + } else { + static_assert(std::is_same_v>, "Non-decayed types not allowed"); + + if(const auto it = pools.find(id); it != pools.cend()) { + ENTT_ASSERT(it->second->type() == type_id(), "Unexpected type"); + return static_cast *>(it->second.get()); } + + return static_cast *>(nullptr); } - }; - - struct group_data { - std::size_t size; - std::shared_ptr group; - bool (*owned)(const id_type) noexcept; - bool (*get)(const id_type) noexcept; - bool (*exclude)(const id_type) noexcept; - }; - - template - [[nodiscard]] auto &assure(const id_type id = type_hash::value()) { - static_assert(std::is_same_v>, "Non-decayed types not allowed"); - auto &cpool = pools[id]; - - if(!cpool) { - cpool = std::allocate_shared>>(get_allocator(), get_allocator()); - cpool->bind(forward_as_any(*this)); - } - - ENTT_ASSERT(cpool->type() == type_id(), "Unexpected type"); - return static_cast &>(*cpool); - } - - template - [[nodiscard]] const auto &assure(const id_type id = type_hash::value()) const { - static_assert(std::is_same_v>, "Non-decayed types not allowed"); - - if(const auto it = pools.find(id); it != pools.cend()) { - ENTT_ASSERT(it->second->type() == type_id(), "Unexpected type"); - return static_cast &>(*it->second); - } - - static storage_for_type placeholder{}; - return placeholder; - } - - auto generate_identifier(const std::size_t pos) noexcept { - ENTT_ASSERT(pos < entity_traits::to_entity(null), "No entities available"); - return entity_traits::combine(static_cast(pos), {}); - } - - auto recycle_identifier() noexcept { - ENTT_ASSERT(free_list != null, "No entities available"); - const auto curr = entity_traits::to_entity(free_list); - free_list = entity_traits::combine(entity_traits::to_integral(epool[curr]), tombstone); - return (epool[curr] = entity_traits::combine(curr, entity_traits::to_integral(epool[curr]))); - } - - auto release_entity(const Entity entt, const typename entity_traits::version_type version) { - const typename entity_traits::version_type vers = version + (version == entity_traits::to_version(tombstone)); - epool[entity_traits::to_entity(entt)] = entity_traits::construct(entity_traits::to_integral(free_list), vers); - free_list = entity_traits::combine(entity_traits::to_integral(entt), tombstone); - return vers; } void rebind() { + entities.bind(forward_as_any(*this)); + for(auto &&curr: pools) { curr.second->bind(forward_as_any(*this)); } } public: + /*! @brief Entity traits. */ + using traits_type = typename base_type::traits_type; /*! @brief Allocator type. */ using allocator_type = Allocator; /*! @brief Underlying entity identifier. */ - using entity_type = Entity; + 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 Common type among all storage types. */ - using base_type = basic_common_type; + using common_type = base_type; /*! @brief Context type. */ - using context = internal::registry_context; + using context = internal::registry_context; + + /** + * @copybrief storage_for + * @tparam Type Storage value type, eventually const. + */ + template + using storage_for_type = typename storage_for>>::type; /*! @brief Default constructor. */ basic_registry() @@ -396,12 +338,12 @@ public: * @param allocator The allocator to use. */ basic_registry(const size_type count, const allocator_type &allocator = allocator_type{}) - : vars{}, - free_list{tombstone}, - epool{allocator}, + : vars{allocator}, pools{allocator}, - groups{allocator} { + groups{allocator}, + entities{allocator} { pools.reserve(count); + rebind(); } /** @@ -410,10 +352,9 @@ public: */ basic_registry(basic_registry &&other) noexcept : vars{std::move(other.vars)}, - free_list{std::move(other.free_list)}, - epool{std::move(other.epool)}, pools{std::move(other.pools)}, - groups{std::move(other.groups)} { + groups{std::move(other.groups)}, + entities{std::move(other.entities)} { rebind(); } @@ -424,10 +365,9 @@ public: */ basic_registry &operator=(basic_registry &&other) noexcept { vars = std::move(other.vars); - free_list = std::move(other.free_list); - epool = std::move(other.epool); pools = std::move(other.pools); groups = std::move(other.groups); + entities = std::move(other.entities); rebind(); @@ -440,11 +380,11 @@ public: */ void swap(basic_registry &other) { using std::swap; + swap(vars, other.vars); - swap(free_list, other.free_list); - swap(epool, other.epool); swap(pools, other.pools); swap(groups, other.groups); + swap(entities, other.entities); rebind(); other.rebind(); @@ -455,7 +395,7 @@ public: * @return The associated allocator. */ [[nodiscard]] constexpr allocator_type get_allocator() const noexcept { - return epool.get_allocator(); + return pools.get_allocator(); } /** @@ -480,8 +420,8 @@ public: * @param id Name used to map the storage within the registry. * @return A pointer to the storage if it exists, a null pointer otherwise. */ - [[nodiscard]] base_type *storage(const id_type id) { - return const_cast(std::as_const(*this).storage(id)); + [[nodiscard]] common_type *storage(const id_type id) { + return const_cast(std::as_const(*this).storage(id)); } /** @@ -489,7 +429,7 @@ public: * @param id Name used to map the storage within the registry. * @return A pointer to the storage if it exists, a null pointer otherwise. */ - [[nodiscard]] const base_type *storage(const id_type id) const { + [[nodiscard]] const common_type *storage(const id_type id) const { const auto it = pools.find(id); return it == pools.cend() ? nullptr : it->second.get(); } @@ -501,23 +441,18 @@ public: * @return The storage for the given component type. */ template - decltype(auto) storage(const id_type id = type_hash::value()) { + storage_for_type &storage(const id_type id = type_hash::value()) { return assure(id); } /** - * @brief Returns the storage for a given component type. - * - * @warning - * If a storage for the given component doesn't exist yet, a temporary - * placeholder is returned instead. - * + * @brief Returns the storage for a given component type, if any. * @tparam Type Type of component of which to return the storage. * @param id Optional name used to map the storage within the registry. * @return The storage for the given component type. */ template - decltype(auto) storage(const id_type id = type_hash::value()) const { + const storage_for_type *storage(const id_type id = type_hash::value()) const { return assure(id); } @@ -525,30 +460,24 @@ public: * @brief Returns the number of entities created so far. * @return Number of entities created so far. */ - [[nodiscard]] size_type size() const noexcept { - return epool.size(); + [[deprecated("use .storage().size() instead")]] [[nodiscard]] size_type size() const noexcept { + return entities.size(); } /** * @brief Returns the number of entities still in use. * @return Number of entities still in use. */ - [[nodiscard]] size_type alive() const { - auto sz = epool.size(); - - for(auto curr = free_list; curr != null; --sz) { - curr = epool[entity_traits::to_entity(curr)]; - } - - return sz; + [[deprecated("use .storage().in_use() instead")]] [[nodiscard]] size_type alive() const { + return entities.in_use(); } /** * @brief Increases the capacity (number of entities) of the registry. * @param cap Desired capacity. */ - void reserve(const size_type cap) { - epool.reserve(cap); + [[deprecated("use .storage().reserve(cap) instead")]] void reserve(const size_type cap) { + entities.reserve(cap); } /** @@ -556,15 +485,15 @@ public: * allocated space for. * @return Capacity of the registry. */ - [[nodiscard]] size_type capacity() const noexcept { - return epool.capacity(); + [[deprecated("use .storage().capacity() instead")]] [[nodiscard]] size_type capacity() const noexcept { + return entities.capacity(); } /** * @brief Checks whether the registry is empty (no entities still in use). * @return True if the registry is empty, false otherwise. */ - [[nodiscard]] bool empty() const { + [[deprecated("use .storage().in_use() instead")]] [[nodiscard]] bool empty() const { return !alive(); } @@ -580,20 +509,16 @@ public: * * @return A pointer to the array of entities. */ - [[nodiscard]] const entity_type *data() const noexcept { - return epool.data(); + [[deprecated("use .storage().data() instead")]] [[nodiscard]] const entity_type *data() const noexcept { + return entities.data(); } /** - * @brief Returns the head of the list of released entities. - * - * This function is intended for use in conjunction with `assign`.
- * The returned entity has an invalid identifier in all cases. - * - * @return The head of the list of released entities. + * @brief Returns the number of released entities. + * @return The number of released entities. */ - [[nodiscard]] entity_type released() const noexcept { - return free_list; + [[deprecated("use .storage().size() and .storage().in_use() instead")]] [[nodiscard]] size_type released() const noexcept { + return (entities.size() - entities.in_use()); } /** @@ -602,8 +527,7 @@ public: * @return True if the identifier is valid, false otherwise. */ [[nodiscard]] bool valid(const entity_type entt) const { - const auto pos = size_type(entity_traits::to_entity(entt)); - return (pos < epool.size() && epool[pos] == entt); + return entities.contains(entt) && (entities.index(entt) < entities.in_use()); } /** @@ -613,8 +537,7 @@ public: * version otherwise. */ [[nodiscard]] version_type current(const entity_type entt) const { - const auto pos = size_type(entity_traits::to_entity(entt)); - return entity_traits::to_version(pos < epool.size() ? epool[pos] : tombstone); + return entities.current(entt); } /** @@ -622,7 +545,7 @@ public: * @return A valid identifier. */ [[nodiscard]] entity_type create() { - return (free_list == null) ? epool.emplace_back(generate_identifier(epool.size())) : recycle_identifier(); + return entities.emplace(); } /** @@ -635,26 +558,7 @@ public: * @return A valid identifier. */ [[nodiscard]] entity_type create(const entity_type hint) { - const auto length = epool.size(); - - if(hint == null || hint == tombstone) { - return create(); - } else if(const auto req = entity_traits::to_entity(hint); !(req < length)) { - epool.resize(size_type(req) + 1u, null); - - for(auto pos = length; pos < req; ++pos) { - release_entity(generate_identifier(pos), {}); - } - - return (epool[req] = hint); - } else if(const auto curr = entity_traits::to_entity(epool[req]); req == curr) { - return create(); - } else { - auto *it = &free_list; - for(; entity_traits::to_entity(*it) != req; it = &epool[entity_traits::to_entity(*it)]) {} - *it = entity_traits::combine(curr, entity_traits::to_integral(*it)); - return (epool[req] = hint); - } + return entities.emplace(hint); } /** @@ -668,16 +572,7 @@ public: */ template void create(It first, It last) { - for(; free_list != null && first != last; ++first) { - *first = recycle_identifier(); - } - - const auto length = epool.size(); - epool.resize(length + std::distance(first, last), null); - - for(auto pos = length; first != last; ++first, ++pos) { - *first = epool[pos] = generate_identifier(pos); - } + entities.insert(std::move(first), std::move(last)); } /** @@ -695,13 +590,13 @@ public: * @tparam It Type of input iterator. * @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 destroyed The head of the list of destroyed entities. + * @param destroyed The number of released entities. */ template - void assign(It first, It last, const entity_type destroyed) { - ENTT_ASSERT(!alive(), "Entities still alive"); - epool.assign(first, last); - free_list = destroyed; + [[deprecated("use .storage().push(first, last) and .storage().in_use(len) instead")]] void assign(It first, It last, const size_type destroyed) { + ENTT_ASSERT(!entities.in_use(), "Non-empty registry"); + entities.push(first, last); + entities.in_use(entities.size() - destroyed); } /** @@ -709,14 +604,13 @@ public: * * The version is updated and the identifier can be recycled at any time. * - * @warning - * Attempting to use an invalid entity results in undefined behavior. - * * @param entt A valid identifier. * @return The version of the recycled entity. */ - version_type release(const entity_type entt) { - return release(entt, static_cast(entity_traits::to_version(entt) + 1u)); + [[deprecated("use .orphan(entt) and .storage().erase(entt) instead")]] version_type release(const entity_type entt) { + ENTT_ASSERT(orphan(entt), "Non-orphan entity"); + entities.erase(entt); + return entities.current(entt); } /** @@ -725,49 +619,47 @@ public: * The suggested version or the valid version closest to the suggested one * is used instead of the implicitly generated version. * - * @sa release - * * @param entt A valid identifier. * @param version A desired version upon destruction. * @return The version actually assigned to the entity. */ - version_type release(const entity_type entt, const version_type version) { - ENTT_ASSERT(valid(entt), "Invalid identifier"); - ENTT_ASSERT(std::all_of(pools.cbegin(), pools.cend(), [entt](auto &&curr) { return (curr.second->current(entt) == entity_traits::to_version(tombstone)); }), "Non-orphan entity"); - return release_entity(entt, version); + [[deprecated("use .orphan(entt), then .storage().erase(entt)/.bump(next) instead")]] version_type release(const entity_type entt, const version_type version) { + ENTT_ASSERT(orphan(entt), "Non-orphan entity"); + entities.erase(entt); + const auto elem = traits_type::construct(traits_type::to_entity(entt), version); + return entities.bump((elem == tombstone) ? traits_type::next(elem) : elem); } /** * @brief Releases all identifiers in a range. * - * @sa release - * * @tparam It Type of input iterator. * @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. */ template - void release(It first, It last) { - for(; first != last; ++first) { - release(*first); - } + [[deprecated("use .orphan(entt) and .storage().erase(first, last) instead")]] void release(It first, It last) { + ENTT_ASSERT(std::all_of(first, last, [this](const auto entt) { return orphan(entt); }), "Non-orphan entity"); + entities.erase(std::move(first), std::move(last)); } /** * @brief Destroys an entity and releases its identifier. * - * @sa release - * * @warning * Adding or removing components to an entity that is being destroyed can - * result in undefined behavior. Attempting to use an invalid entity results - * in undefined behavior. + * result in undefined behavior. * * @param entt A valid identifier. * @return The version of the recycled entity. */ version_type destroy(const entity_type entt) { - return destroy(entt, static_cast(entity_traits::to_version(entt) + 1u)); + for(size_type pos = pools.size(); pos; --pos) { + pools.begin()[pos - 1u].second->remove(entt); + } + + entities.erase(entt); + return entities.current(entt); } /** @@ -783,11 +675,9 @@ public: * @return The version actually assigned to the entity. */ version_type destroy(const entity_type entt, const version_type version) { - for(size_type pos = pools.size(); pos; --pos) { - pools.begin()[pos - 1u].second->remove(entt); - } - - return release(entt, version); + destroy(entt); + const auto elem = traits_type::construct(traits_type::to_entity(entt), version); + return entities.bump((elem == tombstone) ? traits_type::next(elem) : elem); } /** @@ -801,9 +691,14 @@ public: */ template void destroy(It first, It last) { - for(; first != last; ++first) { - destroy(*first); + const auto from = entities.each().cbegin().base(); + const auto to = from + entities.pack(first, last); + + for(size_type pos = pools.size(); pos; --pos) { + pools.begin()[pos - 1u].second->remove(from, to); } + + entities.erase(from, to); } /** @@ -839,7 +734,7 @@ public: */ template void insert(It first, It last, const Type &value = {}) { - assure().insert(first, last, value); + assure().insert(std::move(first), std::move(last), value); } /** @@ -889,13 +784,8 @@ public: * void(Type &); * @endcode * - * @note - * Empty types aren't explicitly instantiated and therefore they are never - * returned. However, this function can be used to trigger an update signal - * for them. - * * @warning - * Attempting to to patch a component of an entity that doesn't own it + * Attempting to patch a component of an entity that doesn't own it * results in undefined behavior. * * @tparam Type Type of component to patch. @@ -931,7 +821,6 @@ public: /** * @brief Removes the given components from an entity. - * * @tparam Type Type of component to remove. * @tparam Other Other types of components to remove. * @param entt A valid identifier. @@ -956,17 +845,27 @@ public: */ template size_type remove(It first, It last) { - if constexpr(sizeof...(Other) == 0u) { - return assure().remove(std::move(first), std::move(last)); - } else { - size_type count{}; + size_type count{}; + if constexpr(std::is_same_v) { + common_type *cpools[sizeof...(Other) + 1u]{&assure(), &assure()...}; + + for(size_type pos{}, len = sizeof...(Other) + 1u; pos < len; ++pos) { + if constexpr(sizeof...(Other) != 0u) { + if(cpools[pos]->data() == first.data()) { + std::swap(cpools[pos], cpools[sizeof...(Other)]); + } + } + + count += cpools[pos]->remove(first, last); + } + } else { for(auto cpools = std::forward_as_tuple(assure(), assure()...); first != last; ++first) { count += std::apply([entt = *first](auto &...curr) { return (curr.remove(entt) + ... + 0u); }, cpools); } - - return count; } + + return count; } /** @@ -998,8 +897,18 @@ public: */ template void erase(It first, It last) { - if constexpr(sizeof...(Other) == 0u) { - assure().erase(std::move(first), std::move(last)); + if constexpr(std::is_same_v) { + common_type *cpools[sizeof...(Other) + 1u]{&assure(), &assure()...}; + + for(size_type pos{}, len = sizeof...(Other) + 1u; pos < len; ++pos) { + if constexpr(sizeof...(Other) != 0u) { + if(cpools[pos]->data() == first.data()) { + std::swap(cpools[pos], cpools[sizeof...(Other)]); + } + } + + cpools[pos]->erase(first, last); + } } else { for(auto cpools = std::forward_as_tuple(assure(), assure()...); first != last; ++first) { std::apply([entt = *first](auto &...curr) { (curr.erase(entt), ...); }, cpools); @@ -1007,6 +916,30 @@ public: } } + /** + * @brief Erases components satisfying specific criteria from an entity. + * + * The function type is equivalent to: + * + * @code{.cpp} + * void(const id_type, typename basic_registry::base_type &); + * @endcode + * + * Only storage where the entity exists are passed to the function. + * + * @tparam Func Type of the function object to invoke. + * @param entt A valid identifier. + * @param func A valid function object. + */ + template + void erase_if(const entity_type entt, Func func) { + for(auto [id, cpool]: storage()) { + if(cpool.contains(entt) && func(id, std::as_const(cpool))) { + cpool.erase(entt); + } + } + } + /** * @brief Removes all tombstones from a registry or only the pools for the * given components. @@ -1014,7 +947,7 @@ public: */ template void compact() { - if constexpr(sizeof...(Type) == 0) { + if constexpr(sizeof...(Type) == 0u) { for(auto &&curr: pools) { curr.second->compact(); } @@ -1031,7 +964,12 @@ public: */ template [[nodiscard]] bool all_of(const entity_type entt) const { - return (assure>().contains(entt) && ...); + if constexpr(sizeof...(Type) == 1u) { + auto *cpool = assure...>(); + return cpool && cpool->contains(entt); + } else { + return (all_of(entt) && ...); + } } /** @@ -1043,7 +981,7 @@ public: */ template [[nodiscard]] bool any_of(const entity_type entt) const { - return (assure>().contains(entt) || ...); + return (all_of(entt) || ...); } /** @@ -1060,7 +998,7 @@ public: template [[nodiscard]] decltype(auto) get([[maybe_unused]] const entity_type entt) const { if constexpr(sizeof...(Type) == 1u) { - return (assure>().get(entt), ...); + return (assure>()->get(entt), ...); } else { return std::forward_as_tuple(get(entt)...); } @@ -1070,7 +1008,7 @@ public: template [[nodiscard]] decltype(auto) get([[maybe_unused]] const entity_type entt) { if constexpr(sizeof...(Type) == 1u) { - return (const_cast(std::as_const(*this).template get(entt)), ...); + return (static_cast &>(assure>()).get(entt), ...); } else { return std::forward_as_tuple(get(entt)...); } @@ -1112,9 +1050,9 @@ public: */ template [[nodiscard]] auto try_get([[maybe_unused]] const entity_type entt) const { - if constexpr(sizeof...(Type) == 1) { - const auto &cpool = assure...>(); - return cpool.contains(entt) ? std::addressof(cpool.get(entt)) : nullptr; + if constexpr(sizeof...(Type) == 1u) { + const auto *cpool = assure...>(); + return (cpool && cpool->contains(entt)) ? std::addressof(cpool->get(entt)) : nullptr; } else { return std::make_tuple(try_get(entt)...); } @@ -1123,8 +1061,9 @@ public: /*! @copydoc try_get */ template [[nodiscard]] auto try_get([[maybe_unused]] const entity_type entt) { - if constexpr(sizeof...(Type) == 1) { - return (const_cast(std::as_const(*this).template try_get(entt)), ...); + if constexpr(sizeof...(Type) == 1u) { + auto &cpool = assure...>(); + return (static_cast(cpool.contains(entt) ? std::addressof(cpool.get(entt)) : nullptr), ...); } else { return std::make_tuple(try_get(entt)...); } @@ -1136,12 +1075,13 @@ public: */ template void clear() { - if constexpr(sizeof...(Type) == 0) { - for(auto &&curr: pools) { - curr.second->clear(); + if constexpr(sizeof...(Type) == 0u) { + for(size_type pos = pools.size(); pos; --pos) { + pools.begin()[pos - 1u].second->clear(); } - each([this](const auto entity) { this->release(entity); }); + const auto iterable = entities.each(); + entities.erase(iterable.begin().base(), iterable.end().base()); } else { (assure().clear(), ...); } @@ -1162,17 +1102,9 @@ public: * @param func A valid function object. */ template - void each(Func func) const { - if(free_list == null) { - for(auto pos = epool.size(); pos; --pos) { - func(epool[pos - 1]); - } - } else { - for(auto pos = epool.size(); pos; --pos) { - if(const auto entity = epool[pos - 1]; entity_traits::to_entity(entity) == (pos - 1)) { - func(entity); - } - } + [[deprecated("use .storage().each() instead")]] void each(Func func) const { + for(auto [entt]: entities.each()) { + func(entt); } } @@ -1201,11 +1133,12 @@ public: * @sa sink * * @tparam Type Type of component of which to get the sink. + * @param id Optional name used to map the storage within the registry. * @return A temporary sink object. */ template - [[nodiscard]] auto on_construct() { - return assure().on_construct(); + [[nodiscard]] auto on_construct(const id_type id = type_hash::value()) { + return assure(id).on_construct(); } /** @@ -1224,11 +1157,12 @@ public: * @sa sink * * @tparam Type Type of component of which to get the sink. + * @param id Optional name used to map the storage within the registry. * @return A temporary sink object. */ template - [[nodiscard]] auto on_update() { - return assure().on_update(); + [[nodiscard]] auto on_update(const id_type id = type_hash::value()) { + return assure(id).on_update(); } /** @@ -1247,21 +1181,16 @@ public: * @sa sink * * @tparam Type Type of component of which to get the sink. + * @param id Optional name used to map the storage within the registry. * @return A temporary sink object. */ template - [[nodiscard]] auto on_destroy() { - return assure().on_destroy(); + [[nodiscard]] auto on_destroy(const id_type id = type_hash::value()) { + return assure(id).on_destroy(); } /** * @brief Returns a view for the given components. - * - * Views are created on the fly and share with the registry its internal - * data structures. Feel free to discard them after the use.
- * Creating and destroying a view is an incredibly cheap operation. As a - * rule of thumb, storing a view should never be an option. - * * @tparam Type Type of component used to construct the view. * @tparam Other Other types of components used to construct the view. * @tparam Exclude Types of components used to filter the view. @@ -1269,172 +1198,79 @@ public: */ template [[nodiscard]] basic_view, storage_for_type...>, exclude_t...>> - view(exclude_t = {}) const { - return {assure>(), assure>()..., assure>()...}; + view(exclude_t = exclude_t{}) const { + const auto cpools = std::make_tuple(assure>(), assure>()..., assure>()...); + basic_view, storage_for_type...>, exclude_t...>> elem{}; + std::apply([&elem](const auto *...curr) { ((curr ? elem.storage(*curr) : void()), ...); }, cpools); + return elem; } /*! @copydoc view */ template [[nodiscard]] basic_view, storage_for_type...>, exclude_t...>> - view(exclude_t = {}) { + view(exclude_t = exclude_t{}) { return {assure>(), assure>()..., assure>()...}; } /** * @brief Returns a group for the given components. - * - * Groups are created on the fly and share with the registry its internal - * data structures. Feel free to discard them after the use.
- * Creating and destroying a group is an incredibly cheap operation. As a - * rule of thumb, storing a group should never be an option. - * - * Groups support exclusion lists and can own types of components. The more - * types are owned by a group, the faster it is to iterate entities and - * components.
- * However, groups also affect some features of the registry such as the - * creation and destruction of components. - * - * @note - * Pools of components that are owned by a group cannot be sorted anymore. - * The group takes the ownership of the pools and arrange components so as - * to iterate them as fast as possible. - * - * @tparam Owned Type of storage _owned_ by the group. - * @tparam Get Type of storage _observed_ by the group. - * @tparam Exclude Type of storage used to filter the group. + * @tparam Owned Types of storage _owned_ by the group. + * @tparam Get Types of storage _observed_ by the group, if any. + * @tparam Exclude Types of storage used to filter the group, if any. * @return A newly created group. */ template - [[nodiscard]] basic_group...>, get_t...>, exclude_t...>> - group(get_t = {}, exclude_t = {}) { - static_assert(sizeof...(Owned) + sizeof...(Get) > 0, "Exclusion-only groups are not supported"); - static_assert(sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude) > 1, "Single component groups are not allowed"); + basic_group...>, get_t...>, exclude_t...>> + group(get_t = get_t{}, exclude_t = exclude_t{}) { + using handler_type = typename basic_group...>, get_t...>, exclude_t...>>::handler; - using handler_type = group_handler...>, get_t...>, std::remove_const_t...>; - - const auto cpools = std::forward_as_tuple(assure>()..., assure>()...); - constexpr auto size = sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude); - handler_type *handler = nullptr; - - auto it = std::find_if(groups.cbegin(), groups.cend(), [size](const auto &gdata) { - return gdata.size == size - && (gdata.owned(type_hash>::value()) && ...) - && (gdata.get(type_hash>::value()) && ...) - && (gdata.exclude(type_hash>::value()) && ...); - }); - - if(it != groups.cend()) { - handler = static_cast(it->group.get()); - } else { - group_data candidate = { - size, - std::apply([this](auto &&...args) { return std::allocate_shared(get_allocator(), std::forward(args)...); }, entt::uses_allocator_construction_args(get_allocator())), - []([[maybe_unused]] const id_type ctype) noexcept { return ((ctype == type_hash>::value()) || ...); }, - []([[maybe_unused]] const id_type ctype) noexcept { return ((ctype == type_hash>::value()) || ...); }, - []([[maybe_unused]] const id_type ctype) noexcept { return ((ctype == type_hash>::value()) || ...); }, - }; - - handler = static_cast(candidate.group.get()); - - const void *maybe_valid_if = nullptr; - const void *discard_if = nullptr; - - if constexpr(sizeof...(Owned) == 0) { - groups.push_back(std::move(candidate)); - } else { - [[maybe_unused]] auto has_conflict = [size](const auto &gdata) { - const auto overlapping = (0u + ... + gdata.owned(type_hash>::value())); - const auto sz = overlapping + (0u + ... + gdata.get(type_hash>::value())) + (0u + ... + gdata.exclude(type_hash>::value())); - return !overlapping || ((sz == size) || (sz == gdata.size)); - }; - - ENTT_ASSERT(std::all_of(groups.cbegin(), groups.cend(), std::move(has_conflict)), "Conflicting groups"); - - const auto next = std::find_if_not(groups.cbegin(), groups.cend(), [size](const auto &gdata) { - return !(0u + ... + gdata.owned(type_hash>::value())) || (size > gdata.size); - }); - - const auto prev = std::find_if(std::make_reverse_iterator(next), groups.crend(), [](const auto &gdata) { - return (0u + ... + gdata.owned(type_hash>::value())); - }); - - maybe_valid_if = (next == groups.cend() ? maybe_valid_if : next->group.get()); - discard_if = (prev == groups.crend() ? discard_if : prev->group.get()); - groups.insert(next, std::move(candidate)); - } - - (on_construct>().before(maybe_valid_if).template connect<&handler_type::template maybe_valid_if>>(*handler), ...); - (on_construct>().before(maybe_valid_if).template connect<&handler_type::template maybe_valid_if>>(*handler), ...); - (on_destroy>().before(maybe_valid_if).template connect<&handler_type::template maybe_valid_if>>(*handler), ...); - - (on_destroy>().before(discard_if).template connect<&handler_type::discard_if>(*handler), ...); - (on_destroy>().before(discard_if).template connect<&handler_type::discard_if>(*handler), ...); - (on_construct>().before(discard_if).template connect<&handler_type::discard_if>(*handler), ...); - - if constexpr(sizeof...(Owned) == 0) { - for(const auto entity: view(exclude)) { - handler->current.emplace(entity); - } - } else { - // we cannot iterate backwards because we want to leave behind valid entities in case of owned types - for(auto *first = std::get<0>(cpools).data(), *last = first + std::get<0>(cpools).size(); first != last; ++first) { - handler->template maybe_valid_if...>>>(*this, *first); - } - } + if(auto it = groups.find(type_hash::value()); it != groups.cend()) { + return {*std::static_pointer_cast(it->second)}; } - return {handler->current, std::get> &>(cpools)..., std::get> &>(cpools)...}; + std::shared_ptr handler{}; + + if constexpr(sizeof...(Owned) == 0u) { + handler = std::allocate_shared(get_allocator(), get_allocator(), assure>()..., assure>()...); + } else { + handler = std::allocate_shared(get_allocator(), assure>()..., assure>()..., assure>()...); + [[maybe_unused]] const id_type elem[]{type_hash>::value()..., type_hash>::value()..., type_hash>::value()...}; + ENTT_ASSERT(std::all_of(groups.cbegin(), groups.cend(), [&elem](const auto &data) { return data.second->owned(elem, sizeof...(Owned)) == 0u; }), "Conflicting groups"); + } + + groups.emplace(type_hash::value(), handler); + return {*handler}; } /*! @copydoc group */ template - [[nodiscard]] basic_group...>, get_t...>, exclude_t...>> - group_if_exists(get_t = {}, exclude_t = {}) const { - auto it = std::find_if(groups.cbegin(), groups.cend(), [](const auto &gdata) { - return gdata.size == (sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude)) - && (gdata.owned(type_hash>::value()) && ...) - && (gdata.get(type_hash>::value()) && ...) - && (gdata.exclude(type_hash>::value()) && ...); - }); + basic_group...>, get_t...>, exclude_t...>> + group_if_exists(get_t = get_t{}, exclude_t = exclude_t{}) const { + using handler_type = typename basic_group...>, get_t...>, exclude_t...>>::handler; - if(it == groups.cend()) { - return {}; - } else { - using handler_type = group_handler...>, get_t...>, std::remove_const_t...>; - return {static_cast(it->group.get())->current, assure>()..., assure>()...}; + if(auto it = groups.find(type_hash::value()); it != groups.cend()) { + return {*std::static_pointer_cast(it->second)}; } + + return {}; } /** * @brief Checks whether the given components belong to any group. - * @tparam Component Types of components in which one is interested. + * @tparam Type Type of component in which one is interested. + * @tparam Other Other types of components in which one is interested. * @return True if the pools of the given components are _free_, false * otherwise. */ - template + template [[nodiscard]] bool owned() const { - return std::any_of(groups.cbegin(), groups.cend(), [](auto &&gdata) { return (gdata.owned(type_hash>::value()) || ...); }); - } - - /** - * @brief Checks whether a group can be sorted. - * @tparam Owned Type of storage _owned_ by the group. - * @tparam Get Type of storage _observed_ by the group. - * @tparam Exclude Type of storage used to filter the group. - * @return True if the group can be sorted, false otherwise. - */ - template - [[nodiscard]] bool sortable(const basic_group, get_t, exclude_t> &) noexcept { - constexpr auto size = sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude); - auto pred = [size](const auto &gdata) { return (0u + ... + gdata.owned(type_hash::value())) && (size < gdata.size); }; - return std::find_if(groups.cbegin(), groups.cend(), std::move(pred)) == groups.cend(); + const id_type elem[]{type_hash>::value(), type_hash>::value()...}; + return std::any_of(groups.cbegin(), groups.cend(), [&elem](auto &&data) { return data.second->owned(elem, 1u + sizeof...(Other)); }); } /** * @brief Sorts the elements of a given component. * - * The order remains valid until a component of the given type is assigned - * to or removed from an entity.
* The comparison function object returns `true` if the first element is * _less_ than the second one, `false` otherwise. Its signature is also * equivalent to one of the following: @@ -1481,15 +1317,9 @@ public: /** * @brief Sorts two pools of components in the same way. * - * Being `To` and `From` the two sets, after invoking this function an - * iterator for `To` returns elements according to the following rules: - * - * * All entities in `To` that are also in `From` are returned first - * according to the order they have in `From`. - * * All entities in `To` that are not in `From` are returned in no - * particular order after all the other entities. - * - * Any subsequent change to `From` won't affect the order in `To`. + * Entities and components in `To` which are part of both storage are sorted + * internally with the order they have in `From`. The others follow in no + * particular order. * * @warning * Pools of components owned by a group cannot be sorted. @@ -1500,7 +1330,7 @@ public: template void sort() { ENTT_ASSERT(!owned(), "Cannot sort owned storage"); - assure().respect(assure()); + assure().sort_as(assure()); } /** @@ -1518,11 +1348,9 @@ public: private: context vars; - entity_type free_list; - std::vector epool; - // std::shared_ptr because of its type erased allocator which is useful here - dense_map, identity, std::equal_to, typename alloc_traits::template rebind_alloc>>> pools; - std::vector> groups; + pool_container_type pools; + group_container_type groups; + storage_for_type entities; }; } // namespace entt diff --git a/src/entt/entity/runtime_view.hpp b/src/entt/entity/runtime_view.hpp index 7ffad5ca..6f0bd195 100644 --- a/src/entt/entity/runtime_view.hpp +++ b/src/entt/entity/runtime_view.hpp @@ -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.
- * 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; + using iterator = internal::runtime_view_iterator; /*! @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.
+ * the entity itself.
* The signature of the function should be equivalent to the following: * * @code{.cpp} diff --git a/src/entt/entity/snapshot.hpp b/src/entt/entity/snapshot.hpp index d60befc7..77dd9cc4 100644 --- a/src/entt/entity/snapshot.hpp +++ b/src/entt/entity/snapshot.hpp @@ -1,7 +1,6 @@ #ifndef ENTT_ENTITY_SNAPSHOT_HPP #define ENTT_ENTITY_SNAPSHOT_HPP -#include #include #include #include @@ -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 +void orphans(Registry ®istry) { + auto view = registry.template view(); + + 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 class basic_snapshot { - using entity_traits = entt_traits; - - template - void get(Archive &archive, std::size_t sz, It first, It last) const { - const auto view = reg->template view(); - archive(typename entity_traits::entity_type(sz)); - - while(first != last) { - const auto entt = *(first++); - - if(reg->template all_of(entt)) { - std::apply(archive, std::tuple_cat(std::make_tuple(entt), view.get(entt))); - } - } - } - - template - void component(Archive &archive, It first, It last, std::index_sequence) const { - std::array size{}; - auto begin = first; - - while(begin != last) { - const auto entt = *(begin++); - ((reg->template all_of(entt) ? ++size[Index] : 0u), ...); - } - - (get(archive, size[Index], first, last), ...); - } + static_assert(!std::is_const_v, "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 - const basic_snapshot &entities(Archive &archive) const { - const auto sz = reg->size(); + template + const basic_snapshot &get(Archive &archive, const id_type id = type_hash::value()) const { + if(const auto *storage = reg->template storage(id); storage) { + archive(static_cast(storage->size())); - archive(typename entity_traits::entity_type(sz + 1u)); - archive(reg->released()); + if constexpr(std::is_same_v) { + archive(static_cast(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(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 + const basic_snapshot &get(Archive &archive, It first, It last, const id_type id = type_hash::value()) const { + static_assert(!std::is_same_v, "Entity types not supported"); + + if(const auto *storage = reg->template storage(id); storage && !storage->empty()) { + archive(static_cast(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(args)), ...); }, storage->get_as_tuple(entt)); + } else { + archive(static_cast(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 + [[deprecated("use .get(archive) instead")]] const basic_snapshot &entities(Archive &archive) const { + return get(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 - const basic_snapshot &component(Archive &archive) const { - if constexpr(sizeof...(Component) == 1u) { - const auto view = reg->template view(); - (component(archive, view.rbegin(), view.rend()), ...); - return *this; - } else { - (component(archive), ...); - return *this; - } + [[deprecated("use .get(archive) instead")]] const basic_snapshot &component(Archive &archive) const { + return (get(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 - const basic_snapshot &component(Archive &archive, It first, It last) const { - component(archive, first, last, std::index_sequence_for{}); - return *this; + [[deprecated("use .get(archive, first, last) instead")]] const basic_snapshot &component(Archive &archive, It first, It last) const { + return (get(archive, first, last), ...); } private: @@ -161,33 +195,8 @@ private: */ template class basic_snapshot_loader { - using entity_traits = entt_traits; - - template - void assign(Archive &archive) const { - typename entity_traits::entity_type length{}; - entity_type entt; - - archive(length); - - if constexpr(ignore_as_empty_v) { - 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(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(entt, std::move(instance)); - } - } - } + static_assert(!std::is_const_v, "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 - const basic_snapshot_loader &entities(Archive &archive) const { - typename entity_traits::entity_type length{}; + template + basic_snapshot_loader &get([[maybe_unused]] Archive &archive, const id_type id = type_hash::value()) { + auto &storage = reg->template storage(id); + typename traits_type::entity_type length{}; archive(length); - std::vector all(length); - for(std::size_t pos{}; pos < length; ++pos) { - archive(all[pos]); + if constexpr(std::is_same_v) { + 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 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::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 + [[deprecated("use .get(archive) instead")]] basic_snapshot_loader &entities(Archive &archive) { + return get(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 - const basic_snapshot_loader &component(Archive &archive) const { - (assign(archive), ...); - return *this; + [[deprecated("use .get(archive) instead")]] basic_snapshot_loader &component(Archive &archive) { + return (get(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.
- * 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.
- * 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 class basic_continuous_loader { - using entity_traits = entt_traits; - - 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, "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 - void remove_if_exists() { - for(auto &&ref: remloc) { - const auto local = ref.second.first; - - if(reg->valid(local)) { - reg->template remove(local); - } - } - } - - template - 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) { - while(length--) { - archive(entt); - restore(entt); - reg->template emplace_or_replace(map(entt)); - } - } else { - Component instance; - - while(length--) { - archive(entt, instance); - (update(instance, member), ...); - restore(entt); - reg->template emplace_or_replace(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.
+ * 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 + basic_continuous_loader &get([[maybe_unused]] Archive &archive, const id_type id = type_hash::value()) { + auto &storage = reg->template storage(id); + typename traits_type::entity_type length{}; + entity_type entt{null}; + + archive(length); + + if constexpr(std::is_same_v) { + 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::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 - 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(archive) instead")]] basic_continuous_loader &entities(Archive &archive) { + return get(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.
- * 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.
+ * 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 - basic_continuous_loader &component(Archive &archive, Member Other::*...member) { - (remove_if_exists(), ...); - (assign(archive, member...), ...); + template + [[deprecated("use .component(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::traits_type::page_size == 0u) { + storage.emplace(map(entt)); + } else { + typename std::remove_reference_t::value_type elem{}; + archive(elem); + (update(elem, member), ...); + storage.emplace(map(entt), std::move(elem)); + } + } + } + }(reg->template storage()), + ...); + 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(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.
- * 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> remloc; + dense_map> remloc; registry_type *reg; }; diff --git a/src/entt/entity/sparse_set.hpp b/src/entt/entity/sparse_set.hpp index 96aa172d..cc902eb0 100644 --- a/src/entt/entity/sparse_set.hpp +++ b/src/entt/entity/sparse_set.hpp @@ -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 -[[nodiscard]] constexpr std::ptrdiff_t operator-(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr std::ptrdiff_t operator-(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { return rhs.index() - lhs.index(); } -template -[[nodiscard]] constexpr bool operator==(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { return lhs.index() == rhs.index(); } -template -[[nodiscard]] constexpr bool operator!=(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { return !(lhs == rhs); } -template -[[nodiscard]] constexpr bool operator<(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { return lhs.index() > rhs.index(); } -template -[[nodiscard]] constexpr bool operator>(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { - return lhs.index() < rhs.index(); +template +[[nodiscard]] constexpr bool operator>(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { + return rhs < lhs; } -template -[[nodiscard]] constexpr bool operator<=(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<=(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { return !(lhs > rhs); } -template -[[nodiscard]] constexpr bool operator>=(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>=(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) noexcept { return !(lhs < rhs); } @@ -139,14 +143,6 @@ template * @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.
- * 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 @@ -176,23 +168,26 @@ class basic_sparse_set { static_assert(std::is_same_v, "Invalid value type"); using sparse_container_type = std::vector>; using packed_container_type = std::vector; - using entity_traits = entt_traits; [[nodiscard]] auto sparse_ptr(const Entity entt) const { - const auto pos = static_cast(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(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(entity_traits::to_entity(entt)); - return sparse[pos / entity_traits::page_size][fast_mod(pos, entity_traits::page_size)]; + const auto pos = static_cast(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(entity_traits::to_entity(entt)); - const auto page = pos / entity_traits::page_size; + const auto pos = static_cast(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; + /** + * @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(lhs); + const auto other = static_cast(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(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(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(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(packed.size() - 1u), entity_traits::to_integral(entt)); + elem = traits_type::combine(static_cast(packed.size() - 1u), traits_type::to_integral(entt)); return begin(); } else { - const auto pos = static_cast(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(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; /*! @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; /*! @brief Constant reverse iterator type. */ - using const_reverse_iterator = reverse_iterator; + using const_reverse_iterator = std::reverse_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(entity_traits::to_entity(sparse_ref(entt))); + return static_cast(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(std::as_const(*this).get(entt)); + /*! @copydoc value */ + [[nodiscard]] void *value(const entity_type entt) noexcept { + return const_cast(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(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 - 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(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) { + 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(to); + sparse_ref(packed[to]) = traits_type::combine(entity, traits_type::to_integral(packed[to])); - const auto entity = static_cast(to); - sparse_ref(packed[to]) = entity_traits::combine(entity, entity_traits::to_integral(packed[to])); - *it = entity_traits::combine(static_cast(from), entity_traits::reserved); + *it = traits_type::combine(static_cast(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(from), static_cast(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(curr); - sparse_ref(entt) = entity_traits::combine(entity, entity_traits::to_integral(packed[curr])); + swap_or_move(next, idx); + const auto entity = static_cast(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.
- * 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`.
+ * 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(); } diff --git a/src/entt/entity/storage.hpp b/src/entt/entity/storage.hpp index 87b21640..25a56ca8 100644 --- a/src/entt/entity/storage.hpp +++ b/src/entt/entity/storage.hpp @@ -9,7 +9,6 @@ #include #include #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 +template class storage_iterator final { - friend storage_iterator; + friend storage_iterator; using container_type = std::remove_const_t; using alloc_traits = std::allocator_traits; - using comp_traits = component_traits>; using iterator_traits = std::iterator_traits, @@ -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, typename = std::enable_if_t> - constexpr storage_iterator(const storage_iterator> &other) noexcept - : storage_iterator{other.packed, other.offset} {} + constexpr storage_iterator(const storage_iterator, 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 -[[nodiscard]] constexpr std::ptrdiff_t operator-(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr std::ptrdiff_t operator-(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { return rhs.index() - lhs.index(); } -template -[[nodiscard]] constexpr bool operator==(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { return lhs.index() == rhs.index(); } -template -[[nodiscard]] constexpr bool operator!=(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { return !(lhs == rhs); } -template -[[nodiscard]] constexpr bool operator<(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { return lhs.index() > rhs.index(); } -template -[[nodiscard]] constexpr bool operator>(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { - return lhs.index() < rhs.index(); +template +[[nodiscard]] constexpr bool operator>(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { + return rhs < lhs; } -template -[[nodiscard]] constexpr bool operator<=(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<=(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { return !(lhs > rhs); } -template -[[nodiscard]] constexpr bool operator>=(const storage_iterator &lhs, const storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>=(const storage_iterator &lhs, const storage_iterator &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()), std::forward_as_tuple(*std::declval()...))); using pointer = input_iterator_pointer; 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), *std::get(it)...}; } - template - friend constexpr bool operator==(const extended_storage_iterator &, const extended_storage_iterator &) noexcept; + [[nodiscard]] constexpr iterator_type base() const noexcept { + return std::get(it); + } + + template + friend constexpr bool operator==(const extended_storage_iterator &, const extended_storage_iterator &) noexcept; private: std::tuple it; }; -template -[[nodiscard]] constexpr bool operator==(const extended_storage_iterator &lhs, const extended_storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const extended_storage_iterator &lhs, const extended_storage_iterator &rhs) noexcept { return std::get<0>(lhs.it) == std::get<0>(rhs.it); } -template -[[nodiscard]] constexpr bool operator!=(const extended_storage_iterator &lhs, const extended_storage_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const extended_storage_iterator &lhs, const extended_storage_iterator &rhs) noexcept { return !(lhs == rhs); } @@ -227,43 +229,43 @@ template * 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 class basic_storage: public basic_sparse_set::template rebind_alloc> { using alloc_traits = std::allocator_traits; static_assert(std::is_same_v, "Invalid value type"); - using underlying_type = basic_sparse_set>; using container_type = std::vector>; - using comp_traits = component_traits; + using underlying_type = basic_sparse_set>; + using underlying_iterator = typename underlying_type::basic_iterator; static constexpr bool is_pinned_type_v = !(std::is_move_constructible_v && std::is_move_assignable_v); [[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 @@ -272,7 +274,7 @@ class basic_storage: public basic_sparse_set(it.index())); - entt::uninitialized_construct_using_allocator(to_address(elem), packed.second(), std::forward(args)...); + entt::uninitialized_construct_using_allocator(to_address(elem), get_allocator(), std::forward(args)...); } ENTT_CATCH { base_type::pop(it, it + 1u); @@ -283,25 +285,24 @@ class basic_storage: public basic_sparse_set(first.index())))); + } + } else { + base_type::swap_and_pop(first); + alloc_traits::destroy(allocator, std::addressof(element_at(static_cast(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) { return emplace_element(entt, force_back, *static_cast(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; /*! @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::const_pointer; /*! @brief Random access iterator type. */ - using iterator = internal::storage_iterator; + using iterator = internal::storage_iterator; /*! @brief Constant random access iterator type. */ - using const_iterator = internal::storage_iterator; + using const_iterator = internal::storage_iterator; /*! @brief Reverse iterator type. */ using reverse_iterator = std::reverse_iterator; /*! @brief Constant reverse iterator type. */ @@ -407,6 +424,10 @@ public: using iterable = iterable_adaptor>; /*! @brief Constant extended iterable storage proxy. */ using const_iterable = iterable_adaptor>; + /*! @brief Extended reverse iterable storage proxy. */ + using reverse_iterable = iterable_adaptor>; + /*! @brief Constant extended reverse iterable storage proxy. */ + using const_reverse_iterable = iterable_adaptor>; /*! @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(), deletion_policy{comp_traits::in_place_delete}, allocator}, - packed{container_type{allocator}, allocator} {} + : base_type{type_id(), 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(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(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 value_type &emplace(const entity_type entt, Args &&...args) { - if constexpr(std::is_aggregate_v) { + if constexpr(std::is_aggregate_v && (sizeof...(Args) != 0u || !std::is_default_constructible_v)) { const auto it = emplace_element(entt, false, Type{std::forward(args)...}); return element_at(static_cast(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 - 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::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 packed; + container_type payload; }; /*! @copydoc basic_storage */ template -class basic_storage>> +class basic_storage::page_size == 0u>> : public basic_sparse_set::template rebind_alloc> { using alloc_traits = std::allocator_traits; static_assert(std::is_same_v, "Invalid value type"); - using underlying_type = basic_sparse_set>; - using comp_traits = component_traits; public: /*! @brief Base type. */ - using base_type = underlying_type; - /*! @brief Allocator type. */ - using allocator_type = Allocator; + using base_type = basic_sparse_set>; /*! @brief Type of the objects assigned to entities. */ using value_type = Type; + /*! @brief Component traits. */ + using traits_type = component_traits; /*! @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>; /*! @brief Constant extended iterable storage proxy. */ using const_iterable = iterable_adaptor>; + /*! @brief Extended reverse iterable storage proxy. */ + using reverse_iterable = iterable_adaptor>; + /*! @brief Constant extended reverse iterable storage proxy. */ + using const_reverse_iterable = iterable_adaptor>; /*! @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(), deletion_policy{comp_traits::in_place_delete}, allocator} {} + : base_type{type_id(), 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 +class basic_storage + : public basic_sparse_set { + using alloc_traits = std::allocator_traits; + static_assert(std::is_same_v, "Invalid value type"); + using underlying_type = basic_sparse_set>; + using underlying_iterator = typename underlying_type::basic_iterator; + using local_traits_type = entt_traits; + + 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(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; + /*! @brief Type of the objects assigned to entities. */ + using value_type = Entity; + /*! @brief Component traits. */ + using traits_type = component_traits; + /*! @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>; + /*! @brief Constant extended iterable storage proxy. */ + using const_iterable = iterable_adaptor>; + /*! @brief Extended reverse iterable storage proxy. */ + using reverse_iterable = iterable_adaptor>; + /*! @brief Constant extended reverse iterable storage proxy. */ + using const_reverse_iterable = iterable_adaptor>; + + /*! @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(), 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(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 + void patch([[maybe_unused]] const entity_type entt, Func &&...func) { + ENTT_ASSERT(base_type::contains(entt), "Storage does not contain entity"); + (std::forward(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 + 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 + 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(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 diff --git a/src/entt/entity/storage_mixin.hpp b/src/entt/entity/storage_mixin.hpp deleted file mode 100644 index 0686e4e7..00000000 --- a/src/entt/entity/storage_mixin.hpp +++ /dev/null @@ -1,236 +0,0 @@ -#ifndef ENTT_ENTITY_SIGH_STORAGE_MIXIN_HPP -#define ENTT_ENTITY_SIGH_STORAGE_MIXIN_HPP - -#include -#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); - * @endcode - * - * This applies to all signals made available. - * - * @tparam Type The type of the underlying storage. - */ -template -class sigh_storage_mixin final: public Type { - using basic_registry_type = basic_registry; - using sigh_type = sigh; - 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.
- * 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.
- * 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.
- * 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 - decltype(auto) emplace(const entity_type entt, Args &&...args) { - ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); - Type::emplace(entt, std::forward(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 - decltype(auto) patch(const entity_type entt, Func &&...func) { - ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); - Type::patch(entt, std::forward(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 - void insert(It first, It last, Args &&...args) { - ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); - Type::insert(first, last, std::forward(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(&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 diff --git a/src/entt/entity/view.hpp b/src/entt/entity/view.hpp index 2b8629ec..5b55ac56 100644 --- a/src/entt/entity/view.hpp +++ b/src/entt/entity/view.hpp @@ -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 +[[nodiscard]] auto filter_as_tuple(const std::array &filter) noexcept { + return std::apply([](const auto *...curr) { return std::make_tuple(static_cast(const_cast *>(curr))...); }, filter); +} + +template +[[nodiscard]] auto none_of(const std::array &filter, const typename Type::entity_type entt) noexcept { + return std::apply([entt](const auto *...curr) { return (!(curr && curr->contains(entt)) && ...); }, filter); +} + +template +[[nodiscard]] auto view_pack(const std::tuple value, const std::tuple excl, std::index_sequence) { + const auto pools = std::tuple_cat(value, excl); + basic_view, exclude_t> elem{}; + (((std::get(pools) != nullptr) ? elem.template storage(*std::get(pools)) : void()), ...); + return elem; +} + template 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 all_of, std::array none_of) noexcept + view_iterator(iterator_type curr, iterator_type to, std::array value, std::array 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 template 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()), std::declval().get_as_tuple({})...)); using pointer = input_iterator_pointer; @@ -107,9 +123,9 @@ struct extended_view_iterator final { : it{}, pools{} {} - extended_view_iterator(It from, std::tuple storage) + extended_view_iterator(It from, std::tuple 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 friend bool constexpr operator==(const extended_view_iterator &, const extended_view_iterator &) 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 class basic_view, exclude_t> { - using underlying_type = std::common_type_t; - using basic_common_type = std::common_type_t; + static constexpr auto offset = sizeof...(Get); + using base_type = std::common_type_t; + using underlying_type = typename base_type::entity_type; template friend class basic_view; template - static constexpr std::size_t index_of = type_list_index_v, type_list>; + static constexpr std::size_t index_of = type_list_index_v, type_list>; [[nodiscard]] auto opaque_check_set() const noexcept { - std::array other{}; + std::array 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{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 @@ -211,18 +233,14 @@ class basic_view, exclude_t> { if constexpr(Curr == Other) { return std::forward_as_tuple(std::get(curr)...); } else { - return storage().get_as_tuple(std::get<0>(curr)); + return std::get(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 void each(Func &func, std::index_sequence) const { - for(const auto curr: storage().each()) { - if(const auto entt = std::get<0>(curr); ((sizeof...(Get) != 1u) || (entt != tombstone)) && ((Curr == Index || storage().contains(entt)) && ...) && !reject(entt)) { + for(const auto curr: std::get(pools)->each()) { + if(const auto entt = std::get<0>(curr); ((sizeof...(Get) != 1u) || (entt != tombstone)) && ((Curr == Index || std::get(pools)->contains(entt)) && ...) && internal::none_of(filter, entt)) { if constexpr(is_applicable_v{}, std::declval().get({})))>) { std::apply(func, std::tuple_cat(std::make_tuple(entt), dispatch_get(curr)...)); } else { @@ -234,7 +252,7 @@ class basic_view, exclude_t> { template void pick_and_each(Func &func, std::index_sequence seq) const { - ((&storage() == view ? each(func, seq) : void()), ...); + ((std::get(pools) == view ? each(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; + using iterator = internal::view_iterator; /*! @brief Iterable view type. */ using iterable = iterable_adaptor>; @@ -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 value, std::tuple 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(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 - [[nodiscard]] basic_view use() const noexcept { - return use>(); + void use() noexcept { + use>(); } /** - * @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 - [[nodiscard]] basic_view use() const noexcept { - basic_view other{*this}; - other.view = &storage(); - return other; + void use() noexcept { + if(view) { + view = std::get(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 - [[nodiscard]] decltype(auto) storage() const noexcept { + [[nodiscard]] auto *storage() const noexcept { return storage>(); } /** - * @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 - [[nodiscard]] decltype(auto) storage() const noexcept { - return *std::get(pools); + [[nodiscard]] auto *storage() const noexcept { + if constexpr(Index < offset) { + return std::get(pools); + } else { + return std::get(internal::filter_as_tuple(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 + void storage(Type &elem) noexcept { + storage>(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 + void storage(Type &elem) noexcept { + if constexpr(Index < offset) { + std::get(pools) = &elem; + refresh(); + } else { + std::get(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 + template [[nodiscard]] decltype(auto) get(const entity_type entt) const { - if constexpr(sizeof...(Type) == 0) { + return get, index_of...>(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 + [[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>().get(entt), ...); + } else if constexpr(sizeof...(Index) == 1) { + return (std::get(pools)->get(entt), ...); } else { - return std::tuple_cat(storage>().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 - [[nodiscard]] decltype(auto) get(const entity_type entt) const { - if constexpr(sizeof...(Other) == 0) { - return storage().get(entt); - } else { - return std::tuple_cat(storage().get_as_tuple(entt), storage().get_as_tuple(entt)...); + return std::tuple_cat(std::get(pools)->get_as_tuple(entt)...); } } @@ -487,7 +524,7 @@ public: */ template void each(Func func) const { - pick_and_each(func, std::index_sequence_for{}); + view ? pick_and_each(func, std::index_sequence_for{}) : void(); } /** @@ -512,14 +549,16 @@ public: */ template [[nodiscard]] auto operator|(const basic_view, exclude_t> &other) const noexcept { - return std::make_from_tuple, exclude_t>>( - 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(filter), internal::filter_as_tuple(other.filter)), + std::index_sequence_for{}); } private: std::tuple pools; - std::tuple filter; - const base_type *view; + std::array 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 -class basic_view, exclude_t<>, std::void_t::in_place_delete>>> { +class basic_view, exclude_t<>, std::void_t>> { template 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().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 ref, std::tuple<> = {}) noexcept - : pools{&std::get<0>(ref)}, - filter{} {} + basic_view(std::tuple 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 - [[nodiscard]] decltype(auto) storage() const noexcept { + [[nodiscard]] auto *storage() const noexcept { static_assert(std::is_same_v, 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 - [[nodiscard]] decltype(auto) storage() const noexcept { - return *std::get(pools); + [[nodiscard]] auto *storage() const noexcept { + return std::get(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 + void storage(Get &elem) noexcept { + view = std::get(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 + template [[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, typename Get::value_type> && ...), "Invalid component type"); - return storage().get(entt); - } + static_assert(std::is_same_v, typename Get::value_type>, "Invalid component type"); + return get<0>(entt); } /*! @copydoc get */ - template + template [[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(pools)->get(entt); + } } /** @@ -794,17 +840,19 @@ public: */ template void each(Func func) const { - if constexpr(is_applicable_v) { - for(const auto pack: each()) { - std::apply(func, pack); - } - } else if constexpr(ignore_as_empty_v) { - for(size_type pos{}, last = size(); pos < last; ++pos) { - func(); - } - } else { - for(auto &&component: storage()) { - func(component); + if(view) { + if constexpr(is_applicable_v) { + 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 [[nodiscard]] auto operator|(const basic_view, exclude_t> &other) const noexcept { - return std::make_from_tuple, exclude_t>>( - 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(other.filter), + std::index_sequence_for{}); } private: std::tuple pools; - std::tuple<> filter; + std::array filter; + const common_type *view; }; /** diff --git a/src/entt/entt.hpp b/src/entt/entt.hpp index 3544fb60..249fd525 100644 --- a/src/entt/entt.hpp +++ b/src/entt/entt.hpp @@ -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" diff --git a/src/entt/graph/adjacency_matrix.hpp b/src/entt/graph/adjacency_matrix.hpp index b0b0e254..8fc0a5a6 100644 --- a/src/entt/graph/adjacency_matrix.hpp +++ b/src/entt/graph/adjacency_matrix.hpp @@ -258,7 +258,7 @@ public: [[nodiscard]] iterable_adaptor 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_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}}; } diff --git a/src/entt/graph/flow.hpp b/src/entt/graph/flow.hpp index d121f500..54f0c12a 100644 --- a/src/entt/graph/flow.hpp +++ b/src/entt/graph/flow.hpp @@ -32,6 +32,7 @@ class basic_flow { using task_container_type = dense_set, typename alloc_traits::template rebind_alloc>; using ro_rw_container_type = std::vector, typename alloc_traits::template rebind_alloc>>; using deps_container_type = dense_map, typename alloc_traits::template rebind_alloc>>; + using adjacency_matrix_type = adjacency_matrix>; 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; + /*! @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 graph() const { - const auto length = vertices.size(); - adjacency_matrix 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; } diff --git a/src/entt/locator/locator.hpp b/src/entt/locator/locator.hpp index e33377ab..6e020c3c 100644 --- a/src/entt/locator/locator.hpp +++ b/src/entt/locator/locator.hpp @@ -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 + template [[nodiscard]] static Service &value_or(Args &&...args) { - return service ? *service : emplace(std::forward(args)...); + return service ? *service : emplace(std::forward(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 + template static Service &emplace(Args &&...args) { - service = std::make_shared(std::forward(args)...); + service = std::make_shared(std::forward(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 - static Service &allocate_emplace(Allocator alloc, Args &&...args) { - service = std::allocate_shared(alloc, std::forward(args)...); + template + static Service &emplace(std::allocator_arg_t, Allocator alloc, Args &&...args) { + service = std::allocate_shared(alloc, std::forward(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> + static void reset(Type *elem, Deleter deleter = {}) { + service = std::shared_ptr{elem, std::move(deleter)}; + } + private: // std::shared_ptr because of its type erased allocator which is useful here inline static std::shared_ptr service{}; diff --git a/src/entt/meta/context.hpp b/src/entt/meta/context.hpp index d9ea6229..a7bf7192 100644 --- a/src/entt/meta/context.hpp +++ b/src/entt/meta/context.hpp @@ -21,8 +21,8 @@ struct meta_type_node; struct meta_context { dense_map 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; } diff --git a/src/entt/meta/factory.hpp b/src/entt/meta/factory.hpp index b548ec88..b0f623f0 100644 --- a/src/entt/meta/factory.hpp +++ b/src/entt/meta/factory.hpp @@ -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 &prop, const id_type id, meta_prop_node node) { - return (prop[id] = std::move(node)); -} - } // namespace internal /** @@ -156,16 +136,8 @@ public: template auto base() noexcept { static_assert(!std::is_same_v && std::is_base_of_v, "Invalid base type"); - - internal::meta_extend( - internal::owner(*ctx, *info), - type_id().hash(), - internal::meta_base_node{ - &internal::resolve, - +[](const void *instance) noexcept { - return static_cast(static_cast(static_cast(instance))); - }}); - + auto *const op = +[](const void *instance) noexcept { return static_cast(static_cast(static_cast(instance))); }; + internal::owner(*ctx, *info).details->base.insert_or_assign(type_id().hash(), internal::meta_base_node{&internal::resolve, op}); bucket = nullptr; return *this; } @@ -185,15 +157,8 @@ public: template auto conv() noexcept { using conv_type = std::remove_cv_t>>; - - internal::meta_extend( - internal::owner(*ctx, *info), - type_id().hash(), - internal::meta_conv_node{ - +[](const meta_ctx &area, const void *instance) { - return forward_as_meta(area, std::invoke(Candidate, *static_cast(instance))); - }}); - + auto *const op = +[](const meta_ctx &area, const void *instance) { return forward_as_meta(area, std::invoke(Candidate, *static_cast(instance))); }; + internal::owner(*ctx, *info).details->conv.insert_or_assign(type_id().hash(), internal::meta_conv_node{op}); bucket = nullptr; return *this; } @@ -210,15 +175,8 @@ public: template auto conv() noexcept { using conv_type = std::remove_cv_t>; - - internal::meta_extend( - internal::owner(*ctx, *info), - type_id().hash(), - internal::meta_conv_node{ - +[](const meta_ctx &area, const void *instance) { - return forward_as_meta(area, static_cast(*static_cast(instance))); - }}); - + auto *const op = +[](const meta_ctx &area, const void *instance) { return forward_as_meta(area, static_cast(*static_cast(instance))); }; + internal::owner(*ctx, *info).details->conv.insert_or_assign(type_id().hash(), internal::meta_conv_node{op}); bucket = nullptr; return *this; } @@ -241,15 +199,7 @@ public: using descriptor = meta_function_helper_t; static_assert(Policy::template value, "Invalid return type for the given policy"); static_assert(std::is_same_v>, Type>, "The function doesn't return an object of the required type"); - - internal::meta_extend( - internal::owner(*ctx, *info), - type_id().hash(), - internal::meta_ctor_node{ - descriptor::args_type::size, - &meta_arg, - &meta_construct}); - + internal::owner(*ctx, *info).details->ctor.insert_or_assign(type_id().hash(), internal::meta_ctor_node{descriptor::args_type::size, &meta_arg, &meta_construct}); 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; - - internal::meta_extend( - internal::owner(*ctx, *info), - type_id().hash(), - internal::meta_ctor_node{ - descriptor::args_type::size, - &meta_arg, - &meta_construct}); + internal::owner(*ctx, *info).details->ctor.insert_or_assign(type_id().hash(), internal::meta_ctor_node{descriptor::args_type::size, &meta_arg, &meta_construct}); } bucket = nullptr; @@ -304,12 +247,8 @@ public: template auto dtor() noexcept { static_assert(std::is_invocable_v, "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(instance)); }}); - + auto *const op = +[](void *instance) { std::invoke(Func, *static_cast(instance)); }; + internal::owner(*ctx, *info).dtor = internal::meta_dtor_node{op}; bucket = nullptr; return *this; } @@ -330,32 +269,39 @@ public: template auto data(const id_type id) noexcept { if constexpr(std::is_member_object_pointer_v) { - using data_type = std::remove_reference_t>; + using data_type = std::invoke_result_t; + static_assert(Policy::template value, "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 ? internal::meta_traits::is_const : internal::meta_traits::is_none, + std::is_const_v> ? internal::meta_traits::is_const : internal::meta_traits::is_none, 1u, - &internal::resolve>, - &meta_arg>>, + &internal::resolve>>, + &meta_arg>>>, &meta_setter, &meta_getter}); bucket = &elem.prop; } else { - using data_type = std::remove_reference_t>; + using data_type = std::remove_pointer_t; + + if constexpr(std::is_pointer_v) { + static_assert(Policy::template value, "Invalid return type for the given policy"); + } else { + static_assert(Policy::template value, "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> || std::is_const_v) ? internal::meta_traits::is_const : internal::meta_traits::is_none) | internal::meta_traits::is_static, + ((std::is_same_v>> || std::is_const_v>) ? internal::meta_traits::is_const : internal::meta_traits::is_none) | internal::meta_traits::is_static, 1u, - &internal::resolve>, - &meta_arg>>, + &internal::resolve>>, + &meta_arg>>>, &meta_setter, &meta_getter}); @@ -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}); + (*bucket)[id] = internal::meta_prop_node{&internal::resolve}; } else { - internal::meta_extend( - *bucket, - id, - internal::meta_prop_node{ - &internal::resolve>..., - std::make_shared>(std::forward(value))...}); + (*bucket)[id] = internal::meta_prop_node{ + &internal::resolve>..., + std::make_shared>(std::forward(value))...}; } return *this; diff --git a/src/entt/meta/meta.hpp b/src/entt/meta/meta.hpp index f8cb917f..055a5f55 100644 --- a/src/entt/meta/meta.hpp +++ b/src/entt/meta/meta.hpp @@ -613,7 +613,7 @@ private: * @return A properly initialized and not necessarily owning wrapper. */ template -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, std::forward(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 -meta_any forward_as_meta(Type &&value) { +[[nodiscard]] meta_any forward_as_meta(Type &&value) { return forward_as_meta(locator::value_or(), std::forward(value)); } @@ -722,6 +722,16 @@ struct meta_handle { return static_cast(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 meta_any invoke(meta_handle instance, Args &&...args) const { - meta_any arguments[sizeof...(Args) + 1u]{{*ctx, std::forward(args)}...}; - return invoke(meta_handle{*ctx, std::move(instance)}, arguments, sizeof...(Args)); + if constexpr(sizeof...(Args) == 0u) { + return invoke(std::move(instance), static_cast(nullptr), size_type{}); + } else { + meta_any arguments[sizeof...(Args)]{{*ctx, std::forward(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 @@ -1339,8 +1410,12 @@ public: */ template [[nodiscard]] meta_any construct(Args &&...args) const { - meta_any arguments[sizeof...(Args) + 1u]{{*ctx, std::forward(args)}...}; - return construct(arguments, sizeof...(Args)); + if constexpr(sizeof...(Args) == 0u) { + return construct(static_cast(nullptr), size_type{}); + } else { + meta_any arguments[sizeof...(Args)]{{*ctx, std::forward(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 meta_any invoke(const id_type id, meta_handle instance, Args &&...args) const { - meta_any arguments[sizeof...(Args) + 1u]{{*ctx, std::forward(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(nullptr), size_type{}); + } else { + meta_any arguments[sizeof...(Args)]{{*ctx, std::forward(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}, - 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, It iter) noexcept : ctx{&area}, vtable{&basic_vtable}, - 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, {}); } /** diff --git a/src/entt/meta/policy.hpp b/src/entt/meta/policy.hpp index 0744816d..e7cf44ee 100644 --- a/src/entt/meta/policy.hpp +++ b/src/entt/meta/policy.hpp @@ -68,11 +68,7 @@ struct as_void_t final { */ template struct is_meta_policy - : std::disjunction< - std::is_same, - std::is_same, - std::is_same, - std::is_same> {}; + : std::bool_constant || std::is_same_v || std::is_same_v || std::is_same_v> {}; /** * @brief Helper variable template. diff --git a/src/entt/meta/range.hpp b/src/entt/meta/range.hpp index e3d8154c..b26fcb2c 100644 --- a/src/entt/meta/range.hpp +++ b/src/entt/meta/range.hpp @@ -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; } diff --git a/src/entt/meta/type_traits.hpp b/src/entt/meta/type_traits.hpp index 51b69f69..9ba167f5 100644 --- a/src/entt/meta/type_traits.hpp +++ b/src/entt/meta/type_traits.hpp @@ -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 struct is_meta_pointer_like: std::false_type {}; diff --git a/src/entt/meta/utility.hpp b/src/entt/meta/utility.hpp index 3ee98ee3..035ba504 100644 --- a/src/entt/meta/utility.hpp +++ b/src/entt/meta/utility.hpp @@ -92,9 +92,12 @@ template struct meta_function_descriptor : meta_function_descriptor_traits< Ret, - std::conditional_t>, Type>, type_list, type_list>, - !std::is_base_of_v>, Type>, - std::is_base_of_v>, Type> && std::is_const_v>> {}; + std::conditional_t< + std::is_same_v>, Type> || std::is_base_of_v>, Type>, + type_list, + type_list>, + !(std::is_same_v>, Type> || std::is_base_of_v>, Type>), + std::is_const_v> && (std::is_same_v>, Type> || std::is_base_of_v>, Type>)> {}; /** * @brief Meta function descriptor. @@ -162,7 +165,7 @@ using meta_function_helper_t = typename meta_function_helper::t * @return A meta any containing the returned value, if any. */ template -std::enable_if_t, meta_any> meta_dispatch(const meta_ctx &ctx, [[maybe_unused]] Type &&value) { +[[nodiscard]] std::enable_if_t, meta_any> meta_dispatch(const meta_ctx &ctx, [[maybe_unused]] Type &&value) { if constexpr(std::is_same_v) { return meta_any{ctx, std::in_place_type}; } else if constexpr(std::is_same_v) { @@ -183,7 +186,7 @@ std::enable_if_t, meta_any> meta_dispatch(const meta_ct * @return A meta any containing the returned value, if any. */ template -std::enable_if_t, meta_any> meta_dispatch(Type &&value) { +[[nodiscard]] std::enable_if_t, meta_any> meta_dispatch(Type &&value) { return meta_dispatch(locator::value_or(), std::forward(value)); } diff --git a/src/entt/poly/poly.hpp b/src/entt/poly/poly.hpp index f10419e0..08b242dc 100644 --- a/src/entt/poly/poly.hpp +++ b/src/entt/poly/poly.hpp @@ -19,7 +19,7 @@ struct poly_inspector { * @brief Generic conversion operator (definition only). * @tparam Type Type to which conversion is requested. */ - template + template operator Type &&() const; /** diff --git a/src/entt/process/fwd.hpp b/src/entt/process/fwd.hpp index a0dc53fe..5f4cda18 100644 --- a/src/entt/process/fwd.hpp +++ b/src/entt/process/fwd.hpp @@ -1,13 +1,18 @@ #ifndef ENTT_PROCESS_FWD_HPP #define ENTT_PROCESS_FWD_HPP +#include + namespace entt { template class process; -template -class scheduler; +template +class basic_scheduler; + +/*! @brief Alias declaration for the most common use case. */ +using scheduler = basic_scheduler<>; } // namespace entt diff --git a/src/entt/process/scheduler.hpp b/src/entt/process/scheduler.hpp index 23c76427..c6bd97eb 100644 --- a/src/entt/process/scheduler.hpp +++ b/src/entt/process/scheduler.hpp @@ -7,6 +7,7 @@ #include #include #include +#include "fwd.hpp" #include "process.hpp" namespace entt { @@ -38,11 +39,11 @@ namespace entt { * @tparam Delta Type to use to provide elapsed time. */ template -class scheduler { +class basic_scheduler { struct process_handler { using instance_type = std::unique_ptr; - 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; instance_type instance; @@ -58,8 +59,8 @@ class scheduler { template continuation then(Args &&...args) { static_assert(std::is_base_of_v, Proc>, "Invalid process type"); - auto proc = typename process_handler::instance_type{new Proc{std::forward(args)...}, &scheduler::deleter}; - handler->next.reset(new process_handler{std::move(proc), &scheduler::update, &scheduler::abort, nullptr}); + auto proc = typename process_handler::instance_type{new Proc{std::forward(args)...}, &basic_scheduler::deleter}; + handler->next.reset(new process_handler{std::move(proc), &basic_scheduler::update, &basic_scheduler::abort, nullptr}); handler = handler->next.get(); return *this; } @@ -74,7 +75,7 @@ class scheduler { }; template - [[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(owner.handlers[pos].instance.get()); process->tick(delta, data); @@ -94,7 +95,7 @@ class scheduler { } template - 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(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 auto attach(Args &&...args) { static_assert(std::is_base_of_v, Proc>, "Invalid process type"); - auto proc = typename process_handler::instance_type{new Proc{std::forward(args)...}, &scheduler::deleter}; - auto &&ref = handlers.emplace_back(process_handler{std::move(proc), &scheduler::update, &scheduler::abort, nullptr}); + auto proc = typename process_handler::instance_type{new Proc{std::forward(args)...}, &basic_scheduler::deleter}; + auto &&ref = handlers.emplace_back(process_handler{std::move(proc), &basic_scheduler::update, &basic_scheduler::abort, 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; diff --git a/src/entt/resource/cache.hpp b/src/entt/resource/cache.hpp index 86886850..2aa6b2c5 100644 --- a/src/entt/resource/cache.hpp +++ b/src/entt/resource/cache.hpp @@ -95,51 +95,51 @@ public: return operator*(); } - template - friend constexpr std::ptrdiff_t operator-(const resource_cache_iterator &, const resource_cache_iterator &) noexcept; + template + friend constexpr std::ptrdiff_t operator-(const resource_cache_iterator &, const resource_cache_iterator &) noexcept; - template - friend constexpr bool operator==(const resource_cache_iterator &, const resource_cache_iterator &) noexcept; + template + friend constexpr bool operator==(const resource_cache_iterator &, const resource_cache_iterator &) noexcept; - template - friend constexpr bool operator<(const resource_cache_iterator &, const resource_cache_iterator &) noexcept; + template + friend constexpr bool operator<(const resource_cache_iterator &, const resource_cache_iterator &) noexcept; private: It it; }; -template -[[nodiscard]] constexpr std::ptrdiff_t operator-(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr std::ptrdiff_t operator-(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) noexcept { return lhs.it - rhs.it; } -template -[[nodiscard]] constexpr bool operator==(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator==(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) noexcept { return lhs.it == rhs.it; } -template -[[nodiscard]] constexpr bool operator!=(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator!=(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) noexcept { return !(lhs == rhs); } -template -[[nodiscard]] constexpr bool operator<(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) noexcept { return lhs.it < rhs.it; } -template -[[nodiscard]] constexpr bool operator>(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) noexcept { return rhs < lhs; } -template -[[nodiscard]] constexpr bool operator<=(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator<=(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) noexcept { return !(lhs > rhs); } -template -[[nodiscard]] constexpr bool operator>=(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) noexcept { +template +[[nodiscard]] constexpr bool operator>=(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) noexcept { return !(lhs < rhs); } @@ -158,7 +158,7 @@ template */ template class resource_cache { - using alloc_traits = typename std::allocator_traits; + using alloc_traits = std::allocator_traits; static_assert(std::is_same_v, "Invalid value type"); using container_allocator = typename alloc_traits::template rebind_alloc>; using container_type = dense_map, 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. */ diff --git a/src/entt/resource/resource.hpp b/src/entt/resource/resource.hpp index 2e3d68de..2705518e 100644 --- a/src/entt/resource/resource.hpp +++ b/src/entt/resource/resource.hpp @@ -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 -[[nodiscard]] bool operator==(const resource &lhs, const resource &rhs) noexcept { +template +[[nodiscard]] bool operator==(const resource &lhs, const resource &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 -[[nodiscard]] bool operator!=(const resource &lhs, const resource &rhs) noexcept { +template +[[nodiscard]] bool operator!=(const resource &lhs, const resource &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 -[[nodiscard]] bool operator<(const resource &lhs, const resource &rhs) noexcept { +template +[[nodiscard]] bool operator<(const resource &lhs, const resource &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 -[[nodiscard]] bool operator>(const resource &lhs, const resource &rhs) noexcept { - return (std::addressof(*lhs) > std::addressof(*rhs)); +template +[[nodiscard]] bool operator>(const resource &lhs, const resource &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 -[[nodiscard]] bool operator<=(const resource &lhs, const resource &rhs) noexcept { +template +[[nodiscard]] bool operator<=(const resource &lhs, const resource &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 -[[nodiscard]] bool operator>=(const resource &lhs, const resource &rhs) noexcept { +template +[[nodiscard]] bool operator>=(const resource &lhs, const resource &rhs) noexcept { return !(lhs < rhs); } diff --git a/src/entt/signal/delegate.hpp b/src/entt/signal/delegate.hpp index 44196162..daa460f6 100644 --- a/src/entt/signal/delegate.hpp +++ b/src/entt/signal/delegate.hpp @@ -76,7 +76,13 @@ class delegate { [[nodiscard]] auto wrap(std::index_sequence) noexcept { return [](const void *, Args... args) -> Ret { [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); - return static_cast(std::invoke(Candidate, std::forward>>(std::get(arguments))...)); + + if constexpr(std::is_invocable_r_v>...>) { + return static_cast(std::invoke(Candidate, std::forward>>(std::get(arguments))...)); + } else { + constexpr auto offset = sizeof...(Args) - sizeof...(Index); + return static_cast(std::invoke(Candidate, std::forward>>(std::get(arguments))...)); + } }; } @@ -85,7 +91,13 @@ class delegate { return [](const void *payload, Args... args) -> Ret { [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); Type *curr = static_cast(const_cast *>(payload)); - return static_cast(std::invoke(Candidate, *curr, std::forward>>(std::get(arguments))...)); + + if constexpr(std::is_invocable_r_v>...>) { + return static_cast(std::invoke(Candidate, *curr, std::forward>>(std::get(arguments))...)); + } else { + constexpr auto offset = sizeof...(Args) - sizeof...(Index); + return static_cast(std::invoke(Candidate, *curr, std::forward>>(std::get(arguments))...)); + } }; } @@ -94,7 +106,13 @@ class delegate { return [](const void *payload, Args... args) -> Ret { [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); Type *curr = static_cast(const_cast *>(payload)); - return static_cast(std::invoke(Candidate, curr, std::forward>>(std::get(arguments))...)); + + if constexpr(std::is_invocable_r_v>...>) { + return static_cast(std::invoke(Candidate, curr, std::forward>>(std::get(arguments))...)); + } else { + constexpr auto offset = sizeof...(Args) - sizeof...(Index); + return static_cast(std::invoke(Candidate, curr, std::forward>>(std::get(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. diff --git a/src/entt/signal/dispatcher.hpp b/src/entt/signal/dispatcher.hpp index 72c5694d..74e8c007 100644 --- a/src/entt/signal/dispatcher.hpp +++ b/src/entt/signal/dispatcher.hpp @@ -75,7 +75,7 @@ public: template void enqueue(Args &&...args) { - if constexpr(std::is_aggregate_v) { + if constexpr(std::is_aggregate_v && (sizeof...(Args) != 0u || !std::is_default_constructible_v)) { events.push_back(Type{std::forward(args)...}); } else { events.emplace_back(std::forward(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; } diff --git a/src/entt/signal/emitter.hpp b/src/entt/signal/emitter.hpp index 263da18f..152d7ec7 100644 --- a/src/entt/signal/emitter.hpp +++ b/src/entt/signal/emitter.hpp @@ -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; } diff --git a/src/entt/signal/sigh.hpp b/src/entt/signal/sigh.hpp index 5e853540..a44c50a9 100644 --- a/src/entt/signal/sigh.hpp +++ b/src/entt/signal/sigh.hpp @@ -1,8 +1,8 @@ #ifndef ENTT_SIGNAL_SIGH_HPP #define ENTT_SIGNAL_SIGH_HPP -#include -#include +#include +#include #include #include #include @@ -56,7 +56,8 @@ class sigh { friend class sink>; using alloc_traits = std::allocator_traits; - using container_type = std::vector, typename alloc_traits::template rebind_alloc>>; + using delegate_type = delegate; + using container_type = std::vector>; 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 void collect(Func func, Args... args) const { - for(auto &&call: calls) { - if constexpr(std::is_void_v) { + for(auto pos = calls.size(); pos; --pos) { + if constexpr(std::is_void_v || !std::is_invocable_v) { + calls[pos - 1u](args...); + if constexpr(std::is_invocable_r_v) { - call(args...); if(func()) { break; } } else { - call(args...); func(); } } else { if constexpr(std::is_invocable_r_v) { - 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 class sink> { using signal_type = sigh; + using delegate_type = typename signal_type::delegate_type; using difference_type = typename signal_type::container_type::difference_type; template @@ -370,13 +372,14 @@ class sink> { sink{*static_cast(signal)}.disconnect(); } - auto before(delegate 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 + 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 &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 - [[nodiscard]] sink before() { - delegate call{}; - call.template connect(); - 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 - [[nodiscard]] sink before(Type &&value_or_instance) { - delegate call{}; - call.template connect(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>, 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.
- * 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(value_or_instance...); - delegate call{}; + delegate_type call{}; call.template connect(value_or_instance...); - signal->calls.insert(signal->calls.end() - offset, std::move(call)); + signal->calls.push_back(std::move(call)); delegate conn{}; conn.template connect<&release>(value_or_instance...); @@ -506,21 +428,9 @@ public: */ template void disconnect(Type &&...value_or_instance) { - auto &calls = signal->calls; - delegate call{}; + delegate_type call{}; call.template connect(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>, 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; }; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ebc63110..3aee1dfc 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -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 + $, /W1, /W4> + # clang-cl goes a little wrong with some warnings instead + $<$: + -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 + $<$:-Wdocumentation> + /EHsc /wd4324 /wd4996 $<$:/Od> $<$:/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 $ 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 $ 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 $ lib/${TEST_NAME}/plugin.cpp) - SETUP_TARGET(_${TEST_NAME} ${ARGVN}) - SETUP_BASIC_TEST(lib_${TEST_NAME} lib/${TEST_NAME}/main.cpp PLUGIN="$" ${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 $ 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="$" ${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 diff --git a/test/benchmark/benchmark.cpp b/test/benchmark/benchmark.cpp index 9af718cc..e595ad63 100644 --- a/test/benchmark/benchmark.cpp +++ b/test/benchmark/benchmark.cpp @@ -19,7 +19,9 @@ struct stable_position: position { }; template -struct comp { int x; }; +struct comp { + int x; +}; struct timer final { timer() @@ -34,16 +36,24 @@ private: std::chrono::time_point start; }; +template +void generic_with(Func func) { + timer timer; + func(); + timer.elapsed(); +} + template -void generic(Iterable &&iterable, Func func) { +void iterate_with(Iterable &&iterable, Func func) { timer timer; std::forward(iterable).each(func); timer.elapsed(); } template -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(entity); } - if(!(++i % 11)) { registry.remove(entity); } - if(!(++i % 13)) { registry.remove>(entity); } - if(!(++i % 17)) { registry.destroy(entity); } + if(!(++i % 7)) { + registry.remove(entity); + } + + if(!(++i % 11)) { + registry.remove(entity); + } + + if(!(++i % 13)) { + registry.remove>(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(registry.create()); - } - - timer.elapsed(); + generic_with([&]() { + for(std::uint64_t i = 0; i < 1000000L; i++) { + static_cast(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(entity); - registry.emplace(entity); - } - - timer.elapsed(); + for(const auto entity: entities) { + registry.emplace(entity); + registry.emplace(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(entities.begin(), entities.end()); - registry.insert(entities.begin(), entities.end()); - timer.elapsed(); + generic_with([&]() { + registry.create(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); + }); } TEST(Benchmark, Erase) { entt::registry registry; std::vector entities(1000000); - auto view = registry.view(); + auto view = registry.view(); std::cout << "Erasing 1000000 components from their entities" << std::endl; registry.create(entities.begin(), entities.end()); - registry.insert(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); - timer timer; - - for(auto entity: view) { - registry.erase(entity); - } - - timer.elapsed(); + generic_with([&]() { + for(auto entity: view) { + registry.erase(entity); + } + }); } TEST(Benchmark, EraseMany) { entt::registry registry; std::vector entities(1000000); - auto view = registry.view(); + auto view = registry.view(); std::cout << "Erasing 1000000 components from their entities at once" << std::endl; registry.create(entities.begin(), entities.end()); - registry.insert(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); - timer timer; - registry.erase(view.begin(), view.end()); - timer.elapsed(); + generic_with([&]() { + registry.erase(view.begin(), view.end()); + }); +} + +TEST(Benchmark, EraseManyMulti) { + entt::registry registry; + std::vector entities(1000000); + auto view = registry.view(); + + std::cout << "Erasing 1000000 components per type from their entities at once" << std::endl; + + registry.create(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); + + generic_with([&]() { + registry.erase(view.begin(), view.end()); + }); } TEST(Benchmark, Remove) { entt::registry registry; std::vector entities(1000000); - auto view = registry.view(); + auto view = registry.view(); std::cout << "Removing 1000000 components from their entities" << std::endl; registry.create(entities.begin(), entities.end()); - registry.insert(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); - timer timer; - - for(auto entity: view) { - registry.remove(entity); - } - - timer.elapsed(); + generic_with([&]() { + for(auto entity: view) { + registry.remove(entity); + } + }); } TEST(Benchmark, RemoveMany) { entt::registry registry; std::vector entities(1000000); - auto view = registry.view(); + auto view = registry.view(); std::cout << "Removing 1000000 components from their entities at once" << std::endl; registry.create(entities.begin(), entities.end()); - registry.insert(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); - timer timer; - registry.remove(view.begin(), view.end()); - timer.elapsed(); + generic_with([&]() { + registry.remove(view.begin(), view.end()); + }); +} + +TEST(Benchmark, RemoveManyMulti) { + entt::registry registry; + std::vector entities(1000000); + auto view = registry.view(); + + std::cout << "Removing 1000000 components per type from their entities at once" << std::endl; + + registry.create(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); + + generic_with([&]() { + registry.remove(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(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); - timer timer; - registry.clear(); - timer.elapsed(); + generic_with([&]() { + registry.clear(); + }); +} + +TEST(Benchmark, ClearMulti) { + entt::registry registry; + std::vector entities(1000000); + + std::cout << "Clearing 1000000 components per type from their entities" << std::endl; + + registry.create(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); + + generic_with([&]() { + registry.clear(); + }); +} + +TEST(Benchmark, ClearStable) { + entt::registry registry; + std::vector entities(1000000); + + std::cout << "Clearing 1000000 stable components from their entities" << std::endl; + + registry.create(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); + + generic_with([&]() { + registry.clear(); + }); } 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(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 entities(1000000); - auto view = registry.view(); + auto view = registry.view(); std::cout << "Destroying 1000000 entities" << std::endl; registry.create(entities.begin(), entities.end()); - registry.insert(entities.begin(), entities.end()); + registry.insert(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 entities(1000000); - auto view = registry.view(); + auto view = registry.view(); std::cout << "Destroying 1000000 entities at once" << std::endl; registry.create(entities.begin(), entities.end()); - registry.insert(entities.begin(), entities.end()); + registry.insert(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 entities(1000000); + auto view = registry.view(); + + std::cout << "Destroying 1000000 entities at once, multiple components" << std::endl; + + registry.create(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); + + generic_with([&]() { + registry.destroy(view.begin(), view.end()); + }); +} + +TEST(Benchmark, GetFromRegistry) { entt::registry registry; std::vector entities(1000000); - std::cout << "Destroying 1000000 entities at once, fast path" << std::endl; + registry.create(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); + + generic_with([&]() { + for(auto entity: entities) { + registry.get(entity).x = 0u; + } + }); +} + +TEST(Benchmark, GetFromRegistryMulti) { + entt::registry registry; + std::vector entities(1000000); registry.create(entities.begin(), entities.end()); - registry.insert(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); - timer timer; - registry.destroy(entities.begin(), entities.end()); - timer.elapsed(); + generic_with([&]() { + for(auto entity: entities) { + registry.get(entity).x = 0u; + registry.get(entity).y = 0u; + } + }); +} + +TEST(Benchmark, GetFromView) { + entt::registry registry; + std::vector entities(1000000); + auto view = registry.view(); + + registry.create(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); + + generic_with([&]() { + for(auto entity: entities) { + view.get(entity).x = 0u; + } + }); +} + +TEST(Benchmark, GetFromViewMulti) { + entt::registry registry; + std::vector entities(1000000); + auto view = registry.view(); + + registry.create(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); + + generic_with([&]() { + for(auto entity: entities) { + view.get(entity).x = 0u; + view.get(entity).y = 0u; + } + }); } TEST(Benchmark, IterateSingleComponent1M) { @@ -306,22 +435,22 @@ TEST(Benchmark, IterateSingleComponent1M) { registry.emplace(entity); } - generic(registry.view(), [](auto &...comp) { + iterate_with(registry.view(), [](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(entity); } - generic(registry.view(), [](auto &...comp) { + iterate_with(registry.view(), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -339,7 +468,7 @@ TEST(Benchmark, IterateSingleComponentRuntime1M) { entt::runtime_view view{}; view.iterate(registry.storage()); - generic(view, [®istry](auto entity) { + iterate_with(view, [&](auto entity) { registry.get(entity).x = {}; }); } @@ -355,15 +484,15 @@ TEST(Benchmark, IterateTwoComponents1M) { registry.emplace(entity); } - generic(registry.view(), [](auto &...comp) { + iterate_with(registry.view(), [](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(entity); } - generic(registry.view(), [](auto &...comp) { + iterate_with(registry.view(), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -390,7 +519,7 @@ TEST(Benchmark, IterateTwoComponents1MHalf) { } } - generic(registry.view(), [](auto &...comp) { + iterate_with(registry.view(), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -409,7 +538,7 @@ TEST(Benchmark, IterateTwoComponents1MOne) { } } - generic(registry.view(), [](auto &...comp) { + iterate_with(registry.view(), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -425,7 +554,7 @@ TEST(Benchmark, IterateTwoComponentsNonOwningGroup1M) { registry.emplace(entity); } - generic(registry.group<>(entt::get), [](auto &...comp) { + iterate_with(registry.group<>(entt::get), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -441,7 +570,7 @@ TEST(Benchmark, IterateTwoComponentsFullOwningGroup1M) { registry.emplace(entity); } - generic(registry.group(), [](auto &...comp) { + iterate_with(registry.group(), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -457,7 +586,7 @@ TEST(Benchmark, IterateTwoComponentsPartialOwningGroup1M) { registry.emplace(entity); } - generic(registry.group(entt::get), [](auto &...comp) { + iterate_with(registry.group(entt::get), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -477,7 +606,7 @@ TEST(Benchmark, IterateTwoComponentsRuntime1M) { view.iterate(registry.storage()) .iterate(registry.storage()); - generic(view, [®istry](auto entity) { + iterate_with(view, [&](auto entity) { registry.get(entity).x = {}; registry.get(entity).x = {}; }); @@ -501,7 +630,7 @@ TEST(Benchmark, IterateTwoComponentsRuntime1MHalf) { view.iterate(registry.storage()) .iterate(registry.storage()); - generic(view, [®istry](auto entity) { + iterate_with(view, [&](auto entity) { registry.get(entity).x = {}; registry.get(entity).x = {}; }); @@ -525,7 +654,7 @@ TEST(Benchmark, IterateTwoComponentsRuntime1MOne) { view.iterate(registry.storage()) .iterate(registry.storage()); - generic(view, [®istry](auto entity) { + iterate_with(view, [&](auto entity) { registry.get(entity).x = {}; registry.get(entity).x = {}; }); @@ -543,15 +672,15 @@ TEST(Benchmark, IterateThreeComponents1M) { registry.emplace>(entity); } - generic(registry.view>(), [](auto &...comp) { + iterate_with(registry.view>(), [](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>(entity); } - generic(registry.view>(), [](auto &...comp) { + iterate_with(registry.view>(), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -580,7 +709,7 @@ TEST(Benchmark, IterateThreeComponents1MHalf) { } } - generic(registry.view>(), [](auto &...comp) { + iterate_with(registry.view>(), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -600,7 +729,7 @@ TEST(Benchmark, IterateThreeComponents1MOne) { } } - generic(registry.view>(), [](auto &...comp) { + iterate_with(registry.view>(), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -617,7 +746,7 @@ TEST(Benchmark, IterateThreeComponentsNonOwningGroup1M) { registry.emplace>(entity); } - generic(registry.group<>(entt::get>), [](auto &...comp) { + iterate_with(registry.group<>(entt::get>), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -634,7 +763,7 @@ TEST(Benchmark, IterateThreeComponentsFullOwningGroup1M) { registry.emplace>(entity); } - generic(registry.group>(), [](auto &...comp) { + iterate_with(registry.group>(), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -651,7 +780,7 @@ TEST(Benchmark, IterateThreeComponentsPartialOwningGroup1M) { registry.emplace>(entity); } - generic(registry.group(entt::get>), [](auto &...comp) { + iterate_with(registry.group(entt::get>), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -673,7 +802,7 @@ TEST(Benchmark, IterateThreeComponentsRuntime1M) { .iterate(registry.storage()) .iterate(registry.storage>()); - generic(view, [®istry](auto entity) { + iterate_with(view, [&](auto entity) { registry.get(entity).x = {}; registry.get(entity).x = {}; registry.get>(entity).x = {}; @@ -700,7 +829,7 @@ TEST(Benchmark, IterateThreeComponentsRuntime1MHalf) { .iterate(registry.storage()) .iterate(registry.storage>()); - generic(view, [®istry](auto entity) { + iterate_with(view, [&](auto entity) { registry.get(entity).x = {}; registry.get(entity).x = {}; registry.get>(entity).x = {}; @@ -727,7 +856,7 @@ TEST(Benchmark, IterateThreeComponentsRuntime1MOne) { .iterate(registry.storage()) .iterate(registry.storage>()); - generic(view, [®istry](auto entity) { + iterate_with(view, [&](auto entity) { registry.get(entity).x = {}; registry.get(entity).x = {}; registry.get>(entity).x = {}; @@ -748,15 +877,15 @@ TEST(Benchmark, IterateFiveComponents1M) { registry.emplace>(entity); } - generic(registry.view, comp<1>, comp<2>>(), [](auto &...comp) { + iterate_with(registry.view, 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>(entity); } - generic(registry.view, comp<1>, comp<2>>(), [](auto &...comp) { + iterate_with(registry.view, comp<1>, comp<2>>(), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -789,7 +918,7 @@ TEST(Benchmark, IterateFiveComponents1MHalf) { } } - generic(registry.view, comp<1>, comp<2>>(), [](auto &...comp) { + iterate_with(registry.view, comp<1>, comp<2>>(), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -811,7 +940,7 @@ TEST(Benchmark, IterateFiveComponents1MOne) { } } - generic(registry.view, comp<1>, comp<2>>(), [](auto &...comp) { + iterate_with(registry.view, comp<1>, comp<2>>(), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -830,7 +959,7 @@ TEST(Benchmark, IterateFiveComponentsNonOwningGroup1M) { registry.emplace>(entity); } - generic(registry.group<>(entt::get, comp<1>, comp<2>>), [](auto &...comp) { + iterate_with(registry.group<>(entt::get, comp<1>, comp<2>>), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -849,7 +978,7 @@ TEST(Benchmark, IterateFiveComponentsFullOwningGroup1M) { registry.emplace>(entity); } - generic(registry.group, comp<1>, comp<2>>(), [](auto &...comp) { + iterate_with(registry.group, comp<1>, comp<2>>(), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -868,7 +997,7 @@ TEST(Benchmark, IterateFiveComponentsPartialFourOfFiveOwningGroup1M) { registry.emplace>(entity); } - generic(registry.group, comp<1>>(entt::get>), [](auto &...comp) { + iterate_with(registry.group, comp<1>>(entt::get>), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -887,7 +1016,7 @@ TEST(Benchmark, IterateFiveComponentsPartialThreeOfFiveOwningGroup1M) { registry.emplace>(entity); } - generic(registry.group>(entt::get, comp<2>>), [](auto &...comp) { + iterate_with(registry.group>(entt::get, comp<2>>), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -913,7 +1042,7 @@ TEST(Benchmark, IterateFiveComponentsRuntime1M) { .iterate(registry.storage>()) .iterate(registry.storage>()); - generic(view, [®istry](auto entity) { + iterate_with(view, [&](auto entity) { registry.get(entity).x = {}; registry.get(entity).x = {}; registry.get>(entity).x = {}; @@ -946,7 +1075,7 @@ TEST(Benchmark, IterateFiveComponentsRuntime1MHalf) { .iterate(registry.storage>()) .iterate(registry.storage>()); - generic(view, [®istry](auto entity) { + iterate_with(view, [&](auto entity) { registry.get(entity).x = {}; registry.get(entity).x = {}; registry.get>(entity).x = {}; @@ -979,7 +1108,7 @@ TEST(Benchmark, IterateFiveComponentsRuntime1MOne) { .iterate(registry.storage>()) .iterate(registry.storage>()); - generic(view, [®istry](auto entity) { + iterate_with(view, [&](auto entity) { registry.get(entity).x = {}; registry.get(entity).x = {}; registry.get>(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>(); }); + pathological_with([](auto ®istry) { return registry.template view>(); }); } TEST(Benchmark, IteratePathologicalNonOwningGroup) { std::cout << "Pathological case (non-owning group)" << std::endl; - pathological([](auto ®istry) { return registry.template group<>(entt::get>); }); + pathological_with([](auto ®istry) { return registry.template group<>(entt::get>); }); } TEST(Benchmark, IteratePathologicalFullOwningGroup) { std::cout << "Pathological case (full-owning group)" << std::endl; - pathological([](auto ®istry) { return registry.template group>(); }); + pathological_with([](auto ®istry) { return registry.template group>(); }); } TEST(Benchmark, IteratePathologicalPartialOwningGroup) { std::cout << "Pathological case (partial-owning group)" << std::endl; - pathological([](auto ®istry) { return registry.template group(entt::get>); }); + pathological_with([](auto ®istry) { return registry.template group(entt::get>); }); } TEST(Benchmark, SortSingle) { @@ -1018,9 +1147,9 @@ TEST(Benchmark, SortSingle) { registry.emplace(entity, i, i); } - timer timer; - registry.sort([](const auto &lhs, const auto &rhs) { return lhs.x < rhs.x && lhs.y < rhs.y; }); - timer.elapsed(); + generic_with([&]() { + registry.sort([](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([](const auto &lhs, const auto &rhs) { return lhs.x < rhs.x && lhs.y < rhs.y; }); - timer timer; - registry.sort(); - timer.elapsed(); + generic_with([&]() { + registry.sort(); + }); } TEST(Benchmark, AlmostSortedStdSort) { @@ -1062,9 +1191,9 @@ TEST(Benchmark, AlmostSortedStdSort) { registry.emplace(entity, 50000 * i, 50000 * i); } - timer timer; - registry.sort([](const auto &lhs, const auto &rhs) { return lhs.x > rhs.x && lhs.y > rhs.y; }); - timer.elapsed(); + generic_with([&]() { + registry.sort([](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(entity, 50000 * i, 50000 * i); } - timer timer; - registry.sort([](const auto &lhs, const auto &rhs) { return lhs.x > rhs.x && lhs.y > rhs.y; }, entt::insertion_sort{}); - timer.elapsed(); + generic_with([&]() { + registry.sort([](const auto &lhs, const auto &rhs) { return lhs.x > rhs.x && lhs.y > rhs.y; }, entt::insertion_sort{}); + }); } diff --git a/test/entt/common/basic_test_allocator.hpp b/test/entt/common/basic_test_allocator.hpp index 18a5013b..73828fd4 100644 --- a/test/entt/common/basic_test_allocator.hpp +++ b/test/entt/common/basic_test_allocator.hpp @@ -22,7 +22,7 @@ struct basic_test_allocator: std::allocator { return *this; } - bool operator==(const basic_test_allocator &other) { + bool operator==(const basic_test_allocator &other) const { return (this == &other); } }; diff --git a/test/entt/common/throwing_allocator.hpp b/test/entt/common/throwing_allocator.hpp index 1db2aba5..d152a076 100644 --- a/test/entt/common/throwing_allocator.hpp +++ b/test/entt/common/throwing_allocator.hpp @@ -25,14 +25,14 @@ public: using propagate_on_container_swap = std::true_type; using exception_type = test_exception; - template + template struct rebind { using other = throwing_allocator; }; throwing_allocator() = default; - template + template throwing_allocator(const throwing_allocator &other) : base{other} {} diff --git a/test/entt/container/dense_map.cpp b/test/entt/container/dense_map.cpp index 8e78de9a..ad098940 100644 --- a/test/entt/container/dense_map.cpp +++ b/test/entt/container/dense_map.cpp @@ -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 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 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 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 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 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 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 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 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 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 map; ASSERT_EQ(map.bucket_count(), minimum_bucket_count); @@ -995,7 +995,7 @@ TEST(DenseMap, LocalIterator) { static_assert(std::is_same_v>>); static_assert(std::is_same_v>); - static constexpr std::size_t minimum_bucket_count = 8u; + constexpr std::size_t minimum_bucket_count = 8u; entt::dense_map 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>>); static_assert(std::is_same_v>); - static constexpr std::size_t minimum_bucket_count = 8u; + constexpr std::size_t minimum_bucket_count = 8u; entt::dense_map 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 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 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::ceil(minimum_bucket_count / map.max_load_factor())))); } TEST(DenseMap, ThrowingAllocator) { @@ -1167,7 +1167,7 @@ TEST(DenseMap, ThrowingAllocator) { using packed_allocator = test::throwing_allocator>; 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::equal_to, allocator> map{}; packed_allocator::trigger_on_allocate = true; diff --git a/test/entt/container/dense_set.cpp b/test/entt/container/dense_set.cpp index e647dcc3..87b27478 100644 --- a/test/entt/container/dense_set.cpp +++ b/test/entt/container/dense_set.cpp @@ -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 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 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 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 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 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 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 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 set; ASSERT_EQ(set.bucket_count(), minimum_bucket_count); @@ -721,7 +721,7 @@ TEST(DenseSet, LocalIterator) { static_assert(std::is_same_v); static_assert(std::is_same_v); - static constexpr std::size_t minimum_bucket_count = 8u; + constexpr std::size_t minimum_bucket_count = 8u; entt::dense_set set; set.emplace(3u); set.emplace(3u + minimum_bucket_count); @@ -749,7 +749,7 @@ TEST(DenseSet, ConstLocalIterator) { static_assert(std::is_same_v); static_assert(std::is_same_v); - static constexpr std::size_t minimum_bucket_count = 8u; + constexpr std::size_t minimum_bucket_count = 8u; entt::dense_set 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 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 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::ceil(minimum_bucket_count / set.max_load_factor())))); } TEST(DenseSet, ThrowingAllocator) { @@ -885,7 +885,7 @@ TEST(DenseSet, ThrowingAllocator) { using packed_allocator = test::throwing_allocator>; 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::equal_to, allocator> set{}; packed_allocator::trigger_on_allocate = true; diff --git a/test/entt/core/any.cpp b/test/entt/core/any.cpp index 83421784..a896b9b5 100644 --- a/test/entt/core/any.cpp +++ b/test/entt/core/any.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -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(any), 42); ASSERT_EQ(entt::any_cast(cany), 42); - not_copyable instance{}; - instance.payload = 42.; + auto instance = std::make_unique(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(std::move(ref)).payload, 42.); ASSERT_EQ(entt::any_cast(std::move(cref)), 42.); + ASSERT_EQ(*entt::any_cast>(std::move(ref)), 42.); ASSERT_EQ(entt::any_cast(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(any), ""); - ASSERT_DEATH(entt::any_cast(cany), ""); + ASSERT_DEATH([[maybe_unused]] auto &elem = entt::any_cast(any), ""); + ASSERT_DEATH([[maybe_unused]] const auto &elem = entt::any_cast(cany), ""); - not_copyable instance{}; - instance.payload = 42.; + auto instance = std::make_unique(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(std::as_const(ref).as_ref()), ""); - ASSERT_DEATH(entt::any_cast(entt::any{42}), ""); + ASSERT_DEATH([[maybe_unused]] auto elem = entt::any_cast>(std::as_const(ref).as_ref()), ""); + ASSERT_DEATH([[maybe_unused]] auto elem = entt::any_cast(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}; + const std::unique_ptr value{}; + entt::any any{std::in_place_type>}; 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 vec{}; - vec.emplace_back(std::in_place_type); + vec.emplace_back(std::in_place_type>); 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); + vec.emplace_back(std::in_place_type>); 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 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 nosbo[2] = {over_aligned{}, over_aligned{}}; const auto *data = nosbo[0].data(); diff --git a/test/entt/core/hashed_string.cpp b/test/entt/core/hashed_string.cpp index a1f2033b..953cf0b9 100644 --- a/test/entt/core/hashed_string.cpp +++ b/test/entt/core/hashed_string.cpp @@ -6,19 +6,19 @@ #include template -struct foobar_t; +struct expected; template<> -struct foobar_t { +struct expected { static constexpr auto value = 0xbf9cf968; }; template<> -struct foobar_t { +struct expected { static constexpr auto value = 0x85944171f73967e8; }; -inline constexpr auto foobar_v = foobar_t::value; +inline constexpr auto expected_v = expected::value; TEST(BasicHashedString, DeductionGuide) { static_assert(std::is_same_v); @@ -47,8 +47,8 @@ TEST(HashedString, Functionalities) { entt::hashed_string hs{"foobar"}; - ASSERT_EQ(static_cast(hs), foobar_v); - ASSERT_EQ(hs.value(), foobar_v); + ASSERT_EQ(static_cast(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(hws), foobar_v); - ASSERT_EQ(hws.value(), foobar_v); + ASSERT_EQ(static_cast(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); diff --git a/test/entt/core/memory.cpp b/test/entt/core/memory.cpp index 983dbe59..05d65f78 100644 --- a/test/entt/core/memory.cpp +++ b/test/entt/core/memory.cpp @@ -24,6 +24,12 @@ TEST(ToAddress, Functionalities) { TEST(PoccaPocmaAndPocs, Functionalities) { test::basic_test_allocator lhs, rhs; + test::basic_test_allocator 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 lhs, rhs; + test::basic_test_allocator 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::pow(2, 16))), static_cast(std::pow(2, 16))); + ASSERT_EQ(entt::next_power_of_two(static_cast(std::pow(2, 16) + 1u)), static_cast(std::pow(2, 17))); } ENTT_DEBUG_TEST(NextPowerOfTwoDeathTest, Functionalities) { diff --git a/test/entt/core/tuple.cpp b/test/entt/core/tuple.cpp index f6895364..80db85ba 100644 --- a/test/entt/core/tuple.cpp +++ b/test/entt/core/tuple.cpp @@ -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'); } diff --git a/test/entt/core/type_traits.cpp b/test/entt/core/type_traits.cpp index bad851b6..17c4db05 100644 --- a/test/entt/core/type_traits.cpp +++ b/test/entt/core/type_traits.cpp @@ -89,7 +89,7 @@ TEST(TypeList, Functionalities) { static_assert(std::is_same_v, entt::type_list>); static_assert(std::is_same_v, entt::type_list>); static_assert(std::is_same_v, entt::type_list>); - static_assert(std::is_same_v>, entt::type_list>); + static_assert(std::is_same_v>, type>); static_assert(entt::type_list_contains_v); static_assert(entt::type_list_contains_v); @@ -112,6 +112,14 @@ TEST(TypeList, Functionalities) { static_assert(std::is_same_v, entt::type_identity>, entt::type_list>); static_assert(std::is_same_v, std::add_const>, entt::type_list>); static_assert(std::is_same_v, multi_argument_operation>, entt::type_list>); + + static_assert(std::tuple_size_v> == 0u); + static_assert(std::tuple_size_v> == 1u); + static_assert(std::tuple_size_v> == 2u); + + static_assert(std::is_same_v>>); + static_assert(std::is_same_v>>); + static_assert(std::is_same_v>>); } TEST(ValueList, Functionalities) { @@ -125,10 +133,33 @@ TEST(ValueList, Functionalities) { static_assert(std::is_same_v, entt::value_list<0, 2, 1, 0, 2, 1>>); static_assert(std::is_same_v, entt::value_list<0, 2, 1>>); static_assert(std::is_same_v, entt::value_list<0, 2, 0, 2>>); + static_assert(std::is_same_v>, value>); + + static_assert(entt::value_list_contains_v); + static_assert(entt::value_list_contains_v); + static_assert(!entt::value_list_contains_v); 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<3, 4>>, entt::value_list<0, 1, 2>>); + static_assert(std::is_same_v, entt::value_list<0, 1, 2>>, entt::value_list<>>); + static_assert(std::is_same_v, entt::value_list<0, 1>>, entt::value_list<2>>); + static_assert(std::is_same_v, entt::value_list<1, 2>>, entt::value_list<0>>); + static_assert(std::is_same_v, entt::value_list<1>>, entt::value_list<0, 2>>); + + static_assert(std::tuple_size_v> == 0u); + static_assert(std::tuple_size_v> == 1u); + static_assert(std::tuple_size_v> == 2u); + + static_assert(std::is_same_v>>); + static_assert(std::is_same_v>>); + static_assert(std::is_same_v>>); } TEST(IsApplicable, Functionalities) { @@ -183,6 +214,7 @@ TEST(IsEqualityComparable, Functionalities) { static_assert(entt::is_equality_comparable_v::iterator>); static_assert(entt::is_equality_comparable_v); + static_assert(!entt::is_equality_comparable_v); static_assert(!entt::is_equality_comparable_v); static_assert(!entt::is_equality_comparable_v); static_assert(!entt::is_equality_comparable_v>); diff --git a/test/entt/entity/component.cpp b/test/entt/entity/component.cpp index 7898420e..e3934502 100644 --- a/test/entt/entity/component.cpp +++ b/test/entt/entity/component.cpp @@ -30,50 +30,43 @@ struct entt::component_traits { }; TEST(Component, VoidType) { - using traits = entt::component_traits; + using traits_type = entt::component_traits; - static_assert(traits::in_place_delete); - static_assert(entt::ignore_as_empty_v); - // 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; + using traits_type = entt::component_traits; - static_assert(!traits::in_place_delete); - static_assert(entt::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, NonEmpty) { - using traits = entt::component_traits; + using traits_type = entt::component_traits; - static_assert(!traits::in_place_delete); - static_assert(!entt::ignore_as_empty_v); - 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; + using traits_type = entt::component_traits; - static_assert(traits::in_place_delete); - static_assert(!entt::ignore_as_empty_v); - 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; + using traits_type = entt::component_traits; - static_assert(traits::in_place_delete); - static_assert(!entt::ignore_as_empty_v); - 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; + using traits_type = entt::component_traits; - static_assert(!traits::in_place_delete); - static_assert(!entt::ignore_as_empty_v); - static_assert(traits::page_size == 8u); + static_assert(!traits_type::in_place_delete); + static_assert(traits_type::page_size == 8u); } diff --git a/test/entt/entity/entity.cpp b/test/entt/entity/entity.cpp index b8212bc5..d875a28e 100644 --- a/test/entt/entity/entity.cpp +++ b/test/entt/entity/entity.cpp @@ -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) { diff --git a/test/entt/entity/group.cpp b/test/entt/entity/group.cpp index 7cd8234a..04b4640e 100644 --- a/test/entt/entity/group.cpp +++ b/test/entt/entity/group.cpp @@ -8,6 +8,7 @@ #include #include #include +#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(entity)), 42); ASSERT_EQ(std::get<1>(group.get(entity)), '2'); - ASSERT_EQ(cgroup.get(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()); registry.emplace(entity); registry.emplace(entity); @@ -187,10 +189,10 @@ TEST(NonOwningGroup, Each) { auto cgroup = std::as_const(registry).group_if_exists(entt::get); registry.emplace(entity[0u], 0); - registry.emplace(entity[0u], 0); + registry.emplace(entity[0u], static_cast(0)); registry.emplace(entity[1u], 1); - registry.emplace(entity[1u], 1); + registry.emplace(entity[1u], static_cast(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(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).each()) { - ASSERT_EQ(entt::to_integral(entt), ivalue); - ASSERT_EQ(entt::to_integral(entt), cvalue); + ASSERT_EQ(static_cast(entt::to_integral(entt)), ivalue); + ASSERT_EQ(static_cast(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(e0)), (std::make_tuple(0, 0u))); - ASSERT_EQ((group.get(e1)), (std::make_tuple(1, 1u))); - ASSERT_EQ((group.get(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(std::less{}); - group.sort(); + group.sort_as(*group.storage()); ASSERT_EQ((group.get(e0)), (std::make_tuple(0, 0u))); - ASSERT_EQ((group.get(e1)), (std::make_tuple(1, 1u))); + ASSERT_EQ((group.get<0, 1>(e1)), (std::make_tuple(1, 1u))); ASSERT_EQ((group.get(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({})), int &>); static_assert(std::is_same_v({})), int &>); + + static_assert(std::is_same_v({})), void>); + static_assert(std::is_same_v({})), void>); + + static_assert(std::is_same_v({})), const char &>); static_assert(std::is_same_v({})), const char &>); - static_assert(std::is_same_v({})), std::tuple>); + + static_assert(std::is_same_v({})), std::tuple>); + static_assert(std::is_same_v({})), std::tuple>); + static_assert(std::is_same_v>); static_assert(std::is_same_v)), decltype(std::as_const(registry).group_if_exists(entt::get))>); @@ -475,7 +488,7 @@ TEST(NonOwningGroup, ExcludedComponents) { if(entity == e0) { ASSERT_EQ(group.get(e0), 0); } else if(entity == e2) { - ASSERT_EQ(group.get(e2), 2); + ASSERT_EQ(group.get<0>(e2), 2); } } @@ -493,7 +506,7 @@ TEST(NonOwningGroup, ExcludedComponents) { if(entity == e1) { ASSERT_EQ(group.get(e1), 1); } else if(entity == e3) { - ASSERT_EQ(group.get(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); + auto group = registry.group(entt::get, entt::exclude); - static_assert(std::is_same_v()), entt::storage_type_t &>); - static_assert(std::is_same_v()), entt::storage_type_t &>); - static_assert(std::is_same_v()), const entt::storage_type_t &>); - static_assert(std::is_same_v()), const entt::storage_type_t &>); + static_assert(std::is_same_v()), entt::storage_type_t *>); + static_assert(std::is_same_v()), entt::storage_type_t *>); + static_assert(std::is_same_v()), entt::storage_type_t *>); + static_assert(std::is_same_v()), const entt::storage_type_t *>); + static_assert(std::is_same_v()), const entt::storage_type_t *>); + static_assert(std::is_same_v()), const entt::storage_type_t *>); + static_assert(std::is_same_v()), entt::storage_type_t *>); + static_assert(std::is_same_v()), entt::storage_type_t *>); + static_assert(std::is_same_v()), entt::storage_type_t *>); + static_assert(std::is_same_v()), const entt::storage_type_t *>); + static_assert(std::is_same_v()), const entt::storage_type_t *>); + static_assert(std::is_same_v()), const entt::storage_type_t *>); + + ASSERT_TRUE(group); + + ASSERT_NE(group.storage(), nullptr); + ASSERT_NE(group.storage<1u>(), nullptr); + ASSERT_NE(group.storage(), nullptr); + ASSERT_NE(group.storage<3u>(), nullptr); ASSERT_EQ(group.size(), 0u); - group.storage().emplace(entity); + group.storage()->emplace(entity); + group.storage()->emplace(entity); registry.emplace(entity); + registry.emplace(entity); + + ASSERT_EQ(group.size(), 0u); + ASSERT_EQ(group.begin(), group.end()); + ASSERT_TRUE(group.storage()->contains(entity)); + ASSERT_TRUE(group.storage()->contains(entity)); + ASSERT_TRUE(group.storage()->contains(entity)); + ASSERT_TRUE(group.storage()->contains(entity)); + ASSERT_TRUE((registry.all_of(entity))); + + group.storage()->erase(entity); + registry.erase(entity); ASSERT_EQ(group.size(), 1u); - ASSERT_TRUE(group.storage().contains(entity)); - ASSERT_TRUE(group.storage().contains(entity)); + ASSERT_NE(group.begin(), group.end()); + ASSERT_TRUE(group.storage()->contains(entity)); + ASSERT_TRUE(group.storage()->contains(entity)); + ASSERT_FALSE(group.storage()->contains(entity)); + ASSERT_FALSE(group.storage()->contains(entity)); ASSERT_TRUE((registry.all_of(entity))); + ASSERT_FALSE((registry.any_of(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(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(entity))); + ASSERT_FALSE((registry.any_of(entity))); + + group = {}; + + ASSERT_FALSE(group); + + ASSERT_EQ(group.storage<0u>(), nullptr); + ASSERT_EQ(group.storage(), nullptr); + ASSERT_EQ(group.storage<2u>(), nullptr); + ASSERT_EQ(group.storage(), nullptr); +} + +TEST(NonOwningGroup, Overlapping) { + entt::registry registry; + + auto group = registry.group(entt::get, entt::exclude); + auto other = registry.group(entt::get, entt::exclude); + + ASSERT_TRUE(group.empty()); + ASSERT_TRUE(other.empty()); + + const auto entity = registry.create(); + registry.emplace(entity, '1'); + + ASSERT_FALSE(group.empty()); + ASSERT_TRUE(other.empty()); + + registry.emplace(entity, 42); + + ASSERT_FALSE(group.empty()); + ASSERT_FALSE(other.empty()); + + registry.emplace(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().raw()[0u][0u], 42); - ASSERT_EQ(group.storage().raw()[0u][0u], 42); + ASSERT_EQ(cgroup.storage()->raw()[0u][0u], 42); + ASSERT_EQ(group.storage()->raw()[0u][0u], 42); for(auto entity: group) { ASSERT_EQ(std::get<0>(cgroup.get(entity)), 42); ASSERT_EQ(std::get<1>(group.get(entity)), '2'); - ASSERT_EQ(cgroup.get(entity), '2'); + ASSERT_EQ(cgroup.get<1>(entity), '2'); } - ASSERT_EQ(group.storage().raw()[0u][0u], 42); + ASSERT_EQ(group.handle().data()[0u], e1); + ASSERT_EQ(group.storage()->raw()[0u][0u], 42); registry.erase(e0); registry.erase(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(entt::get); + auto &&handle = group.handle(); + + ASSERT_TRUE(handle.empty()); + ASSERT_FALSE(handle.contains(entity)); + ASSERT_EQ(&handle, &group.handle()); + ASSERT_EQ(&handle, group.storage()); + + registry.emplace(entity); + registry.emplace(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(entt::get); @@ -831,10 +937,10 @@ TEST(OwningGroup, Each) { auto cgroup = std::as_const(registry).group_if_exists(entt::get); registry.emplace(entity[0u], 0); - registry.emplace(entity[0u], 0); + registry.emplace(entity[0u], static_cast(0)); registry.emplace(entity[1u], 1); - registry.emplace(entity[1u], 1); + registry.emplace(entity[1u], static_cast(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(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(entt::get).each()) { - ASSERT_EQ(entt::to_integral(entt), ivalue); - ASSERT_EQ(entt::to_integral(entt), cvalue); + ASSERT_EQ(static_cast(entt::to_integral(entt)), ivalue); + ASSERT_EQ(static_cast(entt::to_integral(entt)), cvalue); } } @@ -893,27 +1001,27 @@ TEST(OwningGroup, SortOrdered) { registry.emplace(entities[4], 2); group.sort([&group](const entt::entity lhs, const entt::entity rhs) { - return group.get(lhs).value < group.get(rhs).value; + return group.get(lhs).value < group.get<0>(rhs).value; }); - ASSERT_EQ(group.storage().data()[0u], entities[0]); - ASSERT_EQ(group.storage().data()[1u], entities[1]); - ASSERT_EQ(group.storage().data()[2u], entities[2]); - ASSERT_EQ(group.storage().data()[3u], entities[3]); - ASSERT_EQ(group.storage().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().raw()[0u][0u].value, 12); - ASSERT_EQ(group.storage().raw()[0u][1u].value, 9); - ASSERT_EQ(group.storage().raw()[0u][2u].value, 6); - ASSERT_EQ(group.storage().raw()[0u][3u].value, 1); - ASSERT_EQ(group.storage().raw()[0u][4u].value, 2); + ASSERT_EQ(group.storage()->raw()[0u][0u].value, 12); + ASSERT_EQ(group.storage()->raw()[0u][1u].value, 9); + ASSERT_EQ(group.storage()->raw()[0u][2u].value, 6); + ASSERT_EQ(group.storage()->raw()[0u][3u].value, 1); + ASSERT_EQ(group.storage()->raw()[0u][4u].value, 2); - ASSERT_EQ(group.storage().raw()[0u][0u], 'a'); - ASSERT_EQ(group.storage().raw()[0u][1u], 'b'); - ASSERT_EQ(group.storage().raw()[0u][2u], 'c'); + ASSERT_EQ(group.storage()->raw()[0u][0u], 'a'); + ASSERT_EQ(group.storage()->raw()[0u][1u], 'b'); + ASSERT_EQ(group.storage()->raw()[0u][2u], 'c'); ASSERT_EQ((group.get(entities[0])), (std::make_tuple(boxed_int{12}, 'a'))); - ASSERT_EQ((group.get(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(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().data()[0u], entities[2]); - ASSERT_EQ(group.storage().data()[1u], entities[1]); - ASSERT_EQ(group.storage().data()[2u], entities[0]); - ASSERT_EQ(group.storage().data()[3u], entities[3]); - ASSERT_EQ(group.storage().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().raw()[0u][0u].value, 12); - ASSERT_EQ(group.storage().raw()[0u][1u].value, 9); - ASSERT_EQ(group.storage().raw()[0u][2u].value, 6); - ASSERT_EQ(group.storage().raw()[0u][3u].value, 1); - ASSERT_EQ(group.storage().raw()[0u][4u].value, 2); + ASSERT_EQ(group.storage()->raw()[0u][0u].value, 12); + ASSERT_EQ(group.storage()->raw()[0u][1u].value, 9); + ASSERT_EQ(group.storage()->raw()[0u][2u].value, 6); + ASSERT_EQ(group.storage()->raw()[0u][3u].value, 1); + ASSERT_EQ(group.storage()->raw()[0u][4u].value, 2); - ASSERT_EQ(group.storage().raw()[0u][0u], 'c'); - ASSERT_EQ(group.storage().raw()[0u][1u], 'b'); - ASSERT_EQ(group.storage().raw()[0u][2u], 'a'); + ASSERT_EQ(group.storage()->raw()[0u][0u], 'c'); + ASSERT_EQ(group.storage()->raw()[0u][1u], 'b'); + ASSERT_EQ(group.storage()->raw()[0u][2u], 'a'); ASSERT_EQ((group.get(entities[0])), (std::make_tuple(boxed_int{6}, 'a'))); - ASSERT_EQ((group.get(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(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().data()[0u], entities[4]); - ASSERT_EQ(group.storage().data()[1u], entities[3]); - ASSERT_EQ(group.storage().data()[2u], entities[0]); - ASSERT_EQ(group.storage().data()[3u], entities[1]); - ASSERT_EQ(group.storage().data()[4u], entities[2]); - ASSERT_EQ(group.storage().data()[5u], entities[5]); - ASSERT_EQ(group.storage().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().raw()[0u][0u].value, 12); - ASSERT_EQ(group.storage().raw()[0u][1u].value, 9); - ASSERT_EQ(group.storage().raw()[0u][2u].value, 6); - ASSERT_EQ(group.storage().raw()[0u][3u].value, 3); - ASSERT_EQ(group.storage().raw()[0u][4u].value, 1); - ASSERT_EQ(group.storage().raw()[0u][5u].value, 4); - ASSERT_EQ(group.storage().raw()[0u][6u].value, 5); + ASSERT_EQ(group.storage()->raw()[0u][0u].value, 12); + ASSERT_EQ(group.storage()->raw()[0u][1u].value, 9); + ASSERT_EQ(group.storage()->raw()[0u][2u].value, 6); + ASSERT_EQ(group.storage()->raw()[0u][3u].value, 3); + ASSERT_EQ(group.storage()->raw()[0u][4u].value, 1); + ASSERT_EQ(group.storage()->raw()[0u][5u].value, 4); + ASSERT_EQ(group.storage()->raw()[0u][6u].value, 5); - ASSERT_EQ(group.get(group.storage().data()[0u]), 'e'); - ASSERT_EQ(group.get(group.storage().data()[1u]), 'd'); - ASSERT_EQ(group.get(group.storage().data()[2u]), 'c'); - ASSERT_EQ(group.get(group.storage().data()[3u]), 'b'); - ASSERT_EQ(group.get(group.storage().data()[4u]), 'a'); + ASSERT_EQ(group.get(group.handle().data()[0u]), 'e'); + ASSERT_EQ(group.get<1>(group.handle().data()[1u]), 'd'); + ASSERT_EQ(group.get(group.handle().data()[2u]), 'c'); + ASSERT_EQ(group.get<1>(group.handle().data()[3u]), 'b'); + ASSERT_EQ(group.get(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({}, entt::exclude); + auto group = registry.group(entt::get<>, entt::exclude); 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().data()[0u], entities[4]); - ASSERT_EQ(group.storage().data()[1u], entities[3]); - ASSERT_EQ(group.storage().data()[2u], entities[1]); - ASSERT_EQ(group.storage().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().raw()[0u][0u].value, 4); - ASSERT_EQ(group.storage().raw()[0u][1u].value, 3); - ASSERT_EQ(group.storage().raw()[0u][2u].value, 1); - ASSERT_EQ(group.storage().raw()[0u][3u].value, 0); + ASSERT_EQ(group.storage()->raw()[0u][0u].value, 4); + ASSERT_EQ(group.storage()->raw()[0u][1u].value, 3); + ASSERT_EQ(group.storage()->raw()[0u][2u].value, 1); + ASSERT_EQ(group.storage()->raw()[0u][3u].value, 0); ASSERT_EQ(group.get(entities[0]).value, 0); - ASSERT_EQ(group.get(entities[1]).value, 1); + ASSERT_EQ(group.get<0>(entities[1]).value, 1); ASSERT_EQ(group.get(entities[3]).value, 3); - ASSERT_EQ(group.get(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({})), int &>); static_assert(std::is_same_v({})), int &>); + + static_assert(std::is_same_v({})), const char &>); static_assert(std::is_same_v({})), const char &>); + + static_assert(std::is_same_v({})), void>); + static_assert(std::is_same_v({})), void>); + + static_assert(std::is_same_v({})), double &>); static_assert(std::is_same_v({})), double &>); + + static_assert(std::is_same_v({})), const float &>); static_assert(std::is_same_v({})), const float &>); - static_assert(std::is_same_v({})), std::tuple>); + + static_assert(std::is_same_v({})), std::tuple>); + static_assert(std::is_same_v({})), std::tuple>); + static_assert(std::is_same_v>); static_assert(std::is_same_v(entt::get)), decltype(std::as_const(registry).group_if_exists(entt::get))>); @@ -1192,7 +1313,7 @@ TEST(OwningGroup, ExcludedComponents) { registry.emplace(e1, 1); registry.emplace(e1); - const auto group = registry.group({}, entt::exclude); + const auto group = registry.group(entt::get<>, entt::exclude); const auto e2 = registry.create(); registry.emplace(e2, 2); @@ -1207,7 +1328,7 @@ TEST(OwningGroup, ExcludedComponents) { if(entity == e0) { ASSERT_EQ(group.get(e0), 0); } else if(entity == e2) { - ASSERT_EQ(group.get(e2), 2); + ASSERT_EQ(group.get<0>(e2), 2); } } @@ -1225,7 +1346,7 @@ TEST(OwningGroup, ExcludedComponents) { if(entity == e1) { ASSERT_EQ(group.get(e1), 1); } else if(entity == e3) { - ASSERT_EQ(group.get(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({}, entt::exclude); - const auto cgroup = std::as_const(registry).group_if_exists({}, entt::exclude); + const auto group = registry.group(entt::get<>, entt::exclude); + const auto cgroup = std::as_const(registry).group_if_exists(entt::get<>, entt::exclude); const auto entity = registry.create(); registry.emplace(entity); @@ -1403,7 +1524,7 @@ TEST(OwningGroup, SwappingValuesIsAllowed) { // thanks to @andranik3949 for pointing out this missing test registry.view().each([](const auto entity, const auto &value) { - ASSERT_EQ(entt::to_integral(entity), value.value); + ASSERT_EQ(static_cast(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(entt::get); + auto group = registry.group(entt::get, entt::exclude); - static_assert(std::is_same_v()), entt::storage_type_t &>); - static_assert(std::is_same_v()), entt::storage_type_t &>); - static_assert(std::is_same_v()), const entt::storage_type_t &>); - static_assert(std::is_same_v()), const entt::storage_type_t &>); + static_assert(std::is_same_v()), entt::storage_type_t *>); + static_assert(std::is_same_v()), entt::storage_type_t *>); + static_assert(std::is_same_v()), entt::storage_type_t *>); + static_assert(std::is_same_v()), const entt::storage_type_t *>); + static_assert(std::is_same_v()), const entt::storage_type_t *>); + static_assert(std::is_same_v()), const entt::storage_type_t *>); + static_assert(std::is_same_v()), entt::storage_type_t *>); + static_assert(std::is_same_v()), entt::storage_type_t *>); + static_assert(std::is_same_v()), entt::storage_type_t *>); + static_assert(std::is_same_v()), const entt::storage_type_t *>); + static_assert(std::is_same_v()), const entt::storage_type_t *>); + static_assert(std::is_same_v()), const entt::storage_type_t *>); + + ASSERT_TRUE(group); + + ASSERT_NE(group.storage(), nullptr); + ASSERT_NE(group.storage<1u>(), nullptr); + ASSERT_NE(group.storage(), nullptr); + ASSERT_NE(group.storage<3u>(), nullptr); ASSERT_EQ(group.size(), 0u); - group.storage().emplace(entity); + group.storage()->emplace(entity); + group.storage()->emplace(entity); registry.emplace(entity); + registry.emplace(entity); + + ASSERT_EQ(group.size(), 0u); + ASSERT_EQ(group.begin(), group.end()); + ASSERT_TRUE(group.storage()->contains(entity)); + ASSERT_TRUE(group.storage()->contains(entity)); + ASSERT_TRUE(group.storage()->contains(entity)); + ASSERT_TRUE(group.storage()->contains(entity)); + ASSERT_TRUE((registry.all_of(entity))); + + group.storage()->erase(entity); + registry.erase(entity); ASSERT_EQ(group.size(), 1u); - ASSERT_TRUE(group.storage().contains(entity)); - ASSERT_TRUE(group.storage().contains(entity)); + ASSERT_NE(group.begin(), group.end()); + ASSERT_TRUE(group.storage()->contains(entity)); + ASSERT_TRUE(group.storage()->contains(entity)); + ASSERT_FALSE(group.storage()->contains(entity)); + ASSERT_FALSE(group.storage()->contains(entity)); ASSERT_TRUE((registry.all_of(entity))); + ASSERT_FALSE((registry.any_of(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(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(entity))); + ASSERT_FALSE((registry.any_of(entity))); + + group = {}; + + ASSERT_FALSE(group); + + ASSERT_EQ(group.storage<0u>(), nullptr); + ASSERT_EQ(group.storage(), nullptr); + ASSERT_EQ(group.storage<2u>(), nullptr); + ASSERT_EQ(group.storage(), nullptr); +} + +ENTT_DEBUG_TEST(OwningGroupDeathTest, Overlapping) { + entt::registry registry; + registry.group(entt::get, entt::exclude); + + ASSERT_DEATH((registry.group(entt::get, entt::exclude)), ""); + ASSERT_DEATH(registry.group(entt::get, entt::exclude), ""); + ASSERT_DEATH(registry.group(entt::get, entt::exclude), ""); } diff --git a/test/entt/entity/handle.cpp b/test/entt/entity/handle.cpp index 8cbf5dc7..777be022 100644 --- a/test/entt/entity/handle.cpp +++ b/test/entt/entity/handle.cpp @@ -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(entity); registry.emplace(entity); + // required to test the find-first initialization step + registry.storage().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()); } diff --git a/test/entt/entity/helper.cpp b/test/entt/entity/helper.cpp index 00b93cab..50ab87a0 100644 --- a/test/entt/entity/helper.cpp +++ b/test/entt/entity/helper.cpp @@ -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::page_size; + constexpr auto page_size = entt::storage_type_t::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::page_size; + constexpr auto page_size = entt::storage_type_t::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() + .on_construct<&sigh_callback>(counter) + .on_update<&sigh_callback>(counter) + .on_destroy<&sigh_callback>(counter); + + ASSERT_EQ(counter, 0); + + registry.emplace(entt); + registry.replace(entt); + registry.erase(entt); + + ASSERT_EQ(counter, 3); + + helper.with("other"_hs) + .on_construct<&sigh_callback>(counter) + .on_update<&sigh_callback>(counter) + .on_destroy<&sigh_callback>(counter); + + registry.emplace(entt); + registry.replace(entt); + registry.erase(entt); + + ASSERT_EQ(counter, 3); + + registry.storage("other"_hs).emplace(entt); + registry.storage("other"_hs).patch(entt); + registry.storage("other"_hs).erase(entt); + + ASSERT_EQ(counter, 6); +} diff --git a/test/entt/entity/organizer.cpp b/test/entt/entity/organizer.cpp index b3351b32..82db3f94 100644 --- a/test/entt/entity/organizer.cpp +++ b/test/entt/entity/organizer.cpp @@ -1,5 +1,7 @@ #include +#include #include +#include #include #include @@ -20,7 +22,7 @@ struct clazz { static void ro_int_char_with_payload(clazz &, entt::view>) {} }; -void to_args_integrity(entt::view> view, std::size_t &value, entt::registry ®istry) { +void to_args_integrity(entt::view> view, std::size_t &value, entt::registry &) { value = view.size(); } @@ -363,6 +365,10 @@ TEST(Organizer, Prepare) { ASSERT_FALSE(registry.ctx().contains()); ASSERT_FALSE(registry.ctx().contains()); + ASSERT_EQ(std::as_const(registry).storage(), nullptr); + ASSERT_EQ(std::as_const(registry).storage(), nullptr); + ASSERT_EQ(std::as_const(registry).storage(), nullptr); + for(auto &&vertex: graph) { vertex.prepare(registry); } @@ -370,6 +376,10 @@ TEST(Organizer, Prepare) { ASSERT_FALSE(registry.ctx().contains()); ASSERT_FALSE(registry.ctx().contains()); ASSERT_TRUE(registry.ctx().contains()); + + ASSERT_NE(std::as_const(registry).storage(), nullptr); + ASSERT_NE(std::as_const(registry).storage(), nullptr); + ASSERT_EQ(std::as_const(registry).storage(), 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(), 0u); + ASSERT_EQ(registry.ctx().get(), 0u); } diff --git a/test/entt/entity/registry.cpp b/test/entt/entity/registry.cpp index dc02fdef..bb439fa8 100644 --- a/test/entt/entity/registry.cpp +++ b/test/entt/entity/registry.cpp @@ -40,18 +40,16 @@ struct aggregate { }; struct listener { - template + template static void sort(entt::registry ®istry) { - registry.sort([](auto lhs, auto rhs) { return lhs < rhs; }); + registry.sort([](auto lhs, auto rhs) { return lhs < rhs; }); } - template void incr(const entt::registry &, entt::entity entity) { last = entity; ++counter; } - template 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() != nullptr); + *ctx_check = (registry->ctx().find() != nullptr); } ~destruction_order() { - *ctx_check = *ctx_check && (registry->ctx().find() != nullptr); + *ctx_check = *ctx_check && (registry->ctx().find() != 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: entt::basic_entt_traits { + using base_type = entt::basic_entt_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(0), 42); ASSERT_EQ(ctx.find(), cctx.find()); - ASSERT_EQ(ctx.at(), cctx.at()); - ASSERT_EQ(ctx.at(), 42); + ASSERT_EQ(ctx.get(), cctx.get()); + ASSERT_EQ(ctx.get(), 42); ASSERT_EQ(ctx.find(), nullptr); ASSERT_EQ(cctx.find(), nullptr); @@ -139,8 +153,8 @@ TEST(Registry, Context) { ASSERT_EQ(ctx.insert_or_assign(0), 0); ASSERT_EQ(ctx.find(), cctx.find()); - ASSERT_EQ(ctx.at(), cctx.at()); - ASSERT_EQ(ctx.at(), 0); + ASSERT_EQ(ctx.get(), cctx.get()); + ASSERT_EQ(ctx.get(), 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::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().connect<&listener::incr>(listener); + registry.on_construct().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(entities[1]), 'c'); ASSERT_EQ(listener.counter, 3); - registry.on_construct().disconnect<&listener::incr>(listener); - registry.on_construct().connect<&listener::incr>(listener); + registry.on_construct().disconnect<&listener::incr>(listener); + registry.on_construct().connect<&listener::incr>(listener); registry.create(std::begin(entities), std::end(entities)); registry.insert(std::begin(entities), std::end(entities), 'a'); registry.insert(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 registry; + std::vector entities(entt::entt_traits::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(); const auto icview = registry.view(); @@ -722,6 +746,24 @@ TEST(Registry, RangeDestroy) { ASSERT_FALSE(registry.valid(entities[1u])); ASSERT_FALSE(registry.valid(entities[2u])); ASSERT_EQ(registry.storage().size(), 0u); + + entt::sparse_set managed{}; + + registry.create(std::begin(entities), std::end(entities)); + managed.push(std::begin(entities), std::end(entities)); + registry.insert(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().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().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(); + entt::entity entities[3u]; + auto iview = registry.view(); auto cview = registry.view(); - entt::entity entities[3u]; + auto mview = registry.view(); + auto fview = registry.view(entt::exclude); 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::exclude); + + registry.create(std::begin(entities), std::end(entities)); + + registry.emplace(entities[0u], 0); + registry.emplace(entities[2u], 0); + registry.emplace(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(entities[2u], 'c'); std::size_t cnt{}; - auto group = registry.group<>(entt::get); + auto group = registry.group(entt::get); group.each([&cnt](auto...) { ++cnt; }); ASSERT_FALSE((registry.owned())); @@ -974,7 +1063,7 @@ TEST(Registry, NonOwningGroupInitOnFirstUse) { TEST(Registry, NonOwningGroupInitOnEmplace) { entt::registry registry; entt::entity entities[3u]; - auto group = registry.group<>(entt::get); + auto group = registry.group(entt::get); registry.create(std::begin(entities), std::end(entities)); registry.insert(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); + auto group = registry.group(entt::get); const auto entity = registry.create(); registry.emplace(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(entt::get); - registry.create(std::begin(entities), std::end(entities)); - registry.insert(std::begin(entities), std::end(entities)); - registry.insert(std::begin(entities), std::end(entities)); - const auto g1 = registry.group(entt::get, entt::exclude); + ASSERT_DEATH(registry.group(entt::get), ""); + ASSERT_DEATH(registry.group(entt::get), ""); + ASSERT_DEATH(registry.group(entt::get, entt::exclude), ""); + ASSERT_DEATH((registry.group()), ""); +} - ASSERT_TRUE(registry.sortable(g1)); - ASSERT_EQ(g1.size(), 10u); +ENTT_DEBUG_TEST(RegistryDeathTest, ConflictingGroups) { + entt::registry registry; - const auto g2 = registry.group(entt::get); - - 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(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(entities[i * 2 + 1]); - } - - ASSERT_EQ(g1.size(), 0u); - ASSERT_EQ(g2.size(), 5u); - - const auto g3 = registry.group(entt::get, entt::exclude); - - 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(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(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(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(entities[i * 2 + 1]); - registry.erase(entities[i * 2]); - } - - ASSERT_EQ(g1.size(), 0u); - ASSERT_EQ(g2.size(), 0u); - ASSERT_EQ(g3.size(), 0u); + registry.group(entt::get, entt::exclude); + ASSERT_DEATH(registry.group(entt::get, entt::exclude), ""); } TEST(Registry, SortSingle) { @@ -1379,10 +1382,10 @@ TEST(Registry, Signals) { entt::entity entities[2u]; listener listener; - registry.on_construct().connect<&listener::incr>(listener); - registry.on_destroy().connect<&listener::decr>(listener); - registry.on_construct().connect<&listener::incr>(listener); - registry.on_destroy().connect<&listener::decr>(listener); + registry.on_construct().connect<&listener::incr>(listener); + registry.on_destroy().connect<&listener::decr>(listener); + registry.on_construct().connect<&listener::incr>(listener); + registry.on_destroy().connect<&listener::decr>(listener); registry.create(std::begin(entities), std::end(entities)); registry.insert(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().disconnect<&listener::decr>(listener); - registry.on_destroy().disconnect<&listener::decr>(listener); + registry.on_destroy().disconnect<&listener::decr>(listener); + registry.on_destroy().disconnect<&listener::decr>(listener); registry.erase(entities[1u]); ASSERT_EQ(listener.counter, 2); ASSERT_EQ(listener.last, entities[0u]); - registry.on_construct().disconnect<&listener::incr>(listener); - registry.on_construct().disconnect<&listener::incr>(listener); + registry.on_construct().disconnect<&listener::incr>(listener); + registry.on_construct().disconnect<&listener::incr>(listener); registry.emplace(entities[1u]); registry.emplace(entities[1u]); @@ -1417,8 +1420,8 @@ TEST(Registry, Signals) { ASSERT_EQ(listener.counter, 2); ASSERT_EQ(listener.last, entities[0u]); - registry.on_construct().connect<&listener::incr>(listener); - registry.on_destroy().connect<&listener::decr>(listener); + registry.on_construct().connect<&listener::incr>(listener); + registry.on_destroy().connect<&listener::decr>(listener); registry.emplace(entities[0u]); registry.erase(entities[1u]); @@ -1426,8 +1429,8 @@ TEST(Registry, Signals) { ASSERT_EQ(listener.counter, 2); ASSERT_EQ(listener.last, entities[1u]); - registry.on_construct().connect<&listener::incr>(listener); - registry.on_destroy().connect<&listener::decr>(listener); + registry.on_construct().connect<&listener::incr>(listener); + registry.on_destroy().connect<&listener::decr>(listener); registry.erase(entities[1u]); registry.emplace(entities[0u]); @@ -1454,8 +1457,8 @@ TEST(Registry, Signals) { ASSERT_EQ(listener.counter, 2); ASSERT_EQ(listener.last, entities[0u]); - registry.on_destroy().disconnect<&listener::decr>(listener); - registry.on_destroy().disconnect<&listener::decr>(listener); + registry.on_destroy().disconnect<&listener::decr>(listener); + registry.on_destroy().disconnect<&listener::decr>(listener); registry.emplace_or_replace(entities[0u]); registry.emplace_or_replace(entities[0u]); @@ -1463,8 +1466,8 @@ TEST(Registry, Signals) { ASSERT_EQ(listener.counter, 2); ASSERT_EQ(listener.last, entities[0u]); - registry.on_update().connect<&listener::incr>(listener); - registry.on_update().connect<&listener::incr>(listener); + registry.on_update().connect<&listener::incr>(listener); + registry.on_update().connect<&listener::incr>(listener); registry.emplace_or_replace(entities[0u]); registry.emplace_or_replace(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("custom"_hs).connect<&listener::incr>(listener); + registry.on_update("custom"_hs).connect<&listener::incr>(listener); + registry.on_destroy("custom"_hs).connect<&listener::incr>(listener); + + ASSERT_EQ(listener.counter, 0); + + registry.emplace(entity); + registry.patch(entity); + registry.erase(entity); + + ASSERT_EQ(listener.counter, 0); + + registry.storage("custom"_hs).emplace(entity); + registry.storage("custom"_hs).patch(entity); + registry.storage("custom"_hs).erase(entity); + + ASSERT_EQ(listener.counter, 3); +} + +TEST(Registry, SignalsOnEntity) { + entt::registry registry; + listener listener; + + registry.on_construct().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().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().connect<&listener::decr>(listener); + registry.patch(entity); + + ASSERT_EQ(listener.counter, 1); + ASSERT_EQ(listener.last, entity); + + registry.on_update().disconnect(&listener); + registry.patch(other); + + ASSERT_EQ(listener.counter, 1); + ASSERT_NE(listener.last, other); + + registry.on_destroy().connect<&listener::decr>(listener); + registry.destroy(entity); + + ASSERT_EQ(listener.counter, 0); + ASSERT_EQ(listener.last, entity); + + registry.on_destroy().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().size(), 0u); ASSERT_EQ(registry.storage().size(), 1u); + registry.insert(std::begin(entities) + 1, std::end(entities) - 1u); + registry.insert(std::begin(entities) + 1, std::end(entities) - 1u); + + ASSERT_EQ(registry.storage().size(), 1u); + ASSERT_EQ(registry.storage().size(), 1u); + + registry.erase(iview.begin(), iview.end()); + + ASSERT_EQ(registry.storage().size(), 0u); + ASSERT_EQ(registry.storage().size(), 0u); + registry.insert(std::begin(entities), std::end(entities)); registry.insert(std::begin(entities), std::end(entities)); @@ -1643,6 +1735,39 @@ TEST(Registry, StableErase) { ASSERT_EQ(registry.storage().size(), 1u); } +TEST(Registry, EraseIf) { + using namespace entt::literals; + + entt::registry registry; + const auto entity = registry.create(); + + registry.emplace(entity); + registry.storage("other"_hs).emplace(entity); + registry.emplace(entity); + + ASSERT_TRUE(registry.storage().contains(entity)); + ASSERT_TRUE(registry.storage("other"_hs).contains(entity)); + ASSERT_TRUE(registry.storage().contains(entity)); + + registry.erase_if(entity, [](auto &&...) { return false; }); + + ASSERT_TRUE(registry.storage().contains(entity)); + ASSERT_TRUE(registry.storage("other"_hs).contains(entity)); + ASSERT_TRUE(registry.storage().contains(entity)); + + registry.erase_if(entity, [](entt::id_type id, auto &&...) { return id == "other"_hs; }); + + ASSERT_TRUE(registry.storage().contains(entity)); + ASSERT_FALSE(registry.storage("other"_hs).contains(entity)); + ASSERT_TRUE(registry.storage().contains(entity)); + + registry.erase_if(entity, [](auto, const auto &storage) { return storage.type() == entt::type_id(); }); + + ASSERT_TRUE(registry.storage().contains(entity)); + ASSERT_FALSE(registry.storage("other"_hs).contains(entity)); + ASSERT_FALSE(registry.storage().contains(entity)); +} + TEST(Registry, Remove) { entt::registry registry; const auto iview = registry.view(); @@ -1689,6 +1814,18 @@ TEST(Registry, Remove) { ASSERT_EQ(registry.storage().size(), 0u); ASSERT_EQ(registry.storage().size(), 1u); + registry.insert(std::begin(entities) + 1, std::end(entities) - 1u); + registry.insert(std::begin(entities) + 1, std::end(entities) - 1u); + + ASSERT_EQ(registry.storage().size(), 1u); + ASSERT_EQ(registry.storage().size(), 1u); + + registry.remove(iview.begin(), iview.end()); + registry.remove(iview.begin(), iview.end()); + + ASSERT_EQ(registry.storage().size(), 0u); + ASSERT_EQ(registry.storage().size(), 0u); + registry.insert(std::begin(entities), std::end(entities)); registry.insert(std::begin(entities), std::end(entities)); @@ -1791,7 +1928,7 @@ TEST(Registry, NonOwningGroupInterleaved) { registry.emplace(entity); registry.emplace(entity); - const auto group = registry.group<>(entt::get); + const auto group = registry.group(entt::get); entity = registry.create(); registry.emplace(entity); @@ -1845,7 +1982,7 @@ TEST(Registry, PartialOwningGroupInterleaved) { TEST(Registry, NonOwningGroupSortInterleaved) { entt::registry registry; - const auto group = registry.group<>(entt::get); + const auto group = registry.group(entt::get); const auto e0 = registry.create(); registry.emplace(e0, 0); @@ -2009,7 +2146,7 @@ TEST(Registry, ScramblingPoolsIsAllowed) { // thanks to @andranik3949 for pointing out this missing test registry.view().each([](const auto entity, const auto &value) { - ASSERT_EQ(entt::to_integral(entity), value); + ASSERT_EQ(static_cast(entt::to_integral(entity)), value); }); } @@ -2021,7 +2158,7 @@ TEST(Registry, RuntimePools) { const auto entity = registry.create(); static_assert(std::is_same_v()), entt::storage_type_t &>); - static_assert(std::is_same_v()), const entt::storage_type_t &>); + static_assert(std::is_same_v()), const entt::storage_type_t *>); static_assert(std::is_same_v::base_type *>); static_assert(std::is_same_v::base_type *>); @@ -2030,7 +2167,7 @@ TEST(Registry, RuntimePools) { ASSERT_EQ(std::as_const(registry).storage("rehto"_hs), nullptr); ASSERT_EQ(®istry.storage("other"_hs), &storage); - ASSERT_NE(&std::as_const(registry).storage(), &storage); + ASSERT_NE(std::as_const(registry).storage(), &storage); ASSERT_FALSE(registry.any_of(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"_hs); storage.emplace(entity); @@ -2149,8 +2287,7 @@ TEST(Registry, RegistryStorageIterator) { TEST(Registry, RegistryStorageIteratorConversion) { entt::registry registry; - const auto entity = registry.create(); - registry.emplace(entity); + registry.storage(); 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("custom"_hs); + storage.emplace(entity); + + ASSERT_TRUE(registry.storage().empty()); + ASSERT_FALSE(registry.storage("custom"_hs).empty()); + ASSERT_TRUE(registry.storage("custom"_hs).contains(entity)); + + ASSERT_EQ(registry.storage().type(), entt::type_id()); + ASSERT_EQ(registry.storage("custom"_hs).type(), entt::type_id()); +} + TEST(Registry, NoEtoType) { entt::registry registry; const auto entity = registry.create(); diff --git a/test/entt/entity/storage_mixin.cpp b/test/entt/entity/sigh_mixin.cpp similarity index 75% rename from test/entt/entity/storage_mixin.cpp rename to test/entt/entity/sigh_mixin.cpp index 39185287..376e086b 100644 --- a/test/entt/entity/storage_mixin.cpp +++ b/test/entt/entity/sigh_mixin.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #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>: entt::basic_storage> { + basic_storage(const std::allocator &) {} + + [[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> pool; + entt::sigh_mixin> 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>(on_construct); pool.on_destroy().connect<&listener>(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> pool; + entt::sigh_mixin> pool; entt::sparse_set &base = pool; entt::registry registry; @@ -108,7 +131,7 @@ TEST(SighStorageMixin, StableType) { pool.on_construct().connect<&listener>(on_construct); pool.on_destroy().connect<&listener>(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> pool; + entt::sigh_mixin> pool; entt::sparse_set &base = pool; entt::registry registry; @@ -173,7 +196,7 @@ TEST(SighStorageMixin, EmptyType) { pool.on_construct().connect<&listener>(on_construct); pool.on_destroy().connect<&listener>(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> pool; + entt::sigh_mixin> pool; entt::sparse_set &base = pool; entt::registry registry; @@ -238,12 +261,12 @@ TEST(SighStorageMixin, NonDefaultConstructibleType) { pool.on_construct().connect<&listener>(on_construct); pool.on_destroy().connect<&listener>(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> pool; +TEST(SighMixin, VoidType) { + entt::sigh_mixin> pool; entt::registry registry; counter on_construct{}; @@ -297,7 +320,7 @@ TEST(SighStorageMixin, VoidType) { ASSERT_EQ(pool.type(), entt::type_id()); ASSERT_TRUE(pool.contains(entt::entity{99})); - entt::sigh_storage_mixin> other{std::move(pool)}; + entt::sigh_mixin> 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> pool; +TEST(SighMixin, Move) { + entt::sigh_mixin> pool; entt::registry registry; counter on_construct{}; @@ -330,7 +353,7 @@ TEST(SighStorageMixin, Move) { ASSERT_TRUE(std::is_move_assignable_v); ASSERT_EQ(pool.type(), entt::type_id()); - entt::sigh_storage_mixin> other{std::move(pool)}; + entt::sigh_mixin> 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::null)); - other = entt::sigh_storage_mixin>{}; + other = entt::sigh_mixin>{}; 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> pool; - entt::sigh_storage_mixin> other; +TEST(SighMixin, Swap) { + entt::sigh_mixin> pool; + entt::sigh_mixin> 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> pool; + entt::registry registry; + + counter on_destroy{}; + + pool.bind(entt::forward_as_any(registry)); + pool.on_destroy().connect<&listener>(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::sigh_mixin> pool; + entt::registry registry; + + counter on_construct{}; + counter on_destroy{}; + + pool.bind(entt::forward_as_any(registry)); + pool.on_construct().connect<&listener>(on_construct); + pool.on_destroy().connect<&listener>(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 allocator{}; - test(entt::sigh_storage_mixin>>{allocator}, allocator); - test(entt::sigh_storage_mixin>>{allocator}, allocator); - test(entt::sigh_storage_mixin>>{allocator}, allocator); + test(entt::sigh_mixin>>{allocator}, allocator); + test(entt::sigh_mixin>>{allocator}, allocator); + test(entt::sigh_mixin>>{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::trigger_on_allocate = true; - ASSERT_THROW(base.emplace(entt::entity{0}), test::throwing_allocator::exception_type); + ASSERT_THROW(base.push(entt::entity{0}), test::throwing_allocator::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>>{}); - test(entt::sigh_storage_mixin>>{}); + test(entt::sigh_mixin>>{}); + test(entt::sigh_mixin>>{}); } -TEST(SighStorageMixin, ThrowingComponent) { - entt::sigh_storage_mixin> pool; +TEST(SighMixin, ThrowingComponent) { + entt::sigh_mixin> pool; using registry_type = typename decltype(pool)::registry_type; registry_type registry; diff --git a/test/entt/entity/snapshot.cpp b/test/entt/entity/snapshot.cpp index 3a6f1c87..af2fdc8d 100644 --- a/test/entt/entity/snapshot.cpp +++ b/test/entt/entity/snapshot.cpp @@ -1,42 +1,949 @@ #include #include +#include #include #include #include #include #include +#include +#include #include #include #include +#include -struct noncopyable_component { - noncopyable_component() - : value{} {} +struct empty {}; - explicit noncopyable_component(int v) - : value{v} {} +struct shadow { + entt::entity target{entt::null}; - noncopyable_component(const noncopyable_component &) = delete; - noncopyable_component(noncopyable_component &&) = default; - - noncopyable_component &operator=(const noncopyable_component &) = delete; - noncopyable_component &operator=(noncopyable_component &&) = default; - - int value; + static void listener(entt::entity &elem, entt::registry ®istry, const entt::entity entt) { + elem = registry.get(entt).target; + } }; +TEST(BasicSnapshot, Constructors) { + static_assert(!std::is_default_constructible_v>); + static_assert(!std::is_copy_constructible_v>); + static_assert(!std::is_copy_assignable_v>); + static_assert(std::is_move_constructible_v>); + static_assert(std::is_move_assignable_v>); + + entt::registry registry; + entt::basic_snapshot snapshot{registry}; + entt::basic_snapshot other{std::move(snapshot)}; + + ASSERT_NO_FATAL_FAILURE(snapshot = std::move(other)); +} + +TEST(BasicSnapshot, GetEntityType) { + using namespace entt::literals; + using traits_type = entt::entt_traits; + + entt::registry registry; + entt::basic_snapshot snapshot{registry}; + const auto &storage = registry.storage(); + + std::vector data{}; + auto archive = [&data](auto &&value) { data.emplace_back(std::forward(value)); }; + + snapshot.get(archive); + + ASSERT_EQ(data.size(), 2u); + + ASSERT_NE(entt::any_cast(&data[0u]), nullptr); + ASSERT_EQ(entt::any_cast(data[0u]), storage.size()); + + ASSERT_NE(entt::any_cast(&data[1u]), nullptr); + ASSERT_EQ(entt::any_cast(data[1u]), storage.in_use()); + + entt::entity entities[3u]; + + registry.create(std::begin(entities), std::end(entities)); + registry.destroy(entities[1u]); + + data.clear(); + snapshot.get(archive, "ignored"_hs); + + ASSERT_EQ(data.size(), 5u); + + ASSERT_NE(entt::any_cast(&data[0u]), nullptr); + ASSERT_EQ(entt::any_cast(data[0u]), storage.size()); + + ASSERT_NE(entt::any_cast(&data[1u]), nullptr); + ASSERT_EQ(entt::any_cast(data[1u]), storage.in_use()); + + ASSERT_NE(entt::any_cast(&data[2u]), nullptr); + ASSERT_EQ(entt::any_cast(data[2u]), storage.data()[0u]); + + ASSERT_NE(entt::any_cast(&data[3u]), nullptr); + ASSERT_EQ(entt::any_cast(data[3u]), storage.data()[1u]); + + ASSERT_NE(entt::any_cast(&data[4u]), nullptr); + ASSERT_EQ(entt::any_cast(data[4u]), storage.data()[2u]); +} + +TEST(BasicSnapshot, GetType) { + using namespace entt::literals; + using traits_type = entt::entt_traits; + + entt::registry registry; + entt::basic_snapshot snapshot{registry}; + const auto &storage = registry.storage(); + + entt::entity entities[3u]; + const int values[3u]{1, 2, 3}; + + registry.create(std::begin(entities), std::end(entities)); + registry.insert(std::begin(entities), std::end(entities), std::begin(values)); + registry.destroy(entities[1u]); + + std::vector data{}; + auto archive = [&data](auto &&value) { data.emplace_back(std::forward(value)); }; + + snapshot.get(archive, "other"_hs); + + ASSERT_EQ(data.size(), 1u); + + ASSERT_NE(entt::any_cast(&data[0u]), nullptr); + ASSERT_EQ(entt::any_cast(data[0u]), 0u); + + data.clear(); + snapshot.get(archive); + + ASSERT_EQ(data.size(), 5u); + + ASSERT_NE(entt::any_cast(&data[0u]), nullptr); + ASSERT_EQ(entt::any_cast(data[0u]), storage.size()); + + ASSERT_NE(entt::any_cast(&data[1u]), nullptr); + ASSERT_EQ(entt::any_cast(data[1u]), entities[0u]); + + ASSERT_NE(entt::any_cast(&data[2u]), nullptr); + ASSERT_EQ(entt::any_cast(data[2u]), values[0u]); + + ASSERT_NE(entt::any_cast(&data[3u]), nullptr); + ASSERT_EQ(entt::any_cast(data[3u]), entities[2u]); + + ASSERT_NE(entt::any_cast(&data[4u]), nullptr); + ASSERT_EQ(entt::any_cast(data[4u]), values[2u]); +} + +TEST(BasicSnapshot, GetEmptyType) { + using namespace entt::literals; + using traits_type = entt::entt_traits; + + entt::registry registry; + entt::basic_snapshot snapshot{registry}; + const auto &storage = registry.storage(); + + entt::entity entities[3u]; + + registry.create(std::begin(entities), std::end(entities)); + registry.insert(std::begin(entities), std::end(entities)); + registry.destroy(entities[1u]); + + std::vector data{}; + auto archive = [&data](auto &&value) { data.emplace_back(std::forward(value)); }; + + snapshot.get(archive, "other"_hs); + + ASSERT_EQ(data.size(), 1u); + + ASSERT_NE(entt::any_cast(&data[0u]), nullptr); + ASSERT_EQ(entt::any_cast(data[0u]), 0u); + + data.clear(); + snapshot.get(archive); + + ASSERT_EQ(data.size(), 3u); + + ASSERT_NE(entt::any_cast(&data[0u]), nullptr); + ASSERT_EQ(entt::any_cast(data[0u]), storage.size()); + + ASSERT_NE(entt::any_cast(&data[1u]), nullptr); + ASSERT_EQ(entt::any_cast(data[1u]), entities[0u]); + + ASSERT_NE(entt::any_cast(&data[2u]), nullptr); + ASSERT_EQ(entt::any_cast(data[2u]), entities[2u]); +} + +TEST(BasicSnapshot, GetTypeSparse) { + using namespace entt::literals; + using traits_type = entt::entt_traits; + + entt::registry registry; + entt::basic_snapshot snapshot{registry}; + + entt::entity entities[3u]; + const int values[3u]{1, 2, 3}; + + registry.create(std::begin(entities), std::end(entities)); + registry.insert(std::begin(entities), std::end(entities), std::begin(values)); + registry.destroy(entities[1u]); + + std::vector data{}; + auto archive = [&data](auto &&value) { data.emplace_back(std::forward(value)); }; + + snapshot.get(archive, std::begin(entities), std::end(entities), "other"_hs); + + ASSERT_EQ(data.size(), 1u); + + ASSERT_NE(entt::any_cast(&data[0u]), nullptr); + ASSERT_EQ(entt::any_cast(data[0u]), 0u); + + data.clear(); + snapshot.get(archive, std::begin(entities), std::end(entities)); + + ASSERT_EQ(data.size(), 6u); + + ASSERT_NE(entt::any_cast(&data[0u]), nullptr); + ASSERT_EQ(entt::any_cast(data[0u]), static_cast(std::distance(std::begin(entities), std::end(entities)))); + + ASSERT_NE(entt::any_cast(&data[1u]), nullptr); + ASSERT_EQ(entt::any_cast(data[1u]), entities[0u]); + + ASSERT_NE(entt::any_cast(&data[2u]), nullptr); + ASSERT_EQ(entt::any_cast(data[2u]), values[0u]); + + ASSERT_NE(entt::any_cast(&data[3u]), nullptr); + ASSERT_EQ(entt::any_cast(data[3u]), static_cast(entt::null)); + + ASSERT_NE(entt::any_cast(&data[4u]), nullptr); + ASSERT_EQ(entt::any_cast(data[4u]), entities[2u]); + + ASSERT_NE(entt::any_cast(&data[5u]), nullptr); + ASSERT_EQ(entt::any_cast(data[5u]), values[2u]); +} + +TEST(BasicSnapshotLoader, Constructors) { + static_assert(!std::is_default_constructible_v>); + static_assert(!std::is_copy_constructible_v>); + static_assert(!std::is_copy_assignable_v>); + static_assert(std::is_move_constructible_v>); + static_assert(std::is_move_assignable_v>); + + entt::registry registry; + entt::basic_snapshot_loader loader{registry}; + entt::basic_snapshot_loader other{std::move(loader)}; + + ASSERT_NO_FATAL_FAILURE(loader = std::move(other)); +} + +TEST(BasicSnapshotLoader, GetEntityType) { + using namespace entt::literals; + using traits_type = entt::entt_traits; + + entt::registry registry; + entt::basic_snapshot_loader loader{registry}; + const auto &storage = registry.storage(); + + std::vector data{}; + auto archive = [&data, pos = 0u](auto &value) mutable { value = entt::any_cast>(data[pos++]); }; + const entt::entity entities[3u]{traits_type::construct(0u, 0u), traits_type::construct(2u, 0u), traits_type::construct(1u, 1u)}; + + ASSERT_FALSE(registry.valid(entities[0u])); + ASSERT_FALSE(registry.valid(entities[1u])); + ASSERT_FALSE(registry.valid(entities[2u])); + + data.emplace_back(static_cast(0u)); + data.emplace_back(static_cast(0u)); + + loader.get(archive); + + ASSERT_FALSE(registry.valid(entities[0u])); + ASSERT_FALSE(registry.valid(entities[1u])); + ASSERT_FALSE(registry.valid(entities[2u])); + + ASSERT_EQ(storage.size(), 0u); + ASSERT_EQ(storage.in_use(), 0u); + + data.emplace_back(static_cast(3u)); + data.emplace_back(static_cast(2u)); + + data.emplace_back(entities[0u]); + data.emplace_back(entities[1u]); + data.emplace_back(entities[2u]); + + loader.get(archive, "ignored"_hs); + + ASSERT_TRUE(registry.valid(entities[0u])); + ASSERT_TRUE(registry.valid(entities[1u])); + ASSERT_FALSE(registry.valid(entities[2u])); + + ASSERT_EQ(storage.size(), 3u); + ASSERT_EQ(storage.in_use(), 2u); + + ASSERT_EQ(storage[0u], entities[0u]); + ASSERT_EQ(storage[1u], entities[1u]); + ASSERT_EQ(storage[2u], entities[2u]); + + ASSERT_EQ(registry.create(), entities[2u]); +} + +TEST(BasicSnapshotLoader, GetType) { + using namespace entt::literals; + using traits_type = entt::entt_traits; + + entt::registry registry; + entt::basic_snapshot_loader loader{registry}; + const auto &storage = registry.storage(); + + std::vector data{}; + auto archive = [&data, pos = 0u](auto &value) mutable { value = entt::any_cast>(data[pos++]); }; + const entt::entity entities[2u]{traits_type::construct(0u, 0u), traits_type::construct(2u, 0u)}; + const int values[2u]{1, 3}; + + ASSERT_FALSE(registry.valid(entities[0u])); + ASSERT_FALSE(registry.valid(entities[1u])); + + data.emplace_back(static_cast(1u)); + data.emplace_back(entities[0u]); + data.emplace_back(values[0u]); + + loader.get(archive, "other"_hs); + + ASSERT_TRUE(registry.valid(entities[0u])); + ASSERT_FALSE(registry.valid(entities[1u])); + + ASSERT_EQ(storage.size(), 0u); + ASSERT_EQ(registry.storage("other"_hs).size(), 1u); + + data.emplace_back(static_cast(2u)); + + data.emplace_back(entities[0u]); + data.emplace_back(values[0u]); + + data.emplace_back(entities[1u]); + data.emplace_back(values[1u]); + + loader.get(archive); + + ASSERT_TRUE(registry.valid(entities[0u])); + ASSERT_TRUE(registry.valid(entities[1u])); + + ASSERT_EQ(storage.size(), 2u); + ASSERT_TRUE(storage.contains(entities[0u])); + ASSERT_TRUE(storage.contains(entities[1u])); + ASSERT_EQ(storage.get(entities[0u]), values[0u]); + ASSERT_EQ(storage.get(entities[1u]), values[1u]); +} + +TEST(BasicSnapshotLoader, GetEmptyType) { + using namespace entt::literals; + using traits_type = entt::entt_traits; + + entt::registry registry; + entt::basic_snapshot_loader loader{registry}; + const auto &storage = registry.storage(); + + std::vector data{}; + auto archive = [&data, pos = 0u](auto &value) mutable { value = entt::any_cast>(data[pos++]); }; + const entt::entity entities[2u]{traits_type::construct(0u, 0u), traits_type::construct(2u, 0u)}; + + ASSERT_FALSE(registry.valid(entities[0u])); + ASSERT_FALSE(registry.valid(entities[1u])); + + data.emplace_back(static_cast(1u)); + data.emplace_back(entities[0u]); + + loader.get(archive, "other"_hs); + + ASSERT_TRUE(registry.valid(entities[0u])); + ASSERT_FALSE(registry.valid(entities[1u])); + + ASSERT_EQ(storage.size(), 0u); + ASSERT_EQ(registry.storage("other"_hs).size(), 1u); + + data.emplace_back(static_cast(2u)); + + data.emplace_back(entities[0u]); + data.emplace_back(entities[1u]); + + loader.get(archive); + + ASSERT_TRUE(registry.valid(entities[0u])); + ASSERT_TRUE(registry.valid(entities[1u])); + + ASSERT_EQ(storage.size(), 2u); + ASSERT_TRUE(storage.contains(entities[0u])); + ASSERT_TRUE(storage.contains(entities[1u])); +} + +TEST(BasicSnapshotLoader, GetTypeSparse) { + using namespace entt::literals; + using traits_type = entt::entt_traits; + + entt::registry registry; + entt::basic_snapshot_loader loader{registry}; + const auto &storage = registry.storage(); + + std::vector data{}; + auto archive = [&data, pos = 0u](auto &value) mutable { value = entt::any_cast>(data[pos++]); }; + const entt::entity entities[2u]{traits_type::construct(0u, 0u), traits_type::construct(2u, 0u)}; + const int values[2u]{1, 3}; + + ASSERT_FALSE(registry.valid(entities[0u])); + ASSERT_FALSE(registry.valid(entities[1u])); + + data.emplace_back(static_cast(2u)); + data.emplace_back(static_cast(entt::null)); + data.emplace_back(entities[0u]); + data.emplace_back(values[0u]); + + loader.get(archive, "other"_hs); + + ASSERT_TRUE(registry.valid(entities[0u])); + ASSERT_FALSE(registry.valid(entities[1u])); + + ASSERT_EQ(storage.size(), 0u); + ASSERT_EQ(registry.storage("other"_hs).size(), 1u); + + data.emplace_back(static_cast(3u)); + + data.emplace_back(entities[0u]); + data.emplace_back(values[0u]); + + data.emplace_back(static_cast(entt::null)); + + data.emplace_back(entities[1u]); + data.emplace_back(values[1u]); + + loader.get(archive); + + ASSERT_TRUE(registry.valid(entities[0u])); + ASSERT_TRUE(registry.valid(entities[1u])); + + ASSERT_EQ(storage.size(), 2u); + ASSERT_TRUE(storage.contains(entities[0u])); + ASSERT_TRUE(storage.contains(entities[1u])); + ASSERT_EQ(storage.get(entities[0u]), values[0u]); + ASSERT_EQ(storage.get(entities[1u]), values[1u]); +} + +TEST(BasicSnapshotLoader, GetTypeWithListener) { + using traits_type = entt::entt_traits; + + entt::registry registry; + entt::basic_snapshot_loader loader{registry}; + entt::entity check{entt::null}; + + std::vector data{}; + auto archive = [&data, pos = 0u](auto &value) mutable { value = entt::any_cast>(data[pos++]); }; + const auto entity{traits_type::construct(1u, 1u)}; + const shadow value{entity}; + + ASSERT_FALSE(registry.valid(entity)); + ASSERT_EQ(check, static_cast(entt::null)); + + registry.on_construct().connect<&shadow::listener>(check); + + data.emplace_back(static_cast(1u)); + data.emplace_back(entity); + data.emplace_back(value); + + loader.get(archive); + + ASSERT_TRUE(registry.valid(entity)); + ASSERT_EQ(check, entity); +} + +TEST(BasicSnapshotLoader, Orphans) { + using namespace entt::literals; + using traits_type = entt::entt_traits; + + entt::registry registry; + entt::basic_snapshot_loader loader{registry}; + + std::vector data{}; + auto archive = [&data, pos = 0u](auto &value) mutable { value = entt::any_cast>(data[pos++]); }; + const entt::entity entities[2u]{traits_type::construct(0u, 0u), traits_type::construct(2u, 0u)}; + const int value = 42; + + ASSERT_FALSE(registry.valid(entities[0u])); + ASSERT_FALSE(registry.valid(entities[1u])); + + data.emplace_back(static_cast(2u)); + data.emplace_back(static_cast(2u)); + + data.emplace_back(entities[0u]); + data.emplace_back(entities[1u]); + + data.emplace_back(static_cast(1u)); + data.emplace_back(entities[0u]); + data.emplace_back(value); + + loader.get(archive); + loader.get(archive); + + ASSERT_TRUE(registry.valid(entities[0u])); + ASSERT_TRUE(registry.valid(entities[1u])); + + loader.orphans(); + + ASSERT_TRUE(registry.valid(entities[0u])); + ASSERT_FALSE(registry.valid(entities[1u])); +} + +TEST(BasicContinuousLoader, Constructors) { + static_assert(!std::is_default_constructible_v>); + static_assert(!std::is_copy_constructible_v>); + static_assert(!std::is_copy_assignable_v>); + static_assert(std::is_move_constructible_v>); + static_assert(std::is_move_assignable_v>); + + entt::registry registry; + entt::basic_continuous_loader loader{registry}; + entt::basic_continuous_loader other{std::move(loader)}; + + ASSERT_NO_FATAL_FAILURE(loader = std::move(other)); +} + +TEST(BasicContinuousLoader, GetEntityType) { + using namespace entt::literals; + using traits_type = entt::entt_traits; + + entt::registry registry; + entt::basic_continuous_loader loader{registry}; + const auto &storage = registry.storage(); + + std::vector data{}; + auto archive = [&data, pos = 0u](auto &value) mutable { value = entt::any_cast>(data[pos++]); }; + const entt::entity entities[3u]{traits_type::construct(1u, 0u), traits_type::construct(0u, 0u), traits_type::construct(2u, 0u)}; + + ASSERT_FALSE(registry.valid(entities[0u])); + ASSERT_FALSE(registry.valid(entities[1u])); + ASSERT_FALSE(registry.valid(entities[2u])); + + data.emplace_back(static_cast(0u)); + data.emplace_back(static_cast(0u)); + + loader.get(archive); + + ASSERT_FALSE(registry.valid(entities[0u])); + ASSERT_FALSE(registry.valid(entities[1u])); + ASSERT_FALSE(registry.valid(entities[2u])); + + ASSERT_FALSE(loader.contains(entities[0u])); + ASSERT_FALSE(loader.contains(entities[1u])); + ASSERT_FALSE(loader.contains(entities[2u])); + + ASSERT_EQ(loader.map(entities[0u]), static_cast(entt::null)); + ASSERT_EQ(loader.map(entities[1u]), static_cast(entt::null)); + ASSERT_EQ(loader.map(entities[2u]), static_cast(entt::null)); + + ASSERT_EQ(storage.size(), 0u); + ASSERT_EQ(storage.in_use(), 0u); + + data.emplace_back(static_cast(3u)); + data.emplace_back(static_cast(2u)); + + data.emplace_back(entities[0u]); + data.emplace_back(entities[1u]); + data.emplace_back(entities[2u]); + + loader.get(archive, "ignored"_hs); + + ASSERT_TRUE(loader.contains(entities[0u])); + ASSERT_TRUE(loader.contains(entities[1u])); + ASSERT_FALSE(loader.contains(entities[2u])); + + ASSERT_NE(loader.map(entities[0u]), static_cast(entt::null)); + ASSERT_NE(loader.map(entities[1u]), static_cast(entt::null)); + ASSERT_EQ(loader.map(entities[2u]), static_cast(entt::null)); + + ASSERT_TRUE(registry.valid(loader.map(entities[0u]))); + ASSERT_TRUE(registry.valid(loader.map(entities[1u]))); + + ASSERT_EQ(storage.size(), 2u); + ASSERT_EQ(storage.in_use(), 2u); + + ASSERT_EQ(storage[0u], loader.map(entities[0u])); + ASSERT_EQ(storage[1u], loader.map(entities[1u])); + + ASSERT_EQ(registry.create(), entities[2u]); + + data.emplace_back(static_cast(3u)); + data.emplace_back(static_cast(3u)); + + data.emplace_back(entities[0u]); + data.emplace_back(entities[1u]); + data.emplace_back(entities[2u]); + + loader.get(archive); + + ASSERT_TRUE(loader.contains(entities[0u])); + ASSERT_TRUE(loader.contains(entities[1u])); + ASSERT_TRUE(loader.contains(entities[2u])); + + ASSERT_NE(loader.map(entities[0u]), static_cast(entt::null)); + ASSERT_NE(loader.map(entities[1u]), static_cast(entt::null)); + ASSERT_NE(loader.map(entities[2u]), static_cast(entt::null)); + + ASSERT_TRUE(registry.valid(loader.map(entities[0u]))); + ASSERT_TRUE(registry.valid(loader.map(entities[1u]))); + ASSERT_TRUE(registry.valid(loader.map(entities[2u]))); + + ASSERT_EQ(storage.size(), 4u); + ASSERT_EQ(storage.in_use(), 4u); + + ASSERT_EQ(storage[0u], loader.map(entities[0u])); + ASSERT_EQ(storage[1u], loader.map(entities[1u])); + ASSERT_EQ(storage[3u], loader.map(entities[2u])); + + registry.destroy(loader.map(entities[1u])); + + ASSERT_TRUE(loader.contains(entities[1u])); + ASSERT_NE(loader.map(entities[1u]), static_cast(entt::null)); + ASSERT_FALSE(registry.valid(loader.map(entities[1u]))); + + data.emplace_back(static_cast(1u)); + data.emplace_back(static_cast(1u)); + + data.emplace_back(entities[1u]); + + loader.get(archive); + + ASSERT_TRUE(loader.contains(entities[1u])); + ASSERT_NE(loader.map(entities[1u]), static_cast(entt::null)); + ASSERT_TRUE(registry.valid(loader.map(entities[1u]))); + ASSERT_EQ(storage[3u], loader.map(entities[1u])); + + data.emplace_back(static_cast(3u)); + data.emplace_back(static_cast(1u)); + + data.emplace_back(entities[1u]); + data.emplace_back(entities[2u]); + data.emplace_back(entities[0u]); + + loader.get(archive, "ignored"_hs); + + ASSERT_FALSE(loader.contains(entities[0u])); + ASSERT_TRUE(loader.contains(entities[1u])); + ASSERT_FALSE(loader.contains(entities[2u])); + + ASSERT_EQ(loader.map(entities[0u]), static_cast(entt::null)); + ASSERT_NE(loader.map(entities[1u]), static_cast(entt::null)); + ASSERT_EQ(loader.map(entities[2u]), static_cast(entt::null)); + + ASSERT_TRUE(registry.valid(loader.map(entities[1u]))); + + ASSERT_EQ(storage.size(), 4u); + ASSERT_EQ(storage.in_use(), 2u); + + ASSERT_EQ(storage[1u], loader.map(entities[1u])); +} + +TEST(BasicContinuousLoader, GetType) { + using namespace entt::literals; + using traits_type = entt::entt_traits; + + entt::registry registry; + entt::basic_continuous_loader loader{registry}; + const auto &storage = registry.storage(); + + std::vector data{}; + auto archive = [&data, pos = 0u](auto &value) mutable { value = entt::any_cast>(data[pos++]); }; + const entt::entity entities[2u]{traits_type::construct(0u, 0u), traits_type::construct(2u, 0u)}; + const int values[2u]{1, 3}; + + ASSERT_FALSE(loader.contains(entities[0u])); + ASSERT_FALSE(loader.contains(entities[1u])); + + ASSERT_FALSE(registry.valid(loader.map(entities[0u]))); + ASSERT_FALSE(registry.valid(loader.map(entities[1u]))); + + data.emplace_back(static_cast(1u)); + data.emplace_back(entities[0u]); + data.emplace_back(values[0u]); + + loader.get(archive, "other"_hs); + + ASSERT_TRUE(loader.contains(entities[0u])); + ASSERT_FALSE(loader.contains(entities[1u])); + + ASSERT_TRUE(registry.valid(loader.map(entities[0u]))); + ASSERT_FALSE(registry.valid(loader.map(entities[1u]))); + + ASSERT_EQ(storage.size(), 0u); + ASSERT_EQ(registry.storage("other"_hs).size(), 1u); + + data.emplace_back(static_cast(2u)); + + data.emplace_back(entities[0u]); + data.emplace_back(values[0u]); + + data.emplace_back(entities[1u]); + data.emplace_back(values[1u]); + + loader.get(archive); + + ASSERT_TRUE(loader.contains(entities[0u])); + ASSERT_TRUE(loader.contains(entities[1u])); + + ASSERT_TRUE(registry.valid(loader.map(entities[0u]))); + ASSERT_TRUE(registry.valid(loader.map(entities[1u]))); + + ASSERT_EQ(storage.size(), 2u); + ASSERT_TRUE(storage.contains(loader.map(entities[0u]))); + ASSERT_TRUE(storage.contains(loader.map(entities[1u]))); + ASSERT_EQ(storage.get(loader.map(entities[0u])), values[0u]); + ASSERT_EQ(storage.get(loader.map(entities[1u])), values[1u]); +} + +TEST(BasicContinuousLoader, GetTypeExtended) { + using namespace entt::literals; + using traits_type = entt::entt_traits; + + entt::registry registry; + entt::basic_continuous_loader loader{registry}; + const auto &storage = registry.storage(); + + std::vector data{}; + const entt::entity entities[2u]{traits_type::construct(0u, 1u), traits_type::construct(1u, 1u)}; + const shadow value{entities[0u]}; + + auto archive = [&loader, &data, pos = 0u](auto &value) mutable { + value = entt::any_cast>(data[pos++]); + + if constexpr(std::is_same_v, shadow>) { + value.target = loader.map(value.target); + } + }; + + ASSERT_FALSE(loader.contains(entities[0u])); + ASSERT_FALSE(loader.contains(entities[1u])); + + ASSERT_FALSE(registry.valid(loader.map(entities[0u]))); + ASSERT_FALSE(registry.valid(loader.map(entities[1u]))); + + data.emplace_back(static_cast(2u)); + data.emplace_back(static_cast(2u)); + + data.emplace_back(entities[0u]); + data.emplace_back(entities[1u]); + + data.emplace_back(static_cast(1u)); + data.emplace_back(entities[1u]); + data.emplace_back(value); + + loader.get(archive); + loader.get(archive); + + ASSERT_TRUE(loader.contains(entities[0u])); + ASSERT_TRUE(loader.contains(entities[1u])); + + ASSERT_TRUE(registry.valid(loader.map(entities[0u]))); + ASSERT_TRUE(registry.valid(loader.map(entities[1u]))); + + ASSERT_FALSE(registry.valid(entities[0u])); + ASSERT_FALSE(registry.valid(entities[1u])); + + ASSERT_EQ(storage.size(), 1u); + ASSERT_TRUE(storage.contains(loader.map(entities[1u]))); + ASSERT_EQ(storage.get(loader.map(entities[1u])).target, loader.map(entities[0u])); +} + +TEST(BasicContinuousLoader, GetEmptyType) { + using namespace entt::literals; + using traits_type = entt::entt_traits; + + entt::registry registry; + entt::basic_continuous_loader loader{registry}; + const auto &storage = registry.storage(); + + std::vector data{}; + auto archive = [&data, pos = 0u](auto &value) mutable { value = entt::any_cast>(data[pos++]); }; + const entt::entity entities[2u]{traits_type::construct(0u, 0u), traits_type::construct(2u, 0u)}; + + ASSERT_FALSE(loader.contains(entities[0u])); + ASSERT_FALSE(loader.contains(entities[1u])); + + ASSERT_FALSE(registry.valid(loader.map(entities[0u]))); + ASSERT_FALSE(registry.valid(loader.map(entities[1u]))); + + data.emplace_back(static_cast(1u)); + data.emplace_back(entities[0u]); + + loader.get(archive, "other"_hs); + + ASSERT_TRUE(loader.contains(entities[0u])); + ASSERT_FALSE(loader.contains(entities[1u])); + + ASSERT_TRUE(registry.valid(loader.map(entities[0u]))); + ASSERT_FALSE(registry.valid(loader.map(entities[1u]))); + + ASSERT_EQ(storage.size(), 0u); + ASSERT_EQ(registry.storage("other"_hs).size(), 1u); + + data.emplace_back(static_cast(2u)); + + data.emplace_back(entities[0u]); + data.emplace_back(entities[1u]); + + loader.get(archive); + + ASSERT_TRUE(loader.contains(entities[0u])); + ASSERT_TRUE(loader.contains(entities[1u])); + + ASSERT_TRUE(registry.valid(loader.map(entities[0u]))); + ASSERT_TRUE(registry.valid(loader.map(entities[1u]))); + + ASSERT_EQ(storage.size(), 2u); + ASSERT_TRUE(storage.contains(loader.map(entities[0u]))); + ASSERT_TRUE(storage.contains(loader.map(entities[1u]))); +} + +TEST(BasicContinuousLoader, GetTypeSparse) { + using namespace entt::literals; + using traits_type = entt::entt_traits; + + entt::registry registry; + entt::basic_continuous_loader loader{registry}; + const auto &storage = registry.storage(); + + std::vector data{}; + auto archive = [&data, pos = 0u](auto &value) mutable { value = entt::any_cast>(data[pos++]); }; + const entt::entity entities[2u]{traits_type::construct(0u, 0u), traits_type::construct(2u, 0u)}; + const int values[2u]{1, 3}; + + ASSERT_FALSE(loader.contains(entities[0u])); + ASSERT_FALSE(loader.contains(entities[1u])); + + ASSERT_FALSE(registry.valid(loader.map(entities[0u]))); + ASSERT_FALSE(registry.valid(loader.map(entities[1u]))); + + data.emplace_back(static_cast(2u)); + data.emplace_back(static_cast(entt::null)); + data.emplace_back(entities[0u]); + data.emplace_back(values[0u]); + + loader.get(archive, "other"_hs); + + ASSERT_TRUE(loader.contains(entities[0u])); + ASSERT_FALSE(loader.contains(entities[1u])); + + ASSERT_TRUE(registry.valid(loader.map(entities[0u]))); + ASSERT_FALSE(registry.valid(loader.map(entities[1u]))); + + ASSERT_EQ(storage.size(), 0u); + ASSERT_EQ(registry.storage("other"_hs).size(), 1u); + + data.emplace_back(static_cast(3u)); + + data.emplace_back(entities[0u]); + data.emplace_back(values[0u]); + + data.emplace_back(static_cast(entt::null)); + + data.emplace_back(entities[1u]); + data.emplace_back(values[1u]); + + loader.get(archive); + + ASSERT_TRUE(loader.contains(entities[0u])); + ASSERT_TRUE(loader.contains(entities[1u])); + + ASSERT_TRUE(registry.valid(loader.map(entities[0u]))); + ASSERT_TRUE(registry.valid(loader.map(entities[1u]))); + + ASSERT_EQ(storage.size(), 2u); + ASSERT_TRUE(storage.contains(loader.map(entities[0u]))); + ASSERT_TRUE(storage.contains(loader.map(entities[1u]))); + ASSERT_EQ(storage.get(loader.map(entities[0u])), values[0u]); + ASSERT_EQ(storage.get(loader.map(entities[1u])), values[1u]); +} + +TEST(BasicContinuousLoader, GetTypeWithListener) { + using traits_type = entt::entt_traits; + + entt::registry registry; + entt::basic_continuous_loader loader{registry}; + entt::entity check{entt::null}; + + std::vector data{}; + auto archive = [&data, pos = 0u](auto &value) mutable { value = entt::any_cast>(data[pos++]); }; + const auto entity{traits_type::construct(1u, 1u)}; + const shadow value{entity}; + + ASSERT_FALSE(registry.valid(loader.map(entity))); + ASSERT_EQ(check, static_cast(entt::null)); + + registry.on_construct().connect<&shadow::listener>(check); + + data.emplace_back(static_cast(1u)); + data.emplace_back(entity); + data.emplace_back(value); + + loader.get(archive); + + ASSERT_TRUE(registry.valid(loader.map(entity))); + ASSERT_EQ(check, entity); +} + +TEST(BasicContinuousLoader, Shrink) { + entt::registry registry; + entt::basic_continuous_loader loader{registry}; + + ASSERT_NO_FATAL_FAILURE(loader.shrink()); +} + +TEST(BasicContinuousLoader, Orphans) { + using namespace entt::literals; + using traits_type = entt::entt_traits; + + entt::registry registry; + entt::basic_continuous_loader loader{registry}; + + std::vector data{}; + auto archive = [&data, pos = 0u](auto &value) mutable { value = entt::any_cast>(data[pos++]); }; + const entt::entity entities[2u]{traits_type::construct(0u, 0u), traits_type::construct(2u, 0u)}; + const int value = 42; + + ASSERT_FALSE(registry.valid(entities[0u])); + ASSERT_FALSE(registry.valid(entities[1u])); + + data.emplace_back(static_cast(2u)); + data.emplace_back(static_cast(2u)); + + data.emplace_back(entities[0u]); + data.emplace_back(entities[1u]); + + data.emplace_back(static_cast(1u)); + data.emplace_back(entities[0u]); + data.emplace_back(value); + + loader.get(archive); + loader.get(archive); + + ASSERT_TRUE(loader.contains(entities[0u])); + ASSERT_TRUE(loader.contains(entities[1u])); + + ASSERT_TRUE(registry.valid(loader.map(entities[0u]))); + ASSERT_TRUE(registry.valid(loader.map(entities[1u]))); + + loader.orphans(); + + ASSERT_TRUE(loader.contains(entities[0u])); + ASSERT_TRUE(loader.contains(entities[1u])); + + ASSERT_TRUE(registry.valid(loader.map(entities[0u]))); + ASSERT_FALSE(registry.valid(loader.map(entities[1u]))); +} + template struct output_archive { output_archive(Storage &instance) : storage{instance} {} - template - void operator()(const Value &...value) { - (std::get>(storage).push(value), ...); + template + void operator()(const Value &value) { + std::get>(storage).push(value); } - void operator()(const entt::entity &entity, const noncopyable_component &instance) { - (*this)(entity, instance.value); + void operator()(const std::unique_ptr &instance) { + (*this)(*instance); } private: @@ -48,19 +955,20 @@ struct input_archive { input_archive(Storage &instance) : storage{instance} {} - template - void operator()(Value &...value) { + template + void operator()(Value &value) { auto assign = [this](auto &val) { auto &queue = std::get>>(storage); val = queue.front(); queue.pop(); }; - (assign(value), ...); + assign(value); } - void operator()(entt::entity &entity, noncopyable_component &instance) { - (*this)(entity, instance.value); + void operator()(std::unique_ptr &instance) { + instance = std::make_unique(); + (*this)(*instance); } private: @@ -70,19 +978,19 @@ private: struct a_component {}; struct another_component { - int key; - int value; + int key{}; + int value{}; }; struct what_a_component { - entt::entity bar; - std::vector quux; + entt::entity bar{}; + std::vector quux{}; }; struct map_component { - std::map keys; - std::map values; - std::map both; + std::map keys{}; + std::map values{}; + std::map both{}; }; TEST(Snapshot, Dump) { @@ -107,7 +1015,7 @@ TEST(Snapshot, Dump) { registry.destroy(e1); auto v1 = registry.current(e1); - using storage_type = std::tuple< + using archive_type = std::tuple< std::queue, std::queue, std::queue, @@ -116,11 +1024,18 @@ TEST(Snapshot, Dump) { std::queue, std::queue>; - storage_type storage; - output_archive output{storage}; - input_archive input{storage}; + archive_type storage; + output_archive output{storage}; + input_archive input{storage}; + + entt::snapshot{registry} + .entities(output) + .component(output) + .component(output) + .component(output) + .component(output) + .component(output); - entt::snapshot{registry}.entities(output).component(output); registry.clear(); ASSERT_FALSE(registry.valid(e0)); @@ -128,7 +1043,14 @@ TEST(Snapshot, Dump) { ASSERT_FALSE(registry.valid(e2)); ASSERT_FALSE(registry.valid(e3)); - entt::snapshot_loader{registry}.entities(input).component(input).orphans(); + entt::snapshot_loader{registry} + .entities(input) + .component(input) + .component(input) + .component(input) + .component(input) + .component(input) + .orphans(); ASSERT_TRUE(registry.valid(e0)); ASSERT_FALSE(registry.valid(e1)); @@ -171,18 +1093,22 @@ TEST(Snapshot, Partial) { registry.destroy(e1); auto v1 = registry.current(e1); - using storage_type = std::tuple< + using archive_type = std::tuple< std::queue, std::queue, std::queue, std::queue, std::queue>; - storage_type storage; - output_archive output{storage}; - input_archive input{storage}; + archive_type storage; + output_archive output{storage}; + input_archive input{storage}; + + entt::snapshot{registry} + .entities(output) + .component(output) + .component(output); - entt::snapshot{registry}.entities(output).component(output); registry.clear(); ASSERT_FALSE(registry.valid(e0)); @@ -190,7 +1116,10 @@ TEST(Snapshot, Partial) { ASSERT_FALSE(registry.valid(e2)); ASSERT_FALSE(registry.valid(e3)); - entt::snapshot_loader{registry}.entities(input).component(input); + entt::snapshot_loader{registry} + .entities(input) + .component(input) + .component(input); ASSERT_TRUE(registry.valid(e0)); ASSERT_FALSE(registry.valid(e1)); @@ -204,7 +1133,9 @@ TEST(Snapshot, Partial) { ASSERT_EQ(registry.get(e2), 3); ASSERT_EQ(registry.get(e3), '0'); - entt::snapshot{registry}.entities(output); + entt::snapshot{registry} + .entities(output); + registry.clear(); ASSERT_FALSE(registry.valid(e0)); @@ -212,7 +1143,9 @@ TEST(Snapshot, Partial) { ASSERT_FALSE(registry.valid(e2)); ASSERT_FALSE(registry.valid(e3)); - entt::snapshot_loader{registry}.entities(input).orphans(); + entt::snapshot_loader{registry} + .entities(input) + .orphans(); ASSERT_FALSE(registry.valid(e0)); ASSERT_FALSE(registry.valid(e1)); @@ -227,32 +1160,38 @@ TEST(Snapshot, Iterator) { for(auto i = 0; i < 50; ++i) { const auto entity = registry.create(); - registry.emplace(entity, i, i); - registry.emplace(entity, i); + registry.emplace(entity); if(i % 2) { - registry.emplace(entity); + registry.emplace(entity, i, i); + registry.emplace>(entity, std::make_unique(i)); } } - using storage_type = std::tuple< + using archive_type = std::tuple< std::queue, std::queue, std::queue, std::queue>; - storage_type storage; - output_archive output{storage}; - input_archive input{storage}; + archive_type storage; + output_archive output{storage}; + input_archive input{storage}; const auto view = registry.view(); const auto size = view.size(); - entt::snapshot{registry}.component(output, view.begin(), view.end()); - registry.clear(); - entt::snapshot_loader{registry}.component(input); + entt::snapshot{registry} + .component(output, view.begin(), view.end()) + .component>(output, view.begin(), view.end()); - ASSERT_EQ(registry.view().size(), size); + registry.clear(); + + entt::snapshot_loader{registry} + .component(input) + .component>(input); + + ASSERT_EQ(registry.view().size(), size / 2u); registry.view().each([](const auto entity, const auto &) { ASSERT_NE(entt::to_integral(entity) % 2u, 0u); @@ -270,7 +1209,7 @@ TEST(Snapshot, Continuous) { std::vector entities; entt::entity entity; - using storage_type = std::tuple< + using archive_type = std::tuple< std::queue, std::queue, std::queue, @@ -279,9 +1218,9 @@ TEST(Snapshot, Continuous) { std::queue, std::queue>; - storage_type storage; - output_archive output{storage}; - input_archive input{storage}; + archive_type storage; + output_archive output{storage}; + input_archive input{storage}; for(int i = 0; i < 10; ++i) { static_cast(src.create()); @@ -295,7 +1234,7 @@ TEST(Snapshot, Continuous) { src.emplace(entity); src.emplace(entity, i, i); - src.emplace(entity, i); + src.emplace>(entity, std::make_unique(i)); if(i % 2) { src.emplace(entity, entity); @@ -319,25 +1258,30 @@ TEST(Snapshot, Continuous) { entity = dst.create(); dst.emplace(entity); dst.emplace(entity, -1, -1); - dst.emplace(entity, -1); + dst.emplace>(entity, std::make_unique(-1)); - entt::snapshot{src}.entities(output).component(output); + entt::snapshot{src} + .entities(output) + .component(output) + .component(output) + .component(output) + .component(output) + .component>(output); - loader.entities(input) - .component( - input, - &what_a_component::bar, - &what_a_component::quux, - &map_component::keys, - &map_component::values, - &map_component::both) + loader + .entities(input) + .component(input) + .component(input) + .component(input, &what_a_component::bar, &what_a_component::quux) + .component(input, &map_component::keys, &map_component::values, &map_component::both) + .component>(input) .orphans(); decltype(dst.size()) a_component_cnt{}; decltype(dst.size()) another_component_cnt{}; decltype(dst.size()) what_a_component_cnt{}; decltype(dst.size()) map_component_cnt{}; - decltype(dst.size()) noncopyable_component_cnt{}; + decltype(dst.size()) unique_ptr_cnt{}; dst.each([&dst, &a_component_cnt](auto entt) { ASSERT_TRUE(dst.all_of(entt)); @@ -376,9 +1320,9 @@ TEST(Snapshot, Continuous) { ++map_component_cnt; }); - dst.view().each([&dst, &noncopyable_component_cnt](auto, const auto &component) { - ++noncopyable_component_cnt; - ASSERT_EQ(component.value, static_cast(dst.storage().size() - noncopyable_component_cnt - 1u)); + dst.view>().each([&dst, &unique_ptr_cnt](auto, const auto &component) { + ++unique_ptr_cnt; + ASSERT_EQ(*component, static_cast(dst.storage>().size() - unique_ptr_cnt - 1u)); }); src.view().each([](auto, auto &component) { @@ -387,16 +1331,19 @@ TEST(Snapshot, Continuous) { auto size = dst.size(); - entt::snapshot{src}.entities(output).component(output); + entt::snapshot{src} + .entities(output) + .component(output) + .component(output) + .component(output) + .component(output); - loader.entities(input) - .component( - input, - &what_a_component::bar, - &what_a_component::quux, - &map_component::keys, - &map_component::values, - &map_component::both) + loader + .entities(input) + .component(input) + .component(input, &what_a_component::bar, &what_a_component::quux) + .component(input, &map_component::keys, &map_component::values, &map_component::both) + .component(input) .orphans(); ASSERT_EQ(size, dst.size()); @@ -405,7 +1352,7 @@ TEST(Snapshot, Continuous) { ASSERT_EQ(dst.storage().size(), another_component_cnt); ASSERT_EQ(dst.storage().size(), what_a_component_cnt); ASSERT_EQ(dst.storage().size(), map_component_cnt); - ASSERT_EQ(dst.storage().size(), noncopyable_component_cnt); + ASSERT_EQ(dst.storage>().size(), unique_ptr_cnt); dst.view().each([](auto, auto &component) { ASSERT_EQ(component.value, component.key < 0 ? -1 : (2 * component.key)); @@ -417,16 +1364,19 @@ TEST(Snapshot, Continuous) { component.bar = entity; }); - entt::snapshot{src}.entities(output).component(output); + entt::snapshot{src} + .entities(output) + .component(output) + .component(output) + .component(output) + .component(output); - loader.entities(input) - .component( - input, - &what_a_component::bar, - &what_a_component::quux, - &map_component::keys, - &map_component::values, - &map_component::both) + loader + .entities(input) + .component(input) + .component(input, &what_a_component::bar, &what_a_component::quux) + .component(input, &map_component::keys, &map_component::values, &map_component::both) + .component(input) .orphans(); dst.view().each([&loader, entity](auto, auto &component) { @@ -441,16 +1391,19 @@ TEST(Snapshot, Continuous) { src.destroy(entity); loader.shrink(); - entt::snapshot{src}.entities(output).component(output); + entt::snapshot{src} + .entities(output) + .component(output) + .component(output) + .component(output) + .component(output); - loader.entities(input) - .component( - input, - &what_a_component::bar, - &what_a_component::quux, - &map_component::keys, - &map_component::values, - &map_component::both) + loader + .entities(input) + .component(input) + .component(input) + .component(input, &what_a_component::bar, &what_a_component::quux) + .component(input, &map_component::keys, &map_component::values, &map_component::both) .orphans() .shrink(); @@ -469,16 +1422,19 @@ TEST(Snapshot, Continuous) { dst.clear(); a_component_cnt = src.storage().size(); - entt::snapshot{src}.entities(output).component(output); + entt::snapshot{src} + .entities(output) + .component(output) + .component(output) + .component(output) + .component(output); - loader.entities(input) - .component( - input, - &what_a_component::bar, - &what_a_component::quux, - &map_component::keys, - &map_component::values, - &map_component::both) + loader + .entities(input) + .component(input) + .component(input, &what_a_component::bar, &what_a_component::quux) + .component(input, &map_component::keys, &map_component::values, &map_component::both) + .component(input) .orphans(); ASSERT_EQ(dst.storage().size(), a_component_cnt); @@ -486,48 +1442,24 @@ TEST(Snapshot, Continuous) { src.clear(); a_component_cnt = {}; - entt::snapshot{src}.entities(output).component(output); + entt::snapshot{src} + .entities(output) + .component(output) + .component(output) + .component(output) + .component(output); - loader.entities(input) - .component( - input, - &what_a_component::bar, - &what_a_component::quux, - &map_component::keys, - &map_component::values, - &map_component::both) + loader + .entities(input) + .component(input, &what_a_component::bar, &what_a_component::quux) + .component(input, &map_component::keys, &map_component::values, &map_component::both) + .component(input) + .component(input) .orphans(); ASSERT_EQ(dst.storage().size(), a_component_cnt); } -TEST(Snapshot, MoreOnShrink) { - using traits_type = entt::entt_traits; - - entt::registry src; - entt::registry dst; - - entt::continuous_loader loader{dst}; - - using storage_type = std::tuple< - std::queue, - std::queue>; - - storage_type storage; - output_archive output{storage}; - input_archive input{storage}; - - auto entity = src.create(); - entt::snapshot{src}.entities(output); - loader.entities(input).shrink(); - - ASSERT_TRUE(dst.valid(entity)); - - loader.shrink(); - - ASSERT_FALSE(dst.valid(entity)); -} - TEST(Snapshot, SyncDataMembers) { using traits_type = entt::entt_traits; @@ -536,15 +1468,15 @@ TEST(Snapshot, SyncDataMembers) { entt::continuous_loader loader{dst}; - using storage_type = std::tuple< + using archive_type = std::tuple< std::queue, std::queue, std::queue, std::queue>; - storage_type storage; - output_archive output{storage}; - input_archive input{storage}; + archive_type storage; + output_archive output{storage}; + input_archive input{storage}; static_cast(src.create()); static_cast(src.create()); @@ -563,15 +1495,15 @@ TEST(Snapshot, SyncDataMembers) { decltype(map_component::values){{{10, child}}}, decltype(map_component::both){{{child, child}}}); - entt::snapshot{src}.entities(output).component(output); + entt::snapshot{src} + .entities(output) + .component(output) + .component(output); - loader.entities(input).component( - input, - &what_a_component::bar, - &what_a_component::quux, - &map_component::keys, - &map_component::values, - &map_component::both); + loader + .entities(input) + .component(input, &what_a_component::bar, &what_a_component::quux) + .component(input, &map_component::keys, &map_component::values, &map_component::both); ASSERT_FALSE(dst.valid(parent)); ASSERT_FALSE(dst.valid(child)); @@ -586,8 +1518,8 @@ TEST(Snapshot, SyncDataMembers) { ASSERT_EQ(component.bar, loader.map(parent)); ASSERT_EQ(component.quux[0], loader.map(child)); - const auto &foobar = dst.get(loader.map(child)); - ASSERT_EQ(foobar.keys.at(loader.map(child)), 10); - ASSERT_EQ(foobar.values.at(10), loader.map(child)); - ASSERT_EQ(foobar.both.at(loader.map(child)), loader.map(child)); + const auto &elem = dst.get(loader.map(child)); + ASSERT_EQ(elem.keys.at(loader.map(child)), 10); + ASSERT_EQ(elem.values.at(10), loader.map(child)); + ASSERT_EQ(elem.both.at(loader.map(child)), loader.map(child)); } diff --git a/test/entt/entity/sparse_set.cpp b/test/entt/entity/sparse_set.cpp index 3e526240..456356db 100644 --- a/test/entt/entity/sparse_set.cpp +++ b/test/entt/entity/sparse_set.cpp @@ -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::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::null)); ASSERT_EQ(set.at(1u), static_cast(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); ASSERT_TRUE(std::is_move_assignable_v); @@ -195,7 +199,7 @@ TEST(SparseSet, Move) { ASSERT_EQ(other.at(0u), static_cast(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::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::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::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::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::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::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; @@ -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); 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); 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::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> other{std::move(set), allocator}; @@ -1389,11 +1460,11 @@ TEST(SparseSet, ThrowingAllocator) { test::throwing_allocator::trigger_on_allocate = true; - ASSERT_THROW(set.emplace(entt::entity{0}), test::throwing_allocator::exception_type); + ASSERT_THROW(set.push(entt::entity{0}), test::throwing_allocator::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::trigger_on_allocate = true; ASSERT_THROW(set.reserve(2u), test::throwing_allocator::exception_type); @@ -1403,7 +1474,7 @@ TEST(SparseSet, ThrowingAllocator) { test::throwing_allocator::trigger_on_allocate = true; - ASSERT_THROW(set.emplace(entt::entity{1}), test::throwing_allocator::exception_type); + ASSERT_THROW(set.push(entt::entity{1}), test::throwing_allocator::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::trigger_after_allocate = true; - ASSERT_THROW(set.insert(std::begin(entities), std::end(entities)), test::throwing_allocator::exception_type); + ASSERT_THROW(set.push(std::begin(entities), std::end(entities)), test::throwing_allocator::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})); } diff --git a/test/entt/entity/storage.cpp b/test/entt/entity/storage.cpp index 0152d53b..b7001682 100644 --- a/test/entt/entity/storage.cpp +++ b/test/entt/entity/storage.cpp @@ -31,6 +31,15 @@ struct stable_type { int value; }; +struct aggregate_tracking_type { + ~aggregate_tracking_type() { + ++counter; + } + + static inline int counter; + int value; +}; + struct non_default_constructible { non_default_constructible() = delete; @@ -74,9 +83,6 @@ struct create_from_constructor { } } - create_from_constructor(create_from_constructor &&other) noexcept = default; - create_from_constructor &operator=(create_from_constructor &&other) noexcept = default; - entt::entity child; }; @@ -90,9 +96,17 @@ inline bool operator==(const boxed_int &lhs, const boxed_int &rhs) { return lhs.value == rhs.value; } -TEST(Storage, Functionalities) { +struct Storage: ::testing::Test { + void SetUp() override { + aggregate_tracking_type::counter = 0; + } +}; + +using StorageDeathTest = Storage; + +TEST_F(Storage, Functionalities) { entt::storage pool; - constexpr auto page_size = entt::component_traits::page_size; + constexpr auto page_size = decltype(pool)::traits_type::page_size; ASSERT_NO_FATAL_FAILURE([[maybe_unused]] auto alloc = pool.get_allocator()); ASSERT_EQ(pool.type(), entt::type_id()); @@ -158,7 +172,7 @@ TEST(Storage, Functionalities) { ASSERT_EQ(pool.capacity(), 0u); } -TEST(Storage, Move) { +TEST_F(Storage, Move) { entt::storage pool; pool.emplace(entt::entity{3}, 3); @@ -194,7 +208,7 @@ TEST(Storage, Move) { ASSERT_EQ(other.get(entt::entity{3}), 3); } -TEST(Storage, Swap) { +TEST_F(Storage, Swap) { entt::storage pool; entt::storage other; @@ -222,7 +236,7 @@ TEST(Storage, Swap) { ASSERT_EQ(other.get(entt::entity{42}), 41); } -TEST(Storage, StableSwap) { +TEST_F(Storage, StableSwap) { entt::storage pool; entt::storage other; @@ -250,7 +264,7 @@ TEST(Storage, StableSwap) { ASSERT_EQ(other.get(entt::entity{42}).value, 41); } -TEST(Storage, VoidType) { +TEST_F(Storage, VoidType) { entt::storage pool; pool.emplace(entt::entity{99}); @@ -268,7 +282,7 @@ TEST(Storage, VoidType) { ASSERT_FALSE(other.contains(entt::entity{99})); } -TEST(Storage, EmptyType) { +TEST_F(Storage, EmptyType) { entt::storage pool; pool.emplace(entt::entity{99}); @@ -285,12 +299,73 @@ TEST(Storage, EmptyType) { ASSERT_TRUE(pool.contains(entt::entity{99})); ASSERT_FALSE(other.contains(entt::entity{99})); + + ASSERT_NO_THROW(pool.get(entt::entity{99})); + ASSERT_EQ(pool.get_as_tuple(entt::entity{99}), std::tuple<>{}); } -TEST(Storage, Insert) { +ENTT_DEBUG_TEST_F(StorageDeathTest, EmptyType) { + entt::storage 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}), ""); +} + +TEST_F(Storage, Patch) { + entt::storage pool; + entt::entity entity{42}; + auto callback = [](int &value) { ++value; }; + + pool.emplace(entity, 0); + + ASSERT_EQ(pool.get(entity), 0); + + pool.patch(entity); + pool.patch(entity, callback); + pool.patch(entity, callback, callback); + + ASSERT_EQ(pool.get(entity), 3); +} + +ENTT_DEBUG_TEST_F(StorageDeathTest, Patch) { + entt::storage pool; + + ASSERT_DEATH(pool.patch(entt::null), ""); +} + +TEST_F(Storage, PatchEmptyType) { + entt::storage pool; + entt::entity entity{42}; + + int counter = 0; + auto callback = [&counter]() { ++counter; }; + + pool.emplace(entity); + + ASSERT_EQ(counter, 0); + + pool.patch(entity); + pool.patch(entity, callback); + pool.patch(entity, callback, callback); + + ASSERT_EQ(counter, 3); +} + +ENTT_DEBUG_TEST_F(StorageDeathTest, PatchEmptyType) { + entt::storage pool; + + ASSERT_DEATH(pool.patch(entt::null), ""); +} + +TEST_F(Storage, Insert) { entt::storage pool; entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; - pool.insert(std::begin(entities), std::end(entities), stable_type{99}); + entt::storage::iterator it{}; + + it = pool.insert(std::begin(entities), std::end(entities), stable_type{99}); + + ASSERT_EQ(it, pool.cbegin()); ASSERT_TRUE(pool.contains(entities[0u])); ASSERT_TRUE(pool.contains(entities[1u])); @@ -299,10 +374,14 @@ TEST(Storage, Insert) { ASSERT_EQ(pool.size(), 2u); ASSERT_EQ(pool.get(entities[0u]).value, 99); ASSERT_EQ(pool.get(entities[1u]).value, 99); + ASSERT_EQ(it++->value, 99); + ASSERT_EQ(it->value, 99); pool.erase(std::begin(entities), std::end(entities)); const stable_type values[2u] = {stable_type{42}, stable_type{3}}; - pool.insert(std::rbegin(entities), std::rend(entities), std::begin(values)); + it = pool.insert(std::rbegin(entities), std::rend(entities), std::begin(values)); + + ASSERT_EQ(it, pool.cbegin()); ASSERT_EQ(pool.size(), 4u); ASSERT_EQ(pool.at(2u), entities[1u]); @@ -311,9 +390,11 @@ TEST(Storage, Insert) { ASSERT_EQ(pool.index(entities[1u]), 2u); ASSERT_EQ(pool.get(entities[0u]).value, 3); ASSERT_EQ(pool.get(entities[1u]).value, 42); + ASSERT_EQ(it++->value, 3); + ASSERT_EQ(it->value, 42); } -TEST(Storage, InsertEmptyType) { +TEST_F(Storage, InsertEmptyType) { entt::storage pool; entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; @@ -336,7 +417,7 @@ TEST(Storage, InsertEmptyType) { ASSERT_EQ(pool.index(entities[1u]), 2u); } -TEST(Storage, Erase) { +TEST_F(Storage, Erase) { entt::storage pool; entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, entt::entity{9}}; @@ -369,14 +450,14 @@ TEST(Storage, Erase) { ASSERT_EQ(*pool.begin(), 1); } -TEST(Storage, CrossErase) { +TEST_F(Storage, CrossErase) { entt::sparse_set set; entt::storage pool; entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; pool.emplace(entities[0u], 3); pool.emplace(entities[1u], 42); - set.emplace(entities[1u]); + set.push(entities[1u]); pool.erase(set.begin(), set.end()); ASSERT_TRUE(pool.contains(entities[0u])); @@ -384,7 +465,7 @@ TEST(Storage, CrossErase) { ASSERT_EQ(pool.raw()[0u][0u], 3); } -TEST(Storage, StableErase) { +TEST_F(Storage, StableErase) { entt::storage pool; entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, entt::entity{9}}; @@ -478,14 +559,14 @@ TEST(Storage, StableErase) { ASSERT_EQ(pool.get(entities[2u]).value, 1); } -TEST(Storage, CrossStableErase) { +TEST_F(Storage, CrossStableErase) { entt::sparse_set set; entt::storage pool; entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; pool.emplace(entities[0u], 3); pool.emplace(entities[1u], 42); - set.emplace(entities[1u]); + set.push(entities[1u]); pool.erase(set.begin(), set.end()); ASSERT_TRUE(pool.contains(entities[0u])); @@ -493,7 +574,7 @@ TEST(Storage, CrossStableErase) { ASSERT_EQ(pool.raw()[0u][0u].value, 3); } -TEST(Storage, Remove) { +TEST_F(Storage, Remove) { entt::storage pool; entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, entt::entity{9}}; @@ -513,8 +594,8 @@ TEST(Storage, Remove) { ASSERT_FALSE(pool.empty()); ASSERT_EQ(*pool.begin(), 2); - ASSERT_EQ(pool.remove(entities[2u]), 1u); - ASSERT_EQ(pool.remove(entities[2u]), 0u); + ASSERT_TRUE(pool.remove(entities[2u])); + ASSERT_FALSE(pool.remove(entities[2u])); ASSERT_TRUE(pool.empty()); pool.emplace(entities[0u], 0); @@ -527,14 +608,14 @@ TEST(Storage, Remove) { ASSERT_EQ(*pool.begin(), 1); } -TEST(Storage, CrossRemove) { +TEST_F(Storage, CrossRemove) { entt::sparse_set set; entt::storage pool; entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; pool.emplace(entities[0u], 3); pool.emplace(entities[1u], 42); - set.emplace(entities[1u]); + set.push(entities[1u]); pool.remove(set.begin(), set.end()); ASSERT_TRUE(pool.contains(entities[0u])); @@ -542,7 +623,7 @@ TEST(Storage, CrossRemove) { ASSERT_EQ(pool.raw()[0u][0u], 3); } -TEST(Storage, StableRemove) { +TEST_F(Storage, StableRemove) { entt::storage pool; entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, entt::entity{9}}; @@ -575,9 +656,9 @@ TEST(Storage, StableRemove) { ASSERT_EQ(pool.begin()->value, 2); ASSERT_EQ(pool.index(entities[2u]), 2u); - ASSERT_EQ(pool.remove(entities[2u]), 1u); - ASSERT_EQ(pool.remove(entities[2u]), 0u); - ASSERT_EQ(pool.remove(entities[2u]), 0u); + ASSERT_TRUE(pool.remove(entities[2u])); + ASSERT_FALSE(pool.remove(entities[2u])); + ASSERT_FALSE(pool.remove(entities[2u])); ASSERT_FALSE(pool.empty()); ASSERT_EQ(pool.size(), 3u); ASSERT_FALSE(pool.contains(entities[0u])); @@ -610,11 +691,11 @@ TEST(Storage, StableRemove) { pool.emplace(entities[1u], stable_type{2}); pool.emplace(entities[2u], stable_type{1}); - ASSERT_EQ(pool.remove(entities[2u]), 1u); - ASSERT_EQ(pool.remove(entities[2u]), 0u); + ASSERT_TRUE(pool.remove(entities[2u])); + ASSERT_FALSE(pool.remove(entities[2u])); - ASSERT_EQ(pool.remove(entities[0u]), 1u); - ASSERT_EQ(pool.remove(entities[1u]), 1u); + ASSERT_TRUE(pool.remove(entities[0u])); + ASSERT_TRUE(pool.remove(entities[1u])); ASSERT_EQ(pool.remove(entities, entities + 2u), 0u); ASSERT_EQ(pool.size(), 3u); @@ -639,14 +720,14 @@ TEST(Storage, StableRemove) { ASSERT_EQ(pool.get(entities[2u]).value, 1); } -TEST(Storage, CrossStableRemove) { +TEST_F(Storage, CrossStableRemove) { entt::sparse_set set; entt::storage pool; entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; pool.emplace(entities[0u], 3); pool.emplace(entities[1u], 42); - set.emplace(entities[1u]); + set.push(entities[1u]); pool.remove(set.begin(), set.end()); ASSERT_TRUE(pool.contains(entities[0u])); @@ -654,7 +735,7 @@ TEST(Storage, CrossStableRemove) { ASSERT_EQ(pool.raw()[0u][0u].value, 3); } -TEST(Storage, TypeFromBase) { +TEST_F(Storage, TypeFromBase) { entt::storage pool; entt::sparse_set &base = pool; entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; @@ -667,16 +748,16 @@ TEST(Storage, TypeFromBase) { int instance = 42; - ASSERT_NE(base.emplace(entities[0u], &instance), base.end()); + ASSERT_NE(base.push(entities[0u], &instance), base.end()); ASSERT_TRUE(pool.contains(entities[0u])); ASSERT_FALSE(pool.contains(entities[1u])); - ASSERT_EQ(base.get(entities[0u]), &pool.get(entities[0u])); + ASSERT_EQ(base.value(entities[0u]), &pool.get(entities[0u])); ASSERT_EQ(pool.get(entities[0u]), 42); base.erase(entities[0u]); - 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])); @@ -688,7 +769,7 @@ TEST(Storage, TypeFromBase) { ASSERT_TRUE(pool.empty()); } -TEST(Storage, EmptyTypeFromBase) { +TEST_F(Storage, EmptyTypeFromBase) { entt::storage pool; entt::sparse_set &base = pool; entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; @@ -701,17 +782,17 @@ TEST(Storage, EmptyTypeFromBase) { empty_stable_type instance{}; - ASSERT_NE(base.emplace(entities[0u], &instance), base.end()); + ASSERT_NE(base.push(entities[0u], &instance), base.end()); ASSERT_EQ(pool.size(), 1u); ASSERT_TRUE(pool.contains(entities[0u])); ASSERT_FALSE(pool.contains(entities[1u])); - ASSERT_EQ(base.get(entities[0u]), nullptr); + ASSERT_EQ(base.value(entities[0u]), nullptr); ASSERT_EQ(base.index(entities[0u]), 0u); base.erase(entities[0u]); - 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.size(), 3u); ASSERT_TRUE(pool.contains(entities[0u])); @@ -721,7 +802,7 @@ TEST(Storage, EmptyTypeFromBase) { base.erase(std::begin(entities), std::end(entities)); - ASSERT_NE(base.insert(std::rbegin(entities), std::rend(entities)), base.end()); + ASSERT_NE(base.push(std::rbegin(entities), std::rend(entities)), base.end()); ASSERT_EQ(pool.size(), 5u); ASSERT_TRUE(pool.contains(entities[0u])); @@ -739,7 +820,7 @@ TEST(Storage, EmptyTypeFromBase) { } } -TEST(Storage, NonDefaultConstructibleTypeFromBase) { +TEST_F(Storage, NonDefaultConstructibleTypeFromBase) { entt::storage pool; entt::sparse_set &base = pool; entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; @@ -750,7 +831,7 @@ TEST(Storage, NonDefaultConstructibleTypeFromBase) { ASSERT_FALSE(pool.contains(entities[0u])); ASSERT_FALSE(pool.contains(entities[1u])); - ASSERT_EQ(base.emplace(entities[0u]), base.end()); + ASSERT_EQ(base.push(entities[0u]), base.end()); ASSERT_FALSE(pool.contains(entities[0u])); ASSERT_FALSE(pool.contains(entities[1u])); @@ -759,7 +840,7 @@ TEST(Storage, NonDefaultConstructibleTypeFromBase) { non_default_constructible instance{3}; - ASSERT_NE(base.emplace(entities[0u], &instance), base.end()); + ASSERT_NE(base.push(entities[0u], &instance), base.end()); ASSERT_TRUE(pool.contains(entities[0u])); ASSERT_FALSE(pool.contains(entities[1u])); @@ -769,7 +850,7 @@ TEST(Storage, NonDefaultConstructibleTypeFromBase) { ASSERT_TRUE(pool.empty()); ASSERT_FALSE(pool.contains(entities[0u])); - 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])); @@ -778,7 +859,7 @@ TEST(Storage, NonDefaultConstructibleTypeFromBase) { ASSERT_TRUE(pool.empty()); } -TEST(Storage, NonCopyConstructibleTypeFromBase) { +TEST_F(Storage, NonCopyConstructibleTypeFromBase) { entt::storage> pool; entt::sparse_set &base = pool; entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; @@ -789,7 +870,7 @@ TEST(Storage, NonCopyConstructibleTypeFromBase) { ASSERT_FALSE(pool.contains(entities[0u])); ASSERT_FALSE(pool.contains(entities[1u])); - ASSERT_NE(base.emplace(entities[0u]), base.end()); + ASSERT_NE(base.push(entities[0u]), base.end()); ASSERT_TRUE(pool.contains(entities[0u])); ASSERT_FALSE(pool.contains(entities[1u])); @@ -798,7 +879,7 @@ TEST(Storage, NonCopyConstructibleTypeFromBase) { std::unique_ptr instance = std::make_unique(3); - ASSERT_EQ(base.emplace(entities[1u], &instance), base.end()); + ASSERT_EQ(base.push(entities[1u], &instance), base.end()); ASSERT_TRUE(pool.contains(entities[0u])); ASSERT_FALSE(pool.contains(entities[1u])); @@ -808,7 +889,7 @@ TEST(Storage, NonCopyConstructibleTypeFromBase) { ASSERT_TRUE(pool.empty()); ASSERT_FALSE(pool.contains(entities[0u])); - 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])); @@ -817,7 +898,7 @@ TEST(Storage, NonCopyConstructibleTypeFromBase) { ASSERT_FALSE(pool.empty()); } -TEST(Storage, Compact) { +TEST_F(Storage, Compact) { entt::storage pool; ASSERT_TRUE(pool.empty()); @@ -863,9 +944,9 @@ TEST(Storage, Compact) { ASSERT_TRUE(pool.empty()); } -TEST(Storage, ShrinkToFit) { +TEST_F(Storage, ShrinkToFit) { entt::storage pool; - constexpr auto page_size = entt::component_traits::page_size; + constexpr auto page_size = decltype(pool)::traits_type::page_size; for(std::size_t next{}; next < page_size; ++next) { pool.emplace(entt::entity(next)); @@ -893,16 +974,22 @@ TEST(Storage, ShrinkToFit) { ASSERT_EQ(pool.size(), 0u); } -TEST(Storage, AggregatesMustWork) { - struct aggregate_type { - int value; - }; +TEST_F(Storage, AggregatesMustWork) { + static_assert(std::is_aggregate_v); + entt::storage storage{}; - // the goal of this test is to enforce the requirements for aggregate types - entt::storage{}.emplace(entt::entity{0}, 42); + // aggregate types with no args enter the non-aggregate path + storage.emplace(entt::entity{0}); + + ASSERT_EQ(aggregate_tracking_type::counter, 0); + + // aggregate types with args work despite the lack of support in the standard library + storage.emplace(entt::entity{1}, 42); + + ASSERT_EQ(aggregate_tracking_type::counter, 1); } -TEST(Storage, SelfMoveSupport) { +TEST_F(Storage, SelfMoveSupport) { // see #37 - this test shouldn't crash, that's all entt::storage> pool; entt::entity entity{}; @@ -915,7 +1002,7 @@ TEST(Storage, SelfMoveSupport) { ASSERT_FALSE(pool.contains(entity)); } -TEST(Storage, SelfMoveSupportInPlaceDelete) { +TEST_F(Storage, SelfMoveSupportInPlaceDelete) { // see #37 - this test shouldn't crash, that's all entt::storage> pool; entt::entity entity{}; @@ -928,7 +1015,7 @@ TEST(Storage, SelfMoveSupportInPlaceDelete) { ASSERT_FALSE(pool.contains(entity)); } -TEST(Storage, Iterator) { +TEST_F(Storage, Iterator) { using iterator = typename entt::storage::iterator; static_assert(std::is_same_v); @@ -989,7 +1076,7 @@ TEST(Storage, Iterator) { ASSERT_EQ(begin[1u], boxed_int{42}); } -TEST(Storage, ConstIterator) { +TEST_F(Storage, ConstIterator) { using iterator = typename entt::storage::const_iterator; static_assert(std::is_same_v); @@ -1052,7 +1139,7 @@ TEST(Storage, ConstIterator) { ASSERT_EQ(cbegin[1u], boxed_int{42}); } -TEST(Storage, ReverseIterator) { +TEST_F(Storage, ReverseIterator) { using reverse_iterator = typename entt::storage::reverse_iterator; static_assert(std::is_same_v); @@ -1113,7 +1200,7 @@ TEST(Storage, ReverseIterator) { ASSERT_EQ(begin[1u], boxed_int{3}); } -TEST(Storage, ConstReverseIterator) { +TEST_F(Storage, ConstReverseIterator) { using const_reverse_iterator = typename entt::storage::const_reverse_iterator; static_assert(std::is_same_v); @@ -1176,7 +1263,7 @@ TEST(Storage, ConstReverseIterator) { ASSERT_EQ(cbegin[1u], boxed_int{3}); } -TEST(Storage, IteratorConversion) { +TEST_F(Storage, IteratorConversion) { entt::storage pool; pool.emplace(entt::entity{3}, 42); @@ -1199,9 +1286,9 @@ TEST(Storage, IteratorConversion) { ASSERT_NE(++cit, it); } -TEST(Storage, IteratorPageSizeAwareness) { +TEST_F(Storage, IteratorPageSizeAwareness) { entt::storage> pool; - constexpr auto page_size = entt::component_traits>::page_size; + constexpr auto page_size = decltype(pool)::traits_type::page_size; const std::unordered_set check{'c'}; for(unsigned int next{}; next < page_size; ++next) { @@ -1214,15 +1301,16 @@ TEST(Storage, IteratorPageSizeAwareness) { ASSERT_EQ(*pool.begin(), check); } -TEST(Storage, Iterable) { +TEST_F(Storage, Iterable) { using iterator = typename entt::storage::iterable::iterator; - static_assert(std::is_same_v>); static_assert(std::is_same_v>); static_assert(std::is_same_v>>); static_assert(std::is_same_v); entt::storage pool; + entt::sparse_set &base = pool; + pool.emplace(entt::entity{1}, 99); pool.emplace(entt::entity{3}, 42); auto iterable = pool.each(); @@ -1236,13 +1324,18 @@ TEST(Storage, Iterable) { ASSERT_EQ(end, iterable.end()); ASSERT_NE(begin, end); + ASSERT_EQ(begin.base(), base.begin()); + ASSERT_EQ(end.base(), base.end()); + ASSERT_EQ(std::get<0>(*begin.operator->().operator->()), entt::entity{3}); ASSERT_EQ(std::get<1>(*begin.operator->().operator->()), boxed_int{42}); ASSERT_EQ(std::get<0>(*begin), entt::entity{3}); ASSERT_EQ(std::get<1>(*begin), boxed_int{42}); ASSERT_EQ(begin++, iterable.begin()); + ASSERT_EQ(begin.base(), ++base.begin()); ASSERT_EQ(++begin, iterable.end()); + ASSERT_EQ(begin.base(), base.end()); for(auto [entity, element]: iterable) { static_assert(std::is_same_v); @@ -1252,15 +1345,16 @@ TEST(Storage, Iterable) { } } -TEST(Storage, ConstIterable) { +TEST_F(Storage, ConstIterable) { using iterator = typename entt::storage::const_iterable::iterator; - static_assert(std::is_same_v>); static_assert(std::is_same_v>); static_assert(std::is_same_v>>); static_assert(std::is_same_v); entt::storage pool; + entt::sparse_set &base = pool; + pool.emplace(entt::entity{1}, 99); pool.emplace(entt::entity{3}, 42); auto iterable = std::as_const(pool).each(); @@ -1274,13 +1368,18 @@ TEST(Storage, ConstIterable) { ASSERT_EQ(end, iterable.cend()); ASSERT_NE(begin, end); + ASSERT_EQ(begin.base(), base.begin()); + ASSERT_EQ(end.base(), base.end()); + ASSERT_EQ(std::get<0>(*begin.operator->().operator->()), entt::entity{3}); ASSERT_EQ(std::get<1>(*begin.operator->().operator->()), boxed_int{42}); ASSERT_EQ(std::get<0>(*begin), entt::entity{3}); ASSERT_EQ(std::get<1>(*begin), boxed_int{42}); - ASSERT_EQ(begin++, iterable.cbegin()); - ASSERT_EQ(++begin, iterable.cend()); + ASSERT_EQ(begin++, iterable.begin()); + ASSERT_EQ(begin.base(), ++base.begin()); + ASSERT_EQ(++begin, iterable.end()); + ASSERT_EQ(begin.base(), base.end()); for(auto [entity, element]: iterable) { static_assert(std::is_same_v); @@ -1290,7 +1389,7 @@ TEST(Storage, ConstIterable) { } } -TEST(Storage, IterableIteratorConversion) { +TEST_F(Storage, IterableIteratorConversion) { entt::storage pool; pool.emplace(entt::entity{3}, 42); @@ -1304,7 +1403,7 @@ TEST(Storage, IterableIteratorConversion) { ASSERT_NE(++cit, it); } -TEST(Storage, EmptyTypeIterable) { +TEST_F(Storage, EmptyTypeIterable) { using iterator = typename entt::storage::iterable::iterator; static_assert(std::is_same_v>); @@ -1313,6 +1412,8 @@ TEST(Storage, EmptyTypeIterable) { static_assert(std::is_same_v); entt::storage pool; + entt::sparse_set &base = pool; + pool.emplace(entt::entity{1}); pool.emplace(entt::entity{3}); auto iterable = pool.each(); @@ -1326,11 +1427,16 @@ TEST(Storage, EmptyTypeIterable) { ASSERT_EQ(end, iterable.end()); ASSERT_NE(begin, end); + ASSERT_EQ(begin.base(), base.begin()); + ASSERT_EQ(end.base(), base.end()); + ASSERT_EQ(std::get<0>(*begin.operator->().operator->()), entt::entity{3}); ASSERT_EQ(std::get<0>(*begin), entt::entity{3}); ASSERT_EQ(begin++, iterable.begin()); + ASSERT_EQ(begin.base(), ++base.begin()); ASSERT_EQ(++begin, iterable.end()); + ASSERT_EQ(begin.base(), base.end()); for(auto [entity]: iterable) { static_assert(std::is_same_v); @@ -1338,7 +1444,48 @@ TEST(Storage, EmptyTypeIterable) { } } -TEST(Storage, IterableAlgorithmCompatibility) { +TEST_F(Storage, EmptyTypeConstIterable) { + using iterator = typename entt::storage::const_iterable::iterator; + + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); + static_assert(std::is_same_v>>); + static_assert(std::is_same_v); + + entt::storage pool; + entt::sparse_set &base = pool; + + pool.emplace(entt::entity{1}); + pool.emplace(entt::entity{3}); + auto iterable = std::as_const(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_EQ(begin.base(), base.begin()); + ASSERT_EQ(end.base(), base.end()); + + ASSERT_EQ(std::get<0>(*begin.operator->().operator->()), entt::entity{3}); + ASSERT_EQ(std::get<0>(*begin), entt::entity{3}); + + ASSERT_EQ(begin++, iterable.begin()); + ASSERT_EQ(begin.base(), ++base.begin()); + ASSERT_EQ(++begin, iterable.end()); + ASSERT_EQ(begin.base(), base.end()); + + for(auto [entity]: iterable) { + static_assert(std::is_same_v); + ASSERT_TRUE(entity == entt::entity{1} || entity == entt::entity{3}); + } +} + +TEST_F(Storage, IterableAlgorithmCompatibility) { entt::storage pool; pool.emplace(entt::entity{3}, 42); @@ -1348,7 +1495,201 @@ TEST(Storage, IterableAlgorithmCompatibility) { ASSERT_EQ(std::get<0>(*it), entt::entity{3}); } -TEST(Storage, Raw) { +TEST_F(Storage, ReverseIterable) { + using iterator = typename entt::storage::reverse_iterable::iterator; + + static_assert(std::is_same_v>); + static_assert(std::is_same_v>>); + static_assert(std::is_same_v); + + entt::storage pool; + entt::sparse_set &base = pool; + + pool.emplace(entt::entity{1}, 99); + pool.emplace(entt::entity{3}, 42); + 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(), base.rbegin()); + ASSERT_EQ(end.base(), base.rend()); + + ASSERT_EQ(std::get<0>(*begin.operator->().operator->()), entt::entity{1}); + ASSERT_EQ(std::get<1>(*begin.operator->().operator->()), boxed_int{99}); + ASSERT_EQ(std::get<0>(*begin), entt::entity{1}); + ASSERT_EQ(std::get<1>(*begin), boxed_int{99}); + + ASSERT_EQ(begin++, iterable.begin()); + ASSERT_EQ(begin.base(), ++base.rbegin()); + ASSERT_EQ(++begin, iterable.end()); + ASSERT_EQ(begin.base(), base.rend()); + + for(auto [entity, element]: iterable) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + ASSERT_TRUE(entity != entt::entity{1} || element == boxed_int{99}); + ASSERT_TRUE(entity != entt::entity{3} || element == boxed_int{42}); + } +} + +TEST_F(Storage, ConstReverseIterable) { + using iterator = typename entt::storage::const_reverse_iterable::iterator; + + static_assert(std::is_same_v>); + static_assert(std::is_same_v>>); + static_assert(std::is_same_v); + + entt::storage pool; + entt::sparse_set &base = pool; + + pool.emplace(entt::entity{1}, 99); + pool.emplace(entt::entity{3}, 42); + 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(), base.rbegin()); + ASSERT_EQ(end.base(), base.rend()); + + ASSERT_EQ(std::get<0>(*begin.operator->().operator->()), entt::entity{1}); + ASSERT_EQ(std::get<1>(*begin.operator->().operator->()), boxed_int{99}); + ASSERT_EQ(std::get<0>(*begin), entt::entity{1}); + ASSERT_EQ(std::get<1>(*begin), boxed_int{99}); + + ASSERT_EQ(begin++, iterable.begin()); + ASSERT_EQ(begin.base(), ++base.rbegin()); + ASSERT_EQ(++begin, iterable.end()); + ASSERT_EQ(begin.base(), base.rend()); + + for(auto [entity, element]: iterable) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + ASSERT_TRUE(entity != entt::entity{1} || element == boxed_int{99}); + ASSERT_TRUE(entity != entt::entity{3} || element == boxed_int{42}); + } +} + +TEST_F(Storage, ReverseIterableIteratorConversion) { + entt::storage pool; + pool.emplace(entt::entity{3}, 42); + + typename entt::storage::reverse_iterable::iterator it = pool.reach().begin(); + typename entt::storage::const_reverse_iterable::iterator cit = it; + + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); + + ASSERT_EQ(it, cit); + ASSERT_NE(++cit, it); +} + +TEST_F(Storage, EmptyTypeReverseIterable) { + using iterator = typename entt::storage::reverse_iterable::iterator; + + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); + static_assert(std::is_same_v>>); + static_assert(std::is_same_v); + + entt::storage pool; + entt::sparse_set &base = pool; + + pool.emplace(entt::entity{1}); + pool.emplace(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(), base.rbegin()); + ASSERT_EQ(end.base(), base.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(), ++base.rbegin()); + ASSERT_EQ(++begin, iterable.end()); + ASSERT_EQ(begin.base(), base.rend()); + + for(auto [entity]: iterable) { + static_assert(std::is_same_v); + ASSERT_TRUE(entity == entt::entity{1} || entity == entt::entity{3}); + } +} + +TEST_F(Storage, EmptyTypeConstReverseIterable) { + using iterator = typename entt::storage::const_reverse_iterable::iterator; + + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); + static_assert(std::is_same_v>>); + static_assert(std::is_same_v); + + entt::storage pool; + entt::sparse_set &base = pool; + + pool.emplace(entt::entity{1}); + pool.emplace(entt::entity{3}); + auto iterable = std::as_const(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(), base.rbegin()); + ASSERT_EQ(end.base(), base.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(), ++base.rbegin()); + ASSERT_EQ(++begin, iterable.end()); + ASSERT_EQ(begin.base(), base.rend()); + + for(auto [entity]: iterable) { + static_assert(std::is_same_v); + ASSERT_TRUE(entity == entt::entity{1} || entity == entt::entity{3}); + } +} + +TEST_F(Storage, ReverseIterableAlgorithmCompatibility) { + entt::storage pool; + pool.emplace(entt::entity{3}, 42); + + 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_F(Storage, Raw) { entt::storage pool; pool.emplace(entt::entity{3}, 3); @@ -1364,7 +1705,51 @@ TEST(Storage, Raw) { ASSERT_EQ(pool.raw()[0u][2u], 9); } -TEST(Storage, SortOrdered) { +TEST_F(Storage, SwapElements) { + entt::storage pool; + + pool.emplace(entt::entity{3}, 3); + pool.emplace(entt::entity{12}, 6); + pool.emplace(entt::entity{42}, 9); + + pool.erase(entt::entity{12}); + + ASSERT_EQ(pool.get(entt::entity{3}), 3); + ASSERT_EQ(pool.get(entt::entity{42}), 9); + ASSERT_EQ(pool.index(entt::entity{3}), 0u); + ASSERT_EQ(pool.index(entt::entity{42}), 1u); + + pool.swap_elements(entt::entity{3}, entt::entity{42}); + + ASSERT_EQ(pool.get(entt::entity{3}), 3); + ASSERT_EQ(pool.get(entt::entity{42}), 9); + ASSERT_EQ(pool.index(entt::entity{3}), 1u); + ASSERT_EQ(pool.index(entt::entity{42}), 0u); +} + +TEST_F(Storage, StableSwapElements) { + entt::storage pool; + + pool.emplace(entt::entity{3}, 3); + pool.emplace(entt::entity{12}, 6); + pool.emplace(entt::entity{42}, 9); + + pool.erase(entt::entity{12}); + + ASSERT_EQ(pool.get(entt::entity{3}).value, 3); + ASSERT_EQ(pool.get(entt::entity{42}).value, 9); + ASSERT_EQ(pool.index(entt::entity{3}), 0u); + ASSERT_EQ(pool.index(entt::entity{42}), 2u); + + pool.swap_elements(entt::entity{3}, entt::entity{42}); + + ASSERT_EQ(pool.get(entt::entity{3}).value, 3); + ASSERT_EQ(pool.get(entt::entity{42}).value, 9); + ASSERT_EQ(pool.index(entt::entity{3}), 2u); + ASSERT_EQ(pool.index(entt::entity{42}), 0u); +} + +TEST_F(Storage, SortOrdered) { entt::storage pool; entt::entity entities[5u]{entt::entity{12}, entt::entity{42}, entt::entity{7}, entt::entity{3}, entt::entity{9}}; boxed_int values[5u]{{12}, {9}, {6}, {3}, {1}}; @@ -1376,7 +1761,7 @@ TEST(Storage, SortOrdered) { ASSERT_TRUE(std::equal(std::rbegin(values), std::rend(values), pool.begin(), pool.end())); } -TEST(Storage, SortReverse) { +TEST_F(Storage, SortReverse) { entt::storage pool; entt::entity entities[5u]{entt::entity{12}, entt::entity{42}, entt::entity{7}, entt::entity{3}, entt::entity{9}}; boxed_int values[5u]{{1}, {3}, {6}, {9}, {12}}; @@ -1388,7 +1773,7 @@ TEST(Storage, SortReverse) { ASSERT_TRUE(std::equal(std::begin(values), std::end(values), pool.begin(), pool.end())); } -TEST(Storage, SortUnordered) { +TEST_F(Storage, SortUnordered) { entt::storage pool; entt::entity entities[5u]{entt::entity{12}, entt::entity{42}, entt::entity{7}, entt::entity{3}, entt::entity{9}}; boxed_int values[5u]{{6}, {3}, {1}, {9}, {12}}; @@ -1413,7 +1798,7 @@ TEST(Storage, SortUnordered) { ASSERT_EQ(pool.data()[4u], entities[2u]); } -TEST(Storage, SortRange) { +TEST_F(Storage, SortRange) { entt::storage pool; entt::entity entities[5u]{entt::entity{12}, entt::entity{42}, entt::entity{7}, entt::entity{3}, entt::entity{9}}; boxed_int values[5u]{{3}, {6}, {1}, {9}, {12}}; @@ -1453,7 +1838,7 @@ TEST(Storage, SortRange) { ASSERT_EQ(pool.data()[4u], entities[2u]); } -TEST(Storage, RespectDisjoint) { +TEST_F(Storage, RespectDisjoint) { entt::storage lhs; entt::storage rhs; @@ -1464,13 +1849,13 @@ TEST(Storage, RespectDisjoint) { ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.entt::sparse_set::begin(), lhs.entt::sparse_set::end())); ASSERT_TRUE(std::equal(std::rbegin(lhs_values), std::rend(lhs_values), lhs.begin(), lhs.end())); - lhs.respect(rhs); + lhs.sort_as(rhs); ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.entt::sparse_set::begin(), lhs.entt::sparse_set::end())); ASSERT_TRUE(std::equal(std::rbegin(lhs_values), std::rend(lhs_values), lhs.begin(), lhs.end())); } -TEST(Storage, RespectOverlap) { +TEST_F(Storage, RespectOverlap) { entt::storage lhs; entt::storage rhs; @@ -1488,7 +1873,7 @@ TEST(Storage, RespectOverlap) { ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.entt::sparse_set::begin(), rhs.entt::sparse_set::end())); ASSERT_TRUE(std::equal(std::rbegin(rhs_values), std::rend(rhs_values), rhs.begin(), rhs.end())); - lhs.respect(rhs); + lhs.sort_as(rhs); auto begin = lhs.begin(); auto end = lhs.end(); @@ -1503,7 +1888,7 @@ TEST(Storage, RespectOverlap) { ASSERT_EQ(lhs.data()[2u], lhs_entities[1u]); } -TEST(Storage, RespectOrdered) { +TEST_F(Storage, RespectOrdered) { entt::storage lhs; entt::storage rhs; @@ -1521,13 +1906,13 @@ TEST(Storage, RespectOrdered) { ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.entt::sparse_set::begin(), rhs.entt::sparse_set::end())); ASSERT_TRUE(std::equal(std::rbegin(rhs_values), std::rend(rhs_values), rhs.begin(), rhs.end())); - rhs.respect(lhs); + rhs.sort_as(lhs); ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.entt::sparse_set::begin(), rhs.entt::sparse_set::end())); ASSERT_TRUE(std::equal(std::rbegin(rhs_values), std::rend(rhs_values), rhs.begin(), rhs.end())); } -TEST(Storage, RespectReverse) { +TEST_F(Storage, RespectReverse) { entt::storage lhs; entt::storage rhs; @@ -1545,7 +1930,7 @@ TEST(Storage, RespectReverse) { ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.entt::sparse_set::begin(), rhs.entt::sparse_set::end())); ASSERT_TRUE(std::equal(std::rbegin(rhs_values), std::rend(rhs_values), rhs.begin(), rhs.end())); - rhs.respect(lhs); + rhs.sort_as(lhs); auto begin = rhs.begin(); auto end = rhs.end(); @@ -1566,7 +1951,7 @@ TEST(Storage, RespectReverse) { ASSERT_EQ(rhs.data()[5u], rhs_entities[0u]); } -TEST(Storage, RespectUnordered) { +TEST_F(Storage, RespectUnordered) { entt::storage lhs; entt::storage rhs; @@ -1584,7 +1969,7 @@ TEST(Storage, RespectUnordered) { ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.entt::sparse_set::begin(), rhs.entt::sparse_set::end())); ASSERT_TRUE(std::equal(std::rbegin(rhs_values), std::rend(rhs_values), rhs.begin(), rhs.end())); - rhs.respect(lhs); + rhs.sort_as(lhs); auto begin = rhs.begin(); auto end = rhs.end(); @@ -1605,10 +1990,10 @@ TEST(Storage, RespectUnordered) { ASSERT_EQ(rhs.data()[5u], rhs_entities[5u]); } -TEST(Storage, CanModifyDuringIteration) { +TEST_F(Storage, CanModifyDuringIteration) { entt::storage pool; auto *ptr = &pool.emplace(entt::entity{0}, 42); - constexpr auto page_size = entt::component_traits::page_size; + constexpr auto page_size = decltype(pool)::traits_type::page_size; ASSERT_EQ(pool.capacity(), page_size); @@ -1622,7 +2007,7 @@ TEST(Storage, CanModifyDuringIteration) { [[maybe_unused]] const int &value = *it; } -TEST(Storage, ReferencesGuaranteed) { +TEST_F(Storage, ReferencesGuaranteed) { entt::storage pool; pool.emplace(entt::entity{0}, 0); @@ -1650,17 +2035,17 @@ TEST(Storage, ReferencesGuaranteed) { ASSERT_EQ(pool.get(entt::entity{1}).value, 3); } -TEST(Storage, MoveOnlyComponent) { +TEST_F(Storage, MoveOnlyComponent) { // the purpose is to ensure that move only components are always accepted [[maybe_unused]] entt::storage> pool; } -TEST(Storage, PinnedComponent) { +TEST_F(Storage, PinnedComponent) { // the purpose is to ensure that non-movable components are always accepted [[maybe_unused]] entt::storage pool; } -ENTT_DEBUG_TEST(StorageDeathTest, PinnedComponent) { +ENTT_DEBUG_TEST_F(StorageDeathTest, PinnedComponent) { entt::storage pool; const entt::entity entity{0}; const entt::entity destroy{1}; @@ -1677,10 +2062,10 @@ ENTT_DEBUG_TEST(StorageDeathTest, PinnedComponent) { ASSERT_DEATH(pool.sort([](auto &&lhs, auto &&rhs) { return lhs < rhs; }), ""); } -TEST(Storage, UpdateFromDestructor) { - static constexpr auto size = 10u; - +TEST_F(Storage, UpdateFromDestructor) { auto test = [](const auto target) { + constexpr auto size = 10u; + entt::storage pool; for(std::size_t next{}; next < size; ++next) { @@ -1703,12 +2088,12 @@ TEST(Storage, UpdateFromDestructor) { } }; - test(entt::entity(size - 1u)); - test(entt::entity(size - 2u)); + test(entt::entity{9u}); + test(entt::entity{8u}); test(entt::entity{0u}); } -TEST(Storage, CreateFromConstructor) { +TEST_F(Storage, CreateFromConstructor) { entt::storage pool; const entt::entity entity{0u}; const entt::entity other{1u}; @@ -1719,7 +2104,7 @@ TEST(Storage, CreateFromConstructor) { ASSERT_EQ(pool.get(other).child, static_cast(entt::null)); } -TEST(Storage, CustomAllocator) { +TEST_F(Storage, CustomAllocator) { auto test = [](auto pool, auto alloc) { pool.reserve(1u); @@ -1766,14 +2151,14 @@ TEST(Storage, CustomAllocator) { test(entt::basic_storage>{allocator}, allocator); } -TEST(Storage, ThrowingAllocator) { +TEST_F(Storage, ThrowingAllocator) { auto test = [](auto pool) { using pool_allocator_type = typename decltype(pool)::allocator_type; using value_type = typename decltype(pool)::value_type; typename std::decay_t::base_type &base = pool; - constexpr auto packed_page_size = entt::component_traits::page_size; - constexpr auto sparse_page_size = entt::entt_traits::page_size; + constexpr auto packed_page_size = decltype(pool)::traits_type::page_size; + constexpr auto sparse_page_size = std::remove_reference_t::traits_type::page_size; pool_allocator_type::trigger_on_allocate = true; @@ -1797,7 +2182,7 @@ TEST(Storage, ThrowingAllocator) { test::throwing_allocator::trigger_on_allocate = true; - ASSERT_THROW(base.emplace(entt::entity{0}), test::throwing_allocator::exception_type); + ASSERT_THROW(base.push(entt::entity{0}), test::throwing_allocator::exception_type); ASSERT_FALSE(base.contains(entt::entity{0})); ASSERT_TRUE(base.empty()); @@ -1830,7 +2215,7 @@ TEST(Storage, ThrowingAllocator) { test(entt::basic_storage>{}); } -TEST(Storage, ThrowingComponent) { +TEST_F(Storage, ThrowingComponent) { entt::storage pool; test::throwing_type::trigger_on_value = 42; @@ -1884,7 +2269,7 @@ TEST(Storage, ThrowingComponent) { #if defined(ENTT_HAS_TRACKED_MEMORY_RESOURCE) -TEST(Storage, NoUsesAllocatorConstruction) { +TEST_F(Storage, NoUsesAllocatorConstruction) { test::tracked_memory_resource memory_resource{}; entt::basic_storage> pool{&memory_resource}; const entt::entity entity{}; @@ -1899,7 +2284,7 @@ TEST(Storage, NoUsesAllocatorConstruction) { ASSERT_EQ(memory_resource.do_deallocate_counter(), 0u); } -TEST(Storage, UsesAllocatorConstruction) { +TEST_F(Storage, UsesAllocatorConstruction) { using string_type = typename test::tracked_memory_resource::string_type; test::tracked_memory_resource memory_resource{}; @@ -1916,18 +2301,18 @@ TEST(Storage, UsesAllocatorConstruction) { ASSERT_EQ(memory_resource.do_deallocate_counter(), 0u); } -TEST(Storage, StorageType) { - // just a bunch of static asserts to avoid regressions - static_assert(std::is_same_v, entt::sigh_storage_mixin>>); - static_assert(std::is_same_v, entt::sigh_storage_mixin>>); -} - -TEST(Storage, StorageFor) { - // just a bunch of static asserts to avoid regressions - static_assert(std::is_same_v, const entt::sigh_storage_mixin>>); - static_assert(std::is_same_v, entt::sigh_storage_mixin>>); - static_assert(std::is_same_v, const entt::sigh_storage_mixin>>); - static_assert(std::is_same_v, entt::sigh_storage_mixin>>); -} - #endif + +TEST_F(Storage, StorageType) { + // just a bunch of static asserts to avoid regressions + static_assert(std::is_same_v, entt::sigh_mixin>>); + static_assert(std::is_same_v, entt::sigh_mixin>>); +} + +TEST_F(Storage, StorageFor) { + // just a bunch of static asserts to avoid regressions + static_assert(std::is_same_v, const entt::sigh_mixin>>); + static_assert(std::is_same_v, entt::sigh_mixin>>); + static_assert(std::is_same_v, const entt::sigh_mixin>>); + static_assert(std::is_same_v, entt::sigh_mixin>>); +} diff --git a/test/entt/entity/storage_entity.cpp b/test/entt/entity/storage_entity.cpp new file mode 100644 index 00000000..988e16fa --- /dev/null +++ b/test/entt/entity/storage_entity.cpp @@ -0,0 +1,628 @@ +#include +#include +#include +#include +#include "../common/config.h" +#include "../common/throwing_allocator.hpp" + +TEST(StorageEntity, TypeAndPolicy) { + entt::storage pool; + + ASSERT_EQ(pool.type(), entt::type_id()); + 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 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 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 pool; + + pool.push(entt::entity{1}); + + ASSERT_EQ(pool.size(), 2u); + ASSERT_EQ(pool.in_use(), 1u); + + ASSERT_TRUE(std::is_move_constructible_v); + ASSERT_TRUE(std::is_move_assignable_v); + + entt::storage 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::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::null)); + + other = entt::storage{}; + + 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::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 pool; + entt::storage 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::storage 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::storage 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 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 pool; + + ASSERT_DEATH(pool.patch(entt::null), ""); +} + +TEST(StorageEntity, Insert) { + entt::storage 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 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::iterable::iterator; + + static_assert(std::is_same_v>); + static_assert(std::is_same_v>>); + static_assert(std::is_same_v); + + entt::storage 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); + ASSERT_TRUE(entity != entt::entity{3}); + } +} + +TEST(StorageEntity, ConstIterable) { + using iterator = typename entt::storage::const_iterable::iterator; + + static_assert(std::is_same_v>); + static_assert(std::is_same_v>>); + static_assert(std::is_same_v); + + entt::storage 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); + ASSERT_TRUE(entity != entt::entity{3}); + } +} + +TEST(StorageEntity, IterableIteratorConversion) { + entt::storage pool; + pool.emplace(entt::entity{3}); + + typename entt::storage::iterable::iterator it = pool.each().begin(); + typename entt::storage::const_iterable::iterator cit = it; + + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); + + ASSERT_EQ(it, cit); + ASSERT_NE(++cit, it); +} + +TEST(StorageEntity, IterableAlgorithmCompatibility) { + entt::storage 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::reverse_iterable::iterator; + + static_assert(std::is_same_v>); + static_assert(std::is_same_v>>); + static_assert(std::is_same_v); + + entt::storage 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); + ASSERT_TRUE(entity != entt::entity{3}); + } +} + +TEST(StorageEntity, ReverseConstIterable) { + using iterator = typename entt::storage::const_reverse_iterable::iterator; + + static_assert(std::is_same_v>); + static_assert(std::is_same_v>>); + static_assert(std::is_same_v); + + entt::storage 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); + ASSERT_TRUE(entity != entt::entity{3}); + } +} + +TEST(StorageEntity, ReverseIterableIteratorConversion) { + entt::storage pool; + pool.emplace(entt::entity{3}); + + typename entt::storage::reverse_iterable::iterator it = pool.reach().begin(); + typename entt::storage::const_reverse_iterable::iterator cit = it; + + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); + + ASSERT_EQ(it, cit); + ASSERT_NE(++cit, it); +} + +TEST(StorageEntity, ReverseIterableAlgorithmCompatibility) { + entt::storage 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 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 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 pool; + + pool.push(entt::entity{0}); + pool.push(entt::entity{1}); + + ASSERT_DEATH(pool.in_use(3u), ""); +} + +ENTT_DEBUG_TEST(StorageEntityDeathTest, SortAndRespect) { + entt::storage pool; + entt::storage 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 allocator{}; + entt::basic_storage> 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); +} diff --git a/test/entt/entity/view.cpp b/test/entt/entity/view.cpp index be4dc02f..fdfe8bec 100644 --- a/test/entt/entity/view.cpp +++ b/test/entt/entity/view.cpp @@ -24,7 +24,6 @@ TEST(SingleComponentView, Functionalities) { ASSERT_TRUE(view.empty()); - registry.emplace(e1); registry.emplace(e1); ASSERT_NO_FATAL_FAILURE(view.begin()++); @@ -70,6 +69,42 @@ TEST(SingleComponentView, Functionalities) { ASSERT_FALSE(invalid); } +TEST(SingleComponentView, InvalidView) { + entt::basic_view>, 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::null)); + ASSERT_EQ(view.back(), static_cast(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 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 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(); - 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(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(entity); registry.emplace(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(e0); - registry.emplace(e0); - - const auto e1 = registry.create(); - registry.emplace(e1); - auto view = registry.view(); 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(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(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(entities[0u], 0); - registry.emplace(entities[0u]); - registry.emplace(entities[1u], 'c'); + registry.emplace(entity, 0); + registry.emplace(entity); registry.view().each([&](const auto entt) { - ASSERT_EQ(entities[0u], entt); + ASSERT_EQ(entity, entt); }); registry.view().each([check = true]() mutable { @@ -352,11 +382,11 @@ TEST(SingleComponentView, EmptyTypes) { for(auto [entt]: registry.view().each()) { static_assert(std::is_same_v); - ASSERT_EQ(entities[0u], entt); + ASSERT_EQ(entity, entt); } registry.view().each([&](const auto entt, int) { - ASSERT_EQ(entities[0u], entt); + ASSERT_EQ(entity, entt); }); registry.view().each([check = true](int) mutable { @@ -367,7 +397,7 @@ TEST(SingleComponentView, EmptyTypes) { for(auto [entt, iv]: registry.view().each()) { static_assert(std::is_same_v); static_assert(std::is_same_v); - 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(); - const auto cview = registry.view(); + auto view = registry.view(); + auto cview = registry.view(); - static_assert(std::is_same_v &>); - static_assert(std::is_same_v()), entt::storage_type_t &>); - static_assert(std::is_same_v()), entt::storage_type_t &>); - static_assert(std::is_same_v &>); - static_assert(std::is_same_v()), const entt::storage_type_t &>); - static_assert(std::is_same_v()), const entt::storage_type_t &>); + static_assert(std::is_same_v *>); + static_assert(std::is_same_v()), entt::storage_type_t *>); + static_assert(std::is_same_v()), entt::storage_type_t *>); + static_assert(std::is_same_v()), entt::storage_type_t *>); + static_assert(std::is_same_v *>); + static_assert(std::is_same_v()), const entt::storage_type_t *>); + static_assert(std::is_same_v()), const entt::storage_type_t *>); + static_assert(std::is_same_v()), const entt::storage_type_t *>); + + ASSERT_TRUE(view); + ASSERT_TRUE(cview); + + ASSERT_NE(view.storage(), 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(entity); ASSERT_EQ(view.size(), 1u); ASSERT_EQ(cview.size(), 1u); - ASSERT_TRUE(view.storage().contains(entity)); - ASSERT_TRUE(cview.storage<0u>().contains(entity)); + ASSERT_TRUE(view.storage()->contains(entity)); + ASSERT_TRUE(cview.storage<0u>()->contains(entity)); ASSERT_TRUE((registry.all_of(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().contains(entity)); + ASSERT_FALSE(view.storage<0u>()->contains(entity)); + ASSERT_TRUE(cview.storage()->contains(entity)); ASSERT_FALSE((registry.all_of(entity))); + + view = {}; + cview = {}; + + ASSERT_FALSE(view); + ASSERT_FALSE(cview); + + ASSERT_EQ(view.storage<0u>(), nullptr); + ASSERT_EQ(cview.storage(), nullptr); +} + +TEST(SingleComponentView, SwapStorage) { + using namespace entt::literals; + + entt::registry registry; + entt::basic_view>, entt::exclude_t<>> view; + entt::basic_view>, entt::exclude_t<>> cview; + + ASSERT_FALSE(view); + ASSERT_FALSE(cview); + ASSERT_EQ(view.storage<0u>(), nullptr); + ASSERT_EQ(cview.storage(), nullptr); + + const entt::entity entity{42u}; + registry.emplace(entity); + + view.storage(registry.storage()); + cview.storage(registry.storage()); + + ASSERT_TRUE(view); + ASSERT_TRUE(cview); + ASSERT_NE(view.storage<0u>(), nullptr); + ASSERT_NE(cview.storage(), 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("other"_hs)); + cview.storage(registry.storage("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::exclude_t>> 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::null)); + ASSERT_EQ(view.back(), static_cast(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 storage; + view.storage(storage); + + ASSERT_FALSE(view); + + view.each([](const int &) { FAIL(); }); + view.each([](const entt::entity, const int &) { FAIL(); }); + + entt::storage 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 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(); - 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(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(); + 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(); + + ASSERT_NE(other, view.handle()); + ASSERT_EQ(handle, view.handle()); } TEST(MultiComponentView, LazyTypesFromConstRegistry) { @@ -585,7 +712,7 @@ TEST(MultiComponentView, LazyTypesFromConstRegistry) { registry.emplace(entity); registry.emplace(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(entt::exclude); - 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(e0); registry.emplace(e0); registry.emplace(e0); @@ -705,10 +831,10 @@ TEST(MultiComponentView, Each) { auto cview = std::as_const(registry).view(); registry.emplace(entity[0u], 0); - registry.emplace(entity[0u], 0); + registry.emplace(entity[0u], static_cast(0)); registry.emplace(entity[1u], 1); - registry.emplace(entity[1u], 1); + registry.emplace(entity[1u], static_cast(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(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().each()) { - ASSERT_EQ(entt::to_integral(entt), ivalue); - ASSERT_EQ(entt::to_integral(entt), cvalue); + ASSERT_EQ(static_cast(entt::to_integral(entt)), ivalue); + ASSERT_EQ(static_cast(entt::to_integral(entt)), cvalue); } } TEST(MultiComponentView, EachWithSuggestedType) { entt::registry registry; + auto view = registry.view(); 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(entity, 99); - registry.view().use().each([value = 2](const auto curr, const auto) mutable { + view.use(); + 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().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(); - for(auto &&curr: registry.view().use().each()) { + for(auto &&curr: view.each()) { ASSERT_EQ(std::get<1>(curr), static_cast(value++)); } } @@ -955,6 +1087,10 @@ TEST(MultiComponentView, ExcludedComponents) { TEST(MultiComponentView, EmptyTypes) { entt::registry registry; + auto v1 = registry.view(entt::exclude); + auto v2 = registry.view(entt::exclude); + auto v3 = registry.view(entt::exclude); + const auto entity = registry.create(); registry.emplace(entity); registry.emplace(entity); @@ -970,57 +1106,61 @@ TEST(MultiComponentView, EmptyTypes) { registry.emplace(ignored); registry.emplace(ignored); - registry.view(entt::exclude).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(entt::exclude).each()) { + for(auto [entt, iv, cv]: v1.each()) { static_assert(std::is_same_v); static_assert(std::is_same_v); static_assert(std::is_same_v); ASSERT_EQ(entity, entt); } - registry.view(entt::exclude).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(entt::exclude).each()) { + for(auto [entt, iv, cv]: v2.each()) { static_assert(std::is_same_v); static_assert(std::is_same_v); static_assert(std::is_same_v); ASSERT_EQ(entity, entt); } - registry.view(entt::exclude).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(entt::exclude).each()) { + for(auto [entt, iv, cv]: v3.each()) { static_assert(std::is_same_v); static_assert(std::is_same_v); static_assert(std::is_same_v); ASSERT_EQ(entity, entt); } - registry.view(entt::exclude).use().each([entity](const auto entt, int, char) { + v3.use(); + v3.each([entity](const auto entt, int, char) { ASSERT_EQ(entity, entt); }); - for(auto [entt, iv, cv]: registry.view(entt::exclude).use<0u>().each()) { + v3.use<0u>(); + for(auto [entt, iv, cv]: v3.each()) { static_assert(std::is_same_v); static_assert(std::is_same_v); static_assert(std::is_same_v); ASSERT_EQ(entity, entt); } - registry.view(entt::exclude).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(entt::exclude).use().each()) { + v2.use(); + for(auto [entt, iv, cv]: v2.each()) { static_assert(std::is_same_v); static_assert(std::is_same_v); static_assert(std::is_same_v); @@ -1111,7 +1251,7 @@ TEST(MultiComponentView, StableType) { ASSERT_EQ(view.size_hint(), 1u); - view = view.use(); + view.use(); 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(entt::exclude).use(); + auto view = registry.view(entt::exclude); + + view.use(); 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(entt::exclude); + + static_assert(std::is_same_v()), entt::storage_type_t *>); + static_assert(std::is_same_v()), entt::storage_type_t *>); + static_assert(std::is_same_v()), entt::storage_type_t *>); + static_assert(std::is_same_v()), const entt::storage_type_t *>); + static_assert(std::is_same_v()), const entt::storage_type_t *>); + static_assert(std::is_same_v()), const entt::storage_type_t *>); + static_assert(std::is_same_v()), entt::storage_type_t *>); + static_assert(std::is_same_v()), entt::storage_type_t *>); + static_assert(std::is_same_v()), entt::storage_type_t *>); + static_assert(std::is_same_v()), const entt::storage_type_t *>); + static_assert(std::is_same_v()), const entt::storage_type_t *>); + static_assert(std::is_same_v()), const entt::storage_type_t *>); + + ASSERT_TRUE(view); + + ASSERT_NE(view.storage(), nullptr); + ASSERT_NE(view.storage<1u>(), nullptr); + ASSERT_NE(view.storage(), nullptr); + ASSERT_NE(view.storage<3u>(), nullptr); + + ASSERT_EQ(view.size_hint(), 0u); + + view.storage()->emplace(entity); + view.storage()->emplace(entity); + registry.emplace(entity); + registry.emplace(entity); + + ASSERT_EQ(view.size_hint(), 1u); + ASSERT_EQ(view.begin(), view.end()); + ASSERT_TRUE(view.storage()->contains(entity)); + ASSERT_TRUE(view.storage()->contains(entity)); + ASSERT_TRUE(view.storage()->contains(entity)); + ASSERT_TRUE(view.storage()->contains(entity)); + ASSERT_TRUE((registry.all_of(entity))); + + view.storage()->erase(entity); + registry.erase(entity); + + ASSERT_EQ(view.size_hint(), 1u); + ASSERT_NE(view.begin(), view.end()); + ASSERT_TRUE(view.storage()->contains(entity)); + ASSERT_TRUE(view.storage()->contains(entity)); + ASSERT_FALSE(view.storage()->contains(entity)); + ASSERT_FALSE(view.storage()->contains(entity)); + ASSERT_TRUE((registry.all_of(entity))); + ASSERT_FALSE((registry.any_of(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(entity))); + ASSERT_FALSE((registry.any_of(entity))); + + view = {}; + + ASSERT_FALSE(view); + + ASSERT_EQ(view.storage<0u>(), nullptr); + ASSERT_EQ(view.storage(), nullptr); + ASSERT_EQ(view.storage<2u>(), nullptr); + ASSERT_EQ(view.storage(), nullptr); +} + +TEST(MultiComponentView, SwapStorage) { + using namespace entt::literals; + + entt::registry registry; + entt::basic_view>, entt::exclude_t>> view; + + ASSERT_FALSE(view); + ASSERT_EQ(view.storage<0u>(), nullptr); + ASSERT_EQ(view.storage(), nullptr); + + const entt::entity entity{42u}; + registry.emplace(entity); + registry.emplace(entity); + + view.storage(registry.storage()); + view.storage<1u>(registry.storage()); + + ASSERT_TRUE(view); + ASSERT_NE(view.storage(), nullptr); + ASSERT_NE(view.storage<1u>(), nullptr); + + ASSERT_EQ(view.size_hint(), 1u); + ASSERT_FALSE(view.contains(entity)); + + view.storage(registry.storage("other"_hs)); + + ASSERT_EQ(view.size_hint(), 1u); + ASSERT_TRUE(view.contains(entity)); + + view.storage(registry.storage("empty"_hs)); + + ASSERT_EQ(view.size_hint(), 0u); } TEST(View, Pipe) { @@ -1241,10 +1493,10 @@ TEST(View, Pipe) { registry.emplace(other); registry.emplace(other); - const auto view1 = registry.view(entt::exclude); - const auto view2 = registry.view(entt::exclude); - const auto view3 = registry.view(); - const auto view4 = registry.view(); + auto view1 = registry.view(entt::exclude); + auto view2 = registry.view(entt::exclude); + auto view3 = registry.view(); + auto view4 = registry.view(); static_assert(std::is_same_v, const entt::storage_type_t>, entt::exclude_t, entt::storage_type_t>>, decltype(view1 | view2)>); static_assert(std::is_same_v, entt::storage_type_t>, entt::exclude_t, const entt::storage_type_t>>, 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(); - - static_assert(std::is_same_v()), entt::storage_type_t &>); - static_assert(std::is_same_v()), entt::storage_type_t &>); - static_assert(std::is_same_v()), const entt::storage_type_t &>); - static_assert(std::is_same_v()), const entt::storage_type_t &>); - - ASSERT_EQ(view.size_hint(), 0u); - - view.storage().emplace(entity); - registry.emplace(entity); - - ASSERT_EQ(view.size_hint(), 1u); - ASSERT_TRUE(view.storage().contains(entity)); - ASSERT_TRUE((registry.all_of(entity))); - - view.storage<0u>().erase(entity); - - ASSERT_EQ(view.size_hint(), 0u); - ASSERT_TRUE(view.storage<1u>().contains(entity)); - ASSERT_FALSE((registry.all_of(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(), nullptr); + ASSERT_EQ(pack14.storage(), nullptr); + ASSERT_NE(pack14.storage(), nullptr); + + ASSERT_EQ(pack32.storage(), nullptr); + ASSERT_NE(pack32.storage(), nullptr); + ASSERT_NE(pack32.storage(), nullptr); } diff --git a/test/entt/graph/adjacency_matrix.cpp b/test/entt/graph/adjacency_matrix.cpp index d2bf4106..3ec250ba 100644 --- a/test/entt/graph/adjacency_matrix.cpp +++ b/test/entt/graph/adjacency_matrix.cpp @@ -295,6 +295,23 @@ TEST(AdjacencyMatrix, EdgesDirected) { ASSERT_EQ(++it, iterable.end()); } +TEST(AdjacencyMatrix, EdgesBackwardOnlyDirected) { + entt::adjacency_matrix 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 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 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 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 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 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 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 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 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 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 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; using exception = typename allocator::exception_type; diff --git a/test/entt/graph/flow.cpp b/test/entt/graph/flow.cpp index e0c55b56..85295504 100644 --- a/test/entt/graph/flow.cpp +++ b/test/entt/graph/flow.cpp @@ -1,6 +1,7 @@ #include #include #include +#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; using task_allocator = test::throwing_allocator>; diff --git a/test/entt/locator/locator.cpp b/test/entt/locator/locator.cpp index c08089c9..c6268605 100644 --- a/test/entt/locator/locator.cpp +++ b/test/entt/locator/locator.cpp @@ -5,45 +5,57 @@ struct base_service { virtual ~base_service() = default; - virtual void invoke() {} -}; - -struct null_service: base_service { - void invoke() override { - invoked = true; - } - - static inline bool invoked{}; + virtual int invoke(int) = 0; }; struct derived_service: base_service { - void invoke() override { - invoked = true; + derived_service(int val) + : value{val} {} + + int invoke(int other) override { + return value + other; } - static inline bool invoked{}; +private: + int value; }; struct ServiceLocator: ::testing::Test { void SetUp() override { - null_service::invoked = false; - derived_service::invoked = false; + entt::locator::reset(); } }; using ServiceLocatorDeathTest = ServiceLocator; -TEST(ServiceLocator, Functionalities) { +TEST_F(ServiceLocator, ValueAndTheLike) { ASSERT_FALSE(entt::locator::has_value()); - ASSERT_FALSE(derived_service::invoked); - ASSERT_FALSE(null_service::invoked); + ASSERT_EQ(entt::locator::value_or(1).invoke(3), 4); + ASSERT_TRUE(entt::locator::has_value()); + ASSERT_EQ(entt::locator::value().invoke(9), 10); +} - entt::locator::value_or().invoke(); +TEST_F(ServiceLocator, Emplace) { + ASSERT_FALSE(entt::locator::has_value()); + ASSERT_EQ(entt::locator::emplace(5).invoke(1), 6); + ASSERT_TRUE(entt::locator::has_value()); + ASSERT_EQ(entt::locator::value().invoke(3), 8); + + entt::locator::reset(); + + ASSERT_FALSE(entt::locator::has_value()); + ASSERT_EQ(entt::locator::emplace(std::allocator_arg, std::allocator{}, 5).invoke(1), 6); + ASSERT_TRUE(entt::locator::has_value()); + ASSERT_EQ(entt::locator::value().invoke(3), 8); +} + +TEST_F(ServiceLocator, ResetHandle) { + entt::locator::emplace(1); + auto handle = entt::locator::handle(); ASSERT_TRUE(entt::locator::has_value()); - ASSERT_TRUE(null_service::invoked); + ASSERT_EQ(entt::locator::value().invoke(3), 4); - auto handle = entt::locator::handle(); entt::locator::reset(); ASSERT_FALSE(entt::locator::has_value()); @@ -51,28 +63,25 @@ TEST(ServiceLocator, Functionalities) { entt::locator::reset(handle); ASSERT_TRUE(entt::locator::has_value()); - - entt::locator::reset(decltype(handle){}); - - ASSERT_FALSE(entt::locator::has_value()); - - entt::locator::emplace(); - entt::locator::value().invoke(); - - ASSERT_TRUE(entt::locator::has_value()); - ASSERT_TRUE(derived_service::invoked); - - derived_service::invoked = false; - entt::locator::allocate_emplace(std::allocator{}).invoke(); - - ASSERT_TRUE(entt::locator::has_value()); - ASSERT_TRUE(derived_service::invoked); + ASSERT_EQ(entt::locator::value().invoke(3), 4); } -ENTT_DEBUG_TEST(ServiceLocatorDeathTest, UninitializedValue) { - ASSERT_NO_FATAL_FAILURE(entt::locator::value_or().invoke()); +TEST_F(ServiceLocator, ElementWithDeleter) { + derived_service service{1}; + entt::locator::reset(&service, [](base_service *serv) { *static_cast(serv) = derived_service{2}; }); + + ASSERT_TRUE(entt::locator::has_value()); + ASSERT_EQ(entt::locator::value().invoke(1), 2); entt::locator::reset(); - ASSERT_DEATH(entt::locator::value().invoke(), ""); + ASSERT_EQ(service.invoke(1), 3); +} + +ENTT_DEBUG_TEST_F(ServiceLocatorDeathTest, UninitializedValue) { + ASSERT_EQ(entt::locator::value_or(1).invoke(1), 2); + + entt::locator::reset(); + + ASSERT_DEATH(entt::locator::value().invoke(42), ""); } diff --git a/test/entt/meta/meta_any.cpp b/test/entt/meta/meta_any.cpp index 07f2eab2..cb16b167 100644 --- a/test/entt/meta/meta_any.cpp +++ b/test/entt/meta/meta_any.cpp @@ -78,9 +78,6 @@ struct MetaAny: ::testing::Test { void SetUp() override { using namespace entt::literals; - entt::meta() - .type("double"_hs); - entt::meta() .type("empty"_hs) .dtor(); diff --git a/test/entt/meta/meta_container.cpp b/test/entt/meta/meta_container.cpp index 6a2fa318..9cd2b625 100644 --- a/test/entt/meta/meta_container.cpp +++ b/test/entt/meta/meta_container.cpp @@ -15,25 +15,7 @@ struct invalid_type {}; -struct MetaContainer: ::testing::Test { - void SetUp() override { - using namespace entt::literals; - - entt::meta() - .type("double"_hs); - - entt::meta() - .type("int"_hs); - } - - void TearDown() override { - entt::meta_reset(); - } -}; - -using MetaContainerDeathTest = MetaContainer; - -TEST_F(MetaContainer, InvalidContainer) { +TEST(MetaContainer, InvalidContainer) { ASSERT_FALSE(entt::meta_any{42}.as_sequence_container()); ASSERT_FALSE(entt::meta_any{42}.as_associative_container()); @@ -41,7 +23,7 @@ TEST_F(MetaContainer, InvalidContainer) { ASSERT_FALSE(entt::meta_any{std::vector{}}.as_associative_container()); } -TEST_F(MetaContainer, EmptySequenceContainer) { +TEST(MetaContainer, EmptySequenceContainer) { entt::meta_sequence_container container{}; ASSERT_FALSE(container); @@ -52,7 +34,7 @@ TEST_F(MetaContainer, EmptySequenceContainer) { ASSERT_TRUE(container); } -TEST_F(MetaContainer, EmptyAssociativeContainer) { +TEST(MetaContainer, EmptyAssociativeContainer) { entt::meta_associative_container container{}; ASSERT_FALSE(container); @@ -63,7 +45,7 @@ TEST_F(MetaContainer, EmptyAssociativeContainer) { ASSERT_TRUE(container); } -TEST_F(MetaContainer, SequenceContainerIterator) { +TEST(MetaContainer, SequenceContainerIterator) { std::vector vec{2, 3, 4}; auto any = entt::forward_as_meta(vec); entt::meta_sequence_container::iterator first{}; @@ -92,7 +74,7 @@ TEST_F(MetaContainer, SequenceContainerIterator) { ASSERT_EQ((--first)->cast(), 2); } -TEST_F(MetaContainer, AssociativeContainerIterator) { +TEST(MetaContainer, AssociativeContainerIterator) { std::map map{{2, 'c'}, {3, 'd'}, {4, 'e'}}; auto any = entt::forward_as_meta(map); entt::meta_associative_container::iterator first{}; @@ -119,7 +101,7 @@ TEST_F(MetaContainer, AssociativeContainerIterator) { ASSERT_FALSE(first != last); } -TEST_F(MetaContainer, StdVector) { +TEST(MetaContainer, StdVector) { std::vector vec{}; auto any = entt::forward_as_meta(vec); auto view = any.as_sequence_container(); @@ -173,7 +155,7 @@ TEST_F(MetaContainer, StdVector) { ASSERT_EQ(view.size(), 0u); } -TEST_F(MetaContainer, StdArray) { +TEST(MetaContainer, StdArray) { std::array arr{}; auto any = entt::forward_as_meta(arr); auto view = any.as_sequence_container(); @@ -214,7 +196,7 @@ TEST_F(MetaContainer, StdArray) { ASSERT_EQ(view.size(), 3u); } -TEST_F(MetaContainer, StdList) { +TEST(MetaContainer, StdList) { std::list list{}; auto any = entt::forward_as_meta(list); auto view = any.as_sequence_container(); @@ -268,7 +250,7 @@ TEST_F(MetaContainer, StdList) { ASSERT_EQ(view.size(), 0u); } -TEST_F(MetaContainer, StdDeque) { +TEST(MetaContainer, StdDeque) { std::deque deque{}; auto any = entt::forward_as_meta(deque); auto view = any.as_sequence_container(); @@ -322,7 +304,7 @@ TEST_F(MetaContainer, StdDeque) { ASSERT_EQ(view.size(), 0u); } -TEST_F(MetaContainer, StdMap) { +TEST(MetaContainer, StdMap) { std::map map{{2, 'c'}, {3, 'd'}, {4, 'e'}}; auto any = entt::forward_as_meta(map); auto view = any.as_associative_container(); @@ -365,7 +347,7 @@ TEST_F(MetaContainer, StdMap) { ASSERT_EQ(view.size(), 0u); } -TEST_F(MetaContainer, StdSet) { +TEST(MetaContainer, StdSet) { std::set set{2, 3, 4}; auto any = entt::forward_as_meta(set); auto view = any.as_associative_container(); @@ -407,7 +389,7 @@ TEST_F(MetaContainer, StdSet) { ASSERT_EQ(view.size(), 0u); } -TEST_F(MetaContainer, DenseMap) { +TEST(MetaContainer, DenseMap) { entt::dense_map map{}; auto any = entt::forward_as_meta(map); auto view = any.as_associative_container(); @@ -454,7 +436,7 @@ TEST_F(MetaContainer, DenseMap) { ASSERT_EQ(view.size(), 0u); } -TEST_F(MetaContainer, DenseSet) { +TEST(MetaContainer, DenseSet) { entt::dense_set set{}; auto any = entt::forward_as_meta(set); auto view = any.as_associative_container(); @@ -500,7 +482,7 @@ TEST_F(MetaContainer, DenseSet) { ASSERT_EQ(view.size(), 0u); } -TEST_F(MetaContainer, ConstSequenceContainer) { +TEST(MetaContainer, ConstSequenceContainer) { std::vector vec{}; auto any = entt::forward_as_meta(std::as_const(vec)); auto view = any.as_sequence_container(); @@ -538,7 +520,7 @@ TEST_F(MetaContainer, ConstSequenceContainer) { ASSERT_EQ(view.size(), 1u); } -ENTT_DEBUG_TEST_F(MetaContainerDeathTest, ConstSequenceContainer) { +ENTT_DEBUG_TEST(MetaContainerDeathTest, ConstSequenceContainer) { std::vector vec{}; auto any = entt::forward_as_meta(std::as_const(vec)); auto view = any.as_sequence_container(); @@ -547,7 +529,7 @@ ENTT_DEBUG_TEST_F(MetaContainerDeathTest, ConstSequenceContainer) { ASSERT_DEATH(view[0].cast() = 2, ""); } -TEST_F(MetaContainer, ConstKeyValueAssociativeContainer) { +TEST(MetaContainer, ConstKeyValueAssociativeContainer) { std::map map{}; auto any = entt::forward_as_meta(std::as_const(map)); auto view = any.as_associative_container(); @@ -580,7 +562,7 @@ TEST_F(MetaContainer, ConstKeyValueAssociativeContainer) { ASSERT_EQ(view.size(), 1u); } -ENTT_DEBUG_TEST_F(MetaContainerDeathTest, ConstKeyValueAssociativeContainer) { +ENTT_DEBUG_TEST(MetaContainerDeathTest, ConstKeyValueAssociativeContainer) { std::map map{}; auto any = entt::forward_as_meta(std::as_const(map)); auto view = any.as_associative_container(); @@ -589,7 +571,7 @@ ENTT_DEBUG_TEST_F(MetaContainerDeathTest, ConstKeyValueAssociativeContainer) { ASSERT_DEATH(view.find(2)->second.cast() = 'a', ""); } -TEST_F(MetaContainer, ConstKeyOnlyAssociativeContainer) { +TEST(MetaContainer, ConstKeyOnlyAssociativeContainer) { std::set set{}; auto any = entt::forward_as_meta(std::as_const(set)); auto view = any.as_associative_container(); @@ -626,7 +608,7 @@ TEST_F(MetaContainer, ConstKeyOnlyAssociativeContainer) { ASSERT_EQ(view.size(), 1u); } -TEST_F(MetaContainer, SequenceContainerConstMetaAny) { +TEST(MetaContainer, SequenceContainerConstMetaAny) { auto test = [](const entt::meta_any any) { auto view = any.as_sequence_container(); @@ -642,7 +624,7 @@ TEST_F(MetaContainer, SequenceContainerConstMetaAny) { test(entt::forward_as_meta(std::as_const(vec))); } -ENTT_DEBUG_TEST_F(MetaContainerDeathTest, SequenceContainerConstMetaAny) { +ENTT_DEBUG_TEST(MetaContainerDeathTest, SequenceContainerConstMetaAny) { auto test = [](const entt::meta_any any) { auto view = any.as_sequence_container(); @@ -657,7 +639,7 @@ ENTT_DEBUG_TEST_F(MetaContainerDeathTest, SequenceContainerConstMetaAny) { test(entt::forward_as_meta(std::as_const(vec))); } -TEST_F(MetaContainer, KeyValueAssociativeContainerConstMetaAny) { +TEST(MetaContainer, KeyValueAssociativeContainerConstMetaAny) { auto test = [](const entt::meta_any any) { auto view = any.as_associative_container(); @@ -673,7 +655,7 @@ TEST_F(MetaContainer, KeyValueAssociativeContainerConstMetaAny) { test(entt::forward_as_meta(std::as_const(map))); } -ENTT_DEBUG_TEST_F(MetaContainerDeathTest, KeyValueAssociativeContainerConstMetaAny) { +ENTT_DEBUG_TEST(MetaContainerDeathTest, KeyValueAssociativeContainerConstMetaAny) { auto test = [](const entt::meta_any any) { auto view = any.as_associative_container(); @@ -688,7 +670,7 @@ ENTT_DEBUG_TEST_F(MetaContainerDeathTest, KeyValueAssociativeContainerConstMetaA test(entt::forward_as_meta(std::as_const(map))); } -TEST_F(MetaContainer, KeyOnlyAssociativeContainerConstMetaAny) { +TEST(MetaContainer, KeyOnlyAssociativeContainerConstMetaAny) { auto test = [](const entt::meta_any any) { auto view = any.as_associative_container(); @@ -708,7 +690,7 @@ TEST_F(MetaContainer, KeyOnlyAssociativeContainerConstMetaAny) { test(entt::forward_as_meta(std::as_const(set))); } -TEST_F(MetaContainer, StdVectorBool) { +TEST(MetaContainer, StdVectorBool) { using proxy_type = typename std::vector::reference; using const_proxy_type = typename std::vector::const_reference; diff --git a/test/entt/meta/meta_data.cpp b/test/entt/meta/meta_data.cpp index 27a7d84e..36b78237 100644 --- a/test/entt/meta/meta_data.cpp +++ b/test/entt/meta/meta_data.cpp @@ -79,7 +79,7 @@ struct multi_setter_t { : value{0} {} void from_double(double val) { - value = val; + value = static_cast(val); } void from_string(const char *val) { @@ -103,9 +103,6 @@ struct MetaData: ::testing::Test { void SetUp() override { using namespace entt::literals; - entt::meta() - .type("double"_hs); - entt::meta() .type("base"_hs) .dtor() @@ -167,6 +164,12 @@ TEST_F(MetaData, Functionalities) { clazz_t instance{}; ASSERT_TRUE(data); + + ASSERT_EQ(data, data); + ASSERT_NE(data, entt::meta_data{}); + ASSERT_FALSE(data != data); + ASSERT_TRUE(data == data); + ASSERT_EQ(data.arity(), 1u); ASSERT_EQ(data.type(), entt::resolve()); ASSERT_EQ(data.arg(0u), entt::resolve()); diff --git a/test/entt/meta/meta_func.cpp b/test/entt/meta/meta_func.cpp index 76702891..6d3473ae 100644 --- a/test/entt/meta/meta_func.cpp +++ b/test/entt/meta/meta_func.cpp @@ -87,12 +87,17 @@ struct func_t { inline static int value = 0; }; +double double_member(const double &value) { + return value * value; +} + struct MetaFunc: ::testing::Test { void SetUp() override { using namespace entt::literals; entt::meta() - .type("double"_hs); + .type("double"_hs) + .func<&double_member>("member"_hs); entt::meta() .type("base"_hs) @@ -165,6 +170,12 @@ TEST_F(MetaFunc, Functionalities) { func_t instance{}; ASSERT_TRUE(func); + + ASSERT_EQ(func, func); + ASSERT_NE(func, entt::meta_func{}); + ASSERT_FALSE(func != func); + ASSERT_TRUE(func == func); + ASSERT_EQ(func.arity(), 2u); ASSERT_FALSE(func.is_const()); ASSERT_FALSE(func.is_static()); @@ -384,6 +395,30 @@ TEST_F(MetaFunc, StaticAsConstMember) { ASSERT_EQ(any.cast(), 3); } +TEST_F(MetaFunc, NonClassTypeMember) { + using namespace entt::literals; + + double instance = 3.; + auto func = entt::resolve().func("member"_hs); + auto any = func.invoke(instance); + + ASSERT_TRUE(func); + ASSERT_EQ(func.arity(), 0u); + ASSERT_TRUE(func.is_const()); + ASSERT_FALSE(func.is_static()); + ASSERT_EQ(func.ret(), entt::resolve()); + ASSERT_FALSE(func.arg(0u)); + + ASSERT_EQ(func.prop().cbegin(), func.prop().cend()); + + ASSERT_FALSE(func.invoke({})); + ASSERT_TRUE(func.invoke(instance)); + + ASSERT_TRUE(any); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(any.cast(), instance * instance); +} + TEST_F(MetaFunc, MetaAnyArgs) { using namespace entt::literals; diff --git a/test/entt/meta/meta_handle.cpp b/test/entt/meta/meta_handle.cpp index d5786d5e..a4ba5451 100644 --- a/test/entt/meta/meta_handle.cpp +++ b/test/entt/meta/meta_handle.cpp @@ -44,12 +44,22 @@ TEST_F(MetaHandle, Functionalities) { ASSERT_FALSE(handle); ASSERT_FALSE(chandle); + ASSERT_EQ(handle, chandle); + ASSERT_EQ(handle, entt::meta_handle{}); + ASSERT_FALSE(handle != handle); + ASSERT_TRUE(handle == handle); + handle = entt::meta_handle{instance}; chandle = entt::meta_handle{std::as_const(instance)}; ASSERT_TRUE(handle); ASSERT_TRUE(chandle); + ASSERT_EQ(handle, chandle); + ASSERT_NE(handle, entt::meta_handle{}); + ASSERT_FALSE(handle != handle); + ASSERT_TRUE(handle == handle); + ASSERT_TRUE(handle->invoke("incr"_hs)); ASSERT_FALSE(chandle->invoke("incr"_hs)); ASSERT_FALSE(std::as_const(handle)->invoke("incr"_hs)); diff --git a/test/entt/meta/meta_pointer.cpp b/test/entt/meta/meta_pointer.cpp index 6d717846..0924a067 100644 --- a/test/entt/meta/meta_pointer.cpp +++ b/test/entt/meta/meta_pointer.cpp @@ -80,14 +80,6 @@ int test_function() { return 42; } -struct not_copyable_t { - not_copyable_t() = default; - not_copyable_t(const not_copyable_t &) = delete; - not_copyable_t(not_copyable_t &&) = default; - not_copyable_t &operator=(const not_copyable_t &) = delete; - not_copyable_t &operator=(not_copyable_t &&) = default; -}; - TEST(MetaPointerLike, DereferenceOperatorInvalidType) { int value = 0; entt::meta_any any{value}; @@ -213,16 +205,16 @@ TEST(MetaPointerLike, DereferenceOperatorSmartPointer) { } TEST(MetaPointerLike, PointerToConstMoveOnlyType) { - const not_copyable_t instance; + const std::unique_ptr instance; entt::meta_any any{&instance}; auto deref = *any; ASSERT_TRUE(any); ASSERT_TRUE(deref); - ASSERT_EQ(deref.try_cast(), nullptr); - ASSERT_NE(deref.try_cast(), nullptr); - ASSERT_EQ(&deref.cast(), &instance); + ASSERT_EQ(deref.try_cast>(), nullptr); + ASSERT_NE(deref.try_cast>(), nullptr); + ASSERT_EQ(&deref.cast &>(), &instance); } TEST(MetaPointerLike, AsRef) { diff --git a/test/entt/meta/meta_prop.cpp b/test/entt/meta/meta_prop.cpp index 92071c5c..db6dbab0 100644 --- a/test/entt/meta/meta_prop.cpp +++ b/test/entt/meta/meta_prop.cpp @@ -49,7 +49,23 @@ TEST_F(MetaProp, Functionalities) { auto prop = entt::resolve().prop("int"_hs); ASSERT_TRUE(prop); - ASSERT_EQ(prop.value(), 42); + + ASSERT_EQ(prop, prop); + ASSERT_NE(prop, entt::meta_prop{}); + ASSERT_FALSE(prop != prop); + ASSERT_TRUE(prop == prop); + + auto value = prop.value(); + auto cvalue = std::as_const(prop).value(); + + ASSERT_NE(value.try_cast(), nullptr); + ASSERT_EQ(cvalue.try_cast(), nullptr); + + ASSERT_NE(value.try_cast(), nullptr); + ASSERT_NE(cvalue.try_cast(), nullptr); + + ASSERT_EQ(value, 42); + ASSERT_EQ(cvalue, 42); } TEST_F(MetaProp, FromBase) { diff --git a/test/entt/meta/meta_type.cpp b/test/entt/meta/meta_type.cpp index adb6e766..0fc7c376 100644 --- a/test/entt/meta/meta_type.cpp +++ b/test/entt/meta/meta_type.cpp @@ -378,7 +378,10 @@ TEST_F(MetaType, Invoke) { clazz_t instance{}; ASSERT_TRUE(type.invoke("member"_hs, instance)); - ASSERT_FALSE(type.invoke("rebmem"_hs, {})); + ASSERT_FALSE(type.invoke("rebmem"_hs, instance)); + + ASSERT_TRUE(type.invoke("func"_hs, {})); + ASSERT_FALSE(type.invoke("cnuf"_hs, {})); } TEST_F(MetaType, InvokeFromBase) { diff --git a/test/entt/poly/poly.cpp b/test/entt/poly/poly.cpp index 87682245..b2d5c9e1 100644 --- a/test/entt/poly/poly.cpp +++ b/test/entt/poly/poly.cpp @@ -18,7 +18,7 @@ struct common_type: Base { } int get() const { - return entt::poly_call<2>(*this); + return static_cast(entt::poly_call<2>(*this)); } void decr() { @@ -26,11 +26,11 @@ struct common_type: Base { } int mul(int v) const { - return entt::poly_call<4>(*this, v); + return static_cast(entt::poly_call<4>(*this, v)); } int rand() const { - return entt::poly_call<5>(*this); + return static_cast(entt::poly_call<5>(*this)); } }; @@ -385,7 +385,7 @@ TYPED_TEST(Poly, SBOVsZeroedSBOSize) { } TYPED_TEST(Poly, SboAlignment) { - static constexpr auto alignment = alignof(over_aligned); + constexpr auto alignment = alignof(over_aligned); typename TestFixture::template type sbo[2]{over_aligned{}, over_aligned{}}; const auto *data = sbo[0].data(); @@ -401,7 +401,7 @@ TYPED_TEST(Poly, SboAlignment) { } TYPED_TEST(Poly, NoSboAlignment) { - static constexpr auto alignment = alignof(over_aligned); + constexpr auto alignment = alignof(over_aligned); typename TestFixture::template type nosbo[2]{over_aligned{}, over_aligned{}}; const auto *data = nosbo[0].data(); diff --git a/test/entt/process/scheduler.cpp b/test/entt/process/scheduler.cpp index c08c44ca..21487a68 100644 --- a/test/entt/process/scheduler.cpp +++ b/test/entt/process/scheduler.cpp @@ -4,7 +4,7 @@ #include #include -struct foo_process: entt::process { +struct foo_process: entt::process { foo_process(std::function upd, std::function abort) : on_update{upd}, on_aborted{abort} {} @@ -20,7 +20,7 @@ struct foo_process: entt::process { std::function on_aborted; }; -struct succeeded_process: entt::process { +struct succeeded_process: entt::process { void update(delta_type, void *) { ++invoked; succeed(); @@ -29,7 +29,7 @@ struct succeeded_process: entt::process { static inline unsigned int invoked; }; -struct failed_process: entt::process { +struct failed_process: entt::process { void update(delta_type, void *) { ++invoked; fail(); @@ -46,7 +46,7 @@ struct Scheduler: ::testing::Test { }; TEST_F(Scheduler, Functionalities) { - entt::scheduler scheduler{}; + entt::scheduler scheduler{}; bool updated = false; bool aborted = false; @@ -77,7 +77,7 @@ TEST_F(Scheduler, Functionalities) { } TEST_F(Scheduler, Then) { - entt::scheduler scheduler; + entt::scheduler scheduler; // failing process with successor scheduler.attach() @@ -106,7 +106,7 @@ TEST_F(Scheduler, Then) { } TEST_F(Scheduler, Functor) { - entt::scheduler scheduler; + entt::scheduler scheduler; bool first_functor = false; bool second_functor = false; @@ -135,7 +135,7 @@ TEST_F(Scheduler, Functor) { } TEST_F(Scheduler, SpawningProcess) { - entt::scheduler scheduler; + entt::scheduler scheduler; scheduler.attach([&scheduler](auto, void *, auto resolve, auto) { scheduler.attach().then(); diff --git a/test/entt/resource/resource.cpp b/test/entt/resource/resource.cpp index 58acc993..e67c57f8 100644 --- a/test/entt/resource/resource.cpp +++ b/test/entt/resource/resource.cpp @@ -90,12 +90,12 @@ TEST(Resource, ConstNonConstAndAllInBetween) { ASSERT_TRUE(copy); ASSERT_EQ(copy, resource); ASSERT_NE(copy, entt::resource{}); - ASSERT_EQ(copy.handle().use_count(), 3u); + ASSERT_EQ(copy.handle().use_count(), 3); ASSERT_TRUE(move); ASSERT_EQ(move, resource); ASSERT_NE(move, entt::resource{}); - ASSERT_EQ(move.handle().use_count(), 3u); + ASSERT_EQ(move.handle().use_count(), 3); copy = resource; move = std::move(resource); @@ -105,7 +105,7 @@ TEST(Resource, ConstNonConstAndAllInBetween) { ASSERT_TRUE(copy); ASSERT_TRUE(move); - ASSERT_EQ(copy.handle().use_count(), 2u); + ASSERT_EQ(copy.handle().use_count(), 2); } TEST(Resource, DynamicResourceHandleCast) { @@ -113,20 +113,20 @@ TEST(Resource, DynamicResourceHandleCast) { entt::resource other = resource; ASSERT_TRUE(other); - ASSERT_EQ(resource.handle().use_count(), 2u); + ASSERT_EQ(resource.handle().use_count(), 2); ASSERT_EQ(resource, other); entt::resource cast = dynamic_resource_cast(other); ASSERT_TRUE(cast); - ASSERT_EQ(resource.handle().use_count(), 3u); + ASSERT_EQ(resource.handle().use_count(), 3); ASSERT_EQ(resource, cast); other = entt::resource{std::make_shared()}; cast = dynamic_resource_cast(other); ASSERT_FALSE(cast); - ASSERT_EQ(resource.handle().use_count(), 1u); + ASSERT_EQ(resource.handle().use_count(), 1); } TEST(Resource, Comparison) { diff --git a/test/entt/resource/resource_cache.cpp b/test/entt/resource/resource_cache.cpp index 4f8015ed..d27c20c5 100644 --- a/test/entt/resource/resource_cache.cpp +++ b/test/entt/resource/resource_cache.cpp @@ -318,17 +318,17 @@ TEST(ResourceCache, Load) { } TEST(ResourceCache, Erase) { - static constexpr std::size_t resource_count = 8u; + constexpr std::size_t resource_count = 8u; entt::resource_cache cache; for(std::size_t next{}, last = resource_count + 1u; next < last; ++next) { - cache.load(next, next); + cache.load(static_cast(next), next); } ASSERT_EQ(cache.size(), resource_count + 1u); for(std::size_t next{}, last = resource_count + 1u; next < last; ++next) { - ASSERT_TRUE(cache.contains(next)); + ASSERT_TRUE(cache.contains(static_cast(next))); } auto it = cache.erase(++cache.begin()); @@ -346,16 +346,16 @@ TEST(ResourceCache, Erase) { for(std::size_t next{}, last = resource_count + 1u; next < last; ++next) { if(next == 1u || next == 8u || next == 6u) { - ASSERT_FALSE(cache.contains(next)); + ASSERT_FALSE(cache.contains(static_cast(next))); } else { - ASSERT_TRUE(cache.contains(next)); + ASSERT_TRUE(cache.contains(static_cast(next))); } } cache.erase(cache.begin(), cache.end()); for(std::size_t next{}, last = resource_count + 1u; next < last; ++next) { - ASSERT_FALSE(cache.contains(next)); + ASSERT_FALSE(cache.contains(static_cast(next))); } ASSERT_EQ(cache.size(), 0u); diff --git a/test/entt/signal/delegate.cpp b/test/entt/signal/delegate.cpp index d46f1ff1..f0e2e305 100644 --- a/test/entt/signal/delegate.cpp +++ b/test/entt/signal/delegate.cpp @@ -5,16 +5,16 @@ #include #include "../common/config.h" -int delegate_function(const int &i) { +int power_of_two(const int &i) { return i * i; } -int curried_by_ref(const int &i, int j) { +int sum_with_ref(const int &i, int j) { return i + j; } -int curried_by_ptr(const int *i, int j) { - return (*i) * j; +int sum_with_ptr(const int *i, int j) { + return (*i) + j; } int non_const_reference(int &i) { @@ -70,7 +70,7 @@ TEST(Delegate, Functionalities) { ASSERT_FALSE(mf_del); ASSERT_EQ(ff_del, mf_del); - ff_del.connect<&delegate_function>(); + ff_del.connect<&power_of_two>(); mf_del.connect<&delegate_functor::operator()>(functor); lf_del.connect([](const void *ptr, int value) { return static_cast(ptr)->identity(value); }, &functor); @@ -138,44 +138,44 @@ TEST(Delegate, Comparison) { ASSERT_TRUE(lhs == rhs); ASSERT_EQ(lhs, rhs); - lhs.connect<&delegate_function>(); + lhs.connect<&power_of_two>(); - ASSERT_EQ(lhs, entt::delegate{entt::connect_arg<&delegate_function>}); + ASSERT_EQ(lhs, entt::delegate{entt::connect_arg<&power_of_two>}); ASSERT_TRUE(lhs != rhs); ASSERT_FALSE(lhs == rhs); ASSERT_NE(lhs, rhs); - rhs.connect<&delegate_function>(); + rhs.connect<&power_of_two>(); - ASSERT_EQ(rhs, entt::delegate{entt::connect_arg<&delegate_function>}); + ASSERT_EQ(rhs, entt::delegate{entt::connect_arg<&power_of_two>}); ASSERT_FALSE(lhs != rhs); ASSERT_TRUE(lhs == rhs); ASSERT_EQ(lhs, rhs); - lhs.connect<&curried_by_ref>(value); + lhs.connect<&sum_with_ref>(value); - ASSERT_EQ(lhs, (entt::delegate{entt::connect_arg<&curried_by_ref>, value})); + ASSERT_EQ(lhs, (entt::delegate{entt::connect_arg<&sum_with_ref>, value})); ASSERT_TRUE(lhs != rhs); ASSERT_FALSE(lhs == rhs); ASSERT_NE(lhs, rhs); - rhs.connect<&curried_by_ref>(value); + rhs.connect<&sum_with_ref>(value); - ASSERT_EQ(rhs, (entt::delegate{entt::connect_arg<&curried_by_ref>, value})); + ASSERT_EQ(rhs, (entt::delegate{entt::connect_arg<&sum_with_ref>, value})); ASSERT_FALSE(lhs != rhs); ASSERT_TRUE(lhs == rhs); ASSERT_EQ(lhs, rhs); - lhs.connect<&curried_by_ptr>(&value); + lhs.connect<&sum_with_ptr>(&value); - ASSERT_EQ(lhs, (entt::delegate{entt::connect_arg<&curried_by_ptr>, &value})); + ASSERT_EQ(lhs, (entt::delegate{entt::connect_arg<&sum_with_ptr>, &value})); ASSERT_TRUE(lhs != rhs); ASSERT_FALSE(lhs == rhs); ASSERT_NE(lhs, rhs); - rhs.connect<&curried_by_ptr>(&value); + rhs.connect<&sum_with_ptr>(&value); - ASSERT_EQ(rhs, (entt::delegate{entt::connect_arg<&curried_by_ptr>, &value})); + ASSERT_EQ(rhs, (entt::delegate{entt::connect_arg<&sum_with_ptr>, &value})); ASSERT_FALSE(lhs != rhs); ASSERT_TRUE(lhs == rhs); ASSERT_EQ(lhs, rhs); @@ -190,6 +190,8 @@ TEST(Delegate, Comparison) { rhs.connect<&delegate_functor::operator()>(functor); ASSERT_EQ(rhs, (entt::delegate{entt::connect_arg<&delegate_functor::operator()>, functor})); + ASSERT_EQ(lhs.target(), rhs.target()); + ASSERT_EQ(lhs.data(), rhs.data()); ASSERT_FALSE(lhs != rhs); ASSERT_TRUE(lhs == rhs); ASSERT_EQ(lhs, rhs); @@ -197,6 +199,7 @@ TEST(Delegate, Comparison) { lhs.connect<&delegate_functor::operator()>(other); ASSERT_EQ(lhs, (entt::delegate{entt::connect_arg<&delegate_functor::operator()>, other})); + ASSERT_EQ(lhs.target(), rhs.target()); ASSERT_NE(lhs.data(), rhs.data()); ASSERT_TRUE(lhs != rhs); ASSERT_FALSE(lhs == rhs); @@ -205,6 +208,8 @@ TEST(Delegate, Comparison) { lhs.connect([](const void *ptr, int val) { return static_cast(ptr)->identity(val) * val; }, &functor); ASSERT_NE(lhs, (entt::delegate{[](const void *, int val) { return val + val; }, &functor})); + ASSERT_NE(lhs.target(), rhs.target()); + ASSERT_EQ(lhs.data(), rhs.data()); ASSERT_TRUE(lhs != rhs); ASSERT_FALSE(lhs == rhs); ASSERT_NE(lhs, rhs); @@ -254,11 +259,11 @@ TEST(Delegate, DeductionGuide) { const_nonconst_noexcept functor; int value = 0; - entt::delegate func{entt::connect_arg<&delegate_function>}; - entt::delegate curried_func_with_ref{entt::connect_arg<&curried_by_ref>, value}; - entt::delegate curried_func_with_const_ref{entt::connect_arg<&curried_by_ref>, std::as_const(value)}; - entt::delegate curried_func_with_ptr{entt::connect_arg<&curried_by_ptr>, &value}; - entt::delegate curried_func_with_const_ptr{entt::connect_arg<&curried_by_ptr>, &std::as_const(value)}; + entt::delegate plain_func{entt::connect_arg<&power_of_two>}; + entt::delegate sum_func_with_ref{entt::connect_arg<&sum_with_ref>, value}; + entt::delegate sum_func_with_const_ref{entt::connect_arg<&sum_with_ref>, std::as_const(value)}; + entt::delegate sum_func_with_ptr{entt::connect_arg<&sum_with_ptr>, &value}; + entt::delegate sum_func_with_const_ptr{entt::connect_arg<&sum_with_ptr>, &std::as_const(value)}; entt::delegate member_func_f{entt::connect_arg<&const_nonconst_noexcept::f>, functor}; entt::delegate member_func_g{entt::connect_arg<&const_nonconst_noexcept::g>, functor}; entt::delegate member_func_h{entt::connect_arg<&const_nonconst_noexcept::h>, &functor}; @@ -270,11 +275,11 @@ TEST(Delegate, DeductionGuide) { entt::delegate data_member_v_const{entt::connect_arg<&const_nonconst_noexcept::v>, &std::as_const(functor)}; entt::delegate lambda{+[](const void *, int) { return 0; }}; - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); static_assert(std::is_same_v); static_assert(std::is_same_v); static_assert(std::is_same_v); @@ -286,11 +291,11 @@ TEST(Delegate, DeductionGuide) { static_assert(std::is_same_v); static_assert(std::is_same_v); - ASSERT_TRUE(func); - ASSERT_TRUE(curried_func_with_ref); - ASSERT_TRUE(curried_func_with_const_ref); - ASSERT_TRUE(curried_func_with_ptr); - ASSERT_TRUE(curried_func_with_const_ptr); + ASSERT_TRUE(plain_func); + ASSERT_TRUE(sum_func_with_ref); + ASSERT_TRUE(sum_func_with_const_ref); + ASSERT_TRUE(sum_func_with_ptr); + ASSERT_TRUE(sum_func_with_const_ptr); ASSERT_TRUE(member_func_f); ASSERT_TRUE(member_func_g); ASSERT_TRUE(member_func_h); @@ -338,19 +343,46 @@ TEST(Delegate, MoveOnlyType) { ASSERT_FALSE(ptr); } -TEST(Delegate, CurriedFunction) { - entt::delegate delegate; +TEST(Delegate, DiscardLast) { + entt::delegate &)> delegate; const auto value = 3; + const auto other = std::make_unique(42); - delegate.connect<&curried_by_ref>(value); + delegate.connect<&power_of_two>(); ASSERT_TRUE(delegate); - ASSERT_EQ(delegate(1), 4); + ASSERT_EQ(delegate(3, other), 9); - delegate.connect<&curried_by_ptr>(&value); + delegate.connect<&sum_with_ref>(value); ASSERT_TRUE(delegate); - ASSERT_EQ(delegate(2), 6); + ASSERT_EQ(delegate(1, other), 4); + + delegate.connect<&sum_with_ptr>(&value); + + ASSERT_TRUE(delegate); + ASSERT_EQ(delegate(2, other), 5); +} + +TEST(Delegate, SkipFirst) { + entt::delegate &, int)> delegate; + const auto value = 3; + const auto other = std::make_unique(42); + + delegate.connect<&power_of_two>(); + + ASSERT_TRUE(delegate); + ASSERT_EQ(delegate(other, 3), 9); + + delegate.connect<&sum_with_ref>(value); + + ASSERT_TRUE(delegate); + ASSERT_EQ(delegate(other, 1), 4); + + delegate.connect<&sum_with_ptr>(&value); + + ASSERT_TRUE(delegate); + ASSERT_EQ(delegate(other, 2), 5); } TEST(Delegate, Constructors) { @@ -358,9 +390,9 @@ TEST(Delegate, Constructors) { const auto value = 2; entt::delegate empty{}; - entt::delegate func{entt::connect_arg<&delegate_function>}; - entt::delegate ref{entt::connect_arg<&curried_by_ref>, value}; - entt::delegate ptr{entt::connect_arg<&curried_by_ptr>, &value}; + entt::delegate func{entt::connect_arg<&power_of_two>}; + entt::delegate ref{entt::connect_arg<&sum_with_ref>, value}; + entt::delegate ptr{entt::connect_arg<&sum_with_ptr>, &value}; entt::delegate member{entt::connect_arg<&delegate_functor::operator()>, functor}; ASSERT_FALSE(empty); @@ -372,7 +404,7 @@ TEST(Delegate, Constructors) { ASSERT_EQ(5, ref(3)); ASSERT_TRUE(ptr); - ASSERT_EQ(6, ptr(3)); + ASSERT_EQ(5, ptr(3)); ASSERT_TRUE(member); ASSERT_EQ(6, member(3)); @@ -381,7 +413,7 @@ TEST(Delegate, Constructors) { TEST(Delegate, VoidVsNonVoidReturnType) { delegate_functor functor; - entt::delegate func{entt::connect_arg<&delegate_function>}; + entt::delegate func{entt::connect_arg<&power_of_two>}; entt::delegate member{entt::connect_arg<&delegate_functor::operator()>, &functor}; entt::delegate cmember{entt::connect_arg<&delegate_functor::identity>, &std::as_const(functor)}; @@ -411,8 +443,8 @@ TEST(Delegate, TheLessTheBetter) { entt::delegate unbound; delegate_functor functor; - // int delegate_function(const int &); - bound.connect<&delegate_function>(); + // int power_of_two(const int &); + bound.connect<&power_of_two>(); ASSERT_EQ(bound(3, 'c'), 9); diff --git a/test/entt/signal/sigh.cpp b/test/entt/signal/sigh.cpp index 1097fc4f..0a4f96fd 100644 --- a/test/entt/signal/sigh.cpp +++ b/test/entt/signal/sigh.cpp @@ -25,32 +25,6 @@ struct sigh_listener { bool k{false}; }; -struct before_after { - void add(int v) { - value += v; - } - - void mul(int v) { - value *= v; - } - - static void static_add(int v) { - before_after::value += v; - } - - static void static_mul(before_after &instance, int v) { - instance.value *= v; - } - - static inline int value{}; -}; - -struct SigH: ::testing::Test { - void SetUp() override { - before_after::value = 0; - } -}; - struct const_nonconst_noexcept { void f() { ++cnt; @@ -71,7 +45,13 @@ struct const_nonconst_noexcept { mutable int cnt{0}; }; -TEST_F(SigH, Lifetime) { +void connect_and_auto_disconnect(entt::sigh &sigh, const int &) { + entt::sink sink{sigh}; + sink.connect(); + sink.disconnect<&connect_and_auto_disconnect>(sigh); +} + +TEST(SigH, Lifetime) { using signal = entt::sigh; ASSERT_NO_FATAL_FAILURE(signal{}); @@ -86,7 +66,7 @@ TEST_F(SigH, Lifetime) { ASSERT_NO_FATAL_FAILURE(delete new signal{}); } -TEST_F(SigH, Clear) { +TEST(SigH, Clear) { entt::sigh sigh; entt::sink sink{sigh}; @@ -106,7 +86,7 @@ TEST_F(SigH, Clear) { ASSERT_TRUE(sigh.empty()); } -TEST_F(SigH, Swap) { +TEST(SigH, Swap) { entt::sigh sigh1; entt::sigh sigh2; entt::sink sink1{sigh1}; @@ -129,7 +109,7 @@ TEST_F(SigH, Swap) { ASSERT_FALSE(sigh2.empty()); } -TEST_F(SigH, Functions) { +TEST(SigH, Functions) { entt::sigh sigh; entt::sink sink{sigh}; int v = 0; @@ -150,7 +130,7 @@ TEST_F(SigH, Functions) { ASSERT_EQ(v, 0); } -TEST_F(SigH, FunctionsWithPayload) { +TEST(SigH, FunctionsWithPayload) { entt::sigh sigh; entt::sink sink{sigh}; int v = 0; @@ -171,13 +151,13 @@ TEST_F(SigH, FunctionsWithPayload) { ASSERT_EQ(v, 0); sink.connect<&sigh_listener::f>(v); - sink.disconnect(v); + sink.disconnect(&v); sigh.publish(); ASSERT_EQ(v, 0); } -TEST_F(SigH, Members) { +TEST(SigH, Members) { sigh_listener l1, l2; entt::sigh sigh; entt::sink sink{sigh}; @@ -213,7 +193,7 @@ TEST_F(SigH, Members) { ASSERT_EQ(1u, sigh.size()); } -TEST_F(SigH, Collector) { +TEST(SigH, Collector) { sigh_listener listener; entt::sigh sigh; entt::sink sink{sigh}; @@ -247,7 +227,7 @@ TEST_F(SigH, Collector) { ASSERT_EQ(cnt, 1); } -TEST_F(SigH, CollectorVoid) { +TEST(SigH, CollectorVoid) { sigh_listener listener; entt::sigh sigh; entt::sink sink{sigh}; @@ -271,7 +251,7 @@ TEST_F(SigH, CollectorVoid) { ASSERT_EQ(cnt, 1); } -TEST_F(SigH, Connection) { +TEST(SigH, Connection) { entt::sigh sigh; entt::sink sink{sigh}; int v = 0; @@ -292,7 +272,7 @@ TEST_F(SigH, Connection) { ASSERT_EQ(0, v); } -TEST_F(SigH, ScopedConnection) { +TEST(SigH, ScopedConnection) { sigh_listener listener; entt::sigh sigh; entt::sink sink{sigh}; @@ -314,7 +294,7 @@ TEST_F(SigH, ScopedConnection) { ASSERT_TRUE(listener.k); } -TEST_F(SigH, ScopedConnectionMove) { +TEST(SigH, ScopedConnectionMove) { sigh_listener listener; entt::sigh sigh; entt::sink sink{sigh}; @@ -363,7 +343,7 @@ TEST_F(SigH, ScopedConnectionMove) { ASSERT_TRUE(sigh.empty()); } -TEST_F(SigH, ScopedConnectionConstructorsAndOperators) { +TEST(SigH, ScopedConnectionConstructorsAndOperators) { sigh_listener listener; entt::sigh sigh; entt::sink sink{sigh}; @@ -402,7 +382,7 @@ TEST_F(SigH, ScopedConnectionConstructorsAndOperators) { ASSERT_FALSE(listener.k); } -TEST_F(SigH, ConstNonConstNoExcept) { +TEST(SigH, ConstNonConstNoExcept) { entt::sigh sigh; entt::sink sink{sigh}; const_nonconst_noexcept functor; @@ -427,83 +407,7 @@ TEST_F(SigH, ConstNonConstNoExcept) { ASSERT_EQ(cfunctor.cnt, 2); } -TEST_F(SigH, BeforeFunction) { - entt::sigh sigh; - entt::sink sink{sigh}; - before_after functor; - - sink.connect<&before_after::add>(functor); - sink.connect<&before_after::static_add>(); - sink.before<&before_after::static_add>().connect<&before_after::mul>(functor); - sigh.publish(2); - - ASSERT_EQ(functor.value, 6); -} - -TEST_F(SigH, BeforeMemberFunction) { - entt::sigh sigh; - entt::sink sink{sigh}; - before_after functor; - - sink.connect<&before_after::static_add>(); - sink.connect<&before_after::add>(functor); - sink.before<&before_after::add>(functor).connect<&before_after::mul>(functor); - sigh.publish(2); - - ASSERT_EQ(functor.value, 6); -} - -TEST_F(SigH, BeforeFunctionWithPayload) { - entt::sigh sigh; - entt::sink sink{sigh}; - before_after functor; - - sink.connect<&before_after::static_add>(); - sink.connect<&before_after::static_mul>(functor); - sink.before<&before_after::static_mul>(functor).connect<&before_after::add>(functor); - sigh.publish(2); - - ASSERT_EQ(functor.value, 8); -} - -TEST_F(SigH, BeforeInstanceOrPayload) { - entt::sigh sigh; - entt::sink sink{sigh}; - before_after functor; - - sink.connect<&before_after::static_mul>(functor); - sink.connect<&before_after::add>(functor); - sink.before(functor).connect<&before_after::static_add>(); - sigh.publish(2); - - ASSERT_EQ(functor.value, 6); -} - -TEST_F(SigH, BeforeAnythingElse) { - entt::sigh sigh; - entt::sink sink{sigh}; - before_after functor; - - sink.connect<&before_after::add>(functor); - sink.before().connect<&before_after::mul>(functor); - sigh.publish(2); - - ASSERT_EQ(functor.value, 2); -} - -TEST_F(SigH, BeforeListenerNotPresent) { - entt::sigh sigh; - entt::sink sink{sigh}; - before_after functor; - - sink.connect<&before_after::mul>(functor); - sink.before<&before_after::add>(&functor).connect<&before_after::add>(functor); - sigh.publish(2); - - ASSERT_EQ(functor.value, 2); -} - -TEST_F(SigH, UnboundDataMember) { +TEST(SigH, UnboundDataMember) { sigh_listener listener; entt::sigh sigh; entt::sink sink{sigh}; @@ -516,7 +420,7 @@ TEST_F(SigH, UnboundDataMember) { ASSERT_TRUE(listener.k); } -TEST_F(SigH, UnboundMemberFunction) { +TEST(SigH, UnboundMemberFunction) { sigh_listener listener; entt::sigh sigh; entt::sink sink{sigh}; @@ -529,7 +433,33 @@ TEST_F(SigH, UnboundMemberFunction) { ASSERT_TRUE(listener.k); } -TEST_F(SigH, CustomAllocator) { +TEST(SigH, ConnectAndAutoDisconnect) { + sigh_listener listener; + entt::sigh sigh; + entt::sink sink{sigh}; + int v = 0; + + sink.connect<&sigh_listener::g>(listener); + sink.connect<&connect_and_auto_disconnect>(sigh); + + ASSERT_FALSE(listener.k); + ASSERT_EQ(sigh.size(), 2u); + ASSERT_EQ(v, 0); + + sigh.publish(v); + + ASSERT_TRUE(listener.k); + ASSERT_EQ(sigh.size(), 2u); + ASSERT_EQ(v, 0); + + sigh.publish(v); + + ASSERT_FALSE(listener.k); + ASSERT_EQ(sigh.size(), 2u); + ASSERT_EQ(v, 42); +} + +TEST(SigH, CustomAllocator) { std::allocator allocator; entt::sigh sigh{allocator}; @@ -542,7 +472,7 @@ TEST_F(SigH, CustomAllocator) { sink.template connect<&sigh_listener::g>(listener); decltype(sigh) copy{sigh, allocator}; - sink.disconnect(listener); + sink.disconnect(&listener); ASSERT_TRUE(sigh.empty()); ASSERT_FALSE(copy.empty()); diff --git a/test/example/entity_copy.cpp b/test/example/entity_copy.cpp index d4157b19..2c678aaf 100644 --- a/test/example/entity_copy.cpp +++ b/test/example/entity_copy.cpp @@ -1,10 +1,48 @@ #include #include +#include #include +#include +#include +#include enum class my_entity : entt::id_type {}; -TEST(Example, EntityCopy) { +template +struct meta_mixin: Type { + using allocator_type = typename Type::allocator_type; + using value_type = typename Type::value_type; + + explicit meta_mixin(const allocator_type &allocator); +}; + +template +struct entt::storage_type { + using type = meta_mixin>; +}; + +template +meta_mixin::meta_mixin(const allocator_type &allocator) + : Type{allocator} { + using namespace entt::literals; + + entt::meta() + // cross registry, same type + .template func &(const entt::id_type)>(&entt::basic_registry::storage), entt::as_ref_t>("storage"_hs) + // cross registry, different types + .template func &(const entt::id_type)>(&entt::basic_registry::storage), entt::as_ref_t>("storage"_hs); +} + +template +struct EntityCopy: testing::Test { + using type = Type; +}; + +using EntityCopyTypes = ::testing::Types, entt::basic_registry>; + +TYPED_TEST_SUITE(EntityCopy, EntityCopyTypes, ); + +TEST(EntityCopy, SameRegistry) { using namespace entt::literals; entt::registry registry{}; @@ -12,52 +50,40 @@ TEST(Example, EntityCopy) { const auto src = registry.create(); const auto dst = registry.create(); - const auto other = registry.create(); custom.emplace(src, 1.); registry.emplace(src, 42); registry.emplace(src, 'c'); - registry.emplace(other, 3.); + ASSERT_EQ(registry.size(), 2u); ASSERT_TRUE(custom.contains(src)); - ASSERT_FALSE(registry.all_of(src)); - ASSERT_TRUE((registry.all_of(src))); - ASSERT_FALSE((registry.any_of(dst))); ASSERT_FALSE(custom.contains(dst)); + ASSERT_TRUE((registry.all_of(src))); + ASSERT_FALSE((registry.any_of(dst))); for(auto [id, storage]: registry.storage()) { // discard the custom storage because why not, this is just an example after all if(id != "custom"_hs && storage.contains(src)) { - storage.emplace(dst, storage.get(src)); + storage.push(dst, storage.value(src)); } } - ASSERT_TRUE((registry.all_of(dst))); - ASSERT_FALSE((registry.all_of(dst))); + ASSERT_EQ(registry.size(), 2u); + ASSERT_TRUE(custom.contains(src)); ASSERT_FALSE(custom.contains(dst)); + ASSERT_TRUE((registry.all_of(src))); + ASSERT_TRUE((registry.all_of(dst))); ASSERT_EQ(registry.get(dst), 42); ASSERT_EQ(registry.get(dst), 'c'); } -TEST(Example, DifferentRegistryTypes) { +TYPED_TEST(EntityCopy, CrossRegistry) { using namespace entt::literals; entt::basic_registry src{}; - entt::basic_registry dst{}; - - /* - TODO These are currently needed to ensure that the source and - target registries have the proper storage initialized - prior to copying, as this isn't done automatically - when emplacing storages (as is done below). - - There is an open issue about this, and these two - lines should be removed when a fix is properly landed. - https://github.com/skypjack/entt/issues/827 - */ - static_cast(src.storage()); - static_cast(dst.storage()); + // other registry type, see typed test suite + typename TestFixture::type dst{}; const auto entity = src.create(); const auto copy = dst.create(); @@ -65,14 +91,31 @@ TEST(Example, DifferentRegistryTypes) { src.emplace(entity, 42); src.emplace(entity, 'c'); + ASSERT_EQ(src.size(), 1u); + ASSERT_EQ(dst.size(), 1u); + + ASSERT_TRUE((src.all_of(entity))); + ASSERT_FALSE((dst.template all_of(copy))); + for(auto [id, storage]: src.storage()) { - if(auto *other = dst.storage(id); other && storage.contains(entity)) { - other->emplace(copy, storage.get(entity)); + if(storage.contains(entity)) { + auto *other = dst.storage(id); + + if(!other) { + using namespace entt::literals; + entt::resolve(storage.type()).invoke("storage"_hs, {}, entt::forward_as_meta(dst), id); + other = dst.storage(id); + } + + other->push(copy, storage.value(entity)); } } + ASSERT_EQ(src.size(), 1u); + ASSERT_EQ(dst.size(), 1u); + ASSERT_TRUE((src.all_of(entity))); - ASSERT_FALSE(dst.all_of(copy)); - ASSERT_TRUE(dst.all_of(copy)); - ASSERT_EQ(dst.get(copy), 42); + ASSERT_TRUE((dst.template all_of(copy))); + ASSERT_EQ(dst.template get(copy), 42); + ASSERT_EQ(dst.template get(copy), 'c'); } diff --git a/test/example/signal_less.cpp b/test/example/signal_less.cpp index 4c38f460..19efe3f9 100644 --- a/test/example/signal_less.cpp +++ b/test/example/signal_less.cpp @@ -13,7 +13,7 @@ struct entt::storage_type { template struct entt::storage_type { // ... unless it's char, because yes. - using type = sigh_storage_mixin>; + using type = sigh_mixin>; }; template diff --git a/test/lib/dispatcher/common/types.h b/test/lib/dispatcher/common/types.h new file mode 100644 index 00000000..68e11a04 --- /dev/null +++ b/test/lib/dispatcher/common/types.h @@ -0,0 +1,10 @@ +#ifndef ENTT_LIB_DISPATCHER_COMMON_TYPES_H +#define ENTT_LIB_DISPATCHER_COMMON_TYPES_H + +struct message { + int payload; +}; + +struct event {}; + +#endif diff --git a/test/lib/dispatcher_plugin/main.cpp b/test/lib/dispatcher/plugin/main.cpp similarity index 95% rename from test/lib/dispatcher_plugin/main.cpp rename to test/lib/dispatcher/plugin/main.cpp index 1192faa8..67de4e03 100644 --- a/test/lib/dispatcher_plugin/main.cpp +++ b/test/lib/dispatcher/plugin/main.cpp @@ -4,7 +4,7 @@ #include #include #include -#include "types.h" +#include "../common/types.h" struct listener { void on(message msg) { diff --git a/test/lib/dispatcher_plugin/plugin.cpp b/test/lib/dispatcher/plugin/plugin.cpp similarity index 94% rename from test/lib/dispatcher_plugin/plugin.cpp rename to test/lib/dispatcher/plugin/plugin.cpp index f9afafe8..3b2a285a 100644 --- a/test/lib/dispatcher_plugin/plugin.cpp +++ b/test/lib/dispatcher/plugin/plugin.cpp @@ -1,6 +1,6 @@ #include #include -#include "types.h" +#include "../common/types.h" CR_EXPORT int cr_main(cr_plugin *ctx, cr_op operation) { switch(operation) { diff --git a/test/lib/dispatcher/lib.cpp b/test/lib/dispatcher/shared/lib.cpp similarity index 87% rename from test/lib/dispatcher/lib.cpp rename to test/lib/dispatcher/shared/lib.cpp index 2a579c23..b9ff3994 100644 --- a/test/lib/dispatcher/lib.cpp +++ b/test/lib/dispatcher/shared/lib.cpp @@ -1,6 +1,6 @@ #include #include -#include "types.h" +#include "../common/types.h" ENTT_API void trigger(entt::dispatcher &dispatcher) { dispatcher.trigger(); diff --git a/test/lib/dispatcher/main.cpp b/test/lib/dispatcher/shared/main.cpp similarity index 95% rename from test/lib/dispatcher/main.cpp rename to test/lib/dispatcher/shared/main.cpp index edffe256..82ef817d 100644 --- a/test/lib/dispatcher/main.cpp +++ b/test/lib/dispatcher/shared/main.cpp @@ -3,7 +3,7 @@ #include #include #include -#include "types.h" +#include "../common/types.h" ENTT_API void trigger(entt::dispatcher &); diff --git a/test/lib/dispatcher/types.h b/test/lib/dispatcher/types.h deleted file mode 100644 index d130f423..00000000 --- a/test/lib/dispatcher/types.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef ENTT_LIB_DISPATCHER_TYPES_H -#define ENTT_LIB_DISPATCHER_TYPES_H - -#include - -struct ENTT_API message { - int payload; -}; - -struct ENTT_API event {}; - -#endif diff --git a/test/lib/dispatcher_plugin/types.h b/test/lib/dispatcher_plugin/types.h deleted file mode 100644 index b0707106..00000000 --- a/test/lib/dispatcher_plugin/types.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef ENTT_LIB_DISPATCHER_PLUGIN_TYPES_H -#define ENTT_LIB_DISPATCHER_PLUGIN_TYPES_H - -struct message { - int payload; -}; - -struct event {}; - -#endif diff --git a/test/lib/emitter_plugin/types.h b/test/lib/emitter/common/types.h similarity index 66% rename from test/lib/emitter_plugin/types.h rename to test/lib/emitter/common/types.h index a4c78244..a2427f1b 100644 --- a/test/lib/emitter_plugin/types.h +++ b/test/lib/emitter/common/types.h @@ -1,5 +1,5 @@ -#ifndef ENTT_LIB_EMITTER_PLUGIN_TYPES_H -#define ENTT_LIB_EMITTER_PLUGIN_TYPES_H +#ifndef ENTT_LIB_EMITTER_COMMON_TYPES_H +#define ENTT_LIB_EMITTER_COMMON_TYPES_H #include diff --git a/test/lib/emitter_plugin/main.cpp b/test/lib/emitter/plugin/main.cpp similarity index 94% rename from test/lib/emitter_plugin/main.cpp rename to test/lib/emitter/plugin/main.cpp index 2d509bc9..285c7096 100644 --- a/test/lib/emitter_plugin/main.cpp +++ b/test/lib/emitter/plugin/main.cpp @@ -2,7 +2,7 @@ #include #include -#include "types.h" +#include "../common/types.h" TEST(Lib, Emitter) { test_emitter emitter; diff --git a/test/lib/emitter_plugin/plugin.cpp b/test/lib/emitter/plugin/plugin.cpp similarity index 94% rename from test/lib/emitter_plugin/plugin.cpp rename to test/lib/emitter/plugin/plugin.cpp index 4b6a95d5..6946872e 100644 --- a/test/lib/emitter_plugin/plugin.cpp +++ b/test/lib/emitter/plugin/plugin.cpp @@ -1,5 +1,5 @@ #include -#include "types.h" +#include "../common/types.h" CR_EXPORT int cr_main(cr_plugin *ctx, cr_op operation) { switch(operation) { diff --git a/test/lib/emitter/lib.cpp b/test/lib/emitter/shared/lib.cpp similarity index 85% rename from test/lib/emitter/lib.cpp rename to test/lib/emitter/shared/lib.cpp index 69575155..529db0b7 100644 --- a/test/lib/emitter/lib.cpp +++ b/test/lib/emitter/shared/lib.cpp @@ -1,5 +1,5 @@ #include -#include "types.h" +#include "../common/types.h" ENTT_API void emit(test_emitter &emitter) { emitter.publish(event{}); diff --git a/test/lib/emitter/main.cpp b/test/lib/emitter/shared/main.cpp similarity index 92% rename from test/lib/emitter/main.cpp rename to test/lib/emitter/shared/main.cpp index 6ed72810..40e987fe 100644 --- a/test/lib/emitter/main.cpp +++ b/test/lib/emitter/shared/main.cpp @@ -1,6 +1,6 @@ #include #include -#include "types.h" +#include "../common/types.h" ENTT_API void emit(test_emitter &); diff --git a/test/lib/emitter/types.h b/test/lib/emitter/types.h deleted file mode 100644 index ec2c133b..00000000 --- a/test/lib/emitter/types.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef ENTT_LIB_EMITTER_TYPES_H -#define ENTT_LIB_EMITTER_TYPES_H - -#include -#include - -struct ENTT_API test_emitter - : entt::emitter {}; - -struct ENTT_API message { - int payload; -}; - -struct ENTT_API event {}; - -#endif diff --git a/test/lib/locator/common/types.h b/test/lib/locator/common/types.h new file mode 100644 index 00000000..ef299929 --- /dev/null +++ b/test/lib/locator/common/types.h @@ -0,0 +1,8 @@ +#ifndef ENTT_LIB_LOCATOR_COMMON_TYPES_H +#define ENTT_LIB_LOCATOR_COMMON_TYPES_H + +struct service { + int value; +}; + +#endif diff --git a/test/lib/locator_plugin/main.cpp b/test/lib/locator/plugin/main.cpp similarity index 88% rename from test/lib/locator_plugin/main.cpp rename to test/lib/locator/plugin/main.cpp index bbad4018..12940fb4 100644 --- a/test/lib/locator_plugin/main.cpp +++ b/test/lib/locator/plugin/main.cpp @@ -3,6 +3,7 @@ #include #include #include +#include "../common/types.h" #include "types.h" TEST(Lib, Locator) { @@ -10,9 +11,7 @@ TEST(Lib, Locator) { ASSERT_EQ(entt::locator::value().value, 42); - userdata ud{}; - ud.handle = entt::locator::handle(); - ud.value = 3; + userdata ud{entt::locator::handle(), 3}; cr_plugin ctx; ctx.userdata = &ud; diff --git a/test/lib/locator_plugin/plugin.cpp b/test/lib/locator/plugin/plugin.cpp similarity index 94% rename from test/lib/locator_plugin/plugin.cpp rename to test/lib/locator/plugin/plugin.cpp index fcccdfaa..22fdc0f7 100644 --- a/test/lib/locator_plugin/plugin.cpp +++ b/test/lib/locator/plugin/plugin.cpp @@ -1,5 +1,6 @@ #include #include +#include "../common/types.h" #include "types.h" CR_EXPORT int cr_main(cr_plugin *ctx, cr_op operation) { diff --git a/test/lib/locator_plugin/types.h b/test/lib/locator/plugin/types.h similarity index 56% rename from test/lib/locator_plugin/types.h rename to test/lib/locator/plugin/types.h index d91a31c2..78370a05 100644 --- a/test/lib/locator_plugin/types.h +++ b/test/lib/locator/plugin/types.h @@ -3,13 +3,10 @@ #include -struct service { - int value; -}; +struct service; struct userdata { - using node_type = typename entt::locator::node_type; - node_type handle; + typename entt::locator::node_type handle; int value; }; diff --git a/test/lib/locator/lib.cpp b/test/lib/locator/shared/lib.cpp similarity index 90% rename from test/lib/locator/lib.cpp rename to test/lib/locator/shared/lib.cpp index 4c045933..e1d2d84c 100644 --- a/test/lib/locator/lib.cpp +++ b/test/lib/locator/shared/lib.cpp @@ -1,6 +1,6 @@ #include #include -#include "types.h" +#include "../common/types.h" ENTT_API void set_up(const entt::locator::node_type &handle) { entt::locator::reset(handle); diff --git a/test/lib/locator/main.cpp b/test/lib/locator/shared/main.cpp similarity index 95% rename from test/lib/locator/main.cpp rename to test/lib/locator/shared/main.cpp index ce42df32..3c34f5b6 100644 --- a/test/lib/locator/main.cpp +++ b/test/lib/locator/shared/main.cpp @@ -1,7 +1,7 @@ #include #include #include -#include "types.h" +#include "../common/types.h" ENTT_API void set_up(const entt::locator::node_type &); ENTT_API void use_service(int); diff --git a/test/lib/locator/types.h b/test/lib/locator/types.h deleted file mode 100644 index 69e17d2e..00000000 --- a/test/lib/locator/types.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef ENTT_LIB_LOCATOR_TYPES_H -#define ENTT_LIB_LOCATOR_TYPES_H - -struct service { - int value; -}; - -#endif diff --git a/test/lib/meta/types.h b/test/lib/meta/common/types.h similarity index 58% rename from test/lib/meta/types.h rename to test/lib/meta/common/types.h index ad2a8129..b09a5b83 100644 --- a/test/lib/meta/types.h +++ b/test/lib/meta/common/types.h @@ -1,5 +1,5 @@ -#ifndef ENTT_LIB_META_TYPES_H -#define ENTT_LIB_META_TYPES_H +#ifndef ENTT_LIB_META_COMMON_TYPES_H +#define ENTT_LIB_META_COMMON_TYPES_H struct position { int x; diff --git a/test/lib/meta_plugin/main.cpp b/test/lib/meta/plugin/main.cpp similarity index 95% rename from test/lib/meta_plugin/main.cpp rename to test/lib/meta/plugin/main.cpp index 88c78667..ec8aa310 100644 --- a/test/lib/meta_plugin/main.cpp +++ b/test/lib/meta/plugin/main.cpp @@ -14,8 +14,7 @@ TEST(Lib, Meta) { ASSERT_FALSE(entt::resolve("position"_hs)); - userdata ud{}; - ud.ctx = entt::locator::handle(); + userdata ud{entt::locator::handle(), entt::meta_any{}}; cr_plugin ctx; ctx.userdata = &ud; diff --git a/test/lib/meta_plugin/plugin.cpp b/test/lib/meta/plugin/plugin.cpp similarity index 97% rename from test/lib/meta_plugin/plugin.cpp rename to test/lib/meta/plugin/plugin.cpp index 52ce1bee..f3b81330 100644 --- a/test/lib/meta_plugin/plugin.cpp +++ b/test/lib/meta/plugin/plugin.cpp @@ -4,6 +4,7 @@ #include #include #include +#include "../common/types.h" #include "types.h" position create_position(int x, int y) { diff --git a/test/lib/meta_plugin/types.h b/test/lib/meta/plugin/types.h similarity index 71% rename from test/lib/meta_plugin/types.h rename to test/lib/meta/plugin/types.h index d4a0a4eb..3dc4ed15 100644 --- a/test/lib/meta_plugin/types.h +++ b/test/lib/meta/plugin/types.h @@ -4,16 +4,6 @@ #include #include -struct position { - int x; - int y; -}; - -struct velocity { - double dx; - double dy; -}; - struct userdata { entt::locator::node_type ctx; entt::meta_any any; diff --git a/test/lib/meta_plugin_std/main.cpp b/test/lib/meta/plugin_std/main.cpp similarity index 100% rename from test/lib/meta_plugin_std/main.cpp rename to test/lib/meta/plugin_std/main.cpp diff --git a/test/lib/meta_plugin_std/plugin.cpp b/test/lib/meta/plugin_std/plugin.cpp similarity index 97% rename from test/lib/meta_plugin_std/plugin.cpp rename to test/lib/meta/plugin_std/plugin.cpp index 77d60f21..2462f766 100644 --- a/test/lib/meta_plugin_std/plugin.cpp +++ b/test/lib/meta/plugin_std/plugin.cpp @@ -3,6 +3,7 @@ #include #include #include +#include "../common/types.h" #include "types.h" position create_position(int x, int y) { diff --git a/test/lib/meta_plugin_std/types.h b/test/lib/meta/plugin_std/types.h similarity index 80% rename from test/lib/meta_plugin_std/types.h rename to test/lib/meta/plugin_std/types.h index 67d6b7a8..cfbc0cbb 100644 --- a/test/lib/meta_plugin_std/types.h +++ b/test/lib/meta/plugin_std/types.h @@ -1,5 +1,5 @@ -#ifndef ENTT_LIB_META_PLUGIN_TYPES_STD_H -#define ENTT_LIB_META_PLUGIN_TYPES_STD_H +#ifndef ENTT_LIB_META_PLUGIN_STD_TYPES_H +#define ENTT_LIB_META_PLUGIN_STD_TYPES_H #include #include @@ -17,15 +17,8 @@ struct custom_type_hash; } \ } -struct position { - int x; - int y; -}; - -struct velocity { - double dx; - double dy; -}; +struct position; +struct velocity; struct userdata { entt::locator::node_type ctx; diff --git a/test/lib/meta/lib.cpp b/test/lib/meta/shared/lib.cpp similarity index 96% rename from test/lib/meta/lib.cpp rename to test/lib/meta/shared/lib.cpp index 518903c8..ab338ad4 100644 --- a/test/lib/meta/lib.cpp +++ b/test/lib/meta/shared/lib.cpp @@ -2,7 +2,7 @@ #include #include #include -#include "types.h" +#include "../common/types.h" position create_position(int x, int y) { return position{x, y}; diff --git a/test/lib/meta/main.cpp b/test/lib/meta/shared/main.cpp similarity index 98% rename from test/lib/meta/main.cpp rename to test/lib/meta/shared/main.cpp index c8eadf45..81d3162e 100644 --- a/test/lib/meta/main.cpp +++ b/test/lib/meta/shared/main.cpp @@ -4,7 +4,7 @@ #include #include #include -#include "types.h" +#include "../common/types.h" ENTT_API void share(entt::locator::node_type); ENTT_API void set_up(); diff --git a/test/lib/registry_plugin/types.h b/test/lib/registry/common/types.h similarity index 55% rename from test/lib/registry_plugin/types.h rename to test/lib/registry/common/types.h index 2e0c14e4..b2bdaf2e 100644 --- a/test/lib/registry_plugin/types.h +++ b/test/lib/registry/common/types.h @@ -1,5 +1,5 @@ -#ifndef ENTT_LIB_REGISTRY_PLUGIN_TYPES_H -#define ENTT_LIB_REGISTRY_PLUGIN_TYPES_H +#ifndef ENTT_LIB_REGISTRY_COMMON_TYPES_H +#define ENTT_LIB_REGISTRY_COMMON_TYPES_H struct position { int x; diff --git a/test/lib/registry_plugin/main.cpp b/test/lib/registry/plugin/main.cpp similarity index 96% rename from test/lib/registry_plugin/main.cpp rename to test/lib/registry/plugin/main.cpp index dbc04ce1..66be51e5 100644 --- a/test/lib/registry_plugin/main.cpp +++ b/test/lib/registry/plugin/main.cpp @@ -3,7 +3,7 @@ #include #include #include -#include "types.h" +#include "../common/types.h" TEST(Lib, Registry) { entt::registry registry; diff --git a/test/lib/registry_plugin/plugin.cpp b/test/lib/registry/plugin/plugin.cpp similarity index 96% rename from test/lib/registry_plugin/plugin.cpp rename to test/lib/registry/plugin/plugin.cpp index e9c7d9fd..dafc06ff 100644 --- a/test/lib/registry_plugin/plugin.cpp +++ b/test/lib/registry/plugin/plugin.cpp @@ -1,6 +1,6 @@ #include #include -#include "types.h" +#include "../common/types.h" CR_EXPORT int cr_main(cr_plugin *ctx, cr_op operation) { switch(operation) { diff --git a/test/lib/registry/lib.cpp b/test/lib/registry/shared/lib.cpp similarity index 88% rename from test/lib/registry/lib.cpp rename to test/lib/registry/shared/lib.cpp index 49028fc7..96189fda 100644 --- a/test/lib/registry/lib.cpp +++ b/test/lib/registry/shared/lib.cpp @@ -1,6 +1,8 @@ #include #include -#include "types.h" +#include "../common/types.h" + +template class entt::basic_registry; ENTT_API void update_position(entt::registry ®istry) { registry.view().each([](auto &pos, auto &vel) { diff --git a/test/lib/registry/main.cpp b/test/lib/registry/shared/main.cpp similarity index 96% rename from test/lib/registry/main.cpp rename to test/lib/registry/shared/main.cpp index afd2e8d5..3aa8c0d1 100644 --- a/test/lib/registry/main.cpp +++ b/test/lib/registry/shared/main.cpp @@ -2,7 +2,7 @@ #include #include #include -#include "types.h" +#include "../common/types.h" ENTT_API void update_position(entt::registry &); ENTT_API void emplace_velocity(entt::registry &); diff --git a/test/lib/registry/types.h b/test/lib/registry/types.h deleted file mode 100644 index 6f126a84..00000000 --- a/test/lib/registry/types.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef ENTT_LIB_REGISTRY_TYPES_H -#define ENTT_LIB_REGISTRY_TYPES_H - -#include - -struct ENTT_API position { - int x; - int y; -}; - -struct ENTT_API velocity { - double dx; - double dy; -}; - -#endif diff --git a/test/snapshot/snapshot.cpp b/test/snapshot/snapshot.cpp index ac1d6ee0..0ca9fb2e 100644 --- a/test/snapshot/snapshot.cpp +++ b/test/snapshot/snapshot.cpp @@ -64,11 +64,21 @@ TEST(Snapshot, Full) { { // output finishes flushing its contents when it goes out of scope cereal::JSONOutputArchive output{storage}; - entt::snapshot{source}.entities(output).component>(output); + entt::snapshot{source} + .entities(output) + .component(output) + .component(output) + .component(output) + .component>(output); } cereal::JSONInputArchive input{storage}; - entt::snapshot_loader{destination}.entities(input).component>(input); + entt::snapshot_loader{destination} + .entities(input) + .component(input) + .component(input) + .component(input) + .component>(input); ASSERT_TRUE(destination.valid(e0)); ASSERT_TRUE(destination.all_of(e0)); @@ -129,14 +139,22 @@ TEST(Snapshot, Continuous) { { // output finishes flushing its contents when it goes out of scope cereal::JSONOutputArchive output{storage}; - entt::snapshot{source}.entities(output).component>(output); + entt::snapshot{source} + .component(output) + .component(output) + .component(output) + .component(output) + .component>(output); } cereal::JSONInputArchive input{storage}; entt::continuous_loader loader{destination}; - loader.entities(input) - .component(input, &relationship::parent) - .component>(input); + loader + .entities(input) + .component(input) + .component(input, &relationship::parent) + .component(input) + .component>(input); ASSERT_FALSE(destination.valid(e0)); ASSERT_TRUE(loader.contains(e0));