Squashed 'external/entt/entt/' changes from fef921132..344e03ac6

344e03ac6 update single include file to v3.12.2
da56665b0 registry: make ::valid backward compatible
f6f01ef1b snapshot: avoid warnings due to deprecated functions
0ed514628 now working on v3.12.2
a41421d86 update single include file to v3.12.1
c1f6b11f7 snapshot: reintroduce support to storage listeners
b2233064a now working on version v3.12.1
cb974bf56 adjacency_matrix: fix in_edges() is off by 1 in some cases (close #1019)
7b7d82e6f doc: snapshot (close #984)
05c6898fc test: self-fixing archive example for snapshot classes
7ffa459a6 snapshot: drop ::get member template parameter
93e8e94e6 test: basic continuous loader
c4e241662 snapshot: review basic_continuous_loader (and drop shrink)
9c25419b9 test: more on basic_snapshot_loader
1879830df snapshot: drop pointless assert
29298c0eb test: guarantee code coverage, we'll update the test later on
247abef1d test: rollback for code coverage purposes on the snapshot class
6994d98d2 test: typo
9a600ece2 test: snapshot
f91226ef4 snapshot: share ::orphans implementation (to deprecate in future though)
e366ffbd3 doc: snapshot
63b300d39 snapshot: again, dense_map::contains is a thing
afb70d157 test: avoid warnings due to unused variables
49534eec0 snapshot: dense_map::contains is a thing fortunately
3f1277f7b snapshot: use the right allocator for the remote-local mapping
26fad4c38 test: basic snapshot loader
25b3afacf test: basic snapshot
2d25bbb09 snapshot: check registry type
0eb834582 snapshot: small cleanup
124a44052 test: use the new snapshot get functions in the test suite
5c704636e test: use the new snapshot get functions in the test suite
31fd94cc3 snapshot: cleanup to get ready to drop an internal function
573e43272 snapshot: reduce storage lookups
1d8943481 snapshot: drop useless function
e0a1ef7c1 snapshot: check on member type class
48ac0e0eb snapshot: add basic_continuous_loader::get, deprecate ::entities and ::component
bcb6234d9 snapshot: add basic_snapshot_loader::get, deprecate ::entities and ::component
f96796326 snapshot: reject entity type in the range-get (now get instead of get_sparse)
b22c55dd2 doc: typo
4ff5a536c snapshot: add basic_snapshot::get, deprecate ::entities and ::component
fff5f578a test: avoid using deprecated functions in an example
0f44c8c92 doc: reflect recent changes
0b6ad0315 snapshot: * single element only archive functions required * avoid iterating elements more than once
2450b0bc6 test: minor changes (waiting for a rework)
fc8eebf36 snapshot: use component_traits instead of is_empty_v
e4f51f2b7 snapshot: avoid multiple lookups of the same storage
2c2216a89 doc: typo
cafe85180 snapshot: deprecate multi-type component loading function
35e338cc9 snapshot: deprecate multi-type component loading function
8feeaaef7 doc: minor changes
e7a3c4e37 snapshot: add missing [[deprecate(...)]]
ea5c558bd snapshot: cleanup (waiting for further improvements)
94f0ed179 snapshot: deprecate multi-type component loading function
244c35949 snapshot: deprecate multi-type component loading function
1f24fea21 type_traits: formatting
8deaa09b2 test: perform static checks at compile-time
85bffb714 type_traits: std::tuple traits specialization for entt::type_list and entt::value_list (#1011)
325ca310d view: updated ::refresh
d903e268f snapshot: minor changes
f4b26756c snapshot: improved basic_snapshot::component
fb3a34ee9 *: updated TODO
6902bb6c4 doc: typo
379819b2b test: cleanup
59abfbfb5 meta: refine policy check on value types for non-member data
6e2d87184 registry: avoid casting return types directly to better support empty storage
57ec3c85c registry: erase_if (close #977)
4afdf287f doc: minor changes
2810ac7cb registry: suppress a warning on msvc
e0d27f9bf *: updated TODO
de303c999 test: reverse-each for storage entity
1619e780f test: reverse each for plain storage classes
a1e37eca6 storage: reverse-each
c345e7456 doc: note on reverse iterations
d166c026f snapshot: minor changes
5e639996d doc: minor changes
dac2ef5a9 doc: typo
71d7888e8 snapshot: drop redundant check
84a4df9c4 doc: exclude-only views
95bc20319 doc: entity lifecycle
5a9f6d211 doc: cleanup
a29302faa test: more on entity signals
75efa72c6 registry: cleanup ::erase
58a84665b registry: cleanup ::remove
a5263384d doc: drop redundant comments
c0e6759c6 doc: cleanup a little further
d754f7431 doc: cleanup
1df539943 doc: drop pointless tags
c284e6fee doc: minor changes
500239758 test: typo
319ecd808 organizer: fix organizer::vertex::prepare not creating component pools (#1014)
d7891fabc doc: mention named pools support when registering listeners
e287dd041 helper: minor changes
4dee9dde1 registry: named pools support for on_construct/on_update/on_destroy
9bae6e67b doc: update connection helper doc
aa7a7ce25 doc: minor changes
a969468c5 registry: de-deprecate :) on_construct/on_update/on_destroy
a1e76fc63 doc: more about entity storage
d8ed4ca35 registry: refine how entity storage is used internally
3248e3f91 helper: make sigh_helper work with named pools
f00687e6f doc: updated registry documentation
5240c6b60 registry: deprecate on_construct/on_update/on_destroy
67604a88e natvis: update registry snippet
4242dfb8b registry: use entity storage directly as much as possible
f96d8ee83 registry: prepare to split component storage and entity storage
c147ec37c test: try to make gcc happy again
094ddbba3 meta: avoid shadow warnings
634630ca2 test: add missing template keywords (thanks msvc for ignoring them)
d78c26f26 *: updated TODO
fabc6c9bd test: full cross-registry entity-copy example with meta (not strictly required)
b6e8ddd2a meta: fight against the small nuances of the language :)
cf2bbae6e mixin: make it simpler to modify the underlying type
08799616d *: updated TODO
58bebf78d meta: reduce symbols and their sizes if possible
d534fad3e doc: more about views
871dc7a40 doc: drop references to storage placeholders
1fe7c78f7 test: minor changes
22a65f80f test: cleanup
756ea8a38 *: updated TODO
12186cb40 registry: drop internal static storage variables from ::assure
aa9ffb9ee registry: const ::storage<T>(...) returns a pointer to possibly null storage
dcb5aed90 registry: lazily/partially initialize views in the ::view const function
34f6a747a registry: add support for non-existent pools to try_get
912cb2ad5 snapshot: constness review
885488b3d registry: any_of supports non-existing pools now
3d3d3ef2d registry: all_of supports non-existing pools now
a7120b340 registry: coding style
51915205b test: cover stable multi-type model
4a3ee042e view: refine ::storage function
88a1b8d0d view: stable multi-type view ::each(cb) function
7e18a0f96 view: update ::use function
c367082dd view: unchecked_refresh function
9f94b5306 view: double check on none_of
44ed10c50 view: stable multi type view ::find/::back/::front functions
1b2280941 view: stable multi type view ::begin/::end functions
bdabbaa63 view: stable multi type view ::contains function
c79c109b7 view: stable multi type view ::size_hint function
f1a213382 registry: prepare to remove static storage from const assure
17dc06149 view: stable single type view ::each(cb) function
3b8d82330 view: drop unused return
a20829e70 view: ::handle returns a pointer rather than a reference
5be2fdc15 view: stable single type view ::each() function
873b107e6 -: updated TODO
356bbbe53 view: stable single type view ::find function
e3ce4e156 view: stable single type view ::front/::back functions
e02050c51 view: stable single type view ::rbegin/::rend functions
26930633f view: stable single type view ::begin/::end functions
b7a485767 view: stable single type view ::contains function
f54cdccd4 view: stable single type view ::empty function
41c9a32f3 view: stable single type view ::size function
736ef3580 view: make operator bool work properly with partially initialized views
0128cbb4f test: minor changes
ff0a40715 test: prepare test suite for safe invalid views
34f440386 view: avoid using storage, further prepare for empty safe views
b1c78efb6 nativs: updated signal file
28f03ff9c meta: add missing checks on factory<...>::data
a5fe61adb *: minor changes
457f5e59e view: rollback handle() usage and prepare to safe empty views
422fd284e group: refine group ::find function
6f3222573 view: refine single type view ::find function
366bbceb0 doc: use doxygen-awesome-css
7b7f81e08 doc: update reference.md
cfe955f97 doc: update links.md
684ddc9de doc: minor changes
f5d38a9ae doc: drop redundant doxy variable
447e3693f doc: updated doxy file (doxygen 1.9.6)
909490bf6 view: try to make g++ happy again
d90363e4a view: make view pack also work with empty views
ee5de744c view: add missing [[nodiscard]]
d401c88a0 view: assert on null handles
80563b955 view: allow swapping storage elements of a view
c74900057 sigh_mixin: avoid shadow warnings
78867d5c9 group: make msvc happy with constness on virtual functions
d435fc779 basic_entt_traits: suppress a warning by gcc
e6f76e0f9 view: try to make VS happy again :)
1c6b53360 test: minor changes
5c3d8360c view: turn ::use into a self-contained, non-const function
3882c7d9a view: turn ::refresh into a self contained, non-const function
15726218b view: doc
869bfc82c test: minor changes
0eb3d54b2 group: change signature of ::storage to return a (maybe null) pointer rather than a reference
f83290f76 view: change signature of ::storage to return a (maybe null) pointer rather than a reference
686a3b9d7 registry: make storage_for_type available to the final user
4d57d5c32 registry: make ::storage<T> return type explicit
36c21cf7f registry: drop redundant traits usage
7ab10e193 test: minor changes
41467d35a -: updated TODO
d351252a1 doc: entity storage
c6cd4f701 doc: refine storage section
65889cca4 doc: brief mention of void storage
f1914fd94 doc: rearrange a few things
e53af7bef registry: minor changes
b910cd261 *: updated TODO
58d331ca0 registry: minor changes
17f5b0a33 registry: avoid bumping version on destroy if not requested
de386292b registry: deprecate ::each
88bf26a2f registry: deprecate ::assign
3caad4100 mixin: common internal owner_or_assert function
916203a24 test: stress assert on entity limit
62f1971f7 test: minor changes
4fde96357 natvis: updated registry snippet
c3730b65f group: * unified model * drop group handler's size function (no longer required)
1ea072cd3 group: back to the unified model for group handlers
bbe4582ee meta: minor changes
89ab5c328 meta: operator==/!= for meta_func
3a4672793 meta: operator==/!= for meta_prop
0a0446f35 meta: operator==/!= for meta_data (close #1002)
fc58ff74b meta: operator==/!= for meta_handle (see #1002)
fed6831cd locator: support to opaque structures (close #956)
1605c8d9d natvis: updated entity file
d6641c7d8 -: updated TODO file
5079f38e9 storage: allow on_update signals on entity storage
1eab2a4a8 meta: fix constness detection for static functions in meta_type::invoke
c33110765 test: cleanup
117b0bd67 test: more about storage<...>::patch
9b4a6f877 storage: use allocator_traits::destroy rather than destroy_at
f4e6f2b37 group: suppress shadow warning
5971fb7aa -: updated TODO
10dfe7e93 sigh: allow disconnecting listeners during iterations (close #986)
a9208a956 doc: fixed typo
1cc5b32ca test: cleanup
f8a972a3c signal: drop sink::before
5b7cc2002 group: rollback some (no longer required) changes to the owning_group_descriptor
bd34e7f2c group: drop nested groups support, prepare to the large group review and multi storage support
46fe29c3f group: make matching functions virtual for owning groups
c50e2815c group: make owning_group_descriptor depend on the storage base type
fbfee632d group: minor changes
77c59aabf group: group_handler::size function for owning groups
ebb1e8a72 group: single check function for group handlers
1646217f0 group: make types explicit for the next/prev functions
645edfb2b group: decouple constructing and setting prev/next links
61f28298c group/registry: minor changes
d19f97bf2 group: use ::handle() if possible
70c611a84 group: cleanup
286428c19 group: make common_type base of non-owning group handlers
6ec719bcf group: reduce the footprint of non-owning group handlers
11f9bb2d7 registry: use shared_ptr<void> for non-owning groups (prepare to drop the basic handler dependency)
5a1ba5ad7 regisrtry: decouple container types for groups
cf094e7ef registry: finally split owning and non-owning groups as it ought to be
31808bd9a sigh: flip the last commit on its head and drop redundant functions rather than merging them
61a5173a7 sigh: merge a couple of functions
ed6fe9e65 sigh/sink: refine internal definition
e30fa8520 doc: cleanup
ca1069e18 snapshot: avoid allocations if possible
70f73a094 snapshot: drop pointless checks
710fff0e3 entity: make get_t, exclude_t and owned_t constexpr constructible
660bc5843 entity: turn get_t, exclude_t and owned_t into proper classes (close #998)
13295a14e type_traits: v141 toolset workaround for value_list_diff
9ce07ff61 type_traits: value_list_diff[_t]
b272e04ba type_traits: value_list_contains[_v]
28b11912a test: cleanup
b9f096d12 type_traits: value_list_unique[_t]
8c60faa1d type_traits: value_list_index[_v]
1f93ea4ee snapshot: avoid unnecessary lookups
7ca77e53f snapshot: avoid unnecessary lookups
69397f365 snapshot: avoid unnecessary lookups
f907bc066 snapshot: drop redundant checks and avoid unnecessary lookups
bda52701f snapshot: avoid unnecessary lookups
d26f7684c snapshot: minor changes
63d6c2bff snapshot: avoid unnecessary lookups
cc45e7341 snapshot: also avoid using views if not required
5d092bcb1 snapshot: avoid unnecessary lookups
295c68841 snapshot: review ::orphans functions
2664b5255 observer: allocator support
dd3632833 observer: configurable mask type
c8c929e4a group: use type members properly
d1ef7bf15 view: use type members properly
1ab23f17d group: early exit on signal races
a72eb4693 group: minor changes
67579d062 -: updated TODO
766a233f3 view: base_type -> common_type
905671c23 runtime_view: base_type -> common_type
27c1383e4 group: base_type -> common_type
029ccc8f7 registry: base_type -> common_type
cde40d586 group: drop unused using decl
6a16a8a20 group: auto init for owning groups
1a12dede6 group: auto init for non-owning groups
35a78b65e group: cleanup
ada19432f group: support for index based sort
4998e9087 doc: minor changes
471c11c6d sparse_set: respect -> sort_as (naming is hard, you know)
3e13e0b59 group: sort/respect -> sort_as (also decoupled from group types)
53cd105f2 group: reuse pools as much as possible
24b31c379 group: reuse pools as much as possible
def82b534 group: index based get
a424f4ebf view: review get
b8f0a8d8e doc: a couple of interesting articles/series (close #994)
7941226ef group: try to reuse pools when sorting and also please all compilers out there at the same time (aka me figthing ICEs again)
86bbb2f6b group: reuse pools when sorting
3c176f725 test: suppress warnings due to unused variables
3642c8a78 registry: drop [[nodiscard]] from ::group (close #991)
0e80d90a7 group: use storage<idx> as much as possible
4fdf2dccd group: update doc
f8a997e6c group: minor changes
40f676ed1 test: drop unused include
5e346748e test: code coverage for groups and registry
3ef61fe01 meta: support meta member functions on primitive types
3885d280d test: cleanup
f41b91419 meta: allow updating values on meta properties
e0684f634 registry: cleanup/minor changes
fb980a78c registry: further refine the group function(s)
c2430ab48 doc: minor changes
d36d9cb39 registry: further cleanup group functions
0017c08bb group: get pools from handlers
e737ff747 group: get filter from handlers
945dc4093 group: split group handler functions
7ef008511 registry: drop group_data
d2fa68813 registry/group: prepare to get rid of group_data
f22a09a9a group: in-between change to simplify dropping group_data
b0aba79a5 snapshot: minor changes
7c23e4a2f registry: minor changes
7fe035ce4 group: move group size from registry group_data to basic_group_handler
3e7160eda group: minor changes
aaeb686ec group: common base class for group handlers
3fdf4884d group: prepare for group handler common base class
1b23ff4b9 registry: use common group handler types as keys for the group set
88dac318e group: wrap the len of owning groups to avoid changing it by mistake
520c2e660 group: make group handlers work with multiple storage of the same type
f5d0d451b group: split pools and filter in the group handlers
8af6fc0cc group: use ::handle internally if possible
c04b97a31 group: add ::handle function to all group types
1d85414dc doc: drop refs to registry::version (close #992)
c6533827f group: fight with clang format from time to time :)
b5803451b group: make owning groups work with their handlers
3417d66b2 group: make non-owning groups work with their handlers
1e61204e8 registry: deduce group handler type from group type
19c4857ef group: cleanup
66ea94898 registry/group: move group handler to group file as it ought to be
ced6d21c3 registry: break dependency between registry and group handlers
429c7c45c registry: further cleanup things
c03b1111a registry: small cleanup
ebd7d3acd registry: storage based model with pools for groups
5aeec60cf registry: prepare to switch to storage based group handlers
620b4f751 registry: pass handlers to group callbacks
6d58004c1 registry: minor changes to simplify the implementation slightly
df6d926de registry: prepare for a storage based group handler
e63af24cb registry: turn the non-owning group handler in a storage
068d9f8ae registry: discard unused arguments from listeners if possible
c19c848c4 test: suppress warnings due to unused variables
0bf0a0a8f doc: delegate
743e8678e delegate: also support functions that skip first elements (on second attempt only)
a7ad1c06f delegate: prepare to support filtering on both sides
b1af70e70 registry: avoid checking pools in the group handler if possible
c87c3533e registry: avoid checking pools in the group handler if possible
4839a0ee6 registry: cleanup
a0f0c44e6 registry: minor changes
74691dc1d group: just use meaningful names :)
e4957badb registry: split group handler to further refine group management
46791c4c3 registry: turn group handler functions into static ones
56c391784 registry: prepare to rework groups
1fb13d3e9 doc: minor changes
535beb4e2 storage: drop unnecessary use of integral_constant
2d318b88c -: updated TODO
b7f0b76ce entity/mixin: add missing include
d30312f51 entity/helper: add missing include, drop unnecessary traits calls
30772848e meta: avoid unnecessary calls to std::move
eca01a397 doc: add vcpkg badge and vcpkg.link (#985)
35ef0b7ac core: reduces the number of instantiations a bit
19ccba3a6 meta: reduces the number of instantiations a bit
207b7674a doc: fix typo
631c55ba9 storage: minor changes/tests
e7b30fd36 storage: return iterator to elements rather than entities and only if it makes sense
3e959007b storage: ::insert returns an iterator to the range of inserted entities
07ec4ca23 -: updated TODO
6e4946b68 storage: uniform interface to simplify mixin implementation
47ea16f17 test: signals on entity creation/destruction
722857fc0 test: get rid of pointless template parameters
2125b3838 test: minor changes
289de7d57 test: exclude only views
25ecd8e79 test: minor changes
319dfdb07 test: filtered registry view
9dbbcac01 -: updated TODO
f545c8e05 registry: deprecate ::release
c68fa6a65 registry: make ::destroy work without ::release (the latter to be deprecated)
d288ecd70 registry: make ::release use ::bump return value
312d3aba8 sparse_set: bump returns the version in use (for convenience)
4d2b2c6de registry: use traits_type::next if possible
80d55a226 test: increase code coverage
d86a53935 test: suppress warnings due to unused variables
0f7098d0e -: updated TODO
8c96be1e9 registry: deprecate a bunch of functions because of the entity storage
37f396bfe registry: make entity storage storage as any other
75894dc40 storage: update traits_type for entity storage
cdee000ce any: rollback a change that turns vs toolset v141 crazy
54ca62600 dispatcher: refine aggregate check
6f4280ed5 any: refine aggregate check
ddf56b78c storage: backward compatibility on component requirements
53a854f54 any: just cleanup the code to make it easier to work with
4896acac7 storage: typo
e3defeba2 test: suppress warnings due to unused variables
62079908c storage: use proper value type for entity storage
e65a8f2e5 doc: add link to koala engine :)
9f27fb1e5 registry: further prepare to turn the entity storage into a plain pool
04d734e76 registry: prepare to turn the entity pool in a plain storage
df50fa1b5 natvis: cleanup
051872b8c natvis: update registry definition
57ab9e7be registry: avoid using assure if not required
69d95ba75 test: more bench to stress a little an upcoming feature
9caf66d7c test: cleanup
74cb0d40c test: internal rework
deac7f34b dispatcher: refine aggregate support
a9883f27c storage: refine transparent aggregate support
85b1e57d8 sparse_set: drop fast_compact, expect full clear
b7d8e0186 storage: make the entity storage perform a full clear rather than a fake one (still viable via erase)
390a56176 -: updated TODO file
a1b888cce natvis: add optiona storage length item for entity storage
2107dd689 natvis: fix already existing errors due to renaming or design changes
1fca56afe storage: make it easier to refine the natvis file
c0762a6a5 storage: add get/get_as_tuple to entity storage to make it suitable for use with views
f48de1bac test: stress get/get_as_tuple for empty types
c7dfce89e sigh_mixin: refine pop_all
822fafcd4 view: uniform implementation to simplify upcoming changes
1476d4ea9 sparse_set: refine ::respect
c1c63777e -: updated TODO
2fab25ae8 registry: refine internal check
75d449152 -: updated TODO
c7866fb21 storage: use entt traits next function if possible
87987bacd entity: added basic_entt_traits::next (with tests)
bde0219fe snapshot: review basic_continuous_loader::entities
ad64c849b storage: suppress warnings
b808bb83b test: suppress warnings
d0090d35f snapshot: try to make sizes an opaque value to the caller
7a1a06a24 sigh_mixin: avoid shadow warnings
000b17881 -: updated TODO
068b6ed49 registry: first (almost) backward compatible version with opaque hidden entity storage
0187fb48a test: sigh mixin for entity storage types
35a2b3844 sigh_mixin: also support entity storage types
4747c9a4c registry: extended checks to support swap-only entity storage types
7be8d8327 registry: make a couple of conditions opaque
a5d6757d6 registry: prepare to get rid of the vector of entities
3f09d47c8 storage: remove redundant typename keyword
9c06d6ba0 registry: use type member names
b7c819bf4 test: entity storage
9f31803ba storage: swap-only entity storage
1e7deff9c test: drop redundant checks
04ac15d8d test: minor changes
376218991 sigh_mixin: make pop_all use narrow view iterators if any
18d6e466d -: [[nodiscard]]  as appropriate
095ecf314 group: extended_group_iterator::base to return the underlying iterator
433ed863e view: extended_view_iterator::base to return the underlying iterator
0dba68e75 storage: coding style/minor changes
1ab281582 storage: extended_storage_iterator::base to return the underlying iterator
2af5a725e doc: * updated copyright * udpated TODO list
a86bf1332 test: try to make lcov happy
831054bff test: share as much as possible
f94de1c06 test: rework lib stuff to share common files
a3d9503a1 test: try to make lcov happy
3f2b15f9f test: try to make lcov happy
e48817d51 test: try to make lcov happy
d11cebe30 view: uniform design to also help natvis without having to poke into stl internals
77a5efb32 natvis: updated to_entity intrinsic
851006efe -: updated TODO
6fc6b2fb3 sigh_mixin: further improve ::pop_all
ed17a2c48 sparse_set: ::contiguous function
bd00e797a sparse_set: further refine pop_all to make it even faster
e645c4928 -: updated TODO
a425878e8 sparse_set/storage: clear is backward compatible now
f3cd9d374 storage: fixed clear_all counter
b3e93b084 registry: naming convention
314c189c4 test: minor changes
2bb2c5566 build: try to make lcov happy again
d13c126e9 view: avoid name clashes
9b54ee37a flow: propagate allocator to generated graph + internal rework
e1ead9d3e build: update coverage workflow
cf61068dc mixin: suppress a warning with gcc11
82863f829 test: code coverage for range functionalities
e4de59827 test: try to make lcov happy
ccea4c920 memory: code coverage
89166f0e4 build: refine analyzer workflow
7a05a16c5 registry: slightly better destroy (yet not quite there though)
d0854646c test: yet another test to stress the upcoming changes
1e9c9fe5f registry: better, faster range-remove + refine range-erase
80fac8d8e test: minor changes
c774b9838 -: updated TODO
3fd0403cc registry: faster, better range-erase
6eb3347a3 test: a couple of extra functions to stress the upcoming changes
89bceaff7 -: updated TODO
dc25c9c1a sparse_set: invoke release_sparse_pages before clearing the sparse array
e68ba5870 sigh_mixin: add a missing include
c68cb3375 entity: make deletion_policy publicly available via fwd.hpp
59f807fd0 sparse_set: suppress warnings due to unused expressions
232ffebc1 sparse_set: internal clear_all function
3cea845a0 sparse_set: sparse_set_iterator::data function
295f3b32e registry: a couple of extra move calls here and there
254da2c3c sparse_set: better, faster range remove
ecd3b8d93 sparse_set: prevent rework errors as much as possible
c673b9b17 sigh_mixin: slightly improved pop + review insert
cd28de0d6 test: clear-stable bench
672f6a711 test: minor changes
3b50672b7 storage: restore storage_for/storage_type duality, it turned out to be very useful in practice
f0613b1c6 sparse_set/storage: minor changes to reuse type members
2197e160e -: drop file pushed by mistake :)
2dccd9016 handle: discard entity on destruction
2f873f2dd -: storage_mixin.hpp -> mixin.hpp (non-storage mixins are also a thing)
fde1a524e sparse_set: ::get -> ::value (to avoid hiding from derived classes)
055801047 doc: drop references to docsforge + minor changes
79a054a52 sigh_mixin: scope base_type properly
d94e443a1 doc: drop outdated section
3862184e8 sigh_mixin: support self managed storage classes
f40fa3c2f test: * use range destroy * avoid compiler optimizations
01bc93459 test (bench): the new entity storage enables the fast path in all cases
151bd0739 sparse_set: revert optmized range push, it prevents self-managed storage classes
935393aae sparse_set: better, faster range push
fbfde4347 snapshot: avoid unused variable warnings
2ffbe115b component_traits: revert entity customization support
645973eb7 sparse_set: insert -> push
133230797 sparse_set: emplace -> push
b700f5eb5 doc: typo
e60dbdc52 sparse_set/storage: * rename swap_at in swap_or_move to capture the real purpose * define swap_at as a protected function to allow swapping from above
c66623b33 sigh_mixin: avoid hiding basic_iterator type meber
62246d879 storage: avoid hiding basic_iterator type meber
b35f13130 sparse_set: support swap-only mixins
3dd82633a -: drop storage_mixin.cpp, I forgot to do it a couple of commits ago :)
00231bf8a storage: make swap_at non-final to support checks on derived classes
58d392e81 -: minor changes
1d4d99d09 mixin: sigh_storage_mixin -> sigh_mixin
fe3edf2c8 -: minor changes
0864ba042 -: drop useless typename
3a9698001 build: minor changes
423f7a555 is_equality_comparable: detect C-style arrays directly
5db8ad53a build: update gh workflow
c2ab35780 view: make also VS toolset v141 happy
4fb558f14 view: further reduce instantiations
5762a8a08 view: reuse internal functions if possible
ed4c67521 sparse_set/storage: drop move_element
f15789846 config: ENTT_FAIL(msg) -> ENTT_ASSERT(false, msg)
6d20709e0 storage: minor changes
a9a9853c0 sigh_storage_mixin: use entity_type from Type
af14aa4c9 doc: more about signals (sigh_storage_mixin)
24d6b9881 test: minor changes
899f4baa6 storage: * drop storage_for]_t] * make storage_type[_t] deal with constness
c1ab7ba02 sigh_storage_mixin: make all virtual member functions final
9d38f6020 registry: thanks MSVC for accepting invalid C++ code
0efa25cf6 sigh: cool, I keep doing the same error again and again apparently :)
6316b6045 registry: make it work with storage<void> also in C++17
f268fb60a entity: avoid breaking changes due to type members renaming
3520d6915 entity: add base_type
4da7a8451 entity: make checks work with 64b identifiers :)
382dfc3bb entity: strict check on entity/version masks
b6dcdc816 entity: * also expose entity_mask and version mask to the final user * avoid default args with entt_traits::construct for backward compatibility
c9d544089 doc: review/cleanup entity.md a bit (done)
3eb5faeed doc: review/cleanup entity.md a bit (work in progress)
7a328c7ed doc: updated links
6567aa195 doc: a note about listeners disconnection (close #958)
92319f011 entt_traits: split basic impl, simplify def
782d86b6e entt_traits: value_type -> type (cuz it's not a value type after all)
c2cae37c1 entity_traits: make page_size type explicit
1026d26ec entt_traits: drop reserved value
7156803db test: local non-static constexpr variables
f54ed5424 helper: local non-static constexpr variables
f30b50195 algorithm: local non-static constexpr variables
c90ab9aff sparse_set: * break dependency on traits_type::reserved * use a tombstone if all I need is a tombstone
c2f6ca43f doc: graph (close #957)
3e5e41d88 test: cover some corner cases of the flow class
9eafc0431 flow: minor changes
0a82b777b component_traits: support specializations based on entity type
32bcc01a4 component: * make component_traits treat void properly * drop ignore_as_empty_v
9c3fe3546 nativs: entity module
83f8aed58 helper: use traits_type from storage class directly
2fd660274 snapshot: use public registry traits_type member type
a554d406e registry: * public traits_type member type * break dependency on component_traits * use public storage traits_type member type
5f12f872e test: minor changes
be4eb68a3 helper: * break dependency on component_traits * use public storage traits_type member type
df5284d9e view: * break dependency on component_traits * use public storage traits_type member type
0e27d33e7 storage: public traits_type member type
fe6e6ae73 sparse_set: public traits_type member type
9d29713ea entity: naming convention
270d0277d group: cleanup
0bd06c8d5 hashed_string: naming convention
733f215cc storage: break dependency between component_traits and storage_iterator
ad01a69fe *: renaming/coding style
dd9c1dade sparse_set: no need to differentiate template args for sparse_set_iterator
b8f70519f doc: fixed typo
9b9d212dd *: coding style
3fe15969d doc: cleanup
ec4bf222c meta: avoid the +1u trick for 0-sized arrays
1173908ee meta: avoid rebinding when forwarding requests
2595b8a92 doc: sigh_helper
f4e2a8c76 sigh_builder: add all missing .template that msvc kindly accepted anyway
66e1a0565 entity: sigh_helper utility with tests (close #928)
87283dc41 storage: simplified impl in order to introduce multi-type storage more easily
a802ebffe storage: * move storage_type[_t] and storage_for[_t] to fwd.hpp * no need to include storage.hpp when forward defining views
b84b09421 doc: add Arch ECS to references.md (#954)
940fd0939 todo: add a note for a (soon to be released) change
920338be5 doc: add ecsact to links.md (thanks @zaucy for pointing this out)
bcd1155b7 gh: add more gcc and clang versions
1dc88109e gh: update workflows
262c1f53c cmake: only enable -Wdocumentation for clang-cl
4af0a3a0d doc: cleanup
be1641828 doc: cleanup
b54a52fbf doc: fixed typo
ae8815995 doc: fixed typo
62c764f68 doc: fixed typo
2c48cc10a cmake: enable documentation diagnostic for clang
82f286678 sigh: drop redundant function
d56e5a269 registry: propagate allocator to context
1517b2951 doc: document delegate raw access
bea7b43a1 delegate: target member function
2f878f8b5 sigh: refine ::collect
fc68c1b29 view/group: cleanup
9081c185d meta: minor changes
7c4493f23 group: make filter storage available
da4e73ab8 view: make filter storage available
f3e7f98b4 registry: extra check when moving a registry
3925fc612 emitter: extra allocator check when moving
c639130c1 dispatcher: extra allocator check when moving
75c311600 registry: cleanup
e9e14eb49 meta: [[nodiscard]]
d1558304f any: [[nodiscard]]
0531b530b snapshot: minor changes
f9d0178dd workflow: bump iwyu version
b66b8d37e test: suppress warning
05ef4c29d storage: minor changes
9c3d75669 test: cleanup include directives
93651e46f registry: drop [[deprecated]] functions
ea901cbfa test: code coverage
d5dc4f43e doc: meta.md
498e02f15 doc: core.md
d0ea8f4f9 cmake: suppress some warnings for clang-cl, it goes a little wrong otherwise
dec3b7bb3 test: suppress warnings
10bc8b05a test: use /W1 with VS (but for toolset v141, too bugged for that)
ad77b54dc cmake: bump version to get some cool feature/update
b6724b028 group: pass filter storage to groups (in-between change for full storage access)
54270b103 group: make them easily copyable/movable
31dc732a7 doc: graph.md
f0e02d6d3 doc: container.md
156d6e4ea doc: poly.md
4375c1c3d doc: lib.md
24a9cd67e scheduler: forgot to add the fwd file to the previous commit :)
ba8d522c1 doc: add the worst engine (love the name) to the list of links
3ae46214a doc: review process.md
5119fe8d7 scheduler: basic type model with default for common cases
ed0319cdd view: avoid shadow warnings
bc50da6a7 storage: suppress warnings with non copyable nor default constructible types
52b3b4c24 group: suppress warnings for unused variables in case of empty types
74bab529d test: minor changes
b1b143917 meta: [[maybe_unused]] variable to avoid warnings with corner cases
7beb4c85c test: suppress a few warnings (entity)
f3beb5670 test: suppress a few warnings (container)
446c67b69 test: suppress a few warnings (resource)
c4507bd17 test: suppress a few warnings (poly)
61e872bb4 test: suppress a few warnings (meta)
9f22a3e23 test: suppress a few warnings (memory)
653dd5cd4 test: suppress a few warnings (tuple)
bc53ed3be test: suppress a few warnings (flow)
f935bbcce dense_set: suppress warnings due to possible narrowing conversions
c7d505353 dense_map: suppress warnings due to possible narrowing conversions
ea78f1d97 now working on version 3.12
REVERT: fef921132 update single include file
REVERT: e52a93f8a ready to cut v3.11.1
REVERT: cd541f335 storage: * move storage_type[_t] and storage_for[_t] to fwd.hpp * no need to include storage.hpp when forward defining views
REVERT: 255b8be8c view: avoid shadow warnings
REVERT: 8cd7f064a storage: suppress warnings with non copyable nor default constructible types
REVERT: 58ae4117c group: suppress warnings for unused variables in case of empty types
REVERT: cfa1e805b meta: [[maybe_unused]] variable to avoid warnings with corner cases
REVERT: ccedacec8 dense_set: suppress warnings due to possible narrowing conversions
REVERT: 17578dc8c dense_map: suppress warnings due to possible narrowing conversions

git-subtree-dir: external/entt/entt
git-subtree-split: 344e03ac64a1f78424ab1150e2d4778e8df8431d
This commit is contained in:
Green Sky 2023-10-02 15:30:10 +02:00
parent 5c7231b7a3
commit 90ce4bda4e
153 changed files with 24263 additions and 14220 deletions

View File

@ -12,14 +12,14 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
env: env:
IWYU: 0.18 IWYU: 0.19
LLVM: 14 LLVM: 15
runs-on: ubuntu-latest runs-on: ubuntu-latest
continue-on-error: true continue-on-error: true
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Install llvm/clang - name: Install llvm/clang
# see: https://apt.llvm.org/ # see: https://apt.llvm.org/
run: | run: |
@ -39,17 +39,23 @@ jobs:
git clone https://github.com/include-what-you-use/include-what-you-use.git --branch $IWYU --depth 1 git clone https://github.com/include-what-you-use/include-what-you-use.git --branch $IWYU --depth 1
mkdir include-what-you-use/build mkdir include-what-you-use/build
cd 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 make -j4
bin/include-what-you-use --version bin/include-what-you-use --version
- name: Compile tests - name: Compile tests
working-directory: build working-directory: build
run: | run: |
export PATH=$PATH:${GITHUB_WORKSPACE}/build/include-what-you-use/build/bin 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_BENCHMARK=ON \
-DENTT_BUILD_EXAMPLE=ON \ -DENTT_BUILD_EXAMPLE=ON \
-DENTT_BUILD_LIB=ON \ -DENTT_BUILD_LIB=ON \
-DENTT_BUILD_SNAPSHOT=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 make -j4

View File

@ -9,38 +9,61 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest, ubuntu-20.04]
compiler: compiler:
- pkg: g++-7 - { pkg: g++, exe: 'g++', version: 7 }
exe: g++-7 - { pkg: g++, exe: 'g++', version: 8 }
- pkg: g++-8 - { pkg: g++, exe: 'g++', version: 9 }
exe: g++-8 - { pkg: g++, exe: 'g++', version: 10 }
- pkg: g++-9 - { pkg: g++, exe: 'g++', version: 11 }
exe: g++-9 - { pkg: g++, exe: 'g++', version: 12 }
- pkg: g++-10 - { pkg: clang, exe: 'clang++', version: 8 }
exe: g++-10 - { pkg: clang, exe: 'clang++', version: 9 }
- pkg: clang-8 - { pkg: clang, exe: 'clang++', version: 10 }
exe: clang++-8 - { pkg: clang, exe: 'clang++', version: 11 }
- pkg: clang-9 - { pkg: clang, exe: 'clang++', version: 12 }
exe: clang++-9 - { pkg: clang, exe: 'clang++', version: 13 }
- pkg: clang-10 - { pkg: clang, exe: 'clang++', version: 14 }
exe: clang++-10 exclude:
- pkg: clang-11 - os: ubuntu-latest
exe: clang++-11 compiler: { pkg: g++, exe: 'g++', version: 7 }
- pkg: clang-12 - os: ubuntu-latest
exe: clang++-12 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: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Install compiler - name: Install compiler
run: | run: |
sudo apt update sudo apt update
sudo apt install -y ${{ matrix.compiler.pkg }} sudo apt install -y ${{ matrix.compiler.pkg }}-${{ matrix.compiler.version }}
- name: Compile tests - name: Compile tests
working-directory: build working-directory: build
env: env:
CXX: ${{ matrix.compiler.exe }} CXX: ${{ matrix.compiler.exe }}-${{ matrix.compiler.version }}
run: | run: |
cmake -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON .. cmake -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON ..
make -j4 make -j4
@ -50,32 +73,6 @@ jobs:
CTEST_OUTPUT_ON_FAILURE: 1 CTEST_OUTPUT_ON_FAILURE: 1
run: ctest --timeout 30 -C Debug -j4 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: windows:
timeout-minutes: 15 timeout-minutes: 15
@ -93,7 +90,7 @@ jobs:
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Compile tests - name: Compile tests
working-directory: build working-directory: build
run: | run: |
@ -105,35 +102,12 @@ jobs:
CTEST_OUTPUT_ON_FAILURE: 1 CTEST_OUTPUT_ON_FAILURE: 1
run: ctest --timeout 30 -C Debug -j4 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: macos:
timeout-minutes: 15 timeout-minutes: 15
runs-on: macOS-latest runs-on: macOS-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Compile tests - name: Compile tests
working-directory: build working-directory: build
run: | run: |
@ -145,23 +119,24 @@ jobs:
CTEST_OUTPUT_ON_FAILURE: 1 CTEST_OUTPUT_ON_FAILURE: 1
run: ctest --timeout 30 -C Debug -j4 run: ctest --timeout 30 -C Debug -j4
macos-extra: extra:
timeout-minutes: 15 timeout-minutes: 15
strategy: strategy:
matrix: matrix:
os: [windows-latest, macOS-latest, ubuntu-latest]
id_type: ["std::uint32_t", "std::uint64_t"] id_type: ["std::uint32_t", "std::uint64_t"]
cxx_std: [cxx_std_17, cxx_std_20] cxx_std: [cxx_std_17, cxx_std_20]
runs-on: macOS-latest runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Compile tests - name: Compile tests
working-directory: build working-directory: build
run: | run: |
cmake -DENTT_BUILD_TESTING=ON -DENTT_CXX_STD=${{ matrix.cxx_std }} -DENTT_ID_TYPE=${{ matrix.id_type }} .. 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 - name: Run tests
working-directory: build working-directory: build
env: env:

View File

@ -9,11 +9,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Compile tests - name: Compile tests
working-directory: build working-directory: build
env: env:
CXXFLAGS: "--coverage -fno-inline" CXXFLAGS: "--coverage -fno-elide-constructors -fno-inline -fno-default-inline"
CXX: g++ CXX: g++
run: | run: |
cmake -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON .. 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 -c -d . -o coverage.info
lcov -l coverage.info lcov -l coverage.info
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
uses: codecov/codecov-action@v2 uses: codecov/codecov-action@v3
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
files: build/coverage.info files: build/coverage.info

View File

@ -15,7 +15,7 @@ jobs:
FORMULA: entt.rb FORMULA: entt.rb
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Clone repository - name: Clone repository
working-directory: build working-directory: build
env: env:

View File

@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Compile tests - name: Compile tests
working-directory: build working-directory: build
env: env:

View File

@ -2,7 +2,7 @@
# EnTT # EnTT
# #
cmake_minimum_required(VERSION 3.12.4) cmake_minimum_required(VERSION 3.15.7)
# #
# Read project version # Read project version
@ -31,7 +31,7 @@ endif()
message(VERBOSE "*") message(VERBOSE "*")
message(VERBOSE "* ${PROJECT_NAME} v${PROJECT_VERSION} (${CMAKE_BUILD_TYPE})") message(VERBOSE "* ${PROJECT_NAME} v${PROJECT_VERSION} (${CMAKE_BUILD_TYPE})")
message(VERBOSE "* Copyright (c) 2017-2022 Michele Caini <michele.caini@gmail.com>") message(VERBOSE "* Copyright (c) 2017-2023 Michele Caini <michele.caini@gmail.com>")
message(VERBOSE "*") message(VERBOSE "*")
# #
@ -143,6 +143,7 @@ if(ENTT_INCLUDE_HEADERS)
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/fwd.hpp> $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/fwd.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/group.hpp> $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/group.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/handle.hpp> $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/handle.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/mixin.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/helper.hpp> $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/helper.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/observer.hpp> $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/observer.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/organizer.hpp> $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/organizer.hpp>
@ -151,7 +152,6 @@ if(ENTT_INCLUDE_HEADERS)
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/snapshot.hpp> $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/snapshot.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/sparse_set.hpp> $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/sparse_set.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/storage.hpp> $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/storage.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/storage_mixin.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/view.hpp> $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/view.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/graph/adjacency_matrix.hpp> $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/graph/adjacency_matrix.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/graph/dot.hpp> $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/graph/dot.hpp>

View File

@ -1,6 +1,6 @@
The MIT License (MIT) 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -6,7 +6,8 @@
[![Build Status](https://github.com/skypjack/entt/workflows/build/badge.svg)](https://github.com/skypjack/entt/actions) [![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) [![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) [![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) [![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) [![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) [![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 $ cmake .. -DENTT_BUILD_DOCS=ON
$ make $ make
The API reference will be created in HTML format within the directory The API reference is created in HTML format in the `build/docs/html` directory.
`build/docs/html`. To navigate it with your favorite browser: To navigate it with your favorite browser:
$ cd build $ cd build
$ your_favorite_browser docs/html/index.html $ 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 @cond TURN_OFF_DOXYGEN
--> -->
The same version is also available [online](https://skypjack.github.io/entt/) 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 for the latest release, that is the last stable tag.<br/>
something more pleasing to the eye, consider reading the nice-looking version
available on [docsforge](https://entt.docsforge.com/): same documentation, much
more pleasant to read.<br/>
Moreover, there exists a [wiki](https://github.com/skypjack/entt/wiki) dedicated Moreover, there exists a [wiki](https://github.com/skypjack/entt/wiki) dedicated
to the project where users can find all related documentation pages. to the project where users can find all related documentation pages.
<!-- <!--
@ -366,9 +364,8 @@ to the project where users can find all related documentation pages.
# Tests # Tests
To compile and run the tests, `EnTT` requires *googletest*.<br/> To compile and run the tests, `EnTT` requires *googletest*.<br/>
`cmake` will download and compile the library before compiling anything else. `cmake` downloads and compiles the library before compiling anything else. In
In order to build the tests, set the `CMake` option `ENTT_BUILD_TESTING` to order to build the tests, set the `CMake` option `ENTT_BUILD_TESTING` to `ON`.
`ON`.
To build the most basic set of tests: To build the most basic set of tests:
@ -420,7 +417,7 @@ know who has participated so far.
# License # License
Code and documentation Copyright (c) 2017-2022 Michele Caini.<br/> Code and documentation Copyright (c) 2017-2023 Michele Caini.<br/>
Colorful logo Copyright (c) 2018-2021 Richard Caseres. Colorful logo Copyright (c) 2018-2021 Richard Caseres.
Code released under Code released under

21
TODO
View File

@ -1,27 +1,26 @@
* debugging tools (#60): the issue online already contains interesting tips on this, look at it
* work stealing job system (see #100) + mt scheduler based on const awareness for types
EXAMPLES EXAMPLES
* filter on runtime values/variables (not only types) * filter on runtime values/variables (not only types)
* support to polymorphic types (see #859) * support to polymorphic types (see #859)
DOC: DOC:
* storage<void>
* custom storage/view * custom storage/view
* examples (and credits) from @alanjfs :) * examples (and credits) from @alanjfs :)
* update entity doc when the storage based model is in place * update entity doc when the storage based model is in place
* in-place O(1) release/destroy for non-orphaned entities, out-of-sync model
TODO (high prio): TODO (high prio):
* remove the static storage from the const assure in the registry * check natvis files (periodically :)
* resource cache: avoid using shared ptr with loader and the others
* further optimize exclusion lists in multi type views (no existence check)
* further improve the snapshot stuff, ie component functions
* use fixture for storage tests to reduce loc number and duplication as much as possible
* basic_view<...>::reach(...)
* doc: bump entities
WIP: WIP:
* get rid of observers, storage based views made them pointless - document alternatives * get rid of observers, storage based views made them pointless - document alternatives
* add storage getter for filters to views and groups
* exploit the tombstone mechanism to allow enabling/disabling entities (see bump, compact and clear for further details) * exploit the tombstone mechanism to allow enabling/disabling entities (see bump, compact and clear for further details)
* basic_storage::bind for cross-registry setups (see and remove todo from entity_copy.cpp)
* process scheduler: reviews, use free lists internally * process scheduler: reviews, use free lists internally
* dedicated entity storage, in-place O(1) release/destroy for non-orphaned entities, out-of-sync model
* entity-only and exclude-only views (both solved with entity storage and storage<void>)
* custom allocators all over (registry, ...)
* add test for maximum number of entities reached
* deprecate non-owning groups in favor of owning views and view packs, introduce lazy owning views * deprecate non-owning groups in favor of owning views and view packs, introduce lazy owning views
* bring nested groups back in place (see bd34e7f)
* work stealing job system (see #100) + mt scheduler based on const awareness for types

View File

@ -2,8 +2,22 @@
# Doxygen configuration (documentation) # Doxygen configuration (documentation)
# #
set(DOXY_DEPS_DIRECTORY ${EnTT_SOURCE_DIR}/deps) FetchContent_Declare(
doxygen-awesome-css
GIT_REPOSITORY https://github.com/jothepro/doxygen-awesome-css
GIT_TAG main
GIT_SHALLOW 1
)
FetchContent_GetProperties(doxygen-awesome-css)
if(NOT doxygen-awesome-css_POPULATED)
FetchContent_Populate(doxygen-awesome-css)
set(doxygen-awesome-css_INCLUDE_DIR ${doxygen-awesome-css_SOURCE_DIR})
endif()
set(DOXY_SOURCE_DIRECTORY ${EnTT_SOURCE_DIR}/src) set(DOXY_SOURCE_DIRECTORY ${EnTT_SOURCE_DIR}/src)
set(DOXY_CSS_DIRECTORY ${doxygen-awesome-css_INCLUDE_DIR})
set(DOXY_DOCS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) set(DOXY_DOCS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
set(DOXY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) set(DOXY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})

View File

@ -1,4 +1,4 @@
# Doxyfile 1.9.4 # Doxyfile 1.9.6
# This file describes the settings to be used by the documentation system # This file describes the settings to be used by the documentation system
# doxygen (www.doxygen.org) for a project. # doxygen (www.doxygen.org) for a project.
@ -19,7 +19,8 @@
# configuration file: # configuration file:
# doxygen -x [configFile] # doxygen -x [configFile]
# Use doxygen to compare the used configuration file with the template # Use doxygen to compare the used configuration file with the template
# configuration file without replacing the environment variables: # configuration file without replacing the environment variables or CMake type
# replacement variables:
# doxygen -x_noenv [configFile] # doxygen -x_noenv [configFile]
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
@ -85,7 +86,7 @@ CREATE_SUBDIRS = NO
# level increment doubles the number of directories, resulting in 4096 # level increment doubles the number of directories, resulting in 4096
# directories at level 8 which is the default and also the maximum value. The # directories at level 8 which is the default and also the maximum value. The
# sub-directories are organized in 2 levels, the first level always has a fixed # sub-directories are organized in 2 levels, the first level always has a fixed
# numer of 16 directories. # number of 16 directories.
# Minimum value: 0, maximum value: 8, default value: 8. # Minimum value: 0, maximum value: 8, default value: 8.
# This tag requires that the tag CREATE_SUBDIRS is set to YES. # This tag requires that the tag CREATE_SUBDIRS is set to YES.
@ -557,7 +558,8 @@ HIDE_UNDOC_MEMBERS = NO
# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all # If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
# undocumented classes that are normally visible in the class hierarchy. If set # undocumented classes that are normally visible in the class hierarchy. If set
# to NO, these classes will be included in the various overviews. This option # to NO, these classes will be included in the various overviews. This option
# has no effect if EXTRACT_ALL is enabled. # will also hide undocumented C++ concepts if enabled. This option has no effect
# if EXTRACT_ALL is enabled.
# The default value is: NO. # The default value is: NO.
HIDE_UNDOC_CLASSES = NO HIDE_UNDOC_CLASSES = NO
@ -595,7 +597,8 @@ INTERNAL_DOCS = NO
# Windows (including Cygwin) and MacOS, users should typically set this option # Windows (including Cygwin) and MacOS, users should typically set this option
# to NO, whereas on Linux or other Unix flavors it should typically be set to # to NO, whereas on Linux or other Unix flavors it should typically be set to
# YES. # YES.
# The default value is: system dependent. # Possible values are: SYSTEM, NO and YES.
# The default value is: SYSTEM.
CASE_SENSE_NAMES = YES CASE_SENSE_NAMES = YES
@ -847,6 +850,14 @@ WARN_IF_INCOMPLETE_DOC = YES
WARN_NO_PARAMDOC = YES WARN_NO_PARAMDOC = YES
# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about
# undocumented enumeration values. If set to NO, doxygen will accept
# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag
# will automatically be disabled.
# The default value is: NO.
WARN_IF_UNDOC_ENUM_VAL = NO
# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS # a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS
# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but # then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but
@ -905,10 +916,21 @@ INPUT = @DOXY_SOURCE_DIRECTORY@ \
# libiconv (or the iconv built into libc) for the transcoding. See the libiconv # libiconv (or the iconv built into libc) for the transcoding. See the libiconv
# documentation (see: # documentation (see:
# https://www.gnu.org/software/libiconv/) for the list of possible encodings. # https://www.gnu.org/software/libiconv/) for the list of possible encodings.
# See also: INPUT_FILE_ENCODING
# The default value is: UTF-8. # The default value is: UTF-8.
INPUT_ENCODING = UTF-8 INPUT_ENCODING = UTF-8
# This tag can be used to specify the character encoding of the source files
# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify
# character encoding on a per file pattern basis. Doxygen will compare the file
# name with each pattern and apply the encoding instead of the default
# INPUT_ENCODING) if there is a match. The character encodings are a list of the
# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding
# "INPUT_ENCODING" for further information on supported encodings.
INPUT_FILE_ENCODING =
# If the value of the INPUT tag contains directories, you can use the # If the value of the INPUT tag contains directories, you can use the
# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
# *.h) to filter out the source-files in the directories. # *.h) to filter out the source-files in the directories.
@ -945,7 +967,7 @@ RECURSIVE = YES
# Note that relative paths are relative to the directory from which doxygen is # Note that relative paths are relative to the directory from which doxygen is
# run. # run.
EXCLUDE = @DOXY_DEPS_DIRECTORY@ EXCLUDE =
# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
# directories that are symbolic links (a Unix file system feature) are excluded # directories that are symbolic links (a Unix file system feature) are excluded
@ -1015,6 +1037,11 @@ IMAGE_PATH =
# code is scanned, but not when the output code is generated. If lines are added # code is scanned, but not when the output code is generated. If lines are added
# or removed, the anchors will not be placed correctly. # or removed, the anchors will not be placed correctly.
# #
# Note that doxygen will use the data processed and written to standard output
# for further processing, therefore nothing else, like debug statements or used
# commands (so in case of a Windows batch file always use @echo OFF), should be
# written to standard output.
#
# Note that for custom extensions or not directly supported extensions you also # Note that for custom extensions or not directly supported extensions you also
# need to set EXTENSION_MAPPING for the extension otherwise the files are not # need to set EXTENSION_MAPPING for the extension otherwise the files are not
# properly processed by doxygen. # properly processed by doxygen.
@ -1056,6 +1083,15 @@ FILTER_SOURCE_PATTERNS =
USE_MDFILE_AS_MAINPAGE = @PROJECT_SOURCE_DIR@/README.md USE_MDFILE_AS_MAINPAGE = @PROJECT_SOURCE_DIR@/README.md
# The Fortran standard specifies that for fixed formatted Fortran code all
# characters from position 72 are to be considered as comment. A common
# extension is to allow longer lines before the automatic comment starts. The
# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can
# be processed before the automatic comment starts.
# Minimum value: 7, maximum value: 10000, default value: 72.
FORTRAN_COMMENT_AFTER = 72
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
# Configuration options related to source browsing # Configuration options related to source browsing
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
@ -1193,10 +1229,11 @@ CLANG_DATABASE_PATH =
ALPHABETICAL_INDEX = YES ALPHABETICAL_INDEX = YES
# In case all classes in a project start with a common prefix, all classes will # The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes)
# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag # that should be ignored while generating the index headers. The IGNORE_PREFIX
# can be used to specify a prefix (or a list of prefixes) that should be ignored # tag works for classes, function and member names. The entity will be placed in
# while generating the index headers. # the alphabetical list under the first letter of the entity name that remains
# after removing the prefix.
# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
IGNORE_PREFIX = IGNORE_PREFIX =
@ -1265,7 +1302,7 @@ HTML_FOOTER =
# obsolete. # obsolete.
# This tag requires that the tag GENERATE_HTML is set to YES. # This tag requires that the tag GENERATE_HTML is set to YES.
HTML_STYLESHEET = HTML_STYLESHEET = @DOXY_CSS_DIRECTORY@/doxygen-awesome.css
# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
# cascading style sheets that are included after the standard style sheets # cascading style sheets that are included after the standard style sheets
@ -1275,7 +1312,12 @@ HTML_STYLESHEET =
# Doxygen will copy the style sheet files to the output directory. # Doxygen will copy the style sheet files to the output directory.
# Note: The order of the extra style sheet files is of importance (e.g. the last # Note: The order of the extra style sheet files is of importance (e.g. the last
# style sheet in the list overrules the setting of the previous ones in the # style sheet in the list overrules the setting of the previous ones in the
# list). For an example see the documentation. # list).
# Note: Since the styling of scrollbars can currently not be overruled in
# Webkit/Chromium, the styling will be left out of the default doxygen.css if
# one or more extra stylesheets have been specified. So if scrollbar
# customization is desired it has to be added explicitly. For an example see the
# documentation.
# This tag requires that the tag GENERATE_HTML is set to YES. # This tag requires that the tag GENERATE_HTML is set to YES.
HTML_EXTRA_STYLESHEET = HTML_EXTRA_STYLESHEET =
@ -1290,6 +1332,19 @@ HTML_EXTRA_STYLESHEET =
HTML_EXTRA_FILES = HTML_EXTRA_FILES =
# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output
# should be rendered with a dark or light theme.
# Possible values are: LIGHT always generate light mode output, DARK always
# generate dark mode output, AUTO_LIGHT automatically set the mode according to
# the user preference, use light mode if no preference is set (the default),
# AUTO_DARK automatically set the mode according to the user preference, use
# dark mode if no preference is set and TOGGLE allow to user to switch between
# light and dark mode via a button.
# The default value is: AUTO_LIGHT.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_COLORSTYLE = LIGHT
# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
# will adjust the colors in the style sheet and background images according to # will adjust the colors in the style sheet and background images according to
# this color. Hue is specified as an angle on a color-wheel, see # this color. Hue is specified as an angle on a color-wheel, see
@ -1653,17 +1708,6 @@ HTML_FORMULA_FORMAT = png
FORMULA_FONTSIZE = 10 FORMULA_FONTSIZE = 10
# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
# generated for formulas are transparent PNGs. Transparent PNGs are not
# supported properly for IE 6.0, but are supported on all modern browsers.
#
# Note that when changing this option you need to delete any form_*.png files in
# the HTML output directory before the changes have effect.
# The default value is: YES.
# This tag requires that the tag GENERATE_HTML is set to YES.
FORMULA_TRANSPARENT = YES
# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands # The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
# to create new LaTeX commands to be used in formulas as building blocks. See # to create new LaTeX commands to be used in formulas as building blocks. See
# the section "Including formulas" for details. # the section "Including formulas" for details.
@ -2379,26 +2423,38 @@ HAVE_DOT = YES
DOT_NUM_THREADS = 0 DOT_NUM_THREADS = 0
# When you want a differently looking font in the dot files that doxygen # DOT_COMMON_ATTR is common attributes for nodes, edges and labels of
# generates you can specify the font name using DOT_FONTNAME. You need to make # subgraphs. When you want a differently looking font in the dot files that
# sure dot is able to find the font, which can be done by putting it in a # doxygen generates you can specify fontname, fontcolor and fontsize attributes.
# standard location or by setting the DOTFONTPATH environment variable or by # For details please see <a href=https://graphviz.org/doc/info/attrs.html>Node,
# setting DOT_FONTPATH to the directory containing the font. # Edge and Graph Attributes specification</a> You need to make sure dot is able
# The default value is: Helvetica. # to find the font, which can be done by putting it in a standard location or by
# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the
# directory containing the font. Default graphviz fontsize is 14.
# The default value is: fontname=Helvetica,fontsize=10.
# This tag requires that the tag HAVE_DOT is set to YES. # This tag requires that the tag HAVE_DOT is set to YES.
DOT_FONTNAME = Helvetica DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10"
# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of # DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can
# dot graphs. # add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. <a
# Minimum value: 4, maximum value: 24, default value: 10. # href=https://graphviz.org/doc/info/arrows.html>Complete documentation about
# arrows shapes.</a>
# The default value is: labelfontname=Helvetica,labelfontsize=10.
# This tag requires that the tag HAVE_DOT is set to YES. # This tag requires that the tag HAVE_DOT is set to YES.
DOT_FONTSIZE = 10 DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10"
# By default doxygen will tell dot to use the default font as specified with # DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes
# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set # around nodes set 'shape=plain' or 'shape=plaintext' <a
# the path where dot can find it using this tag. # href=https://www.graphviz.org/doc/info/shapes.html>Shapes specification</a>
# The default value is: shape=box,height=0.2,width=0.4.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4"
# You can set the path where dot can find font specified with fontname in
# DOT_COMMON_ATTR and others dot attributes.
# This tag requires that the tag HAVE_DOT is set to YES. # This tag requires that the tag HAVE_DOT is set to YES.
DOT_FONTPATH = DOT_FONTPATH =
@ -2641,18 +2697,6 @@ DOT_GRAPH_MAX_NODES = 50
MAX_DOT_GRAPH_DEPTH = 0 MAX_DOT_GRAPH_DEPTH = 0
# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
# background. This is disabled by default, because dot on Windows does not seem
# to support this out of the box.
#
# Warning: Depending on the platform used, enabling this option may lead to
# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
# read).
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_TRANSPARENT = NO
# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output # Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
# files in one run (i.e. multiple -o and -T options on the command line). This # files in one run (i.e. multiple -o and -T options on the command line). This
# makes dot run faster, but since only newer versions of dot (>1.8.10) support # makes dot run faster, but since only newer versions of dot (>1.8.10) support

View File

@ -17,7 +17,6 @@
* [ENTT_DISABLE_ASSERT](#entt_disable_assert) * [ENTT_DISABLE_ASSERT](#entt_disable_assert)
* [ENTT_NO_ETO](#entt_no_eto) * [ENTT_NO_ETO](#entt_no_eto)
* [ENTT_STANDARD_CPP](#entt_standard_cpp) * [ENTT_STANDARD_CPP](#entt_standard_cpp)
<!-- <!--
@endcond TURN_OFF_DOXYGEN @endcond TURN_OFF_DOXYGEN
--> -->

View File

@ -9,7 +9,6 @@
* [Containers](#containers) * [Containers](#containers)
* [Dense map](#dense-map) * [Dense map](#dense-map)
* [Dense set](#dense-set) * [Dense set](#dense-set)
<!-- <!--
@endcond TURN_OFF_DOXYGEN @endcond TURN_OFF_DOXYGEN
--> -->
@ -21,7 +20,7 @@ difficult to do better (although it's very easy to do worse, as many examples
available online demonstrate).<br/> available online demonstrate).<br/>
`EnTT` doesn't try in any way to replace what is offered by the standard. Quite `EnTT` doesn't try in any way to replace what is offered by the standard. Quite
the opposite, given the widespread use that is made of standard containers.<br/> the opposite, given the widespread use that is made of standard containers.<br/>
However, the library also tries to fill a gap in features and functionality by However, the library also tries to fill a gap in features and functionalities by
making available some containers initially developed for internal use. making available some containers initially developed for internal use.
This section of the library is likely to grow larger over time. However, for the 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. implicit list within the packed array itself.
The interface is very close to its counterpart in the standard library, that is, The interface is very close to its counterpart in the standard library, that is,
`std::unordered_map`.<br/> the `std::unordered_map` class.<br/>
However, both local and non-local iterators returned by a dense map belong to 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 the input iterator category although they respectively model the concepts of a
_forward iterator_ type and a _random access iterator_ type.<br/> _forward iterator_ type and a _random access iterator_ type.<br/>
@ -63,5 +62,5 @@ The implementation is based on _sparse sets_ and each bucket is identified by an
implicit list within the packed array itself. implicit list within the packed array itself.
The interface is in all respects similar to its counterpart in the standard The interface is in all respects similar to its counterpart in the standard
library, that is, `std::unordered_set`.<br/> library, that is, the `std::unordered_set` class.<br/>
Therefore, there is no need to go into the API description. Therefore, there is no need to go into the API description.

View File

@ -46,25 +46,24 @@
# Introduction # Introduction
`EnTT` comes with a bunch of core functionalities mostly used by the other parts `EnTT` comes with a bunch of core functionalities mostly used by the other parts
of the library itself.<br/> of the library.<br/>
Hardly users will include these features in their code, but it's worth Many of these tools are also useful in everyday work. Therefore, it's worth
describing what `EnTT` offers so as not to reinvent the wheel in case of need. describing them so as not to reinvent the wheel in case of need.
# Any as in any type # Any as in any type
`EnTT` comes with its own `any` type. It may seem redundant considering that `EnTT` offers its own `any` type. It may seem redundant considering that C++17
C++17 introduced `std::any`, but it is not (hopefully).<br/> introduced `std::any`, but it is not (hopefully).<br/>
First of all, the _type_ returned by an `std::any` is a const reference to an 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 `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 wants to see in a software. Furthermore, there is no way to bind it to the type
type system of the library and therefore with its integrated RTTI support.<br/> system of the library and therefore with its integrated RTTI support.
Note that this class is largely used internally by the library itself.
The API is very similar to that of its most famous counterpart, mainly because The `any` API is very similar to that of its most famous counterpart, mainly
this class serves the same purpose of being an opaque container for any type of because this class serves the same purpose of being an opaque container for any
value.<br/> type of value.<br/>
Instances of `any` also minimize the number of allocations by relying on a well Instances also minimize the number of allocations by relying on a well known
known technique called _small buffer optimization_ and a fake vtable. technique called _small buffer optimization_ and a fake vtable.
Creating an object of the `any` type, whether empty or not, is trivial: 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 wrapper is reconfigured when it's assigned a new object of a type other than
the one it contains. the one it contains.
There exists also a way to directly assign a value to the variable contained by There is also a way to directly assign a value to the variable contained by an
an `entt::any`, without necessarily replacing it. This is especially useful when `entt::any`, without necessarily replacing it. This is especially useful when
the object is used in _aliasing mode_, as described below: the object is used in _aliasing mode_, as described below:
```cpp ```cpp
@ -108,15 +107,15 @@ any.assign(value);
any.assign(std::move(value)); any.assign(std::move(value));
``` ```
The `any` class will also perform a check on the type information and whether or The `any` class performs a check on the type information and whether or not the
not the original type was copy or move assignable, as appropriate.<br/> original type was copy or move assignable, as appropriate.<br/>
In all cases, the `assign` function returns a boolean value to indicate the In all cases, the `assign` function returns a boolean value that is true in case
success or failure of the operation. of success and false otherwise.
When in doubt about the type of object contained, the `type` member function of When in doubt about the type of object contained, the `type` member function
`any` returns a const reference to the `type_info` associated with its element, returns a const reference to the `type_info` associated with its element, or
or `type_id<void>()` if the container is empty. The type is also used internally `type_id<void>()` if the container is empty.<br/>
when comparing two `any` objects: The type is also used internally when comparing two `any` objects:
```cpp ```cpp
if(any == empty) { /* ... */ } if(any == empty) { /* ... */ }
@ -125,7 +124,7 @@ if(any == empty) { /* ... */ }
In this case, before proceeding with a comparison, it's verified that the _type_ In this case, before proceeding with a comparison, it's verified that the _type_
of the two objects is actually the same.<br/> of the two objects is actually the same.<br/>
Refer to the `EnTT` type system documentation for more details about how 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 A particularly interesting feature of this class is that it can also be used as
an opaque container for const and non-const references: 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 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 object or is as a reference for unmanaged elements already. The new instance
thus created won't create copies and will only serve as a reference for the thus created doesn't create copies and only serves as a reference for the
original item.<br/> 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.
As a side note, it's worth mentioning that, while everything works transparently It's worth mentioning that, while everything works transparently when it comes
when it comes to non-const references, there are some exceptions when it comes to non-const references, there are some exceptions when it comes to const
to const references.<br/> references.<br/>
In particular, the `data` member function invoked on a non-const instance of 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` To cast an instance of `any` to a type, the library offers a set of `any_cast`
functions in all respects similar to their most famous counterparts.<br/> functions in all respects similar to their most famous counterparts.<br/>
The only difference is that, in the case of `EnTT`, these won't raise exceptions The only difference is that, in the case of `EnTT`, they won't raise exceptions
but will only trigger an assert in debug mode, otherwise resulting in undefined but will only trigger an assert in debug mode, otherwise resulting in undefined
behavior in case of misuse in release mode. behavior in case of misuse in release mode.
@ -188,31 +184,23 @@ using my_any = entt::basic_any<sizeof(double[4])>;
This feature, in addition to allowing the choice of a size that best suits the 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 needs of an application, also offers the possibility of forcing dynamic creation
of objects during construction.<br/> of objects during construction.<br/>
In other terms, if the size is 0, `any` avoids the use of any optimization and In other terms, if the size is 0, `any` suppresses the small buffer optimization
always dynamically allocates objects (except for aliasing cases). 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.
## Alignment requirement ## Alignment requirement
The alignment requirement is optional and by default the most stringent (the The alignment requirement is optional and by default the most stringent (the
largest) for any object whose size is at most equal to the one provided.<br/> largest) for any object whose size is at most equal to the one provided.<br/>
The `basic_any` class template inspects the alignment requirements in each case, It's provided as an optional second parameter following the desired size for the
even when not provided and may decide not to use the small buffer optimization internal storage:
in order to meet them.
The alignment requirement is provided as an optional second parameter following
the desired size for the internal storage:
```cpp ```cpp
using my_any = entt::basic_any<sizeof(double[4]), alignof(double[4])>; using my_any = entt::basic_any<sizeof(double[4]), alignof(double[4])>;
``` ```
Note that the alignment requirements as well as the size of the internal storage The `basic_any` class template inspects the alignment requirements in each case,
are directly part of the type and therefore contribute to define different types even when not provided and may decide not to use the small buffer optimization
that won't be able to interoperate with each other. in order to meet them.
# Compressed pair # 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 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 the template parameters are inferred from the constructor and therefore there is
no` entt::make_compressed_pair`), the major difference is that `first` and no `entt::make_compressed_pair`), the major difference is that `first` and
`second` are functions for implementation needs: `second` are functions for implementation requirements:
```cpp ```cpp
entt::compressed_pair pair{0, 3.}; 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 # Enum as bitmask
Sometimes it's useful to be able to use enums as bitmasks. However, enum classes 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 aren't really suitable for the purpose. Main problem is that they don't convert
don't convert implicitly to their underlying type.<br/> implicitly to their underlying type.<br/>
All that remains is to make a choice between using old-fashioned enums (with all The choice is then between using old-fashioned enums (with all their problems
their problems that I don't want to discuss here) or writing _ugly_ code. 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 Fortunately, there is also a third way: adding enough operators in the global
scope to treat enum classes as bitmask transparently.<br/> scope to treat enum classes as bitmasks transparently.<br/>
The ultimate goal is to be able to write code like the following (or maybe The ultimate goal is to write code like the following (or maybe something more
something more meaningful, but this should give a grasp and remain simple at the meaningful, but this should give a grasp and remain simple at the same time):
same time):
```cpp ```cpp
enum class my_flag { enum class my_flag {
@ -261,11 +248,11 @@ const my_flag flags = my_flag::enabled;
const bool is_enabled = !!(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 The problem with adding all operators to the global scope is that these come
come into play even when not required, with the risk of introducing errors that into play even when not required, with the risk of introducing errors that are
are difficult to deal with.<br/> difficult to deal with.<br/>
However, C++ offers enough tools to get around this problem. In particular, the 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: should be enabled:
```cpp ```cpp
@ -276,7 +263,7 @@ struct entt::enum_as_bitmask<my_flag>
``` ```
This is handy when dealing with enum classes defined by third party libraries 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: avoided by adding a specific value to the enum class itself:
```cpp ```cpp
@ -289,23 +276,21 @@ enum class my_flag {
``` ```
In this case, there is no need to specialize the `enum_as_bitmask` traits, since In this case, there is no need to specialize the `enum_as_bitmask` traits, since
`EnTT` will automatically detect the flag and enable the bitmask support.<br/> `EnTT` automatically detects the flag and enables the bitmask support.<br/>
Once the enum class has been registered (in one way or the other) all the most Once the enum class is registered (in one way or the other), the most common
common operators will be available, such as `&`, `|` but also `&=` and `|=`. operators such as `&`, `|` but also `&=` and `|=` are available for use.
Refer to the official documentation for the full list of operators. Refer to the official documentation for the full list of operators.
# Hashed strings # Hashed strings
A hashed string is a zero overhead unique identifier. Users can use Hashed strings are human-readable identifiers in the codebase that turn into
human-readable identifiers in the codebase while using their numeric numeric values at runtime, thus without affecting performance.<br/>
counterparts at runtime, thus without affecting performance.<br/>
The class has an implicit `constexpr` constructor that chews a bunch of 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 characters. Once created, one can get the original string by means of the `data`
original string through the `data` member function or converting the instance member function or convert the instance into a number.<br/>
into a number.<br/> A hashed string is well suited wherever a constant expression is required. No
The good part is that a hashed string can be used wherever a constant expression _string-to-number_ conversion will take place at runtime if used carefully.
is required and no _string-to-number_ conversion will take place at runtime if
used carefully.
Example of use: 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 There is also a _user defined literal_ dedicated to hashed strings to make them
more user-friendly: more _user-friendly_:
```cpp ```cpp
using namespace entt::literals; using namespace entt::literals;
constexpr auto str = "text"_hs; constexpr auto str = "text"_hs;
``` ```
To use it, remember that all user defined literals in `EnTT` are enclosed in the User defined literals in `EnTT` are enclosed in the `entt::literals` namespace.
`entt::literals` namespace. Therefore, the entire namespace or selectively the Therefore, the entire namespace or selectively the literal of interest must be
literal of interest must be explicitly included before each use, a bit like explicitly included before each use, a bit like `std::literals`.<br/>
`std::literals`.<br/> The class also offers the necessary functionalities to create hashed strings at
Finally, in case users need to create hashed strings at runtime, this class also runtime:
offers the necessary functionalities:
```cpp ```cpp
std::string orig{"text"}; 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 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. performance to some degrees.
## Wide characters ## Wide characters
The hashed string has a design that is close to that of an `std::basic_string`. The `hashed_string` class is an alias for `basic_hashed_string<char>`. To use
It means that `hashed_string` is nothing more than an alias for the C++ type for wide character representations, there exists also the alias
`basic_hashed_string<char>`. For those who want to use the C++ type for wide `hashed_wstring` for `basic_hashed_string<wchar_t>`.<br/>
character representation, there exists also the alias `hashed_wstring` for
`basic_hashed_string<wchar_t>`.<br/>
In this case, the user defined literal to use to create hashed strings on the In this case, the user defined literal to use to create hashed strings on the
fly is `_hws`: fly is `_hws`:
@ -360,16 +342,15 @@ fly is `_hws`:
constexpr auto str = L"text"_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 ## Conflicts
The hashed string class uses internally FNV-1a to compute the numeric The hashed string class uses FNV-1a internally to hash strings. Because of the
counterpart of a string. Because of the _pigeonhole principle_, conflicts are _pigeonhole principle_, conflicts are possible. This is a fact.<br/>
possible. This is a fact.<br/>
There is no silver bullet to solve the problem of conflicts when dealing with 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. hashing functions. In this case, the best solution is likely to give up. That's
That's all.<br/> all.<br/>
After all, human-readable unique identifiers aren't something strictly defined After all, human-readable unique identifiers aren't something strictly defined
and over which users have not the control. Choosing a slightly different 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 identifier is probably the best solution to make the conflict disappear in this
@ -377,8 +358,8 @@ case.
# Iterators # Iterators
Writing and working with iterators isn't always easy and more often than not Writing and working with iterators isn't always easy. More often than not it
leads to duplicated code.<br/> also leads to duplicated code.<br/>
`EnTT` tries to overcome this problem by offering some utilities designed to `EnTT` tries to overcome this problem by offering some utilities designed to
make this hard work easier. make this hard work easier.
@ -431,7 +412,7 @@ user.
## Iterable adaptor ## Iterable adaptor
Typically, a container class provides `begin` and `end` member functions (with Typically, a container class provides `begin` and `end` member functions (with
their const counterparts) to be iterated by the user.<br/> their const counterparts) for iteration.<br/>
However, it can happen that a class offers multiple iteration methods or allows However, it can happen that a class offers multiple iteration methods or allows
users to iterate different sets of _elements_. 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 There are a handful of tools within `EnTT` to interact with memory in one way or
another.<br/> another.<br/>
Some are geared towards simplifying the implementation of (internal or external) Some are geared towards simplifying the implementation of (internal or external)
allocator aware containers. Others, on the other hand, are designed to help the allocator aware containers. Others are designed to help the developer with
developer with everyday problems. everyday problems.
The former are very specific and for niche problems. These are tools designed to 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 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 A nasty thing in C++ (at least up to C++20) is the fact that shared pointers
support allocators while unique pointers don't.<br/> support allocators while unique pointers don't.<br/>
There is a proposal at the moment that also shows among the other things how There is a proposal at the moment that also shows (among the other things) how
this can be implemented without any compiler support. this can be implemented without any compiler support.
The `allocate_unique` function follows this proposal, making a virtue out of The `allocate_unique` function follows this proposal, making a virtue out of
@ -498,13 +479,16 @@ the same feature.
# Monostate # Monostate
The monostate pattern is often presented as an alternative to a singleton based The monostate pattern is often presented as an alternative to a singleton based
configuration system. This is exactly its purpose in `EnTT`. Moreover, this configuration system.<br/>
implementation is thread safe by design (hopefully).<br/> This is exactly its purpose in `EnTT`. Moreover, this implementation is thread
Keys are represented by hashed strings, values are basic types like `int`s or safe by design (hopefully).
`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 Keys are integral values (easily obtained by hashed strings), values are basic
both during an assignment and when they try to read back their data. Otherwise, types like `int`s or `bool`s. Values of different types can be associated with
they will probably incur in unexpected results. each key, even more than one at a time.<br/>
Because of this, one should pay attention to use the same type both during an
assignment and when trying to read back the data. Otherwise, there is the risk
to incur in unexpected results.
Example of use: Example of use:
@ -542,17 +526,10 @@ Basically, the whole system relies on a handful of classes. In particular:
auto index = entt::type_index<a_type>::value(); auto index = entt::type_index<a_type>::value();
``` ```
The returned value isn't guaranteed to be stable across different runs. The returned value isn't guaranteed to be stable across different runs.<br/>
However, it can be very useful as index in associative and unordered However, it can be very useful as index in associative and unordered
associative containers or for positional accesses in a vector or an array. associative containers or for positional accesses in a vector or an array.
So as not to conflict with the other tools available, the `family` class isn't
used to generate these indexes. Therefore, the numeric identifiers returned by
the two tools may differ.<br/>
On the other hand, this leaves users with full powers over the `family` class
and therefore the generation of custom runtime sequences of indices for their
own purposes, if necessary.
An external generator can also be used if needed. In fact, `type_index` can be 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 specialized by type and is also _sfinae-friendly_ in order to allow more
refined specializations such as: refined specializations such as:
@ -566,7 +543,7 @@ Basically, the whole system relies on a handful of classes. In particular:
}; };
``` ```
Note that indexes **must** still be generated sequentially in this case.<br/> Indexes **must** be sequentially generated in this case.<br/>
The tool is widely used within `EnTT`. Generating indices not sequentially The tool is widely used within `EnTT`. Generating indices not sequentially
would break an assumption and would likely lead to undesired behaviors. 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 This function **can** use non-standard features of the language for its own
purposes. This makes it possible to provide compile-time identifiers that purposes. This makes it possible to provide compile-time identifiers that
remain stable across different runs.<br/> remain stable across different runs.<br/>
In all cases, users can prevent the library from using these features by means Users can prevent the library from using these features by means of the
of the `ENTT_STANDARD_CPP` definition. In this case, there is no guarantee `ENTT_STANDARD_CPP` definition. In this case, there is no guarantee that
that identifiers remain stable across executions. Moreover, they are generated identifiers remain stable across executions. Moreover, they are generated
at runtime and are no longer a compile-time thing. 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 As it happens with `type_index`, also `type_hash` is a _sfinae-friendly_ class
specialized in order to customize its behavior globally or on a per-type or that can be specialized in order to customize its behavior globally or on a
per-traits basis. per-type or per-traits basis.
* The name associated with a given type: * The name associated with a given type:
@ -598,10 +575,9 @@ Basically, the whole system relies on a handful of classes. In particular:
auto name = entt::type_name<a_type>::value(); auto name = entt::type_name<a_type>::value();
``` ```
The name associated with a type is extracted from some information generally This value is extracted from some information generally made available by the
made available by the compiler in use. Therefore, it may differ depending on compiler in use. Therefore, it may differ depending on the compiler and may be
the compiler and may be empty in the event that this information isn't empty in the event that this information isn't available.<br/>
available.<br/>
For example, given the following class: For example, given the following class:
```cpp ```cpp
@ -612,21 +588,20 @@ Basically, the whole system relies on a handful of classes. In particular:
when MSVC is in use.<br/> when MSVC is in use.<br/>
Most of the time the name is also retrieved at compile-time and is therefore 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 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 modify it as needed, for example by removing the word `struct` to normalize
the result. `EnTT` won't do this for obvious reasons, since it requires the result. `EnTT` doesn't do this for obvious reasons, since it would be
copying and creating a new string potentially at runtime. creating a new string at runtime otherwise.
This function **can** use non-standard features of the language for its own 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 purposes. Users can prevent the library from using these features by means of
means of the `ENTT_STANDARD_CPP` definition. In this case, the name will be the `ENTT_STANDARD_CPP` definition. In this case, the name is just empty.
empty by default.
As for `type_index`, also `type_name` is a _sfinae-friendly_ class that can be As it happens with `type_index`, also `type_name` is a _sfinae-friendly_ class
specialized in order to customize its behavior globally or on a per-type or that can be specialized in order to customize its behavior globally or on a
per-traits basis. per-type or per-traits basis.
These are then combined into utilities that aim to offer an API that is somewhat 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 ### 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 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) 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 have the same fully qualified name. In this case, `type_name` returns the same
the same value for the two types.<br/> value for the two types.<br/>
Fortunately, there are several easy ways to deal with this: Fortunately, there are several easy ways to deal with this:
* The most trivial one is to define the `ENTT_STANDARD_CPP` macro. Runtime * The most trivial one is to define the `ENTT_STANDARD_CPP` macro. Runtime
@ -739,9 +714,9 @@ offered by this module.
### Size of ### Size of
The standard operator `sizeof` complains when users provide it for example with The standard operator `sizeof` complains if users provide it with functions or
function or incomplete types. On the other hand, it's guaranteed that its result incomplete types. On the other hand, it's guaranteed that its result is always
is always nonzero, even if applied to an empty class type.<br/> non-zero, even if applied to an empty class type.<br/>
This small class combines the two and offers an alternative to `sizeof` that 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: works under all circumstances, returning zero if the type isn't supported:
@ -777,8 +752,8 @@ A utility to easily transfer the constness of a type to another type:
using type = entt::constness_as_t<dst_type, const src_type>; using type = entt::constness_as_t<dst_type, const src_type>;
``` ```
The trait is subject to the rules of the language. Therefore, for example, The trait is subject to the rules of the language. For example, _transferring_
transferring constness between references won't give the desired effect. constness between references won't give the desired effect.
### Member class type ### 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): data member (for blind operations on opaque types):
```cpp ```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 Disambiguation of overloaded functions is the responsibility of the user, should
@ -825,8 +800,8 @@ registry.emplace<enemy_tag>(entity);
### Tag ### Tag
Since `id_type` is very important and widely used in `EnTT`, there is a more Type `id_type` is very important and widely used in `EnTT`. Therefore, there is
user-friendly shortcut for the creation of integral constants based on it.<br/> a more user-friendly shortcut for the creation of constants based on it.<br/>
This shortcut is the alias template `entt::tag`. This shortcut is the alias template `entt::tag`.
If used in combination with hashed strings, it helps to use human-readable names 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 ## Runtime generator
To generate sequential numeric identifiers at runtime, `EnTT` offers the The `family` class template helps to generate sequential numeric identifiers for
`family` class template: types at runtime:
```cpp ```cpp
// defines a custom generator // defines a custom generator
@ -936,8 +911,8 @@ numeric identifier for the given type.<br/>
The generator is customizable, so as to get different _sequences_ for different The generator is customizable, so as to get different _sequences_ for different
purposes if needed. purposes if needed.
Please, note that identifiers aren't guaranteed to be stable across different Identifiers aren't guaranteed to be stable across different runs. Indeed it
runs. Indeed it mostly depends on the flow of execution. mostly depends on the flow of execution.
# Utilities # Utilities

File diff suppressed because it is too large Load Diff

View File

@ -193,7 +193,7 @@ to mitigate the problem makes it manageable.
## Which functions trigger which signals ## Which functions trigger which signals
The `registry` class offers three signals that are emitted following specific Storage classes offer three _signals_ that are emitted following specific
operations. Maybe not everyone knows what these operations are, though.<br/> operations. Maybe not everyone knows what these operations are, though.<br/>
If this isn't clear, below you can find a _vademecum_ for this purpose: If this isn't clear, below you can find a _vademecum_ for this purpose:

View File

@ -14,7 +14,6 @@
* [Fake resources and order of execution](#fake-resources-and-order-of-execution) * [Fake resources and order of execution](#fake-resources-and-order-of-execution)
* [Sync points](#sync-points) * [Sync points](#sync-points)
* [Execution graph](#execution-graph) * [Execution graph](#execution-graph)
<!-- <!--
@endcond TURN_OFF_DOXYGEN @endcond TURN_OFF_DOXYGEN
--> -->
@ -23,15 +22,15 @@
`EnTT` doesn't aim to offer everything one needs to work with graphs. Therefore, `EnTT` doesn't aim to offer everything one needs to work with graphs. Therefore,
anyone looking for this in the _graph_ submodule will be disappointed.<br/> anyone looking for this in the _graph_ submodule will be disappointed.<br/>
Quite the opposite is true. This submodule is minimal and contains only the data Quite the opposite is true though. This submodule is minimal and contains only
structures and algorithms strictly necessary for the development of some tools the data structures and algorithms strictly necessary for the development of
such as the _flow builder_. some tools such as the _flow builder_.
# Data structures # Data structures
As anticipated in the introduction, the aim isn't to offer all possible data 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 structures suitable for representing and working with graphs. Many will likely
be added or refined over time, however I want to discourage anyone expecting be added or refined over time. However I want to discourage anyone expecting
tight scheduling on the subject.<br/> tight scheduling on the subject.<br/>
The data structures presented in this section are mainly useful for the 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. development and support of some tools which are also part of the same submodule.
@ -49,7 +48,7 @@ The `directed_tag` type _creates_ the graph as directed. There is also an
`undirected_tag` counterpart which creates it as undirected.<br/> `undirected_tag` counterpart which creates it as undirected.<br/>
The interface deviates slightly from the typical double indexing of C and offers 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 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[]`: `erase` functions rather than a double call to an `operator[]`:
```cpp ```cpp
@ -60,14 +59,14 @@ if(adjacency_matrix.contains(0u, 1u)) {
} }
``` ```
Both `insert` and` erase` are idempotent functions which have no effect if the Both `insert` and` erase` are _idempotent_ functions which have no effect if the
element already exists or has already been deleted.<br/> element already exists or has already been deleted.<br/>
The first one returns an `std::pair` containing the iterator to the element and 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 a boolean value indicating whether the element was newly inserted or not. The
present. The second one instead returns the number of deleted elements (0 or 1). second one returns the number of deleted elements (0 or 1).
An adjacency matrix must be initialized with the number of elements (vertices) An adjacency matrix is initialized with the number of elements (vertices) when
when constructing it but can also be resized later using the `resize` function: constructing it but can also be resized later using the `resize` function:
```cpp ```cpp
entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{3u}; entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{3u};
@ -82,8 +81,8 @@ for(auto &&vertex: adjacency_matrix.vertices()) {
} }
``` ```
Note that the same result can be obtained with the following snippet, since the The same result is obtained with the following snippet, since the vertices are
vertices are unsigned integral values: plain unsigned integral values:
```cpp ```cpp
for(auto last = adjacency_matrix.size(), pos = {}; pos < last; ++pos) { for(auto last = adjacency_matrix.size(), pos = {}; pos < last; ++pos) {
@ -93,8 +92,8 @@ for(auto last = adjacency_matrix.size(), pos = {}; pos < last; ++pos) {
As for visiting the edges, a few functions are available.<br/> As for visiting the edges, a few functions are available.<br/>
When the purpose is to visit all the edges of a given adjacency matrix, the 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 `edges` function returns an iterable object that is used to get them as pairs of
pairs of vertices: vertices:
```cpp ```cpp
for(auto [lhs, rhs]: adjacency_matrix.edges()) { 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 If the goal is to visit all the in- or out-edges of a given vertex instead, the
vertex, the `in_edges` and `out_edges` functions are meant for that: `in_edges` and `out_edges` functions are meant for that:
```cpp ```cpp
for(auto [lhs, rhs]: adjacency_matrix.out_edges(3u)) { 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 Both the functions expect the vertex to visit (that is, to return the in- or
return the in- or out-edges for) as an argument.<br/> out-edges for) as an argument.<br/>
Finally, the adjacency matrix is an allocator-aware container and offers most of 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` the functionalities one would expect from this type of containers, such as
or 'get_allocator` and so on. `clear` or 'get_allocator` and so on.
## Graphviz dot language ## Graphviz dot language
@ -129,19 +128,19 @@ std::ostringstream output{};
entt::dot(output, adjacency_matrix); entt::dot(output, adjacency_matrix);
``` ```
However, there is also the option of providing a callback to which the vertices It's also possible to provide a callback to which the vertices are passed and
are passed and which can be used to add (`dot`) properties to the output from which can be used to add (`dot`) properties to the output as needed:
time to time:
```cpp ```cpp
std::ostringstream output{}; std::ostringstream output{};
entt::dot(output, adjacency_matrix, [](auto &output, auto vertex) { entt::dot(output, adjacency_matrix, [](auto &output, auto vertex) {
out << "label=\"v\"" << vertex << ",shape=\"box\""; out << "label=\"v\"" << vertex << ",shape=\"box\"";
}); });
``` ```
This second mode is particularly convenient when the user wants to associate 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 # Flow builder
@ -155,42 +154,42 @@ specified.<br/>
Most of the functions in the API also return the flow builder itself, according 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. 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 Once all tasks are registered and resources assigned to them, an execution graph
graph in the form of an adjacency matrix is returned to the user.<br/> in the form of an adjacency matrix is returned to the user.<br/>
This graph contains all the tasks assigned to the flow builder in the form of 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 _vertices_. The _vertex_ itself is used as an index to get the identifier passed
passed during registration. during registration.
## Tasks and resources ## Tasks and resources
Although these terms are used extensively in the documentation, the flow builder Although these terms are used extensively in the documentation, the flow builder
has no real concept of tasks and resources.<br/> has no real concept of tasks and resources.<br/>
This class works mainly with _identifiers_, that is, values of type `id_type`. This class works mainly with _identifiers_, that is, values of type `id_type`.
That is, both tasks and resources are identified by integral values.<br/> In other terms, both tasks and resources are identified by integral values.<br/>
This allows not to couple the class itself to the rest of the library or to any 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 particular data structure. On the other hand, it requires the user to keep track
of the association between identifiers and operations or actual data. of the association between identifiers and operations or actual data.
Once a flow builder has been created (which requires no constructor arguments), Once a flow builder is created (which requires no constructor arguments), the
the first thing to do is to bind a task. This will indicate to the builder who first thing to do is to bind a task. This tells to the builder _who_ intends to
intends to consume the resources that will be specified immediately after: consume the resources that are specified immediately after:
```cpp ```cpp
entt::flow builder{}; entt::flow builder{};
builder.bind("task_1"_hs); builder.bind("task_1"_hs);
``` ```
Note that the example uses the `EnTT` hashed string to generate an identifier The example uses the `EnTT` hashed string to generate an identifier for the
for the task.<br/> task.<br/>
Indeed, the use of `id_type` as an identifier type is not by accident. In fact, 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 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 same type returned by the hash function of the internal RTTI system, in case the
user wants to rely on that.<br/> user wants to rely on that.<br/>
However, being an integral value, it leaves the user full freedom to rely on his 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 Once a task is associated with the flow builder, it's also assigned read-only or
read-only or read-write resources, as appropriate: read-write resources as appropriate:
```cpp ```cpp
builder builder
@ -203,13 +202,87 @@ builder
As mentioned, many functions return the builder itself and it's therefore easy As mentioned, many functions return the builder itself and it's therefore easy
to concatenate the different calls.<br/> to concatenate the different calls.<br/>
Also in the case of resources, these are identified by numeric values of type Also in the case of resources, they are identified by numeric values of type
`id_type`. As above, the choice is not entirely random. This goes well with the `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. 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 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. pair of iterators, so that one can pass a range of resources in one go.
### Rebinding
The `flow` class is resource based rather than task based. This means that graph
generation is driven by resources and not by the order of _appearance_ of tasks
during flow definition.<br/>
Although this concept is particularly important, it's almost irrelevant for the
vast majority of cases. However, it becomes relevant when _rebinding_ resources
or tasks.
In fact, nothing prevents rebinding elements to a flow.<br/>
However, the behavior changes slightly from case to case and has some nuances
that it's worth knowing about.
Directly rebinding a resource without the task being replaced trivially results
in the task's access mode for that resource being updated:
```cpp
builder.bind("task"_hs).rw("resource"_hs).ro("resource"_hs)
```
In this case, the resource is accessed in read-only mode, regardless of the
first call to `rw`.<br/>
Behind the scenes, the call doesn't actually _replace_ the previous one but is
queued after it instead, overwriting it when generating the graph. Thus, a large
number of resource rebindings may even impact processing times (very difficult
to observe but theoretically possible).
Rebinding resources and also combining it with changes to tasks has far more
implications instead.<br/>
As mentioned, graph generation takes place starting from resources and not from
tasks. Therefore, the result may not be as expected:
```cpp
builder
.bind("task_1"_hs)
.ro("resource"_hs)
.bind("task_2"_hs)
.ro("resource"_hs)
.bind("task_1"_hs)
.rw("resource"_hs);
```
What happens here is that the resource first _sees_ a read-only access request
from the first task, then a read-write request from the second task and finally
a new read-only request from the first task.<br/>
Although this definition would probably be counted as an error, the resulting
graph may be unexpected. This in fact consists of a single edge outgoing from
the second task and directed to the first task.<br/>
To intuitively understand what happens, it's enough to think of the fact that a
task never has an edge pointing to itself.
While not obvious, this approach has its pros and cons like any other solution.
For example, creating loops is actually simple in the context of resource-based
graph generations:
```cpp
builder
.bind("task_1"_hs)
.rw("resource"_hs)
.bind("task_2"_hs)
.rw("resource"_hs)
.bind("task_1"_hs)
.rw("resource"_hs);
```
As expected, this definition leads to the creation of two edges that define a
loop between the two tasks.
As a general rule, rebinding resources and tasks is highly discouraged because
it could lead to subtle bugs if users don't know what they're doing.<br/>
However, once the mechanisms of resource-based graph generation are understood,
it can offer to the expert user a flexibility and a range of possibilities
otherwise inaccessible.
## Fake resources and order of execution ## Fake resources and order of execution
The flow builder doesn't offer the ability to specify when a task should execute The flow builder doesn't offer the ability to specify when a task should execute
@ -217,10 +290,10 @@ before or after another task.<br/>
In fact, the order of _registration_ on the resources also determines the order In 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. in which the tasks are processed during the generation of the execution graph.
However, there is a way to force the execution order of two processes.<br/> However, there is a way to _force_ the execution order of two processes.<br/>
Briefly, since accessing a resource in opposite modes requires sequential rather 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 than parallel scheduling, it's possible to make use of fake resources to rule on
the order execution: the execution order:
```cpp ```cpp
builder builder
@ -235,10 +308,10 @@ builder
.ro("fake"_hs) .ro("fake"_hs)
``` ```
This snippet forces the execution of `task_2` and `task_3` **after** `task_1`. This snippet forces the execution of `task_1` **before** `task_2` and `task_3`.
This is due to the fact that the latter sets a read-write requirement on a fake This is due to the fact that the former sets a read-write requirement on a fake
resource that the other tasks also want to access in read-only mode.<br/> resource that the other tasks also want to access in read-only mode.<br/>
Similarly, it's possible to force a task to run after a certain group: Similarly, it's possible to force a task to run **after** a certain group:
```cpp ```cpp
builder builder
@ -261,7 +334,7 @@ others tasks.
Sometimes it's useful to assign the role of _sync point_ to a node.<br/> Sometimes it's useful to assign the role of _sync point_ to a node.<br/>
Whether it accesses new resources or is simply a watershed, the procedure for 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: builder, then the `sync` function is invoked:
```cpp ```cpp
@ -283,7 +356,7 @@ all specified constraints to return the best scheduling for the vertices:
entt::adjacency_matrix<entt::directed_tag> graph = builder.graph(); entt::adjacency_matrix<entt::directed_tag> graph = builder.graph();
``` ```
The search for the main vertices, that is those without in-edges, is usually the Searching for the main vertices (that is, those without in-edges) is usually the
first thing required: first thing required:
```cpp ```cpp
@ -294,6 +367,6 @@ for(auto &&vertex: graph) {
} }
``` ```
Starting from them, using the other functions appropriately (such as `out_edges` Then it's possible to instantiate an execution graph by means of other functions
to retrieve the children of a given task or `edges` to access their identifiers) such as `out_edges` to retrieve the children of a given task or `edges` to get
it will be possible to instantiate an execution graph. the identifiers.

View File

@ -19,14 +19,12 @@
general and on GNU/Linux when default visibility was set to hidden. The 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 limitation was mainly due to a custom utility used to assign unique, sequential
identifiers with different types.<br/> identifiers with different types.<br/>
Fortunately, nowadays using `EnTT` across boundaries is much easier. Fortunately, nowadays `EnTT` works smoothly across boundaries.
## Smooth until proven otherwise ## Smooth until proven otherwise
Many classes in `EnTT` make extensive use of type erasure for their purposes. 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 This raises the need to identify objects whose type has been erased.<br/>
to use). However, a way is needed to recognize the objects whose type has been
erased on the other side of a boundary.<br/>
The `type_hash` class template is how identifiers are generated and thus made 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 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 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. issue in a concise and elegant way. Please refer to the specific documentation.
When working with linked libraries, compile definitions `ENTT_API_EXPORT` and 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, `ENTT_API_IMPORT` are to import or export symbols, so as to make everything work
so as to make everything work nicely across boundaries.<br/> nicely across boundaries.<br/>
On the other hand, everything should run smoothly when working with plugins or On the other hand, everything should run smoothly when working with plugins or
shared libraries that don't export any symbols. shared libraries that don't export any symbols.
For anyone who needs more details, the test suite contains multiple examples For those who need more details, the test suite contains many examples covering
covering the most common cases (see the `lib` directory for all details).<br/> the most common cases (see the `lib` directory for all details).<br/>
It goes without saying that it's impossible to cover **all** possible cases. 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. However, what is offered should hopefully serve as a basis for all of them.
@ -70,8 +68,8 @@ entt::locator<entt::meta_ctx>::reset(handle);
From now on, both spaces refer to the same context and on it are attached all From now on, both spaces refer to the same context and on it are attached all
new meta types, no matter where they are created.<br/> new meta types, no matter where they are created.<br/>
Note that resetting the main context doesn't also propagate changes across Note that _replacing_ the main context doesn't also propagate changes across
boundaries. In other words, resetting a context results in the decoupling of the boundaries. In other words, replacing a context results in the decoupling of the
two sides and therefore a divergence in the contents. two sides and therefore a divergence in the contents.
## Memory Management ## Memory Management

View File

@ -1,5 +1,22 @@
# EnTT in Action # EnTT in Action
<!--
@cond TURN_OFF_DOXYGEN
-->
# Table of Contents
* [Introduction](#introduction)
* [EnTT in Action](#entt-in-action)
* [Games](#games)
* [Engines and the like](#engines-and-the-like)
* [Articles, videos and blog posts](#articles-videos-and-blog-posts)
* [Any Other Business](#any-other-business)
<!--
@endcond TURN_OFF_DOXYGEN
-->
# Introduction
`EnTT` is widely used in private and commercial applications. I cannot even `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 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 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. them.
Below an incomplete list of games, applications and articles that can be used as 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 a reference.<br/>
documented but the authors didn't make explicit announcements or contacted me Where I put the word _apparently_ means that the use of `EnTT` is documented but
directly. the authors didn't make explicit announcements or contacted me directly.
I hope this list can grow much more in the future: If you know of other resources out there that are about `EnTT`, feel free to
open an issue or a PR and I'll be glad to add them to this page.<br/>
I hope the following lists can grow much more in the future.
# EnTT in Action
## Games
* Games:
* [Minecraft](https://minecraft.net/en-us/attribution/) by * [Minecraft](https://minecraft.net/en-us/attribution/) by
[Mojang](https://mojang.com/): of course, **that** Minecraft, see the [Mojang](https://mojang.com/): of course, **that** Minecraft, see the
open source attributions page for more details. 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 * [Confetti Party](https://github.com/hexerei/entt-confetti): C++ sample
application as a starting point using `EnTT` and `SDL2`. application as a starting point using `EnTT` and `SDL2`.
* Engines and the like: ## Engines and the like:
* [Aether Engine](https://hadean.com/spatial-simulation/) * [Aether Engine](https://hadean.com/spatial-simulation/)
[v1.1+](https://docs.hadean.com/v1.1/Licenses/) by [v1.1+](https://docs.hadean.com/v1.1/Licenses/) by
[Hadean](https://hadean.com/): a library designed for spatially partitioning [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`. engine based on `SDL2` and `EnTT`.
* [Ducktape](https://github.com/DucktapeEngine/Ducktape): an open source C++ * [Ducktape](https://github.com/DucktapeEngine/Ducktape): an open source C++
2D & 3D game engine that focuses on being fast and powerful. 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 * [Some posts](https://skypjack.github.io/tags/#entt) on my personal
[blog](https://skypjack.github.io/) are about `EnTT`, for those who want to [blog](https://skypjack.github.io/) are about `EnTT`, for those who want to
know **more** on this project. know **more** on this project.
@ -193,6 +227,12 @@ I hope this list can grow much more in the future:
- ... And so on. - ... And so on.
[Check out](https://www.youtube.com/channel/UCQ-W1KE9EYfdxhL6S4twUNw) the [Check out](https://www.youtube.com/channel/UCQ-W1KE9EYfdxhL6S4twUNw) the
_Game Engine Series_ by The Cherno for more videos. _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): * [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. huge space battle built entirely from scratch.
* [Space Battle](https://github.com/vblanco20-1/ECS_SpaceBattle): huge space * [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 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. 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 * [ArcGIS Runtime SDKs](https://developers.arcgis.com/arcgis-runtime/) by
[Esri](https://www.esri.com/): they use `EnTT` for the internal ECS and the [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 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 * GitHub contains also
[many other examples](https://github.com/search?o=desc&q=%22skypjack%2Fentt%22&s=indexed&type=Code) [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. 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.

View File

@ -67,17 +67,15 @@ recommended.
# Reflection in a nutshell # Reflection in a nutshell
Reflection always starts from real types (users cannot reflect imaginary types Reflection always starts from actual C++ types. Users cannot reflect _imaginary_
and it would not make much sense, we wouldn't be talking about reflection types.<br/>
anymore).<br/> The `meta` function is where it all starts:
To create a meta node, the library provides the `meta` function that accepts a
type to reflect as a template parameter:
```cpp ```cpp
auto factory = entt::meta<my_type>(); auto factory = entt::meta<my_type>();
``` ```
The returned value is a factory object to use to continue building the meta The returned value is a _factory object_ to use to continue building the meta
type. type.
By default, a meta type is associated with the identifier returned by the By default, a meta type is associated with the identifier returned by the
@ -88,45 +86,42 @@ However, it's also possible to assign custom identifiers to meta types:
auto factory = entt::meta<my_type>().type("reflected_type"_hs); auto factory = entt::meta<my_type>().type("reflected_type"_hs);
``` ```
Identifiers are important because users can retrieve meta types at runtime by Identifiers are used to _retrieve_ meta types at runtime by _name_ other than by
searching for them by _name_ other than by type.<br/> type.<br/>
On the other hand, there are cases in which users can be interested in adding However, users can be interested in adding features to a reflected type so that
features to a reflected type so that the reflection system can use it correctly the reflection system can use it correctly under the hood, while they don't want
under the hood, but they don't want to also make the type _searchable_. In this to also make the type _searchable_. In this case, it's sufficient not to invoke
case, it's sufficient not to invoke `type`. `type`.
A factory is such that all its member functions return the factory itself or a A factory is such that all its member functions return the factory itself. It's
decorated version of it. This object can be used to add the following: generally used to create the following:
* _Constructors_. Actual constructors can be assigned to a reflected type by * _Constructors_. A constructors is assigned to a reflected type by specifying
specifying their list of arguments. Free functions (namely, factories) can be its _list of arguments_. Free functions are also accepted if the return type
used as well, as long as the return type is the expected one. From a client's is the expected one. From a client perspective, nothing changes between a free
point of view, nothing changes if a constructor is a free function or an function or an actual constructor:
actual constructor.<br/>
Use the `ctor` member function for this purpose:
```cpp ```cpp
entt::meta<my_type>().ctor<int, char>().ctor<&factory>(); entt::meta<my_type>().ctor<int, char>().ctor<&factory>();
``` ```
* _Destructors_. Free functions and member functions can be used as destructors Meta default constructors are implicitly generated, if possible.
of reflected types. The purpose is to give users the ability to free up
resources that require special treatment before an object is actually * _Destructors_. Both free functions and member functions are valid destructors:
destroyed.<br/>
Use the `dtor` member function for this purpose:
```cpp ```cpp
entt::meta<my_type>().dtor<&destroy>(); entt::meta<my_type>().dtor<&destroy>();
``` ```
The purpose is to offer the possibility to free up resources that require
_special treatment_ before an object is actually destroyed.<br/>
A function should neither delete nor explicitly invoke the destructor of a A function should neither delete nor explicitly invoke the destructor of a
given instance. given instance.
* _Data members_. Both real data members of the underlying type and static and * _Data members_. Meta data members are actual data members of the underlying
global variables, as well as constants of any kind, can be attached to a meta type but also static and global variables or constants of any kind. From the
type. From the point of view of the client, all the variables associated with point of view of the client, all the variables associated with the reflected
the reflected type will appear as if they were part of the type itself.<br/> type appear as if they were part of the type itself:
Use the `data` member function for this purpose:
```cpp ```cpp
entt::meta<my_type>() entt::meta<my_type>()
@ -135,13 +130,11 @@ decorated version of it. This object can be used to add the following:
.data<&global_variable>("global"_hs); .data<&global_variable>("global"_hs);
``` ```
The function requires as an argument the identifier to give to the meta data The `data` function requires the identifier to use for the meta data member.
once created. Users can then access meta data at runtime by searching for them Users can then access it by _name_ at runtime.<br/>
by _name_.<br/> Data members are also defined by means of a setter and getter pair. These are
Data members can also be defined by means of a setter and getter pair. Setters either free functions, class members or a mix of them. This approach is also
and getters can be either free functions, class members or a mix of them, as convenient to create read-only properties from a non-const data member:
long as they respect the required signatures. This approach is also convenient
to create a read-only variable from a non-const data member:
```cpp ```cpp
entt::meta<my_type>().data<nullptr, &my_type::data_member>("member"_hs); entt::meta<my_type>().data<nullptr, &my_type::data_member>("member"_hs);
@ -153,13 +146,10 @@ decorated version of it. This object can be used to add the following:
entt::meta<my_type>().data<entt::value_list<&from_int, &from_string>, &my_type::data_member>("member"_hs); entt::meta<my_type>().data<entt::value_list<&from_int, &from_string>, &my_type::data_member>("member"_hs);
``` ```
Refer to the inline documentation for all the details. * _Member functions_. Meta member functions are actual member functions of the
underlying type but also plain free functions. From the point of view of the
* _Member functions_. Both real member functions of the underlying type and free client, all the functions associated with the reflected type appear as if they
functions can be attached to a meta type. From the point of view of the were part of the type itself:
client, all the functions associated with the reflected type will appear as if
they were part of the type itself.<br/>
Use the `func` member function for this purpose:
```cpp ```cpp
entt::meta<my_type>() entt::meta<my_type>()
@ -168,40 +158,31 @@ decorated version of it. This object can be used to add the following:
.func<&free_function>("free"_hs); .func<&free_function>("free"_hs);
``` ```
The function requires as an argument the identifier to give to the meta The `func` function requires the identifier to use for the meta data function.
function once created. Users can then access meta functions at runtime by Users can then access it by _name_ at runtime.<br/>
searching for them by _name_.<br/>
Overloading of meta functions is supported. Overloaded functions are resolved Overloading of meta functions is supported. Overloaded functions are resolved
at runtime by the reflection system according to the types of the arguments. 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 * _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 derived from it:
and allows for implicit casts at runtime when required.<br/>
Use the `base` member function for this purpose:
```cpp ```cpp
entt::meta<derived_type>().base<base_type>(); entt::meta<derived_type>().base<base_type>();
``` ```
From now on, wherever a `base_type` is required, an instance of `derived_type` The reflection system tracks the relationship and allows for implicit casts at
will also be accepted. 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 * _Conversion functions_. Conversion functions allow users to define conversions
think of the relationship between a `double` and an `int` to see it. Similar that are implicitly performed by the reflection system when required:
to bases, conversion functions allow users to define conversions that will be
implicitly performed by the reflection system when required.<br/>
Use the `conv` member function for this purpose:
```cpp ```cpp
entt::meta<double>().conv<int>(); entt::meta<double>().conv<int>();
``` ```
That's all, everything users need to create meta types and enjoy the reflection This is everything users need to create meta types. Refer to the inline
system. At first glance it may not seem that much, but users usually learn to documentation for further details.
appreciate it over time.<br/>
Also, do not forget what these few lines hide under the hood: a built-in,
non-intrusive and macro-free system for reflection in C++. Features that are
definitely worth the price, at least for me.
## Any to the rescue ## 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 many of the feature to infer a meta node, before forwarding some or all of the
arguments to the underlying storage.<br/> arguments to the underlying storage.<br/>
Among the few relevant differences, `meta_any` adds support for containers and Among the few relevant differences, `meta_any` adds support for containers and
pointer-like types (see the following sections for more details), while `any` pointer-like types, while `any` doesn't.<br/>
does not.<br/> Similar to `any`, this class is also used to create _aliases_ for unmanaged
Similar to `any`, this class can also be used to create _aliases_ for unmanaged
objects either with `forward_as_meta` or using the `std::in_place_type<T &>` objects either with `forward_as_meta` or using the `std::in_place_type<T &>`
disambiguation tag, as well as from an existing object by means of the `as_ref` 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 member function.<br/>
one initialized with `void` differently: Unlike `any` instead, `meta_any` treats an empty instance and one initialized
with `void` differently:
```cpp ```cpp
entt::meta_any empty{}; entt::meta_any empty{};
@ -229,21 +210,19 @@ entt::meta_any other{std::in_place_type<void>};
While `any` considers both as empty, `meta_any` treats objects initialized with 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 `void` as if they were _valid_ ones. This allows to differentiate between failed
function calls and function calls that are successful but return nothing.<br/> function calls and function calls that are successful but return nothing.
Finally, the member functions `try_cast`, `cast` and `allow_cast` are used to 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) 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 or to _convert_ a `meta_any` in such a way that a cast becomes viable for the
resulting object. There is in fact no `any_cast` equivalent for `meta_any`. resulting object.<br/>
There is in fact no `any_cast` equivalent for `meta_any`.
## Enjoy the runtime ## Enjoy the runtime
Once the web of reflected types has been constructed, it's a matter of using it Once the web of reflected types is constructed, it's a matter of using it at
at runtime where required.<br/> runtime where required.<br/>
All this has the great merit that the reflection system stands in fact as a There are a few options to search for a reflected type:
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:
```cpp ```cpp
// direct access to a reflected type // direct access to a reflected type
@ -257,8 +236,8 @@ auto by_type_id = entt::resolve(entt::type_id<my_type>());
``` ```
There exists also an overload of the `resolve` function to use to iterate all 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 reflected types at once. It returns an iterable object to be used in a range-for
range-for loop: loop:
```cpp ```cpp
for(auto &&[id, type]: entt::resolve()) { 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_, 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 to iterate all the meta objects associated with them and even to build instances
of the underlying type.<br/> of the underlying type.<br/>
Refer to the inline documentation for all the details. Meta data members and functions are accessed by name:
Meta data members and functions are accessed by name among the other things:
* Meta data members: * 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 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 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 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 addition, a meta function object is used to invoke the underlying function and
and then get the return value in the form of a `meta_any` object. 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 All the meta objects thus obtained as well as the meta types explicitly convert
converted to a boolean value to check if they are valid: to a boolean value to check for validity:
```cpp ```cpp
if(auto func = entt::resolve<my_type>().func("member"_hs); func) { if(auto func = entt::resolve<my_type>().func("member"_hs); func) {
@ -319,26 +296,23 @@ for(auto &&[id, type]: entt::resolve<my_type>().base()) {
} }
``` ```
A meta type can also be used to `construct` actual instances of the underlying Meta type are also used to `construct` actual instances of the underlying
type.<br/> type.<br/>
In particular, the `construct` member function accepts a variable number of 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 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 or may not be initialized, depending on whether a suitable constructor was found
found or not. or not.
There is no object that wraps the destructor of a meta type nor a `destroy` 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` 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, 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 they've no name, cannot be searched and wouldn't have member functions to expose
expose anyway.<br/> anyway.<br/>
Similarly, conversion functions aren't directly accessible. They are used Similarly, conversion functions aren't directly accessible. They're used
internally by `meta_any` and the meta objects when needed. 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 Meta types and meta objects in general contain much more than what was said.
plethora of functions in addition to those listed whose purposes and uses go Refer to the inline documentation for further details.
unfortunately beyond the scope of this document.<br/>
I invite anyone interested in the subject to look at the code, experiment and
read the inline documentation to get the best out of this powerful tool.
## Container support ## 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 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 to provide specializations for either the `meta_sequence_container_traits` class
or the `meta_associative_container_traits` class, according to the actual type or the `meta_associative_container_traits` class, according to the actual _type_
of the container.<br/> of the container.<br/>
`EnTT` already exports the specializations for some common classes. In `EnTT` already exports the specializations for some common classes. In
particular: particular:
@ -386,11 +360,10 @@ if(any.type().is_sequence_container()) {
The method to use to get a proxy object for associative containers is The method to use to get a proxy object for associative containers is
`as_associative_container` instead.<br/> `as_associative_container` instead.<br/>
It goes without saying that it's not necessary to perform a double check. It's not necessary to perform a double check actually. Instead, it's enough to
Instead, it's sufficient to query the meta type or verify that the proxy object query the meta type or verify that the proxy object is valid. In fact, proxies
is valid. In fact, proxies are contextually convertible to bool to know if they are contextually convertible to bool to check for validity. For example, invalid
are valid. For example, invalid proxies are returned when the wrapped object proxies are returned when the wrapped object isn't a container.<br/>
isn't a container.<br/>
In all cases, users aren't expected to _reflect_ containers explicitly. It's 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 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. 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 `value_type` member function returns the meta type of the elements.
* The `size` member function returns the number of elements in the container as * The `size` member function returns the number of elements in the container as
an unsigned integer value: an unsigned integer value.
```cpp
const auto size = view.size();
```
* The `resize` member function allows to resize the wrapped container and * The `resize` member function allows to resize the wrapped container and
returns true in case of success: returns true in case of success.<br/>
```cpp
const bool ok = view.resize(3u);
```
For example, it's not possible to resize fixed size containers. For example, it's not possible to resize fixed size containers.
* The `clear` member function allows to clear the wrapped container and returns * The `clear` member function allows to clear the wrapped container and returns
true in case of success: true in case of success.<br/>
```cpp
const bool ok = view.clear();
```
For example, it's not possible to clear fixed size containers. For example, it's not possible to clear fixed size containers.
* The `begin` and `end` member functions return opaque iterators that can be * The `begin` and `end` member functions return opaque iterators that is used to
used to iterate the container directly: iterate the container directly:
```cpp ```cpp
for(entt::meta_any element: view) { 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 All meta iterators are input iterators and don't offer an indirection operator
on purpose. 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: accepts a meta iterator and the element to insert:
```cpp ```cpp
@ -451,15 +410,15 @@ to case. In particular:
``` ```
This function returns a meta iterator pointing to the inserted element and a 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 boolean value to indicate whether the operation was successful or not. A call
that a call to `insert` may silently fail in case of fixed size containers or to `insert` may silently fail in case of fixed size containers or whether the
whether the arguments aren't at least convertible to the required types.<br/> arguments aren't at least convertible to the required types.<br/>
Since the meta iterators are contextually convertible to bool, users can rely Since meta iterators are contextually convertible to bool, users can rely on
on them to know if the operation has failed on the actual container or them to know if the operation failed on the actual container or upstream, for
upstream, for example for an argument conversion problem. example due to an argument conversion problem.
* The `erase` member function can be used to remove elements from the container. * The `erase` member function is used to remove elements from the container. It
It accepts a meta iterator to the element to remove: accepts a meta iterator to the element to remove:
```cpp ```cpp
auto first = view.begin(); 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 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 boolean value to indicate whether the operation was successful or not. A call
that a call to `erase` may silently fail in case of fixed size containers. 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 * The `operator[]` is used to access container elements. It accepts a single
single argument, that is the position of the element to return: argument, the position of the element to return:
```cpp ```cpp
for(std::size_t pos{}, last = view.size(); pos < last; ++pos) { 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 The function returns instances of `meta_any` that directly refer to the actual
elements. Modifying the returned object will then directly modify the element elements. Modifying the returned object directly modifies the element inside
inside the container.<br/> the container.<br/>
Depending on the underlying sequence container, this operation may not be as 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 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 translates to a linear visit of the list itself (probably not what the user
@ -508,21 +467,13 @@ differences in behavior in the case of key-only containers. In particular:
`std::map<int, char>`. `std::map<int, char>`.
* The `size` member function returns the number of elements in the container as * The `size` member function returns the number of elements in the container as
an unsigned integer value: an unsigned integer value.
```cpp
const auto size = view.size();
```
* The `clear` member function allows to clear the wrapped container and returns * The `clear` member function allows to clear the wrapped container and returns
true in case of success: true in case of success.
```cpp * The `begin` and `end` member functions return opaque iterators that are used
const bool ok = view.clear(); to iterate the container directly:
```
* The `begin` and `end` member functions return opaque iterators that can be
used to iterate the container directly:
```cpp ```cpp
for(std::pair<entt::meta_any, entt::meta_any> element: view) { for(std::pair<entt::meta_any, entt::meta_any> element: view) {
@ -539,11 +490,11 @@ differences in behavior in the case of key-only containers. In particular:
While the accessed key is usually constant in the associative containers and 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 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 `meta_any` that directly refers to the actual element. Modifying it directly
directly modify the element inside the container. modifies the element inside the container.
* The `insert` member function can be used to add elements to the container. It * The `insert` member function is used to add elements to a container. It gets
accepts two arguments, respectively the key and the value to be inserted: two arguments, respectively the key and the value to insert:
```cpp ```cpp
auto last = view.end(); 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 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 successful or not. A call to `insert` may fail when the arguments aren't at
aren't at least convertible to the required types. least convertible to the required types.
* The `erase` member function can be used to remove elements from the container. * The `erase` member function is used to remove elements from a container. It
It accepts a single argument, that is the key to be removed: gets a single argument, the key to remove:
```cpp ```cpp
view.erase(42); view.erase(42);
``` ```
This function returns a boolean value to indicate whether the operation was 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 successful or not. A call to `erase` may fail when the argument isn't at least
isn't at least convertible to the required type. convertible to the required type.
* The `operator[]` can be used to access elements in a container. It accepts a * The `operator[]` is used to access elements in a container. It gets a single
single argument, that is the key of the element to return: argument, the key of the element to return:
```cpp ```cpp
entt::meta_any value = view[42]; entt::meta_any value = view[42];
``` ```
The function returns instances of `meta_any` that directly refer to the actual The function returns instances of `meta_any` that directly refer to the actual
elements. Modifying the returned object will then directly modify the element elements. Modifying the returned object directly modifies the element inside
inside the container. the container.
Container support is minimal but likely sufficient to satisfy all needs. Container support is minimal but likely sufficient to satisfy all needs.
## Pointer-like types ## Pointer-like types
As with containers, it's also possible to communicate to the meta system which As with containers, it's also possible to _tell_ to the meta system which types
types to consider _pointers_. This will allow to dereference instances of are _pointers_. This makes it possible to dereference instances of `meta_any`,
`meta_any`, thus obtaining light _references_ to the pointed objects that are thus obtaining light _references_ to pointed objects that are also correctly
also correctly associated with their meta types.<br/> associated with their meta types.<br/>
To make the meta system recognize a type as _pointer-like_, users can specialize 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 the `is_meta_pointer_like` class. `EnTT` already exports the specializations for
some common classes. In particular: 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 It's not necessary to perform a double check. Instead, it's enough to query the
query the meta type or verify that the returned object is valid. For example, meta type or verify that the returned object is valid. For example, invalid
invalid instances are returned when the wrapped object isn't a pointer-like instances are returned when the wrapped object isn't a pointer-like type.<br/>
type.<br/> Dereferencing a pointer-like object returns an instance of `meta_any` which
Note that dereferencing a pointer-like object returns an instance of `meta_any` _refers_ to the pointed object. Modifying it means modifying the pointed object
which refers to the pointed object and allows users to modify it directly directly (unless the returned element is const).
(unless the returned element is const, of course).
In general, _dereferencing_ a pointer-like type boils down to a `*ptr`. However, 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: `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 In all other cases and when dereferencing a pointer works as expected regardless
regardless of the pointed type, no user intervention is required. of the pointed type, no user intervention is required.
## Template information ## Template information
Meta types also provide a minimal set of information about the nature of the Meta types also provide a minimal set of information about the _nature_ of the
original type in case it's a class template.<br/> original type in case it's a class template.<br/>
By default, this works out of the box and requires no user action. However, it's 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 important to include the header file `template.hpp` to make this information
@ -688,9 +638,9 @@ template<typename Ret, typename... Args>
struct function_type<Ret(Args...)> {}; struct function_type<Ret(Args...)> {};
``` ```
In this case, rather than the function type, the user might want the return type In this case, rather than the function type, it might be useful to provide the
and unpacked arguments as if they were different template parameters for the return type and unpacked arguments as if they were different template parameters
original class template.<br/> for the original class template.<br/>
To achieve this, users must enter the library internals and provide their own To achieve this, users must enter the library internals and provide their own
specialization for the class template `entt::meta_template_traits`, such as: specialization for the class template `entt::meta_template_traits`, such as:
@ -704,8 +654,8 @@ struct entt::meta_template_traits<function_type<Ret(Args...)>> {
The reflection system doesn't verify the accuracy of the information nor infer a The reflection system doesn't verify the accuracy of the information nor infer a
correspondence between real types and meta types.<br/> correspondence between real types and meta types.<br/>
Therefore, the specialization will be used as is and the information it contains Therefore, the specialization is used as is and the information it contains is
will be associated with the appropriate type when required. associated with the appropriate type when required.
## Automatic conversions ## Automatic conversions
@ -752,29 +702,29 @@ any.allow_cast(type);
int value = any.cast<int>(); int value = any.cast<int>();
``` ```
This should make working with arithmetic types and scoped or unscoped enums as This makes working with arithmetic types and scoped or unscoped enums as easy as
easy as it is in C++.<br/> it is in C++.<br/>
It's also worth noting that it's still possible to set up conversion functions It's still possible to set up conversion functions manually and these are always
manually and these will always be preferred over the automatic ones. preferred over the automatic ones.
## Implicitly generated default constructor ## Implicitly generated default constructor
In many cases, it's useful to be able to create objects of default constructible Creating objects of default constructible types through the reflection system
types through the reflection system, while not having to explicitly register the while not having to explicitly register the meta type or its default constructor
meta type or the default constructor.<br/> is also possible.<br/>
For example, in the case of primitive types like `int` or `char`, but not just For example, in the case of primitive types like `int` or `char`, but not just
them. them.
For this reason and only for default constructible types, default constructors For default constructible types only, default constructors are automatically
are automatically defined and associated with their meta types, whether they are defined and associated with their meta types, whether they are explicitly or
explicitly or implicitly generated.<br/> implicitly generated.<br/>
Therefore, this is all is needed to construct an integer from its meta type: Therefore, this is all is needed to construct an integer from its meta type:
```cpp ```cpp
entt::resolve<int>().construct(); entt::resolve<int>().construct();
``` ```
Where the meta type can be for example the one returned from a meta container, Where the meta type is for example the one returned from a meta container,
useful for building keys without knowing or having to register the actual types. 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 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 ## From void to any
Sometimes all a user has is an opaque pointer to an object of a known meta type. 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 It would be handy in this case to be able to construct a `meta_any` element from
them.<br/> it.<br/>
For this purpose, the `meta_type` class offers a `from_void` member function For this purpose, the `meta_type` class offers a `from_void` member function
designed to convert an opaque pointer into a `meta_any`: 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); 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. Unfortunately, it's not possible to do a check on the actual type. Therefore,
Therefore, this call can be considered as a _static cast_ with all the problems this call can be considered as a _static cast_ with all its _problems_.<br/>
and undefined behaviors of the case following errors.<br/>
On the other hand, the ability to construct a `meta_any` from an opaque pointer 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. opens the door to some pretty interesting uses that are worth exploring.
@ -826,17 +775,17 @@ There are a few alternatives available at the moment:
entt::meta<my_type>().func<&my_type::member_function, entt::as_void_t>("member"_hs); entt::meta<my_type>().func<&my_type::member_function, entt::as_void_t>("member"_hs);
``` ```
If the use with functions is obvious, it must be said that it's also possible If the use with functions is obvious, perhaps less so is use with constructors
to use this policy with constructors and data members. In the first case, the and data members. In the first case, the returned wrapper is always empty even
constructor will be invoked but the returned wrapper will actually be empty. though the constructor is still invoked. In the second case, the property
In the second case, instead, the property will not be accessible for reading. isn't accessible for reading instead.
* The _as-ref_ and _as-cref_ policies, associated with the types * The _as-ref_ and _as-cref_ policies, associated with the types
`entt::as_ref_t` and `entt::as_cref_t`.<br/> `entt::as_ref_t` and `entt::as_cref_t`.<br/>
They allow to build wrappers that act as references to unmanaged objects. They allow to build wrappers that act as references to unmanaged objects.
Accessing the object contained in the wrapper for which the _reference_ was Accessing the object contained in the wrapper for which the _reference_ was
requested will make it possible to directly access the instance used to requested makes it possible to directly access the instance used to initialize
initialize the wrapper itself: the wrapper itself:
```cpp ```cpp
entt::meta<my_type>().data<&my_type::data_member, entt::as_ref_t>("member"_hs); entt::meta<my_type>().data<&my_type::data_member, entt::as_ref_t>("member"_hs);
@ -854,21 +803,16 @@ obvious corner cases that can in turn be solved with the use of policies.
## Named constants and enums ## Named constants and enums
A special mention should be made for constant values and enums. It wouldn't be As mentioned, the `data` member function is used to reflect constants of any
necessary, but it will help distracted readers. type.<br/>
This allows users to create meta types for enums that work exactly like any
As mentioned, the `data` member function can be used to reflect constants of any other meta type built from a class. Similarly, arithmetic types are _enriched_
type among the other things.<br/>
This allows users to create meta types for enums that will work exactly like any
other meta type built from a class. Similarly, arithmetic types can be enriched
with constants of special meaning where required.<br/> with constants of special meaning where required.<br/>
Personally, I find it very useful not to export what is the difference between All values thus exported appear to users as if they were constant data members
enums and classes in C++ directly in the space of the reflected types. 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 Exposing constant values or elements from an enum is quite simple:
members of the reflected types.
Exporting constant values or elements from an enum is as simple as ever:
```cpp ```cpp
entt::meta<my_enum>() entt::meta<my_enum>()
@ -878,28 +822,22 @@ entt::meta<my_enum>()
entt::meta<int>().data<2048>("max_int"_hs); entt::meta<int>().data<2048>("max_int"_hs);
``` ```
It goes without saying that accessing them is trivial as well. It's a matter of Accessing them is trivial as well. It's a matter of doing the following, as with
doing the following, as with any other data member of a meta type: any other data member of a meta type:
```cpp ```cpp
auto value = entt::resolve<my_enum>().data("a_value"_hs).get({}).cast<my_enum>(); auto value = entt::resolve<my_enum>().data("a_value"_hs).get({}).cast<my_enum>();
auto max = entt::resolve<int>().data("max_int"_hs).get({}).cast<int>(); auto max = entt::resolve<int>().data("max_int"_hs).get({}).cast<int>();
``` ```
As a side note, remember that all this happens behind the scenes without any All this happens behind the scenes without any allocation because of the small
allocation because of the small object optimization performed by the `meta_any` object optimization performed by the `meta_any` class.
class.
## Properties and meta objects ## Properties and meta objects
Sometimes (for example, when it comes to creating an editor) it might be useful 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 to attach properties to the meta objects created. Fortunately, this is possible
for most of them.<br/> 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.<br/>
Apparently, it's more difficult to say than to do:
```cpp ```cpp
entt::meta<my_type>().type("reflected_type"_hs).prop("tooltip"_hs, "message"); entt::meta<my_type>().type("reflected_type"_hs).prop("tooltip"_hs, "message");
@ -914,10 +852,10 @@ Key only properties are also supported out of the box:
entt::meta<my_type>().type("reflected_type"_hs).prop(my_enum::key_only); entt::meta<my_type>().type("reflected_type"_hs).prop(my_enum::key_only);
``` ```
To attach multiple properties to a meta object, it's possible to invoke `prop` To attach multiple properties to a meta object, just invoke `prop` more than
more than once.<br/> once.<br/>
It's also possible to invoke `prop` at different times, as long as the factory It's also possible to call `prop` at different times, as long as the factory is
is reset to the meta object of interest. reset to the meta object of interest.
The meta objects for which properties are supported are currently meta types, The meta objects for which properties are supported are currently meta types,
meta data and meta functions.<br/> meta data and meta functions.<br/>
@ -940,7 +878,7 @@ form of a `meta_any` object.
## Unregister types ## 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 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 and so on. However, base classes aren't unregistered as well, since they don't
necessarily depend on it.<br/> necessarily depend on it.<br/>
@ -969,7 +907,7 @@ A type can be re-registered later with a completely different name and form.
## Meta context ## Meta context
All meta types and their parts are created at runtime and stored in a default 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 ```cpp
auto &&context = entt::locator<entt::meta_context>::value_or(); auto &&context = entt::locator<entt::meta_context>::value_or();
@ -984,8 +922,8 @@ auto &&context = entt::locator<entt::meta_context>::value_or();
std::swap(context, other); std::swap(context, other);
``` ```
This can be useful for testing purposes or to define multiple contexts with This is useful for testing purposes or to define multiple context objects with
different meta objects to be used as appropriate. different meta type to use as appropriate.
If _replacing_ the default context isn't enough, `EnTT` also offers the ability If _replacing_ the default context isn't enough, `EnTT` also offers the ability
to use multiple and externally managed contexts with the runtime reflection to use multiple and externally managed contexts with the runtime reflection
@ -998,16 +936,16 @@ entt::meta_ctx context{};
auto factory = entt::meta<my_type>(context).type("reflected_type"_hs); auto factory = entt::meta<my_type>(context).type("reflected_type"_hs);
``` ```
By doing so, the new meta type won't be available in the default context but By doing so, the new meta type isn't available in the default context but is
will be usable by passing around the new context when needed, such as when usable by passing around the new context when needed, such as when creating a
creating a new `meta_any` object: new `meta_any` object:
```cpp ```cpp
entt::meta_any any{context, std::in_place_type<my_type>}; entt::meta_any any{context, std::in_place_type<my_type>};
``` ```
Similarly, to search for meta types in a context other than the default one, it Similarly, to search for meta types in a context other than the default one,
will be necessary to pass it to the `resolve` function: it's necessary to pass it to the `resolve` function:
```cpp ```cpp
entt::meta_type type = entt::resolve(context, "reflected_type"_hs) entt::meta_type type = entt::resolve(context, "reflected_type"_hs)

View File

@ -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 The library allows to define _concepts_ as interfaces to fulfill with concrete
classes without having to inherit from a common base.<br/> classes without having to inherit from a common base.<br/>
This is, among others, one of the advantages of static polymorphism in general Among others, this is one of the advantages of static polymorphism in general
and of a generic wrapper like that offered by the `poly` class template in and of a generic wrapper like that offered by the `poly` class template in
particular.<br/> particular.<br/>
What users get is an object that can be passed around as such and not through a The result is an object to pass around as such and not through a reference or a
reference or a pointer, as happens when it comes to working with dynamic pointer, as it happens when it comes to working with dynamic polymorphism.
polymorphism.
Since the `poly` class template makes use of `entt::any` internally, it also 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 supports most of its feature. For example, the possibility to create aliases to
create aliases to existing and thus unmanaged objects. This allows users to existing and thus unmanaged objects. This allows users to exploit the static
exploit the static polymorphism while maintaining ownership of objects.<br/> polymorphism while maintaining ownership of objects.<br/>
Likewise, the `poly` class template also benefits from the small buffer Likewise, the `poly` class template also benefits from the small buffer
optimization offered by the `entt::any` class and therefore minimizes the number optimization offered by the `entt::any` class and therefore minimizes the number
of allocations, avoiding them altogether where possible. of allocations, avoiding them altogether where possible.
@ -44,7 +43,7 @@ of allocations, avoiding them altogether where possible.
## Other libraries ## Other libraries
There are some very interesting libraries regarding static polymorphism.<br/> There are some very interesting libraries regarding static polymorphism.<br/>
Among all, the two that I prefer are: The ones that I like more are:
* [`dyno`](https://github.com/ldionne/dyno): runtime polymorphism done right. * [`dyno`](https://github.com/ldionne/dyno): runtime polymorphism done right.
* [`Poly`](https://github.com/facebook/folly/blob/master/folly/docs/Poly.md): * [`Poly`](https://github.com/facebook/folly/blob/master/folly/docs/Poly.md):
@ -69,18 +68,18 @@ use the terminology introduced by Eric Niebler) is to define a _concept_ that
types will have to adhere to.<br/> types will have to adhere to.<br/>
For this purpose, the library offers a single class that supports both deduced For this purpose, the library offers a single class that supports both deduced
and fully defined interfaces. Although having interfaces deduced automatically 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 limitations and it's therefore useful to be able to get around the deduction by
providing a custom definition for the static virtual table. providing a custom definition for the static virtual table.
Once the interface is defined, it will be sufficient to provide a generic Once the interface is defined, a generic implementation is needed to fulfill the
implementation to fulfill the concept.<br/> concept itself.<br/>
Also in this case, the library allows customizations based on types or families 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. of types, so as to be able to go beyond the generic case where necessary.
## Deduced interface ## 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 ```cpp
struct Drawable: entt::type_list<> { struct Drawable: entt::type_list<> {
@ -108,12 +107,12 @@ struct Drawable: entt::type_list<> {
}; };
``` ```
In this case, all parameters must be passed to `invoke` after the reference to In this case, all parameters are passed to `invoke` after the reference to
`this` and the return value is whatever the internal call returns.<br/> `this` and the return value is whatever the internal call returns.<br/>
As for `invoke`, this is a name that is injected into the _concept_ through 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 `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 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: external call:
```cpp ```cpp
@ -165,12 +164,12 @@ struct Drawable: entt::type_list<bool(int) const> {
Why should a user fully define a concept if the function types are the same as Why should a user fully define a concept if the function types are the same as
the deduced ones?<br> the deduced ones?<br>
Because, in fact, this is exactly the limitation that can be worked around by In fact, this is the limitation that can be worked around by manually defining
manually defining the static virtual table. the static virtual table.
When things are deduced, there is an implicit constraint.<br/> When things are deduced, there is an implicit constraint.<br/>
If the concept exposes a member function called `draw` with function type 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 * Either by a class that exposes a member function with the same name and the
same signature. same signature.
@ -179,7 +178,7 @@ If the concept exposes a member function called `draw` with function type
interface itself. interface itself.
In other words, it's not possible to make use of functions not belonging to the In other words, it's not possible to make use of functions not belonging to the
interface, even if they are present in the types that fulfill the concept.<br/> interface, even if they're part of the types that fulfill the concept.<br/>
Similarly, it's not possible to deduce a function in the static virtual table 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 with a function type different from that of the associated member function in
the interface itself. 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 In this case, it's stated that the `draw` method of a generic type is enough to
enough to satisfy the requirements of the `Drawable` concept.<br/> satisfy the requirements of the `Drawable` concept.<br/>
Both member functions and free functions are supported to fulfill concepts: Both member functions and free functions are supported to fulfill concepts:
```cpp ```cpp
@ -251,15 +250,15 @@ struct DrawableAndErasable: entt::type_list<> {
``` ```
The static virtual table is empty and must remain so.<br/> The static virtual table is empty and must remain so.<br/>
On the other hand, `type` no longer inherits from `Base` and instead forwards On the other hand, `type` no longer inherits from `Base`. Instead, it forwards
its template parameter to the type exposed by the _base class_. Internally, the 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 _size_ of the static virtual table of the base class is used as an offset for
local indexes.<br/> the local indexes.<br/>
Finally, by means of the `value_list_cat_t` utility, the implementation consists Finally, by means of the `value_list_cat_t` utility, the implementation consists
in appending the new functions to the previous list. 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 As for a defined concept instead, the list of types is _extended_ in a similar
similar way to what is shown for the implementation of the above concept.<br/> way to what is shown for the implementation of the above concept.<br/>
To do this, it's useful to declare a function that allows to convert a _concept_ To do this, it's useful to declare a function that allows to convert a _concept_
into its underlying `type_list` object: into its underlying `type_list` object:
@ -268,8 +267,8 @@ template<typename... Type>
entt::type_list<Type...> as_type_list(const entt::type_list<Type...> &); entt::type_list<Type...> as_type_list(const entt::type_list<Type...> &);
``` ```
The definition isn't strictly required, since the function will only be used The definition isn't strictly required, since the function is only used through
through a `decltype` as it follows: a `decltype` as it follows:
```cpp ```cpp
struct DrawableAndErasable: entt::type_list_cat_t< 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 # Static polymorphism in the wild
Once the _concept_ and implementation have been introduced, it will be possible Once the _concept_ and implementation are defined, it's possible to use the
to use the `poly` class template to contain instances that meet the `poly` class template to _wrap_ instances that meet the requirements:
requirements:
```cpp ```cpp
using drawable = entt::poly<Drawable>; using drawable = entt::poly<Drawable>;
@ -310,9 +308,9 @@ instance = square{};
instance->draw(); instance->draw();
``` ```
The `poly` class template offers a wide range of constructors, from the default This class offers a wide range of constructors, from the default one (which
one (which will return an uninitialized `poly` object) to the copy and move returns an uninitialized `poly` object) to the copy and move constructors, as
constructors, as well as the ability to create objects in-place.<br/> well as the ability to create objects in-place.<br/>
Among others, there is also a constructor that allows users to wrap unmanaged Among others, there is also a constructor that allows users to wrap unmanaged
objects in a `poly` instance (either const or non-const ones): 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 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 Note also how the underlying concept is accessed via a call to `operator->` and
not directly as `instance.draw()`.<br/> not directly as `instance.draw()`.<br/>
This allows users to decouple the API of the wrapper from that of the concept. 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 Therefore, where `instance.data()` invokes the `data` member function of the
poly object, `instance->data()` will map directly to the functionality exposed poly object, `instance->data()` maps directly to the functionality exposed by
by the underlying concept. the underlying concept.
# Storage size and alignment requirement # Storage size and alignment requirement
@ -351,9 +349,9 @@ entt::basic_poly<Drawable, sizeof(double[4]), alignof(double[4])>
The default size is `sizeof(double[2])`, which seems like a good compromise 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 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 an integer. The alignment requirement is optional and by default such that it's
that it's the most stringent (the largest) for any object whose size is at most the most stringent (the largest) for any object whose size is at most equal to
equal to the one provided.<br/> the one provided.<br/>
It's worth noting that providing a size of 0 (which is an accepted value in all 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 respects) will force the system to dynamically allocate the contained objects in
all cases. all cases.

View File

@ -15,18 +15,17 @@
# Introduction # Introduction
Sometimes processes are a useful tool to work around the strict definition of a Processes are a useful tool to work around the strict definition of a system and
system and introduce logic in a different way, usually without resorting to the introduce logic in a different way, usually without resorting to other component
introduction of other components. types.<br/>
`EnTT` offers minimal support to this paradigm by introducing a few classes used
`EnTT` offers a minimal support to this paradigm by introducing a few classes to define and execute cooperative processes.
that users can use to define and execute cooperative processes.
# The process # The process
A typical process must inherit from the `process` class template that stays true A typical task inherits from the `process` class template that stays true to the
to the CRTP idiom. Moreover, derived classes must specify what's the intended CRTP idiom. Moreover, derived classes specify what the intended type for elapsed
type for elapsed times. times is.
A process should expose publicly the following member functions whether needed 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 (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 *);` * `void update(Delta, void *);`
It's invoked once per tick until a process is explicitly aborted or it This is invoked once per tick until a process is explicitly aborted or ends
terminates either with or without errors. Even though it's not mandatory to either with or without errors. Even though it's not mandatory to declare this
declare this member function, as a rule of thumb each process should at member function, as a rule of thumb each process should at least define it to
least define it to work properly. The `void *` parameter is an opaque pointer work _properly_. The `void *` parameter is an opaque pointer to user data (if
to user data (if any) forwarded directly to the process during an update. any) forwarded directly to the process during an update.
* `void init();` * `void init();`
It's invoked when the process joins the running queue of a scheduler. This This is invoked when the process joins the running queue of a scheduler. It
happens as soon as it's attached to the scheduler if the process is a top happens usually as soon as the process is attached to the scheduler if it's a
level one, otherwise when it replaces its parent if the process is a top level one, otherwise when it replaces its parent if it's a _continuation_.
continuation.
* `void succeeded();` * `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. same tick.
* `void failed();` * `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. same tick.
* `void aborted();` * `void aborted();`
It's invoked only if a process is explicitly aborted. There is no guarantee This is 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 that it executes in the same tick, it depends solely on whether the process is
process is aborted immediately or not. aborted immediately or not.
Derived classes can also change the internal state of a process by invoking 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 `succeed` and `fail`, as well as `pause` and `unpause` the process itself.<br/>
these are protected member functions made available to be able to manage the All these are protected member functions made available to manage the life cycle
life cycle of a process from a derived class. of a process from a derived class.
Here is a minimal example for the sake of curiosity: Here is a minimal example for the sake of curiosity:
@ -95,14 +93,14 @@ private:
## Adaptor ## Adaptor
Lambdas and functors can't be used directly with a scheduler for they are not Lambdas and functors can't be used directly with a scheduler because they aren't
properly defined processes with managed life cycles.<br/> properly defined processes with managed life cycles.<br/>
This class helps in filling the gap and turning lambdas and functors into This class helps in filling the gap and turning lambdas and functors into
full-featured processes usable by a scheduler. full-featured processes usable by a scheduler.
The function call operator has a signature similar to the one of the `update` 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 function of a process but for the fact that it receives two extra callbacks to
call whenever a process is terminated with success or with an error: invoke whenever a process terminates with success or with an error:
```cpp ```cpp
void(Delta delta, void *data, auto succeed, auto fail); 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. cycles.
Each process is invoked once per tick. If it terminates, it's removed Each process is invoked once per tick. If it terminates, it's removed
automatically from the scheduler and it's never invoked again. Otherwise it's automatically from the scheduler and it's never invoked again. Otherwise, it's
a good candidate to run one more time the next tick.<br/> a good candidate to run one more time the next tick.<br/>
A process can also have a child. In this case, the parent process is replaced A process can also have a _child_. In this case, the parent process is replaced
with its child when it terminates and only if it returns with success. In case 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 of errors, both the parent process and its child are discarded. This way, it's
easy to create chain of processes to run sequentially. 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: type for the elapsed times and no arguments at all:
```cpp ```cpp
entt::scheduler<std::uint32_t> scheduler; entt::basic_scheduler<std::uint64_t> scheduler;
``` ```
It has member functions to query its internal data structures, like `empty` or Otherwise, the `scheduler` alias is also available for the most common cases. It
`size`, as well as a `clear` utility to reset it to a clean state: 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 ```cpp
// checks if there are processes still running // checks if there are processes still running
const auto empty = scheduler.empty(); const auto empty = scheduler.empty();
// gets the number of processes still running // gets the number of processes still running
entt::scheduler<std::uint32_t>::size_type size = scheduler.size(); entt::scheduler::size_type size = scheduler.size();
// resets the scheduler to its initial state and discards all the processes // resets the scheduler to its initial state and discards all the processes
scheduler.clear(); 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 In both cases, the return value is an opaque object that offers a `then` member
function to use to create chains of processes to run sequentially.<br/> function used to create chains of processes to run sequentially.<br/>
As a minimal example of use: As a minimal example of use:
```cpp ```cpp
@ -201,7 +206,7 @@ scheduler.update(delta, &data);
``` ```
In addition to these functions, the scheduler offers an `abort` member function 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 ```cpp
// aborts all the processes abruptly ... // aborts all the processes abruptly ...

View File

@ -1,18 +1,35 @@
# Similar projects # Similar projects
<!--
@cond TURN_OFF_DOXYGEN
-->
# Table of Contents
* [Introduction](#introduction)
* [Similar projects](#similar-projects)
<!--
@endcond TURN_OFF_DOXYGEN
-->
# Introduction
There are many projects similar to `EnTT`, both open source and not.<br/> There are many projects similar to `EnTT`, both open source and not.<br/>
Some even borrowed some ideas from this library and expressed them in different Some even borrowed some ideas from this library and expressed them in different
languages.<br/> languages.<br/>
Others developed different architectures from scratch and therefore offer Others developed different architectures from scratch and therefore offer
alternative solutions with their pros and cons. alternative solutions with their pros and cons.
Below an incomplete list of those that I've come across so far.<br/> If you know of other similar projects out there, feel free to open an issue or a
PR and I'll be glad to add them to this page.<br/>
I hope the following lists can grow much more in the future.
# Similar projects
Below an incomplete list of similar projects that I've come across so far.<br/>
If some terms or designs aren't clear, I recommend referring to the 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 [_ECS Back and Forth_](https://skypjack.github.io/tags/#ecs) series for all the
details. details.
I hope this list can grow much more in the future:
* C: * C:
* [destral_ecs](https://github.com/roig/destral_ecs): a single-file ECS based * [destral_ecs](https://github.com/roig/destral_ecs): a single-file ECS based
on sparse sets. 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. solution between an ECS and dynamic mixins.
* C# * 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 * [Entitas](https://github.com/sschmid/Entitas-CSharp): the ECS framework for
C# and Unity, where _reactive systems_ were invented. C# and Unity, where _reactive systems_ were invented.
* [LeoECS](https://github.com/Leopotam/ecs): simple lightweight C# Entity * [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
* [zig-ecs](https://github.com/prime31/zig-ecs): a _zig-ification_ of `EnTT`. * [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.

View File

@ -9,6 +9,7 @@
* [Delegate](#delegate) * [Delegate](#delegate)
* [Runtime arguments](#runtime-arguments) * [Runtime arguments](#runtime-arguments)
* [Lambda support](#lambda-support) * [Lambda support](#lambda-support)
* [Raw access](#raw-access)
* [Signals](#signals) * [Signals](#signals)
* [Event dispatcher](#event-dispatcher) * [Event dispatcher](#event-dispatcher)
* [Named queues](#named-queues) * [Named queues](#named-queues)
@ -38,7 +39,7 @@ lightweight classes to solve the same and many other problems.
# Delegate # Delegate
A delegate can be used as a general purpose invoker with no memory overhead for A delegate can be used as a general purpose invoker with no memory overhead for
free functions and member functions provided along with an instance on which to free functions, lambdas and members provided along with an instance on which to
invoke them.<br/> invoke them.<br/>
It doesn't claim to be a drop-in replacement for an `std::function`, so don't 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 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); delegate(42);
``` ```
The function `g` is invoked with a reference to `c` and `42`. However, the Function `g` is invoked with a reference to `c` and `42`. However, the function
function type of the delegate is still `void(int)`. This is also the signature type of the delegate is still `void(int)`. This is also the signature of its
of its function call operator.<br/> function call operator.<br/>
Another interesting aspect of the delegate class is that it accepts functions 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: 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 Where the function type of the delegate is `void(int)` as above. It goes without
saying that the extra arguments are silently discarded internally.<br/> saying that the extra arguments are silently discarded internally. This is a
This is a nice-to-have feature in a lot of cases, as an example when the nice-to-have feature in a lot of cases, as an example when the `delegate` class
`delegate` class is used as a building block of a signal-slot system. is used as a building block of a signal-slot system.<br/>
In fact, this filtering works both ways. The class tries to pass its first
_count_ arguments **first**, then the last _count_. Watch out for conversion
rules if in doubt when connecting a listener!<br/>
Arbitrary functions that pull random arguments from the delegate list aren't
supported instead. Other feature were preferred, such as support for functions
with compatible argument lists although not equal to those of the delegate.
To create and initialize a delegate at once, there are a few specialized 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 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 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)`. other terms, the function type of the delegate above is `int(int)`.
## Raw access
While not recommended, a delegate also allows direct access to the stored
callable function target and underlying data, if any.<br/>
This makes it possible to bypass the behavior of the delegate itself and force
calls on different instances:
```cpp
my_struct other;
delegate.target(&other, 42);
```
It goes without saying that this type of approach is **very** risky, especially
since there is no way of knowing whether the contained function was originally a
member function of some class, a free function or a lambda.<br/>
Another possible (and meaningful) use of this feature is that of identifying a
particular delegate through its descriptive _traits_ instead.
# Signals # Signals
Signal handlers work with references to classes, function pointers and pointers Signal handlers work with references to classes, function pointers and pointers
@ -290,7 +315,7 @@ sink.disconnect<&foo>();
sink.disconnect<&listener::bar>(instance); sink.disconnect<&listener::bar>(instance);
// disconnect all member functions of an instance, if any // disconnect all member functions of an instance, if any
sink.disconnect(instance); sink.disconnect(&instance);
// discards all listeners at once // discards all listeners at once
sink.disconnect(); 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 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 result that is convertible to the given return type, everything works just
fine.<br/> fine.<br/>
It's also possible to connect a listener before other elements already contained
by the signal. The `before` function returns a `sink` object that is correctly
initialized for the purpose and can be used to connect one or more listeners in
order and in the desired position:
```cpp
sink.before<&foo>().connect<&listener::bar>(instance);
```
In all cases, the `connect` member function returns by default a `connection` 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 object to be used as an alternative to break a connection by means of its
`release` member function.<br/> `release` member function.<br/>
@ -409,7 +425,7 @@ of them at once:
```cpp ```cpp
dispatcher.sink<an_event>().disconnect<&listener::receive>(listener); dispatcher.sink<an_event>().disconnect<&listener::receive>(listener);
dispatcher.sink<another_event>().disconnect(listener); dispatcher.sink<another_event>().disconnect(&listener);
``` ```
The `trigger` member function serves the purpose of sending an immediate event The `trigger` member function serves the purpose of sending an immediate event

View File

@ -1,62 +1,31 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> <AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<Type Name="entt::basic_registry&lt;*&gt;"> <Type Name="entt::basic_registry&lt;*&gt;">
<Intrinsic Name="pools_size" Expression="pools.packed.first_base::value.size()"/> <Intrinsic Name="to_entity" Expression="*((traits_type::entity_type *)&amp;entity) &amp; traits_type::entity_mask">
<Intrinsic Name="vars_size" Expression="vars.ctx.packed.first_base::value.size()"/> <Parameter Name="entity" Type="traits_type::value_type &amp;"/>
<Intrinsic Name="to_entity" Expression="*((entity_traits::entity_type *)&amp;entity) &amp; entity_traits::entity_mask">
<Parameter Name="entity" Type="entity_traits::value_type &amp;"/>
</Intrinsic> </Intrinsic>
<DisplayString>{{ size={ epool.size() } }}</DisplayString> <DisplayString>{{ pools={ pools.size() } }}</DisplayString>
<Expand> <Expand>
<Item IncludeView="simple" Name="[epool]">epool,view(simple)nr</Item> <Item Name="[entities]">entities</Item>
<Synthetic Name="[epool]" ExcludeView="simple">
<DisplayString>{ epool.size() }</DisplayString>
<Expand>
<CustomListItems>
<Variable Name="pos" InitialValue="0" />
<Variable Name="last" InitialValue="epool.size()"/>
<Loop>
<Break Condition="pos == last"/>
<If Condition="to_entity(epool[pos]) == pos">
<Item Name="[{ pos }]">epool[pos]</Item>
</If>
<Exec>++pos</Exec>
</Loop>
</CustomListItems>
</Expand>
</Synthetic>
<Synthetic Name="[destroyed]" ExcludeView="simple">
<DisplayString>{ to_entity(free_list) != entity_traits::entity_mask }</DisplayString>
<Expand>
<CustomListItems>
<Variable Name="it" InitialValue="to_entity(free_list)" />
<Loop>
<Break Condition="it == entity_traits::entity_mask"/>
<Item Name="[{ it }]">epool[it]</Item>
<Exec>it = to_entity(epool[it])</Exec>
</Loop>
</CustomListItems>
</Expand>
</Synthetic>
<Synthetic Name="[pools]"> <Synthetic Name="[pools]">
<DisplayString>{ pools_size() }</DisplayString> <DisplayString>{ pools.size() }</DisplayString>
<Expand> <Expand>
<IndexListItems ExcludeView="simple"> <IndexListItems ExcludeView="simple">
<Size>pools_size()</Size> <Size>pools.size()</Size>
<ValueNode>*pools.packed.first_base::value[$i].element.second</ValueNode> <ValueNode>*pools.packed.first_base::value[$i].element.second</ValueNode>
</IndexListItems> </IndexListItems>
<IndexListItems IncludeView="simple"> <IndexListItems IncludeView="simple">
<Size>pools_size()</Size> <Size>pools.size()</Size>
<ValueNode>*pools.packed.first_base::value[$i].element.second,view(simple)</ValueNode> <ValueNode>*pools.packed.first_base::value[$i].element.second,view(simple)</ValueNode>
</IndexListItems> </IndexListItems>
</Expand> </Expand>
</Synthetic> </Synthetic>
<Item Name="[groups]" ExcludeView="simple">groups.size()</Item> <Item Name="[groups]" ExcludeView="simple">groups.size()</Item>
<Synthetic Name="[vars]"> <Synthetic Name="[vars]">
<DisplayString>{ vars_size() }</DisplayString> <DisplayString>{ vars.ctx.size() }</DisplayString>
<Expand> <Expand>
<IndexListItems> <IndexListItems>
<Size>vars_size()</Size> <Size>vars.ctx.size()</Size>
<ValueNode>vars.ctx.packed.first_base::value[$i].element.second</ValueNode> <ValueNode>vars.ctx.packed.first_base::value[$i].element.second</ValueNode>
</IndexListItems> </IndexListItems>
</Expand> </Expand>
@ -69,20 +38,20 @@
<Item Name="[capacity]" ExcludeView="simple">packed.capacity()</Item> <Item Name="[capacity]" ExcludeView="simple">packed.capacity()</Item>
<Item Name="[policy]">mode,en</Item> <Item Name="[policy]">mode,en</Item>
<Synthetic Name="[sparse]"> <Synthetic Name="[sparse]">
<DisplayString>{ sparse.size() * entity_traits::page_size }</DisplayString> <DisplayString>{ sparse.size() * traits_type::page_size }</DisplayString>
<Expand> <Expand>
<ExpandedItem IncludeView="simple">sparse,view(simple)</ExpandedItem> <ExpandedItem IncludeView="simple">sparse,view(simple)</ExpandedItem>
<CustomListItems ExcludeView="simple"> <CustomListItems ExcludeView="simple">
<Variable Name="pos" InitialValue="0"/> <Variable Name="pos" InitialValue="0"/>
<Variable Name="page" InitialValue="0"/> <Variable Name="page" InitialValue="0"/>
<Variable Name="offset" InitialValue="0"/> <Variable Name="offset" InitialValue="0"/>
<Variable Name="last" InitialValue="sparse.size() * entity_traits::page_size"/> <Variable Name="last" InitialValue="sparse.size() * traits_type::page_size"/>
<Loop> <Loop>
<Break Condition="pos == last"/> <Break Condition="pos == last"/>
<Exec>page = pos / entity_traits::page_size</Exec> <Exec>page = pos / traits_type::page_size</Exec>
<Exec>offset = pos &amp; (entity_traits::page_size - 1)</Exec> <Exec>offset = pos &amp; (traits_type::page_size - 1)</Exec>
<If Condition="sparse[page] &amp;&amp; (*((entity_traits::entity_type *)&amp;sparse[page][offset]) &lt; ~entity_traits::entity_mask)"> <If Condition="sparse[page] &amp;&amp; (*((traits_type::entity_type *)&amp;sparse[page][offset]) &lt; ~traits_type::entity_mask)">
<Item Name="[{ pos }]">*((entity_traits::entity_type *)&amp;sparse[page][offset]) &amp; entity_traits::entity_mask</Item> <Item Name="[{ pos }]">*((traits_type::entity_type *)&amp;sparse[page][offset]) &amp; traits_type::entity_mask</Item>
</If> </If>
<Exec>++pos</Exec> <Exec>++pos</Exec>
</Loop> </Loop>
@ -98,7 +67,7 @@
<Variable Name="last" InitialValue="packed.size()"/> <Variable Name="last" InitialValue="packed.size()"/>
<Loop> <Loop>
<Break Condition="pos == last"/> <Break Condition="pos == last"/>
<If Condition="*((entity_traits::entity_type *)&amp;packed[pos]) &lt; ~entity_traits::entity_mask"> <If Condition="*((traits_type::entity_type *)&amp;packed[pos]) &lt; ~traits_type::entity_mask">
<Item Name="[{ pos }]">packed[pos]</Item> <Item Name="[{ pos }]">packed[pos]</Item>
</If> </If>
<Exec>++pos</Exec> <Exec>++pos</Exec>
@ -111,18 +80,19 @@
<Type Name="entt::basic_storage&lt;*&gt;"> <Type Name="entt::basic_storage&lt;*&gt;">
<DisplayString>{{ size={ base_type::packed.size() }, type={ base_type::info->alias,na } }}</DisplayString> <DisplayString>{{ size={ base_type::packed.size() }, type={ base_type::info->alias,na } }}</DisplayString>
<Expand> <Expand>
<Item Name="[capacity]" Optional="true" ExcludeView="simple">packed.first_base::value.capacity() * comp_traits::page_size</Item> <Item Name="[capacity]" Optional="true" ExcludeView="simple">payload.capacity() * traits_type::page_size</Item>
<Item Name="[page size]" Optional="true" ExcludeView="simple">comp_traits::page_size</Item> <Item Name="[page size]" Optional="true" ExcludeView="simple">traits_type::page_size</Item>
<Item Name="[length]" Optional="true" ExcludeView="simple">length</Item>
<Item Name="[base]" ExcludeView="simple">(base_type*)this,nand</Item> <Item Name="[base]" ExcludeView="simple">(base_type*)this,nand</Item>
<Item Name="[base]" IncludeView="simple">(base_type*)this,view(simple)nand</Item> <Item Name="[base]" IncludeView="simple">(base_type*)this,view(simple)nand</Item>
<!-- having SFINAE-like techniques in natvis is priceless :) --> <!-- having SFINAE-like techniques in natvis is priceless :) -->
<CustomListItems Condition="packed.first_base::value.size() != 0" Optional="true"> <CustomListItems Condition="payload.size() != 0" Optional="true">
<Variable Name="pos" InitialValue="0" /> <Variable Name="pos" InitialValue="0" />
<Variable Name="last" InitialValue="base_type::packed.size()"/> <Variable Name="last" InitialValue="base_type::packed.size()"/>
<Loop> <Loop>
<Break Condition="pos == last"/> <Break Condition="pos == last"/>
<If Condition="*((base_type::entity_traits::entity_type *)&amp;base_type::packed[pos]) &lt; ~base_type::entity_traits::entity_mask"> <If Condition="*((base_type::traits_type::entity_type *)&amp;base_type::packed[pos]) &lt; ~base_type::traits_type::entity_mask">
<Item Name="[{ pos }:{ base_type::packed[pos] }]">packed.first_base::value[pos / comp_traits::page_size][pos &amp; (comp_traits::page_size - 1)]</Item> <Item Name="[{ pos }:{ base_type::packed[pos] }]">payload[pos / traits_type::page_size][pos &amp; (traits_type::page_size - 1)]</Item>
</If> </If>
<Exec>++pos</Exec> <Exec>++pos</Exec>
</Loop> </Loop>

View File

@ -8,7 +8,7 @@
</Expand> </Expand>
</Type> </Type>
<Type Name="entt::basic_dispatcher&lt;*&gt;"> <Type Name="entt::basic_dispatcher&lt;*&gt;">
<Intrinsic Name="size" Expression="pools.first_base::value.packed.first_base::value.size()"/> <Intrinsic Name="size" Expression="pools.first_base::value.size()"/>
<DisplayString>{{ size={ size() } }}</DisplayString> <DisplayString>{{ size={ size() } }}</DisplayString>
<Expand> <Expand>
<Synthetic Name="[pools]"> <Synthetic Name="[pools]">
@ -50,7 +50,6 @@
<DisplayString>{{ type={ "$T1" } }}</DisplayString> <DisplayString>{{ type={ "$T1" } }}</DisplayString>
<Expand> <Expand>
<Item Name="[signal]">signal,na</Item> <Item Name="[signal]">signal,na</Item>
<Item Name="[offset]">offset</Item>
</Expand> </Expand>
</Type> </Type>
</AutoVisualizer> </AutoVisualizer>

File diff suppressed because it is too large Load Diff

View File

@ -50,6 +50,8 @@
# define ENTT_ASSERT_CONSTEXPR(condition, msg) ENTT_ASSERT(condition, msg) # define ENTT_ASSERT_CONSTEXPR(condition, msg) ENTT_ASSERT(condition, msg)
#endif #endif
#define ENTT_FAIL(msg) ENTT_ASSERT(false, msg);
#ifdef ENTT_NO_ETO #ifdef ENTT_NO_ETO
# define ENTT_ETO_TYPE(Type) void # define ENTT_ETO_TYPE(Type) void
#else #else

View File

@ -4,8 +4,8 @@
#include "macro.h" #include "macro.h"
#define ENTT_VERSION_MAJOR 3 #define ENTT_VERSION_MAJOR 3
#define ENTT_VERSION_MINOR 11 #define ENTT_VERSION_MINOR 12
#define ENTT_VERSION_PATCH 1 #define ENTT_VERSION_PATCH 2
#define ENTT_VERSION \ #define ENTT_VERSION \
ENTT_XSTR(ENTT_VERSION_MAJOR) \ ENTT_XSTR(ENTT_VERSION_MAJOR) \

View File

@ -128,51 +128,51 @@ public:
return {it->element.first, it->element.second}; return {it->element.first, it->element.second};
} }
template<typename ILhs, typename IRhs> template<typename Lhs, typename Rhs>
friend constexpr std::ptrdiff_t operator-(const dense_map_iterator<ILhs> &, const dense_map_iterator<IRhs> &) noexcept; friend constexpr std::ptrdiff_t operator-(const dense_map_iterator<Lhs> &, const dense_map_iterator<Rhs> &) noexcept;
template<typename ILhs, typename IRhs> template<typename Lhs, typename Rhs>
friend constexpr bool operator==(const dense_map_iterator<ILhs> &, const dense_map_iterator<IRhs> &) noexcept; friend constexpr bool operator==(const dense_map_iterator<Lhs> &, const dense_map_iterator<Rhs> &) noexcept;
template<typename ILhs, typename IRhs> template<typename Lhs, typename Rhs>
friend constexpr bool operator<(const dense_map_iterator<ILhs> &, const dense_map_iterator<IRhs> &) noexcept; friend constexpr bool operator<(const dense_map_iterator<Lhs> &, const dense_map_iterator<Rhs> &) noexcept;
private: private:
It it; It it;
}; };
template<typename ILhs, typename IRhs> template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) noexcept { [[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_map_iterator<Lhs> &lhs, const dense_map_iterator<Rhs> &rhs) noexcept {
return lhs.it - rhs.it; return lhs.it - rhs.it;
} }
template<typename ILhs, typename IRhs> template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator==(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator==(const dense_map_iterator<Lhs> &lhs, const dense_map_iterator<Rhs> &rhs) noexcept {
return lhs.it == rhs.it; return lhs.it == rhs.it;
} }
template<typename ILhs, typename IRhs> template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator!=(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator!=(const dense_map_iterator<Lhs> &lhs, const dense_map_iterator<Rhs> &rhs) noexcept {
return !(lhs == rhs); return !(lhs == rhs);
} }
template<typename ILhs, typename IRhs> template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator<(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator<(const dense_map_iterator<Lhs> &lhs, const dense_map_iterator<Rhs> &rhs) noexcept {
return lhs.it < rhs.it; return lhs.it < rhs.it;
} }
template<typename ILhs, typename IRhs> template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator>(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator>(const dense_map_iterator<Lhs> &lhs, const dense_map_iterator<Rhs> &rhs) noexcept {
return rhs < lhs; return rhs < lhs;
} }
template<typename ILhs, typename IRhs> template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator<=(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator<=(const dense_map_iterator<Lhs> &lhs, const dense_map_iterator<Rhs> &rhs) noexcept {
return !(lhs > rhs); return !(lhs > rhs);
} }
template<typename ILhs, typename IRhs> template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator>=(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator>=(const dense_map_iterator<Lhs> &lhs, const dense_map_iterator<Rhs> &rhs) noexcept {
return !(lhs < rhs); return !(lhs < rhs);
} }
@ -230,13 +230,13 @@ private:
std::size_t offset; std::size_t offset;
}; };
template<typename ILhs, typename IRhs> template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator==(const dense_map_local_iterator<ILhs> &lhs, const dense_map_local_iterator<IRhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator==(const dense_map_local_iterator<Lhs> &lhs, const dense_map_local_iterator<Rhs> &rhs) noexcept {
return lhs.index() == rhs.index(); return lhs.index() == rhs.index();
} }
template<typename ILhs, typename IRhs> template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator!=(const dense_map_local_iterator<ILhs> &lhs, const dense_map_local_iterator<IRhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator!=(const dense_map_local_iterator<Lhs> &lhs, const dense_map_local_iterator<Rhs> &rhs) noexcept {
return !(lhs == rhs); return !(lhs == rhs);
} }
@ -266,7 +266,7 @@ class dense_map {
static constexpr std::size_t minimum_capacity = 8u; static constexpr std::size_t minimum_capacity = 8u;
using node_type = internal::dense_map_node<Key, Type>; using node_type = internal::dense_map_node<Key, Type>;
using alloc_traits = typename std::allocator_traits<Allocator>; using alloc_traits = std::allocator_traits<Allocator>;
static_assert(std::is_same_v<typename alloc_traits::value_type, std::pair<const Key, Type>>, "Invalid value type"); static_assert(std::is_same_v<typename alloc_traits::value_type, std::pair<const Key, Type>>, "Invalid value type");
using sparse_container_type = std::vector<std::size_t, typename alloc_traits::template rebind_alloc<std::size_t>>; using sparse_container_type = std::vector<std::size_t, typename alloc_traits::template rebind_alloc<std::size_t>>;
using packed_container_type = std::vector<node_type, typename alloc_traits::template rebind_alloc<node_type>>; using packed_container_type = std::vector<node_type, typename alloc_traits::template rebind_alloc<node_type>>;
@ -464,7 +464,6 @@ public:
/** /**
* @brief Returns an iterator to the beginning. * @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()`. * If the array is empty, the returned iterator will be equal to `end()`.
* *
* @return An iterator to the first instance of the internal array. * @return An iterator to the first instance of the internal array.
@ -485,11 +484,6 @@ public:
/** /**
* @brief Returns an iterator to the end. * @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 * @return An iterator to the element following the last instance of the
* internal array. * internal array.
*/ */
@ -838,7 +832,7 @@ public:
} }
/*! @copydoc equal_range */ /*! @copydoc equal_range */
template<class Other> template<typename Other>
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, std::pair<const_iterator, const_iterator>>> [[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, std::pair<const_iterator, const_iterator>>>
equal_range(const Other &key) const { equal_range(const Other &key) const {
const auto it = find(key); const auto it = find(key);

View File

@ -96,51 +96,51 @@ public:
return *operator->(); return *operator->();
} }
template<typename ILhs, typename IRhs> template<typename Lhs, typename Rhs>
friend constexpr std::ptrdiff_t operator-(const dense_set_iterator<ILhs> &, const dense_set_iterator<IRhs> &) noexcept; friend constexpr std::ptrdiff_t operator-(const dense_set_iterator<Lhs> &, const dense_set_iterator<Rhs> &) noexcept;
template<typename ILhs, typename IRhs> template<typename Lhs, typename Rhs>
friend constexpr bool operator==(const dense_set_iterator<ILhs> &, const dense_set_iterator<IRhs> &) noexcept; friend constexpr bool operator==(const dense_set_iterator<Lhs> &, const dense_set_iterator<Rhs> &) noexcept;
template<typename ILhs, typename IRhs> template<typename Lhs, typename Rhs>
friend constexpr bool operator<(const dense_set_iterator<ILhs> &, const dense_set_iterator<IRhs> &) noexcept; friend constexpr bool operator<(const dense_set_iterator<Lhs> &, const dense_set_iterator<Rhs> &) noexcept;
private: private:
It it; It it;
}; };
template<typename ILhs, typename IRhs> template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) noexcept { [[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
return lhs.it - rhs.it; return lhs.it - rhs.it;
} }
template<typename ILhs, typename IRhs> template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator==(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator==(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
return lhs.it == rhs.it; return lhs.it == rhs.it;
} }
template<typename ILhs, typename IRhs> template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator!=(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator!=(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
return !(lhs == rhs); return !(lhs == rhs);
} }
template<typename ILhs, typename IRhs> template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator<(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator<(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
return lhs.it < rhs.it; return lhs.it < rhs.it;
} }
template<typename ILhs, typename IRhs> template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator>(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator>(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
return rhs < lhs; return rhs < lhs;
} }
template<typename ILhs, typename IRhs> template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator<=(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator<=(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
return !(lhs > rhs); return !(lhs > rhs);
} }
template<typename ILhs, typename IRhs> template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator>=(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator>=(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
return !(lhs < rhs); return !(lhs < rhs);
} }
@ -195,13 +195,13 @@ private:
std::size_t offset; std::size_t offset;
}; };
template<typename ILhs, typename IRhs> template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator==(const dense_set_local_iterator<ILhs> &lhs, const dense_set_local_iterator<IRhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator==(const dense_set_local_iterator<Lhs> &lhs, const dense_set_local_iterator<Rhs> &rhs) noexcept {
return lhs.index() == rhs.index(); return lhs.index() == rhs.index();
} }
template<typename ILhs, typename IRhs> template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator!=(const dense_set_local_iterator<ILhs> &lhs, const dense_set_local_iterator<IRhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator!=(const dense_set_local_iterator<Lhs> &lhs, const dense_set_local_iterator<Rhs> &rhs) noexcept {
return !(lhs == rhs); return !(lhs == rhs);
} }
@ -410,7 +410,6 @@ public:
/** /**
* @brief Returns an iterator to the beginning. * @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()`. * If the array is empty, the returned iterator will be equal to `end()`.
* *
* @return An iterator to the first instance of the internal array. * @return An iterator to the first instance of the internal array.
@ -431,11 +430,6 @@ public:
/** /**
* @brief Returns an iterator to the end. * @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 * @return An iterator to the element following the last instance of the
* internal array. * internal array.
*/ */
@ -691,7 +685,7 @@ public:
} }
/*! @copydoc equal_range */ /*! @copydoc equal_range */
template<class Other> template<typename Other>
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, std::pair<const_iterator, const_iterator>>> [[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, std::pair<const_iterator, const_iterator>>>
equal_range(const Other &value) const { equal_range(const Other &value) const {
const auto it = find(value); const auto it = find(value);

View File

@ -95,14 +95,15 @@ struct radix_sort {
template<typename It, typename Getter = identity> template<typename It, typename Getter = identity>
void operator()(It first, It last, Getter getter = Getter{}) const { void operator()(It first, It last, Getter getter = Getter{}) const {
if(first < last) { if(first < last) {
static constexpr auto mask = (1 << Bit) - 1; constexpr auto passes = N / Bit;
static constexpr auto buckets = 1 << Bit;
static constexpr auto passes = N / Bit;
using value_type = typename std::iterator_traits<It>::value_type; using value_type = typename std::iterator_traits<It>::value_type;
std::vector<value_type> aux(std::distance(first, last)); std::vector<value_type> aux(std::distance(first, last));
auto part = [getter = std::move(getter)](auto from, auto to, auto out, auto start) { 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 index[buckets]{};
std::size_t count[buckets]{}; std::size_t count[buckets]{};

View File

@ -59,7 +59,7 @@ class basic_any {
}; };
template<typename Type> template<typename Type>
static constexpr bool in_situ = Len && alignof(Type) <= Align && sizeof(Type) <= Len &&std::is_nothrow_move_constructible_v<Type>; static constexpr bool in_situ = Len && alignof(Type) <= Align && sizeof(Type) <= Len && std::is_nothrow_move_constructible_v<Type>;
template<typename Type> template<typename Type>
static const void *basic_vtable(const operation op, const basic_any &value, const void *other) { static const void *basic_vtable(const operation op, const basic_any &value, const void *other) {
@ -128,17 +128,17 @@ class basic_any {
vtable = basic_vtable<std::remove_cv_t<std::remove_reference_t<Type>>>; vtable = basic_vtable<std::remove_cv_t<std::remove_reference_t<Type>>>;
if constexpr(std::is_lvalue_reference_v<Type>) { if constexpr(std::is_lvalue_reference_v<Type>) {
static_assert(sizeof...(Args) == 1u && (std::is_lvalue_reference_v<Args> && ...), "Invalid arguments"); static_assert((std::is_lvalue_reference_v<Args> && ...) && (sizeof...(Args) == 1u), "Invalid arguments");
mode = std::is_const_v<std::remove_reference_t<Type>> ? policy::cref : policy::ref; mode = std::is_const_v<std::remove_reference_t<Type>> ? policy::cref : policy::ref;
instance = (std::addressof(args), ...); instance = (std::addressof(args), ...);
} else if constexpr(in_situ<std::remove_cv_t<std::remove_reference_t<Type>>>) { } else if constexpr(in_situ<std::remove_cv_t<std::remove_reference_t<Type>>>) {
if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v<std::remove_cv_t<std::remove_reference_t<Type>>>) { if constexpr(std::is_aggregate_v<std::remove_cv_t<std::remove_reference_t<Type>>> && (sizeof...(Args) != 0u || !std::is_default_constructible_v<std::remove_cv_t<std::remove_reference_t<Type>>>)) {
new(&storage) std::remove_cv_t<std::remove_reference_t<Type>>{std::forward<Args>(args)...}; new(&storage) std::remove_cv_t<std::remove_reference_t<Type>>{std::forward<Args>(args)...};
} else { } else {
new(&storage) std::remove_cv_t<std::remove_reference_t<Type>>(std::forward<Args>(args)...); new(&storage) std::remove_cv_t<std::remove_reference_t<Type>>(std::forward<Args>(args)...);
} }
} else { } else {
if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v<std::remove_cv_t<std::remove_reference_t<Type>>>) { if constexpr(std::is_aggregate_v<std::remove_cv_t<std::remove_reference_t<Type>>> && (sizeof...(Args) != 0u || !std::is_default_constructible_v<std::remove_cv_t<std::remove_reference_t<Type>>>)) {
instance = new std::remove_cv_t<std::remove_reference_t<Type>>{std::forward<Args>(args)...}; instance = new std::remove_cv_t<std::remove_reference_t<Type>>{std::forward<Args>(args)...};
} else { } else {
instance = new std::remove_cv_t<std::remove_reference_t<Type>>(std::forward<Args>(args)...); instance = new std::remove_cv_t<std::remove_reference_t<Type>>(std::forward<Args>(args)...);
@ -428,7 +428,7 @@ private:
* @return The element converted to the requested type. * @return The element converted to the requested type.
*/ */
template<typename Type, std::size_t Len, std::size_t Align> template<typename Type, std::size_t Len, std::size_t Align>
Type any_cast(const basic_any<Len, Align> &data) noexcept { [[nodiscard]] Type any_cast(const basic_any<Len, Align> &data) noexcept {
const auto *const instance = any_cast<std::remove_reference_t<Type>>(&data); const auto *const instance = any_cast<std::remove_reference_t<Type>>(&data);
ENTT_ASSERT(instance, "Invalid instance"); ENTT_ASSERT(instance, "Invalid instance");
return static_cast<Type>(*instance); return static_cast<Type>(*instance);
@ -436,7 +436,7 @@ Type any_cast(const basic_any<Len, Align> &data) noexcept {
/*! @copydoc any_cast */ /*! @copydoc any_cast */
template<typename Type, std::size_t Len, std::size_t Align> template<typename Type, std::size_t Len, std::size_t Align>
Type any_cast(basic_any<Len, Align> &data) noexcept { [[nodiscard]] Type any_cast(basic_any<Len, Align> &data) noexcept {
// forces const on non-reference types to make them work also with wrappers for const references // forces const on non-reference types to make them work also with wrappers for const references
auto *const instance = any_cast<std::remove_reference_t<const Type>>(&data); auto *const instance = any_cast<std::remove_reference_t<const Type>>(&data);
ENTT_ASSERT(instance, "Invalid instance"); ENTT_ASSERT(instance, "Invalid instance");
@ -445,7 +445,7 @@ Type any_cast(basic_any<Len, Align> &data) noexcept {
/*! @copydoc any_cast */ /*! @copydoc any_cast */
template<typename Type, std::size_t Len, std::size_t Align> template<typename Type, std::size_t Len, std::size_t Align>
Type any_cast(basic_any<Len, Align> &&data) noexcept { [[nodiscard]] Type any_cast(basic_any<Len, Align> &&data) noexcept {
if constexpr(std::is_copy_constructible_v<std::remove_cv_t<std::remove_reference_t<Type>>>) { if constexpr(std::is_copy_constructible_v<std::remove_cv_t<std::remove_reference_t<Type>>>) {
if(auto *const instance = any_cast<std::remove_reference_t<Type>>(&data); instance) { if(auto *const instance = any_cast<std::remove_reference_t<Type>>(&data); instance) {
return static_cast<Type>(std::move(*instance)); return static_cast<Type>(std::move(*instance));
@ -461,14 +461,14 @@ Type any_cast(basic_any<Len, Align> &&data) noexcept {
/*! @copydoc any_cast */ /*! @copydoc any_cast */
template<typename Type, std::size_t Len, std::size_t Align> template<typename Type, std::size_t Len, std::size_t Align>
const Type *any_cast(const basic_any<Len, Align> *data) noexcept { [[nodiscard]] const Type *any_cast(const basic_any<Len, Align> *data) noexcept {
const auto &info = type_id<std::remove_cv_t<Type>>(); const auto &info = type_id<std::remove_cv_t<Type>>();
return static_cast<const Type *>(data->data(info)); return static_cast<const Type *>(data->data(info));
} }
/*! @copydoc any_cast */ /*! @copydoc any_cast */
template<typename Type, std::size_t Len, std::size_t Align> template<typename Type, std::size_t Len, std::size_t Align>
Type *any_cast(basic_any<Len, Align> *data) noexcept { [[nodiscard]] Type *any_cast(basic_any<Len, Align> *data) noexcept {
if constexpr(std::is_const_v<Type>) { if constexpr(std::is_const_v<Type>) {
// last attempt to make wrappers for const references return their values // last attempt to make wrappers for const references return their values
return any_cast<Type>(&std::as_const(*data)); return any_cast<Type>(&std::as_const(*data));
@ -488,7 +488,7 @@ Type *any_cast(basic_any<Len, Align> *data) noexcept {
* @return A properly initialized wrapper for an object of the given type. * @return A properly initialized wrapper for an object of the given type.
*/ */
template<typename Type, std::size_t Len = basic_any<>::length, std::size_t Align = basic_any<Len>::alignment, typename... Args> template<typename Type, std::size_t Len = basic_any<>::length, std::size_t Align = basic_any<Len>::alignment, typename... Args>
basic_any<Len, Align> make_any(Args &&...args) { [[nodiscard]] basic_any<Len, Align> make_any(Args &&...args) {
return basic_any<Len, Align>{std::in_place_type<Type>, std::forward<Args>(args)...}; return basic_any<Len, Align>{std::in_place_type<Type>, std::forward<Args>(args)...};
} }
@ -501,7 +501,7 @@ basic_any<Len, Align> make_any(Args &&...args) {
* @return A properly initialized and not necessarily owning wrapper. * @return A properly initialized and not necessarily owning wrapper.
*/ */
template<std::size_t Len = basic_any<>::length, std::size_t Align = basic_any<Len>::alignment, typename Type> template<std::size_t Len = basic_any<>::length, std::size_t Align = basic_any<Len>::alignment, typename Type>
basic_any<Len, Align> forward_as_any(Type &&value) { [[nodiscard]] basic_any<Len, Align> forward_as_any(Type &&value) {
return basic_any<Len, Align>{std::in_place_type<Type &&>, std::forward<Type>(value)}; return basic_any<Len, Align>{std::in_place_type<Type &&>, std::forward<Type>(value)};
} }

View File

@ -67,7 +67,7 @@ struct basic_hashed_string {
template<typename Char> template<typename Char>
class basic_hashed_string: internal::basic_hashed_string<Char> { class basic_hashed_string: internal::basic_hashed_string<Char> {
using base_type = internal::basic_hashed_string<Char>; using base_type = internal::basic_hashed_string<Char>;
using hs_traits = internal::fnv1a_traits<id_type>; using traits_type = internal::fnv1a_traits<id_type>;
struct const_wrapper { struct const_wrapper {
// non-explicit constructor on purpose // non-explicit constructor on purpose
@ -79,10 +79,10 @@ class basic_hashed_string: internal::basic_hashed_string<Char> {
// FowlerNollVo hash function v. 1a - the good // FowlerNollVo hash function v. 1a - the good
[[nodiscard]] static constexpr auto helper(const Char *str) noexcept { [[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) { for(; str[base.length]; ++base.length) {
base.hash = (base.hash ^ static_cast<hs_traits::type>(str[base.length])) * hs_traits::prime; base.hash = (base.hash ^ static_cast<traits_type::type>(str[base.length])) * traits_type::prime;
} }
return base; return base;
@ -90,10 +90,10 @@ class basic_hashed_string: internal::basic_hashed_string<Char> {
// FowlerNollVo hash function v. 1a - the good // FowlerNollVo hash function v. 1a - the good
[[nodiscard]] static constexpr auto helper(const Char *str, const std::size_t len) noexcept { [[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) { for(size_type pos{}; pos < len; ++pos) {
base.hash = (base.hash ^ static_cast<hs_traits::type>(str[pos])) * hs_traits::prime; base.hash = (base.hash ^ static_cast<traits_type::type>(str[pos])) * traits_type::prime;
} }
return base; return base;

View File

@ -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). * @brief Deleter for allocator-aware unique pointers (waiting for C++20).
* @tparam Args Types of arguments to use to construct the object. * @tparam Allocator Type of allocator used to manage memory and elements.
*/ */
template<typename Allocator> template<typename Allocator>
struct allocation_deleter: private Allocator { struct allocation_deleter: private Allocator {
@ -127,7 +127,7 @@ struct allocation_deleter: private Allocator {
* @param ptr A valid pointer to an object of the given type. * @param ptr A valid pointer to an object of the given type.
*/ */
constexpr void operator()(pointer ptr) noexcept(std::is_nothrow_destructible_v<typename allocator_type::value_type>) { constexpr void operator()(pointer ptr) noexcept(std::is_nothrow_destructible_v<typename allocator_type::value_type>) {
using alloc_traits = typename std::allocator_traits<Allocator>; using alloc_traits = std::allocator_traits<Allocator>;
alloc_traits::destroy(*this, to_address(ptr)); alloc_traits::destroy(*this, to_address(ptr));
alloc_traits::deallocate(*this, ptr, 1u); alloc_traits::deallocate(*this, ptr, 1u);
} }

View File

@ -69,7 +69,7 @@ struct forward_apply: private Func {
* @tparam Args Types of arguments to use to construct the new instance. * @tparam Args Types of arguments to use to construct the new instance.
* @param args Parameters to use to construct the instance. * @param args Parameters to use to construct the instance.
*/ */
template<class... Args> template<typename... Args>
constexpr forward_apply(Args &&...args) noexcept(std::is_nothrow_constructible_v<Func, Args...>) constexpr forward_apply(Args &&...args) noexcept(std::is_nothrow_constructible_v<Func, Args...>)
: Func{std::forward<Args>(args)...} {} : Func{std::forward<Args>(args)...} {}
@ -79,13 +79,13 @@ struct forward_apply: private Func {
* @param args Parameters to forward to the underlying function. * @param args Parameters to forward to the underlying function.
* @return Return value of the underlying function, if any. * @return Return value of the underlying function, if any.
*/ */
template<class Type> template<typename Type>
constexpr decltype(auto) operator()(Type &&args) noexcept(noexcept(std::apply(std::declval<Func &>(), args))) { constexpr decltype(auto) operator()(Type &&args) noexcept(noexcept(std::apply(std::declval<Func &>(), args))) {
return std::apply(static_cast<Func &>(*this), std::forward<Type>(args)); return std::apply(static_cast<Func &>(*this), std::forward<Type>(args));
} }
/*! @copydoc operator()() */ /*! @copydoc operator()() */
template<class Type> template<typename Type>
constexpr decltype(auto) operator()(Type &&args) const noexcept(noexcept(std::apply(std::declval<const Func &>(), args))) { constexpr decltype(auto) operator()(Type &&args) const noexcept(noexcept(std::apply(std::declval<const Func &>(), args))) {
return std::apply(static_cast<const Func &>(*this), std::forward<Type>(args)); return std::apply(static_cast<const Func &>(*this), std::forward<Type>(args));
} }

View File

@ -3,6 +3,7 @@
#include <cstddef> #include <cstddef>
#include <iterator> #include <iterator>
#include <tuple>
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
#include "../config/config.h" #include "../config/config.h"
@ -55,7 +56,6 @@ using type_identity_t = typename type_identity<Type>::type;
/** /**
* @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains. * @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains.
* @tparam Type The type of which to return the size. * @tparam Type The type of which to return the size.
* @tparam The size of the type if `sizeof` accepts it, 0 otherwise.
*/ */
template<typename Type, typename = void> template<typename Type, typename = void>
struct size_of: std::integral_constant<std::size_t, 0u> {}; struct size_of: std::integral_constant<std::size_t, 0u> {};
@ -297,7 +297,8 @@ struct type_list_contains;
* @tparam Other Type to look for. * @tparam Other Type to look for.
*/ */
template<typename... Type, typename Other> template<typename... Type, typename Other>
struct type_list_contains<type_list<Type...>, Other>: std::disjunction<std::is_same<Type, Other>...> {}; struct type_list_contains<type_list<Type...>, Other>
: std::bool_constant<(std::is_same_v<Type, Other> || ...)> {};
/** /**
* @brief Helper variable template. * @brief Helper variable template.
@ -385,10 +386,20 @@ struct value_list_element<Index, value_list<Value, Other...>>
*/ */
template<auto Value, auto... Other> template<auto Value, auto... Other>
struct value_list_element<0u, value_list<Value, Other...>> { struct value_list_element<0u, value_list<Value, Other...>> {
/*! @brief Searched type. */
using type = decltype(Value);
/*! @brief Searched value. */ /*! @brief Searched value. */
static constexpr auto value = Value; static constexpr auto value = Value;
}; };
/**
* @brief Helper type.
* @tparam Index Index of the type to return.
* @tparam List Value list to search into.
*/
template<std::size_t Index, typename List>
using value_list_element_t = typename value_list_element<Index, List>::type;
/** /**
* @brief Helper type. * @brief Helper type.
* @tparam Index Index of the value to return. * @tparam Index Index of the value to return.
@ -397,6 +408,58 @@ struct value_list_element<0u, value_list<Value, Other...>> {
template<std::size_t Index, typename List> template<std::size_t Index, typename List>
inline constexpr auto value_list_element_v = value_list_element<Index, List>::value; inline constexpr auto value_list_element_v = value_list_element<Index, List>::value;
/*! @brief Primary template isn't defined on purpose. */
template<auto, typename>
struct value_list_index;
/**
* @brief Provides compile-time type access to the values of a value list.
* @tparam Value Value to look for and for which to return the index.
* @tparam First First value provided by the value list.
* @tparam Other Other values provided by the value list.
*/
template<auto Value, auto First, auto... Other>
struct value_list_index<Value, value_list<First, Other...>> {
/*! @brief Unsigned integer type. */
using value_type = std::size_t;
/*! @brief Compile-time position of the given value in the sublist. */
static constexpr value_type value = 1u + value_list_index<Value, value_list<Other...>>::value;
};
/**
* @brief Provides compile-time type access to the values of a value list.
* @tparam Value Value to look for and for which to return the index.
* @tparam Other Other values provided by the value list.
*/
template<auto Value, auto... Other>
struct value_list_index<Value, value_list<Value, Other...>> {
static_assert(value_list_index<Value, value_list<Other...>>::value == sizeof...(Other), "Non-unique type");
/*! @brief Unsigned integer type. */
using value_type = std::size_t;
/*! @brief Compile-time position of the given value in the sublist. */
static constexpr value_type value = 0u;
};
/**
* @brief Provides compile-time type access to the values of a value list.
* @tparam Value Value to look for and for which to return the index.
*/
template<auto Value>
struct value_list_index<Value, value_list<>> {
/*! @brief Unsigned integer type. */
using value_type = std::size_t;
/*! @brief Compile-time position of the given type in the sublist. */
static constexpr value_type value = 0u;
};
/**
* @brief Helper variable template.
* @tparam List Value list.
* @tparam Value Value to look for and for which to return the index.
*/
template<auto Value, typename List>
inline constexpr std::size_t value_list_index_v = value_list_index<Value, List>::value;
/** /**
* @brief Concatenates multiple value lists. * @brief Concatenates multiple value lists.
* @tparam Value Values provided by the first value list. * @tparam Value Values provided by the first value list.
@ -448,6 +511,89 @@ struct value_list_cat<value_list<Value...>> {
template<typename... List> template<typename... List>
using value_list_cat_t = typename value_list_cat<List...>::type; using value_list_cat_t = typename value_list_cat<List...>::type;
/*! @brief Primary template isn't defined on purpose. */
template<typename>
struct value_list_unique;
/**
* @brief Removes duplicates values from a value list.
* @tparam Value One of the values provided by the given value list.
* @tparam Other The other values provided by the given value list.
*/
template<auto Value, auto... Other>
struct value_list_unique<value_list<Value, Other...>> {
/*! @brief A value list without duplicate types. */
using type = std::conditional_t<
((Value == Other) || ...),
typename value_list_unique<value_list<Other...>>::type,
value_list_cat_t<value_list<Value>, typename value_list_unique<value_list<Other...>>::type>>;
};
/*! @brief Removes duplicates values from a value list. */
template<>
struct value_list_unique<value_list<>> {
/*! @brief A value list without duplicate types. */
using type = value_list<>;
};
/**
* @brief Helper type.
* @tparam Type A value list.
*/
template<typename Type>
using value_list_unique_t = typename value_list_unique<Type>::type;
/**
* @brief Provides the member constant `value` to true if a value list contains
* a given value, false otherwise.
* @tparam List Value list.
* @tparam Value Value to look for.
*/
template<typename List, auto Value>
struct value_list_contains;
/**
* @copybrief value_list_contains
* @tparam Value Values provided by the value list.
* @tparam Other Value to look for.
*/
template<auto... Value, auto Other>
struct value_list_contains<value_list<Value...>, Other>
: std::bool_constant<((Value == Other) || ...)> {};
/**
* @brief Helper variable template.
* @tparam List Value list.
* @tparam Value Value to look for.
*/
template<typename List, auto Value>
inline constexpr bool value_list_contains_v = value_list_contains<List, Value>::value;
/*! @brief Primary template isn't defined on purpose. */
template<typename...>
class value_list_diff;
/**
* @brief Computes the difference between two value lists.
* @tparam Value Values provided by the first value list.
* @tparam Other Values provided by the second value list.
*/
template<auto... Value, auto... Other>
class value_list_diff<value_list<Value...>, value_list<Other...>> {
using v141_toolset_workaround = value_list<Other...>;
public:
/*! @brief A value list that is the difference between the two value lists. */
using type = value_list_cat_t<std::conditional_t<value_list_contains_v<v141_toolset_workaround, Value>, value_list<>, value_list<Value>>...>;
};
/**
* @brief Helper type.
* @tparam List Value lists between which to compute the difference.
*/
template<typename... List>
using value_list_diff_t = typename value_list_diff<List...>::type;
/*! @brief Same as std::is_invocable, but with tuples. */ /*! @brief Same as std::is_invocable, but with tuples. */
template<typename, typename> template<typename, typename>
struct is_applicable: std::false_type {}; struct is_applicable: std::false_type {};
@ -568,7 +714,7 @@ inline constexpr bool is_iterator_v = is_iterator<Type>::value;
*/ */
template<typename Type> template<typename Type>
struct is_ebco_eligible struct is_ebco_eligible
: std::conjunction<std::is_empty<Type>, std::negation<std::is_final<Type>>> {}; : std::bool_constant<std::is_empty_v<Type> && !std::is_final_v<Type>> {};
/** /**
* @brief Helper variable template. * @brief Helper variable template.
@ -659,6 +805,10 @@ template<typename Type>
struct is_equality_comparable<Type, std::void_t<decltype(std::declval<Type>() == std::declval<Type>())>> struct is_equality_comparable<Type, std::void_t<decltype(std::declval<Type>() == std::declval<Type>())>>
: std::bool_constant<internal::maybe_equality_comparable<Type>(choice<2>)> {}; : std::bool_constant<internal::maybe_equality_comparable<Type>(choice<2>)> {};
/*! @copydoc is_equality_comparable */
template<typename Type, auto N>
struct is_equality_comparable<Type[N]>: std::false_type {};
/** /**
* @brief Helper variable template. * @brief Helper variable template.
* @tparam Type The type to test. * @tparam Type The type to test.
@ -755,4 +905,16 @@ using nth_argument_t = typename nth_argument<Index, Candidate>::type;
} // namespace entt } // namespace entt
template<typename... Type>
struct std::tuple_size<entt::type_list<Type...>>: std::integral_constant<std::size_t, entt::type_list<Type...>::size> {};
template<std::size_t Index, typename... Type>
struct std::tuple_element<Index, entt::type_list<Type...>>: entt::type_list_element<Index, entt::type_list<Type...>> {};
template<auto... Value>
struct std::tuple_size<entt::value_list<Value...>>: std::integral_constant<std::size_t, entt::value_list<Value...>::size> {};
template<std::size_t Index, auto... Value>
struct std::tuple_element<Index, entt::value_list<Value...>>: entt::value_list_element<Index, entt::value_list<Value...>> {};
#endif #endif

View File

@ -17,7 +17,7 @@ struct identity {
* @param value The actual argument. * @param value The actual argument.
* @return The submitted value as-is. * @return The submitted value as-is.
*/ */
template<class Type> template<typename Type>
[[nodiscard]] constexpr Type &&operator()(Type &&value) const noexcept { [[nodiscard]] constexpr Type &&operator()(Type &&value) const noexcept {
return std::forward<Type>(value); return std::forward<Type>(value);
} }
@ -50,7 +50,7 @@ template<typename Func>
* @brief Helper type for visitors. * @brief Helper type for visitors.
* @tparam Func Types of function objects. * @tparam Func Types of function objects.
*/ */
template<class... Func> template<typename... Func>
struct overloaded: Func... { struct overloaded: Func... {
using Func::operator()...; using Func::operator()...;
}; };
@ -59,14 +59,14 @@ struct overloaded: Func... {
* @brief Deduction guide. * @brief Deduction guide.
* @tparam Func Types of function objects. * @tparam Func Types of function objects.
*/ */
template<class... Func> template<typename... Func>
overloaded(Func...) -> overloaded<Func...>; overloaded(Func...) -> overloaded<Func...>;
/** /**
* @brief Basic implementation of a y-combinator. * @brief Basic implementation of a y-combinator.
* @tparam Func Type of a potentially recursive function. * @tparam Func Type of a potentially recursive function.
*/ */
template<class Func> template<typename Func>
struct y_combinator { struct y_combinator {
/** /**
* @brief Constructs a y-combinator from a given function. * @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. * @param args Parameters to use to invoke the underlying function.
* @return Return value of the underlying function, if any. * @return Return value of the underlying function, if any.
*/ */
template<class... Args> template<typename... Args>
constexpr decltype(auto) operator()(Args &&...args) const noexcept(std::is_nothrow_invocable_v<Func, const y_combinator &, Args...>) { constexpr decltype(auto) operator()(Args &&...args) const noexcept(std::is_nothrow_invocable_v<Func, const y_combinator &, Args...>) {
return func(*this, std::forward<Args>(args)...); return func(*this, std::forward<Args>(args)...);
} }
/*! @copydoc operator()() */ /*! @copydoc operator()() */
template<class... Args> template<typename... Args>
constexpr decltype(auto) operator()(Args &&...args) noexcept(std::is_nothrow_invocable_v<Func, y_combinator &, Args...>) { constexpr decltype(auto) operator()(Args &&...args) noexcept(std::is_nothrow_invocable_v<Func, y_combinator &, Args...>) {
return func(*this, std::forward<Args>(args)...); return func(*this, std::forward<Args>(args)...);
} }

View File

@ -4,6 +4,7 @@
#include <cstddef> #include <cstddef>
#include <type_traits> #include <type_traits>
#include "../config/config.h" #include "../config/config.h"
#include "fwd.hpp"
namespace entt { namespace entt {
@ -17,6 +18,9 @@ namespace internal {
template<typename Type, typename = void> template<typename Type, typename = void>
struct in_place_delete: std::bool_constant<!(std::is_move_constructible_v<Type> && std::is_move_assignable_v<Type>)> {}; struct in_place_delete: std::bool_constant<!(std::is_move_constructible_v<Type> && std::is_move_assignable_v<Type>)> {};
template<>
struct in_place_delete<void>: std::false_type {};
template<typename Type> template<typename Type>
struct in_place_delete<Type, std::enable_if_t<Type::in_place_delete>> struct in_place_delete<Type, std::enable_if_t<Type::in_place_delete>>
: std::true_type {}; : std::true_type {};
@ -24,6 +28,9 @@ struct in_place_delete<Type, std::enable_if_t<Type::in_place_delete>>
template<typename Type, typename = void> template<typename Type, typename = void>
struct page_size: std::integral_constant<std::size_t, !std::is_empty_v<ENTT_ETO_TYPE(Type)> * ENTT_PACKED_PAGE> {}; struct page_size: std::integral_constant<std::size_t, !std::is_empty_v<ENTT_ETO_TYPE(Type)> * ENTT_PACKED_PAGE> {};
template<>
struct page_size<void>: std::integral_constant<std::size_t, 0u> {};
template<typename Type> template<typename Type>
struct page_size<Type, std::enable_if_t<std::is_convertible_v<decltype(Type::page_size), std::size_t>>> struct page_size<Type, std::enable_if_t<std::is_convertible_v<decltype(Type::page_size), std::size_t>>>
: std::integral_constant<std::size_t, Type::page_size> {}; : std::integral_constant<std::size_t, Type::page_size> {};
@ -52,13 +59,6 @@ struct component_traits {
static constexpr std::size_t page_size = internal::page_size<Type>::value; static constexpr std::size_t page_size = internal::page_size<Type>::value;
}; };
/**
* @brief Helper variable template.
* @tparam Type Type of component.
*/
template<class Type>
inline constexpr bool ignore_as_empty_v = (std::is_void_v<Type> || component_traits<Type>::page_size == 0u);
} // namespace entt } // namespace entt
#endif #endif

View File

@ -16,35 +16,47 @@ namespace entt {
namespace internal { namespace internal {
// waiting for C++20 (and std::popcount)
template<typename Type>
static constexpr int popcount(Type value) noexcept {
return value ? (int(value & 1) + popcount(value >> 1)) : 0;
}
template<typename, typename = void> template<typename, typename = void>
struct entt_traits; struct entt_traits;
template<typename Type> template<typename Type>
struct entt_traits<Type, std::enable_if_t<std::is_enum_v<Type>>> struct entt_traits<Type, std::enable_if_t<std::is_enum_v<Type>>>
: entt_traits<std::underlying_type_t<Type>> {}; : entt_traits<std::underlying_type_t<Type>> {
using value_type = Type;
};
template<typename Type> template<typename Type>
struct entt_traits<Type, std::enable_if_t<std::is_class_v<Type>>> struct entt_traits<Type, std::enable_if_t<std::is_class_v<Type>>>
: entt_traits<typename Type::entity_type> {}; : entt_traits<typename Type::entity_type> {
using value_type = Type;
};
template<> template<>
struct entt_traits<std::uint32_t> { struct entt_traits<std::uint32_t> {
using value_type = std::uint32_t;
using entity_type = std::uint32_t; using entity_type = std::uint32_t;
using version_type = std::uint16_t; using version_type = std::uint16_t;
static constexpr entity_type entity_mask = 0xFFFFF; static constexpr entity_type entity_mask = 0xFFFFF;
static constexpr entity_type version_mask = 0xFFF; static constexpr entity_type version_mask = 0xFFF;
static constexpr std::size_t entity_shift = 20u;
}; };
template<> template<>
struct entt_traits<std::uint64_t> { struct entt_traits<std::uint64_t> {
using value_type = std::uint64_t;
using entity_type = std::uint64_t; using entity_type = std::uint64_t;
using version_type = std::uint32_t; using version_type = std::uint32_t;
static constexpr entity_type entity_mask = 0xFFFFFFFF; static constexpr entity_type entity_mask = 0xFFFFFFFF;
static constexpr entity_type version_mask = 0xFFFFFFFF; static constexpr entity_type version_mask = 0xFFFFFFFF;
static constexpr std::size_t entity_shift = 32u;
}; };
} // namespace internal } // namespace internal
@ -55,24 +67,28 @@ struct entt_traits<std::uint64_t> {
*/ */
/** /**
* @brief Entity traits. * @brief Common basic entity traits implementation.
* @tparam Type Type of identifier. * @tparam Traits Actual entity traits to use.
*/ */
template<typename Type> template<typename Traits>
class entt_traits: internal::entt_traits<Type> { class basic_entt_traits {
using base_type = internal::entt_traits<Type>; 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: public:
/*! @brief Value type. */ /*! @brief Value type. */
using value_type = Type; using value_type = typename Traits::value_type;
/*! @brief Underlying entity type. */ /*! @brief Underlying entity type. */
using entity_type = typename base_type::entity_type; using entity_type = typename Traits::entity_type;
/*! @brief Underlying version type. */ /*! @brief Underlying version type. */
using version_type = typename base_type::version_type; using version_type = typename Traits::version_type;
/*! @brief Reserved identifier. */
static constexpr entity_type reserved = base_type::entity_mask | (base_type::version_mask << base_type::entity_shift); /*! @brief Entity mask size. */
/*! @brief Page size, default is `ENTT_SPARSE_PAGE`. */ static constexpr entity_type entity_mask = Traits::entity_mask;
static constexpr auto page_size = ENTT_SPARSE_PAGE; /*! @brief Version mask size */
static constexpr entity_type version_mask = Traits::version_mask;
/** /**
* @brief Converts an entity to its underlying type. * @brief Converts an entity to its underlying type.
@ -89,7 +105,7 @@ public:
* @return The integral representation of the entity part. * @return The integral representation of the entity part.
*/ */
[[nodiscard]] static constexpr entity_type to_entity(const value_type value) noexcept { [[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. * @return The integral representation of the version part.
*/ */
[[nodiscard]] static constexpr version_type to_version(const value_type value) noexcept { [[nodiscard]] static constexpr version_type to_version(const value_type value) noexcept {
return (to_integral(value) >> base_type::entity_shift); return static_cast<version_type>(to_integral(value) >> length);
}
/**
* @brief Returns the successor of a given identifier.
* @param value The identifier of which to return the successor.
* @return The successor of the given identifier.
*/
[[nodiscard]] static constexpr value_type next(const value_type value) noexcept {
const auto vers = to_version(value) + 1;
return construct(to_entity(value), static_cast<version_type>(vers + (vers == version_mask)));
} }
/** /**
@ -112,7 +138,7 @@ public:
* @return A properly constructed identifier. * @return A properly constructed identifier.
*/ */
[[nodiscard]] static constexpr value_type construct(const entity_type entity, const version_type version) noexcept { [[nodiscard]] static constexpr value_type construct(const entity_type entity, const version_type version) noexcept {
return value_type{(entity & base_type::entity_mask) | (static_cast<entity_type>(version) << base_type::entity_shift)}; return value_type{(entity & entity_mask) | (static_cast<entity_type>(version) << length)};
} }
/** /**
@ -126,11 +152,23 @@ public:
* @return A properly constructed identifier. * @return A properly constructed identifier.
*/ */
[[nodiscard]] static constexpr value_type combine(const entity_type lhs, const entity_type rhs) noexcept { [[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); constexpr auto mask = (version_mask << length);
return value_type{(lhs & base_type::entity_mask) | (rhs & mask)}; return value_type{(lhs & entity_mask) | (rhs & mask)};
} }
}; };
/**
* @brief Entity traits.
* @tparam Type Type of identifier.
*/
template<typename Type>
struct entt_traits: basic_entt_traits<internal::entt_traits<Type>> {
/*! @brief Base type. */
using base_type = basic_entt_traits<internal::entt_traits<Type>>;
/*! @brief Page size, default is `ENTT_SPARSE_PAGE`. */
static constexpr std::size_t page_size = ENTT_SPARSE_PAGE;
};
/** /**
* @copydoc entt_traits<Entity>::to_integral * @copydoc entt_traits<Entity>::to_integral
* @tparam Entity The value type. * @tparam Entity The value type.
@ -167,8 +205,9 @@ struct null_t {
*/ */
template<typename Entity> template<typename Entity>
[[nodiscard]] constexpr operator Entity() const noexcept { [[nodiscard]] constexpr operator Entity() const noexcept {
using entity_traits = entt_traits<Entity>; using traits_type = entt_traits<Entity>;
return entity_traits::combine(entity_traits::reserved, entity_traits::reserved); constexpr auto value = traits_type::construct(traits_type::entity_mask, traits_type::version_mask);
return value;
} }
/** /**
@ -197,8 +236,8 @@ struct null_t {
*/ */
template<typename Entity> template<typename Entity>
[[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept { [[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept {
using entity_traits = entt_traits<Entity>; using traits_type = entt_traits<Entity>;
return entity_traits::to_entity(entity) == entity_traits::to_entity(*this); return traits_type::to_entity(entity) == traits_type::to_entity(*this);
} }
/** /**
@ -246,8 +285,9 @@ struct tombstone_t {
*/ */
template<typename Entity> template<typename Entity>
[[nodiscard]] constexpr operator Entity() const noexcept { [[nodiscard]] constexpr operator Entity() const noexcept {
using entity_traits = entt_traits<Entity>; using traits_type = entt_traits<Entity>;
return entity_traits::combine(entity_traits::reserved, entity_traits::reserved); constexpr auto value = traits_type::construct(traits_type::entity_mask, traits_type::version_mask);
return value;
} }
/** /**
@ -276,8 +316,8 @@ struct tombstone_t {
*/ */
template<typename Entity> template<typename Entity>
[[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept { [[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept {
using entity_traits = entt_traits<Entity>; using traits_type = entt_traits<Entity>;
return entity_traits::to_version(entity) == entity_traits::to_version(*this); return traits_type::to_version(entity) == traits_type::to_version(*this);
} }
/** /**

View File

@ -1,6 +1,7 @@
#ifndef ENTT_ENTITY_FWD_HPP #ifndef ENTT_ENTITY_FWD_HPP
#define ENTT_ENTITY_FWD_HPP #define ENTT_ENTITY_FWD_HPP
#include <cstdint>
#include <memory> #include <memory>
#include <type_traits> #include <type_traits>
#include "../core/fwd.hpp" #include "../core/fwd.hpp"
@ -11,6 +12,14 @@ namespace entt {
/*! @brief Default entity identifier. */ /*! @brief Default entity identifier. */
enum class entity : id_type {}; enum class entity : id_type {};
/*! @brief Storage deletion policy. */
enum class deletion_policy : std::uint8_t {
/*! @brief Swap-and-pop deletion policy. */
swap_and_pop = 0u,
/*! @brief In-place deletion policy. */
in_place = 1u
};
template<typename Entity = entity, typename = std::allocator<Entity>> template<typename Entity = entity, typename = std::allocator<Entity>>
class basic_sparse_set; class basic_sparse_set;
@ -18,18 +27,18 @@ template<typename Type, typename = entity, typename = std::allocator<Type>, type
class basic_storage; class basic_storage;
template<typename Type> template<typename Type>
class sigh_storage_mixin; class sigh_mixin;
/** /**
* @brief Provides a common way to define storage types. * @brief Provides a common way to define storage types.
* @tparam Type Storage value type. * @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. * @tparam Allocator Type of allocator used to manage memory and elements.
*/ */
template<typename Type, typename Entity = entity, typename Allocator = std::allocator<Type>, typename = void> template<typename Type, typename Entity = entity, typename Allocator = std::allocator<Type>, typename = void>
struct storage_type { struct storage_type {
/*! @brief Type-to-storage conversion result. */ /*! @brief Type-to-storage conversion result. */
using type = sigh_storage_mixin<basic_storage<Type, Entity, Allocator>>; using type = sigh_mixin<basic_storage<Type, Entity, Allocator>>;
}; };
/** /**
@ -42,7 +51,7 @@ using storage_type_t = typename storage_type<Args...>::type;
/** /**
* Type-to-storage conversion utility that preserves constness. * Type-to-storage conversion utility that preserves constness.
* @tparam Type Storage value type, eventually const. * @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. * @tparam Allocator Type of allocator used to manage memory and elements.
*/ */
template<typename Type, typename Entity = entity, typename Allocator = std::allocator<std::remove_const_t<Type>>> template<typename Type, typename Entity = entity, typename Allocator = std::allocator<std::remove_const_t<Type>>>
@ -70,7 +79,7 @@ class basic_runtime_view;
template<typename, typename, typename> template<typename, typename, typename>
class basic_group; class basic_group;
template<typename> template<typename, typename Mask = std::uint32_t, typename = std::allocator<Mask>>
class basic_observer; class basic_observer;
template<typename> template<typename>
@ -93,7 +102,10 @@ class basic_continuous_loader;
* @tparam Type List of types. * @tparam Type List of types.
*/ */
template<typename... Type> template<typename... Type>
using exclude_t = type_list<Type...>; struct exclude_t final: type_list<Type...> {
/*! @brief Default constructor. */
explicit constexpr exclude_t() {}
};
/** /**
* @brief Variable template for exclusion lists. * @brief Variable template for exclusion lists.
@ -107,7 +119,10 @@ inline constexpr exclude_t<Type...> exclude{};
* @tparam Type List of types. * @tparam Type List of types.
*/ */
template<typename... Type> template<typename... Type>
using get_t = type_list<Type...>; struct get_t final: type_list<Type...> {
/*! @brief Default constructor. */
explicit constexpr get_t() {}
};
/** /**
* @brief Variable template for lists of observed components. * @brief Variable template for lists of observed components.
@ -121,7 +136,10 @@ inline constexpr get_t<Type...> get{};
* @tparam Type List of types. * @tparam Type List of types.
*/ */
template<typename... Type> template<typename... Type>
using owned_t = type_list<Type...>; struct owned_t final: type_list<Type...> {
/*! @brief Default constructor. */
explicit constexpr owned_t() {}
};
/** /**
* @brief Variable template for lists of owned components. * @brief Variable template for lists of owned components.
@ -130,6 +148,39 @@ using owned_t = type_list<Type...>;
template<typename... Type> template<typename... Type>
inline constexpr owned_t<Type...> owned{}; inline constexpr owned_t<Type...> owned{};
/**
* @brief Applies a given _function_ to a get list and generate a new list.
* @tparam Type Types provided by the get list.
* @tparam Op Unary operation as template class with a type member named `type`.
*/
template<typename... Type, template<typename...> class Op>
struct type_list_transform<get_t<Type...>, Op> {
/*! @brief Resulting get list after applying the transform function. */
using type = get_t<typename Op<Type>::type...>;
};
/**
* @brief Applies a given _function_ to an exclude list and generate a new list.
* @tparam Type Types provided by the exclude list.
* @tparam Op Unary operation as template class with a type member named `type`.
*/
template<typename... Type, template<typename...> class Op>
struct type_list_transform<exclude_t<Type...>, Op> {
/*! @brief Resulting exclude list after applying the transform function. */
using type = exclude_t<typename Op<Type>::type...>;
};
/**
* @brief Applies a given _function_ to an owned list and generate a new list.
* @tparam Type Types provided by the owned list.
* @tparam Op Unary operation as template class with a type member named `type`.
*/
template<typename... Type, template<typename...> class Op>
struct type_list_transform<owned_t<Type...>, Op> {
/*! @brief Resulting owned list after applying the transform function. */
using type = owned_t<typename Op<Type>::type...>;
};
/*! @brief Alias declaration for the most common use case. */ /*! @brief Alias declaration for the most common use case. */
using sparse_set = basic_sparse_set<>; using sparse_set = basic_sparse_set<>;

View File

@ -5,9 +5,10 @@
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
#include "../config/config.h" #include "../config/config.h"
#include "../core/fwd.hpp"
#include "../core/iterator.hpp" #include "../core/iterator.hpp"
#include "../core/type_info.hpp"
#include "../core/type_traits.hpp" #include "../core/type_traits.hpp"
#include "component.hpp"
#include "entity.hpp" #include "entity.hpp"
#include "fwd.hpp" #include "fwd.hpp"
#include "sparse_set.hpp" #include "sparse_set.hpp"
@ -29,7 +30,7 @@ template<typename It, typename... Owned, typename... Get>
class extended_group_iterator<It, owned_t<Owned...>, get_t<Get...>> { class extended_group_iterator<It, owned_t<Owned...>, get_t<Get...>> {
template<typename Type> template<typename Type>
auto index_to_element([[maybe_unused]] Type &cpool) const { auto index_to_element([[maybe_unused]] Type &cpool) const {
if constexpr(ignore_as_empty_v<typename Type::value_type>) { if constexpr(Type::traits_type::page_size == 0u) {
return std::make_tuple(); return std::make_tuple();
} else { } else {
return std::forward_as_tuple(cpool.rbegin()[it.index()]); return std::forward_as_tuple(cpool.rbegin()[it.index()]);
@ -37,6 +38,7 @@ class extended_group_iterator<It, owned_t<Owned...>, get_t<Get...>> {
} }
public: public:
using iterator_type = It;
using difference_type = std::ptrdiff_t; using difference_type = std::ptrdiff_t;
using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval<It>()), std::declval<Owned>().get_as_tuple({})..., std::declval<Get>().get_as_tuple({})...)); using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval<It>()), std::declval<Owned>().get_as_tuple({})..., std::declval<Get>().get_as_tuple({})...));
using pointer = input_iterator_pointer<value_type>; using pointer = input_iterator_pointer<value_type>;
@ -68,6 +70,10 @@ public:
return operator*(); return operator*();
} }
[[nodiscard]] constexpr iterator_type base() const noexcept {
return it;
}
template<typename... Lhs, typename... Rhs> template<typename... Lhs, typename... Rhs>
friend constexpr bool operator==(const extended_group_iterator<Lhs...> &, const extended_group_iterator<Rhs...> &) noexcept; friend constexpr bool operator==(const extended_group_iterator<Lhs...> &, const extended_group_iterator<Rhs...> &) noexcept;
@ -86,6 +92,164 @@ template<typename... Lhs, typename... Rhs>
return !(lhs == rhs); return !(lhs == rhs);
} }
struct group_descriptor {
using size_type = std::size_t;
virtual ~group_descriptor() = default;
virtual size_type owned(const id_type *, const size_type) const noexcept {
return 0u;
}
};
template<typename, typename, typename>
class group_handler;
template<typename... Owned, typename... Get, typename... Exclude>
class group_handler<owned_t<Owned...>, get_t<Get...>, exclude_t<Exclude...>> final: public group_descriptor {
// nasty workaround for an issue with the toolset v141 that doesn't accept a fold expression here
static_assert(!std::disjunction_v<std::bool_constant<Owned::traits_type::in_place_delete>...>, "Groups do not support in-place delete");
static_assert(!std::disjunction_v<std::is_const<Owned>..., std::is_const<Get>..., std::is_const<Exclude>...>, "Const storage type not allowed");
using base_type = std::common_type_t<typename Owned::base_type..., typename Get::base_type..., typename Exclude::base_type...>;
using entity_type = typename base_type::entity_type;
void swap_elements(const std::size_t pos, const entity_type entt) {
std::apply([pos, entt](auto *...cpool) { (cpool->swap_elements(cpool->data()[pos], entt), ...); }, pools);
}
void push_on_construct(const entity_type entt) {
if(std::apply([entt, len = len](auto *cpool, auto *...other) { return cpool->contains(entt) && !(cpool->index(entt) < len) && (other->contains(entt) && ...); }, pools)
&& std::apply([entt](auto *...cpool) { return (!cpool->contains(entt) && ...); }, filter)) {
swap_elements(len++, entt);
}
}
void push_on_destroy(const entity_type entt) {
if(std::apply([entt, len = len](auto *cpool, auto *...other) { return cpool->contains(entt) && !(cpool->index(entt) < len) && (other->contains(entt) && ...); }, pools)
&& std::apply([entt](auto *...cpool) { return (0u + ... + cpool->contains(entt)) == 1u; }, filter)) {
swap_elements(len++, entt);
}
}
void remove_if(const entity_type entt) {
if(std::get<0>(pools)->contains(entt) && (std::get<0>(pools)->index(entt) < len)) {
swap_elements(--len, entt);
}
}
public:
using size_type = typename base_type::size_type;
group_handler(Owned &...opool, Get &...gpool, Exclude &...epool)
: pools{&opool..., &gpool...},
filter{&epool...},
len{} {
std::apply([this](auto *...cpool) { ((cpool->on_construct().template connect<&group_handler::push_on_construct>(*this), cpool->on_destroy().template connect<&group_handler::remove_if>(*this)), ...); }, pools);
std::apply([this](auto *...cpool) { ((cpool->on_construct().template connect<&group_handler::remove_if>(*this), cpool->on_destroy().template connect<&group_handler::push_on_destroy>(*this)), ...); }, filter);
// we cannot iterate backwards because we want to leave behind valid entities in case of owned types
for(auto *first = std::get<0>(pools)->data(), *last = first + std::get<0>(pools)->size(); first != last; ++first) {
push_on_construct(*first);
}
}
size_type owned(const id_type *elem, const size_type length) const noexcept final {
size_type cnt = 0u;
for(auto pos = 0u; pos < length; ++pos) {
cnt += ((elem[pos] == entt::type_hash<typename Owned::value_type>::value()) || ...);
}
return cnt;
}
[[nodiscard]] size_type length() const noexcept {
return len;
}
template<typename Type>
Type pools_as() const noexcept {
return pools;
}
template<typename Type>
Type filter_as() const noexcept {
return filter;
}
private:
std::tuple<Owned *..., Get *...> pools;
std::tuple<Exclude *...> filter;
std::size_t len;
};
template<typename... Get, typename... Exclude>
class group_handler<owned_t<>, get_t<Get...>, exclude_t<Exclude...>> final: public group_descriptor {
// nasty workaround for an issue with the toolset v141 that doesn't accept a fold expression here
static_assert(!std::disjunction_v<std::is_const<Get>..., std::is_const<Exclude>...>, "Const storage type not allowed");
using base_type = std::common_type_t<typename Get::base_type..., typename Exclude::base_type...>;
using entity_type = typename base_type::entity_type;
void push_on_construct(const entity_type entt) {
if(!elem.contains(entt)
&& std::apply([entt](auto *...cpool) { return (cpool->contains(entt) && ...); }, pools)
&& std::apply([entt](auto *...cpool) { return (!cpool->contains(entt) && ...); }, filter)) {
elem.push(entt);
}
}
void push_on_destroy(const entity_type entt) {
if(!elem.contains(entt)
&& std::apply([entt](auto *...cpool) { return (cpool->contains(entt) && ...); }, pools)
&& std::apply([entt](auto *...cpool) { return (0u + ... + cpool->contains(entt)) == 1u; }, filter)) {
elem.push(entt);
}
}
void remove_if(const entity_type entt) {
elem.remove(entt);
}
public:
using common_type = base_type;
template<typename Alloc>
group_handler(const Alloc &alloc, Get &...gpool, Exclude &...epool)
: pools{&gpool...},
filter{&epool...},
elem{alloc} {
std::apply([this](auto *...cpool) { ((cpool->on_construct().template connect<&group_handler::push_on_construct>(*this), cpool->on_destroy().template connect<&group_handler::remove_if>(*this)), ...); }, pools);
std::apply([this](auto *...cpool) { ((cpool->on_construct().template connect<&group_handler::remove_if>(*this), cpool->on_destroy().template connect<&group_handler::push_on_destroy>(*this)), ...); }, filter);
for(const auto entity: static_cast<base_type &>(*std::get<0>(pools))) {
push_on_construct(entity);
}
}
common_type &handle() noexcept {
return elem;
}
const common_type &handle() const noexcept {
return elem;
}
template<typename Type>
Type pools_as() const noexcept {
return pools;
}
template<typename Type>
Type filter_as() const noexcept {
return filter;
}
private:
std::tuple<Get *...> pools;
std::tuple<Exclude *...> filter;
base_type elem;
};
} // namespace internal } // namespace internal
/** /**
@ -119,18 +283,28 @@ class basic_group;
* * The entity currently pointed is destroyed. * * The entity currently pointed is destroyed.
* *
* In all other cases, modifying the pools iterated by the group in any way * 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 Get Types of storage _observed_ by the group.
* @tparam Exclude Types of storage used to filter the group. * @tparam Exclude Types of storage used to filter the group.
*/ */
template<typename... Get, typename... Exclude> template<typename... Get, typename... Exclude>
class basic_group<owned_t<>, get_t<Get...>, exclude_t<Exclude...>> { class basic_group<owned_t<>, get_t<Get...>, exclude_t<Exclude...>> {
using underlying_type = std::common_type_t<typename Get::entity_type..., typename Exclude::entity_type...>; using base_type = std::common_type_t<typename Get::base_type..., typename Exclude::base_type...>;
using basic_common_type = std::common_type_t<typename Get::base_type..., typename Exclude::base_type...>; using underlying_type = typename base_type::entity_type;
template<typename Type> template<typename Type>
static constexpr std::size_t index_of = type_list_index_v<std::remove_const_t<Type>, type_list<typename Get::value_type...>>; static constexpr std::size_t index_of = type_list_index_v<std::remove_const_t<Type>, type_list<typename Get::value_type..., typename Exclude::value_type...>>;
auto pools() const noexcept {
using return_type = std::tuple<Get *...>;
return descriptor ? descriptor->template pools_as<return_type>() : return_type{};
}
auto filter() const noexcept {
using return_type = std::tuple<Exclude *...>;
return descriptor ? descriptor->template filter_as<return_type>() : return_type{};
}
public: public:
/*! @brief Underlying entity identifier. */ /*! @brief Underlying entity identifier. */
@ -138,53 +312,59 @@ public:
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
using size_type = std::size_t; using size_type = std::size_t;
/*! @brief Common type among all storage types. */ /*! @brief Common type among all storage types. */
using base_type = basic_common_type; using common_type = base_type;
/*! @brief Random access iterator type. */ /*! @brief Random access iterator type. */
using iterator = typename base_type::iterator; using iterator = typename common_type::iterator;
/*! @brief Reversed iterator type. */ /*! @brief Reversed iterator type. */
using reverse_iterator = typename base_type::reverse_iterator; using reverse_iterator = typename common_type::reverse_iterator;
/*! @brief Iterable group type. */ /*! @brief Iterable group type. */
using iterable = iterable_adaptor<internal::extended_group_iterator<iterator, owned_t<>, get_t<Get...>>>; using iterable = iterable_adaptor<internal::extended_group_iterator<iterator, owned_t<>, get_t<Get...>>>;
/*! @brief Group handler type. */
using handler = internal::group_handler<owned_t<>, get_t<std::remove_const_t<Get>...>, exclude_t<std::remove_const_t<Exclude>...>>;
/*! @brief Default constructor to use to create empty, invalid groups. */ /*! @brief Default constructor to use to create empty, invalid groups. */
basic_group() noexcept basic_group() noexcept
: handler{} {} : descriptor{} {}
/** /**
* @brief Constructs a group from a set of storage classes. * @brief Constructs a group from a set of storage classes.
* @param ref The actual entities to iterate. * @param ref A reference to a group handler.
* @param gpool Storage types to iterate _observed_ by the group.
*/ */
basic_group(basic_common_type &ref, Get &...gpool) noexcept basic_group(handler &ref) noexcept
: handler{&ref}, : descriptor{&ref} {}
pools{&gpool...} {}
/** /**
* @brief Returns a const reference to the underlying handler. * @brief Returns the leading storage of a group.
* @return A const reference to the underlying handler. * @return The leading storage of the group.
*/ */
[[nodiscard]] const base_type &handle() const noexcept { [[nodiscard]] const common_type &handle() const noexcept {
return *handler; 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. * @tparam Type Type of component of which to return the storage.
* @return The storage for the given component type. * @return The storage for the given component type.
*/ */
template<typename Type> template<typename Type>
[[nodiscard]] decltype(auto) storage() const noexcept { [[nodiscard]] auto *storage() const noexcept {
return storage<index_of<Type>>(); return storage<index_of<Type>>();
} }
/** /**
* @brief Returns the storage for a given index. * @brief Returns the storage for a given index, if any.
* @tparam Index Index of the storage to return. * @tparam Index Index of the storage to return.
* @return The storage for the given index. * @return The storage for the given index.
*/ */
template<std::size_t Index> template<std::size_t Index>
[[nodiscard]] decltype(auto) storage() const noexcept { [[nodiscard]] auto *storage() const noexcept {
return *std::get<Index>(pools); constexpr auto offset = sizeof...(Get);
if constexpr(Index < offset) {
return std::get<Index>(pools());
} else {
return std::get<Index - offset>(filter());
}
} }
/** /**
@ -192,7 +372,7 @@ public:
* @return Number of entities that are part of the group. * @return Number of entities that are part of the group.
*/ */
[[nodiscard]] size_type size() const noexcept { [[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. * @return Capacity of the group.
*/ */
[[nodiscard]] size_type capacity() const noexcept { [[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. */ /*! @brief Requests the removal of unused capacity. */
void shrink_to_fit() { void shrink_to_fit() {
if(*this) { 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. * @return True if the group is empty, false otherwise.
*/ */
[[nodiscard]] bool empty() const noexcept { [[nodiscard]] bool empty() const noexcept {
return !*this || handler->empty(); return !*this || handle().empty();
} }
/** /**
* @brief Returns an iterator to the first entity of the group. * @brief Returns an iterator to the first entity of the group.
* *
* The returned iterator points to the first entity of the group. If the * If the group is empty, the returned iterator will be equal to `end()`.
* group is empty, the returned iterator will be equal to `end()`.
* *
* @return An iterator to the first entity of the group. * @return An iterator to the first entity of the group.
*/ */
[[nodiscard]] iterator begin() const noexcept { [[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. * @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 * @return An iterator to the entity following the last entity of the
* group. * group.
*/ */
[[nodiscard]] iterator end() const noexcept { [[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. * @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()`. * If the group is empty, the returned iterator will be equal to `rend()`.
* *
* @return An iterator to the first entity of the reversed group. * @return An iterator to the first entity of the reversed group.
*/ */
[[nodiscard]] reverse_iterator rbegin() const noexcept { [[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 * @brief Returns an iterator that is past the last entity of the reversed
* group. * 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 * @return An iterator to the entity following the last entity of the
* reversed group. * reversed group.
*/ */
[[nodiscard]] reverse_iterator rend() const noexcept { [[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. * iterator otherwise.
*/ */
[[nodiscard]] iterator find(const entity_type entt) const noexcept { [[nodiscard]] iterator find(const entity_type entt) const noexcept {
const auto it = *this ? handler->find(entt) : iterator{}; return *this ? handle().find(entt) : iterator{};
return it != end() && *it == entt ? it : end();
} }
/** /**
@ -317,7 +484,7 @@ public:
* @return True if the group is properly initialized, false otherwise. * @return True if the group is properly initialized, false otherwise.
*/ */
[[nodiscard]] explicit operator bool() const noexcept { [[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. * @return True if the group contains the given entity, false otherwise.
*/ */
[[nodiscard]] bool contains(const entity_type entt) const noexcept { [[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. * @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 * @warning
* Attempting to use an invalid component type results in a compilation * Attempting to use an entity that doesn't belong to the group results in
* error. Attempting to use an entity that doesn't belong to the group * undefined behavior.
* 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. * @param entt A valid identifier.
* @return The components assigned to the entity. * @return The components assigned to the entity.
*/ */
template<typename... Type> template<typename Type, typename... Other>
[[nodiscard]] decltype(auto) get(const entity_type entt) const { [[nodiscard]] decltype(auto) get(const entity_type entt) const {
if constexpr(sizeof...(Type) == 0) { return get<index_of<Type>, index_of<Other>...>(entt);
return std::apply([entt](auto *...curr) { return std::tuple_cat(curr->get_as_tuple(entt)...); }, pools); }
} else if constexpr(sizeof...(Type) == 1) {
return (std::get<index_of<Type>>(pools)->get(entt), ...); /**
* @brief Returns the components assigned to the given entity.
*
* @warning
* Attempting to use an entity that doesn't belong to the groups results in
* undefined behavior.
*
* @tparam Index Indexes of the components to get.
* @param entt A valid identifier.
* @return The components assigned to the entity.
*/
template<std::size_t... Index>
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
const auto cpools = pools();
if constexpr(sizeof...(Index) == 0) {
return std::apply([entt](auto *...curr) { return std::tuple_cat(curr->get_as_tuple(entt)...); }, cpools);
} else if constexpr(sizeof...(Index) == 1) {
return (std::get<Index>(cpools)->get(entt), ...);
} else { } else {
return std::tuple_cat(std::get<index_of<Type>>(pools)->get_as_tuple(entt)...); return std::tuple_cat(std::get<Index>(cpools)->get_as_tuple(entt)...);
} }
} }
@ -402,16 +584,13 @@ public:
* @return An iterable object to use to _visit_ the group. * @return An iterable object to use to _visit_ the group.
*/ */
[[nodiscard]] iterable each() const noexcept { [[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. * @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 * The comparison function object must return `true` if the first element
* is _less_ than the second one, `false` otherwise. The signature of the * is _less_ than the second one, `false` otherwise. The signature of the
* comparison function should be equivalent to one of the following: * 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. * * An iterator past the last element of the range to sort.
* * A comparison function to use to compare the elements. * * 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 Compare Type of comparison function object.
* @tparam Sort Type of sort function object. * @tparam Sort Type of sort function object.
* @tparam Args Types of arguments to forward to the 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 algo A valid sort function object.
* @param args Arguments to forward to the sort function object, if any. * @param args Arguments to forward to the sort function object, if any.
*/ */
template<typename... Type, typename Compare, typename Sort = std_sort, typename... Args> template<typename Type, typename... Other, typename Compare, typename Sort = std_sort, typename... Args>
void sort(Compare compare, Sort algo = Sort{}, Args &&...args) {
sort<index_of<Type>, index_of<Other>...>(std::move(compare), std::move(algo), std::forward<Args>(args)...);
}
/**
* @brief Sort a group according to the given comparison function.
*
* @sa sort
*
* @tparam Index Optional indexes of components to compare.
* @tparam Compare Type of comparison function object.
* @tparam Sort Type of sort function object.
* @tparam Args Types of arguments to forward to the sort function object.
* @param compare A valid comparison function object.
* @param algo A valid sort function object.
* @param args Arguments to forward to the sort function object, if any.
*/
template<std::size_t... Index, typename Compare, typename Sort = std_sort, typename... Args>
void sort(Compare compare, Sort algo = Sort{}, Args &&...args) { void sort(Compare compare, Sort algo = Sort{}, Args &&...args) {
if(*this) { if(*this) {
if constexpr(sizeof...(Type) == 0) { if constexpr(sizeof...(Index) == 0) {
static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>, "Invalid comparison function"); static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>, "Invalid comparison function");
handler->sort(std::move(compare), std::move(algo), std::forward<Args>(args)...); descriptor->handle().sort(std::move(compare), std::move(algo), std::forward<Args>(args)...);
} else { } else {
auto comp = [this, &compare](const entity_type lhs, const entity_type rhs) { auto comp = [&compare, cpools = pools()](const entity_type lhs, const entity_type rhs) {
if constexpr(sizeof...(Type) == 1) { if constexpr(sizeof...(Index) == 1) {
return compare((std::get<index_of<Type>>(pools)->get(lhs), ...), (std::get<index_of<Type>>(pools)->get(rhs), ...)); return compare((std::get<Index>(cpools)->get(lhs), ...), (std::get<Index>(cpools)->get(rhs), ...));
} else { } else {
return compare(std::forward_as_tuple(std::get<index_of<Type>>(pools)->get(lhs)...), std::forward_as_tuple(std::get<index_of<Type>>(pools)->get(rhs)...)); return compare(std::forward_as_tuple(std::get<Index>(cpools)->get(lhs)...), std::forward_as_tuple(std::get<Index>(cpools)->get(rhs)...));
} }
}; };
handler->sort(std::move(comp), std::move(algo), std::forward<Args>(args)...); descriptor->handle().sort(std::move(comp), std::move(algo), std::forward<Args>(args)...);
} }
} }
} }
/** /**
* @brief Sort the shared pool of entities according to the given component. * @brief Sort the shared pool of entities according to a given storage.
* *
* Non-owning groups of the same type share with the registry a pool of
* entities with its own order that doesn't depend on the order of any pool
* of components. Users can order the underlying data structure so that it
* respects the order of the pool of the given component.
*
* @note
* The shared pool of entities and thus its order is affected by the changes * 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 * to each and every pool that it tracks.
* 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. * @param other The storage to use to impose the order.
*/ */
template<typename Type> void sort_as(const common_type &other) const {
void sort() const {
if(*this) { if(*this) {
handler->respect(*std::get<index_of<Type>>(pools)); descriptor->handle().sort_as(other);
} }
} }
private: private:
base_type *const handler; handler *descriptor;
const std::tuple<Get *...> pools;
}; };
/** /**
@ -514,7 +702,7 @@ private:
* * The entity currently pointed is destroyed. * * The entity currently pointed is destroyed.
* *
* In all other cases, modifying the pools iterated by the group in any way * 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 Owned Types of storage _owned_ by the group.
* @tparam Get Types of storage _observed_ by the group. * @tparam Get Types of storage _observed_ by the group.
@ -522,11 +710,21 @@ private:
*/ */
template<typename... Owned, typename... Get, typename... Exclude> template<typename... Owned, typename... Get, typename... Exclude>
class basic_group<owned_t<Owned...>, get_t<Get...>, exclude_t<Exclude...>> { class basic_group<owned_t<Owned...>, get_t<Get...>, exclude_t<Exclude...>> {
using underlying_type = std::common_type_t<typename Owned::entity_type..., typename Get::entity_type..., typename Exclude::entity_type...>; using base_type = std::common_type_t<typename Owned::base_type..., typename Get::base_type..., typename Exclude::base_type...>;
using basic_common_type = std::common_type_t<typename Owned::base_type..., typename Get::base_type..., typename Exclude::base_type...>; using underlying_type = typename base_type::entity_type;
template<typename Type> template<typename Type>
static constexpr std::size_t index_of = type_list_index_v<std::remove_const_t<Type>, type_list<typename Owned::value_type..., typename Get::value_type...>>; static constexpr std::size_t index_of = type_list_index_v<std::remove_const_t<Type>, type_list<typename Owned::value_type..., typename Get::value_type..., typename Exclude::value_type...>>;
auto pools() const noexcept {
using return_type = std::tuple<Owned *..., Get *...>;
return descriptor ? descriptor->template pools_as<return_type>() : return_type{};
}
auto filter() const noexcept {
using return_type = std::tuple<Exclude *...>;
return descriptor ? descriptor->template filter_as<return_type>() : return_type{};
}
public: public:
/*! @brief Underlying entity identifier. */ /*! @brief Underlying entity identifier. */
@ -534,46 +732,59 @@ public:
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
using size_type = std::size_t; using size_type = std::size_t;
/*! @brief Common type among all storage types. */ /*! @brief Common type among all storage types. */
using base_type = basic_common_type; using common_type = base_type;
/*! @brief Random access iterator type. */ /*! @brief Random access iterator type. */
using iterator = typename base_type::iterator; using iterator = typename common_type::iterator;
/*! @brief Reversed iterator type. */ /*! @brief Reversed iterator type. */
using reverse_iterator = typename base_type::reverse_iterator; using reverse_iterator = typename common_type::reverse_iterator;
/*! @brief Iterable group type. */ /*! @brief Iterable group type. */
using iterable = iterable_adaptor<internal::extended_group_iterator<iterator, owned_t<Owned...>, get_t<Get...>>>; using iterable = iterable_adaptor<internal::extended_group_iterator<iterator, owned_t<Owned...>, get_t<Get...>>>;
/*! @brief Group handler type. */
using handler = internal::group_handler<owned_t<std::remove_const_t<Owned>...>, get_t<std::remove_const_t<Get>...>, exclude_t<std::remove_const_t<Exclude>...>>;
/*! @brief Default constructor to use to create empty, invalid groups. */ /*! @brief Default constructor to use to create empty, invalid groups. */
basic_group() noexcept basic_group() noexcept
: length{} {} : descriptor{} {}
/** /**
* @brief Constructs a group from a set of storage classes. * @brief Constructs a group from a set of storage classes.
* @param extent The actual number of entities to iterate. * @param ref A reference to a group handler.
* @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 basic_group(handler &ref) noexcept
: pools{&opool..., &gpool...}, : descriptor{&ref} {}
length{&extent} {}
/** /**
* @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. * @tparam Type Type of component of which to return the storage.
* @return The storage for the given component type. * @return The storage for the given component type.
*/ */
template<typename Type> template<typename Type>
[[nodiscard]] decltype(auto) storage() const noexcept { [[nodiscard]] auto *storage() const noexcept {
return storage<index_of<Type>>(); return storage<index_of<Type>>();
} }
/** /**
* @brief Returns the storage for a given index. * @brief Returns the storage for a given index, if any.
* @tparam Index Index of the storage to return. * @tparam Index Index of the storage to return.
* @return The storage for the given index. * @return The storage for the given index.
*/ */
template<std::size_t Index> template<std::size_t Index>
[[nodiscard]] decltype(auto) storage() const noexcept { [[nodiscard]] auto *storage() const noexcept {
return *std::get<Index>(pools); constexpr auto offset = sizeof...(Owned) + sizeof...(Get);
if constexpr(Index < offset) {
return std::get<Index>(pools());
} else {
return std::get<Index - offset>(filter());
}
} }
/** /**
@ -581,7 +792,7 @@ public:
* @return Number of entities that that are part of the group. * @return Number of entities that that are part of the group.
*/ */
[[nodiscard]] size_type size() const noexcept { [[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. * @return True if the group is empty, false otherwise.
*/ */
[[nodiscard]] bool empty() const noexcept { [[nodiscard]] bool empty() const noexcept {
return !*this || !*length; return !*this || !descriptor->length();
} }
/** /**
* @brief Returns an iterator to the first entity of the group. * @brief Returns an iterator to the first entity of the group.
* *
* The returned iterator points to the first entity of the group. If the * If the group is empty, the returned iterator will be equal to `end()`.
* group is empty, the returned iterator will be equal to `end()`.
* *
* @return An iterator to the first entity of the group. * @return An iterator to the first entity of the group.
*/ */
[[nodiscard]] iterator begin() const noexcept { [[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. * @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 * @return An iterator to the entity following the last entity of the
* group. * group.
*/ */
[[nodiscard]] iterator end() const noexcept { [[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. * @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()`. * If the group is empty, the returned iterator will be equal to `rend()`.
* *
* @return An iterator to the first entity of the reversed group. * @return An iterator to the first entity of the reversed group.
*/ */
[[nodiscard]] reverse_iterator rbegin() const noexcept { [[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 * @brief Returns an iterator that is past the last entity of the reversed
* group. * 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 * @return An iterator to the entity following the last entity of the
* reversed group. * reversed group.
*/ */
[[nodiscard]] reverse_iterator rend() const noexcept { [[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. * iterator otherwise.
*/ */
[[nodiscard]] iterator find(const entity_type entt) const noexcept { [[nodiscard]] iterator find(const entity_type entt) const noexcept {
const auto it = *this ? std::get<0>(pools)->find(entt) : iterator{}; const auto it = *this ? handle().find(entt) : iterator{};
return it != end() && it >= begin() && *it == entt ? it : end(); return it >= begin() ? it : iterator{};
} }
/** /**
@ -690,7 +889,7 @@ public:
* @return True if the group is properly initialized, false otherwise. * @return True if the group is properly initialized, false otherwise.
*/ */
[[nodiscard]] explicit operator bool() const noexcept { [[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. * @return True if the group contains the given entity, false otherwise.
*/ */
[[nodiscard]] bool contains(const entity_type entt) const noexcept { [[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. * @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 * @warning
* Attempting to use an invalid component type results in a compilation * Attempting to use an entity that doesn't belong to the group results in
* error. Attempting to use an entity that doesn't belong to the group * undefined behavior.
* 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. * @param entt A valid identifier.
* @return The components assigned to the entity. * @return The components assigned to the entity.
*/ */
template<typename... Type> template<typename Type, typename... Other>
[[nodiscard]] decltype(auto) get(const entity_type entt) const { [[nodiscard]] decltype(auto) get(const entity_type entt) const {
if constexpr(sizeof...(Type) == 0) { return get<index_of<Type>, index_of<Other>...>(entt);
return std::apply([entt](auto *...curr) { return std::tuple_cat(curr->get_as_tuple(entt)...); }, pools); }
} else if constexpr(sizeof...(Type) == 1) {
return (std::get<index_of<Type>>(pools)->get(entt), ...); /**
* @brief Returns the components assigned to the given entity.
*
* @warning
* Attempting to use an entity that doesn't belong to the groups results in
* undefined behavior.
*
* @tparam Index Indexes of the components to get.
* @param entt A valid identifier.
* @return The components assigned to the entity.
*/
template<std::size_t... Index>
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
const auto cpools = pools();
if constexpr(sizeof...(Index) == 0) {
return std::apply([entt](auto *...curr) { return std::tuple_cat(curr->get_as_tuple(entt)...); }, cpools);
} else if constexpr(sizeof...(Index) == 1) {
return (std::get<Index>(cpools)->get(entt), ...);
} else { } else {
return std::tuple_cat(std::get<index_of<Type>>(pools)->get_as_tuple(entt)...); return std::tuple_cat(std::get<Index>(cpools)->get_as_tuple(entt)...);
} }
} }
@ -775,16 +989,13 @@ public:
* @return An iterable object to use to _visit_ the group. * @return An iterable object to use to _visit_ the group.
*/ */
[[nodiscard]] iterable each() const noexcept { [[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. * @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 * The comparison function object must return `true` if the first element
* is _less_ than the second one, `false` otherwise. The signature of the * is _less_ than the second one, `false` otherwise. The signature of the
* comparison function should be equivalent to one of the following: * 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. * * An iterator past the last element of the range to sort.
* * A comparison function to use to compare the elements. * * 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 Compare Type of comparison function object.
* @tparam Sort Type of sort function object. * @tparam Sort Type of sort function object.
* @tparam Args Types of arguments to forward to the 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 algo A valid sort function object.
* @param args Arguments to forward to the sort function object, if any. * @param args Arguments to forward to the sort function object, if any.
*/ */
template<typename... Type, typename Compare, typename Sort = std_sort, typename... Args> template<typename Type, typename... Other, typename Compare, typename Sort = std_sort, typename... Args>
void sort(Compare compare, Sort algo = Sort{}, Args &&...args) const { void sort(Compare compare, Sort algo = Sort{}, Args &&...args) const {
if constexpr(sizeof...(Type) == 0) { sort<index_of<Type>, index_of<Other>...>(std::move(compare), std::move(algo), std::forward<Args>(args)...);
}
/**
* @brief Sort a group according to the given comparison function.
*
* @sa sort
*
* @tparam Index Optional indexes of components to compare.
* @tparam Compare Type of comparison function object.
* @tparam Sort Type of sort function object.
* @tparam Args Types of arguments to forward to the sort function object.
* @param compare A valid comparison function object.
* @param algo A valid sort function object.
* @param args Arguments to forward to the sort function object, if any.
*/
template<std::size_t... Index, typename Compare, typename Sort = std_sort, typename... Args>
void sort(Compare compare, Sort algo = Sort{}, Args &&...args) const {
const auto cpools = pools();
if constexpr(sizeof...(Index) == 0) {
static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>, "Invalid comparison function"); static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>, "Invalid comparison function");
std::get<0>(pools)->sort_n(*length, std::move(compare), std::move(algo), std::forward<Args>(args)...); storage<0>()->sort_n(descriptor->length(), std::move(compare), std::move(algo), std::forward<Args>(args)...);
} else { } else {
auto comp = [this, &compare](const entity_type lhs, const entity_type rhs) { auto comp = [&compare, &cpools](const entity_type lhs, const entity_type rhs) {
if constexpr(sizeof...(Type) == 1) { if constexpr(sizeof...(Index) == 1) {
return compare((std::get<index_of<Type>>(pools)->get(lhs), ...), (std::get<index_of<Type>>(pools)->get(rhs), ...)); return compare((std::get<Index>(cpools)->get(lhs), ...), (std::get<Index>(cpools)->get(rhs), ...));
} else { } else {
return compare(std::forward_as_tuple(std::get<index_of<Type>>(pools)->get(lhs)...), std::forward_as_tuple(std::get<index_of<Type>>(pools)->get(rhs)...)); return compare(std::forward_as_tuple(std::get<Index>(cpools)->get(lhs)...), std::forward_as_tuple(std::get<Index>(cpools)->get(rhs)...));
} }
}; };
std::get<0>(pools)->sort_n(*length, std::move(comp), std::move(algo), std::forward<Args>(args)...); storage<0>()->sort_n(descriptor->length(), std::move(comp), std::move(algo), std::forward<Args>(args)...);
} }
std::apply([this](auto *head, auto *...other) { auto cb = [this](auto *head, auto *...other) {
for(auto next = *length; next; --next) { for(auto next = descriptor->length(); next; --next) {
const auto pos = next - 1; const auto pos = next - 1;
[[maybe_unused]] const auto entt = head->data()[pos]; [[maybe_unused]] const auto entt = head->data()[pos];
(other->swap_elements(other->data()[pos], entt), ...); (other->swap_elements(other->data()[pos], entt), ...);
} }
}, };
pools);
std::apply(cb, cpools);
} }
private: private:
const std::tuple<Owned *..., Get *...> pools; handler *descriptor;
const size_type *const length;
}; };
} // namespace entt } // namespace entt

View File

@ -43,7 +43,9 @@ public:
: entt{value}, : entt{value},
it{from}, it{from},
last{to} { last{to} {
while(it != last && !it->second.contains(entt)) { ++it; } while(it != last && !it->second.contains(entt)) {
++it;
}
} }
constexpr handle_storage_iterator &operator++() noexcept { constexpr handle_storage_iterator &operator++() noexcept {
@ -141,7 +143,7 @@ struct basic_handle {
/** /**
* @brief Constructs a const handle from a non-const one. * @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. * @tparam Args Scope of the handle to construct.
* @return A const handle referring to the same registry and the same * @return A const handle referring to the same registry and the same
* entity. * entity.
@ -196,7 +198,7 @@ struct basic_handle {
/*! @brief Destroys the entity associated with a handle. */ /*! @brief Destroys the entity associated with a handle. */
void destroy() { 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. * @param version A desired version upon destruction.
*/ */
void destroy(const version_type version) { void destroy(const version_type version) {
reg->destroy(entt, version); reg->destroy(std::exchange(entt, null), version);
} }
/** /**

View File

@ -3,10 +3,10 @@
#include <memory> #include <memory>
#include <type_traits> #include <type_traits>
#include <utility>
#include "../core/fwd.hpp" #include "../core/fwd.hpp"
#include "../core/type_traits.hpp" #include "../core/type_traits.hpp"
#include "../signal/delegate.hpp" #include "../signal/delegate.hpp"
#include "component.hpp"
#include "fwd.hpp" #include "fwd.hpp"
#include "group.hpp" #include "group.hpp"
#include "view.hpp" #include "view.hpp"
@ -28,7 +28,7 @@ public:
/*! @brief Type of registry to convert. */ /*! @brief Type of registry to convert. */
using registry_type = Registry; using registry_type = Registry;
/*! @brief Underlying entity identifier. */ /*! @brief Underlying entity identifier. */
using entity_type = std::remove_const_t<typename registry_type::entity_type>; using entity_type = typename registry_type::entity_type;
/** /**
* @brief Constructs a converter for a given registry. * @brief Constructs a converter for a given registry.
@ -71,7 +71,7 @@ public:
/*! @brief Type of registry to convert. */ /*! @brief Type of registry to convert. */
using registry_type = Registry; using registry_type = Registry;
/*! @brief Underlying entity identifier. */ /*! @brief Underlying entity identifier. */
using entity_type = std::remove_const_t<typename registry_type::entity_type>; using entity_type = typename registry_type::entity_type;
/** /**
* @brief Constructs a converter for a given registry. * @brief Constructs a converter for a given registry.
@ -126,19 +126,133 @@ void invoke(Registry &reg, const typename Registry::entity_type entt) {
*/ */
template<typename Registry, typename Component> template<typename Registry, typename Component>
typename Registry::entity_type to_entity(const Registry &reg, const Component &instance) { typename Registry::entity_type to_entity(const Registry &reg, const Component &instance) {
const auto &storage = reg.template storage<Component>(); if(const auto *storage = reg.template storage<Component>(); storage) {
const typename Registry::base_type &base = storage; constexpr auto page_size = std::remove_const_t<std::remove_pointer_t<decltype(storage)>>::traits_type::page_size;
const typename Registry::common_type &base = *storage;
const auto *addr = std::addressof(instance); const auto *addr = std::addressof(instance);
for(auto it = base.rbegin(), last = base.rend(); it < last; it += component_traits<Component>::page_size) { for(auto it = base.rbegin(), last = base.rend(); it < last; it += page_size) {
if(const auto dist = (addr - std::addressof(storage.get(*it))); dist >= 0 && dist < static_cast<decltype(dist)>(component_traits<Component>::page_size)) { if(const auto dist = (addr - std::addressof(storage->get(*it))); dist >= 0 && dist < static_cast<decltype(dist)>(page_size)) {
return *(it + dist); return *(it + dist);
} }
} }
}
return null; return null;
} }
/*! @brief Primary template isn't defined on purpose. */
template<typename...>
struct sigh_helper;
/**
* @brief Signal connection helper for registries.
* @tparam Registry Basic registry type.
*/
template<typename Registry>
struct sigh_helper<Registry> {
/*! @brief Registry type. */
using registry_type = Registry;
/**
* @brief Constructs a helper for a given registry.
* @param ref A valid reference to a registry.
*/
sigh_helper(registry_type &ref)
: bucket{&ref} {}
/**
* @brief Binds a properly initialized helper to a given signal type.
* @tparam Type Type of signal to bind the helper to.
* @param id Optional name for the underlying storage to use.
* @return A helper for a given registry and signal type.
*/
template<typename Type>
auto with(const id_type id = type_hash<Type>::value()) noexcept {
return sigh_helper<registry_type, Type>{*bucket, id};
}
/**
* @brief Returns a reference to the underlying registry.
* @return A reference to the underlying registry.
*/
[[nodiscard]] registry_type &registry() noexcept {
return *bucket;
}
private:
registry_type *bucket;
};
/**
* @brief Signal connection helper for registries.
* @tparam Registry Basic registry type.
* @tparam Type Type of signal to connect listeners to.
*/
template<typename Registry, typename Type>
struct sigh_helper<Registry, Type> final: sigh_helper<Registry> {
/*! @brief Registry type. */
using registry_type = Registry;
/**
* @brief Constructs a helper for a given registry.
* @param ref A valid reference to a registry.
* @param id Optional name for the underlying storage to use.
*/
sigh_helper(registry_type &ref, const id_type id = type_hash<Type>::value())
: sigh_helper<Registry>{ref},
name{id} {}
/**
* @brief Forwards the call to `on_construct` on the underlying storage.
* @tparam Candidate Function or member to connect.
* @tparam Args Type of class or type of payload, if any.
* @param args A valid object that fits the purpose, if any.
* @return This helper.
*/
template<auto Candidate, typename... Args>
auto on_construct(Args &&...args) {
this->registry().template on_construct<Type>(name).template connect<Candidate>(std::forward<Args>(args)...);
return *this;
}
/**
* @brief Forwards the call to `on_update` on the underlying storage.
* @tparam Candidate Function or member to connect.
* @tparam Args Type of class or type of payload, if any.
* @param args A valid object that fits the purpose, if any.
* @return This helper.
*/
template<auto Candidate, typename... Args>
auto on_update(Args &&...args) {
this->registry().template on_update<Type>(name).template connect<Candidate>(std::forward<Args>(args)...);
return *this;
}
/**
* @brief Forwards the call to `on_destroy` on the underlying storage.
* @tparam Candidate Function or member to connect.
* @tparam Args Type of class or type of payload, if any.
* @param args A valid object that fits the purpose, if any.
* @return This helper.
*/
template<auto Candidate, typename... Args>
auto on_destroy(Args &&...args) {
this->registry().template on_destroy<Type>(name).template connect<Candidate>(std::forward<Args>(args)...);
return *this;
}
private:
id_type name;
};
/**
* @brief Deduction guide.
* @tparam Registry Basic registry type.
*/
template<typename Registry>
sigh_helper(Registry &) -> sigh_helper<Registry>;
} // namespace entt } // namespace entt
#endif #endif

293
src/entt/entity/mixin.hpp Normal file
View File

@ -0,0 +1,293 @@
#ifndef ENTT_ENTITY_MIXIN_HPP
#define ENTT_ENTITY_MIXIN_HPP
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "../core/any.hpp"
#include "../signal/sigh.hpp"
#include "entity.hpp"
#include "fwd.hpp"
namespace entt {
/**
* @brief Mixin type used to add signal support to storage types.
*
* The function type of a listener is equivalent to:
*
* @code{.cpp}
* void(basic_registry<entity_type> &, entity_type);
* @endcode
*
* This applies to all signals made available.
*
* @tparam Type The type of the underlying storage.
*/
template<typename Type>
class sigh_mixin final: public Type {
using underlying_type = Type;
using basic_registry_type = basic_registry<typename underlying_type::entity_type, typename underlying_type::base_type::allocator_type>;
using sigh_type = sigh<void(basic_registry_type &, const typename underlying_type::entity_type), typename underlying_type::allocator_type>;
using underlying_iterator = typename underlying_type::base_type::basic_iterator;
basic_registry_type &owner_or_assert() const noexcept {
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
return *owner;
}
void pop(underlying_iterator first, underlying_iterator last) final {
if(auto &reg = 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 &reg = owner_or_assert(); !destruction.empty()) {
for(auto pos = underlying_type::each().begin().base().index(); !(pos < 0); --pos) {
if constexpr(underlying_type::traits_type::in_place_delete) {
if(const auto entt = underlying_type::operator[](static_cast<typename underlying_type::size_type>(pos)); entt != tombstone) {
destruction.publish(reg, entt);
}
} else {
destruction.publish(reg, underlying_type::operator[](static_cast<typename underlying_type::size_type>(pos)));
}
}
}
underlying_type::pop_all();
}
underlying_iterator try_emplace(const typename underlying_type::entity_type entt, const bool force_back, const void *value) final {
const auto it = underlying_type::try_emplace(entt, force_back, value);
if(auto &reg = owner_or_assert(); it != underlying_type::base_type::end()) {
construction.publish(reg, *it);
}
return it;
}
public:
/*! @brief Allocator type. */
using allocator_type = typename underlying_type::allocator_type;
/*! @brief Underlying entity identifier. */
using entity_type = typename underlying_type::entity_type;
/*! @brief Expected registry type. */
using registry_type = basic_registry_type;
/*! @brief Default constructor. */
sigh_mixin()
: sigh_mixin{allocator_type{}} {}
/**
* @brief Constructs an empty storage with a given allocator.
* @param allocator The allocator to use.
*/
explicit sigh_mixin(const allocator_type &allocator)
: underlying_type{allocator},
owner{},
construction{allocator},
destruction{allocator},
update{allocator} {}
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
sigh_mixin(sigh_mixin &&other) noexcept
: underlying_type{std::move(other)},
owner{other.owner},
construction{std::move(other.construction)},
destruction{std::move(other.destruction)},
update{std::move(other.update)} {}
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
sigh_mixin(sigh_mixin &&other, const allocator_type &allocator) noexcept
: underlying_type{std::move(other), allocator},
owner{other.owner},
construction{std::move(other.construction), allocator},
destruction{std::move(other.destruction), allocator},
update{std::move(other.update), allocator} {}
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This storage.
*/
sigh_mixin &operator=(sigh_mixin &&other) noexcept {
underlying_type::operator=(std::move(other));
owner = other.owner;
construction = std::move(other.construction);
destruction = std::move(other.destruction);
update = std::move(other.update);
return *this;
}
/**
* @brief Exchanges the contents with those of a given storage.
* @param other Storage to exchange the content with.
*/
void swap(sigh_mixin &other) {
using std::swap;
underlying_type::swap(other);
swap(owner, other.owner);
swap(construction, other.construction);
swap(destruction, other.destruction);
swap(update, other.update);
}
/**
* @brief Returns a sink object.
*
* The sink returned by this function can be used to receive notifications
* whenever a new instance is created and assigned to an entity.<br/>
* Listeners are invoked after the object has been assigned to the entity.
*
* @sa sink
*
* @return A temporary sink object.
*/
[[nodiscard]] auto on_construct() noexcept {
return sink{construction};
}
/**
* @brief Returns a sink object.
*
* The sink returned by this function can be used to receive notifications
* whenever an instance is explicitly updated.<br/>
* Listeners are invoked after the object has been updated.
*
* @sa sink
*
* @return A temporary sink object.
*/
[[nodiscard]] auto on_update() noexcept {
return sink{update};
}
/**
* @brief Returns a sink object.
*
* The sink returned by this function can be used to receive notifications
* whenever an instance is removed from an entity and thus destroyed.<br/>
* Listeners are invoked before the object has been removed from the entity.
*
* @sa sink
*
* @return A temporary sink object.
*/
[[nodiscard]] auto on_destroy() noexcept {
return sink{destruction};
}
/**
* @brief Emplace elements into a storage.
*
* The behavior of this operation depends on the underlying storage type
* (for example, components vs entities).<br/>
* Refer to the specific documentation for more details.
*
* @return A return value as returned by the underlying storage.
*/
auto emplace() {
const auto entt = underlying_type::emplace();
construction.publish(owner_or_assert(), entt);
return entt;
}
/**
* @brief Emplace elements into a storage.
*
* The behavior of this operation depends on the underlying storage type
* (for example, components vs entities).<br/>
* Refer to the specific documentation for more details.
*
* @tparam Args Types of arguments to forward to the underlying storage.
* @param hint A valid identifier.
* @param args Parameters to forward to the underlying storage.
* @return A return value as returned by the underlying storage.
*/
template<typename... Args>
decltype(auto) emplace(const entity_type hint, Args &&...args) {
if constexpr(std::is_same_v<typename underlying_type::value_type, typename underlying_type::entity_type>) {
const auto entt = underlying_type::emplace(hint, std::forward<Args>(args)...);
construction.publish(owner_or_assert(), entt);
return entt;
} else {
underlying_type::emplace(hint, std::forward<Args>(args)...);
construction.publish(owner_or_assert(), hint);
return this->get(hint);
}
}
/**
* @brief Patches the given instance for an entity.
* @tparam Func Types of the function objects to invoke.
* @param entt A valid identifier.
* @param func Valid function objects.
* @return A reference to the patched instance.
*/
template<typename... Func>
decltype(auto) patch(const entity_type entt, Func &&...func) {
underlying_type::patch(entt, std::forward<Func>(func)...);
update.publish(owner_or_assert(), entt);
return this->get(entt);
}
/**
* @brief Emplace elements into a storage.
*
* The behavior of this operation depends on the underlying storage type
* (for example, components vs entities).<br/>
* Refer to the specific documentation for more details.
*
* @tparam It Iterator type (as required by the underlying storage type).
* @tparam Args Types of arguments to forward to the underlying storage.
* @param first An iterator to the first element of the range.
* @param last An iterator past the last element of the range.
* @param args Parameters to use to forward to the underlying storage.
*/
template<typename It, typename... Args>
void insert(It first, It last, Args &&...args) {
underlying_type::insert(first, last, std::forward<Args>(args)...);
if(auto &reg = owner_or_assert(); !construction.empty()) {
for(; first != last; ++first) {
construction.publish(reg, *first);
}
}
}
/**
* @brief Forwards variables to derived classes, if any.
* @param value A variable wrapped in an opaque container.
*/
void bind(any value) noexcept final {
auto *reg = any_cast<basic_registry_type>(&value);
owner = reg ? reg : owner;
underlying_type::bind(std::move(value));
}
private:
basic_registry_type *owner;
sigh_type construction;
sigh_type destruction;
sigh_type update;
};
} // namespace entt
#endif

View File

@ -43,7 +43,7 @@ struct basic_collector<> {
* @return The updated collector. * @return The updated collector.
*/ */
template<typename... AllOf, typename... NoneOf> template<typename... AllOf, typename... NoneOf>
static constexpr auto group(exclude_t<NoneOf...> = {}) noexcept { static constexpr auto group(exclude_t<NoneOf...> = exclude_t{}) noexcept {
return basic_collector<matcher<type_list<>, type_list<>, type_list<NoneOf...>, AllOf...>>{}; return basic_collector<matcher<type_list<>, type_list<>, type_list<NoneOf...>, AllOf...>>{};
} }
@ -78,7 +78,7 @@ struct basic_collector<matcher<type_list<Reject...>, type_list<Require...>, Rule
* @return The updated collector. * @return The updated collector.
*/ */
template<typename... AllOf, typename... NoneOf> template<typename... AllOf, typename... NoneOf>
static constexpr auto group(exclude_t<NoneOf...> = {}) noexcept { static constexpr auto group(exclude_t<NoneOf...> = exclude_t{}) noexcept {
return basic_collector<matcher<type_list<>, type_list<>, type_list<NoneOf...>, AllOf...>, current_type, Other...>{}; return basic_collector<matcher<type_list<>, type_list<>, type_list<NoneOf...>, AllOf...>, current_type, Other...>{};
} }
@ -99,7 +99,7 @@ struct basic_collector<matcher<type_list<Reject...>, type_list<Require...>, Rule
* @return The updated collector. * @return The updated collector.
*/ */
template<typename... AllOf, typename... NoneOf> template<typename... AllOf, typename... NoneOf>
static constexpr auto where(exclude_t<NoneOf...> = {}) noexcept { static constexpr auto where(exclude_t<NoneOf...> = exclude_t{}) noexcept {
using extended_type = matcher<type_list<Reject..., NoneOf...>, type_list<Require..., AllOf...>, Rule...>; using extended_type = matcher<type_list<Reject..., NoneOf...>, type_list<Require..., AllOf...>, Rule...>;
return basic_collector<extended_type, Other...>{}; return basic_collector<extended_type, Other...>{};
} }
@ -146,8 +146,7 @@ inline constexpr basic_collector<> collector{};
* * The entity currently pointed is destroyed. * * The entity currently pointed is destroyed.
* *
* In all the other cases, modifying the pools of the given components in any * 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 * way invalidates all the iterators.
* behavior.
* *
* @warning * @warning
* Lifetime of an observer doesn't necessarily have to overcome that of the * Lifetime of an observer doesn't necessarily have to overcome that of the
@ -156,10 +155,12 @@ inline constexpr basic_collector<> collector{};
* pointers. * pointers.
* *
* @tparam Registry Basic registry type. * @tparam Registry Basic registry type.
* @tparam Mask Mask type.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/ */
template<typename Registry> template<typename Registry, typename Mask, typename Allocator>
class basic_observer: private basic_storage<std::uint32_t, typename Registry::entity_type> { class basic_observer: private basic_storage<Mask, typename Registry::entity_type, Allocator> {
using base_type = basic_storage<std::uint32_t, typename Registry::entity_type>; using base_type = basic_storage<Mask, typename Registry::entity_type, Allocator>;
template<typename> template<typename>
struct matcher_handler; struct matcher_handler;
@ -193,10 +194,10 @@ class basic_observer: private basic_storage<std::uint32_t, typename Registry::en
} }
static void disconnect(basic_observer &obs, Registry &reg) { static void disconnect(basic_observer &obs, Registry &reg) {
(reg.template on_destroy<Require>().disconnect(obs), ...); (reg.template on_destroy<Require>().disconnect(&obs), ...);
(reg.template on_construct<Reject>().disconnect(obs), ...); (reg.template on_construct<Reject>().disconnect(&obs), ...);
reg.template on_update<AnyOf>().disconnect(obs); reg.template on_update<AnyOf>().disconnect(&obs);
reg.template on_destroy<AnyOf>().disconnect(obs); reg.template on_destroy<AnyOf>().disconnect(&obs);
} }
}; };
@ -239,12 +240,12 @@ class basic_observer: private basic_storage<std::uint32_t, typename Registry::en
} }
static void disconnect(basic_observer &obs, Registry &reg) { static void disconnect(basic_observer &obs, Registry &reg) {
(reg.template on_destroy<Require>().disconnect(obs), ...); (reg.template on_destroy<Require>().disconnect(&obs), ...);
(reg.template on_construct<Reject>().disconnect(obs), ...); (reg.template on_construct<Reject>().disconnect(&obs), ...);
(reg.template on_construct<AllOf>().disconnect(obs), ...); (reg.template on_construct<AllOf>().disconnect(&obs), ...);
(reg.template on_destroy<NoneOf>().disconnect(obs), ...); (reg.template on_destroy<NoneOf>().disconnect(&obs), ...);
(reg.template on_destroy<AllOf>().disconnect(obs), ...); (reg.template on_destroy<AllOf>().disconnect(&obs), ...);
(reg.template on_construct<NoneOf>().disconnect(obs), ...); (reg.template on_construct<NoneOf>().disconnect(&obs), ...);
} }
}; };
@ -267,15 +268,26 @@ public:
using entity_type = typename registry_type::entity_type; using entity_type = typename registry_type::entity_type;
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
using size_type = std::size_t; using size_type = std::size_t;
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Random access iterator type. */ /*! @brief Random access iterator type. */
using iterator = typename registry_type::base_type::iterator; using iterator = typename registry_type::common_type::iterator;
/*! @brief Default constructor. */ /*! @brief Default constructor. */
basic_observer() 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. */ /*! @brief Default copy constructor, deleted on purpose. */
basic_observer(const basic_observer &) = delete; basic_observer(const basic_observer &) = delete;
/*! @brief Default move constructor, deleted on purpose. */ /*! @brief Default move constructor, deleted on purpose. */
basic_observer(basic_observer &&) = delete; basic_observer(basic_observer &&) = delete;
@ -283,16 +295,14 @@ public:
* @brief Creates an observer and connects it to a given registry. * @brief Creates an observer and connects it to a given registry.
* @tparam Matcher Types of matchers to use to initialize the observer. * @tparam Matcher Types of matchers to use to initialize the observer.
* @param reg A valid reference to a registry. * @param reg A valid reference to a registry.
* @param allocator The allocator to use.
*/ */
template<typename... Matcher> template<typename... Matcher>
basic_observer(registry_type &reg, basic_collector<Matcher...>) basic_observer(registry_type &reg, basic_collector<Matcher...>, const allocator_type &allocator = allocator_type{})
: basic_observer{} { : basic_observer{allocator} {
connect<Matcher...>(reg, std::index_sequence_for<Matcher...>{}); connect<Matcher...>(reg, std::index_sequence_for<Matcher...>{});
} }
/*! @brief Default destructor. */
~basic_observer() = default;
/** /**
* @brief Default copy assignment operator, deleted on purpose. * @brief Default copy assignment operator, deleted on purpose.
* @return This observer. * @return This observer.
@ -360,8 +370,7 @@ public:
/** /**
* @brief Returns an iterator to the first entity of the observer. * @brief Returns an iterator to the first entity of the observer.
* *
* The returned iterator points to the first entity of the observer. If the * If the observer is empty, the returned iterator will be equal to `end()`.
* container is empty, the returned iterator will be equal to `end()`.
* *
* @return An iterator to the first entity of the observer. * @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. * @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 * @return An iterator to the entity following the last entity of the
* observer. * observer.
*/ */

View File

@ -126,7 +126,7 @@ class basic_organizer final {
if constexpr(std::is_same_v<Type, Registry>) { if constexpr(std::is_same_v<Type, Registry>) {
return reg; return reg;
} else if constexpr(internal::is_view_v<Type>) { } else if constexpr(internal::is_view_v<Type>) {
return as_view{reg}; return static_cast<Type>(as_view{reg});
} else { } else {
return reg.ctx().template emplace<std::remove_reference_t<Type>>(); return reg.ctx().template emplace<std::remove_reference_t<Type>>();
} }

File diff suppressed because it is too large Load Diff

View File

@ -104,38 +104,22 @@ private:
/** /**
* @brief Generic runtime view. * @brief Generic runtime view.
* *
* Runtime views iterate over those entities that have at least all the given * Runtime views iterate over those entities that are at least in the given
* components in their bags. During initialization, a runtime view looks at the * storage. During initialization, a runtime view looks at the number of
* number of entities available for each component and picks up a reference to * entities available for each component and uses the smallest set in order to
* the smallest set of candidate entities in order to get a performance boost * get a performance boost when iterating.
* when iterate.<br/>
* Order of elements during iterations are highly dependent on the order of the
* underlying data structures. See sparse_set and its specializations for more
* details.
* *
* @b Important * @b Important
* *
* Iterators aren't invalidated if: * Iterators aren't invalidated if:
* *
* * New instances of the given components are created and assigned to entities. * * New elements are added to the storage.
* * The entity currently pointed is modified (as an example, if one of the * * The entity currently pointed is modified (for example, components are added
* given components is removed from the entity to which the iterator points). * or removed from it).
* * The entity currently pointed is destroyed. * * The entity currently pointed is destroyed.
* *
* In all the other cases, modifying the pools of the given components in any * In all other cases, modifying the storage iterated by the view in any way
* way invalidates all the iterators and using them results in undefined * invalidates all the iterators.
* 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.
* *
* @tparam Type Common base type. * @tparam Type Common base type.
* @tparam Allocator Type of allocator used to manage memory and elements. * @tparam Allocator Type of allocator used to manage memory and elements.
@ -154,9 +138,9 @@ public:
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
using size_type = std::size_t; using size_type = std::size_t;
/*! @brief Common type among all storage types. */ /*! @brief Common type among all storage types. */
using base_type = Type; using common_type = Type;
/*! @brief Bidirectional iterator type. */ /*! @brief Bidirectional iterator type. */
using iterator = internal::runtime_view_iterator<base_type>; using iterator = internal::runtime_view_iterator<common_type>;
/*! @brief Default constructor to use to create empty, invalid views. */ /*! @brief Default constructor to use to create empty, invalid views. */
basic_runtime_view() noexcept basic_runtime_view() noexcept
@ -235,7 +219,7 @@ public:
* @param base An opaque reference to a storage object. * @param base An opaque reference to a storage object.
* @return This runtime view. * @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())) { if(pools.empty() || !(base.size() < pools[0u]->size())) {
pools.push_back(&base); pools.push_back(&base);
} else { } else {
@ -250,7 +234,7 @@ public:
* @param base An opaque reference to a storage object. * @param base An opaque reference to a storage object.
* @return This runtime view. * @return This runtime view.
*/ */
basic_runtime_view &exclude(base_type &base) { basic_runtime_view &exclude(common_type &base) {
filter.push_back(&base); filter.push_back(&base);
return *this; return *this;
} }
@ -267,9 +251,7 @@ public:
* @brief Returns an iterator to the first entity that has the given * @brief Returns an iterator to the first entity that has the given
* components. * components.
* *
* The returned iterator points to the first entity that has the given * If the view is empty, the returned iterator will be equal to `end()`.
* components. 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. * @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 * @brief Returns an iterator that is past the last entity that has the
* given components. * 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 * @return An iterator to the entity following the last entity that has the
* given components. * given components.
*/ */
@ -307,8 +284,7 @@ public:
* @brief Iterates entities and applies the given function object to them. * @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 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 * the entity itself.<br/>
* which the view was built.<br/>
* The signature of the function should be equivalent to the following: * The signature of the function should be equivalent to the following:
* *
* @code{.cpp} * @code{.cpp}

View File

@ -1,7 +1,6 @@
#ifndef ENTT_ENTITY_SNAPSHOT_HPP #ifndef ENTT_ENTITY_SNAPSHOT_HPP
#define ENTT_ENTITY_SNAPSHOT_HPP #define ENTT_ENTITY_SNAPSHOT_HPP
#include <array>
#include <cstddef> #include <cstddef>
#include <iterator> #include <iterator>
#include <tuple> #include <tuple>
@ -11,13 +10,37 @@
#include "../config/config.h" #include "../config/config.h"
#include "../container/dense_map.hpp" #include "../container/dense_map.hpp"
#include "../core/type_traits.hpp" #include "../core/type_traits.hpp"
#include "component.hpp"
#include "entity.hpp" #include "entity.hpp"
#include "fwd.hpp" #include "fwd.hpp"
#include "view.hpp" #include "view.hpp"
namespace entt { namespace entt {
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
namespace internal {
template<typename Registry>
void orphans(Registry &registry) {
auto view = registry.template view<typename Registry::entity_type>();
for(auto entt: view) {
if(registry.orphan(entt)) {
view.storage()->erase(entt);
}
}
}
} // namespace internal
/**
* Internal details not to be documented.
* @endcond
*/
/** /**
* @brief Utility class to create snapshots from a registry. * @brief Utility class to create snapshots from a registry.
* *
@ -30,34 +53,8 @@ namespace entt {
*/ */
template<typename Registry> template<typename Registry>
class basic_snapshot { class basic_snapshot {
using entity_traits = entt_traits<typename Registry::entity_type>; static_assert(!std::is_const_v<Registry>, "Non-const registry type required");
using traits_type = typename Registry::traits_type;
template<typename Component, typename Archive, typename It>
void get(Archive &archive, std::size_t sz, It first, It last) const {
const auto view = reg->template view<const Component>();
archive(typename entity_traits::entity_type(sz));
while(first != last) {
const auto entt = *(first++);
if(reg->template all_of<Component>(entt)) {
std::apply(archive, std::tuple_cat(std::make_tuple(entt), view.get(entt)));
}
}
}
template<typename... Component, typename Archive, typename It, std::size_t... Index>
void component(Archive &archive, It first, It last, std::index_sequence<Index...>) const {
std::array<std::size_t, sizeof...(Index)> size{};
auto begin = first;
while(begin != last) {
const auto entt = *(begin++);
((reg->template all_of<Component>(entt) ? ++size[Index] : 0u), ...);
}
(get<Component>(archive, size[Index], first, last), ...);
}
public: public:
/*! Basic registry type. */ /*! Basic registry type. */
@ -79,58 +76,96 @@ public:
basic_snapshot &operator=(basic_snapshot &&) noexcept = default; basic_snapshot &operator=(basic_snapshot &&) noexcept = default;
/** /**
* @brief Puts aside all the entities from the underlying registry. * @brief Serializes all elements of a type with associated identifiers.
* * @tparam Type Type of elements to serialize.
* Entities are serialized along with their versions. Destroyed entities are
* taken in consideration as well by this function.
*
* @tparam Archive Type of output archive. * @tparam Archive Type of output archive.
* @param archive A valid reference to an 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. * @return An object of this type to continue creating the snapshot.
*/ */
template<typename Archive> template<typename Type, typename Archive>
const basic_snapshot &entities(Archive &archive) const { const basic_snapshot &get(Archive &archive, const id_type id = type_hash<Type>::value()) const {
const auto sz = reg->size(); if(const auto *storage = reg->template storage<Type>(id); storage) {
archive(static_cast<typename traits_type::entity_type>(storage->size()));
archive(typename entity_traits::entity_type(sz + 1u)); if constexpr(std::is_same_v<Type, entity_type>) {
archive(reg->released()); archive(static_cast<typename traits_type::entity_type>(storage->in_use()));
for(auto first = reg->data(), last = first + sz; first != last; ++first) { for(auto first = storage->data(), last = first + storage->size(); first != last; ++first) {
archive(*first); archive(*first);
} }
} else {
for(auto elem: storage->reach()) {
std::apply([&archive](auto &&...args) { (archive(std::forward<decltype(args)>(args)), ...); }, elem);
}
}
} else {
archive(typename traits_type::entity_type{});
}
return *this; return *this;
} }
/** /**
* @brief Puts aside the given components. * @brief Serializes all elements of a type with associated identifiers for
* * the entities in a range.
* Each instance is serialized together with the entity to which it belongs. * @tparam Type Type of elements to serialize.
* Entities are serialized along with their versions. * @tparam Archive Type of output archive.
* * @tparam It Type of input iterator.
* @param archive A valid reference to an output archive.
* @param first An iterator to the first element of the range to serialize.
* @param last An iterator past the last element of the range to serialize.
* @param id Optional name used to map the storage within the registry.
* @return An object of this type to continue creating the snapshot.
*/
template<typename Type, typename Archive, typename It>
const basic_snapshot &get(Archive &archive, It first, It last, const id_type id = type_hash<Type>::value()) const {
static_assert(!std::is_same_v<Type, entity_type>, "Entity types not supported");
if(const auto *storage = reg->template storage<Type>(id); storage && !storage->empty()) {
archive(static_cast<typename traits_type::entity_type>(std::distance(first, last)));
for(; first != last; ++first) {
if(const auto entt = *first; storage->contains(entt)) {
archive(entt);
std::apply([&archive](auto &&...args) { (archive(std::forward<decltype(args)>(args)), ...); }, storage->get_as_tuple(entt));
} else {
archive(static_cast<entity_type>(null));
}
}
} else {
archive(typename traits_type::entity_type{});
}
return *this;
}
/**
* @brief Serializes all identifiers, including those to be recycled.
* @tparam Archive Type of output archive.
* @param archive A valid reference to an output archive.
* @return An object of this type to continue creating the snapshot.
*/
template<typename Archive>
[[deprecated("use .get<Entity>(archive) instead")]] const basic_snapshot &entities(Archive &archive) const {
return get<entity_type>(archive);
}
/**
* @brief Serializes all elements of a type with associated identifiers.
* @tparam Component Types of components to serialize. * @tparam Component Types of components to serialize.
* @tparam Archive Type of output archive. * @tparam Archive Type of output archive.
* @param archive A valid reference to an output archive. * @param archive A valid reference to an output archive.
* @return An object of this type to continue creating the snapshot. * @return An object of this type to continue creating the snapshot.
*/ */
template<typename... Component, typename Archive> template<typename... Component, typename Archive>
const basic_snapshot &component(Archive &archive) const { [[deprecated("use .get<Type>(archive) instead")]] const basic_snapshot &component(Archive &archive) const {
if constexpr(sizeof...(Component) == 1u) { return (get<Component>(archive), ...);
const auto view = reg->template view<const Component...>();
(component<Component>(archive, view.rbegin(), view.rend()), ...);
return *this;
} else {
(component<Component>(archive), ...);
return *this;
}
} }
/** /**
* @brief Puts aside the given components for the entities in a range. * @brief Serializes all elements of a type with associated identifiers 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.
*
* @tparam Component Types of components to serialize. * @tparam Component Types of components to serialize.
* @tparam Archive Type of output archive. * @tparam Archive Type of output archive.
* @tparam It Type of input iterator. * @tparam It Type of input iterator.
@ -140,9 +175,8 @@ public:
* @return An object of this type to continue creating the snapshot. * @return An object of this type to continue creating the snapshot.
*/ */
template<typename... Component, typename Archive, typename It> template<typename... Component, typename Archive, typename It>
const basic_snapshot &component(Archive &archive, It first, It last) const { [[deprecated("use .get<Type>(archive, first, last) instead")]] const basic_snapshot &component(Archive &archive, It first, It last) const {
component<Component...>(archive, first, last, std::index_sequence_for<Component...>{}); return (get<Component>(archive, first, last), ...);
return *this;
} }
private: private:
@ -161,33 +195,8 @@ private:
*/ */
template<typename Registry> template<typename Registry>
class basic_snapshot_loader { class basic_snapshot_loader {
using entity_traits = entt_traits<typename Registry::entity_type>; static_assert(!std::is_const_v<Registry>, "Non-const registry type required");
using traits_type = typename Registry::traits_type;
template<typename Component, typename Archive>
void assign(Archive &archive) const {
typename entity_traits::entity_type length{};
entity_type entt;
archive(length);
if constexpr(ignore_as_empty_v<Component>) {
while(length--) {
archive(entt);
const auto entity = reg->valid(entt) ? entt : reg->create(entt);
ENTT_ASSERT(entity == entt, "Entity not available for use");
reg->template emplace<Component>(entt);
}
} else {
Component instance;
while(length--) {
archive(entt, instance);
const auto entity = reg->valid(entt) ? entt : reg->create(entt);
ENTT_ASSERT(entity == entt, "Entity not available for use");
reg->template emplace<Component>(entt, std::move(instance));
}
}
}
public: public:
/*! Basic registry type. */ /*! Basic registry type. */
@ -202,7 +211,9 @@ public:
basic_snapshot_loader(registry_type &source) noexcept basic_snapshot_loader(registry_type &source) noexcept
: reg{&source} { : reg{&source} {
// restoring a snapshot as a whole requires a clean registry // 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. */ /*! @brief Default move constructor. */
@ -212,48 +223,80 @@ public:
basic_snapshot_loader &operator=(basic_snapshot_loader &&) noexcept = default; basic_snapshot_loader &operator=(basic_snapshot_loader &&) noexcept = default;
/** /**
* @brief Restores entities that were in use during serialization. * @brief Restores all elements of a type with associated identifiers.
* * @tparam Type Type of elements to restore.
* This function restores the entities that were in use during serialization
* and gives them the versions they originally had.
*
* @tparam Archive Type of input archive. * @tparam Archive Type of input archive.
* @param archive A valid reference to an 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. * @return A valid loader to continue restoring data.
*/ */
template<typename Archive> template<typename Type, typename Archive>
const basic_snapshot_loader &entities(Archive &archive) const { basic_snapshot_loader &get([[maybe_unused]] Archive &archive, const id_type id = type_hash<Type>::value()) {
typename entity_traits::entity_type length{}; auto &storage = reg->template storage<Type>(id);
typename traits_type::entity_type length{};
archive(length); archive(length);
std::vector<entity_type> all(length);
for(std::size_t pos{}; pos < length; ++pos) { if constexpr(std::is_same_v<Type, entity_type>) {
archive(all[pos]); 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);
} }
reg->assign(++all.cbegin(), all.cend(), all[0u]); storage.in_use(in_use);
} else {
auto &other = reg->template storage<entity_type>();
entity_type entt{null};
while(length--) {
if(archive(entt); entt != null) {
const auto entity = other.contains(entt) ? entt : other.emplace(entt);
ENTT_ASSERT(entity == entt, "Entity not available for use");
if constexpr(Registry::template storage_for_type<Type>::traits_type::page_size == 0u) {
storage.emplace(entity);
} else {
Type elem{};
archive(elem);
storage.emplace(entity, std::move(elem));
}
}
}
}
return *this; return *this;
} }
/** /**
* @brief Restores components and assigns them to the right entities. * @brief Restores all identifiers, including those to be recycled.
* @tparam Archive Type of input archive.
* @param archive A valid reference to an input archive.
* @return A valid loader to continue restoring data.
*/
template<typename Archive>
[[deprecated("use .get<Entity>(archive) instead")]] basic_snapshot_loader &entities(Archive &archive) {
return get<entity_type>(archive);
}
/**
* @brief Restores all elements of a type with associated identifiers.
* *
* The template parameter list must be exactly the same used during * The template parameter list must be exactly the same used during
* serialization. In the event that the entity to which the component is * serialization.
* assigned doesn't exist yet, the loader will take care to create it with
* the version it originally had.
* *
* @tparam Component Types of components to restore. * @tparam Component Type of component to restore.
* @tparam Archive Type of input archive. * @tparam Archive Type of input archive.
* @param archive A valid reference to an input archive. * @param archive A valid reference to an input archive.
* @return A valid loader to continue restoring data. * @return A valid loader to continue restoring data.
*/ */
template<typename... Component, typename Archive> template<typename... Component, typename Archive>
const basic_snapshot_loader &component(Archive &archive) const { [[deprecated("use .get<Type>(archive) instead")]] basic_snapshot_loader &component(Archive &archive) {
(assign<Component>(archive), ...); return (get<Component>(archive), ...);
return *this;
} }
/** /**
@ -262,17 +305,12 @@ public:
* In case all the entities were serialized but only part of the components * 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 * was saved, it could happen that some of the entities have no components
* once restored.<br/> * once restored.<br/>
* This functions helps to identify and destroy those entities. * This function helps to identify and destroy those entities.
* *
* @return A valid loader to continue restoring data. * @return A valid loader to continue restoring data.
*/ */
const basic_snapshot_loader &orphans() const { basic_snapshot_loader &orphans() {
reg->each([this](const auto entt) { internal::orphans(*reg);
if(reg->orphan(entt)) {
reg->release(entt);
}
});
return *this; return *this;
} }
@ -290,7 +328,7 @@ private:
* Identifiers that entities originally had are not transferred to the target. * Identifiers that entities originally had are not transferred to the target.
* Instead, the loader maps remote identifiers to local ones while restoring a * Instead, the loader maps remote identifiers to local ones while restoring a
* snapshot.<br/> * snapshot.<br/>
* An example of use is the implementation of a client-server applications with * An example of use is the implementation of a client-server application with
* the requirement of transferring somehow parts of the representation side to * the requirement of transferring somehow parts of the representation side to
* side. * side.
* *
@ -298,29 +336,16 @@ private:
*/ */
template<typename Registry> template<typename Registry>
class basic_continuous_loader { class basic_continuous_loader {
using entity_traits = entt_traits<typename Registry::entity_type>; static_assert(!std::is_const_v<Registry>, "Non-const registry type required");
using traits_type = typename Registry::traits_type;
void destroy(typename Registry::entity_type entt) {
if(const auto it = remloc.find(entt); it == remloc.cend()) {
const auto local = reg->create();
remloc.emplace(entt, std::make_pair(local, true));
reg->destroy(local);
}
}
void restore(typename Registry::entity_type entt) { void restore(typename Registry::entity_type entt) {
const auto it = remloc.find(entt); if(const auto entity = to_entity(entt); remloc.contains(entity) && remloc[entity].first == entt) {
if(!reg->valid(remloc[entity].second)) {
if(it == remloc.cend()) { remloc[entity].second = reg->create();
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();
} }
} else {
// set the dirty flag remloc.insert_or_assign(entity, std::make_pair(entt, reg->create()));
remloc[entt].second = true;
} }
} }
@ -369,42 +394,6 @@ class basic_continuous_loader {
} }
} }
template<typename Component>
void remove_if_exists() {
for(auto &&ref: remloc) {
const auto local = ref.second.first;
if(reg->valid(local)) {
reg->template remove<Component>(local);
}
}
}
template<typename Component, typename Archive, typename... Other, typename... Member>
void assign(Archive &archive, [[maybe_unused]] Member Other::*...member) {
typename entity_traits::entity_type length{};
entity_type entt;
archive(length);
if constexpr(ignore_as_empty_v<Component>) {
while(length--) {
archive(entt);
restore(entt);
reg->template emplace_or_replace<Component>(map(entt));
}
} else {
Component instance;
while(length--) {
archive(entt, instance);
(update(instance, member), ...);
restore(entt);
reg->template emplace_or_replace<Component>(map(entt), std::move(instance));
}
}
}
public: public:
/*! Basic registry type. */ /*! Basic registry type. */
using registry_type = Registry; using registry_type = Registry;
@ -416,7 +405,8 @@ public:
* @param source A valid reference to a registry. * @param source A valid reference to a registry.
*/ */
basic_continuous_loader(registry_type &source) noexcept basic_continuous_loader(registry_type &source) noexcept
: reg{&source} {} : remloc{source.get_allocator()},
reg{&source} {}
/*! @brief Default move constructor. */ /*! @brief Default move constructor. */
basic_continuous_loader(basic_continuous_loader &&) = default; basic_continuous_loader(basic_continuous_loader &&) = default;
@ -425,90 +415,142 @@ public:
basic_continuous_loader &operator=(basic_continuous_loader &&) = default; 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 * It creates local counterparts for remote elements as needed.<br/>
* and creates local counterparts for them if required. * Members are either data members of type entity_type or containers of
* entities. In both cases, a loader visits them and replaces entities with
* their local counterpart.
*
* @tparam Type Type of elements to restore.
* @tparam Archive Type of input archive.
* @param archive A valid reference to an input archive.
* @param id Optional name used to map the storage within the registry.
* @return A valid loader to continue restoring data.
*/
template<typename Type, typename Archive>
basic_continuous_loader &get([[maybe_unused]] Archive &archive, const id_type id = type_hash<Type>::value()) {
auto &storage = reg->template storage<Type>(id);
typename traits_type::entity_type length{};
entity_type entt{null};
archive(length);
if constexpr(std::is_same_v<Type, entity_type>) {
typename traits_type::entity_type in_use{};
storage.reserve(length);
archive(in_use);
for(std::size_t pos{}; pos < in_use; ++pos) {
archive(entt);
restore(entt);
}
for(std::size_t pos = in_use; pos < length; ++pos) {
archive(entt);
if(const auto entity = to_entity(entt); remloc.contains(entity)) {
if(reg->valid(remloc[entity].second)) {
reg->destroy(remloc[entity].second);
}
remloc.erase(entity);
}
}
} else {
for(auto &&ref: remloc) {
storage.remove(ref.second.second);
}
while(length--) {
if(archive(entt); entt != null) {
restore(entt);
if constexpr(Registry::template storage_for_type<Type>::traits_type::page_size == 0u) {
storage.emplace(map(entt));
} else {
Type elem{};
archive(elem);
storage.emplace(map(entt), std::move(elem));
}
}
}
}
return *this;
}
/**
* @brief Restores all identifiers, including those to be recycled.
*
* It creates local counterparts for remote elements as needed.
* *
* @tparam Archive Type of input archive. * @tparam Archive Type of input archive.
* @param archive A valid reference to an input archive. * @param archive A valid reference to an input archive.
* @return A non-const reference to this loader. * @return A non-const reference to this loader.
*/ */
template<typename Archive> template<typename Archive>
basic_continuous_loader &entities(Archive &archive) { [[deprecated("use .get<Entity>(archive) instead")]] basic_continuous_loader &entities(Archive &archive) {
typename entity_traits::entity_type length{}; return get<entity_type>(archive);
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;
} }
/** /**
* @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 * It creates local counterparts for remote elements as needed.<br/>
* serialization. In the event that the entity to which the component is * Members are either data members of type entity_type or containers of
* assigned doesn't exist yet, the loader will take care to create a local * entities. In both cases, a loader visits them and replaces entities with
* counterpart for it.<br/> * their local counterpart.
* 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.
* *
* @tparam Component Type of component to restore. * @tparam Component Type of component to restore.
* @tparam Archive Type of input archive. * @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. * @tparam Member Types of members to update with their local counterparts.
* @param archive A valid reference to an input archive. * @param archive A valid reference to an input archive.
* @param member Members to update with their local counterparts. * @param member Members to update with their local counterparts.
* @return A non-const reference to this loader. * @return A non-const reference to this loader.
*/ */
template<typename... Component, typename Archive, typename... Other, typename... Member> template<typename... Component, typename Archive, typename... Member, typename... Clazz>
basic_continuous_loader &component(Archive &archive, Member Other::*...member) { [[deprecated("use .component<Type>(archive, members...) instead")]] basic_continuous_loader &component(Archive &archive, Member Clazz::*...member) {
(remove_if_exists<Component>(), ...); ([&](auto &storage) {
(assign<Component>(archive, member...), ...); for(auto &&ref: remloc) {
storage.remove(ref.second.second);
}
typename traits_type::entity_type length{};
entity_type entt{null};
archive(length);
while(length--) {
if(archive(entt); entt != null) {
restore(entt);
if constexpr(std::remove_reference_t<decltype(storage)>::traits_type::page_size == 0u) {
storage.emplace(map(entt));
} else {
typename std::remove_reference_t<decltype(storage)>::value_type elem{};
archive(elem);
(update(elem, member), ...);
storage.emplace(map(entt), std::move(elem));
}
}
}
}(reg->template storage<Component>()),
...);
return *this; 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, * Users should invoke this member function after restoring each snapshot,
* unless they know exactly what they are doing. * unless they know exactly what they are doing.
* *
* @return A non-const reference to this loader. * @return A non-const reference to this loader.
*/ */
basic_continuous_loader &shrink() { [[deprecated("use .get<Entity>(archive) instead")]] 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);
}
}
return *this; return *this;
} }
@ -518,17 +560,12 @@ public:
* In case all the entities were serialized but only part of the components * 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 * was saved, it could happen that some of the entities have no components
* once restored.<br/> * once restored.<br/>
* This functions helps to identify and destroy those entities. * This function helps to identify and destroy those entities.
* *
* @return A non-const reference to this loader. * @return A non-const reference to this loader.
*/ */
basic_continuous_loader &orphans() { basic_continuous_loader &orphans() {
reg->each([this](const auto entt) { internal::orphans(*reg);
if(reg->orphan(entt)) {
reg->release(entt);
}
});
return *this; return *this;
} }
@ -538,7 +575,8 @@ public:
* @return True if `entity` is managed by the loader, false otherwise. * @return True if `entity` is managed by the loader, false otherwise.
*/ */
[[nodiscard]] bool contains(entity_type entt) const noexcept { [[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. * @return The local identifier if any, the null entity otherwise.
*/ */
[[nodiscard]] entity_type map(entity_type entt) const noexcept { [[nodiscard]] entity_type map(entity_type entt) const noexcept {
const auto it = remloc.find(entt); if(const auto it = remloc.find(to_entity(entt)); it != remloc.cend() && it->second.first == entt) {
entity_type other = null; return it->second.second;
if(it != remloc.cend()) {
other = it->second.first;
} }
return other; return null;
} }
private: private:
dense_map<entity_type, std::pair<entity_type, bool>> remloc; dense_map<typename traits_type::entity_type, std::pair<entity_type, entity_type>> remloc;
registry_type *reg; registry_type *reg;
}; };

View File

@ -88,6 +88,10 @@ struct sparse_set_iterator final {
return *operator->(); return *operator->();
} }
[[nodiscard]] constexpr pointer data() const noexcept {
return packed ? packed->data() : nullptr;
}
[[nodiscard]] constexpr difference_type index() const noexcept { [[nodiscard]] constexpr difference_type index() const noexcept {
return offset - 1; return offset - 1;
} }
@ -97,38 +101,38 @@ private:
difference_type offset; difference_type offset;
}; };
template<typename Type, typename Other> template<typename Container>
[[nodiscard]] constexpr std::ptrdiff_t operator-(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) noexcept { [[nodiscard]] constexpr std::ptrdiff_t operator-(const sparse_set_iterator<Container> &lhs, const sparse_set_iterator<Container> &rhs) noexcept {
return rhs.index() - lhs.index(); return rhs.index() - lhs.index();
} }
template<typename Type, typename Other> template<typename Container>
[[nodiscard]] constexpr bool operator==(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) noexcept { [[nodiscard]] constexpr bool operator==(const sparse_set_iterator<Container> &lhs, const sparse_set_iterator<Container> &rhs) noexcept {
return lhs.index() == rhs.index(); return lhs.index() == rhs.index();
} }
template<typename Type, typename Other> template<typename Container>
[[nodiscard]] constexpr bool operator!=(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) noexcept { [[nodiscard]] constexpr bool operator!=(const sparse_set_iterator<Container> &lhs, const sparse_set_iterator<Container> &rhs) noexcept {
return !(lhs == rhs); return !(lhs == rhs);
} }
template<typename Type, typename Other> template<typename Container>
[[nodiscard]] constexpr bool operator<(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) noexcept { [[nodiscard]] constexpr bool operator<(const sparse_set_iterator<Container> &lhs, const sparse_set_iterator<Container> &rhs) noexcept {
return lhs.index() > rhs.index(); return lhs.index() > rhs.index();
} }
template<typename Type, typename Other> template<typename Container>
[[nodiscard]] constexpr bool operator>(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) noexcept { [[nodiscard]] constexpr bool operator>(const sparse_set_iterator<Container> &lhs, const sparse_set_iterator<Container> &rhs) noexcept {
return lhs.index() < rhs.index(); return rhs < lhs;
} }
template<typename Type, typename Other> template<typename Container>
[[nodiscard]] constexpr bool operator<=(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) noexcept { [[nodiscard]] constexpr bool operator<=(const sparse_set_iterator<Container> &lhs, const sparse_set_iterator<Container> &rhs) noexcept {
return !(lhs > rhs); return !(lhs > rhs);
} }
template<typename Type, typename Other> template<typename Container>
[[nodiscard]] constexpr bool operator>=(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) noexcept { [[nodiscard]] constexpr bool operator>=(const sparse_set_iterator<Container> &lhs, const sparse_set_iterator<Container> &rhs) noexcept {
return !(lhs < rhs); return !(lhs < rhs);
} }
@ -139,14 +143,6 @@ template<typename Type, typename Other>
* @endcond * @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. * @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 * 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 * _packed_ one; one used for direct access through contiguous memory, the other
* one used to get the data through an extra level of indirection.<br/> * one used to get the data through an extra level of indirection.<br/>
* This is largely used by the registry to offer users the fastest access ever
* to the components. Views and groups in general are almost entirely designed
* around sparse sets.
*
* This type of data structure is widely documented in the literature and on the * 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 * web. This is nothing more than a customized implementation suitable for the
* purpose of the framework. * 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 * 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. * 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. * @tparam Allocator Type of allocator used to manage memory and elements.
*/ */
template<typename Entity, typename Allocator> template<typename Entity, typename Allocator>
@ -176,23 +168,26 @@ class basic_sparse_set {
static_assert(std::is_same_v<typename alloc_traits::value_type, Entity>, "Invalid value type"); static_assert(std::is_same_v<typename alloc_traits::value_type, Entity>, "Invalid value type");
using sparse_container_type = std::vector<typename alloc_traits::pointer, typename alloc_traits::template rebind_alloc<typename alloc_traits::pointer>>; using sparse_container_type = std::vector<typename alloc_traits::pointer, typename alloc_traits::template rebind_alloc<typename alloc_traits::pointer>>;
using packed_container_type = std::vector<Entity, Allocator>; using packed_container_type = std::vector<Entity, Allocator>;
using entity_traits = entt_traits<Entity>;
[[nodiscard]] auto sparse_ptr(const Entity entt) const { [[nodiscard]] auto sparse_ptr(const Entity entt) const {
const auto pos = static_cast<size_type>(entity_traits::to_entity(entt)); const auto pos = static_cast<size_type>(traits_type::to_entity(entt));
const auto page = pos / entity_traits::page_size; const auto page = pos / traits_type::page_size;
return (page < sparse.size() && sparse[page]) ? (sparse[page] + fast_mod(pos, entity_traits::page_size)) : nullptr; return (page < sparse.size() && sparse[page]) ? (sparse[page] + fast_mod(pos, traits_type::page_size)) : nullptr;
} }
[[nodiscard]] auto &sparse_ref(const Entity entt) const { [[nodiscard]] auto &sparse_ref(const Entity entt) const {
ENTT_ASSERT(sparse_ptr(entt), "Invalid element"); ENTT_ASSERT(sparse_ptr(entt), "Invalid element");
const auto pos = static_cast<size_type>(entity_traits::to_entity(entt)); const auto pos = static_cast<size_type>(traits_type::to_entity(entt));
return sparse[pos / entity_traits::page_size][fast_mod(pos, entity_traits::page_size)]; 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) { [[nodiscard]] auto &assure_at_least(const Entity entt) {
const auto pos = static_cast<size_type>(entity_traits::to_entity(entt)); const auto pos = static_cast<size_type>(traits_type::to_entity(entt));
const auto page = pos / entity_traits::page_size; const auto page = pos / traits_type::page_size;
if(!(page < sparse.size())) { if(!(page < sparse.size())) {
sparse.resize(page + 1u, nullptr); sparse.resize(page + 1u, nullptr);
@ -200,11 +195,11 @@ class basic_sparse_set {
if(!sparse[page]) { if(!sparse[page]) {
auto page_allocator{packed.get_allocator()}; auto page_allocator{packed.get_allocator()};
sparse[page] = alloc_traits::allocate(page_allocator, entity_traits::page_size); sparse[page] = alloc_traits::allocate(page_allocator, traits_type::page_size);
std::uninitialized_fill(sparse[page], sparse[page] + entity_traits::page_size, null); 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"); ENTT_ASSERT(elem == null, "Slot not available");
return elem; return elem;
} }
@ -214,8 +209,8 @@ class basic_sparse_set {
for(auto &&page: sparse) { for(auto &&page: sparse) {
if(page != nullptr) { if(page != nullptr) {
std::destroy(page, page + entity_traits::page_size); std::destroy(page, page + traits_type::page_size);
alloc_traits::deallocate(page_allocator, page, entity_traits::page_size); alloc_traits::deallocate(page_allocator, page, traits_type::page_size);
page = nullptr; page = nullptr;
} }
} }
@ -226,13 +221,28 @@ private:
return nullptr; return nullptr;
} }
virtual void swap_at(const std::size_t, const std::size_t) {} virtual void swap_or_move(const std::size_t, const std::size_t) {}
virtual void move_element(const std::size_t, const std::size_t) {}
protected: protected:
/*! @brief Random access iterator type. */ /*! @brief Random access iterator type. */
using basic_iterator = internal::sparse_set_iterator<packed_container_type>; using basic_iterator = internal::sparse_set_iterator<packed_container_type>;
/**
* @brief Swaps two items at specific locations.
* @param lhs A position to move from.
* @param rhs The other position to move from.
*/
void swap_at(const std::size_t lhs, const std::size_t rhs) {
const auto entity = static_cast<typename traits_type::entity_type>(lhs);
const auto other = static_cast<typename traits_type::entity_type>(rhs);
sparse_ref(packed[lhs]) = traits_type::combine(other, traits_type::to_integral(packed[lhs]));
sparse_ref(packed[rhs]) = traits_type::combine(entity, traits_type::to_integral(packed[rhs]));
using std::swap;
swap(packed[lhs], packed[rhs]);
}
/** /**
* @brief Erases an entity from a sparse set. * @brief Erases an entity from a sparse set.
* @param it An iterator to the element to pop. * @param it An iterator to the element to pop.
@ -240,8 +250,8 @@ protected:
void swap_and_pop(const basic_iterator it) { void swap_and_pop(const basic_iterator it) {
ENTT_ASSERT(mode == deletion_policy::swap_and_pop, "Deletion policy mismatched"); ENTT_ASSERT(mode == deletion_policy::swap_and_pop, "Deletion policy mismatched");
auto &self = sparse_ref(*it); auto &self = sparse_ref(*it);
const auto entt = entity_traits::to_entity(self); const auto entt = traits_type::to_entity(self);
sparse_ref(packed.back()) = entity_traits::combine(entt, entity_traits::to_integral(packed.back())); sparse_ref(packed.back()) = traits_type::combine(entt, traits_type::to_integral(packed.back()));
packed[static_cast<size_type>(entt)] = packed.back(); packed[static_cast<size_type>(entt)] = packed.back();
// unnecessary but it helps to detect nasty bugs // unnecessary but it helps to detect nasty bugs
ENTT_ASSERT((packed.back() = null, true), ""); ENTT_ASSERT((packed.back() = null, true), "");
@ -256,8 +266,8 @@ protected:
*/ */
void in_place_pop(const basic_iterator it) { void in_place_pop(const basic_iterator it) {
ENTT_ASSERT(mode == deletion_policy::in_place, "Deletion policy mismatched"); ENTT_ASSERT(mode == deletion_policy::in_place, "Deletion policy mismatched");
const auto entt = entity_traits::to_entity(std::exchange(sparse_ref(*it), null)); const auto entt = traits_type::to_entity(std::exchange(sparse_ref(*it), null));
packed[static_cast<size_type>(entt)] = std::exchange(free_list, entity_traits::combine(entt, entity_traits::reserved)); packed[static_cast<size_type>(entt)] = std::exchange(free_list, traits_type::combine(entt, tombstone));
} }
protected: 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. * @brief Assigns an entity to a sparse set.
* @param entt A valid identifier. * @param entt A valid identifier.
@ -289,25 +316,27 @@ protected:
if(auto &elem = assure_at_least(entt); free_list == null || force_back) { if(auto &elem = assure_at_least(entt); free_list == null || force_back) {
packed.push_back(entt); packed.push_back(entt);
elem = entity_traits::combine(static_cast<typename entity_traits::entity_type>(packed.size() - 1u), entity_traits::to_integral(entt)); elem = traits_type::combine(static_cast<typename traits_type::entity_type>(packed.size() - 1u), traits_type::to_integral(entt));
return begin(); return begin();
} else { } else {
const auto pos = static_cast<size_type>(entity_traits::to_entity(free_list)); const auto pos = static_cast<size_type>(traits_type::to_entity(free_list));
elem = entity_traits::combine(entity_traits::to_integral(free_list), entity_traits::to_integral(entt)); elem = traits_type::combine(traits_type::to_integral(free_list), traits_type::to_integral(entt));
free_list = std::exchange(packed[pos], entt); free_list = std::exchange(packed[pos], entt);
return --(end() - pos); return --(end() - pos);
} }
} }
public: public:
/*! @brief Allocator type. */ /*! @brief Entity traits. */
using allocator_type = Allocator; using traits_type = entt_traits<Entity>;
/*! @brief Underlying entity identifier. */ /*! @brief Underlying entity identifier. */
using entity_type = typename entity_traits::value_type; using entity_type = typename traits_type::value_type;
/*! @brief Underlying version 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. */ /*! @brief Unsigned integer type. */
using size_type = std::size_t; using size_type = std::size_t;
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Pointer type to contained entities. */ /*! @brief Pointer type to contained entities. */
using pointer = typename packed_container_type::const_pointer; using pointer = typename packed_container_type::const_pointer;
/*! @brief Random access iterator type. */ /*! @brief Random access iterator type. */
@ -317,7 +346,7 @@ public:
/*! @brief Reverse iterator type. */ /*! @brief Reverse iterator type. */
using reverse_iterator = std::reverse_iterator<iterator>; using reverse_iterator = std::reverse_iterator<iterator>;
/*! @brief Constant reverse iterator type. */ /*! @brief Constant reverse iterator type. */
using const_reverse_iterator = reverse_iterator; using const_reverse_iterator = std::reverse_iterator<const_iterator>;
/*! @brief Default constructor. */ /*! @brief Default constructor. */
basic_sparse_set() basic_sparse_set()
@ -341,14 +370,14 @@ public:
/** /**
* @brief Constructs an empty container with the given value type, policy * @brief Constructs an empty container with the given value type, policy
* and allocator. * and allocator.
* @param value Returned value type, if any. * @param elem Returned value type, if any.
* @param pol Type of deletion policy. * @param pol Type of deletion policy.
* @param allocator The allocator to use (possibly default-constructed). * @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}, : sparse{allocator},
packed{allocator}, packed{allocator},
info{&value}, info{&elem},
free_list{tombstone}, free_list{tombstone},
mode{pol} {} mode{pol} {}
@ -465,7 +494,7 @@ public:
* @return Extent of the sparse set. * @return Extent of the sparse set.
*/ */
[[nodiscard]] size_type extent() const noexcept { [[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(); 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. * @brief Direct access to the internal packed array.
* @return A pointer 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. * @brief Returns an iterator to the beginning.
* *
* The returned iterator points to the first entity of the internal packed * If the sparse set is empty, the returned iterator will be equal to
* array. If the sparse set is empty, the returned iterator will be equal to
* `end()`. * `end()`.
* *
* @return An iterator to the first entity of the sparse set. * @return An iterator to the first entity of the sparse set.
@ -519,11 +555,6 @@ public:
/** /**
* @brief Returns an iterator to the end. * @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 * @return An iterator to the element following the last entity of a sparse
* set. * set.
*/ */
@ -539,9 +570,8 @@ public:
/** /**
* @brief Returns a reverse iterator to the beginning. * @brief Returns a reverse iterator to the beginning.
* *
* The returned iterator points to the first entity of the reversed internal * If the sparse set is empty, the returned iterator will be equal to
* packed array. If the sparse set is empty, the returned iterator will be * `rend()`.
* equal to `rend()`.
* *
* @return An iterator to the first entity of the reversed internal packed * @return An iterator to the first entity of the reversed internal packed
* array. * array.
@ -557,11 +587,6 @@ public:
/** /**
* @brief Returns a reverse iterator to the end. * @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 * @return An iterator to the element following the last entity of the
* reversed sparse set. * reversed sparse set.
*/ */
@ -581,7 +606,7 @@ public:
* iterator otherwise. * iterator otherwise.
*/ */
[[nodiscard]] iterator find(const entity_type entt) const noexcept { [[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 { [[nodiscard]] bool contains(const entity_type entt) const noexcept {
const auto elem = sparse_ptr(entt); 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 // 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 { [[nodiscard]] version_type current(const entity_type entt) const noexcept {
const auto elem = sparse_ptr(entt); const auto elem = sparse_ptr(entt);
constexpr auto fallback = entity_traits::to_version(tombstone); constexpr auto fallback = traits_type::to_version(tombstone);
return elem ? entity_traits::to_version(*elem) : fallback; return elem ? traits_type::to_version(*elem) : fallback;
} }
/** /**
@ -620,7 +645,7 @@ public:
*/ */
[[nodiscard]] size_type index(const entity_type entt) const noexcept { [[nodiscard]] size_type index(const entity_type entt) const noexcept {
ENTT_ASSERT(contains(entt), "Set does not contain entity"); ENTT_ASSERT(contains(entt), "Set does not contain entity");
return static_cast<size_type>(entity_traits::to_entity(sparse_ref(entt))); return static_cast<size_type>(traits_type::to_entity(sparse_ref(entt)));
} }
/** /**
@ -652,13 +677,13 @@ public:
* @param entt A valid identifier. * @param entt A valid identifier.
* @return An opaque pointer to the element assigned to the entity, if any. * @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)); return get_at(index(entt));
} }
/*! @copydoc get */ /*! @copydoc value */
[[nodiscard]] void *get(const entity_type entt) noexcept { [[nodiscard]] void *value(const entity_type entt) noexcept {
return const_cast<void *>(std::as_const(*this).get(entt)); return const_cast<void *>(std::as_const(*this).value(entt));
} }
/** /**
@ -669,28 +694,12 @@ public:
* results in undefined behavior. * results in undefined behavior.
* *
* @param entt A valid identifier. * @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 * @return Iterator pointing to the emplaced element in case of success, the
* `end()` iterator otherwise. * `end()` iterator otherwise.
*/ */
iterator emplace(const entity_type entt, const void *value = nullptr) { iterator push(const entity_type entt, const void *elem = nullptr) {
return try_emplace(entt, false, value); return try_emplace(entt, false, elem);
}
/**
* @brief Bump the version number of an entity.
*
* @warning
* Attempting to bump the version of an entity that doesn't belong to the
* sparse set results in undefined behavior.
*
* @param entt A valid identifier.
*/
void bump(const entity_type entt) {
auto &entity = sparse_ref(entt);
ENTT_ASSERT(entt != tombstone && entity != null, "Cannot set the required version");
entity = entity_traits::combine(entity_traits::to_integral(entity), entity_traits::to_integral(entt));
packed[static_cast<size_type>(entity_traits::to_entity(entity))] = entt;
} }
/** /**
@ -707,7 +716,7 @@ public:
* success, the `end()` iterator otherwise. * success, the `end()` iterator otherwise.
*/ */
template<typename It> template<typename It>
iterator insert(It first, It last) { iterator push(It first, It last) {
for(auto it = first; it != last; ++it) { for(auto it = first; it != last; ++it) {
try_emplace(*it, true); try_emplace(*it, true);
} }
@ -715,6 +724,24 @@ public:
return first == last ? end() : find(*first); return first == last ? end() : find(*first);
} }
/**
* @brief Bump the version number of an entity.
*
* @warning
* Attempting to bump the version of an entity that doesn't belong to the
* sparse set results in undefined behavior.
*
* @param entt A valid identifier.
* @return The version of the given identifier.
*/
version_type bump(const entity_type entt) {
auto &entity = sparse_ref(entt);
ENTT_ASSERT(entt != tombstone && entity != null, "Cannot set the required version");
entity = traits_type::combine(traits_type::to_integral(entity), traits_type::to_integral(entt));
packed[static_cast<size_type>(traits_type::to_entity(entity))] = entt;
return traits_type::to_version(entt);
}
/** /**
* @brief Erases an entity from a sparse set. * @brief Erases an entity from a sparse set.
* *
@ -725,7 +752,7 @@ public:
* @param entt A valid identifier. * @param entt A valid identifier.
*/ */
void erase(const entity_type entt) { void erase(const entity_type entt) {
const auto it = --(end() - index(entt)); const auto it = to_iterator(entt);
pop(it, it + 1u); pop(it, it + 1u);
} }
@ -769,29 +796,45 @@ public:
size_type remove(It first, It last) { size_type remove(It first, It last) {
size_type count{}; size_type count{};
if constexpr(std::is_same_v<It, basic_iterator>) {
while(first != last) {
while(first != last && !contains(*first)) {
++first;
}
const auto it = first;
while(first != last && contains(*first)) {
++first;
}
count += std::distance(it, first);
erase(it, first);
}
} else {
for(; first != last; ++first) { for(; first != last; ++first) {
count += remove(*first); count += remove(*first);
} }
}
return count; return count;
} }
/*! @brief Removes all tombstones from the packed array of a sparse set. */ /*! @brief Removes all tombstones from a sparse set. */
void compact() { void compact() {
size_type from = packed.size(); size_type from = packed.size();
for(; from && packed[from - 1u] == tombstone; --from) {} for(; from && packed[from - 1u] == tombstone; --from) {}
for(auto *it = &free_list; *it != null && from; it = std::addressof(packed[entity_traits::to_entity(*it)])) { for(auto *it = &free_list; *it != null && from; it = std::addressof(packed[traits_type::to_entity(*it)])) {
if(const size_type to = entity_traits::to_entity(*it); to < from) { if(const size_type to = traits_type::to_entity(*it); to < from) {
--from; --from;
move_element(from, to); swap_or_move(from, to);
using std::swap; packed[to] = std::exchange(packed[from], tombstone);
swap(packed[from], packed[to]); const auto entity = static_cast<typename traits_type::entity_type>(to);
sparse_ref(packed[to]) = traits_type::combine(entity, traits_type::to_integral(packed[to]));
const auto entity = static_cast<typename entity_traits::entity_type>(to); *it = traits_type::combine(static_cast<typename traits_type::entity_type>(from), tombstone);
sparse_ref(packed[to]) = entity_traits::combine(entity, entity_traits::to_integral(packed[to]));
*it = entity_traits::combine(static_cast<typename entity_traits::entity_type>(from), entity_traits::reserved);
for(; from && packed[from - 1u] == tombstone; --from) {} for(; from && packed[from - 1u] == tombstone; --from) {}
} }
} }
@ -814,21 +857,12 @@ public:
* @param rhs A valid identifier. * @param rhs A valid identifier.
*/ */
void swap_elements(const entity_type lhs, const entity_type rhs) { 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); // basic no-leak guarantee if swapping throws
auto &other = sparse_ref(rhs); swap_or_move(from, to);
swap_at(from, to);
const auto from = entity_traits::to_entity(entt);
const auto to = entity_traits::to_entity(other);
// basic no-leak guarantee (with invalid state) if swapping throws
swap_at(static_cast<size_type>(from), static_cast<size_type>(to));
entt = entity_traits::combine(to, entity_traits::to_integral(packed[from]));
other = entity_traits::combine(from, entity_traits::to_integral(packed[to]));
using std::swap;
swap(packed[from], packed[to]);
} }
/** /**
@ -876,9 +910,9 @@ public:
const auto idx = index(packed[next]); const auto idx = index(packed[next]);
const auto entt = packed[curr]; const auto entt = packed[curr];
swap_at(next, idx); swap_or_move(next, idx);
const auto entity = static_cast<typename entity_traits::entity_type>(curr); const auto entity = static_cast<typename traits_type::entity_type>(curr);
sparse_ref(entt) = entity_traits::combine(entity, entity_traits::to_integral(packed[curr])); sparse_ref(entt) = traits_type::combine(entity, traits_type::to_integral(packed[curr]));
curr = std::exchange(next, idx); curr = std::exchange(next, idx);
} }
} }
@ -906,48 +940,35 @@ public:
* @brief Sort entities according to their order in another sparse set. * @brief Sort entities according to their order in another sparse set.
* *
* Entities that are part of both the sparse sets are ordered internally * 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 * according to the order they have in `other`.<br/>
* to the end of the list and there are no guarantees on their order.<br/> * All the other entities goes to the end of the list and there are no
* In other terms, this function can be used to impose the same order on two * guarantees on their order.
* 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.
* *
* @param other The sparse sets that imposes the order of the entities. * @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(); compact();
const auto to = other.end(); const auto to = other.end();
auto from = other.begin(); auto from = other.begin();
for(size_type pos = packed.size() - 1; pos && from != to; ++from) { for(auto it = begin(); it.index() && from != to; ++from) {
if(contains(*from)) { if(const auto curr = *from; contains(curr)) {
if(*from != packed[pos]) { if(const auto entt = *it; entt != curr) {
// basic no-leak guarantee (with invalid state) if swapping throws // 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. */ /*! @brief Clears a sparse set. */
void clear() { void clear() {
if(const auto last = end(); free_list == null) { pop_all();
pop(begin(), last); // sanity check to avoid subtle issues due to storage classes
} else { ENTT_ASSERT((compact(), size()) == 0u, "Non-empty set");
for(auto &&entity: *this) {
// tombstone filter on itself
if(const auto it = find(entity); it != last) {
pop(it, it + 1u);
}
}
}
free_list = tombstone; free_list = tombstone;
packed.clear(); packed.clear();
} }

View File

@ -9,7 +9,6 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "../config/config.h" #include "../config/config.h"
#include "../core/compressed_pair.hpp"
#include "../core/iterator.hpp" #include "../core/iterator.hpp"
#include "../core/memory.hpp" #include "../core/memory.hpp"
#include "../core/type_info.hpp" #include "../core/type_info.hpp"
@ -17,7 +16,6 @@
#include "entity.hpp" #include "entity.hpp"
#include "fwd.hpp" #include "fwd.hpp"
#include "sparse_set.hpp" #include "sparse_set.hpp"
#include "storage_mixin.hpp"
namespace entt { namespace entt {
@ -28,13 +26,12 @@ namespace entt {
namespace internal { namespace internal {
template<typename Container> template<typename Container, std::size_t Size>
class storage_iterator final { class storage_iterator final {
friend storage_iterator<const Container>; friend storage_iterator<const Container, Size>;
using container_type = std::remove_const_t<Container>; using container_type = std::remove_const_t<Container>;
using alloc_traits = std::allocator_traits<typename container_type::allocator_type>; using alloc_traits = std::allocator_traits<typename container_type::allocator_type>;
using comp_traits = component_traits<std::remove_pointer_t<typename container_type::value_type>>;
using iterator_traits = std::iterator_traits<std::conditional_t< using iterator_traits = std::iterator_traits<std::conditional_t<
std::is_const_v<Container>, std::is_const_v<Container>,
@ -51,12 +48,12 @@ public:
constexpr storage_iterator() noexcept = default; constexpr storage_iterator() noexcept = default;
constexpr storage_iterator(Container *ref, const difference_type idx) noexcept constexpr storage_iterator(Container *ref, const difference_type idx) noexcept
: packed{ref}, : payload{ref},
offset{idx} {} offset{idx} {}
template<bool Const = std::is_const_v<Container>, typename = std::enable_if_t<Const>> template<bool Const = std::is_const_v<Container>, typename = std::enable_if_t<Const>>
constexpr storage_iterator(const storage_iterator<std::remove_const_t<Container>> &other) noexcept constexpr storage_iterator(const storage_iterator<std::remove_const_t<Container>, Size> &other) noexcept
: storage_iterator{other.packed, other.offset} {} : storage_iterator{other.payload, other.offset} {}
constexpr storage_iterator &operator++() noexcept { constexpr storage_iterator &operator++() noexcept {
return --offset, *this; return --offset, *this;
@ -96,12 +93,12 @@ public:
[[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept { [[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept {
const auto pos = index() - value; 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 { [[nodiscard]] constexpr pointer operator->() const noexcept {
const auto pos = index(); 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 { [[nodiscard]] constexpr reference operator*() const noexcept {
@ -113,42 +110,42 @@ public:
} }
private: private:
Container *packed; Container *payload;
difference_type offset; difference_type offset;
}; };
template<typename CLhs, typename CRhs> template<typename Lhs, typename Rhs, std::size_t Size>
[[nodiscard]] constexpr std::ptrdiff_t operator-(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) noexcept { [[nodiscard]] constexpr std::ptrdiff_t operator-(const storage_iterator<Lhs, Size> &lhs, const storage_iterator<Rhs, Size> &rhs) noexcept {
return rhs.index() - lhs.index(); return rhs.index() - lhs.index();
} }
template<typename CLhs, typename CRhs> template<typename Lhs, typename Rhs, std::size_t Size>
[[nodiscard]] constexpr bool operator==(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator==(const storage_iterator<Lhs, Size> &lhs, const storage_iterator<Rhs, Size> &rhs) noexcept {
return lhs.index() == rhs.index(); return lhs.index() == rhs.index();
} }
template<typename CLhs, typename CRhs> template<typename Lhs, typename Rhs, std::size_t Size>
[[nodiscard]] constexpr bool operator!=(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator!=(const storage_iterator<Lhs, Size> &lhs, const storage_iterator<Rhs, Size> &rhs) noexcept {
return !(lhs == rhs); return !(lhs == rhs);
} }
template<typename CLhs, typename CRhs> template<typename Lhs, typename Rhs, std::size_t Size>
[[nodiscard]] constexpr bool operator<(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator<(const storage_iterator<Lhs, Size> &lhs, const storage_iterator<Rhs, Size> &rhs) noexcept {
return lhs.index() > rhs.index(); return lhs.index() > rhs.index();
} }
template<typename CLhs, typename CRhs> template<typename Lhs, typename Rhs, std::size_t Size>
[[nodiscard]] constexpr bool operator>(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator>(const storage_iterator<Lhs, Size> &lhs, const storage_iterator<Rhs, Size> &rhs) noexcept {
return lhs.index() < rhs.index(); return rhs < lhs;
} }
template<typename CLhs, typename CRhs> template<typename Lhs, typename Rhs, std::size_t Size>
[[nodiscard]] constexpr bool operator<=(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator<=(const storage_iterator<Lhs, Size> &lhs, const storage_iterator<Rhs, Size> &rhs) noexcept {
return !(lhs > rhs); return !(lhs > rhs);
} }
template<typename CLhs, typename CRhs> template<typename Lhs, typename Rhs, std::size_t Size>
[[nodiscard]] constexpr bool operator>=(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator>=(const storage_iterator<Lhs, Size> &lhs, const storage_iterator<Rhs, Size> &rhs) noexcept {
return !(lhs < rhs); return !(lhs < rhs);
} }
@ -158,10 +155,11 @@ class extended_storage_iterator final {
friend class extended_storage_iterator; friend class extended_storage_iterator;
public: public:
using iterator_type = It;
using difference_type = std::ptrdiff_t;
using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval<It>()), std::forward_as_tuple(*std::declval<Other>()...))); using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval<It>()), std::forward_as_tuple(*std::declval<Other>()...)));
using pointer = input_iterator_pointer<value_type>; using pointer = input_iterator_pointer<value_type>;
using reference = value_type; using reference = value_type;
using difference_type = std::ptrdiff_t;
using iterator_category = std::input_iterator_tag; using iterator_category = std::input_iterator_tag;
constexpr extended_storage_iterator() constexpr extended_storage_iterator()
@ -191,20 +189,24 @@ public:
return {*std::get<It>(it), *std::get<Other>(it)...}; return {*std::get<It>(it), *std::get<Other>(it)...};
} }
template<typename... CLhs, typename... CRhs> [[nodiscard]] constexpr iterator_type base() const noexcept {
friend constexpr bool operator==(const extended_storage_iterator<CLhs...> &, const extended_storage_iterator<CRhs...> &) noexcept; return std::get<It>(it);
}
template<typename... Lhs, typename... Rhs>
friend constexpr bool operator==(const extended_storage_iterator<Lhs...> &, const extended_storage_iterator<Rhs...> &) noexcept;
private: private:
std::tuple<It, Other...> it; std::tuple<It, Other...> it;
}; };
template<typename... CLhs, typename... CRhs> template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator==(const extended_storage_iterator<CLhs...> &lhs, const extended_storage_iterator<CRhs...> &rhs) noexcept { [[nodiscard]] constexpr bool operator==(const extended_storage_iterator<Lhs...> &lhs, const extended_storage_iterator<Rhs...> &rhs) noexcept {
return std::get<0>(lhs.it) == std::get<0>(rhs.it); return std::get<0>(lhs.it) == std::get<0>(rhs.it);
} }
template<typename... CLhs, typename... CRhs> template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator!=(const extended_storage_iterator<CLhs...> &lhs, const extended_storage_iterator<CRhs...> &rhs) noexcept { [[nodiscard]] constexpr bool operator!=(const extended_storage_iterator<Lhs...> &lhs, const extended_storage_iterator<Rhs...> &rhs) noexcept {
return !(lhs == rhs); return !(lhs == rhs);
} }
@ -227,43 +229,43 @@ template<typename... CLhs, typename... CRhs>
* normally available for non-empty types will not be available for empty ones. * normally available for non-empty types will not be available for empty ones.
* *
* @tparam Type Type of objects assigned to the entities. * @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. * @tparam Allocator Type of allocator used to manage memory and elements.
*/ */
template<typename Type, typename Entity, typename Allocator, typename> template<typename Type, typename Entity, typename Allocator, typename>
class basic_storage: public basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>> { class basic_storage: public basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>> {
using alloc_traits = std::allocator_traits<Allocator>; using alloc_traits = std::allocator_traits<Allocator>;
static_assert(std::is_same_v<typename alloc_traits::value_type, Type>, "Invalid value type"); static_assert(std::is_same_v<typename alloc_traits::value_type, Type>, "Invalid value type");
using underlying_type = basic_sparse_set<Entity, typename alloc_traits::template rebind_alloc<Entity>>;
using container_type = std::vector<typename alloc_traits::pointer, typename alloc_traits::template rebind_alloc<typename alloc_traits::pointer>>; using container_type = std::vector<typename alloc_traits::pointer, typename alloc_traits::template rebind_alloc<typename alloc_traits::pointer>>;
using comp_traits = component_traits<Type>; using underlying_type = basic_sparse_set<Entity, typename alloc_traits::template rebind_alloc<Entity>>;
using underlying_iterator = typename underlying_type::basic_iterator;
static constexpr bool is_pinned_type_v = !(std::is_move_constructible_v<Type> && std::is_move_assignable_v<Type>); static constexpr bool is_pinned_type_v = !(std::is_move_constructible_v<Type> && std::is_move_assignable_v<Type>);
[[nodiscard]] auto &element_at(const std::size_t pos) const { [[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 assure_at_least(const std::size_t pos) {
auto &&container = packed.first(); const auto idx = pos / traits_type::page_size;
const auto idx = pos / comp_traits::page_size;
if(!(idx < container.size())) { if(!(idx < payload.size())) {
auto curr = container.size(); auto curr = payload.size();
container.resize(idx + 1u, nullptr); allocator_type allocator{get_allocator()};
payload.resize(idx + 1u, nullptr);
ENTT_TRY { ENTT_TRY {
for(const auto last = container.size(); curr < last; ++curr) { for(const auto last = payload.size(); curr < last; ++curr) {
container[curr] = alloc_traits::allocate(packed.second(), comp_traits::page_size); payload[curr] = alloc_traits::allocate(allocator, traits_type::page_size);
} }
} }
ENTT_CATCH { ENTT_CATCH {
container.resize(curr); payload.resize(curr);
ENTT_THROW; ENTT_THROW;
} }
} }
return container[idx] + fast_mod(pos, comp_traits::page_size); return payload[idx] + fast_mod(pos, traits_type::page_size);
} }
template<typename... Args> template<typename... Args>
@ -272,7 +274,7 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
ENTT_TRY { ENTT_TRY {
auto elem = assure_at_least(static_cast<size_type>(it.index())); auto elem = assure_at_least(static_cast<size_type>(it.index()));
entt::uninitialized_construct_using_allocator(to_address(elem), packed.second(), std::forward<Args>(args)...); entt::uninitialized_construct_using_allocator(to_address(elem), get_allocator(), std::forward<Args>(args)...);
} }
ENTT_CATCH { ENTT_CATCH {
base_type::pop(it, it + 1u); base_type::pop(it, it + 1u);
@ -283,25 +285,24 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
} }
void shrink_to_size(const std::size_t sz) { 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) { for(auto pos = sz, length = base_type::size(); pos < length; ++pos) {
if constexpr(comp_traits::in_place_delete) { if constexpr(traits_type::in_place_delete) {
if(base_type::at(pos) != tombstone) { if(base_type::at(pos) != tombstone) {
std::destroy_at(std::addressof(element_at(pos))); alloc_traits::destroy(allocator, std::addressof(element_at(pos)));
} }
} else { } else {
std::destroy_at(std::addressof(element_at(pos))); alloc_traits::destroy(allocator, std::addressof(element_at(pos)));
} }
} }
auto &&container = packed.first(); for(auto pos = from, last = payload.size(); pos < last; ++pos) {
auto page_allocator{packed.second()}; alloc_traits::deallocate(allocator, payload[pos], traits_type::page_size);
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); payload.resize(from);
} }
private: private:
@ -309,54 +310,68 @@ private:
return std::addressof(element_at(pos)); return std::addressof(element_at(pos));
} }
void swap_at([[maybe_unused]] const std::size_t lhs, [[maybe_unused]] const std::size_t rhs) final { void swap_or_move([[maybe_unused]] const std::size_t from, [[maybe_unused]] const std::size_t to) override {
// use a runtime value to avoid compile-time suppression that drives the code coverage tool crazy
ENTT_ASSERT((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 // 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"); ENTT_ASSERT((from + 1u) && !is_pinned_type_v, "Pinned type");
if constexpr(!is_pinned_type_v) { if constexpr(!is_pinned_type_v) {
auto &elem = element_at(from); auto &elem = element_at(from);
entt::uninitialized_construct_using_allocator(to_address(assure_at_least(to)), packed.second(), std::move(elem));
std::destroy_at(std::addressof(elem)); if constexpr(traits_type::in_place_delete) {
if(base_type::operator[](to) == tombstone) {
allocator_type allocator{get_allocator()};
entt::uninitialized_construct_using_allocator(to_address(assure_at_least(to)), allocator, std::move(elem));
alloc_traits::destroy(allocator, std::addressof(elem));
return;
}
}
using std::swap;
swap(elem, element_at(to));
} }
} }
protected: protected:
/*! @brief Random access iterator type. */
using basic_iterator = typename underlying_type::basic_iterator;
/** /**
* @brief Erases entities from a sparse set. * @brief Erases entities from a storage.
* @param first An iterator to the first element of the range of entities. * @param 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 last An iterator past the last element of the range of entities.
*/ */
void pop(basic_iterator first, basic_iterator last) override { void pop(underlying_iterator first, underlying_iterator last) override {
for(; first != last; ++first) { for(allocator_type allocator{get_allocator()}; first != last; ++first) {
// cannot use first.index() because it would break with cross iterators // cannot use first.index() because it would break with cross iterators
auto &elem = element_at(base_type::index(*first)); auto &elem = element_at(base_type::index(*first));
if constexpr(comp_traits::in_place_delete) { if constexpr(traits_type::in_place_delete) {
base_type::in_place_pop(first); base_type::in_place_pop(first);
std::destroy_at(std::addressof(elem)); alloc_traits::destroy(allocator, std::addressof(elem));
} else { } else {
auto &other = element_at(base_type::size() - 1u); auto &other = element_at(base_type::size() - 1u);
// destroying on exit allows reentrant destructors // destroying on exit allows reentrant destructors
[[maybe_unused]] auto unused = std::exchange(elem, std::move(other)); [[maybe_unused]] auto unused = std::exchange(elem, std::move(other));
std::destroy_at(std::addressof(other)); alloc_traits::destroy(allocator, std::addressof(other));
base_type::swap_and_pop(first); base_type::swap_and_pop(first);
} }
} }
} }
/*! @brief Erases all entities of a storage. */
void pop_all() override {
allocator_type allocator{get_allocator()};
for(auto first = base_type::begin(); !(first.index() < 0); ++first) {
if constexpr(traits_type::in_place_delete) {
if(*first != tombstone) {
base_type::in_place_pop(first);
alloc_traits::destroy(allocator, std::addressof(element_at(static_cast<size_type>(first.index()))));
}
} else {
base_type::swap_and_pop(first);
alloc_traits::destroy(allocator, std::addressof(element_at(static_cast<size_type>(first.index()))));
}
}
}
/** /**
* @brief Assigns an entity to a storage. * @brief Assigns an entity to a storage.
* @param entt A valid identifier. * @param entt A valid identifier.
@ -364,7 +379,7 @@ protected:
* @param force_back Force back insertion. * @param force_back Force back insertion.
* @return Iterator pointing to the emplaced element. * @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(value) {
if constexpr(std::is_copy_constructible_v<value_type>) { if constexpr(std::is_copy_constructible_v<value_type>) {
return emplace_element(entt, force_back, *static_cast<const value_type *>(value)); return emplace_element(entt, force_back, *static_cast<const value_type *>(value));
@ -383,22 +398,24 @@ protected:
public: public:
/*! @brief Base type. */ /*! @brief Base type. */
using base_type = underlying_type; using base_type = underlying_type;
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Type of the objects assigned to entities. */ /*! @brief Type of the objects assigned to entities. */
using value_type = Type; using value_type = Type;
/*! @brief Component traits. */
using traits_type = component_traits<value_type>;
/*! @brief Underlying entity identifier. */ /*! @brief Underlying entity identifier. */
using entity_type = Entity; using entity_type = Entity;
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
using size_type = std::size_t; using size_type = std::size_t;
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Pointer type to contained elements. */ /*! @brief Pointer type to contained elements. */
using pointer = typename container_type::pointer; using pointer = typename container_type::pointer;
/*! @brief Constant pointer type to contained elements. */ /*! @brief Constant pointer type to contained elements. */
using const_pointer = typename alloc_traits::template rebind_traits<typename alloc_traits::const_pointer>::const_pointer; using const_pointer = typename alloc_traits::template rebind_traits<typename alloc_traits::const_pointer>::const_pointer;
/*! @brief Random access iterator type. */ /*! @brief Random access iterator type. */
using iterator = internal::storage_iterator<container_type>; using iterator = internal::storage_iterator<container_type, traits_type::page_size>;
/*! @brief Constant random access iterator type. */ /*! @brief Constant random access iterator type. */
using const_iterator = internal::storage_iterator<const container_type>; using const_iterator = internal::storage_iterator<const container_type, traits_type::page_size>;
/*! @brief Reverse iterator type. */ /*! @brief Reverse iterator type. */
using reverse_iterator = std::reverse_iterator<iterator>; using reverse_iterator = std::reverse_iterator<iterator>;
/*! @brief Constant reverse iterator type. */ /*! @brief Constant reverse iterator type. */
@ -407,6 +424,10 @@ public:
using iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::iterator, iterator>>; using iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::iterator, iterator>>;
/*! @brief Constant extended iterable storage proxy. */ /*! @brief Constant extended iterable storage proxy. */
using const_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_iterator, const_iterator>>; using const_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_iterator, const_iterator>>;
/*! @brief Extended reverse iterable storage proxy. */
using reverse_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::reverse_iterator, reverse_iterator>>;
/*! @brief Constant extended reverse iterable storage proxy. */
using const_reverse_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_reverse_iterator, const_reverse_iterator>>;
/*! @brief Default constructor. */ /*! @brief Default constructor. */
basic_storage() basic_storage()
@ -417,8 +438,8 @@ public:
* @param allocator The allocator to use. * @param allocator The allocator to use.
*/ */
explicit basic_storage(const allocator_type &allocator) explicit basic_storage(const allocator_type &allocator)
: base_type{type_id<value_type>(), deletion_policy{comp_traits::in_place_delete}, allocator}, : base_type{type_id<value_type>(), deletion_policy{traits_type::in_place_delete}, allocator},
packed{container_type{allocator}, allocator} {} payload{allocator} {}
/** /**
* @brief Move constructor. * @brief Move constructor.
@ -426,7 +447,7 @@ public:
*/ */
basic_storage(basic_storage &&other) noexcept basic_storage(basic_storage &&other) noexcept
: base_type{std::move(other)}, : base_type{std::move(other)},
packed{std::move(other.packed)} {} payload{std::move(other.payload)} {}
/** /**
* @brief Allocator-extended move constructor. * @brief Allocator-extended move constructor.
@ -435,8 +456,8 @@ public:
*/ */
basic_storage(basic_storage &&other, const allocator_type &allocator) noexcept basic_storage(basic_storage &&other, const allocator_type &allocator) noexcept
: base_type{std::move(other), allocator}, : base_type{std::move(other), allocator},
packed{container_type{std::move(other.packed.first()), allocator}, allocator} { payload{std::move(other.payload), allocator} {
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");
} }
/*! @brief Default destructor. */ /*! @brief Default destructor. */
@ -450,12 +471,11 @@ public:
* @return This storage. * @return This storage.
*/ */
basic_storage &operator=(basic_storage &&other) noexcept { 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); shrink_to_size(0u);
base_type::operator=(std::move(other)); base_type::operator=(std::move(other));
packed.first() = std::move(other.packed.first()); payload = std::move(other.payload);
propagate_on_container_move_assignment(packed.second(), other.packed.second());
return *this; return *this;
} }
@ -465,9 +485,8 @@ public:
*/ */
void swap(basic_storage &other) { void swap(basic_storage &other) {
using std::swap; using std::swap;
underlying_type::swap(other); base_type::swap(other);
propagate_on_container_swap(packed.second(), other.packed.second()); swap(payload, other.payload);
swap(packed.first(), other.packed.first());
} }
/** /**
@ -475,7 +494,7 @@ public:
* @return The associated allocator. * @return The associated allocator.
*/ */
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept { [[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. * @return Capacity of the storage.
*/ */
[[nodiscard]] size_type capacity() const noexcept override { [[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. */ /*! @brief Requests the removal of unused capacity. */
@ -513,25 +532,24 @@ public:
* @return A pointer to the array of objects. * @return A pointer to the array of objects.
*/ */
[[nodiscard]] const_pointer raw() const noexcept { [[nodiscard]] const_pointer raw() const noexcept {
return packed.first().data(); return payload.data();
} }
/*! @copydoc raw */ /*! @copydoc raw */
[[nodiscard]] pointer raw() noexcept { [[nodiscard]] pointer raw() noexcept {
return packed.first().data(); return payload.data();
} }
/** /**
* @brief Returns an iterator to the beginning. * @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()`. * If the storage is empty, the returned iterator will be equal to `end()`.
* *
* @return An iterator to the first instance of the internal array. * @return An iterator to the first instance of the internal array.
*/ */
[[nodiscard]] const_iterator cbegin() const noexcept { [[nodiscard]] const_iterator cbegin() const noexcept {
const auto pos = static_cast<typename iterator::difference_type>(base_type::size()); const auto pos = static_cast<typename iterator::difference_type>(base_type::size());
return const_iterator{&packed.first(), pos}; return const_iterator{&payload, pos};
} }
/*! @copydoc cbegin */ /*! @copydoc cbegin */
@ -542,21 +560,16 @@ public:
/*! @copydoc begin */ /*! @copydoc begin */
[[nodiscard]] iterator begin() noexcept { [[nodiscard]] iterator begin() noexcept {
const auto pos = static_cast<typename iterator::difference_type>(base_type::size()); const auto pos = static_cast<typename iterator::difference_type>(base_type::size());
return iterator{&packed.first(), pos}; return iterator{&payload, pos};
} }
/** /**
* @brief Returns an iterator to the end. * @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 * @return An iterator to the element following the last instance of the
* internal array. * internal array.
*/ */
[[nodiscard]] const_iterator cend() const noexcept { [[nodiscard]] const_iterator cend() const noexcept {
return const_iterator{&packed.first(), {}}; return const_iterator{&payload, {}};
} }
/*! @copydoc cend */ /*! @copydoc cend */
@ -566,15 +579,13 @@ public:
/*! @copydoc end */ /*! @copydoc end */
[[nodiscard]] iterator end() noexcept { [[nodiscard]] iterator end() noexcept {
return iterator{&packed.first(), {}}; return iterator{&payload, {}};
} }
/** /**
* @brief Returns a reverse iterator to the beginning. * @brief Returns a reverse iterator to the beginning.
* *
* The returned iterator points to the first instance of the reversed * If the storage is empty, the returned iterator will be equal to `rend()`.
* 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. * @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. * @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 * @return An iterator to the element following the last instance of the
* reversed internal array. * reversed internal array.
*/ */
@ -663,7 +669,7 @@ public:
*/ */
template<typename... Args> template<typename... Args>
value_type &emplace(const entity_type entt, Args &&...args) { value_type &emplace(const entity_type entt, Args &&...args) {
if constexpr(std::is_aggregate_v<value_type>) { if constexpr(std::is_aggregate_v<value_type> && (sizeof...(Args) != 0u || !std::is_default_constructible_v<value_type>)) {
const auto it = emplace_element(entt, false, Type{std::forward<Args>(args)...}); const auto it = emplace_element(entt, false, Type{std::forward<Args>(args)...});
return element_at(static_cast<size_type>(it.index())); return element_at(static_cast<size_type>(it.index()));
} else { } else {
@ -699,12 +705,15 @@ public:
* @param first An iterator to the first element of the range of 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 last An iterator past the last element of the range of entities.
* @param value An instance of the object to construct. * @param value An instance of the object to construct.
* @return Iterator pointing to the last element inserted, if any.
*/ */
template<typename It> template<typename It>
void insert(It first, It last, const value_type &value = {}) { iterator insert(It first, It last, const value_type &value = {}) {
for(; first != last; ++first) { for(; first != last; ++first) {
emplace_element(*first, true, value); 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 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 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. * @param from An iterator to the first element of the range of objects.
* @return Iterator pointing to the first element inserted, if any.
*/ */
template<typename EIt, typename CIt, typename = std::enable_if_t<std::is_same_v<typename std::iterator_traits<CIt>::value_type, value_type>>> template<typename EIt, typename CIt, typename = std::enable_if_t<std::is_same_v<typename std::iterator_traits<CIt>::value_type, value_type>>>
void insert(EIt first, EIt last, CIt from) { iterator insert(EIt first, EIt last, CIt from) {
for(; first != last; ++first, ++from) { for(; first != last; ++first, ++from) {
emplace_element(*first, true, *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()}}; 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: private:
compressed_pair<container_type, allocator_type> packed; container_type payload;
}; };
/*! @copydoc basic_storage */ /*! @copydoc basic_storage */
template<typename Type, typename Entity, typename Allocator> template<typename Type, typename Entity, typename Allocator>
class basic_storage<Type, Entity, Allocator, std::enable_if_t<ignore_as_empty_v<Type>>> class basic_storage<Type, Entity, Allocator, std::enable_if_t<component_traits<Type>::page_size == 0u>>
: public basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>> { : public basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>> {
using alloc_traits = std::allocator_traits<Allocator>; using alloc_traits = std::allocator_traits<Allocator>;
static_assert(std::is_same_v<typename alloc_traits::value_type, Type>, "Invalid value type"); static_assert(std::is_same_v<typename alloc_traits::value_type, Type>, "Invalid value type");
using underlying_type = basic_sparse_set<Entity, typename alloc_traits::template rebind_alloc<Entity>>;
using comp_traits = component_traits<Type>;
public: public:
/*! @brief Base type. */ /*! @brief Base type. */
using base_type = underlying_type; using base_type = basic_sparse_set<Entity, typename alloc_traits::template rebind_alloc<Entity>>;
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Type of the objects assigned to entities. */ /*! @brief Type of the objects assigned to entities. */
using value_type = Type; using value_type = Type;
/*! @brief Component traits. */
using traits_type = component_traits<value_type>;
/*! @brief Underlying entity identifier. */ /*! @brief Underlying entity identifier. */
using entity_type = Entity; using entity_type = Entity;
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
using size_type = std::size_t; using size_type = std::size_t;
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Extended iterable storage proxy. */ /*! @brief Extended iterable storage proxy. */
using iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::iterator>>; using iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::iterator>>;
/*! @brief Constant extended iterable storage proxy. */ /*! @brief Constant extended iterable storage proxy. */
using const_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_iterator>>; using const_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_iterator>>;
/*! @brief Extended reverse iterable storage proxy. */
using reverse_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::reverse_iterator>>;
/*! @brief Constant extended reverse iterable storage proxy. */
using const_reverse_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_reverse_iterator>>;
/*! @brief Default constructor. */ /*! @brief Default constructor. */
basic_storage() basic_storage()
@ -781,7 +813,7 @@ public:
* @param allocator The allocator to use. * @param allocator The allocator to use.
*/ */
explicit basic_storage(const allocator_type &allocator) explicit basic_storage(const allocator_type &allocator)
: base_type{type_id<value_type>(), deletion_policy{comp_traits::in_place_delete}, allocator} {} : base_type{type_id<value_type>(), deletion_policy{traits_type::in_place_delete}, allocator} {}
/** /**
* @brief Move constructor. * @brief Move constructor.
@ -896,6 +928,325 @@ public:
[[nodiscard]] const_iterable each() const noexcept { [[nodiscard]] const_iterable each() const noexcept {
return {internal::extended_storage_iterator{base_type::cbegin()}, internal::extended_storage_iterator{base_type::cend()}}; return {internal::extended_storage_iterator{base_type::cbegin()}, internal::extended_storage_iterator{base_type::cend()}};
} }
/**
* @brief Returns a reverse iterable object to use to _visit_ a storage.
*
* @sa each
*
* @return A reverse iterable object to use to _visit_ the storage.
*/
[[nodiscard]] reverse_iterable reach() noexcept {
return {internal::extended_storage_iterator{base_type::rbegin()}, internal::extended_storage_iterator{base_type::rend()}};
}
/*! @copydoc reach */
[[nodiscard]] const_reverse_iterable reach() const noexcept {
return {internal::extended_storage_iterator{base_type::crbegin()}, internal::extended_storage_iterator{base_type::crend()}};
}
};
/**
* @brief Swap-only entity storage specialization.
* @tparam Entity A valid entity type.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Entity, typename Allocator>
class basic_storage<Entity, Entity, Allocator>
: public basic_sparse_set<Entity, Allocator> {
using alloc_traits = std::allocator_traits<Allocator>;
static_assert(std::is_same_v<typename alloc_traits::value_type, Entity>, "Invalid value type");
using underlying_type = basic_sparse_set<Entity, typename alloc_traits::template rebind_alloc<Entity>>;
using underlying_iterator = typename underlying_type::basic_iterator;
using local_traits_type = entt_traits<Entity>;
auto entity_at(const std::size_t pos) const noexcept {
ENTT_ASSERT(pos < local_traits_type::to_entity(null), "Invalid element");
return local_traits_type::combine(static_cast<typename local_traits_type::entity_type>(pos), {});
}
private:
void swap_or_move([[maybe_unused]] const std::size_t lhs, [[maybe_unused]] const std::size_t rhs) override {
ENTT_ASSERT(((lhs < length) + (rhs < length)) != 1u, "Cross swapping is not supported");
}
protected:
/**
* @brief Erases entities from a storage.
* @param first An iterator to the first element of the range of entities.
* @param last An iterator past the last element of the range of entities.
*/
void pop(underlying_iterator first, underlying_iterator last) override {
for(; first != last; ++first) {
if(const auto pos = base_type::index(*first); pos < length) {
base_type::bump(local_traits_type::next(*first));
if(pos != --length) {
base_type::swap_at(pos, length);
}
}
}
}
/*! @brief Erases all entities of a sparse set. */
void pop_all() override {
length = 0u;
base_type::pop_all();
}
/**
* @brief Assigns an entity to a storage.
* @param hint A valid identifier.
* @return Iterator pointing to the emplaced element.
*/
underlying_iterator try_emplace(const Entity hint, const bool, const void *) override {
return base_type::find(emplace(hint));
}
public:
/*! @brief Base type. */
using base_type = basic_sparse_set<Entity, Allocator>;
/*! @brief Type of the objects assigned to entities. */
using value_type = Entity;
/*! @brief Component traits. */
using traits_type = component_traits<value_type>;
/*! @brief Underlying entity identifier. */
using entity_type = Entity;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Extended iterable storage proxy. */
using iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::iterator>>;
/*! @brief Constant extended iterable storage proxy. */
using const_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_iterator>>;
/*! @brief Extended reverse iterable storage proxy. */
using reverse_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::reverse_iterator>>;
/*! @brief Constant extended reverse iterable storage proxy. */
using const_reverse_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_reverse_iterator>>;
/*! @brief Default constructor. */
basic_storage()
: basic_storage{allocator_type{}} {
}
/**
* @brief Constructs an empty container with a given allocator.
* @param allocator The allocator to use.
*/
explicit basic_storage(const allocator_type &allocator)
: base_type{type_id<value_type>(), deletion_policy::swap_and_pop, allocator},
length{} {}
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
basic_storage(basic_storage &&other) noexcept
: base_type{std::move(other)},
length{std::exchange(other.length, size_type{})} {}
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
basic_storage(basic_storage &&other, const allocator_type &allocator) noexcept
: base_type{std::move(other), allocator},
length{std::exchange(other.length, size_type{})} {}
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This storage.
*/
basic_storage &operator=(basic_storage &&other) noexcept {
base_type::operator=(std::move(other));
length = std::exchange(other.length, size_type{});
return *this;
}
/**
* @brief Returns the object assigned to an entity, that is `void`.
*
* @warning
* Attempting to use an entity that doesn't belong to the storage results in
* undefined behavior.
*
* @param entt A valid identifier.
*/
void get([[maybe_unused]] const entity_type entt) const noexcept {
ENTT_ASSERT(base_type::index(entt) < length, "The requested entity is not a live one");
}
/**
* @brief Returns an empty tuple.
*
* @warning
* Attempting to use an entity that doesn't belong to the storage results in
* undefined behavior.
*
* @param entt A valid identifier.
* @return Returns an empty tuple.
*/
[[nodiscard]] std::tuple<> get_as_tuple([[maybe_unused]] const entity_type entt) const noexcept {
ENTT_ASSERT(base_type::index(entt) < length, "The requested entity is not a live one");
return std::tuple{};
}
/**
* @brief Exchanges the contents with those of a given storage.
* @param other Storage to exchange the content with.
*/
void swap(basic_storage &other) {
using std::swap;
base_type::swap(other);
swap(length, other.length);
}
/**
* @brief Creates a new identifier or recycles a destroyed one.
* @return A valid identifier.
*/
entity_type emplace() {
if(length == base_type::size()) {
return *base_type::try_emplace(entity_at(length++), true);
}
return base_type::operator[](length++);
}
/**
* @brief Creates a new identifier or recycles a destroyed one.
*
* If the requested identifier isn't in use, the suggested one is used.
* Otherwise, a new identifier is returned.
*
* @param hint Required identifier.
* @return A valid identifier.
*/
entity_type emplace(const entity_type hint) {
if(hint == null || hint == tombstone) {
return emplace();
} else if(const auto curr = local_traits_type::construct(local_traits_type::to_entity(hint), base_type::current(hint)); curr == tombstone) {
const auto pos = static_cast<size_type>(local_traits_type::to_entity(hint));
while(!(pos < base_type::size())) {
base_type::try_emplace(entity_at(base_type::size()), true);
}
base_type::swap_at(pos, length++);
} else if(const auto idx = base_type::index(curr); idx < length) {
return emplace();
} else {
base_type::swap_at(idx, length++);
}
base_type::bump(hint);
return hint;
}
/**
* @brief Updates a given identifier.
* @tparam Func Types of the function objects to invoke.
* @param entt A valid identifier.
* @param func Valid function objects.
*/
template<typename... Func>
void patch([[maybe_unused]] const entity_type entt, Func &&...func) {
ENTT_ASSERT(base_type::contains(entt), "Storage does not contain entity");
(std::forward<Func>(func)(), ...);
}
/**
* @brief Assigns each element in a range an identifier.
* @tparam It Type of mutable forward iterator.
* @param first An iterator to the first element of the range to generate.
* @param last An iterator past the last element of the range to generate.
*/
template<typename It>
void insert(It first, It last) {
for(const auto sz = base_type::size(); first != last && length != sz; ++first, ++length) {
*first = base_type::operator[](length);
}
for(; first != last; ++first) {
*first = *base_type::try_emplace(entity_at(length++), true);
}
}
/**
* @brief Makes all elements in a range contiguous.
* @tparam It Type of forward iterator.
* @param first An iterator to the first element of the range to pack.
* @param last An iterator past the last element of the range to pack.
* @return The number of elements within the newly created range.
*/
template<typename It>
size_type pack(It first, It last) {
size_type len = length;
for(; first != last; ++first, --len) {
const auto pos = base_type::index(*first);
ENTT_ASSERT(pos < length, "Invalid element");
base_type::swap_at(pos, static_cast<size_type>(len - 1u));
}
return (length - len);
}
/**
* @brief Returns the number of elements considered still in use.
* @return The number of elements considered still in use.
*/
[[nodiscard]] size_type in_use() const noexcept {
return length;
}
/**
* @brief Sets the number of elements considered still in use.
* @param len The number of elements considered still in use.
*/
void in_use(const size_type len) noexcept {
ENTT_ASSERT(!(len > base_type::size()), "Invalid length");
length = len;
}
/**
* @brief Returns an iterable object to use to _visit_ a storage.
*
* The iterable object returns a tuple that contains the current entity.
*
* @return An iterable object to use to _visit_ the storage.
*/
[[nodiscard]] iterable each() noexcept {
return {internal::extended_storage_iterator{base_type::end() - length}, internal::extended_storage_iterator{base_type::end()}};
}
/*! @copydoc each */
[[nodiscard]] const_iterable each() const noexcept {
return {internal::extended_storage_iterator{base_type::cend() - length}, internal::extended_storage_iterator{base_type::cend()}};
}
/**
* @brief Returns a reverse iterable object to use to _visit_ a storage.
*
* @sa each
*
* @return A reverse iterable object to use to _visit_ the storage.
*/
[[nodiscard]] reverse_iterable reach() noexcept {
return {internal::extended_storage_iterator{base_type::rbegin()}, internal::extended_storage_iterator{base_type::rbegin() + length}};
}
/*! @copydoc reach */
[[nodiscard]] const_reverse_iterable reach() const noexcept {
return {internal::extended_storage_iterator{base_type::crbegin()}, internal::extended_storage_iterator{base_type::crbegin() + length}};
}
private:
size_type length;
}; };
} // namespace entt } // namespace entt

View File

@ -1,236 +0,0 @@
#ifndef ENTT_ENTITY_SIGH_STORAGE_MIXIN_HPP
#define ENTT_ENTITY_SIGH_STORAGE_MIXIN_HPP
#include <utility>
#include "../config/config.h"
#include "../core/any.hpp"
#include "../signal/sigh.hpp"
#include "fwd.hpp"
namespace entt {
/**
* @brief Mixin type used to add signal support to storage types.
*
* The function type of a listener is equivalent to:
*
* @code{.cpp}
* void(basic_registry<entity_type> &, entity_type);
* @endcode
*
* This applies to all signals made available.
*
* @tparam Type The type of the underlying storage.
*/
template<typename Type>
class sigh_storage_mixin final: public Type {
using basic_registry_type = basic_registry<typename Type::entity_type, typename Type::base_type::allocator_type>;
using sigh_type = sigh<void(basic_registry_type &, const typename Type::entity_type), typename Type::allocator_type>;
using basic_iterator = typename Type::basic_iterator;
void pop(basic_iterator first, basic_iterator last) override {
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
for(; first != last; ++first) {
const auto entt = *first;
destruction.publish(*owner, entt);
const auto it = Type::find(entt);
Type::pop(it, it + 1u);
}
}
basic_iterator try_emplace(const typename basic_registry_type::entity_type entt, const bool force_back, const void *value) final {
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
Type::try_emplace(entt, force_back, value);
construction.publish(*owner, entt);
return Type::find(entt);
}
public:
/*! @brief Allocator type. */
using allocator_type = typename Type::allocator_type;
/*! @brief Underlying entity identifier. */
using entity_type = typename Type::entity_type;
/*! @brief Expected registry type. */
using registry_type = basic_registry_type;
/*! @brief Default constructor. */
sigh_storage_mixin()
: sigh_storage_mixin{allocator_type{}} {}
/**
* @brief Constructs an empty storage with a given allocator.
* @param allocator The allocator to use.
*/
explicit sigh_storage_mixin(const allocator_type &allocator)
: Type{allocator},
owner{},
construction{allocator},
destruction{allocator},
update{allocator} {}
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
sigh_storage_mixin(sigh_storage_mixin &&other) noexcept
: Type{std::move(other)},
owner{other.owner},
construction{std::move(other.construction)},
destruction{std::move(other.destruction)},
update{std::move(other.update)} {}
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
sigh_storage_mixin(sigh_storage_mixin &&other, const allocator_type &allocator) noexcept
: Type{std::move(other), allocator},
owner{other.owner},
construction{std::move(other.construction), allocator},
destruction{std::move(other.destruction), allocator},
update{std::move(other.update), allocator} {}
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This storage.
*/
sigh_storage_mixin &operator=(sigh_storage_mixin &&other) noexcept {
Type::operator=(std::move(other));
owner = other.owner;
construction = std::move(other.construction);
destruction = std::move(other.destruction);
update = std::move(other.update);
return *this;
}
/**
* @brief Exchanges the contents with those of a given storage.
* @param other Storage to exchange the content with.
*/
void swap(sigh_storage_mixin &other) {
using std::swap;
Type::swap(other);
swap(owner, other.owner);
swap(construction, other.construction);
swap(destruction, other.destruction);
swap(update, other.update);
}
/**
* @brief Returns a sink object.
*
* The sink returned by this function can be used to receive notifications
* whenever a new instance is created and assigned to an entity.<br/>
* Listeners are invoked after the object has been assigned to the entity.
*
* @sa sink
*
* @return A temporary sink object.
*/
[[nodiscard]] auto on_construct() noexcept {
return sink{construction};
}
/**
* @brief Returns a sink object.
*
* The sink returned by this function can be used to receive notifications
* whenever an instance is explicitly updated.<br/>
* Listeners are invoked after the object has been updated.
*
* @sa sink
*
* @return A temporary sink object.
*/
[[nodiscard]] auto on_update() noexcept {
return sink{update};
}
/**
* @brief Returns a sink object.
*
* The sink returned by this function can be used to receive notifications
* whenever an instance is removed from an entity and thus destroyed.<br/>
* Listeners are invoked before the object has been removed from the entity.
*
* @sa sink
*
* @return A temporary sink object.
*/
[[nodiscard]] auto on_destroy() noexcept {
return sink{destruction};
}
/**
* @brief Assigns entities to a storage.
* @tparam Args Types of arguments to use to construct the object.
* @param entt A valid identifier.
* @param args Parameters to use to initialize the object.
* @return A reference to the newly created object.
*/
template<typename... Args>
decltype(auto) emplace(const entity_type entt, Args &&...args) {
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
Type::emplace(entt, std::forward<Args>(args)...);
construction.publish(*owner, entt);
return this->get(entt);
}
/**
* @brief Patches the given instance for an entity.
* @tparam Func Types of the function objects to invoke.
* @param entt A valid identifier.
* @param func Valid function objects.
* @return A reference to the patched instance.
*/
template<typename... Func>
decltype(auto) patch(const entity_type entt, Func &&...func) {
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
Type::patch(entt, std::forward<Func>(func)...);
update.publish(*owner, entt);
return this->get(entt);
}
/**
* @brief Assigns entities to a storage.
* @tparam It Type of input iterator.
* @tparam Args Types of arguments to use to construct the objects assigned
* to the entities.
* @param first An iterator to the first element of the range of entities.
* @param last An iterator past the last element of the range of entities.
* @param args Parameters to use to initialize the objects assigned to the
* entities.
*/
template<typename It, typename... Args>
void insert(It first, It last, Args &&...args) {
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
Type::insert(first, last, std::forward<Args>(args)...);
for(auto it = construction.empty() ? last : first; it != last; ++it) {
construction.publish(*owner, *it);
}
}
/**
* @brief Forwards variables to derived classes, if any.
* @param value A variable wrapped in an opaque container.
*/
void bind(any value) noexcept final {
auto *reg = any_cast<basic_registry_type>(&value);
owner = reg ? reg : owner;
Type::bind(std::move(value));
}
private:
basic_registry_type *owner;
sigh_type construction;
sigh_type destruction;
sigh_type update;
};
} // namespace entt
#endif

View File

@ -9,11 +9,8 @@
#include "../config/config.h" #include "../config/config.h"
#include "../core/iterator.hpp" #include "../core/iterator.hpp"
#include "../core/type_traits.hpp" #include "../core/type_traits.hpp"
#include "component.hpp"
#include "entity.hpp" #include "entity.hpp"
#include "fwd.hpp" #include "fwd.hpp"
#include "sparse_set.hpp"
#include "storage.hpp"
namespace entt { namespace entt {
@ -24,6 +21,24 @@ namespace entt {
namespace internal { namespace internal {
template<typename... Args, typename Type, std::size_t N>
[[nodiscard]] auto filter_as_tuple(const std::array<const Type *, N> &filter) noexcept {
return std::apply([](const auto *...curr) { return std::make_tuple(static_cast<Args *>(const_cast<constness_as_t<Type, Args> *>(curr))...); }, filter);
}
template<typename Type, std::size_t N>
[[nodiscard]] auto none_of(const std::array<const Type *, N> &filter, const typename Type::entity_type entt) noexcept {
return std::apply([entt](const auto *...curr) { return (!(curr && curr->contains(entt)) && ...); }, filter);
}
template<typename... Get, typename... Exclude, std::size_t... Index>
[[nodiscard]] auto view_pack(const std::tuple<Get *...> value, const std::tuple<Exclude *...> excl, std::index_sequence<Index...>) {
const auto pools = std::tuple_cat(value, excl);
basic_view<get_t<Get...>, exclude_t<Exclude...>> elem{};
(((std::get<Index>(pools) != nullptr) ? elem.template storage<Index>(*std::get<Index>(pools)) : void()), ...);
return elem;
}
template<typename Type, std::size_t Get, std::size_t Exclude> template<typename Type, std::size_t Get, std::size_t Exclude>
class view_iterator final { class view_iterator final {
using iterator_type = typename Type::const_iterator; using iterator_type = typename Type::const_iterator;
@ -31,7 +46,7 @@ class view_iterator final {
[[nodiscard]] bool valid() const noexcept { [[nodiscard]] bool valid() const noexcept {
return ((Get != 0u) || (*it != tombstone)) 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) && ...); }, pools)
&& std::apply([entt = *it](const auto *...curr) { return (!curr->contains(entt) && ...); }, filter); && none_of(filter, *it);
} }
public: public:
@ -47,11 +62,11 @@ public:
pools{}, pools{},
filter{} {} filter{} {}
view_iterator(iterator_type curr, iterator_type to, std::array<const Type *, Get> all_of, std::array<const Type *, Exclude> none_of) noexcept view_iterator(iterator_type curr, iterator_type to, std::array<const Type *, Get> value, std::array<const Type *, Exclude> excl) noexcept
: it{curr}, : it{curr},
last{to}, last{to},
pools{all_of}, pools{value},
filter{none_of} { filter{excl} {
while(it != last && !valid()) { while(it != last && !valid()) {
++it; ++it;
} }
@ -97,6 +112,7 @@ template<typename LhsType, auto... LhsArgs, typename RhsType, auto... RhsArgs>
template<typename It, typename... Type> template<typename It, typename... Type>
struct extended_view_iterator final { struct extended_view_iterator final {
using iterator_type = It;
using difference_type = std::ptrdiff_t; using difference_type = std::ptrdiff_t;
using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval<It>()), std::declval<Type>().get_as_tuple({})...)); using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval<It>()), std::declval<Type>().get_as_tuple({})...));
using pointer = input_iterator_pointer<value_type>; using pointer = input_iterator_pointer<value_type>;
@ -107,9 +123,9 @@ struct extended_view_iterator final {
: it{}, : it{},
pools{} {} pools{} {}
extended_view_iterator(It from, std::tuple<Type *...> storage) extended_view_iterator(It from, std::tuple<Type *...> value)
: it{from}, : it{from},
pools{storage} {} pools{value} {}
extended_view_iterator &operator++() noexcept { extended_view_iterator &operator++() noexcept {
return ++it, *this; return ++it, *this;
@ -128,6 +144,10 @@ struct extended_view_iterator final {
return operator*(); return operator*();
} }
[[nodiscard]] constexpr iterator_type base() const noexcept {
return it;
}
template<typename... Lhs, typename... Rhs> template<typename... Lhs, typename... Rhs>
friend bool constexpr operator==(const extended_view_iterator<Lhs...> &, const extended_view_iterator<Rhs...> &) noexcept; friend bool constexpr operator==(const extended_view_iterator<Lhs...> &, const extended_view_iterator<Rhs...> &) noexcept;
@ -179,31 +199,33 @@ class basic_view;
* or removed from it). * or removed from it).
* * The entity currently pointed is destroyed. * * The entity currently pointed is destroyed.
* *
* In all other cases, modifying the pools iterated by the view in any way * In all other cases, modifying the storage iterated by the view in any way
* invalidates all the iterators and using them results in undefined behavior. * invalidates all the iterators.
* *
* @tparam Get Types of storage iterated by the view. * @tparam Get Types of storage iterated by the view.
* @tparam Exclude Types of storage used to filter the view. * @tparam Exclude Types of storage used to filter the view.
*/ */
template<typename... Get, typename... Exclude> template<typename... Get, typename... Exclude>
class basic_view<get_t<Get...>, exclude_t<Exclude...>> { class basic_view<get_t<Get...>, exclude_t<Exclude...>> {
using underlying_type = std::common_type_t<typename Get::entity_type..., typename Exclude::entity_type...>; static constexpr auto offset = sizeof...(Get);
using basic_common_type = std::common_type_t<typename Get::base_type..., typename Exclude::base_type...>; using base_type = std::common_type_t<typename Get::base_type..., typename Exclude::base_type...>;
using underlying_type = typename base_type::entity_type;
template<typename, typename, typename> template<typename, typename, typename>
friend class basic_view; friend class basic_view;
template<typename Type> template<typename Type>
static constexpr std::size_t index_of = type_list_index_v<std::remove_const_t<Type>, type_list<typename Get::value_type...>>; static constexpr std::size_t index_of = type_list_index_v<std::remove_const_t<Type>, type_list<typename Get::value_type..., typename Exclude::value_type...>>;
[[nodiscard]] auto opaque_check_set() const noexcept { [[nodiscard]] auto opaque_check_set() const noexcept {
std::array<const base_type *, sizeof...(Get) - 1u> other{}; std::array<const common_type *, sizeof...(Get) - 1u> other{};
std::apply([&other, pos = 0u, view = view](const auto *...curr) mutable { ((curr == view ? void() : void(other[pos++] = curr)), ...); }, pools); std::apply([&other, pos = 0u, view = view](const auto *...curr) mutable { ((curr == view ? void() : void(other[pos++] = curr)), ...); }, pools);
return other; return other;
} }
[[nodiscard]] auto filter_as_array() const noexcept { void unchecked_refresh() noexcept {
return std::apply([](const auto *...curr) { return std::array<const base_type *, sizeof...(Exclude)>{curr...}; }, filter); view = std::get<0>(pools);
std::apply([this](auto *, auto *...other) { ((this->view = other->size() < this->view->size() ? other : this->view), ...); }, pools);
} }
template<std::size_t Curr, std::size_t Other, typename... Args> template<std::size_t Curr, std::size_t Other, typename... Args>
@ -211,18 +233,14 @@ class basic_view<get_t<Get...>, exclude_t<Exclude...>> {
if constexpr(Curr == Other) { if constexpr(Curr == Other) {
return std::forward_as_tuple(std::get<Args>(curr)...); return std::forward_as_tuple(std::get<Args>(curr)...);
} else { } else {
return storage<Other>().get_as_tuple(std::get<0>(curr)); return std::get<Other>(pools)->get_as_tuple(std::get<0>(curr));
} }
} }
[[nodiscard]] auto reject(const underlying_type entt) const noexcept {
return std::apply([entt](const auto *...curr) { return (curr->contains(entt) || ...); }, filter);
}
template<std::size_t Curr, typename Func, std::size_t... Index> template<std::size_t Curr, typename Func, std::size_t... Index>
void each(Func &func, std::index_sequence<Index...>) const { void each(Func &func, std::index_sequence<Index...>) const {
for(const auto curr: storage<Curr>().each()) { for(const auto curr: std::get<Curr>(pools)->each()) {
if(const auto entt = std::get<0>(curr); ((sizeof...(Get) != 1u) || (entt != tombstone)) && ((Curr == Index || storage<Index>().contains(entt)) && ...) && !reject(entt)) { if(const auto entt = std::get<0>(curr); ((sizeof...(Get) != 1u) || (entt != tombstone)) && ((Curr == Index || std::get<Index>(pools)->contains(entt)) && ...) && internal::none_of(filter, entt)) {
if constexpr(is_applicable_v<Func, decltype(std::tuple_cat(std::tuple<entity_type>{}, std::declval<basic_view>().get({})))>) { if constexpr(is_applicable_v<Func, decltype(std::tuple_cat(std::tuple<entity_type>{}, std::declval<basic_view>().get({})))>) {
std::apply(func, std::tuple_cat(std::make_tuple(entt), dispatch_get<Curr, Index>(curr)...)); std::apply(func, std::tuple_cat(std::make_tuple(entt), dispatch_get<Curr, Index>(curr)...));
} else { } else {
@ -234,7 +252,7 @@ class basic_view<get_t<Get...>, exclude_t<Exclude...>> {
template<typename Func, std::size_t... Index> template<typename Func, std::size_t... Index>
void pick_and_each(Func &func, std::index_sequence<Index...> seq) const { void pick_and_each(Func &func, std::index_sequence<Index...> seq) const {
((&storage<Index>() == view ? each<Index>(func, seq) : void()), ...); ((std::get<Index>(pools) == view ? each<Index>(func, seq) : void()), ...);
} }
public: public:
@ -243,9 +261,9 @@ public:
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
using size_type = std::size_t; using size_type = std::size_t;
/*! @brief Common type among all storage types. */ /*! @brief Common type among all storage types. */
using base_type = basic_common_type; using common_type = base_type;
/*! @brief Bidirectional iterator type. */ /*! @brief Bidirectional iterator type. */
using iterator = internal::view_iterator<base_type, sizeof...(Get) - 1u, sizeof...(Exclude)>; using iterator = internal::view_iterator<common_type, sizeof...(Get) - 1u, sizeof...(Exclude)>;
/*! @brief Iterable view type. */ /*! @brief Iterable view type. */
using iterable = iterable_adaptor<internal::extended_view_iterator<iterator, Get...>>; using iterable = iterable_adaptor<internal::extended_view_iterator<iterator, Get...>>;
@ -258,12 +276,14 @@ public:
/** /**
* @brief Constructs a multi-type view from a set of storage classes. * @brief Constructs a multi-type view from a set of storage classes.
* @param value The storage for the types to iterate. * @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...}, : pools{&value...},
filter{&exclude...}, filter{&excl...},
view{[](const base_type *first, const auto *...other) { ((first = other->size() < first->size() ? other : first), ...); return first; }(&value...)} {} view{} {
unchecked_refresh();
}
/** /**
* @brief Constructs a multi-type view from a set of storage classes. * @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. * @param excl The storage for the types used to filter the view.
*/ */
basic_view(std::tuple<Get &...> value, std::tuple<Exclude &...> excl = {}) noexcept basic_view(std::tuple<Get &...> value, std::tuple<Exclude &...> excl = {}) noexcept
: pools{std::apply([](auto &...curr) { return std::make_tuple(&curr...); }, value)}, : basic_view{std::make_from_tuple<basic_view>(std::tuple_cat(value, excl))} {}
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. * @brief Forces a view to use a given component to drive iterations
* @tparam Type Type of component used to drive the iteration. * @tparam Type Type of component to use to drive iterations.
* @return A new view driven by the given component in its iterations.
*/ */
template<typename Type> template<typename Type>
[[nodiscard]] basic_view use() const noexcept { void use() noexcept {
return use<index_of<Type>>(); use<index_of<Type>>();
} }
/** /**
* @brief Creates a new view driven by a given component in its iterations. * @brief Forces a view to use a given component to drive iterations
* @tparam Index Index of the component used to drive the iteration. * @tparam Index Index of the component to use to drive iterations.
* @return A new view driven by the given component in its iterations.
*/ */
template<std::size_t Index> template<std::size_t Index>
[[nodiscard]] basic_view use() const noexcept { void use() noexcept {
basic_view other{*this}; if(view) {
other.view = &storage<Index>(); view = std::get<Index>(pools);
return other; }
}
/*! @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. * @brief Returns the leading storage of a view, if any.
* @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. * @return The leading storage of the view.
*/ */
[[nodiscard]] const base_type &handle() const noexcept { [[nodiscard]] const common_type *handle() const noexcept {
return *view; return view;
} }
/** /**
* @brief Returns the storage for a given component type. * @brief Returns the storage for a given component type, if any.
* @tparam Comp Type of component of which to return the storage. * @tparam Type Type of component of which to return the storage.
* @return The storage for the given component type. * @return The storage for the given component type.
*/ */
template<typename Type> template<typename Type>
[[nodiscard]] decltype(auto) storage() const noexcept { [[nodiscard]] auto *storage() const noexcept {
return storage<index_of<Type>>(); return storage<index_of<Type>>();
} }
/** /**
* @brief Returns the storage for a given index. * @brief Returns the storage for a given index, if any.
* @tparam Index Index of the storage to return. * @tparam Index Index of the storage to return.
* @return The storage for the given index. * @return The storage for the given index.
*/ */
template<std::size_t Index> template<std::size_t Index>
[[nodiscard]] decltype(auto) storage() const noexcept { [[nodiscard]] auto *storage() const noexcept {
return *std::get<Index>(pools); if constexpr(Index < offset) {
return std::get<Index>(pools);
} else {
return std::get<Index - offset>(internal::filter_as_tuple<Exclude...>(filter));
}
}
/**
* @brief Assigns a storage to a view.
* @tparam Type Type of storage to assign to the view.
* @param elem A storage to assign to the view.
*/
template<typename Type>
void storage(Type &elem) noexcept {
storage<index_of<typename Type::value_type>>(elem);
}
/**
* @brief Assigns a storage to a view.
* @tparam Index Index of the storage to assign to the view.
* @tparam Type Type of storage to assign to the view.
* @param elem A storage to assign to the view.
*/
template<std::size_t Index, typename Type>
void storage(Type &elem) noexcept {
if constexpr(Index < offset) {
std::get<Index>(pools) = &elem;
refresh();
} else {
std::get<Index - offset>(filter) = &elem;
}
} }
/** /**
@ -338,32 +383,26 @@ public:
* @return Estimated number of entities iterated by the view. * @return Estimated number of entities iterated by the view.
*/ */
[[nodiscard]] size_type size_hint() const noexcept { [[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. * @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 * If the view is empty, the returned iterator will be equal to `end()`.
* is empty, the returned iterator will be equal to `end()`.
* *
* @return An iterator to the first entity of the view. * @return An iterator to the first entity of the view.
*/ */
[[nodiscard]] iterator begin() const noexcept { [[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. * @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. * @return An iterator to the entity following the last entity of the view.
*/ */
[[nodiscard]] iterator end() const noexcept { [[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,11 +421,15 @@ public:
* otherwise. * otherwise.
*/ */
[[nodiscard]] entity_type back() const noexcept { [[nodiscard]] entity_type back() const noexcept {
if(view) {
auto it = view->rbegin(); auto it = view->rbegin();
for(const auto last = view->rend(); it != last && !contains(*it); ++it) {} for(const auto last = view->rend(); it != last && !contains(*it); ++it) {}
return it == view->rend() ? null : *it; return it == view->rend() ? null : *it;
} }
return null;
}
/** /**
* @brief Finds an entity. * @brief Finds an entity.
* @param entt A valid identifier. * @param entt A valid identifier.
@ -394,7 +437,7 @@ public:
* iterator otherwise. * iterator otherwise.
*/ */
[[nodiscard]] iterator find(const entity_type entt) const noexcept { [[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. * @brief Checks if a view is fully initialized.
* @return True if the view is properly initialized, false otherwise. * @return True if the view is fully initialized, false otherwise.
*/ */
[[nodiscard]] explicit operator bool() const noexcept { [[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. * @return True if the view contains the given entity, false otherwise.
*/ */
[[nodiscard]] bool contains(const entity_type entt) const noexcept { [[nodiscard]] bool contains(const entity_type entt) const noexcept {
return std::apply([entt](const auto *...curr) { return (curr->contains(entt) && ...); }, pools) return view && std::apply([entt](const auto *...curr) { return (curr->contains(entt) && ...); }, pools) && internal::none_of(filter, entt);
&& std::apply([entt](const auto *...curr) { return (!curr->contains(entt) && ...); }, filter);
} }
/** /**
@ -431,39 +474,33 @@ public:
* Attempting to use an entity that doesn't belong to the view results in * Attempting to use an entity that doesn't belong to the view results in
* undefined behavior. * 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. * @param entt A valid identifier.
* @return The components assigned to the entity. * @return The components assigned to the entity.
*/ */
template<typename... Type> template<typename Type, typename... Other>
[[nodiscard]] decltype(auto) get(const entity_type entt) const { [[nodiscard]] decltype(auto) get(const entity_type entt) const {
if constexpr(sizeof...(Type) == 0) { return get<index_of<Type>, index_of<Other>...>(entt);
}
/**
* @brief Returns the components assigned to the given entity.
*
* @sa get
*
* @tparam Index Indexes of the components to get.
* @param entt A valid identifier.
* @return The components assigned to the entity.
*/
template<std::size_t... Index>
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
if constexpr(sizeof...(Index) == 0) {
return std::apply([entt](auto *...curr) { return std::tuple_cat(curr->get_as_tuple(entt)...); }, pools); return std::apply([entt](auto *...curr) { return std::tuple_cat(curr->get_as_tuple(entt)...); }, pools);
} else if constexpr(sizeof...(Type) == 1) { } else if constexpr(sizeof...(Index) == 1) {
return (storage<index_of<Type>>().get(entt), ...); return (std::get<Index>(pools)->get(entt), ...);
} else { } else {
return std::tuple_cat(storage<index_of<Type>>().get_as_tuple(entt)...); return std::tuple_cat(std::get<Index>(pools)->get_as_tuple(entt)...);
}
}
/**
* @brief Returns the components assigned to the given entity.
*
* @warning
* Attempting to use an entity that doesn't belong to the view results in
* undefined behavior.
*
* @tparam First Index of a component to get.
* @tparam Other Indexes of other components to get.
* @param entt A valid identifier.
* @return The components assigned to the entity.
*/
template<std::size_t First, std::size_t... Other>
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
if constexpr(sizeof...(Other) == 0) {
return storage<First>().get(entt);
} else {
return std::tuple_cat(storage<First>().get_as_tuple(entt), storage<Other>().get_as_tuple(entt)...);
} }
} }
@ -487,7 +524,7 @@ public:
*/ */
template<typename Func> template<typename Func>
void each(Func func) const { void each(Func func) const {
pick_and_each(func, std::index_sequence_for<Get...>{}); view ? pick_and_each(func, std::index_sequence_for<Get...>{}) : void();
} }
/** /**
@ -512,14 +549,16 @@ public:
*/ */
template<typename... OGet, typename... OExclude> template<typename... OGet, typename... OExclude>
[[nodiscard]] auto operator|(const basic_view<get_t<OGet...>, exclude_t<OExclude...>> &other) const noexcept { [[nodiscard]] auto operator|(const basic_view<get_t<OGet...>, exclude_t<OExclude...>> &other) const noexcept {
return std::make_from_tuple<basic_view<get_t<Get..., OGet...>, exclude_t<Exclude..., OExclude...>>>( return internal::view_pack(
std::apply([](auto *...curr) { return std::forward_as_tuple(*curr...); }, std::tuple_cat(pools, other.pools, filter, other.filter))); std::tuple_cat(pools, other.pools),
std::tuple_cat(internal::filter_as_tuple<Exclude...>(filter), internal::filter_as_tuple<OExclude...>(other.filter)),
std::index_sequence_for<Get..., OGet..., Exclude..., OExclude...>{});
} }
private: private:
std::tuple<Get *...> pools; std::tuple<Get *...> pools;
std::tuple<Exclude *...> filter; std::array<const common_type *, sizeof...(Exclude)> filter;
const base_type *view; const common_type *view;
}; };
/** /**
@ -538,13 +577,13 @@ private:
* or removed from it). * or removed from it).
* * The entity currently pointed is destroyed. * * The entity currently pointed is destroyed.
* *
* In all other cases, modifying the pool iterated by the view in any way * In all other cases, modifying the storage iterated by the view in any way
* invalidates all the iterators and using them results in undefined behavior. * invalidates all the iterators.
* *
* @tparam Get Type of storage iterated by the view. * @tparam Get Type of storage iterated by the view.
*/ */
template<typename Get> template<typename Get>
class basic_view<get_t<Get>, exclude_t<>, std::void_t<std::enable_if_t<!component_traits<typename Get::value_type>::in_place_delete>>> { class basic_view<get_t<Get>, exclude_t<>, std::void_t<std::enable_if_t<!Get::traits_type::in_place_delete>>> {
template<typename, typename, typename> template<typename, typename, typename>
friend class basic_view; friend class basic_view;
@ -554,62 +593,81 @@ public:
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
using size_type = std::size_t; using size_type = std::size_t;
/*! @brief Common type among all storage types. */ /*! @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. */ /*! @brief Random access iterator type. */
using iterator = typename base_type::iterator; using iterator = typename common_type::iterator;
/*! @brief Reversed iterator type. */ /*! @brief Reversed iterator type. */
using reverse_iterator = typename base_type::reverse_iterator; using reverse_iterator = typename common_type::reverse_iterator;
/*! @brief Iterable view type. */ /*! @brief Iterable view type. */
using iterable = decltype(std::declval<Get>().each()); using iterable = decltype(std::declval<Get>().each());
/*! @brief Default constructor to use to create empty, invalid views. */ /*! @brief Default constructor to use to create empty, invalid views. */
basic_view() noexcept basic_view() noexcept
: pools{}, : pools{},
filter{} {} filter{},
view{} {}
/** /**
* @brief Constructs a single-type view from a storage class. * @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 basic_view(Get &value) noexcept
: pools{&ref}, : pools{&value},
filter{} {} filter{},
view{&value} {}
/** /**
* @brief Constructs a single-type view from a storage class. * @brief Constructs a single-type view from a storage class.
* @param ref The storage for the type to iterate. * @param value The storage for the type to iterate.
*/ */
basic_view(std::tuple<Get &> ref, std::tuple<> = {}) noexcept basic_view(std::tuple<Get &> value, std::tuple<> = {}) noexcept
: pools{&std::get<0>(ref)}, : basic_view{std::get<0>(value)} {}
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. * @return The leading storage of the view.
*/ */
[[nodiscard]] const base_type &handle() const noexcept { [[nodiscard]] const common_type *handle() const noexcept {
return storage(); 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. * @tparam Type Type of component of which to return the storage.
* @return The storage for the given component type. * @return The storage for the given component type.
*/ */
template<typename Type = typename Get::value_type> template<typename Type = typename Get::value_type>
[[nodiscard]] decltype(auto) storage() const noexcept { [[nodiscard]] auto *storage() const noexcept {
static_assert(std::is_same_v<std::remove_const_t<Type>, typename Get::value_type>, "Invalid component type"); static_assert(std::is_same_v<std::remove_const_t<Type>, typename Get::value_type>, "Invalid component type");
return storage<0>(); 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. * @tparam Index Index of the storage to return.
* @return The storage for the given index. * @return The storage for the given index.
*/ */
template<std::size_t Index> template<std::size_t Index>
[[nodiscard]] decltype(auto) storage() const noexcept { [[nodiscard]] auto *storage() const noexcept {
return *std::get<Index>(pools); return std::get<Index>(pools);
}
/**
* @brief Assigns a storage to a view.
* @param elem A storage to assign to the view.
*/
void storage(Get &elem) noexcept {
storage<0>(elem);
}
/**
* @brief Assigns a storage to a view.
* @tparam Index Index of the storage to assign to the view.
* @param elem A storage to assign to the view.
*/
template<std::size_t Index>
void storage(Get &elem) noexcept {
view = std::get<Index>(pools) = &elem;
} }
/** /**
@ -617,7 +675,7 @@ public:
* @return Number of entities that have the given component. * @return Number of entities that have the given component.
*/ */
[[nodiscard]] size_type size() const noexcept { [[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. * @return True if the view is empty, false otherwise.
*/ */
[[nodiscard]] bool empty() const noexcept { [[nodiscard]] bool empty() const noexcept {
return handle().empty(); return !view || view->empty();
} }
/** /**
* @brief Returns an iterator to the first entity of the view. * @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 * If the view is empty, the returned iterator will be equal to `end()`.
* is empty, the returned iterator will be equal to `end()`.
* *
* @return An iterator to the first entity of the view. * @return An iterator to the first entity of the view.
*/ */
[[nodiscard]] iterator begin() const noexcept { [[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. * @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. * @return An iterator to the entity following the last entity of the view.
*/ */
[[nodiscard]] iterator end() const noexcept { [[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. * @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 * If the view is empty, the returned iterator will be equal to `rend()`.
* the view is empty, the returned iterator will be equal to `rend()`.
* *
* @return An iterator to the first entity of the reversed view. * @return An iterator to the first entity of the reversed view.
*/ */
[[nodiscard]] reverse_iterator rbegin() const noexcept { [[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 * @brief Returns an iterator that is past the last entity of the reversed
* view. * 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 * @return An iterator to the entity following the last entity of the
* reversed view. * reversed view.
*/ */
[[nodiscard]] reverse_iterator rend() const noexcept { [[nodiscard]] reverse_iterator rend() const noexcept {
return handle().rend(); return view ? view->rend() : reverse_iterator{};
} }
/** /**
@ -686,7 +732,7 @@ public:
* otherwise. * otherwise.
*/ */
[[nodiscard]] entity_type front() const noexcept { [[nodiscard]] entity_type front() const noexcept {
return empty() ? null : *begin(); return (!view || view->empty()) ? null : *view->begin();
} }
/** /**
@ -695,7 +741,7 @@ public:
* otherwise. * otherwise.
*/ */
[[nodiscard]] entity_type back() const noexcept { [[nodiscard]] entity_type back() const noexcept {
return empty() ? null : *rbegin(); return (!view || view->empty()) ? null : *view->rbegin();
} }
/** /**
@ -705,7 +751,7 @@ public:
* iterator otherwise. * iterator otherwise.
*/ */
[[nodiscard]] iterator find(const entity_type entt) const noexcept { [[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. * @return The component assigned to the given entity.
*/ */
[[nodiscard]] decltype(auto) operator[](const entity_type entt) const { [[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. * @brief Checks if a view is fully initialized.
* @return True if the view is properly initialized, false otherwise. * @return True if the view is fully initialized, false otherwise.
*/ */
[[nodiscard]] explicit operator bool() const noexcept { [[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. * @return True if the view contains the given entity, false otherwise.
*/ */
[[nodiscard]] bool contains(const entity_type entt) const noexcept { [[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 * Attempting to use an entity that doesn't belong to the view results in
* undefined behavior. * 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. * @param entt A valid identifier.
* @return The component assigned to the entity. * @return The component assigned to the entity.
*/ */
template<typename... Type> template<typename Elem>
[[nodiscard]] decltype(auto) get(const entity_type entt) const { [[nodiscard]] decltype(auto) get(const entity_type entt) const {
if constexpr(sizeof...(Type) == 0) { static_assert(std::is_same_v<std::remove_const_t<Elem>, typename Get::value_type>, "Invalid component type");
return storage().get_as_tuple(entt); return get<0>(entt);
} else {
static_assert((std::is_same_v<std::remove_const_t<Type>, typename Get::value_type> && ...), "Invalid component type");
return storage().get(entt);
}
} }
/*! @copydoc get */ /*! @copydoc get */
template<std::size_t Index> template<std::size_t... Elem>
[[nodiscard]] decltype(auto) get(const entity_type entt) const { [[nodiscard]] decltype(auto) get(const entity_type entt) const {
return storage().get(entt); if constexpr(sizeof...(Elem) == 0) {
return std::get<0>(pools)->get_as_tuple(entt);
} else {
return std::get<Elem...>(pools)->get(entt);
}
} }
/** /**
@ -794,20 +840,22 @@ public:
*/ */
template<typename Func> template<typename Func>
void each(Func func) const { void each(Func func) const {
if(view) {
if constexpr(is_applicable_v<Func, decltype(*each().begin())>) { if constexpr(is_applicable_v<Func, decltype(*each().begin())>) {
for(const auto pack: each()) { for(const auto pack: each()) {
std::apply(func, pack); std::apply(func, pack);
} }
} else if constexpr(ignore_as_empty_v<typename Get::value_type>) { } else if constexpr(Get::traits_type::page_size == 0u) {
for(size_type pos{}, last = size(); pos < last; ++pos) { for(size_type pos{}, last = size(); pos < last; ++pos) {
func(); func();
} }
} else { } else {
for(auto &&component: storage()) { for(auto &&component: *std::get<0>(pools)) {
func(component); func(component);
} }
} }
} }
}
/** /**
* @brief Returns an iterable object to use to _visit_ a view. * @brief Returns an iterable object to use to _visit_ a view.
@ -819,7 +867,7 @@ public:
* @return An iterable object to use to _visit_ the view. * @return An iterable object to use to _visit_ the view.
*/ */
[[nodiscard]] iterable each() const noexcept { [[nodiscard]] iterable each() const noexcept {
return storage().each(); return view ? std::get<0>(pools)->each() : iterable{};
} }
/** /**
@ -831,13 +879,16 @@ public:
*/ */
template<typename... OGet, typename... OExclude> template<typename... OGet, typename... OExclude>
[[nodiscard]] auto operator|(const basic_view<get_t<OGet...>, exclude_t<OExclude...>> &other) const noexcept { [[nodiscard]] auto operator|(const basic_view<get_t<OGet...>, exclude_t<OExclude...>> &other) const noexcept {
return std::make_from_tuple<basic_view<get_t<Get, OGet...>, exclude_t<OExclude...>>>( return internal::view_pack(
std::apply([](auto *...curr) { return std::forward_as_tuple(*curr...); }, std::tuple_cat(pools, other.pools, other.filter))); std::tuple_cat(pools, other.pools),
internal::filter_as_tuple<OExclude...>(other.filter),
std::index_sequence_for<Get, OGet..., OExclude...>{});
} }
private: private:
std::tuple<Get *> pools; std::tuple<Get *> pools;
std::tuple<> filter; std::array<const common_type *, 0u> filter;
const common_type *view;
}; };
/** /**

View File

@ -24,6 +24,7 @@
#include "entity/group.hpp" #include "entity/group.hpp"
#include "entity/handle.hpp" #include "entity/handle.hpp"
#include "entity/helper.hpp" #include "entity/helper.hpp"
#include "entity/mixin.hpp"
#include "entity/observer.hpp" #include "entity/observer.hpp"
#include "entity/organizer.hpp" #include "entity/organizer.hpp"
#include "entity/registry.hpp" #include "entity/registry.hpp"
@ -31,7 +32,6 @@
#include "entity/snapshot.hpp" #include "entity/snapshot.hpp"
#include "entity/sparse_set.hpp" #include "entity/sparse_set.hpp"
#include "entity/storage.hpp" #include "entity/storage.hpp"
#include "entity/storage_mixin.hpp"
#include "entity/view.hpp" #include "entity/view.hpp"
#include "graph/adjacency_matrix.hpp" #include "graph/adjacency_matrix.hpp"
#include "graph/dot.hpp" #include "graph/dot.hpp"

View File

@ -258,7 +258,7 @@ public:
[[nodiscard]] iterable_adaptor<out_edge_iterator> out_edges(const vertex_type vertex) const noexcept { [[nodiscard]] iterable_adaptor<out_edge_iterator> out_edges(const vertex_type vertex) const noexcept {
const auto it = matrix.cbegin(); const auto it = matrix.cbegin();
const auto from = vertex * vert; 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}}; return {{it, vert, from, to, 1u}, {it, vert, to, to, 1u}};
} }
@ -270,7 +270,7 @@ public:
[[nodiscard]] iterable_adaptor<in_edge_iterator> in_edges(const vertex_type vertex) const noexcept { [[nodiscard]] iterable_adaptor<in_edge_iterator> in_edges(const vertex_type vertex) const noexcept {
const auto it = matrix.cbegin(); const auto it = matrix.cbegin();
const auto from = vertex; 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}}; return {{it, vert, from, to, vert}, {it, vert, to, to, vert}};
} }

View File

@ -32,6 +32,7 @@ class basic_flow {
using task_container_type = dense_set<id_type, identity, std::equal_to<id_type>, typename alloc_traits::template rebind_alloc<id_type>>; using task_container_type = dense_set<id_type, identity, std::equal_to<id_type>, typename alloc_traits::template rebind_alloc<id_type>>;
using ro_rw_container_type = std::vector<std::pair<std::size_t, bool>, typename alloc_traits::template rebind_alloc<std::pair<std::size_t, bool>>>; using ro_rw_container_type = std::vector<std::pair<std::size_t, bool>, typename alloc_traits::template rebind_alloc<std::pair<std::size_t, bool>>>;
using deps_container_type = dense_map<id_type, ro_rw_container_type, identity, std::equal_to<id_type>, typename alloc_traits::template rebind_alloc<std::pair<const id_type, ro_rw_container_type>>>; using deps_container_type = dense_map<id_type, ro_rw_container_type, identity, std::equal_to<id_type>, typename alloc_traits::template rebind_alloc<std::pair<const id_type, ro_rw_container_type>>>;
using adjacency_matrix_type = adjacency_matrix<directed_tag, typename alloc_traits::template rebind_alloc<std::size_t>>;
void emplace(const id_type res, const bool is_rw) { void emplace(const id_type res, const bool is_rw) {
ENTT_ASSERT(index.first() < vertices.size(), "Invalid node"); ENTT_ASSERT(index.first() < vertices.size(), "Invalid node");
@ -43,6 +44,76 @@ class basic_flow {
deps[res].emplace_back(index.first(), is_rw); 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: public:
/*! @brief Allocator type. */ /*! @brief Allocator type. */
using allocator_type = Allocator; using allocator_type = Allocator;
@ -50,6 +121,8 @@ public:
using size_type = std::size_t; using size_type = std::size_t;
/*! @brief Iterable task list. */ /*! @brief Iterable task list. */
using iterable = iterable_adaptor<typename task_container_type::const_iterator>; using iterable = iterable_adaptor<typename task_container_type::const_iterator>;
/*! @brief Adjacency matrix type. */
using graph_type = adjacency_matrix_type;
/*! @brief Default constructor. */ /*! @brief Default constructor. */
basic_flow() basic_flow()
@ -124,9 +197,10 @@ public:
/*! @brief Clears the flow builder. */ /*! @brief Clears the flow builder. */
void clear() noexcept { void clear() noexcept {
index.first() = sync_on = {}; index.first() = {};
vertices.clear(); vertices.clear();
deps.clear(); deps.clear();
sync_on = {};
} }
/** /**
@ -245,72 +319,12 @@ public:
* @brief Generates a task graph for the current content. * @brief Generates a task graph for the current content.
* @return The adjacency matrix of the task graph. * @return The adjacency matrix of the task graph.
*/ */
[[nodiscard]] adjacency_matrix<directed_tag> graph() const { [[nodiscard]] graph_type graph() const {
const auto length = vertices.size(); graph_type matrix{vertices.size(), get_allocator()};
adjacency_matrix<directed_tag> matrix{length};
// creates the adjacency matrix setup_graph(matrix);
for(const auto &elem: deps) { transitive_closure(matrix);
const auto last = elem.second.cend(); transitive_reduction(matrix);
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);
}
}
}
}
}
return matrix; return matrix;
} }

View File

@ -70,40 +70,40 @@ public:
* cases, they are discarded. * cases, they are discarded.
* *
* @tparam Args Types of arguments to use to construct the fallback service. * @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. * @param args Parameters to use to construct the fallback service.
* @return A reference to a valid service. * @return A reference to a valid service.
*/ */
template<typename Impl = Service, typename... Args> template<typename Type = Service, typename... Args>
[[nodiscard]] static Service &value_or(Args &&...args) { [[nodiscard]] static Service &value_or(Args &&...args) {
return service ? *service : emplace<Impl>(std::forward<Args>(args)...); return service ? *service : emplace<Type>(std::forward<Args>(args)...);
} }
/** /**
* @brief Sets or replaces a service. * @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. * @tparam Args Types of arguments to use to construct the service.
* @param args Parameters to use to construct the service. * @param args Parameters to use to construct the service.
* @return A reference to a valid service. * @return A reference to a valid service.
*/ */
template<typename Impl = Service, typename... Args> template<typename Type = Service, typename... Args>
static Service &emplace(Args &&...args) { static Service &emplace(Args &&...args) {
service = std::make_shared<Impl>(std::forward<Args>(args)...); service = std::make_shared<Type>(std::forward<Args>(args)...);
return *service; return *service;
} }
/** /**
* @brief Sets or replaces a service using a given allocator. * @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 Allocator Type of allocator used to manage memory and elements.
* @tparam Args Types of arguments to use to construct the service. * @tparam Args Types of arguments to use to construct the service.
* @param alloc The allocator to use. * @param alloc The allocator to use.
* @param args Parameters to use to construct the service. * @param args Parameters to use to construct the service.
* @return A reference to a valid service. * @return A reference to a valid service.
*/ */
template<typename Impl = Service, typename Allocator, typename... Args> template<typename Type = Service, typename Allocator, typename... Args>
static Service &allocate_emplace(Allocator alloc, Args &&...args) { static Service &emplace(std::allocator_arg_t, Allocator alloc, Args &&...args) {
service = std::allocate_shared<Impl>(alloc, std::forward<Args>(args)...); service = std::allocate_shared<Type>(alloc, std::forward<Args>(args)...);
return *service; return *service;
} }
@ -125,6 +125,18 @@ public:
service = other.value; service = other.value;
} }
/**
* @brief Resets or replaces a service.
* @tparam Type Service type.
* @tparam Deleter Deleter type.
* @param elem A pointer to a service to manage.
* @param deleter A deleter to use to destroy the service.
*/
template<typename Type, typename Deleter = std::default_delete<Type>>
static void reset(Type *elem, Deleter deleter = {}) {
service = std::shared_ptr<Service>{elem, std::move(deleter)};
}
private: private:
// std::shared_ptr because of its type erased allocator which is useful here // std::shared_ptr because of its type erased allocator which is useful here
inline static std::shared_ptr<Service> service{}; inline static std::shared_ptr<Service> service{};

View File

@ -21,8 +21,8 @@ struct meta_type_node;
struct meta_context { struct meta_context {
dense_map<id_type, meta_type_node, identity> value{}; dense_map<id_type, meta_type_node, identity> value{};
static inline meta_context &from(meta_ctx &ctx); [[nodiscard]] static inline meta_context &from(meta_ctx &ctx);
static inline const meta_context &from(const meta_ctx &ctx); [[nodiscard]] static inline const meta_context &from(const meta_ctx &ctx);
}; };
} // namespace internal } // namespace internal
@ -49,11 +49,11 @@ class meta_ctx: private internal::meta_context {
* Internal details not to be documented. * 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; 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; return ctx;
} }

View File

@ -29,28 +29,12 @@ namespace entt {
namespace internal { 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); auto &&context = internal::meta_context::from(ctx);
ENTT_ASSERT(context.value.contains(info.hash()), "Type not available"); ENTT_ASSERT(context.value.contains(info.hash()), "Type not available");
return context.value[info.hash()]; 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) { 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; 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; return parent.details->func.insert_or_assign(id, std::move(node)).first->second;
} }
inline meta_prop_node &meta_extend(dense_map<id_type, meta_prop_node, identity> &prop, const id_type id, meta_prop_node node) {
return (prop[id] = std::move(node));
}
} // namespace internal } // namespace internal
/** /**
@ -156,16 +136,8 @@ public:
template<typename Base> template<typename Base>
auto base() noexcept { auto base() noexcept {
static_assert(!std::is_same_v<Type, Base> && std::is_base_of_v<Base, Type>, "Invalid base type"); static_assert(!std::is_same_v<Type, Base> && std::is_base_of_v<Base, Type>, "Invalid base type");
auto *const op = +[](const void *instance) noexcept { return static_cast<const void *>(static_cast<const Base *>(static_cast<const Type *>(instance))); };
internal::meta_extend( internal::owner(*ctx, *info).details->base.insert_or_assign(type_id<Base>().hash(), internal::meta_base_node{&internal::resolve<Base>, op});
internal::owner(*ctx, *info),
type_id<Base>().hash(),
internal::meta_base_node{
&internal::resolve<Base>,
+[](const void *instance) noexcept {
return static_cast<const void *>(static_cast<const Base *>(static_cast<const Type *>(instance)));
}});
bucket = nullptr; bucket = nullptr;
return *this; return *this;
} }
@ -185,15 +157,8 @@ public:
template<auto Candidate> template<auto Candidate>
auto conv() noexcept { auto conv() noexcept {
using conv_type = std::remove_cv_t<std::remove_reference_t<std::invoke_result_t<decltype(Candidate), Type &>>>; using conv_type = std::remove_cv_t<std::remove_reference_t<std::invoke_result_t<decltype(Candidate), Type &>>>;
auto *const op = +[](const meta_ctx &area, const void *instance) { return forward_as_meta(area, std::invoke(Candidate, *static_cast<const Type *>(instance))); };
internal::meta_extend( internal::owner(*ctx, *info).details->conv.insert_or_assign(type_id<conv_type>().hash(), internal::meta_conv_node{op});
internal::owner(*ctx, *info),
type_id<conv_type>().hash(),
internal::meta_conv_node{
+[](const meta_ctx &area, const void *instance) {
return forward_as_meta(area, std::invoke(Candidate, *static_cast<const Type *>(instance)));
}});
bucket = nullptr; bucket = nullptr;
return *this; return *this;
} }
@ -210,15 +175,8 @@ public:
template<typename To> template<typename To>
auto conv() noexcept { auto conv() noexcept {
using conv_type = std::remove_cv_t<std::remove_reference_t<To>>; using conv_type = std::remove_cv_t<std::remove_reference_t<To>>;
auto *const op = +[](const meta_ctx &area, const void *instance) { return forward_as_meta(area, static_cast<To>(*static_cast<const Type *>(instance))); };
internal::meta_extend( internal::owner(*ctx, *info).details->conv.insert_or_assign(type_id<conv_type>().hash(), internal::meta_conv_node{op});
internal::owner(*ctx, *info),
type_id<conv_type>().hash(),
internal::meta_conv_node{
+[](const meta_ctx &area, const void *instance) {
return forward_as_meta(area, static_cast<To>(*static_cast<const Type *>(instance)));
}});
bucket = nullptr; bucket = nullptr;
return *this; return *this;
} }
@ -241,15 +199,7 @@ public:
using descriptor = meta_function_helper_t<Type, decltype(Candidate)>; using descriptor = meta_function_helper_t<Type, decltype(Candidate)>;
static_assert(Policy::template value<typename descriptor::return_type>, "Invalid return type for the given policy"); static_assert(Policy::template value<typename descriptor::return_type>, "Invalid return type for the given policy");
static_assert(std::is_same_v<std::remove_cv_t<std::remove_reference_t<typename descriptor::return_type>>, Type>, "The function doesn't return an object of the required type"); static_assert(std::is_same_v<std::remove_cv_t<std::remove_reference_t<typename descriptor::return_type>>, Type>, "The function doesn't return an object of the required type");
internal::owner(*ctx, *info).details->ctor.insert_or_assign(type_id<typename descriptor::args_type>().hash(), internal::meta_ctor_node{descriptor::args_type::size, &meta_arg<typename descriptor::args_type>, &meta_construct<Type, Candidate, Policy>});
internal::meta_extend(
internal::owner(*ctx, *info),
type_id<typename descriptor::args_type>().hash(),
internal::meta_ctor_node{
descriptor::args_type::size,
&meta_arg<typename descriptor::args_type>,
&meta_construct<Type, Candidate, Policy>});
bucket = nullptr; bucket = nullptr;
return *this; return *this;
} }
@ -269,14 +219,7 @@ public:
// default constructor is already implicitly generated, no need for redundancy // default constructor is already implicitly generated, no need for redundancy
if constexpr(sizeof...(Args) != 0u) { if constexpr(sizeof...(Args) != 0u) {
using descriptor = meta_function_helper_t<Type, Type (*)(Args...)>; using descriptor = meta_function_helper_t<Type, Type (*)(Args...)>;
internal::owner(*ctx, *info).details->ctor.insert_or_assign(type_id<typename descriptor::args_type>().hash(), internal::meta_ctor_node{descriptor::args_type::size, &meta_arg<typename descriptor::args_type>, &meta_construct<Type, Args...>});
internal::meta_extend(
internal::owner(*ctx, *info),
type_id<typename descriptor::args_type>().hash(),
internal::meta_ctor_node{
descriptor::args_type::size,
&meta_arg<typename descriptor::args_type>,
&meta_construct<Type, Args...>});
} }
bucket = nullptr; bucket = nullptr;
@ -304,12 +247,8 @@ public:
template<auto Func> template<auto Func>
auto dtor() noexcept { auto dtor() noexcept {
static_assert(std::is_invocable_v<decltype(Func), Type &>, "The function doesn't accept an object of the type provided"); static_assert(std::is_invocable_v<decltype(Func), Type &>, "The function doesn't accept an object of the type provided");
auto *const op = +[](void *instance) { std::invoke(Func, *static_cast<Type *>(instance)); };
internal::meta_extend( internal::owner(*ctx, *info).dtor = internal::meta_dtor_node{op};
internal::owner(*ctx, *info),
internal::meta_dtor_node{
+[](void *instance) { std::invoke(Func, *static_cast<Type *>(instance)); }});
bucket = nullptr; bucket = nullptr;
return *this; return *this;
} }
@ -330,32 +269,39 @@ public:
template<auto Data, typename Policy = as_is_t> template<auto Data, typename Policy = as_is_t>
auto data(const id_type id) noexcept { auto data(const id_type id) noexcept {
if constexpr(std::is_member_object_pointer_v<decltype(Data)>) { if constexpr(std::is_member_object_pointer_v<decltype(Data)>) {
using data_type = std::remove_reference_t<std::invoke_result_t<decltype(Data), Type &>>; using data_type = std::invoke_result_t<decltype(Data), Type &>;
static_assert(Policy::template value<data_type>, "Invalid return type for the given policy");
auto &&elem = internal::meta_extend( auto &&elem = internal::meta_extend(
internal::owner(*ctx, *info), internal::owner(*ctx, *info),
id, id,
internal::meta_data_node{ internal::meta_data_node{
/* this is never static */ /* this is never static */
std::is_const_v<data_type> ? internal::meta_traits::is_const : internal::meta_traits::is_none, std::is_const_v<std::remove_reference_t<data_type>> ? internal::meta_traits::is_const : internal::meta_traits::is_none,
1u, 1u,
&internal::resolve<std::remove_cv_t<data_type>>, &internal::resolve<std::remove_cv_t<std::remove_reference_t<data_type>>>,
&meta_arg<type_list<std::remove_cv_t<data_type>>>, &meta_arg<type_list<std::remove_cv_t<std::remove_reference_t<data_type>>>>,
&meta_setter<Type, Data>, &meta_setter<Type, Data>,
&meta_getter<Type, Data, Policy>}); &meta_getter<Type, Data, Policy>});
bucket = &elem.prop; bucket = &elem.prop;
} else { } else {
using data_type = std::remove_reference_t<std::remove_pointer_t<decltype(Data)>>; using data_type = std::remove_pointer_t<decltype(Data)>;
if constexpr(std::is_pointer_v<decltype(Data)>) {
static_assert(Policy::template value<decltype(*Data)>, "Invalid return type for the given policy");
} else {
static_assert(Policy::template value<data_type>, "Invalid return type for the given policy");
}
auto &&elem = internal::meta_extend( auto &&elem = internal::meta_extend(
internal::owner(*ctx, *info), internal::owner(*ctx, *info),
id, id,
internal::meta_data_node{ internal::meta_data_node{
((std::is_same_v<Type, std::remove_cv_t<data_type>> || std::is_const_v<data_type>) ? internal::meta_traits::is_const : internal::meta_traits::is_none) | internal::meta_traits::is_static, ((std::is_same_v<Type, std::remove_cv_t<std::remove_reference_t<data_type>>> || std::is_const_v<std::remove_reference_t<data_type>>) ? internal::meta_traits::is_const : internal::meta_traits::is_none) | internal::meta_traits::is_static,
1u, 1u,
&internal::resolve<std::remove_cv_t<data_type>>, &internal::resolve<std::remove_cv_t<std::remove_reference_t<data_type>>>,
&meta_arg<type_list<std::remove_cv_t<data_type>>>, &meta_arg<type_list<std::remove_cv_t<std::remove_reference_t<data_type>>>>,
&meta_setter<Type, Data>, &meta_setter<Type, Data>,
&meta_getter<Type, Data, Policy>}); &meta_getter<Type, Data, Policy>});
@ -495,18 +441,11 @@ public:
ENTT_ASSERT(bucket != nullptr, "Meta object does not support properties"); ENTT_ASSERT(bucket != nullptr, "Meta object does not support properties");
if constexpr(sizeof...(Value) == 0u) { if constexpr(sizeof...(Value) == 0u) {
internal::meta_extend( (*bucket)[id] = internal::meta_prop_node{&internal::resolve<void>};
*bucket,
id,
internal::meta_prop_node{
&internal::resolve<void>});
} else { } else {
internal::meta_extend( (*bucket)[id] = internal::meta_prop_node{
*bucket,
id,
internal::meta_prop_node{
&internal::resolve<std::decay_t<Value>>..., &internal::resolve<std::decay_t<Value>>...,
std::make_shared<std::decay_t<Value>>(std::forward<Value>(value))...}); std::make_shared<std::decay_t<Value>>(std::forward<Value>(value))...};
} }
return *this; return *this;

View File

@ -613,7 +613,7 @@ private:
* @return A properly initialized and not necessarily owning wrapper. * @return A properly initialized and not necessarily owning wrapper.
*/ */
template<typename Type> template<typename Type>
meta_any forward_as_meta(const meta_ctx &ctx, Type &&value) { [[nodiscard]] meta_any forward_as_meta(const meta_ctx &ctx, Type &&value) {
return meta_any{ctx, std::in_place_type<Type &&>, std::forward<Type>(value)}; return meta_any{ctx, std::in_place_type<Type &&>, std::forward<Type>(value)};
} }
@ -624,7 +624,7 @@ meta_any forward_as_meta(const meta_ctx &ctx, Type &&value) {
* @return A properly initialized and not necessarily owning wrapper. * @return A properly initialized and not necessarily owning wrapper.
*/ */
template<typename Type> template<typename Type>
meta_any forward_as_meta(Type &&value) { [[nodiscard]] meta_any forward_as_meta(Type &&value) {
return forward_as_meta(locator<meta_ctx>::value_or(), std::forward<Type>(value)); return forward_as_meta(locator<meta_ctx>::value_or(), std::forward<Type>(value));
} }
@ -722,6 +722,16 @@ struct meta_handle {
return static_cast<bool>(any); return static_cast<bool>(any);
} }
/*! @copydoc meta_any::operator== */
[[nodiscard]] bool operator==(const meta_handle &other) const noexcept {
return (any == other.any);
}
/*! @copydoc meta_any::operator!= */
[[nodiscard]] bool operator!=(const meta_handle &other) const noexcept {
return !(*this == other);
}
/** /**
* @brief Access operator for accessing the contained opaque object. * @brief Access operator for accessing the contained opaque object.
* @return A wrapper that shares a reference to an unmanaged object. * @return A wrapper that shares a reference to an unmanaged object.
@ -756,13 +766,21 @@ struct meta_prop {
ctx{&area} {} 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. * @return A wrapper containing the value stored with the property.
*/ */
[[nodiscard]] meta_any value() const { [[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}; 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. * @brief Returns true if an object is valid, false otherwise.
* @return True if the object is valid, false otherwise. * @return True if the object is valid, false otherwise.
@ -771,11 +789,30 @@ struct meta_prop {
return (node != nullptr); 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: private:
const internal::meta_prop_node *node; const internal::meta_prop_node *node;
const meta_ctx *ctx; 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. */ /*! @brief Opaque wrapper for data members. */
struct meta_data { struct meta_data {
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
@ -876,11 +913,26 @@ struct meta_data {
return (node != nullptr); return (node != nullptr);
} }
/*! @copydoc meta_prop::operator== */
[[nodiscard]] bool operator==(const meta_data &other) const noexcept {
return (ctx == other.ctx && node == other.node);
}
private: private:
const internal::meta_data_node *node; const internal::meta_data_node *node;
const meta_ctx *ctx; 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. */ /*! @brief Opaque wrapper for member functions. */
struct meta_func { struct meta_func {
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
@ -962,8 +1014,12 @@ struct meta_func {
*/ */
template<typename... Args> template<typename... Args>
meta_any invoke(meta_handle instance, Args &&...args) const { meta_any invoke(meta_handle instance, Args &&...args) const {
meta_any arguments[sizeof...(Args) + 1u]{{*ctx, std::forward<Args>(args)}...}; if constexpr(sizeof...(Args) == 0u) {
return invoke(meta_handle{*ctx, std::move(instance)}, arguments, sizeof...(Args)); return invoke(std::move(instance), static_cast<meta_any *>(nullptr), size_type{});
} else {
meta_any arguments[sizeof...(Args)]{{*ctx, std::forward<Args>(args)}...};
return invoke(std::move(instance), arguments, sizeof...(Args));
}
} }
/*! @copydoc meta_data::prop */ /*! @copydoc meta_data::prop */
@ -997,11 +1053,26 @@ struct meta_func {
return (node != nullptr); return (node != nullptr);
} }
/*! @copydoc meta_prop::operator== */
[[nodiscard]] bool operator==(const meta_func &other) const noexcept {
return (ctx == other.ctx && node == other.node);
}
private: private:
const internal::meta_func_node *node; const internal::meta_func_node *node;
const meta_ctx *ctx; 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. */ /*! @brief Opaque wrapper for types. */
class meta_type { class meta_type {
template<typename Func> template<typename Func>
@ -1339,21 +1410,25 @@ public:
*/ */
template<typename... Args> template<typename... Args>
[[nodiscard]] meta_any construct(Args &&...args) const { [[nodiscard]] meta_any construct(Args &&...args) const {
meta_any arguments[sizeof...(Args) + 1u]{{*ctx, std::forward<Args>(args)}...}; if constexpr(sizeof...(Args) == 0u) {
return construct(static_cast<meta_any *>(nullptr), size_type{});
} else {
meta_any arguments[sizeof...(Args)]{{*ctx, std::forward<Args>(args)}...};
return construct(arguments, sizeof...(Args)); return construct(arguments, sizeof...(Args));
} }
}
/** /**
* @brief Wraps an opaque element of the underlying type. * @brief Wraps an opaque element of the underlying type.
* @param element A valid pointer to an element of the underlying type. * @param element A valid pointer to an element of the underlying type.
* @return A wrapper that references the given instance. * @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}; return (element && node.from_void) ? node.from_void(*ctx, element, nullptr) : meta_any{meta_ctx_arg, *ctx};
} }
/*! @copydoc from_void */ /*! @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}; 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 { meta_any invoke(const id_type id, meta_handle instance, meta_any *const args, const size_type sz) const {
if(node.details) { if(node.details) {
if(auto it = node.details->func.find(id); it != node.details->func.cend()) { 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); return candidate->invoke(*ctx, meta_handle{*ctx, std::move(instance)}, args);
} }
} }
@ -1399,8 +1474,12 @@ public:
*/ */
template<typename... Args> template<typename... Args>
meta_any invoke(const id_type id, meta_handle instance, Args &&...args) const { meta_any invoke(const id_type id, meta_handle instance, Args &&...args) const {
meta_any arguments[sizeof...(Args) + 1u]{{*ctx, std::forward<Args>(args)}...}; if constexpr(sizeof...(Args) == 0u) {
return invoke(id, meta_handle{*ctx, std::move(instance)}, arguments, sizeof...(Args)); return invoke(id, std::move(instance), static_cast<meta_any *>(nullptr), size_type{});
} else {
meta_any arguments[sizeof...(Args)]{{*ctx, std::forward<Args>(args)}...};
return invoke(id, std::move(instance), arguments, sizeof...(Args));
}
} }
/** /**
@ -1466,11 +1545,7 @@ public:
return !(ctx == nullptr); return !(ctx == nullptr);
} }
/** /*! @copydoc meta_prop::operator== */
* @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_type &other) const noexcept { [[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)); 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 explicit meta_iterator(const meta_ctx &area, It iter) noexcept
: ctx{&area}, : ctx{&area},
vtable{&basic_vtable<It>}, vtable{&basic_vtable<It>},
handle{std::move(iter)} {} handle{iter} {}
meta_iterator &operator++() noexcept { meta_iterator &operator++() noexcept {
vtable(operation::incr, handle, 1, nullptr); vtable(operation::incr, handle, 1, nullptr);
@ -1716,7 +1791,7 @@ public:
meta_iterator(const meta_ctx &area, std::integral_constant<bool, KeyOnly>, It iter) noexcept meta_iterator(const meta_ctx &area, std::integral_constant<bool, KeyOnly>, It iter) noexcept
: ctx{&area}, : ctx{&area},
vtable{&basic_vtable<KeyOnly, It>}, vtable{&basic_vtable<KeyOnly, It>},
handle{std::move(iter)} {} handle{iter} {}
meta_iterator &operator++() noexcept { meta_iterator &operator++() noexcept {
vtable(operation::incr, handle, nullptr); 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. * @return A possibly invalid iterator following the last removed element.
*/ */
inline meta_sequence_container::iterator meta_sequence_container::erase(iterator it) { inline meta_sequence_container::iterator meta_sequence_container::erase(iterator it) {
return insert(std::move(it), {}); return insert(it, {});
} }
/** /**

View File

@ -68,11 +68,7 @@ struct as_void_t final {
*/ */
template<typename Type> template<typename Type>
struct is_meta_policy struct is_meta_policy
: std::disjunction< : std::bool_constant<std::is_same_v<Type, as_ref_t> || std::is_same_v<Type, as_cref_t> || std::is_same_v<Type, as_is_t> || std::is_same_v<Type, as_void_t>> {};
std::is_same<Type, as_ref_t>,
std::is_same<Type, as_cref_t>,
std::is_same<Type, as_is_t>,
std::is_same<Type, as_void_t>> {};
/** /**
* @brief Helper variable template. * @brief Helper variable template.

View File

@ -25,19 +25,19 @@ struct meta_range_iterator final {
using reference = value_type; using reference = value_type;
using iterator_category = std::input_iterator_tag; using iterator_category = std::input_iterator_tag;
meta_range_iterator() noexcept constexpr meta_range_iterator() noexcept
: it{}, : it{},
ctx{} {} 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}, : it{iter},
ctx{&area} {} ctx{&area} {}
meta_range_iterator &operator++() noexcept { constexpr meta_range_iterator &operator++() noexcept {
return ++it, *this; return ++it, *this;
} }
meta_range_iterator operator++(int) noexcept { constexpr meta_range_iterator operator++(int) noexcept {
meta_range_iterator orig = *this; meta_range_iterator orig = *this;
return ++(*this), orig; return ++(*this), orig;
} }

View File

@ -30,7 +30,6 @@ struct meta_associative_container_traits;
/** /**
* @brief Provides the member constant `value` to true if a given type is a * @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. * pointer-like type from the point of view of the meta system, false otherwise.
* @tparam Type Potentially pointer-like type.
*/ */
template<typename> template<typename>
struct is_meta_pointer_like: std::false_type {}; struct is_meta_pointer_like: std::false_type {};

View File

@ -92,9 +92,12 @@ template<typename Type, typename Ret, typename MaybeType, typename... Args>
struct meta_function_descriptor<Type, Ret (*)(MaybeType, Args...)> struct meta_function_descriptor<Type, Ret (*)(MaybeType, Args...)>
: meta_function_descriptor_traits< : meta_function_descriptor_traits<
Ret, Ret,
std::conditional_t<std::is_base_of_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type>, type_list<Args...>, type_list<MaybeType, Args...>>, std::conditional_t<
!std::is_base_of_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type>, std::is_same_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type> || std::is_base_of_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type>,
std::is_base_of_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type> && std::is_const_v<std::remove_reference_t<MaybeType>>> {}; type_list<Args...>,
type_list<MaybeType, Args...>>,
!(std::is_same_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type> || std::is_base_of_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type>),
std::is_const_v<std::remove_reference_t<MaybeType>> && (std::is_same_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type> || std::is_base_of_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type>)> {};
/** /**
* @brief Meta function descriptor. * @brief Meta function descriptor.
@ -162,7 +165,7 @@ using meta_function_helper_t = typename meta_function_helper<Type, Candidate>::t
* @return A meta any containing the returned value, if any. * @return A meta any containing the returned value, if any.
*/ */
template<typename Policy = as_is_t, typename Type> template<typename Policy = as_is_t, typename Type>
std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_dispatch(const meta_ctx &ctx, [[maybe_unused]] Type &&value) { [[nodiscard]] std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_dispatch(const meta_ctx &ctx, [[maybe_unused]] Type &&value) {
if constexpr(std::is_same_v<Policy, as_void_t>) { if constexpr(std::is_same_v<Policy, as_void_t>) {
return meta_any{ctx, std::in_place_type<void>}; return meta_any{ctx, std::in_place_type<void>};
} else if constexpr(std::is_same_v<Policy, as_ref_t>) { } else if constexpr(std::is_same_v<Policy, as_ref_t>) {
@ -183,7 +186,7 @@ std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_dispatch(const meta_ct
* @return A meta any containing the returned value, if any. * @return A meta any containing the returned value, if any.
*/ */
template<typename Policy = as_is_t, typename Type> template<typename Policy = as_is_t, typename Type>
std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_dispatch(Type &&value) { [[nodiscard]] std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_dispatch(Type &&value) {
return meta_dispatch<Policy, Type>(locator<meta_ctx>::value_or(), std::forward<Type>(value)); return meta_dispatch<Policy, Type>(locator<meta_ctx>::value_or(), std::forward<Type>(value));
} }

View File

@ -19,7 +19,7 @@ struct poly_inspector {
* @brief Generic conversion operator (definition only). * @brief Generic conversion operator (definition only).
* @tparam Type Type to which conversion is requested. * @tparam Type Type to which conversion is requested.
*/ */
template<class Type> template<typename Type>
operator Type &&() const; operator Type &&() const;
/** /**

View File

@ -1,13 +1,18 @@
#ifndef ENTT_PROCESS_FWD_HPP #ifndef ENTT_PROCESS_FWD_HPP
#define ENTT_PROCESS_FWD_HPP #define ENTT_PROCESS_FWD_HPP
#include <cstdint>
namespace entt { namespace entt {
template<typename, typename> template<typename, typename>
class process; class process;
template<typename> template<typename = std::uint32_t>
class scheduler; class basic_scheduler;
/*! @brief Alias declaration for the most common use case. */
using scheduler = basic_scheduler<>;
} // namespace entt } // namespace entt

View File

@ -7,6 +7,7 @@
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "fwd.hpp"
#include "process.hpp" #include "process.hpp"
namespace entt { namespace entt {
@ -38,11 +39,11 @@ namespace entt {
* @tparam Delta Type to use to provide elapsed time. * @tparam Delta Type to use to provide elapsed time.
*/ */
template<typename Delta> template<typename Delta>
class scheduler { class basic_scheduler {
struct process_handler { struct process_handler {
using instance_type = std::unique_ptr<void, void (*)(void *)>; using instance_type = std::unique_ptr<void, void (*)(void *)>;
using update_fn_type = bool(scheduler &, std::size_t, Delta, void *); using update_fn_type = bool(basic_scheduler &, std::size_t, Delta, void *);
using abort_fn_type = void(scheduler &, std::size_t, bool); using abort_fn_type = void(basic_scheduler &, std::size_t, bool);
using next_type = std::unique_ptr<process_handler>; using next_type = std::unique_ptr<process_handler>;
instance_type instance; instance_type instance;
@ -58,8 +59,8 @@ class scheduler {
template<typename Proc, typename... Args> template<typename Proc, typename... Args>
continuation then(Args &&...args) { continuation then(Args &&...args) {
static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>, "Invalid process type"); static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>, "Invalid process type");
auto proc = typename process_handler::instance_type{new Proc{std::forward<Args>(args)...}, &scheduler::deleter<Proc>}; auto proc = typename process_handler::instance_type{new Proc{std::forward<Args>(args)...}, &basic_scheduler::deleter<Proc>};
handler->next.reset(new process_handler{std::move(proc), &scheduler::update<Proc>, &scheduler::abort<Proc>, nullptr}); handler->next.reset(new process_handler{std::move(proc), &basic_scheduler::update<Proc>, &basic_scheduler::abort<Proc>, nullptr});
handler = handler->next.get(); handler = handler->next.get();
return *this; return *this;
} }
@ -74,7 +75,7 @@ class scheduler {
}; };
template<typename Proc> template<typename Proc>
[[nodiscard]] static bool update(scheduler &owner, std::size_t pos, const Delta delta, void *data) { [[nodiscard]] static bool update(basic_scheduler &owner, std::size_t pos, const Delta delta, void *data) {
auto *process = static_cast<Proc *>(owner.handlers[pos].instance.get()); auto *process = static_cast<Proc *>(owner.handlers[pos].instance.get());
process->tick(delta, data); process->tick(delta, data);
@ -94,7 +95,7 @@ class scheduler {
} }
template<typename Proc> template<typename Proc>
static void abort(scheduler &owner, std::size_t pos, const bool immediately) { static void abort(basic_scheduler &owner, std::size_t pos, const bool immediately) {
static_cast<Proc *>(owner.handlers[pos].instance.get())->abort(immediately); static_cast<Proc *>(owner.handlers[pos].instance.get())->abort(immediately);
} }
@ -104,18 +105,20 @@ class scheduler {
} }
public: public:
/*! @brief Unsigned integer type. */
using delta_type = Delta;
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
using size_type = std::size_t; using size_type = std::size_t;
/*! @brief Default constructor. */ /*! @brief Default constructor. */
scheduler() basic_scheduler()
: handlers{} {} : handlers{} {}
/*! @brief Default move constructor. */ /*! @brief Default move constructor. */
scheduler(scheduler &&) = default; basic_scheduler(basic_scheduler &&) = default;
/*! @brief Default move assignment operator. @return This scheduler. */ /*! @brief Default move assignment operator. @return This scheduler. */
scheduler &operator=(scheduler &&) = default; basic_scheduler &operator=(basic_scheduler &&) = default;
/** /**
* @brief Number of processes currently scheduled. * @brief Number of processes currently scheduled.
@ -171,8 +174,8 @@ public:
template<typename Proc, typename... Args> template<typename Proc, typename... Args>
auto attach(Args &&...args) { auto attach(Args &&...args) {
static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>, "Invalid process type"); static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>, "Invalid process type");
auto proc = typename process_handler::instance_type{new Proc{std::forward<Args>(args)...}, &scheduler::deleter<Proc>}; auto proc = typename process_handler::instance_type{new Proc{std::forward<Args>(args)...}, &basic_scheduler::deleter<Proc>};
auto &&ref = handlers.emplace_back(process_handler{std::move(proc), &scheduler::update<Proc>, &scheduler::abort<Proc>, nullptr}); auto &&ref = handlers.emplace_back(process_handler{std::move(proc), &basic_scheduler::update<Proc>, &basic_scheduler::abort<Proc>, nullptr});
// forces the process to exit the uninitialized state // forces the process to exit the uninitialized state
ref.update(*this, handlers.size() - 1u, {}, nullptr); ref.update(*this, handlers.size() - 1u, {}, nullptr);
return continuation{&handlers.back()}; return continuation{&handlers.back()};
@ -246,7 +249,7 @@ public:
* @param delta Elapsed time. * @param delta Elapsed time.
* @param data Optional data. * @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) { for(auto pos = handlers.size(); pos; --pos) {
const auto curr = pos - 1u; const auto curr = pos - 1u;

View File

@ -95,51 +95,51 @@ public:
return operator*(); return operator*();
} }
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs> template<typename... Lhs, typename... Rhs>
friend constexpr std::ptrdiff_t operator-(const resource_cache_iterator<TLhs, ILhs> &, const resource_cache_iterator<TRhs, IRhs> &) noexcept; friend constexpr std::ptrdiff_t operator-(const resource_cache_iterator<Lhs...> &, const resource_cache_iterator<Rhs...> &) noexcept;
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs> template<typename... Lhs, typename... Rhs>
friend constexpr bool operator==(const resource_cache_iterator<TLhs, ILhs> &, const resource_cache_iterator<TRhs, IRhs> &) noexcept; friend constexpr bool operator==(const resource_cache_iterator<Lhs...> &, const resource_cache_iterator<Rhs...> &) noexcept;
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs> template<typename... Lhs, typename... Rhs>
friend constexpr bool operator<(const resource_cache_iterator<TLhs, ILhs> &, const resource_cache_iterator<TRhs, IRhs> &) noexcept; friend constexpr bool operator<(const resource_cache_iterator<Lhs...> &, const resource_cache_iterator<Rhs...> &) noexcept;
private: private:
It it; It it;
}; };
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs> template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr std::ptrdiff_t operator-(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) noexcept { [[nodiscard]] constexpr std::ptrdiff_t operator-(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
return lhs.it - rhs.it; return lhs.it - rhs.it;
} }
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs> template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator==(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator==(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
return lhs.it == rhs.it; return lhs.it == rhs.it;
} }
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs> template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator!=(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator!=(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
return !(lhs == rhs); return !(lhs == rhs);
} }
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs> template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator<(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator<(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
return lhs.it < rhs.it; return lhs.it < rhs.it;
} }
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs> template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator>(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator>(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
return rhs < lhs; return rhs < lhs;
} }
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs> template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator<=(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator<=(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
return !(lhs > rhs); return !(lhs > rhs);
} }
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs> template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator>=(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator>=(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
return !(lhs < rhs); return !(lhs < rhs);
} }
@ -158,7 +158,7 @@ template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
*/ */
template<typename Type, typename Loader, typename Allocator> template<typename Type, typename Loader, typename Allocator>
class resource_cache { class resource_cache {
using alloc_traits = typename std::allocator_traits<Allocator>; using alloc_traits = std::allocator_traits<Allocator>;
static_assert(std::is_same_v<typename alloc_traits::value_type, Type>, "Invalid value type"); static_assert(std::is_same_v<typename alloc_traits::value_type, Type>, "Invalid value type");
using container_allocator = typename alloc_traits::template rebind_alloc<std::pair<const id_type, typename Loader::result_type>>; using container_allocator = typename alloc_traits::template rebind_alloc<std::pair<const id_type, typename Loader::result_type>>;
using container_type = dense_map<id_type, typename Loader::result_type, identity, std::equal_to<id_type>, container_allocator>; using container_type = dense_map<id_type, typename Loader::result_type, identity, std::equal_to<id_type>, container_allocator>;
@ -241,8 +241,7 @@ public:
/** /**
* @brief Returns an iterator to the beginning. * @brief Returns an iterator to the beginning.
* *
* The returned iterator points to the first instance of the cache. If the * If the cache is empty, the returned iterator will be equal to `end()`.
* cache is empty, the returned iterator will be equal to `end()`.
* *
* @return An iterator to the first instance of the internal cache. * @return An iterator to the first instance of the internal cache.
*/ */
@ -262,11 +261,6 @@ public:
/** /**
* @brief Returns an iterator to the end. * @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 * @return An iterator to the element following the last instance of the
* internal cache. * internal cache.
*/ */

View File

@ -163,81 +163,81 @@ private:
/** /**
* @brief Compares two handles. * @brief Compares two handles.
* @tparam Res Type of resource managed by the first handle. * @tparam Lhs Type of resource managed by the first handle.
* @tparam Other Type of resource managed by the second handle. * @tparam Rhs Type of resource managed by the second handle.
* @param lhs A valid handle. * @param lhs A valid handle.
* @param rhs A valid handle. * @param rhs A valid handle.
* @return True if both handles refer to the same resource, false otherwise. * @return True if both handles refer to the same resource, false otherwise.
*/ */
template<typename Res, typename Other> template<typename Lhs, typename Rhs>
[[nodiscard]] bool operator==(const resource<Res> &lhs, const resource<Other> &rhs) noexcept { [[nodiscard]] bool operator==(const resource<Lhs> &lhs, const resource<Rhs> &rhs) noexcept {
return (std::addressof(*lhs) == std::addressof(*rhs)); return (std::addressof(*lhs) == std::addressof(*rhs));
} }
/** /**
* @brief Compares two handles. * @brief Compares two handles.
* @tparam Res Type of resource managed by the first handle. * @tparam Lhs Type of resource managed by the first handle.
* @tparam Other Type of resource managed by the second handle. * @tparam Rhs Type of resource managed by the second handle.
* @param lhs A valid handle. * @param lhs A valid handle.
* @param rhs A valid handle. * @param rhs A valid handle.
* @return False if both handles refer to the same registry, true otherwise. * @return False if both handles refer to the same resource, true otherwise.
*/ */
template<typename Res, typename Other> template<typename Lhs, typename Rhs>
[[nodiscard]] bool operator!=(const resource<Res> &lhs, const resource<Other> &rhs) noexcept { [[nodiscard]] bool operator!=(const resource<Lhs> &lhs, const resource<Rhs> &rhs) noexcept {
return !(lhs == rhs); return !(lhs == rhs);
} }
/** /**
* @brief Compares two handles. * @brief Compares two handles.
* @tparam Res Type of resource managed by the first handle. * @tparam Lhs Type of resource managed by the first handle.
* @tparam Other Type of resource managed by the second handle. * @tparam Rhs Type of resource managed by the second handle.
* @param lhs A valid handle. * @param lhs A valid handle.
* @param rhs A valid handle. * @param rhs A valid handle.
* @return True if the first handle is less than the second, false otherwise. * @return True if the first handle is less than the second, false otherwise.
*/ */
template<typename Res, typename Other> template<typename Lhs, typename Rhs>
[[nodiscard]] bool operator<(const resource<Res> &lhs, const resource<Other> &rhs) noexcept { [[nodiscard]] bool operator<(const resource<Lhs> &lhs, const resource<Rhs> &rhs) noexcept {
return (std::addressof(*lhs) < std::addressof(*rhs)); return (std::addressof(*lhs) < std::addressof(*rhs));
} }
/** /**
* @brief Compares two handles. * @brief Compares two handles.
* @tparam Res Type of resource managed by the first handle. * @tparam Lhs Type of resource managed by the first handle.
* @tparam Other Type of resource managed by the second handle. * @tparam Rhs Type of resource managed by the second handle.
* @param lhs A valid handle. * @param lhs A valid handle.
* @param rhs A valid handle. * @param rhs A valid handle.
* @return True if the first handle is greater than the second, false otherwise. * @return True if the first handle is greater than the second, false otherwise.
*/ */
template<typename Res, typename Other> template<typename Lhs, typename Rhs>
[[nodiscard]] bool operator>(const resource<Res> &lhs, const resource<Other> &rhs) noexcept { [[nodiscard]] bool operator>(const resource<Lhs> &lhs, const resource<Rhs> &rhs) noexcept {
return (std::addressof(*lhs) > std::addressof(*rhs)); return rhs < lhs;
} }
/** /**
* @brief Compares two handles. * @brief Compares two handles.
* @tparam Res Type of resource managed by the first handle. * @tparam Lhs Type of resource managed by the first handle.
* @tparam Other Type of resource managed by the second handle. * @tparam Rhs Type of resource managed by the second handle.
* @param lhs A valid handle. * @param lhs A valid handle.
* @param rhs A valid handle. * @param rhs A valid handle.
* @return True if the first handle is less than or equal to the second, false * @return True if the first handle is less than or equal to the second, false
* otherwise. * otherwise.
*/ */
template<typename Res, typename Other> template<typename Lhs, typename Rhs>
[[nodiscard]] bool operator<=(const resource<Res> &lhs, const resource<Other> &rhs) noexcept { [[nodiscard]] bool operator<=(const resource<Lhs> &lhs, const resource<Rhs> &rhs) noexcept {
return !(lhs > rhs); return !(lhs > rhs);
} }
/** /**
* @brief Compares two handles. * @brief Compares two handles.
* @tparam Res Type of resource managed by the first handle. * @tparam Lhs Type of resource managed by the first handle.
* @tparam Other Type of resource managed by the second handle. * @tparam Rhs Type of resource managed by the second handle.
* @param lhs A valid handle. * @param lhs A valid handle.
* @param rhs A valid handle. * @param rhs A valid handle.
* @return True if the first handle is greater than or equal to the second, * @return True if the first handle is greater than or equal to the second,
* false otherwise. * false otherwise.
*/ */
template<typename Res, typename Other> template<typename Lhs, typename Rhs>
[[nodiscard]] bool operator>=(const resource<Res> &lhs, const resource<Other> &rhs) noexcept { [[nodiscard]] bool operator>=(const resource<Lhs> &lhs, const resource<Rhs> &rhs) noexcept {
return !(lhs < rhs); return !(lhs < rhs);
} }

View File

@ -76,7 +76,13 @@ class delegate<Ret(Args...)> {
[[nodiscard]] auto wrap(std::index_sequence<Index...>) noexcept { [[nodiscard]] auto wrap(std::index_sequence<Index...>) noexcept {
return [](const void *, Args... args) -> Ret { return [](const void *, Args... args) -> Ret {
[[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward<Args>(args)...); [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward<Args>(args)...);
if constexpr(std::is_invocable_r_v<Ret, decltype(Candidate), type_list_element_t<Index, type_list<Args...>>...>) {
return static_cast<Ret>(std::invoke(Candidate, std::forward<type_list_element_t<Index, type_list<Args...>>>(std::get<Index>(arguments))...)); return static_cast<Ret>(std::invoke(Candidate, std::forward<type_list_element_t<Index, type_list<Args...>>>(std::get<Index>(arguments))...));
} else {
constexpr auto offset = sizeof...(Args) - sizeof...(Index);
return static_cast<Ret>(std::invoke(Candidate, std::forward<type_list_element_t<Index + offset, type_list<Args...>>>(std::get<Index + offset>(arguments))...));
}
}; };
} }
@ -85,7 +91,13 @@ class delegate<Ret(Args...)> {
return [](const void *payload, Args... args) -> Ret { return [](const void *payload, Args... args) -> Ret {
[[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward<Args>(args)...); [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward<Args>(args)...);
Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload)); Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload));
if constexpr(std::is_invocable_r_v<Ret, decltype(Candidate), Type &, type_list_element_t<Index, type_list<Args...>>...>) {
return static_cast<Ret>(std::invoke(Candidate, *curr, std::forward<type_list_element_t<Index, type_list<Args...>>>(std::get<Index>(arguments))...)); return static_cast<Ret>(std::invoke(Candidate, *curr, std::forward<type_list_element_t<Index, type_list<Args...>>>(std::get<Index>(arguments))...));
} else {
constexpr auto offset = sizeof...(Args) - sizeof...(Index);
return static_cast<Ret>(std::invoke(Candidate, *curr, std::forward<type_list_element_t<Index + offset, type_list<Args...>>>(std::get<Index + offset>(arguments))...));
}
}; };
} }
@ -94,7 +106,13 @@ class delegate<Ret(Args...)> {
return [](const void *payload, Args... args) -> Ret { return [](const void *payload, Args... args) -> Ret {
[[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward<Args>(args)...); [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward<Args>(args)...);
Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload)); Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload));
if constexpr(std::is_invocable_r_v<Ret, decltype(Candidate), Type *, type_list_element_t<Index, type_list<Args...>>...>) {
return static_cast<Ret>(std::invoke(Candidate, curr, std::forward<type_list_element_t<Index, type_list<Args...>>>(std::get<Index>(arguments))...)); return static_cast<Ret>(std::invoke(Candidate, curr, std::forward<type_list_element_t<Index, type_list<Args...>>>(std::get<Index>(arguments))...));
} else {
constexpr auto offset = sizeof...(Args) - sizeof...(Index);
return static_cast<Ret>(std::invoke(Candidate, curr, std::forward<type_list_element_t<Index + offset, type_list<Args...>>>(std::get<Index + offset>(arguments))...));
}
}; };
} }
@ -233,6 +251,14 @@ public:
fn = nullptr; 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. * @brief Returns the instance or the payload linked to a delegate, if any.
* @return An opaque pointer to the underlying data. * @return An opaque pointer to the underlying data.

View File

@ -75,7 +75,7 @@ public:
template<typename... Args> template<typename... Args>
void enqueue(Args &&...args) { void enqueue(Args &&...args) {
if constexpr(std::is_aggregate_v<Type>) { if constexpr(std::is_aggregate_v<Type> && (sizeof...(Args) != 0u || !std::is_default_constructible_v<Type>)) {
events.push_back(Type{std::forward<Args>(args)...}); events.push_back(Type{std::forward<Args>(args)...});
} else { } else {
events.emplace_back(std::forward<Args>(args)...); events.emplace_back(std::forward<Args>(args)...);
@ -179,7 +179,9 @@ public:
* @param allocator The allocator to use. * @param allocator The allocator to use.
*/ */
basic_dispatcher(basic_dispatcher &&other, const allocator_type &allocator) noexcept 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. * @brief Move assignment operator.
@ -187,6 +189,8 @@ public:
* @return This dispatcher. * @return This dispatcher.
*/ */
basic_dispatcher &operator=(basic_dispatcher &&other) noexcept { 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); pools = std::move(other.pools);
return *this; return *this;
} }

View File

@ -76,7 +76,9 @@ public:
* @param allocator The allocator to use. * @param allocator The allocator to use.
*/ */
emitter(emitter &&other, const allocator_type &allocator) noexcept 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. * @brief Move assignment operator.
@ -84,6 +86,8 @@ public:
* @return This dispatcher. * @return This dispatcher.
*/ */
emitter &operator=(emitter &&other) noexcept { 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); handlers = std::move(other.handlers);
return *this; return *this;
} }

View File

@ -1,8 +1,8 @@
#ifndef ENTT_SIGNAL_SIGH_HPP #ifndef ENTT_SIGNAL_SIGH_HPP
#define ENTT_SIGNAL_SIGH_HPP #define ENTT_SIGNAL_SIGH_HPP
#include <algorithm> #include <cstddef>
#include <functional> #include <memory>
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
#include <vector> #include <vector>
@ -56,7 +56,8 @@ class sigh<Ret(Args...), Allocator> {
friend class sink<sigh<Ret(Args...), Allocator>>; friend class sink<sigh<Ret(Args...), Allocator>>;
using alloc_traits = std::allocator_traits<Allocator>; using alloc_traits = std::allocator_traits<Allocator>;
using container_type = std::vector<delegate<Ret(Args...)>, typename alloc_traits::template rebind_alloc<delegate<Ret(Args...)>>>; using delegate_type = delegate<Ret(Args...)>;
using container_type = std::vector<delegate_type, typename alloc_traits::template rebind_alloc<delegate_type>>;
public: public:
/*! @brief Allocator type. */ /*! @brief Allocator type. */
@ -168,8 +169,8 @@ public:
* @param args Arguments to use to invoke listeners. * @param args Arguments to use to invoke listeners.
*/ */
void publish(Args... args) const { void publish(Args... args) const {
for(auto &&call: std::as_const(calls)) { for(auto pos = calls.size(); pos; --pos) {
call(args...); calls[pos - 1u](args...);
} }
} }
@ -189,24 +190,24 @@ public:
*/ */
template<typename Func> template<typename Func>
void collect(Func func, Args... args) const { void collect(Func func, Args... args) const {
for(auto &&call: calls) { for(auto pos = calls.size(); pos; --pos) {
if constexpr(std::is_void_v<Ret>) { if constexpr(std::is_void_v<Ret> || !std::is_invocable_v<Func, Ret>) {
calls[pos - 1u](args...);
if constexpr(std::is_invocable_r_v<bool, Func>) { if constexpr(std::is_invocable_r_v<bool, Func>) {
call(args...);
if(func()) { if(func()) {
break; break;
} }
} else { } else {
call(args...);
func(); func();
} }
} else { } else {
if constexpr(std::is_invocable_r_v<bool, Func, Ret>) { if constexpr(std::is_invocable_r_v<bool, Func, Ret>) {
if(func(call(args...))) { if(func(calls[pos - 1u](args...))) {
break; break;
} }
} else { } else {
func(call(args...)); func(calls[pos - 1u](args...));
} }
} }
} }
@ -358,6 +359,7 @@ private:
template<typename Ret, typename... Args, typename Allocator> template<typename Ret, typename... Args, typename Allocator>
class sink<sigh<Ret(Args...), Allocator>> { class sink<sigh<Ret(Args...), Allocator>> {
using signal_type = sigh<Ret(Args...), Allocator>; using signal_type = sigh<Ret(Args...), Allocator>;
using delegate_type = typename signal_type::delegate_type;
using difference_type = typename signal_type::container_type::difference_type; using difference_type = typename signal_type::container_type::difference_type;
template<auto Candidate, typename Type> template<auto Candidate, typename Type>
@ -370,13 +372,14 @@ class sink<sigh<Ret(Args...), Allocator>> {
sink{*static_cast<signal_type *>(signal)}.disconnect<Candidate>(); sink{*static_cast<signal_type *>(signal)}.disconnect<Candidate>();
} }
auto before(delegate<Ret(Args...)> call) { template<typename Func>
const auto &calls = signal->calls; void disconnect_if(Func callback) {
const auto it = std::find(calls.cbegin(), calls.cend(), std::move(call)); for(auto pos = signal->calls.size(); pos; --pos) {
if(auto &elem = signal->calls[pos - 1u]; callback(elem)) {
sink other{*this}; elem = std::move(signal->calls.back());
other.offset = calls.cend() - it; signal->calls.pop_back();
return other; }
}
} }
public: public:
@ -385,8 +388,7 @@ public:
* @param ref A valid reference to a signal object. * @param ref A valid reference to a signal object.
*/ */
sink(sigh<Ret(Args...), Allocator> &ref) noexcept sink(sigh<Ret(Args...), Allocator> &ref) noexcept
: offset{}, : signal{&ref} {}
signal{&ref} {}
/** /**
* @brief Returns false if at least a listener is connected to the sink. * @brief Returns false if at least a listener is connected to the sink.
@ -396,89 +398,9 @@ public:
return signal->calls.empty(); return signal->calls.empty();
} }
/**
* @brief Returns a sink that connects before a given free function or an
* unbound member.
* @tparam Function A valid free function pointer.
* @return A properly initialized sink object.
*/
template<auto Function>
[[nodiscard]] sink before() {
delegate<Ret(Args...)> call{};
call.template connect<Function>();
return before(std::move(call));
}
/**
* @brief Returns a sink that connects before a free function with payload
* or a bound member.
* @tparam Candidate Member or free function to look for.
* @tparam Type Type of class or type of payload.
* @param value_or_instance A valid object that fits the purpose.
* @return A properly initialized sink object.
*/
template<auto Candidate, typename Type>
[[nodiscard]] sink before(Type &&value_or_instance) {
delegate<Ret(Args...)> call{};
call.template connect<Candidate>(value_or_instance);
return before(std::move(call));
}
/**
* @brief Returns a sink that connects before a given instance or specific
* payload.
* @tparam Type Type of class or type of payload.
* @param value_or_instance A valid object that fits the purpose.
* @return A properly initialized sink object.
*/
template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::decay_t<std::remove_pointer_t<Type>>, void>, sink>>
[[nodiscard]] sink before(Type &value_or_instance) {
return before(&value_or_instance);
}
/**
* @brief Returns a sink that connects before a given instance or specific
* payload.
* @param value_or_instance A valid pointer that fits the purpose.
* @return A properly initialized sink object.
*/
[[nodiscard]] sink before(const void *value_or_instance) {
sink other{*this};
if(value_or_instance) {
const auto &calls = signal->calls;
const auto it = std::find_if(calls.cbegin(), calls.cend(), [value_or_instance](const auto &delegate) {
return delegate.data() == value_or_instance;
});
other.offset = calls.cend() - it;
}
return other;
}
/**
* @brief Returns a sink that connects before anything else.
* @return A properly initialized sink object.
*/
[[nodiscard]] sink before() {
sink other{*this};
other.offset = signal->calls.size();
return other;
}
/** /**
* @brief Connects a free function (with or without payload), a bound or an * @brief Connects a free function (with or without payload), a bound or an
* unbound member to a signal. * unbound member to a signal.
*
* The signal isn't responsible for the connected object or the payload, if
* any. Users must guarantee that the lifetime of the instance overcomes the
* one of the signal. On the other side, the signal handler performs
* checks to avoid multiple connections for the same function.<br/>
* When used to connect a free function with payload, its signature must be
* such that the instance is the first argument before the ones used to
* define the signal itself.
*
* @tparam Candidate Function or member to connect to the signal. * @tparam Candidate Function or member to connect to the signal.
* @tparam Type Type of class or type of payload, if any. * @tparam Type Type of class or type of payload, if any.
* @param value_or_instance A valid object that fits the purpose, 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) { connection connect(Type &&...value_or_instance) {
disconnect<Candidate>(value_or_instance...); disconnect<Candidate>(value_or_instance...);
delegate<Ret(Args...)> call{}; delegate_type call{};
call.template connect<Candidate>(value_or_instance...); call.template connect<Candidate>(value_or_instance...);
signal->calls.insert(signal->calls.end() - offset, std::move(call)); signal->calls.push_back(std::move(call));
delegate<void(void *)> conn{}; delegate<void(void *)> conn{};
conn.template connect<&release<Candidate, Type...>>(value_or_instance...); conn.template connect<&release<Candidate, Type...>>(value_or_instance...);
@ -506,21 +428,9 @@ public:
*/ */
template<auto Candidate, typename... Type> template<auto Candidate, typename... Type>
void disconnect(Type &&...value_or_instance) { void disconnect(Type &&...value_or_instance) {
auto &calls = signal->calls; delegate_type call{};
delegate<Ret(Args...)> call{};
call.template connect<Candidate>(value_or_instance...); call.template connect<Candidate>(value_or_instance...);
calls.erase(std::remove(calls.begin(), calls.end(), std::move(call)), calls.end()); disconnect_if([&call](const auto &elem) { return elem == call; });
}
/**
* @brief Disconnects free functions with payload or bound members from a
* signal.
* @tparam Type Type of class or type of payload.
* @param value_or_instance A valid object that fits the purpose.
*/
template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::decay_t<std::remove_pointer_t<Type>>, void>>>
void disconnect(Type &value_or_instance) {
disconnect(&value_or_instance);
} }
/** /**
@ -530,9 +440,7 @@ public:
*/ */
void disconnect(const void *value_or_instance) { void disconnect(const void *value_or_instance) {
if(value_or_instance) { if(value_or_instance) {
auto &calls = signal->calls; disconnect_if([value_or_instance](const auto &elem) { return elem.data() == value_or_instance; });
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());
} }
} }
@ -542,7 +450,6 @@ public:
} }
private: private:
difference_type offset;
signal_type *signal; signal_type *signal;
}; };

View File

@ -51,7 +51,20 @@ function(SETUP_TARGET TARGET_NAME)
target_compile_options( target_compile_options(
${TARGET_NAME} ${TARGET_NAME}
PRIVATE PRIVATE
/EHsc /W1 /wd4996 /w14800 # vs2017 emits too many false positives for my tastes
$<IF:$<EQUAL:${MSVC_TOOLSET_VERSION},141>, /W1, /W4>
# clang-cl goes a little wrong with some warnings instead
$<$<STREQUAL:"${CMAKE_CXX_COMPILER_ID}","Clang">:
-Wno-deprecated-declarations
-Wno-ignored-qualifiers
-Wno-unknown-warning-option
-Wno-exceptions
-Wno-unused-local-typedef
-Wno-unused-private-field
>
# documentation diagnostic turned on for clang-cl only
$<$<STREQUAL:"${CMAKE_CXX_COMPILER_ID}","Clang">:-Wdocumentation>
/EHsc /wd4324 /wd4996
$<$<CONFIG:Debug>:/Od> $<$<CONFIG:Debug>:/Od>
$<$<CONFIG:Release>:/O2> $<$<CONFIG:Release>:/O2>
) )
@ -94,21 +107,23 @@ function(SETUP_BASIC_TEST TEST_NAME TEST_SOURCES)
add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}) add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME})
endfunction() endfunction()
function(SETUP_LIB_TEST TEST_NAME) function(SETUP_LIB_SHARED_TEST TEST_NAME SUB_PATH)
add_library(_${TEST_NAME} SHARED $<TARGET_OBJECTS:odr> lib/${TEST_NAME}/lib.cpp) set(TARGET_NAME ${TEST_NAME}_${SUB_PATH})
SETUP_TARGET(_${TEST_NAME} ENTT_API_EXPORT) add_library(_${TARGET_NAME} SHARED $<TARGET_OBJECTS:odr> lib/${TEST_NAME}/${SUB_PATH}/lib.cpp)
SETUP_BASIC_TEST(lib_${TEST_NAME} lib/${TEST_NAME}/main.cpp ENTT_API_IMPORT) SETUP_TARGET(_${TARGET_NAME} ENTT_API_EXPORT)
target_link_libraries(lib_${TEST_NAME} PRIVATE _${TEST_NAME}) 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() endfunction()
function(SETUP_PLUGIN_TEST TEST_NAME) function(SETUP_LIB_PLUGIN_TEST TEST_NAME SUB_PATH)
add_library(_${TEST_NAME} MODULE $<TARGET_OBJECTS:odr> lib/${TEST_NAME}/plugin.cpp) set(TARGET_NAME ${TEST_NAME}_${SUB_PATH})
SETUP_TARGET(_${TEST_NAME} ${ARGVN}) add_library(_${TARGET_NAME} MODULE $<TARGET_OBJECTS:odr> lib/${TEST_NAME}/${SUB_PATH}/plugin.cpp)
SETUP_BASIC_TEST(lib_${TEST_NAME} lib/${TEST_NAME}/main.cpp PLUGIN="$<TARGET_FILE:_${TEST_NAME}>" ${ARGVN}) SETUP_TARGET(_${TARGET_NAME} ${ARGVN})
target_include_directories(_${TEST_NAME} PRIVATE ${cr_INCLUDE_DIR}) SETUP_BASIC_TEST(lib_${TARGET_NAME} lib/${TEST_NAME}/${SUB_PATH}/main.cpp PLUGIN="$<TARGET_FILE:_${TARGET_NAME}>" ${ARGVN})
target_include_directories(lib_${TEST_NAME} PRIVATE ${cr_INCLUDE_DIR}) target_include_directories(_${TARGET_NAME} PRIVATE ${cr_INCLUDE_DIR})
target_link_libraries(lib_${TEST_NAME} PRIVATE ${CMAKE_DL_LIBS}) target_include_directories(lib_${TARGET_NAME} PRIVATE ${cr_INCLUDE_DIR})
add_dependencies(lib_${TEST_NAME} _${TEST_NAME}) target_link_libraries(lib_${TARGET_NAME} PRIVATE ${CMAKE_DL_LIBS})
add_dependencies(lib_${TARGET_NAME} _${TARGET_NAME})
endfunction() endfunction()
# Test benchmark # Test benchmark
@ -142,19 +157,21 @@ if(ENTT_BUILD_LIB)
set(cr_INCLUDE_DIR ${cr_SOURCE_DIR}) set(cr_INCLUDE_DIR ${cr_SOURCE_DIR})
endif() endif()
SETUP_LIB_TEST(dispatcher) SETUP_LIB_SHARED_TEST(dispatcher shared)
SETUP_LIB_TEST(emitter) SETUP_LIB_PLUGIN_TEST(dispatcher plugin)
SETUP_LIB_TEST(locator)
SETUP_LIB_TEST(meta)
SETUP_LIB_TEST(registry)
SETUP_PLUGIN_TEST(dispatcher_plugin) SETUP_LIB_SHARED_TEST(emitter shared)
SETUP_PLUGIN_TEST(emitter_plugin) SETUP_LIB_PLUGIN_TEST(emitter plugin)
SETUP_PLUGIN_TEST(locator_plugin)
SETUP_PLUGIN_TEST(meta_plugin)
SETUP_PLUGIN_TEST(registry_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() endif()
# Test snapshot # 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(organizer entt/entity/organizer.cpp)
SETUP_BASIC_TEST(registry entt/entity/registry.cpp) SETUP_BASIC_TEST(registry entt/entity/registry.cpp)
SETUP_BASIC_TEST(runtime_view entt/entity/runtime_view.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(snapshot entt/entity/snapshot.cpp)
SETUP_BASIC_TEST(sparse_set entt/entity/sparse_set.cpp) SETUP_BASIC_TEST(sparse_set entt/entity/sparse_set.cpp)
SETUP_BASIC_TEST(storage entt/entity/storage.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) SETUP_BASIC_TEST(view entt/entity/view.cpp)
# Test graph # Test graph

View File

@ -19,7 +19,9 @@ struct stable_position: position {
}; };
template<auto> template<auto>
struct comp { int x; }; struct comp {
int x;
};
struct timer final { struct timer final {
timer() timer()
@ -34,16 +36,24 @@ private:
std::chrono::time_point<std::chrono::system_clock> start; std::chrono::time_point<std::chrono::system_clock> start;
}; };
template<typename Func, typename... Args>
void generic_with(Func func) {
timer timer;
func();
timer.elapsed();
}
template<typename Iterable, typename Func> template<typename Iterable, typename Func>
void generic(Iterable &&iterable, Func func) { void iterate_with(Iterable &&iterable, Func func) {
timer timer; timer timer;
std::forward<Iterable>(iterable).each(func); std::forward<Iterable>(iterable).each(func);
timer.elapsed(); timer.elapsed();
} }
template<typename Func> template<typename Func>
void pathological(Func func) { void pathological_with(Func func) {
entt::registry registry; entt::registry registry;
auto view = func(registry);
for(std::uint64_t i = 0; i < 500000L; i++) { for(std::uint64_t i = 0; i < 500000L; i++) {
const auto entity = registry.create(); const auto entity = registry.create();
@ -54,10 +64,21 @@ void pathological(Func func) {
for(auto i = 0; i < 10; ++i) { for(auto i = 0; i < 10; ++i) {
registry.each([i = 0, &registry](const auto entity) mutable { registry.each([i = 0, &registry](const auto entity) mutable {
if(!(++i % 7)) { registry.remove<position>(entity); } if(!(++i % 7)) {
if(!(++i % 11)) { registry.remove<velocity>(entity); } registry.remove<position>(entity);
if(!(++i % 13)) { registry.remove<comp<0>>(entity); } }
if(!(++i % 17)) { registry.destroy(entity); }
if(!(++i % 11)) {
registry.remove<velocity>(entity);
}
if(!(++i % 13)) {
registry.remove<comp<0>>(entity);
}
if(!(++i % 17)) {
registry.destroy(entity);
}
}); });
for(std::uint64_t j = 0; j < 50000L; j++) { for(std::uint64_t j = 0; j < 50000L; j++) {
@ -69,7 +90,7 @@ void pathological(Func func) {
} }
timer timer; timer timer;
func(registry).each([](auto &...comp) { ((comp.x = {}), ...); }); view.each([](auto &...comp) { ((comp.x = {}), ...); });
timer.elapsed(); timer.elapsed();
} }
@ -78,13 +99,11 @@ TEST(Benchmark, Create) {
std::cout << "Creating 1000000 entities" << std::endl; std::cout << "Creating 1000000 entities" << std::endl;
timer timer; generic_with([&]() {
for(std::uint64_t i = 0; i < 1000000L; i++) { for(std::uint64_t i = 0; i < 1000000L; i++) {
static_cast<void>(registry.create()); static_cast<void>(registry.create());
} }
});
timer.elapsed();
} }
TEST(Benchmark, CreateMany) { TEST(Benchmark, CreateMany) {
@ -93,9 +112,9 @@ TEST(Benchmark, CreateMany) {
std::cout << "Creating 1000000 entities at once" << std::endl; std::cout << "Creating 1000000 entities at once" << std::endl;
timer timer; generic_with([&]() {
registry.create(entities.begin(), entities.end()); registry.create(entities.begin(), entities.end());
timer.elapsed(); });
} }
TEST(Benchmark, CreateManyAndEmplaceComponents) { TEST(Benchmark, CreateManyAndEmplaceComponents) {
@ -104,15 +123,14 @@ TEST(Benchmark, CreateManyAndEmplaceComponents) {
std::cout << "Creating 1000000 entities at once and emplace components" << std::endl; std::cout << "Creating 1000000 entities at once and emplace components" << std::endl;
timer timer; generic_with([&]() {
registry.create(entities.begin(), entities.end()); registry.create(entities.begin(), entities.end());
for(const auto entity: entities) { for(const auto entity: entities) {
registry.emplace<position>(entity); registry.emplace<position>(entity);
registry.emplace<velocity>(entity); registry.emplace<velocity>(entity);
} }
});
timer.elapsed();
} }
TEST(Benchmark, CreateManyWithComponents) { TEST(Benchmark, CreateManyWithComponents) {
@ -121,79 +139,107 @@ TEST(Benchmark, CreateManyWithComponents) {
std::cout << "Creating 1000000 entities at once with components" << std::endl; std::cout << "Creating 1000000 entities at once with components" << std::endl;
timer timer; generic_with([&]() {
registry.create(entities.begin(), entities.end()); registry.create(entities.begin(), entities.end());
registry.insert<position>(entities.begin(), entities.end()); registry.insert<position>(entities.begin(), entities.end());
registry.insert<velocity>(entities.begin(), entities.end()); registry.insert<velocity>(entities.begin(), entities.end());
timer.elapsed(); });
} }
TEST(Benchmark, Erase) { TEST(Benchmark, Erase) {
entt::registry registry; entt::registry registry;
std::vector<entt::entity> entities(1000000); std::vector<entt::entity> entities(1000000);
auto view = registry.view<int>(); auto view = registry.view<position>();
std::cout << "Erasing 1000000 components from their entities" << std::endl; std::cout << "Erasing 1000000 components from their entities" << std::endl;
registry.create(entities.begin(), entities.end()); registry.create(entities.begin(), entities.end());
registry.insert<int>(entities.begin(), entities.end()); registry.insert<position>(entities.begin(), entities.end());
timer timer;
generic_with([&]() {
for(auto entity: view) { for(auto entity: view) {
registry.erase<int>(entity); registry.erase<position>(entity);
} }
});
timer.elapsed();
} }
TEST(Benchmark, EraseMany) { TEST(Benchmark, EraseMany) {
entt::registry registry; entt::registry registry;
std::vector<entt::entity> entities(1000000); std::vector<entt::entity> entities(1000000);
auto view = registry.view<int>(); auto view = registry.view<position>();
std::cout << "Erasing 1000000 components from their entities at once" << std::endl; std::cout << "Erasing 1000000 components from their entities at once" << std::endl;
registry.create(entities.begin(), entities.end()); registry.create(entities.begin(), entities.end());
registry.insert<int>(entities.begin(), entities.end()); registry.insert<position>(entities.begin(), entities.end());
timer timer; generic_with([&]() {
registry.erase<int>(view.begin(), view.end()); registry.erase<position>(view.begin(), view.end());
timer.elapsed(); });
}
TEST(Benchmark, EraseManyMulti) {
entt::registry registry;
std::vector<entt::entity> entities(1000000);
auto view = registry.view<position>();
std::cout << "Erasing 1000000 components per type from their entities at once" << std::endl;
registry.create(entities.begin(), entities.end());
registry.insert<position>(entities.begin(), entities.end());
registry.insert<velocity>(entities.begin(), entities.end());
generic_with([&]() {
registry.erase<position, velocity>(view.begin(), view.end());
});
} }
TEST(Benchmark, Remove) { TEST(Benchmark, Remove) {
entt::registry registry; entt::registry registry;
std::vector<entt::entity> entities(1000000); std::vector<entt::entity> entities(1000000);
auto view = registry.view<int>(); auto view = registry.view<position>();
std::cout << "Removing 1000000 components from their entities" << std::endl; std::cout << "Removing 1000000 components from their entities" << std::endl;
registry.create(entities.begin(), entities.end()); registry.create(entities.begin(), entities.end());
registry.insert<int>(entities.begin(), entities.end()); registry.insert<position>(entities.begin(), entities.end());
timer timer;
generic_with([&]() {
for(auto entity: view) { for(auto entity: view) {
registry.remove<int>(entity); registry.remove<position>(entity);
} }
});
timer.elapsed();
} }
TEST(Benchmark, RemoveMany) { TEST(Benchmark, RemoveMany) {
entt::registry registry; entt::registry registry;
std::vector<entt::entity> entities(1000000); std::vector<entt::entity> entities(1000000);
auto view = registry.view<int>(); auto view = registry.view<position>();
std::cout << "Removing 1000000 components from their entities at once" << std::endl; std::cout << "Removing 1000000 components from their entities at once" << std::endl;
registry.create(entities.begin(), entities.end()); registry.create(entities.begin(), entities.end());
registry.insert<int>(entities.begin(), entities.end()); registry.insert<position>(entities.begin(), entities.end());
timer timer; generic_with([&]() {
registry.remove<int>(view.begin(), view.end()); registry.remove<position>(view.begin(), view.end());
timer.elapsed(); });
}
TEST(Benchmark, RemoveManyMulti) {
entt::registry registry;
std::vector<entt::entity> entities(1000000);
auto view = registry.view<position>();
std::cout << "Removing 1000000 components per type from their entities at once" << std::endl;
registry.create(entities.begin(), entities.end());
registry.insert<position>(entities.begin(), entities.end());
registry.insert<velocity>(entities.begin(), entities.end());
generic_with([&]() {
registry.remove<position, velocity>(view.begin(), view.end());
});
} }
TEST(Benchmark, Clear) { TEST(Benchmark, Clear) {
@ -203,11 +249,40 @@ TEST(Benchmark, Clear) {
std::cout << "Clearing 1000000 components from their entities" << std::endl; std::cout << "Clearing 1000000 components from their entities" << std::endl;
registry.create(entities.begin(), entities.end()); registry.create(entities.begin(), entities.end());
registry.insert<int>(entities.begin(), entities.end()); registry.insert<position>(entities.begin(), entities.end());
timer timer; generic_with([&]() {
registry.clear<int>(); registry.clear<position>();
timer.elapsed(); });
}
TEST(Benchmark, ClearMulti) {
entt::registry registry;
std::vector<entt::entity> entities(1000000);
std::cout << "Clearing 1000000 components per type from their entities" << std::endl;
registry.create(entities.begin(), entities.end());
registry.insert<position>(entities.begin(), entities.end());
registry.insert<velocity>(entities.begin(), entities.end());
generic_with([&]() {
registry.clear<position, velocity>();
});
}
TEST(Benchmark, ClearStable) {
entt::registry registry;
std::vector<entt::entity> entities(1000000);
std::cout << "Clearing 1000000 stable components from their entities" << std::endl;
registry.create(entities.begin(), entities.end());
registry.insert<stable_position>(entities.begin(), entities.end());
generic_with([&]() {
registry.clear<stable_position>();
});
} }
TEST(Benchmark, Recycle) { TEST(Benchmark, Recycle) {
@ -217,18 +292,13 @@ TEST(Benchmark, Recycle) {
std::cout << "Recycling 1000000 entities" << std::endl; std::cout << "Recycling 1000000 entities" << std::endl;
registry.create(entities.begin(), entities.end()); registry.create(entities.begin(), entities.end());
registry.destroy(entities.begin(), entities.end());
registry.each([&registry](auto entity) { generic_with([&]() {
registry.destroy(entity);
});
timer timer;
for(auto next = entities.size(); next; --next) { for(auto next = entities.size(); next; --next) {
static_cast<void>(registry.create()); entities[next] = registry.create();
} }
});
timer.elapsed();
} }
TEST(Benchmark, RecycleMany) { TEST(Benchmark, RecycleMany) {
@ -238,62 +308,121 @@ TEST(Benchmark, RecycleMany) {
std::cout << "Recycling 1000000 entities" << std::endl; std::cout << "Recycling 1000000 entities" << std::endl;
registry.create(entities.begin(), entities.end()); registry.create(entities.begin(), entities.end());
registry.destroy(entities.begin(), entities.end());
registry.each([&registry](auto entity) { generic_with([&]() {
registry.destroy(entity);
});
timer timer;
registry.create(entities.begin(), entities.end()); registry.create(entities.begin(), entities.end());
timer.elapsed(); });
} }
TEST(Benchmark, Destroy) { TEST(Benchmark, Destroy) {
entt::registry registry; entt::registry registry;
std::vector<entt::entity> entities(1000000); std::vector<entt::entity> entities(1000000);
auto view = registry.view<int>(); auto view = registry.view<position>();
std::cout << "Destroying 1000000 entities" << std::endl; std::cout << "Destroying 1000000 entities" << std::endl;
registry.create(entities.begin(), entities.end()); registry.create(entities.begin(), entities.end());
registry.insert<int>(entities.begin(), entities.end()); registry.insert<position>(entities.begin(), entities.end());
timer timer;
generic_with([&]() {
for(auto entity: view) { for(auto entity: view) {
registry.destroy(entity); registry.destroy(entity);
} }
});
timer.elapsed();
} }
TEST(Benchmark, DestroyMany) { TEST(Benchmark, DestroyMany) {
entt::registry registry; entt::registry registry;
std::vector<entt::entity> entities(1000000); std::vector<entt::entity> entities(1000000);
auto view = registry.view<int>(); auto view = registry.view<position>();
std::cout << "Destroying 1000000 entities at once" << std::endl; std::cout << "Destroying 1000000 entities at once" << std::endl;
registry.create(entities.begin(), entities.end()); registry.create(entities.begin(), entities.end());
registry.insert<int>(entities.begin(), entities.end()); registry.insert<position>(entities.begin(), entities.end());
timer timer; generic_with([&]() {
registry.destroy(view.begin(), view.end()); registry.destroy(view.begin(), view.end());
timer.elapsed(); });
} }
TEST(Benchmark, DestroyManyFastPath) { TEST(Benchmark, DestroyManyMulti) {
entt::registry registry;
std::vector<entt::entity> entities(1000000);
auto view = registry.view<position>();
std::cout << "Destroying 1000000 entities at once, multiple components" << std::endl;
registry.create(entities.begin(), entities.end());
registry.insert<position>(entities.begin(), entities.end());
registry.insert<velocity>(entities.begin(), entities.end());
generic_with([&]() {
registry.destroy(view.begin(), view.end());
});
}
TEST(Benchmark, GetFromRegistry) {
entt::registry registry; entt::registry registry;
std::vector<entt::entity> entities(1000000); std::vector<entt::entity> entities(1000000);
std::cout << "Destroying 1000000 entities at once, fast path" << std::endl; registry.create(entities.begin(), entities.end());
registry.insert<position>(entities.begin(), entities.end());
generic_with([&]() {
for(auto entity: entities) {
registry.get<position>(entity).x = 0u;
}
});
}
TEST(Benchmark, GetFromRegistryMulti) {
entt::registry registry;
std::vector<entt::entity> entities(1000000);
registry.create(entities.begin(), entities.end()); registry.create(entities.begin(), entities.end());
registry.insert<int>(entities.begin(), entities.end()); registry.insert<position>(entities.begin(), entities.end());
registry.insert<velocity>(entities.begin(), entities.end());
timer timer; generic_with([&]() {
registry.destroy(entities.begin(), entities.end()); for(auto entity: entities) {
timer.elapsed(); registry.get<position>(entity).x = 0u;
registry.get<velocity>(entity).y = 0u;
}
});
}
TEST(Benchmark, GetFromView) {
entt::registry registry;
std::vector<entt::entity> entities(1000000);
auto view = registry.view<position>();
registry.create(entities.begin(), entities.end());
registry.insert<position>(entities.begin(), entities.end());
generic_with([&]() {
for(auto entity: entities) {
view.get<position>(entity).x = 0u;
}
});
}
TEST(Benchmark, GetFromViewMulti) {
entt::registry registry;
std::vector<entt::entity> entities(1000000);
auto view = registry.view<position, velocity>();
registry.create(entities.begin(), entities.end());
registry.insert<position>(entities.begin(), entities.end());
registry.insert<velocity>(entities.begin(), entities.end());
generic_with([&]() {
for(auto entity: entities) {
view.get<position>(entity).x = 0u;
view.get<velocity>(entity).y = 0u;
}
});
} }
TEST(Benchmark, IterateSingleComponent1M) { TEST(Benchmark, IterateSingleComponent1M) {
@ -306,22 +435,22 @@ TEST(Benchmark, IterateSingleComponent1M) {
registry.emplace<position>(entity); registry.emplace<position>(entity);
} }
generic(registry.view<position>(), [](auto &...comp) { iterate_with(registry.view<position>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
TEST(Benchmark, IterateSingleComponentTombstonePolicy1M) { TEST(Benchmark, IterateSingleStableComponent1M) {
entt::registry registry; 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++) { for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create(); const auto entity = registry.create();
registry.emplace<stable_position>(entity); registry.emplace<stable_position>(entity);
} }
generic(registry.view<stable_position>(), [](auto &...comp) { iterate_with(registry.view<stable_position>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -339,7 +468,7 @@ TEST(Benchmark, IterateSingleComponentRuntime1M) {
entt::runtime_view view{}; entt::runtime_view view{};
view.iterate(registry.storage<position>()); view.iterate(registry.storage<position>());
generic(view, [&registry](auto entity) { iterate_with(view, [&](auto entity) {
registry.get<position>(entity).x = {}; registry.get<position>(entity).x = {};
}); });
} }
@ -355,15 +484,15 @@ TEST(Benchmark, IterateTwoComponents1M) {
registry.emplace<velocity>(entity); registry.emplace<velocity>(entity);
} }
generic(registry.view<position, velocity>(), [](auto &...comp) { iterate_with(registry.view<position, velocity>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
TEST(Benchmark, IterateTombstonePolicyTwoComponentsTombstonePolicy1M) { TEST(Benchmark, IterateTwoStableComponents1M) {
entt::registry registry; 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++) { for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create(); const auto entity = registry.create();
@ -371,7 +500,7 @@ TEST(Benchmark, IterateTombstonePolicyTwoComponentsTombstonePolicy1M) {
registry.emplace<velocity>(entity); registry.emplace<velocity>(entity);
} }
generic(registry.view<stable_position, velocity>(), [](auto &...comp) { iterate_with(registry.view<stable_position, velocity>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -390,7 +519,7 @@ TEST(Benchmark, IterateTwoComponents1MHalf) {
} }
} }
generic(registry.view<position, velocity>(), [](auto &...comp) { iterate_with(registry.view<position, velocity>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -409,7 +538,7 @@ TEST(Benchmark, IterateTwoComponents1MOne) {
} }
} }
generic(registry.view<position, velocity>(), [](auto &...comp) { iterate_with(registry.view<position, velocity>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -425,7 +554,7 @@ TEST(Benchmark, IterateTwoComponentsNonOwningGroup1M) {
registry.emplace<velocity>(entity); registry.emplace<velocity>(entity);
} }
generic(registry.group<>(entt::get<position, velocity>), [](auto &...comp) { iterate_with(registry.group<>(entt::get<position, velocity>), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -441,7 +570,7 @@ TEST(Benchmark, IterateTwoComponentsFullOwningGroup1M) {
registry.emplace<velocity>(entity); registry.emplace<velocity>(entity);
} }
generic(registry.group<position, velocity>(), [](auto &...comp) { iterate_with(registry.group<position, velocity>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -457,7 +586,7 @@ TEST(Benchmark, IterateTwoComponentsPartialOwningGroup1M) {
registry.emplace<velocity>(entity); registry.emplace<velocity>(entity);
} }
generic(registry.group<position>(entt::get<velocity>), [](auto &...comp) { iterate_with(registry.group<position>(entt::get<velocity>), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -477,7 +606,7 @@ TEST(Benchmark, IterateTwoComponentsRuntime1M) {
view.iterate(registry.storage<position>()) view.iterate(registry.storage<position>())
.iterate(registry.storage<velocity>()); .iterate(registry.storage<velocity>());
generic(view, [&registry](auto entity) { iterate_with(view, [&](auto entity) {
registry.get<position>(entity).x = {}; registry.get<position>(entity).x = {};
registry.get<velocity>(entity).x = {}; registry.get<velocity>(entity).x = {};
}); });
@ -501,7 +630,7 @@ TEST(Benchmark, IterateTwoComponentsRuntime1MHalf) {
view.iterate(registry.storage<position>()) view.iterate(registry.storage<position>())
.iterate(registry.storage<velocity>()); .iterate(registry.storage<velocity>());
generic(view, [&registry](auto entity) { iterate_with(view, [&](auto entity) {
registry.get<position>(entity).x = {}; registry.get<position>(entity).x = {};
registry.get<velocity>(entity).x = {}; registry.get<velocity>(entity).x = {};
}); });
@ -525,7 +654,7 @@ TEST(Benchmark, IterateTwoComponentsRuntime1MOne) {
view.iterate(registry.storage<position>()) view.iterate(registry.storage<position>())
.iterate(registry.storage<velocity>()); .iterate(registry.storage<velocity>());
generic(view, [&registry](auto entity) { iterate_with(view, [&](auto entity) {
registry.get<position>(entity).x = {}; registry.get<position>(entity).x = {};
registry.get<velocity>(entity).x = {}; registry.get<velocity>(entity).x = {};
}); });
@ -543,15 +672,15 @@ TEST(Benchmark, IterateThreeComponents1M) {
registry.emplace<comp<0>>(entity); registry.emplace<comp<0>>(entity);
} }
generic(registry.view<position, velocity, comp<0>>(), [](auto &...comp) { iterate_with(registry.view<position, velocity, comp<0>>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
TEST(Benchmark, IterateThreeComponentsTombstonePolicy1M) { TEST(Benchmark, IterateThreeStableComponents1M) {
entt::registry registry; 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++) { for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create(); const auto entity = registry.create();
@ -560,7 +689,7 @@ TEST(Benchmark, IterateThreeComponentsTombstonePolicy1M) {
registry.emplace<comp<0>>(entity); registry.emplace<comp<0>>(entity);
} }
generic(registry.view<stable_position, velocity, comp<0>>(), [](auto &...comp) { iterate_with(registry.view<stable_position, velocity, comp<0>>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -580,7 +709,7 @@ TEST(Benchmark, IterateThreeComponents1MHalf) {
} }
} }
generic(registry.view<position, velocity, comp<0>>(), [](auto &...comp) { iterate_with(registry.view<position, velocity, comp<0>>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -600,7 +729,7 @@ TEST(Benchmark, IterateThreeComponents1MOne) {
} }
} }
generic(registry.view<position, velocity, comp<0>>(), [](auto &...comp) { iterate_with(registry.view<position, velocity, comp<0>>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -617,7 +746,7 @@ TEST(Benchmark, IterateThreeComponentsNonOwningGroup1M) {
registry.emplace<comp<0>>(entity); registry.emplace<comp<0>>(entity);
} }
generic(registry.group<>(entt::get<position, velocity, comp<0>>), [](auto &...comp) { iterate_with(registry.group<>(entt::get<position, velocity, comp<0>>), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -634,7 +763,7 @@ TEST(Benchmark, IterateThreeComponentsFullOwningGroup1M) {
registry.emplace<comp<0>>(entity); registry.emplace<comp<0>>(entity);
} }
generic(registry.group<position, velocity, comp<0>>(), [](auto &...comp) { iterate_with(registry.group<position, velocity, comp<0>>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -651,7 +780,7 @@ TEST(Benchmark, IterateThreeComponentsPartialOwningGroup1M) {
registry.emplace<comp<0>>(entity); registry.emplace<comp<0>>(entity);
} }
generic(registry.group<position, velocity>(entt::get<comp<0>>), [](auto &...comp) { iterate_with(registry.group<position, velocity>(entt::get<comp<0>>), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -673,7 +802,7 @@ TEST(Benchmark, IterateThreeComponentsRuntime1M) {
.iterate(registry.storage<velocity>()) .iterate(registry.storage<velocity>())
.iterate(registry.storage<comp<0>>()); .iterate(registry.storage<comp<0>>());
generic(view, [&registry](auto entity) { iterate_with(view, [&](auto entity) {
registry.get<position>(entity).x = {}; registry.get<position>(entity).x = {};
registry.get<velocity>(entity).x = {}; registry.get<velocity>(entity).x = {};
registry.get<comp<0>>(entity).x = {}; registry.get<comp<0>>(entity).x = {};
@ -700,7 +829,7 @@ TEST(Benchmark, IterateThreeComponentsRuntime1MHalf) {
.iterate(registry.storage<velocity>()) .iterate(registry.storage<velocity>())
.iterate(registry.storage<comp<0>>()); .iterate(registry.storage<comp<0>>());
generic(view, [&registry](auto entity) { iterate_with(view, [&](auto entity) {
registry.get<position>(entity).x = {}; registry.get<position>(entity).x = {};
registry.get<velocity>(entity).x = {}; registry.get<velocity>(entity).x = {};
registry.get<comp<0>>(entity).x = {}; registry.get<comp<0>>(entity).x = {};
@ -727,7 +856,7 @@ TEST(Benchmark, IterateThreeComponentsRuntime1MOne) {
.iterate(registry.storage<velocity>()) .iterate(registry.storage<velocity>())
.iterate(registry.storage<comp<0>>()); .iterate(registry.storage<comp<0>>());
generic(view, [&registry](auto entity) { iterate_with(view, [&](auto entity) {
registry.get<position>(entity).x = {}; registry.get<position>(entity).x = {};
registry.get<velocity>(entity).x = {}; registry.get<velocity>(entity).x = {};
registry.get<comp<0>>(entity).x = {}; registry.get<comp<0>>(entity).x = {};
@ -748,15 +877,15 @@ TEST(Benchmark, IterateFiveComponents1M) {
registry.emplace<comp<2>>(entity); registry.emplace<comp<2>>(entity);
} }
generic(registry.view<position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) { iterate_with(registry.view<position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
TEST(Benchmark, IterateFiveComponentsTombstonePolicy1M) { TEST(Benchmark, IterateFiveStableComponents1M) {
entt::registry registry; 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++) { for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create(); const auto entity = registry.create();
@ -767,7 +896,7 @@ TEST(Benchmark, IterateFiveComponentsTombstonePolicy1M) {
registry.emplace<comp<2>>(entity); registry.emplace<comp<2>>(entity);
} }
generic(registry.view<stable_position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) { iterate_with(registry.view<stable_position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -789,7 +918,7 @@ TEST(Benchmark, IterateFiveComponents1MHalf) {
} }
} }
generic(registry.view<position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) { iterate_with(registry.view<position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -811,7 +940,7 @@ TEST(Benchmark, IterateFiveComponents1MOne) {
} }
} }
generic(registry.view<position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) { iterate_with(registry.view<position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -830,7 +959,7 @@ TEST(Benchmark, IterateFiveComponentsNonOwningGroup1M) {
registry.emplace<comp<2>>(entity); registry.emplace<comp<2>>(entity);
} }
generic(registry.group<>(entt::get<position, velocity, comp<0>, comp<1>, comp<2>>), [](auto &...comp) { iterate_with(registry.group<>(entt::get<position, velocity, comp<0>, comp<1>, comp<2>>), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -849,7 +978,7 @@ TEST(Benchmark, IterateFiveComponentsFullOwningGroup1M) {
registry.emplace<comp<2>>(entity); registry.emplace<comp<2>>(entity);
} }
generic(registry.group<position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) { iterate_with(registry.group<position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -868,7 +997,7 @@ TEST(Benchmark, IterateFiveComponentsPartialFourOfFiveOwningGroup1M) {
registry.emplace<comp<2>>(entity); registry.emplace<comp<2>>(entity);
} }
generic(registry.group<position, velocity, comp<0>, comp<1>>(entt::get<comp<2>>), [](auto &...comp) { iterate_with(registry.group<position, velocity, comp<0>, comp<1>>(entt::get<comp<2>>), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -887,7 +1016,7 @@ TEST(Benchmark, IterateFiveComponentsPartialThreeOfFiveOwningGroup1M) {
registry.emplace<comp<2>>(entity); registry.emplace<comp<2>>(entity);
} }
generic(registry.group<position, velocity, comp<0>>(entt::get<comp<1>, comp<2>>), [](auto &...comp) { iterate_with(registry.group<position, velocity, comp<0>>(entt::get<comp<1>, comp<2>>), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -913,7 +1042,7 @@ TEST(Benchmark, IterateFiveComponentsRuntime1M) {
.iterate(registry.storage<comp<1>>()) .iterate(registry.storage<comp<1>>())
.iterate(registry.storage<comp<2>>()); .iterate(registry.storage<comp<2>>());
generic(view, [&registry](auto entity) { iterate_with(view, [&](auto entity) {
registry.get<position>(entity).x = {}; registry.get<position>(entity).x = {};
registry.get<velocity>(entity).x = {}; registry.get<velocity>(entity).x = {};
registry.get<comp<0>>(entity).x = {}; registry.get<comp<0>>(entity).x = {};
@ -946,7 +1075,7 @@ TEST(Benchmark, IterateFiveComponentsRuntime1MHalf) {
.iterate(registry.storage<comp<1>>()) .iterate(registry.storage<comp<1>>())
.iterate(registry.storage<comp<2>>()); .iterate(registry.storage<comp<2>>());
generic(view, [&registry](auto entity) { iterate_with(view, [&](auto entity) {
registry.get<position>(entity).x = {}; registry.get<position>(entity).x = {};
registry.get<velocity>(entity).x = {}; registry.get<velocity>(entity).x = {};
registry.get<comp<0>>(entity).x = {}; registry.get<comp<0>>(entity).x = {};
@ -979,7 +1108,7 @@ TEST(Benchmark, IterateFiveComponentsRuntime1MOne) {
.iterate(registry.storage<comp<1>>()) .iterate(registry.storage<comp<1>>())
.iterate(registry.storage<comp<2>>()); .iterate(registry.storage<comp<2>>());
generic(view, [&registry](auto entity) { iterate_with(view, [&](auto entity) {
registry.get<position>(entity).x = {}; registry.get<position>(entity).x = {};
registry.get<velocity>(entity).x = {}; registry.get<velocity>(entity).x = {};
registry.get<comp<0>>(entity).x = {}; registry.get<comp<0>>(entity).x = {};
@ -990,22 +1119,22 @@ TEST(Benchmark, IterateFiveComponentsRuntime1MOne) {
TEST(Benchmark, IteratePathological) { TEST(Benchmark, IteratePathological) {
std::cout << "Pathological case" << std::endl; std::cout << "Pathological case" << std::endl;
pathological([](auto &registry) { return registry.template view<position, velocity, comp<0>>(); }); pathological_with([](auto &registry) { return registry.template view<position, velocity, comp<0>>(); });
} }
TEST(Benchmark, IteratePathologicalNonOwningGroup) { TEST(Benchmark, IteratePathologicalNonOwningGroup) {
std::cout << "Pathological case (non-owning group)" << std::endl; std::cout << "Pathological case (non-owning group)" << std::endl;
pathological([](auto &registry) { return registry.template group<>(entt::get<position, velocity, comp<0>>); }); pathological_with([](auto &registry) { return registry.template group<>(entt::get<position, velocity, comp<0>>); });
} }
TEST(Benchmark, IteratePathologicalFullOwningGroup) { TEST(Benchmark, IteratePathologicalFullOwningGroup) {
std::cout << "Pathological case (full-owning group)" << std::endl; std::cout << "Pathological case (full-owning group)" << std::endl;
pathological([](auto &registry) { return registry.template group<position, velocity, comp<0>>(); }); pathological_with([](auto &registry) { return registry.template group<position, velocity, comp<0>>(); });
} }
TEST(Benchmark, IteratePathologicalPartialOwningGroup) { TEST(Benchmark, IteratePathologicalPartialOwningGroup) {
std::cout << "Pathological case (partial-owning group)" << std::endl; std::cout << "Pathological case (partial-owning group)" << std::endl;
pathological([](auto &registry) { return registry.template group<position, velocity>(entt::get<comp<0>>); }); pathological_with([](auto &registry) { return registry.template group<position, velocity>(entt::get<comp<0>>); });
} }
TEST(Benchmark, SortSingle) { TEST(Benchmark, SortSingle) {
@ -1018,9 +1147,9 @@ TEST(Benchmark, SortSingle) {
registry.emplace<position>(entity, i, i); registry.emplace<position>(entity, i, i);
} }
timer timer; generic_with([&]() {
registry.sort<position>([](const auto &lhs, const auto &rhs) { return lhs.x < rhs.x && lhs.y < rhs.y; }); registry.sort<position>([](const auto &lhs, const auto &rhs) { return lhs.x < rhs.x && lhs.y < rhs.y; });
timer.elapsed(); });
} }
TEST(Benchmark, SortMulti) { TEST(Benchmark, SortMulti) {
@ -1036,9 +1165,9 @@ TEST(Benchmark, SortMulti) {
registry.sort<position>([](const auto &lhs, const auto &rhs) { return lhs.x < rhs.x && lhs.y < rhs.y; }); registry.sort<position>([](const auto &lhs, const auto &rhs) { return lhs.x < rhs.x && lhs.y < rhs.y; });
timer timer; generic_with([&]() {
registry.sort<velocity, position>(); registry.sort<velocity, position>();
timer.elapsed(); });
} }
TEST(Benchmark, AlmostSortedStdSort) { TEST(Benchmark, AlmostSortedStdSort) {
@ -1062,9 +1191,9 @@ TEST(Benchmark, AlmostSortedStdSort) {
registry.emplace<position>(entity, 50000 * i, 50000 * i); registry.emplace<position>(entity, 50000 * i, 50000 * i);
} }
timer timer; generic_with([&]() {
registry.sort<position>([](const auto &lhs, const auto &rhs) { return lhs.x > rhs.x && lhs.y > rhs.y; }); registry.sort<position>([](const auto &lhs, const auto &rhs) { return lhs.x > rhs.x && lhs.y > rhs.y; });
timer.elapsed(); });
} }
TEST(Benchmark, AlmostSortedInsertionSort) { TEST(Benchmark, AlmostSortedInsertionSort) {
@ -1088,7 +1217,7 @@ TEST(Benchmark, AlmostSortedInsertionSort) {
registry.emplace<position>(entity, 50000 * i, 50000 * i); registry.emplace<position>(entity, 50000 * i, 50000 * i);
} }
timer timer; generic_with([&]() {
registry.sort<position>([](const auto &lhs, const auto &rhs) { return lhs.x > rhs.x && lhs.y > rhs.y; }, entt::insertion_sort{}); registry.sort<position>([](const auto &lhs, const auto &rhs) { return lhs.x > rhs.x && lhs.y > rhs.y; }, entt::insertion_sort{});
timer.elapsed(); });
} }

View File

@ -22,7 +22,7 @@ struct basic_test_allocator: std::allocator<Type> {
return *this; return *this;
} }
bool operator==(const basic_test_allocator &other) { bool operator==(const basic_test_allocator &other) const {
return (this == &other); return (this == &other);
} }
}; };

View File

@ -25,14 +25,14 @@ public:
using propagate_on_container_swap = std::true_type; using propagate_on_container_swap = std::true_type;
using exception_type = test_exception; using exception_type = test_exception;
template<class Other> template<typename Other>
struct rebind { struct rebind {
using other = throwing_allocator<Other>; using other = throwing_allocator<Other>;
}; };
throwing_allocator() = default; throwing_allocator() = default;
template<class Other> template<typename Other>
throwing_allocator(const throwing_allocator<Other> &other) throwing_allocator(const throwing_allocator<Other> &other)
: base{other} {} : base{other} {}

View File

@ -100,7 +100,7 @@ TEST(DenseMap, Functionalities) {
} }
TEST(DenseMap, Constructors) { TEST(DenseMap, Constructors) {
static constexpr std::size_t minimum_bucket_count = 8u; constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<int, int> map; entt::dense_map<int, int> map;
ASSERT_EQ(map.bucket_count(), minimum_bucket_count); ASSERT_EQ(map.bucket_count(), minimum_bucket_count);
@ -395,7 +395,7 @@ TEST(DenseMap, Insert) {
} }
TEST(DenseMap, InsertRehash) { TEST(DenseMap, InsertRehash) {
static constexpr std::size_t minimum_bucket_count = 8u; constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map; entt::dense_map<std::size_t, std::size_t, entt::identity> map;
ASSERT_EQ(map.size(), 0u); ASSERT_EQ(map.size(), 0u);
@ -429,7 +429,7 @@ TEST(DenseMap, InsertRehash) {
} }
TEST(DenseMap, InsertSameBucket) { TEST(DenseMap, InsertSameBucket) {
static constexpr std::size_t minimum_bucket_count = 8u; constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map; entt::dense_map<std::size_t, std::size_t, entt::identity> map;
for(std::size_t next{}; next < minimum_bucket_count; ++next) { for(std::size_t next{}; next < minimum_bucket_count; ++next) {
@ -598,7 +598,7 @@ TEST(DenseMap, Emplace) {
} }
TEST(DenseMap, EmplaceRehash) { TEST(DenseMap, EmplaceRehash) {
static constexpr std::size_t minimum_bucket_count = 8u; constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map; entt::dense_map<std::size_t, std::size_t, entt::identity> map;
ASSERT_EQ(map.size(), 0u); ASSERT_EQ(map.size(), 0u);
@ -633,7 +633,7 @@ TEST(DenseMap, EmplaceRehash) {
} }
TEST(DenseMap, EmplaceSameBucket) { TEST(DenseMap, EmplaceSameBucket) {
static constexpr std::size_t minimum_bucket_count = 8u; constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map; entt::dense_map<std::size_t, std::size_t, entt::identity> map;
for(std::size_t next{}; next < minimum_bucket_count; ++next) { for(std::size_t next{}; next < minimum_bucket_count; ++next) {
@ -681,7 +681,7 @@ TEST(DenseMap, TryEmplace) {
} }
TEST(DenseMap, TryEmplaceRehash) { TEST(DenseMap, TryEmplaceRehash) {
static constexpr std::size_t minimum_bucket_count = 8u; constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map; entt::dense_map<std::size_t, std::size_t, entt::identity> map;
ASSERT_EQ(map.size(), 0u); ASSERT_EQ(map.size(), 0u);
@ -715,7 +715,7 @@ TEST(DenseMap, TryEmplaceRehash) {
} }
TEST(DenseMap, TryEmplaceSameBucket) { TEST(DenseMap, TryEmplaceSameBucket) {
static constexpr std::size_t minimum_bucket_count = 8u; constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map; entt::dense_map<std::size_t, std::size_t, entt::identity> map;
for(std::size_t next{}; next < minimum_bucket_count; ++next) { for(std::size_t next{}; next < minimum_bucket_count; ++next) {
@ -749,7 +749,7 @@ TEST(DenseMap, TryEmplaceMovableType) {
} }
TEST(DenseMap, Erase) { TEST(DenseMap, Erase) {
static constexpr std::size_t minimum_bucket_count = 8u; constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map; entt::dense_map<std::size_t, std::size_t, entt::identity> map;
for(std::size_t next{}, last = minimum_bucket_count + 1u; next < last; ++next) { for(std::size_t next{}, last = minimum_bucket_count + 1u; next < last; ++next) {
@ -799,7 +799,7 @@ TEST(DenseMap, Erase) {
} }
TEST(DenseMap, EraseWithMovableKeyValue) { TEST(DenseMap, EraseWithMovableKeyValue) {
static constexpr std::size_t minimum_bucket_count = 8u; constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::string, std::size_t> map; entt::dense_map<std::string, std::size_t> map;
map.emplace("0", 0u); map.emplace("0", 0u);
@ -817,7 +817,7 @@ TEST(DenseMap, EraseWithMovableKeyValue) {
} }
TEST(DenseMap, EraseFromBucket) { TEST(DenseMap, EraseFromBucket) {
static constexpr std::size_t minimum_bucket_count = 8u; constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map; entt::dense_map<std::size_t, std::size_t, entt::identity> map;
ASSERT_EQ(map.bucket_count(), minimum_bucket_count); ASSERT_EQ(map.bucket_count(), minimum_bucket_count);
@ -995,7 +995,7 @@ TEST(DenseMap, LocalIterator) {
static_assert(std::is_same_v<iterator::pointer, entt::input_iterator_pointer<std::pair<const std::size_t &, std::size_t &>>>); static_assert(std::is_same_v<iterator::pointer, entt::input_iterator_pointer<std::pair<const std::size_t &, std::size_t &>>>);
static_assert(std::is_same_v<iterator::reference, std::pair<const std::size_t &, std::size_t &>>); static_assert(std::is_same_v<iterator::reference, std::pair<const std::size_t &, std::size_t &>>);
static constexpr std::size_t minimum_bucket_count = 8u; constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map; entt::dense_map<std::size_t, std::size_t, entt::identity> map;
map.emplace(3u, 42u); map.emplace(3u, 42u);
map.emplace(3u + minimum_bucket_count, 99u); map.emplace(3u + minimum_bucket_count, 99u);
@ -1023,7 +1023,7 @@ TEST(DenseMap, ConstLocalIterator) {
static_assert(std::is_same_v<iterator::pointer, entt::input_iterator_pointer<std::pair<const std::size_t &, const std::size_t &>>>); static_assert(std::is_same_v<iterator::pointer, entt::input_iterator_pointer<std::pair<const std::size_t &, const std::size_t &>>>);
static_assert(std::is_same_v<iterator::reference, std::pair<const std::size_t &, const std::size_t &>>); static_assert(std::is_same_v<iterator::reference, std::pair<const std::size_t &, const std::size_t &>>);
static constexpr std::size_t minimum_bucket_count = 8u; constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map; entt::dense_map<std::size_t, std::size_t, entt::identity> map;
map.emplace(3u, 42u); map.emplace(3u, 42u);
map.emplace(3u + minimum_bucket_count, 99u); map.emplace(3u + minimum_bucket_count, 99u);
@ -1064,7 +1064,7 @@ TEST(DenseMap, LocalIteratorConversion) {
} }
TEST(DenseMap, Rehash) { TEST(DenseMap, Rehash) {
static constexpr std::size_t minimum_bucket_count = 8u; constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map; entt::dense_map<std::size_t, std::size_t, entt::identity> map;
map[32u] = 99u; map[32u] = 99u;
@ -1147,7 +1147,7 @@ TEST(DenseMap, Rehash) {
} }
TEST(DenseMap, Reserve) { TEST(DenseMap, Reserve) {
static constexpr std::size_t minimum_bucket_count = 8u; constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<int, int> map; entt::dense_map<int, int> map;
ASSERT_EQ(map.bucket_count(), minimum_bucket_count); ASSERT_EQ(map.bucket_count(), minimum_bucket_count);
@ -1159,7 +1159,7 @@ TEST(DenseMap, Reserve) {
map.reserve(minimum_bucket_count); map.reserve(minimum_bucket_count);
ASSERT_EQ(map.bucket_count(), 2 * minimum_bucket_count); ASSERT_EQ(map.bucket_count(), 2 * minimum_bucket_count);
ASSERT_EQ(map.bucket_count(), entt::next_power_of_two(std::ceil(minimum_bucket_count / map.max_load_factor()))); ASSERT_EQ(map.bucket_count(), entt::next_power_of_two(static_cast<std::size_t>(std::ceil(minimum_bucket_count / map.max_load_factor()))));
} }
TEST(DenseMap, ThrowingAllocator) { TEST(DenseMap, ThrowingAllocator) {
@ -1167,7 +1167,7 @@ TEST(DenseMap, ThrowingAllocator) {
using packed_allocator = test::throwing_allocator<entt::internal::dense_map_node<std::size_t, std::size_t>>; using packed_allocator = test::throwing_allocator<entt::internal::dense_map_node<std::size_t, std::size_t>>;
using packed_exception = typename packed_allocator::exception_type; using packed_exception = typename packed_allocator::exception_type;
static constexpr std::size_t minimum_bucket_count = 8u; constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, std::hash<std::size_t>, std::equal_to<std::size_t>, allocator> map{}; entt::dense_map<std::size_t, std::size_t, std::hash<std::size_t>, std::equal_to<std::size_t>, allocator> map{};
packed_allocator::trigger_on_allocate = true; packed_allocator::trigger_on_allocate = true;

View File

@ -98,7 +98,7 @@ TEST(DenseSet, Functionalities) {
} }
TEST(DenseSet, Constructors) { TEST(DenseSet, Constructors) {
static constexpr std::size_t minimum_bucket_count = 8u; constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<int> set; entt::dense_set<int> set;
ASSERT_EQ(set.bucket_count(), minimum_bucket_count); ASSERT_EQ(set.bucket_count(), minimum_bucket_count);
@ -357,7 +357,7 @@ TEST(DenseSet, Insert) {
} }
TEST(DenseSet, InsertRehash) { TEST(DenseSet, InsertRehash) {
static constexpr std::size_t minimum_bucket_count = 8u; constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::size_t, entt::identity> set; entt::dense_set<std::size_t, entt::identity> set;
ASSERT_EQ(set.size(), 0u); ASSERT_EQ(set.size(), 0u);
@ -388,7 +388,7 @@ TEST(DenseSet, InsertRehash) {
} }
TEST(DenseSet, InsertSameBucket) { TEST(DenseSet, InsertSameBucket) {
static constexpr std::size_t minimum_bucket_count = 8u; constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::size_t, entt::identity> set; entt::dense_set<std::size_t, entt::identity> set;
for(std::size_t next{}; next < minimum_bucket_count; ++next) { for(std::size_t next{}; next < minimum_bucket_count; ++next) {
@ -451,7 +451,7 @@ TEST(DenseSet, Emplace) {
} }
TEST(DenseSet, EmplaceRehash) { TEST(DenseSet, EmplaceRehash) {
static constexpr std::size_t minimum_bucket_count = 8u; constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::size_t, entt::identity> set; entt::dense_set<std::size_t, entt::identity> set;
ASSERT_EQ(set.size(), 0u); ASSERT_EQ(set.size(), 0u);
@ -483,7 +483,7 @@ TEST(DenseSet, EmplaceRehash) {
} }
TEST(DenseSet, EmplaceSameBucket) { TEST(DenseSet, EmplaceSameBucket) {
static constexpr std::size_t minimum_bucket_count = 8u; constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::size_t, entt::identity> set; entt::dense_set<std::size_t, entt::identity> set;
for(std::size_t next{}; next < minimum_bucket_count; ++next) { for(std::size_t next{}; next < minimum_bucket_count; ++next) {
@ -503,7 +503,7 @@ TEST(DenseSet, EmplaceSameBucket) {
} }
TEST(DenseSet, Erase) { TEST(DenseSet, Erase) {
static constexpr std::size_t minimum_bucket_count = 8u; constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::size_t, entt::identity> set; entt::dense_set<std::size_t, entt::identity> set;
for(std::size_t next{}, last = minimum_bucket_count + 1u; next < last; ++next) { for(std::size_t next{}, last = minimum_bucket_count + 1u; next < last; ++next) {
@ -553,7 +553,7 @@ TEST(DenseSet, Erase) {
} }
TEST(DenseSet, EraseWithMovableKeyValue) { TEST(DenseSet, EraseWithMovableKeyValue) {
static constexpr std::size_t minimum_bucket_count = 8u; constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::string> set; entt::dense_set<std::string> set;
set.emplace("0"); set.emplace("0");
@ -570,7 +570,7 @@ TEST(DenseSet, EraseWithMovableKeyValue) {
} }
TEST(DenseSet, EraseFromBucket) { TEST(DenseSet, EraseFromBucket) {
static constexpr std::size_t minimum_bucket_count = 8u; constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::size_t, entt::identity> set; entt::dense_set<std::size_t, entt::identity> set;
ASSERT_EQ(set.bucket_count(), minimum_bucket_count); ASSERT_EQ(set.bucket_count(), minimum_bucket_count);
@ -721,7 +721,7 @@ TEST(DenseSet, LocalIterator) {
static_assert(std::is_same_v<iterator::pointer, const std::size_t *>); static_assert(std::is_same_v<iterator::pointer, const std::size_t *>);
static_assert(std::is_same_v<iterator::reference, const std::size_t &>); static_assert(std::is_same_v<iterator::reference, const std::size_t &>);
static constexpr std::size_t minimum_bucket_count = 8u; constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::size_t, entt::identity> set; entt::dense_set<std::size_t, entt::identity> set;
set.emplace(3u); set.emplace(3u);
set.emplace(3u + minimum_bucket_count); set.emplace(3u + minimum_bucket_count);
@ -749,7 +749,7 @@ TEST(DenseSet, ConstLocalIterator) {
static_assert(std::is_same_v<iterator::pointer, const std::size_t *>); static_assert(std::is_same_v<iterator::pointer, const std::size_t *>);
static_assert(std::is_same_v<iterator::reference, const std::size_t &>); static_assert(std::is_same_v<iterator::reference, const std::size_t &>);
static constexpr std::size_t minimum_bucket_count = 8u; constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::size_t, entt::identity> set; entt::dense_set<std::size_t, entt::identity> set;
set.emplace(3u); set.emplace(3u);
set.emplace(3u + minimum_bucket_count); set.emplace(3u + minimum_bucket_count);
@ -790,7 +790,7 @@ TEST(DenseSet, LocalIteratorConversion) {
} }
TEST(DenseSet, Rehash) { TEST(DenseSet, Rehash) {
static constexpr std::size_t minimum_bucket_count = 8u; constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::size_t, entt::identity> set; entt::dense_set<std::size_t, entt::identity> set;
set.emplace(32u); set.emplace(32u);
@ -865,7 +865,7 @@ TEST(DenseSet, Rehash) {
} }
TEST(DenseSet, Reserve) { TEST(DenseSet, Reserve) {
static constexpr std::size_t minimum_bucket_count = 8u; constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<int> set; entt::dense_set<int> set;
ASSERT_EQ(set.bucket_count(), minimum_bucket_count); ASSERT_EQ(set.bucket_count(), minimum_bucket_count);
@ -877,7 +877,7 @@ TEST(DenseSet, Reserve) {
set.reserve(minimum_bucket_count); set.reserve(minimum_bucket_count);
ASSERT_EQ(set.bucket_count(), 2 * minimum_bucket_count); ASSERT_EQ(set.bucket_count(), 2 * minimum_bucket_count);
ASSERT_EQ(set.bucket_count(), entt::next_power_of_two(std::ceil(minimum_bucket_count / set.max_load_factor()))); ASSERT_EQ(set.bucket_count(), entt::next_power_of_two(static_cast<std::size_t>(std::ceil(minimum_bucket_count / set.max_load_factor()))));
} }
TEST(DenseSet, ThrowingAllocator) { TEST(DenseSet, ThrowingAllocator) {
@ -885,7 +885,7 @@ TEST(DenseSet, ThrowingAllocator) {
using packed_allocator = test::throwing_allocator<std::pair<std::size_t, std::size_t>>; using packed_allocator = test::throwing_allocator<std::pair<std::size_t, std::size_t>>;
using packed_exception = typename packed_allocator::exception_type; using packed_exception = typename packed_allocator::exception_type;
static constexpr std::size_t minimum_bucket_count = 8u; constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::size_t, std::hash<std::size_t>, std::equal_to<std::size_t>, allocator> set{}; entt::dense_set<std::size_t, std::hash<std::size_t>, std::equal_to<std::size_t>, allocator> set{};
packed_allocator::trigger_on_allocate = true; packed_allocator::trigger_on_allocate = true;

View File

@ -2,6 +2,7 @@
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include <iterator> #include <iterator>
#include <memory>
#include <type_traits> #include <type_traits>
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>
@ -39,21 +40,9 @@ struct not_comparable {
bool operator==(const not_comparable &) const = delete; 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 { struct not_movable {
not_movable() = default; not_movable() = default;
not_movable(const not_movable &) = default; not_movable(const not_movable &) = default;
not_movable(not_movable &&) = delete; not_movable(not_movable &&) = delete;
@ -1183,13 +1172,12 @@ TEST_F(Any, AnyCast) {
ASSERT_EQ(entt::any_cast<int &>(any), 42); ASSERT_EQ(entt::any_cast<int &>(any), 42);
ASSERT_EQ(entt::any_cast<const int &>(cany), 42); ASSERT_EQ(entt::any_cast<const int &>(cany), 42);
not_copyable instance{}; auto instance = std::make_unique<double>(42.);
instance.payload = 42.;
entt::any ref{entt::forward_as_any(instance)}; entt::any ref{entt::forward_as_any(instance)};
entt::any cref{entt::forward_as_any(std::as_const(instance).payload)}; entt::any cref{entt::forward_as_any(std::as_const(*instance))};
ASSERT_EQ(entt::any_cast<not_copyable>(std::move(ref)).payload, 42.);
ASSERT_EQ(entt::any_cast<double>(std::move(cref)), 42.); ASSERT_EQ(entt::any_cast<double>(std::move(cref)), 42.);
ASSERT_EQ(*entt::any_cast<std::unique_ptr<double>>(std::move(ref)), 42.);
ASSERT_EQ(entt::any_cast<int>(entt::any{42}), 42); ASSERT_EQ(entt::any_cast<int>(entt::any{42}), 42);
} }
@ -1197,16 +1185,15 @@ ENTT_DEBUG_TEST_F(AnyDeathTest, AnyCast) {
entt::any any{42}; entt::any any{42};
const auto &cany = any; const auto &cany = any;
ASSERT_DEATH(entt::any_cast<double &>(any), ""); ASSERT_DEATH([[maybe_unused]] auto &elem = entt::any_cast<double &>(any), "");
ASSERT_DEATH(entt::any_cast<const double &>(cany), ""); ASSERT_DEATH([[maybe_unused]] const auto &elem = entt::any_cast<const double &>(cany), "");
not_copyable instance{}; auto instance = std::make_unique<double>(42.);
instance.payload = 42.;
entt::any ref{entt::forward_as_any(instance)}; entt::any ref{entt::forward_as_any(instance)};
entt::any cref{entt::forward_as_any(std::as_const(instance).payload)}; entt::any cref{entt::forward_as_any(std::as_const(*instance))};
ASSERT_DEATH(entt::any_cast<not_copyable>(std::as_const(ref).as_ref()), ""); ASSERT_DEATH([[maybe_unused]] auto elem = entt::any_cast<std::unique_ptr<double>>(std::as_const(ref).as_ref()), "");
ASSERT_DEATH(entt::any_cast<double>(entt::any{42}), ""); ASSERT_DEATH([[maybe_unused]] auto elem = entt::any_cast<double>(entt::any{42}), "");
} }
TEST_F(Any, MakeAny) { TEST_F(Any, MakeAny) {
@ -1263,8 +1250,8 @@ TEST_F(Any, ForwardAsAny) {
} }
TEST_F(Any, NotCopyableType) { TEST_F(Any, NotCopyableType) {
const not_copyable value{}; const std::unique_ptr<int> value{};
entt::any any{std::in_place_type<not_copyable>}; entt::any any{std::in_place_type<std::unique_ptr<int>>};
entt::any other = entt::forward_as_any(value); entt::any other = entt::forward_as_any(value);
ASSERT_TRUE(any); ASSERT_TRUE(any);
@ -1296,7 +1283,7 @@ TEST_F(Any, NotCopyableType) {
TEST_F(Any, NotCopyableValueType) { TEST_F(Any, NotCopyableValueType) {
std::vector<entt::any> vec{}; std::vector<entt::any> vec{};
vec.emplace_back(std::in_place_type<not_copyable>); vec.emplace_back(std::in_place_type<std::unique_ptr<int>>);
vec.shrink_to_fit(); vec.shrink_to_fit();
ASSERT_EQ(vec.size(), 1u); ASSERT_EQ(vec.size(), 1u);
@ -1304,7 +1291,7 @@ TEST_F(Any, NotCopyableValueType) {
ASSERT_TRUE(vec[0u]); ASSERT_TRUE(vec[0u]);
// strong exception guarantee due to noexcept move ctor // strong exception guarantee due to noexcept move ctor
vec.emplace_back(std::in_place_type<not_copyable>); vec.emplace_back(std::in_place_type<std::unique_ptr<int>>);
ASSERT_EQ(vec.size(), 2u); ASSERT_EQ(vec.size(), 2u);
ASSERT_TRUE(vec[0u]); ASSERT_TRUE(vec[0u]);
@ -1411,7 +1398,7 @@ TEST_F(Any, SBOVsZeroedSBOSize) {
} }
TEST_F(Any, SboAlignment) { TEST_F(Any, SboAlignment) {
static constexpr auto alignment = alignof(over_aligned); constexpr auto alignment = alignof(over_aligned);
entt::basic_any<alignment, alignment> sbo[2] = {over_aligned{}, over_aligned{}}; entt::basic_any<alignment, alignment> sbo[2] = {over_aligned{}, over_aligned{}};
const auto *data = sbo[0].data(); const auto *data = sbo[0].data();
@ -1427,7 +1414,7 @@ TEST_F(Any, SboAlignment) {
} }
TEST_F(Any, NoSboAlignment) { TEST_F(Any, NoSboAlignment) {
static constexpr auto alignment = alignof(over_aligned); constexpr auto alignment = alignof(over_aligned);
entt::basic_any<alignment> nosbo[2] = {over_aligned{}, over_aligned{}}; entt::basic_any<alignment> nosbo[2] = {over_aligned{}, over_aligned{}};
const auto *data = nosbo[0].data(); const auto *data = nosbo[0].data();

View File

@ -6,19 +6,19 @@
#include <entt/core/hashed_string.hpp> #include <entt/core/hashed_string.hpp>
template<typename> template<typename>
struct foobar_t; struct expected;
template<> template<>
struct foobar_t<std::uint32_t> { struct expected<std::uint32_t> {
static constexpr auto value = 0xbf9cf968; static constexpr auto value = 0xbf9cf968;
}; };
template<> template<>
struct foobar_t<std::uint64_t> { struct expected<std::uint64_t> {
static constexpr auto value = 0x85944171f73967e8; static constexpr auto value = 0x85944171f73967e8;
}; };
inline constexpr auto foobar_v = foobar_t<entt::id_type>::value; inline constexpr auto expected_v = expected<entt::id_type>::value;
TEST(BasicHashedString, DeductionGuide) { TEST(BasicHashedString, DeductionGuide) {
static_assert(std::is_same_v<decltype(entt::basic_hashed_string{"foo"}), entt::hashed_string>); static_assert(std::is_same_v<decltype(entt::basic_hashed_string{"foo"}), entt::hashed_string>);
@ -47,8 +47,8 @@ TEST(HashedString, Functionalities) {
entt::hashed_string hs{"foobar"}; entt::hashed_string hs{"foobar"};
ASSERT_EQ(static_cast<hash_type>(hs), foobar_v); ASSERT_EQ(static_cast<hash_type>(hs), expected_v);
ASSERT_EQ(hs.value(), foobar_v); ASSERT_EQ(hs.value(), expected_v);
ASSERT_EQ(foo_hs, "foo"_hs); ASSERT_EQ(foo_hs, "foo"_hs);
ASSERT_NE(bar_hs, "foo"_hs); ASSERT_NE(bar_hs, "foo"_hs);
@ -78,13 +78,13 @@ TEST(HashedString, Correctness) {
const char *foobar = "foobar"; const char *foobar = "foobar";
std::string_view view{"foobar__", 6}; std::string_view view{"foobar__", 6};
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()}), foobar_v); ASSERT_EQ((entt::hashed_string{view.data(), view.size()}), expected_v);
ASSERT_EQ(entt::hashed_string{"foobar"}, foobar_v); ASSERT_EQ(entt::hashed_string{"foobar"}, expected_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()), foobar_v); ASSERT_EQ(entt::hashed_string::value(view.data(), view.size()), expected_v);
ASSERT_EQ(entt::hashed_string::value("foobar"), foobar_v); ASSERT_EQ(entt::hashed_string::value("foobar"), expected_v);
ASSERT_EQ(entt::hashed_string{foobar}.size(), 6u); ASSERT_EQ(entt::hashed_string{foobar}.size(), 6u);
ASSERT_EQ((entt::hashed_string{view.data(), view.size()}).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}; constexpr std::string_view view{"foobar__", 6};
static_assert(entt::hashed_string{"quux"} == "quux"_hs); 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("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{"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("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"} < "foo"_hs);
static_assert(entt::hashed_string{"bar"} <= "bar"_hs); static_assert(entt::hashed_string{"bar"} <= "bar"_hs);
@ -151,8 +151,8 @@ TEST(HashedWString, Functionalities) {
entt::hashed_wstring hws{L"foobar"}; entt::hashed_wstring hws{L"foobar"};
ASSERT_EQ(static_cast<hash_type>(hws), foobar_v); ASSERT_EQ(static_cast<hash_type>(hws), expected_v);
ASSERT_EQ(hws.value(), foobar_v); ASSERT_EQ(hws.value(), expected_v);
ASSERT_EQ(foo_hws, L"foo"_hws); ASSERT_EQ(foo_hws, L"foo"_hws);
ASSERT_NE(bar_hws, L"foo"_hws); ASSERT_NE(bar_hws, L"foo"_hws);
@ -172,13 +172,13 @@ TEST(HashedWString, Correctness) {
const wchar_t *foobar = L"foobar"; const wchar_t *foobar = L"foobar";
std::wstring_view view{L"foobar__", 6}; std::wstring_view view{L"foobar__", 6};
ASSERT_EQ(entt::hashed_wstring{foobar}, foobar_v); ASSERT_EQ(entt::hashed_wstring{foobar}, expected_v);
ASSERT_EQ((entt::hashed_wstring{view.data(), view.size()}), foobar_v); ASSERT_EQ((entt::hashed_wstring{view.data(), view.size()}), expected_v);
ASSERT_EQ(entt::hashed_wstring{L"foobar"}, foobar_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(foobar), expected_v);
ASSERT_EQ(entt::hashed_wstring::value(view.data(), view.size()), foobar_v); ASSERT_EQ(entt::hashed_wstring::value(view.data(), view.size()), expected_v);
ASSERT_EQ(entt::hashed_wstring::value(L"foobar"), foobar_v); ASSERT_EQ(entt::hashed_wstring::value(L"foobar"), expected_v);
ASSERT_EQ(entt::hashed_wstring{foobar}.size(), 6u); ASSERT_EQ(entt::hashed_wstring{foobar}.size(), 6u);
ASSERT_EQ((entt::hashed_wstring{view.data(), view.size()}).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}; constexpr std::wstring_view view{L"foobar__", 6};
static_assert(entt::hashed_wstring{L"quux"} == L"quux"_hws); 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"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{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(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"foo"_hws);
static_assert(entt::hashed_wstring{L"bar"} <= L"bar"_hws); static_assert(entt::hashed_wstring{L"bar"} <= L"bar"_hws);

View File

@ -24,6 +24,12 @@ TEST(ToAddress, Functionalities) {
TEST(PoccaPocmaAndPocs, Functionalities) { TEST(PoccaPocmaAndPocs, Functionalities) {
test::basic_test_allocator<int> lhs, rhs; test::basic_test_allocator<int> lhs, rhs;
test::basic_test_allocator<int, std::false_type> no_pocs;
// code coverage purposes
ASSERT_FALSE(lhs == rhs);
ASSERT_NO_FATAL_FAILURE(entt::propagate_on_container_swap(no_pocs, no_pocs));
// honestly, I don't even know how one is supposed to test such a thing :) // 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_copy_assignment(lhs, rhs);
entt::propagate_on_container_move_assignment(lhs, rhs); entt::propagate_on_container_move_assignment(lhs, rhs);
@ -31,8 +37,8 @@ TEST(PoccaPocmaAndPocs, Functionalities) {
} }
ENTT_DEBUG_TEST(PoccaPocmaAndPocsDeathTest, Functionalities) { ENTT_DEBUG_TEST(PoccaPocmaAndPocsDeathTest, Functionalities) {
using pocs = std::false_type; test::basic_test_allocator<int, std::false_type> lhs, rhs;
test::basic_test_allocator<int, pocs> lhs, rhs;
ASSERT_DEATH(entt::propagate_on_container_swap(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(17u), 32u);
ASSERT_EQ(entt::next_power_of_two(32u), 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(33u), 64u);
ASSERT_EQ(entt::next_power_of_two(std::pow(2, 16)), std::pow(2, 16)); ASSERT_EQ(entt::next_power_of_two(static_cast<std::size_t>(std::pow(2, 16))), static_cast<std::size_t>(std::pow(2, 16)));
ASSERT_EQ(entt::next_power_of_two(std::pow(2, 16) + 1u), std::pow(2, 17)); ASSERT_EQ(entt::next_power_of_two(static_cast<std::size_t>(std::pow(2, 16) + 1u)), static_cast<std::size_t>(std::pow(2, 17)));
} }
ENTT_DEBUG_TEST(NextPowerOfTwoDeathTest, Functionalities) { ENTT_DEBUG_TEST(NextPowerOfTwoDeathTest, Functionalities) {

View File

@ -34,5 +34,5 @@ TEST(Tuple, UnwrapTuple) {
TEST(Tuple, ForwardApply) { TEST(Tuple, ForwardApply) {
ASSERT_EQ(entt::forward_apply{[](auto &&...args) { return sizeof...(args); }}(std::make_tuple()), 0u); 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{[](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');
} }

View File

@ -89,7 +89,7 @@ TEST(TypeList, Functionalities) {
static_assert(std::is_same_v<entt::type_list_cat_t<type, other, type, other>, entt::type_list<int, char, double, int, char, double>>); static_assert(std::is_same_v<entt::type_list_cat_t<type, other, type, other>, entt::type_list<int, char, double, int, char, double>>);
static_assert(std::is_same_v<entt::type_list_cat_t<type, other>, entt::type_list<int, char, double>>); static_assert(std::is_same_v<entt::type_list_cat_t<type, other>, entt::type_list<int, char, double>>);
static_assert(std::is_same_v<entt::type_list_cat_t<type, type>, entt::type_list<int, char, int, char>>); static_assert(std::is_same_v<entt::type_list_cat_t<type, type>, entt::type_list<int, char, int, char>>);
static_assert(std::is_same_v<entt::type_list_unique_t<entt::type_list_cat_t<type, type>>, entt::type_list<int, char>>); static_assert(std::is_same_v<entt::type_list_unique_t<entt::type_list_cat_t<type, type>>, type>);
static_assert(entt::type_list_contains_v<type, int>); static_assert(entt::type_list_contains_v<type, int>);
static_assert(entt::type_list_contains_v<type, char>); static_assert(entt::type_list_contains_v<type, char>);
@ -112,6 +112,14 @@ TEST(TypeList, Functionalities) {
static_assert(std::is_same_v<entt::type_list_transform_t<entt::type_list<int, char>, entt::type_identity>, entt::type_list<int, char>>); static_assert(std::is_same_v<entt::type_list_transform_t<entt::type_list<int, char>, entt::type_identity>, entt::type_list<int, char>>);
static_assert(std::is_same_v<entt::type_list_transform_t<entt::type_list<int, char>, std::add_const>, entt::type_list<const int, const char>>); static_assert(std::is_same_v<entt::type_list_transform_t<entt::type_list<int, char>, std::add_const>, entt::type_list<const int, const char>>);
static_assert(std::is_same_v<entt::type_list_transform_t<entt::type_list<int, char>, multi_argument_operation>, entt::type_list<void, void>>); static_assert(std::is_same_v<entt::type_list_transform_t<entt::type_list<int, char>, multi_argument_operation>, entt::type_list<void, void>>);
static_assert(std::tuple_size_v<entt::type_list<>> == 0u);
static_assert(std::tuple_size_v<entt::type_list<int>> == 1u);
static_assert(std::tuple_size_v<entt::type_list<int, float>> == 2u);
static_assert(std::is_same_v<int, std::tuple_element_t<0, entt::type_list<int>>>);
static_assert(std::is_same_v<int, std::tuple_element_t<0, entt::type_list<int, float>>>);
static_assert(std::is_same_v<float, std::tuple_element_t<1, entt::type_list<int, float>>>);
} }
TEST(ValueList, Functionalities) { TEST(ValueList, Functionalities) {
@ -125,10 +133,33 @@ TEST(ValueList, Functionalities) {
static_assert(std::is_same_v<entt::value_list_cat_t<value, other, value, other>, entt::value_list<0, 2, 1, 0, 2, 1>>); static_assert(std::is_same_v<entt::value_list_cat_t<value, other, value, other>, entt::value_list<0, 2, 1, 0, 2, 1>>);
static_assert(std::is_same_v<entt::value_list_cat_t<value, other>, entt::value_list<0, 2, 1>>); static_assert(std::is_same_v<entt::value_list_cat_t<value, other>, entt::value_list<0, 2, 1>>);
static_assert(std::is_same_v<entt::value_list_cat_t<value, value>, entt::value_list<0, 2, 0, 2>>); static_assert(std::is_same_v<entt::value_list_cat_t<value, value>, entt::value_list<0, 2, 0, 2>>);
static_assert(std::is_same_v<entt::value_list_unique_t<entt::value_list_cat_t<value, value>>, value>);
static_assert(entt::value_list_contains_v<value, 0>);
static_assert(entt::value_list_contains_v<value, 2>);
static_assert(!entt::value_list_contains_v<value, 1>);
static_assert(entt::value_list_element_v<0u, value> == 0); static_assert(entt::value_list_element_v<0u, value> == 0);
static_assert(entt::value_list_element_v<1u, value> == 2); 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_element_v<0u, other> == 1);
static_assert(entt::value_list_index_v<0, value> == 0u);
static_assert(entt::value_list_index_v<2, value> == 1u);
static_assert(entt::value_list_index_v<1, other> == 0u);
static_assert(std::is_same_v<entt::value_list_diff_t<entt::value_list<0, 1, 2>, entt::value_list<3, 4>>, entt::value_list<0, 1, 2>>);
static_assert(std::is_same_v<entt::value_list_diff_t<entt::value_list<0, 1, 2>, entt::value_list<0, 1, 2>>, entt::value_list<>>);
static_assert(std::is_same_v<entt::value_list_diff_t<entt::value_list<0, 1, 2>, entt::value_list<0, 1>>, entt::value_list<2>>);
static_assert(std::is_same_v<entt::value_list_diff_t<entt::value_list<0, 1, 2>, entt::value_list<1, 2>>, entt::value_list<0>>);
static_assert(std::is_same_v<entt::value_list_diff_t<entt::value_list<0, 1, 2>, entt::value_list<1>>, entt::value_list<0, 2>>);
static_assert(std::tuple_size_v<entt::value_list<>> == 0u);
static_assert(std::tuple_size_v<entt::value_list<42>> == 1u);
static_assert(std::tuple_size_v<entt::value_list<42, 'a'>> == 2u);
static_assert(std::is_same_v<int, std::tuple_element_t<0, entt::value_list<42>>>);
static_assert(std::is_same_v<int, std::tuple_element_t<0, entt::value_list<42, 'a'>>>);
static_assert(std::is_same_v<char, std::tuple_element_t<1, entt::value_list<42, 'a'>>>);
} }
TEST(IsApplicable, Functionalities) { TEST(IsApplicable, Functionalities) {
@ -183,6 +214,7 @@ TEST(IsEqualityComparable, Functionalities) {
static_assert(entt::is_equality_comparable_v<std::vector<not_comparable>::iterator>); static_assert(entt::is_equality_comparable_v<std::vector<not_comparable>::iterator>);
static_assert(entt::is_equality_comparable_v<nlohmann_json_like>); static_assert(entt::is_equality_comparable_v<nlohmann_json_like>);
static_assert(!entt::is_equality_comparable_v<int[3u]>);
static_assert(!entt::is_equality_comparable_v<not_comparable>); static_assert(!entt::is_equality_comparable_v<not_comparable>);
static_assert(!entt::is_equality_comparable_v<const not_comparable>); static_assert(!entt::is_equality_comparable_v<const not_comparable>);
static_assert(!entt::is_equality_comparable_v<std::vector<not_comparable>>); static_assert(!entt::is_equality_comparable_v<std::vector<not_comparable>>);

View File

@ -30,50 +30,43 @@ struct entt::component_traits<traits_based> {
}; };
TEST(Component, VoidType) { TEST(Component, VoidType) {
using traits = entt::component_traits<void>; using traits_type = entt::component_traits<void>;
static_assert(traits::in_place_delete); static_assert(!traits_type::in_place_delete);
static_assert(entt::ignore_as_empty_v<typename traits::type>); static_assert(traits_type::page_size == 0u);
// we don't really care about this thanks to ignore_as_empty_v
static_assert(traits::page_size != 0u);
} }
TEST(Component, Empty) { TEST(Component, Empty) {
using traits = entt::component_traits<empty>; using traits_type = entt::component_traits<empty>;
static_assert(!traits::in_place_delete); static_assert(!traits_type::in_place_delete);
static_assert(entt::ignore_as_empty_v<typename traits::type>); static_assert(traits_type::page_size == 0u);
static_assert(traits::page_size == 0u);
} }
TEST(Component, NonEmpty) { TEST(Component, NonEmpty) {
using traits = entt::component_traits<non_empty>; using traits_type = entt::component_traits<non_empty>;
static_assert(!traits::in_place_delete); static_assert(!traits_type::in_place_delete);
static_assert(!entt::ignore_as_empty_v<typename traits::type>); static_assert(traits_type::page_size == ENTT_PACKED_PAGE);
static_assert(traits::page_size == ENTT_PACKED_PAGE);
} }
TEST(Component, NonMovable) { TEST(Component, NonMovable) {
using traits = entt::component_traits<non_movable>; using traits_type = entt::component_traits<non_movable>;
static_assert(traits::in_place_delete); static_assert(traits_type::in_place_delete);
static_assert(!entt::ignore_as_empty_v<typename traits::type>); static_assert(traits_type::page_size == ENTT_PACKED_PAGE);
static_assert(traits::page_size == ENTT_PACKED_PAGE);
} }
TEST(Component, SelfContained) { TEST(Component, SelfContained) {
using traits = entt::component_traits<self_contained>; using traits_type = entt::component_traits<self_contained>;
static_assert(traits::in_place_delete); static_assert(traits_type::in_place_delete);
static_assert(!entt::ignore_as_empty_v<typename traits::type>); static_assert(traits_type::page_size == 4u);
static_assert(traits::page_size == 4u);
} }
TEST(Component, TraitsBased) { TEST(Component, TraitsBased) {
using traits = entt::component_traits<traits_based>; using traits_type = entt::component_traits<traits_based>;
static_assert(!traits::in_place_delete); static_assert(!traits_type::in_place_delete);
static_assert(!entt::ignore_as_empty_v<typename traits::type>); static_assert(traits_type::page_size == 8u);
static_assert(traits::page_size == 8u);
} }

View File

@ -29,6 +29,12 @@ TEST(Entity, Traits) {
ASSERT_EQ(traits_type::combine(entt::tombstone, entt::null), tombstone); ASSERT_EQ(traits_type::combine(entt::tombstone, entt::null), tombstone);
ASSERT_EQ(traits_type::combine(entt::null, entt::tombstone), null); 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) { TEST(Entity, Null) {

View File

@ -8,6 +8,7 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <entt/entity/group.hpp> #include <entt/entity/group.hpp>
#include <entt/entity/registry.hpp> #include <entt/entity/registry.hpp>
#include "../common/config.h"
struct empty_type {}; struct empty_type {};
@ -56,7 +57,7 @@ TEST(NonOwningGroup, Functionalities) {
for(auto entity: group) { for(auto entity: group) {
ASSERT_EQ(std::get<0>(cgroup.get<const int, const char>(entity)), 42); ASSERT_EQ(std::get<0>(cgroup.get<const int, const char>(entity)), 42);
ASSERT_EQ(std::get<1>(group.get<int, char>(entity)), '2'); ASSERT_EQ(std::get<1>(group.get<int, char>(entity)), '2');
ASSERT_EQ(cgroup.get<const char>(entity), '2'); ASSERT_EQ(cgroup.get<1>(entity), '2');
} }
ASSERT_EQ(group.handle().data()[0u], e1); ASSERT_EQ(group.handle().data()[0u], e1);
@ -93,6 +94,7 @@ TEST(NonOwningGroup, Handle) {
ASSERT_TRUE(handle.empty()); ASSERT_TRUE(handle.empty());
ASSERT_FALSE(handle.contains(entity)); ASSERT_FALSE(handle.contains(entity));
ASSERT_EQ(&handle, &group.handle()); ASSERT_EQ(&handle, &group.handle());
ASSERT_NE(&handle, group.storage<int>());
registry.emplace<int>(entity); registry.emplace<int>(entity);
registry.emplace<char>(entity); registry.emplace<char>(entity);
@ -187,10 +189,10 @@ TEST(NonOwningGroup, Each) {
auto cgroup = std::as_const(registry).group_if_exists(entt::get<const int, const char>); auto cgroup = std::as_const(registry).group_if_exists(entt::get<const int, const char>);
registry.emplace<int>(entity[0u], 0); registry.emplace<int>(entity[0u], 0);
registry.emplace<char>(entity[0u], 0); registry.emplace<char>(entity[0u], static_cast<char>(0));
registry.emplace<int>(entity[1u], 1); registry.emplace<int>(entity[1u], 1);
registry.emplace<char>(entity[1u], 1); registry.emplace<char>(entity[1u], static_cast<char>(1));
auto iterable = group.each(); auto iterable = group.each();
auto citerable = cgroup.each(); auto citerable = cgroup.each();
@ -201,16 +203,18 @@ TEST(NonOwningGroup, Each) {
auto it = iterable.begin(); auto it = iterable.begin();
ASSERT_EQ(it.base(), group.begin());
ASSERT_EQ((it++, ++it), iterable.end()); ASSERT_EQ((it++, ++it), iterable.end());
ASSERT_EQ(it.base(), group.end());
group.each([expected = 1u](auto entt, int &ivalue, char &cvalue) mutable { group.each([expected = 1](auto entt, int &ivalue, char &cvalue) mutable {
ASSERT_EQ(entt::to_integral(entt), expected); ASSERT_EQ(static_cast<int>(entt::to_integral(entt)), expected);
ASSERT_EQ(ivalue, expected); ASSERT_EQ(ivalue, expected);
ASSERT_EQ(cvalue, expected); ASSERT_EQ(cvalue, expected);
--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(ivalue, expected);
ASSERT_EQ(cvalue, expected); ASSERT_EQ(cvalue, expected);
--expected; --expected;
@ -224,8 +228,8 @@ TEST(NonOwningGroup, Each) {
// do not use iterable, make sure an iterable group works when created from a temporary // do not use iterable, make sure an iterable group works when created from a temporary
for(auto [entt, ivalue, cvalue]: registry.group(entt::get<int, char>).each()) { for(auto [entt, ivalue, cvalue]: registry.group(entt::get<int, char>).each()) {
ASSERT_EQ(entt::to_integral(entt), ivalue); ASSERT_EQ(static_cast<int>(entt::to_integral(entt)), ivalue);
ASSERT_EQ(entt::to_integral(entt), cvalue); ASSERT_EQ(static_cast<char>(entt::to_integral(entt)), cvalue);
} }
} }
@ -273,9 +277,9 @@ TEST(NonOwningGroup, Sort) {
ASSERT_EQ(group.handle().data()[1u], e1); ASSERT_EQ(group.handle().data()[1u], e1);
ASSERT_EQ(group.handle().data()[2u], e2); ASSERT_EQ(group.handle().data()[2u], e2);
ASSERT_EQ((group.get<const int, unsigned int>(e0)), (std::make_tuple(0, 0u))); ASSERT_EQ((group.get<0, 1>(e0)), (std::make_tuple(0, 0u)));
ASSERT_EQ((group.get<const int, unsigned int>(e1)), (std::make_tuple(1, 1u))); ASSERT_EQ((group.get<0, 1>(e1)), (std::make_tuple(1, 1u)));
ASSERT_EQ((group.get<const int, unsigned int>(e2)), (std::make_tuple(2, 2u))); ASSERT_EQ((group.get<0, 1>(e2)), (std::make_tuple(2, 2u)));
ASSERT_FALSE(group.contains(e3)); ASSERT_FALSE(group.contains(e3));
@ -323,10 +327,10 @@ TEST(NonOwningGroup, SortAsAPool) {
} }
registry.sort<unsigned int>(std::less<unsigned int>{}); registry.sort<unsigned int>(std::less<unsigned int>{});
group.sort<unsigned int>(); group.sort_as(*group.storage<unsigned int>());
ASSERT_EQ((group.get<const int, unsigned int>(e0)), (std::make_tuple(0, 0u))); ASSERT_EQ((group.get<const int, unsigned int>(e0)), (std::make_tuple(0, 0u)));
ASSERT_EQ((group.get<const int, unsigned int>(e1)), (std::make_tuple(1, 1u))); ASSERT_EQ((group.get<0, 1>(e1)), (std::make_tuple(1, 1u)));
ASSERT_EQ((group.get<const int, unsigned int>(e2)), (std::make_tuple(2, 2u))); ASSERT_EQ((group.get<const int, unsigned int>(e2)), (std::make_tuple(2, 2u)));
ASSERT_FALSE(group.contains(e3)); ASSERT_FALSE(group.contains(e3));
@ -384,9 +388,18 @@ TEST(NonOwningGroup, ConstNonConstAndAllInBetween) {
ASSERT_EQ(group.size(), 1u); ASSERT_EQ(group.size(), 1u);
static_assert(std::is_same_v<decltype(group.get<0>({})), int &>);
static_assert(std::is_same_v<decltype(group.get<int>({})), int &>); static_assert(std::is_same_v<decltype(group.get<int>({})), int &>);
static_assert(std::is_same_v<decltype(group.get<1>({})), void>);
static_assert(std::is_same_v<decltype(group.get<empty_type>({})), void>);
static_assert(std::is_same_v<decltype(group.get<2>({})), const char &>);
static_assert(std::is_same_v<decltype(group.get<const char>({})), const char &>); static_assert(std::is_same_v<decltype(group.get<const char>({})), const char &>);
static_assert(std::is_same_v<decltype(group.get<int, const char>({})), std::tuple<int &, const char &>>);
static_assert(std::is_same_v<decltype(group.get<int, empty_type, const char>({})), std::tuple<int &, const char &>>);
static_assert(std::is_same_v<decltype(group.get<0, 1, 2>({})), std::tuple<int &, const char &>>);
static_assert(std::is_same_v<decltype(group.get({})), std::tuple<int &, const char &>>); static_assert(std::is_same_v<decltype(group.get({})), std::tuple<int &, const char &>>);
static_assert(std::is_same_v<decltype(std::as_const(registry).group_if_exists(entt::get<int, char>)), decltype(std::as_const(registry).group_if_exists(entt::get<const int, const char>))>); static_assert(std::is_same_v<decltype(std::as_const(registry).group_if_exists(entt::get<int, char>)), decltype(std::as_const(registry).group_if_exists(entt::get<const int, const char>))>);
@ -475,7 +488,7 @@ TEST(NonOwningGroup, ExcludedComponents) {
if(entity == e0) { if(entity == e0) {
ASSERT_EQ(group.get<int>(e0), 0); ASSERT_EQ(group.get<int>(e0), 0);
} else if(entity == e2) { } else if(entity == e2) {
ASSERT_EQ(group.get<int>(e2), 2); ASSERT_EQ(group.get<0>(e2), 2);
} }
} }
@ -493,7 +506,7 @@ TEST(NonOwningGroup, ExcludedComponents) {
if(entity == e1) { if(entity == e1) {
ASSERT_EQ(group.get<int>(e1), 1); ASSERT_EQ(group.get<int>(e1), 1);
} else if(entity == e3) { } else if(entity == e3) {
ASSERT_EQ(group.get<int>(e3), 3); ASSERT_EQ(group.get<0>(e3), 3);
} }
} }
} }
@ -663,28 +676,100 @@ TEST(NonOwningGroup, IterableGroupAlgorithmCompatibility) {
TEST(NonOwningGroup, Storage) { TEST(NonOwningGroup, Storage) {
entt::registry registry; entt::registry registry;
const auto entity = registry.create(); const auto entity = registry.create();
const auto group = registry.group(entt::get<int, const char>); auto group = registry.group(entt::get<int, const char>, entt::exclude<double, const float>);
static_assert(std::is_same_v<decltype(group.storage<0u>()), entt::storage_type_t<int> &>); static_assert(std::is_same_v<decltype(group.storage<0u>()), entt::storage_type_t<int> *>);
static_assert(std::is_same_v<decltype(group.storage<int>()), entt::storage_type_t<int> &>); static_assert(std::is_same_v<decltype(group.storage<int>()), entt::storage_type_t<int> *>);
static_assert(std::is_same_v<decltype(group.storage<1u>()), const entt::storage_type_t<char> &>); static_assert(std::is_same_v<decltype(group.storage<const int>()), entt::storage_type_t<int> *>);
static_assert(std::is_same_v<decltype(group.storage<const char>()), const entt::storage_type_t<char> &>); static_assert(std::is_same_v<decltype(group.storage<1u>()), const entt::storage_type_t<char> *>);
static_assert(std::is_same_v<decltype(group.storage<char>()), const entt::storage_type_t<char> *>);
static_assert(std::is_same_v<decltype(group.storage<const char>()), const entt::storage_type_t<char> *>);
static_assert(std::is_same_v<decltype(group.storage<2u>()), entt::storage_type_t<double> *>);
static_assert(std::is_same_v<decltype(group.storage<double>()), entt::storage_type_t<double> *>);
static_assert(std::is_same_v<decltype(group.storage<const double>()), entt::storage_type_t<double> *>);
static_assert(std::is_same_v<decltype(group.storage<3u>()), const entt::storage_type_t<float> *>);
static_assert(std::is_same_v<decltype(group.storage<float>()), const entt::storage_type_t<float> *>);
static_assert(std::is_same_v<decltype(group.storage<const float>()), const entt::storage_type_t<float> *>);
ASSERT_TRUE(group);
ASSERT_NE(group.storage<int>(), nullptr);
ASSERT_NE(group.storage<1u>(), nullptr);
ASSERT_NE(group.storage<double>(), nullptr);
ASSERT_NE(group.storage<3u>(), nullptr);
ASSERT_EQ(group.size(), 0u); ASSERT_EQ(group.size(), 0u);
group.storage<int>().emplace(entity); group.storage<int>()->emplace(entity);
group.storage<double>()->emplace(entity);
registry.emplace<char>(entity); registry.emplace<char>(entity);
registry.emplace<float>(entity);
ASSERT_EQ(group.size(), 0u);
ASSERT_EQ(group.begin(), group.end());
ASSERT_TRUE(group.storage<int>()->contains(entity));
ASSERT_TRUE(group.storage<const char>()->contains(entity));
ASSERT_TRUE(group.storage<double>()->contains(entity));
ASSERT_TRUE(group.storage<const float>()->contains(entity));
ASSERT_TRUE((registry.all_of<int, char, double, float>(entity)));
group.storage<double>()->erase(entity);
registry.erase<float>(entity);
ASSERT_EQ(group.size(), 1u); ASSERT_EQ(group.size(), 1u);
ASSERT_TRUE(group.storage<int>().contains(entity)); ASSERT_NE(group.begin(), group.end());
ASSERT_TRUE(group.storage<const char>().contains(entity)); ASSERT_TRUE(group.storage<const int>()->contains(entity));
ASSERT_TRUE(group.storage<char>()->contains(entity));
ASSERT_FALSE(group.storage<const double>()->contains(entity));
ASSERT_FALSE(group.storage<float>()->contains(entity));
ASSERT_TRUE((registry.all_of<int, char>(entity))); ASSERT_TRUE((registry.all_of<int, char>(entity)));
ASSERT_FALSE((registry.any_of<double, float>(entity)));
group.storage<0u>().erase(entity); group.storage<0u>()->erase(entity);
ASSERT_EQ(group.size(), 0u); ASSERT_EQ(group.size(), 0u);
ASSERT_TRUE(group.storage<1u>().contains(entity)); ASSERT_EQ(group.begin(), group.end());
ASSERT_FALSE((registry.all_of<int, char>(entity))); ASSERT_FALSE(group.storage<0u>()->contains(entity));
ASSERT_TRUE(group.storage<1u>()->contains(entity));
ASSERT_FALSE(group.storage<2u>()->contains(entity));
ASSERT_FALSE(group.storage<3u>()->contains(entity));
ASSERT_TRUE((registry.all_of<char>(entity)));
ASSERT_FALSE((registry.any_of<int, double, float>(entity)));
group = {};
ASSERT_FALSE(group);
ASSERT_EQ(group.storage<0u>(), nullptr);
ASSERT_EQ(group.storage<const char>(), nullptr);
ASSERT_EQ(group.storage<2u>(), nullptr);
ASSERT_EQ(group.storage<const float>(), nullptr);
}
TEST(NonOwningGroup, Overlapping) {
entt::registry registry;
auto group = registry.group(entt::get<char>, entt::exclude<double>);
auto other = registry.group<int>(entt::get<char>, entt::exclude<double>);
ASSERT_TRUE(group.empty());
ASSERT_TRUE(other.empty());
const auto entity = registry.create();
registry.emplace<char>(entity, '1');
ASSERT_FALSE(group.empty());
ASSERT_TRUE(other.empty());
registry.emplace<int>(entity, 42);
ASSERT_FALSE(group.empty());
ASSERT_FALSE(other.empty());
registry.emplace<double>(entity, 3.);
ASSERT_TRUE(group.empty());
ASSERT_TRUE(other.empty());
} }
TEST(OwningGroup, Functionalities) { TEST(OwningGroup, Functionalities) {
@ -721,16 +806,17 @@ TEST(OwningGroup, Functionalities) {
ASSERT_EQ(group.size(), 1u); ASSERT_EQ(group.size(), 1u);
ASSERT_EQ(cgroup.storage<const int>().raw()[0u][0u], 42); ASSERT_EQ(cgroup.storage<const int>()->raw()[0u][0u], 42);
ASSERT_EQ(group.storage<int>().raw()[0u][0u], 42); ASSERT_EQ(group.storage<int>()->raw()[0u][0u], 42);
for(auto entity: group) { for(auto entity: group) {
ASSERT_EQ(std::get<0>(cgroup.get<const int, const char>(entity)), 42); ASSERT_EQ(std::get<0>(cgroup.get<const int, const char>(entity)), 42);
ASSERT_EQ(std::get<1>(group.get<int, char>(entity)), '2'); ASSERT_EQ(std::get<1>(group.get<int, char>(entity)), '2');
ASSERT_EQ(cgroup.get<const char>(entity), '2'); ASSERT_EQ(cgroup.get<1>(entity), '2');
} }
ASSERT_EQ(group.storage<int>().raw()[0u][0u], 42); ASSERT_EQ(group.handle().data()[0u], e1);
ASSERT_EQ(group.storage<int>()->raw()[0u][0u], 42);
registry.erase<char>(e0); registry.erase<char>(e0);
registry.erase<char>(e1); registry.erase<char>(e1);
@ -748,6 +834,26 @@ TEST(OwningGroup, Functionalities) {
ASSERT_FALSE(invalid); ASSERT_FALSE(invalid);
} }
TEST(OwningGroup, Handle) {
entt::registry registry;
const auto entity = registry.create();
auto group = registry.group<int>(entt::get<char>);
auto &&handle = group.handle();
ASSERT_TRUE(handle.empty());
ASSERT_FALSE(handle.contains(entity));
ASSERT_EQ(&handle, &group.handle());
ASSERT_EQ(&handle, group.storage<int>());
registry.emplace<int>(entity);
registry.emplace<char>(entity);
ASSERT_FALSE(handle.empty());
ASSERT_TRUE(handle.contains(entity));
ASSERT_EQ(&handle, &group.handle());
}
TEST(OwningGroup, Invalid) { TEST(OwningGroup, Invalid) {
entt::registry registry{}; entt::registry registry{};
auto group = std::as_const(registry).group_if_exists<const int>(entt::get<const empty_type>); auto group = std::as_const(registry).group_if_exists<const int>(entt::get<const empty_type>);
@ -831,10 +937,10 @@ TEST(OwningGroup, Each) {
auto cgroup = std::as_const(registry).group_if_exists<const int>(entt::get<const char>); auto cgroup = std::as_const(registry).group_if_exists<const int>(entt::get<const char>);
registry.emplace<int>(entity[0u], 0); registry.emplace<int>(entity[0u], 0);
registry.emplace<char>(entity[0u], 0); registry.emplace<char>(entity[0u], static_cast<char>(0));
registry.emplace<int>(entity[1u], 1); registry.emplace<int>(entity[1u], 1);
registry.emplace<char>(entity[1u], 1); registry.emplace<char>(entity[1u], static_cast<char>(1));
auto iterable = group.each(); auto iterable = group.each();
auto citerable = cgroup.each(); auto citerable = cgroup.each();
@ -845,16 +951,18 @@ TEST(OwningGroup, Each) {
auto it = iterable.begin(); auto it = iterable.begin();
ASSERT_EQ(it.base(), group.begin());
ASSERT_EQ((it++, ++it), iterable.end()); ASSERT_EQ((it++, ++it), iterable.end());
ASSERT_EQ(it.base(), group.end());
group.each([expected = 1u](auto entt, int &ivalue, char &cvalue) mutable { group.each([expected = 1](auto entt, int &ivalue, char &cvalue) mutable {
ASSERT_EQ(entt::to_integral(entt), expected); ASSERT_EQ(static_cast<int>(entt::to_integral(entt)), expected);
ASSERT_EQ(ivalue, expected); ASSERT_EQ(ivalue, expected);
ASSERT_EQ(cvalue, expected); ASSERT_EQ(cvalue, expected);
--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(ivalue, expected);
ASSERT_EQ(cvalue, expected); ASSERT_EQ(cvalue, expected);
--expected; --expected;
@ -868,8 +976,8 @@ TEST(OwningGroup, Each) {
// do not use iterable, make sure an iterable group works when created from a temporary // do not use iterable, make sure an iterable group works when created from a temporary
for(auto [entt, ivalue, cvalue]: registry.group<int>(entt::get<char>).each()) { for(auto [entt, ivalue, cvalue]: registry.group<int>(entt::get<char>).each()) {
ASSERT_EQ(entt::to_integral(entt), ivalue); ASSERT_EQ(static_cast<int>(entt::to_integral(entt)), ivalue);
ASSERT_EQ(entt::to_integral(entt), cvalue); ASSERT_EQ(static_cast<char>(entt::to_integral(entt)), cvalue);
} }
} }
@ -893,27 +1001,27 @@ TEST(OwningGroup, SortOrdered) {
registry.emplace<boxed_int>(entities[4], 2); registry.emplace<boxed_int>(entities[4], 2);
group.sort([&group](const entt::entity lhs, const entt::entity rhs) { group.sort([&group](const entt::entity lhs, const entt::entity rhs) {
return group.get<boxed_int>(lhs).value < group.get<boxed_int>(rhs).value; return group.get<boxed_int>(lhs).value < group.get<0>(rhs).value;
}); });
ASSERT_EQ(group.storage<boxed_int>().data()[0u], entities[0]); ASSERT_EQ(group.handle().data()[0u], entities[0]);
ASSERT_EQ(group.storage<boxed_int>().data()[1u], entities[1]); ASSERT_EQ(group.handle().data()[1u], entities[1]);
ASSERT_EQ(group.storage<boxed_int>().data()[2u], entities[2]); ASSERT_EQ(group.handle().data()[2u], entities[2]);
ASSERT_EQ(group.storage<boxed_int>().data()[3u], entities[3]); ASSERT_EQ(group.handle().data()[3u], entities[3]);
ASSERT_EQ(group.storage<boxed_int>().data()[4u], entities[4]); ASSERT_EQ(group.handle().data()[4u], entities[4]);
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][0u].value, 12); ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][0u].value, 12);
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][1u].value, 9); ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][1u].value, 9);
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][2u].value, 6); ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][2u].value, 6);
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][3u].value, 1); ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][3u].value, 1);
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][4u].value, 2); ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][4u].value, 2);
ASSERT_EQ(group.storage<char>().raw()[0u][0u], 'a'); ASSERT_EQ(group.storage<char>()->raw()[0u][0u], 'a');
ASSERT_EQ(group.storage<char>().raw()[0u][1u], 'b'); ASSERT_EQ(group.storage<char>()->raw()[0u][1u], 'b');
ASSERT_EQ(group.storage<char>().raw()[0u][2u], 'c'); ASSERT_EQ(group.storage<char>()->raw()[0u][2u], 'c');
ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{12}, 'a'))); ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{12}, 'a')));
ASSERT_EQ((group.get<boxed_int, char>(entities[1])), (std::make_tuple(boxed_int{9}, 'b'))); ASSERT_EQ((group.get<0, 1>(entities[1])), (std::make_tuple(boxed_int{9}, 'b')));
ASSERT_EQ((group.get<boxed_int, char>(entities[2])), (std::make_tuple(boxed_int{6}, 'c'))); ASSERT_EQ((group.get<boxed_int, char>(entities[2])), (std::make_tuple(boxed_int{6}, 'c')));
ASSERT_FALSE(group.contains(entities[3])); ASSERT_FALSE(group.contains(entities[3]));
@ -943,24 +1051,24 @@ TEST(OwningGroup, SortReverse) {
return lhs.value < rhs.value; return lhs.value < rhs.value;
}); });
ASSERT_EQ(group.storage<boxed_int>().data()[0u], entities[2]); ASSERT_EQ(group.handle().data()[0u], entities[2]);
ASSERT_EQ(group.storage<boxed_int>().data()[1u], entities[1]); ASSERT_EQ(group.handle().data()[1u], entities[1]);
ASSERT_EQ(group.storage<boxed_int>().data()[2u], entities[0]); ASSERT_EQ(group.handle().data()[2u], entities[0]);
ASSERT_EQ(group.storage<boxed_int>().data()[3u], entities[3]); ASSERT_EQ(group.handle().data()[3u], entities[3]);
ASSERT_EQ(group.storage<boxed_int>().data()[4u], entities[4]); ASSERT_EQ(group.handle().data()[4u], entities[4]);
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][0u].value, 12); ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][0u].value, 12);
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][1u].value, 9); ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][1u].value, 9);
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][2u].value, 6); ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][2u].value, 6);
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][3u].value, 1); ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][3u].value, 1);
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][4u].value, 2); ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][4u].value, 2);
ASSERT_EQ(group.storage<char>().raw()[0u][0u], 'c'); ASSERT_EQ(group.storage<char>()->raw()[0u][0u], 'c');
ASSERT_EQ(group.storage<char>().raw()[0u][1u], 'b'); ASSERT_EQ(group.storage<char>()->raw()[0u][1u], 'b');
ASSERT_EQ(group.storage<char>().raw()[0u][2u], 'a'); ASSERT_EQ(group.storage<char>()->raw()[0u][2u], 'a');
ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{6}, 'a'))); ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{6}, 'a')));
ASSERT_EQ((group.get<boxed_int, char>(entities[1])), (std::make_tuple(boxed_int{9}, 'b'))); ASSERT_EQ((group.get<0, 1>(entities[1])), (std::make_tuple(boxed_int{9}, 'b')));
ASSERT_EQ((group.get<boxed_int, char>(entities[2])), (std::make_tuple(boxed_int{12}, 'c'))); ASSERT_EQ((group.get<boxed_int, char>(entities[2])), (std::make_tuple(boxed_int{12}, 'c')));
ASSERT_FALSE(group.contains(entities[3])); ASSERT_FALSE(group.contains(entities[3]));
@ -998,27 +1106,27 @@ TEST(OwningGroup, SortUnordered) {
return std::get<1>(lhs) < std::get<1>(rhs); return std::get<1>(lhs) < std::get<1>(rhs);
}); });
ASSERT_EQ(group.storage<boxed_int>().data()[0u], entities[4]); ASSERT_EQ(group.handle().data()[0u], entities[4]);
ASSERT_EQ(group.storage<boxed_int>().data()[1u], entities[3]); ASSERT_EQ(group.handle().data()[1u], entities[3]);
ASSERT_EQ(group.storage<boxed_int>().data()[2u], entities[0]); ASSERT_EQ(group.handle().data()[2u], entities[0]);
ASSERT_EQ(group.storage<boxed_int>().data()[3u], entities[1]); ASSERT_EQ(group.handle().data()[3u], entities[1]);
ASSERT_EQ(group.storage<boxed_int>().data()[4u], entities[2]); ASSERT_EQ(group.handle().data()[4u], entities[2]);
ASSERT_EQ(group.storage<boxed_int>().data()[5u], entities[5]); ASSERT_EQ(group.handle().data()[5u], entities[5]);
ASSERT_EQ(group.storage<boxed_int>().data()[6u], entities[6]); ASSERT_EQ(group.handle().data()[6u], entities[6]);
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][0u].value, 12); ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][0u].value, 12);
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][1u].value, 9); ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][1u].value, 9);
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][2u].value, 6); ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][2u].value, 6);
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][3u].value, 3); ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][3u].value, 3);
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][4u].value, 1); ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][4u].value, 1);
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][5u].value, 4); ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][5u].value, 4);
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][6u].value, 5); ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][6u].value, 5);
ASSERT_EQ(group.get<char>(group.storage<boxed_int>().data()[0u]), 'e'); ASSERT_EQ(group.get<char>(group.handle().data()[0u]), 'e');
ASSERT_EQ(group.get<char>(group.storage<boxed_int>().data()[1u]), 'd'); ASSERT_EQ(group.get<1>(group.handle().data()[1u]), 'd');
ASSERT_EQ(group.get<char>(group.storage<boxed_int>().data()[2u]), 'c'); ASSERT_EQ(group.get<char>(group.handle().data()[2u]), 'c');
ASSERT_EQ(group.get<char>(group.storage<boxed_int>().data()[3u]), 'b'); ASSERT_EQ(group.get<1>(group.handle().data()[3u]), 'b');
ASSERT_EQ(group.get<char>(group.storage<boxed_int>().data()[4u]), 'a'); ASSERT_EQ(group.get<char>(group.handle().data()[4u]), 'a');
ASSERT_FALSE(group.contains(entities[5])); ASSERT_FALSE(group.contains(entities[5]));
ASSERT_FALSE(group.contains(entities[6])); ASSERT_FALSE(group.contains(entities[6]));
@ -1026,7 +1134,7 @@ TEST(OwningGroup, SortUnordered) {
TEST(OwningGroup, SortWithExclusionList) { TEST(OwningGroup, SortWithExclusionList) {
entt::registry registry; entt::registry registry;
auto group = registry.group<boxed_int>({}, entt::exclude<char>); auto group = registry.group<boxed_int>(entt::get<>, entt::exclude<char>);
entt::entity entities[5]{}; entt::entity entities[5]{};
registry.create(std::begin(entities), std::end(entities)); registry.create(std::begin(entities), std::end(entities));
@ -1043,20 +1151,20 @@ TEST(OwningGroup, SortWithExclusionList) {
return lhs < rhs; return lhs < rhs;
}); });
ASSERT_EQ(group.storage<boxed_int>().data()[0u], entities[4]); ASSERT_EQ(group.handle().data()[0u], entities[4]);
ASSERT_EQ(group.storage<boxed_int>().data()[1u], entities[3]); ASSERT_EQ(group.handle().data()[1u], entities[3]);
ASSERT_EQ(group.storage<boxed_int>().data()[2u], entities[1]); ASSERT_EQ(group.handle().data()[2u], entities[1]);
ASSERT_EQ(group.storage<boxed_int>().data()[3u], entities[0]); ASSERT_EQ(group.handle().data()[3u], entities[0]);
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][0u].value, 4); ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][0u].value, 4);
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][1u].value, 3); ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][1u].value, 3);
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][2u].value, 1); ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][2u].value, 1);
ASSERT_EQ(group.storage<boxed_int>().raw()[0u][3u].value, 0); ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][3u].value, 0);
ASSERT_EQ(group.get<boxed_int>(entities[0]).value, 0); ASSERT_EQ(group.get<boxed_int>(entities[0]).value, 0);
ASSERT_EQ(group.get<boxed_int>(entities[1]).value, 1); ASSERT_EQ(group.get<0>(entities[1]).value, 1);
ASSERT_EQ(group.get<boxed_int>(entities[3]).value, 3); ASSERT_EQ(group.get<boxed_int>(entities[3]).value, 3);
ASSERT_EQ(group.get<boxed_int>(entities[4]).value, 4); ASSERT_EQ(group.get<0>(entities[4]).value, 4);
ASSERT_FALSE(group.contains(entities[2])); ASSERT_FALSE(group.contains(entities[2]));
} }
@ -1110,11 +1218,24 @@ TEST(OwningGroup, ConstNonConstAndAllInBetween) {
ASSERT_EQ(group.size(), 1u); ASSERT_EQ(group.size(), 1u);
static_assert(std::is_same_v<decltype(group.get<0>({})), int &>);
static_assert(std::is_same_v<decltype(group.get<int>({})), int &>); static_assert(std::is_same_v<decltype(group.get<int>({})), int &>);
static_assert(std::is_same_v<decltype(group.get<1>({})), const char &>);
static_assert(std::is_same_v<decltype(group.get<const char>({})), const char &>); static_assert(std::is_same_v<decltype(group.get<const char>({})), const char &>);
static_assert(std::is_same_v<decltype(group.get<2>({})), void>);
static_assert(std::is_same_v<decltype(group.get<empty_type>({})), void>);
static_assert(std::is_same_v<decltype(group.get<3>({})), double &>);
static_assert(std::is_same_v<decltype(group.get<double>({})), double &>); static_assert(std::is_same_v<decltype(group.get<double>({})), double &>);
static_assert(std::is_same_v<decltype(group.get<4>({})), const float &>);
static_assert(std::is_same_v<decltype(group.get<const float>({})), const float &>); static_assert(std::is_same_v<decltype(group.get<const float>({})), const float &>);
static_assert(std::is_same_v<decltype(group.get<int, const char, double, const float>({})), std::tuple<int &, const char &, double &, const float &>>);
static_assert(std::is_same_v<decltype(group.get<int, const char, empty_type, double, const float>({})), std::tuple<int &, const char &, double &, const float &>>);
static_assert(std::is_same_v<decltype(group.get<0, 1, 2, 3, 4>({})), std::tuple<int &, const char &, double &, const float &>>);
static_assert(std::is_same_v<decltype(group.get({})), std::tuple<int &, const char &, double &, const float &>>); static_assert(std::is_same_v<decltype(group.get({})), std::tuple<int &, const char &, double &, const float &>>);
static_assert(std::is_same_v<decltype(std::as_const(registry).group_if_exists<int>(entt::get<char>)), decltype(std::as_const(registry).group_if_exists<const int>(entt::get<const char>))>); static_assert(std::is_same_v<decltype(std::as_const(registry).group_if_exists<int>(entt::get<char>)), decltype(std::as_const(registry).group_if_exists<const int>(entt::get<const char>))>);
@ -1192,7 +1313,7 @@ TEST(OwningGroup, ExcludedComponents) {
registry.emplace<int>(e1, 1); registry.emplace<int>(e1, 1);
registry.emplace<char>(e1); registry.emplace<char>(e1);
const auto group = registry.group<int>({}, entt::exclude<char, double>); const auto group = registry.group<int>(entt::get<>, entt::exclude<char, double>);
const auto e2 = registry.create(); const auto e2 = registry.create();
registry.emplace<int>(e2, 2); registry.emplace<int>(e2, 2);
@ -1207,7 +1328,7 @@ TEST(OwningGroup, ExcludedComponents) {
if(entity == e0) { if(entity == e0) {
ASSERT_EQ(group.get<int>(e0), 0); ASSERT_EQ(group.get<int>(e0), 0);
} else if(entity == e2) { } else if(entity == e2) {
ASSERT_EQ(group.get<int>(e2), 2); ASSERT_EQ(group.get<0>(e2), 2);
} }
} }
@ -1225,7 +1346,7 @@ TEST(OwningGroup, ExcludedComponents) {
if(entity == e1) { if(entity == e1) {
ASSERT_EQ(group.get<int>(e1), 1); ASSERT_EQ(group.get<int>(e1), 1);
} else if(entity == e3) { } else if(entity == e3) {
ASSERT_EQ(group.get<int>(e3), 3); ASSERT_EQ(group.get<0>(e3), 3);
} }
} }
} }
@ -1263,8 +1384,8 @@ TEST(OwningGroup, EmptyAndNonEmptyTypes) {
TEST(OwningGroup, TrackEntitiesOnComponentDestruction) { TEST(OwningGroup, TrackEntitiesOnComponentDestruction) {
entt::registry registry; entt::registry registry;
const auto group = registry.group<int>({}, entt::exclude<char>); const auto group = registry.group<int>(entt::get<>, entt::exclude<char>);
const auto cgroup = std::as_const(registry).group_if_exists<const int>({}, entt::exclude<char>); const auto cgroup = std::as_const(registry).group_if_exists<const int>(entt::get<>, entt::exclude<char>);
const auto entity = registry.create(); const auto entity = registry.create();
registry.emplace<int>(entity); registry.emplace<int>(entity);
@ -1403,7 +1524,7 @@ TEST(OwningGroup, SwappingValuesIsAllowed) {
// thanks to @andranik3949 for pointing out this missing test // thanks to @andranik3949 for pointing out this missing test
registry.view<const boxed_int>().each([](const auto entity, const auto &value) { registry.view<const boxed_int>().each([](const auto entity, const auto &value) {
ASSERT_EQ(entt::to_integral(entity), value.value); ASSERT_EQ(static_cast<int>(entt::to_integral(entity)), value.value);
}); });
} }
@ -1443,26 +1564,81 @@ TEST(OwningGroup, IterableGroupAlgorithmCompatibility) {
TEST(OwningGroup, Storage) { TEST(OwningGroup, Storage) {
entt::registry registry; entt::registry registry;
const auto entity = registry.create(); const auto entity = registry.create();
const auto group = registry.group<int>(entt::get<const char>); auto group = registry.group<int>(entt::get<const char>, entt::exclude<double, const float>);
static_assert(std::is_same_v<decltype(group.storage<0u>()), entt::storage_type_t<int> &>); static_assert(std::is_same_v<decltype(group.storage<0u>()), entt::storage_type_t<int> *>);
static_assert(std::is_same_v<decltype(group.storage<int>()), entt::storage_type_t<int> &>); static_assert(std::is_same_v<decltype(group.storage<int>()), entt::storage_type_t<int> *>);
static_assert(std::is_same_v<decltype(group.storage<1u>()), const entt::storage_type_t<char> &>); static_assert(std::is_same_v<decltype(group.storage<const int>()), entt::storage_type_t<int> *>);
static_assert(std::is_same_v<decltype(group.storage<const char>()), const entt::storage_type_t<char> &>); static_assert(std::is_same_v<decltype(group.storage<1u>()), const entt::storage_type_t<char> *>);
static_assert(std::is_same_v<decltype(group.storage<char>()), const entt::storage_type_t<char> *>);
static_assert(std::is_same_v<decltype(group.storage<const char>()), const entt::storage_type_t<char> *>);
static_assert(std::is_same_v<decltype(group.storage<2u>()), entt::storage_type_t<double> *>);
static_assert(std::is_same_v<decltype(group.storage<double>()), entt::storage_type_t<double> *>);
static_assert(std::is_same_v<decltype(group.storage<const double>()), entt::storage_type_t<double> *>);
static_assert(std::is_same_v<decltype(group.storage<3u>()), const entt::storage_type_t<float> *>);
static_assert(std::is_same_v<decltype(group.storage<float>()), const entt::storage_type_t<float> *>);
static_assert(std::is_same_v<decltype(group.storage<const float>()), const entt::storage_type_t<float> *>);
ASSERT_TRUE(group);
ASSERT_NE(group.storage<int>(), nullptr);
ASSERT_NE(group.storage<1u>(), nullptr);
ASSERT_NE(group.storage<double>(), nullptr);
ASSERT_NE(group.storage<3u>(), nullptr);
ASSERT_EQ(group.size(), 0u); ASSERT_EQ(group.size(), 0u);
group.storage<int>().emplace(entity); group.storage<int>()->emplace(entity);
group.storage<double>()->emplace(entity);
registry.emplace<char>(entity); registry.emplace<char>(entity);
registry.emplace<float>(entity);
ASSERT_EQ(group.size(), 0u);
ASSERT_EQ(group.begin(), group.end());
ASSERT_TRUE(group.storage<int>()->contains(entity));
ASSERT_TRUE(group.storage<const char>()->contains(entity));
ASSERT_TRUE(group.storage<double>()->contains(entity));
ASSERT_TRUE(group.storage<const float>()->contains(entity));
ASSERT_TRUE((registry.all_of<int, char, double, float>(entity)));
group.storage<double>()->erase(entity);
registry.erase<float>(entity);
ASSERT_EQ(group.size(), 1u); ASSERT_EQ(group.size(), 1u);
ASSERT_TRUE(group.storage<int>().contains(entity)); ASSERT_NE(group.begin(), group.end());
ASSERT_TRUE(group.storage<const char>().contains(entity)); ASSERT_TRUE(group.storage<const int>()->contains(entity));
ASSERT_TRUE(group.storage<char>()->contains(entity));
ASSERT_FALSE(group.storage<const double>()->contains(entity));
ASSERT_FALSE(group.storage<float>()->contains(entity));
ASSERT_TRUE((registry.all_of<int, char>(entity))); ASSERT_TRUE((registry.all_of<int, char>(entity)));
ASSERT_FALSE((registry.any_of<double, float>(entity)));
group.storage<0u>().erase(entity); group.storage<0u>()->erase(entity);
ASSERT_EQ(group.size(), 0u); ASSERT_EQ(group.size(), 0u);
ASSERT_TRUE(group.storage<1u>().contains(entity)); ASSERT_EQ(group.begin(), group.end());
ASSERT_FALSE((registry.all_of<int, char>(entity))); ASSERT_FALSE(group.storage<0u>()->contains(entity));
ASSERT_TRUE(group.storage<1u>()->contains(entity));
ASSERT_FALSE(group.storage<2u>()->contains(entity));
ASSERT_FALSE(group.storage<3u>()->contains(entity));
ASSERT_TRUE((registry.all_of<char>(entity)));
ASSERT_FALSE((registry.any_of<int, double, float>(entity)));
group = {};
ASSERT_FALSE(group);
ASSERT_EQ(group.storage<0u>(), nullptr);
ASSERT_EQ(group.storage<const char>(), nullptr);
ASSERT_EQ(group.storage<2u>(), nullptr);
ASSERT_EQ(group.storage<const float>(), nullptr);
}
ENTT_DEBUG_TEST(OwningGroupDeathTest, Overlapping) {
entt::registry registry;
registry.group<char>(entt::get<int>, entt::exclude<double>);
ASSERT_DEATH((registry.group<char, float>(entt::get<float>, entt::exclude<double>)), "");
ASSERT_DEATH(registry.group<char>(entt::get<int, float>, entt::exclude<double>), "");
ASSERT_DEATH(registry.group<char>(entt::get<int>, entt::exclude<double, float>), "");
} }

View File

@ -83,8 +83,8 @@ TEST(BasicHandle, Destruction) {
ASSERT_FALSE(handle); ASSERT_FALSE(handle);
ASSERT_FALSE(handle.valid()); ASSERT_FALSE(handle.valid());
ASSERT_NE(handle.registry(), nullptr); ASSERT_NE(handle.registry(), nullptr);
ASSERT_EQ(handle.entity(), entity);
ASSERT_EQ(registry.current(entity), typename entt::registry::version_type{}); ASSERT_EQ(registry.current(entity), typename entt::registry::version_type{});
ASSERT_EQ(handle.entity(), entt::entity{entt::null});
handle = entt::handle{registry, registry.create()}; handle = entt::handle{registry, registry.create()};
@ -98,8 +98,8 @@ TEST(BasicHandle, Destruction) {
ASSERT_FALSE(handle); ASSERT_FALSE(handle);
ASSERT_FALSE(handle.valid()); ASSERT_FALSE(handle.valid());
ASSERT_NE(handle.registry(), nullptr); ASSERT_NE(handle.registry(), nullptr);
ASSERT_EQ(handle.entity(), entity);
ASSERT_NE(registry.current(entity), typename entt::registry::version_type{}); ASSERT_NE(registry.current(entity), typename entt::registry::version_type{});
ASSERT_EQ(handle.entity(), entt::entity{entt::null});
} }
TEST(BasicHandle, Comparison) { TEST(BasicHandle, Comparison) {
@ -275,6 +275,8 @@ TEST(BasicHandle, HandleStorageIterator) {
registry.emplace<int>(entity); registry.emplace<int>(entity);
registry.emplace<double>(entity); registry.emplace<double>(entity);
// required to test the find-first initialization step
registry.storage<entt::entity>().erase(entity);
auto test = [](auto iterable) { auto test = [](auto iterable) {
auto end{iterable.begin()}; auto end{iterable.begin()};
@ -290,6 +292,13 @@ TEST(BasicHandle, HandleStorageIterator) {
ASSERT_EQ(++begin, iterable.end()); ASSERT_EQ(++begin, iterable.end());
}; };
test(entt::handle{registry, entity}.storage()); const auto handle = entt::handle{registry, entity};
test(entt::const_handle{std::as_const(registry), entity}.storage()); 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());
} }

View File

@ -16,6 +16,10 @@ struct stable_type {
int value; int value;
}; };
void sigh_callback(int &value) {
++value;
}
TEST(Helper, AsView) { TEST(Helper, AsView) {
entt::registry registry; entt::registry registry;
const entt::registry cregistry; const entt::registry cregistry;
@ -48,7 +52,7 @@ TEST(Helper, Invoke) {
TEST(Helper, ToEntity) { TEST(Helper, ToEntity) {
entt::registry registry; entt::registry registry;
const entt::entity null = entt::null; const entt::entity null = entt::null;
constexpr auto page_size = entt::component_traits<int>::page_size; constexpr auto page_size = entt::storage_type_t<int>::traits_type::page_size;
const int value = 42; const int value = 42;
ASSERT_EQ(entt::to_entity(registry, 42), null); ASSERT_EQ(entt::to_entity(registry, 42), null);
@ -88,7 +92,7 @@ TEST(Helper, ToEntity) {
TEST(Helper, ToEntityStableType) { TEST(Helper, ToEntityStableType) {
entt::registry registry; entt::registry registry;
const entt::entity null = entt::null; const entt::entity null = entt::null;
constexpr auto page_size = entt::component_traits<stable_type>::page_size; constexpr auto page_size = entt::storage_type_t<stable_type>::traits_type::page_size;
const stable_type value{42}; const stable_type value{42};
ASSERT_EQ(entt::to_entity(registry, stable_type{42}), null); 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, stable_type{42}), null);
ASSERT_EQ(entt::to_entity(registry, value), 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(), &registry);
helper.with<int>()
.on_construct<&sigh_callback>(counter)
.on_update<&sigh_callback>(counter)
.on_destroy<&sigh_callback>(counter);
ASSERT_EQ(counter, 0);
registry.emplace<int>(entt);
registry.replace<int>(entt);
registry.erase<int>(entt);
ASSERT_EQ(counter, 3);
helper.with<double>("other"_hs)
.on_construct<&sigh_callback>(counter)
.on_update<&sigh_callback>(counter)
.on_destroy<&sigh_callback>(counter);
registry.emplace<double>(entt);
registry.replace<double>(entt);
registry.erase<double>(entt);
ASSERT_EQ(counter, 3);
registry.storage<double>("other"_hs).emplace(entt);
registry.storage<double>("other"_hs).patch(entt);
registry.storage<double>("other"_hs).erase(entt);
ASSERT_EQ(counter, 6);
}

View File

@ -1,5 +1,7 @@
#include <cstddef> #include <cstddef>
#include <utility>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <entt/core/type_info.hpp>
#include <entt/entity/organizer.hpp> #include <entt/entity/organizer.hpp>
#include <entt/entity/registry.hpp> #include <entt/entity/registry.hpp>
@ -20,7 +22,7 @@ struct clazz {
static void ro_int_char_with_payload(clazz &, entt::view<entt::get_t<const int, const char>>) {} static void ro_int_char_with_payload(clazz &, entt::view<entt::get_t<const int, const char>>) {}
}; };
void to_args_integrity(entt::view<entt::get_t<int>> view, std::size_t &value, entt::registry &registry) { void to_args_integrity(entt::view<entt::get_t<int>> view, std::size_t &value, entt::registry &) {
value = view.size(); value = view.size();
} }
@ -363,6 +365,10 @@ TEST(Organizer, Prepare) {
ASSERT_FALSE(registry.ctx().contains<char>()); ASSERT_FALSE(registry.ctx().contains<char>());
ASSERT_FALSE(registry.ctx().contains<double>()); ASSERT_FALSE(registry.ctx().contains<double>());
ASSERT_EQ(std::as_const(registry).storage<int>(), nullptr);
ASSERT_EQ(std::as_const(registry).storage<char>(), nullptr);
ASSERT_EQ(std::as_const(registry).storage<double>(), nullptr);
for(auto &&vertex: graph) { for(auto &&vertex: graph) {
vertex.prepare(registry); vertex.prepare(registry);
} }
@ -370,6 +376,10 @@ TEST(Organizer, Prepare) {
ASSERT_FALSE(registry.ctx().contains<int>()); ASSERT_FALSE(registry.ctx().contains<int>());
ASSERT_FALSE(registry.ctx().contains<char>()); ASSERT_FALSE(registry.ctx().contains<char>());
ASSERT_TRUE(registry.ctx().contains<double>()); ASSERT_TRUE(registry.ctx().contains<double>());
ASSERT_NE(std::as_const(registry).storage<int>(), nullptr);
ASSERT_NE(std::as_const(registry).storage<char>(), nullptr);
ASSERT_EQ(std::as_const(registry).storage<double>(), nullptr);
} }
TEST(Organizer, Dependencies) { TEST(Organizer, Dependencies) {
@ -428,5 +438,5 @@ TEST(Organizer, ToArgsIntegrity) {
auto graph = organizer.graph(); auto graph = organizer.graph();
graph[0u].callback()(graph[0u].data(), registry); graph[0u].callback()(graph[0u].data(), registry);
ASSERT_EQ(registry.ctx().at<std::size_t>(), 0u); ASSERT_EQ(registry.ctx().get<std::size_t>(), 0u);
} }

View File

@ -40,18 +40,16 @@ struct aggregate {
}; };
struct listener { struct listener {
template<typename Component> template<typename Type>
static void sort(entt::registry &registry) { static void sort(entt::registry &registry) {
registry.sort<Component>([](auto lhs, auto rhs) { return lhs < rhs; }); registry.sort<Type>([](auto lhs, auto rhs) { return lhs < rhs; });
} }
template<typename Component>
void incr(const entt::registry &, entt::entity entity) { void incr(const entt::registry &, entt::entity entity) {
last = entity; last = entity;
++counter; ++counter;
} }
template<typename Component>
void decr(const entt::registry &, entt::entity entity) { void decr(const entt::registry &, entt::entity entity) {
last = entity; last = entity;
--counter; --counter;
@ -75,11 +73,11 @@ struct destruction_order {
destruction_order(const entt::registry &ref, bool &ctx) destruction_order(const entt::registry &ref, bool &ctx)
: registry{&ref}, : registry{&ref},
ctx_check{&ctx} { ctx_check{&ctx} {
*ctx_check = (registry->ctx().find<int>() != nullptr); *ctx_check = (registry->ctx().find<ctx_check_type>() != nullptr);
} }
~destruction_order() { ~destruction_order() {
*ctx_check = *ctx_check && (registry->ctx().find<int>() != nullptr); *ctx_check = *ctx_check && (registry->ctx().find<ctx_check_type>() != nullptr);
} }
private: private:
@ -87,6 +85,22 @@ private:
bool *ctx_check{}; bool *ctx_check{};
}; };
enum class small_entity : std::uint32_t {};
struct small_entity_traits {
using value_type = small_entity;
using entity_type = uint32_t;
using version_type = uint16_t;
static constexpr entity_type entity_mask = 0xFF;
static constexpr entity_type version_mask = 0x00;
};
template<>
struct entt::entt_traits<small_entity>: entt::basic_entt_traits<small_entity_traits> {
using base_type = entt::basic_entt_traits<small_entity_traits>;
static constexpr auto page_size = ENTT_SPARSE_PAGE;
};
TEST(Registry, Context) { TEST(Registry, Context) {
entt::registry registry; entt::registry registry;
auto &ctx = registry.ctx(); auto &ctx = registry.ctx();
@ -126,8 +140,8 @@ TEST(Registry, Context) {
ASSERT_EQ(ctx.emplace<const int>(0), 42); ASSERT_EQ(ctx.emplace<const int>(0), 42);
ASSERT_EQ(ctx.find<const int>(), cctx.find<int>()); ASSERT_EQ(ctx.find<const int>(), cctx.find<int>());
ASSERT_EQ(ctx.at<int>(), cctx.at<const int>()); ASSERT_EQ(ctx.get<int>(), cctx.get<const int>());
ASSERT_EQ(ctx.at<int>(), 42); ASSERT_EQ(ctx.get<int>(), 42);
ASSERT_EQ(ctx.find<double>(), nullptr); ASSERT_EQ(ctx.find<double>(), nullptr);
ASSERT_EQ(cctx.find<double>(), nullptr); ASSERT_EQ(cctx.find<double>(), nullptr);
@ -139,8 +153,8 @@ TEST(Registry, Context) {
ASSERT_EQ(ctx.insert_or_assign<const int>(0), 0); ASSERT_EQ(ctx.insert_or_assign<const int>(0), 0);
ASSERT_EQ(ctx.find<const int>(), cctx.find<int>()); ASSERT_EQ(ctx.find<const int>(), cctx.find<int>());
ASSERT_EQ(ctx.at<int>(), cctx.at<const int>()); ASSERT_EQ(ctx.get<int>(), cctx.get<const int>());
ASSERT_EQ(ctx.at<int>(), 0); ASSERT_EQ(ctx.get<int>(), 0);
} }
TEST(Registry, ContextHint) { TEST(Registry, ContextHint) {
@ -376,13 +390,12 @@ TEST(Registry, Functionalities) {
TEST(Registry, Constructors) { TEST(Registry, Constructors) {
entt::registry registry; entt::registry registry;
entt::registry other{42}; entt::registry other{42};
const entt::entity entity = entt::tombstone;
ASSERT_TRUE(registry.empty()); ASSERT_TRUE(registry.empty());
ASSERT_TRUE(other.empty()); ASSERT_TRUE(other.empty());
ASSERT_EQ(registry.released(), entity); ASSERT_EQ(registry.released(), 0u);
ASSERT_EQ(other.released(), entity); ASSERT_EQ(other.released(), 0u);
} }
TEST(Registry, Move) { TEST(Registry, Move) {
@ -486,6 +499,8 @@ TEST(Registry, Identifiers) {
} }
TEST(Registry, Data) { TEST(Registry, Data) {
using traits_type = entt::entt_traits<entt::entity>;
entt::registry registry; entt::registry registry;
ASSERT_EQ(std::as_const(registry).data(), nullptr); ASSERT_EQ(std::as_const(registry).data(), nullptr);
@ -497,8 +512,8 @@ TEST(Registry, Data) {
const auto other = registry.create(); const auto other = registry.create();
registry.release(entity); registry.release(entity);
ASSERT_NE(*std::as_const(registry).data(), entity); ASSERT_EQ(*std::as_const(registry).data(), other);
ASSERT_EQ(*(std::as_const(registry).data() + 1u), other); ASSERT_EQ(*(std::as_const(registry).data() + 1u), traits_type::next(entity));
} }
TEST(Registry, CreateManyEntitiesAtOnce) { TEST(Registry, CreateManyEntitiesAtOnce) {
@ -533,7 +548,7 @@ TEST(Registry, CreateManyEntitiesAtOnceWithListener) {
entt::entity entities[3]; entt::entity entities[3];
listener listener; listener listener;
registry.on_construct<int>().connect<&listener::incr<int>>(listener); registry.on_construct<int>().connect<&listener::incr>(listener);
registry.create(std::begin(entities), std::end(entities)); registry.create(std::begin(entities), std::end(entities));
registry.insert(std::begin(entities), std::end(entities), 42); registry.insert(std::begin(entities), std::end(entities), 42);
registry.insert(std::begin(entities), std::end(entities), 'c'); registry.insert(std::begin(entities), std::end(entities), 'c');
@ -542,8 +557,8 @@ TEST(Registry, CreateManyEntitiesAtOnceWithListener) {
ASSERT_EQ(registry.get<char>(entities[1]), 'c'); ASSERT_EQ(registry.get<char>(entities[1]), 'c');
ASSERT_EQ(listener.counter, 3); ASSERT_EQ(listener.counter, 3);
registry.on_construct<int>().disconnect<&listener::incr<int>>(listener); registry.on_construct<int>().disconnect<&listener::incr>(listener);
registry.on_construct<empty_type>().connect<&listener::incr<empty_type>>(listener); registry.on_construct<empty_type>().connect<&listener::incr>(listener);
registry.create(std::begin(entities), std::end(entities)); 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), 'a');
registry.insert<empty_type>(std::begin(entities), std::end(entities)); registry.insert<empty_type>(std::begin(entities), std::end(entities));
@ -560,8 +575,9 @@ TEST(Registry, CreateWithHint) {
auto e3 = registry.create(entt::entity{3}); auto e3 = registry.create(entt::entity{3});
auto e2 = registry.create(entt::entity{3}); auto e2 = registry.create(entt::entity{3});
ASSERT_EQ(e2, entt::entity{2}); ASSERT_EQ(e2, entt::entity{1});
ASSERT_FALSE(registry.valid(entt::entity{1})); ASSERT_FALSE(registry.valid(entt::entity{0}));
ASSERT_FALSE(registry.valid(entt::entity{2}));
ASSERT_EQ(e3, entt::entity{3}); ASSERT_EQ(e3, entt::entity{3});
registry.release(e2); registry.release(e2);
@ -572,10 +588,10 @@ TEST(Registry, CreateWithHint) {
e2 = registry.create(); e2 = registry.create();
auto e1 = registry.create(entt::entity{2}); 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_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); ASSERT_EQ(traits_type::to_version(e1), 0u);
registry.release(e1); registry.release(e1);
@ -640,6 +656,14 @@ TEST(Registry, CreateDestroyReleaseCornerCase) {
ASSERT_EQ(registry.current(e1), 1u); ASSERT_EQ(registry.current(e1), 1u);
} }
ENTT_DEBUG_TEST(RegistryDeathTest, CreateTooManyEntities) {
entt::basic_registry<small_entity> registry;
std::vector<small_entity> entities(entt::entt_traits<small_entity>::to_entity(entt::null));
registry.create(entities.begin(), entities.end());
ASSERT_DEATH([[maybe_unused]] const auto entity = registry.create(), "");
}
TEST(Registry, DestroyVersion) { TEST(Registry, DestroyVersion) {
entt::registry registry; entt::registry registry;
@ -666,7 +690,7 @@ ENTT_DEBUG_TEST(RegistryDeathTest, DestroyVersion) {
ASSERT_DEATH(registry.destroy(entity, 3), ""); ASSERT_DEATH(registry.destroy(entity, 3), "");
} }
TEST(Registry, RangeDestroy) { TEST(Registry, DestroyRange) {
entt::registry registry; entt::registry registry;
const auto iview = registry.view<int>(); const auto iview = registry.view<int>();
const auto icview = registry.view<int, char>(); const auto icview = registry.view<int, char>();
@ -722,6 +746,24 @@ TEST(Registry, RangeDestroy) {
ASSERT_FALSE(registry.valid(entities[1u])); ASSERT_FALSE(registry.valid(entities[1u]));
ASSERT_FALSE(registry.valid(entities[2u])); ASSERT_FALSE(registry.valid(entities[2u]));
ASSERT_EQ(registry.storage<int>().size(), 0u); ASSERT_EQ(registry.storage<int>().size(), 0u);
entt::sparse_set managed{};
registry.create(std::begin(entities), std::end(entities));
managed.push(std::begin(entities), std::end(entities));
registry.insert<int>(managed.begin(), managed.end());
ASSERT_TRUE(registry.valid(managed[0u]));
ASSERT_TRUE(registry.valid(managed[1u]));
ASSERT_TRUE(registry.valid(managed[2u]));
ASSERT_EQ(registry.storage<int>().size(), 3u);
registry.destroy(managed.begin(), managed.end());
ASSERT_FALSE(registry.valid(managed[0u]));
ASSERT_FALSE(registry.valid(managed[1u]));
ASSERT_FALSE(registry.valid(managed[2u]));
ASSERT_EQ(registry.storage<int>().size(), 0u);
} }
TEST(Registry, StableDestroy) { TEST(Registry, StableDestroy) {
@ -792,7 +834,7 @@ ENTT_DEBUG_TEST(RegistryDeathTest, ReleaseVersion) {
ASSERT_DEATH(registry.release(entity, 3), ""); ASSERT_DEATH(registry.release(entity, 3), "");
} }
TEST(Registry, RangeRelease) { TEST(Registry, ReleaseRange) {
entt::registry registry; entt::registry registry;
entt::entity entities[3u]; entt::entity entities[3u];
@ -930,10 +972,12 @@ TEST(Registry, Orphans) {
TEST(Registry, View) { TEST(Registry, View) {
entt::registry registry; entt::registry registry;
auto mview = registry.view<int, char>(); entt::entity entities[3u];
auto iview = registry.view<int>(); auto iview = registry.view<int>();
auto cview = registry.view<char>(); auto cview = registry.view<char>();
entt::entity entities[3u]; auto mview = registry.view<int, char>();
auto fview = registry.view<int>(entt::exclude<char>);
registry.create(std::begin(entities), std::end(entities)); registry.create(std::begin(entities), std::end(entities));
@ -948,10 +992,55 @@ TEST(Registry, View) {
ASSERT_EQ(iview.size(), 3u); ASSERT_EQ(iview.size(), 3u);
ASSERT_EQ(cview.size(), 2u); ASSERT_EQ(cview.size(), 2u);
std::size_t cnt{}; ASSERT_EQ(mview.size_hint(), 3u);
mview.each([&cnt](auto...) { ++cnt; }); ASSERT_EQ(fview.size_hint(), 3u);
ASSERT_EQ(cnt, 2u); mview.refresh();
fview.refresh();
ASSERT_EQ(mview.size_hint(), 2u);
ASSERT_EQ(fview.size_hint(), 3u);
ASSERT_NE(mview.begin(), mview.end());
ASSERT_NE(fview.begin(), fview.end());
ASSERT_EQ(std::distance(mview.begin(), mview.end()), 2);
ASSERT_EQ(std::distance(fview.begin(), fview.end()), 1);
mview.each([&entities, first = true](auto entity, auto &&...) mutable {
ASSERT_EQ(entity, entities[2u * first]);
first = false;
});
fview.each([&entities](auto entity, auto &&...) {
ASSERT_EQ(entity, entities[1u]);
});
}
TEST(Registry, ExcludeOnlyView) {
entt::registry registry;
entt::entity entities[4u];
auto view = registry.view<entt::entity>(entt::exclude<int>);
registry.create(std::begin(entities), std::end(entities));
registry.emplace<int>(entities[0u], 0);
registry.emplace<int>(entities[2u], 0);
registry.emplace<int>(entities[3u], 0);
registry.destroy(entities[3u]);
ASSERT_EQ(view.size_hint(), 4u);
ASSERT_NE(view.begin(), view.end());
// returns all matching identifiers, both in-use and available ones
ASSERT_EQ(std::distance(view.begin(), view.end()), 2);
// skips available identifiers automatically, only returns in-use elements
view.each([&entities](auto entity, auto &&...) {
ASSERT_EQ(entity, entities[1u]);
});
} }
TEST(Registry, NonOwningGroupInitOnFirstUse) { TEST(Registry, NonOwningGroupInitOnFirstUse) {
@ -964,7 +1053,7 @@ TEST(Registry, NonOwningGroupInitOnFirstUse) {
registry.emplace<char>(entities[2u], 'c'); registry.emplace<char>(entities[2u], 'c');
std::size_t cnt{}; std::size_t cnt{};
auto group = registry.group<>(entt::get<int, char>); auto group = registry.group(entt::get<int, char>);
group.each([&cnt](auto...) { ++cnt; }); group.each([&cnt](auto...) { ++cnt; });
ASSERT_FALSE((registry.owned<int, char>())); ASSERT_FALSE((registry.owned<int, char>()));
@ -974,7 +1063,7 @@ TEST(Registry, NonOwningGroupInitOnFirstUse) {
TEST(Registry, NonOwningGroupInitOnEmplace) { TEST(Registry, NonOwningGroupInitOnEmplace) {
entt::registry registry; entt::registry registry;
entt::entity entities[3u]; entt::entity entities[3u];
auto group = registry.group<>(entt::get<int, char>); auto group = registry.group(entt::get<int, char>);
registry.create(std::begin(entities), std::end(entities)); registry.create(std::begin(entities), std::end(entities));
registry.insert<int>(std::begin(entities), std::end(entities), 0); registry.insert<int>(std::begin(entities), std::end(entities), 0);
@ -1097,7 +1186,7 @@ TEST(Registry, CleanViewAfterRemoveAndClear) {
TEST(Registry, CleanNonOwningGroupViewAfterRemoveAndClear) { TEST(Registry, CleanNonOwningGroupViewAfterRemoveAndClear) {
entt::registry registry; entt::registry registry;
auto group = registry.group<>(entt::get<int, char>); auto group = registry.group(entt::get<int, char>);
const auto entity = registry.create(); const auto entity = registry.create();
registry.emplace<int>(entity, 0); registry.emplace<int>(entity, 0);
@ -1188,107 +1277,21 @@ TEST(Registry, CleanPartialOwningGroupViewAfterRemoveAndClear) {
ASSERT_EQ(group.size(), 0u); ASSERT_EQ(group.size(), 0u);
} }
TEST(Registry, NestedGroups) { ENTT_DEBUG_TEST(RegistryDeathTest, NestedGroups) {
entt::registry registry; entt::registry registry;
entt::entity entities[10]; registry.group<int, double>(entt::get<char>);
registry.create(std::begin(entities), std::end(entities)); ASSERT_DEATH(registry.group<int>(entt::get<char>), "");
registry.insert<int>(std::begin(entities), std::end(entities)); ASSERT_DEATH(registry.group<int>(entt::get<char, double>), "");
registry.insert<char>(std::begin(entities), std::end(entities)); ASSERT_DEATH(registry.group<int>(entt::get<char>, entt::exclude<double>), "");
const auto g1 = registry.group<int>(entt::get<char>, entt::exclude<double>); ASSERT_DEATH((registry.group<int, double>()), "");
}
ASSERT_TRUE(registry.sortable(g1)); ENTT_DEBUG_TEST(RegistryDeathTest, ConflictingGroups) {
ASSERT_EQ(g1.size(), 10u); entt::registry registry;
const auto g2 = registry.group<int>(entt::get<char>); registry.group<char>(entt::get<int>, entt::exclude<double>);
ASSERT_DEATH(registry.group<char>(entt::get<float>, entt::exclude<double>), "");
ASSERT_TRUE(registry.sortable(g1));
ASSERT_FALSE(registry.sortable(g2));
ASSERT_EQ(g1.size(), 10u);
ASSERT_EQ(g2.size(), 10u);
for(auto i = 0u; i < 5u; ++i) {
ASSERT_TRUE(g1.contains(entities[i * 2 + 1]));
ASSERT_TRUE(g1.contains(entities[i * 2]));
ASSERT_TRUE(g2.contains(entities[i * 2 + 1]));
ASSERT_TRUE(g2.contains(entities[i * 2]));
registry.emplace<double>(entities[i * 2]);
}
ASSERT_EQ(g1.size(), 5u);
ASSERT_EQ(g2.size(), 10u);
for(auto i = 0u; i < 5u; ++i) {
ASSERT_TRUE(g1.contains(entities[i * 2 + 1]));
ASSERT_FALSE(g1.contains(entities[i * 2]));
ASSERT_TRUE(g2.contains(entities[i * 2 + 1]));
ASSERT_TRUE(g2.contains(entities[i * 2]));
registry.erase<int>(entities[i * 2 + 1]);
}
ASSERT_EQ(g1.size(), 0u);
ASSERT_EQ(g2.size(), 5u);
const auto g3 = registry.group<int, float>(entt::get<char>, entt::exclude<double>);
ASSERT_FALSE(registry.sortable(g1));
ASSERT_FALSE(registry.sortable(g2));
ASSERT_TRUE(registry.sortable(g3));
ASSERT_EQ(g1.size(), 0u);
ASSERT_EQ(g2.size(), 5u);
ASSERT_EQ(g3.size(), 0u);
for(auto i = 0u; i < 5u; ++i) {
ASSERT_FALSE(g1.contains(entities[i * 2 + 1]));
ASSERT_FALSE(g1.contains(entities[i * 2]));
ASSERT_FALSE(g2.contains(entities[i * 2 + 1]));
ASSERT_TRUE(g2.contains(entities[i * 2]));
ASSERT_FALSE(g3.contains(entities[i * 2 + 1]));
ASSERT_FALSE(g3.contains(entities[i * 2]));
registry.emplace<int>(entities[i * 2 + 1]);
}
ASSERT_EQ(g1.size(), 5u);
ASSERT_EQ(g2.size(), 10u);
ASSERT_EQ(g3.size(), 0u);
for(auto i = 0u; i < 5u; ++i) {
ASSERT_TRUE(g1.contains(entities[i * 2 + 1]));
ASSERT_FALSE(g1.contains(entities[i * 2]));
ASSERT_TRUE(g2.contains(entities[i * 2 + 1]));
ASSERT_TRUE(g2.contains(entities[i * 2]));
ASSERT_FALSE(g3.contains(entities[i * 2 + 1]));
ASSERT_FALSE(g3.contains(entities[i * 2]));
registry.emplace<float>(entities[i * 2]);
}
ASSERT_EQ(g1.size(), 5u);
ASSERT_EQ(g2.size(), 10u);
ASSERT_EQ(g3.size(), 0u);
for(auto i = 0u; i < 5u; ++i) {
registry.erase<double>(entities[i * 2]);
}
ASSERT_EQ(g1.size(), 10u);
ASSERT_EQ(g2.size(), 10u);
ASSERT_EQ(g3.size(), 5u);
for(auto i = 0u; i < 5u; ++i) {
ASSERT_TRUE(g1.contains(entities[i * 2 + 1]));
ASSERT_TRUE(g1.contains(entities[i * 2]));
ASSERT_TRUE(g2.contains(entities[i * 2 + 1]));
ASSERT_TRUE(g2.contains(entities[i * 2]));
ASSERT_FALSE(g3.contains(entities[i * 2 + 1]));
ASSERT_TRUE(g3.contains(entities[i * 2]));
registry.erase<int>(entities[i * 2 + 1]);
registry.erase<int>(entities[i * 2]);
}
ASSERT_EQ(g1.size(), 0u);
ASSERT_EQ(g2.size(), 0u);
ASSERT_EQ(g3.size(), 0u);
} }
TEST(Registry, SortSingle) { TEST(Registry, SortSingle) {
@ -1379,10 +1382,10 @@ TEST(Registry, Signals) {
entt::entity entities[2u]; entt::entity entities[2u];
listener listener; listener listener;
registry.on_construct<empty_type>().connect<&listener::incr<empty_type>>(listener); registry.on_construct<empty_type>().connect<&listener::incr>(listener);
registry.on_destroy<empty_type>().connect<&listener::decr<empty_type>>(listener); registry.on_destroy<empty_type>().connect<&listener::decr>(listener);
registry.on_construct<int>().connect<&listener::incr<int>>(listener); registry.on_construct<int>().connect<&listener::incr>(listener);
registry.on_destroy<int>().connect<&listener::decr<int>>(listener); registry.on_destroy<int>().connect<&listener::decr>(listener);
registry.create(std::begin(entities), std::end(entities)); registry.create(std::begin(entities), std::end(entities));
registry.insert<empty_type>(std::begin(entities), std::end(entities)); registry.insert<empty_type>(std::begin(entities), std::end(entities));
@ -1400,16 +1403,16 @@ TEST(Registry, Signals) {
ASSERT_EQ(listener.counter, 2); ASSERT_EQ(listener.counter, 2);
ASSERT_EQ(listener.last, entities[0u]); ASSERT_EQ(listener.last, entities[0u]);
registry.on_destroy<empty_type>().disconnect<&listener::decr<empty_type>>(listener); registry.on_destroy<empty_type>().disconnect<&listener::decr>(listener);
registry.on_destroy<int>().disconnect<&listener::decr<int>>(listener); registry.on_destroy<int>().disconnect<&listener::decr>(listener);
registry.erase<empty_type, int>(entities[1u]); registry.erase<empty_type, int>(entities[1u]);
ASSERT_EQ(listener.counter, 2); ASSERT_EQ(listener.counter, 2);
ASSERT_EQ(listener.last, entities[0u]); ASSERT_EQ(listener.last, entities[0u]);
registry.on_construct<empty_type>().disconnect<&listener::incr<empty_type>>(listener); registry.on_construct<empty_type>().disconnect<&listener::incr>(listener);
registry.on_construct<int>().disconnect<&listener::incr<int>>(listener); registry.on_construct<int>().disconnect<&listener::incr>(listener);
registry.emplace<empty_type>(entities[1u]); registry.emplace<empty_type>(entities[1u]);
registry.emplace<int>(entities[1u]); registry.emplace<int>(entities[1u]);
@ -1417,8 +1420,8 @@ TEST(Registry, Signals) {
ASSERT_EQ(listener.counter, 2); ASSERT_EQ(listener.counter, 2);
ASSERT_EQ(listener.last, entities[0u]); ASSERT_EQ(listener.last, entities[0u]);
registry.on_construct<int>().connect<&listener::incr<int>>(listener); registry.on_construct<int>().connect<&listener::incr>(listener);
registry.on_destroy<int>().connect<&listener::decr<int>>(listener); registry.on_destroy<int>().connect<&listener::decr>(listener);
registry.emplace<int>(entities[0u]); registry.emplace<int>(entities[0u]);
registry.erase<int>(entities[1u]); registry.erase<int>(entities[1u]);
@ -1426,8 +1429,8 @@ TEST(Registry, Signals) {
ASSERT_EQ(listener.counter, 2); ASSERT_EQ(listener.counter, 2);
ASSERT_EQ(listener.last, entities[1u]); ASSERT_EQ(listener.last, entities[1u]);
registry.on_construct<empty_type>().connect<&listener::incr<empty_type>>(listener); registry.on_construct<empty_type>().connect<&listener::incr>(listener);
registry.on_destroy<empty_type>().connect<&listener::decr<empty_type>>(listener); registry.on_destroy<empty_type>().connect<&listener::decr>(listener);
registry.erase<empty_type>(entities[1u]); registry.erase<empty_type>(entities[1u]);
registry.emplace<empty_type>(entities[0u]); registry.emplace<empty_type>(entities[0u]);
@ -1454,8 +1457,8 @@ TEST(Registry, Signals) {
ASSERT_EQ(listener.counter, 2); ASSERT_EQ(listener.counter, 2);
ASSERT_EQ(listener.last, entities[0u]); ASSERT_EQ(listener.last, entities[0u]);
registry.on_destroy<empty_type>().disconnect<&listener::decr<empty_type>>(listener); registry.on_destroy<empty_type>().disconnect<&listener::decr>(listener);
registry.on_destroy<int>().disconnect<&listener::decr<int>>(listener); registry.on_destroy<int>().disconnect<&listener::decr>(listener);
registry.emplace_or_replace<empty_type>(entities[0u]); registry.emplace_or_replace<empty_type>(entities[0u]);
registry.emplace_or_replace<int>(entities[0u]); registry.emplace_or_replace<int>(entities[0u]);
@ -1463,8 +1466,8 @@ TEST(Registry, Signals) {
ASSERT_EQ(listener.counter, 2); ASSERT_EQ(listener.counter, 2);
ASSERT_EQ(listener.last, entities[0u]); ASSERT_EQ(listener.last, entities[0u]);
registry.on_update<empty_type>().connect<&listener::incr<empty_type>>(listener); registry.on_update<empty_type>().connect<&listener::incr>(listener);
registry.on_update<int>().connect<&listener::incr<int>>(listener); registry.on_update<int>().connect<&listener::incr>(listener);
registry.emplace_or_replace<empty_type>(entities[0u]); registry.emplace_or_replace<empty_type>(entities[0u]);
registry.emplace_or_replace<int>(entities[0u]); registry.emplace_or_replace<int>(entities[0u]);
@ -1479,6 +1482,84 @@ TEST(Registry, Signals) {
ASSERT_EQ(listener.last, entities[0u]); ASSERT_EQ(listener.last, entities[0u]);
} }
TEST(Registry, SignalsOnRuntimePool) {
using namespace entt::literals;
entt::registry registry;
const auto entity = registry.create();
listener listener;
registry.on_construct<int>("custom"_hs).connect<&listener::incr>(listener);
registry.on_update<int>("custom"_hs).connect<&listener::incr>(listener);
registry.on_destroy<int>("custom"_hs).connect<&listener::incr>(listener);
ASSERT_EQ(listener.counter, 0);
registry.emplace<int>(entity);
registry.patch<int>(entity);
registry.erase<int>(entity);
ASSERT_EQ(listener.counter, 0);
registry.storage<int>("custom"_hs).emplace(entity);
registry.storage<int>("custom"_hs).patch(entity);
registry.storage<int>("custom"_hs).erase(entity);
ASSERT_EQ(listener.counter, 3);
}
TEST(Registry, SignalsOnEntity) {
entt::registry registry;
listener listener;
registry.on_construct<entt::entity>().connect<&listener::incr>(listener);
entt::entity entity = registry.create();
entt::entity other = registry.create();
ASSERT_EQ(listener.counter, 2);
ASSERT_EQ(listener.last, other);
registry.destroy(other);
registry.destroy(entity);
ASSERT_EQ(listener.counter, 2);
ASSERT_EQ(listener.last, other);
registry.on_construct<entt::entity>().disconnect(&listener);
other = registry.create();
entity = registry.create();
ASSERT_EQ(listener.counter, 2);
ASSERT_NE(listener.last, entity);
ASSERT_NE(listener.last, other);
registry.on_update<entt::entity>().connect<&listener::decr>(listener);
registry.patch<entt::entity>(entity);
ASSERT_EQ(listener.counter, 1);
ASSERT_EQ(listener.last, entity);
registry.on_update<entt::entity>().disconnect(&listener);
registry.patch<entt::entity>(other);
ASSERT_EQ(listener.counter, 1);
ASSERT_NE(listener.last, other);
registry.on_destroy<entt::entity>().connect<&listener::decr>(listener);
registry.destroy(entity);
ASSERT_EQ(listener.counter, 0);
ASSERT_EQ(listener.last, entity);
registry.on_destroy<entt::entity>().disconnect(&listener);
registry.destroy(other);
ASSERT_EQ(listener.counter, 0);
ASSERT_NE(listener.last, other);
}
TEST(Registry, SignalWhenDestroying) { TEST(Registry, SignalWhenDestroying) {
entt::registry registry; entt::registry registry;
const auto entity = registry.create(); const auto entity = registry.create();
@ -1576,6 +1657,17 @@ TEST(Registry, Erase) {
ASSERT_EQ(registry.storage<char>().size(), 0u); ASSERT_EQ(registry.storage<char>().size(), 0u);
ASSERT_EQ(registry.storage<double>().size(), 1u); ASSERT_EQ(registry.storage<double>().size(), 1u);
registry.insert<int>(std::begin(entities) + 1, std::end(entities) - 1u);
registry.insert<char>(std::begin(entities) + 1, std::end(entities) - 1u);
ASSERT_EQ(registry.storage<int>().size(), 1u);
ASSERT_EQ(registry.storage<char>().size(), 1u);
registry.erase<int, char>(iview.begin(), iview.end());
ASSERT_EQ(registry.storage<int>().size(), 0u);
ASSERT_EQ(registry.storage<char>().size(), 0u);
registry.insert<int>(std::begin(entities), std::end(entities)); registry.insert<int>(std::begin(entities), std::end(entities));
registry.insert<char>(std::begin(entities), std::end(entities)); registry.insert<char>(std::begin(entities), std::end(entities));
@ -1643,6 +1735,39 @@ TEST(Registry, StableErase) {
ASSERT_EQ(registry.storage<double>().size(), 1u); ASSERT_EQ(registry.storage<double>().size(), 1u);
} }
TEST(Registry, EraseIf) {
using namespace entt::literals;
entt::registry registry;
const auto entity = registry.create();
registry.emplace<int>(entity);
registry.storage<int>("other"_hs).emplace(entity);
registry.emplace<char>(entity);
ASSERT_TRUE(registry.storage<int>().contains(entity));
ASSERT_TRUE(registry.storage<int>("other"_hs).contains(entity));
ASSERT_TRUE(registry.storage<char>().contains(entity));
registry.erase_if(entity, [](auto &&...) { return false; });
ASSERT_TRUE(registry.storage<int>().contains(entity));
ASSERT_TRUE(registry.storage<int>("other"_hs).contains(entity));
ASSERT_TRUE(registry.storage<char>().contains(entity));
registry.erase_if(entity, [](entt::id_type id, auto &&...) { return id == "other"_hs; });
ASSERT_TRUE(registry.storage<int>().contains(entity));
ASSERT_FALSE(registry.storage<int>("other"_hs).contains(entity));
ASSERT_TRUE(registry.storage<char>().contains(entity));
registry.erase_if(entity, [](auto, const auto &storage) { return storage.type() == entt::type_id<char>(); });
ASSERT_TRUE(registry.storage<int>().contains(entity));
ASSERT_FALSE(registry.storage<int>("other"_hs).contains(entity));
ASSERT_FALSE(registry.storage<char>().contains(entity));
}
TEST(Registry, Remove) { TEST(Registry, Remove) {
entt::registry registry; entt::registry registry;
const auto iview = registry.view<int>(); const auto iview = registry.view<int>();
@ -1689,6 +1814,18 @@ TEST(Registry, Remove) {
ASSERT_EQ(registry.storage<char>().size(), 0u); ASSERT_EQ(registry.storage<char>().size(), 0u);
ASSERT_EQ(registry.storage<double>().size(), 1u); ASSERT_EQ(registry.storage<double>().size(), 1u);
registry.insert<int>(std::begin(entities) + 1, std::end(entities) - 1u);
registry.insert<char>(std::begin(entities) + 1, std::end(entities) - 1u);
ASSERT_EQ(registry.storage<int>().size(), 1u);
ASSERT_EQ(registry.storage<char>().size(), 1u);
registry.remove<int, char>(iview.begin(), iview.end());
registry.remove<int, char>(iview.begin(), iview.end());
ASSERT_EQ(registry.storage<int>().size(), 0u);
ASSERT_EQ(registry.storage<char>().size(), 0u);
registry.insert<int>(std::begin(entities), std::end(entities)); registry.insert<int>(std::begin(entities), std::end(entities));
registry.insert<char>(std::begin(entities), std::end(entities)); registry.insert<char>(std::begin(entities), std::end(entities));
@ -1791,7 +1928,7 @@ TEST(Registry, NonOwningGroupInterleaved) {
registry.emplace<int>(entity); registry.emplace<int>(entity);
registry.emplace<char>(entity); registry.emplace<char>(entity);
const auto group = registry.group<>(entt::get<int, char>); const auto group = registry.group(entt::get<int, char>);
entity = registry.create(); entity = registry.create();
registry.emplace<int>(entity); registry.emplace<int>(entity);
@ -1845,7 +1982,7 @@ TEST(Registry, PartialOwningGroupInterleaved) {
TEST(Registry, NonOwningGroupSortInterleaved) { TEST(Registry, NonOwningGroupSortInterleaved) {
entt::registry registry; entt::registry registry;
const auto group = registry.group<>(entt::get<int, char>); const auto group = registry.group(entt::get<int, char>);
const auto e0 = registry.create(); const auto e0 = registry.create();
registry.emplace<int>(e0, 0); registry.emplace<int>(e0, 0);
@ -2009,7 +2146,7 @@ TEST(Registry, ScramblingPoolsIsAllowed) {
// thanks to @andranik3949 for pointing out this missing test // thanks to @andranik3949 for pointing out this missing test
registry.view<const int>().each([](const auto entity, const auto &value) { registry.view<const int>().each([](const auto entity, const auto &value) {
ASSERT_EQ(entt::to_integral(entity), value); ASSERT_EQ(static_cast<int>(entt::to_integral(entity)), value);
}); });
} }
@ -2021,7 +2158,7 @@ TEST(Registry, RuntimePools) {
const auto entity = registry.create(); const auto entity = registry.create();
static_assert(std::is_same_v<decltype(registry.storage<empty_type>()), entt::storage_type_t<empty_type> &>); static_assert(std::is_same_v<decltype(registry.storage<empty_type>()), entt::storage_type_t<empty_type> &>);
static_assert(std::is_same_v<decltype(std::as_const(registry).storage<empty_type>()), const entt::storage_type_t<empty_type> &>); static_assert(std::is_same_v<decltype(std::as_const(registry).storage<empty_type>()), const entt::storage_type_t<empty_type> *>);
static_assert(std::is_same_v<decltype(registry.storage("other"_hs)), entt::storage_type_t<empty_type>::base_type *>); static_assert(std::is_same_v<decltype(registry.storage("other"_hs)), entt::storage_type_t<empty_type>::base_type *>);
static_assert(std::is_same_v<decltype(std::as_const(registry).storage("other"_hs)), const entt::storage_type_t<empty_type>::base_type *>); static_assert(std::is_same_v<decltype(std::as_const(registry).storage("other"_hs)), const entt::storage_type_t<empty_type>::base_type *>);
@ -2030,7 +2167,7 @@ TEST(Registry, RuntimePools) {
ASSERT_EQ(std::as_const(registry).storage("rehto"_hs), nullptr); ASSERT_EQ(std::as_const(registry).storage("rehto"_hs), nullptr);
ASSERT_EQ(&registry.storage<empty_type>("other"_hs), &storage); ASSERT_EQ(&registry.storage<empty_type>("other"_hs), &storage);
ASSERT_NE(&std::as_const(registry).storage<empty_type>(), &storage); ASSERT_NE(std::as_const(registry).storage<empty_type>(), &storage);
ASSERT_FALSE(registry.any_of<empty_type>(entity)); ASSERT_FALSE(registry.any_of<empty_type>(entity));
ASSERT_FALSE(storage.contains(entity)); ASSERT_FALSE(storage.contains(entity));
@ -2070,6 +2207,7 @@ TEST(Registry, Storage) {
entt::registry registry; entt::registry registry;
const auto entity = registry.create(); const auto entity = registry.create();
auto &storage = registry.storage<int>("int"_hs); auto &storage = registry.storage<int>("int"_hs);
storage.emplace(entity); storage.emplace(entity);
@ -2149,8 +2287,7 @@ TEST(Registry, RegistryStorageIterator) {
TEST(Registry, RegistryStorageIteratorConversion) { TEST(Registry, RegistryStorageIteratorConversion) {
entt::registry registry; entt::registry registry;
const auto entity = registry.create(); registry.storage<int>();
registry.emplace<int>(entity);
auto proxy = registry.storage(); auto proxy = registry.storage();
auto cproxy = std::as_const(registry).storage(); auto cproxy = std::as_const(registry).storage();
@ -2176,6 +2313,22 @@ TEST(Registry, RegistryStorageIteratorConversion) {
ASSERT_NE(++cit, it); ASSERT_NE(++cit, it);
} }
TEST(Registry, VoidType) {
using namespace entt::literals;
entt::registry registry;
const auto entity = registry.create();
auto &storage = registry.storage<void>("custom"_hs);
storage.emplace(entity);
ASSERT_TRUE(registry.storage<void>().empty());
ASSERT_FALSE(registry.storage<void>("custom"_hs).empty());
ASSERT_TRUE(registry.storage<void>("custom"_hs).contains(entity));
ASSERT_EQ(registry.storage<void>().type(), entt::type_id<void>());
ASSERT_EQ(registry.storage<void>("custom"_hs).type(), entt::type_id<void>());
}
TEST(Registry, NoEtoType) { TEST(Registry, NoEtoType) {
entt::registry registry; entt::registry registry;
const auto entity = registry.create(); const auto entity = registry.create();

View File

@ -1,5 +1,6 @@
#include <iterator> #include <iterator>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <entt/entity/mixin.hpp>
#include <entt/entity/registry.hpp> #include <entt/entity/registry.hpp>
#include <entt/entity/storage.hpp> #include <entt/entity/storage.hpp>
#include "../common/throwing_allocator.hpp" #include "../common/throwing_allocator.hpp"
@ -30,9 +31,20 @@ void listener(counter &counter, Registry &, typename Registry::entity_type) {
++counter.value; ++counter.value;
} }
TEST(SighStorageMixin, GenericType) { struct empty_each_tag final {};
template<>
struct entt::basic_storage<empty_each_tag, entt::entity, std::allocator<empty_each_tag>>: entt::basic_storage<void, entt::entity, std::allocator<void>> {
basic_storage(const std::allocator<empty_each_tag> &) {}
[[nodiscard]] iterable each() noexcept {
return {internal::extended_storage_iterator{base_type::end()}, internal::extended_storage_iterator{base_type::end()}};
}
};
TEST(SighMixin, GenericType) {
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
entt::sigh_storage_mixin<entt::storage<int>> pool; entt::sigh_mixin<entt::storage<int>> pool;
entt::sparse_set &base = pool; entt::sparse_set &base = pool;
entt::registry registry; entt::registry registry;
@ -40,10 +52,21 @@ TEST(SighStorageMixin, GenericType) {
counter on_destroy{}; counter on_destroy{};
pool.bind(entt::forward_as_any(registry)); pool.bind(entt::forward_as_any(registry));
ASSERT_TRUE(pool.empty());
pool.insert(entities, entities + 1u);
pool.erase(entities[0u]);
ASSERT_TRUE(pool.empty());
ASSERT_EQ(on_construct.value, 0);
ASSERT_EQ(on_destroy.value, 0);
pool.on_construct().connect<&listener<entt::registry>>(on_construct); pool.on_construct().connect<&listener<entt::registry>>(on_construct);
pool.on_destroy().connect<&listener<entt::registry>>(on_destroy); pool.on_destroy().connect<&listener<entt::registry>>(on_destroy);
ASSERT_NE(base.emplace(entities[0u]), base.end()); ASSERT_NE(base.push(entities[0u]), base.end());
pool.emplace(entities[1u]); pool.emplace(entities[1u]);
@ -61,7 +84,7 @@ TEST(SighStorageMixin, GenericType) {
ASSERT_EQ(on_destroy.value, 2); ASSERT_EQ(on_destroy.value, 2);
ASSERT_TRUE(pool.empty()); 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[0u]), 0);
ASSERT_EQ(pool.get(entities[1u]), 0); ASSERT_EQ(pool.get(entities[1u]), 0);
@ -95,9 +118,9 @@ TEST(SighStorageMixin, GenericType) {
ASSERT_TRUE(pool.empty()); ASSERT_TRUE(pool.empty());
} }
TEST(SighStorageMixin, StableType) { TEST(SighMixin, StableType) {
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
entt::sigh_storage_mixin<entt::storage<stable_type>> pool; entt::sigh_mixin<entt::storage<stable_type>> pool;
entt::sparse_set &base = pool; entt::sparse_set &base = pool;
entt::registry registry; entt::registry registry;
@ -108,7 +131,7 @@ TEST(SighStorageMixin, StableType) {
pool.on_construct().connect<&listener<entt::registry>>(on_construct); pool.on_construct().connect<&listener<entt::registry>>(on_construct);
pool.on_destroy().connect<&listener<entt::registry>>(on_destroy); pool.on_destroy().connect<&listener<entt::registry>>(on_destroy);
ASSERT_NE(base.emplace(entities[0u]), base.end()); ASSERT_NE(base.push(entities[0u]), base.end());
pool.emplace(entities[1u]); pool.emplace(entities[1u]);
@ -126,7 +149,7 @@ TEST(SighStorageMixin, StableType) {
ASSERT_EQ(on_destroy.value, 2); ASSERT_EQ(on_destroy.value, 2);
ASSERT_FALSE(pool.empty()); 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[0u]).value, 0);
ASSERT_EQ(pool.get(entities[1u]).value, 0); ASSERT_EQ(pool.get(entities[1u]).value, 0);
@ -160,9 +183,9 @@ TEST(SighStorageMixin, StableType) {
ASSERT_FALSE(pool.empty()); ASSERT_FALSE(pool.empty());
} }
TEST(SighStorageMixin, EmptyType) { TEST(SighMixin, EmptyType) {
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
entt::sigh_storage_mixin<entt::storage<empty_type>> pool; entt::sigh_mixin<entt::storage<empty_type>> pool;
entt::sparse_set &base = pool; entt::sparse_set &base = pool;
entt::registry registry; entt::registry registry;
@ -173,7 +196,7 @@ TEST(SighStorageMixin, EmptyType) {
pool.on_construct().connect<&listener<entt::registry>>(on_construct); pool.on_construct().connect<&listener<entt::registry>>(on_construct);
pool.on_destroy().connect<&listener<entt::registry>>(on_destroy); pool.on_destroy().connect<&listener<entt::registry>>(on_destroy);
ASSERT_NE(base.emplace(entities[0u]), base.end()); ASSERT_NE(base.push(entities[0u]), base.end());
pool.emplace(entities[1u]); pool.emplace(entities[1u]);
@ -191,7 +214,7 @@ TEST(SighStorageMixin, EmptyType) {
ASSERT_EQ(on_destroy.value, 2); ASSERT_EQ(on_destroy.value, 2);
ASSERT_TRUE(pool.empty()); 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[0u]));
ASSERT_TRUE(pool.contains(entities[1u])); ASSERT_TRUE(pool.contains(entities[1u]));
@ -225,9 +248,9 @@ TEST(SighStorageMixin, EmptyType) {
ASSERT_TRUE(pool.empty()); ASSERT_TRUE(pool.empty());
} }
TEST(SighStorageMixin, NonDefaultConstructibleType) { TEST(SighMixin, NonDefaultConstructibleType) {
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
entt::sigh_storage_mixin<entt::storage<non_default_constructible>> pool; entt::sigh_mixin<entt::storage<non_default_constructible>> pool;
entt::sparse_set &base = pool; entt::sparse_set &base = pool;
entt::registry registry; entt::registry registry;
@ -238,12 +261,12 @@ TEST(SighStorageMixin, NonDefaultConstructibleType) {
pool.on_construct().connect<&listener<entt::registry>>(on_construct); pool.on_construct().connect<&listener<entt::registry>>(on_construct);
pool.on_destroy().connect<&listener<entt::registry>>(on_destroy); pool.on_destroy().connect<&listener<entt::registry>>(on_destroy);
ASSERT_EQ(base.emplace(entities[0u]), base.end()); ASSERT_EQ(base.push(entities[0u]), base.end());
pool.emplace(entities[1u], 3); pool.emplace(entities[1u], 3);
ASSERT_EQ(pool.size(), 1u); ASSERT_EQ(pool.size(), 1u);
ASSERT_EQ(on_construct.value, 2); ASSERT_EQ(on_construct.value, 1);
ASSERT_EQ(on_destroy.value, 0); ASSERT_EQ(on_destroy.value, 0);
ASSERT_FALSE(pool.empty()); ASSERT_FALSE(pool.empty());
@ -253,11 +276,11 @@ TEST(SighStorageMixin, NonDefaultConstructibleType) {
base.erase(entities[1u]); base.erase(entities[1u]);
ASSERT_EQ(pool.size(), 0u); ASSERT_EQ(pool.size(), 0u);
ASSERT_EQ(on_construct.value, 2); ASSERT_EQ(on_construct.value, 1);
ASSERT_EQ(on_destroy.value, 1); ASSERT_EQ(on_destroy.value, 1);
ASSERT_TRUE(pool.empty()); 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[0u]));
ASSERT_FALSE(pool.contains(entities[1u])); ASSERT_FALSE(pool.contains(entities[1u]));
@ -266,7 +289,7 @@ TEST(SighStorageMixin, NonDefaultConstructibleType) {
pool.insert(std::begin(entities), std::end(entities), 3); pool.insert(std::begin(entities), std::end(entities), 3);
ASSERT_EQ(pool.size(), 2u); ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(on_construct.value, 6); ASSERT_EQ(on_construct.value, 3);
ASSERT_EQ(on_destroy.value, 1); ASSERT_EQ(on_destroy.value, 1);
ASSERT_FALSE(pool.empty()); ASSERT_FALSE(pool.empty());
@ -276,13 +299,13 @@ TEST(SighStorageMixin, NonDefaultConstructibleType) {
pool.erase(std::begin(entities), std::end(entities)); pool.erase(std::begin(entities), std::end(entities));
ASSERT_EQ(pool.size(), 0u); ASSERT_EQ(pool.size(), 0u);
ASSERT_EQ(on_construct.value, 6); ASSERT_EQ(on_construct.value, 3);
ASSERT_EQ(on_destroy.value, 3); ASSERT_EQ(on_destroy.value, 3);
ASSERT_TRUE(pool.empty()); ASSERT_TRUE(pool.empty());
} }
TEST(SighStorageMixin, VoidType) { TEST(SighMixin, VoidType) {
entt::sigh_storage_mixin<entt::storage<void>> pool; entt::sigh_mixin<entt::storage<void>> pool;
entt::registry registry; entt::registry registry;
counter on_construct{}; counter on_construct{};
@ -297,7 +320,7 @@ TEST(SighStorageMixin, VoidType) {
ASSERT_EQ(pool.type(), entt::type_id<void>()); ASSERT_EQ(pool.type(), entt::type_id<void>());
ASSERT_TRUE(pool.contains(entt::entity{99})); ASSERT_TRUE(pool.contains(entt::entity{99}));
entt::sigh_storage_mixin<entt::storage<void>> other{std::move(pool)}; entt::sigh_mixin<entt::storage<void>> other{std::move(pool)};
ASSERT_FALSE(pool.contains(entt::entity{99})); ASSERT_FALSE(pool.contains(entt::entity{99}));
ASSERT_TRUE(other.contains(entt::entity{99})); ASSERT_TRUE(other.contains(entt::entity{99}));
@ -313,8 +336,8 @@ TEST(SighStorageMixin, VoidType) {
ASSERT_EQ(on_destroy.value, 1); ASSERT_EQ(on_destroy.value, 1);
} }
TEST(SighStorageMixin, Move) { TEST(SighMixin, Move) {
entt::sigh_storage_mixin<entt::storage<int>> pool; entt::sigh_mixin<entt::storage<int>> pool;
entt::registry registry; entt::registry registry;
counter on_construct{}; counter on_construct{};
@ -330,7 +353,7 @@ TEST(SighStorageMixin, Move) {
ASSERT_TRUE(std::is_move_assignable_v<decltype(pool)>); ASSERT_TRUE(std::is_move_assignable_v<decltype(pool)>);
ASSERT_EQ(pool.type(), entt::type_id<int>()); ASSERT_EQ(pool.type(), entt::type_id<int>());
entt::sigh_storage_mixin<entt::storage<int>> other{std::move(pool)}; entt::sigh_mixin<entt::storage<int>> other{std::move(pool)};
ASSERT_TRUE(pool.empty()); ASSERT_TRUE(pool.empty());
ASSERT_FALSE(other.empty()); ASSERT_FALSE(other.empty());
@ -347,7 +370,7 @@ TEST(SighStorageMixin, Move) {
ASSERT_EQ(pool.get(entt::entity{3}), 3); ASSERT_EQ(pool.get(entt::entity{3}), 3);
ASSERT_EQ(other.at(0u), static_cast<entt::entity>(entt::null)); ASSERT_EQ(other.at(0u), static_cast<entt::entity>(entt::null));
other = entt::sigh_storage_mixin<entt::storage<int>>{}; other = entt::sigh_mixin<entt::storage<int>>{};
other.bind(entt::forward_as_any(registry)); other.bind(entt::forward_as_any(registry));
other.emplace(entt::entity{42}, 42); other.emplace(entt::entity{42}, 42);
@ -365,9 +388,9 @@ TEST(SighStorageMixin, Move) {
ASSERT_EQ(on_destroy.value, 1); ASSERT_EQ(on_destroy.value, 1);
} }
TEST(SighStorageMixin, Swap) { TEST(SighMixin, Swap) {
entt::sigh_storage_mixin<entt::storage<int>> pool; entt::sigh_mixin<entt::storage<int>> pool;
entt::sigh_storage_mixin<entt::storage<int>> other; entt::sigh_mixin<entt::storage<int>> other;
entt::registry registry; entt::registry registry;
counter on_construct{}; counter on_construct{};
@ -411,7 +434,100 @@ TEST(SighStorageMixin, Swap) {
ASSERT_EQ(on_destroy.value, 3); ASSERT_EQ(on_destroy.value, 3);
} }
TEST(SighStorageMixin, CustomAllocator) { TEST(SighMixin, EmptyEachStorage) {
entt::sigh_mixin<entt::storage<empty_each_tag>> pool;
entt::registry registry;
counter on_destroy{};
pool.bind(entt::forward_as_any(registry));
pool.on_destroy().connect<&listener<entt::registry>>(on_destroy);
ASSERT_TRUE(pool.empty());
ASSERT_EQ(on_destroy.value, 0);
pool.push(entt::entity{42});
ASSERT_FALSE(pool.empty());
ASSERT_EQ(on_destroy.value, 0);
ASSERT_NE(pool.begin(), pool.end());
ASSERT_EQ(pool.each().begin(), pool.each().end());
ASSERT_EQ(on_destroy.value, 0);
pool.clear();
ASSERT_EQ(pool.begin(), pool.end());
ASSERT_EQ(pool.each().begin(), pool.each().end());
// no signal at all because of the (fake) empty iterable
ASSERT_EQ(on_destroy.value, 0);
}
TEST(SighMixin, StorageEntity) {
using traits_type = entt::entt_traits<entt::entity>;
entt::sigh_mixin<entt::storage<entt::entity>> pool;
entt::registry registry;
counter on_construct{};
counter on_destroy{};
pool.bind(entt::forward_as_any(registry));
pool.on_construct().connect<&listener<entt::registry>>(on_construct);
pool.on_destroy().connect<&listener<entt::registry>>(on_destroy);
pool.push(entt::entity{1});
ASSERT_EQ(on_construct.value, 1);
ASSERT_EQ(on_destroy.value, 0);
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.in_use(), 1u);
pool.erase(entt::entity{1});
ASSERT_EQ(on_construct.value, 1);
ASSERT_EQ(on_destroy.value, 1);
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.in_use(), 0u);
pool.push(traits_type::construct(0, 2));
pool.push(traits_type::construct(2, 1));
ASSERT_TRUE(pool.contains(traits_type::construct(0, 2)));
ASSERT_TRUE(pool.contains(traits_type::construct(1, 1)));
ASSERT_TRUE(pool.contains(traits_type::construct(2, 1)));
ASSERT_EQ(on_construct.value, 3);
ASSERT_EQ(on_destroy.value, 1);
ASSERT_EQ(pool.size(), 3u);
ASSERT_EQ(pool.in_use(), 2u);
pool.clear();
ASSERT_EQ(pool.size(), 0u);
ASSERT_EQ(pool.in_use(), 0u);
ASSERT_EQ(on_construct.value, 3);
ASSERT_EQ(on_destroy.value, 3);
pool.emplace();
pool.emplace(entt::entity{0});
entt::entity entities[1u]{};
pool.insert(entities, entities + 1u);
ASSERT_EQ(on_construct.value, 6);
ASSERT_EQ(on_destroy.value, 3);
ASSERT_EQ(pool.size(), 3u);
ASSERT_EQ(pool.in_use(), 3u);
pool.clear();
ASSERT_EQ(pool.size(), 0u);
ASSERT_EQ(pool.in_use(), 0u);
}
TEST(SighMixin, CustomAllocator) {
auto test = [](auto pool, auto alloc) { auto test = [](auto pool, auto alloc) {
using registry_type = typename decltype(pool)::registry_type; using registry_type = typename decltype(pool)::registry_type;
registry_type registry; registry_type registry;
@ -466,12 +582,12 @@ TEST(SighStorageMixin, CustomAllocator) {
test::throwing_allocator<entt::entity> allocator{}; test::throwing_allocator<entt::entity> allocator{};
test(entt::sigh_storage_mixin<entt::basic_storage<int, entt::entity, test::throwing_allocator<int>>>{allocator}, allocator); test(entt::sigh_mixin<entt::basic_storage<int, entt::entity, test::throwing_allocator<int>>>{allocator}, allocator);
test(entt::sigh_storage_mixin<entt::basic_storage<std::true_type, entt::entity, test::throwing_allocator<std::true_type>>>{allocator}, allocator); test(entt::sigh_mixin<entt::basic_storage<std::true_type, entt::entity, test::throwing_allocator<std::true_type>>>{allocator}, allocator);
test(entt::sigh_storage_mixin<entt::basic_storage<stable_type, entt::entity, test::throwing_allocator<stable_type>>>{allocator}, allocator); test(entt::sigh_mixin<entt::basic_storage<stable_type, entt::entity, test::throwing_allocator<stable_type>>>{allocator}, allocator);
} }
TEST(SighStorageMixin, ThrowingAllocator) { TEST(SighMixin, ThrowingAllocator) {
auto test = [](auto pool) { auto test = [](auto pool) {
using pool_allocator_type = typename decltype(pool)::allocator_type; using pool_allocator_type = typename decltype(pool)::allocator_type;
using value_type = typename decltype(pool)::value_type; using value_type = typename decltype(pool)::value_type;
@ -511,7 +627,7 @@ TEST(SighStorageMixin, ThrowingAllocator) {
test::throwing_allocator<entt::entity>::trigger_on_allocate = true; test::throwing_allocator<entt::entity>::trigger_on_allocate = true;
ASSERT_THROW(base.emplace(entt::entity{0}), test::throwing_allocator<entt::entity>::exception_type); ASSERT_THROW(base.push(entt::entity{0}), test::throwing_allocator<entt::entity>::exception_type);
ASSERT_FALSE(base.contains(entt::entity{0})); ASSERT_FALSE(base.contains(entt::entity{0}));
ASSERT_TRUE(base.empty()); ASSERT_TRUE(base.empty());
@ -543,12 +659,12 @@ TEST(SighStorageMixin, ThrowingAllocator) {
ASSERT_EQ(on_destroy.value, 1); ASSERT_EQ(on_destroy.value, 1);
}; };
test(entt::sigh_storage_mixin<entt::basic_storage<int, entt::entity, test::throwing_allocator<int>>>{}); test(entt::sigh_mixin<entt::basic_storage<int, entt::entity, test::throwing_allocator<int>>>{});
test(entt::sigh_storage_mixin<entt::basic_storage<stable_type, entt::entity, test::throwing_allocator<stable_type>>>{}); test(entt::sigh_mixin<entt::basic_storage<stable_type, entt::entity, test::throwing_allocator<stable_type>>>{});
} }
TEST(SighStorageMixin, ThrowingComponent) { TEST(SighMixin, ThrowingComponent) {
entt::sigh_storage_mixin<entt::storage<test::throwing_type>> pool; entt::sigh_mixin<entt::storage<test::throwing_type>> pool;
using registry_type = typename decltype(pool)::registry_type; using registry_type = typename decltype(pool)::registry_type;
registry_type registry; registry_type registry;

File diff suppressed because it is too large Load Diff

View File

@ -26,6 +26,7 @@ TEST(SparseSet, Functionalities) {
ASSERT_EQ(set.capacity(), 42u); ASSERT_EQ(set.capacity(), 42u);
ASSERT_TRUE(set.empty()); ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u); ASSERT_EQ(set.size(), 0u);
ASSERT_TRUE(set.contiguous());
ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end()); ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end());
ASSERT_EQ(set.begin(), set.end()); ASSERT_EQ(set.begin(), set.end());
ASSERT_FALSE(set.contains(entt::entity{0})); ASSERT_FALSE(set.contains(entt::entity{0}));
@ -36,10 +37,11 @@ TEST(SparseSet, Functionalities) {
ASSERT_EQ(set.capacity(), 42u); ASSERT_EQ(set.capacity(), 42u);
ASSERT_TRUE(set.empty()); ASSERT_TRUE(set.empty());
set.emplace(entt::entity{42}); set.push(entt::entity{42});
ASSERT_FALSE(set.empty()); ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 1u); ASSERT_EQ(set.size(), 1u);
ASSERT_TRUE(set.contiguous());
ASSERT_NE(std::as_const(set).begin(), std::as_const(set).end()); ASSERT_NE(std::as_const(set).begin(), std::as_const(set).end());
ASSERT_NE(set.begin(), set.end()); ASSERT_NE(set.begin(), set.end());
ASSERT_FALSE(set.contains(entt::entity{0})); 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(0u), entt::entity{42});
ASSERT_EQ(set.at(1u), static_cast<entt::entity>(entt::null)); ASSERT_EQ(set.at(1u), static_cast<entt::entity>(entt::null));
ASSERT_EQ(set[0u], entt::entity{42}); 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}); set.erase(entt::entity{42});
ASSERT_TRUE(set.empty()); ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u); ASSERT_EQ(set.size(), 0u);
ASSERT_TRUE(set.contiguous());
ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end()); ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end());
ASSERT_EQ(set.begin(), set.end()); ASSERT_EQ(set.begin(), set.end());
ASSERT_FALSE(set.contains(entt::entity{0})); ASSERT_FALSE(set.contains(entt::entity{0}));
@ -61,7 +64,7 @@ TEST(SparseSet, Functionalities) {
ASSERT_EQ(set.at(0u), static_cast<entt::entity>(entt::null)); ASSERT_EQ(set.at(0u), static_cast<entt::entity>(entt::null));
ASSERT_EQ(set.at(1u), static_cast<entt::entity>(entt::null)); ASSERT_EQ(set.at(1u), static_cast<entt::entity>(entt::null));
set.emplace(entt::entity{42}); set.push(entt::entity{42});
ASSERT_FALSE(set.empty()); ASSERT_FALSE(set.empty());
ASSERT_EQ(set.index(entt::entity{42}), 0u); ASSERT_EQ(set.index(entt::entity{42}), 0u);
@ -73,6 +76,7 @@ TEST(SparseSet, Functionalities) {
ASSERT_TRUE(set.empty()); ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u); ASSERT_EQ(set.size(), 0u);
ASSERT_TRUE(set.contiguous());
ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end()); ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end());
ASSERT_EQ(set.begin(), set.end()); ASSERT_EQ(set.begin(), set.end());
ASSERT_FALSE(set.contains(entt::entity{0})); ASSERT_FALSE(set.contains(entt::entity{0}));
@ -86,11 +90,11 @@ TEST(SparseSet, Contains) {
entt::sparse_set set{entt::deletion_policy::in_place}; entt::sparse_set set{entt::deletion_policy::in_place};
set.emplace(entt::entity{0}); set.push(entt::entity{0});
set.emplace(entt::entity{3}); set.push(entt::entity{3});
set.emplace(entt::entity{42}); set.push(entt::entity{42});
set.emplace(entt::entity{99}); set.push(entt::entity{99});
set.emplace(traits_type::construct(1, 5)); set.push(traits_type::construct(1, 5));
ASSERT_FALSE(set.contains(entt::null)); ASSERT_FALSE(set.contains(entt::null));
ASSERT_FALSE(set.contains(entt::tombstone)); 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(0, 0)), traits_type::to_version(entt::tombstone));
ASSERT_EQ(set.current(traits_type::construct(3, 3)), 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.push(traits_type::construct(0, 0));
set.emplace(traits_type::construct(3, 3)); 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(0, 0)), traits_type::to_version(entt::tombstone));
ASSERT_NE(set.current(traits_type::construct(3, 3)), 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{}; entt::sparse_set set{};
set.emplace(traits_type::construct(0, 0)); set.push(traits_type::construct(0, 0));
set.emplace(traits_type::construct(3, 3)); set.push(traits_type::construct(3, 3));
ASSERT_EQ(set.index(traits_type::construct(0, 0)), 0u); ASSERT_EQ(set.index(traits_type::construct(0, 0)), 0u);
ASSERT_EQ(set.index(traits_type::construct(3, 3)), 1u); ASSERT_EQ(set.index(traits_type::construct(3, 3)), 1u);
@ -175,7 +179,7 @@ ENTT_DEBUG_TEST(SparseSetDeathTest, Index) {
TEST(SparseSet, Move) { TEST(SparseSet, Move) {
entt::sparse_set set; entt::sparse_set set;
set.emplace(entt::entity{42}); set.push(entt::entity{42});
ASSERT_TRUE(std::is_move_constructible_v<decltype(set)>); ASSERT_TRUE(std::is_move_constructible_v<decltype(set)>);
ASSERT_TRUE(std::is_move_assignable_v<decltype(set)>); ASSERT_TRUE(std::is_move_assignable_v<decltype(set)>);
@ -195,7 +199,7 @@ TEST(SparseSet, Move) {
ASSERT_EQ(other.at(0u), static_cast<entt::entity>(entt::null)); ASSERT_EQ(other.at(0u), static_cast<entt::entity>(entt::null));
other = entt::sparse_set{}; other = entt::sparse_set{};
other.emplace(entt::entity{3}); other.push(entt::entity{3});
other = std::move(set); other = std::move(set);
ASSERT_TRUE(set.empty()); ASSERT_TRUE(set.empty());
@ -208,10 +212,10 @@ TEST(SparseSet, Swap) {
entt::sparse_set set; entt::sparse_set set;
entt::sparse_set other{entt::deletion_policy::in_place}; 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.push(entt::entity{9});
other.emplace(entt::entity{3}); other.push(entt::entity{3});
other.erase(entt::entity{9}); other.erase(entt::entity{9});
ASSERT_EQ(set.size(), 1u); ASSERT_EQ(set.size(), 1u);
@ -233,12 +237,12 @@ TEST(SparseSet, Pagination) {
ASSERT_EQ(set.extent(), 0u); 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_EQ(set.extent(), traits_type::page_size);
ASSERT_TRUE(set.contains(entt::entity{traits_type::page_size - 1u})); 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_EQ(set.extent(), 2 * traits_type::page_size);
ASSERT_TRUE(set.contains(entt::entity{traits_type::page_size - 1u})); 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); 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::sparse_set set{entt::deletion_policy::in_place};
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
ASSERT_TRUE(set.empty()); ASSERT_TRUE(set.empty());
ASSERT_NE(set.emplace(entities[0u]), set.end()); ASSERT_NE(set.push(entities[0u]), set.end());
set.erase(entities[0u]); set.erase(entities[0u]);
ASSERT_NE(set.emplace(entities[1u]), set.end()); ASSERT_NE(set.push(entities[1u]), set.end());
ASSERT_NE(set.emplace(entities[0u]), set.end()); ASSERT_NE(set.push(entities[0u]), set.end());
ASSERT_EQ(set.at(0u), entities[1u]); ASSERT_EQ(set.at(0u), entities[1u]);
ASSERT_EQ(set.at(1u), entities[0u]); ASSERT_EQ(set.at(1u), entities[0u]);
@ -282,8 +286,8 @@ TEST(SparseSet, Emplace) {
set.erase(std::begin(entities), std::end(entities)); set.erase(std::begin(entities), std::end(entities));
ASSERT_NE(set.emplace(entities[1u]), set.end()); ASSERT_NE(set.push(entities[1u]), set.end());
ASSERT_NE(set.emplace(entities[0u]), set.end()); ASSERT_NE(set.push(entities[0u]), set.end());
ASSERT_EQ(set.at(0u), entities[1u]); ASSERT_EQ(set.at(0u), entities[1u]);
ASSERT_EQ(set.at(1u), entities[0u]); ASSERT_EQ(set.at(1u), entities[0u]);
@ -291,72 +295,16 @@ TEST(SparseSet, Emplace) {
ASSERT_EQ(set.index(entities[1u]), 0u); ASSERT_EQ(set.index(entities[1u]), 0u);
} }
ENTT_DEBUG_TEST(SparseSetDeathTest, Emplace) { TEST(SparseSet, PushRange) {
entt::sparse_set set{entt::deletion_policy::in_place};
set.emplace(entt::entity{42});
ASSERT_DEATH(set.emplace(entt::entity{42}), "");
}
TEST(SparseSet, EmplaceOutOfBounds) {
using traits_type = entt::entt_traits<entt::entity>;
entt::sparse_set set{entt::deletion_policy::in_place};
entt::entity entities[2u]{entt::entity{0}, entt::entity{traits_type::page_size}};
ASSERT_NE(set.emplace(entities[0u]), set.end());
ASSERT_EQ(set.extent(), traits_type::page_size);
ASSERT_EQ(set.index(entities[0u]), 0u);
set.erase(entities[0u]);
ASSERT_NE(set.emplace(entities[1u]), set.end());
ASSERT_EQ(set.extent(), 2u * traits_type::page_size);
ASSERT_EQ(set.index(entities[1u]), 0u);
}
TEST(SparseSet, Bump) {
using traits_type = entt::entt_traits<entt::entity>;
entt::sparse_set set;
entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, traits_type::construct(9, 3)};
set.insert(std::begin(entities), std::end(entities));
ASSERT_EQ(set.current(entities[0u]), 0u);
ASSERT_EQ(set.current(entities[1u]), 0u);
ASSERT_EQ(set.current(entities[2u]), 3u);
set.bump(entities[0u]);
set.bump(traits_type::construct(traits_type::to_entity(entities[1u]), 1));
set.bump(traits_type::construct(traits_type::to_entity(entities[2u]), 0));
ASSERT_EQ(set.current(entities[0u]), 0u);
ASSERT_EQ(set.current(entities[1u]), 1u);
ASSERT_EQ(set.current(entities[2u]), 0u);
}
ENTT_DEBUG_TEST(SparseSetDeathTest, Bump) {
using traits_type = entt::entt_traits<entt::entity>;
entt::sparse_set set{entt::deletion_policy::in_place};
set.emplace(entt::entity{3});
ASSERT_DEATH(set.bump(entt::null), "");
ASSERT_DEATH(set.bump(entt::tombstone), "");
ASSERT_DEATH(set.bump(entt::entity{42}), "");
ASSERT_DEATH(set.bump(traits_type::construct(traits_type::to_entity(entt::entity{3}), traits_type::to_version(entt::tombstone))), "");
}
TEST(SparseSet, Insert) {
entt::sparse_set set{entt::deletion_policy::in_place}; entt::sparse_set set{entt::deletion_policy::in_place};
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; 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_EQ(set.push(std::end(entities), std::end(entities)), set.end());
ASSERT_NE(set.insert(std::begin(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[0u]));
ASSERT_TRUE(set.contains(entities[1u])); ASSERT_TRUE(set.contains(entities[1u]));
@ -378,7 +326,7 @@ TEST(SparseSet, Insert) {
set.erase(std::begin(entities), std::end(entities)); 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.size(), 6u);
ASSERT_EQ(set.at(4u), entities[1u]); ASSERT_EQ(set.at(4u), entities[1u]);
@ -387,6 +335,64 @@ TEST(SparseSet, Insert) {
ASSERT_EQ(set.index(entities[1u]), 4u); ASSERT_EQ(set.index(entities[1u]), 4u);
} }
ENTT_DEBUG_TEST(SparseSetDeathTest, Push) {
entt::sparse_set set{entt::deletion_policy::in_place};
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
set.push(entt::entity{42});
ASSERT_DEATH(set.push(entt::entity{42}), "");
ASSERT_DEATH(set.push(std::begin(entities), std::end(entities)), "");
}
TEST(SparseSet, PushOutOfBounds) {
using traits_type = entt::entt_traits<entt::entity>;
entt::sparse_set set{entt::deletion_policy::in_place};
entt::entity entities[2u]{entt::entity{0}, entt::entity{traits_type::page_size}};
ASSERT_NE(set.push(entities[0u]), set.end());
ASSERT_EQ(set.extent(), traits_type::page_size);
ASSERT_EQ(set.index(entities[0u]), 0u);
set.erase(entities[0u]);
ASSERT_NE(set.push(entities[1u]), set.end());
ASSERT_EQ(set.extent(), 2u * traits_type::page_size);
ASSERT_EQ(set.index(entities[1u]), 0u);
}
TEST(SparseSet, Bump) {
using traits_type = entt::entt_traits<entt::entity>;
entt::sparse_set set;
entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, traits_type::construct(9, 3)};
set.push(std::begin(entities), std::end(entities));
ASSERT_EQ(set.current(entities[0u]), 0u);
ASSERT_EQ(set.current(entities[1u]), 0u);
ASSERT_EQ(set.current(entities[2u]), 3u);
ASSERT_EQ(set.bump(entities[0u]), 0u);
ASSERT_EQ(set.bump(traits_type::construct(traits_type::to_entity(entities[1u]), 1)), 1u);
ASSERT_EQ(set.bump(traits_type::construct(traits_type::to_entity(entities[2u]), 0)), 0u);
ASSERT_EQ(set.current(entities[0u]), 0u);
ASSERT_EQ(set.current(entities[1u]), 1u);
ASSERT_EQ(set.current(entities[2u]), 0u);
}
ENTT_DEBUG_TEST(SparseSetDeathTest, Bump) {
using traits_type = entt::entt_traits<entt::entity>;
entt::sparse_set set{entt::deletion_policy::in_place};
set.push(entt::entity{3});
ASSERT_DEATH(set.bump(entt::null), "");
ASSERT_DEATH(set.bump(entt::tombstone), "");
ASSERT_DEATH(set.bump(entt::entity{42}), "");
ASSERT_DEATH(set.bump(traits_type::construct(traits_type::to_entity(entt::entity{3}), traits_type::to_version(entt::tombstone))), "");
}
TEST(SparseSet, Erase) { TEST(SparseSet, Erase) {
using traits_type = entt::entt_traits<entt::entity>; using traits_type = entt::entt_traits<entt::entity>;
@ -396,7 +402,7 @@ TEST(SparseSet, Erase) {
ASSERT_EQ(set.policy(), entt::deletion_policy::swap_and_pop); ASSERT_EQ(set.policy(), entt::deletion_policy::swap_and_pop);
ASSERT_TRUE(set.empty()); 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()); set.erase(set.begin(), set.end());
ASSERT_TRUE(set.empty()); 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[1u]), traits_type::to_version(entt::tombstone));
ASSERT_EQ(set.current(entities[2u]), 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); set.erase(entities, entities + 2u);
ASSERT_FALSE(set.empty()); ASSERT_FALSE(set.empty());
@ -418,7 +424,7 @@ TEST(SparseSet, Erase) {
ASSERT_TRUE(set.empty()); ASSERT_TRUE(set.empty());
ASSERT_EQ(set.current(entities[2u]), 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));
std::swap(entities[1u], entities[2u]); std::swap(entities[1u], entities[2u]);
set.erase(entities, entities + 2u); set.erase(entities, entities + 2u);
@ -442,8 +448,8 @@ TEST(SparseSet, CrossErase) {
entt::sparse_set other; entt::sparse_set other;
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
set.insert(std::begin(entities), std::end(entities)); set.push(std::begin(entities), std::end(entities));
other.emplace(entities[1u]); other.push(entities[1u]);
set.erase(other.begin(), other.end()); set.erase(other.begin(), other.end());
ASSERT_TRUE(set.contains(entities[0u])); ASSERT_TRUE(set.contains(entities[0u]));
@ -461,9 +467,9 @@ TEST(SparseSet, StableErase) {
ASSERT_TRUE(set.empty()); ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u); ASSERT_EQ(set.size(), 0u);
set.emplace(entities[0u]); set.push(entities[0u]);
set.emplace(entities[1u]); set.push(entities[1u]);
set.emplace(entities[2u]); set.push(entities[2u]);
set.erase(set.begin(), set.end()); set.erase(set.begin(), set.end());
@ -476,9 +482,9 @@ TEST(SparseSet, StableErase) {
ASSERT_TRUE(set.at(1u) == entt::tombstone); ASSERT_TRUE(set.at(1u) == entt::tombstone);
ASSERT_TRUE(set.at(2u) == entt::tombstone); ASSERT_TRUE(set.at(2u) == entt::tombstone);
set.emplace(entities[0u]); set.push(entities[0u]);
set.emplace(entities[1u]); set.push(entities[1u]);
set.emplace(entities[2u]); set.push(entities[2u]);
set.erase(entities, entities + 2u); set.erase(entities, entities + 2u);
@ -497,9 +503,9 @@ TEST(SparseSet, StableErase) {
ASSERT_EQ(set.size(), 3u); ASSERT_EQ(set.size(), 3u);
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
set.emplace(entities[0u]); set.push(entities[0u]);
set.emplace(entities[1u]); set.push(entities[1u]);
set.emplace(entities[2u]); set.push(entities[2u]);
std::swap(entities[1u], entities[2u]); std::swap(entities[1u], entities[2u]);
set.erase(entities, entities + 2u); set.erase(entities, entities + 2u);
@ -527,9 +533,9 @@ TEST(SparseSet, StableErase) {
ASSERT_EQ(set.size(), 0u); ASSERT_EQ(set.size(), 0u);
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
set.emplace(entities[0u]); set.push(entities[0u]);
set.emplace(entities[1u]); set.push(entities[1u]);
set.emplace(entities[2u]); set.push(entities[2u]);
set.erase(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_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
ASSERT_TRUE(*set.begin() == entt::tombstone); ASSERT_TRUE(*set.begin() == entt::tombstone);
set.emplace(entities[0u]); set.push(entities[0u]);
ASSERT_EQ(*++set.begin(), entities[0u]); ASSERT_EQ(*++set.begin(), entities[0u]);
set.emplace(entities[1u]); set.push(entities[1u]);
set.emplace(entities[2u]); set.push(entities[2u]);
set.emplace(entt::entity{0}); set.push(entt::entity{0});
ASSERT_EQ(set.size(), 4u); ASSERT_EQ(set.size(), 4u);
ASSERT_EQ(*set.begin(), entt::entity{0}); ASSERT_EQ(*set.begin(), entt::entity{0});
@ -580,8 +586,8 @@ TEST(SparseSet, CrossStableErase) {
entt::sparse_set other{entt::deletion_policy::in_place}; entt::sparse_set other{entt::deletion_policy::in_place};
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
set.insert(std::begin(entities), std::end(entities)); set.push(std::begin(entities), std::end(entities));
other.emplace(entities[1u]); other.push(entities[1u]);
set.erase(other.begin(), other.end()); set.erase(other.begin(), other.end());
ASSERT_TRUE(set.contains(entities[0u])); ASSERT_TRUE(set.contains(entities[0u]));
@ -599,11 +605,11 @@ TEST(SparseSet, Remove) {
ASSERT_TRUE(set.empty()); ASSERT_TRUE(set.empty());
ASSERT_EQ(set.remove(std::begin(entities), std::end(entities)), 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_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_EQ(set.remove(set.begin(), set.end()), 3u);
ASSERT_TRUE(set.empty()); 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[1u]), traits_type::to_version(entt::tombstone));
ASSERT_EQ(set.current(entities[2u]), 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_EQ(set.remove(entities, entities + 2u), 2u);
ASSERT_FALSE(set.empty()); 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.current(entities[2u]), traits_type::to_version(entities[2u]));
ASSERT_EQ(*set.begin(), entities[2u]); ASSERT_EQ(*set.begin(), entities[2u]);
ASSERT_EQ(set.remove(entities[2u]), 1u); ASSERT_TRUE(set.remove(entities[2u]));
ASSERT_EQ(set.remove(entities[2u]), 0u); ASSERT_FALSE(set.remove(entities[2u]));
ASSERT_TRUE(set.empty()); ASSERT_TRUE(set.empty());
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); 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.remove(std::begin(entities), std::end(entities)), 2u);
ASSERT_EQ(set.current(entities[0u]), traits_type::to_version(entt::tombstone)); 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_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
ASSERT_TRUE(set.empty()); 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]); std::swap(entities[1u], entities[2u]);
ASSERT_EQ(set.remove(entities, entities + 2u), 2u); ASSERT_EQ(set.remove(entities, entities + 2u), 2u);
@ -641,9 +647,9 @@ TEST(SparseSet, Remove) {
ASSERT_FALSE(set.empty()); ASSERT_FALSE(set.empty());
ASSERT_EQ(*set.begin(), entities[2u]); ASSERT_EQ(*set.begin(), entities[2u]);
ASSERT_EQ(set.remove(traits_type::construct(9, 0)), 0u); ASSERT_FALSE(set.remove(traits_type::construct(9, 0)));
ASSERT_EQ(set.remove(entt::tombstone), 0u); ASSERT_FALSE(set.remove(entt::tombstone));
ASSERT_EQ(set.remove(entt::null), 0u); ASSERT_FALSE(set.remove(entt::null));
} }
TEST(SparseSet, CrossRemove) { TEST(SparseSet, CrossRemove) {
@ -651,8 +657,8 @@ TEST(SparseSet, CrossRemove) {
entt::sparse_set other; entt::sparse_set other;
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
set.insert(std::begin(entities), std::end(entities)); set.push(std::begin(entities), std::end(entities));
other.emplace(entities[1u]); other.push(entities[1u]);
set.remove(other.begin(), other.end()); set.remove(other.begin(), other.end());
ASSERT_TRUE(set.contains(entities[0u])); ASSERT_TRUE(set.contains(entities[0u]));
@ -671,14 +677,14 @@ TEST(SparseSet, StableRemove) {
ASSERT_EQ(set.size(), 0u); ASSERT_EQ(set.size(), 0u);
ASSERT_EQ(set.remove(std::begin(entities), std::end(entities)), 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_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u); ASSERT_EQ(set.size(), 0u);
set.emplace(entities[0u]); set.push(entities[0u]);
set.emplace(entities[1u]); set.push(entities[1u]);
set.emplace(entities[2u]); set.push(entities[2u]);
ASSERT_EQ(set.remove(set.begin(), set.end()), 3u); ASSERT_EQ(set.remove(set.begin(), set.end()), 3u);
ASSERT_EQ(set.remove(set.begin(), set.end()), 0u); 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(1u) == entt::tombstone);
ASSERT_TRUE(set.at(2u) == entt::tombstone); ASSERT_TRUE(set.at(2u) == entt::tombstone);
set.emplace(entities[0u]); set.push(entities[0u]);
set.emplace(entities[1u]); set.push(entities[1u]);
set.emplace(entities[2u]); set.push(entities[2u]);
ASSERT_EQ(set.remove(entities, entities + 2u), 2u); ASSERT_EQ(set.remove(entities, entities + 2u), 2u);
ASSERT_EQ(set.remove(entities, entities + 2u), 0u); 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(0u) == entt::tombstone);
ASSERT_TRUE(set.at(1u) == entt::tombstone); ASSERT_TRUE(set.at(1u) == entt::tombstone);
ASSERT_EQ(set.remove(entities[2u]), 1u); ASSERT_TRUE(set.remove(entities[2u]));
ASSERT_EQ(set.remove(entities[2u]), 0u); ASSERT_FALSE(set.remove(entities[2u]));
ASSERT_FALSE(set.empty()); ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 3u); ASSERT_EQ(set.size(), 3u);
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
set.emplace(entities[0u]); set.push(entities[0u]);
set.emplace(entities[1u]); set.push(entities[1u]);
set.emplace(entities[2u]); set.push(entities[2u]);
std::swap(entities[1u], entities[2u]); std::swap(entities[1u], entities[2u]);
@ -747,19 +753,19 @@ TEST(SparseSet, StableRemove) {
ASSERT_EQ(set.size(), 0u); ASSERT_EQ(set.size(), 0u);
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
set.emplace(entities[0u]); set.push(entities[0u]);
set.emplace(entities[1u]); set.push(entities[1u]);
set.emplace(entities[2u]); set.push(entities[2u]);
ASSERT_EQ(set.remove(entities[2u]), 1u); ASSERT_TRUE(set.remove(entities[2u]));
ASSERT_EQ(set.remove(entities[2u]), 0u); ASSERT_FALSE(set.remove(entities[2u]));
ASSERT_NE(set.current(entities[0u]), traits_type::to_version(entt::tombstone)); 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_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.current(entities[2u]), traits_type::to_version(entt::tombstone));
ASSERT_EQ(set.remove(entities[0u]), 1u); ASSERT_TRUE(set.remove(entities[0u]));
ASSERT_EQ(set.remove(entities[1u]), 1u); ASSERT_TRUE(set.remove(entities[1u]));
ASSERT_EQ(set.remove(entities, entities + 2u), 0u); ASSERT_EQ(set.remove(entities, entities + 2u), 0u);
ASSERT_EQ(set.size(), 3u); 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_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
ASSERT_TRUE(*set.begin() == entt::tombstone); ASSERT_TRUE(*set.begin() == entt::tombstone);
set.emplace(entities[0u]); set.push(entities[0u]);
ASSERT_EQ(*++set.begin(), entities[0u]); ASSERT_EQ(*++set.begin(), entities[0u]);
set.emplace(entities[1u]); set.push(entities[1u]);
set.emplace(entities[2u]); set.push(entities[2u]);
set.emplace(entt::entity{0}); set.push(entt::entity{0});
ASSERT_EQ(set.size(), 4u); ASSERT_EQ(set.size(), 4u);
ASSERT_EQ(*set.begin(), entt::entity{0}); 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[1u]), traits_type::to_version(entt::tombstone));
ASSERT_NE(set.current(entities[2u]), 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_FALSE(set.remove(traits_type::construct(9, 0)));
ASSERT_EQ(set.remove(entt::null), 0u); ASSERT_FALSE(set.remove(entt::null));
} }
TEST(SparseSet, CrossStableRemove) { TEST(SparseSet, CrossStableRemove) {
@ -795,8 +801,8 @@ TEST(SparseSet, CrossStableRemove) {
entt::sparse_set other{entt::deletion_policy::in_place}; entt::sparse_set other{entt::deletion_policy::in_place};
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
set.insert(std::begin(entities), std::end(entities)); set.push(std::begin(entities), std::end(entities));
other.emplace(entities[1u]); other.push(entities[1u]);
set.remove(other.begin(), other.end()); set.remove(other.begin(), other.end());
ASSERT_TRUE(set.contains(entities[0u])); ASSERT_TRUE(set.contains(entities[0u]));
@ -815,13 +821,13 @@ TEST(SparseSet, Compact) {
ASSERT_TRUE(set.empty()); ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u); ASSERT_EQ(set.size(), 0u);
set.emplace(entt::entity{0}); set.push(entt::entity{0});
set.compact(); set.compact();
ASSERT_FALSE(set.empty()); ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 1u); ASSERT_EQ(set.size(), 1u);
set.emplace(entt::entity{42}); set.push(entt::entity{42});
set.erase(entt::entity{0}); set.erase(entt::entity{0});
ASSERT_EQ(set.size(), 2u); ASSERT_EQ(set.size(), 2u);
@ -832,7 +838,7 @@ TEST(SparseSet, Compact) {
ASSERT_EQ(set.size(), 1u); ASSERT_EQ(set.size(), 1u);
ASSERT_EQ(set.index(entt::entity{42}), 0u); ASSERT_EQ(set.index(entt::entity{42}), 0u);
set.emplace(entt::entity{0}); set.push(entt::entity{0});
set.compact(); set.compact();
ASSERT_EQ(set.size(), 2u); ASSERT_EQ(set.size(), 2u);
@ -851,8 +857,8 @@ TEST(SparseSet, SwapEntity) {
entt::sparse_set set; entt::sparse_set set;
set.emplace(traits_type::construct(3, 5)); set.push(traits_type::construct(3, 5));
set.emplace(traits_type::construct(42, 99)); set.push(traits_type::construct(42, 99));
ASSERT_EQ(set.index(traits_type::construct(3, 5)), 0u); ASSERT_EQ(set.index(traits_type::construct(3, 5)), 0u);
ASSERT_EQ(set.index(traits_type::construct(42, 99)), 1u); ASSERT_EQ(set.index(traits_type::construct(42, 99)), 1u);
@ -878,9 +884,9 @@ ENTT_DEBUG_TEST(SparseSetDeathTest, SwapEntity) {
TEST(SparseSet, Clear) { TEST(SparseSet, Clear) {
entt::sparse_set set{entt::deletion_policy::in_place}; entt::sparse_set set{entt::deletion_policy::in_place};
set.emplace(entt::entity{3}); set.push(entt::entity{3});
set.emplace(entt::entity{42}); set.push(entt::entity{42});
set.emplace(entt::entity{9}); set.push(entt::entity{9});
set.erase(entt::entity{42}); set.erase(entt::entity{42});
ASSERT_FALSE(set.empty()); 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{3}), set.end());
ASSERT_EQ(set.find(entt::entity{9}), set.end()); ASSERT_EQ(set.find(entt::entity{9}), set.end());
set.emplace(entt::entity{3}); set.push(entt::entity{3});
set.emplace(entt::entity{42}); set.push(entt::entity{42});
set.emplace(entt::entity{9}); set.push(entt::entity{9});
ASSERT_FALSE(set.empty()); ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 3u); ASSERT_EQ(set.size(), 3u);
@ -913,6 +919,64 @@ TEST(SparseSet, Clear) {
ASSERT_EQ(set.find(entt::entity{9}), set.end()); 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) { TEST(SparseSet, Iterator) {
using iterator = typename entt::sparse_set::iterator; using iterator = typename entt::sparse_set::iterator;
@ -921,13 +985,20 @@ TEST(SparseSet, Iterator) {
static_assert(std::is_same_v<iterator::reference, const entt::entity &>); static_assert(std::is_same_v<iterator::reference, const entt::entity &>);
entt::sparse_set set; entt::sparse_set set;
set.emplace(entt::entity{3}); set.push(entt::entity{3});
iterator end{set.begin()}; iterator end{set.begin()};
iterator begin{}; iterator begin{};
ASSERT_EQ(end.data(), set.data());
ASSERT_EQ(begin.data(), nullptr);
begin = set.end(); begin = set.end();
std::swap(begin, end); std::swap(begin, end);
ASSERT_EQ(end.data(), set.data());
ASSERT_EQ(begin.data(), set.data());
ASSERT_EQ(begin, set.cbegin()); ASSERT_EQ(begin, set.cbegin());
ASSERT_EQ(end, set.cend()); ASSERT_EQ(end, set.cend());
ASSERT_NE(begin, end); ASSERT_NE(begin, end);
@ -967,7 +1038,7 @@ TEST(SparseSet, Iterator) {
ASSERT_EQ(begin.index(), 0); ASSERT_EQ(begin.index(), 0);
ASSERT_EQ(end.index(), -1); ASSERT_EQ(end.index(), -1);
set.emplace(entt::entity{42}); set.push(entt::entity{42});
begin = set.begin(); begin = set.begin();
ASSERT_EQ(begin.index(), 1); ASSERT_EQ(begin.index(), 1);
@ -985,7 +1056,7 @@ TEST(SparseSet, ReverseIterator) {
static_assert(std::is_same_v<reverse_iterator::reference, const entt::entity &>); static_assert(std::is_same_v<reverse_iterator::reference, const entt::entity &>);
entt::sparse_set set; entt::sparse_set set;
set.emplace(entt::entity{3}); set.push(entt::entity{3});
reverse_iterator end{set.rbegin()}; reverse_iterator end{set.rbegin()};
reverse_iterator begin{}; reverse_iterator begin{};
@ -1031,7 +1102,7 @@ TEST(SparseSet, ReverseIterator) {
ASSERT_EQ(begin.base().index(), -1); ASSERT_EQ(begin.base().index(), -1);
ASSERT_EQ(end.base().index(), 0); ASSERT_EQ(end.base().index(), 0);
set.emplace(entt::entity{42}); set.push(entt::entity{42});
end = set.rend(); end = set.rend();
ASSERT_EQ(begin.base().index(), -1); ASSERT_EQ(begin.base().index(), -1);
@ -1045,9 +1116,9 @@ TEST(SparseSet, Find) {
using traits_type = entt::entt_traits<entt::entity>; using traits_type = entt::entt_traits<entt::entity>;
entt::sparse_set set; entt::sparse_set set;
set.emplace(entt::entity{3}); set.push(entt::entity{3});
set.emplace(entt::entity{42}); set.push(entt::entity{42});
set.emplace(traits_type::construct(99, 1)); set.push(traits_type::construct(99, 1));
ASSERT_NE(set.find(entt::entity{3}), set.end()); ASSERT_NE(set.find(entt::entity{3}), set.end());
ASSERT_NE(set.find(entt::entity{42}), set.end()); ASSERT_NE(set.find(entt::entity{42}), set.end());
@ -1069,9 +1140,9 @@ TEST(SparseSet, Find) {
TEST(SparseSet, Data) { TEST(SparseSet, Data) {
entt::sparse_set set; entt::sparse_set set;
set.emplace(entt::entity{3}); set.push(entt::entity{3});
set.emplace(entt::entity{12}); set.push(entt::entity{12});
set.emplace(entt::entity{42}); set.push(entt::entity{42});
ASSERT_EQ(set.index(entt::entity{3}), 0u); ASSERT_EQ(set.index(entt::entity{3}), 0u);
ASSERT_EQ(set.index(entt::entity{12}), 1u); ASSERT_EQ(set.index(entt::entity{12}), 1u);
@ -1086,7 +1157,7 @@ TEST(SparseSet, SortOrdered) {
entt::sparse_set set; entt::sparse_set set;
entt::entity entities[5u]{entt::entity{42}, entt::entity{12}, entt::entity{9}, entt::entity{7}, entt::entity{3}}; 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{}); set.sort(std::less{});
ASSERT_TRUE(std::equal(std::rbegin(entities), std::rend(entities), set.begin(), set.end())); 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::sparse_set set;
entt::entity entities[5u]{entt::entity{3}, entt::entity{7}, entt::entity{9}, entt::entity{12}, entt::entity{42}}; 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{}); set.sort(std::less{});
ASSERT_TRUE(std::equal(std::begin(entities), std::end(entities), set.begin(), set.end())); 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::sparse_set set;
entt::entity entities[5u]{entt::entity{9}, entt::entity{7}, entt::entity{3}, entt::entity{12}, entt::entity{42}}; 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{}); set.sort(std::less{});
auto begin = set.begin(); auto begin = set.begin();
@ -1124,7 +1195,7 @@ TEST(SparseSet, SortRange) {
entt::sparse_set set{entt::deletion_policy::in_place}; 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}}; 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]); set.erase(entities[0u]);
ASSERT_EQ(set.size(), 5u); ASSERT_EQ(set.size(), 5u);
@ -1139,7 +1210,7 @@ TEST(SparseSet, SortRange) {
set.clear(); set.clear();
set.compact(); set.compact();
set.insert(std::begin(entities), std::end(entities)); set.push(std::begin(entities), std::end(entities));
set.sort_n(0u, std::less{}); set.sort_n(0u, std::less{});
ASSERT_TRUE(std::equal(std::rbegin(entities), std::rend(entities), set.begin(), set.end())); 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::sparse_set set{entt::deletion_policy::in_place};
entt::entity entity{42}; entt::entity entity{42};
set.emplace(entity); set.push(entity);
set.erase(entity); set.erase(entity);
ASSERT_DEATH(set.sort_n(0u, std::less{});, ""); ASSERT_DEATH(set.sort_n(0u, std::less{});, "");
@ -1179,11 +1250,11 @@ TEST(SparseSet, RespectDisjoint) {
entt::sparse_set rhs; entt::sparse_set rhs;
entt::entity lhs_entities[3u]{entt::entity{3}, entt::entity{12}, entt::entity{42}}; 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())); 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())); 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::sparse_set rhs;
entt::entity lhs_entities[3u]{entt::entity{3}, entt::entity{12}, entt::entity{42}}; 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}}; 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(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())); 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 begin = lhs.begin();
auto end = lhs.end(); auto end = lhs.end();
@ -1217,15 +1288,15 @@ TEST(SparseSet, RespectOrdered) {
entt::sparse_set rhs; entt::sparse_set rhs;
entt::entity lhs_entities[5u]{entt::entity{1}, entt::entity{2}, entt::entity{3}, entt::entity{4}, entt::entity{5}}; 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}}; 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(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())); 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())); 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::sparse_set rhs;
entt::entity lhs_entities[5u]{entt::entity{1}, entt::entity{2}, entt::entity{3}, entt::entity{4}, entt::entity{5}}; 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}}; 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(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())); 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 begin = rhs.begin();
auto end = rhs.end(); auto end = rhs.end();
@ -1262,15 +1333,15 @@ TEST(SparseSet, RespectUnordered) {
entt::sparse_set rhs; entt::sparse_set rhs;
entt::entity lhs_entities[5u]{entt::entity{1}, entt::entity{2}, entt::entity{3}, entt::entity{4}, entt::entity{5}}; 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}}; 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(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())); 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 begin = rhs.begin();
auto end = rhs.end(); auto end = rhs.end();
@ -1291,15 +1362,15 @@ TEST(SparseSet, RespectInvalid) {
entt::sparse_set rhs; entt::sparse_set rhs;
entt::entity lhs_entities[3u]{entt::entity{1}, entt::entity{2}, traits_type::construct(3, 1)}; 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)}; 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(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())); 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 begin = rhs.begin();
auto end = rhs.end(); auto end = rhs.end();
@ -1315,7 +1386,7 @@ TEST(SparseSet, RespectInvalid) {
TEST(SparseSet, CanModifyDuringIteration) { TEST(SparseSet, CanModifyDuringIteration) {
entt::sparse_set set; entt::sparse_set set;
set.emplace(entt::entity{0}); set.push(entt::entity{0});
ASSERT_EQ(set.capacity(), 1u); ASSERT_EQ(set.capacity(), 1u);
@ -1338,8 +1409,8 @@ TEST(SparseSet, CustomAllocator) {
ASSERT_EQ(set.capacity(), 1u); ASSERT_EQ(set.capacity(), 1u);
set.emplace(entt::entity{0}); set.push(entt::entity{0});
set.emplace(entt::entity{1}); set.push(entt::entity{1});
entt::basic_sparse_set<entt::entity, test::throwing_allocator<entt::entity>> other{std::move(set), allocator}; entt::basic_sparse_set<entt::entity, test::throwing_allocator<entt::entity>> other{std::move(set), allocator};
@ -1389,11 +1460,11 @@ TEST(SparseSet, ThrowingAllocator) {
test::throwing_allocator<entt::entity>::trigger_on_allocate = true; test::throwing_allocator<entt::entity>::trigger_on_allocate = true;
ASSERT_THROW(set.emplace(entt::entity{0}), test::throwing_allocator<entt::entity>::exception_type); ASSERT_THROW(set.push(entt::entity{0}), test::throwing_allocator<entt::entity>::exception_type);
ASSERT_EQ(set.extent(), traits_type::page_size); ASSERT_EQ(set.extent(), traits_type::page_size);
ASSERT_EQ(set.capacity(), 0u); ASSERT_EQ(set.capacity(), 0u);
set.emplace(entt::entity{0}); set.push(entt::entity{0});
test::throwing_allocator<entt::entity>::trigger_on_allocate = true; test::throwing_allocator<entt::entity>::trigger_on_allocate = true;
ASSERT_THROW(set.reserve(2u), test::throwing_allocator<entt::entity>::exception_type); ASSERT_THROW(set.reserve(2u), test::throwing_allocator<entt::entity>::exception_type);
@ -1403,7 +1474,7 @@ TEST(SparseSet, ThrowingAllocator) {
test::throwing_allocator<entt::entity>::trigger_on_allocate = true; test::throwing_allocator<entt::entity>::trigger_on_allocate = true;
ASSERT_THROW(set.emplace(entt::entity{1}), test::throwing_allocator<entt::entity>::exception_type); ASSERT_THROW(set.push(entt::entity{1}), test::throwing_allocator<entt::entity>::exception_type);
ASSERT_EQ(set.extent(), traits_type::page_size); ASSERT_EQ(set.extent(), traits_type::page_size);
ASSERT_TRUE(set.contains(entt::entity{0})); ASSERT_TRUE(set.contains(entt::entity{0}));
ASSERT_FALSE(set.contains(entt::entity{1})); 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}}; entt::entity entities[2u]{entt::entity{1}, entt::entity{traits_type::page_size}};
test::throwing_allocator<entt::entity>::trigger_after_allocate = true; test::throwing_allocator<entt::entity>::trigger_after_allocate = true;
ASSERT_THROW(set.insert(std::begin(entities), std::end(entities)), test::throwing_allocator<entt::entity>::exception_type); ASSERT_THROW(set.push(std::begin(entities), std::end(entities)), test::throwing_allocator<entt::entity>::exception_type);
ASSERT_EQ(set.extent(), 2 * traits_type::page_size); ASSERT_EQ(set.extent(), 2 * traits_type::page_size);
ASSERT_TRUE(set.contains(entt::entity{0})); ASSERT_TRUE(set.contains(entt::entity{0}));
ASSERT_TRUE(set.contains(entt::entity{1})); ASSERT_TRUE(set.contains(entt::entity{1}));
@ -1420,7 +1491,7 @@ TEST(SparseSet, ThrowingAllocator) {
ASSERT_EQ(set.capacity(), 2u); ASSERT_EQ(set.capacity(), 2u);
ASSERT_EQ(set.size(), 2u); ASSERT_EQ(set.size(), 2u);
set.emplace(entities[1u]); set.push(entities[1u]);
ASSERT_TRUE(set.contains(entt::entity{traits_type::page_size})); ASSERT_TRUE(set.contains(entt::entity{traits_type::page_size}));
} }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,628 @@
#include <type_traits>
#include <utility>
#include <gtest/gtest.h>
#include <entt/entity/storage.hpp>
#include "../common/config.h"
#include "../common/throwing_allocator.hpp"
TEST(StorageEntity, TypeAndPolicy) {
entt::storage<entt::entity> pool;
ASSERT_EQ(pool.type(), entt::type_id<entt::entity>());
ASSERT_EQ(pool.policy(), entt::deletion_policy::swap_and_pop);
}
TEST(StorageEntity, Functionalities) {
entt::entity entities[2u]{entt::entity{0}, entt::entity{1}};
entt::storage<entt::entity> pool;
ASSERT_TRUE(pool.empty());
ASSERT_EQ(pool.size(), 0u);
ASSERT_EQ(pool.in_use(), 0u);
ASSERT_EQ(*pool.push(entt::null), entities[0u]);
ASSERT_EQ(*pool.push(entt::tombstone), entities[1u]);
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.in_use(), 2u);
pool.in_use(1u);
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.in_use(), 1u);
ASSERT_NO_THROW(pool.get(entities[0u]));
ASSERT_EQ(pool.get_as_tuple(entities[0u]), std::tuple<>{});
pool.erase(entities[0u]);
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.in_use(), 0u);
}
ENTT_DEBUG_TEST(StorageEntityDeathTest, Get) {
entt::storage<entt::entity> pool;
pool.emplace(entt::entity{99});
ASSERT_DEATH(pool.get(entt::entity{3}), "");
ASSERT_DEATH([[maybe_unused]] auto tup = pool.get_as_tuple(entt::entity{3}), "");
ASSERT_NO_THROW(pool.get(entt::entity{99}));
ASSERT_NO_THROW([[maybe_unused]] auto tup = pool.get_as_tuple(entt::entity{99}));
pool.erase(entt::entity{99});
ASSERT_DEATH(pool.get(entt::entity{99}), "");
ASSERT_DEATH([[maybe_unused]] auto tup = pool.get_as_tuple(entt::entity{99}), "");
}
TEST(StorageEntity, Move) {
entt::storage<entt::entity> pool;
pool.push(entt::entity{1});
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.in_use(), 1u);
ASSERT_TRUE(std::is_move_constructible_v<decltype(pool)>);
ASSERT_TRUE(std::is_move_assignable_v<decltype(pool)>);
entt::storage<entt::entity> other{std::move(pool)};
ASSERT_EQ(pool.size(), 0u);
ASSERT_EQ(other.size(), 2u);
ASSERT_EQ(pool.in_use(), 0u);
ASSERT_EQ(other.in_use(), 1u);
ASSERT_EQ(pool.at(0u), static_cast<entt::entity>(entt::null));
ASSERT_EQ(other.at(0u), entt::entity{1});
pool = std::move(other);
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(other.size(), 0u);
ASSERT_EQ(pool.in_use(), 1u);
ASSERT_EQ(other.in_use(), 0u);
ASSERT_EQ(pool.at(0u), entt::entity{1});
ASSERT_EQ(other.at(0u), static_cast<entt::entity>(entt::null));
other = entt::storage<entt::entity>{};
other.push(entt::entity{3});
other = std::move(pool);
ASSERT_EQ(pool.size(), 0u);
ASSERT_EQ(other.size(), 2u);
ASSERT_EQ(pool.in_use(), 0u);
ASSERT_EQ(other.in_use(), 1u);
ASSERT_EQ(pool.at(0u), static_cast<entt::entity>(entt::null));
ASSERT_EQ(other.at(0u), entt::entity{1});
other.clear();
ASSERT_EQ(other.size(), 0u);
ASSERT_EQ(other.in_use(), 0u);
ASSERT_EQ(*other.push(entt::null), entt::entity{0});
}
TEST(StorageEntity, Swap) {
entt::storage<entt::entity> pool;
entt::storage<entt::entity> other;
pool.push(entt::entity{1});
other.push(entt::entity{2});
other.push(entt::entity{0});
other.erase(entt::entity{2});
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(other.size(), 3u);
ASSERT_EQ(pool.in_use(), 1u);
ASSERT_EQ(other.in_use(), 1u);
pool.swap(other);
ASSERT_EQ(pool.size(), 3u);
ASSERT_EQ(other.size(), 2u);
ASSERT_EQ(pool.in_use(), 1u);
ASSERT_EQ(other.in_use(), 1u);
ASSERT_EQ(pool.at(0u), entt::entity{0});
ASSERT_EQ(other.at(0u), entt::entity{1});
pool.clear();
other.clear();
ASSERT_EQ(pool.size(), 0u);
ASSERT_EQ(other.size(), 0u);
ASSERT_EQ(pool.in_use(), 0u);
ASSERT_EQ(other.in_use(), 0u);
ASSERT_EQ(*other.push(entt::null), entt::entity{0});
}
TEST(StorageEntity, Push) {
using traits_type = entt::entt_traits<entt::entity>;
entt::storage<entt::entity> pool;
ASSERT_EQ(*pool.push(entt::null), entt::entity{0});
ASSERT_EQ(*pool.push(entt::tombstone), entt::entity{1});
ASSERT_EQ(*pool.push(entt::entity{0}), entt::entity{2});
ASSERT_EQ(*pool.push(traits_type::construct(1, 1)), entt::entity{3});
ASSERT_EQ(*pool.push(traits_type::construct(5, 3)), traits_type::construct(5, 3));
ASSERT_LT(pool.index(entt::entity{0}), pool.in_use());
ASSERT_LT(pool.index(entt::entity{1}), pool.in_use());
ASSERT_LT(pool.index(entt::entity{2}), pool.in_use());
ASSERT_LT(pool.index(entt::entity{3}), pool.in_use());
ASSERT_GE(pool.index(entt::entity{4}), pool.in_use());
ASSERT_LT(pool.index(traits_type::construct(5, 3)), pool.in_use());
ASSERT_EQ(*pool.push(traits_type::construct(4, 42)), traits_type::construct(4, 42));
ASSERT_EQ(*pool.push(traits_type::construct(4, 43)), entt::entity{6});
entt::entity entities[2u]{entt::entity{1}, traits_type::construct(5, 3)};
pool.erase(entities, entities + 2u);
pool.erase(entt::entity{2});
ASSERT_EQ(pool.current(entities[0u]), 1);
ASSERT_EQ(pool.current(entities[1u]), 4);
ASSERT_EQ(pool.current(entt::entity{2}), 1);
ASSERT_LT(pool.index(entt::entity{0}), pool.in_use());
ASSERT_GE(pool.index(traits_type::construct(1, 1)), pool.in_use());
ASSERT_GE(pool.index(traits_type::construct(2, 1)), pool.in_use());
ASSERT_LT(pool.index(entt::entity{3}), pool.in_use());
ASSERT_LT(pool.index(traits_type::construct(4, 42)), pool.in_use());
ASSERT_GE(pool.index(traits_type::construct(5, 4)), pool.in_use());
ASSERT_EQ(*pool.push(entt::null), traits_type::construct(2, 1));
ASSERT_EQ(*pool.push(traits_type::construct(1, 3)), traits_type::construct(1, 3));
ASSERT_EQ(*pool.push(entt::null), traits_type::construct(5, 4));
ASSERT_EQ(*pool.push(entt::null), entt::entity{7});
}
TEST(StorageEntity, Emplace) {
using traits_type = entt::entt_traits<entt::entity>;
entt::storage<entt::entity> pool;
entt::entity entities[2u]{};
ASSERT_EQ(pool.emplace(), entt::entity{0});
ASSERT_EQ(pool.emplace(entt::null), entt::entity{1});
ASSERT_EQ(pool.emplace(entt::tombstone), entt::entity{2});
ASSERT_EQ(pool.emplace(entt::entity{0}), entt::entity{3});
ASSERT_EQ(pool.emplace(traits_type::construct(1, 1)), entt::entity{4});
ASSERT_EQ(pool.emplace(traits_type::construct(6, 3)), traits_type::construct(6, 3));
ASSERT_LT(pool.index(entt::entity{0}), pool.in_use());
ASSERT_LT(pool.index(entt::entity{1}), pool.in_use());
ASSERT_LT(pool.index(entt::entity{2}), pool.in_use());
ASSERT_LT(pool.index(entt::entity{3}), pool.in_use());
ASSERT_LT(pool.index(entt::entity{4}), pool.in_use());
ASSERT_GE(pool.index(entt::entity{5}), pool.in_use());
ASSERT_LT(pool.index(traits_type::construct(6, 3)), pool.in_use());
ASSERT_EQ(pool.emplace(traits_type::construct(5, 42)), traits_type::construct(5, 42));
ASSERT_EQ(pool.emplace(traits_type::construct(5, 43)), entt::entity{7});
pool.erase(entt::entity{2});
ASSERT_EQ(pool.emplace(), traits_type::construct(2, 1));
pool.erase(traits_type::construct(2, 1));
pool.insert(entities, entities + 2u);
ASSERT_EQ(entities[0u], traits_type::construct(2, 2));
ASSERT_EQ(entities[1u], entt::entity{8});
}
TEST(StorageEntity, Patch) {
entt::storage<entt::entity> pool;
const auto entity = pool.emplace();
int counter = 0;
auto callback = [&counter]() { ++counter; };
ASSERT_EQ(counter, 0);
pool.patch(entity);
pool.patch(entity, callback);
pool.patch(entity, callback, callback);
ASSERT_EQ(counter, 3);
}
ENTT_DEBUG_TEST(StorageEntityDeathTest, Patch) {
entt::storage<entt::entity> pool;
ASSERT_DEATH(pool.patch(entt::null), "");
}
TEST(StorageEntity, Insert) {
entt::storage<entt::entity> pool;
entt::entity entities[2u]{};
pool.insert(std::begin(entities), std::end(entities));
ASSERT_TRUE(pool.contains(entities[0u]));
ASSERT_TRUE(pool.contains(entities[1u]));
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.in_use(), 2u);
pool.erase(std::begin(entities), std::end(entities));
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.in_use(), 0u);
pool.insert(entities, entities + 1u);
ASSERT_TRUE(pool.contains(entities[0u]));
ASSERT_FALSE(pool.contains(entities[1u]));
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.in_use(), 1u);
}
TEST(StorageEntity, Pack) {
entt::storage<entt::entity> pool;
entt::entity entities[3u]{entt::entity{1}, entt::entity{3}, entt::entity{42}};
pool.push(entities, entities + 3u);
std::swap(entities[0u], entities[1u]);
const auto len = pool.pack(entities + 1u, entities + 3u);
auto it = pool.each().cbegin().base();
ASSERT_NE(it, pool.cbegin());
ASSERT_NE(it, pool.cend());
ASSERT_EQ(len, 2u);
ASSERT_NE(it + len, pool.cend());
ASSERT_EQ(it + len + 1u, pool.cend());
ASSERT_EQ(*it++, entities[1u]);
ASSERT_EQ(*it++, entities[2u]);
ASSERT_NE(it, pool.cend());
ASSERT_EQ(*it++, entities[0u]);
ASSERT_EQ(it, pool.cend());
}
TEST(StorageEntity, Iterable) {
using iterator = typename entt::storage<entt::entity>::iterable::iterator;
static_assert(std::is_same_v<iterator::value_type, std::tuple<entt::entity>>);
static_assert(std::is_same_v<typename iterator::pointer, entt::input_iterator_pointer<std::tuple<entt::entity>>>);
static_assert(std::is_same_v<typename iterator::reference, typename iterator::value_type>);
entt::storage<entt::entity> pool;
pool.emplace(entt::entity{1});
pool.emplace(entt::entity{3});
pool.emplace(entt::entity{42});
pool.erase(entt::entity{3});
auto iterable = pool.each();
iterator end{iterable.begin()};
iterator begin{};
begin = iterable.end();
std::swap(begin, end);
ASSERT_EQ(begin, iterable.begin());
ASSERT_EQ(end, iterable.end());
ASSERT_NE(begin, end);
ASSERT_NE(begin.base(), pool.begin());
ASSERT_EQ(begin.base(), pool.end() - pool.in_use());
ASSERT_EQ(end.base(), pool.end());
ASSERT_EQ(std::get<0>(*begin.operator->().operator->()), entt::entity{42});
ASSERT_EQ(std::get<0>(*begin), entt::entity{42});
ASSERT_EQ(begin++, iterable.begin());
ASSERT_EQ(begin.base(), pool.end() - 1);
ASSERT_EQ(++begin, iterable.end());
ASSERT_EQ(begin.base(), pool.end());
for(auto [entity]: iterable) {
static_assert(std::is_same_v<decltype(entity), entt::entity>);
ASSERT_TRUE(entity != entt::entity{3});
}
}
TEST(StorageEntity, ConstIterable) {
using iterator = typename entt::storage<entt::entity>::const_iterable::iterator;
static_assert(std::is_same_v<iterator::value_type, std::tuple<entt::entity>>);
static_assert(std::is_same_v<typename iterator::pointer, entt::input_iterator_pointer<std::tuple<entt::entity>>>);
static_assert(std::is_same_v<typename iterator::reference, typename iterator::value_type>);
entt::storage<entt::entity> pool;
pool.emplace(entt::entity{1});
pool.emplace(entt::entity{3});
pool.emplace(entt::entity{42});
pool.erase(entt::entity{3});
auto iterable = std::as_const(pool).each();
iterator end{iterable.cbegin()};
iterator begin{};
begin = iterable.cend();
std::swap(begin, end);
ASSERT_EQ(begin, iterable.cbegin());
ASSERT_EQ(end, iterable.cend());
ASSERT_NE(begin, end);
ASSERT_NE(begin.base(), pool.begin());
ASSERT_EQ(begin.base(), pool.end() - pool.in_use());
ASSERT_EQ(end.base(), pool.end());
ASSERT_EQ(std::get<0>(*begin.operator->().operator->()), entt::entity{42});
ASSERT_EQ(std::get<0>(*begin), entt::entity{42});
ASSERT_EQ(begin++, iterable.begin());
ASSERT_EQ(begin.base(), pool.end() - 1);
ASSERT_EQ(++begin, iterable.end());
ASSERT_EQ(begin.base(), pool.end());
for(auto [entity]: iterable) {
static_assert(std::is_same_v<decltype(entity), entt::entity>);
ASSERT_TRUE(entity != entt::entity{3});
}
}
TEST(StorageEntity, IterableIteratorConversion) {
entt::storage<entt::entity> pool;
pool.emplace(entt::entity{3});
typename entt::storage<entt::entity>::iterable::iterator it = pool.each().begin();
typename entt::storage<entt::entity>::const_iterable::iterator cit = it;
static_assert(std::is_same_v<decltype(*it), std::tuple<entt::entity>>);
static_assert(std::is_same_v<decltype(*cit), std::tuple<entt::entity>>);
ASSERT_EQ(it, cit);
ASSERT_NE(++cit, it);
}
TEST(StorageEntity, IterableAlgorithmCompatibility) {
entt::storage<entt::entity> pool;
pool.emplace(entt::entity{3});
const auto iterable = pool.each();
const auto it = std::find_if(iterable.begin(), iterable.end(), [](auto args) { return std::get<0>(args) == entt::entity{3}; });
ASSERT_EQ(std::get<0>(*it), entt::entity{3});
}
TEST(StorageEntity, ReverseIterable) {
using iterator = typename entt::storage<entt::entity>::reverse_iterable::iterator;
static_assert(std::is_same_v<iterator::value_type, std::tuple<entt::entity>>);
static_assert(std::is_same_v<typename iterator::pointer, entt::input_iterator_pointer<std::tuple<entt::entity>>>);
static_assert(std::is_same_v<typename iterator::reference, typename iterator::value_type>);
entt::storage<entt::entity> pool;
pool.emplace(entt::entity{1});
pool.emplace(entt::entity{3});
pool.emplace(entt::entity{42});
pool.erase(entt::entity{3});
auto iterable = pool.reach();
iterator end{iterable.begin()};
iterator begin{};
begin = iterable.end();
std::swap(begin, end);
ASSERT_EQ(begin, iterable.begin());
ASSERT_EQ(end, iterable.end());
ASSERT_NE(begin, end);
ASSERT_EQ(begin.base(), pool.rbegin());
ASSERT_EQ(end.base(), pool.rbegin() + pool.in_use());
ASSERT_NE(end.base(), pool.rend());
ASSERT_EQ(std::get<0>(*begin.operator->().operator->()), entt::entity{1});
ASSERT_EQ(std::get<0>(*begin), entt::entity{1});
ASSERT_EQ(begin++, iterable.begin());
ASSERT_EQ(begin.base(), pool.rbegin() + 1);
ASSERT_EQ(++begin, iterable.end());
ASSERT_EQ(begin.base(), pool.rbegin() + 2);
for(auto [entity]: iterable) {
static_assert(std::is_same_v<decltype(entity), entt::entity>);
ASSERT_TRUE(entity != entt::entity{3});
}
}
TEST(StorageEntity, ReverseConstIterable) {
using iterator = typename entt::storage<entt::entity>::const_reverse_iterable::iterator;
static_assert(std::is_same_v<iterator::value_type, std::tuple<entt::entity>>);
static_assert(std::is_same_v<typename iterator::pointer, entt::input_iterator_pointer<std::tuple<entt::entity>>>);
static_assert(std::is_same_v<typename iterator::reference, typename iterator::value_type>);
entt::storage<entt::entity> pool;
pool.emplace(entt::entity{1});
pool.emplace(entt::entity{3});
pool.emplace(entt::entity{42});
pool.erase(entt::entity{3});
auto iterable = std::as_const(pool).reach();
iterator end{iterable.cbegin()};
iterator begin{};
begin = iterable.cend();
std::swap(begin, end);
ASSERT_EQ(begin, iterable.cbegin());
ASSERT_EQ(end, iterable.cend());
ASSERT_NE(begin, end);
ASSERT_EQ(begin.base(), pool.rbegin());
ASSERT_EQ(end.base(), pool.rbegin() + pool.in_use());
ASSERT_NE(end.base(), pool.rend());
ASSERT_EQ(std::get<0>(*begin.operator->().operator->()), entt::entity{1});
ASSERT_EQ(std::get<0>(*begin), entt::entity{1});
ASSERT_EQ(begin++, iterable.begin());
ASSERT_EQ(begin.base(), pool.rbegin() + 1);
ASSERT_EQ(++begin, iterable.end());
ASSERT_EQ(begin.base(), pool.rbegin() + 2);
for(auto [entity]: iterable) {
static_assert(std::is_same_v<decltype(entity), entt::entity>);
ASSERT_TRUE(entity != entt::entity{3});
}
}
TEST(StorageEntity, ReverseIterableIteratorConversion) {
entt::storage<entt::entity> pool;
pool.emplace(entt::entity{3});
typename entt::storage<entt::entity>::reverse_iterable::iterator it = pool.reach().begin();
typename entt::storage<entt::entity>::const_reverse_iterable::iterator cit = it;
static_assert(std::is_same_v<decltype(*it), std::tuple<entt::entity>>);
static_assert(std::is_same_v<decltype(*cit), std::tuple<entt::entity>>);
ASSERT_EQ(it, cit);
ASSERT_NE(++cit, it);
}
TEST(StorageEntity, ReverseIterableAlgorithmCompatibility) {
entt::storage<entt::entity> pool;
pool.emplace(entt::entity{3});
const auto iterable = pool.reach();
const auto it = std::find_if(iterable.begin(), iterable.end(), [](auto args) { return std::get<0>(args) == entt::entity{3}; });
ASSERT_EQ(std::get<0>(*it), entt::entity{3});
}
TEST(StorageEntity, SwapElements) {
entt::storage<entt::entity> pool;
pool.push(entt::entity{0});
pool.push(entt::entity{1});
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.in_use(), 2u);
ASSERT_TRUE(pool.contains(entt::entity{0}));
ASSERT_TRUE(pool.contains(entt::entity{1}));
ASSERT_EQ(*pool.begin(), entt::entity{1});
ASSERT_EQ(*++pool.begin(), entt::entity{0});
pool.swap_elements(entt::entity{0}, entt::entity{1});
ASSERT_EQ(*pool.begin(), entt::entity{0});
ASSERT_EQ(*++pool.begin(), entt::entity{1});
}
ENTT_DEBUG_TEST(StorageEntityDeathTest, SwapElements) {
entt::storage<entt::entity> pool;
pool.push(entt::entity{1});
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.in_use(), 1u);
ASSERT_TRUE(pool.contains(entt::entity{0}));
ASSERT_TRUE(pool.contains(entt::entity{1}));
ASSERT_DEATH(pool.swap_elements(entt::entity{0}, entt::entity{1}), "");
}
ENTT_DEBUG_TEST(StorageEntityDeathTest, InUse) {
entt::storage<entt::entity> pool;
pool.push(entt::entity{0});
pool.push(entt::entity{1});
ASSERT_DEATH(pool.in_use(3u), "");
}
ENTT_DEBUG_TEST(StorageEntityDeathTest, SortAndRespect) {
entt::storage<entt::entity> pool;
entt::storage<entt::entity> other;
pool.push(entt::entity{1});
pool.push(entt::entity{2});
pool.erase(entt::entity{2});
other.push(entt::entity{2});
ASSERT_DEATH(pool.sort([](auto...) { return true; }), "");
ASSERT_DEATH(pool.sort_as(other), "");
}
TEST(StorageEntity, CustomAllocator) {
test::throwing_allocator<entt::entity> allocator{};
entt::basic_storage<entt::entity, entt::entity, test::throwing_allocator<entt::entity>> pool{allocator};
pool.reserve(1u);
ASSERT_EQ(pool.size(), 0u);
ASSERT_EQ(pool.in_use(), 0u);
pool.push(entt::entity{0});
pool.push(entt::entity{1});
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.in_use(), 2u);
decltype(pool) other{std::move(pool), allocator};
ASSERT_TRUE(pool.empty());
ASSERT_FALSE(other.empty());
ASSERT_EQ(pool.size(), 0u);
ASSERT_EQ(other.size(), 2u);
ASSERT_EQ(pool.in_use(), 0u);
ASSERT_EQ(other.in_use(), 2u);
pool = std::move(other);
ASSERT_FALSE(pool.empty());
ASSERT_TRUE(other.empty());
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(other.size(), 0u);
ASSERT_EQ(pool.in_use(), 2u);
ASSERT_EQ(other.in_use(), 0u);
pool.swap(other);
pool = std::move(other);
ASSERT_FALSE(pool.empty());
ASSERT_TRUE(other.empty());
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(other.size(), 0u);
ASSERT_EQ(pool.in_use(), 2u);
ASSERT_EQ(other.in_use(), 0u);
pool.clear();
ASSERT_EQ(pool.size(), 0u);
ASSERT_EQ(pool.in_use(), 0u);
}

View File

@ -24,7 +24,6 @@ TEST(SingleComponentView, Functionalities) {
ASSERT_TRUE(view.empty()); ASSERT_TRUE(view.empty());
registry.emplace<int>(e1);
registry.emplace<char>(e1); registry.emplace<char>(e1);
ASSERT_NO_FATAL_FAILURE(view.begin()++); ASSERT_NO_FATAL_FAILURE(view.begin()++);
@ -70,6 +69,42 @@ TEST(SingleComponentView, Functionalities) {
ASSERT_FALSE(invalid); ASSERT_FALSE(invalid);
} }
TEST(SingleComponentView, InvalidView) {
entt::basic_view<entt::get_t<entt::storage<int>>, entt::exclude_t<>> view{};
ASSERT_FALSE(view);
ASSERT_EQ(view.size(), 0u);
ASSERT_TRUE(view.empty());
ASSERT_FALSE(view.contains(entt::null));
ASSERT_EQ(view.find(entt::null), view.end());
ASSERT_EQ(view.front(), static_cast<entt::entity>(entt::null));
ASSERT_EQ(view.back(), static_cast<entt::entity>(entt::null));
ASSERT_EQ(view.begin(), typename decltype(view)::iterator{});
ASSERT_EQ(view.begin(), view.end());
ASSERT_EQ(view.rbegin(), typename decltype(view)::reverse_iterator{});
ASSERT_EQ(view.rbegin(), view.rend());
auto iterable = view.each();
ASSERT_EQ(iterable.begin(), iterable.end());
ASSERT_EQ(iterable.cbegin(), iterable.cend());
view.each([](const int &) { FAIL(); });
view.each([](const entt::entity, const int &) { FAIL(); });
entt::storage<int> storage;
view.storage(storage);
ASSERT_TRUE(view);
view.each([](const int &) { FAIL(); });
view.each([](const entt::entity, const int &) { FAIL(); });
}
TEST(SingleComponentView, Constructors) { TEST(SingleComponentView, Constructors) {
entt::storage<int> storage{}; entt::storage<int> storage{};
@ -81,7 +116,8 @@ TEST(SingleComponentView, Constructors) {
ASSERT_TRUE(from_storage); ASSERT_TRUE(from_storage);
ASSERT_TRUE(from_tuple); 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) { TEST(SingleComponentView, Handle) {
@ -89,17 +125,19 @@ TEST(SingleComponentView, Handle) {
const auto entity = registry.create(); const auto entity = registry.create();
auto view = registry.view<int>(); auto view = registry.view<int>();
auto &&handle = view.handle(); auto *handle = view.handle();
ASSERT_TRUE(handle.empty()); ASSERT_NE(handle, nullptr);
ASSERT_FALSE(handle.contains(entity));
ASSERT_EQ(&handle, &view.handle()); ASSERT_TRUE(handle->empty());
ASSERT_FALSE(handle->contains(entity));
ASSERT_EQ(handle, view.handle());
registry.emplace<int>(entity); registry.emplace<int>(entity);
ASSERT_FALSE(handle.empty()); ASSERT_FALSE(handle->empty());
ASSERT_TRUE(handle.contains(entity)); ASSERT_TRUE(handle->contains(entity));
ASSERT_EQ(&handle, &view.handle()); ASSERT_EQ(handle, view.handle());
} }
TEST(SingleComponentView, LazyTypeFromConstRegistry) { TEST(SingleComponentView, LazyTypeFromConstRegistry) {
@ -111,8 +149,8 @@ TEST(SingleComponentView, LazyTypeFromConstRegistry) {
registry.emplace<empty_type>(entity); registry.emplace<empty_type>(entity);
registry.emplace<int>(entity); registry.emplace<int>(entity);
ASSERT_TRUE(cview); ASSERT_FALSE(cview);
ASSERT_TRUE(eview); ASSERT_FALSE(eview);
ASSERT_TRUE(cview.empty()); ASSERT_TRUE(cview.empty());
ASSERT_EQ(eview.size(), 0u); ASSERT_EQ(eview.size(), 0u);
@ -164,14 +202,6 @@ TEST(SingleComponentView, Contains) {
TEST(SingleComponentView, Empty) { TEST(SingleComponentView, Empty) {
entt::registry registry; entt::registry registry;
const auto e0 = registry.create();
registry.emplace<char>(e0);
registry.emplace<double>(e0);
const auto e1 = registry.create();
registry.emplace<char>(e1);
auto view = registry.view<int>(); auto view = registry.view<int>();
ASSERT_EQ(view.size(), 0u); ASSERT_EQ(view.size(), 0u);
@ -198,15 +228,17 @@ TEST(SingleComponentView, Each) {
auto it = iterable.begin(); auto it = iterable.begin();
ASSERT_EQ(it.base(), view.begin());
ASSERT_EQ((it++, ++it), iterable.end()); ASSERT_EQ((it++, ++it), iterable.end());
ASSERT_EQ(it.base(), view.end());
view.each([expected = 1u](auto entt, int &value) mutable { view.each([expected = 1](auto entt, int &value) mutable {
ASSERT_EQ(entt::to_integral(entt), expected); ASSERT_EQ(static_cast<int>(entt::to_integral(entt)), expected);
ASSERT_EQ(value, expected); ASSERT_EQ(value, expected);
--expected; --expected;
}); });
cview.each([expected = 1u](const int &value) mutable { cview.each([expected = 1](const int &value) mutable {
ASSERT_EQ(value, expected); ASSERT_EQ(value, expected);
--expected; --expected;
}); });
@ -219,7 +251,7 @@ TEST(SingleComponentView, Each) {
// do not use iterable, make sure an iterable view works when created from a temporary // do not use iterable, make sure an iterable view works when created from a temporary
for(auto [entt, value]: view.each()) { for(auto [entt, value]: view.each()) {
ASSERT_EQ(entt::to_integral(entt), value); ASSERT_EQ(static_cast<int>(entt::to_integral(entt)), value);
} }
} }
@ -334,15 +366,13 @@ TEST(SingleComponentView, Find) {
TEST(SingleComponentView, EmptyTypes) { TEST(SingleComponentView, EmptyTypes) {
entt::registry registry; entt::registry registry;
entt::entity entities[2u]; entt::entity entity = registry.create();
registry.create(std::begin(entities), std::end(entities)); registry.emplace<int>(entity, 0);
registry.emplace<int>(entities[0u], 0); registry.emplace<empty_type>(entity);
registry.emplace<empty_type>(entities[0u]);
registry.emplace<char>(entities[1u], 'c');
registry.view<empty_type>().each([&](const auto entt) { registry.view<empty_type>().each([&](const auto entt) {
ASSERT_EQ(entities[0u], entt); ASSERT_EQ(entity, entt);
}); });
registry.view<empty_type>().each([check = true]() mutable { registry.view<empty_type>().each([check = true]() mutable {
@ -352,11 +382,11 @@ TEST(SingleComponentView, EmptyTypes) {
for(auto [entt]: registry.view<empty_type>().each()) { for(auto [entt]: registry.view<empty_type>().each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>); static_assert(std::is_same_v<decltype(entt), entt::entity>);
ASSERT_EQ(entities[0u], entt); ASSERT_EQ(entity, entt);
} }
registry.view<int>().each([&](const auto entt, int) { registry.view<int>().each([&](const auto entt, int) {
ASSERT_EQ(entities[0u], entt); ASSERT_EQ(entity, entt);
}); });
registry.view<int>().each([check = true](int) mutable { registry.view<int>().each([check = true](int) mutable {
@ -367,7 +397,7 @@ TEST(SingleComponentView, EmptyTypes) {
for(auto [entt, iv]: registry.view<int>().each()) { for(auto [entt, iv]: registry.view<int>().each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>); static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>); static_assert(std::is_same_v<decltype(iv), int &>);
ASSERT_EQ(entities[0u], entt); ASSERT_EQ(entity, entt);
} }
} }
@ -459,35 +489,87 @@ TEST(SingleComponentView, StableType) {
TEST(SingleComponentView, Storage) { TEST(SingleComponentView, Storage) {
entt::registry registry; entt::registry registry;
const auto entity = registry.create(); const auto entity = registry.create();
const auto view = registry.view<int>(); auto view = registry.view<int>();
const auto cview = registry.view<const char>(); auto cview = registry.view<const char>();
static_assert(std::is_same_v<decltype(view.storage()), entt::storage_type_t<int> &>); static_assert(std::is_same_v<decltype(view.storage()), entt::storage_type_t<int> *>);
static_assert(std::is_same_v<decltype(view.storage<0u>()), entt::storage_type_t<int> &>); static_assert(std::is_same_v<decltype(view.storage<0u>()), entt::storage_type_t<int> *>);
static_assert(std::is_same_v<decltype(view.storage<int>()), entt::storage_type_t<int> &>); static_assert(std::is_same_v<decltype(view.storage<int>()), entt::storage_type_t<int> *>);
static_assert(std::is_same_v<decltype(cview.storage()), const entt::storage_type_t<char> &>); static_assert(std::is_same_v<decltype(view.storage<const int>()), entt::storage_type_t<int> *>);
static_assert(std::is_same_v<decltype(cview.storage<0u>()), const entt::storage_type_t<char> &>); static_assert(std::is_same_v<decltype(cview.storage()), const entt::storage_type_t<char> *>);
static_assert(std::is_same_v<decltype(cview.storage<const char>()), const entt::storage_type_t<char> &>); static_assert(std::is_same_v<decltype(cview.storage<0u>()), const entt::storage_type_t<char> *>);
static_assert(std::is_same_v<decltype(cview.storage<char>()), const entt::storage_type_t<char> *>);
static_assert(std::is_same_v<decltype(cview.storage<const char>()), const entt::storage_type_t<char> *>);
ASSERT_TRUE(view);
ASSERT_TRUE(cview);
ASSERT_NE(view.storage<int>(), nullptr);
ASSERT_NE(cview.storage<0u>(), nullptr);
ASSERT_EQ(view.size(), 0u); ASSERT_EQ(view.size(), 0u);
ASSERT_EQ(cview.size(), 0u); ASSERT_EQ(cview.size(), 0u);
view.storage().emplace(entity); view.storage()->emplace(entity);
registry.emplace<char>(entity); registry.emplace<char>(entity);
ASSERT_EQ(view.size(), 1u); ASSERT_EQ(view.size(), 1u);
ASSERT_EQ(cview.size(), 1u); ASSERT_EQ(cview.size(), 1u);
ASSERT_TRUE(view.storage<int>().contains(entity)); ASSERT_TRUE(view.storage<int>()->contains(entity));
ASSERT_TRUE(cview.storage<0u>().contains(entity)); ASSERT_TRUE(cview.storage<0u>()->contains(entity));
ASSERT_TRUE((registry.all_of<int, char>(entity))); ASSERT_TRUE((registry.all_of<int, char>(entity)));
view.storage().erase(entity); view.storage()->erase(entity);
ASSERT_EQ(view.size(), 0u); ASSERT_EQ(view.size(), 0u);
ASSERT_EQ(cview.size(), 1u); ASSERT_EQ(cview.size(), 1u);
ASSERT_FALSE(view.storage<0u>().contains(entity)); ASSERT_FALSE(view.storage<0u>()->contains(entity));
ASSERT_TRUE(cview.storage<const char>().contains(entity)); ASSERT_TRUE(cview.storage<const char>()->contains(entity));
ASSERT_FALSE((registry.all_of<int, char>(entity))); ASSERT_FALSE((registry.all_of<int, char>(entity)));
view = {};
cview = {};
ASSERT_FALSE(view);
ASSERT_FALSE(cview);
ASSERT_EQ(view.storage<0u>(), nullptr);
ASSERT_EQ(cview.storage<const char>(), nullptr);
}
TEST(SingleComponentView, SwapStorage) {
using namespace entt::literals;
entt::registry registry;
entt::basic_view<entt::get_t<entt::storage<int>>, entt::exclude_t<>> view;
entt::basic_view<entt::get_t<const entt::storage<int>>, entt::exclude_t<>> cview;
ASSERT_FALSE(view);
ASSERT_FALSE(cview);
ASSERT_EQ(view.storage<0u>(), nullptr);
ASSERT_EQ(cview.storage<const int>(), nullptr);
const entt::entity entity{42u};
registry.emplace<int>(entity);
view.storage(registry.storage<int>());
cview.storage(registry.storage<int>());
ASSERT_TRUE(view);
ASSERT_TRUE(cview);
ASSERT_NE(view.storage<0u>(), nullptr);
ASSERT_NE(cview.storage<const int>(), nullptr);
ASSERT_EQ(view.size(), 1u);
ASSERT_EQ(cview.size(), 1u);
ASSERT_TRUE(view.contains(entity));
ASSERT_TRUE(cview.contains(entity));
view.storage(registry.storage<int>("other"_hs));
cview.storage(registry.storage<int>("other"_hs));
ASSERT_TRUE(view.empty());
ASSERT_TRUE(cview.empty());
} }
TEST(MultiComponentView, Functionalities) { TEST(MultiComponentView, Functionalities) {
@ -532,6 +614,46 @@ TEST(MultiComponentView, Functionalities) {
ASSERT_FALSE(invalid); ASSERT_FALSE(invalid);
} }
TEST(MultiComponentView, InvalidView) {
entt::basic_view<entt::get_t<entt::storage<int>>, entt::exclude_t<entt::storage<char>>> view{};
ASSERT_FALSE(view);
ASSERT_EQ(view.size_hint(), 0u);
ASSERT_FALSE(view.contains(entt::null));
ASSERT_EQ(view.find(entt::null), view.end());
ASSERT_EQ(view.front(), static_cast<entt::entity>(entt::null));
ASSERT_EQ(view.back(), static_cast<entt::entity>(entt::null));
ASSERT_EQ(view.begin(), typename decltype(view)::iterator{});
ASSERT_EQ(view.begin(), view.end());
auto iterable = view.each();
ASSERT_EQ(iterable.begin(), iterable.end());
ASSERT_EQ(iterable.cbegin(), iterable.cend());
view.each([](const int &) { FAIL(); });
view.each([](const entt::entity, const int &) { FAIL(); });
entt::storage<int> storage;
view.storage(storage);
ASSERT_FALSE(view);
view.each([](const int &) { FAIL(); });
view.each([](const entt::entity, const int &) { FAIL(); });
entt::storage<char> other;
view.storage(other);
ASSERT_TRUE(view);
view.each([](const int &) { FAIL(); });
view.each([](const entt::entity, const int &) { FAIL(); });
}
TEST(MultiComponentView, Constructors) { TEST(MultiComponentView, Constructors) {
entt::storage<int> storage{}; entt::storage<int> storage{};
@ -543,7 +665,8 @@ TEST(MultiComponentView, Constructors) {
ASSERT_TRUE(from_storage); ASSERT_TRUE(from_storage);
ASSERT_TRUE(from_tuple); 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) { TEST(MultiComponentView, Handle) {
@ -551,30 +674,34 @@ TEST(MultiComponentView, Handle) {
const auto entity = registry.create(); const auto entity = registry.create();
auto view = registry.view<int, char>(); auto view = registry.view<int, char>();
auto &&handle = view.handle(); auto *handle = view.handle();
ASSERT_TRUE(handle.empty()); ASSERT_NE(handle, nullptr);
ASSERT_FALSE(handle.contains(entity));
ASSERT_EQ(&handle, &view.handle()); ASSERT_TRUE(handle->empty());
ASSERT_FALSE(handle->contains(entity));
ASSERT_EQ(handle, view.handle());
registry.emplace<int>(entity); registry.emplace<int>(entity);
ASSERT_FALSE(handle.empty()); ASSERT_FALSE(handle->empty());
ASSERT_TRUE(handle.contains(entity)); ASSERT_TRUE(handle->contains(entity));
ASSERT_EQ(&handle, &view.handle()); ASSERT_EQ(handle, view.handle());
view = view.refresh(); view.refresh();
auto &&other = view.handle(); auto *other = view.handle();
ASSERT_TRUE(other.empty()); ASSERT_NE(other, nullptr);
ASSERT_FALSE(other.contains(entity));
ASSERT_EQ(&other, &view.handle());
ASSERT_NE(&handle, &other);
view = view.use<int>(); ASSERT_TRUE(other->empty());
ASSERT_FALSE(other->contains(entity));
ASSERT_EQ(other, view.handle());
ASSERT_NE(handle, other);
ASSERT_NE(&other, &view.handle()); view.use<int>();
ASSERT_EQ(&handle, &view.handle());
ASSERT_NE(other, view.handle());
ASSERT_EQ(handle, view.handle());
} }
TEST(MultiComponentView, LazyTypesFromConstRegistry) { TEST(MultiComponentView, LazyTypesFromConstRegistry) {
@ -585,7 +712,7 @@ TEST(MultiComponentView, LazyTypesFromConstRegistry) {
registry.emplace<empty_type>(entity); registry.emplace<empty_type>(entity);
registry.emplace<int>(entity); registry.emplace<int>(entity);
ASSERT_TRUE(view); ASSERT_FALSE(view);
ASSERT_EQ(view.size_hint(), 0u); ASSERT_EQ(view.size_hint(), 0u);
ASSERT_FALSE(view.contains(entity)); ASSERT_FALSE(view.contains(entity));
@ -604,7 +731,7 @@ TEST(MultiComponentView, LazyExcludedTypeFromConstRegistry) {
auto view = std::as_const(registry).view<const int>(entt::exclude<char>); auto view = std::as_const(registry).view<const int>(entt::exclude<char>);
ASSERT_TRUE(view); ASSERT_FALSE(view);
ASSERT_EQ(view.size_hint(), 1u); ASSERT_EQ(view.size_hint(), 1u);
ASSERT_TRUE(view.contains(entity)); ASSERT_TRUE(view.contains(entity));
@ -683,7 +810,6 @@ TEST(MultiComponentView, SizeHint) {
entt::registry registry; entt::registry registry;
const auto e0 = registry.create(); const auto e0 = registry.create();
registry.emplace<double>(e0);
registry.emplace<int>(e0); registry.emplace<int>(e0);
registry.emplace<float>(e0); registry.emplace<float>(e0);
@ -705,10 +831,10 @@ TEST(MultiComponentView, Each) {
auto cview = std::as_const(registry).view<const int, const char>(); auto cview = std::as_const(registry).view<const int, const char>();
registry.emplace<int>(entity[0u], 0); registry.emplace<int>(entity[0u], 0);
registry.emplace<char>(entity[0u], 0); registry.emplace<char>(entity[0u], static_cast<char>(0));
registry.emplace<int>(entity[1u], 1); registry.emplace<int>(entity[1u], 1);
registry.emplace<char>(entity[1u], 1); registry.emplace<char>(entity[1u], static_cast<char>(1));
auto iterable = view.each(); auto iterable = view.each();
auto citerable = cview.each(); auto citerable = cview.each();
@ -719,16 +845,18 @@ TEST(MultiComponentView, Each) {
auto it = iterable.begin(); auto it = iterable.begin();
ASSERT_EQ(it.base(), view.begin());
ASSERT_EQ((it++, ++it), iterable.end()); ASSERT_EQ((it++, ++it), iterable.end());
ASSERT_EQ(it.base(), view.end());
view.each([expected = 1u](auto entt, int &ivalue, char &cvalue) mutable { view.each([expected = 1](auto entt, int &ivalue, char &cvalue) mutable {
ASSERT_EQ(entt::to_integral(entt), expected); ASSERT_EQ(static_cast<int>(entt::to_integral(entt)), expected);
ASSERT_EQ(ivalue, expected); ASSERT_EQ(ivalue, expected);
ASSERT_EQ(cvalue, expected); ASSERT_EQ(cvalue, expected);
--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(ivalue, expected);
ASSERT_EQ(cvalue, expected); ASSERT_EQ(cvalue, expected);
--expected; --expected;
@ -742,13 +870,14 @@ TEST(MultiComponentView, Each) {
// do not use iterable, make sure an iterable view works when created from a temporary // do not use iterable, make sure an iterable view works when created from a temporary
for(auto [entt, ivalue, cvalue]: registry.view<int, char>().each()) { for(auto [entt, ivalue, cvalue]: registry.view<int, char>().each()) {
ASSERT_EQ(entt::to_integral(entt), ivalue); ASSERT_EQ(static_cast<int>(entt::to_integral(entt)), ivalue);
ASSERT_EQ(entt::to_integral(entt), cvalue); ASSERT_EQ(static_cast<char>(entt::to_integral(entt)), cvalue);
} }
} }
TEST(MultiComponentView, EachWithSuggestedType) { TEST(MultiComponentView, EachWithSuggestedType) {
entt::registry registry; entt::registry registry;
auto view = registry.view<int, char>();
for(auto i = 0; i < 3; ++i) { for(auto i = 0; i < 3; ++i) {
const auto entity = registry.create(); const auto entity = registry.create();
@ -760,7 +889,8 @@ TEST(MultiComponentView, EachWithSuggestedType) {
const auto entity = registry.create(); const auto entity = registry.create();
registry.emplace<int>(entity, 99); registry.emplace<int>(entity, 99);
registry.view<int, char>().use<int>().each([value = 2](const auto curr, const auto) mutable { view.use<int>();
view.each([value = 2](const auto curr, const auto) mutable {
ASSERT_EQ(curr, value--); ASSERT_EQ(curr, value--);
}); });
@ -768,7 +898,8 @@ TEST(MultiComponentView, EachWithSuggestedType) {
return lhs < rhs; return lhs < rhs;
}); });
registry.view<int, char>().use<0u>().each([value = 0](const auto curr, const auto) mutable { view.use<0u>();
view.each([value = 0](const auto curr, const auto) mutable {
ASSERT_EQ(curr, value++); ASSERT_EQ(curr, value++);
}); });
@ -787,8 +918,9 @@ TEST(MultiComponentView, EachWithSuggestedType) {
}); });
value = {}; value = {};
view.use<int>();
for(auto &&curr: registry.view<int, char>().use<int>().each()) { for(auto &&curr: view.each()) {
ASSERT_EQ(std::get<1>(curr), static_cast<int>(value++)); ASSERT_EQ(std::get<1>(curr), static_cast<int>(value++));
} }
} }
@ -955,6 +1087,10 @@ TEST(MultiComponentView, ExcludedComponents) {
TEST(MultiComponentView, EmptyTypes) { TEST(MultiComponentView, EmptyTypes) {
entt::registry registry; entt::registry registry;
auto v1 = registry.view<int, char, empty_type>(entt::exclude<double>);
auto v2 = registry.view<int, empty_type, char>(entt::exclude<double>);
auto v3 = registry.view<empty_type, int, char>(entt::exclude<double>);
const auto entity = registry.create(); const auto entity = registry.create();
registry.emplace<int>(entity); registry.emplace<int>(entity);
registry.emplace<char>(entity); registry.emplace<char>(entity);
@ -970,57 +1106,61 @@ TEST(MultiComponentView, EmptyTypes) {
registry.emplace<int>(ignored); registry.emplace<int>(ignored);
registry.emplace<char>(ignored); registry.emplace<char>(ignored);
registry.view<int, char, empty_type>(entt::exclude<double>).each([entity](const auto entt, int, char) { v1.each([entity](const auto entt, int, char) {
ASSERT_EQ(entity, entt); ASSERT_EQ(entity, entt);
}); });
for(auto [entt, iv, cv]: registry.view<int, char, empty_type>(entt::exclude<double>).each()) { for(auto [entt, iv, cv]: v1.each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>); static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>); static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), char &>); static_assert(std::is_same_v<decltype(cv), char &>);
ASSERT_EQ(entity, entt); ASSERT_EQ(entity, entt);
} }
registry.view<int, empty_type, char>(entt::exclude<double>).each([check = true](int, char) mutable { v2.each([check = true](int, char) mutable {
ASSERT_TRUE(check); ASSERT_TRUE(check);
check = false; check = false;
}); });
for(auto [entt, iv, cv]: registry.view<int, empty_type, char>(entt::exclude<double>).each()) { for(auto [entt, iv, cv]: v2.each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>); static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>); static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), char &>); static_assert(std::is_same_v<decltype(cv), char &>);
ASSERT_EQ(entity, entt); ASSERT_EQ(entity, entt);
} }
registry.view<empty_type, int, char>(entt::exclude<double>).each([entity](const auto entt, int, char) { v3.each([entity](const auto entt, int, char) {
ASSERT_EQ(entity, entt); ASSERT_EQ(entity, entt);
}); });
for(auto [entt, iv, cv]: registry.view<empty_type, int, char>(entt::exclude<double>).each()) { for(auto [entt, iv, cv]: v3.each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>); static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>); static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), char &>); static_assert(std::is_same_v<decltype(cv), char &>);
ASSERT_EQ(entity, entt); ASSERT_EQ(entity, entt);
} }
registry.view<empty_type, int, char>(entt::exclude<double>).use<empty_type>().each([entity](const auto entt, int, char) { v3.use<empty_type>();
v3.each([entity](const auto entt, int, char) {
ASSERT_EQ(entity, entt); ASSERT_EQ(entity, entt);
}); });
for(auto [entt, iv, cv]: registry.view<empty_type, int, char>(entt::exclude<double>).use<0u>().each()) { v3.use<0u>();
for(auto [entt, iv, cv]: v3.each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>); static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>); static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), char &>); static_assert(std::is_same_v<decltype(cv), char &>);
ASSERT_EQ(entity, entt); ASSERT_EQ(entity, entt);
} }
registry.view<int, empty_type, char>(entt::exclude<double>).use<1u>().each([check = true](int, char) mutable { v2.use<1u>();
v2.each([check = true](int, char) mutable {
ASSERT_TRUE(check); ASSERT_TRUE(check);
check = false; check = false;
}); });
for(auto [entt, iv, cv]: registry.view<int, empty_type, char>(entt::exclude<double>).use<empty_type>().each()) { v2.use<empty_type>();
for(auto [entt, iv, cv]: v2.each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>); static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>); static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), char &>); static_assert(std::is_same_v<decltype(cv), char &>);
@ -1111,7 +1251,7 @@ TEST(MultiComponentView, StableType) {
ASSERT_EQ(view.size_hint(), 1u); ASSERT_EQ(view.size_hint(), 1u);
view = view.use<stable_type>(); view.use<stable_type>();
ASSERT_EQ(view.size_hint(), 2u); ASSERT_EQ(view.size_hint(), 2u);
ASSERT_FALSE(view.contains(entity)); ASSERT_FALSE(view.contains(entity));
@ -1146,7 +1286,9 @@ TEST(MultiComponentView, StableType) {
TEST(MultiComponentView, StableTypeWithExcludedComponent) { TEST(MultiComponentView, StableTypeWithExcludedComponent) {
entt::registry registry; entt::registry registry;
auto view = registry.view<stable_type>(entt::exclude<int>).use<stable_type>(); auto view = registry.view<stable_type>(entt::exclude<int>);
view.use<stable_type>();
const auto entity = registry.create(); const auto entity = registry.create();
const auto other = registry.create(); const auto other = registry.create();
@ -1223,8 +1365,118 @@ TEST(MultiComponentView, SameComponentTypes) {
ASSERT_EQ(second, 9); ASSERT_EQ(second, 9);
} }
ASSERT_EQ(&view.handle(), &storage); ASSERT_EQ(view.handle(), &storage);
ASSERT_EQ(&view.use<1u>().handle(), &other);
view.use<1u>();
ASSERT_EQ(view.handle(), &other);
}
TEST(MultiComponentView, Storage) {
entt::registry registry;
const auto entity = registry.create();
auto view = registry.view<int, const char>(entt::exclude<double, const float>);
static_assert(std::is_same_v<decltype(view.storage<0u>()), entt::storage_type_t<int> *>);
static_assert(std::is_same_v<decltype(view.storage<int>()), entt::storage_type_t<int> *>);
static_assert(std::is_same_v<decltype(view.storage<const int>()), entt::storage_type_t<int> *>);
static_assert(std::is_same_v<decltype(view.storage<1u>()), const entt::storage_type_t<char> *>);
static_assert(std::is_same_v<decltype(view.storage<char>()), const entt::storage_type_t<char> *>);
static_assert(std::is_same_v<decltype(view.storage<const char>()), const entt::storage_type_t<char> *>);
static_assert(std::is_same_v<decltype(view.storage<2u>()), entt::storage_type_t<double> *>);
static_assert(std::is_same_v<decltype(view.storage<double>()), entt::storage_type_t<double> *>);
static_assert(std::is_same_v<decltype(view.storage<const double>()), entt::storage_type_t<double> *>);
static_assert(std::is_same_v<decltype(view.storage<3u>()), const entt::storage_type_t<float> *>);
static_assert(std::is_same_v<decltype(view.storage<float>()), const entt::storage_type_t<float> *>);
static_assert(std::is_same_v<decltype(view.storage<const float>()), const entt::storage_type_t<float> *>);
ASSERT_TRUE(view);
ASSERT_NE(view.storage<int>(), nullptr);
ASSERT_NE(view.storage<1u>(), nullptr);
ASSERT_NE(view.storage<double>(), nullptr);
ASSERT_NE(view.storage<3u>(), nullptr);
ASSERT_EQ(view.size_hint(), 0u);
view.storage<int>()->emplace(entity);
view.storage<double>()->emplace(entity);
registry.emplace<char>(entity);
registry.emplace<float>(entity);
ASSERT_EQ(view.size_hint(), 1u);
ASSERT_EQ(view.begin(), view.end());
ASSERT_TRUE(view.storage<int>()->contains(entity));
ASSERT_TRUE(view.storage<const char>()->contains(entity));
ASSERT_TRUE(view.storage<double>()->contains(entity));
ASSERT_TRUE(view.storage<const float>()->contains(entity));
ASSERT_TRUE((registry.all_of<int, char, double, float>(entity)));
view.storage<double>()->erase(entity);
registry.erase<float>(entity);
ASSERT_EQ(view.size_hint(), 1u);
ASSERT_NE(view.begin(), view.end());
ASSERT_TRUE(view.storage<const int>()->contains(entity));
ASSERT_TRUE(view.storage<char>()->contains(entity));
ASSERT_FALSE(view.storage<const double>()->contains(entity));
ASSERT_FALSE(view.storage<float>()->contains(entity));
ASSERT_TRUE((registry.all_of<int, char>(entity)));
ASSERT_FALSE((registry.any_of<double, float>(entity)));
view.storage<0u>()->erase(entity);
ASSERT_EQ(view.size_hint(), 0u);
ASSERT_EQ(view.begin(), view.end());
ASSERT_FALSE(view.storage<0u>()->contains(entity));
ASSERT_TRUE(view.storage<1u>()->contains(entity));
ASSERT_FALSE(view.storage<2u>()->contains(entity));
ASSERT_FALSE(view.storage<3u>()->contains(entity));
ASSERT_TRUE((registry.all_of<char>(entity)));
ASSERT_FALSE((registry.any_of<int, double, float>(entity)));
view = {};
ASSERT_FALSE(view);
ASSERT_EQ(view.storage<0u>(), nullptr);
ASSERT_EQ(view.storage<const char>(), nullptr);
ASSERT_EQ(view.storage<2u>(), nullptr);
ASSERT_EQ(view.storage<const float>(), nullptr);
}
TEST(MultiComponentView, SwapStorage) {
using namespace entt::literals;
entt::registry registry;
entt::basic_view<entt::get_t<entt::storage<int>>, entt::exclude_t<const entt::storage<char>>> view;
ASSERT_FALSE(view);
ASSERT_EQ(view.storage<0u>(), nullptr);
ASSERT_EQ(view.storage<const char>(), nullptr);
const entt::entity entity{42u};
registry.emplace<int>(entity);
registry.emplace<char>(entity);
view.storage(registry.storage<int>());
view.storage<1u>(registry.storage<char>());
ASSERT_TRUE(view);
ASSERT_NE(view.storage<int>(), nullptr);
ASSERT_NE(view.storage<1u>(), nullptr);
ASSERT_EQ(view.size_hint(), 1u);
ASSERT_FALSE(view.contains(entity));
view.storage(registry.storage<char>("other"_hs));
ASSERT_EQ(view.size_hint(), 1u);
ASSERT_TRUE(view.contains(entity));
view.storage(registry.storage<int>("empty"_hs));
ASSERT_EQ(view.size_hint(), 0u);
} }
TEST(View, Pipe) { TEST(View, Pipe) {
@ -1241,10 +1493,10 @@ TEST(View, Pipe) {
registry.emplace<char>(other); registry.emplace<char>(other);
registry.emplace<stable_type>(other); registry.emplace<stable_type>(other);
const auto view1 = registry.view<int>(entt::exclude<const double>); auto view1 = registry.view<int>(entt::exclude<const double>);
const auto view2 = registry.view<const char>(entt::exclude<float>); auto view2 = registry.view<const char>(entt::exclude<float>);
const auto view3 = registry.view<empty_type>(); auto view3 = registry.view<empty_type>();
const auto view4 = registry.view<stable_type>(); auto view4 = registry.view<stable_type>();
static_assert(std::is_same_v<entt::basic_view<entt::get_t<entt::storage_type_t<int>, const entt::storage_type_t<char>>, entt::exclude_t<const entt::storage_type_t<double>, entt::storage_type_t<float>>>, decltype(view1 | view2)>); static_assert(std::is_same_v<entt::basic_view<entt::get_t<entt::storage_type_t<int>, const entt::storage_type_t<char>>, entt::exclude_t<const entt::storage_type_t<double>, entt::storage_type_t<float>>>, decltype(view1 | view2)>);
static_assert(std::is_same_v<entt::basic_view<entt::get_t<const entt::storage_type_t<char>, entt::storage_type_t<int>>, entt::exclude_t<entt::storage_type_t<float>, const entt::storage_type_t<double>>>, decltype(view2 | view1)>); static_assert(std::is_same_v<entt::basic_view<entt::get_t<const entt::storage_type_t<char>, entt::storage_type_t<int>>, entt::exclude_t<entt::storage_type_t<float>, const entt::storage_type_t<double>>>, decltype(view2 | view1)>);
@ -1261,30 +1513,26 @@ TEST(View, Pipe) {
ASSERT_FALSE((view1 | view4 | view2).contains(entity)); ASSERT_FALSE((view1 | view4 | view2).contains(entity));
ASSERT_TRUE((view1 | view4 | view2).contains(other)); ASSERT_TRUE((view1 | view4 | view2).contains(other));
}
view1 = {};
TEST(MultiComponentView, Storage) { view3 = {};
entt::registry registry;
const auto entity = registry.create(); ASSERT_FALSE(view1);
const auto view = registry.view<int, const char>(); ASSERT_TRUE(view2);
ASSERT_FALSE(view3);
static_assert(std::is_same_v<decltype(view.storage<0u>()), entt::storage_type_t<int> &>); ASSERT_TRUE(view4);
static_assert(std::is_same_v<decltype(view.storage<int>()), entt::storage_type_t<int> &>);
static_assert(std::is_same_v<decltype(view.storage<1u>()), const entt::storage_type_t<char> &>); auto pack14 = view1 | view4;
static_assert(std::is_same_v<decltype(view.storage<const char>()), const entt::storage_type_t<char> &>); auto pack32 = view3 | view2;
ASSERT_EQ(view.size_hint(), 0u); ASSERT_FALSE(pack14);
ASSERT_FALSE(pack32);
view.storage<int>().emplace(entity);
registry.emplace<char>(entity); ASSERT_EQ(pack14.storage<int>(), nullptr);
ASSERT_EQ(pack14.storage<const double>(), nullptr);
ASSERT_EQ(view.size_hint(), 1u); ASSERT_NE(pack14.storage<stable_type>(), nullptr);
ASSERT_TRUE(view.storage<const char>().contains(entity));
ASSERT_TRUE((registry.all_of<int, char>(entity))); ASSERT_EQ(pack32.storage<empty_type>(), nullptr);
ASSERT_NE(pack32.storage<const char>(), nullptr);
view.storage<0u>().erase(entity); ASSERT_NE(pack32.storage<float>(), nullptr);
ASSERT_EQ(view.size_hint(), 0u);
ASSERT_TRUE(view.storage<1u>().contains(entity));
ASSERT_FALSE((registry.all_of<int, char>(entity)));
} }

View File

@ -295,6 +295,23 @@ TEST(AdjacencyMatrix, EdgesDirected) {
ASSERT_EQ(++it, iterable.end()); ASSERT_EQ(++it, iterable.end());
} }
TEST(AdjacencyMatrix, EdgesBackwardOnlyDirected) {
entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{2u};
auto iterable = adjacency_matrix.edges();
ASSERT_EQ(iterable.begin(), iterable.end());
adjacency_matrix.insert(1u, 0u);
iterable = adjacency_matrix.edges();
ASSERT_NE(iterable.begin(), iterable.end());
auto it = iterable.begin();
ASSERT_EQ(*it++, std::make_pair(std::size_t{1u}, std::size_t{0u}));
ASSERT_EQ(it, iterable.end());
}
TEST(AdjacencyMatrix, EdgesUndirected) { TEST(AdjacencyMatrix, EdgesUndirected) {
entt::adjacency_matrix<entt::undirected_tag> adjacency_matrix{3u}; entt::adjacency_matrix<entt::undirected_tag> adjacency_matrix{3u};
auto iterable = adjacency_matrix.edges(); auto iterable = adjacency_matrix.edges();
@ -317,6 +334,24 @@ TEST(AdjacencyMatrix, EdgesUndirected) {
ASSERT_EQ(++it, iterable.end()); ASSERT_EQ(++it, iterable.end());
} }
TEST(AdjacencyMatrix, EdgesBackwardOnlyUndirected) {
entt::adjacency_matrix<entt::undirected_tag> adjacency_matrix{2u};
auto iterable = adjacency_matrix.edges();
ASSERT_EQ(iterable.begin(), iterable.end());
adjacency_matrix.insert(1u, 0u);
iterable = adjacency_matrix.edges();
ASSERT_NE(iterable.begin(), iterable.end());
auto it = iterable.begin();
ASSERT_EQ(*it++, std::make_pair(std::size_t{0u}, std::size_t{1u}));
ASSERT_EQ(*it++, std::make_pair(std::size_t{1u}, std::size_t{0u}));
ASSERT_EQ(it, iterable.end());
}
TEST(AdjacencyMatrix, OutEdgesDirected) { TEST(AdjacencyMatrix, OutEdgesDirected) {
entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{3u}; entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{3u};
auto iterable = adjacency_matrix.out_edges(0u); auto iterable = adjacency_matrix.out_edges(0u);
@ -340,6 +375,28 @@ TEST(AdjacencyMatrix, OutEdgesDirected) {
ASSERT_EQ(it, iterable.cend()); ASSERT_EQ(it, iterable.cend());
} }
TEST(AdjacencyMatrix, OutEdgesBackwardOnlyDirected) {
entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{2u};
auto iterable = adjacency_matrix.out_edges(1u);
ASSERT_EQ(iterable.begin(), iterable.end());
adjacency_matrix.insert(1u, 0u);
iterable = adjacency_matrix.out_edges(1u);
ASSERT_NE(iterable.begin(), iterable.end());
auto it = iterable.begin();
ASSERT_EQ(*it++, std::make_pair(std::size_t{1u}, std::size_t{0u}));
ASSERT_EQ(it, iterable.end());
iterable = adjacency_matrix.out_edges(0u);
it = iterable.cbegin();
ASSERT_EQ(it, iterable.cend());
}
TEST(AdjacencyMatrix, OutEdgesUndirected) { TEST(AdjacencyMatrix, OutEdgesUndirected) {
entt::adjacency_matrix<entt::undirected_tag> adjacency_matrix{3u}; entt::adjacency_matrix<entt::undirected_tag> adjacency_matrix{3u};
auto iterable = adjacency_matrix.out_edges(0u); auto iterable = adjacency_matrix.out_edges(0u);
@ -365,6 +422,30 @@ TEST(AdjacencyMatrix, OutEdgesUndirected) {
ASSERT_EQ(it, iterable.cend()); ASSERT_EQ(it, iterable.cend());
} }
TEST(AdjacencyMatrix, OutEdgesBackwardOnlyUndirected) {
entt::adjacency_matrix<entt::undirected_tag> adjacency_matrix{2u};
auto iterable = adjacency_matrix.out_edges(1u);
ASSERT_EQ(iterable.begin(), iterable.end());
adjacency_matrix.insert(1u, 0u);
iterable = adjacency_matrix.out_edges(1u);
ASSERT_NE(iterable.begin(), iterable.end());
auto it = iterable.begin();
ASSERT_EQ(*it++, std::make_pair(std::size_t{1u}, std::size_t{0u}));
ASSERT_EQ(it, iterable.end());
iterable = adjacency_matrix.out_edges(0u);
it = iterable.cbegin();
ASSERT_NE(it, iterable.cend());
ASSERT_EQ(*it++, std::make_pair(std::size_t{0u}, std::size_t{1u}));
ASSERT_EQ(it, iterable.cend());
}
TEST(AdjacencyMatrix, InEdgesDirected) { TEST(AdjacencyMatrix, InEdgesDirected) {
entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{3u}; entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{3u};
auto iterable = adjacency_matrix.in_edges(1u); auto iterable = adjacency_matrix.in_edges(1u);
@ -388,6 +469,28 @@ TEST(AdjacencyMatrix, InEdgesDirected) {
ASSERT_EQ(it, iterable.cend()); ASSERT_EQ(it, iterable.cend());
} }
TEST(AdjacencyMatrix, InEdgesBackwardOnlyDirected) {
entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{2u};
auto iterable = adjacency_matrix.in_edges(0u);
ASSERT_EQ(iterable.begin(), iterable.end());
adjacency_matrix.insert(1u, 0u);
iterable = adjacency_matrix.in_edges(0u);
ASSERT_NE(iterable.begin(), iterable.end());
auto it = iterable.begin();
ASSERT_EQ(*it++, std::make_pair(std::size_t{1u}, std::size_t{0u}));
ASSERT_EQ(it, iterable.end());
iterable = adjacency_matrix.in_edges(1u);
it = iterable.cbegin();
ASSERT_EQ(it, iterable.cend());
}
TEST(AdjacencyMatrix, InEdgesUndirected) { TEST(AdjacencyMatrix, InEdgesUndirected) {
entt::adjacency_matrix<entt::undirected_tag> adjacency_matrix{3u}; entt::adjacency_matrix<entt::undirected_tag> adjacency_matrix{3u};
auto iterable = adjacency_matrix.in_edges(1u); auto iterable = adjacency_matrix.in_edges(1u);
@ -403,6 +506,7 @@ TEST(AdjacencyMatrix, InEdgesUndirected) {
auto it = iterable.begin(); 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{0u}, std::size_t{1u}));
ASSERT_EQ(*it++, std::make_pair(std::size_t{2u}, std::size_t{1u}));
ASSERT_EQ(it, iterable.end()); ASSERT_EQ(it, iterable.end());
iterable = adjacency_matrix.in_edges(0u); iterable = adjacency_matrix.in_edges(0u);
@ -413,6 +517,30 @@ TEST(AdjacencyMatrix, InEdgesUndirected) {
ASSERT_EQ(it, iterable.cend()); ASSERT_EQ(it, iterable.cend());
} }
TEST(AdjacencyMatrix, InEdgesBackwardOnlyUndirected) {
entt::adjacency_matrix<entt::undirected_tag> adjacency_matrix{2u};
auto iterable = adjacency_matrix.in_edges(0u);
ASSERT_EQ(iterable.begin(), iterable.end());
adjacency_matrix.insert(1u, 0u);
iterable = adjacency_matrix.in_edges(0u);
ASSERT_NE(iterable.begin(), iterable.end());
auto it = iterable.begin();
ASSERT_EQ(*it++, std::make_pair(std::size_t{1u}, std::size_t{0u}));
ASSERT_EQ(it, iterable.end());
iterable = adjacency_matrix.in_edges(1u);
it = iterable.cbegin();
ASSERT_NE(it, iterable.cend());
ASSERT_EQ(*it++, std::make_pair(std::size_t{0u}, std::size_t{1u}));
ASSERT_EQ(it, iterable.cend());
}
TEST(AdjacencyMatrix, ThrowingAllocator) { TEST(AdjacencyMatrix, ThrowingAllocator) {
using allocator = test::throwing_allocator<std::size_t>; using allocator = test::throwing_allocator<std::size_t>;
using exception = typename allocator::exception_type; using exception = typename allocator::exception_type;

View File

@ -1,6 +1,7 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <entt/core/hashed_string.hpp> #include <entt/core/hashed_string.hpp>
#include <entt/graph/flow.hpp> #include <entt/graph/flow.hpp>
#include "../common/config.h"
#include "../common/throwing_allocator.hpp" #include "../common/throwing_allocator.hpp"
TEST(Flow, Constructors) { TEST(Flow, Constructors) {
@ -24,9 +25,9 @@ TEST(Flow, Constructors) {
ASSERT_EQ(flow.size(), 0u); ASSERT_EQ(flow.size(), 0u);
ASSERT_EQ(other.size(), 3u); ASSERT_EQ(other.size(), 3u);
ASSERT_EQ(other[0u], 0); ASSERT_EQ(other[0u], 0u);
ASSERT_EQ(other[1u], 3); ASSERT_EQ(other[1u], 3u);
ASSERT_EQ(other[2u], 99); ASSERT_EQ(other[2u], 99u);
} }
TEST(Flow, Copy) { TEST(Flow, Copy) {
@ -41,9 +42,9 @@ TEST(Flow, Copy) {
ASSERT_EQ(flow.size(), 3u); ASSERT_EQ(flow.size(), 3u);
ASSERT_EQ(other.size(), 3u); ASSERT_EQ(other.size(), 3u);
ASSERT_EQ(other[0u], 0); ASSERT_EQ(other[0u], 0u);
ASSERT_EQ(other[1u], 3); ASSERT_EQ(other[1u], 3u);
ASSERT_EQ(other[2u], 99); ASSERT_EQ(other[2u], 99u);
flow.bind(1); flow.bind(1);
other.bind(2); other.bind(2);
@ -53,10 +54,10 @@ TEST(Flow, Copy) {
ASSERT_EQ(other.size(), 4u); ASSERT_EQ(other.size(), 4u);
ASSERT_EQ(flow.size(), 4u); ASSERT_EQ(flow.size(), 4u);
ASSERT_EQ(other[0u], 0); ASSERT_EQ(other[0u], 0u);
ASSERT_EQ(other[1u], 3); ASSERT_EQ(other[1u], 3u);
ASSERT_EQ(other[2u], 99); ASSERT_EQ(other[2u], 99u);
ASSERT_EQ(other[3u], 1); ASSERT_EQ(other[3u], 1u);
} }
TEST(Flow, Move) { TEST(Flow, Move) {
@ -71,9 +72,9 @@ TEST(Flow, Move) {
ASSERT_EQ(flow.size(), 0u); ASSERT_EQ(flow.size(), 0u);
ASSERT_EQ(other.size(), 3u); ASSERT_EQ(other.size(), 3u);
ASSERT_EQ(other[0u], 0); ASSERT_EQ(other[0u], 0u);
ASSERT_EQ(other[1u], 3); ASSERT_EQ(other[1u], 3u);
ASSERT_EQ(other[2u], 99); ASSERT_EQ(other[2u], 99u);
flow = {}; flow = {};
flow.bind(1); flow.bind(1);
@ -84,7 +85,7 @@ TEST(Flow, Move) {
ASSERT_EQ(other.size(), 1u); ASSERT_EQ(other.size(), 1u);
ASSERT_EQ(flow.size(), 0u); ASSERT_EQ(flow.size(), 0u);
ASSERT_EQ(other[0u], 1); ASSERT_EQ(other[0u], 1u);
} }
TEST(Flow, Swap) { TEST(Flow, Swap) {
@ -95,13 +96,13 @@ TEST(Flow, Swap) {
ASSERT_EQ(other.size(), 0u); ASSERT_EQ(other.size(), 0u);
ASSERT_EQ(flow.size(), 1u); ASSERT_EQ(flow.size(), 1u);
ASSERT_EQ(flow[0u], 7); ASSERT_EQ(flow[0u], 7u);
flow.swap(other); flow.swap(other);
ASSERT_EQ(other.size(), 1u); ASSERT_EQ(other.size(), 1u);
ASSERT_EQ(flow.size(), 0u); ASSERT_EQ(flow.size(), 0u);
ASSERT_EQ(other[0u], 7); ASSERT_EQ(other[0u], 7u);
} }
TEST(Flow, Clear) { TEST(Flow, Clear) {
@ -111,8 +112,8 @@ TEST(Flow, Clear) {
flow.bind(99); flow.bind(99);
ASSERT_EQ(flow.size(), 2u); ASSERT_EQ(flow.size(), 2u);
ASSERT_EQ(flow[0u], 0); ASSERT_EQ(flow[0u], 0u);
ASSERT_EQ(flow[1u], 99); ASSERT_EQ(flow[1u], 99u);
flow.clear(); flow.clear();
@ -270,6 +271,57 @@ TEST(Flow, Sync) {
ASSERT_EQ(it, last); 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) { TEST(Flow, ThrowingAllocator) {
using allocator = test::throwing_allocator<entt::id_type>; using allocator = test::throwing_allocator<entt::id_type>;
using task_allocator = test::throwing_allocator<std::pair<std::size_t, entt::id_type>>; using task_allocator = test::throwing_allocator<std::pair<std::size_t, entt::id_type>>;

Some files were not shown because too many files have changed in this diff Show More