Compare commits

..

378 Commits

Author SHA1 Message Date
Michele Caini
2d5a3f24aa doc: updated documentation 2021-07-21 00:07:02 +02:00
Michele Caini
1639429edd registry: minor changes 2021-07-01 15:32:30 +02:00
Michele Caini
f81abf4883 add cpp.hint to .gitignore 2021-07-01 12:32:18 +02:00
Michele Caini
dcddb7d50e registry: allow non-const types in const overloads of registry context variable getters (close #720) 2021-06-30 09:35:36 +02:00
Michele Caini
42a763031c storage: minor changes 2021-06-30 08:55:21 +02:00
Michele Caini
0964ca5918 storage: turn to assure_at_least based on position to avoid errors at call site 2021-06-29 16:36:00 +02:00
Michele Caini
4e870b83cd test: suppress warnings due to suggested braces 2021-06-29 15:17:58 +02:00
Michele Caini
0b19c9be0b storage: add override specifier 2021-06-29 15:10:41 +02:00
Michele Caini
ac655902a0 storage:
* internal review for memory management
* added support for nosy destructors to ::in_place_pop
* optimized ::emplace/::insert
2021-06-29 15:05:03 +02:00
Michele Caini
e4fb293b55 sparse_set: added ::emplace_back 2021-06-29 12:37:39 +02:00
Michele Caini
d3df64ef4b sparse_set: emplace returns the slot used for insertion 2021-06-28 18:51:28 +02:00
Michele Caini
93e3d14f34 sparse_set: added ::slot to return the next slot available for insertion 2021-06-28 18:46:02 +02:00
Michele Caini
bc687d412d test: suppress some warnings due to [[nodiscard]] 2021-06-28 18:02:43 +02:00
Michele Caini
71e85b44b0 sparse_set: make ::policy [[nodiscard]] 2021-06-28 18:00:40 +02:00
Michele Caini
d4b59aff97 sparse_set: tombstone/null debug check in ::contains 2021-06-28 16:51:24 +02:00
Michele Caini
e3c21e1f3d sparse_set/storage (close #707):
* improved perf on erase/remove (and therefore also registry::destroy)
* allow to inhibit a pop from derived classes
* removed about_to_pop (virtual) function
2021-06-28 15:53:59 +02:00
Michele Caini
e012250e0a type_traits: minor changes 2021-06-28 11:43:27 +02:00
Michele Caini
c6bf82664c doc: added a note about optimized range-destroy 2021-06-28 11:20:40 +02:00
Michele Caini
c61e98009f registry/benchmark: rollback optimized remove/erase (benchmarks have shown that they are not worth it) 2021-06-28 10:19:27 +02:00
Michele Caini
44bccaaad6 basic_registry:
* added fast path to destroy
* added fast path to remove
* added fast path to erase
* minor changes
2021-06-28 09:24:12 +02:00
Michele Caini
aebca14dea view: added member type iterator_type to internal class view_iterator 2021-06-28 09:10:52 +02:00
Michele Caini
4576f27f6e sparse_set: minor changes 2021-06-28 09:10:12 +02:00
Michele Caini
0c213fca40 type_traits:
* added entt::is_iterator[_v]
* added entt::is_iterator_type[_v]
2021-06-28 09:09:55 +02:00
Michele Caini
891f596191 view: minor changes 2021-06-25 17:26:22 +02:00
Michele Caini
eb2b724902 benchmark: tombstone policy runs 2021-06-24 14:04:35 +02:00
Michele Caini
1f8c896181 registry:
* deprecated ::destroyed (use ::released instead)
* added ::release overloads (aka force-destroy)
2021-06-24 10:51:03 +02:00
Michele Caini
12b39e1fbe doc: added missing tokens 2021-06-23 16:35:00 +02:00
Michele Caini
206d31f27a registry: const ::view/::group also support non-const types (close #715) 2021-06-23 15:37:46 +02:00
Michele Caini
6709331182 resource_handle: conversion functions (close #706) 2021-06-22 18:54:27 +02:00
Michele Caini
9460e04ea5 entt_traits: review/cleanup 2021-06-22 15:39:15 +02:00
Michele Caini
462bfea733 entt_traits: review to reduce the risk of ambiguous calls 2021-06-22 13:03:32 +02:00
Michele Caini
a2d61dfaeb runtime_view: suppress warning for shadow parameter 2021-06-22 09:58:19 +02:00
Michele Caini
538425e35c doc: in-place delete and pointer stability 2021-06-22 09:53:56 +02:00
Michele Caini
2e86c1f1a2 component: add basic_component_traits aside component_traits 2021-06-22 09:52:43 +02:00
Michele Caini
8aa4d46ce0 runtime_view: support for storage policy (tombstones) 2021-06-21 15:18:13 +02:00
Michele Caini
cdf67a1421 sparse_set: public ::policy method for opaque runtime detection 2021-06-21 14:44:09 +02:00
Michele Caini
93d905d93d updated TODO 2021-06-21 13:04:46 +02:00
Michele Caini
2c35203647 view:
* split view iterator from multi-type view
* stable/packed storage policy support
2021-06-21 13:04:37 +02:00
Michele Caini
216871fe56 runtime_view: minor changes 2021-06-21 12:59:44 +02:00
Michele Caini
03511f39b1 registry:
* prevent groups from being used with tombstones
* support to compact one, several or all pools
2021-06-21 12:59:26 +02:00
Michele Caini
5ad0832b22 group: minor changes 2021-06-21 12:57:35 +02:00
Michele Caini
d49e7ba4b2 sparse_set/storage:
* added component traits (component.hpp)
* support for in-place delete (tombstones)
2021-06-21 12:56:33 +02:00
Michele Caini
a621b36389 type_traits: removed internal is_empty[_v] 2021-06-21 12:44:50 +02:00
Michele Caini
d093df02ac test: minor changes 2021-06-21 12:39:18 +02:00
Michele Caini
2e1529e78d entity:
* Updated doc
* No const-ref for basic types
2021-06-21 12:32:30 +02:00
Michele Caini
90be1db402 entity:
* extended entt_traits<T>::to_type support
* added null_t::operator|
* added tombstone_t::operator|
2021-06-04 11:01:26 +02:00
Michele Caini
29265e4181 doc: updated doxy.in file 2021-06-03 10:44:13 +02:00
Michele Caini
2689a7ef13 registry: minor changes 2021-06-03 09:06:38 +02:00
Michele Caini
88b58cf23d doc: added a note about tombstones 2021-06-03 09:06:36 +02:00
Michele Caini
e2e433480b entity/registry:
* added entt_traits<T>::reserved()
* added tombstone_t type and tombstone constant
* create-with-hint also accepts null entities (discarded)
* registry skips over tombstone versions and rejects them
2021-06-03 09:06:34 +02:00
Michele Caini
b8bc3e4e94 test: exception safety for sparse set and storage 2021-06-03 08:44:48 +02:00
Michele Caini
c28f52b816 sparse_set/storage: exception safety (kind of) 2021-06-03 08:44:43 +02:00
Michele Caini
ca34309f75 config: removed ENTT_NOEXCEPT, added ENTT_NOEXCEPTION 2021-06-03 08:44:30 +02:00
Michele Caini
ea614a0f3f entity: review of entt_traits<T>::to_type 2021-05-28 08:59:50 +02:00
Michele Caini
3e7dc7af29 entity:
* review of entt_traits design
* added static constexpr member function entt_traits::to_integral
* added static constexpr member function entt_traits::to_entity
* added static constexpr member function entt_traits::to_version
* added static constexpr member function entt_traits::to_type
* custom class identifiers must expose member type entity_field
* it's no longer required to specialize entt_traits (breaking change)
2021-05-28 08:59:50 +02:00
Michele Caini
0b3e3fd19a doc: updated links (close #709) and TODO file 2021-05-28 08:58:36 +02:00
Michele Caini
d3372dc05c organizer: wrap std::min to trick windows.h (close #710) 2021-05-28 08:54:41 +02:00
Michele Caini
7d141cb183 storage: fix move ctor/op 2021-05-27 16:16:45 +02:00
Michele Caini
755699c31b config:
* removed entt::page_size
* renamed ENTT_PAGE_SIZE to ENTT_SPARSE_PAGE
* added ENTT_PACKED_PAGE (page size for component arrays)
2021-05-25 10:07:20 +02:00
Michele Caini
acf3d4cd74 sparse_set/storage: suppress a couple of warnings 2021-05-25 10:06:55 +02:00
Michele Caini
92414c91b5 sparse_set/storage: review memory management for fancy allocators and the like 2021-05-24 11:53:45 +02:00
Michele Caini
ff67f402bb sparse_set/storage:
* better performance on insertion
* exception guarantee for nothrow default constructible entity types
2021-05-24 10:36:33 +02:00
Michele Caini
f048a3f1d8 sparse_set: minor changes 2021-05-24 10:27:30 +02:00
Michele Caini
9d39cb51cf storage/registry: review range ::insert (breaking change) 2021-05-24 10:27:30 +02:00
Michele Caini
7624a9d34c sparse_set/storage:
* exception safety guarantee (sort of)
* ::insert optimization for non random access iterators
2021-05-24 10:27:30 +02:00
Michele Caini
0fe73fb6ed storage: free memory in move assignment operator (with tests) 2021-05-24 10:27:10 +02:00
Michele Caini
ae11652493 sparse_set: free memory in move assignment operator (with tests) 2021-05-24 09:52:09 +02:00
Michele Caini
0ab46870ae updated TODO list 2021-05-21 09:00:18 +02:00
Michele Caini
a5381374b8 updated workflow 2021-05-20 11:00:42 +02:00
Michele Caini
7fae655651 updated TODO file 2021-05-19 10:49:23 +02:00
Michele Caini
3d9959c8e5 sparse_set/storage: small review and optimizations 2021-05-19 09:29:37 +02:00
Michele Caini
bc86576aa8 storage: improved performance when emplacing 2021-05-17 12:34:42 +02:00
Michele Caini
7c2bce8baf core: added entt::page_size 2021-05-17 12:07:48 +02:00
Michele Caini
b062bbf58d storage/view/group: reintroduce (opaque) ::raw functions as it ought to be 2021-05-17 11:59:51 +02:00
Michele Caini
8e5a048913 storage: updated pointer type for storage iterator 2021-05-17 10:10:54 +02:00
Michele Caini
ff642ffff7 sparse_set: updated pointer type for sparse set iterator 2021-05-17 10:09:50 +02:00
Michele Caini
656c12cebd view/group: opaque pointer type for ::data 2021-05-17 00:00:06 +02:00
Michele Caini
48f3fcf3bd doc: updated links (close #704) 2021-05-16 22:33:04 +02:00
Michele Caini
d797af695b registry: much faster range ::create 2021-05-16 00:26:57 +02:00
Michele Caini
75e0161c5f view :minor changes 2021-05-15 23:01:50 +02:00
Michele Caini
c0662816f1 updated TODO file 2021-05-14 18:24:09 +02:00
Michele Caini
415a31ce23 storage: forward allocator to base class 2021-05-14 15:12:29 +02:00
Michele Caini
6e603e2e51 sparse_set: suppress conversion warning 2021-05-14 15:12:26 +02:00
Michele Caini
9af318a767 test: minor changes 2021-05-14 15:12:23 +02:00
Michele Caini
30827120f6 registry: cleanup 2021-05-14 15:12:20 +02:00
Michele Caini
6194e12616 entity:
* allocator support for sparse set and storage
* pointer stability when assigning components
* removed ::raw functions (registry, view, group)
* to_entity returns null for invalid components
2021-05-14 15:12:16 +02:00
Michele Caini
59a27fb652 registry: deprecated ::reserve_pools 2021-05-13 11:01:46 +02:00
Michele Caini
f8ec57c2f6 *: cleanup 2021-05-11 10:50:38 +02:00
Michele Caini
844ef5e232 meta: avoid warning for unused variable 2021-05-10 09:49:44 +02:00
Michele Caini
5bf3f7b77e meta: suppress warning for extra ; 2021-05-08 17:19:06 +02:00
Michele Caini
00c0afd093 meta: avoid using y_combinator to clean up types (close #700) 2021-05-08 17:13:52 +02:00
Michele Caini
5228739c87 meta: make it work also with VS2017 2021-05-08 17:13:52 +02:00
Michele Caini
0f6d1268c7 meta: further reduced number of instantiations/size of symbols 2021-05-08 17:13:52 +02:00
Michele Caini
b2a4515d2b any: minor changes 2021-05-08 17:13:51 +02:00
Michele Caini
3576c80d33 meta_any: drastically reduce the number of instantiations/size of vtable 2021-05-08 17:13:51 +02:00
Michele Caini
77afd2d36c any: ::owner function (useful to also review meta_any) 2021-05-08 17:13:51 +02:00
Michele Caini
e6ddd5d9c2 any: drastically reduced the number of instantiations/size of vtable 2021-05-08 17:13:51 +02:00
Michele Caini
b9bf1a234e config: added ENTT_LAUNDER 2021-05-08 17:13:51 +02:00
Michele Caini
0ce9122449 any: reduce vtable size, simplify ref/cref creation 2021-05-08 17:13:51 +02:00
Michele Caini
dc41657872 any: empty vtable for void type 2021-05-08 17:13:51 +02:00
Michele Caini
64d9380031 *: updated messages for deprecated functions 2021-05-08 17:13:51 +02:00
Michele Caini
eca7484e30 doc: updated doc for any/meta_any 2021-05-08 17:13:51 +02:00
Michele Caini
2b5c393a13 meta: forward_as_meta 2021-05-08 17:13:51 +02:00
Michele Caini
0817d416a3 meta: make_meta_any -> make_meta 2021-05-08 17:13:51 +02:00
Michele Caini
ccff753305 any: forward_as_any 2021-05-08 17:13:51 +02:00
Michele Caini
11481a430a meta: minor changes 2021-05-08 17:13:51 +02:00
Michele Caini
e252b22735 test: code coverage 2021-05-08 17:13:51 +02:00
Michele Caini
a32ca8eb1d doc: updated doc for any/meta_any/poly 2021-05-08 17:13:50 +02:00
Michele Caini
1979a2279f poly: removed std::reference_wrapper support (incomplete, breaking change) 2021-05-08 17:13:50 +02:00
Michele Caini
1434f942dd *: make it work with VS2017 2021-05-08 17:13:50 +02:00
Michele Caini
955d325f07 meta_any: deprecated std::reference_wrapper support 2021-05-08 17:13:50 +02:00
Michele Caini
3fac3fe2d7 any: deprecate std::reference_wrapper support 2021-05-08 17:13:50 +02:00
Michele Caini
6ff217e74e meta: removed any reference to std::reference_wrapper/std::ref/std::cref 2021-05-08 17:13:50 +02:00
Michele Caini
cd667fe34b meta: make_meta_any 2021-05-08 17:13:50 +02:00
Michele Caini
3d6202ecfd any: make_any 2021-05-08 17:13:50 +02:00
Michele Caini
c40f0ef2bb type traits: is_equality_comparable support for nlohmann json like types (where T::value_type is T) - close #701 2021-05-08 10:43:27 +02:00
Michele Caini
c4b169edd1 registry: make ::remove and ::erase work also with iterators returned by views 2021-05-07 11:16:32 +02:00
Michele Caini
c6bba98828 registry: ::destroy review 2021-05-06 16:34:36 +02:00
Michele Caini
e5d4f1bb58 entity: deprecate handle::remove_all and registry::remove_all 2021-05-06 16:33:43 +02:00
Michele Caini
cf522d60ca doc: updated doc for handle 2021-05-06 10:31:43 +02:00
Michele Caini
daf72a7c61 entity: registry ::remove/::erase entity/range and safe/unsafe versions (close #486) 2021-05-04 11:36:02 +02:00
Michele Caini
f116ad0594 test: use iterators in the poly storage example 2021-05-03 22:29:39 +02:00
Michele Caini
e59d40834d any: reduce vtable options 2021-04-29 11:17:15 +02:00
Michele Caini
d06328af7f any: small review 2021-04-29 10:24:18 +02:00
Michele Caini
baa9d7d836 any: avoid invalid state from assignment operator for non-copyable types 2021-04-29 10:05:01 +02:00
Michele Caini
b030df55ee registry: clean up 2021-04-22 08:55:20 +02:00
Michele Caini
64eb8a2d0d sparse_set: ::remove returns number of actually removed elements 2021-04-22 08:42:35 +02:00
Michele Caini
7d7c36e0c7 registry: clean up ::remove_all 2021-04-21 16:50:33 +02:00
Michele Caini
9ad5768050 registry: clean up ::clear 2021-04-21 16:46:39 +02:00
Michele Caini
1cafbcff38 sparse_set: remove (and range-remove) if exists 2021-04-21 16:39:51 +02:00
Michele Caini
4bbf93fd0c sparse_set: remove -> erase 2021-04-21 15:46:02 +02:00
Michele Caini
120918bc37 any: refine the aggregate support to make any closer to its standard counterpart 2021-04-21 13:45:51 +02:00
Michele Caini
253448cffb sparse_set: suppress warnings for unused variables 2021-04-20 12:38:06 +02:00
Michele Caini
26f19fb90e storage: cleanup 2021-04-20 12:37:50 +02:00
Michele Caini
e8f982e909 view/group: raw() isn't bound to pointer types 2021-04-19 16:05:08 +02:00
Michele Caini
0fa433187e storage: valid state on component removal (allows component destructors to access pools) 2021-04-19 15:34:41 +02:00
Michele Caini
b0069299ea meta_any: avoid dereferencing C-style arrays 2021-04-15 11:45:10 +02:00
Michele Caini
375d5d3e9b doc: cleanup 2021-04-14 15:44:33 +02:00
Michele Caini
2d9398ae1a entity: handle review 2021-04-14 15:44:27 +02:00
Michele Caini
fc69e91636 test: refined custom id example 2021-04-14 15:44:17 +02:00
Michele Caini
5c56cbd672 process: dead -> finished 2021-04-13 09:02:29 +02:00
Michele Caini
a42255158d config: added error messages to ENTT_ASSERT 2021-04-13 09:02:29 +02:00
Michele Caini
f3d10a97df now working on v3.8.0 2021-04-13 09:02:26 +02:00
Michele Caini
5d15a3d69f updated single include file 2021-04-13 08:46:28 +02:00
Michele Caini
94292872dc ready to cut v3.7.1 (general quality of life improvements, no bug fixes, no new feature) 2021-04-12 23:37:53 +02:00
Michele Caini
5c8a1e7d10 type traits: removed is_std_hashable[_v] 2021-04-12 23:37:46 +02:00
Michele Caini
e98d8426bb meta: treat T(*)[N] as non-pointer-like types 2021-04-12 15:59:35 +02:00
Michele Caini
77f80cecf9 test: re-enable tests left commented by mistake 2021-04-12 15:59:31 +02:00
Michele Caini
bce26a1499 meta: remove dependency on std::array 2021-04-12 15:59:28 +02:00
Michele Caini
453f1c6edc test: std::strcmp ... 🤦‍♂️ 2021-04-12 15:59:20 +02:00
Michele Caini
9f8a36f2c9 any/meta: cleanup/better traits usage 2021-04-12 15:58:59 +02:00
Michele Caini
e6a5945463 meta_any: suppress a warning from clang 2021-04-12 15:56:01 +02:00
Michele Caini
60393fbc5f meta_any: decays Type on storage construction to handle correctly const T[N] types (close #687) 2021-04-12 15:55:59 +02:00
Michele Caini
b9a925dbd4 test: avoid regressions with any and deduced const T[N] types 2021-04-12 15:55:57 +02:00
Michele Caini
5dbdb1bcb5 any: internal support for both aggreagates and non-aggregates (qol enhancement :) 2021-04-12 15:55:55 +02:00
Michele Caini
d55cefc086 meta_any: no redundant copies, better perf 2021-04-12 15:55:53 +02:00
Michele Caini
5380e6d98b any: no redundant copies, better perf 2021-04-12 15:55:49 +02:00
Michele Caini
45cc24e0b8 any: favor aggregate initialization upon construction 2021-04-12 15:55:45 +02:00
Michele Caini
76bf1791eb test: cleanup 2021-04-12 13:02:23 +02:00
Michele Caini
7e9a4c4b16 view: initialize all data members on construction 2021-04-12 12:52:22 +02:00
Michele Caini
75cd5f169f *: review deduction guides 2021-04-12 12:48:36 +02:00
Michele Caini
10636c82a2 view/group: review extended iterable mode (close #686) 2021-04-07 10:33:52 +02:00
Michele Caini
04a6729963 build_system: minor changes 2021-04-06 16:49:11 +02:00
Michele Caini
b5617398ad doc: fixed typo 2021-04-06 16:47:20 +02:00
Michele Caini
da14641ccb meta: reduce instantiations due to meta_prop 2021-04-06 16:47:14 +02:00
Michele Caini
3997bd2396 meta: minor changes 2021-04-06 16:47:03 +02:00
Michele Caini
c876350e05 updated single include file 2021-04-02 12:04:31 +02:00
Michele Caini
1e07b981f0 build_system: manually install missing compilers 2021-04-01 14:42:10 +02:00
Michele Caini
756e909f5e *: (static) assert messages 2021-03-31 15:10:15 +02:00
Michele Caini
2404392daf meta_any: vtable review for empty/void types 2021-03-30 15:55:48 +02:00
Michele Caini
bf6576f5af any: use std::addressof to get the address of an object reliably 2021-03-30 15:27:49 +02:00
Michele Caini
158c2ab76e delegate: removed redundant calls to delegate constructors 2021-03-30 11:52:16 +02:00
Michele Caini
56fa50385e build system: minor changes 2021-03-29 23:32:35 +02:00
Michele Caini
eb05ab5fab test: code coverage 2021-03-29 23:30:13 +02:00
Michele Caini
7f87b637d5 workflow: use lcov to process codecoverage 2021-03-29 23:12:34 +02:00
Michele Caini
4b213adc75 test: cleanup 2021-03-29 19:03:10 +02:00
Michele Caini
7e7d5bbf17 registry: suppress a wrong warning from gcc 7.5 2021-03-29 19:00:09 +02:00
Michele Caini
e18c0a0c19 test: verify that dtor isn't invoked for a meta_any that wraps a reference 2021-03-29 17:01:05 +02:00
Michele Caini
02b6ffc771 build system: add ubsan to the CI 2021-03-29 12:19:52 +02:00
Michele Caini
340d66c24c build system: split build and sanitizer runs 2021-03-29 12:07:49 +02:00
Michele Caini
2cbf9d3620 build system: added poly.md to fake docs all target 2021-03-29 12:03:36 +02:00
Michele Caini
11e6793544 build system: cleanup 2021-03-29 12:00:42 +02:00
Michele Caini
d07e0336a2 test: fix ub in the tests 2021-03-29 10:28:58 +02:00
Michele Caini
7dbfb3d9ae test: code coverage (and try to please new gcov) 2021-03-29 08:23:30 +02:00
Michele Caini
02ef5db6e3 test: cleanup 2021-03-28 00:02:51 +01:00
Michele Caini
bb43bb5508 test: code coverage 2021-03-27 23:59:13 +01:00
Michele Caini
a771f083a7 process: added rejected state 2021-03-27 23:19:15 +01:00
Michele Caini
67f8bce1dd test: code coverage 2021-03-27 23:03:24 +01:00
Michele Caini
3b6dd23598 scheduler: small review for perf improvements 2021-03-27 22:48:54 +01:00
Michele Caini
66bbeae7da test: code coverage 2021-03-27 22:47:15 +01:00
Michele Caini
059334a861 test: code coverage 2021-03-27 18:14:36 +01:00
Michele Caini
f874c8309f registry: make vs2017 happy again 2021-03-26 15:35:11 +01:00
Michele Caini
27be812cc3 registry: small review of const correctness for components 2021-03-26 15:11:37 +01:00
Michele Caini
2adc2e97e1 registry: a "better" get 2021-03-26 14:49:42 +01:00
Michele Caini
dc4279a2d0 any/poly: review align parameter and default values (close #676) 2021-03-26 13:05:52 +01:00
Michele Caini
85bff4525a registry: non-const try_get doesn't create pools anymore 2021-03-26 09:38:58 +01:00
Michele Caini
bd648c0745 registry: avoid embedding asserts with return expressions 2021-03-25 11:13:50 +01:00
Michele Caini
d5e7005edd doc: minor changes to make doxygen happy 2021-03-25 09:46:06 +01:00
Michele Caini
8913c7ea18 poly: make vs2017 happy again 2021-03-24 13:53:02 +01:00
Michele Caini
0e2afe4f98 resource: remove the requirement to use shared_ptr (close #679) 2021-03-24 12:03:43 +01:00
Michele Caini
a936f7cdbe doc: added a note about configurable alignment requirements for any and poly 2021-03-24 12:03:43 +01:00
Michele Caini
44fa466618 poly: support for alignment requirements 2021-03-24 12:03:43 +01:00
Michele Caini
b05d84e8e3 test: minor changes 2021-03-24 12:03:43 +01:00
Michele Caini
7787c1ddd7 poly: redefine poly to basic_poly, introduce poly alias declaration 2021-03-24 12:03:43 +01:00
Michele Caini
52bfddd2e7 any: support for sbo storage alignment (close #676) 2021-03-24 12:03:43 +01:00
Michele Caini
f59adcdc37 test: no sbo overaligned any 2021-03-24 12:03:43 +01:00
Michele Caini
e996d3398f any: take in consideration alignment requirements for in_situ test (see #676) 2021-03-24 12:03:43 +01:00
Michele Caini
d73892d25a build system: more on pkg-config + updated README file 2021-03-18 13:48:38 +01:00
Alexandru-Sergiu Marton
1ebf614d79 build system: add pkg-config support (#674) 2021-03-18 13:48:38 +01:00
Michele Caini
f9c995f03f storage: cleanup 2021-03-18 13:47:53 +01:00
Michele Caini
2dcdd561f0 registry: minor changes 2021-03-17 16:10:00 +01:00
Michele Caini
90f97aa8d7 doc: updated try-it link (close #678) 2021-03-17 15:10:10 +01:00
Michele Caini
5a4b067cee view:
* removed view_pack and its tests
* added the possibility of combining different views
* updated documentation
2021-03-17 14:57:51 +01:00
Michele Caini
cabcc761c6 view: uniform design (prepare for chaining) 2021-03-16 17:14:20 +01:00
Michele Caini
a40ac1c46c registry: minor changes 2021-03-16 13:38:29 +01:00
Michele Caini
85ddfc4d21 test: clean up 2021-03-16 13:13:55 +01:00
Michele Caini
037a35df1d storage: removed storage category 2021-03-15 18:47:17 +01:00
Michele Caini
64c753023e doc: minor changes 2021-03-15 17:52:50 +01:00
Michele Caini
9d72ffb9fe test: minor changes 2021-03-12 13:52:46 +01:00
Michele Caini
4162d4fbc6 type_info: a more reliable operator bool 2021-03-12 09:33:42 +01:00
Michele Caini
a4d16bffd3 any: minor changes 2021-03-12 08:42:41 +01:00
Michele Caini
bcaf1489c0 meta: extended template info support 2021-03-11 12:17:48 +01:00
Michele Caini
a85712e8aa meta:
* removed has_meta_sequence_container[_v]
* removed has_meta_associative_container[_v]
2021-03-11 10:40:13 +01:00
Michele Caini
0f95d02cdc any: minor changes 2021-03-10 08:55:02 +01:00
Michele Caini
c151d55237 dispatcher: help std::is_copy_constructible to solve its problems with std::vector :) 2021-03-10 08:51:06 +01:00
Michele Caini
785cd6bc11 storage (close #675):
* stable sigh_storage_mixin::swap_and_pop
* added tests to avoid regressions
2021-03-09 16:09:19 +01:00
Michele Caini
8ec7e3cc97 test: suppress warnings for unused variables 2021-03-09 12:25:32 +01:00
Michele Caini
e376493970 test: removed unnecessary static functions 2021-03-09 12:17:00 +01:00
Michele Caini
08e2322d79 test: meta corner cases and re-registration 2021-03-09 12:11:25 +01:00
Michele Caini
2832767daa meta: updated doc 2021-03-09 12:10:55 +01:00
Michele Caini
62e12ee0aa meta: no need to check the instance in the dtor wrapper 2021-03-09 10:34:06 +01:00
Michele Caini
80e73c1089 meta:
* props no longer depend on the value type
* more tests for meta_prop
2021-03-09 10:26:21 +01:00
Michele Caini
4f93995bd7 test: suppress warnings + increase code coverage 2021-03-09 10:24:28 +01:00
Michele Caini
03c7dac92f meta: internal review to fully support re-registration 2021-03-09 09:50:48 +01:00
Michele Caini
d0b93f565a meta: prop key is returned by const reference rather than by copy 2021-03-08 15:22:25 +01:00
Michele Caini
1512fbae55 meta: safer factory, no-op re-registration 2021-03-08 14:52:46 +01:00
Michele Caini
4d91afdf9f meta: allow range-reset 2021-03-08 14:44:08 +01:00
Michele Caini
c239c3fea3 factory: avoid re-registration of data and type if already registered 2021-03-08 09:29:54 +01:00
Michele Caini
a0b431e8c4 doc: context aliased properties 2021-03-08 09:01:13 +01:00
Michele Caini
005e03aeb3 registry:
* context const correctness
* added support for aliased variables to context
* code coverage to avoid regressions
2021-03-06 22:51:01 +01:00
Michele Caini
6002d373e2 meta: factory ignores already existing unnamed meta objects 2021-03-05 11:32:32 +01:00
Michele Caini
a53066424b meta: cleanup/minor changes 2021-03-04 10:22:36 +01:00
Michele Caini
38ab02ff88 doc: minor changes 2021-03-03 16:49:20 +01:00
Michele Caini
fee0b29a0b registry: added missing template keyword (thanks msvc for accepting it anyway) 2021-03-03 14:24:38 +01:00
Michele Caini
2553695029 meta: minor changes 2021-03-03 12:26:59 +01:00
Michele Caini
301939983f registry: minor changes 2021-03-03 12:26:11 +01:00
Michele Caini
cd9ae1fbad poly: ::emplace support for reference types 2021-03-03 12:25:34 +01:00
Michele Caini
54e9bc86cc doc: updated links 2021-03-02 12:05:49 +01:00
Michele Caini
a1dd4c28c3 view: clean up 2021-03-02 11:21:40 +01:00
Michele Caini
6d3857f337 registry: clean up 2021-03-01 23:55:57 +01:00
Michele Caini
c4be3e731a poly: fwd.hpp 2021-02-28 23:29:36 +01:00
Michele Caini
ea4407b847 doc: fixed typo 2021-02-28 23:24:32 +01:00
Michele Caini
a62a83044f poly: make it work also with non-default Len 2021-02-27 17:34:09 +01:00
Michele Caini
e7e7b06744 doc: updated links 2021-02-26 14:36:20 +01:00
Michele Caini
2222e31885 config: ENTT_PAGE_SIZE sets the number of elements of a page, not the size in bytes 2021-02-25 00:18:04 +01:00
Michele Caini
c3f7f83c55 registry/view (close #660):
* const registry::view is no longer thread safe
* views are always valid when created from a registry, either const or not
* added a note to the doc about const registry and multithreading
2021-02-23 17:01:38 +01:00
Michele Caini
86e18f68c4 sparse_set: added unchecked operator[](pos) 2021-02-23 15:01:05 +01:00
Michele Caini
cc7d9e03d9 doc: updated list of similar projects 2021-02-23 14:39:37 +01:00
Michele Caini
c8639ae434 meta: minor changes 2021-02-23 12:00:59 +01:00
Michele Caini
85cf32516e view: minor changes 2021-02-22 16:52:54 +01:00
Michele Caini
bc85817b07 doc: updated doc for type_seq and the like 2021-02-22 16:39:42 +01:00
Michele Caini
1016394be5 *: minor changes 2021-02-22 16:35:36 +01:00
Michele Caini
de35cbf4af type_info: add conversion operators to underlying types (close #664) 2021-02-22 14:48:35 +01:00
Michele Caini
bb1acee36a entity:
* sparse_set: added optional user data to ::remove and ::clear
* sparse_set: remove the payload, it was really meh
* storage: review sigh_storage_mixin, simplified storage_adapter_mixin
* registry: pass itself twhen adding/patchin/removing components

test: updated tests for sparse set and poly storage
examples: added a test to avoid regressions on storage_adapter_mixin
2021-02-22 14:32:29 +01:00
Michele Caini
c6a2ed78c3 poly: add missing template keyword 2021-02-22 09:45:55 +01:00
Michele Caini
7729958082 doc: updated links 2021-02-21 16:39:29 +01:00
Michele Caini
ccdaec86b3 poly: added configurable sbo size (0 -> forced dyn) 2021-02-20 00:12:43 +01:00
Michele Caini
42afeef993 any: updated tests (sbo vs zeroed sbo size) 2021-02-19 23:46:44 +01:00
Michele Caini
3d06911886 test: code coverage (codecov is odd sometimes) 2021-02-19 16:26:55 +01:00
Michele Caini
98f929e41e any: try to get around an issue with clang only 2021-02-19 16:23:33 +01:00
Michele Caini
94d4e0231e *: suppress a few warnings + fix a call that msvc accepted by mistake 2021-02-19 16:11:19 +01:00
Michele Caini
e60cdb2e3b config: update ENTT_PAGE_SIZE 2021-02-19 15:50:09 +01:00
Michele Caini
550c021097 any: make VS happy :) 2021-02-19 14:56:39 +01:00
Michele Caini
1aec7f71b8 any: doc + test for configurable sbo 2021-02-19 14:45:26 +01:00
Michele Caini
07565927f2 any:
* added configurable sbo size (0 -> forced dyn)
* reverted changes for forced dyn vtable
2021-02-19 12:51:30 +01:00
Michele Caini
fda78ede1b any: prepare for in-place forced dynamic construction 2021-02-19 11:48:25 +01:00
Michele Caini
1249a4f8d3 any/meta_any/poly: as_ref is a member function, no longer an in-class friend function exported in the global namespace 2021-02-19 11:19:32 +01:00
Michele Caini
4e631f1536 meta_handle: deleted copy constructor (close #661) 2021-02-18 23:49:39 +01:00
Michele Caini
1757dbc225 registry: get around an issue of gcc 2021-02-16 17:12:02 +01:00
Michele Caini
8149e204d2 *: refine code that msvc accepts by mistake (🤦‍♂️) 2021-02-16 15:55:45 +01:00
Michele Caini
412f2ef63e doc: updated/reviewed 2021-02-16 15:08:34 +01:00
Michele Caini
ea33673daa registry: faster, better ::clear 2021-02-16 14:53:33 +01:00
Michele Caini
169b816613 entity:
* sparse_set: stable virtual remove
* basic_storage: fake get for empty types,
* sigh_storage_mixin: use swap_and_pop, drop ::remove, ignore emptiness
* poly_storage: ::remove is no longer part of its interface by default
2021-02-16 14:46:42 +01:00
Michele Caini
48edf077fc sigh_storage_mixin:
* Owner as template parameter (to make it reusable in other contexts)
* Use storage payload for retrieving the owner from the storage
2021-02-16 10:44:03 +01:00
Michele Caini
91db153710 registry: rebind pools from move constructor/assignment operator 2021-02-15 18:11:02 +01:00
Michele Caini
ea1f010b1a sparse_set/storage: swap_at and swap_and_pop are protected and non-final 2021-02-15 15:57:17 +01:00
Michele Caini
935a852745 storage:
* Removed storage_adapter_mixin (intermediate step towards the owner-as-payload model)
* The class sigh_storage_mixin expects a plain storage as a base class (intermediate step towards the owner-as-payload model)
* Further reduced the number of instantiations
* All storage classes must expose a ::patch function
2021-02-15 15:56:27 +01:00
Michele Caini
0eb6d7de3c sparse_set: added opaque payload (mainly meant to attach owners for augmented storage mixins) 2021-02-15 15:15:41 +01:00
Michele Caini
3118cd4fa5 sparse_set: added ::at to get the entity at a given position, if any 2021-02-15 14:56:18 +01:00
Michele Caini
18832fcb37 core: make type_id [[nodiscard]] (see #658) 2021-02-14 11:04:12 +01:00
Michele Caini
612554f6af registry: minor changes 2021-02-12 18:18:39 +01:00
Michele Caini
1c794591b9 sparse_set: review sort_n 2021-02-12 15:47:52 +01:00
Michele Caini
a8d95b284d meta: general renaming, size -> arity when it returns the arity of a function 2021-02-12 11:05:07 +01:00
Michele Caini
772c2dba2f type traits: add is_std_hashable[_v] (see #629) 2021-02-11 11:57:55 +01:00
Kartik Saranathan
9d1a210c97 core: PS4 build fix for entt::any (#657)
PS4 headers doesn't define __cpp_lib_launder which fails to compile with -Werror
2021-02-11 10:16:28 +01:00
Michele Caini
b04b966db1 meta:
* updated meta_any::operator== to make it consistent across boundaries
* constness review for meta_type_node
2021-02-10 15:57:20 +01:00
Michele Caini
e2c61d90e9 meta: use meta_node internally to generate template_info 2021-02-09 18:27:40 +01:00
Michele Caini
d50655c4f7 test: suppress a couple of shadow warnings 2021-02-09 18:20:42 +01:00
Michele Caini
5e4c63736b meta: added minimal class template support to meta_type 2021-02-09 17:18:24 +01:00
Michele Caini
b90b71c8c0 meta: support for proxy pointers 2021-02-09 10:57:50 +01:00
Michele Caini
722033e906 meta: split node.hpp and meta.hpp 2021-02-09 09:59:51 +01:00
Michele Caini
b6ec91fcd0 doc: updated links, removed broken one 2021-02-09 08:17:49 +01:00
Michele Caini
e94c0d003a any/registry:
* copying an entt::any always returns a copy of the contained object
* registry::storage returns references (eventually const) to the poly storage
2021-02-08 11:46:22 +01:00
Michele Caini
5c46ccb37e meta:
* avoid copies from meta_type::invoke (small perf improvement)
* meta_handle::operator* no longer available
* added meta_handle::operator bool
2021-02-08 11:33:53 +01:00
Michele Caini
fd989feea3 any: review copy, prepare for #652 2021-02-06 23:22:32 +01:00
Michele Caini
1b53e83dde any: minor changes 2021-02-06 18:35:08 +01:00
Michele Caini
3ebe827b51 doc: minor changes 2021-02-05 11:10:04 +01:00
Michele Caini
b8266e1169 registry: minor changes 2021-02-04 16:12:06 +01:00
Michele Caini
4efbe24607 test: minor changes (code coverage) 2021-02-04 12:14:35 +01:00
Michele Caini
e96ac1f6ff handle: rename ::has/::any to ::all_of/any_of 2021-02-04 12:03:38 +01:00
Michele Caini
14915368c7 test: avoid some ASSERT_DEATH that don't trigger in release mode 2021-02-04 11:50:52 +01:00
Michele Caini
0ff5c18743 registry: rename ::any to ::any_of 2021-02-04 09:45:11 +01:00
Michele Caini
240814cc85 registry: rename ::has to ::all_of 2021-02-04 09:36:28 +01:00
Michele Caini
1912350cc6 registry: use entt::any to store context variables 2021-02-04 09:21:27 +01:00
Michele Caini
146faa6008 doc: updated links 2021-02-04 08:20:03 +01:00
Michele Caini
e5fa67850b sparse_set/registry: avoids UB with sparse_set::clear and component-less registry::clear 2021-02-03 16:18:25 +01:00
Michele Caini
1377b51341 doc: minor changes 2021-02-03 13:07:39 +01:00
Ευάριστος
db67fb3539 build system: delete @bazel_tools//src/conditions:windows_msys. (#649)
This condition is no longer available from Bazel 4.0.0.
2021-02-03 10:08:38 +01:00
Michele Caini
6d216406f3 registry: minor changes 2021-02-03 10:04:16 +01:00
Michele Caini
ab225eaec1 registry: updated precondition of ::assign 2021-02-02 23:37:09 +01:00
Michele Caini
4d6d9e567d registry: minor changes 2021-02-02 13:53:51 +01:00
Michele Caini
0fcf0142ba sparse_set: prepare for detached pools 2021-02-02 12:59:07 +01:00
Michele Caini
ab907e4fef updated README 2021-02-01 10:04:54 +01:00
Michele Caini
1c1c3eb271 meta: added support to self pointer in meta_any 2021-01-29 22:13:31 +01:00
Michele Caini
a03a569534 meta: support for std::vector<bool> and the like 2021-01-29 18:58:18 +01:00
Michele Caini
309fb0fa83 test: updated storage poly example (close #644) 2021-01-29 11:10:01 +01:00
Michele Caini
14c1431848 doc: fixed typo (close #646) 2021-01-29 10:06:22 +01:00
Michele Caini
c3dc32415b meta: make meta utility public and freely available to users 2021-01-28 12:19:02 +01:00
Michele Caini
44d6246278 doc: fixed typo 2021-01-28 12:18:33 +01:00
Michele Caini
5bbd7e03f9 meta: prepare to make internal utilities public 2021-01-27 16:47:01 +01:00
Michele Caini
f5f463b411 any: relaxed checks/constraints 2021-01-26 16:45:02 +01:00
Michele Caini
6eaefbe25c doc: update doc of the runtime reflection system (close #641) 2021-01-26 08:41:41 +01:00
Michele Caini
4b5c2c85a5 doc: updated inline doc (close #640) 2021-01-26 08:36:56 +01:00
Michele Caini
0f951cd322 test: suppress a warning due to an unused variable 2021-01-25 15:22:46 +01:00
Michele Caini
cf0da32fd0 meta: iterators of meta associative containers return keys by const reference rather than by copy 2021-01-25 15:12:59 +01:00
Michele Caini
5a3085c42b registry: removed data<T> and raw<T> (use view<T>::data/::raw instead) 2021-01-25 12:29:22 +01:00
Michele Caini
42cc480bf5 doc: updated links and TODO file 2021-01-23 18:39:08 +01:00
Michele Caini
2822eda858 poly: added ::reset 2021-01-23 18:22:15 +01:00
Michele Caini
6c804b5ca2 meta: added meta_any::reset 2021-01-23 18:19:11 +01:00
Michele Caini
5149d1395e any: minor changes 2021-01-23 18:18:54 +01:00
Michele Caini
f75a2dab8d any (meta/poly): make T& in place construction work with references rather than pointers 2021-01-23 18:09:41 +01:00
Michele Caini
a045c88c61 any: added ::reset 2021-01-23 18:04:03 +01:00
Michele Caini
a5fdf917df test: minor changes (code coverage) 2021-01-22 20:13:15 +01:00
Michele Caini
80a0e47f1a core: more tests for hashed_string (code coverage) 2021-01-22 15:39:03 +01:00
Michele Caini
a304313ad4 any: more tests for the support to not copyable types (code coverage) 2021-01-22 15:28:24 +01:00
Michele Caini
05b1d5a4da meta: more tests for entt::resolve (code coverage) 2021-01-22 15:22:36 +01:00
Michele Caini
edeef3541c meta: tests (code coverage) for meta_ctor (implicitly generated default constructor) 2021-01-22 15:20:03 +01:00
Michele Caini
cd929d8e65 meta: review + tests (code coverage) for implicitly generated default constructors 2021-01-22 15:17:58 +01:00
Michele Caini
b005971427 doc: removed broken badge 2021-01-22 15:17:19 +01:00
Michele Caini
c35614fb63 meta: more tests for meta pointers (code coverage) 2021-01-22 15:08:45 +01:00
Michele Caini
18e16b09f2 any: more tests (code coverage) 2021-01-22 14:57:50 +01:00
Michele Caini
b18b76c1f0 meta: as_ref_t adapts to const objects/const-ref return types 2021-01-22 11:41:17 +01:00
Michele Caini
c9f65267ba meta: factory getter, better is_invocable_v test 2021-01-22 09:17:37 +01:00
Michele Caini
cce52c673b view: use get_as_tuple internally 2021-01-22 09:17:00 +01:00
Michele Caini
fdde216f0e doc: minor changes 2021-01-21 10:32:25 +01:00
Michele Caini
d1901a97d1 doc: minor changes 2021-01-21 09:59:43 +01:00
Michele Caini
eb2077b95b meta: suppress warnings 2021-01-20 19:51:59 +01:00
Michele Caini
fad3bdeed4 meta: ::reset doesn't remove the implicitly generated default constructors 2021-01-20 15:29:35 +01:00
Michele Caini
e1a537e547 doc: minor changes 2021-01-20 15:20:51 +01:00
Michele Caini
ba098e1199 any: use actual constructors to be more like std::any 2021-01-15 17:24:10 +01:00
Michele Caini
0ec755fccf meta: iterable base types as instances of meta_type 2021-01-15 14:34:27 +01:00
Michele Caini
997dd433b3 doc: type list and value list 2021-01-15 11:04:06 +01:00
Michele Caini
4aea1567db type_traits: make is_equality_comparable work with all major compilers 2021-01-15 10:37:14 +01:00
Michele Caini
446c8df3b4 type_traits: added type_list_diff[_t] 2021-01-15 08:36:22 +01:00
Michele Caini
f4bd868d6a type_traits: is_equality_comparable, support for standard containers 2021-01-14 12:56:31 +01:00
Michele Caini
8490264af3 doc: updated list for similar projects 2021-01-13 15:21:01 +01:00
Michele Caini
505cfdd193 any: detect C-style arrays and use delete[] when needed 2021-01-13 13:30:10 +01:00
Michele Caini
39ecd1545c meta: support for dereferencing pointers to functions 2021-01-13 13:14:21 +01:00
Michele Caini
9b80c0028d meta:
* implicitly generated default constructor (overridable)
* pointer-like to void support (no dereference)
2021-01-13 11:53:15 +01:00
Michele Caini
35276b55af any: support for non-copyable types 2021-01-12 16:24:18 +01:00
Michele Caini
eb63dd6227 meta: merged internal.hpp with meta.hpp 2021-01-11 15:08:44 +01:00
Michele Caini
9f24b3c584 meta: minor changes 2021-01-11 14:58:50 +01:00
Michele Caini
53e951d96d meta: removed internal::meta_range 2021-01-11 12:31:55 +01:00
Michele Caini
17dd386937 meta_any: minor changes 2021-01-11 11:53:06 +01:00
Michele Caini
3eb00382e7 meta: removed meta_base, meta_base_node is used internally when needed 2021-01-11 10:46:51 +01:00
Michele Caini
98b5f7a26a meta: removed meta_conv, meta_conv_node is used internally when needed 2021-01-11 10:34:30 +01:00
Michele Caini
fb70ec1cb0 doc: minor changes 2021-01-11 09:46:47 +01:00
Michele Caini
47ada87ba2 now working on version 3.7.0 2021-01-09 17:15:42 +01:00
120 changed files with 17306 additions and 10050 deletions

View File

@@ -10,26 +10,51 @@ jobs:
strategy:
matrix:
compiler: [
g++-8, g++-9, g++,
clang++-8, clang++-9, clang++
g++-7, g++-8, g++-9, g++,
clang++-8, clang++-9, clang++-10, clang++
]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install g++-7
if: ${{ matrix.compiler == 'g++-7' }}
run: |
sudo apt-get update
sudo apt-get install g++-7 -y
- name: Install g++-8
if: ${{ matrix.compiler == 'g++-8' }}
run: |
sudo apt-get update
sudo apt-get install g++-8 -y
- name: Install clang-8
if: ${{ matrix.compiler == 'clang++-8' }}
run: |
sudo apt-get update
sudo apt-get install clang-8 -y
- name: Install clang-9
if: ${{ matrix.compiler == 'clang++-9' }}
run: |
sudo apt-get update
sudo apt-get install clang-9 -y
- name: Install clang-10
if: ${{ matrix.compiler == 'clang++-10' }}
run: |
sudo apt-get update
sudo apt-get install clang-10 -y
- name: Compile tests
working-directory: build
env:
CXX: ${{ matrix.compiler }}
run: |
cmake -DENTT_USE_ASAN=ON -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
- name: Run tests
working-directory: build
env:
CTEST_OUTPUT_ON_FAILURE: 1
run: ctest --timeout 5 -C Debug -j4
run: ctest --timeout 10 -C Debug -j4
windows:
timeout-minutes: 10
@@ -62,7 +87,7 @@ jobs:
working-directory: build
env:
CTEST_OUTPUT_ON_FAILURE: 1
run: ctest --timeout 5 -C Debug -j4
run: ctest --timeout 10 -C Debug -j4
macos:
timeout-minutes: 10
@@ -79,4 +104,4 @@ jobs:
working-directory: build
env:
CTEST_OUTPUT_ON_FAILURE: 1
run: ctest --timeout 5 -C Debug -j4
run: ctest --timeout 10 -C Debug -j4

View File

@@ -9,25 +9,30 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Compile tests
working-directory: build
env:
CXXFLAGS: "-O0 --coverage -fno-inline -fno-inline-small-functions -fno-default-inline"
CXX: g++
run: |
cmake -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON ..
make -j4
- name: Run tests
working-directory: build
env:
CTEST_OUTPUT_ON_FAILURE: 1
run: ctest --timeout 5 -C Debug -j4
- name: Upload coverage to Codecov
working-directory: build
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
run: |
wget https://codecov.io/bash -O codecov
chmod +x codecov
./codecov -t $CODECOV_TOKEN -B $GITHUB_REF -s .
- uses: actions/checkout@v2
- name: Compile tests
working-directory: build
env:
CXXFLAGS: "--coverage -fno-inline"
CXX: g++
run: |
cmake -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON ..
make -j4
- name: Run tests
working-directory: build
env:
CTEST_OUTPUT_ON_FAILURE: 1
run: ctest --timeout 10 -C Debug -j4
- name: Collect data
working-directory: build
run: |
sudo apt install lcov
lcov -c -d . -o coverage.info
lcov -l coverage.info
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: build/coverage.info
name: EnTT
fail_ci_if_error: true

View File

@@ -20,8 +20,7 @@ jobs:
working-directory: build
env:
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
run: |
git clone https://$GITHUB_ACTOR:$PERSONAL_ACCESS_TOKEN@github.com/$GITHUB_ACTOR/$GH_REPO.git
run: git clone https://$GITHUB_ACTOR:$PERSONAL_ACCESS_TOKEN@github.com/$GITHUB_ACTOR/$GH_REPO.git
- name: Prepare formula
working-directory: build
run: |

29
.github/workflows/sanitizer.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: sanitizer
on: [push, pull_request]
jobs:
linux:
timeout-minutes: 10
strategy:
matrix:
compiler: [clang++]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Compile tests
working-directory: build
env:
CXX: ${{ matrix.compiler }}
run: |
cmake -DENTT_USE_SANITIZER=ON -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON ..
make -j4
- name: Run tests
working-directory: build
env:
CTEST_OUTPUT_ON_FAILURE: 1
run: ctest --timeout 10 -C Debug -j4

1
.gitignore vendored
View File

@@ -7,6 +7,7 @@ conan/test_package/build
.vscode
.vs
CMakeSettings.json
cpp.hint
# Bazel
/bazel-*

View File

@@ -9,7 +9,6 @@ cc_library(
copts = select({
"@bazel_tools//src/conditions:windows": _msvc_copts,
"@bazel_tools//src/conditions:windows_msvc": _msvc_copts,
"@bazel_tools//src/conditions:windows_msys": _msvc_copts,
"//conditions:default": _gcc_copts,
}),
)

View File

@@ -43,7 +43,7 @@ message(VERBOSE "* Copyright (c) 2017-2021 Michele Caini <michele.caini@gmail.co
message(VERBOSE "*")
option(ENTT_USE_LIBCPP "Use libc++ by adding -stdlib=libc++ flag if availbale." ON)
option(ENTT_USE_ASAN "Use address sanitizer by adding -fsanitize=address -fno-omit-frame-pointer flags" OFF)
option(ENTT_USE_SANITIZER "Enable sanitizers by adding -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined flags" OFF)
#
# Compiler stuff
@@ -85,9 +85,9 @@ target_include_directories(
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)
if(ENTT_USE_ASAN)
target_compile_options(EnTT INTERFACE $<$<CONFIG:Debug>:-fsanitize=address -fno-omit-frame-pointer>)
target_link_libraries(EnTT INTERFACE $<$<CONFIG:Debug>:-fsanitize=address -fno-omit-frame-pointer>)
if(ENTT_USE_SANITIZER)
target_compile_options(EnTT INTERFACE $<$<CONFIG:Debug>:-fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined>)
target_link_libraries(EnTT INTERFACE $<$<CONFIG:Debug>:-fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined>)
endif()
if(ENTT_HAS_LIBCPP)
@@ -96,6 +96,23 @@ endif()
target_compile_features(EnTT INTERFACE cxx_std_17)
#
# Install pkg-config file
#
set(EnTT_PKGCONFIG ${CMAKE_CURRENT_BINARY_DIR}/entt.pc)
configure_file(
${EnTT_SOURCE_DIR}/cmake/in/entt.pc.in
${EnTT_PKGCONFIG}
@ONLY
)
install(
FILES ${EnTT_PKGCONFIG}
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig
)
#
# Install EnTT
#
@@ -186,6 +203,7 @@ add_custom_target(
.github/workflows/build.yml
.github/workflows/coverage.yml
.github/workflows/deploy.yml
.github/workflows/sanitizer.yml
.github/FUNDING.yml
AUTHORS
CONTRIBUTING.md

209
README.md
View File

@@ -3,10 +3,9 @@
<!--
@cond TURN_OFF_DOXYGEN
-->
[![GitHub version](https://badge.fury.io/gh/skypjack%2Fentt.svg)](https://github.com/skypjack/entt/releases)
[![Build Status](https://github.com/skypjack/entt/workflows/build/badge.svg)](https://github.com/skypjack/entt/actions)
[![Coverage](https://codecov.io/gh/skypjack/entt/branch/master/graph/badge.svg)](https://codecov.io/gh/skypjack/entt)
[![Try online](https://img.shields.io/badge/try-online-brightgreen)](https://godbolt.org/z/cOUcm1)
[![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/)
[![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)
@@ -17,8 +16,7 @@ much more written in **modern C++**.<br/>
[Among others](https://github.com/skypjack/entt/wiki/EnTT-in-Action), it's used
in [**Minecraft**](https://minecraft.net/en-us/attribution/) by Mojang, the
[**ArcGIS Runtime SDKs**](https://developers.arcgis.com/arcgis-runtime/) by Esri
and the amazing [**Ragdoll**](https://ragdolldynamics.com/) Autodesk Maya
plugin.<br/>
and the amazing [**Ragdoll**](https://ragdolldynamics.com/).<br/>
If you don't see your project in the list, please open an issue, submit a PR or
add the [#entt](https://github.com/topics/entt) tag to your _topics_! :+1:
@@ -26,23 +24,21 @@ add the [#entt](https://github.com/topics/entt) tag to your _topics_! :+1:
Do you want to **keep up with changes** or do you have a **question** that
doesn't require you to open an issue?<br/>
Join the [gitter channel](https://gitter.im/skypjack/entt) or the
[discord server](https://discord.gg/5BjPWBd) and meet other users like you. The
more we are, the better for everyone.
Join the [gitter channel](https://gitter.im/skypjack/entt) and the
[discord server](https://discord.gg/5BjPWBd), meet other users like you. The
more we are, the better for everyone.<br/>
Don't forget to check the
[FAQs](https://github.com/skypjack/entt/wiki/Frequently-Asked-Questions) and the
[wiki](https://github.com/skypjack/entt/wiki) too. Your answers may already be
there.
Wondering why your **debug build** is so slow on Windows or how to represent a
**hierarchy** with components?<br/>
Check out the
[FAQ](https://github.com/skypjack/entt/wiki/Frequently-Asked-Questions) and the
[wiki](https://github.com/skypjack/entt/wiki) if you have these or other doubts,
your answers may already be there.
Do you want to support `EnTT`? Consider becoming a
[**sponsor**](https://github.com/users/skypjack/sponsorship).
Many thanks to [these people](https://skypjack.github.io/sponsorship/) and
**special** thanks to:
If you use `EnTT` and you want to say thanks or support the project, please
**consider becoming a
[sponsor](https://github.com/users/skypjack/sponsorship)**.<br/>
You can help me make the difference.
[Many thanks](https://skypjack.github.io/sponsorship/) to those who supported me
and still support me today.
[![mojang](https://user-images.githubusercontent.com/1812216/106253145-67ca1980-6217-11eb-9c0b-d93561b37098.png)](https://mojang.com)
[![imgly](https://user-images.githubusercontent.com/1812216/106253726-271ed000-6218-11eb-98e0-c9c681925770.png)](https://img.ly/)
# Table of Contents
@@ -50,16 +46,16 @@ and still support me today.
* [Code Example](#code-example)
* [Motivation](#motivation)
* [Performance](#performance)
* [Build Instructions](#build-instructions)
* [Integration](#integration)
* [Requirements](#requirements)
* [Library](#library)
* [Documentation](#documentation)
* [Tests](#tests)
* [Packaging Tools](#packaging-tools)
* [CMake](#cmake)
* [Packaging Tools](#packaging-tools)
* [pkg-config](#pkg-config)
* [Documentation](#documentation)
* [Tests](#tests)
* [EnTT in Action](#entt-in-action)
* [Contributors](#contributors)
* [License](#license)
* [Support](#support)
<!--
@endcond TURN_OFF_DOXYGEN
-->
@@ -103,7 +99,7 @@ Here is a brief, yet incomplete list of what it offers today:
Consider this list a work in progress as well as the project. The whole API is
fully documented in-code for those who are brave enough to read it.
It is also known that `EnTT` (version 3) is used in **Minecraft**.<br/>
It is also known that `EnTT` is used in **Minecraft**.<br/>
Given that the game is available literally everywhere, I can confidently say
that the library has been sufficiently tested on every platform that can come to
mind.
@@ -203,26 +199,7 @@ new features, mainly for fun.<br/>
If you want to contribute and/or have suggestions, feel free to make a PR or
open an issue to discuss your idea.
# Build Instructions
## Requirements
To be able to use `EnTT`, users must provide a full-featured compiler that
supports at least C++17.<br/>
The requirements below are mandatory to compile the tests and to extract the
documentation:
* `CMake` version 3.7 or later.
* `Doxygen` version 1.8 or later.
Alternatively, [Bazel](https://bazel.build) is also supported as a build system
(credits to [zaucy](https://github.com/zaucy) who offered to maintain it).<br/>
In the documentation below I'll still refer to `CMake`, this being the official
build system of the library.
If you are looking for a C++14 version of `EnTT`, check out the git tag `cpp14`.
## Library
# Integration
`EnTT` is a header-only library. This means that including the `entt.hpp` header
is enough to include the library as a whole and use it. For those who are
@@ -243,51 +220,33 @@ Use the line below to include only the entity-component system instead:
Then pass the proper `-I` argument to the compiler to add the `src` directory to
the include paths.
## Documentation
## Requirements
The documentation is based on [doxygen](http://www.doxygen.nl/).
To build it:
To be able to use `EnTT`, users must provide a full-featured compiler that
supports at least C++17.<br/>
The requirements below are mandatory to compile the tests and to extract the
documentation:
$ cd build
$ cmake .. -DENTT_BUILD_DOCS=ON
$ make
* `CMake` version 3.7 or later.
* `Doxygen` version 1.8 or later.
The API reference will be created in HTML format within the directory
`build/docs/html`. To navigate it with your favorite browser:
Alternatively, [Bazel](https://bazel.build) is also supported as a build system
(credits to [zaucy](https://github.com/zaucy) who offered to maintain it).<br/>
In the documentation below I'll still refer to `CMake`, this being the official
build system of the library.
$ cd build
$ your_favorite_browser docs/html/index.html
## CMake
<!--
@cond TURN_OFF_DOXYGEN
-->
The same version is also available [online](https://skypjack.github.io/entt/)
for the latest release, that is the last stable tag. If you are looking for
something more pleasing to the eye, consider reading the nice-looking version
available on [docsforge](https://entt.docsforge.com/): same documentation, much
more pleasant to read.<br/>
Moreover, there exists a [wiki](https://github.com/skypjack/entt/wiki) dedicated
to the project where users can find all related documentation pages.
<!--
@endcond TURN_OFF_DOXYGEN
-->
To use `EnTT` from a `CMake` project, just link an existing target to the
`EnTT::EnTT` alias.<br/>
The library offers everything you need for locating (as in `find_package`),
embedding (as in `add_subdirectory`), fetching (as in `FetchContent`) or using
it in many of the ways that you can think of and that involve `CMake`.<br/>
Covering all possible cases would require a treaty and not a simple README file,
but I'm confident that anyone reading this section also knows what it's about
and can use `EnTT` from a `CMake` project without problems.
## Tests
To compile and run the tests, `EnTT` requires *googletest*.<br/>
`cmake` will download and compile the library before compiling anything else.
In order to build the tests, set the CMake option `ENTT_BUILD_TESTING` to `ON`.
To build the most basic set of tests:
* `$ cd build`
* `$ cmake -DENTT_BUILD_TESTING=ON ..`
* `$ make`
* `$ make test`
Note that benchmarks are not part of this set.
# Packaging Tools
## Packaging Tools
`EnTT` is available for some of the most known packaging tools. In particular:
@@ -346,7 +305,58 @@ Note that benchmarks are not part of this set.
[documentation](https://build2.org/build2-toolchain/doc/build2-toolchain-intro.xhtml#guide-repositories)
for more details.
Consider this list a work in progress and help me to make it longer.
Consider this list a work in progress and help me to make it longer if you like.
## pkg-config
`EnTT` also supports `pkg-config` (for some definition of _supports_ at least).
A suitable file called `entt.pc` is generated and installed in a proper
directory when running `CMake`.<br/>
This should also make it easier to use with tools such as `Meson` or similar.
# Documentation
The documentation is based on [doxygen](http://www.doxygen.nl/). To build it:
$ cd build
$ cmake .. -DENTT_BUILD_DOCS=ON
$ make
The API reference will be created in HTML format within the directory
`build/docs/html`. To navigate it with your favorite browser:
$ cd build
$ your_favorite_browser docs/html/index.html
<!--
@cond TURN_OFF_DOXYGEN
-->
The same version is also available [online](https://skypjack.github.io/entt/)
for the latest release, that is the last stable tag. If you are looking for
something more pleasing to the eye, consider reading the nice-looking version
available on [docsforge](https://entt.docsforge.com/): same documentation, much
more pleasant to read.<br/>
Moreover, there exists a [wiki](https://github.com/skypjack/entt/wiki) dedicated
to the project where users can find all related documentation pages.
<!--
@endcond TURN_OFF_DOXYGEN
-->
# Tests
To compile and run the tests, `EnTT` requires *googletest*.<br/>
`cmake` will download and compile the library before compiling anything else.
In order to build the tests, set the `CMake` option `ENTT_BUILD_TESTING` to
`ON`.
To build the most basic set of tests:
* `$ cd build`
* `$ cmake -DENTT_BUILD_TESTING=ON ..`
* `$ make`
* `$ make test`
Note that benchmarks are not part of this set.
<!--
@cond TURN_OFF_DOXYGEN
@@ -368,18 +378,14 @@ open an issue or a PR and I'll be glad to add them to the list.
# Contributors
`EnTT` was written initially as a faster alternative to other well known and
open source entity-component systems. Nowadays this library is moving its first
steps. Much more will come in the future and hopefully I'm going to work on it
for a long time.<br/>
Requests for features, PR, suggestions ad feedback are highly appreciated.
Requests for features, PRs, suggestions ad feedback are highly appreciated.
If you find you can help me and want to contribute to the project with your
experience or you do want to get part of the project for some other reasons,
feel free to contact me directly (you can find the mail in the
If you find you can help and want to contribute to the project with your
experience or you do want to get part of the project for some other reason, feel
free to contact me directly (you can find the mail in the
[profile](https://github.com/skypjack)).<br/>
I can't promise that each and every contribution will be accepted, but I can
assure that I'll do my best to take them all seriously.
assure that I'll do my best to take them all as soon as possible.
If you decide to participate, please see the guidelines for
[contributing](CONTRIBUTING.md) before to create issues or pull
@@ -402,16 +408,3 @@ Documentation released under
[CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).<br/>
All logos released under
[CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/).
<!--
@cond TURN_OFF_DOXYGEN
-->
# Support
If you want to support this project, you can
[offer me](https://github.com/users/skypjack/sponsorship) an espresso.<br/>
If you find that it's not enough, feel free to
[help me](https://www.paypal.me/skypjack) the way you prefer.
<!--
@endcond TURN_OFF_DOXYGEN
-->

54
TODO
View File

@@ -1,40 +1,36 @@
* long term feature: shared_ptr less locator and resource cache
* custom allocators and EnTT allocator-aware in general (long term feature, I don't actually need it at the moment) - see #22
* 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
* allow to replace std:: with custom implementations
* add examples (and credits) from @alanjfs :)
* static reflection, hint: template<> meta_type_t<Type>: meta_descriptor<name, func..., props..., etc...> (see #342)
* update documentation for meta, it contains less than half of the actual feature
* custom pools example:
- lockless fully concurrent ro/rw pool with free lists
- multi instance
- tables
- enable/disable component
- spatial query
- runtime types pool
- off-line/off-memory/remote
- ...
* custom pools example (multi instance, tables, enable/disable, and so on...)
WIP:
* HP: paginate pools
* HP: headless (sparse set only) view
* HP: pass the registry to pools, basic poly storage should have only component member
* HP: make view pack work also with groups, make packs input iterator only, add view adapter for external sources
* HP: write documentation for custom storages and views!!
* HP: any/poly: configurable sbo size, compile-time policies like sbo-required.
* HP: registry: use a poly object for pools, no more pool_data type.
* HP: make runtime views use opaque storage and therefore return also elements.
* HP: poly: support for data members
* remove view/storage dispatcher, add support to relax policy constraints on user request (eg view.use<T>())
* improve perf for sparse_set/storage::insert/emplace/destroy/remove/...
* custom allocators all over
WIP:
* make value_type available from meta container types, otherwise we have to default construct a container to get it
* make it possible to register externally managed pools with the registry (allow for system centric mode)
* registry: switch to the udata/mixin model and get rid of poly storage, use pointer to sparse set only for pools, discard pool_data type.
* it's now possible to have 0 as null entity/version, so we can finally switch to it
* make pools available (registry/view/group), review operator| for views
* page size: add per-pool size, allow for 0 sizes (old fully packed array)
* compressed pair to exploit ebo in sparse set and the others
* isolate view iterator, unwrap iterators in registry ::remove/::erase/::destroy to use the faster solution for non-view iterators
* remove view each<T>(F), each<T>(), make view::use return a view and remove the mutable data member
* resource, forward the id to the loader from the cache and if constexpr the call to load, update doc and describe customization points
* make it possible to create views of the type `view<T, T>`, add get by index and such, allow to register custom pools by name with the registry
* add user data to type_info
* any_vector for context variables
* make const registry::view thread safe, switch to a view<T...>{registry} model (long term goal)
* weak reference wrapper example with custom storage
* headless (sparse set only) view
* write documentation for custom storages and views!!
* make runtime views use opaque storage and therefore return also elements.
* add exclude-only views to combine with packs
* entity-aware observer, add observer functions aside observer class
* deprecate non-owning groups in favor of owning views and view packs, introduce lazy owning views
* view pack: plain function as an alias for operator|, reverse iterators, rbegin and rend
* pagination doesn't work nicely across boundaries probably, give it a look. RO operations are fine, adding components maybe not.
* add observer functions aside observer class
* snapshot: support for range-based archives
* update snapshot documentation to describe alternatives
* page size 0 -> page less mode
* add example: 64 bit ids with 32 bits reserved for users' purposes
* add meta dynamic cast (search base for T in parent, we have the meta type already)
* make meta base/conv node work with storage/any and deprecate/remove meta_base, meta_conv, ...

8
cmake/in/entt.pc.in Normal file
View File

@@ -0,0 +1,8 @@
prefix=@CMAKE_INSTALL_PREFIX@
includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
Name: EnTT
Description: Gaming meets modern C++
Url: https://github.com/skypjack/entt
Version: @ENTT_VERSION@
Cflags: -I${includedir}

View File

@@ -24,8 +24,9 @@ add_custom_target(
md/links.md
md/locator.md
md/meta.md
md/poly.md
md/process.md
md/references.md
md/reference.md
md/resource.md
md/signal.md
md/unreal.md

View File

@@ -1,4 +1,4 @@
# Doxyfile 1.8.20
# Doxyfile 1.9.1
# This file describes the settings to be used by the documentation system
# doxygen (www.doxygen.org) for a project.
@@ -313,7 +313,10 @@ OPTIMIZE_OUTPUT_SLICE = NO
# Note: For files without extension you can use no_extension as a placeholder.
#
# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
# the files are not read by doxygen.
# the files are not read by doxygen. When specifying no_extension you should add
# * to the FILE_PATTERNS.
#
# Note see also the list of default file extension mappings.
EXTENSION_MAPPING =
@@ -523,6 +526,13 @@ EXTRACT_LOCAL_METHODS = NO
EXTRACT_ANON_NSPACES = NO
# If this flag is set to YES, the name of an unnamed parameter in a declaration
# will be determined by the corresponding definition. By default unnamed
# parameters remain unnamed in the output.
# The default value is: YES.
RESOLVE_UNNAMED_PARAMS = YES
# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
# undocumented members inside documented classes or files. If set to NO these
# members will be included in the various overviews, but no documentation
@@ -560,11 +570,18 @@ HIDE_IN_BODY_DOCS = NO
INTERNAL_DOCS = NO
# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
# names in lower-case letters. If set to YES, upper-case letters are also
# allowed. This is useful if you have classes or files whose names only differ
# in case and if your file system supports case sensitive file names. Windows
# (including Cygwin) and Mac users are advised to set this option to NO.
# With the correct setting of option CASE_SENSE_NAMES doxygen will better be
# able to match the capabilities of the underlying filesystem. In case the
# filesystem is case sensitive (i.e. it supports files in the same directory
# whose names only differ in casing), the option must be set to YES to properly
# deal with such files in case they appear in the input. For filesystems that
# are not case sensitive the option should be be set to NO to properly deal with
# output files written for symbols that only differ in casing, such as for two
# classes, one named CLASS and the other named Class, and to also support
# references to files without having to specify the exact matching casing. On
# Windows (including Cygwin) and MacOS, users should typically set this option
# to NO, whereas on Linux or other Unix flavors it should typically be set to
# YES.
# The default value is: system dependent.
CASE_SENSE_NAMES = YES
@@ -803,7 +820,10 @@ WARN_IF_DOC_ERROR = YES
WARN_NO_PARAMDOC = YES
# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
# a warning is encountered.
# 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
# at the end of the doxygen process doxygen will return with a non-zero status.
# Possible values are: NO, YES and FAIL_ON_WARNINGS.
# The default value is: NO.
WARN_AS_ERROR = NO
@@ -841,8 +861,8 @@ INPUT = @DOXY_SOURCE_DIRECTORY@ \
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
# documentation (see: https://www.gnu.org/software/libiconv/) for the list of
# possible encodings.
# documentation (see:
# https://www.gnu.org/software/libiconv/) for the list of possible encodings.
# The default value is: UTF-8.
INPUT_ENCODING = UTF-8
@@ -855,13 +875,15 @@ INPUT_ENCODING = UTF-8
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
# read by doxygen.
#
# Note the list of default checked file patterns might differ from the list of
# default file extension mappings.
#
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment),
# *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen
# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
# *.vhdl, *.ucf, *.qsf and *.ice.
# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl,
# *.ucf, *.qsf and *.ice.
FILE_PATTERNS = *.h \
*.hpp \
@@ -1079,16 +1101,22 @@ USE_HTAGS = NO
VERBATIM_HEADERS = YES
# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the
# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the
# cost of reduced performance. This can be particularly helpful with template
# rich C++ code for which doxygen's built-in parser lacks the necessary type
# information.
# clang parser (see:
# http://clang.llvm.org/) for more accurate parsing at the cost of reduced
# performance. This can be particularly helpful with template rich C++ code for
# which doxygen's built-in parser lacks the necessary type information.
# Note: The availability of this option depends on whether or not doxygen was
# generated with the -Duse_libclang=ON option for CMake.
# The default value is: NO.
CLANG_ASSISTED_PARSING = NO
# If clang assisted parsing is enabled and the CLANG_ADD_INC_PATHS tag is set to
# YES then doxygen will add the directory of each input to the include path.
# The default value is: YES.
CLANG_ADD_INC_PATHS = YES
# If clang assisted parsing is enabled you can provide the compiler with command
# line options that you would normally use when invoking the compiler. Note that
# the include paths will already be set by doxygen for the files and directories
@@ -1102,7 +1130,7 @@ CLANG_OPTIONS =
# file is the compilation database (see:
# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the
# options used when the source files were built. This is equivalent to
# specifying the "-p" option to a clang tool, such as clang-check. These options
# specifying the -p option to a clang tool, such as clang-check. These options
# will then be passed to the parser. Any options specified with CLANG_OPTIONS
# will be added as well.
# Note: The availability of this option depends on whether or not doxygen was
@@ -1121,13 +1149,6 @@ CLANG_DATABASE_PATH =
ALPHABETICAL_INDEX = YES
# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
# which the alphabetical index list will be split.
# Minimum value: 1, maximum value: 20, default value: 5.
# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
COLS_IN_ALPHA_INDEX = 5
# In case all classes in a project start with a common prefix, all classes will
# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
# can be used to specify a prefix (or a list of prefixes) that should be ignored
@@ -1298,10 +1319,11 @@ HTML_INDEX_NUM_ENTRIES = 100
# If the GENERATE_DOCSET tag is set to YES, additional index files will be
# generated that can be used as input for Apple's Xcode 3 integrated development
# environment (see: https://developer.apple.com/xcode/), introduced with OSX
# 10.5 (Leopard). To create a documentation set, doxygen will generate a
# Makefile in the HTML output directory. Running make will produce the docset in
# that directory and running make install will install the docset in
# environment (see:
# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To
# create a documentation set, doxygen will generate a Makefile in the HTML
# output directory. Running make will produce the docset in that directory and
# running make install will install the docset in
# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy
# genXcode/_index.html for more information.
@@ -1343,8 +1365,8 @@ DOCSET_PUBLISHER_NAME = Publisher
# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on
# Windows.
# (see:
# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows.
#
# The HTML Help Workshop contains a compiler that can convert all HTML output
# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
@@ -1419,7 +1441,8 @@ QCH_FILE =
# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
# Project output. For more information please see Qt Help Project / Namespace
# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
# (see:
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
# The default value is: org.doxygen.Project.
# This tag requires that the tag GENERATE_QHP is set to YES.
@@ -1427,8 +1450,8 @@ QHP_NAMESPACE = org.doxygen.Project
# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
# Help Project output. For more information please see Qt Help Project / Virtual
# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-
# folders).
# Folders (see:
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders).
# The default value is: doc.
# This tag requires that the tag GENERATE_QHP is set to YES.
@@ -1436,16 +1459,16 @@ QHP_VIRTUAL_FOLDER = doc
# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
# filter to add. For more information please see Qt Help Project / Custom
# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
# filters).
# Filters (see:
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
# This tag requires that the tag GENERATE_QHP is set to YES.
QHP_CUST_FILTER_NAME =
# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
# custom filter to add. For more information please see Qt Help Project / Custom
# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
# filters).
# Filters (see:
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
# This tag requires that the tag GENERATE_QHP is set to YES.
QHP_CUST_FILTER_ATTRS =
@@ -1457,9 +1480,9 @@ QHP_CUST_FILTER_ATTRS =
QHP_SECT_FILTER_ATTRS =
# The QHG_LOCATION tag can be used to specify the location of Qt's
# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
# generated .qhp file.
# The QHG_LOCATION tag can be used to specify the location (absolute path
# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to
# run qhelpgenerator on the generated .qhp file.
# This tag requires that the tag GENERATE_QHP is set to YES.
QHG_LOCATION =
@@ -1586,7 +1609,7 @@ USE_MATHJAX = NO
# When MathJax is enabled you can set the default output format to be used for
# the MathJax output. See the MathJax site (see:
# http://docs.mathjax.org/en/latest/output.html) for more details.
# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details.
# Possible values are: HTML-CSS (which is slower, but has the best
# compatibility), NativeMML (i.e. MathML) and SVG.
# The default value is: HTML-CSS.
@@ -1616,7 +1639,8 @@ MATHJAX_EXTENSIONS =
# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
# of code that will be used on startup of the MathJax code. See the MathJax site
# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
# (see:
# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an
# example see the documentation.
# This tag requires that the tag USE_MATHJAX is set to YES.
@@ -1663,7 +1687,8 @@ SERVER_BASED_SEARCH = NO
#
# Doxygen ships with an example indexer (doxyindexer) and search engine
# (doxysearch.cgi) which are based on the open source search engine library
# Xapian (see: https://xapian.org/).
# Xapian (see:
# https://xapian.org/).
#
# See the section "External Indexing and Searching" for details.
# The default value is: NO.
@@ -1676,8 +1701,9 @@ EXTERNAL_SEARCH = NO
#
# Doxygen ships with an example indexer (doxyindexer) and search engine
# (doxysearch.cgi) which are based on the open source search engine library
# Xapian (see: https://xapian.org/). See the section "External Indexing and
# Searching" for details.
# Xapian (see:
# https://xapian.org/). See the section "External Indexing and Searching" for
# details.
# This tag requires that the tag SEARCHENGINE is set to YES.
SEARCHENGINE_URL =
@@ -2277,7 +2303,7 @@ HIDE_UNDOC_RELATIONS = YES
# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
# Bell Labs. The other options in this section have no effect if this option is
# set to NO
# The default value is: NO.
# The default value is: YES.
HAVE_DOT = YES
@@ -2356,10 +2382,32 @@ UML_LOOK = NO
# but if the number exceeds 15, the total amount of fields shown is limited to
# 10.
# Minimum value: 0, maximum value: 100, default value: 10.
# This tag requires that the tag HAVE_DOT is set to YES.
# This tag requires that the tag UML_LOOK is set to YES.
UML_LIMIT_NUM_FIELDS = 10
# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and
# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS
# tag is set to YES, doxygen will add type and arguments for attributes and
# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen
# will not generate fields with class member information in the UML graphs. The
# class diagrams will look similar to the default class diagrams but using UML
# notation for the relationships.
# Possible values are: NO, YES and NONE.
# The default value is: NO.
# This tag requires that the tag UML_LOOK is set to YES.
DOT_UML_DETAILS = NO
# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters
# to display on a single line. If the actual line length exceeds this threshold
# significantly it will wrapped across multiple lines. Some heuristics are apply
# to avoid ugly line breaks.
# Minimum value: 0, maximum value: 1000, default value: 17.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_WRAP_THRESHOLD = 17
# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
# collaboration graphs will show the relations between templates and their
# instances.
@@ -2433,7 +2481,9 @@ DIRECTORY_GRAPH = YES
# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
# to make the SVG files visible in IE 9+ (other browsers do not have this
# requirement).
# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd,
# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo,
# gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd, png:cairo,
# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
# png:gdiplus:gdiplus.
# The default value is: png.
@@ -2549,9 +2599,11 @@ DOT_MULTI_TARGETS = NO
GENERATE_LEGEND = YES
# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot
# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate
# files that are used to generate the various graphs.
#
# Note: This setting is not only used for dot files but also for msc and
# plantuml temporary files.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_CLEANUP = YES

View File

@@ -7,10 +7,11 @@
* [Introduction](#introduction)
* [Definitions](#definitions)
* [ENTT_NOEXCEPT](#entt_noexcept)
* [ENTT_NOEXCEPTION](#entt_noexcept)
* [ENTT_USE_ATOMIC](#entt_use_atomic)
* [ENTT_ID_TYPE](#entt_id_type)
* [ENTT_PAGE_SIZE](#entt_page_size)
* [ENTT_SPARSE_PAGE](#entt_sparse_page)
* [ENTT_PACKED_PAGE](#entt_packed_page)
* [ENTT_ASSERT](#entt_assert)
* [ENTT_DISABLE_ASSERT](#entt_disable_assert)
* [ENTT_NO_ETO](#entt_no_eto)
@@ -37,11 +38,11 @@ Each parameter can result in internal library definitions. It's not recommended
to try to also modify these definitions, since there is no guarantee that they
will remain stable over time unlike the options below.
## ENTT_NOEXCEPT
## ENTT_NOEXCEPTION
The purpose of this parameter is to suppress the use of `noexcept` by this
library.<br/>
To do this, simply define the variable without assigning any value to it.
This parameter can be used to switch off exception handling in `EnTT`.<br/>
To do this, simply define the variable without assigning any value to it. This
is roughly equivalent to setting the compiler flag `-ff-noexceptions`.
## ENTT_USE_ATOMIC
@@ -59,13 +60,22 @@ the library.<br/>
By default, its type is `std::uint32_t`. However, users can define a different
default type if necessary.
## ENTT_PAGE_SIZE
## ENTT_SPARSE_PAGE
As is known, the ECS module of `EnTT` is based on _sparse sets_. What is less
known perhaps is that these are paged to reduce memory consumption in some
corner cases.<br/>
The default size of a page is 32kB but users can adjust it if appropriate. In
all case, the chosen value **must** be a power of 2.
It's known that the ECS module of `EnTT` is based on _sparse sets_. What is less
known perhaps is that the sparse arrays are paged to reduce memory usage.<br/>
Default size of pages (that is, the number of elements they contain) is 4096 but
users can adjust it if appropriate. In all case, the chosen value **must** be a
power of 2.
## ENTT_PACKED_PAGE
Similar to sparse arrays, packed arrays of components are paginated as well. In
However, int this case the aim isn't to reduce memory usage but to have pointer
stability upon component creation.<br/>
Default size of pages (that is, the number of elements they contain) is 1024 but
users can adjust it if appropriate. In all case, the chosen value **must** be a
power of 2.
## ENTT_ASSERT

View File

@@ -14,6 +14,8 @@
* [Conflicts](#conflicts)
* [Monostate](#monostate)
* [Any as in any type](#any-as-in-any-type)
* [Small buffer optimization](#small-buffer-optimization)
* [Alignment requirement](#alignment-requirement)
* [Type support](#type-support)
* [Type info](#type-info)
* [Almost unique identifiers](#almost-unique-identifiers)
@@ -24,6 +26,7 @@
* [Member class type](#member-class-type)
* [Integral constant](#integral-constant)
* [Tag](#tag)
* [Type list and value list](#type-list-and-value-list)
* [Utilities](#utilities)
<!--
@endcond TURN_OFF_DOXYGEN
@@ -244,8 +247,16 @@ entt::any any{0};
entt::any in_place{std::in_place_type<int>, 42};
```
The `any` class takes the burden of destroying the contained element when
required, regardless of the storage strategy used for the specific object.<br/>
Alternatively, the `make_any` function serves the same purpose but requires to
always be explicit about the type:
```cpp
entt::any any = entt::make_any<int>(42);
```
In both cases, the `any` class takes the burden of destroying the contained
element when required, regardless of the storage strategy used for the specific
object.<br/>
Furthermore, an instance of `any` is not tied to an actual type. Therefore, the
wrapper will be reconfigured by assigning it an object of a different type than
the one contained, so as to be able to handle the new instance.<br/>
@@ -269,26 +280,23 @@ an opaque container for const and non-const references:
```cpp
int value = 42;
// reference construction
entt::any any{std::ref(value)};
entt::any cany{std::cref(value)};
entt::any any{std::in_place_type<int &>(value)};
entt::any cany = entt::make_any<const int &>(value);
entt::any fwd = entt::forward_as_any(value);
// alias construction
int value = 42;
entt::any in_place{std::in_place_type<int &>, &value};
any.emplace<const int &>(value);
```
In other words, whenever `any` intercepts a `reference_wrapper` or is explicitly
told that users want to construct an alias, it acts as a pointer to the original
instance rather than making a copy of it or moving it internally. The contained
object is never destroyed and users must ensure that its lifetime exceeds that
of the container.<br/>
In other words, whenever `any` is explicitly told to construct an _alias_, it
acts as a pointer to the original instance rather than making a copy of it or
moving it internally. The contained object is never destroyed and users must
ensure that its lifetime exceeds that of the container.<br/>
Similarly, it's possible to create non-owning copies of `any` from an existing
object:
```cpp
// aliasing constructor
entt::any ref = as_ref(other);
entt::any ref = other.as_ref();
```
In this case, it doesn't matter if the original container actually holds an
@@ -311,6 +319,48 @@ The only difference is that, in the case of `EnTT`, these won't raise exceptions
but will only trigger an assert in debug mode, otherwise resulting in undefined
behavior in case of misuse in release mode.
## Small buffer optimization
The `any` class uses a technique called _small buffer optimization_ to reduce
the number of allocations where possible.<br/>
The default reserved size for an instance of `any` is `sizeof(double[2])`.
However, this is also configurable if needed. In fact, `any` is defined as an
alias for `basic_any<Len>`, where `Len` is the size above.<br/>
Users can easily set a custom size or define their own aliases:
```cpp
using my_any = entt::basic_any<sizeof(double[4])>;
```
This feature, in addition to allowing the choice of a size that best suits the
needs of an application, also offers the possibility of forcing dynamic creation
of objects during construction.<br/>
In other terms, if the size is 0, `any` avoids the use of any optimization and
always dynamically allocates objects (except for aliasing cases).
Note that the size of the internal storage as well as the alignment requirements
are directly part of the type and therefore contribute to define different types
that won't be able to interoperate with each other.
## Alignment requirement
The alignment requirement is optional and by default the most stringent (the
largest) for any object whose size is at most equal to the one provided.<br/>
The `basic_any` class template inspects the alignment requirements in each case,
even when not provided and may decide not to use the small buffer optimization
in order to meet them.
The alignment requirement is provided as an optional second parameter following
the desired size for the internal storage:
```cpp
using my_any = entt::basic_any<sizeof(double[4]), alignof(double[4])>;
```
Note that the alignment requirements as well as the size of the internal storage
are directly part of the type and therefore contribute to define different types
that won't be able to interoperate with each other.
# Type support
`EnTT` provides some basic information about types of all kinds.<br/>
@@ -571,6 +621,29 @@ registry.emplace<entt::tag<"enemy"_hs>>(entity);
However, this isn't the only permitted use. Literally any value convertible to
`id_type` is a good candidate, such as the named constants of an unscoped enum.
### Type list and value list
There is no respectable library where the much desired _type list_ can be
missing.<br/>
`EnTT` is no exception and provides (making extensive use of it internally) the
`type_list` type, in addition to its `value_list` counterpart dedicated to
non-type template parameters.
Here is a (possibly incomplete) list of the functionalities that come with a
type list:
* `type_list_element[_t]` to get the N-th element of a type list.
* `type_list_cast[_t]` and a handy `operator+` to concatenate type lists.
* `type_list_unique[_t]` to remove duplicate types from a type list.
* `type_list_contains[_v]` to know if a type list contains a given type.
* `type_list_diff[_t]` to remove types from type lists.
I'm also pretty sure that more and more utilities will be added over time as
needs become apparent.<br/>
Many of these functionalities also exist in their version dedicated to value
lists. We therefore have `value_list_element[_v]` as well as
`value_list_cat[_t]`and so on.
# Utilities
It's not possible to escape the temptation to add utilities of some kind to a

View File

@@ -7,10 +7,10 @@
* [Introduction](#introduction)
* [Design decisions](#design-decisions)
* [A bitset-free entity-component system](#a-bitset-free-entity-component-system)
* [Type-less and bitset-free](#type-less-and-bitset-free)
* [Build your own](#build-your-own)
* [Pay per use](#pay-per-use)
* [All or nothing](#all-or-nothing)
* [Stateless systems](#stateless-systems)
* [Vademecum](#vademecum)
* [Pools](#pools)
* [The Registry, the Entity and the Component](#the-registry-the-entity-and-the-component)
@@ -19,12 +19,18 @@
* [Sorting: is it possible?](#sorting-is-it-possible)
* [Helpers](#helpers)
* [Null entity](#null-entity)
* [Tombstone](#tombstone)
* [To entity](#to-entity)
* [Dependencies](#dependencies)
* [Invoke](#invoke)
* [Handle](#handle)
* [Context variables](#context-variables)
* [Organizer](#organizer)
* [Context variables](#context-variables)
* [Aliased properties](#aliased-properties)
* [In-place delete](#in-place-delete)
* [Pointer stability](#pointer-stability)
* [Hierarchies and the like](#hierarchies-and-the-like)
* [Making the most of range-destroy](#making-the-most-of-range-destroy)
* [Meet the runtime](#meet-the-runtime)
* [Snapshot: complete vs continuous](#snapshot-complete-vs-continuous)
* [Snapshot loader](#snapshot-loader)
@@ -40,14 +46,15 @@
* [Partial-owning groups](#partial-owning-groups)
* [Non-owning groups](#non-owning-groups)
* [Nested groups](#nested-groups)
* [Invalid views and groups](#invalid-views-and-groups)
* [Types: const, non-const and all in between](#types-const-non-const-and-all-in-between)
* [Give me everything](#give-me-everything)
* [Stable storage](#stable-storage)
* [What is allowed and what is not](#what-is-allowed-and-what-is-not)
* [More performance, more constraints](#more-performance-more-constraints)
* [Empty type optimization](#empty-type-optimization)
* [Multithreading](#multithreading)
* [Iterators](#iterators)
* [Const registry](#const-registry)
* [Beyond this document](#beyond-this-document)
<!--
@endcond TURN_OFF_DOXYGEN
@@ -62,7 +69,7 @@ used mostly in game development.
# Design decisions
## A bitset-free entity-component system
## Type-less and bitset-free
`EnTT` offers a _bitset-free_ entity-component system that doesn't require users
to specify the set of components neither at compile-time nor at runtime.<br/>
@@ -81,6 +88,23 @@ entt::registry<comp_0, comp_1, ..., comp_n> registry;
Furthermore, it isn't necessary to announce the existence of a component type.
When the time comes, just use it and that's all.
## Build your own
`EnTT` is designed as a container that can be used at any time just as a vector
or any other tool would be used. It doesn't attempt in any way to take over on
the user code base, nor to control its main loop or process scheduling.<br/>
Unlike other more or less known models, it makes use of independent pools. This
has some advantages and disadvantages. The main purpose is to provide a fully
customizable tool, where users have the freedom to define pools and opaque
proxies for types with specific requirements.
The library provides a default implementation for many things and a mixin model
that allows users to completely replace or even just enrich the pool dedicated
to one or more components.<br/>
The built-in signal support is an example of that: defined as a mixin, it's
easily disabled if not needed. Similarly, poly storage is another example of how
everything is customizable down to the smallest detail.
## Pay per use
`EnTT` is entirely designed around the principle that users have to pay only for
@@ -103,26 +127,17 @@ many others besides me.
## All or nothing
`EnTT` is such that at every moment a pair `(T *, size)` is available to
directly access all the instances of a given component type `T`.<br/>
This was a guideline and a design decision that influenced many choices, for
better and for worse. I cannot say whether it will be useful or not to the
reader, but it's worth to mention it since it's one of the corner stones of
this library.
`EnTT` is such that a `T**` pointer (or whatever a custom pool returns) is
always available to directly access all the instances of a given component type
`T`.<br/>
I cannot say whether it will be useful or not to the reader, but it's worth to
mention it since it's one of the corner stones of this library.
Many of the tools described below give the possibility to get this information
and have been designed around this need.<br/>
The rest is experimentation and the desire to invent something new, hoping to
have succeeded.
## Stateless systems
`EnTT` is designed so that it can work with _stateless systems_. In other words,
all systems can be free functions and there is no need to define them as classes
(although nothing prevents users from doing so).<br/>
This is possible because the main class with which the users will work provides
all what is needed to act as the sole _source of truth_ of an application.
# Vademecum
The registry to store, the views and the groups to iterate. That's all.
@@ -170,11 +185,8 @@ the alias `entt::registry` for `entt::basic_registry<entt::entity>`.
Entities are represented by _entity identifiers_. An entity identifier carries
information about the entity itself and its version.<br/>
User defined identifiers can be introduced by means of enum classes and custom
types for which a specialization of `entt_traits` exists. For this purpose,
`entt_traits` is also defined as a _sfinae-friendly_ class template. In theory,
integral types can also be used as entity identifiers, even though this may
break in future and isn't recommended in general.
User defined identifiers can be introduced through enum classes and class types
that define an `entity_type` member of type `std::uint32_t` or `std::uint64_t`.
A registry is used both to construct and to destroy entities:
@@ -196,10 +208,22 @@ auto view = registry.view<a_component, another_component>();
registry.destroy(view.begin(), view.end());
```
When an entity is destroyed, the registry can freely reuse it internally with a
slightly different identifier. In particular, the version of an entity is
increased after destruction (unless the overload that forces a version is used
instead of the default one).<br/>
In addition to offering an overload to force the version upon destruction. Note
that this function removes all components from an entity before releasing its
identifier. There exists also a _lighter_ alternative that only releases the
elements without poking in any pool, for use with orphaned entities:
```cpp
// releases an orphaned identifier
registry.release(entity);
```
As with the `destroy` function, also in this case entity ranges are supported
and it's possible to force the version during release.
In both cases, when an identifier is released, the registry can freely reuse it
internally. In particular, the version of an entity is increased (unless the
overload that forces a version is used instead of the default one).<br/>
Users can probe an identifier to know the information it carries:
```cpp
@@ -277,42 +301,42 @@ registry.emplace_or_replace<position>(entity, 0., 0.);
This is a slightly faster alternative for the following snippet:
```cpp
if(registry.has<velocity>(entity)) {
if(registry.all_of<velocity>(entity)) {
registry.replace<velocity>(entity, 0., 0.);
} else {
registry.emplace<velocity>(entity, 0., 0.);
}
```
The `has` and `any` member functions may also be useful if in doubt about
The `all_of` and `any_of` member functions may also be useful if in doubt about
whether or not an entity has all the components in a set or any of them:
```cpp
// true if entity has all the given components
bool all = registry.has<position, velocity>(entity);
bool all = registry.all_of<position, velocity>(entity);
// true if entity has at least one of the given components
bool any = registry.any<position, velocity>(entity);
bool any = registry.any_of<position, velocity>(entity);
```
If the goal is to delete a component from an entity that owns it, the `remove`
If the goal is to delete a component from an entity that owns it, the `erase`
member function template is the way to go:
```cpp
registry.erase<position>(entity);
```
When in doubt whether the entity owns the component, use the `remove` member
function instead. It behaves similarly to `erase` but it erases the component
if and only if it exists, otherwise it returns safely to the caller:
```cpp
registry.remove<position>(entity);
```
When in doubt whether the entity owns the component, use the `remove_if_exists`
member function instead. It behaves similarly to `remove` but it discards the
component if and only if it exists, otherwise it returns safely to the caller:
```cpp
registry.remove_if_exists<position>(entity);
```
The `clear` member function works similarly and can be used to either:
* Remove all instances of the given components from the entities that own them:
* Erases all instances of the given components from the entities that own them:
```cpp
registry.clear<position>();
@@ -615,11 +639,54 @@ const auto entity = registry.create();
const bool null = (entity == entt::null);
```
As for its integral form, the null entity only affects the entity part of an
identifier and is instead completely transparent to its version.
Be aware that `entt::null` and entity 0 aren't the same thing. Likewise, a zero
initialized entity isn't the same as `entt::null`. Therefore, although
`entt::entity{}` is in some sense an alias for entity 0, none of them can be
used to create a null entity.
### Tombstone
In addition to the null entity, `EnTT` also models the concept of _tombstone_
with the `entt::tombstone` variable.<br/>
Once created, the integral form of the two values is the same, although they
affect different parts of an identifier. In fact, the tombstone uses only the
version part and is completely transparent to the entity part.
Also in this case, the following expression always returns false:
```cpp
registry.valid(entt::tombstone);
```
Moreover, users cannot set set the tombstone version when releasing an entity:
```
registry.destroy(entity, entt::tombstone);
```
In this case, a different version number is implicitly generated.<br/>
The type of a tombstone is internal and can change at any time. However, there
exist implicit conversions from a tombstone to identifiers of any allowed type:
```cpp
entt::entity null = entt::tombstone;
```
Similarly, the tombstone can be compared to any other identifier:
```cpp
const auto entity = registry.create();
const bool tombstone = (entity == entt::tombstone);
```
Be aware that `entt::tombstone` and entity 0 aren't the same thing. Likewise, a
zero initialized entity isn't the same as `entt::tombstone`. Therefore, although
`entt::entity{}` is in some sense an alias for entity 0, none of them can be
used to create tombstones.
### To entity
Sometimes it's useful to get the entity from a component instance.<br/>
@@ -630,9 +697,7 @@ instance of a component and returns the entity associated with the latter:
const auto entity = entt::to_entity(registry, position);
```
This utility doesn't perform any check on the validity of the component.
Therefore, trying to take the entity of an invalid element or of an instance
that isn't associated with the given registry can result in undefined behavior.
A null entity is returned in case the component doesn't belong to the registry.
### Dependencies
@@ -713,39 +778,6 @@ This class is intended to simplify function signatures. In case of functions
that take a registry and an entity and do most of their work on that entity,
users might want to consider using handles, either const or non-const.
### Context variables
It is often convenient to assign context variables to a registry, so as to make
it the only _source of truth_ of an application.<br/>
This is possible by means of a member function named `set` to use to create a
context variable from a given type. Either `ctx` or `try_ctx` can be used to
retrieve the newly created instance, while `unset` is meant to clear the
variable if needed.
Example of use:
```cpp
// creates a new context variable initialized with the given values
registry.set<my_type>(42, 'c');
// gets the context variable
const auto &var = registry.ctx<my_type>();
// if in doubts, probe the registry to avoid assertions in case of errors
if(auto *ptr = registry.try_ctx<my_type>(); ptr) {
// uses the context variable associated with the registry, if any
}
// unsets the context variable
registry.unset<my_type>();
```
The type of a context variable must be such that it's default constructible and
can be moved. The `set` member function either creates a new instance of the
context variable or overwrites an already existing one if any. The `try_ctx`
member function returns a pointer to the context variable if it exists,
otherwise it returns a null pointer.
### Organizer
The `organizer` class template offers minimal support (but sufficient in many
@@ -880,6 +912,231 @@ for(auto &&node: graph) {
The actual scheduling of the tasks is the responsibility of the user, who can
use the preferred tool.
## Context variables
It is often convenient to assign context variables to a registry, so as to make
it the only _source of truth_ of an application.<br/>
This is possible by means of a member function named `set` to use to create a
context variable from a given type. Either `ctx` or `try_ctx` can be used to
retrieve the newly created instance, while `unset` is meant to clear the
variable if needed:
```cpp
// creates a new context variable initialized with the given values
registry.set<my_type>(42, 'c');
// gets the context variable as a non-const reference from a non-const registry
auto &var = registry.ctx<my_type>();
// gets the context variable as a const reference from either a const or a non-const registry
const auto &cvar = registry.ctx<const my_type>();
// unsets the context variable
registry.unset<my_type>();
```
The type of a context variable must be such that it's default constructible and
can be moved. The `set` member function either creates a new instance of the
context variable or overwrites an already existing one if any.<br/>
The `try_ctx` member function returns a pointer to the context variable if it
exists, otherwise it returns a null pointer. As `ctx`, it supports both const
and non-const types and requires a const one when used on a const registry:
```cpp
if(auto *cptr = registry.try_ctx<const my_type>(); cptr) {
// uses the context variable associated with the registry, if any
}
```
### Aliased properties
Context variables can also be used to create aliases for existing variables that
aren't directly managed by the registry. In this case, it's also possible to
make them read-only.<br/>
To do that, the type used upon construction must be a reference type and an
lvalue is necessarily provided as an argument:
```cpp
time clock;
registry.set<my_type &>(clock);
```
Read-only aliased properties are created using const types instead:
```cpp
registry.set<const my_type &>(clock);
```
From the point of view of the user, there are no differences between a variable
that is managed by the registry and an aliased property. However, read-only
variables aren't accesible as non-const references:
```cpp
// read-only variables only support const access
const my_type *ptr = registry.try_ctx<const my_type>();
const my_type &var = registry.ctx<const my_type>();
```
Aliased properties can be unset and are overwritten when `set` is invoked, as it
happens with standard variables.
## In-place delete
By default, `EnTT` keeps all pools compact when a component is removed. This is
done through a swap-and-pop between the removed item and the one occupying the
last position in the storage.<br/>
Unfortunately, this also inevitably leads the components to change position
within the storage, making direct access almost impossible (be it via pointer or
index).
However, the underlying model with its independent pools helps introduce storage
with different deletion policies, so that users can best choose type by
type.<br/>
In particular, the library offers out of the box support for in-place deletion,
thus offering storage with completely stable pointers. To do so, it's required
to specialize the `component_traits` class.<br/>
The definition common to all components is the following:
```cpp
struct basic_component_traits {
using in_place_delete = std::false_type;
using ignore_if_empty = ENTT_IGNORE_IF_EMPTY;
};
```
Where `in_place_delete` instructs the library on the deletion policy for a given
type while `ignore_if_empty` selectively disables empty type optimization.<br/>
The `component_traits` class template is _sfinae-friendly_, it supports single-
and multi-type specializations as well as feature-based ones:
```cpp
template<>
struct entt::component_traits<position>: basic_component_traits {
using in_place_delete = std::true_type;
};
```
This will ensure in-place deletion for the `position` component without further
user intervention.<br/>
Pools, views and groups will adapt accordingly when they detect a storage with a
different deletion policy than the default. No specific action is required from
the user once in-place deletion is enabled.
### Pointer stability
The ability to achieve pointer stability for one, several or all components is a
direct consequence of the design of `EnTT` and of its default storage.<br/>
In fact, although it contains what is commonly referred to as a _packed array_,
the default storage is paged and doesn't suffer from invalidation of references
when it runs out of space and has to reallocate.<br/>
However, this isn't enough to ensure pointer stability in case of deletion. For
this reason, a _stable_ deletion method is also offered. This one is such that
the position of the elements is preserved by creating tombstones upon deletion
rather than trying to fill the holes that are created.
For performance reasons, `EnTT` will also favor storage compaction in all cases,
although often accessing a component occurs mostly randomly or traversing pools
in a non-linear order on the user side (as in the case of a hierarchy).<br/>
In other words, pointer stability is not automatic but is enabled on request. To
have it at the project level and for all components, it's required to partially
specialize the `component_traits` class for all possible types:
```cpp
template<typename Type>
struct entt::component_traits<Type>: basic_component_traits {
using in_place_delete = std::true_type;
};
```
Because of how C++ works, this specialization will obviously have to be visible
every time operations are performed on a storage.
### Hierarchies and the like
`EnTT` doesn't attempt in any way to offer built-in methods with hidden or
unclear costs to facilitate the creation of hierarchies.<br/>
There are various solutions to the problem, such as using the following class:
```cpp
struct relationship {
std::size_t children{};
entt::entity first{entt::null};
entt::entity prev{entt::null};
entt::entity next{entt::null};
entt::entity parent{entt::null};
// ... other data members ...
};
```
However, it should be pointed out that the possibility of having stable pointers
for one, many or all types solves the problem of hierarchies at the root in many
cases.<br/>
In fact, if a certain type of component is visited mainly in random order or
according to hierarchical relationships, using direct pointers has many
advantages:
```cpp
struct transform {
transform *parent;
// ... other data members ...
};
template<>
struct entt::component_traits<transform>: basic_component_traits {
using in_place_delete = std::true_type;
};
```
Furthermore, it's quite common for a group of elements to be created close in
time and therefore fallback into adjacent positions, thus favoring locality even
on random accesses. Locality that won't be sacrificed over time given the
stability of storage positions, with undoubted performance advantages.<br/>
Of course, the cost moves to linear iterations, where views and groups will have
to identify (and discard) all tombstones. However, once considered the benefits,
from performance to ease of use, and given the many optimizations that make this
cost negligible, this is configured as one of the most convenient solutions and
certainly something to take into consideration.
## Making the most of range-destroy
The range-destroy functionality offers an improved path under the hood. To
understand it, let's try to describe what problem it tries to solve.<br/>
This function accepts two iterators that point to the beginning and end of a
range of entities. If the iterators are those returned from a view, this pair
cannot be passed to the first storage asking to remove all entities and then to
all other storage. This is because the range may be empty when passed to the
second pool, as not all of those entities still own all the components iterated
from the view itself.<br/>
As a result, only one component is removed and no entities are destroyed.
To avoid this, in many cases the registry doesn't pass the range to all pools.
Instead, it iterates the range and passes an entity at a time to all pools.<br/>
It goes without saying that the latter is slightly slower than the former.
On the other side, the `destroy` function also uses `is_iterator_type` under the
hood to detect _dangerous_ iterators. Whenever possible, it still chooses the
fastest path.<br/>
This means that performance will improve if, for example, two iterators returned
from an `std::vector` are used or, more in general, with all iterators that are
not part of `EnTT`.
Unfortunately, this risks falling into the error described above in some corner
cases. In particular, where an iterator is used that is not defined by `EnTT`
but which uses one of the latter _within_ it.<br/>
It's quite unlikely to happen even in large software. However, the library
offers a solution also in this case, so as to allow for custom iterators and
better performance at the same time.<br/>
In particular, it's necessary to either expose the member type `iterator_type`
and declare that an iterator from `EnTT` is used internally or specialize the
`is_iterator_type` class to drive the choice of the `destroy` function.<br/>
In both cases, the aim is to not choose the optimized route if it can cause
problems.
With a good chance, the last note can be ignored and there will never be a need
to do the above even after writing millions of lines of code.<br/>
However, it's good to know how to exploit the `destroy` function to get the best
out of it.
## Meet the runtime
`EnTT` takes full advantage of what the language offers at compile-time.<br/>
@@ -911,7 +1168,7 @@ copying an entity will be as easy as:
```cpp
registry.visit(entity, [&](const auto info) {
auto storage = registry.storage(info);
auto &&storage = registry.storage(info);
storage->emplace(registry, other, storage->get(entity));
});
```
@@ -921,8 +1178,7 @@ Similarly, copying entire pools between different registries can look like this:
```cpp
registry.visit([&](const auto info) {
auto storage = registry.storage(info);
other.storage(info)->insert(other, storage->data(), storage->raw(), storage->size());
registry.storage(info)->copy_to(other);
});
```
@@ -966,7 +1222,7 @@ to use in which case mostly depends on the goal and there is not a golden rule
for that.
The `entities` member function makes the snapshot serialize all entities (both
those still alive and those destroyed) along with their versions.<br/>
those still alive and those released) along with their versions.<br/>
On the other hand, the `component` member function is a function template the
aim of which is to store aside components. The presence of a template parameter
list is a consequence of a couple of design choices from the past and in the
@@ -1031,11 +1287,11 @@ The `component` member function restores all and only the components specified
and assigns them to the right entities. Note that the template parameter list
must be exactly the same used during the serialization.
The `orphans` member function literally destroys those entities that have no
The `orphans` member function literally releases those entities that have no
components attached. It's usually useless if the snapshot is a full dump of the
source. However, in case all the entities are serialized but only few components
are saved, it could happen that some of the entities have no components once
restored. The best the users can do to deal with them is to destroy those
restored. The best the users can do to deal with them is to release those
entities and thus update their versions.
### Continuous loader
@@ -1082,7 +1338,7 @@ In case the component contains entities itself (either as data members of type
automatically. To do that, it's enough to specify the data members to update as
shown in the example.
The `orphans` member function literally destroys those entities that have no
The `orphans` member function literally releases those entities that have no
components after a restore. It has exactly the same purpose described in the
previous section and works the same way.
@@ -1210,12 +1466,12 @@ different in the two cases.
Single component views are specialized in order to give a boost in terms of
performance in all the situations. This kind of views can access the underlying
data structures directly and avoid superfluous checks. There is nothing as fast
as a single component view. In fact, they walk through a packed array of
components and return them one at a time.<br/>
Single component views offer a bunch of functionalities to get the number of
entities they are going to return and a raw access to the entity list as well as
to the component list. It's also possible to ask a view if it contains a given
entity.<br/>
as a single component view. In fact, they walk through a packed (actually paged)
array of components and return them one at a time.<br/>
Single component views also offer a bunch of functionalities to get the number
of entities they are going to return and a raw access to the entity list as well
as to the component list. It's also possible to ask a view if it contains a
given entity.<br/>
Refer to the inline documentation for all the details.
Multi component views iterate entities that have at least all the given
@@ -1308,14 +1564,11 @@ registry during iterations to get the types iterated by the view itself.
### View pack
The view pack allows users to combine multiple views into a single _view-like_
iterable object, while also giving them full control over which view should lead
the iteration.<br/>
This object returns all and only the entities present in all views. Its intended
primary use is for custom storage and views, but it can also be very convenient
in everyday use.
Views are combined with each other to create new and more specific types.<br/>
The type returned when combining multiple views together is itself a view, more
in general a multi component one.
The creation of a view pack tries to mimic C++20 ranges:
Combining different views tries to mimic C++20 ranges:
```cpp
auto view = registry.view<position>();
@@ -1324,54 +1577,18 @@ auto other = registry.view<velocity>();
auto pack = view | other;
```
The return type is a specialization of the class template `entt::view_pack`.
This is nothing more than a _view-like_ iterable object that combines two or
more views into a single instance.<br/>
The first view used to create a pack will also be the same that will lead the
iteration.
A view pack offers functionalities similar to those of a multi component view,
especially with regard to the possibilities of iteration. In particular, it only
returns entities if iterated directly:
```cpp
for(auto entt: pack) {
// ...
}
```
On the other hand, both the (optional) entity and the components are returned
when the `each` member function is used, be it with callback or to get an
extended iterable object:
```cpp
// with a callback
pack.each([](const auto entt, auto &pos, auto &vel) { /* ... */ });
// with an extended iterable object
for(auto [entt, pos, vel]: pack.each()) {
// ...
}
```
Furthermore, the constness of the types returned by a view pack is directly
inherited by the views that compose it:
```
(registry.view<position>() | registry.view<const velocity>()).each([](auto &pos, const auto &vel) {
// ...
});
```
Read also the dedicated section to know how a view pack is involved in the
creation and use of custom storage and pools.
The constness of the types is preserved and their order depends on the order in
which the views are combined. Therefore, the pack in the example above will
return an instance of `position` first and then one of `velocity`.<br/>
Since combining views generates views, a chain can be of arbitrary length and
the above type order rules apply sequentially.
### Runtime views
Runtime views iterate entities that have at least all the given components in
their bags. During construction, these views look at the number of entities
available for each component and pick up a reference to the smallest
set of candidates in order to speed up iterations.<br/>
available for each component and pick up a reference to the smallest set of
candidates in order to speed up iterations.<br/>
They offer more or less the same functionalities of a multi component view.
However, they don't expose a `get` member function and users should refer to the
registry that generated the view to access components. In particular, a runtime
@@ -1666,32 +1883,6 @@ restrictive of them. To prevent users from having to remember which of their
groups is the most restrictive, the registry class offers the `sortable` member
function to know if a group can be sorted or not.
## Invalid views and groups
Views and groups as returned by a registry are generally valid. However, there
are some exceptions where an invalid object might be returned.<br/>
In these cases, they should be renewed as soon as possible. In fact, an invalid
view or group contains a broken reference to one or more pools and this will
never be fixed. The view or the group will continue to return no data, even if
the pool for the pending reference is created in the registry in the meantime.
There is only one case in which an invalid object can be returned, that is when
the view or the group is created from a constant reference to a registry in
which the required pools haven't yet been created.<br/>
Pools are typically created whenever any method is used on a non-const registry.
This also means that creating views and groups from a non-const registry can
never result in an invalid object.
It's also perfectly fine to use an invalid view or group, to invoke `each` on
them or to iterate them like any other object. The only difference from a valid
view or group is that the invalid ones will always appear as _empty_.<br/>
In general, when views and groups are created on the fly and used at the same
time, then discarded immediately afterwards, it doesn't matter whether or not
they may be invalid. Therefore, this remains the recommended approach.
To know if a view or a group is properly initialized, both can be converted to
bool explicitly and used in a guard.
## Types: const, non-const and all in between
The `registry` class offers two overloads when it comes to constructing views
@@ -1785,20 +1976,58 @@ In general, all these functions can result in poor performance.<br/>
entity. For similar reasons, `orphans` can be even slower. Both functions should
not be used frequently to avoid the risk of a performance hit.
## Stable storage
Since it's possible to have completely stable storage in `EnTT`, it's also
required that all views behave accordingly.<br/>
In general, this aspect is quite transparent to the user who doesn't have to do
anything in the vast majority of cases. In particular:
* Groups are incompatible with stable storage and will trigger a compile-time
error if detected.
* Views detect the type of storage with the most stringent requirements when
built and self-configure themselves to use the correct iteration policy.
* Views created as view packs adjust their policy by choosing the most stringent
among those available.
The policy adopted doesn't emerge from the view type, although it's available
through the `storage_policy` alias.<br/>
However, this can affect the feature set offered by the view itself. In the case
of storage that also support tombstones, all views (even single-component ones)
will always behave as a multi-type views. Therefore, for example, it won't be
possible to directly access the raw representation of entities and components.
In other words, the more generic version of a view will be provided in case of
stable storage, even for single components, always supported by an appropriate
iteration policy.<br/>
The latter will be such that in no case will a tombstone be returned from the
view itself, regardless of the iteration method. Similarly, no non-existent
components will be accessed, which could result in an UB otherwise.
## What is allowed and what is not
Most of the _ECS_ available out there don't allow to create and destroy entities
and components during iterations.<br/>
and components during iterations, nor to have pointer stability.<br/>
`EnTT` partially solves the problem with a few limitations:
* Creating entities and components is allowed during iterations in most cases.
* Creating entities and components is allowed during iterations in most cases
and it never invalidates already existing references.
* Deleting the current entity or removing its components is allowed during
iterations. For all the other entities, destroying them or removing their
components isn't allowed and can result in undefined behavior.
iterations but it could invalidate references. For all the other entities,
destroying them or removing their components isn't allowed and can result in
undefined behavior.
In these cases, iterators aren't invalidated. To be clear, it doesn't mean that
also references will continue to be valid.<br/>
* If a type has stable pointers, it's possible to destroy any entity and any
component, even if not currently iterated, without the risk of invalidating
any references.
In other terms, iterators are never invalidated. Also, component references
aren't invalidated when a new element is added while they could be invalidated
upon destruction due to the _swap-and-pop_ policy, unless the type leading the
iteration undergoes in-place deletion.<br/>
Consider the following example:
```cpp
@@ -1808,14 +2037,16 @@ registry.view<position>([&](const auto entity, auto &pos) {
});
```
The `each` member function won't break (because iterators aren't invalidated)
but there are no guarantees on references. Use a common range-for loop and get
components directly from the view or move the creation of components at the end
of the function to avoid dangling pointers.
The `each` member function won't break (because iterators remain valid) nor will
any reference be invalidated. Instead, more attention should be paid to the
destruction of entities or the removal of components.<br/>
Use a common range-for loop and get components directly from the view or move
the deletion of entities and components at the end of the function to avoid
dangling pointers.
Iterators are invalidated instead and the behavior is undefined if an entity is
modified or destroyed and it's not the one currently returned by the iterator
nor a newly created one.<br/>
For all types that don't offer stable pointers, iterators are also invalidated
and the behavior is undefined if an entity is modified or destroyed and it's not
the one currently returned by the iterator nor a newly created one.<br/>
To work around it, possible approaches are:
* Store aside the entities and the components to be removed and perform the
@@ -1878,11 +2109,11 @@ mentioning.
When an empty type is detected, it's not instantiated in any case. Therefore,
only the entities to which it's assigned are made available.<br/>
There doesn't exist a way to _iterate_ empty types. Views and groups will never
return instances of empty types (for example, during a call to `each`) and some
functions such as `try_get` or the raw access to the list of components aren't
available for them. Finally, the `sort` functionality accepts only callbacks
that require to return entities rather than components:
There doesn't exist a way to _get_ empty types from a registry, views and groups
will never return instances for them (for example, during a call to `each`) and
some functions such as `try_get` or the raw access to the list of components
aren't available for empty types. Finally, the `sort` functionality will only
accepts callbacks that require to return entities rather than components:
```cpp
registry.sort<empty_type>([](const entt::entity lhs, const entt::entity rhs) {
@@ -1897,8 +2128,12 @@ it is assigned to.
More in general, none of the features offered by the library is affected, but
for the ones that require to return actual instances.<br/>
This optimization can be disabled by defining the `ENTT_NO_ETO` macro. In this
case, empty types will be treated like all other types, no matter what.
This optimization can be disabled for the whole application by defining the
`ENTT_NO_ETO` macro. In this case, empty types will be treated like all other
types, no matter what.<br/>
Otherwise, users can specialize the `component_traits` template class and in
particular the `ignore_if_empty` alias, disabling this optimization for some
types only.
# Multithreading
@@ -1975,6 +2210,28 @@ both the entities and a list of references to their components by default sooner
or later. Multi-pass guarantee won't break in any case and the performance
should even benefit from it further.
## Const registry
Contrary to what the standard library containers offer, a const registry is
generally but not completely thread safe.<br/>
In particular, one (and only one) of its const member functions isn't fully
thread safe. That is the `view` method.
The reason for this is easy to explain. To avoid requiring types to be
_announced_ in advance, the registry lazily initializes the storage objects for
the different components.<br/>
In most cases, this isn't even necessary. The absence of a storage is itself the
required information. However, when building a view, all pools must necessarily
exist. This makes the `view` member function not thread safe even in its const
overload, unless all pools already exist.
Fortunately, there is also a way to instantiate storage classes early when in
doubt or when there are special requirements.<br/>
Calling the `prepare` method is equivalent to _announcing_ the existence of a
particular storage, to avoid running into problems. For those interested, there
are also alternative approaches, such as a single threaded tick for the registry
warm-up, but these are not always applicable.
# Beyond this document
There are many other features and functions not listed in this document.<br/>

View File

@@ -77,45 +77,30 @@ not different techniques depending on how the data are laid out.
I tried to describe some of the techniques that fit well with the model of
`EnTT`. [Here](https://skypjack.github.io/2019-06-25-ecs-baf-part-4/) is the
first post of a series that tries to explore the problem. More will probably
come in future.
Long story short, you can always define a tree where the nodes expose implicit
lists of children by means of the following type:
```cpp
struct relationship {
std::size_t children{};
entt::entity first{entt::null};
entt::entity prev{entt::null};
entt::entity next{entt::null};
entt::entity parent{entt::null};
// ... other data members ...
};
```
The sort functionalities of `EnTT`, the groups and all the other features of the
library can help then to get the best in terms of data locality and therefore
performance from this component.
come in future.<br/>
In addition, `EnTT` also offers the possibility to create stable storage types
and therefore have pointer stability for one, all or some components. This is by
far the most convenient solution when it comes to creating hierarchies and
whatnot. See the documentation for the ECS part of the library and in particular
what concerns the `component_traits` class for further details.
## Custom entity identifiers: yay or nay?
Custom entity identifiers are definitely a good idea in two cases at least:
* If `std::uint32_t` is too large or isn't large enough for your purposes, since
this is the underlying type of `entt::entity`.
* If `std::uint32_t` isn't large enough for your purposes, since this is the
underlying type of `entt::entity`.
* If you want to avoid conflicts when using multiple registries.
Identifiers can be defined through enum classes and custom types for which a
specialization of `entt_traits` exists. For this purpose, `entt_traits` is also
defined as a _sfinae-friendly_ class template.<br/>
Identifiers can be defined through enum classes and class types that define an
`entity_type` member of type `std::uint32_t` or `std::uint64_t`.<br/>
In fact, this is a definition equivalent to that of `entt::entity`:
```cpp
enum class entity: std::uint32_t {};
```
In theory, integral types can also be used as entity identifiers, even though
this may break in future and isn't recommended in general.
There is no limit to the number of identifiers that can be defined.
## Warning C4307: integral constant overflow

View File

@@ -91,7 +91,7 @@ attached the new visible meta types, no matter where they are created.<br/>
A context can also be reset and then associated again locally as:
```cpp
entt::meta_ctx::bind{entt::meta_ctx{});
entt::meta_ctx::bind(entt::meta_ctx{});
```
This is allowed because local and global contexts are separated. Therefore, it's

View File

@@ -43,8 +43,8 @@ I hope this list can grow much more in the future:
* [The Machine](https://github.com/Kerndog73/The-Machine): a box pushing
puzzler with logic gates and other cool stuff.
[Check it out](https://indi-kernick.itch.io/the-machine-web-version).
* [EnttPong 2.0](https://github.com/DomRe/EnttPong): a basic game made to
showcase different parts of EnTT and C++17.
* [EnTTPong](https://github.com/DomRe/EnttPong): a basic game made to showcase
different parts of EnTT and C++17.
* [Randballs](https://github.com/gale93/randballs): simple `SFML` and `EnTT`
playground.
* [EnTT Tower Defense](https://github.com/Daivuk/tddod): a data oriented tower
@@ -64,6 +64,14 @@ I hope this list can grow much more in the future:
* [DungeonSlayer](https://github.com/alohaeee/DungeonSlayer): 2D game made
from scratch in C++.
* [3DGame](https://github.com/kwarkGorny/3DGame): 2.5D top-down space shooter.
* [Pulcher](https://github.com/AODQ/pulcher): 2D cross-platform game inspired
by Quake.
* [Destroid](https://github.com/tyrannicaltoucan/destroid): _one-bazillionth_
arcade game about shooting dirty rocks in space, inspired by Asteroids.
* [Wanderer](https://github.com/albin-johansson/wanderer): a 2D exploration
based indie game.
* [Spelunky® Classic remake](https://github.com/dbeef/spelunky-psp): A truly
multiplatform experience with a rewrite from scratch.
* Engines and the like:
* [Aether Engine](https://hadean.com/spatial-simulation/)
@@ -88,8 +96,6 @@ I hope this list can grow much more in the future:
[Wisp](https://teamwisp.github.io/product/) by
[Team Wisp](https://teamwisp.github.io/): an advanced real-time ray tracing
renderer built for the demands of video game artists.
* [Apparently](https://github.com/JosiahWI/qub3d-libdeps)
[Qub3d](https://qub3d.org/): because blocks should be open source.
* [shiva](https://github.com/Milerius/shiva): modern C++ engine with
modularity.
* [ImGui/EnTT editor](https://github.com/Green-Sky/imgui_entt_entity_editor):
@@ -104,6 +110,12 @@ I hope this list can grow much more in the future:
Box Generator.
* [Lina Engine](https://github.com/inanevin/LinaEngine): an open-source,
modular, tiny and fast C++ game engine, aimed to develop 3D desktop games.
* [Spike](https://github.com/FahimFuad/Spike): a powerful game engine which
can run on a toaster.
* [Helena Framework](https://github.com/NIKEA-SOFT/HelenaFramework): a modern
framework in C++17 for backend development.
* [Unity/EnTT](https://github.com/TongTungGiang/unity-entt): tech demo of a
native simulation layer using `EnTT` and `Unity` as a rendering engine.
* Articles, videos and blog posts:
* [Some posts](https://skypjack.github.io/tags/#entt) on my personal
@@ -147,8 +159,10 @@ I hope this list can grow much more in the future:
* [Creating Minecraft in One Week with C++ and Vulkan](https://vazgriz.com/189/creating-minecraft-in-one-week-with-c-and-vulkan/):
a crack at recreating Minecraft in one week using a custom C++ engine and
Vulkan ([code included](https://github.com/vazgriz/VoxelGame)).
* [Ability Creator](https://www.erichildebrand.net/blog/ability-creator-project-retrospect).
* [Ability Creator](https://www.erichildebrand.net/blog/ability-creator-project-retrospect):
project retrospect by [Eric Hildebrand](https://www.erichildebrand.net/).
* [EnTT Entity Component System Gaming Library](https://gamefromscratch.com/entt-entity-component-system-gaming-library/):
`EnTT` on GameFromScratch.com.
* Any Other Business:
* [ArcGIS Runtime SDKs](https://developers.arcgis.com/arcgis-runtime/) by

View File

@@ -12,6 +12,8 @@
* [Enjoy the runtime](#enjoy-the-runtime)
* [Container support](#container-support)
* [Pointer-like types](#pointer-like-types)
* [Template information](#template-information)
* [Implicitly generated default constructor](#implicitly-generated-default-constructor)
* [Policies: the more, the less](#policies-the-more-the-less)
* [Named constants and enums](#named-constants-and-enums)
* [Properties and meta objects](#properties-and-meta-objects)
@@ -22,11 +24,11 @@
# Introduction
Reflection (or rather, its lack) is a trending topic in the C++ world and, in
the specific case of `EnTT`, a tool that can unlock a lot of other features. I
Reflection (or rather, its lack) is a trending topic in the C++ world and a tool
that can unlock a lot of interesting feature in the specific case of `EnTT`. I
looked for a third-party library that met my needs on the subject, but I always
came across some details that I didn't like: macros, being intrusive, too many
allocations. In one word: unsatisfactory.<br/>
allocations, and so on.<br/>
I finally decided to write a built-in, non-intrusive and macro-free runtime
reflection system for `EnTT`. Maybe I didn't do better than others or maybe yes,
time will tell me, but at least I can model this tool around the library to
@@ -44,7 +46,7 @@ compile-time or with custom functions.
That being said, the examples in the following sections are all based on the
`hashed_string` class as provided by this library. Therefore, where an
identifier is required, it's likely that a user defined literal is used as
identifier is required, it's likely that an user defined literal is used as
follows:
```cpp
@@ -93,8 +95,8 @@ features to a reflected type so that the reflection system can use it correctly
under the hood, but they don't want to also make the type _searchable_. In this
case, it's sufficient not to invoke `type`.
A factory is such that all its member functions returns the factory itself or
a decorated version of it. This object can be used to add the following:
A factory is such that all its member functions return the factory itself or a
decorated version of it. This object can be used to add the following:
* _Constructors_. Actual constructors can be assigned to a reflected type by
specifying their list of arguments. Free functions (namely, factories) can be
@@ -135,7 +137,7 @@ a decorated version of it. This object can be used to add the following:
The function requires as an argument the identifier to give to the meta data
once created. Users can then access meta data at runtime by searching for them
by _name_.<br/>
Data members can also be defined by means of a _setter_ and _getter_. Setters
Data members can also be defined by means of a setter and getter pair. Setters
and getters can be either free functions, class members or a mix of them, as
long as they respect the required signatures. This approach is also convenient
to create a read-only variable from a non-const data member:
@@ -161,7 +163,9 @@ a decorated version of it. This object can be used to add the following:
The function requires as an argument the identifier to give to the meta
function once created. Users can then access meta functions at runtime by
searching for them by _name_.
searching for them by _name_.<br/>
Overloading of meta functions is supported. Overloaded functions are resolved
at runtime by the reflection system according to the types of the arguments.
* _Base classes_. A base class is such that the underlying type is actually
derived from it. In this case, the reflection system tracks the relationship
@@ -206,19 +210,19 @@ Among the few relevant differences, `meta_any` adds support for containers and
pointer-like types (see the following sections for more details), while `any`
does not.<br/>
Similar to `any`, this class can also be used to create _aliases_ for unmanaged
objects either upon construction using `std::ref` and `std::cref` or from an
existing instance by means of the `as_ref` function. However, unlike `any`,
`meta_any` treats an empty instance and one initialized with `void` differently:
objects either with `forward_as_meta` or using the `std::in_place_type<T &>`
disambiguation tag, as well as from an existing object by means of the `as_ref`
member function. However, unlike `any`, `meta_any` treats an empty instance and
one initialized with `void` differently:
```cpp
entt::meta_any empty{};
entt::meta_any other{std::in_place_type<void>};
```
While `any` treats both objects as empty, `meta_any` treats objects initialized
with `void` as if they were _valid_ ones. This allows to differentiate between
failed function calls and function calls that are successful but return
nothing.<br/>
While `any` considers both as empty, `meta_any` treats objects initialized with
`void` as if they were _valid_ ones. This allows to differentiate between failed
function calls and function calls that are successful but return nothing.<br/>
Finally, the member functions `try_cast`, `cast` and `allow_cast` are used to
cast the underlying object to a given type (either a reference or a value type)
or to _convert_ a `meta_any` in such a way that a cast becomes viable for the
@@ -228,9 +232,9 @@ resulting object. There is in fact no `any_cast` equivalent for `meta_any`.
Once the web of reflected types has been constructed, it's a matter of using it
at runtime where required.<br/>
All this has the great merit that, unlike the vast majority of the things
present in this library and closely linked to the compile-time, the reflection
system stands in fact as a non-intrusive tool for the runtime.
All this has the great merit that the reflection system stands in fact as a
non-intrusive tool for the runtime, unlike the vast majority of the things
offered by this library and closely linked to the compile-time.
To search for a reflected type there are a few options:
@@ -238,11 +242,11 @@ To search for a reflected type there are a few options:
// direct access to a reflected type
auto by_type = entt::resolve<my_type>();
// lookup of a reflected type by identifier
auto by_id = entt::resolve_id("reflected_type"_hs);
// look up a reflected type by identifier
auto by_id = entt::resolve("reflected_type"_hs);
// lookup of a reflected type by type id
auto by_type_id = entt::resolve_type(entt::type_hash<my_type>::value());
// look up a reflected type by type info
auto by_type_id = entt::resolve(entt::type_id<my_type>());
```
There exits also an overload of the `resolve` function to use to iterate all the
@@ -308,24 +312,8 @@ The meta objects that compose a meta type are accessed in the following ways:
auto base = entt::resolve<derived_type>().base("base"_hs);
```
The returned type is `meta_base` and may be invalid if there is no meta base
object associated with the given identifier.<br/>
Meta bases aren't meant to be used directly, even though they are freely
accessible. They expose only a few methods to use to know the meta type of the
base class and to convert a raw pointer between types.
* _Meta conversion functions_. They are accessed by type:
```cpp
auto conv = entt::resolve<double>().conv<int>();
```
The returned type is `meta_conv` and may be invalid if there is no meta
conversion function associated with the given type.<br/>
The meta conversion functions are as thin as the meta bases and with a very
similar interface. The sole difference is that they return a newly created
instance wrapped in a `meta_any` object when they convert between different
types.
The returned type is `meta_type` and may be invalid if there is no meta base
object associated with the given identifier.
All the objects thus obtained as well as the meta types can be explicitly
converted to a boolean value to check if they are valid:
@@ -337,7 +325,7 @@ if(auto func = entt::resolve<my_type>().func("member"_hs); func) {
```
Furthermore, all them are also returned by specific overloads that provide the
caller with iterable objects. As an example:
caller with iterable ranges of top-level elements. As an example:
```cpp
for(auto data = entt::resolve<my_type>().data()) {
@@ -353,12 +341,12 @@ or may not be initialized, depending on whether a suitable constructor has been
found or not.
There is no object that wraps the destructor of a meta type nor a `destroy`
member function in its API. The reason is quickly explained: destructors are
invoked implicitly by `meta_any` behind the scenes and users have not to deal
with them explicitly. Furthermore, they have no name, cannot be searched and
wouldn't have member functions to expose anyway.<br/>
Therefore, exposing destructors would be pointless and would add nothing to the
library itself.
member function in its API. Destructors are invoked implicitly by `meta_any`
behind the scenes and users have not to deal with them explicitly. Furthermore,
they have no name, cannot be searched and wouldn't have member functions to
expose anyway.<br/>
Similarly, conversion functions aren't directly accessible. They are used
internally by `meta_any` and the meta objects when needed.
Meta types and meta objects in general contain much more than what is said: a
plethora of functions in addition to those listed whose purposes and uses go
@@ -368,14 +356,14 @@ read the inline documentation to get the best out of this powerful tool.
## Container support
The meta module supports containers of all types out of the box.<br/>
The runtime reflection system also supports containers of all types.<br/>
Moreover, _containers_ doesn't necessarily mean those offered by the C++
standard library. In fact, user defined data structures can also work with the
meta system in many cases.
To make a container be recognized by the meta module, users are required to
provide specializations for either the `meta_sequence_container_traits` class or
the `meta_associative_container_traits` class, according with the actual _type_
To make a container be recognized as such by the meta system, users are required
to provide specializations for either the `meta_sequence_container_traits` class
or the `meta_associative_container_traits` class, according with the actual type
of the container.<br/>
`EnTT` already exports the specializations for some common classes. In
particular:
@@ -399,7 +387,7 @@ object for a sequence container:
```cpp
std::vector<int> vec{1, 2, 3};
entt::meta_any any{std::ref(vec)};
entt::meta_any any = entt::forward_as_meta(vec);
if(any.type().is_sequence_container()) {
if(auto view = any.as_sequence_container(); view) {
@@ -597,15 +585,14 @@ differences in behavior in the case of key-only containers. In particular:
elements. Modifying the returned object will then directly modify the element
inside the container.
Container support is deliberately minimal but theoretically sufficient to
satisfy all needs.
Container support is minimal but likely sufficient to satisfy all needs.
## Pointer-like types
As with containers, it's also possible to communicate to the meta system which
types to consider _pointers_. This will allow to dereference instances of
`meta_any`, obtaining light _references_ to the pointed objects that are also
correctly associated with their meta types.<br/>
`meta_any`, thus obtaining light _references_ to the pointed objects that are
also correctly associated with their meta types.<br/>
To make the meta system recognize a type as _pointer-like_, users can specialize
the `is_meta_pointer_like` class. `EnTT` already exports the specializations for
some common classes. In particular:
@@ -616,7 +603,7 @@ some common classes. In particular:
It's important to include the header file `pointer.hpp` to make these
specializations available to the compiler when needed.<br/>
The same file also contains many examples for the users that are interested in
making their own containers available to the meta system.
making their own pointer-like types available to the meta system.
When a type is recognized as a pointer-like one by the meta system, it's
possible to dereference the instances of `meta_any` that contain these objects.
@@ -640,7 +627,8 @@ query the meta type or verify that the returned object is valid. For example,
invalid instances are returned when the wrapped object isn't a pointer-like
type.<br/>
Note that dereferencing a pointer-like object returns an instance of `meta_any`
which refers to the pointed object and allows users to modify it directly.
which refers to the pointed object and allows users to modify it directly
(unless the returned element is const, of course).
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:
@@ -656,8 +644,8 @@ In general, _dereferencing_ a pointer-like type boils down to a `*ptr`. However,
```
* When not in control of the type's namespace, it's possible to inject into the
`entt` namespace a specialization of `adl_meta_pointer_like` class template to
bypass the adl lookup as a whole:
`entt` namespace a specialization of the `adl_meta_pointer_like` class
template to bypass the adl lookup as a whole:
```cpp
template<typename Type>
@@ -671,6 +659,95 @@ In general, _dereferencing_ a pointer-like type boils down to a `*ptr`. However,
In all other cases, that is, when dereferencing a pointer works as expected and
regardless of the pointed type, no user intervention is required.
## Template information
Meta types also provide a minimal set of information about the nature of the
original type in case it's a class template.<br/>
By default, this works out of the box and requires no user action. However, it's
important to include the header file `template.hpp` to make these information
available to the compiler when needed.
Meta template information are easily found:
```cpp
// this method returns true if the type is recognized as a class template specialization
if(auto type = entt::resolve<std::shared_ptr<my_type>>(); type.is_template_specialization()) {
// meta type of the class template conveniently wrapped by entt::meta_class_template_tag
auto class_type = type.template_type();
// number of template arguments
std::size_t arity = type.template_arity();
// meta type of the i-th argument
auto arg_type = type.template_arg(0u);
}
```
Typically, when template information for a type are required, what the library
provides is sufficient. However, there are some cases where a user may want more
details or a different set of information.<br/>
Consider the case of a class template that is meant to wrap function types:
```cpp
template<typename>
struct function_type;
template<typename Ret, typename... Args>
struct function_type<Ret(Args...)> {};
```
In this case, rather than the function type, the user might want the return type
and unpacked arguments as if they were different template parameters for the
original class template.<br/>
To achieve this, users must enter the library internals and provide their own
specialization for the class template `entt::meta_template_traits`, such as:
```cpp
template<typename Ret, typename... Args>
struct entt::meta_template_traits<function_type<Ret(Args...)>> {
using class_type = meta_class_template_tag<function_type>;
using args_type = type_list<Ret, Args...>;
};
```
The reflection system doesn't verify the accuracy of the information nor infer a
correspondence between real types and meta types.<br/>
Therefore, the specialization will be used as is and the information it contains
will be associated with the appropriate type when required.
## Implicitly generated default constructor
In many cases, it's useful to be able to create objects of default constructible
types through the reflection system, while not having to explicitly register the
meta type or the default constructor.<br/>
For example, in the case of primitive types like `int` or `char`, but not just
them.
For this reason and only for default constructible types, default constructors
are automatically defined and associated with their meta types, whether they are
explicitly or implicitly generated.<br/>
Therefore, it won't be necessary to do this in order to construct an integer
from its meta type:
```cpp
entt::meta<int>().ctor<>();
```
Instead, just do this:
```cpp
entt::resolve<int>().construct();
```
Where the meta type can be for example the one returned from a meta container,
useful for building keys without knowing or having to register the actual types.
In all cases, when users register custom defaul constructors, they are preferred
both during searches and when the `construct` member function is invoked.<br/>
However, the implicitly generated default constructor will always be returned,
either if one is not explicitly specified or if all constructors are iterated
for some reason (in this case, it will always be the last element).
## Policies: the more, the less
Policies are a kind of compile-time directives that can be used when registering
@@ -693,33 +770,30 @@ There are a few alternatives available at the moment:
* The _as-void_ policy, associated with the type `entt::as_void_t`.<br/>
Its purpose is to discard the return value of a meta object, whatever it is,
thus making it appear as if its type were `void`.<br/>
thus making it appear as if its type were `void`:
```cpp
entt::meta<my_type>().func<&my_type::member_function, entt::as_void_t>("member"_hs);
```
If the use with functions is obvious, it must be said that it's also possible
to use this policy with constructors and data members. In the first case, the
constructor will be invoked but the returned wrapper will actually be empty.
In the second case, instead, the property will not be accessible for reading.
As an example of use:
```cpp
entt::meta<my_type>().func<&my_type::member_function, entt::as_void_t>("member"_hs);
```
* The _as-ref_ and _as-cref_ policies, associated with the types
`entt::as_ref_t` and `entt::as_cref_t`.<br/>
They allow to build wrappers that act as references to unmanaged objects.
Accessing the object contained in the wrapper for which the _reference_ was
requested will make it possible to directly access the instance used to
initialize the wrapper itself.<br/>
These policies work with constructors (for example, when objects are taken
from an external container rather than created on demand), data members and
functions in general (as long as their return types are lvalue references).
As an example of use:
initialize the wrapper itself:
```cpp
entt::meta<my_type>().data<&my_type::data_member, entt::as_ref_t>("member"_hs);
```
These policies work with constructors (for example, when objects are taken
from an external container rather than created on demand), data members and
functions in general.<br/>
If on the one hand `as_cref_t` always forces the return type to be const,
`as_ref_t` _adapts_ to the constness of the passed object and to that of the
return type if any.
Some uses are rather trivial, but it's useful to note that there are some less
obvious corner cases that can in turn be solved with the use of policies.
@@ -744,8 +818,8 @@ Exporting constant values or elements from an enum is as simple as ever:
```cpp
entt::meta<my_enum>()
.data<my_enum::a_value>("a_value"_hs)
.data<my_enum::another_value>("another_value"_hs);
.data<my_enum::a_value>("a_value"_hs)
.data<my_enum::another_value>("another_value"_hs);
entt::meta<int>().data<2048>("max_int"_hs);
```
@@ -859,10 +933,10 @@ the key and the value contained in the form of `meta_any` objects, respectively.
A type registered with the reflection system can also be unregistered. This
means unregistering all its data members, member functions, conversion functions
and so on. However, the base classes won't be unregistered, since they don't
and so on. However, base classes aren't unregistered as well, since they don't
necessarily depend on it. Similarly, implicitly generated types (as an example,
the meta types implicitly generated for function parameters when needed) won't
be unregistered.<br/>
the meta types implicitly generated for function parameters when needed) aren't
unregistered.<br/>
Roughly speaking, unregistering a type means disconnecting all associated meta
objects from it and making its identifier no longer visible. The underlying node
will remain available though, as if it were implicitly generated:

View File

@@ -13,6 +13,7 @@
* [Fullfill a concept](#fullfill-a-concept)
* [Inheritance](#inheritance)
* [Static polymorphism in the wild](#static-polymorphism-in-the-wild)
* [Storage size and alignment requirement](#storage-size-and-alignment-requirement)
<!--
@endcond TURN_OFF_DOXYGEN
-->
@@ -236,7 +237,7 @@ For a deduced concept, inheritance is achieved in a few steps:
```cpp
struct DrawableAndErasable: entt::type_list<> {
template<typename Base>
struct type: typename Drawable::type<Base> {
struct type: typename Drawable::template type<Base> {
static constexpr auto base = std::tuple_size_v<typename entt::poly_vtable<Drawable>::type>;
void erase() { entt::poly_call<base + 0>(*this); }
};
@@ -302,37 +303,57 @@ struct square {
// ...
drawable d{circle{}};
d->draw();
drawable instance{circle{}};
instance->draw();
d = square{};
d->draw();
instance = square{};
instance->draw();
```
The `poly` class template offers a wide range of constructors, from the default
one (which will return an uninitialized `poly` object) to the copy and move
constructor, as well as the ability to create objects in-place.<br/>
Among others, there is a constructor that allows users to wrap unmanaged objects
in a `poly` instance (either const or non-const ones):
constructors, as well as the ability to create objects in-place.<br/>
Among others, there is also a constructor that allows users to wrap unmanaged
objects in a `poly` instance (either const or non-const ones):
```cpp
circle c;
drawable d{std::ref(c)};
circle shape;
drawable instance{std::in_place_type<circle &>, shape};
```
Similarly, it's possible to create non-owning copies of `poly` from an existing
object:
```cpp
drawable other = as_ref(d);
drawable other = instance.as_ref();
```
In both cases, although the interface of the `poly` object doesn't change, it
won't construct any element or take care of destroying the referenced objects.
Note also how the underlying concept is accessed via a call to `operator->` and
not directly as `d.draw()`.<br/>
not directly as `instance.draw()`.<br/>
This allows users to decouple the API of the wrapper from that of the concept.
Therefore, where `d.data()` will invoke the `data` member function of the poly
object, `d->data()` will map directly to the functionality exposed by the
underlying concept.
Therefore, where `instance.data()` will invoke the `data` member function of the
poly object, `instance->data()` will map directly to the functionality exposed
by the underlying concept.
# Storage size and alignment requirement
Under the hood, the `poly` class template makes use of `entt::any`. Therefore,
it can take advantage of the possibility of defining at compile-time the size of
the storage suitable for the small buffer optimization as well as the alignment
requirements:
```cpp
entt::basic_poly<Drawable, sizeof(double[4]), alignof(double[4])>
```
The default size is `sizeof(double[2])`, which seems like a good compromise
between a buffer that is too large and one unable to hold anything larger than
an integer. The alignment requirement is optional instead and by default such
that it's the most stringent (the largest) for any object whose size is at most
equal to the one provided.<br/>
It's worth noting that providing a size of 0 (which is an accepted value in all
respects) will force the system to dynamically allocate the contained objects in
all cases.

View File

@@ -28,9 +28,9 @@ A typical process must inherit from the `process` class template that stays true
to the CRTP idiom. Moreover, derived classes must specify what's the intended
type for elapsed times.
A process should expose publicly the following member functions whether
required (note that it isn't required to define a function unless the derived
class wants to _override_ the default behavior):
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
to _override_ the default behavior):
* `void update(Delta, void *);`

View File

@@ -28,10 +28,14 @@ I hope this list can grow much more in the future:
ECS that uses sparse sets to keep track of entities in systems.
* [EntityX](https://github.com/alecthomas/entityx): a bitset based ECS that
uses a single large matrix of components indexed with entities.
* [Polypropylene](https://github.com/pmbittner/Polypropylene): a hybrid
solution between an ECS and dynamic mixins.
* C#
* [Entitas](https://github.com/sschmid/Entitas-CSharp): the ECS framework for
C# and Unity, where _reactive systems_ were invented.
* [LeoECS](https://github.com/Leopotam/ecs): simple lightweight C# Entity
Component System framework.
* [Svelto.ECS](https://github.com/sebas77/Svelto.ECS): a very interesting
platform agnostic and table based ECS framework.

View File

@@ -23,7 +23,7 @@ loading, and so on.
`EnTT` doesn't pretend to offer a _one-fits-all_ solution for the different
cases. Instead, it offers a minimal and perhaps trivial cache that can be useful
most of the time during prototyping and sometimes even in a production
environment.<br/>
environments.<br/>
For those interested in the subject, the plan is to improve it considerably over
time in terms of performance, memory usage and functionalities. Hoping to make
it, of course, one step at a time.
@@ -126,7 +126,7 @@ identifier and the parameters used to construct the resource as arguments:
// uses the identifier declared above
cache.load<my_loader>(identifier, 0);
// uses a const char * directly as an identifier
// uses a hashed string directly
cache.load<my_loader>("another/identifier"_hs, 42);
```

File diff suppressed because it is too large Load Diff

View File

@@ -2,8 +2,24 @@
#define ENTT_CONFIG_CONFIG_H
#ifndef ENTT_NOEXCEPT
#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION)
# define ENTT_NOEXCEPT noexcept
# define ENTT_THROW throw
# define ENTT_TRY try
# define ENTT_CATCH catch(...)
#else
# define ENTT_NOEXCEPT
# define ENTT_THROW
# define ENTT_TRY if(true)
# define ENTT_CATCH if(false)
#endif
#if defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606L
# include <new>
# define ENTT_LAUNDER(expr) std::launder(expr)
#else
# define ENTT_LAUNDER(expr) expr
#endif
@@ -21,8 +37,17 @@
#endif
#ifndef ENTT_PAGE_SIZE
# define ENTT_PAGE_SIZE 4096
#ifdef ENTT_SPARSE_PAGE
static_assert(ENTT_SPARSE_PAGE && ((ENTT_SPARSE_PAGE & (ENTT_SPARSE_PAGE - 1)) == 0), "ENTT_SPARSE_PAGE must be a power of two");
#else
# define ENTT_SPARSE_PAGE 4096
#endif
#ifdef ENTT_PACKED_PAGE
static_assert(ENTT_PACKED_PAGE && ((ENTT_PACKED_PAGE & (ENTT_PACKED_PAGE - 1)) == 0), "ENTT_PACKED_PAGE must be a power of two");
#else
# define ENTT_PACKED_PAGE 1024
#endif
@@ -31,16 +56,16 @@
# define ENTT_ASSERT(...) (void(0))
#elif !defined ENTT_ASSERT
# include <cassert>
# define ENTT_ASSERT(condition) assert(condition)
# define ENTT_ASSERT(condition, ...) assert(condition)
#endif
#ifndef ENTT_NO_ETO
#ifdef ENTT_NO_ETO
# include <type_traits>
# define ENTT_IS_EMPTY(Type) std::is_empty<Type>
# define ENTT_IGNORE_IF_EMPTY std::false_type
#else
# include <type_traits>
# define ENTT_IS_EMPTY(Type) std::false_type
# define ENTT_IGNORE_IF_EMPTY std::true_type
#endif

View File

@@ -3,7 +3,7 @@
#define ENTT_VERSION_MAJOR 3
#define ENTT_VERSION_MINOR 6
#define ENTT_VERSION_MINOR 8
#define ENTT_VERSION_PATCH 0

View File

@@ -2,11 +2,14 @@
#define ENTT_CORE_ANY_HPP
#include <cstddef>
#include <functional>
#include <new>
#include <memory>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "../core/utility.hpp"
#include "fwd.hpp"
#include "type_info.hpp"
#include "type_traits.hpp"
@@ -14,15 +17,34 @@
namespace entt {
/*! @brief A SBO friendly, type-safe container for single values of any type. */
class any {
enum class operation { COPY, MOVE, DTOR, COMP, ADDR, CADDR, REF, CREF, TYPE };
/**
* @brief A SBO friendly, type-safe container for single values of any type.
* @tparam Len Size of the storage reserved for the small buffer optimization.
* @tparam Align Optional alignment requirement.
*/
template<std::size_t Len, std::size_t Align>
class basic_any {
enum class operation: std::uint8_t { COPY, MOVE, DTOR, COMP, ADDR, CADDR, TYPE };
enum class policy: std::uint8_t { OWNER, REF, CREF };
using storage_type = std::aligned_storage_t<sizeof(double[2]), alignof(double[2])>;
using vtable_type = const void *(const operation, const any &, const void *);
using storage_type = std::aligned_storage_t<Len + !Len, Align>;
using vtable_type = const void *(const operation, const basic_any &, void *);
template<typename Type>
static constexpr auto in_situ = sizeof(Type) <= sizeof(storage_type) && std::is_nothrow_move_constructible_v<Type>;
static constexpr bool in_situ = Len && alignof(Type) <= alignof(storage_type) && sizeof(Type) <= sizeof(storage_type) && std::is_nothrow_move_constructible_v<Type>;
template<typename Type>
[[nodiscard]] static constexpr policy type_to_policy() {
if constexpr(std::is_lvalue_reference_v<Type>) {
if constexpr(std::is_const_v<std::remove_reference_t<Type>>) {
return policy::CREF;
} else {
return policy::REF;
}
} else {
return policy::OWNER;
}
}
template<typename Type>
[[nodiscard]] static bool compare(const void *lhs, const void *rhs) {
@@ -33,186 +55,216 @@ class any {
}
}
static type_info & as_type_info(const void *data) {
return *const_cast<type_info *>(static_cast<const type_info *>(data));
}
static any & as_any(const void *data) {
return *const_cast<any *>(static_cast<const any *>(data));
}
template<typename Type>
static const void * basic_vtable([[maybe_unused]] const operation op, [[maybe_unused]] const any &from, [[maybe_unused]] const void *to) {
static const void * basic_vtable([[maybe_unused]] const operation op, [[maybe_unused]] const basic_any &from, [[maybe_unused]] void *to) {
static_assert(std::is_same_v<std::remove_reference_t<std::remove_const_t<Type>>, Type>, "Invalid type");
if constexpr(!std::is_void_v<Type>) {
if constexpr(std::is_lvalue_reference_v<Type>) {
using base_type = std::remove_reference_t<Type>;
const Type *instance = (in_situ<Type> && from.mode == policy::OWNER)
? ENTT_LAUNDER(reinterpret_cast<const Type *>(&from.storage))
: static_cast<const Type *>(from.instance);
switch(op) {
case operation::REF:
case operation::CREF:
as_any(to).vtable = (op == operation::REF) ? basic_vtable<Type> : basic_vtable<const base_type &>;
[[fallthrough]];
case operation::COPY:
case operation::MOVE:
as_any(to).instance = from.instance;
[[fallthrough]];
case operation::DTOR:
break;
case operation::COMP:
return compare<std::remove_const_t<base_type>>(from.instance, to) ? to : nullptr;
case operation::ADDR:
return std::is_const_v<base_type> ? nullptr : from.instance;
case operation::CADDR:
return from.instance;
case operation::TYPE:
as_type_info(to) = type_id<std::remove_const_t<base_type>>();
break;
switch(op) {
case operation::COPY:
if constexpr(std::is_copy_constructible_v<Type>) {
static_cast<basic_any *>(to)->emplace<Type>(*instance);
}
break;
case operation::MOVE:
if constexpr(in_situ<Type>) {
if(from.mode == policy::OWNER) {
return new (&static_cast<basic_any *>(to)->storage) Type{std::move(*const_cast<Type *>(instance))};
}
}
} else if constexpr(in_situ<Type>) {
#if __cpp_lib_launder >= 201606L
auto *instance = const_cast<Type *>(std::launder(reinterpret_cast<const Type *>(&from.storage)));
#else
auto *instance = const_cast<Type *>(reinterpret_cast<const Type *>(&from.storage));
#endif
switch(op) {
case operation::COPY:
new (&as_any(to).storage) Type{std::as_const(*instance)};
break;
case operation::MOVE:
new (&as_any(to).storage) Type{std::move(*instance)};
[[fallthrough]];
case operation::DTOR:
instance->~Type();
break;
case operation::COMP:
return compare<Type>(instance, to) ? to : nullptr;
case operation::ADDR:
case operation::CADDR:
return instance;
case operation::REF:
case operation::CREF:
as_any(to).vtable = (op == operation::REF) ? basic_vtable<Type &> : basic_vtable<const Type &>;
as_any(to).instance = instance;
break;
case operation::TYPE:
as_type_info(to) = type_id<Type>();
break;
return (static_cast<basic_any *>(to)->instance = std::exchange(const_cast<basic_any &>(from).instance, nullptr));
case operation::DTOR:
if(from.mode == policy::OWNER) {
if constexpr(in_situ<Type>) {
instance->~Type();
} else if constexpr(std::is_array_v<Type>) {
delete[] instance;
} else {
delete instance;
}
}
} else {
switch(op) {
case operation::COPY:
as_any(to).instance = new Type{*static_cast<const Type *>(from.instance)};
break;
case operation::REF:
case operation::CREF:
as_any(to).vtable = (op == operation::REF) ? basic_vtable<Type &> : basic_vtable<const Type &>;
[[fallthrough]];
case operation::MOVE:
as_any(to).instance = from.instance;
break;
case operation::DTOR:
delete static_cast<const Type *>(from.instance);
break;
case operation::COMP:
return compare<Type>(from.instance, to) ? to : nullptr;
case operation::ADDR:
case operation::CADDR:
return from.instance;
case operation::TYPE:
as_type_info(to) = type_id<Type>();
break;
break;
case operation::COMP:
return compare<Type>(instance, (*static_cast<const basic_any **>(to))->data()) ? to : nullptr;
case operation::ADDR:
if(from.mode == policy::CREF) {
return nullptr;
}
[[fallthrough]];
case operation::CADDR:
return instance;
case operation::TYPE:
*static_cast<type_info *>(to) = type_id<Type>();
break;
}
}
return nullptr;
}
template<typename Type, typename... Args>
void initialize([[maybe_unused]] Args &&... args) {
if constexpr(!std::is_void_v<Type>) {
if constexpr(std::is_lvalue_reference_v<Type>) {
static_assert(sizeof...(Args) == 1u && (std::is_lvalue_reference_v<Args> && ...), "Invalid arguments");
instance = (std::addressof(args), ...);
} else if constexpr(in_situ<Type>) {
if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v<Type>) {
new (&storage) Type{std::forward<Args>(args)...};
} else {
new (&storage) Type(std::forward<Args>(args)...);
}
} else {
if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v<Type>) {
instance = new Type{std::forward<Args>(args)...};
} else {
instance = new Type(std::forward<Args>(args)...);
}
}
}
}
basic_any(const basic_any &other, const policy pol) ENTT_NOEXCEPT
: instance{other.data()},
vtable{other.vtable},
mode{pol}
{}
public:
/*! @brief Size of the internal storage. */
static constexpr auto length = Len;
/*! @brief Alignment requirement. */
static constexpr auto alignment = Align;
/*! @brief Default constructor. */
any() ENTT_NOEXCEPT
: any{std::in_place_type<void>}
basic_any() ENTT_NOEXCEPT
: instance{},
vtable{&basic_vtable<void>},
mode{policy::OWNER}
{}
/**
* @brief Constructs an any by directly initializing the new object.
* @brief Constructs a wrapper by directly initializing the new object.
* @tparam Type Type of object to use to initialize the wrapper.
* @tparam Args Types of arguments to use to construct the new instance.
* @param args Parameters to use to construct the instance.
*/
template<typename Type, typename... Args>
explicit any(std::in_place_type_t<Type>, [[maybe_unused]] Args &&... args)
: vtable{&basic_vtable<Type>},
instance{}
explicit basic_any(std::in_place_type_t<Type>, Args &&... args)
: instance{},
vtable{&basic_vtable<std::remove_const_t<std::remove_reference_t<Type>>>},
mode{type_to_policy<Type>()}
{
if constexpr(!std::is_void_v<Type>) {
if constexpr(std::is_lvalue_reference_v<Type>) {
static_assert(sizeof...(Args) == 1u && (std::is_pointer_v<std::remove_reference_t<Args>> && ...));
ENTT_ASSERT(((args != nullptr) && ...));
instance = (args, ...);
} else if constexpr(in_situ<Type>) {
new (&storage) Type{std::forward<Args>(args)...};
} else {
instance = new Type{std::forward<Args>(args)...};
}
}
initialize<Type>(std::forward<Args>(args)...);
}
/**
* @brief Constructs an any that holds an unmanaged object.
* @brief Constructs a wrapper that holds an unmanaged object.
* @tparam Type Type of object to use to initialize the wrapper.
* @param value An instance of an object to use to initialize the wrapper.
*/
template<typename Type>
any(std::reference_wrapper<Type> value) ENTT_NOEXCEPT
: any{std::in_place_type<Type &>, &value.get()}
{}
basic_any(std::reference_wrapper<Type> value) ENTT_NOEXCEPT
: basic_any{}
{
// invokes deprecated assignment operator (and avoids issues with vs2017)
*this = value;
}
/**
* @brief Constructs an any from a given value.
* @brief Constructs a wrapper from a given value.
* @tparam Type Type of object to use to initialize the wrapper.
* @param value An instance of an object to use to initialize the wrapper.
*/
template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_reference_t<Type>>, any>>>
any(Type &&value)
: any{std::in_place_type<std::remove_cv_t<std::remove_reference_t<Type>>>, std::forward<Type>(value)}
{}
template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::decay_t<Type>, basic_any>>>
basic_any(Type &&value)
: instance{},
vtable{&basic_vtable<std::decay_t<Type>>},
mode{policy::OWNER}
{
initialize<std::decay_t<Type>>(std::forward<Type>(value));
}
/**
* @brief Copy constructor.
* @param other The instance to copy from.
*/
any(const any &other)
: any{}
basic_any(const basic_any &other)
: instance{},
vtable{&basic_vtable<void>},
mode{policy::OWNER}
{
vtable = other.vtable;
vtable(operation::COPY, other, this);
other.vtable(operation::COPY, other, this);
}
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
any(any &&other) ENTT_NOEXCEPT
: any{}
basic_any(basic_any &&other) ENTT_NOEXCEPT
: instance{},
vtable{other.vtable},
mode{other.mode}
{
vtable = std::exchange(other.vtable, &basic_vtable<void>);
vtable(operation::MOVE, other, this);
}
/*! @brief Frees the internal storage, whatever it means. */
~any() {
~basic_any() {
vtable(operation::DTOR, *this, nullptr);
}
/**
* @brief Assignment operator.
* @param other The instance to assign from.
* @brief Copy assignment operator.
* @param other The instance to copy from.
* @return This any object.
*/
any & operator=(any other) {
swap(*this, other);
basic_any & operator=(const basic_any &other) {
reset();
other.vtable(operation::COPY, other, this);
return *this;
}
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This any object.
*/
basic_any & operator=(basic_any &&other) ENTT_NOEXCEPT {
std::exchange(vtable, other.vtable)(operation::DTOR, *this, nullptr);
other.vtable(operation::MOVE, other, this);
mode = other.mode;
return *this;
}
/**
* @brief Value assignment operator.
* @tparam Type Type of object to use to initialize the wrapper.
* @param value An instance of an object to use to initialize the wrapper.
* @return This any object.
*/
template<typename Type>
[[deprecated("Use std::in_place_type<T &>, entt::make_any<T &>, emplace<Type &> or forward_as_any instead")]]
basic_any & operator=(std::reference_wrapper<Type> value) ENTT_NOEXCEPT {
emplace<Type &>(value.get());
return *this;
}
/**
* @brief Value assignment operator.
* @tparam Type Type of object to use to initialize the wrapper.
* @param value An instance of an object to use to initialize the wrapper.
* @return This any object.
*/
template<typename Type>
std::enable_if_t<!std::is_same_v<std::decay_t<Type>, basic_any>, basic_any &>
operator=(Type &&value) {
emplace<std::decay_t<Type>>(std::forward<Type>(value));
return *this;
}
@@ -221,7 +273,7 @@ public:
* @return The type of the contained object, if any.
*/
[[nodiscard]] type_info type() const ENTT_NOEXCEPT {
type_info info;
type_info info{};
vtable(operation::TYPE, *this, &info);
return info;
}
@@ -247,7 +299,15 @@ public:
*/
template<typename Type, typename... Args>
void emplace(Args &&... args) {
*this = any{std::in_place_type<Type>, std::forward<Args>(args)...};
std::exchange(vtable, &basic_vtable<std::remove_const_t<std::remove_reference_t<Type>>>)(operation::DTOR, *this, nullptr);
mode = type_to_policy<Type>();
initialize<Type>(std::forward<Args>(args)...);
}
/*! @brief Destroys contained object */
void reset() {
std::exchange(vtable, &basic_vtable<void>)(operation::DTOR, *this, nullptr);
mode = policy::OWNER;
}
/**
@@ -263,54 +323,49 @@ public:
* @param other Wrapper with which to compare.
* @return False if the two objects differ in their content, true otherwise.
*/
bool operator==(const any &other) const ENTT_NOEXCEPT {
return type() == other.type() && (vtable(operation::COMP, *this, other.data()) == other.data());
}
/**
* @brief Swaps two any objects.
* @param lhs A valid any object.
* @param rhs A valid any object.
*/
friend void swap(any &lhs, any &rhs) {
any tmp{};
lhs.vtable(operation::MOVE, lhs, &tmp);
rhs.vtable(operation::MOVE, rhs, &lhs);
lhs.vtable(operation::MOVE, tmp, &rhs);
std::swap(lhs.vtable, rhs.vtable);
bool operator==(const basic_any &other) const ENTT_NOEXCEPT {
const basic_any *trampoline = &other;
return type() == other.type() && (vtable(operation::COMP, *this, &trampoline) || !other.data());
}
/**
* @brief Aliasing constructor.
* @param other A reference to an object that isn't necessarily initialized.
* @return An any that shares a reference to an unmanaged object.
* @return A wrapper that shares a reference to an unmanaged object.
*/
[[nodiscard]] friend any as_ref(any &other) ENTT_NOEXCEPT {
any ref{};
other.vtable(operation::REF, other, &ref);
return ref;
[[nodiscard]] basic_any as_ref() ENTT_NOEXCEPT {
return basic_any{*this, (mode == policy::CREF ? policy::CREF : policy::REF)};
}
/*! @copydoc as_ref */
[[nodiscard]] friend any as_ref(const any &other) ENTT_NOEXCEPT {
any ref{};
other.vtable(operation::CREF, other, &ref);
return ref;
[[nodiscard]] basic_any as_ref() const ENTT_NOEXCEPT {
return basic_any{*this, policy::CREF};
}
/**
* @brief Returns true if a wrapper owns its object, false otherwise.
* @return True if the wrapper owns its object, false otherwise.
*/
[[nodiscard]] bool owner() const ENTT_NOEXCEPT {
return (mode == policy::OWNER);
}
private:
vtable_type *vtable;
union { const void *instance; storage_type storage; };
vtable_type *vtable;
policy mode;
};
/**
* @brief Checks if two wrappers differ in their content.
* @tparam Len Size of the storage reserved for the small buffer optimization.
* @tparam Align Alignment requirement.
* @param lhs A wrapper, either empty or not.
* @param rhs A wrapper, either empty or not.
* @return True if the two wrappers differ in their content, false otherwise.
*/
[[nodiscard]] inline bool operator!=(const any &lhs, const any &rhs) ENTT_NOEXCEPT {
template<std::size_t Len, std::size_t Align>
[[nodiscard]] inline bool operator!=(const basic_any<Len, Align> &lhs, const basic_any<Len, Align> &rhs) ENTT_NOEXCEPT {
return !(lhs == rhs);
}
@@ -318,49 +373,80 @@ private:
/**
* @brief Performs type-safe access to the contained object.
* @tparam Type Type to which conversion is required.
* @tparam Len Size of the storage reserved for the small buffer optimization.
* @tparam Align Alignment requirement.
* @param data Target any object.
* @return The element converted to the requested type.
*/
template<typename Type>
Type any_cast(const any &data) ENTT_NOEXCEPT {
template<typename Type, std::size_t Len, std::size_t Align>
Type any_cast(const basic_any<Len, Align> &data) ENTT_NOEXCEPT {
const auto * const instance = any_cast<std::remove_reference_t<Type>>(&data);
ENTT_ASSERT(instance);
ENTT_ASSERT(instance, "Invalid instance");
return static_cast<Type>(*instance);
}
/*! @copydoc any_cast */
template<typename Type>
Type any_cast(any &data) ENTT_NOEXCEPT {
template<typename Type, std::size_t Len, std::size_t Align>
Type any_cast(basic_any<Len, Align> &data) ENTT_NOEXCEPT {
// forces const on non-reference types to make them work also with wrappers for const references
auto * const instance = any_cast<std::conditional_t<std::is_reference_v<Type>, std::remove_reference_t<Type>, const Type>>(&data);
ENTT_ASSERT(instance);
auto * const instance = any_cast<std::remove_reference_t<const Type>>(&data);
ENTT_ASSERT(instance, "Invalid instance");
return static_cast<Type>(*instance);
}
/*! @copydoc any_cast */
template<typename Type>
Type any_cast(any &&data) ENTT_NOEXCEPT {
template<typename Type, std::size_t Len, std::size_t Align>
Type any_cast(basic_any<Len, Align> &&data) ENTT_NOEXCEPT {
// forces const on non-reference types to make them work also with wrappers for const references
auto * const instance = any_cast<std::conditional_t<std::is_reference_v<Type>, std::remove_reference_t<Type>, const Type>>(&data);
ENTT_ASSERT(instance);
auto * const instance = any_cast<std::remove_reference_t<const Type>>(&data);
ENTT_ASSERT(instance, "Invalid instance");
return static_cast<Type>(std::move(*instance));
}
/*! @copydoc any_cast */
template<typename Type>
const Type * any_cast(const any *data) ENTT_NOEXCEPT {
template<typename Type, std::size_t Len, std::size_t Align>
const Type * any_cast(const basic_any<Len, Align> *data) ENTT_NOEXCEPT {
return (data->type() == type_id<Type>() ? static_cast<const Type *>(data->data()) : nullptr);
}
/*! @copydoc any_cast */
template<typename Type>
Type * any_cast(any *data) ENTT_NOEXCEPT {
template<typename Type, std::size_t Len, std::size_t Align>
Type * any_cast(basic_any<Len, Align> *data) ENTT_NOEXCEPT {
// last attempt to make wrappers for const references return their values
return (data->type() == type_id<Type>() ? static_cast<Type *>(static_cast<constness_as_t<any, Type> *>(data)->data()) : nullptr);
return (data->type() == type_id<Type>() ? static_cast<Type *>(static_cast<constness_as_t<basic_any<Len, Align>, Type> *>(data)->data()) : nullptr);
}
/**
* @brief Constructs a wrapper from a given type, passing it all arguments.
* @tparam Type Type of object to use to initialize the wrapper.
* @tparam Len Size of the storage reserved for the small buffer optimization.
* @tparam Align Optional alignment requirement.
* @tparam Args Types of arguments to use to construct the new instance.
* @param args Parameters to use to construct the instance.
* @return A properly initialized wrapper for an object of the given type.
*/
template<typename Type, std::size_t Len = basic_any<>::length, std::size_t Align = basic_any<Len>::alignment, typename... Args>
basic_any<Len, Align> make_any(Args &&... args) {
return basic_any<Len, Align>{std::in_place_type<Type>, std::forward<Args>(args)...};
}
/**
* @brief Forwards its argument and avoids copies for lvalue references.
* @tparam Len Size of the storage reserved for the small buffer optimization.
* @tparam Align Optional alignment requirement.
* @tparam Type Type of argument to use to construct the new instance.
* @param value Parameter to use to construct the instance.
* @return A properly initialized and not necessarily owning wrapper.
*/
template<std::size_t Len = basic_any<>::length, std::size_t Align = basic_any<Len>::alignment, typename Type>
basic_any<Len, Align> forward_as_any(Type &&value) {
return basic_any<Len, Align>{std::in_place_type<std::conditional_t<std::is_rvalue_reference_v<Type>, std::decay_t<Type>, Type>>, std::forward<Type>(value)};
}

View File

@@ -2,16 +2,25 @@
#define ENTT_CORE_FWD_HPP
#include <type_traits>
#include "../config/config.h"
namespace entt {
template<std::size_t Len = sizeof(double[2]), std::size_t = alignof(typename std::aligned_storage_t<Len + !Len>)>
class basic_any;
/*! @brief Alias declaration for type identifiers. */
using id_type = ENTT_ID_TYPE;
/*! @brief Alias declaration for the most common use case. */
using any = basic_any<>;
}

View File

@@ -212,7 +212,7 @@ private:
* @param str Human-readable identifer.
*/
template<typename Char, std::size_t N>
basic_hashed_string(const Char (&str)[N]) ENTT_NOEXCEPT
basic_hashed_string(const Char (&str)[N])
-> basic_hashed_string<Char>;

View File

@@ -38,7 +38,7 @@ template<typename Type>
auto value = pretty_function.substr(first, pretty_function.find_last_of(ENTT_PRETTY_FUNCTION_SUFFIX) - first);
return value;
#else
return std::string_view{};
return std::string_view{""};
#endif
}
@@ -97,6 +97,9 @@ struct ENTT_API type_seq final {
static const id_type value = internal::type_seq::next();
return value;
}
/*! @copydoc value */
[[nodiscard]] constexpr operator id_type() const ENTT_NOEXCEPT { return value(); }
};
@@ -118,6 +121,9 @@ struct type_hash final {
return type_seq<Type>::value();
#endif
}
/*! @copydoc value */
[[nodiscard]] constexpr operator id_type() const ENTT_NOEXCEPT { return value(); }
};
@@ -134,6 +140,9 @@ struct type_name final {
[[nodiscard]] static constexpr std::string_view value() ENTT_NOEXCEPT {
return internal::type_name<Type>(0);
}
/*! @copydoc value */
[[nodiscard]] constexpr operator std::string_view() const ENTT_NOEXCEPT { return value(); }
};
@@ -176,7 +185,7 @@ public:
* @return True if the object is properly initialized, false otherwise.
*/
[[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT {
return !name_value.empty();
return name_value.data() != nullptr;
}
/**
@@ -236,7 +245,7 @@ private:
* @return The type info object for the given type.
*/
template<typename Type>
type_info type_id() ENTT_NOEXCEPT {
[[nodiscard]] type_info type_id() ENTT_NOEXCEPT {
return type_info{
type_seq<std::remove_cv_t<std::remove_reference_t<Type>>>::value(),
type_hash<std::remove_cv_t<std::remove_reference_t<Type>>>::value(),

View File

@@ -3,8 +3,9 @@
#include <cstddef>
#include <utility>
#include <iterator>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "fwd.hpp"
@@ -12,6 +13,32 @@
namespace entt {
/**
* @brief Utility class to disambiguate overloaded functions.
* @tparam N Number of choices available.
*/
template<std::size_t N>
struct choice_t
// Unfortunately, doxygen cannot parse such a construct.
/*! @cond TURN_OFF_DOXYGEN */
: choice_t<N-1>
/*! @endcond */
{};
/*! @copybrief choice_t */
template<>
struct choice_t<0> {};
/**
* @brief Variable template for the choice trick.
* @tparam N Number of choices available.
*/
template<std::size_t N>
inline constexpr choice_t<N> choice{};
/**
* @brief Identity type trait.
*
@@ -56,7 +83,7 @@ struct size_of<Type, std::void_t<decltype(sizeof(Type))>>
* @tparam Type The type of which to return the size.
*/
template<class Type>
inline constexpr auto size_of_v = size_of<Type>::value;
inline constexpr std::size_t size_of_v = size_of<Type>::value;
/**
@@ -93,32 +120,6 @@ template<id_type Value>
using tag = integral_constant<Value>;
/**
* @brief Utility class to disambiguate overloaded functions.
* @tparam N Number of choices available.
*/
template<std::size_t N>
struct choice_t
// Unfortunately, doxygen cannot parse such a construct.
/*! @cond TURN_OFF_DOXYGEN */
: choice_t<N-1>
/*! @endcond */
{};
/*! @copybrief choice_t */
template<>
struct choice_t<0> {};
/**
* @brief Variable template for the choice trick.
* @tparam N Number of choices available.
*/
template<std::size_t N>
inline constexpr choice_t<N> choice{};
/**
* @brief A class to use to push around lists of types, nothing more.
* @tparam Type Types provided by the type list.
@@ -287,7 +288,32 @@ struct type_list_contains<type_list<Type...>, Other>: std::disjunction<std::is_s
* @tparam Type Type to look for.
*/
template<class List, typename Type>
inline constexpr auto type_list_contains_v = type_list_contains<List, Type>::value;
inline constexpr bool type_list_contains_v = type_list_contains<List, Type>::value;
/*! @brief Primary template isn't defined on purpose. */
template<typename...>
struct type_list_diff;
/**
* @brief Computes the difference between two type lists.
* @tparam Type Types provided by the first type list.
* @tparam Other Types provided by the second type list.
*/
template<typename... Type, typename... Other>
struct type_list_diff<type_list<Type...>, type_list<Other...>> {
/*! @brief A type list that is the difference between the two type lists. */
using type = type_list_cat_t<std::conditional_t<type_list_contains_v<type_list<Other...>, Type>, type_list<>, type_list<Type>>...>;
};
/**
* @brief Helper type.
* @tparam List Type lists between which to compute the difference.
*/
template<typename... List>
using type_list_diff_t = typename type_list_diff<List...>::type;
/**
@@ -396,28 +422,66 @@ template<typename... List>
using value_list_cat_t = typename value_list_cat<List...>::type;
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
namespace internal {
template<typename>
[[nodiscard]] constexpr bool is_equality_comparable(...) { return false; }
template<typename Type>
[[nodiscard]] constexpr auto is_equality_comparable(choice_t<0>)
-> decltype(std::declval<Type>() == std::declval<Type>()) { return true; }
template<typename Type>
[[nodiscard]] constexpr auto is_equality_comparable(choice_t<1>)
-> decltype(std::declval<typename Type::value_type>(), std::declval<Type>() == std::declval<Type>()) {
if constexpr(std::is_same_v<typename Type::value_type, Type>) {
return is_equality_comparable<Type>(choice<0>);
} else {
return is_equality_comparable<typename Type::value_type>(choice<2>);
}
}
template<typename Type>
[[nodiscard]] constexpr auto is_equality_comparable(choice_t<2>)
-> decltype(std::declval<typename Type::mapped_type>(), std::declval<Type>() == std::declval<Type>()) {
return is_equality_comparable<typename Type::key_type>(choice<2>) && is_equality_comparable<typename Type::mapped_type>(choice<2>);
}
}
/**
* Internal details not to be documented.
* @endcond
*/
/**
* @brief Provides the member constant `value` to true if a given type is
* equality comparable, false otherwise.
* @tparam Type Potentially equality comparable type.
* @tparam Type The type to test.
*/
template<typename Type, typename = void>
struct is_equality_comparable: std::false_type {};
/*! @copydoc is_equality_comparable */
template<typename Type>
struct is_equality_comparable<Type, std::void_t<decltype(std::declval<Type>() == std::declval<Type>())>>
: std::true_type
{};
struct is_equality_comparable: std::bool_constant<internal::is_equality_comparable<Type>(choice<2>)> {};
/**
* @brief Helper variable template.
* @tparam Type Potentially equality comparable type.
* @tparam Type The type to test.
*/
template<class Type>
inline constexpr auto is_equality_comparable_v = is_equality_comparable<Type>::value;
inline constexpr bool is_equality_comparable_v = is_equality_comparable<Type>::value;
/*! @brief Same as std::is_invocable, but with tuples. */
@@ -436,11 +500,11 @@ struct is_applicable<Func, Tuple<Args...>>: std::is_invocable<Func, Args...> {};
/**
* @copybrief is_applicable
* @tparam Func A valid function type.
* @tparam Tuple Tuple-like type.
* @tparam Args The list of arguments to use to probe the function type.
*/
* @copybrief is_applicable
* @tparam Func A valid function type.
* @tparam Tuple Tuple-like type.
* @tparam Args The list of arguments to use to probe the function type.
*/
template<typename Func, template<typename...> class Tuple, typename... Args>
struct is_applicable<Func, const Tuple<Args...>>: std::is_invocable<Func, Args...> {};
@@ -451,7 +515,7 @@ struct is_applicable<Func, const Tuple<Args...>>: std::is_invocable<Func, Args..
* @tparam Args The list of arguments to use to probe the function type.
*/
template<typename Func, typename Args>
inline constexpr auto is_applicable_v = is_applicable<Func, Args>::value;
inline constexpr bool is_applicable_v = is_applicable<Func, Args>::value;
/*! @brief Same as std::is_invocable_r, but with tuples for arguments. */
@@ -478,14 +542,14 @@ struct is_applicable_r<Ret, Func, std::tuple<Args...>>: std::is_invocable_r<Ret,
* @tparam Args The list of arguments to use to probe the function type.
*/
template<typename Ret, typename Func, typename Args>
inline constexpr auto is_applicable_r_v = is_applicable_r<Ret, Func, Args>::value;
inline constexpr bool is_applicable_r_v = is_applicable_r<Ret, Func, Args>::value;
/**
* @brief Provides the member constant `value` to true if a given type is
* complete, false otherwise.
* @tparam Type Potential complete type.
*/
* @brief Provides the member constant `value` to true if a given type is
* complete, false otherwise.
* @tparam Type The type to test.
*/
template<typename Type, typename = void>
struct is_complete: std::false_type {};
@@ -496,28 +560,68 @@ struct is_complete<Type, std::void_t<decltype(sizeof(Type))>>: std::true_type {}
/**
* @brief Helper variable template.
* @tparam Type Potential complete type.
*/
* @brief Helper variable template.
* @tparam Type The type to test.
*/
template<typename Type>
inline constexpr auto is_complete_v = is_complete<Type>::value;
inline constexpr bool is_complete_v = is_complete<Type>::value;
/**
* @brief Provides the member constant `value` to true if a given type is empty
* and the empty type optimization is enabled, false otherwise.
* @tparam Type Potential empty type.
* @brief Provides the member constant `value` to true if a given type is an
* iterator, false otherwise.
* @tparam Type The type to test.
*/
template<typename Type, typename = void>
struct is_empty: ENTT_IS_EMPTY(Type) {};
struct is_iterator: std::false_type {};
/*! @copydoc is_iterator */
template<typename Type>
struct is_iterator<Type, std::void_t<typename std::iterator_traits<Type>::iterator_category>>
: std::true_type
{};
/**
* @brief Helper variable template.
* @tparam Type Potential empty type.
* @tparam Type The type to test.
*/
template<typename Type>
inline constexpr auto is_empty_v = is_empty<Type>::value;
inline constexpr bool is_iterator_v = is_iterator<Type>::value;
/**
* @brief Provides the member constant `value` to true if a given type is of the
* required iterator type, false otherwise.
* @tparam Type The type to test.
* @tparam It Required iterator type.
*/
template<typename Type, typename It, typename = void>
struct is_iterator_type: std::false_type {};
/*! @copydoc is_iterator_type */
template<typename Type, typename It>
struct is_iterator_type<Type, It, std::enable_if_t<is_iterator_v<Type> && std::is_same_v<Type, It>>>
: std::true_type
{};
/*! @copydoc is_iterator_type */
template<typename Type, typename It>
struct is_iterator_type<Type, It, std::enable_if_t<!std::is_same_v<Type, It>, std::void_t<typename It::iterator_type>>>
: is_iterator_type<Type, typename It::iterator_type>
{};
/**
* @brief Helper variable template.
* @tparam Type The type to test.
* @tparam It Required iterator type.
*/
template<typename Type, typename It>
inline constexpr bool is_iterator_type_v = is_iterator_type<Type, It>::value;
/**
@@ -527,7 +631,7 @@ inline constexpr auto is_empty_v = is_empty<Type>::value;
*/
template<typename To, typename From>
struct constness_as {
/*! @brief The type resulting from the transcription of the constness */
/*! @brief The type resulting from the transcription of the constness. */
using type = std::remove_const_t<To>;
};
@@ -535,7 +639,7 @@ struct constness_as {
/*! @copydoc constness_as */
template<typename To, typename From>
struct constness_as<To, const From> {
/*! @brief The type resulting from the transcription of the constness */
/*! @brief The type resulting from the transcription of the constness. */
using type = std::add_const_t<To>;
};

View File

@@ -60,7 +60,8 @@ struct overloaded: Func... {
* @tparam Func Types of function objects.
*/
template<class... Func>
overloaded(Func...) -> overloaded<Func...>;
overloaded(Func...)
-> overloaded<Func...>;
/**

View File

@@ -0,0 +1,34 @@
#ifndef ENTT_ENTITY_COMPONENT_HPP
#define ENTT_ENTITY_COMPONENT_HPP
#include <type_traits>
#include "../config/config.h"
namespace entt {
/*! @brief Commonly used default traits for all types. */
struct basic_component_traits {
/*! @brief Pointer stability, default is `std::false_type`. */
using in_place_delete = std::false_type;
/*! @brief Empty type optimization, default is `ENTT_IGNORE_IF_EMPTY`. */
using ignore_if_empty = ENTT_IGNORE_IF_EMPTY;
};
/**
* @brief Common way to access various properties of components.
* @tparam Type Type of component.
*/
template<typename Type, typename = void>
struct component_traits: basic_component_traits {
static_assert(std::is_same_v<std::decay_t<Type>, Type>, "Unsupported type");
};
}
#endif

View File

@@ -12,86 +12,134 @@ namespace entt {
/**
* @brief Entity traits.
*
* Primary template isn't defined on purpose. All the specializations give a
* compile-time error unless the template parameter is an accepted entity type.
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
namespace internal {
template<typename, typename = void>
struct entt_traits;
/**
* @brief Entity traits for enumeration types.
* @tparam Type The type to check.
*/
template<typename Type>
struct entt_traits<Type, std::enable_if_t<std::is_enum_v<Type>>>
: entt_traits<std::underlying_type_t<Type>>
: entt_traits<std::underlying_type_t<Type>>
{};
template<typename Type>
struct entt_traits<Type, std::enable_if_t<std::is_class_v<Type>>>
: entt_traits<typename Type::entity_type>
{};
/**
* @brief Entity traits for a 32 bits entity identifier.
*
* A 32 bits entity identifier guarantees:
*
* * 20 bits for the entity number (suitable for almost all the games).
* * 12 bit for the version (resets in [0-4095]).
*/
template<>
struct entt_traits<std::uint32_t> {
/*! @brief Underlying entity type. */
using entity_type = std::uint32_t;
/*! @brief Underlying version type. */
using version_type = std::uint16_t;
/*! @brief Difference type. */
using difference_type = std::int64_t;
/*! @brief Mask to use to get the entity number out of an identifier. */
static constexpr entity_type entity_mask = 0xFFFFF;
/*! @brief Mask to use to get the version out of an identifier. */
static constexpr entity_type version_mask = 0xFFF;
/*! @brief Extent of the entity number within an identifier. */
static constexpr std::size_t entity_shift = 20u;
};
/**
* @brief Entity traits for a 64 bits entity identifier.
*
* A 64 bits entity identifier guarantees:
*
* * 32 bits for the entity number (an indecently large number).
* * 32 bit for the version (an indecently large number).
*/
template<>
struct entt_traits<std::uint64_t> {
/*! @brief Underlying entity type. */
using entity_type = std::uint64_t;
/*! @brief Underlying version type. */
using version_type = std::uint32_t;
/*! @brief Difference type. */
using difference_type = std::int64_t;
/*! @brief Mask to use to get the entity number out of an identifier. */
static constexpr entity_type entity_mask = 0xFFFFFFFF;
/*! @brief Mask to use to get the version out of an identifier. */
static constexpr entity_type version_mask = 0xFFFFFFFF;
/*! @brief Extent of the entity number within an identifier. */
static constexpr std::size_t entity_shift = 32u;
};
}
/**
* @brief Converts an entity type to its underlying type.
* Internal details not to be documented.
* @endcond
*/
/**
* @brief Entity traits.
* @tparam Type Type of identifier.
*/
template<typename Type>
class entt_traits: private internal::entt_traits<Type> {
using traits_type = internal::entt_traits<Type>;
public:
/*! @brief Value type. */
using value_type = Type;
/*! @brief Underlying entity type. */
using entity_type = typename traits_type::entity_type;
/*! @brief Underlying version type. */
using version_type = typename traits_type::version_type;
/*! @brief Difference type. */
using difference_type = typename traits_type::difference_type;
/**
* @brief Converts an entity to its underlying type.
* @param value The value to convert.
* @return The integral representation of the given value.
*/
[[nodiscard]] static constexpr entity_type to_integral(const value_type value) ENTT_NOEXCEPT {
return static_cast<entity_type>(value);
}
/**
* @brief Returns the entity part once converted to the underlying type.
* @param value The value to convert.
* @return The integral representation of the entity part.
*/
[[nodiscard]] static constexpr entity_type to_entity(const value_type value) ENTT_NOEXCEPT {
return (to_integral(value) & traits_type::entity_mask);
}
/**
* @brief Returns the version part once converted to the underlying type.
* @param value The value to convert.
* @return The integral representation of the version part.
*/
[[nodiscard]] static constexpr version_type to_version(const value_type value) ENTT_NOEXCEPT {
constexpr auto mask = (traits_type::version_mask << traits_type::entity_shift);
return ((to_integral(value) & mask) >> traits_type::entity_shift);
}
/**
* @brief Constructs an identifier from its parts.
*
* If the version part is not provided, a tombstone is returned.<br/>
* If the entity part is not provided, a null identifier is returned.
*
* @param entity The entity part of the identifier.
* @param version The version part of the identifier.
* @return A properly constructed identifier.
*/
[[nodiscard]] static constexpr value_type construct(const entity_type entity = traits_type::entity_mask, const version_type version = traits_type::version_mask) ENTT_NOEXCEPT {
return value_type{(entity & traits_type::entity_mask) | (version << traits_type::entity_shift)};
}
};
/**
* @brief Converts an entity to its underlying type.
* @tparam Entity The value type.
* @param entity The value to convert.
* @return The integral representation of the given value.
*/
template<typename Entity>
[[nodiscard]] constexpr auto to_integral(const Entity entity) ENTT_NOEXCEPT {
return static_cast<typename entt_traits<Entity>::entity_type>(entity);
return entt_traits<Entity>::to_integral(entity);
}
@@ -100,26 +148,28 @@ struct null_t {
/**
* @brief Converts the null object to identifiers of any type.
* @tparam Entity Type of entity identifier.
* @return The null representation for the given identifier.
* @return The null representation for the given type.
*/
template<typename Entity>
[[nodiscard]] constexpr operator Entity() const ENTT_NOEXCEPT {
return Entity{entt_traits<Entity>::entity_mask};
return entt_traits<Entity>::construct();
}
/**
* @brief Compares two null objects.
* @param other A null object.
* @return True in all cases.
*/
[[nodiscard]] constexpr bool operator==(const null_t &) const ENTT_NOEXCEPT {
[[nodiscard]] constexpr bool operator==([[maybe_unused]] const null_t other) const ENTT_NOEXCEPT {
return true;
}
/**
* @brief Compares two null objects.
* @param other A null object.
* @return False in all cases.
*/
[[nodiscard]] constexpr bool operator!=(const null_t &) const ENTT_NOEXCEPT {
[[nodiscard]] constexpr bool operator!=([[maybe_unused]] const null_t other) const ENTT_NOEXCEPT {
return false;
}
@@ -130,8 +180,8 @@ struct null_t {
* @return False if the two elements differ, true otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator==(const Entity &entity) const ENTT_NOEXCEPT {
return (to_integral(entity) & entt_traits<Entity>::entity_mask) == to_integral(static_cast<Entity>(*this));
[[nodiscard]] constexpr bool operator==(const Entity entity) const ENTT_NOEXCEPT {
return entt_traits<Entity>::to_entity(entity) == entt_traits<Entity>::to_entity(*this);
}
/**
@@ -141,9 +191,20 @@ struct null_t {
* @return True if the two elements differ, false otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator!=(const Entity &entity) const ENTT_NOEXCEPT {
[[nodiscard]] constexpr bool operator!=(const Entity entity) const ENTT_NOEXCEPT {
return !(entity == *this);
}
/**
* @brief Creates a null object from an entity identifier of any type.
* @tparam Entity Type of entity identifier.
* @param entity Entity identifier to turn into a null object.
* @return The null representation for the given identifier.
*/
template<typename Entity>
[[nodiscard]] constexpr Entity operator|(const Entity entity) const ENTT_NOEXCEPT {
return entt_traits<Entity>::construct(entt_traits<Entity>::to_entity(*this), entt_traits<Entity>::to_version(entity));
}
};
@@ -155,7 +216,7 @@ struct null_t {
* @return False if the two elements differ, true otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator==(const Entity &entity, const null_t &other) ENTT_NOEXCEPT {
[[nodiscard]] constexpr bool operator==(const Entity entity, const null_t other) ENTT_NOEXCEPT {
return other.operator==(entity);
}
@@ -168,15 +229,100 @@ template<typename Entity>
* @return True if the two elements differ, false otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator!=(const Entity &entity, const null_t &other) ENTT_NOEXCEPT {
[[nodiscard]] constexpr bool operator!=(const Entity entity, const null_t other) ENTT_NOEXCEPT {
return !(other == entity);
}
/*! @brief Tombstone object for all entity identifiers. */
struct tombstone_t {
/**
* @brief Converts the tombstone object to identifiers of any type.
* @tparam Entity Type of entity identifier.
* @return The tombstone representation for the given type.
*/
template<typename Entity>
[[nodiscard]] constexpr operator Entity() const ENTT_NOEXCEPT {
return entt_traits<Entity>::construct();
}
/**
* @brief Compares two tombstone objects.
* @param other A tombstone object.
* @return True in all cases.
*/
[[nodiscard]] constexpr bool operator==([[maybe_unused]] const tombstone_t other) const ENTT_NOEXCEPT {
return true;
}
/**
* @brief Compares two tombstone objects.
* @param other A tombstone object.
* @return False in all cases.
*/
[[nodiscard]] constexpr bool operator!=([[maybe_unused]] const tombstone_t other) const ENTT_NOEXCEPT {
return false;
}
/**
* @brief Compares a tombstone object and an entity identifier of any type.
* @tparam Entity Type of entity identifier.
* @param entity Entity identifier with which to compare.
* @return False if the two elements differ, true otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator==(const Entity entity) const ENTT_NOEXCEPT {
return entt_traits<Entity>::to_version(entity) == entt_traits<Entity>::to_version(*this);
}
/**
* @brief Compares a tombstone object and an entity identifier of any type.
* @tparam Entity Type of entity identifier.
* @param entity Entity identifier with which to compare.
* @return True if the two elements differ, false otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator!=(const Entity entity) const ENTT_NOEXCEPT {
return !(entity == *this);
}
/**
* @brief Creates a tombstone object from an entity identifier of any type.
* @tparam Entity Type of entity identifier.
* @param entity Entity identifier to turn into a tombstone object.
* @return The tombstone representation for the given identifier.
*/
template<typename Entity>
[[nodiscard]] constexpr Entity operator|(const Entity entity) const ENTT_NOEXCEPT {
return entt_traits<Entity>::construct(entt_traits<Entity>::to_entity(entity));
}
};
/**
* Internal details not to be documented.
* @endcond
* @brief Compares a tombstone object and an entity identifier of any type.
* @tparam Entity Type of entity identifier.
* @param entity Entity identifier with which to compare.
* @param other A tombstone object yet to be converted.
* @return False if the two elements differ, true otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator==(const Entity entity, const tombstone_t other) ENTT_NOEXCEPT {
return other.operator==(entity);
}
/**
* @brief Compares a tombstone object and an entity identifier of any type.
* @tparam Entity Type of entity identifier.
* @param entity Entity identifier with which to compare.
* @param other A tombstone object yet to be converted.
* @return True if the two elements differ, false otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator!=(const Entity entity, const tombstone_t other) ENTT_NOEXCEPT {
return !(other == entity);
}
/**
@@ -189,6 +335,16 @@ template<typename Entity>
inline constexpr null_t null{};
/**
* @brief Compile-time constant for tombstone entities.
*
* There exist implicit conversions from this variable to entity identifiers of
* any allowed type. Similarly, there exist comparision operators between the
* tombstone entity and any other entity identifier.
*/
inline constexpr tombstone_t tombstone{};
}

View File

@@ -2,18 +2,19 @@
#define ENTT_ENTITY_FWD_HPP
#include <memory>
#include "../core/fwd.hpp"
namespace entt {
template<typename>
template<typename Entity, typename = std::allocator<Entity>>
class basic_sparse_set;
template<typename, typename, typename>
class basic_storage;
template<typename, typename Type, typename = std::allocator<Type>>
struct basic_storage;
template<typename>
@@ -21,7 +22,7 @@ class basic_registry;
template<typename...>
class basic_view;
struct basic_view;
template<typename>

View File

@@ -68,35 +68,32 @@ class basic_group<Entity, exclude_t<Exclude...>, get_t<Get...>> final {
/*! @brief A registry is allowed to create groups. */
friend class basic_registry<Entity>;
using basic_common_type = basic_sparse_set<Entity>;
template<typename Component>
using storage_type = constness_as_t<typename storage_traits<Entity, std::remove_const_t<Component>>::storage_type, Component>;
class iterable_group final {
friend class basic_group<Entity, exclude_t<Exclude...>, get_t<Get...>>;
class iterable final {
template<typename It>
class iterable_group_iterator final {
friend class iterable_group;
template<typename... Args>
iterable_group_iterator(It from, const std::tuple<storage_type<Get> *...> &args) ENTT_NOEXCEPT
: it{from},
pools{args}
{}
public:
struct iterable_iterator final {
using difference_type = std::ptrdiff_t;
using value_type = decltype(std::tuple_cat(std::tuple<Entity>{}, std::declval<basic_group>().get({})));
using pointer = void;
using reference = value_type;
using iterator_category = std::input_iterator_tag;
iterable_group_iterator & operator++() ENTT_NOEXCEPT {
template<typename... Args>
iterable_iterator(It from, const std::tuple<storage_type<Get> *...> &args) ENTT_NOEXCEPT
: it{from},
pools{args}
{}
iterable_iterator & operator++() ENTT_NOEXCEPT {
return ++it, *this;
}
iterable_group_iterator operator++(int) ENTT_NOEXCEPT {
iterable_group_iterator orig = *this;
iterable_iterator operator++(int) ENTT_NOEXCEPT {
iterable_iterator orig = *this;
return ++(*this), orig;
}
@@ -105,28 +102,28 @@ class basic_group<Entity, exclude_t<Exclude...>, get_t<Get...>> final {
return std::tuple_cat(std::make_tuple(entt), get_as_tuple(*std::get<storage_type<Get> *>(pools), entt)...);
}
[[nodiscard]] bool operator==(const iterable_group_iterator &other) const ENTT_NOEXCEPT {
[[nodiscard]] bool operator==(const iterable_iterator &other) const ENTT_NOEXCEPT {
return other.it == it;
}
[[nodiscard]] bool operator!=(const iterable_group_iterator &other) const ENTT_NOEXCEPT {
[[nodiscard]] bool operator!=(const iterable_iterator &other) const ENTT_NOEXCEPT {
return !(*this == other);
}
private:
It it;
const std::tuple<storage_type<Get> *...> pools;
std::tuple<storage_type<Get> *...> pools;
};
iterable_group(basic_sparse_set<Entity> * const ref, const std::tuple<storage_type<Get> *...> &cpools)
public:
using iterator = iterable_iterator<typename basic_common_type::iterator>;
using reverse_iterator = iterable_iterator<typename basic_common_type::reverse_iterator>;
iterable(basic_common_type * const ref, const std::tuple<storage_type<Get> *...> &cpools)
: handler{ref},
pools{cpools}
{}
public:
using iterator = iterable_group_iterator<typename basic_sparse_set<Entity>::iterator>;
using reverse_iterator = iterable_group_iterator<typename basic_sparse_set<Entity>::reverse_iterator>;
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
return handler ? iterator{handler->begin(), pools} : iterator{{}, pools};
}
@@ -144,11 +141,11 @@ class basic_group<Entity, exclude_t<Exclude...>, get_t<Get...>> final {
}
private:
basic_sparse_set<Entity> * const handler;
basic_common_type * const handler;
const std::tuple<storage_type<Get> *...> pools;
};
basic_group(basic_sparse_set<Entity> &ref, storage_type<Get> &... gpool) ENTT_NOEXCEPT
basic_group(basic_common_type &ref, storage_type<Get> &... gpool) ENTT_NOEXCEPT
: handler{&ref},
pools{&gpool...}
{}
@@ -159,9 +156,11 @@ public:
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Random access iterator type. */
using iterator = typename basic_sparse_set<Entity>::iterator;
using iterator = typename basic_common_type::iterator;
/*! @brief Reversed iterator type. */
using reverse_iterator = typename basic_sparse_set<Entity>::reverse_iterator;
using reverse_iterator = typename basic_common_type::reverse_iterator;
/*! @brief Iterable group type. */
using iterable_group = iterable;
/*! @brief Default constructor to use to create empty, invalid groups. */
basic_group() ENTT_NOEXCEPT
@@ -208,7 +207,7 @@ public:
*
* @return A pointer to the array of entities.
*/
[[nodiscard]] const entity_type * data() const ENTT_NOEXCEPT {
[[nodiscard]] auto data() const ENTT_NOEXCEPT {
return *this ? handler->data() : nullptr;
}
@@ -339,7 +338,7 @@ public:
*/
template<typename... Component>
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
ENTT_ASSERT(contains(entt));
ENTT_ASSERT(contains(entt), "Group does not contain entity");
if constexpr(sizeof...(Component) == 0) {
return std::tuple_cat(get_as_tuple(*std::get<storage_type<Get> *>(pools), entt)...);
@@ -478,7 +477,7 @@ public:
}
private:
basic_sparse_set<entity_type> * const handler;
basic_common_type * const handler;
const std::tuple<storage_type<Get> *...> pools;
};
@@ -534,39 +533,36 @@ class basic_group<Entity, exclude_t<Exclude...>, get_t<Get...>, Owned...> final
/*! @brief A registry is allowed to create groups. */
friend class basic_registry<Entity>;
using basic_common_type = basic_sparse_set<Entity>;
template<typename Component>
using storage_type = constness_as_t<typename storage_traits<Entity, std::remove_const_t<Component>>::storage_type, Component>;
class iterable_group final {
friend class basic_group<Entity, exclude_t<Exclude...>, get_t<Get...>, Owned...>;
class iterable final {
template<typename, typename>
class iterable_group_iterator;
struct iterable_iterator;
template<typename It, typename... OIt>
class iterable_group_iterator<It, type_list<OIt...>> final {
friend class iterable_group;
template<typename... Other>
iterable_group_iterator(It from, const std::tuple<Other...> &other, const std::tuple<storage_type<Get> *...> &cpools) ENTT_NOEXCEPT
: it{from},
owned{std::get<OIt>(other)...},
get{cpools}
{}
public:
struct iterable_iterator<It, type_list<OIt...>> final {
using difference_type = std::ptrdiff_t;
using value_type = decltype(std::tuple_cat(std::tuple<Entity>{}, std::declval<basic_group>().get({})));
using pointer = void;
using reference = value_type;
using iterator_category = std::input_iterator_tag;
iterable_group_iterator & operator++() ENTT_NOEXCEPT {
template<typename... Other>
iterable_iterator(It from, const std::tuple<Other...> &other, const std::tuple<storage_type<Get> *...> &cpools) ENTT_NOEXCEPT
: it{from},
owned{std::get<OIt>(other)...},
get{cpools}
{}
iterable_iterator & operator++() ENTT_NOEXCEPT {
return ++it, (++std::get<OIt>(owned), ...), *this;
}
iterable_group_iterator operator++(int) ENTT_NOEXCEPT {
iterable_group_iterator orig = *this;
iterable_iterator operator++(int) ENTT_NOEXCEPT {
iterable_iterator orig = *this;
return ++(*this), orig;
}
@@ -578,11 +574,11 @@ class basic_group<Entity, exclude_t<Exclude...>, get_t<Get...>, Owned...> final
);
}
[[nodiscard]] bool operator==(const iterable_group_iterator &other) const ENTT_NOEXCEPT {
[[nodiscard]] bool operator==(const iterable_iterator &other) const ENTT_NOEXCEPT {
return other.it == it;
}
[[nodiscard]] bool operator!=(const iterable_group_iterator &other) const ENTT_NOEXCEPT {
[[nodiscard]] bool operator!=(const iterable_iterator &other) const ENTT_NOEXCEPT {
return !(*this == other);
}
@@ -592,24 +588,24 @@ class basic_group<Entity, exclude_t<Exclude...>, get_t<Get...>, Owned...> final
std::tuple<storage_type<Get> *...> get;
};
iterable_group(std::tuple<storage_type<Owned> *..., storage_type<Get> *...> cpools, const std::size_t * const extent)
public:
using iterator = iterable_iterator<
typename basic_common_type::iterator,
type_list_cat_t<std::conditional_t<std::is_void_v<decltype(std::declval<storage_type<Owned>>().get({}))>, type_list<>, type_list<decltype(std::declval<storage_type<Owned>>().end())>>...>
>;
using reverse_iterator = iterable_iterator<
typename basic_common_type::reverse_iterator,
type_list_cat_t<std::conditional_t<std::is_void_v<decltype(std::declval<storage_type<Owned>>().get({}))>, type_list<>, type_list<decltype(std::declval<storage_type<Owned>>().rbegin())>>...>
>;
iterable(std::tuple<storage_type<Owned> *..., storage_type<Get> *...> cpools, const std::size_t * const extent)
: pools{cpools},
length{extent}
{}
public:
using iterator = iterable_group_iterator<
typename basic_sparse_set<Entity>::iterator,
type_list_cat_t<std::conditional_t<std::is_same_v<typename storage_type<Owned>::storage_category, empty_storage_tag>, type_list<>, type_list<decltype(std::declval<storage_type<Owned>>().end())>>...>
>;
using reverse_iterator = iterable_group_iterator<
typename basic_sparse_set<Entity>::reverse_iterator,
type_list_cat_t<std::conditional_t<std::is_same_v<typename storage_type<Owned>::storage_category, empty_storage_tag>, type_list<>, type_list<decltype(std::declval<storage_type<Owned>>().rbegin())>>...>
>;
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
return length ? iterator{
std::get<0>(pools)->basic_sparse_set<Entity>::end() - *length,
std::get<0>(pools)->basic_common_type::end() - *length,
std::make_tuple((std::get<storage_type<Owned> *>(pools)->end() - *length)...),
std::make_tuple(std::get<storage_type<Get> *>(pools)...)
} : iterator{{}, std::make_tuple(decltype(std::get<storage_type<Owned> *>(pools)->end()){}...), std::make_tuple(std::get<storage_type<Get> *>(pools)...)};
@@ -617,7 +613,7 @@ class basic_group<Entity, exclude_t<Exclude...>, get_t<Get...>, Owned...> final
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
return length ? iterator{
std::get<0>(pools)->basic_sparse_set<Entity>::end(),
std::get<0>(pools)->basic_common_type::end(),
std::make_tuple((std::get<storage_type<Owned> *>(pools)->end())...),
std::make_tuple(std::get<storage_type<Get> *>(pools)...)
} : iterator{{}, std::make_tuple(decltype(std::get<storage_type<Owned> *>(pools)->end()){}...), std::make_tuple(std::get<storage_type<Get> *>(pools)...)};
@@ -625,7 +621,7 @@ class basic_group<Entity, exclude_t<Exclude...>, get_t<Get...>, Owned...> final
[[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
return length ? reverse_iterator{
std::get<0>(pools)->basic_sparse_set<Entity>::rbegin(),
std::get<0>(pools)->basic_common_type::rbegin(),
std::make_tuple((std::get<storage_type<Owned> *>(pools)->rbegin())...),
std::make_tuple(std::get<storage_type<Get> *>(pools)...)
} : reverse_iterator{{}, std::make_tuple(decltype(std::get<storage_type<Owned> *>(pools)->rbegin()){}...), std::make_tuple(std::get<storage_type<Get> *>(pools)...)};
@@ -633,7 +629,7 @@ class basic_group<Entity, exclude_t<Exclude...>, get_t<Get...>, Owned...> final
[[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
return length ? reverse_iterator{
std::get<0>(pools)->basic_sparse_set<Entity>::rbegin() + *length,
std::get<0>(pools)->basic_common_type::rbegin() + *length,
std::make_tuple((std::get<storage_type<Owned> *>(pools)->rbegin() + *length)...),
std::make_tuple(std::get<storage_type<Get> *>(pools)...)
} : reverse_iterator{{}, std::make_tuple(decltype(std::get<storage_type<Owned> *>(pools)->rbegin()){}...), std::make_tuple(std::get<storage_type<Get> *>(pools)...)};
@@ -655,9 +651,11 @@ public:
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Random access iterator type. */
using iterator = typename basic_sparse_set<Entity>::iterator;
using iterator = typename basic_common_type::iterator;
/*! @brief Reversed iterator type. */
using reverse_iterator = typename basic_sparse_set<Entity>::reverse_iterator;
using reverse_iterator = typename basic_common_type::reverse_iterator;
/*! @brief Iterable group type. */
using iterable_group = iterable;
/*! @brief Default constructor to use to create empty, invalid groups. */
basic_group() ENTT_NOEXCEPT
@@ -681,11 +679,7 @@ public:
}
/**
* @brief Direct access to the list of components of a given pool.
*
* The returned pointer is such that range
* `[raw<Component>(), raw<Component>() + size())` is always a valid range,
* even if the container is empty.<br/>
* @brief Direct access to the raw representation offered by the storage.
*
* @warning
* This function is only available for owned types.
@@ -694,10 +688,10 @@ public:
* @return A pointer to the array of components.
*/
template<typename Component>
[[nodiscard]] Component * raw() const ENTT_NOEXCEPT {
static_assert((std::is_same_v<Component, Owned> || ...));
[[nodiscard]] auto raw() const ENTT_NOEXCEPT {
static_assert((std::is_same_v<Component, Owned> || ...), "Non-owned type");
auto *cpool = std::get<storage_type<Component> *>(pools);
return cpool ? cpool->raw() : nullptr;
return cpool ? cpool->raw() : decltype(cpool->raw()){};
}
/**
@@ -708,7 +702,7 @@ public:
*
* @return A pointer to the array of entities.
*/
[[nodiscard]] const entity_type * data() const ENTT_NOEXCEPT {
[[nodiscard]] auto data() const ENTT_NOEXCEPT {
return *this ? std::get<0>(pools)->data() : nullptr;
}
@@ -721,7 +715,7 @@ public:
* @return An iterator to the first entity of the group.
*/
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
return *this ? (std::get<0>(pools)->basic_sparse_set<entity_type>::end() - *length) : iterator{};
return *this ? (std::get<0>(pools)->basic_common_type::end() - *length) : iterator{};
}
/**
@@ -735,7 +729,7 @@ public:
* group.
*/
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
return *this ? std::get<0>(pools)->basic_sparse_set<entity_type>::end() : iterator{};
return *this ? std::get<0>(pools)->basic_common_type::end() : iterator{};
}
/**
@@ -747,7 +741,7 @@ public:
* @return An iterator to the first entity of the reversed group.
*/
[[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
return *this ? std::get<0>(pools)->basic_sparse_set<entity_type>::rbegin() : reverse_iterator{};
return *this ? std::get<0>(pools)->basic_common_type::rbegin() : reverse_iterator{};
}
/**
@@ -762,7 +756,7 @@ public:
* reversed group.
*/
[[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
return *this ? (std::get<0>(pools)->basic_sparse_set<entity_type>::rbegin() + *length) : reverse_iterator{};
return *this ? (std::get<0>(pools)->basic_common_type::rbegin() + *length) : reverse_iterator{};
}
/**
@@ -839,7 +833,7 @@ public:
*/
template<typename... Component>
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
ENTT_ASSERT(contains(entt));
ENTT_ASSERT(contains(entt), "Group does not contain entity");
if constexpr(sizeof...(Component) == 0) {
return std::tuple_cat(get_as_tuple(*std::get<storage_type<Owned> *>(pools), entt)..., get_as_tuple(*std::get<storage_type<Get> *>(pools), entt)...);
@@ -940,7 +934,7 @@ public:
template<typename... Component, typename Compare, typename Sort = std_sort, typename... Args>
void sort(Compare compare, Sort algo = Sort{}, Args &&... args) const {
auto *cpool = std::get<0>(pools);
if constexpr(sizeof...(Component) == 0) {
static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>, "Invalid comparison function");
cpool->sort_n(*length, std::move(compare), std::move(algo), std::forward<Args>(args)...);
@@ -953,7 +947,7 @@ public:
return compare(std::forward_as_tuple(std::get<storage_type<Component> *>(pools)->get(lhs)...), std::forward_as_tuple(std::get<storage_type<Component> *>(pools)->get(rhs)...));
}, std::move(algo), std::forward<Args>(args)...);
}
[this](auto *head, auto *... other) {
for(auto next = *length; next; --next) {
const auto pos = next - 1;

View File

@@ -24,10 +24,14 @@ namespace entt {
*/
template<typename Entity, typename... Type>
struct basic_handle {
/*! @brief Underlying entity identifier. */
using entity_type = std::remove_const_t<Entity>;
/*! @brief Type of registry accepted by the handle. */
using registry_type = constness_as_t<basic_registry<entity_type>, Entity>;
using registry_type = constness_as_t<basic_registry<std::remove_const_t<Entity>>, Entity>;
/*! @brief Underlying entity identifier. */
using entity_type = typename registry_type::entity_type;
/*! @brief Underlying version type. */
using version_type = typename registry_type::version_type;
/*! @brief Unsigned integer type. */
using size_type = typename registry_type::size_type;
/*! @brief Constructs an invalid handle. */
basic_handle() ENTT_NOEXCEPT
@@ -126,7 +130,7 @@ struct basic_handle {
* @sa basic_registry::destroy
* @param version A desired version upon destruction.
*/
void destroy(const typename registry_type::version_type version) {
void destroy(const version_type version) {
reg->destroy(entt, version);
}
@@ -190,29 +194,37 @@ struct basic_handle {
* @brief Removes the given components from a handle.
* @sa basic_registry::remove
* @tparam Component Types of components to remove.
*/
template<typename... Component>
void remove() const {
static_assert(sizeof...(Type) == 0 || (type_list_contains_v<type_list<Type...>, Component> && ...), "Invalid type");
reg->template remove<Component...>(entt);
}
/**
* @brief Removes the given components from a handle.
* @sa basic_registry::remove_if_exists
* @tparam Component Types of components to remove.
* @return The number of components actually removed.
*/
template<typename... Component>
decltype(auto) remove_if_exists() const {
size_type remove() const {
static_assert(sizeof...(Type) == 0 || (type_list_contains_v<type_list<Type...>, Component> && ...), "Invalid type");
return reg->template remove_if_exists<Component...>(entt);
return reg->template remove<Component...>(entt);
}
/**
* @brief Erases the given components from a handle.
* @sa basic_registry::erase
* @tparam Component Types of components to erase.
*/
template<typename... Component>
void erase() const {
static_assert(sizeof...(Type) == 0 || (type_list_contains_v<type_list<Type...>, Component> && ...), "Invalid type");
reg->template erase<Component...>(entt);
}
/*! @copydoc remove */
template<typename... Component>
[[deprecated("Use ::remove instead")]]
size_type remove_if_exists() const {
return remove<Component...>();
}
/**
* @brief Removes all the components from a handle and makes it orphaned.
* @sa basic_registry::remove_all
*/
[[deprecated("No longer supported")]]
void remove_all() const {
static_assert(sizeof...(Type) == 0, "Invalid operation");
reg->remove_all(entt);
@@ -220,25 +232,25 @@ struct basic_handle {
/**
* @brief Checks if a handle has all the given components.
* @sa basic_registry::has
* @sa basic_registry::all_of
* @tparam Component Components for which to perform the check.
* @return True if the handle has all the components, false otherwise.
*/
template<typename... Component>
[[nodiscard]] decltype(auto) has() const {
return reg->template has<Component...>(entt);
[[nodiscard]] decltype(auto) all_of() const {
return reg->template all_of<Component...>(entt);
}
/**
* @brief Checks if a handle has at least one of the given components.
* @sa basic_registry::any
* @sa basic_registry::any_of
* @tparam Component Components for which to perform the check.
* @return True if the handle has at least one of the given components,
* false otherwise.
*/
template<typename... Component>
[[nodiscard]] decltype(auto) any() const {
return reg->template any<Component...>(entt);
[[nodiscard]] decltype(auto) any_of() const {
return reg->template any_of<Component...>(entt);
}
/**
@@ -324,7 +336,8 @@ bool operator!=(const basic_handle<Type> &lhs, const basic_handle<Other> &rhs) E
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
basic_handle(basic_registry<Entity> &, Entity) -> basic_handle<Entity>;
basic_handle(basic_registry<Entity> &, Entity)
-> basic_handle<Entity>;
/**
@@ -332,7 +345,8 @@ basic_handle(basic_registry<Entity> &, Entity) -> basic_handle<Entity>;
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
basic_handle(const basic_registry<Entity> &, Entity) -> basic_handle<const Entity>;
basic_handle(const basic_registry<Entity> &, Entity)
-> basic_handle<const Entity>;
}

View File

@@ -4,6 +4,7 @@
#include <type_traits>
#include "../config/config.h"
#include "../core/fwd.hpp"
#include "../core/type_traits.hpp"
#include "../signal/delegate.hpp"
#include "registry.hpp"
@@ -48,19 +49,18 @@ private:
/**
* @brief Deduction guide.
*
* It allows to deduce the constness of a registry directly from the instance
* provided to the constructor.
*
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
as_view(basic_registry<Entity> &) ENTT_NOEXCEPT -> as_view<Entity>;
as_view(basic_registry<Entity> &) -> as_view<Entity>;
/*! @copydoc as_view */
/**
* @brief Deduction guide.
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
as_view(const basic_registry<Entity> &) ENTT_NOEXCEPT -> as_view<const Entity>;
as_view(const basic_registry<Entity> &) -> as_view<const Entity>;
/**
@@ -103,19 +103,18 @@ private:
/**
* @brief Deduction guide.
*
* It allows to deduce the constness of a registry directly from the instance
* provided to the constructor.
*
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
as_group(basic_registry<Entity> &) ENTT_NOEXCEPT -> as_group<Entity>;
as_group(basic_registry<Entity> &) -> as_group<Entity>;
/*! @copydoc as_group */
/**
* @brief Deduction guide.
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
as_group(const basic_registry<Entity> &) ENTT_NOEXCEPT -> as_group<const Entity>;
as_group(const basic_registry<Entity> &) -> as_group<const Entity>;
@@ -137,15 +136,29 @@ void invoke(basic_registry<Entity> &reg, const Entity entt) {
/**
* @brief Returns the entity associated with a given component.
*
* @warning
* Currently, this function only works correctly with the default pool as it
* makes assumptions about how the components are laid out.
*
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Component Type of component.
* @param reg A registry that contains the given entity and its components.
* @param component A valid component instance.
* @param instance A valid component instance.
* @return The entity associated with the given component.
*/
template<typename Entity, typename Component>
Entity to_entity(const basic_registry<Entity> &reg, const Component &component) {
return *(reg.template data<Component>() + (&component - reg.template raw<Component>()));
Entity to_entity(const basic_registry<Entity> &reg, const Component &instance) {
const auto view = reg.template view<const Component>();
const auto *addr = std::addressof(instance);
for(auto it = view.rbegin(), last = view.rend(); it < last; it += ENTT_PACKED_PAGE) {
if(const auto dist = (addr - std::addressof(view.template get<const Component>(*it))); dist >= 0 && dist < ENTT_PACKED_PAGE) {
return *(it + dist);
}
}
return entt::null;
}

View File

@@ -179,19 +179,19 @@ class basic_observer {
struct matcher_handler<matcher<type_list<Reject...>, type_list<Require...>, AnyOf>> {
template<std::size_t Index>
static void maybe_valid_if(basic_observer &obs, basic_registry<Entity> &reg, const Entity entt) {
if(reg.template has<Require...>(entt) && !reg.template any<Reject...>(entt)) {
if(!obs.view.contains(entt)) {
obs.view.emplace(entt);
if(reg.template all_of<Require...>(entt) && !reg.template any_of<Reject...>(entt)) {
if(!obs.storage.contains(entt)) {
obs.storage.emplace(entt);
}
obs.view.get(entt) |= (1 << Index);
obs.storage.get(entt) |= (1 << Index);
}
}
template<std::size_t Index>
static void discard_if(basic_observer &obs, basic_registry<Entity> &, const Entity entt) {
if(obs.view.contains(entt) && !(obs.view.get(entt) &= (~(1 << Index)))) {
obs.view.remove(entt);
if(obs.storage.contains(entt) && !(obs.storage.get(entt) &= (~(1 << Index)))) {
obs.storage.erase(entt);
}
}
@@ -217,24 +217,24 @@ class basic_observer {
static void maybe_valid_if(basic_observer &obs, basic_registry<Entity> &reg, const Entity entt) {
if([&reg, entt]() {
if constexpr(sizeof...(Ignore) == 0) {
return reg.template has<AllOf..., Require...>(entt) && !reg.template any<NoneOf..., Reject...>(entt);
return reg.template all_of<AllOf..., Require...>(entt) && !reg.template any_of<NoneOf..., Reject...>(entt);
} else {
return reg.template has<AllOf..., Require...>(entt) && ((std::is_same_v<Ignore..., NoneOf> || !reg.template any<NoneOf>(entt)) && ...) && !reg.template any<Reject...>(entt);
return reg.template all_of<AllOf..., Require...>(entt) && ((std::is_same_v<Ignore..., NoneOf> || !reg.template any_of<NoneOf>(entt)) && ...) && !reg.template any_of<Reject...>(entt);
}
}())
{
if(!obs.view.contains(entt)) {
obs.view.emplace(entt);
if(!obs.storage.contains(entt)) {
obs.storage.emplace(entt);
}
obs.view.get(entt) |= (1 << Index);
obs.storage.get(entt) |= (1 << Index);
}
}
template<std::size_t Index>
static void discard_if(basic_observer &obs, basic_registry<Entity> &, const Entity entt) {
if(obs.view.contains(entt) && !(obs.view.get(entt) &= (~(1 << Index)))) {
obs.view.remove(entt);
if(obs.storage.contains(entt) && !(obs.storage.get(entt) &= (~(1 << Index)))) {
obs.storage.erase(entt);
}
}
@@ -281,7 +281,7 @@ public:
/*! @brief Default constructor. */
basic_observer()
: release{},
view{}
storage{}
{}
/*! @brief Default copy constructor, deleted on purpose. */
@@ -325,7 +325,7 @@ public:
void connect(basic_registry<entity_type> &reg, basic_collector<Matcher...>) {
disconnect();
connect<Matcher...>(reg, std::index_sequence_for<Matcher...>{});
view.clear();
storage.clear();
}
/*! @brief Disconnects an observer from the registry it keeps track of. */
@@ -341,7 +341,7 @@ public:
* @return Number of elements.
*/
[[nodiscard]] size_type size() const ENTT_NOEXCEPT {
return view.size();
return storage.size();
}
/**
@@ -349,7 +349,7 @@ public:
* @return True if the observer is empty, false otherwise.
*/
[[nodiscard]] bool empty() const ENTT_NOEXCEPT {
return view.empty();
return storage.empty();
}
/**
@@ -365,7 +365,7 @@ public:
* @return A pointer to the array of entities.
*/
[[nodiscard]] const entity_type * data() const ENTT_NOEXCEPT {
return view.data();
return storage.data();
}
/**
@@ -377,7 +377,7 @@ public:
* @return An iterator to the first entity of the observer.
*/
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
return view.basic_sparse_set<entity_type>::begin();
return storage.basic_sparse_set<entity_type>::begin();
}
/**
@@ -391,12 +391,12 @@ public:
* observer.
*/
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
return view.basic_sparse_set<entity_type>::end();
return storage.basic_sparse_set<entity_type>::end();
}
/*! @brief Clears the underlying container. */
void clear() ENTT_NOEXCEPT {
view.clear();
storage.clear();
}
/**
@@ -436,7 +436,7 @@ public:
private:
delegate<void(basic_observer &)> release;
basic_storage<entity_type, payload_type> view;
basic_storage<entity_type, payload_type> storage;
};

View File

@@ -160,7 +160,7 @@ class basic_organizer final {
return {};
} else {
type_info info[sizeof...(Type)]{type_id<Type>()...};
const auto length = std::min(count, sizeof...(Type));
const auto length = (std::min)(count, sizeof...(Type));
std::copy_n(info, length, buffer);
return length;
}

View File

@@ -18,15 +18,7 @@ namespace entt {
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
struct Storage: type_list<
type_info() const ENTT_NOEXCEPT,
void(basic_registry<Entity> &, const Entity *, const Entity *)
> {
/*! @brief Underlying entity identifier. */
using entity_type = Entity;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
struct Storage: type_list<type_info() const ENTT_NOEXCEPT> {
/**
* @brief Concept definition.
* @tparam Base Opaque base class from which to inherit.
@@ -40,18 +32,6 @@ struct Storage: type_list<
type_info value_type() const ENTT_NOEXCEPT {
return poly_call<0>(*this);
}
/**
* @brief Removes entities from a storage.
* @param owner The registry that issued the request.
* @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 remove(basic_registry<entity_type> &owner, const entity_type *first, const entity_type *last) {
poly_call<1>(*this, owner, first, last);
}
};
/**
@@ -59,10 +39,7 @@ struct Storage: type_list<
* @tparam Type Type for which to generate an implementation.
*/
template<typename Type>
using impl = value_list<
&type_id<typename Type::value_type>,
&Type::template remove<const entity_type *>
>;
using impl = value_list<&type_id<typename Type::value_type>>;
};

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,7 @@
#include <algorithm>
#include <type_traits>
#include "../config/config.h"
#include "entity.hpp"
#include "sparse_set.hpp"
#include "fwd.hpp"
@@ -55,24 +56,16 @@ namespace entt {
*/
template<typename Entity>
class basic_runtime_view final {
using underlying_iterator = typename basic_sparse_set<Entity>::iterator;
using basic_common_type = basic_sparse_set<Entity>;
using underlying_iterator = typename basic_common_type::iterator;
class view_iterator final {
friend class basic_runtime_view<Entity>;
view_iterator(const std::vector<const basic_sparse_set<Entity> *> &cpools, const std::vector<const basic_sparse_set<Entity> *> &ignore, underlying_iterator curr) ENTT_NOEXCEPT
: pools{&cpools},
filter{&ignore},
it{curr}
{
if(it != (*pools)[0]->end() && !valid()) {
++(*this);
}
}
[[nodiscard]] bool valid() const {
return std::all_of(pools->begin()++, pools->end(), [entt = *it](const auto *curr) { return curr->contains(entt); })
&& std::none_of(filter->cbegin(), filter->cend(), [entt = *it](const auto *curr) { return curr && curr->contains(entt); });
const auto entt = *it;
return (!stable_storage || (entt != tombstone))
&& std::all_of(pools->begin()++, pools->end(), [entt](const auto *curr) { return curr->contains(entt); })
&& std::none_of(filter->cbegin(), filter->cend(), [entt](const auto *curr) { return curr && curr->contains(entt); });
}
public:
@@ -84,6 +77,17 @@ class basic_runtime_view final {
view_iterator() ENTT_NOEXCEPT = default;
view_iterator(const std::vector<const basic_common_type *> &cpools, const std::vector<const basic_common_type *> &ignore, underlying_iterator curr) ENTT_NOEXCEPT
: pools{&cpools},
filter{&ignore},
it{curr},
stable_storage{std::any_of(pools->cbegin(), pools->cend(), [](const basic_common_type *cpool) { return (cpool->policy() == deletion_policy::in_place); })}
{
if(it != (*pools)[0]->end() && !valid()) {
++(*this);
}
}
view_iterator & operator++() {
while(++it != (*pools)[0]->end() && !valid());
return *this;
@@ -121,9 +125,10 @@ class basic_runtime_view final {
}
private:
const std::vector<const basic_sparse_set<Entity> *> *pools;
const std::vector<const basic_sparse_set<Entity> *> *filter;
const std::vector<const basic_common_type *> *pools;
const std::vector<const basic_common_type *> *filter;
underlying_iterator it;
bool stable_storage;
};
[[nodiscard]] bool valid() const {
@@ -149,16 +154,14 @@ public:
* @param cpools The storage for the types to iterate.
* @param epools The storage for the types used to filter the view.
*/
basic_runtime_view(std::vector<const basic_sparse_set<Entity> *> cpools, std::vector<const basic_sparse_set<Entity> *> epools) ENTT_NOEXCEPT
basic_runtime_view(std::vector<const basic_common_type *> cpools, std::vector<const basic_common_type *> epools) ENTT_NOEXCEPT
: pools{std::move(cpools)},
filter{std::move(epools)}
{
const auto it = std::min_element(pools.begin(), pools.end(), [](const auto *lhs, const auto *rhs) {
return (!lhs && rhs) || (lhs && rhs && lhs->size() < rhs->size());
});
// brings the best candidate (if any) on front of the vector
std::rotate(pools.begin(), it, pools.end());
std::rotate(pools.begin(), std::min_element(pools.begin(), pools.end(), [](const auto *lhs, const auto *rhs) {
return (!lhs && rhs) || (lhs && rhs && lhs->size() < rhs->size());
}), pools.end());
}
/**
@@ -231,8 +234,8 @@ public:
}
private:
std::vector<const basic_sparse_set<Entity> *> pools;
std::vector<const basic_sparse_set<Entity> *> filter;
std::vector<const basic_common_type *> pools;
std::vector<const basic_common_type *> filter;
};

View File

@@ -42,7 +42,7 @@ class basic_snapshot {
while(first != last) {
const auto entt = *(first++);
if(reg->template has<Component>(entt)) {
if(reg->template all_of<Component>(entt)) {
std::apply(archive, std::tuple_cat(std::make_tuple(entt), view.get(entt)));
}
}
@@ -55,7 +55,7 @@ class basic_snapshot {
while(begin != last) {
const auto entt = *(begin++);
((reg->template has<Component>(entt) ? ++size[Index] : size[Index]), ...);
((reg->template all_of<Component>(entt) ? ++size[Index] : size[Index]), ...);
}
(get<Component>(archive, size[Index], first, last), ...);
@@ -99,7 +99,7 @@ public:
archive(*first);
}
archive(reg->destroyed());
archive(reg->released());
return *this;
}
@@ -117,8 +117,14 @@ public:
*/
template<typename... Component, typename Archive>
const basic_snapshot & component(Archive &archive) const {
(component<Component>(archive, reg->template data<Component>(), reg->template data<Component>() + reg->template size<Component>()), ...);
return *this;
if constexpr(sizeof...(Component) == 1u) {
const auto view = reg->template view<const Component...>();
(component<Component>(archive, view.data(), view.data() + view.size()), ...);
return *this;
} else {
(component<Component>(archive), ...);
return *this;
}
}
/**
@@ -171,7 +177,7 @@ class basic_snapshot_loader {
while(length--) {
archive(entt);
const auto entity = reg->valid(entt) ? entt : reg->create(entt);
ENTT_ASSERT(entity == entt);
ENTT_ASSERT(entity == entt, "Entity not available for use");
reg->template emplace<Type>(entity);
}
} else {
@@ -180,7 +186,7 @@ class basic_snapshot_loader {
while(length--) {
archive(entt, instance);
const auto entity = reg->valid(entt) ? entt : reg->create(entt);
ENTT_ASSERT(entity == entt);
ENTT_ASSERT(entity == entt, "Entity not available for use");
reg->template emplace<Type>(entity, std::move(instance));
}
}
@@ -198,7 +204,7 @@ public:
: reg{&source}
{
// restoring a snapshot as a whole requires a clean registry
ENTT_ASSERT(reg->empty());
ENTT_ASSERT(reg->empty(), "Registry must be empty");
}
/*! @brief Default move constructor. */
@@ -267,7 +273,7 @@ public:
*/
const basic_snapshot_loader & orphans() const {
reg->orphans([this](const auto entt) {
reg->destroy(entt);
reg->release(entt);
});
return *this;
@@ -374,7 +380,7 @@ class basic_continuous_loader {
const auto local = ref.second.first;
if(reg->valid(local)) {
reg->template remove_if_exists<Component>(local);
reg->template remove<Component>(local);
}
}
}
@@ -442,7 +448,7 @@ public:
for(decltype(length) pos{}; pos < length; ++pos) {
archive(entt);
if(const auto entity = (to_integral(entt) & traits_type::entity_mask); entity == pos) {
if(const auto entity = traits_type::to_entity(entt); entity == pos) {
restore(entt);
} else {
destroy(entt);
@@ -523,7 +529,7 @@ public:
*/
basic_continuous_loader & orphans() {
reg->orphans([this](const auto entt) {
reg->destroy(entt);
reg->release(entt);
});
return *this;

View File

@@ -2,14 +2,14 @@
#define ENTT_ENTITY_SPARSE_SET_HPP
#include <iterator>
#include <utility>
#include <vector>
#include <memory>
#include <cstddef>
#include <iterator>
#include <memory>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "../core/algorithm.hpp"
#include "../core/fwd.hpp"
#include "entity.hpp"
#include "fwd.hpp"
@@ -17,6 +17,15 @@
namespace entt {
/*! @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.
*
@@ -38,27 +47,27 @@ namespace entt {
* 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 Allocator Type of allocator used to manage memory and elements.
*/
template<typename Entity>
template<typename Entity, typename Allocator>
class basic_sparse_set {
static_assert(ENTT_PAGE_SIZE && ((ENTT_PAGE_SIZE & (ENTT_PAGE_SIZE - 1)) == 0), "ENTT_PAGE_SIZE must be a power of two");
static constexpr auto entt_per_page = ENTT_PAGE_SIZE / sizeof(Entity);
static constexpr auto growth_factor = 1.5;
static constexpr auto sparse_page = ENTT_SPARSE_PAGE;
using traits_type = entt_traits<Entity>;
using page_type = std::unique_ptr<Entity[]>;
class sparse_set_iterator final {
friend class basic_sparse_set<Entity>;
using alloc_traits = typename std::allocator_traits<Allocator>::template rebind_traits<Entity>;
using alloc_pointer = typename alloc_traits::pointer;
using alloc_const_pointer = typename alloc_traits::const_pointer;
using packed_type = std::vector<Entity>;
using index_type = typename traits_type::difference_type;
using bucket_alloc_traits = typename std::allocator_traits<Allocator>::template rebind_traits<alloc_pointer>;
using bucket_alloc_pointer = typename bucket_alloc_traits::pointer;
sparse_set_iterator(const packed_type &ref, const index_type idx) ENTT_NOEXCEPT
: packed{&ref}, index{idx}
{}
static_assert(alloc_traits::propagate_on_container_move_assignment::value);
static_assert(bucket_alloc_traits::propagate_on_container_move_assignment::value);
public:
using difference_type = index_type;
struct sparse_set_iterator final {
using difference_type = typename traits_type::difference_type;
using value_type = Entity;
using pointer = const value_type *;
using reference = const value_type &;
@@ -66,6 +75,11 @@ class basic_sparse_set {
sparse_set_iterator() ENTT_NOEXCEPT = default;
sparse_set_iterator(const alloc_const_pointer *ref, const difference_type idx) ENTT_NOEXCEPT
: packed{ref},
index{idx}
{}
sparse_set_iterator & operator++() ENTT_NOEXCEPT {
return --index, *this;
}
@@ -137,7 +151,7 @@ class basic_sparse_set {
[[nodiscard]] pointer operator->() const {
const auto pos = size_type(index-1u);
return &(*packed)[pos];
return std::addressof((*packed)[pos]);
}
[[nodiscard]] reference operator*() const {
@@ -145,59 +159,216 @@ class basic_sparse_set {
}
private:
const packed_type *packed;
index_type index;
const alloc_const_pointer *packed;
difference_type index;
};
[[nodiscard]] auto page(const Entity entt) const ENTT_NOEXCEPT {
return size_type{(to_integral(entt) & traits_type::entity_mask) / entt_per_page};
[[nodiscard]] static auto page(const Entity entt) ENTT_NOEXCEPT {
return size_type{traits_type::to_entity(entt) / sparse_page};
}
[[nodiscard]] auto offset(const Entity entt) const ENTT_NOEXCEPT {
return size_type{to_integral(entt) & (entt_per_page - 1)};
[[nodiscard]] static auto offset(const Entity entt) ENTT_NOEXCEPT {
return size_type{traits_type::to_entity(entt) & (sparse_page - 1)};
}
[[nodiscard]] page_type & assure(const std::size_t pos) {
if(!(pos < sparse.size())) {
sparse.resize(pos+1);
[[nodiscard]] auto assure_page(const std::size_t idx) {
if(!(idx < bucket)) {
const size_type sz = idx + 1u;
const auto mem = bucket_alloc_traits::allocate(bucket_allocator, sz);
std::uninitialized_value_construct(mem + bucket, mem + sz);
std::uninitialized_copy(sparse, sparse + bucket, mem);
std::destroy(sparse, sparse + bucket);
bucket_alloc_traits::deallocate(bucket_allocator, sparse, bucket);
sparse = mem;
bucket = sz;
}
if(!sparse[pos]) {
sparse[pos].reset(new entity_type[entt_per_page]);
// null is safe in all cases for our purposes
for(auto *first = sparse[pos].get(), *last = first + entt_per_page; first != last; ++first) {
*first = null;
if(!sparse[idx]) {
sparse[idx] = alloc_traits::allocate(allocator, sparse_page);
std::uninitialized_fill(sparse[idx], sparse[idx] + sparse_page, null);
}
return sparse[idx];
}
void resize_packed(const std::size_t req) {
ENTT_ASSERT((req != reserved) && !(req < count), "Invalid request");
const auto mem = alloc_traits::allocate(allocator, req);
std::uninitialized_copy(packed, packed + count, mem);
std::uninitialized_fill(mem + count, mem + req, tombstone);
std::destroy(packed, packed + reserved);
alloc_traits::deallocate(allocator, packed, reserved);
packed = mem;
reserved = req;
}
void release_memory() {
if(packed) {
for(size_type pos{}; pos < bucket; ++pos) {
if(sparse[pos]) {
std::destroy(sparse[pos], sparse[pos] + sparse_page);
alloc_traits::deallocate(allocator, sparse[pos], sparse_page);
}
}
}
return sparse[pos];
std::destroy(packed, packed + reserved);
std::destroy(sparse, sparse + bucket);
alloc_traits::deallocate(allocator, packed, reserved);
bucket_alloc_traits::deallocate(bucket_allocator, sparse, bucket);
}
}
virtual void swap_at(const std::size_t, const std::size_t) {}
virtual void swap_and_pop(const std::size_t) {}
virtual void clear_all() {}
protected:
/**
* @brief Swaps two entities in the internal packed array.
* @param lhs A valid position of an entity within storage.
* @param rhs A valid position of an entity within storage.
*/
virtual void swap_at([[maybe_unused]] const std::size_t lhs, [[maybe_unused]] const std::size_t rhs) {}
/**
* @brief Moves an entity in the internal packed array.
* @param from A valid position of an entity within storage.
* @param to A valid position of an entity within storage.
*/
virtual void move_and_pop([[maybe_unused]] const std::size_t from, [[maybe_unused]] const std::size_t to) {}
/**
* @brief Attempts to erase an entity from the internal packed array.
* @param entt A valid entity identifier.
* @param ud Optional user data that are forwarded as-is to derived classes.
*/
virtual void swap_and_pop(const Entity entt, [[maybe_unused]] void *ud) {
auto &ref = sparse[page(entt)][offset(entt)];
const auto pos = size_type{traits_type::to_entity(ref)};
ENTT_ASSERT(packed[pos] == entt, "Invalid entity identifier");
auto &last = packed[--count];
packed[pos] = last;
sparse[page(last)][offset(last)] = ref;
// lazy self-assignment guard
ref = null;
// unnecessary but it helps to detect nasty bugs
ENTT_ASSERT((last = tombstone, true), "");
}
/**
* @brief Attempts to erase an entity from the internal packed array.
* @param entt A valid entity identifier.
* @param ud Optional user data that are forwarded as-is to derived classes.
*/
virtual void in_place_pop(const Entity entt, [[maybe_unused]] void *ud) {
auto &ref = sparse[page(entt)][offset(entt)];
const auto pos = size_type{traits_type::to_entity(ref)};
ENTT_ASSERT(packed[pos] == entt, "Invalid entity identifier");
packed[pos] = std::exchange(free_list, traits_type::construct(static_cast<typename traits_type::entity_type>(pos)));
// lazy self-assignment guard
ref = null;
}
public:
/*! @brief Allocator type. */
using allocator_type = typename alloc_traits::allocator_type;
/*! @brief Underlying entity identifier. */
using entity_type = Entity;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Pointer type to contained entities. */
using pointer = alloc_const_pointer;
/*! @brief Random access iterator type. */
using iterator = sparse_set_iterator;
/*! @brief Reverse iterator type. */
using reverse_iterator = const entity_type *;
using reverse_iterator = std::reverse_iterator<iterator>;
/*! @brief Default constructor. */
basic_sparse_set() = default;
/**
* @brief Constructs an empty container with the given policy and allocator.
* @param pol Type of deletion policy.
* @param alloc Allocator to use (possibly default-constructed).
*/
explicit basic_sparse_set(deletion_policy pol, const allocator_type &alloc = {})
: allocator{alloc},
bucket_allocator{alloc},
sparse{bucket_alloc_traits::allocate(bucket_allocator, 0u)},
packed{alloc_traits::allocate(allocator, 0u)},
bucket{0u},
count{0u},
reserved{0u},
free_list{tombstone},
mode{pol}
{}
/*! @brief Default move constructor. */
basic_sparse_set(basic_sparse_set &&) = default;
/**
* @brief Constructs an empty container with the given allocator.
* @param alloc Allocator to use (possibly default-constructed).
*/
explicit basic_sparse_set(const allocator_type &alloc = {})
: basic_sparse_set{deletion_policy::swap_and_pop, alloc}
{}
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
basic_sparse_set(basic_sparse_set &&other) ENTT_NOEXCEPT
: allocator{std::move(other.allocator)},
bucket_allocator{std::move(other.bucket_allocator)},
sparse{std::exchange(other.sparse, bucket_alloc_pointer{})},
packed{std::exchange(other.packed, alloc_pointer{})},
bucket{std::exchange(other.bucket, 0u)},
count{std::exchange(other.count, 0u)},
reserved{std::exchange(other.reserved, 0u)},
free_list{std::exchange(other.free_list, tombstone)},
mode{other.mode}
{}
/*! @brief Default destructor. */
virtual ~basic_sparse_set() = default;
virtual ~basic_sparse_set() {
release_memory();
}
/*! @brief Default move assignment operator. @return This sparse set. */
basic_sparse_set & operator=(basic_sparse_set &&) = default;
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This sparse set.
*/
basic_sparse_set & operator=(basic_sparse_set &&other) ENTT_NOEXCEPT {
release_memory();
allocator = std::move(other.allocator);
bucket_allocator = std::move(other.bucket_allocator);
sparse = std::exchange(other.sparse, bucket_alloc_pointer{});
packed = std::exchange(other.packed, alloc_pointer{});
bucket = std::exchange(other.bucket, 0u);
count = std::exchange(other.count, 0u);
reserved = std::exchange(other.reserved, 0u);
free_list = std::exchange(other.free_list, tombstone);
mode = other.mode;
return *this;
}
/**
* @brief Returns the deletion policy of a sparse set.
* @return The deletion policy of the sparse set.
*/
[[nodiscard]] deletion_policy policy() const ENTT_NOEXCEPT {
return mode;
}
/**
* @brief Returns the next slot available for insertion.
* @return The next slot available for insertion.
*/
[[nodiscard]] size_type slot() const ENTT_NOEXCEPT {
return free_list == null ? count : size_type{traits_type::to_entity(free_list)};
}
/**
* @brief Increases the capacity of a sparse set.
@@ -208,7 +379,9 @@ public:
* @param cap Desired capacity.
*/
void reserve(const size_type cap) {
packed.reserve(cap);
if(cap > reserved) {
resize_packed(cap);
}
}
/**
@@ -217,18 +390,14 @@ public:
* @return Capacity of the sparse set.
*/
[[nodiscard]] size_type capacity() const ENTT_NOEXCEPT {
return packed.capacity();
return reserved;
}
/*! @brief Requests the removal of unused capacity. */
void shrink_to_fit() {
// conservative approach
if(packed.empty()) {
sparse.clear();
if(count < reserved) {
resize_packed(count);
}
sparse.shrink_to_fit();
packed.shrink_to_fit();
}
/**
@@ -242,7 +411,7 @@ public:
* @return Extent of the sparse set.
*/
[[nodiscard]] size_type extent() const ENTT_NOEXCEPT {
return sparse.size() * entt_per_page;
return bucket * sparse_page;
}
/**
@@ -256,7 +425,7 @@ public:
* @return Number of elements.
*/
[[nodiscard]] size_type size() const ENTT_NOEXCEPT {
return packed.size();
return count;
}
/**
@@ -264,23 +433,15 @@ public:
* @return True if the sparse set is empty, false otherwise.
*/
[[nodiscard]] bool empty() const ENTT_NOEXCEPT {
return packed.empty();
return (count == size_type{});
}
/**
* @brief Direct access to the internal packed array.
*
* The returned pointer is such that range `[data(), data() + size())` is
* always a valid range, even if the container is empty.
*
* @note
* Entities are in the reverse order as returned by the `begin`/`end`
* iterators.
*
* @return A pointer to the internal packed array.
*/
[[nodiscard]] const entity_type * data() const ENTT_NOEXCEPT {
return packed.data();
[[nodiscard]] pointer data() const ENTT_NOEXCEPT {
return packed;
}
/**
@@ -293,8 +454,7 @@ public:
* @return An iterator to the first entity of the internal packed array.
*/
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
const typename traits_type::difference_type pos = packed.size();
return iterator{packed, pos};
return iterator{std::addressof(packed), static_cast<typename traits_type::difference_type>(count)};
}
/**
@@ -308,7 +468,7 @@ public:
* internal packed array.
*/
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
return iterator{packed, {}};
return iterator{std::addressof(packed), {}};
}
/**
@@ -322,7 +482,7 @@ public:
* array.
*/
[[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
return packed.data();
return std::make_reverse_iterator(end());
}
/**
@@ -336,7 +496,7 @@ public:
* reversed internal packed array.
*/
[[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
return rbegin() + packed.size();
return std::make_reverse_iterator(begin());
}
/**
@@ -345,7 +505,7 @@ public:
* @return An iterator to the given entity if it's found, past the end
* iterator otherwise.
*/
[[nodiscard]] iterator find(const entity_type entt) const {
[[nodiscard]] iterator find(const entity_type entt) const ENTT_NOEXCEPT {
return contains(entt) ? --(end() - index(entt)) : end();
}
@@ -354,10 +514,11 @@ public:
* @param entt A valid entity identifier.
* @return True if the sparse set contains the entity, false otherwise.
*/
[[nodiscard]] bool contains(const entity_type entt) const {
[[nodiscard]] bool contains(const entity_type entt) const ENTT_NOEXCEPT {
ENTT_ASSERT(entt != tombstone && entt != null, "Invalid entity");
const auto curr = page(entt);
// testing against null permits to avoid accessing the packed array
return (curr < sparse.size() && sparse[curr] && sparse[curr][offset(entt)] != null);
// testing versions permits to avoid accessing the packed array
return (curr < bucket && sparse[curr] && sparse[curr][offset(entt)] != null);
}
/**
@@ -370,9 +531,51 @@ public:
* @param entt A valid entity identifier.
* @return The position of the entity in the sparse set.
*/
[[nodiscard]] size_type index(const entity_type entt) const {
ENTT_ASSERT(contains(entt));
return size_type{to_integral(sparse[page(entt)][offset(entt)])};
[[nodiscard]] size_type index(const entity_type entt) const ENTT_NOEXCEPT {
ENTT_ASSERT(contains(entt), "Set does not contain entity");
return size_type{traits_type::to_entity(sparse[page(entt)][offset(entt)])};
}
/**
* @brief Returns the entity at specified location, with bounds checking.
* @param pos The position for which to return the entity.
* @return The entity at specified location if any, a null entity otherwise.
*/
[[nodiscard]] entity_type at(const size_type pos) const ENTT_NOEXCEPT {
return pos < count ? packed[pos] : null;
}
/**
* @brief Returns the entity at specified location, without bounds checking.
* @param pos The position for which to return the entity.
* @return The entity at specified location.
*/
[[nodiscard]] entity_type operator[](const size_type pos) const ENTT_NOEXCEPT {
ENTT_ASSERT(pos < count, "Position is out of bounds");
return packed[pos];
}
/**
* @brief Appends an entity to a sparse set.
*
* @warning
* Attempting to assign an entity that already belongs to the sparse set
* results in undefined behavior.
*
* @param entt A valid entity identifier.
* @return The slot used for insertion.
*/
size_type emplace_back(const entity_type entt) {
ENTT_ASSERT(!contains(entt), "Set already contains entity");
if(count == reserved) {
const size_type sz = static_cast<size_type>(reserved * growth_factor);
resize_packed(sz + !(sz > reserved));
}
assure_page(page(entt))[offset(entt)] = traits_type::construct(static_cast<typename traits_type::entity_type>(count));
packed[count] = entt;
return count++;
}
/**
@@ -383,11 +586,18 @@ public:
* results in undefined behavior.
*
* @param entt A valid entity identifier.
* @return The slot used for insertion.
*/
void emplace(const entity_type entt) {
ENTT_ASSERT(!contains(entt));
assure(page(entt))[offset(entt)] = entity_type{static_cast<typename traits_type::entity_type>(packed.size())};
packed.push_back(entt);
size_type emplace(const entity_type entt) {
if(free_list == null) {
return emplace_back(entt);
} else {
ENTT_ASSERT(!contains(entt), "Set already contains entity");
const auto pos = size_type{traits_type::to_entity(free_list)};
sparse[page(entt)][offset(entt)] = traits_type::construct(static_cast<typename traits_type::entity_type>(pos));
free_list = std::exchange(packed[pos], entt);
return pos;
}
}
/**
@@ -403,58 +613,99 @@ public:
*/
template<typename It>
void insert(It first, It last) {
auto next = static_cast<typename traits_type::entity_type>(packed.size());
packed.insert(packed.end(), first, last);
reserve(count + std::distance(first, last));
for(; first != last; ++first) {
ENTT_ASSERT(!contains(*first));
assure(page(*first))[offset(*first)] = entity_type{next++};
const auto entt = *first;
ENTT_ASSERT(!contains(entt), "Set already contains entity");
assure_page(page(entt))[offset(entt)] = traits_type::construct(static_cast<typename traits_type::entity_type>(count));
packed[count++] = entt;
}
}
/**
* @brief Removes an entity from a sparse set.
* @brief Erases an entity from a sparse set.
*
* @warning
* Attempting to remove an entity that doesn't belong to the sparse set
* Attempting to erase an entity that doesn't belong to the sparse set
* results in undefined behavior.
*
* @param entt A valid entity identifier.
* @param ud Optional user data that are forwarded as-is to derived classes.
*/
void remove(const entity_type entt) {
ENTT_ASSERT(contains(entt));
auto &ref = sparse[page(entt)][offset(entt)];
const auto pos = size_type{to_integral(ref)};
const auto other = packed.back();
sparse[page(other)][offset(other)] = ref;
packed[pos] = other;
ref = null;
packed.pop_back();
swap_and_pop(pos);
void erase(const entity_type entt, void *ud = nullptr) {
ENTT_ASSERT(contains(entt), "Set does not contain entity");
(mode == deletion_policy::in_place) ? in_place_pop(entt, ud) : swap_and_pop(entt, ud);
}
/**
* @brief Removes multiple entities from a pool.
* @brief Erases entities from a set.
*
* @sa erase
*
* @tparam It Type of input iterator.
* @param first An iterator to the first element of the range of entities.
* @param last An iterator past the last element of the range of entities.
* @param ud Optional user data that are forwarded as-is to derived classes.
*/
template<typename It>
void remove(It first, It last) {
if(std::distance(first, last) == std::distance(packed.begin(), packed.end())) {
// no validity check, let it be misused
clear();
} else {
for(; first != last; ++first) {
remove(*first);
}
void erase(It first, It last, void *ud = nullptr) {
for(; first != last; ++first) {
erase(*first, ud);
}
}
/**
* @brief Swaps two entities in the internal packed array.
* @brief Removes an entity from a sparse set if it exists.
* @param entt A valid entity identifier.
* @param ud Optional user data that are forwarded as-is to derived classes.
* @return True if the entity is actually removed, false otherwise.
*/
bool remove(const entity_type entt, void *ud = nullptr) {
return contains(entt) && (erase(entt, ud), true);
}
/**
* @brief Removes entities from a sparse set if they exist.
* @tparam It Type of input iterator.
* @param first An iterator to the first element of the range of entities.
* @param last An iterator past the last element of the range of entities.
* @param ud Optional user data that are forwarded as-is to derived classes.
* @return The number of entities actually removed.
*/
template<typename It>
size_type remove(It first, It last, void *ud = nullptr) {
size_type found{};
for(; first != last; ++first) {
found += remove(*first, ud);
}
return found;
}
/*! @brief Removes all tombstones from the packed array of a sparse set. */
void compact() {
size_type next = count;
for(; next && packed[next - 1u] == tombstone; --next);
for(auto *it = &free_list; *it != null && next; it = std::addressof(packed[traits_type::to_entity(*it)])) {
if(const size_type pos = traits_type::to_entity(*it); pos < next) {
--next;
move_and_pop(next, pos);
std::swap(packed[next], packed[pos]);
sparse[page(packed[pos])][offset(packed[pos])] = traits_type::construct(static_cast<const typename traits_type::entity_type>(pos));
*it = traits_type::construct(static_cast<typename traits_type::entity_type>(next));
for(; next && packed[next - 1u] == tombstone; --next);
}
}
free_list = tombstone;
count = next;
}
/**
* @copybrief swap_at
*
* For what it's worth, this function affects both the internal sparse array
* and the internal packed array. Users should not care of that anyway.
@@ -467,11 +718,19 @@ public:
* @param rhs A valid entity identifier.
*/
void swap(const entity_type lhs, const entity_type rhs) {
const auto from = index(lhs);
const auto to = index(rhs);
std::swap(sparse[page(lhs)][offset(lhs)], sparse[page(rhs)][offset(rhs)]);
std::swap(packed[from], packed[to]);
ENTT_ASSERT(contains(lhs), "Set does not contain entity");
ENTT_ASSERT(contains(rhs), "Set does not contain entity");
auto &entt = sparse[page(lhs)][offset(lhs)];
auto &other = sparse[page(rhs)][offset(rhs)];
const auto from = size_type{traits_type::to_entity(entt)};
const auto to = size_type{traits_type::to_entity(other)};
// basic no-leak guarantee (with invalid state) if swapping throws
swap_at(from, to);
std::swap(entt, other);
std::swap(packed[from], packed[to]);
}
/**
@@ -499,27 +758,30 @@ public:
* @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 count Number of elements to sort.
* @param length Number of elements to sort.
* @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<typename Compare, typename Sort = std_sort, typename... Args>
void sort_n(const size_type count, Compare compare, Sort algo = Sort{}, Args &&... args) {
ENTT_ASSERT(!(count > size()));
void sort_n(const size_type length, Compare compare, Sort algo = Sort{}, Args &&... args) {
// basic no-leak guarantee (with invalid state) if sorting throws
ENTT_ASSERT(!(length > count), "Length exceeds the number of elements");
compact();
algo(packed.rend() - count, packed.rend(), std::move(compare), std::forward<Args>(args)...);
algo(std::make_reverse_iterator(packed + length), std::make_reverse_iterator(packed), std::move(compare), std::forward<Args>(args)...);
for(size_type pos{}; pos < count; ++pos) {
for(size_type pos{}; pos < length; ++pos) {
auto curr = pos;
auto next = index(packed[curr]);
while(curr != next) {
swap_at(next, index(packed[next]));
sparse[page(packed[curr])][offset(packed[curr])] = entity_type{static_cast<typename traits_type::entity_type>(curr)};
const auto idx = index(packed[next]);
const auto entt = packed[curr];
curr = next;
next = index(packed[curr]);
swap_at(next, idx);
sparse[page(entt)][offset(entt)] = traits_type::construct(static_cast<typename traits_type::entity_type>(curr));
curr = std::exchange(next, idx);
}
}
}
@@ -538,7 +800,7 @@ public:
*/
template<typename Compare, typename Sort = std_sort, typename... Args>
void sort(Compare compare, Sort algo = Sort{}, Args &&... args) {
sort_n(size(), std::move(compare), std::move(algo), std::forward<Args>(args)...);
sort_n(count, std::move(compare), std::move(algo), std::forward<Args>(args)...);
}
/**
@@ -557,34 +819,48 @@ public:
* @param other The sparse sets that imposes the order of the entities.
*/
void respect(const basic_sparse_set &other) {
compact();
const auto to = other.end();
auto from = other.begin();
size_type pos = packed.size() - 1;
while(pos && from != to) {
for(size_type pos = count - 1; pos && from != to; ++from) {
if(contains(*from)) {
if(*from != packed[pos]) {
// basic no-leak guarantee (with invalid state) if swapping throws
swap(packed[pos], *from);
}
--pos;
}
++from;
}
}
/*! @brief Clears a sparse set. */
void clear() ENTT_NOEXCEPT {
sparse.clear();
packed.clear();
clear_all();
/**
* @brief Clears a sparse set.
* @param ud Optional user data that are forwarded as-is to derived classes.
*/
void clear(void *ud = nullptr) {
for(auto &&entity: *this) {
if(entity != tombstone) {
in_place_pop(entity, ud);
}
}
free_list = tombstone;
count = 0u;
}
private:
std::vector<page_type> sparse;
std::vector<entity_type> packed;
typename alloc_traits::allocator_type allocator;
typename bucket_alloc_traits::allocator_type bucket_allocator;
bucket_alloc_pointer sparse;
alloc_pointer packed;
std::size_t bucket;
std::size_t count;
std::size_t reserved;
entity_type free_list;
deletion_policy mode;
};

View File

@@ -2,17 +2,18 @@
#define ENTT_ENTITY_STORAGE_HPP
#include <algorithm>
#include <cstddef>
#include <iterator>
#include <memory>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
#include "../config/config.h"
#include "../core/algorithm.hpp"
#include "../core/fwd.hpp"
#include "../core/type_traits.hpp"
#include "../signal/sigh.hpp"
#include "component.hpp"
#include "entity.hpp"
#include "fwd.hpp"
#include "sparse_set.hpp"
@@ -21,12 +22,6 @@
namespace entt {
/*! @brief Empty storage category tag. */
struct empty_storage_tag {};
/*! @brief Dense storage category tag. */
struct dense_storage_tag: empty_storage_tag {};
/**
* @brief Basic storage implementation.
*
@@ -36,9 +31,7 @@ struct dense_storage_tag: empty_storage_tag {};
* to the entities.
*
* @note
* Entities and objects have the same order. It's guaranteed both in case of raw
* access (either to entities or objects) and when using random or input access
* iterators.
* Entities and objects have the same order.
*
* @note
* Internal data structures arrange elements to maximize performance. There are
@@ -53,27 +46,33 @@ struct dense_storage_tag: empty_storage_tag {};
*
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Type Type of objects assigned to the entities.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Entity, typename Type, typename = void>
class basic_storage: public basic_sparse_set<Entity> {
static_assert(std::is_move_constructible_v<Type> && std::is_move_assignable_v<Type>, "The managed type must be at least move constructible and assignable");
template<typename Entity, typename Type, typename Allocator, typename = void>
class basic_storage_impl: public basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>> {
static constexpr auto packed_page = ENTT_PACKED_PAGE;
using underlying_type = basic_sparse_set<Entity>;
using traits_type = entt_traits<Entity>;
using comp_traits = component_traits<Type>;
using underlying_type = basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>>;
using difference_type = typename entt_traits<Entity>::difference_type;
using alloc_traits = typename std::allocator_traits<Allocator>::template rebind_traits<Type>;
using alloc_pointer = typename alloc_traits::pointer;
using alloc_const_pointer = typename alloc_traits::const_pointer;
using bucket_alloc_traits = typename std::allocator_traits<Allocator>::template rebind_traits<alloc_pointer>;
using bucket_alloc_pointer = typename bucket_alloc_traits::pointer;
using bucket_alloc_const_type = typename std::allocator_traits<Allocator>::template rebind_alloc<alloc_const_pointer>;
using bucket_alloc_const_pointer = typename std::allocator_traits<bucket_alloc_const_type>::const_pointer;
static_assert(alloc_traits::propagate_on_container_move_assignment::value);
static_assert(bucket_alloc_traits::propagate_on_container_move_assignment::value);
template<typename Value>
class storage_iterator final {
friend class basic_storage<Entity, Type>;
using instance_type = constness_as_t<std::vector<Type>, Value>;
using index_type = typename traits_type::difference_type;
storage_iterator(instance_type &ref, const index_type idx) ENTT_NOEXCEPT
: instances{&ref}, index{idx}
{}
public:
using difference_type = index_type;
struct storage_iterator final {
using difference_type = typename basic_storage_impl::difference_type;
using value_type = Value;
using pointer = value_type *;
using reference = value_type &;
@@ -81,6 +80,11 @@ class basic_storage: public basic_sparse_set<Entity> {
storage_iterator() ENTT_NOEXCEPT = default;
storage_iterator(bucket_alloc_pointer const *ref, const typename basic_storage_impl::difference_type idx) ENTT_NOEXCEPT
: packed{ref},
index{idx}
{}
storage_iterator & operator++() ENTT_NOEXCEPT {
return --index, *this;
}
@@ -123,7 +127,7 @@ class basic_storage: public basic_sparse_set<Entity> {
[[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT {
const auto pos = size_type(index-value-1);
return (*instances)[pos];
return (*packed)[page(pos)][offset(pos)];
}
[[nodiscard]] bool operator==(const storage_iterator &other) const ENTT_NOEXCEPT {
@@ -152,7 +156,7 @@ class basic_storage: public basic_sparse_set<Entity> {
[[nodiscard]] pointer operator->() const ENTT_NOEXCEPT {
const auto pos = size_type(index-1u);
return &(*instances)[pos];
return std::addressof((*packed)[page(pos)][offset(pos)]);
}
[[nodiscard]] reference operator*() const ENTT_NOEXCEPT {
@@ -160,41 +164,199 @@ class basic_storage: public basic_sparse_set<Entity> {
}
private:
instance_type *instances;
index_type index;
bucket_alloc_pointer const *packed;
difference_type index;
};
[[nodiscard]] static auto page(const std::size_t pos) ENTT_NOEXCEPT {
return pos / packed_page;
}
[[nodiscard]] static auto offset(const std::size_t pos) ENTT_NOEXCEPT {
return pos & (packed_page - 1);
}
void release_memory() {
if(packed) {
// no-throw stable erase iteration
underlying_type::clear();
for(size_type pos{}; pos < bucket; ++pos) {
alloc_traits::deallocate(allocator, packed[pos], packed_page);
bucket_alloc_traits::destroy(bucket_allocator, std::addressof(packed[pos]));
}
bucket_alloc_traits::deallocate(bucket_allocator, packed, bucket);
}
}
void assure_at_least(const std::size_t last) {
if(const auto idx = page(last - 1u); !(idx < bucket)) {
const size_type sz = idx + 1u;
const auto mem = bucket_alloc_traits::allocate(bucket_allocator, sz);
std::uninitialized_copy(packed, packed + bucket, mem);
size_type pos{};
ENTT_TRY {
for(pos = bucket; pos < sz; ++pos) {
auto pg = alloc_traits::allocate(allocator, packed_page);
bucket_alloc_traits::construct(bucket_allocator, std::addressof(mem[pos]), pg);
}
} ENTT_CATCH {
for(auto next = bucket; next < pos; ++next) {
alloc_traits::deallocate(allocator, mem[next], packed_page);
}
std::destroy(mem, mem + pos);
bucket_alloc_traits::deallocate(bucket_allocator, mem, sz);
ENTT_THROW;
}
std::destroy(packed, packed + bucket);
bucket_alloc_traits::deallocate(bucket_allocator, packed, bucket);
packed = mem;
bucket = sz;
}
}
void release_unused_pages() {
if(const auto length = underlying_type::size() / packed_page; length < bucket) {
const auto mem = bucket_alloc_traits::allocate(bucket_allocator, length);
std::uninitialized_copy(packed, packed + length, mem);
for(auto pos = length; pos < bucket; ++pos) {
alloc_traits::deallocate(allocator, packed[pos], packed_page);
bucket_alloc_traits::destroy(bucket_allocator, std::addressof(packed[pos]));
}
bucket_alloc_traits::deallocate(bucket_allocator, packed, bucket);
packed = mem;
bucket = length;
}
}
template<typename... Args>
auto & push_at(const std::size_t pos, Args &&... args) {
ENTT_ASSERT(pos < (bucket * packed_page), "Out of bounds index");
auto *instance = std::addressof(packed[page(pos)][offset(pos)]);
if constexpr(std::is_aggregate_v<value_type>) {
alloc_traits::construct(allocator, instance, Type{std::forward<Args>(args)...});
} else {
alloc_traits::construct(allocator, instance, std::forward<Args>(args)...);
}
return *instance;
}
void pop_at(const std::size_t pos) {
alloc_traits::destroy(allocator, std::addressof(packed[page(pos)][offset(pos)]));
}
protected:
/*! @copydoc basic_sparse_set::swap_at */
void swap_at(const std::size_t lhs, const std::size_t rhs) final {
std::swap(instances[lhs], instances[rhs]);
std::swap(packed[page(lhs)][offset(lhs)], packed[page(rhs)][offset(rhs)]);
}
void swap_and_pop(const std::size_t pos) final {
auto other = std::move(instances.back());
instances[pos] = std::move(other);
instances.pop_back();
/*! @copydoc basic_sparse_set::move_and_pop */
void move_and_pop(const std::size_t from, const std::size_t to) final {
push_at(to, std::move(packed[page(from)][offset(from)]));
pop_at(from);
}
void clear_all() ENTT_NOEXCEPT final {
instances.clear();
/*! @copydoc basic_sparse_set::swap_and_pop */
void swap_and_pop(const Entity entt, void *ud) override {
const auto pos = underlying_type::index(entt);
const auto last = underlying_type::size() - 1u;
auto &&elem = packed[page(pos)][offset(pos)];
// support for nosy destructors
[[maybe_unused]] auto unused = std::move(elem);
elem = std::move(packed[page(last)][offset(last)]);
pop_at(last);
underlying_type::swap_and_pop(entt, ud);
}
/*! @copydoc basic_sparse_set::in_place_pop */
void in_place_pop(const Entity entt, void *ud) override {
const auto pos = underlying_type::index(entt);
underlying_type::in_place_pop(entt, ud);
// support for nosy destructors
pop_at(pos);
}
public:
/*! @brief Type of the objects associated with the entities. */
/*! @brief Allocator type. */
using allocator_type = typename alloc_traits::allocator_type;
/*! @brief Type of the objects assigned to entities. */
using value_type = Type;
/*! @brief Underlying entity identifier. */
using entity_type = Entity;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Pointer type to contained elements. */
using pointer = bucket_alloc_pointer;
/*! @brief Constant pointer type to contained elements. */
using const_pointer = bucket_alloc_const_pointer;
/*! @brief Random access iterator type. */
using iterator = storage_iterator<Type>;
using iterator = storage_iterator<value_type>;
/*! @brief Constant random access iterator type. */
using const_iterator = storage_iterator<const Type>;
using const_iterator = storage_iterator<const value_type>;
/*! @brief Reverse iterator type. */
using reverse_iterator = Type *;
using reverse_iterator = std::reverse_iterator<iterator>;
/*! @brief Constant reverse iterator type. */
using const_reverse_iterator = const Type *;
/*! @brief Storage category. */
using storage_category = dense_storage_tag;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
/**
* @brief Default constructor.
* @param alloc Allocator to use (possibly default-constructed).
*/
explicit basic_storage_impl(const allocator_type &alloc = {})
: underlying_type{deletion_policy{comp_traits::in_place_delete::value}, alloc},
allocator{alloc},
bucket_allocator{alloc},
packed{bucket_alloc_traits::allocate(bucket_allocator, 0u)},
bucket{}
{}
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
basic_storage_impl(basic_storage_impl &&other) ENTT_NOEXCEPT
: underlying_type{std::move(other)},
allocator{std::move(other.allocator)},
bucket_allocator{std::move(other.bucket_allocator)},
packed{std::exchange(other.packed, bucket_alloc_pointer{})},
bucket{std::exchange(other.bucket, 0u)}
{}
/*! @brief Default destructor. */
~basic_storage_impl() override {
release_memory();
}
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This sparse set.
*/
basic_storage_impl & operator=(basic_storage_impl &&other) ENTT_NOEXCEPT {
release_memory();
underlying_type::operator=(std::move(other));
allocator = std::move(other.allocator);
bucket_allocator = std::move(other.bucket_allocator);
packed = std::exchange(other.packed, bucket_alloc_pointer{});
bucket = std::exchange(other.bucket, 0u);
return *this;
}
/**
* @brief Increases the capacity of a storage.
@@ -206,34 +368,38 @@ public:
*/
void reserve(const size_type cap) {
underlying_type::reserve(cap);
instances.reserve(cap);
if(cap > underlying_type::size()) {
assure_at_least(cap);
}
}
/**
* @brief Returns the number of elements that a storage has currently
* allocated space for.
* @return Capacity of the storage.
*/
[[nodiscard]] size_type capacity() const ENTT_NOEXCEPT {
return bucket * packed_page;
}
/*! @brief Requests the removal of unused capacity. */
void shrink_to_fit() {
underlying_type::shrink_to_fit();
instances.shrink_to_fit();
release_unused_pages();
}
/**
* @brief Direct access to the array of objects.
*
* The returned pointer is such that range `[raw(), raw() + size())` is
* always a valid range, even if the container is empty.
*
* @note
* Objects are in the reverse order as returned by the `begin`/`end`
* iterators.
*
* @return A pointer to the array of objects.
*/
[[nodiscard]] const value_type * raw() const ENTT_NOEXCEPT {
return instances.data();
[[nodiscard]] const_pointer raw() const ENTT_NOEXCEPT {
return packed;
}
/*! @copydoc raw */
[[nodiscard]] value_type * raw() ENTT_NOEXCEPT {
return const_cast<value_type *>(std::as_const(*this).raw());
[[nodiscard]] pointer raw() ENTT_NOEXCEPT {
return packed;
}
/**
@@ -245,8 +411,8 @@ public:
* @return An iterator to the first instance of the internal array.
*/
[[nodiscard]] const_iterator cbegin() const ENTT_NOEXCEPT {
const typename traits_type::difference_type pos = underlying_type::size();
return const_iterator{instances, pos};
const difference_type pos = underlying_type::size();
return const_iterator{std::addressof(packed), pos};
}
/*! @copydoc cbegin */
@@ -256,8 +422,8 @@ public:
/*! @copydoc begin */
[[nodiscard]] iterator begin() ENTT_NOEXCEPT {
const typename traits_type::difference_type pos = underlying_type::size();
return iterator{instances, pos};
const difference_type pos = underlying_type::size();
return iterator{std::addressof(packed), pos};
}
/**
@@ -271,7 +437,7 @@ public:
* internal array.
*/
[[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT {
return const_iterator{instances, {}};
return const_iterator{std::addressof(packed), {}};
}
/*! @copydoc cend */
@@ -281,7 +447,7 @@ public:
/*! @copydoc end */
[[nodiscard]] iterator end() ENTT_NOEXCEPT {
return iterator{instances, {}};
return iterator{std::addressof(packed), {}};
}
/**
@@ -294,7 +460,7 @@ public:
* @return An iterator to the first instance of the reversed internal array.
*/
[[nodiscard]] const_reverse_iterator crbegin() const ENTT_NOEXCEPT {
return instances.data();
return std::make_reverse_iterator(cend());
}
/*! @copydoc crbegin */
@@ -304,7 +470,7 @@ public:
/*! @copydoc rbegin */
[[nodiscard]] reverse_iterator rbegin() ENTT_NOEXCEPT {
return instances.data();
return std::make_reverse_iterator(end());
}
/**
@@ -318,7 +484,7 @@ public:
* reversed internal array.
*/
[[nodiscard]] const_reverse_iterator crend() const ENTT_NOEXCEPT {
return crbegin() + instances.size();
return std::make_reverse_iterator(cbegin());
}
/*! @copydoc crend */
@@ -328,25 +494,26 @@ public:
/*! @copydoc rend */
[[nodiscard]] reverse_iterator rend() ENTT_NOEXCEPT {
return rbegin() + instances.size();
return std::make_reverse_iterator(begin());
}
/**
* @brief Returns the object associated with an entity.
* @brief Returns the object assigned to an entity.
*
* @warning
* Attempting to use an entity that doesn't belong to the storage results in
* undefined behavior.
*
* @param entt A valid entity identifier.
* @return The object associated with the entity.
* @return The object assigned to the entity.
*/
[[nodiscard]] const value_type & get(const entity_type entt) const {
return instances[underlying_type::index(entt)];
[[nodiscard]] const value_type & get(const entity_type entt) const ENTT_NOEXCEPT {
const auto idx = underlying_type::index(entt);
return packed[page(idx)][offset(idx)];
}
/*! @copydoc get */
[[nodiscard]] value_type & get(const entity_type entt) {
[[nodiscard]] value_type & get(const entity_type entt) ENTT_NOEXCEPT {
return const_cast<value_type &>(std::as_const(*this).get(entt));
}
@@ -368,15 +535,35 @@ public:
*/
template<typename... Args>
value_type & emplace(const entity_type entt, Args &&... args) {
if constexpr(std::is_aggregate_v<value_type>) {
instances.push_back(Type{std::forward<Args>(args)...});
} else {
instances.emplace_back(std::forward<Args>(args)...);
const auto pos = underlying_type::slot();
assure_at_least(pos + 1u);
auto &value = push_at(pos, std::forward<Args>(args)...);
ENTT_TRY {
[[maybe_unused]] const auto curr = underlying_type::emplace(entt);
ENTT_ASSERT(pos == curr, "Misplaced component");
} ENTT_CATCH {
pop_at(pos);
ENTT_THROW;
}
// entity goes after component in case constructor throws
underlying_type::emplace(entt);
return instances.back();
return value;
}
/**
* @brief Updates the instance assigned to a given entity in-place.
* @tparam Func Types of the function objects to invoke.
* @param entt A valid entity identifier.
* @param func Valid function objects.
* @return A reference to the updated instance.
*/
template<typename... Func>
decltype(auto) patch(const entity_type entt, Func &&... func) {
const auto idx = underlying_type::index(entt);
auto &&elem = packed[page(idx)][offset(idx)];
(std::forward<Func>(func)(elem), ...);
return elem;
}
/**
@@ -394,9 +581,20 @@ public:
*/
template<typename It>
void insert(It first, It last, const value_type &value = {}) {
instances.insert(instances.end(), std::distance(first, last), value);
// entities go after components in case constructors throw
underlying_type::insert(first, last);
const auto cap = underlying_type::size() + std::distance(first, last);
underlying_type::reserve(cap);
assure_at_least(cap);
for(; first != last; ++first) {
push_at(underlying_type::size(), value);
ENTT_TRY {
underlying_type::emplace_back(*first);
} ENTT_CATCH {
pop_at(underlying_type::size());
ENTT_THROW;
}
}
}
/**
@@ -410,13 +608,23 @@ public:
* @param first An iterator to the first element of the range of entities.
* @param last An iterator past the last element of the range of entities.
* @param from An iterator to the first element of the range of objects.
* @param to An iterator past the last element of the range of objects.
*/
template<typename EIt, typename CIt>
void insert(EIt first, EIt last, CIt from, CIt to) {
instances.insert(instances.end(), from, to);
// entities go after components in case constructors throw
underlying_type::insert(first, last);
template<typename EIt, typename CIt, typename = std::enable_if_t<std::is_same_v<std::decay_t<typename std::iterator_traits<CIt>::value_type>, value_type>>>
void insert(EIt first, EIt last, CIt from) {
const auto cap = underlying_type::size() + std::distance(first, last);
underlying_type::reserve(cap);
assure_at_least(cap);
for(; first != last; ++first, ++from) {
push_at(underlying_type::size(), *from);
ENTT_TRY {
underlying_type::emplace_back(*first);
} ENTT_CATCH {
pop_at(underlying_type::size());
ENTT_THROW;
}
}
}
/**
@@ -449,19 +657,20 @@ public:
* @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 count Number of elements to sort.
* @param length Number of elements to sort.
* @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<typename Compare, typename Sort = std_sort, typename... Args>
void sort_n(const size_type count, Compare compare, Sort algo = Sort{}, Args &&... args) {
void sort_n(const size_type length, Compare compare, Sort algo = Sort{}, Args &&... args) {
if constexpr(std::is_invocable_v<Compare, const value_type &, const value_type &>) {
underlying_type::sort_n(count, [this, compare = std::move(compare)](const auto lhs, const auto rhs) {
return compare(std::as_const(instances[underlying_type::index(lhs)]), std::as_const(instances[underlying_type::index(rhs)]));
underlying_type::sort_n(length, [this, compare = std::move(compare)](const auto lhs, const auto rhs) {
const auto ilhs = underlying_type::index(lhs), irhs = underlying_type::index(rhs);
return compare(std::as_const(packed[page(ilhs)][offset(ilhs)]), std::as_const(packed[page(irhs)][offset(irhs)]));
}, std::move(algo), std::forward<Args>(args)...);
} else {
underlying_type::sort_n(count, std::move(compare), std::move(algo), std::forward<Args>(args)...);
underlying_type::sort_n(length, std::move(compare), std::move(algo), std::forward<Args>(args)...);
}
}
@@ -479,28 +688,56 @@ public:
*/
template<typename Compare, typename Sort = std_sort, typename... Args>
void sort(Compare compare, Sort algo = Sort{}, Args &&... args) {
sort_n(this->size(), std::move(compare), std::move(algo), std::forward<Args>(args)...);
sort_n(underlying_type::size(), std::move(compare), std::move(algo), std::forward<Args>(args)...);
}
private:
std::vector<value_type> instances;
typename alloc_traits::allocator_type allocator;
typename bucket_alloc_traits::allocator_type bucket_allocator;
bucket_alloc_pointer packed;
size_type bucket;
};
/*! @copydoc basic_storage */
template<typename Entity, typename Type>
class basic_storage<Entity, Type, std::enable_if_t<is_empty_v<Type>>>: public basic_sparse_set<Entity> {
using underlying_type = basic_sparse_set<Entity>;
/*! @copydoc basic_storage_impl */
template<typename Entity, typename Type, typename Allocator>
class basic_storage_impl<Entity, Type, Allocator, std::enable_if_t<component_traits<Type>::ignore_if_empty::value && std::is_empty_v<Type>>>
: public basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>>
{
using comp_traits = component_traits<Type>;
using underlying_type = basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>>;
using alloc_traits = typename std::allocator_traits<Allocator>::template rebind_traits<Type>;
public:
/*! @brief Type of the objects associated with the entities. */
/*! @brief Allocator type. */
using allocator_type = typename alloc_traits::allocator_type;
/*! @brief Type of the objects assigned to entities. */
using value_type = Type;
/*! @brief Underlying entity identifier. */
using entity_type = Entity;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Storage category. */
using storage_category = empty_storage_tag;
/**
* @brief Default constructor.
* @param alloc Allocator to use (possibly default-constructed).
*/
explicit basic_storage_impl(const allocator_type &alloc = {})
: underlying_type{deletion_policy{comp_traits::in_place_delete::value}, alloc}
{}
/**
* @brief Fake get function.
*
* @warning
* Attempting to use an entity that doesn't belong to the storage results in
* undefined behavior.
*
* @param entt A valid entity identifier.
*/
void get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT {
ENTT_ASSERT(underlying_type::contains(entt), "Storage does not contain entity");
}
/**
* @brief Assigns an entity to a storage and constructs its object.
@@ -519,6 +756,18 @@ public:
underlying_type::emplace(entt);
}
/**
* @brief Updates the instance assigned to a given entity in-place.
* @tparam Func Types of the function objects to invoke.
* @param entt A valid entity identifier.
* @param func Valid function objects.
*/
template<typename... Func>
void patch([[maybe_unused]] const entity_type entt, Func &&... func) {
ENTT_ASSERT(underlying_type::contains(entt), "Storage does not contain entity");
(std::forward<Func>(func)(), ...);
}
/**
* @brief Assigns one or more entities to a storage.
*
@@ -545,71 +794,51 @@ template<typename Type>
struct storage_adapter_mixin: Type {
static_assert(std::is_same_v<typename Type::value_type, std::decay_t<typename Type::value_type>>, "Invalid object type");
/*! @brief Type of the objects associated with the entities. */
/*! @brief Type of the objects assigned to entities. */
using value_type = typename Type::value_type;
/*! @brief Underlying entity identifier. */
using entity_type = typename Type::entity_type;
/*! @brief Storage category. */
using storage_category = typename Type::storage_category;
/*! @brief Inherited constructors. */
using Type::Type;
/**
* @brief Assigns entities to a storage.
* @tparam Args Types of arguments to use to construct the object.
* @param entity A valid entity identifier.
* @param entt A valid entity identifier.
* @param args Parameters to use to initialize the object.
* @return A reference to the newly created object.
*/
template<typename... Args>
decltype(auto) emplace(basic_registry<entity_type> &, const entity_type entity, Args &&... args) {
return Type::emplace(entity, std::forward<Args>(args)...);
decltype(auto) emplace(basic_registry<entity_type> &, const entity_type entt, Args &&... args) {
return Type::emplace(entt, std::forward<Args>(args)...);
}
/**
* @brief Assigns entities to a storage.
* @tparam It Type of input iterator.
* @tparam Args Types of arguments to use to construct the objects
* associated with the entities.
* @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 associated with
* the entities.
* @param args Parameters to use to initialize the objects assigned to the
* entities.
*/
template<typename It, typename... Args>
void insert(basic_registry<entity_type> &, It first, It last, Args &&... args) {
Type::insert(first, last, std::forward<Args>(args)...);
}
/**
* @brief Removes entities from a storage.
* @param entity A valid entity identifier.
*/
void remove(basic_registry<entity_type> &, const entity_type entity) {
Type::remove(entity);
}
/**
* @copybrief remove
* @tparam It Type of input iterator.
* @param first An iterator to the first element of the range of entities.
* @param last An iterator past the last element of the range of entities.
*/
template<typename It>
void remove(basic_registry<entity_type> &, It first, It last) {
Type::remove(first, last);
}
/**
* @brief Patches the given instance for an entity.
* @tparam Func Types of the function objects to invoke.
* @param entity A valid entity identifier.
* @param entt A valid entity identifier.
* @param func Valid function objects.
* @return A reference to the patched instance.
*/
template<typename... Func>
decltype(auto) patch(basic_registry<entity_type> &, const entity_type entity, [[maybe_unused]] Func &&... func) {
auto &instance = this->get(entity);
(std::forward<Func>(func)(instance), ...);
return instance;
decltype(auto) patch(basic_registry<entity_type> &, const entity_type entt, Func &&... func) {
return Type::patch(entt, std::forward<Func>(func)...);
}
};
@@ -619,13 +848,29 @@ struct storage_adapter_mixin: Type {
* @tparam Type The type of the underlying storage.
*/
template<typename Type>
struct sigh_storage_mixin: Type {
class sigh_storage_mixin final: public Type {
/*! @copydoc basic_sparse_set::swap_and_pop */
void swap_and_pop(const typename Type::entity_type entt, void *ud) final {
ENTT_ASSERT(ud != nullptr, "Invalid pointer to registry");
destruction.publish(*static_cast<basic_registry<typename Type::entity_type> *>(ud), entt);
Type::swap_and_pop(entt, ud);
}
/*! @copydoc basic_sparse_set::in_place_pop */
void in_place_pop(const typename Type::entity_type entt, void *ud) final {
ENTT_ASSERT(ud != nullptr, "Invalid pointer to registry");
destruction.publish(*static_cast<basic_registry<typename Type::entity_type> *>(ud), entt);
Type::in_place_pop(entt, ud);
}
public:
/*! @brief Underlying value type. */
using value_type = typename Type::value_type;
/*! @brief Underlying entity identifier. */
using entity_type = typename Type::entity_type;
/*! @brief Storage category. */
using storage_category = typename Type::storage_category;
/*! @brief Inherited constructors. */
using Type::Type;
/**
* @brief Returns a sink object.
@@ -693,37 +938,34 @@ struct sigh_storage_mixin: Type {
}
/**
* @copybrief storage_adapter_mixin::emplace
* @brief Assigns entities to a storage.
* @tparam Args Types of arguments to use to construct the object.
* @param owner The registry that issued the request.
* @param entity A valid entity identifier.
* @param entt A valid entity identifier.
* @param args Parameters to use to initialize the object.
* @return A reference to the newly created object.
*/
template<typename... Args>
decltype(auto) emplace(basic_registry<entity_type> &owner, const entity_type entity, Args &&... args) {
Type::emplace(owner, entity, std::forward<Args>(args)...);
construction.publish(owner, entity);
if constexpr(!std::is_same_v<storage_category, empty_storage_tag>) {
return this->get(entity);
}
decltype(auto) emplace(basic_registry<entity_type> &owner, const entity_type entt, Args &&... args) {
Type::emplace(entt, std::forward<Args>(args)...);
construction.publish(owner, entt);
return this->get(entt);
}
/**
* @copybrief storage_adapter_mixin::insert
* @brief Assigns entities to a storage.
* @tparam It Type of input iterator.
* @tparam Args Types of arguments to use to construct the objects
* associated with the entities.
* @tparam Args Types of arguments to use to construct the objects assigned
* to the entities.
* @param owner The registry that issued the request.
* @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 associated with
* the entities.
* @param args Parameters to use to initialize the objects assigned to the
* entities.
*/
template<typename It, typename... Args>
void insert(basic_registry<entity_type> &owner, It first, It last, Args &&... args) {
Type::insert(owner, first, last, std::forward<Args>(args)...);
Type::insert(first, last, std::forward<Args>(args)...);
if(!construction.empty()) {
for(; first != last; ++first) {
@@ -733,50 +975,18 @@ struct sigh_storage_mixin: Type {
}
/**
* @copybrief storage_adapter_mixin::remove
* @param owner The registry that issued the request.
* @param entity A valid entity identifier.
*/
void remove(basic_registry<entity_type> &owner, const entity_type entity) {
destruction.publish(owner, entity);
Type::remove(owner, entity);
}
/**
* @copybrief storage_adapter_mixin::remove
* @tparam It Type of input iterator.
* @param owner The registry that issued the request.
* @param first An iterator to the first element of the range of entities.
* @param last An iterator past the last element of the range of entities.
*/
template<typename It>
void remove(basic_registry<entity_type> &owner, It first, It last) {
if(!destruction.empty()) {
for(auto it = first; it != last; ++it) {
destruction.publish(owner, *it);
}
}
Type::remove(owner, first, last);
}
/**
* @copybrief storage_adapter_mixin::patch
* @brief Patches the given instance for an entity.
* @tparam Func Types of the function objects to invoke.
* @param owner The registry that issued the request.
* @param entity A valid entity identifier.
* @param entt A valid entity identifier.
* @param func Valid function objects.
* @return A reference to the patched instance.
*/
template<typename... Func>
decltype(auto) patch(basic_registry<entity_type> &owner, const entity_type entity, [[maybe_unused]] Func &&... func) {
if constexpr(std::is_same_v<storage_category, empty_storage_tag>) {
update.publish(owner, entity);
} else {
Type::patch(owner, entity, std::forward<Func>(func)...);
update.publish(owner, entity);
return this->get(entity);
}
decltype(auto) patch(basic_registry<entity_type> &owner, const entity_type entt, Func &&... func) {
Type::patch(entt, std::forward<Func>(func)...);
update.publish(owner, entt);
return this->get(entt);
}
private:
@@ -787,41 +997,44 @@ private:
/**
* @brief Defines the component-to-storage conversion.
*
* Formally:
*
* * If the component type is a non-const one, the member typedef type is the
* declared storage type.
* * If the component type is a const one, the member typedef type is the
* declared storage type, except it has a const-qualifier added.
*
* @brief Storage implementation dispatcher.
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Type Type of objects assigned to the entities.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Entity, typename Type, typename = void>
struct storage_traits {
/*! @brief Resulting type after component-to-storage conversion. */
using storage_type = sigh_storage_mixin<storage_adapter_mixin<basic_storage<Entity, Type>>>;
template<typename Entity, typename Type, typename Allocator>
struct basic_storage: basic_storage_impl<Entity, Type, Allocator> {
using basic_storage_impl<Entity, Type, Allocator>::basic_storage_impl;
};
/**
* @brief Gets the element associated with an entity from a storage, if any.
* @brief Provides a common way to access certain properties of storage types.
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Type Type of objects managed by the storage class.
*/
template<typename Entity, typename Type, typename = void>
struct storage_traits {
/*! @brief Resulting type after component-to-storage conversion. */
using storage_type = sigh_storage_mixin<basic_storage<Entity, Type>>;
};
/**
* @brief Gets the element assigned to an entity from a storage, if any.
* @tparam Type Storage type.
* @param container A valid instance of a storage class.
* @param entity A valid entity identifier.
* @param entt A valid entity identifier.
* @return A possibly empty tuple containing the requested element.
*/
template<typename Type>
[[nodiscard]] auto get_as_tuple([[maybe_unused]] Type &container, [[maybe_unused]] const typename Type::entity_type entity) {
static_assert(std::is_same_v<std::remove_const_t<Type>, typename storage_traits<typename Type::entity_type, typename Type::value_type>::storage_type>);
[[nodiscard]] auto get_as_tuple([[maybe_unused]] Type &container, [[maybe_unused]] const typename Type::entity_type entt) {
static_assert(std::is_same_v<std::remove_const_t<Type>, typename storage_traits<typename Type::entity_type, typename Type::value_type>::storage_type>, "Invalid storage");
if constexpr(std::is_base_of_v<dense_storage_tag, typename Type::storage_category>) {
return std::forward_as_tuple(container.get(entity));
} else {
static_assert(std::is_base_of_v<empty_storage_tag, typename Type::storage_category>, "Unknown storage category");
if constexpr(std::is_void_v<decltype(container.get({}))>) {
return std::make_tuple();
} else {
return std::forward_as_tuple(container.get(entt));
}
}

View File

@@ -10,6 +10,7 @@
#include <type_traits>
#include "../config/config.h"
#include "../core/type_traits.hpp"
#include "component.hpp"
#include "entity.hpp"
#include "fwd.hpp"
#include "sparse_set.hpp"
@@ -21,13 +22,154 @@ namespace entt {
/**
* @brief View.
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
namespace internal {
template<typename Policy, typename It, std::size_t AllOf, std::size_t NoneOf>
class view_iterator final {
using basic_common_type = basic_sparse_set<typename std::iterator_traits<It>::value_type>;
[[nodiscard]] bool valid() const {
const auto entt = *it;
return Policy::accept(entt)
&& std::apply([entt](const auto *... curr) { return (curr->contains(entt) && ...); }, pools)
&& std::apply([entt](const auto *... curr) { return (!curr->contains(entt) && ...); }, filter);
}
public:
using iterator_type = It;
using difference_type = typename std::iterator_traits<It>::difference_type;
using value_type = typename std::iterator_traits<It>::value_type;
using pointer = typename std::iterator_traits<It>::pointer;
using reference = typename std::iterator_traits<It>::reference;
using iterator_category = std::bidirectional_iterator_tag;
view_iterator() ENTT_NOEXCEPT
: first{},
last{},
it{},
pools{},
filter{}
{}
view_iterator(It from, It to, It curr, std::array<const basic_common_type *, AllOf> all_of, std::array<const basic_common_type *, NoneOf> none_of) ENTT_NOEXCEPT
: first{from},
last{to},
it{curr},
pools{all_of},
filter{none_of}
{
if(it != last && !valid()) {
++(*this);
}
}
view_iterator & operator++() ENTT_NOEXCEPT {
while(++it != last && !valid());
return *this;
}
view_iterator operator++(int) ENTT_NOEXCEPT {
view_iterator orig = *this;
return ++(*this), orig;
}
view_iterator & operator--() ENTT_NOEXCEPT {
while(--it != first && !valid());
return *this;
}
view_iterator operator--(int) ENTT_NOEXCEPT {
view_iterator orig = *this;
return operator--(), orig;
}
[[nodiscard]] bool operator==(const view_iterator &other) const ENTT_NOEXCEPT {
return other.it == it;
}
[[nodiscard]] bool operator!=(const view_iterator &other) const ENTT_NOEXCEPT {
return !(*this == other);
}
[[nodiscard]] pointer operator->() const {
return &*it;
}
[[nodiscard]] reference operator*() const {
return *operator->();
}
private:
It first;
It last;
It it;
std::array<const basic_common_type *, AllOf> pools;
std::array<const basic_common_type *, NoneOf> filter;
};
}
/**
* Internal details not to be documented.
* @endcond
*/
/*! @brief Stable storage policy, aimed at pointer stability. */
struct stable_storage_policy {
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
template<typename Entity>
[[nodiscard]] static constexpr bool accept(const Entity entity) ENTT_NOEXCEPT {
return entity != tombstone;
}
/**
* Internal details not to be documented.
* @endcond
*/
};
/*! @brief Packed storage policy, aimed at faster linear iteration. */
struct packed_storage_policy {
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
template<typename Entity>
[[nodiscard]] static constexpr bool accept(const Entity) ENTT_NOEXCEPT {
return true;
}
/**
* Internal details not to be documented.
* @endcond
*/
};
/**
* @brief View implementation.
*
* Primary template isn't defined on purpose. All the specializations give a
* compile-time error, but for a few reasonable cases.
*/
template<typename...>
class basic_view;
class basic_view_impl;
/*! @brief View implementation dispatcher. */
template<typename...>
struct basic_view;
/**
@@ -59,181 +201,103 @@ class basic_view;
* 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 Policy Common (stricter) storage policy.
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Exclude Types of components used to filter the view.
* @tparam Component Types of components iterated by the view.
*/
template<typename Entity, typename... Exclude, typename... Component>
class basic_view<Entity, exclude_t<Exclude...>, Component...> final {
template<typename Policy, typename Entity, typename... Exclude, typename... Component>
class basic_view_impl<Policy, Entity, exclude_t<Exclude...>, Component...> {
using basic_common_type = basic_sparse_set<Entity>;
template<typename Comp>
using storage_type = constness_as_t<typename storage_traits<Entity, std::remove_const_t<Comp>>::storage_type, Comp>;
using unchecked_type = std::array<const basic_sparse_set<Entity> *, (sizeof...(Component) - 1)>;
template<typename It>
class view_iterator final {
friend class basic_view<Entity, exclude_t<Exclude...>, Component...>;
view_iterator(It from, It to, It curr, unchecked_type other, const std::tuple<const storage_type<Exclude> *...> &ignore) ENTT_NOEXCEPT
: first{from},
last{to},
it{curr},
unchecked{other},
filter{ignore}
{
if(it != last && !valid()) {
++(*this);
}
}
[[nodiscard]] bool valid() const {
const auto entt = *it;
return std::all_of(unchecked.cbegin(), unchecked.cend(), [entt](const basic_sparse_set<Entity> *curr) { return curr->contains(entt); })
&& !(std::get<const storage_type<Exclude> *>(filter)->contains(entt) || ...);
}
public:
using difference_type = typename std::iterator_traits<It>::difference_type;
using value_type = typename std::iterator_traits<It>::value_type;
using pointer = typename std::iterator_traits<It>::pointer;
using reference = typename std::iterator_traits<It>::reference;
using iterator_category = std::bidirectional_iterator_tag;
view_iterator() ENTT_NOEXCEPT
: view_iterator{{}, {}, {}, {}, {}}
{}
view_iterator & operator++() ENTT_NOEXCEPT {
while(++it != last && !valid());
return *this;
}
view_iterator operator++(int) ENTT_NOEXCEPT {
view_iterator orig = *this;
return ++(*this), orig;
}
view_iterator & operator--() ENTT_NOEXCEPT {
while(--it != first && !valid());
return *this;
}
view_iterator operator--(int) ENTT_NOEXCEPT {
view_iterator orig = *this;
return operator--(), orig;
}
[[nodiscard]] bool operator==(const view_iterator &other) const ENTT_NOEXCEPT {
return other.it == it;
}
[[nodiscard]] bool operator!=(const view_iterator &other) const ENTT_NOEXCEPT {
return !(*this == other);
}
[[nodiscard]] pointer operator->() const {
return &*it;
}
[[nodiscard]] reference operator*() const {
return *operator->();
}
private:
It first;
It last;
It it;
unchecked_type unchecked;
std::tuple<const storage_type<Exclude> *...> filter;
};
class iterable_view final {
friend class basic_view<Entity, exclude_t<Exclude...>, Component...>;
class iterable final {
template<typename It>
class iterable_view_iterator final {
friend class iterable_view;
iterable_view_iterator(It from, const basic_view &parent) ENTT_NOEXCEPT
: it{from},
view{parent}
{}
public:
struct iterable_iterator final {
using difference_type = std::ptrdiff_t;
using value_type = decltype(std::tuple_cat(std::tuple<Entity>{}, std::declval<basic_view>().get({})));
using value_type = decltype(std::tuple_cat(std::tuple<Entity>{}, std::declval<basic_view_impl>().get({})));
using pointer = void;
using reference = value_type;
using iterator_category = std::input_iterator_tag;
iterable_view_iterator & operator++() ENTT_NOEXCEPT {
iterable_iterator(It from, const basic_view_impl *parent) ENTT_NOEXCEPT
: it{from},
view{parent}
{}
iterable_iterator & operator++() ENTT_NOEXCEPT {
return ++it, *this;
}
iterable_view_iterator operator++(int) ENTT_NOEXCEPT {
iterable_view_iterator orig = *this;
iterable_iterator operator++(int) ENTT_NOEXCEPT {
iterable_iterator orig = *this;
return ++(*this), orig;
}
[[nodiscard]] reference operator*() const ENTT_NOEXCEPT {
return std::tuple_cat(std::make_tuple(*it), view.get(*it));
return std::tuple_cat(std::make_tuple(*it), view->get(*it));
}
[[nodiscard]] bool operator==(const iterable_view_iterator &other) const ENTT_NOEXCEPT {
[[nodiscard]] bool operator==(const iterable_iterator &other) const ENTT_NOEXCEPT {
return other.it == it;
}
[[nodiscard]] bool operator!=(const iterable_view_iterator &other) const ENTT_NOEXCEPT {
[[nodiscard]] bool operator!=(const iterable_iterator &other) const ENTT_NOEXCEPT {
return !(*this == other);
}
private:
It it;
const basic_view view;
const basic_view_impl *view;
};
iterable_view(const basic_view &parent)
public:
using iterator = iterable_iterator<internal::view_iterator<Policy, typename basic_common_type::iterator, sizeof...(Component) - 1u, sizeof...(Exclude)>>;
using reverse_iterator = iterable_iterator<internal::view_iterator<Policy, typename basic_common_type::reverse_iterator, sizeof...(Component) - 1u, sizeof...(Exclude)>>;
iterable(const basic_view_impl &parent)
: view{parent}
{}
public:
using iterator = iterable_view_iterator<view_iterator<typename basic_sparse_set<Entity>::iterator>>;
using reverse_iterator = iterable_view_iterator<view_iterator<typename basic_sparse_set<Entity>::reverse_iterator>>;
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
return { view.begin(), view };
return { view.begin(), &view };
}
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
return { view.end(), view };
return { view.end(), &view };
}
[[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
return { view.rbegin(), view };
return { view.rbegin(), &view };
}
[[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
return { view.rend(), view };
return { view.rend(), &view };
}
private:
const basic_view view;
const basic_view_impl view;
};
[[nodiscard]] const basic_sparse_set<Entity> * candidate() const ENTT_NOEXCEPT {
return (std::min)({ static_cast<const basic_sparse_set<entity_type> *>(std::get<storage_type<Component> *>(pools))... }, [](const auto *lhs, const auto *rhs) {
[[nodiscard]] const auto * candidate() const ENTT_NOEXCEPT {
return (std::min)({ static_cast<const basic_common_type *>(std::get<storage_type<Component> *>(pools))... }, [](const auto *lhs, const auto *rhs) {
return lhs->size() < rhs->size();
});
}
[[nodiscard]] unchecked_type unchecked(const basic_sparse_set<Entity> *cpool) const {
[[nodiscard]] auto pools_to_unchecked_array() const ENTT_NOEXCEPT {
std::size_t pos{};
unchecked_type other{};
(static_cast<void>(std::get<storage_type<Component> *>(pools) == cpool ? nullptr : (other[pos] = std::get<storage_type<Component> *>(pools), other[pos++])), ...);
std::array<const basic_common_type *, sizeof...(Component) - 1u> other{};
(static_cast<void>(std::get<storage_type<Component> *>(pools) == view ? void() : void(other[pos++] = std::get<storage_type<Component> *>(pools))), ...);
return other;
}
[[nodiscard]] auto filter_to_array() const ENTT_NOEXCEPT {
return std::array<const basic_common_type *, sizeof...(Exclude)>{std::get<const storage_type<Exclude> *>(filter)...};
}
template<typename Comp, typename It>
[[nodiscard]] auto dispatch_get([[maybe_unused]] It &it, [[maybe_unused]] const Entity entt) const {
if constexpr(std::is_same_v<typename std::iterator_traits<It>::value_type, typename storage_type<Comp>::value_type>) {
@@ -245,35 +309,33 @@ class basic_view<Entity, exclude_t<Exclude...>, Component...> final {
template<typename Comp, typename Func>
void traverse(Func func) const {
if(*this) {
if constexpr(std::is_same_v<typename storage_type<Comp>::storage_category, empty_storage_tag>) {
for(const auto entt: static_cast<const basic_sparse_set<entity_type> &>(*std::get<storage_type<Comp> *>(pools))) {
if(((std::is_same_v<Comp, Component> || std::get<storage_type<Component> *>(pools)->contains(entt)) && ...)
&& !(std::get<const storage_type<Exclude> *>(filter)->contains(entt) || ...))
{
if constexpr(is_applicable_v<Func, decltype(std::tuple_cat(std::tuple<entity_type>{}, std::declval<basic_view>().get({})))>) {
std::apply(func, std::tuple_cat(std::make_tuple(entt), get(entt)));
} else {
std::apply(func, get(entt));
}
if constexpr(std::is_void_v<decltype(std::get<storage_type<Comp> *>(pools)->get({}))>) {
for(const auto entt: static_cast<const basic_common_type &>(*std::get<storage_type<Comp> *>(pools))) {
if(Policy::accept(entt) && ((std::is_same_v<Comp, Component> || std::get<storage_type<Component> *>(pools)->contains(entt)) && ...)
&& (!std::get<const storage_type<Exclude> *>(filter)->contains(entt) && ...))
{
if constexpr(is_applicable_v<Func, decltype(std::tuple_cat(std::tuple<entity_type>{}, std::declval<basic_view_impl>().get({})))>) {
std::apply(func, std::tuple_cat(std::make_tuple(entt), get(entt)));
} else {
std::apply(func, get(entt));
}
}
} else {
auto it = std::get<storage_type<Comp> *>(pools)->begin();
}
} else {
auto it = std::get<storage_type<Comp> *>(pools)->begin();
for(const auto entt: static_cast<const basic_sparse_set<entity_type> &>(*std::get<storage_type<Comp> *>(pools))) {
if(((std::is_same_v<Comp, Component> || std::get<storage_type<Component> *>(pools)->contains(entt)) && ...)
&& !(std::get<const storage_type<Exclude> *>(filter)->contains(entt) || ...))
{
if constexpr(is_applicable_v<Func, decltype(std::tuple_cat(std::tuple<entity_type>{}, std::declval<basic_view>().get({})))>) {
std::apply(func, std::tuple_cat(std::make_tuple(entt), dispatch_get<Component>(it, entt)...));
} else {
std::apply(func, std::tuple_cat(dispatch_get<Component>(it, entt)...));
}
for(const auto entt: static_cast<const basic_common_type &>(*std::get<storage_type<Comp> *>(pools))) {
if(Policy::accept(entt) && ((std::is_same_v<Comp, Component> || std::get<storage_type<Component> *>(pools)->contains(entt)) && ...)
&& (!std::get<const storage_type<Exclude> *>(filter)->contains(entt) && ...))
{
if constexpr(is_applicable_v<Func, decltype(std::tuple_cat(std::tuple<entity_type>{}, std::declval<basic_view_impl>().get({})))>) {
std::apply(func, std::tuple_cat(std::make_tuple(entt), dispatch_get<Component>(it, entt)...));
} else {
std::apply(func, std::tuple_cat(dispatch_get<Component>(it, entt)...));
}
++it;
}
++it;
}
}
}
@@ -284,12 +346,14 @@ public:
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Bidirectional iterator type. */
using iterator = view_iterator<typename basic_sparse_set<entity_type>::iterator>;
using iterator = internal::view_iterator<Policy, typename basic_common_type::iterator, sizeof...(Component) - 1u, sizeof...(Exclude)>;
/*! @brief Reverse iterator type. */
using reverse_iterator = view_iterator<typename basic_sparse_set<entity_type>::reverse_iterator>;
using reverse_iterator = internal::view_iterator<Policy, typename basic_common_type::reverse_iterator, sizeof...(Component) - 1u, sizeof...(Exclude)>;
/*! @brief Iterable view type. */
using iterable_view = iterable;
/*! @brief Default constructor to use to create empty, invalid views. */
basic_view() ENTT_NOEXCEPT
basic_view_impl() ENTT_NOEXCEPT
: view{}
{}
@@ -298,7 +362,7 @@ public:
* @param component The storage for the types to iterate.
* @param epool The storage for the types used to filter the view.
*/
basic_view(storage_type<Component> &... component, const storage_type<Exclude> &... epool) ENTT_NOEXCEPT
basic_view_impl(storage_type<Component> &... component, const storage_type<Exclude> &... epool) ENTT_NOEXCEPT
: pools{&component...},
filter{&epool...},
view{candidate()}
@@ -310,7 +374,7 @@ public:
*/
template<typename Comp>
void use() const ENTT_NOEXCEPT {
view = *this ? std::get<storage_type<Comp> *>(pools) : nullptr;
view = std::get<storage_type<Comp> *>(pools);
}
/**
@@ -318,7 +382,7 @@ public:
* @return Estimated number of entities iterated by the view.
*/
[[nodiscard]] size_type size_hint() const ENTT_NOEXCEPT {
return *this ? view->size() : size_type{};
return view->size();
}
/**
@@ -330,7 +394,7 @@ public:
* @return An iterator to the first entity of the view.
*/
[[nodiscard]] iterator begin() const {
return *this ? iterator{view->begin(), view->end(), view->begin(), unchecked(view), filter} : iterator{};
return iterator{view->begin(), view->end(), view->begin(), pools_to_unchecked_array(), filter_to_array()};
}
/**
@@ -343,7 +407,7 @@ public:
* @return An iterator to the entity following the last entity of the view.
*/
[[nodiscard]] iterator end() const {
return *this ? iterator{view->begin(), view->end(), view->end(), unchecked(view), filter} : iterator{};
return iterator{view->begin(), view->end(), view->end(), pools_to_unchecked_array(), filter_to_array()};
}
/**
@@ -355,7 +419,7 @@ public:
* @return An iterator to the first entity of the reversed view.
*/
[[nodiscard]] reverse_iterator rbegin() const {
return *this ? reverse_iterator{view->rbegin(), view->rend(), view->rbegin(), unchecked(view), filter} : reverse_iterator{};
return reverse_iterator{view->rbegin(), view->rend(), view->rbegin(), pools_to_unchecked_array(), filter_to_array()};
}
/**
@@ -370,7 +434,7 @@ public:
* reversed view.
*/
[[nodiscard]] reverse_iterator rend() const {
return *this ? reverse_iterator{view->rbegin(), view->rend(), view->rend(), unchecked(view), filter} : reverse_iterator{};
return reverse_iterator{view->rbegin(), view->rend(), view->rend(), pools_to_unchecked_array(), filter_to_array()};
}
/**
@@ -400,7 +464,7 @@ public:
* iterator otherwise.
*/
[[nodiscard]] iterator find(const entity_type entt) const {
const auto it = *this ? iterator{view->begin(), view->end(), view->find(entt), unchecked(view), filter} : end();
const auto it = iterator{view->begin(), view->end(), view->find(entt), pools_to_unchecked_array(), filter_to_array()};
return (it != end() && *it == entt) ? it : end();
}
@@ -418,7 +482,7 @@ public:
* @return True if the view contains the given entity, false otherwise.
*/
[[nodiscard]] bool contains(const entity_type entt) const {
return *this && (std::get<storage_type<Component> *>(pools)->contains(entt) && ...) && !(std::get<const storage_type<Exclude> *>(filter)->contains(entt) || ...);
return (std::get<storage_type<Component> *>(pools)->contains(entt) && ...) && (!std::get<const storage_type<Exclude> *>(filter)->contains(entt) && ...);
}
/**
@@ -438,14 +502,15 @@ public:
*/
template<typename... Comp>
[[nodiscard]] decltype(auto) get([[maybe_unused]] const entity_type entt) const {
ENTT_ASSERT(contains(entt));
ENTT_ASSERT(contains(entt), "View does not contain entity");
if constexpr(sizeof...(Comp) == 0) {
return std::tuple_cat(get_as_tuple(*std::get<storage_type<Component> *>(pools), entt)...);
} else if constexpr(sizeof...(Comp) == 1) {
return (std::get<storage_type<Comp> *>(pools)->get(entt), ...);
} else {
return std::tuple_cat(get_as_tuple(*std::get<storage_type<Comp> *>(pools), entt)...); }
return std::tuple_cat(get_as_tuple(*std::get<storage_type<Comp> *>(pools), entt)...);
}
}
/**
@@ -481,9 +546,7 @@ public:
*
* The pool of the suggested component is used to lead the iterations. The
* returned entities will therefore respect the order of the pool associated
* with that type.<br/>
* It is no longer guaranteed that the performance is the best possible, but
* there will be greater control over the order of iteration.
* with that type.
*
* @sa each
*
@@ -519,9 +582,7 @@ public:
*
* The pool of the suggested component is used to lead the iterations. The
* returned elements will therefore respect the order of the pool associated
* with that type.<br/>
* It is no longer guaranteed that the performance is the best possible, but
* there will be greater control over the order of iteration.
* with that type.
*
* @sa each
*
@@ -534,10 +595,22 @@ public:
return iterable_view{*this};
}
/**
* @brief Combines two views in a _more specific_ one (friend function).
* @tparam Id A valid entity type (see entt_traits for more details).
* @tparam ELhs Filter list of the first view.
* @tparam CLhs Component list of the first view.
* @tparam ERhs Filter list of the second view.
* @tparam CRhs Component list of the second view.
* @return A more specific view.
*/
template<typename Id, typename... ELhs, typename... CLhs, typename... ERhs, typename... CRhs>
friend auto operator|(const basic_view<Id, exclude_t<ELhs...>, CLhs...> &, const basic_view<Id, exclude_t<ERhs...>, CRhs...> &);
private:
const std::tuple<storage_type<Component> *...> pools;
const std::tuple<const storage_type<Exclude> *...> filter;
mutable const basic_sparse_set<entity_type> *view;
mutable const basic_common_type *view;
};
@@ -573,38 +646,30 @@ private:
* @tparam Component Type of component iterated by the view.
*/
template<typename Entity, typename Component>
class basic_view<Entity, exclude_t<>, Component> final {
class basic_view_impl<packed_storage_policy, Entity, exclude_t<>, Component> {
using basic_common_type = basic_sparse_set<Entity>;
using storage_type = constness_as_t<typename storage_traits<Entity, std::remove_const_t<Component>>::storage_type, Component>;
class iterable_view {
friend class basic_view<Entity, exclude_t<>, Component>;
class iterable final {
template<typename... It>
class iterable_view_iterator {
friend class iterable_view;
iterable_view_iterator() ENTT_NOEXCEPT
: iterable_view_iterator{It{}...}
{}
template<typename... Discard>
iterable_view_iterator(It... from, Discard...) ENTT_NOEXCEPT
: it{from...}
{}
public:
struct iterable_iterator final {
using difference_type = std::ptrdiff_t;
using value_type = decltype(std::tuple_cat(std::tuple<Entity>{}, std::declval<basic_view>().get({})));
using value_type = decltype(std::tuple_cat(std::tuple<Entity>{}, std::declval<basic_view_impl>().get({})));
using pointer = void;
using reference = value_type;
using iterator_category = std::input_iterator_tag;
iterable_view_iterator & operator++() ENTT_NOEXCEPT {
template<typename... Discard>
iterable_iterator(It... from, Discard...) ENTT_NOEXCEPT
: it{from...}
{}
iterable_iterator & operator++() ENTT_NOEXCEPT {
return (++std::get<It>(it), ...), *this;
}
iterable_view_iterator operator++(int) ENTT_NOEXCEPT {
iterable_view_iterator orig = *this;
iterable_iterator operator++(int) ENTT_NOEXCEPT {
iterable_iterator orig = *this;
return ++(*this), orig;
}
@@ -612,11 +677,11 @@ class basic_view<Entity, exclude_t<>, Component> final {
return { *std::get<It>(it)... };
}
[[nodiscard]] bool operator==(const iterable_view_iterator &other) const ENTT_NOEXCEPT {
[[nodiscard]] bool operator==(const iterable_iterator &other) const ENTT_NOEXCEPT {
return std::get<0>(other.it) == std::get<0>(it);
}
[[nodiscard]] bool operator!=(const iterable_view_iterator &other) const ENTT_NOEXCEPT {
[[nodiscard]] bool operator!=(const iterable_iterator &other) const ENTT_NOEXCEPT {
return !(*this == other);
}
@@ -624,36 +689,36 @@ class basic_view<Entity, exclude_t<>, Component> final {
std::tuple<It...> it;
};
iterable_view(storage_type * const ref)
: pool{ref}
{}
public:
using iterator = std::conditional_t<
std::is_same_v<typename storage_type::storage_category, empty_storage_tag>,
iterable_view_iterator<typename basic_sparse_set<Entity>::iterator>,
iterable_view_iterator<typename basic_sparse_set<Entity>::iterator, decltype(std::declval<storage_type>().begin())>
std::is_void_v<decltype(std::declval<storage_type>().get({}))>,
iterable_iterator<typename basic_common_type::iterator>,
iterable_iterator<typename basic_common_type::iterator, decltype(std::declval<storage_type>().begin())>
>;
using reverse_iterator = std::conditional_t<
std::is_same_v<typename storage_type::storage_category, empty_storage_tag>,
iterable_view_iterator<typename basic_sparse_set<Entity>::reverse_iterator>,
iterable_view_iterator<typename basic_sparse_set<Entity>::reverse_iterator, decltype(std::declval<storage_type>().rbegin())>
std::is_void_v<decltype(std::declval<storage_type>().get({}))>,
iterable_iterator<typename basic_common_type::reverse_iterator>,
iterable_iterator<typename basic_common_type::reverse_iterator, decltype(std::declval<storage_type>().rbegin())>
>;
iterable(storage_type &ref)
: pool{&ref}
{}
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
return pool ? iterator{pool->basic_sparse_set<entity_type>::begin(), pool->begin()} : iterator{};
return iterator{pool->basic_common_type::begin(), pool->begin()};
}
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
return pool ? iterator{pool->basic_sparse_set<entity_type>::end(), pool->end()} : iterator{};
return iterator{pool->basic_common_type::end(), pool->end()};
}
[[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
return pool ? reverse_iterator{pool->basic_sparse_set<entity_type>::rbegin(), pool->rbegin()} : reverse_iterator{};
return reverse_iterator{pool->basic_common_type::rbegin(), pool->rbegin()};
}
[[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
return pool ? reverse_iterator{pool->basic_sparse_set<entity_type>::rend(), pool->rend()} : reverse_iterator{};
return reverse_iterator{pool->basic_common_type::rend(), pool->rend()};
}
private:
@@ -661,28 +726,30 @@ class basic_view<Entity, exclude_t<>, Component> final {
};
public:
/*! @brief Type of component iterated by the view. */
using raw_type = Component;
/*! @brief Underlying entity identifier. */
using entity_type = Entity;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Random access iterator type. */
using iterator = typename basic_sparse_set<Entity>::iterator;
using iterator = typename basic_common_type::iterator;
/*! @brief Reversed iterator type. */
using reverse_iterator = typename basic_sparse_set<Entity>::reverse_iterator;
using reverse_iterator = typename basic_common_type::reverse_iterator;
/*! @brief Iterable view type. */
using iterable_view = iterable;
/*! @brief Default constructor to use to create empty, invalid views. */
basic_view() ENTT_NOEXCEPT
: pool{}
basic_view_impl() ENTT_NOEXCEPT
: pools{},
filter{}
{}
/**
* @brief Constructs a single-type view from a storage class.
* @param ref The storage for the type to iterate.
*/
basic_view(storage_type &ref) ENTT_NOEXCEPT
: pool{&ref}
basic_view_impl(storage_type &ref) ENTT_NOEXCEPT
: pools{&ref},
filter{}
{}
/**
@@ -690,7 +757,7 @@ public:
* @return Number of entities that have the given component.
*/
[[nodiscard]] size_type size() const ENTT_NOEXCEPT {
return *this ? pool->size() : size_type{};
return std::get<0>(pools)->size();
}
/**
@@ -698,19 +765,15 @@ public:
* @return True if the view is empty, false otherwise.
*/
[[nodiscard]] bool empty() const ENTT_NOEXCEPT {
return !*this || pool->empty();
return std::get<0>(pools)->empty();
}
/**
* @brief Direct access to the list of components.
*
* The returned pointer is such that range `[raw(), raw() + size())` is
* always a valid range, even if the container is empty.
*
* @brief Direct access to the raw representation offered by the storage.
* @return A pointer to the array of components.
*/
[[nodiscard]] raw_type * raw() const ENTT_NOEXCEPT {
return *this ? pool->raw() : nullptr;
[[nodiscard]] auto raw() const ENTT_NOEXCEPT {
return std::get<0>(pools)->raw();
}
/**
@@ -721,8 +784,8 @@ public:
*
* @return A pointer to the array of entities.
*/
[[nodiscard]] const entity_type * data() const ENTT_NOEXCEPT {
return *this ? pool->data() : nullptr;
[[nodiscard]] auto data() const ENTT_NOEXCEPT {
return std::get<0>(pools)->data();
}
/**
@@ -734,7 +797,7 @@ public:
* @return An iterator to the first entity of the view.
*/
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
return *this ? pool->basic_sparse_set<entity_type>::begin() : iterator{};
return std::get<0>(pools)->basic_common_type::begin();
}
/**
@@ -747,7 +810,7 @@ public:
* @return An iterator to the entity following the last entity of the view.
*/
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
return *this ? pool->basic_sparse_set<entity_type>::end() : iterator{};
return std::get<0>(pools)->basic_common_type::end();
}
/**
@@ -759,7 +822,7 @@ public:
* @return An iterator to the first entity of the reversed view.
*/
[[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
return *this ? pool->basic_sparse_set<entity_type>::rbegin() : reverse_iterator{};
return std::get<0>(pools)->basic_common_type::rbegin();
}
/**
@@ -774,7 +837,7 @@ public:
* reversed view.
*/
[[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
return *this ? pool->basic_sparse_set<entity_type>::rend() : reverse_iterator{};
return std::get<0>(pools)->basic_common_type::rend();
}
/**
@@ -804,7 +867,7 @@ public:
* iterator otherwise.
*/
[[nodiscard]] iterator find(const entity_type entt) const {
const auto it = *this ? pool->find(entt) : end();
const auto it = std::get<0>(pools)->find(entt);
return it != end() && *it == entt ? it : end();
}
@@ -822,7 +885,7 @@ public:
* @return True if the view is properly initialized, false otherwise.
*/
[[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT {
return pool != nullptr;
return std::get<0>(pools) != nullptr;
}
/**
@@ -831,7 +894,7 @@ public:
* @return True if the view contains the given entity, false otherwise.
*/
[[nodiscard]] bool contains(const entity_type entt) const {
return *this && pool->contains(entt);
return std::get<0>(pools)->contains(entt);
}
/**
@@ -851,17 +914,13 @@ public:
*/
template<typename... Comp>
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
ENTT_ASSERT(contains(entt));
ENTT_ASSERT(contains(entt), "View does not contain entity");
if constexpr(sizeof...(Comp) == 0) {
if constexpr(std::is_same_v<typename storage_type::storage_category, empty_storage_tag>) {
return std::make_tuple();
} else {
return std::forward_as_tuple(pool->get(entt));
}
return get_as_tuple(*std::get<0>(pools), entt);
} else {
static_assert(std::is_same_v<Comp..., Component>, "Invalid component type");
return pool->get(entt);
return std::get<0>(pools)->get(entt);
}
}
@@ -889,14 +948,14 @@ public:
*/
template<typename Func>
void each(Func func) const {
if constexpr(std::is_same_v<typename storage_type::storage_category, empty_storage_tag>) {
if constexpr(std::is_void_v<decltype(std::get<0>(pools)->get({}))>) {
if constexpr(std::is_invocable_v<Func>) {
for(auto pos = size(); pos; --pos) {
func();
}
} else {
for(auto &&component: *this) {
func(component);
for(auto entity: *this) {
func(entity);
}
}
} else {
@@ -905,10 +964,8 @@ public:
std::apply(func, pack);
}
} else {
if(*this) {
for(auto &&component: *pool) {
func(component);
}
for(auto &&component: *std::get<0>(pools)) {
func(component);
}
}
}
@@ -928,11 +985,40 @@ public:
* @return An iterable object to use to _visit_ the view.
*/
[[nodiscard]] iterable_view each() const ENTT_NOEXCEPT {
return iterable_view{pool};
return iterable_view{*std::get<0>(pools)};
}
/**
* @brief Combines two views in a _more specific_ one (friend function).
* @tparam Id A valid entity type (see entt_traits for more details).
* @tparam ELhs Filter list of the first view.
* @tparam CLhs Component list of the first view.
* @tparam ERhs Filter list of the second view.
* @tparam CRhs Component list of the second view.
* @return A more specific view.
*/
template<typename Id, typename... ELhs, typename... CLhs, typename... ERhs, typename... CRhs>
friend auto operator|(const basic_view<Id, exclude_t<ELhs...>, CLhs...> &, const basic_view<Id, exclude_t<ERhs...>, CRhs...> &);
private:
storage_type * const pool;
const std::tuple<storage_type *> pools;
const std::tuple<> filter;
};
/**
* @brief View implementation dispatcher.
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Exclude Types of components used to filter the view.
* @tparam Component Types of components iterated by the view.
*/
template<typename Entity, typename... Exclude, typename... Component>
struct basic_view<Entity, exclude_t<Exclude...>, Component...>
: basic_view_impl<std::conditional_t<std::disjunction_v<typename component_traits<std::remove_const_t<Component>>::in_place_delete...>, stable_storage_policy, packed_storage_policy>, Entity, exclude_t<Exclude...>, Component...>
{
/*! @brief Most restrictive storage policy of all component types. */
using storage_policy = std::conditional_t<std::disjunction_v<typename component_traits<std::remove_const_t<Component>>::in_place_delete...>, stable_storage_policy, packed_storage_policy>;
using basic_view_impl<storage_policy, Entity, exclude_t<Exclude...>, Component...>::basic_view_impl;
};
@@ -942,10 +1028,28 @@ private:
* @param storage The storage for the types to iterate.
*/
template<typename... Storage>
basic_view(Storage &... storage) ENTT_NOEXCEPT
basic_view(Storage &... storage)
-> basic_view<std::common_type_t<typename Storage::entity_type...>, entt::exclude_t<>, constness_as_t<typename Storage::value_type, Storage>...>;
/**
* @brief Combines two views in a _more specific_ one.
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam ELhs Filter list of the first view.
* @tparam CLhs Component list of the first view.
* @tparam ERhs Filter list of the second view.
* @tparam CRhs Component list of the second view.
* @param lhs A valid reference to the first view.
* @param rhs A valid reference to the second view.
* @return A more specific view.
*/
template<typename Entity, typename... ELhs, typename... CLhs, typename... ERhs, typename... CRhs>
[[nodiscard]] auto operator|(const basic_view<Entity, exclude_t<ELhs...>, CLhs...> &lhs, const basic_view<Entity, exclude_t<ERhs...>, CRhs...> &rhs) {
using view_type = basic_view<Entity, exclude_t<ELhs..., ERhs...>, CLhs..., CRhs...>;
return std::apply([](auto *... storage) { return view_type{*storage...}; }, std::tuple_cat(lhs.pools, rhs.pools, lhs.filter, rhs.filter));
}
}

View File

@@ -1,435 +0,0 @@
#ifndef ENTT_ENTITY_VIEW_PACK_HPP
#define ENTT_ENTITY_VIEW_PACK_HPP
#include <iterator>
#include <tuple>
#include <type_traits>
#include <utility>
#include "../core/type_traits.hpp"
#include "fwd.hpp"
#include "utility.hpp"
namespace entt {
/**
* @brief View pack.
*
* The view pack allows users to combine multiple views into a single iterable
* object, while also giving them full control over which view should lead the
* iteration.<br/>
* This class returns all and only the entities present in all views. Its
* intended primary use is for custom storage and views, but it can also be very
* convenient in everyday use.
*
* @tparam Head Type of the leading view of the pack.
* @tparam Tail Types of all other views of the pack.
*/
template<typename Head, typename... Tail>
class view_pack {
template<typename It>
class view_pack_iterator final {
friend class view_pack<Head, Tail...>;
view_pack_iterator(It from, It to, const std::tuple<Tail...> &other) ENTT_NOEXCEPT
: it{from},
last{to},
tail{other}
{
if(it != last && !valid()) {
++(*this);
}
}
[[nodiscard]] bool valid() const {
return std::apply([entity = *it](auto &&... curr) { return (curr.contains(entity) && ...); }, tail);
}
public:
using difference_type = typename std::iterator_traits<It>::difference_type;
using value_type = typename std::iterator_traits<It>::value_type;
using pointer = typename std::iterator_traits<It>::pointer;
using reference = typename std::iterator_traits<It>::reference;
using iterator_category = std::input_iterator_tag;
view_pack_iterator & operator++() ENTT_NOEXCEPT {
while(++it != last && !valid());
return *this;
}
view_pack_iterator operator++(int) ENTT_NOEXCEPT {
view_pack_iterator orig = *this;
return ++(*this), orig;
}
[[nodiscard]] reference operator*() const {
return *it;
}
[[nodiscard]] bool operator==(const view_pack_iterator &other) const ENTT_NOEXCEPT {
return other.it == it;
}
[[nodiscard]] bool operator!=(const view_pack_iterator &other) const ENTT_NOEXCEPT {
return !(*this == other);
}
private:
It it;
const It last;
const std::tuple<Tail...> tail;
};
class iterable_view_pack final {
friend class view_pack<Head, Tail...>;
using iterable_view = decltype(std::declval<Head>().each());
template<typename It>
class iterable_view_pack_iterator final {
friend class iterable_view_pack;
iterable_view_pack_iterator(It from, It to, const std::tuple<Tail...> &other) ENTT_NOEXCEPT
: it{from},
last{to},
tail{other}
{
if(it != last && !valid()) {
++(*this);
}
}
[[nodiscard]] bool valid() const {
return std::apply([entity = std::get<0>(*it)](auto &&... curr) { return (curr.contains(entity) && ...); }, tail);
}
public:
using difference_type = typename std::iterator_traits<It>::difference_type;
using value_type = decltype(std::tuple_cat(*std::declval<It>(), std::declval<Tail>().get({})...));
using pointer = void;
using reference = value_type;
using iterator_category = std::input_iterator_tag;
iterable_view_pack_iterator & operator++() ENTT_NOEXCEPT {
while(++it != last && !valid());
return *this;
}
iterable_view_pack_iterator operator++(int) ENTT_NOEXCEPT {
iterable_view_pack_iterator orig = *this;
return ++(*this), orig;
}
[[nodiscard]] reference operator*() const {
return std::apply([value = *it](auto &&... curr) { return std::tuple_cat(value, curr.get(std::get<0>(value))...); }, tail);
}
[[nodiscard]] bool operator==(const iterable_view_pack_iterator &other) const ENTT_NOEXCEPT {
return other.it == it;
}
[[nodiscard]] bool operator!=(const iterable_view_pack_iterator &other) const ENTT_NOEXCEPT {
return !(*this == other);
}
private:
It it;
const It last;
const std::tuple<Tail...> tail;
};
iterable_view_pack(const Head &first, const std::tuple<Tail...> &last)
: iterable{first.each()},
tail{last}
{}
public:
using iterator = iterable_view_pack_iterator<typename iterable_view::iterator>;
using reverse_iterator = iterable_view_pack_iterator<typename iterable_view::reverse_iterator>;
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
return { iterable.begin(), iterable.end(), tail };
}
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
return { iterable.end(), iterable.end(), tail };
}
[[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
return { iterable.rbegin(), iterable.rend(), tail };
}
[[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
return { iterable.rend(), iterable.rend(), tail };
}
private:
iterable_view iterable;
std::tuple<Tail...> tail;
};
public:
/*! @brief Underlying entity identifier. */
using entity_type = std::common_type_t<typename Head::entity_type, typename Tail::entity_type...>;
/*! @brief Underlying entity identifier. */
using size_type = std::common_type_t<typename Head::size_type, typename Tail::size_type...>;
/*! @brief Input iterator type. */
using iterator = view_pack_iterator<typename Head::iterator>;
/*! @brief Reversed iterator type. */
using reverse_iterator = view_pack_iterator<typename Head::reverse_iterator>;
/**
* @brief Constructs a pack from a bunch of views.
* @param first A reference to the leading view for the pack.
* @param last References to the other views to use to construct the pack.
*/
view_pack(const Head &first, const Tail &... last)
: head{first},
tail{last...}
{}
/**
* @brief Returns an iterator to the first entity of the pack.
*
* The returned iterator points to the first entity of the pack. If the pack
* is empty, the returned iterator will be equal to `end()`.
*
* @return An iterator to the first entity of the pack.
*/
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
return { head.begin(), head.end(), tail };
}
/**
* @brief Returns an iterator that is past the last entity of the pack.
*
* The returned iterator points to the entity following the last entity of
* the pack. Attempting to dereference the returned iterator results in
* undefined behavior.
*
* @return An iterator to the entity following the last entity of the pack.
*/
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
return { head.end(), head.end(), tail };
}
/**
* @brief Returns an iterator to the first entity of the pack.
*
* The returned iterator points to the first entity of the reversed pack. If
* the pack is empty, the returned iterator will be equal to `rend()`.
*
* @return An iterator to the first entity of the pack.
*/
[[nodiscard]] reverse_iterator rbegin() const {
return { head.rbegin(), head.rend(), tail };
}
/**
* @brief Returns an iterator that is past the last entity of the reversed
* pack.
*
* The returned iterator points to the entity following the last entity of
* the reversed pack. Attempting to dereference the returned iterator
* results in undefined behavior.
*
* @return An iterator to the entity following the last entity of the
* reversed pack.
*/
[[nodiscard]] reverse_iterator rend() const {
return { head.rend(), head.rend(), tail };
}
/**
* @brief Returns the first entity of the pack, if any.
* @return The first entity of the pack if one exists, the null entity
* otherwise.
*/
[[nodiscard]] entity_type front() const {
const auto it = begin();
return it != end() ? *it : null;
}
/**
* @brief Returns the last entity of the pack, if any.
* @return The last entity of the pack if one exists, the null entity
* otherwise.
*/
[[nodiscard]] entity_type back() const {
const auto it = rbegin();
return it != rend() ? *it : null;
}
/**
* @brief Finds an entity.
* @param entt A valid entity identifier.
* @return An iterator to the given entity if it's found, past the end
* iterator otherwise.
*/
[[nodiscard]] iterator find(const entity_type entt) const {
iterator it{head.find(entt), head.end(), tail};
return (it != end() && *it == entt) ? it : end();
}
/**
* @brief Checks if a pack contains an entity.
* @param entt A valid entity identifier.
* @return True if the pack contains the given entity, false otherwise.
*/
[[nodiscard]] bool contains(const entity_type entt) const {
return head.contains(entt) && std::apply([entt](auto &&... curr) { return (curr.contains(entt) && ...); }, tail);
}
/**
* @brief Returns the components assigned to the given entity.
*
* Prefer this function instead of `registry::get` during iterations. It has
* far better performance than its counterpart.
*
* @warning
* Attempting to use an invalid component type results in a compilation
* error. Attempting to use an entity that doesn't belong to the pack
* results in undefined behavior.
*
* @tparam Comp Types of components to get.
* @param entt A valid entity identifier.
* @return The components assigned to the entity.
*/
template<typename... Comp>
[[nodiscard]] decltype(auto) get([[maybe_unused]] const entity_type entt) const {
ENTT_ASSERT(contains(entt));
auto component = std::apply([this, entt](auto &&... curr) { return std::tuple_cat(head.get(entt), curr.get(entt)...); }, tail);
if constexpr(sizeof...(Comp) == 0) {
return component;
} else if constexpr(sizeof...(Comp) == 1) {
return (std::get<Comp &>(component), ...);
} else {
return std::forward_as_tuple(std::get<Comp &>(component)...);
}
}
/**
* @brief Iterates entities and components and applies the given function
* object to them.
*
* The function object is invoked for each entity. It is provided with the
* entity itself and a set of references to non-empty components. The
* _constness_ of the components is as requested.<br/>
* The signature of the function must be equivalent to one of the following
* forms:
*
* @code{.cpp}
* void(const entity_type, Type &...);
* void(Type &...);
* @endcode
*
* @note
* Empty types aren't explicitly instantiated and therefore they are never
* returned during iterations.
*
* @tparam Func Type of the function object to invoke.
* @param func A valid function object.
*/
template<typename Func>
void each(Func func) const {
for(auto &&value: head.each()) {
if(std::apply([&value](auto &&... curr) { return (curr.contains(std::get<0>(value)) && ...); }, tail)) {
auto args = std::apply([&value](auto &&... curr) { return std::tuple_cat(value, curr.get(std::get<0>(value))...); }, tail);
if constexpr(is_applicable_v<Func, decltype(args)>) {
std::apply(func, args);
} else {
std::apply([&func](const auto, auto &&... component) { func(std::forward<decltype(component)>(component)...); }, args);
}
}
}
}
/**
* @brief Returns an iterable object to use to _visit_ the pack.
*
* The iterable object returns tuples that contain the current entity and a
* set of references to its non-empty components. The _constness_ of the
* components is as requested.
*
* @note
* Empty types aren't explicitly instantiated and therefore they are never
* returned during iterations.
*
* @return An iterable object to use to _visit_ the pack.
*/
[[nodiscard]] iterable_view_pack each() const ENTT_NOEXCEPT {
return { head, tail };
}
/**
* @brief Returns a copy of the views stored by the pack.
* @return A copy of the views stored by the pack.
*/
std::tuple<Head, Tail...> pack() const ENTT_NOEXCEPT {
return std::apply([this](auto &&... curr) { return std::make_tuple(head, curr...); }, tail);
}
/**
* @brief Appends a view to a pack.
* @tparam Args View template arguments.
* @param other A reference to a view to append to the pack.
* @return The extended pack.
*/
template<typename... Args>
[[nodiscard]] auto operator|(const basic_view<Args...> &other) const {
return std::make_from_tuple<view_pack<Head, Tail..., basic_view<Args...>>>(std::tuple_cat(std::make_tuple(head), tail, std::make_tuple(other)));
}
/**
* @brief Appends a pack and therefore all its views to another pack.
* @tparam Pack Types of views of the pack to append.
* @param other A reference to the pack to append.
* @return The extended pack.
*/
template<typename... Pack>
[[nodiscard]] auto operator|(const view_pack<Pack...> &other) const {
return std::make_from_tuple<view_pack<Head, Tail..., Pack...>>(std::tuple_cat(std::make_tuple(head), tail, other.pack()));
}
private:
Head head;
std::tuple<Tail...> tail;
};
/**
* @brief Combines two views in a pack.
* @tparam Args Template arguments of the first view.
* @tparam Other Template arguments of the second view.
* @param lhs A reference to the first view with which to create the pack.
* @param rhs A reference to the second view with which to create the pack.
* @return A pack that combines the two views in a single iterable object.
*/
template<typename... Args, typename... Other>
[[nodiscard]] auto operator|(const basic_view<Args...> &lhs, const basic_view<Other...> &rhs) {
return view_pack{lhs, rhs};
}
/**
* @brief Combines a view with a pack.
* @tparam Args View template arguments.
* @tparam Pack Types of views of the pack.
* @param view A reference to the view to combine with the pack.
* @param pack A reference to the pack to combine with the view.
* @return The extended pack.
*/
template<typename... Args, typename... Pack>
[[nodiscard]] auto operator|(const basic_view<Args...> &view, const view_pack<Pack...> &pack) {
return view_pack{view} | pack;
}
}
#endif

View File

@@ -9,6 +9,7 @@
#include "core/type_info.hpp"
#include "core/type_traits.hpp"
#include "core/utility.hpp"
#include "entity/component.hpp"
#include "entity/entity.hpp"
#include "entity/group.hpp"
#include "entity/handle.hpp"
@@ -23,19 +24,20 @@
#include "entity/storage.hpp"
#include "entity/utility.hpp"
#include "entity/view.hpp"
#include "entity/view_pack.hpp"
#include "locator/locator.hpp"
#include "meta/adl_pointer.hpp"
#include "meta/container.hpp"
#include "meta/ctx.hpp"
#include "meta/factory.hpp"
#include "meta/internal.hpp"
#include "meta/meta.hpp"
#include "meta/node.hpp"
#include "meta/pointer.hpp"
#include "meta/policy.hpp"
#include "meta/range.hpp"
#include "meta/resolve.hpp"
#include "meta/template.hpp"
#include "meta/type_traits.hpp"
#include "meta/utility.hpp"
#include "platform/android-ndk-r17.hpp"
#include "poly/poly.hpp"
#include "process/process.hpp"

View File

@@ -1,4 +1,5 @@
#include "core/fwd.hpp"
#include "entity/fwd.hpp"
#include "poly/fwd.hpp"
#include "resource/fwd.hpp"
#include "signal/fwd.hpp"

View File

@@ -87,7 +87,7 @@ struct service_locator {
* @param ptr Service to use to replace the current one.
*/
static void set(std::shared_ptr<Service> ptr) {
ENTT_ASSERT(static_cast<bool>(ptr));
ENTT_ASSERT(static_cast<bool>(ptr), "Null service not allowed");
service = std::move(ptr);
}

View File

@@ -24,7 +24,10 @@ namespace entt {
* @tparam Trait Traits associated with the underlying container.
*/
template<typename Container, template<typename> class... Trait>
struct meta_container_traits: public Trait<Container>... {};
struct meta_container_traits: public Trait<Container>... {
/*! @brief Type of container. */
using type = Container;
};
/**
@@ -33,21 +36,12 @@ struct meta_container_traits: public Trait<Container>... {};
*/
template<typename Container>
struct basic_container {
/*! @brief Iterator type of the container. */
using iterator = typename Container::iterator;
/*! @brief Iterator type of the container. */
using const_iterator = typename Container::const_iterator;
/*! @brief Unsigned integer type. */
using size_type = typename Container::size_type;
/*! @brief Value type of the container. */
using value_type = typename Container::value_type;
/**
* @brief Returns the size of the given container.
* @param cont The container for which to return the size.
* @return The size of the given container.
*/
[[nodiscard]] static size_type size(const Container &cont) ENTT_NOEXCEPT {
[[nodiscard]] static typename Container::size_type size(const Container &cont) ENTT_NOEXCEPT {
return cont.size();
}
@@ -56,7 +50,7 @@ struct basic_container {
* @param cont The container for which to return the iterator.
* @return An iterator to the first element of the given container.
*/
[[nodiscard]] static iterator begin(Container &cont) {
[[nodiscard]] static typename Container::iterator begin(Container &cont) {
return cont.begin();
}
@@ -65,7 +59,7 @@ struct basic_container {
* @param cont The container for which to return the iterator.
* @return An iterator to the first element of the given container.
*/
[[nodiscard]] static const_iterator cbegin(const Container &cont) {
[[nodiscard]] static typename Container::const_iterator cbegin(const Container &cont) {
return cont.begin();
}
@@ -74,7 +68,7 @@ struct basic_container {
* @param cont The container for which to return the iterator.
* @return An iterator past the last element of the given container.
*/
[[nodiscard]] static iterator end(Container &cont) {
[[nodiscard]] static typename Container::iterator end(Container &cont) {
return cont.end();
}
@@ -83,7 +77,7 @@ struct basic_container {
* @param cont The container for which to return the iterator.
* @return An iterator past the last element of the given container.
*/
[[nodiscard]] static const_iterator cend(const Container &cont) {
[[nodiscard]] static typename Container::const_iterator cend(const Container &cont) {
return cont.end();
}
};
@@ -95,9 +89,6 @@ struct basic_container {
*/
template<typename Container>
struct basic_associative_container {
/*! @brief Key type of the sequence container. */
using key_type = typename Container::key_type;
/**
* @brief Returns an iterator to the element with key equivalent to the
* given one, if any.
@@ -105,12 +96,12 @@ struct basic_associative_container {
* @param key The key of the element to search.
* @return An iterator to the element with the given key, if any.
*/
[[nodiscard]] static typename Container::iterator find(Container &cont, const key_type &key) {
[[nodiscard]] static typename Container::iterator find(Container &cont, const typename Container::key_type &key) {
return cont.find(key);
}
/*! @copydoc find */
[[nodiscard]] static typename Container::const_iterator cfind(const Container &cont, const key_type &key) {
[[nodiscard]] static typename Container::const_iterator cfind(const Container &cont, const typename Container::key_type &key) {
return cont.find(key);
}
};
@@ -165,12 +156,12 @@ struct basic_sequence_container {
* @param pos The position of the element to return.
* @return A reference to the requested element.
*/
[[nodiscard]] static typename Container::value_type & get(Container &cont, typename Container::size_type pos) {
[[nodiscard]] static typename Container::reference get(Container &cont, typename Container::size_type pos) {
return cont[pos];
}
/*! @copydoc get */
[[nodiscard]] static const typename Container::value_type & cget(const Container &cont, typename Container::size_type pos) {
[[nodiscard]] static typename Container::const_reference cget(const Container &cont, typename Container::size_type pos) {
return cont[pos];
}
};
@@ -349,10 +340,7 @@ struct meta_associative_container_traits<std::map<Key, Value, Args...>>
basic_dynamic_associative_container,
dynamic_associative_key_value_container
>
{
/*! @brief Mapped type of the sequence container. */
using mapped_type = typename std::map<Key, Value, Args...>::mapped_type;
};
{};
/**
@@ -372,10 +360,7 @@ struct meta_associative_container_traits<std::unordered_map<Key, Value, Args...>
basic_dynamic_associative_container,
dynamic_associative_key_value_container
>
{
/*! @brief Mapped type of the sequence container. */
using mapped_type = typename std::unordered_map<Key, Value, Args...>::mapped_type;
};
{};
/**

View File

@@ -2,9 +2,7 @@
#define ENTT_META_FACTORY_HPP
#include <array>
#include <cstddef>
#include <functional>
#include <tuple>
#include <type_traits>
#include <utility>
@@ -12,9 +10,10 @@
#include "../core/fwd.hpp"
#include "../core/type_info.hpp"
#include "../core/type_traits.hpp"
#include "internal.hpp"
#include "meta.hpp"
#include "node.hpp"
#include "policy.hpp"
#include "utility.hpp"
namespace entt {
@@ -29,165 +28,19 @@ namespace entt {
namespace internal {
template<typename, bool, bool>
struct meta_function_helper;
template<typename Ret, typename... Args, bool Const, bool Static>
struct meta_function_helper<Ret(Args...), Const, Static> {
using return_type = Ret;
using args_type = type_list<Args...>;
static constexpr auto is_static = Static;
static constexpr auto is_const = Const;
[[nodiscard]] static auto arg(typename internal::meta_func_node::size_type index) ENTT_NOEXCEPT {
return std::array<meta_type_node *, sizeof...(Args)>{{meta_info<Args>::resolve()...}}[index];
}
};
template<typename Type, typename Ret, typename... Args, typename Class>
constexpr meta_function_helper<std::conditional_t<std::is_same_v<Type, Class>, Ret(Args...), Ret(Class &, Args...)>, true, !std::is_same_v<Type, Class>>
to_meta_function_helper(Ret(Class:: *)(Args...) const);
template<typename Type, typename Ret, typename... Args, typename Class>
constexpr meta_function_helper<std::conditional_t<std::is_same_v<Type, Class>, Ret(Args...), Ret(Class &, Args...)>, false, !std::is_same_v<Type, Class>>
to_meta_function_helper(Ret(Class:: *)(Args...));
template<typename Type, typename Ret, typename... Args>
constexpr meta_function_helper<Ret(Args...), false, true>
to_meta_function_helper(Ret(*)(Args...));
template<typename Type>
constexpr void to_meta_function_helper(...);
template<typename Type, typename Candidate>
using meta_function_helper_t = decltype(to_meta_function_helper<Type>(std::declval<Candidate>()));
template<typename Type, typename... Args, std::size_t... Index>
[[nodiscard]] meta_any construct(meta_any * const args, std::index_sequence<Index...>) {
if(((args+Index)->allow_cast<Args>() && ...)) {
return Type{(args+Index)->cast<Args>()...};
}
return {};
template<typename Node>
[[nodiscard]] bool find_if(const Node *candidate, const Node *node) ENTT_NOEXCEPT {
return node && (node == candidate || find_if(candidate, node->next));
}
template<typename Type, auto Data>
[[nodiscard]] bool setter([[maybe_unused]] meta_handle instance, [[maybe_unused]] meta_any value) {
if constexpr(std::is_function_v<std::remove_reference_t<std::remove_pointer_t<decltype(Data)>>> || std::is_member_function_pointer_v<decltype(Data)>) {
using helper_type = meta_function_helper_t<Type, decltype(Data)>;
using data_type = type_list_element_t<!std::is_member_function_pointer_v<decltype(Data)>, typename helper_type::args_type>;
if(auto * const clazz = instance->try_cast<Type>(); clazz) {
if(value.allow_cast<data_type>()) {
std::invoke(Data, *clazz, value.cast<data_type>());
return true;
}
}
} else if constexpr(std::is_member_object_pointer_v<decltype(Data)>) {
using data_type = std::remove_cv_t<std::remove_reference_t<decltype(std::declval<Type>().*Data)>>;
if constexpr(!std::is_array_v<data_type>) {
if(auto * const clazz = instance->try_cast<Type>(); clazz) {
if(value.allow_cast<data_type>()) {
std::invoke(Data, clazz) = value.cast<data_type>();
return true;
}
}
}
template<typename Id, typename Node>
[[nodiscard]] bool find_if_not(const Id id, Node *node, const Node *owner) ENTT_NOEXCEPT {
if constexpr(std::is_pointer_v<Id>) {
return node && ((*node->id == *id && node != owner) || find_if_not(id, node->next, owner));
} else {
using data_type = std::remove_cv_t<std::remove_reference_t<decltype(*Data)>>;
if constexpr(!std::is_array_v<data_type>) {
if(value.allow_cast<data_type>()) {
*Data = value.cast<data_type>();
return true;
}
}
return node && ((node->id == id && node != owner) || find_if_not(id, node->next, owner));
}
return false;
}
template<typename Type, auto Data, typename Policy>
[[nodiscard]] meta_any getter([[maybe_unused]] meta_handle instance) {
[[maybe_unused]] auto dispatch = [](auto &&value) {
if constexpr(std::is_same_v<Policy, as_void_t>) {
return meta_any{std::in_place_type<void>, std::forward<decltype(value)>(value)};
} else if constexpr(std::is_same_v<Policy, as_ref_t>) {
return meta_any{std::ref(std::forward<decltype(value)>(value))};
} else if constexpr(std::is_same_v<Policy, as_cref_t>) {
return meta_any{std::cref(std::forward<decltype(value)>(value))};
} else {
static_assert(std::is_same_v<Policy, as_is_t>, "Policy not supported");
return meta_any{std::forward<decltype(value)>(value)};
}
};
if constexpr(std::is_function_v<std::remove_reference_t<std::remove_pointer_t<decltype(Data)>>> || std::is_member_function_pointer_v<decltype(Data)>) {
auto * const clazz = instance->try_cast<std::conditional_t<std::is_invocable_v<decltype(Data), const Type *>, const Type, Type>>();
return clazz ? dispatch(std::invoke(Data, *clazz)) : meta_any{};
} else if constexpr(std::is_member_object_pointer_v<decltype(Data)>) {
if constexpr(std::is_array_v<std::remove_cv_t<std::remove_reference_t<decltype(std::declval<Type>().*Data)>>>) {
return meta_any{};
} else {
auto * const clazz = instance->try_cast<std::conditional_t<std::is_same_v<Policy, as_ref_t>, Type, const Type>>();
return clazz ? dispatch(std::invoke(Data, clazz)) : meta_any{};
}
} else if constexpr(std::is_pointer_v<std::decay_t<decltype(Data)>>) {
if constexpr(std::is_array_v<std::remove_pointer_t<decltype(Data)>>) {
return meta_any{};
} else {
return dispatch(*Data);
}
} else {
return dispatch(Data);
}
}
template<typename Type, auto Candidate, typename Policy, std::size_t... Index>
[[nodiscard]] meta_any invoke([[maybe_unused]] meta_handle instance, meta_any *args, std::index_sequence<Index...>) {
using helper_type = meta_function_helper_t<Type, decltype(Candidate)>;
auto dispatch = [](auto &&... params) {
if constexpr(std::is_void_v<std::remove_cv_t<typename helper_type::return_type>> || std::is_same_v<Policy, as_void_t>) {
std::invoke(Candidate, std::forward<decltype(params)>(params)...);
return meta_any{std::in_place_type<void>};
} else if constexpr(std::is_same_v<Policy, as_ref_t>) {
return meta_any{std::ref(std::invoke(Candidate, std::forward<decltype(params)>(params)...))};
} else if constexpr(std::is_same_v<Policy, as_cref_t>) {
return meta_any{std::cref(std::invoke(Candidate, std::forward<decltype(params)>(params)...))};
} else {
static_assert(std::is_same_v<Policy, as_is_t>, "Policy not supported");
return meta_any{std::invoke(Candidate, std::forward<decltype(params)>(params)...)};
}
};
if constexpr(std::is_invocable_v<decltype(Candidate), const Type &, type_list_element_t<Index, typename helper_type::args_type>...>) {
if(const auto * const clazz = instance->try_cast<const Type>(); clazz && ((args+Index)->allow_cast<type_list_element_t<Index, typename helper_type::args_type>>() && ...)) {
return dispatch(*clazz, (args+Index)->cast<type_list_element_t<Index, typename helper_type::args_type>>()...);
}
} else if constexpr(std::is_invocable_v<decltype(Candidate), Type &, type_list_element_t<Index, typename helper_type::args_type>...>) {
if(auto * const clazz = instance->try_cast<Type>(); clazz && ((args+Index)->allow_cast<type_list_element_t<Index, typename helper_type::args_type>>() && ...)) {
return dispatch(*clazz, (args+Index)->cast<type_list_element_t<Index, typename helper_type::args_type>>()...);
}
} else {
if(((args+Index)->allow_cast<type_list_element_t<Index, typename helper_type::args_type>>() && ...)) {
return dispatch((args+Index)->cast<type_list_element_t<Index, typename helper_type::args_type>>()...);
}
}
return meta_any{};
}
@@ -209,7 +62,7 @@ template<typename Type, auto Candidate, typename Policy, std::size_t... Index>
* there are no subtle errors at runtime.
*/
template<typename...>
class meta_factory;
struct meta_factory;
/**
@@ -218,11 +71,8 @@ class meta_factory;
* @tparam Spec Property specialization pack used to disambiguate overloads.
*/
template<typename Type, typename... Spec>
class meta_factory<Type, Spec...>: public meta_factory<Type> {
[[nodiscard]] bool exists(const meta_any &key, const internal::meta_prop_node *node) ENTT_NOEXCEPT {
return node && (node->key() == key || exists(key, node->next));
}
struct meta_factory<Type, Spec...>: public meta_factory<Type> {
private:
template<std::size_t Step = 0, std::size_t... Index, typename... Property, typename... Other>
void unpack(std::index_sequence<Index...>, std::tuple<Property...> property, Other &&... other) {
unroll<Step>(choice<3>, std::move(std::get<Index>(property))..., std::forward<Other>(other)...);
@@ -254,27 +104,25 @@ class meta_factory<Type, Spec...>: public meta_factory<Type> {
template<std::size_t>
void unroll(choice_t<0>) {}
template<std::size_t = 0, typename Key, typename... Value>
void assign(Key &&key, Value &&... value) {
static const auto property{std::make_tuple(std::forward<Key>(key), std::forward<Value>(value)...)};
template<std::size_t = 0, typename Key>
void assign(Key &&key, meta_any value = {}) {
static meta_any property[2u]{};
static internal::meta_prop_node node{
nullptr,
[]() -> meta_any {
return std::get<0>(property);
},
[]() -> meta_any {
if constexpr(sizeof...(Value) == 0) {
return {};
} else {
return std::get<1>(property);
}
}
property[0u],
property[1u]
};
ENTT_ASSERT(!exists(node.key(), *curr));
node.next = *curr;
*curr = &node;
entt::meta_any instance{std::forward<Key>(key)};
ENTT_ASSERT(!internal::find_if_not(&instance, *curr, &node), "Duplicate key");
property[0u] = std::move(instance);
property[1u] = std::move(value);
if(!internal::find_if(&node, *curr)) {
node.next = *curr;
*curr = &node;
}
}
public:
@@ -334,18 +182,7 @@ private:
* @tparam Type Reflected type for which the factory was created.
*/
template<typename Type>
class meta_factory<Type> {
template<typename Node>
bool exists(const Node *candidate, const Node *node) ENTT_NOEXCEPT {
return node && (node == candidate || exists(candidate, node->next));
}
template<typename Node>
bool exists(const id_type id, const Node *node) ENTT_NOEXCEPT {
return node && (node->id == id || exists(id, node->next));
}
public:
struct meta_factory<Type> {
/**
* @brief Makes a meta type _searchable_.
* @param id Optional unique identifier.
@@ -354,11 +191,13 @@ public:
auto type(const id_type id = type_hash<Type>::value()) {
auto * const node = internal::meta_info<Type>::resolve();
ENTT_ASSERT(!exists(id, *internal::meta_context::global()));
ENTT_ASSERT(!exists(node, *internal::meta_context::global()));
ENTT_ASSERT(!internal::find_if_not(id, *internal::meta_context::global(), node), "Duplicate identifier");
node->id = id;
node->next = *internal::meta_context::global();
*internal::meta_context::global() = node;
if(!internal::find_if(node, *internal::meta_context::global())) {
node->next = *internal::meta_context::global();
*internal::meta_context::global() = node;
}
return meta_factory<Type, Type>{&node->prop};
}
@@ -385,9 +224,67 @@ public:
}
};
ENTT_ASSERT(!exists(&node, type->base));
node.next = type->base;
type->base = &node;
if(!internal::find_if(&node, type->base)) {
node.next = type->base;
type->base = &node;
}
return meta_factory<Type>{};
}
/**
* @brief Assigns a meta conversion function to a meta type.
*
* Conversion functions can be either free functions or member
* functions.<br/>
* In case of free functions, they must accept a const reference to an
* instance of the parent type as an argument. In case of member functions,
* they should have no arguments at all.
*
* @tparam Candidate The actual function to use for the conversion.
* @return A meta factory for the parent type.
*/
template<auto Candidate>
std::enable_if_t<std::is_member_function_pointer_v<decltype(Candidate)>, meta_factory<Type>> conv() ENTT_NOEXCEPT {
using conv_type = std::invoke_result_t<decltype(Candidate), Type &>;
auto * const type = internal::meta_info<Type>::resolve();
static internal::meta_conv_node node{
type,
nullptr,
&internal::meta_info<conv_type>::resolve,
[](const void *instance) -> meta_any {
return (static_cast<const Type *>(instance)->*Candidate)();
}
};
if(!internal::find_if(&node, type->conv)) {
node.next = type->conv;
type->conv = &node;
}
return meta_factory<Type>{};
}
/*! @copydoc conv */
template<auto Candidate>
std::enable_if_t<!std::is_member_function_pointer_v<decltype(Candidate)>, meta_factory<Type>> conv() ENTT_NOEXCEPT {
using conv_type = std::invoke_result_t<decltype(Candidate), Type &>;
auto * const type = internal::meta_info<Type>::resolve();
static internal::meta_conv_node node{
type,
nullptr,
&internal::meta_info<conv_type>::resolve,
[](const void *instance) -> meta_any {
return Candidate(*static_cast<const Type *>(instance));
}
};
if(!internal::find_if(&node, type->conv)) {
node.next = type->conv;
type->conv = &node;
}
return meta_factory<Type>{};
}
@@ -415,42 +312,10 @@ public:
}
};
ENTT_ASSERT(!exists(&node, type->conv));
node.next = type->conv;
type->conv = &node;
return meta_factory<Type>{};
}
/**
* @brief Assigns a meta conversion function to a meta type.
*
* Conversion functions can be either free functions or member
* functions.<br/>
* In case of free functions, they must accept a const reference to an
* instance of the parent type as an argument. In case of member functions,
* they should have no arguments at all.
*
* @tparam Candidate The actual function to use for the conversion.
* @return A meta factory for the parent type.
*/
template<auto Candidate>
auto conv() ENTT_NOEXCEPT {
using conv_type = std::invoke_result_t<decltype(Candidate), Type &>;
auto * const type = internal::meta_info<Type>::resolve();
static internal::meta_conv_node node{
type,
nullptr,
&internal::meta_info<conv_type>::resolve,
[](const void *instance) -> meta_any {
return std::invoke(Candidate, *static_cast<const Type *>(instance));
}
};
ENTT_ASSERT(!exists(&node, type->conv));
node.next = type->conv;
type->conv = &node;
if(!internal::find_if(&node, type->conv)) {
node.next = type->conv;
type->conv = &node;
}
return meta_factory<Type>{};
}
@@ -470,24 +335,27 @@ public:
*/
template<auto Candidate, typename Policy = as_is_t>
auto ctor() ENTT_NOEXCEPT {
using helper_type = internal::meta_function_helper_t<Type, decltype(Candidate)>;
static_assert(std::is_same_v<std::remove_cv_t<std::remove_reference_t<typename helper_type::return_type>>, Type>, "The function doesn't return an object of the required type");
using descriptor = meta_function_helper_t<Type, decltype(Candidate)>;
static_assert(std::is_same_v<std::decay_t<typename descriptor::return_type>, Type>, "The function doesn't return an object of the required type");
auto * const type = internal::meta_info<Type>::resolve();
static internal::meta_ctor_node node{
type,
nullptr,
nullptr,
helper_type::args_type::size,
&helper_type::arg,
[](meta_any * const any) {
return internal::invoke<Type, Candidate, Policy>({}, any, std::make_index_sequence<helper_type::args_type::size>{});
descriptor::args_type::size,
[](const typename internal::meta_ctor_node::size_type index) ENTT_NOEXCEPT {
return meta_arg(typename descriptor::args_type{}, index);
},
[](meta_any * const args) {
return meta_invoke<Type, Candidate, Policy>({}, args, std::make_index_sequence<descriptor::args_type::size>{});
}
};
ENTT_ASSERT(!exists(&node, type->ctor));
node.next = type->ctor;
type->ctor = &node;
if(!internal::find_if(&node, type->ctor)) {
node.next = type->ctor;
type->ctor = &node;
}
return meta_factory<Type, std::integral_constant<decltype(Candidate), Candidate>>{&node.prop};
}
@@ -504,23 +372,26 @@ public:
*/
template<typename... Args>
auto ctor() ENTT_NOEXCEPT {
using helper_type = internal::meta_function_helper_t<Type, Type(*)(Args...)>;
using descriptor = meta_function_helper_t<Type, Type(*)(Args...)>;
auto * const type = internal::meta_info<Type>::resolve();
static internal::meta_ctor_node node{
type,
nullptr,
nullptr,
helper_type::args_type::size,
&helper_type::arg,
[](meta_any * const any) {
return internal::construct<Type, Args...>(any, std::make_index_sequence<helper_type::args_type::size>{});
descriptor::args_type::size,
[](const typename internal::meta_ctor_node::size_type index) ENTT_NOEXCEPT {
return meta_arg(typename descriptor::args_type{}, index);
},
[](meta_any * const args) {
return meta_construct<Type, Args...>(args, std::make_index_sequence<descriptor::args_type::size>{});
}
};
ENTT_ASSERT(!exists(&node, type->ctor));
node.next = type->ctor;
type->ctor = &node;
if(!internal::find_if(&node, type->ctor)) {
node.next = type->ctor;
type->ctor = &node;
}
return meta_factory<Type, Type(Args...)>{&node.prop};
}
@@ -546,12 +417,8 @@ public:
static_assert(std::is_invocable_v<decltype(Func), Type &>, "The function doesn't accept an object of the type provided");
auto * const type = internal::meta_info<Type>::resolve();
ENTT_ASSERT(!type->dtor);
type->dtor = [](void *instance) {
if(instance) {
std::invoke(Func, *static_cast<Type *>(instance));
}
Func(*static_cast<Type *>(instance));
};
return meta_factory<Type>{};
@@ -575,7 +442,7 @@ public:
if constexpr(std::is_member_object_pointer_v<decltype(Data)>) {
return data<Data, Data, Policy>(id);
} else {
using data_type = std::remove_pointer_t<std::decay_t<decltype(Data)>>;
using data_type = std::remove_pointer_t<decltype(Data)>;
auto * const type = internal::meta_info<Type>::resolve();
static internal::meta_data_node node{
@@ -583,23 +450,20 @@ public:
type,
nullptr,
nullptr,
std::is_same_v<Type, data_type> || std::is_const_v<data_type>,
true,
&internal::meta_info<data_type>::resolve,
[]() -> std::remove_cv_t<decltype(internal::meta_data_node::set)> {
if constexpr(std::is_same_v<Type, data_type> || std::is_const_v<data_type>) {
return nullptr;
} else {
return &internal::setter<Type, Data>;
}
}(),
&internal::getter<Type, Data, Policy>
&meta_setter<Type, Data>,
&meta_getter<Type, Data, Policy>
};
ENTT_ASSERT(!exists(id, type->data));
ENTT_ASSERT(!exists(&node, type->data));
ENTT_ASSERT(!internal::find_if_not(id, type->data, &node), "Duplicate identifier");
node.id = id;
node.next = type->data;
type->data = &node;
if(!internal::find_if(&node, type->data)) {
node.next = type->data;
type->data = &node;
}
return meta_factory<Type, std::integral_constant<decltype(Data), Data>>{&node.prop};
}
@@ -635,23 +499,20 @@ public:
type,
nullptr,
nullptr,
std::is_same_v<decltype(Setter), std::nullptr_t> || (std::is_member_object_pointer_v<decltype(Setter)> && std::is_const_v<underlying_type>),
false,
&internal::meta_info<underlying_type>::resolve,
[]() -> std::remove_cv_t<decltype(internal::meta_data_node::set)> {
if constexpr(std::is_same_v<decltype(Setter), std::nullptr_t> || (std::is_member_object_pointer_v<decltype(Setter)> && std::is_const_v<underlying_type>)) {
return nullptr;
} else {
return &internal::setter<Type, Setter>;
}
}(),
&internal::getter<Type, Getter, Policy>
&meta_setter<Type, Setter>,
&meta_getter<Type, Getter, Policy>
};
ENTT_ASSERT(!exists(id, type->data));
ENTT_ASSERT(!exists(&node, type->data));
ENTT_ASSERT(!internal::find_if_not(id, type->data, &node), "Duplicate identifier");
node.id = id;
node.next = type->data;
type->data = &node;
if(!internal::find_if(&node, type->data)) {
node.next = type->data;
type->data = &node;
}
return meta_factory<Type, std::integral_constant<decltype(Setter), Setter>, std::integral_constant<decltype(Getter), Getter>>{&node.prop};
}
@@ -671,7 +532,7 @@ public:
*/
template<auto Candidate, typename Policy = as_is_t>
auto func(const id_type id) ENTT_NOEXCEPT {
using helper_type = internal::meta_function_helper_t<Type, decltype(Candidate)>;
using descriptor = meta_function_helper_t<Type, decltype(Candidate)>;
auto * const type = internal::meta_info<Type>::resolve();
static internal::meta_func_node node{
@@ -679,21 +540,28 @@ public:
type,
nullptr,
nullptr,
helper_type::args_type::size,
helper_type::is_const,
helper_type::is_static,
&internal::meta_info<std::conditional_t<std::is_same_v<Policy, as_void_t>, void, typename helper_type::return_type>>::resolve,
&helper_type::arg,
descriptor::args_type::size,
descriptor::is_const,
descriptor::is_static,
&internal::meta_info<std::conditional_t<std::is_same_v<Policy, as_void_t>, void, typename descriptor::return_type>>::resolve,
[](const typename internal::meta_func_node::size_type index) ENTT_NOEXCEPT {
return meta_arg(typename descriptor::args_type{}, index);
},
[](meta_handle instance, meta_any *args) {
return internal::invoke<Type, Candidate, Policy>(std::move(instance), args, std::make_index_sequence<helper_type::args_type::size>{});
return meta_invoke<Type, Candidate, Policy>(std::move(instance), args, std::make_index_sequence<descriptor::args_type::size>{});
}
};
ENTT_ASSERT(!exists(&node, type->func));
for(auto *it = &type->func; *it; it = &(*it)->next) {
if(*it == &node) {
*it = node.next;
break;
}
}
internal::meta_func_node **it = &type->func;
for(; *it && (*it)->id != id; it = &(*it)->next);
for(; *it && (*it)->id == id && (*it)->size < node.size; it = &(*it)->next);
for(; *it && (*it)->id == id && (*it)->arity < node.arity; it = &(*it)->next);
node.id = id;
node.next = *it;

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,12 @@
#ifndef ENTT_META_INTERNAL_HPP
#define ENTT_META_INTERNAL_HPP
#ifndef ENTT_META_NODE_HPP
#define ENTT_META_NODE_HPP
#include <cstddef>
#include <functional>
#include <iterator>
#include <type_traits>
#include <utility>
#include "../core/attribute.h"
#include "../config/config.h"
#include "../core/attribute.h"
#include "../core/fwd.hpp"
#include "../core/type_info.hpp"
#include "../core/type_traits.hpp"
@@ -19,6 +17,7 @@ namespace entt {
class meta_any;
class meta_type;
struct meta_handle;
@@ -36,8 +35,8 @@ struct meta_type_node;
struct meta_prop_node {
meta_prop_node * next;
meta_any(* const key)();
meta_any(* const value)();
const meta_any &id;
meta_any &value;
};
@@ -62,8 +61,8 @@ struct meta_ctor_node {
meta_type_node * const parent;
meta_ctor_node * next;
meta_prop_node * prop;
const size_type size;
meta_type_node *(* const arg)(size_type) ENTT_NOEXCEPT;
const size_type arity;
meta_type(* const arg)(const size_type) ENTT_NOEXCEPT;
meta_any(* const invoke)(meta_any * const);
};
@@ -73,6 +72,7 @@ struct meta_data_node {
meta_type_node * const parent;
meta_data_node * next;
meta_prop_node * prop;
const bool is_const;
const bool is_static;
meta_type_node *(* const type)() ENTT_NOEXCEPT;
bool(* const set)(meta_handle, meta_any);
@@ -86,15 +86,24 @@ struct meta_func_node {
meta_type_node * const parent;
meta_func_node * next;
meta_prop_node * prop;
const size_type size;
const size_type arity;
const bool is_const;
const bool is_static;
meta_type_node *(* const ret)() ENTT_NOEXCEPT;
meta_type_node *(* const arg)(size_type) ENTT_NOEXCEPT;
meta_type(* const arg)(const size_type) ENTT_NOEXCEPT;
meta_any(* const invoke)(meta_handle, meta_any *);
};
struct meta_template_info {
using size_type = std::size_t;
const bool is_template_specialization;
const size_type arity;
meta_type_node *(* const type)() ENTT_NOEXCEPT;
meta_type_node *(* const arg)(const size_type) ENTT_NOEXCEPT;
};
struct meta_type_node {
using size_type = std::size_t;
const type_info info;
@@ -116,97 +125,35 @@ struct meta_type_node {
const bool is_pointer_like;
const bool is_sequence_container;
const bool is_associative_container;
const meta_template_info template_info;
const size_type rank;
size_type(* const extent)(size_type);
size_type(* const extent)(const size_type) ENTT_NOEXCEPT ;
meta_type_node *(* const remove_pointer)() ENTT_NOEXCEPT;
meta_type_node *(* const remove_extent)() ENTT_NOEXCEPT;
meta_ctor_node * const def_ctor;
meta_ctor_node *ctor{nullptr};
meta_base_node *base{nullptr};
meta_conv_node *conv{nullptr};
meta_ctor_node *ctor{nullptr};
meta_data_node *data{nullptr};
meta_func_node *func{nullptr};
void(* dtor)(void *){nullptr};
};
template<typename Node>
class meta_range {
struct range_iterator {
using difference_type = std::ptrdiff_t;
using value_type = Node;
using pointer = value_type *;
using reference = value_type &;
using iterator_category = std::forward_iterator_tag;
range_iterator() ENTT_NOEXCEPT = default;
range_iterator(Node *head) ENTT_NOEXCEPT
: node{head}
{}
range_iterator & operator++() ENTT_NOEXCEPT {
return node = node->next, *this;
}
range_iterator operator++(int) ENTT_NOEXCEPT {
range_iterator orig = *this;
return ++(*this), orig;
}
[[nodiscard]] bool operator==(const range_iterator &other) const ENTT_NOEXCEPT {
return other.node == node;
}
[[nodiscard]] bool operator!=(const range_iterator &other) const ENTT_NOEXCEPT {
return !(*this == other);
}
[[nodiscard]] pointer operator->() const ENTT_NOEXCEPT {
return node;
}
[[nodiscard]] reference operator*() const ENTT_NOEXCEPT {
return *operator->();
}
private:
Node *node{nullptr};
};
public:
using iterator = range_iterator;
meta_range() ENTT_NOEXCEPT = default;
meta_range(Node *head)
: node{head}
{}
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
return iterator{node};
}
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
return iterator{};
}
private:
Node *node{nullptr};
};
template<auto Member, typename Op>
auto find_if(const Op &op, const meta_type_node *node)
template<auto Member, typename Op, typename Node>
auto meta_visit(const Op &op, const Node *node)
-> std::decay_t<decltype(node->*Member)> {
for(auto &&curr: meta_range{node->*Member}) {
if(op(&curr)) {
return &curr;
for(auto *curr = node->*Member; curr; curr = curr->next) {
if(op(curr)) {
return curr;
}
}
for(auto &&curr: meta_range{node->base}) {
if(auto *ret = find_if<Member>(op, curr.type()); ret) {
return ret;
if constexpr(std::is_same_v<Node, meta_type_node>) {
for(auto *curr = node->base; curr; curr = curr->next) {
if(auto *ret = meta_visit<Member>(op, curr->type()); ret) {
return ret;
}
}
}
@@ -214,17 +161,53 @@ auto find_if(const Op &op, const meta_type_node *node)
}
template<typename... Args>
meta_type_node * meta_arg_node(type_list<Args...>, const std::size_t index) ENTT_NOEXCEPT;
template<typename Type>
class ENTT_API meta_node {
static_assert(std::is_same_v<Type, std::remove_cv_t<std::remove_reference_t<Type>>>, "Invalid type");
template<std::size_t... Index>
[[nodiscard]] static auto extent(meta_type_node::size_type dim, std::index_sequence<Index...>) {
[[nodiscard]] static auto extent(const meta_type_node::size_type dim, std::index_sequence<Index...>) ENTT_NOEXCEPT {
meta_type_node::size_type ext{};
((ext = (dim == Index ? std::extent_v<Type, Index> : ext)), ...);
return ext;
}
[[nodiscard]] static meta_ctor_node * meta_default_constructor([[maybe_unused]] meta_type_node *type) ENTT_NOEXCEPT {
if constexpr(std::is_default_constructible_v<Type>) {
static meta_ctor_node node{
type,
nullptr,
nullptr,
0u,
nullptr,
[](meta_any * const) { return meta_any{std::in_place_type<Type>}; }
};
return &node;
} else {
return nullptr;
}
}
[[nodiscard]] static meta_template_info meta_template_descriptor() ENTT_NOEXCEPT {
if constexpr(is_complete_v<meta_template_traits<Type>>) {
return {
true,
meta_template_traits<Type>::args_type::size,
&meta_node<typename meta_template_traits<Type>::class_type>::resolve,
[](const std::size_t index) ENTT_NOEXCEPT {
return meta_arg_node(typename meta_template_traits<Type>::args_type{}, index);
}
};
} else {
return { false, 0u, nullptr, nullptr };
}
}
public:
[[nodiscard]] static meta_type_node * resolve() ENTT_NOEXCEPT {
static meta_type_node node{
@@ -245,14 +228,15 @@ public:
std::is_member_object_pointer_v<Type>,
std::is_member_function_pointer_v<Type>,
is_meta_pointer_like_v<Type>,
has_meta_sequence_container_traits_v<Type>,
has_meta_associative_container_traits_v<Type>,
is_complete_v<meta_sequence_container_traits<Type>>,
is_complete_v<meta_associative_container_traits<Type>>,
meta_template_descriptor(),
std::rank_v<Type>,
[](meta_type_node::size_type dim) {
return extent(dim, std::make_index_sequence<std::rank_v<Type>>{});
},
&meta_node<std::remove_cv_t<std::remove_pointer_t<Type>>>::resolve,
&meta_node<std::remove_cv_t<std::remove_extent_t<Type>>>::resolve
[](meta_type_node::size_type dim) ENTT_NOEXCEPT { return extent(dim, std::make_index_sequence<std::rank_v<Type>>{}); },
&meta_node<std::remove_cv_t<std::remove_reference_t<std::remove_pointer_t<Type>>>>::resolve,
&meta_node<std::remove_cv_t<std::remove_reference_t<std::remove_extent_t<Type>>>>::resolve,
meta_default_constructor(&node),
meta_default_constructor(&node)
};
return &node;
@@ -260,8 +244,15 @@ public:
};
template<typename... Type>
struct meta_info: meta_node<std::remove_cv_t<std::remove_reference_t<Type>>...> {};
template<typename Type>
struct meta_info: meta_node<std::remove_cv_t<std::remove_reference_t<Type>>> {};
template<typename... Args>
meta_type_node * meta_arg_node(type_list<Args...>, const std::size_t index) ENTT_NOEXCEPT {
meta_type_node *args[sizeof...(Args) + 1u]{nullptr, internal::meta_info<Args>::resolve()...};
return args[index + 1u];
}
}

View File

@@ -20,6 +20,17 @@ struct is_meta_pointer_like<Type *>
{};
/**
* @brief Partial specialization used to reject pointers to arrays.
* @tparam Type Type of elements of the array.
* @tparam N Number of elements of the array.
*/
template<typename Type, std::size_t N>
struct is_meta_pointer_like<Type(*)[N]>
: std::false_type
{};
/**
* @brief Makes `std::shared_ptr`s of any type pointer-like types for the meta
* system.

View File

@@ -4,7 +4,6 @@
#include <cstddef>
#include <iterator>
#include "internal.hpp"
namespace entt {
@@ -12,9 +11,10 @@ namespace entt {
/**
* @brief Iterable range to use to iterate all types of meta objects.
* @tparam Type Type of meta objects iterated.
* @tparam Type Type of meta objects returned.
* @tparam Node Type of meta nodes iterated.
*/
template<typename Type>
template<typename Type, typename Node = typename Type::node_type>
class meta_range {
struct range_iterator {
using difference_type = std::ptrdiff_t;
@@ -22,7 +22,7 @@ class meta_range {
using pointer = void;
using reference = value_type;
using iterator_category = std::input_iterator_tag;
using node_type = typename Type::node_type;
using node_type = Node;
range_iterator() ENTT_NOEXCEPT = default;
@@ -31,16 +31,16 @@ class meta_range {
{}
range_iterator & operator++() ENTT_NOEXCEPT {
return ++it, *this;
return (it = it->next), *this;
}
range_iterator operator++(int) ENTT_NOEXCEPT {
range_iterator orig = *this;
return it++, orig;
return ++(*this), orig;
}
[[nodiscard]] reference operator*() const ENTT_NOEXCEPT {
return it.operator->();
return it;
}
[[nodiscard]] bool operator==(const range_iterator &other) const ENTT_NOEXCEPT {
@@ -52,12 +52,12 @@ class meta_range {
}
private:
typename internal::meta_range<node_type>::iterator it{};
node_type *it{};
};
public:
/*! @brief Node type. */
using node_type = typename Type::node_type;
using node_type = Node;
/*! @brief Input iterator type. */
using iterator = range_iterator;

View File

@@ -6,6 +6,7 @@
#include "../core/type_info.hpp"
#include "ctx.hpp"
#include "meta.hpp"
#include "node.hpp"
#include "range.hpp"
@@ -38,8 +39,13 @@ template<typename Type>
* @return The meta type associated with the given identifier, if any.
*/
[[nodiscard]] inline meta_type resolve(const id_type id) ENTT_NOEXCEPT {
internal::meta_range range{*internal::meta_context::global()};
return std::find_if(range.begin(), range.end(), [id](const auto &curr) { return curr.id == id; }).operator->();
for(auto *curr = *internal::meta_context::global(); curr; curr = curr->next) {
if(curr->id == id) {
return curr;
}
}
return {};
}
@@ -50,8 +56,13 @@ template<typename Type>
* @return The meta type associated with the given type info object, if any.
*/
[[nodiscard]] inline meta_type resolve(const type_info info) ENTT_NOEXCEPT {
internal::meta_range range{*internal::meta_context::global()};
return std::find_if(range.begin(), range.end(), [info](const auto &curr) { return curr.info == info; }).operator->();
for(auto *curr = *internal::meta_context::global(); curr; curr = curr->next) {
if(curr->info == info) {
return curr;
}
}
return {};
}

View File

@@ -0,0 +1,33 @@
#ifndef ENTT_META_TEMPLATE_HPP
#define ENTT_META_TEMPLATE_HPP
#include "../core/type_traits.hpp"
namespace entt {
/*! @brief Utility class to disambiguate class templates. */
template<template<typename...> typename>
struct meta_class_template_tag {};
/**
* @brief General purpose traits class for generating meta template information.
* @tparam Clazz Type of class template.
* @tparam Args Types of template arguments.
*/
template<template<typename...> typename Clazz, typename... Args>
struct meta_template_traits<Clazz<Args...>> {
/*! @brief Wrapped class template. */
using class_type = meta_class_template_tag<Clazz>;
/*! @brief List of template arguments. */
using args_type = type_list<Args...>;
};
}
#endif

View File

@@ -8,6 +8,14 @@
namespace entt {
/**
* @brief Traits class template to be specialized to enable support for meta
* template information.
*/
template<typename>
struct meta_template_traits;
/**
* @brief Traits class template to be specialized to enable support for meta
* sequence containers.
@@ -24,54 +32,6 @@ template<typename>
struct meta_associative_container_traits;
/**
* @brief Provides the member constant `value` to true if support for meta
* sequence containers is enabled for the given type, false otherwise.
* @tparam Type Potentially sequence container type.
*/
template<typename Type, typename = void>
struct has_meta_sequence_container_traits: std::false_type {};
/*! @copydoc has_meta_sequence_container_traits */
template<typename Type>
struct has_meta_sequence_container_traits<Type, std::void_t<typename meta_sequence_container_traits<Type>::value_type>>
: std::true_type
{};
/**
* @brief Helper variable template.
* @tparam Type Potentially sequence container type.
*/
template<typename Type>
inline constexpr auto has_meta_sequence_container_traits_v = has_meta_sequence_container_traits<Type>::value;
/**
* @brief Provides the member constant `value` to true if support for meta
* associative containers is enabled for the given type, false otherwise.
* @tparam Type Potentially associative container type.
*/
template<typename, typename = void>
struct has_meta_associative_container_traits: std::false_type {};
/*! @copydoc has_meta_associative_container_traits */
template<typename Type>
struct has_meta_associative_container_traits<Type, std::void_t<typename meta_associative_container_traits<Type>::key_type>>
: std::true_type
{};
/**
* @brief Helper variable template.
* @tparam Type Potentially associative container type.
*/
template<typename Type>
inline constexpr auto has_meta_associative_container_traits_v = has_meta_associative_container_traits<Type>::value;
/**
* @brief Provides the member constant `value` to true if a meta associative
* container claims to wrap a key-only type, false otherwise.
@@ -83,8 +43,8 @@ struct is_key_only_meta_associative_container: std::true_type {};
/*! @copydoc is_key_only_meta_associative_container */
template<typename Type>
struct is_key_only_meta_associative_container<Type, std::void_t<typename meta_associative_container_traits<Type>::mapped_type>>
: std::false_type
struct is_key_only_meta_associative_container<Type, std::void_t<typename meta_associative_container_traits<Type>::type::mapped_type>>
: std::false_type
{};

333
src/entt/meta/utility.hpp Normal file
View File

@@ -0,0 +1,333 @@
#ifndef ENTT_META_UTILITY_HPP
#define ENTT_META_UTILITY_HPP
#include <cstddef>
#include <functional>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "../core/type_traits.hpp"
#include "meta.hpp"
#include "node.hpp"
#include "policy.hpp"
namespace entt {
/*! @brief Primary template isn't defined on purpose. */
template<typename, typename>
struct meta_function_descriptor;
/**
* @brief Meta function descriptor.
* @tparam Type Reflected type to which the meta function is associated.
* @tparam Ret Function return type.
* @tparam Class Actual owner of the member function.
* @tparam Args Function arguments.
*/
template<typename Type, typename Ret, typename Class, typename... Args>
struct meta_function_descriptor<Type, Ret(Class:: *)(Args...) const> {
/*! @brief Meta function return type. */
using return_type = Ret;
/*! @brief Meta function arguments. */
using args_type = std::conditional_t<std::is_same_v<Type, Class>, type_list<Args...>, type_list<const Class &, Args...>>;
/*! @brief True if the meta function is const, false otherwise. */
static constexpr auto is_const = true;
/*! @brief True if the meta function is static, false otherwise. */
static constexpr auto is_static = !std::is_same_v<Type, Class>;
};
/**
* @brief Meta function descriptor.
* @tparam Type Reflected type to which the meta function is associated.
* @tparam Ret Function return type.
* @tparam Class Actual owner of the member function.
* @tparam Args Function arguments.
*/
template<typename Type, typename Ret, typename Class, typename... Args>
struct meta_function_descriptor<Type, Ret(Class:: *)(Args...)> {
/*! @brief Meta function return type. */
using return_type = Ret;
/*! @brief Meta function arguments. */
using args_type = std::conditional_t<std::is_same_v<Type, Class>, type_list<Args...>, type_list<Class &, Args...>>;
/*! @brief True if the meta function is const, false otherwise. */
static constexpr auto is_const = false;
/*! @brief True if the meta function is static, false otherwise. */
static constexpr auto is_static = !std::is_same_v<Type, Class>;
};
/**
* @brief Meta function descriptor.
* @tparam Type Reflected type to which the meta function is associated.
* @tparam Ret Function return type.
* @tparam Args Function arguments.
*/
template<typename Type, typename Ret, typename... Args>
struct meta_function_descriptor<Type, Ret(*)(Args...)> {
/*! @brief Meta function return type. */
using return_type = Ret;
/*! @brief Meta function arguments. */
using args_type = type_list<Args...>;
/*! @brief True if the meta function is const, false otherwise. */
static constexpr auto is_const = false;
/*! @brief True if the meta function is static, false otherwise. */
static constexpr auto is_static = true;
};
/**
* @brief Meta function helper.
*
* Converts a function type to be associated with a reflected type into its meta
* function descriptor.
*
* @tparam Type Reflected type to which the meta function is associated.
* @tparam Candidate The actual function to associate with the reflected type.
*/
template<typename Type, typename Candidate>
class meta_function_helper {
template<typename Ret, typename... Args, typename Class>
static constexpr meta_function_descriptor<Type, Ret(Class:: *)(Args...) const> get_rid_of_noexcept(Ret(Class:: *)(Args...) const);
template<typename Ret, typename... Args, typename Class>
static constexpr meta_function_descriptor<Type, Ret(Class:: *)(Args...)> get_rid_of_noexcept(Ret(Class:: *)(Args...));
template<typename Ret, typename... Args>
static constexpr meta_function_descriptor<Type, Ret(*)(Args...)> get_rid_of_noexcept(Ret(*)(Args...));
public:
/*! @brief The meta function descriptor of the given function. */
using type = decltype(get_rid_of_noexcept(std::declval<Candidate>()));
};
/**
* @brief Helper type.
* @tparam Type Reflected type to which the meta function is associated.
* @tparam Candidate The actual function to associate with the reflected type.
*/
template<typename Type, typename Candidate>
using meta_function_helper_t = typename meta_function_helper<Type, Candidate>::type;
/**
* @brief Returns the meta type of the i-th element of a list of arguments.
* @tparam Args Actual types of arguments.
* @return The meta type of the i-th element of the list of arguments.
*/
template<typename... Args>
[[nodiscard]] static meta_type meta_arg(type_list<Args...>, const std::size_t index) ENTT_NOEXCEPT {
return internal::meta_arg_node(type_list<Args...>{}, index);
}
/**
* @brief Constructs an instance given a list of erased parameters, if possible.
* @tparam Type Actual type of the instance to construct.
* @tparam Args Types of arguments expected.
* @tparam Index Indexes to use to extract erased arguments from their list.
* @param args Parameters to use to construct the instance.
* @return A meta any containing the new instance, if any.
*/
template<typename Type, typename... Args, std::size_t... Index>
[[nodiscard]] meta_any meta_construct(meta_any * const args, std::index_sequence<Index...>) {
if(((args+Index)->allow_cast<Args>() && ...)) {
return Type{(args+Index)->cast<Args>()...};
}
return {};
}
/**
* @brief Sets the value of a given variable.
* @tparam Type Reflected type to which the variable is associated.
* @tparam Data The actual variable to set.
* @param instance An opaque instance of the underlying type, if required.
* @param value Parameter to use to set the variable.
* @return True in case of success, false otherwise.
*/
template<typename Type, auto Data>
[[nodiscard]] bool meta_setter([[maybe_unused]] meta_handle instance, [[maybe_unused]] meta_any value) {
if constexpr(!std::is_same_v<decltype(Data), Type> && !std::is_same_v<decltype(Data), std::nullptr_t>) {
if constexpr(std::is_function_v<std::remove_reference_t<std::remove_pointer_t<decltype(Data)>>>) {
using data_type = type_list_element_t<1u, typename meta_function_helper_t<Type, decltype(Data)>::args_type>;
if(auto * const clazz = instance->try_cast<Type>(); clazz && value.allow_cast<data_type>()) {
Data(*clazz, value.cast<data_type>());
return true;
}
} else if constexpr(std::is_member_function_pointer_v<decltype(Data)>) {
using data_type = type_list_element_t<0u, typename meta_function_helper_t<Type, decltype(Data)>::args_type>;
if(auto * const clazz = instance->try_cast<Type>(); clazz && value.allow_cast<data_type>()) {
(clazz->*Data)(value.cast<data_type>());
return true;
}
} else if constexpr(std::is_member_object_pointer_v<decltype(Data)>) {
using data_type = std::remove_reference_t<decltype(std::declval<Type>().*Data)>;
if constexpr(!std::is_array_v<data_type> && !std::is_const_v<data_type>) {
if(auto * const clazz = instance->try_cast<Type>(); clazz && value.allow_cast<data_type>()) {
clazz->*Data = value.cast<data_type>();
return true;
}
}
} else {
using data_type = std::remove_reference_t<decltype(*Data)>;
if constexpr(!std::is_array_v<data_type> && !std::is_const_v<data_type>) {
if(value.allow_cast<data_type>()) {
*Data = value.cast<data_type>();
return true;
}
}
}
}
return false;
}
/**
* @brief Wraps a value depending on the given policy.
* @tparam Policy Optional policy (no policy set by default).
* @tparam Type Type of value to wrap.
* @param value Value to wrap.
* @return A meta any containing the returned value.
*/
template<typename Policy = as_is_t, typename Type>
meta_any meta_dispatch(Type &&value) {
if constexpr(std::is_same_v<Policy, as_void_t>) {
return meta_any{std::in_place_type<void>, std::forward<Type>(value)};
} else if constexpr(std::is_same_v<Policy, as_ref_t>) {
return meta_any{std::in_place_type<Type>, std::forward<Type>(value)};
} else if constexpr(std::is_same_v<Policy, as_cref_t>) {
static_assert(std::is_lvalue_reference_v<Type>, "Invalid type");
return meta_any{std::in_place_type<const std::remove_reference_t<Type> &>, std::as_const(value)};
} else {
static_assert(std::is_same_v<Policy, as_is_t>, "Policy not supported");
return meta_any{std::forward<Type>(value)};
}
}
/**
* @brief Gets the value of a given variable.
* @tparam Type Reflected type to which the variable is associated.
* @tparam Data The actual variable to get.
* @tparam Policy Optional policy (no policy set by default).
* @param instance An opaque instance of the underlying type, if required.
* @return A meta any containing the value of the underlying variable.
*/
template<typename Type, auto Data, typename Policy = as_is_t>
[[nodiscard]] meta_any meta_getter([[maybe_unused]] meta_handle instance) {
if constexpr(std::is_function_v<std::remove_reference_t<std::remove_pointer_t<decltype(Data)>>>) {
auto * const clazz = instance->try_cast<std::conditional_t<std::is_invocable_v<decltype(Data), const Type &>, const Type, Type>>();
return clazz ? meta_dispatch<Policy>(Data(*clazz)) : meta_any{};
} else if constexpr(std::is_member_function_pointer_v<decltype(Data)>) {
auto * const clazz = instance->try_cast<std::conditional_t<std::is_invocable_v<decltype(Data), const Type &>, const Type, Type>>();
return clazz ? meta_dispatch<Policy>((clazz->*Data)()) : meta_any{};
} else if constexpr(std::is_member_object_pointer_v<decltype(Data)>) {
if constexpr(!std::is_array_v<std::remove_cv_t<std::remove_reference_t<decltype(std::declval<Type>().*Data)>>>) {
if(auto * clazz = instance->try_cast<Type>(); clazz) {
return meta_dispatch<Policy>(clazz->*Data);
} else if(auto * fallback = instance->try_cast<const Type>(); fallback) {
return meta_dispatch<Policy>(fallback->*Data);
}
}
return meta_any{};
} else if constexpr(std::is_pointer_v<decltype(Data)>) {
if constexpr(std::is_array_v<std::remove_pointer_t<decltype(Data)>>) {
return meta_any{};
} else {
return meta_dispatch<Policy>(*Data);
}
} else {
return meta_dispatch<Policy>(Data);
}
}
/**
* @brief Invokes a function given a list of erased parameters, if possible.
* @tparam Type Reflected type to which the function is associated.
* @tparam Candidate The actual function to invoke.
* @tparam Policy Optional policy (no policy set by default).
* @tparam Index Indexes to use to extract erased arguments from their list.
* @param instance An opaque instance of the underlying type, if required.
* @param args Parameters to use to invoke the function.
* @return A meta any containing the returned value, if any.
*/
template<typename Type, auto Candidate, typename Policy = as_is_t, std::size_t... Index>
[[nodiscard]] std::enable_if_t<!std::is_invocable_v<decltype(Candidate)>, meta_any> meta_invoke([[maybe_unused]] meta_handle instance, meta_any *args, std::index_sequence<Index...>) {
using descriptor = meta_function_helper_t<Type, decltype(Candidate)>;
const auto invoke = [](auto &&maybe_clazz, auto &&... other) {
if constexpr(std::is_member_function_pointer_v<decltype(Candidate)>) {
if constexpr(std::is_void_v<typename descriptor::return_type>) {
(std::forward<decltype(maybe_clazz)>(maybe_clazz).*Candidate)(std::forward<decltype(other)>(other)...);
return meta_any{std::in_place_type<void>};
} else {
return meta_dispatch<Policy>((std::forward<decltype(maybe_clazz)>(maybe_clazz).*Candidate)(std::forward<decltype(other)>(other)...));
}
} else {
if constexpr(std::is_void_v<typename descriptor::return_type>) {
Candidate(std::forward<decltype(maybe_clazz)>(maybe_clazz), std::forward<decltype(other)>(other)...);
return meta_any{std::in_place_type<void>};
} else {
return meta_dispatch<Policy>(Candidate(std::forward<decltype(maybe_clazz)>(maybe_clazz), std::forward<decltype(other)>(other)...));
}
}
};
if constexpr(std::is_invocable_v<decltype(Candidate), const Type &, type_list_element_t<Index, typename descriptor::args_type>...>) {
if(const auto * const clazz = instance->try_cast<const Type>(); clazz && ((args+Index)->allow_cast<type_list_element_t<Index, typename descriptor::args_type>>() && ...)) {
return invoke(*clazz, (args+Index)->cast<type_list_element_t<Index, typename descriptor::args_type>>()...);
}
} else if constexpr(std::is_invocable_v<decltype(Candidate), Type &, type_list_element_t<Index, typename descriptor::args_type>...>) {
if(auto * const clazz = instance->try_cast<Type>(); clazz && ((args+Index)->allow_cast<type_list_element_t<Index, typename descriptor::args_type>>() && ...)) {
return invoke(*clazz, (args+Index)->cast<type_list_element_t<Index, typename descriptor::args_type>>()...);
}
} else {
if(((args+Index)->allow_cast<type_list_element_t<Index, typename descriptor::args_type>>() && ...)) {
return invoke((args+Index)->cast<type_list_element_t<Index, typename descriptor::args_type>>()...);
}
}
return meta_any{};
}
/**
* @brief Invokes a function given a list of erased parameters, if possible.
* @tparam Type Reflected type to which the function is associated.
* @tparam Candidate The actual function to invoke.
* @tparam Policy Optional policy (no policy set by default).
* @tparam Index Indexes to use to extract erased arguments from their list.
* @return A meta any containing the returned value, if any.
*/
template<typename Type, auto Candidate, typename Policy = as_is_t, std::size_t... Index>
[[nodiscard]] std::enable_if_t<std::is_invocable_v<decltype(Candidate)>, meta_any> meta_invoke(meta_handle, meta_any *, std::index_sequence<Index...>) {
if constexpr(std::is_void_v<decltype(Candidate())>) {
Candidate();
return meta_any{std::in_place_type<void>};
} else {
return meta_dispatch<Policy>(Candidate());
}
}
}
#endif

26
src/entt/poly/fwd.hpp Normal file
View File

@@ -0,0 +1,26 @@
#ifndef ENTT_POLY_FWD_HPP
#define ENTT_POLY_FWD_HPP
#include <type_traits>
namespace entt {
template<typename, std::size_t Len, std::size_t = alignof(typename std::aligned_storage_t<Len + !Len>)>
class basic_poly;
/**
* @brief Alias declaration for the most common use case.
* @tparam Concept Concept descriptor.
*/
template<typename Concept>
using poly = basic_poly<Concept, sizeof(double[2])>;
}
#endif

View File

@@ -11,6 +11,7 @@
#include "../core/any.hpp"
#include "../core/type_info.hpp"
#include "../core/type_traits.hpp"
#include "fwd.hpp"
namespace entt {
@@ -44,29 +45,31 @@ struct poly_inspector {
/**
* @brief Static virtual table factory.
* @tparam Concept Concept descriptor.
* @tparam Len Size of the storage reserved for the small buffer optimization.
* @tparam Align Alignment requirement.
*/
template<typename Concept>
template<typename Concept, std::size_t Len, std::size_t Align>
class poly_vtable {
using inspector = typename Concept::template type<poly_inspector>;
template<typename Ret, typename... Args>
static auto vtable_entry(Ret(*)(inspector &, Args...)) -> Ret(*)(any &, Args...);
static auto vtable_entry(Ret(*)(inspector &, Args...)) -> Ret(*)(basic_any<Len, Align> &, Args...);
template<typename Ret, typename... Args>
static auto vtable_entry(Ret(*)(const inspector &, Args...)) -> Ret(*)(const any &, Args...);
static auto vtable_entry(Ret(*)(const inspector &, Args...)) -> Ret(*)(const basic_any<Len, Align> &, Args...);
template<typename Ret, typename... Args>
static auto vtable_entry(Ret(*)(Args...)) -> Ret(*)(const any &, Args...);
static auto vtable_entry(Ret(*)(Args...)) -> Ret(*)(const basic_any<Len, Align> &, Args...);
template<typename Ret, typename... Args>
static auto vtable_entry(Ret(inspector:: *)(Args...)) -> Ret(*)(any &, Args...);
static auto vtable_entry(Ret(inspector:: *)(Args...)) -> Ret(*)(basic_any<Len, Align> &, Args...);
template<typename Ret, typename... Args>
static auto vtable_entry(Ret(inspector:: *)(Args...) const) -> Ret(*)(const any &, Args...);
static auto vtable_entry(Ret(inspector:: *)(Args...) const) -> Ret(*)(const basic_any<Len, Align> &, Args...);
template<auto... Candidate>
static auto make_vtable(value_list<Candidate...>)
-> std::tuple<decltype(vtable_entry(Candidate))...>;
-> decltype(std::make_tuple(vtable_entry(Candidate)...));
template<typename... Func>
[[nodiscard]] static constexpr auto make_vtable(type_list<Func...>) {
@@ -84,8 +87,8 @@ class poly_vtable {
return std::invoke(Candidate, std::forward<Args>(args)...);
};
} else {
entry = +[](Any &any, Args... args) -> Ret {
return static_cast<Ret>(std::invoke(Candidate, any_cast<constness_as_t<Type, Any> &>(any), std::forward<Args>(args)...));
entry = +[](Any &instance, Args... args) -> Ret {
return static_cast<Ret>(std::invoke(Candidate, any_cast<constness_as_t<Type, Any> &>(instance), std::forward<Args>(args)...));
};
}
}
@@ -171,20 +174,22 @@ decltype(auto) poly_call(Poly &&self, Args &&... args) {
* Moreover, the `poly` class template also works with unmanaged objects.
*
* @tparam Concept Concept descriptor.
* @tparam Len Size of the storage reserved for the small buffer optimization.
* @tparam Align Optional alignment requirement.
*/
template<typename Concept>
class poly: private Concept::template type<poly_base<poly<Concept>>> {
template<typename Concept, std::size_t Len, std::size_t Align>
class basic_poly: private Concept::template type<poly_base<basic_poly<Concept, Len, Align>>> {
/*! @brief A poly base is allowed to snoop into a poly object. */
friend struct poly_base<poly<Concept>>;
friend struct poly_base<basic_poly>;
using vtable_type = typename poly_vtable<Concept>::type;
using vtable_type = typename poly_vtable<Concept, Len, Align>::type;
public:
/*! @brief Concept type. */
using concept_type = typename Concept::template type<poly_base<poly<Concept>>>;
using concept_type = typename Concept::template type<poly_base<basic_poly>>;
/*! @brief Default constructor. */
poly() ENTT_NOEXCEPT
basic_poly() ENTT_NOEXCEPT
: storage{},
vtable{}
{}
@@ -196,19 +201,9 @@ public:
* @param args Parameters to use to construct the instance.
*/
template<typename Type, typename... Args>
explicit poly(std::in_place_type_t<Type>, Args &&... args)
explicit basic_poly(std::in_place_type_t<Type>, Args &&... args)
: storage{std::in_place_type<Type>, std::forward<Args>(args)...},
vtable{poly_vtable<Concept>::template instance<std::remove_const_t<std::remove_reference_t<Type>>>()}
{}
/**
* @brief Constructs a poly that holds an unmanaged object.
* @tparam Type Type of object to use to initialize the poly.
* @param value An instance of an object to use to initialize the poly.
*/
template<typename Type>
poly(std::reference_wrapper<Type> value)
: poly{std::in_place_type<Type &>, &value.get()}
vtable{poly_vtable<Concept, Len, Align>::template instance<std::remove_const_t<std::remove_reference_t<Type>>>()}
{}
/**
@@ -216,23 +211,23 @@ public:
* @tparam Type Type of object to use to initialize the poly.
* @param value An instance of an object to use to initialize the poly.
*/
template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_reference_t<Type>>, poly>>>
poly(Type &&value) ENTT_NOEXCEPT
: poly{std::in_place_type<std::remove_cv_t<std::remove_reference_t<Type>>>, std::forward<Type>(value)}
template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_reference_t<Type>>, basic_poly>>>
basic_poly(Type &&value) ENTT_NOEXCEPT
: basic_poly{std::in_place_type<std::remove_cv_t<std::remove_reference_t<Type>>>, std::forward<Type>(value)}
{}
/**
* @brief Copy constructor.
* @param other The instance to copy from.
*/
poly(const poly &other) = default;
basic_poly(const basic_poly &other) = default;
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
poly(poly &&other) ENTT_NOEXCEPT
: poly{}
basic_poly(basic_poly &&other) ENTT_NOEXCEPT
: basic_poly{}
{
swap(*this, other);
}
@@ -242,7 +237,7 @@ public:
* @param other The instance to assign from.
* @return This poly object.
*/
poly & operator=(poly other) {
basic_poly & operator=(basic_poly other) {
swap(other, *this);
return *this;
}
@@ -276,8 +271,12 @@ public:
*/
template<typename Type, typename... Args>
void emplace(Args &&... args) {
storage.emplace<Type>(std::forward<Args>(args)...);
vtable = poly_vtable<Concept>::template instance<Type>();
*this = basic_poly{std::in_place_type<Type>, std::forward<Args>(args)...};
}
/*! @brief Destroys contained object */
void reset() {
*this = basic_poly{};
}
/**
@@ -306,7 +305,7 @@ public:
* @param lhs A valid poly object.
* @param rhs A valid poly object.
*/
friend void swap(poly &lhs, poly &rhs) {
friend void swap(basic_poly &lhs, basic_poly &rhs) {
using std::swap;
swap(lhs.storage, rhs.storage);
swap(lhs.vtable, rhs.vtable);
@@ -314,26 +313,24 @@ public:
/**
* @brief Aliasing constructor.
* @param other A reference to an object that isn't necessarily initialized.
* @return A poly that shares a reference to an unmanaged object.
*/
[[nodiscard]] friend poly as_ref(poly &other) ENTT_NOEXCEPT {
poly ref;
ref.storage = as_ref(other.storage);
ref.vtable = other.vtable;
[[nodiscard]] basic_poly as_ref() ENTT_NOEXCEPT {
basic_poly ref = std::as_const(*this).as_ref();
ref.storage = storage.as_ref();
return ref;
}
/*! @copydoc as_ref */
[[nodiscard]] friend poly as_ref(const poly &other) ENTT_NOEXCEPT {
poly ref;
ref.storage = as_ref(other.storage);
ref.vtable = other.vtable;
[[nodiscard]] basic_poly as_ref() const ENTT_NOEXCEPT {
basic_poly ref{};
ref.storage = storage.as_ref();
ref.vtable = vtable;
return ref;
}
private:
any storage;
basic_any<Len, Align> storage;
const vtable_type *vtable;
};

View File

@@ -78,7 +78,8 @@ class process {
SUCCEEDED,
FAILED,
ABORTED,
FINISHED
FINISHED,
REJECTED
};
template<typename Target = Derived>
@@ -201,7 +202,7 @@ public:
* @brief Returns true if a process is already terminated.
* @return True if the process is terminated, false otherwise.
*/
[[nodiscard]] bool dead() const ENTT_NOEXCEPT {
[[nodiscard]] bool finished() const ENTT_NOEXCEPT {
return current == state::FINISHED;
}
@@ -218,7 +219,7 @@ public:
* @return True if the process terminated with errors, false otherwise.
*/
[[nodiscard]] bool rejected() const ENTT_NOEXCEPT {
return stopped;
return current == state::REJECTED;
}
/**
@@ -248,13 +249,11 @@ public:
break;
case state::FAILED:
next(std::integral_constant<state, state::FAILED>{});
current = state::FINISHED;
stopped = true;
current = state::REJECTED;
break;
case state::ABORTED:
next(std::integral_constant<state, state::ABORTED>{});
current = state::FINISHED;
stopped = true;
current = state::REJECTED;
break;
default:
// suppress warnings
@@ -264,7 +263,6 @@ public:
private:
state current{state::UNINITIALIZED};
bool stopped{false};
};

View File

@@ -57,9 +57,7 @@ class scheduler {
struct continuation {
continuation(process_handler *ref)
: handler{ref}
{
ENTT_ASSERT(handler);
}
{}
template<typename Proc, typename... Args>
continuation then(Args &&... args) {
@@ -84,19 +82,19 @@ class scheduler {
auto *process = static_cast<Proc *>(handler.instance.get());
process->tick(delta, data);
auto dead = process->dead();
if(dead) {
if(handler.next && !process->rejected()) {
if(process->rejected()) {
return true;
} else if(process->finished()) {
if(handler.next) {
handler = std::move(*handler.next);
// forces the process to exit the uninitialized state
dead = handler.update(handler, {}, nullptr);
} else {
handler.instance.reset();
return handler.update(handler, {}, nullptr);
}
return true;
}
return dead;
return false;
}
template<typename Proc>
@@ -252,19 +250,17 @@ public:
* @param data Optional data.
*/
void update(const Delta delta, void *data = nullptr) {
bool clean = false;
auto sz = handlers.size();
for(auto pos = handlers.size(); pos; --pos) {
auto &handler = handlers[pos-1];
const bool dead = handler.update(handler, delta, data);
clean = clean || dead;
if(const auto dead = handler.update(handler, delta, data); dead) {
std::swap(handler, handlers[--sz]);
}
}
if(clean) {
handlers.erase(std::remove_if(handlers.begin(), handlers.end(), [](auto &handler) {
return !handler.instance;
}), handlers.end());
}
handlers.erase(handlers.begin() + sz, handlers.end());
}
/**

View File

@@ -2,7 +2,6 @@
#define ENTT_RESOURCE_CACHE_HPP
#include <memory>
#include <type_traits>
#include <unordered_map>
#include <utility>
@@ -92,19 +91,15 @@ struct resource_cache {
*/
template<typename Loader, typename... Args>
resource_handle<Resource> load(const id_type id, Args &&... args) {
static_assert(std::is_base_of_v<resource_loader<Loader, Resource>, Loader>, "Invalid loader type");
resource_handle<Resource> resource{};
if(auto it = resources.find(id); it == resources.cend()) {
if(auto instance = Loader{}.get(std::forward<Args>(args)...); instance) {
resources[id] = instance;
resource = std::move(instance);
if(auto handle = temp<Loader>(std::forward<Args>(args)...); handle) {
return (resources[id] = std::move(handle));
}
} else {
resource = it->second;
return it->second;
}
return resource;
return {};
}
/**
@@ -149,7 +144,7 @@ struct resource_cache {
*/
template<typename Loader, typename... Args>
[[nodiscard]] resource_handle<Resource> temp(Args &&... args) const {
return { Loader{}.get(std::forward<Args>(args)...) };
return Loader{}.get(std::forward<Args>(args)...);
}
/**
@@ -166,8 +161,11 @@ struct resource_cache {
* @return A handle for the given resource.
*/
[[nodiscard]] resource_handle<Resource> handle(const id_type id) const {
auto it = resources.find(id);
return { it == resources.end() ? nullptr : it->second };
if(auto it = resources.find(id); it != resources.cend()) {
return it->second;
}
return {};
}
/**
@@ -221,15 +219,15 @@ struct resource_cache {
if constexpr(std::is_invocable_v<Func, id_type>) {
func(curr->first);
} else if constexpr(std::is_invocable_v<Func, resource_handle<Resource>>) {
func(resource_handle{ curr->second });
func(curr->second);
} else {
func(curr->first, resource_handle{ curr->second });
func(curr->first, curr->second);
}
}
}
private:
std::unordered_map<id_type, std::shared_ptr<Resource>> resources;
std::unordered_map<id_type, resource_handle<Resource>> resources;
};

View File

@@ -3,6 +3,7 @@
#include <memory>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "fwd.hpp"
@@ -25,17 +26,94 @@ namespace entt {
*/
template<typename Resource>
class resource_handle {
/*! @brief Resource handles are friends of their caches. */
friend struct resource_cache<Resource>;
resource_handle(std::shared_ptr<Resource> res) ENTT_NOEXCEPT
: resource{std::move(res)}
{}
/*! @brief Resource handles are friends with each other. */
template<typename>
friend class resource_handle;
public:
/*! @brief Default constructor. */
resource_handle() ENTT_NOEXCEPT = default;
/**
* @brief Creates a handle from a shared pointer, namely a resource.
* @param res A pointer to a properly initialized resource.
*/
resource_handle(std::shared_ptr<Resource> res) ENTT_NOEXCEPT
: resource{std::move(res)}
{}
/**
* @brief Copy constructor.
* @param other The instance to copy from.
*/
resource_handle(const resource_handle<Resource> &other) ENTT_NOEXCEPT = default;
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
resource_handle(resource_handle<Resource> &&other) ENTT_NOEXCEPT = default;
/**
* @brief Copy constructs a handle which shares ownership of the resource.
* @tparam Other Type of resource managed by the received handle.
* @param other The handle to copy from.
*/
template<typename Other, typename = std::enable_if_t<!std::is_same_v<Other, Resource> && std::is_base_of_v<Resource, Other>>>
resource_handle(const resource_handle<Other> &other) ENTT_NOEXCEPT
: resource{other.resource}
{}
/**
* @brief Move constructs a handle which takes ownership of the resource.
* @tparam Other Type of resource managed by the received handle.
* @param other The handle to move from.
*/
template<typename Other, typename = std::enable_if_t<!std::is_same_v<Other, Resource> && std::is_base_of_v<Resource, Other>>>
resource_handle(resource_handle<Other> &&other) ENTT_NOEXCEPT
: resource{std::move(other.resource)}
{}
/**
* @brief Copy assignment operator.
* @param other The instance to copy from.
* @return This resource handle.
*/
resource_handle & operator=(const resource_handle<Resource> &other) ENTT_NOEXCEPT = default;
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This resource handle.
*/
resource_handle & operator=(resource_handle<Resource> &&other) ENTT_NOEXCEPT = default;
/**
* @brief Copy assignment operator from foreign handle.
* @tparam Other Type of resource managed by the received handle.
* @param other The handle to copy from.
* @return This resource handle.
*/
template<typename Other>
std::enable_if_t<!std::is_same_v<Other, Resource> && std::is_base_of_v<Resource, Other>, resource_handle &>
operator=(const resource_handle<Other> &other) ENTT_NOEXCEPT {
resource = other.resource;
return *this;
}
/**
* @brief Move assignment operator from foreign handle.
* @tparam Other Type of resource managed by the received handle.
* @param other The handle to move from.
* @return This resource handle.
*/
template<typename Other>
std::enable_if_t<!std::is_same_v<Other, Resource> && std::is_base_of_v<Resource, Other>, resource_handle &>
operator=(resource_handle<Other> &&other) ENTT_NOEXCEPT {
resource = std::move(other.resource);
return *this;
}
/**
* @brief Gets a reference to the managed resource.
*
@@ -45,7 +123,7 @@ public:
* @return A reference to the managed resource.
*/
[[nodiscard]] const Resource & get() const ENTT_NOEXCEPT {
ENTT_ASSERT(static_cast<bool>(resource));
ENTT_ASSERT(static_cast<bool>(resource), "Invalid resource");
return *resource;
}
@@ -84,7 +162,6 @@ public:
* contains no resource at all.
*/
[[nodiscard]] const Resource * operator->() const ENTT_NOEXCEPT {
ENTT_ASSERT(static_cast<bool>(resource));
return resource.get();
}

View File

@@ -2,8 +2,8 @@
#define ENTT_RESOURCE_LOADER_HPP
#include <memory>
#include "fwd.hpp"
#include "handle.hpp"
namespace entt {
@@ -15,14 +15,14 @@ namespace entt {
* Resource loaders must inherit from this class and stay true to the CRTP
* idiom. Moreover, a resource loader must expose a public, const member
* function named `load` that accepts a variable number of arguments and returns
* a shared pointer to the resource just created.<br/>
* a handle to the resource just created.<br/>
* As an example:
*
* @code{.cpp}
* struct my_resource {};
*
* struct my_loader: entt::resource_loader<my_loader, my_resource> {
* std::shared_ptr<my_resource> load(int) const {
* resource_handle<my_resource> load(int value) const {
* // use the integer value somehow
* return std::make_shared<my_resource>();
* }
@@ -44,7 +44,8 @@ namespace entt {
template<typename Loader, typename Resource>
class resource_loader {
/*! @brief Resource loaders are friends of their caches. */
friend struct resource_cache<Resource>;
template<typename Other>
friend struct resource_cache;
/**
* @brief Loads the resource and returns it.
@@ -53,7 +54,7 @@ class resource_loader {
* @return The resource just loaded or an empty pointer in case of errors.
*/
template<typename... Args>
[[nodiscard]] std::shared_ptr<Resource> get(Args &&... args) const {
[[nodiscard]] resource_handle<Resource> get(Args &&... args) const {
return static_cast<const Loader *>(this)->load(std::forward<Args>(args)...);
}
};

View File

@@ -288,7 +288,7 @@ public:
* @return The value returned by the underlying function.
*/
Ret operator()(Args... args) const {
ENTT_ASSERT(fn);
ENTT_ASSERT(static_cast<bool>(*this), "Uninitialized delegate");
return fn(data, std::forward<Args>(args)...);
}
@@ -335,7 +335,7 @@ template<typename Ret, typename... Args>
* @tparam Candidate Function or member to connect to the delegate.
*/
template<auto Candidate>
delegate(connect_arg_t<Candidate>) ENTT_NOEXCEPT
delegate(connect_arg_t<Candidate>)
-> delegate<std::remove_pointer_t<internal::function_pointer_t<decltype(Candidate)>>>;
@@ -345,7 +345,7 @@ delegate(connect_arg_t<Candidate>) ENTT_NOEXCEPT
* @tparam Type Type of class or type of payload.
*/
template<auto Candidate, typename Type>
delegate(connect_arg_t<Candidate>, Type &&) ENTT_NOEXCEPT
delegate(connect_arg_t<Candidate>, Type &&)
-> delegate<std::remove_pointer_t<internal::function_pointer_t<decltype(Candidate), Type>>>;
@@ -355,7 +355,7 @@ delegate(connect_arg_t<Candidate>, Type &&) ENTT_NOEXCEPT
* @tparam Args Types of arguments of a function type.
*/
template<typename Ret, typename... Args>
delegate(Ret(*)(const void *, Args...), const void * = nullptr) ENTT_NOEXCEPT
delegate(Ret(*)(const void *, Args...), const void * = nullptr)
-> delegate<Ret(Args...)>;

View File

@@ -101,6 +101,15 @@ class dispatcher {
}
public:
/*! @brief Default constructor. */
dispatcher() = default;
/*! @brief Default move constructor. */
dispatcher(dispatcher &&) = default;
/*! @brief Default move assignment operator. @return This dispatcher. */
dispatcher & operator=(dispatcher &&) = default;
/**
* @brief Returns a sink object for the given event.
*

View File

@@ -510,7 +510,8 @@ private:
* @tparam Args Types of arguments of a function type.
*/
template<typename Ret, typename... Args>
sink(sigh<Ret(Args...)> &) ENTT_NOEXCEPT -> sink<Ret(Args...)>;
sink(sigh<Ret(Args...)> &)
-> sink<Ret(Args...)>;
}

View File

@@ -58,7 +58,13 @@ function(SETUP_TARGET TARGET_NAME)
)
endif()
target_compile_definitions(${TARGET_NAME} PRIVATE ENTT_STANDALONE ${ARGN})
target_compile_definitions(
${TARGET_NAME}
PRIVATE
_ENABLE_EXTENDED_ALIGNED_STORAGE
NOMINMAX
${ARGN}
)
endfunction()
add_library(odr OBJECT odr.cpp)
@@ -80,8 +86,8 @@ endfunction()
function(SETUP_PLUGIN_TEST TEST_NAME)
add_library(_${TEST_NAME} MODULE $<TARGET_OBJECTS:odr> lib/${TEST_NAME}/plugin.cpp)
SETUP_TARGET(_${TEST_NAME} NOMINMAX ${ARGVN})
SETUP_BASIC_TEST(lib_${TEST_NAME} lib/${TEST_NAME}/main.cpp NOMINMAX PLUGIN="$<TARGET_FILE:_${TEST_NAME}>" ${ARGVN})
SETUP_TARGET(_${TEST_NAME} ${ARGVN})
SETUP_BASIC_TEST(lib_${TEST_NAME} lib/${TEST_NAME}/main.cpp PLUGIN="$<TARGET_FILE:_${TEST_NAME}>" ${ARGVN})
target_include_directories(_${TEST_NAME} PRIVATE ${cr_INCLUDE_DIR})
target_include_directories(lib_${TEST_NAME} PRIVATE ${cr_INCLUDE_DIR})
target_link_libraries(lib_${TEST_NAME} PRIVATE ${CMAKE_DL_LIBS})
@@ -97,6 +103,7 @@ endif()
if(ENTT_BUILD_EXAMPLE)
SETUP_BASIC_TEST(custom_identifier example/custom_identifier.cpp)
SETUP_BASIC_TEST(signal_less example/signal_less.cpp)
endif()
# Test lib
@@ -178,7 +185,6 @@ SETUP_BASIC_TEST(snapshot entt/entity/snapshot.cpp)
SETUP_BASIC_TEST(sparse_set entt/entity/sparse_set.cpp)
SETUP_BASIC_TEST(storage entt/entity/storage.cpp)
SETUP_BASIC_TEST(view entt/entity/view.cpp)
SETUP_BASIC_TEST(view_pack entt/entity/view_pack.cpp)
# Test locator
@@ -192,11 +198,13 @@ SETUP_BASIC_TEST(meta_container entt/meta/meta_container.cpp)
SETUP_BASIC_TEST(meta_conv entt/meta/meta_conv.cpp)
SETUP_BASIC_TEST(meta_ctor entt/meta/meta_ctor.cpp)
SETUP_BASIC_TEST(meta_data entt/meta/meta_data.cpp)
SETUP_BASIC_TEST(meta_dtor entt/meta/meta_dtor.cpp)
SETUP_BASIC_TEST(meta_func entt/meta/meta_func.cpp)
SETUP_BASIC_TEST(meta_handle entt/meta/meta_handle.cpp)
SETUP_BASIC_TEST(meta_pointer entt/meta/meta_pointer.cpp)
SETUP_BASIC_TEST(meta_prop entt/meta/meta_prop.cpp)
SETUP_BASIC_TEST(meta_range entt/meta/meta_range.cpp)
SETUP_BASIC_TEST(meta_template entt/meta/meta_template.cpp)
SETUP_BASIC_TEST(meta_type entt/meta/meta_type.cpp)
# Test poly

View File

@@ -12,14 +12,17 @@ struct position {
std::uint64_t y;
};
struct velocity {
std::uint64_t x;
std::uint64_t y;
};
struct velocity: position {};
struct stable_position: position {};
template<std::size_t>
struct comp { int x; };
template<>
struct entt::component_traits<stable_position>: basic_component_traits {
using in_place_delete = std::true_type;
};
struct timer final {
timer(): start{std::chrono::system_clock::now()} {}
@@ -45,9 +48,9 @@ void pathological(Func func) {
for(auto i = 0; i < 10; ++i) {
registry.each([i = 0, &registry](const auto entity) mutable {
if(!(++i % 7)) { registry.remove_if_exists<position>(entity); }
if(!(++i % 11)) { registry.remove_if_exists<velocity>(entity); }
if(!(++i % 13)) { registry.remove_if_exists<comp<0>>(entity); }
if(!(++i % 7)) { registry.remove<position>(entity); }
if(!(++i % 11)) { registry.remove<velocity>(entity); }
if(!(++i % 13)) { registry.remove<comp<0>>(entity); }
if(!(++i % 17)) { registry.destroy(entity); }
});
@@ -72,7 +75,7 @@ TEST(Benchmark, Create) {
timer timer;
for(std::uint64_t i = 0; i < 1000000L; i++) {
registry.create();
static_cast<void>(registry.create());
}
timer.elapsed();
@@ -120,6 +123,39 @@ TEST(Benchmark, CreateManyWithComponents) {
timer.elapsed();
}
TEST(Benchmark, Erase) {
entt::registry registry;
std::vector<entt::entity> entities(1000000);
std::cout << "Erasing 1000000 components from their entities" << std::endl;
registry.create(entities.begin(), entities.end());
registry.insert<int>(entities.begin(), entities.end());
timer timer;
for(auto entity: registry.view<int>()) {
registry.erase<int>(entity);
}
timer.elapsed();
}
TEST(Benchmark, EraseMany) {
entt::registry registry;
std::vector<entt::entity> entities(1000000);
std::cout << "Erasing 1000000 components from their entities at once" << std::endl;
registry.create(entities.begin(), entities.end());
registry.insert<int>(entities.begin(), entities.end());
timer timer;
auto view = registry.view<int>();
registry.erase<int>(view.begin(), view.end());
timer.elapsed();
}
TEST(Benchmark, Remove) {
entt::registry registry;
std::vector<entt::entity> entities(1000000);
@@ -142,21 +178,6 @@ TEST(Benchmark, RemoveMany) {
entt::registry registry;
std::vector<entt::entity> entities(1000000);
std::cout << "Removing 999999 components from their entities at once" << std::endl;
registry.create(entities.begin(), entities.end());
registry.insert<int>(entities.begin(), entities.end());
timer timer;
auto view = registry.view<int>();
registry.remove<int>(++view.begin(), view.end());
timer.elapsed();
}
TEST(Benchmark, RemoveAll) {
entt::registry registry;
std::vector<entt::entity> entities(1000000);
std::cout << "Removing 1000000 components from their entities at once" << std::endl;
registry.create(entities.begin(), entities.end());
@@ -168,6 +189,20 @@ TEST(Benchmark, RemoveAll) {
timer.elapsed();
}
TEST(Benchmark, Clear) {
entt::registry registry;
std::vector<entt::entity> entities(1000000);
std::cout << "Clearing 1000000 components from their entities" << std::endl;
registry.create(entities.begin(), entities.end());
registry.insert<int>(entities.begin(), entities.end());
timer timer;
registry.clear<int>();
timer.elapsed();
}
TEST(Benchmark, Recycle) {
entt::registry registry;
std::vector<entt::entity> entities(1000000);
@@ -183,7 +218,7 @@ TEST(Benchmark, Recycle) {
timer timer;
for(auto next = entities.size(); next; --next) {
registry.create();
static_cast<void>(registry.create());
}
timer.elapsed();
@@ -228,7 +263,7 @@ TEST(Benchmark, DestroyMany) {
entt::registry registry;
std::vector<entt::entity> entities(1000000);
std::cout << "Destroying 1000000 entities" << std::endl;
std::cout << "Destroying 1000000 entities at once" << std::endl;
registry.create(entities.begin(), entities.end());
registry.insert<int>(entities.begin(), entities.end());
@@ -239,6 +274,20 @@ TEST(Benchmark, DestroyMany) {
timer.elapsed();
}
TEST(Benchmark, DestroyManyFastPath) {
entt::registry registry;
std::vector<entt::entity> entities(1000000);
std::cout << "Destroying 1000000 entities at once, fast path" << std::endl;
registry.create(entities.begin(), entities.end());
registry.insert<int>(entities.begin(), entities.end());
timer timer;
registry.destroy(entities.begin(), entities.end());
timer.elapsed();
}
TEST(Benchmark, IterateSingleComponent1M) {
entt::registry registry;
@@ -260,6 +309,27 @@ TEST(Benchmark, IterateSingleComponent1M) {
});
}
TEST(Benchmark, IterateSingleComponentTombstonePolicy1M) {
entt::registry registry;
std::cout << "Iterating over 1000000 entities, one component, tombstone policy" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create();
registry.emplace<stable_position>(entity);
}
auto test = [&](auto func) {
timer timer;
registry.view<stable_position>().each(func);
timer.elapsed();
};
test([](auto &... comp) {
((comp.x = {}), ...);
});
}
TEST(Benchmark, IterateSingleComponentRuntime1M) {
entt::registry registry;
@@ -305,6 +375,28 @@ TEST(Benchmark, IterateTwoComponents1M) {
});
}
TEST(Benchmark, IterateTombstonePolicyTwoComponentsTombstonePolicy1M) {
entt::registry registry;
std::cout << "Iterating over 1000000 entities, two components, tombstone policy" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create();
registry.emplace<stable_position>(entity);
registry.emplace<velocity>(entity);
}
auto test = [&](auto func) {
timer timer;
registry.view<stable_position, velocity>().each(func);
timer.elapsed();
};
test([](auto &... comp) {
((comp.x = {}), ...);
});
}
TEST(Benchmark, IterateTwoComponents1MHalf) {
entt::registry registry;
@@ -537,6 +629,29 @@ TEST(Benchmark, IterateThreeComponents1M) {
});
}
TEST(Benchmark, IterateThreeComponentsTombstonePolicy1M) {
entt::registry registry;
std::cout << "Iterating over 1000000 entities, three components, tombstone policy" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create();
registry.emplace<stable_position>(entity);
registry.emplace<velocity>(entity);
registry.emplace<comp<0>>(entity);
}
auto test = [&](auto func) {
timer timer;
registry.view<stable_position, velocity, comp<0>>().each(func);
timer.elapsed();
};
test([](auto &... comp) {
((comp.x = {}), ...);
});
}
TEST(Benchmark, IterateThreeComponents1MHalf) {
entt::registry registry;
@@ -785,6 +900,31 @@ TEST(Benchmark, IterateFiveComponents1M) {
});
}
TEST(Benchmark, IterateFiveComponentsTombstonePolicy1M) {
entt::registry registry;
std::cout << "Iterating over 1000000 entities, five components, tombstone policy" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create();
registry.emplace<stable_position>(entity);
registry.emplace<velocity>(entity);
registry.emplace<comp<0>>(entity);
registry.emplace<comp<1>>(entity);
registry.emplace<comp<2>>(entity);
}
auto test = [&](auto func) {
timer timer;
registry.view<stable_position, velocity, comp<0>, comp<1>, comp<2>>().each(func);
timer.elapsed();
};
test([](auto &... comp) {
((comp.x = {}), ...);
});
}
TEST(Benchmark, IterateFiveComponents1MHalf) {
entt::registry registry;

File diff suppressed because it is too large Load Diff

View File

@@ -34,6 +34,16 @@ TEST(HashedString, Functionalities) {
ASSERT_EQ(foo_hs, "foo"_hs);
ASSERT_NE(bar_hs, "foo"_hs);
entt::hashed_string empty_hs{};
ASSERT_EQ(empty_hs, entt::hashed_string{});
ASSERT_NE(empty_hs, foo_hs);
empty_hs = foo_hs;
ASSERT_NE(empty_hs, entt::hashed_string{});
ASSERT_EQ(empty_hs, foo_hs);
}
TEST(HashedString, Empty) {

View File

@@ -5,17 +5,26 @@
#include <entt/core/type_info.hpp>
#include <entt/core/type_traits.hpp>
template<>
struct entt::type_name<float> final {
[[nodiscard]] static constexpr std::string_view value() ENTT_NOEXCEPT {
return std::string_view{""};
}
};
TEST(TypeSeq, Functionalities) {
ASSERT_EQ(entt::type_seq<int>::value(), entt::type_seq<int>::value());
ASSERT_NE(entt::type_seq<int>::value(), entt::type_seq<char>::value());
ASSERT_NE(entt::type_seq<int>::value(), entt::type_seq<int &&>::value());
ASSERT_NE(entt::type_seq<int &>::value(), entt::type_seq<const int &>::value());
ASSERT_EQ(static_cast<entt::id_type>(entt::type_seq<int>{}), entt::type_seq<int>::value());
}
TEST(TypeHash, Functionalities) {
ASSERT_NE(entt::type_hash<int>::value(), entt::type_hash<const int>::value());
ASSERT_NE(entt::type_hash<int>::value(), entt::type_hash<char>::value());
ASSERT_EQ(entt::type_hash<int>::value(), entt::type_hash<int>::value());
ASSERT_EQ(static_cast<entt::id_type>(entt::type_hash<int>{}), entt::type_hash<int>::value());
}
TEST(TypeName, Functionalities) {
@@ -27,6 +36,8 @@ TEST(TypeName, Functionalities) {
ASSERT_TRUE(((entt::type_name<entt::type_list<entt::type_list<int, char>, double>>::value()) == std::string_view{"entt::type_list<entt::type_list<int, char>, double>"})
|| ((entt::type_name<entt::type_list<entt::type_list<int, char>, double>>::value()) == std::string_view{"struct entt::type_list<struct entt::type_list<int,char>,double>"}));
ASSERT_EQ(static_cast<std::string_view>(entt::type_name<int>{}), entt::type_name<int>::value());
}
TEST(TypeInfo, Functionalities) {
@@ -40,7 +51,8 @@ TEST(TypeInfo, Functionalities) {
ASSERT_NE(entt::type_id<int>(), entt::type_info{});
ASSERT_NE(entt::type_id<int>(), entt::type_id<char>());
auto info = entt::type_id<int>();
const auto info = entt::type_id<int>();
const auto unnamed = entt::type_id<float>();
entt::type_info empty{};
ASSERT_NE(info, empty);
@@ -51,8 +63,9 @@ TEST(TypeInfo, Functionalities) {
ASSERT_EQ(info.hash(), entt::type_hash<int>::value());
ASSERT_EQ(info.name(), entt::type_name<int>::value());
ASSERT_FALSE(empty);
ASSERT_TRUE(info);
ASSERT_TRUE(unnamed);
ASSERT_FALSE(empty);
empty = info;

View File

@@ -1,10 +1,22 @@
#include <iterator>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <vector>
#include <gtest/gtest.h>
#include <entt/config/config.h>
#include <entt/core/hashed_string.hpp>
#include <entt/core/type_traits.hpp>
struct not_comparable {
bool operator==(const not_comparable &) const = delete;
};
struct nlohmann_json_like {
using value_type = nlohmann_json_like;
bool operator==(const nlohmann_json_like &) const { return true; }
};
TEST(TypeTraits, SizeOf) {
static_assert(entt::size_of_v<void> == 0u);
static_assert(entt::size_of_v<char> == sizeof(char));
@@ -58,6 +70,12 @@ TEST(TypeTraits, TypeList) {
static_assert(std::is_same_v<entt::type_list_element_t<0u, type>, int>);
static_assert(std::is_same_v<entt::type_list_element_t<1u, type>, char>);
static_assert(std::is_same_v<entt::type_list_element_t<0u, other>, double>);
static_assert(std::is_same_v<entt::type_list_diff_t<entt::type_list<int, char, double>, entt::type_list<float, bool>>, entt::type_list<int, char, double>>);
static_assert(std::is_same_v<entt::type_list_diff_t<entt::type_list<int, char, double>, entt::type_list<int, char, double>>, entt::type_list<>>);
static_assert(std::is_same_v<entt::type_list_diff_t<entt::type_list<int, char, double>, entt::type_list<int, char>>, entt::type_list<double>>);
static_assert(std::is_same_v<entt::type_list_diff_t<entt::type_list<int, char, double>, entt::type_list<char, double>>, entt::type_list<int>>);
static_assert(std::is_same_v<entt::type_list_diff_t<entt::type_list<int, char, double>, entt::type_list<char>>, entt::type_list<int, double>>);
}
TEST(TypeTraits, ValueList) {
@@ -79,6 +97,17 @@ TEST(TypeTraits, ValueList) {
TEST(TypeTraits, IsEqualityComparable) {
static_assert(entt::is_equality_comparable_v<int>);
static_assert(entt::is_equality_comparable_v<std::vector<int>>);
static_assert(entt::is_equality_comparable_v<std::vector<std::vector<int>>>);
static_assert(entt::is_equality_comparable_v<std::unordered_map<int, int>>);
static_assert(entt::is_equality_comparable_v<std::unordered_map<int, std::unordered_map<int, char>>>);
static_assert(entt::is_equality_comparable_v<nlohmann_json_like>);
static_assert(!entt::is_equality_comparable_v<not_comparable>);
static_assert(!entt::is_equality_comparable_v<std::vector<not_comparable>>);
static_assert(!entt::is_equality_comparable_v<std::vector<std::vector<not_comparable>>>);
static_assert(!entt::is_equality_comparable_v<std::unordered_map<int, not_comparable>>);
static_assert(!entt::is_equality_comparable_v<std::unordered_map<int, std::unordered_map<int, not_comparable>>>);
static_assert(!entt::is_equality_comparable_v<void>);
}
@@ -92,8 +121,28 @@ TEST(TypeTraits, IsApplicable) {
}
TEST(TypeTraits, IsComplete) {
static_assert(entt::is_complete_v<int>);
static_assert(!entt::is_complete_v<void>);
static_assert(entt::is_complete_v<int>);
}
TEST(TypeTraits, IsIterator) {
static_assert(!entt::is_iterator_v<void>);
static_assert(!entt::is_iterator_v<int>);
static_assert(entt::is_iterator_v<int *>);
static_assert(entt::is_iterator_v<std::vector<int>::iterator>);
static_assert(entt::is_iterator_v<std::vector<int>::const_iterator>);
static_assert(entt::is_iterator_v<std::vector<int>::reverse_iterator>);
}
TEST(TypeTraits, IsIteratorType) {
static_assert(!entt::is_iterator_type_v<void, std::vector<int>::iterator>);
static_assert(!entt::is_iterator_type_v<std::vector<int>::iterator, std::vector<int>::const_iterator>);
static_assert(!entt::is_iterator_type_v<std::vector<int>::iterator, int *>);
static_assert(entt::is_iterator_type_v<std::vector<int>::iterator, std::vector<int>::iterator>);
static_assert(entt::is_iterator_type_v<std::vector<int>::iterator, std::reverse_iterator<std::vector<int>::iterator>>);
static_assert(entt::is_iterator_type_v<std::vector<int>::iterator, std::reverse_iterator<std::reverse_iterator<std::vector<int>::iterator>>>);
}
TEST(TypeTraits, ConstnessAs) {

View File

@@ -27,11 +27,11 @@ TEST(Utility, Overload) {
functions instance;
ASSERT_NO_THROW(entt::overload<void(int)>(&functions::foo)(0));
ASSERT_NO_THROW(entt::overload<void()>(&functions::foo)());
ASSERT_NO_FATAL_FAILURE(entt::overload<void(int)>(&functions::foo)(0));
ASSERT_NO_FATAL_FAILURE(entt::overload<void()>(&functions::foo)());
ASSERT_NO_THROW((instance.*entt::overload<void(int)>(&functions::bar))(0));
ASSERT_NO_THROW((instance.*entt::overload<void()>(&functions::bar))());
ASSERT_NO_FATAL_FAILURE((instance.*entt::overload<void(int)>(&functions::bar))(0));
ASSERT_NO_FATAL_FAILURE((instance.*entt::overload<void()>(&functions::bar))());
}
TEST(Utility, Overloaded) {

View File

@@ -4,26 +4,97 @@
#include <entt/entity/entity.hpp>
#include <entt/entity/registry.hpp>
TEST(Entity, Traits) {
using traits_type = entt::entt_traits<entt::entity>;
entt::registry registry{};
registry.destroy(registry.create());
const auto entity = registry.create();
const auto other = registry.create();
ASSERT_EQ(entt::to_integral(entity), traits_type::to_integral(entity));
ASSERT_NE(entt::to_integral(entity), entt::to_integral<entt::entity>(entt::null));
ASSERT_NE(entt::to_integral(entity), entt::to_integral(entt::entity{}));
ASSERT_EQ(traits_type::to_entity(entity), 0u);
ASSERT_EQ(traits_type::to_version(entity), 1u);
ASSERT_EQ(traits_type::to_entity(other), 1u);
ASSERT_EQ(traits_type::to_version(other), 0u);
ASSERT_EQ(traits_type::construct(traits_type::to_entity(entity), traits_type::to_version(entity)), entity);
ASSERT_EQ(traits_type::construct(traits_type::to_entity(other), traits_type::to_version(other)), other);
ASSERT_NE(traits_type::construct(traits_type::to_entity(entity), {}), entity);
ASSERT_EQ(traits_type::construct(), entt::tombstone | static_cast<entt::entity>(entt::null));
ASSERT_EQ(traits_type::construct(), entt::null | static_cast<entt::entity>(entt::tombstone));
ASSERT_EQ(traits_type::construct(), static_cast<entt::entity>(entt::null));
ASSERT_EQ(traits_type::construct(), static_cast<entt::entity>(entt::tombstone));
ASSERT_EQ(traits_type::construct(), entt::entity{0xFFFFFFFF});
}
TEST(Entity, Null) {
using traits_type = entt::entt_traits<entt::entity>;
constexpr entt::entity tombstone = entt::tombstone;
constexpr entt::entity null = entt::null;
ASSERT_FALSE(entt::entity{} == entt::null);
ASSERT_TRUE(entt::entity{traits_type::construct()} == entt::null);
ASSERT_TRUE(entt::null == entt::null);
ASSERT_FALSE(entt::null != entt::null);
entt::registry registry{};
const auto entity = registry.create();
ASSERT_EQ((entt::null | entity), (traits_type::construct(traits_type::to_entity(null), traits_type::to_version(entity))));
ASSERT_EQ((entt::null | null), null);
ASSERT_EQ((entt::null | tombstone), null);
registry.emplace<int>(entity, 42);
ASSERT_FALSE(entt::entity{} == entt::null);
ASSERT_TRUE(entt::entity{traits_type::entity_mask} == entt::null);
ASSERT_TRUE(entt::entity{~typename traits_type::entity_type{}} == entt::null);
ASSERT_TRUE(entt::null == entt::null);
ASSERT_FALSE(entt::null != entt::null);
ASSERT_FALSE(entity == entt::null);
ASSERT_FALSE(entt::null == entity);
ASSERT_TRUE(entity != entt::null);
ASSERT_TRUE(entt::null != entity);
ASSERT_FALSE(registry.valid(entt::null));
const entt::entity other = entt::null;
ASSERT_FALSE(registry.valid(other));
ASSERT_NE(registry.create(other), other);
}
TEST(Entity, Tombstone) {
using traits_type = entt::entt_traits<entt::entity>;
constexpr entt::entity tombstone = entt::tombstone;
constexpr entt::entity null = entt::null;
ASSERT_FALSE(entt::entity{} == entt::tombstone);
ASSERT_TRUE(entt::entity{traits_type::construct()} == entt::tombstone);
ASSERT_TRUE(entt::tombstone == entt::tombstone);
ASSERT_FALSE(entt::tombstone != entt::tombstone);
entt::registry registry{};
const auto entity = registry.create();
ASSERT_EQ((entt::tombstone | entity), (traits_type::construct(traits_type::to_entity(entity), traits_type::to_version(tombstone))));
ASSERT_EQ((entt::tombstone | tombstone), tombstone);
ASSERT_EQ((entt::tombstone | null), tombstone);
registry.emplace<int>(entity, 42);
ASSERT_FALSE(entity == entt::tombstone);
ASSERT_FALSE(entt::tombstone == entity);
ASSERT_TRUE(entity != entt::tombstone);
ASSERT_TRUE(entt::tombstone != entity);
const auto vers = traits_type::to_version(entt::tombstone);
const auto other = traits_type::construct(traits_type::to_entity(entity), vers);
ASSERT_FALSE(registry.valid(entt::tombstone));
ASSERT_NE(registry.destroy(entity, vers), vers);
ASSERT_NE(registry.create(other), other);
}

View File

@@ -1,7 +1,7 @@
#include <utility>
#include <iterator>
#include <algorithm>
#include <iterator>
#include <type_traits>
#include <utility>
#include <gtest/gtest.h>
#include <entt/entity/registry.hpp>
#include <entt/entity/group.hpp>
@@ -28,10 +28,10 @@ TEST(NonOwningGroup, Functionalities) {
registry.emplace<char>(e1, '2');
ASSERT_FALSE(group.empty());
ASSERT_NO_THROW(group.begin()++);
ASSERT_NO_THROW(++cgroup.begin());
ASSERT_NO_THROW([](auto it) { return it++; }(group.rbegin()));
ASSERT_NO_THROW([](auto it) { return ++it; }(cgroup.rbegin()));
ASSERT_NO_FATAL_FAILURE(group.begin()++);
ASSERT_NO_FATAL_FAILURE(++cgroup.begin());
ASSERT_NO_FATAL_FAILURE([](auto it) { return it++; }(group.rbegin()));
ASSERT_NO_FATAL_FAILURE([](auto it) { return ++it; }(cgroup.rbegin()));
ASSERT_NE(group.begin(), group.end());
ASSERT_NE(cgroup.begin(), cgroup.end());
@@ -43,7 +43,7 @@ TEST(NonOwningGroup, Functionalities) {
ASSERT_EQ(group.size(), 2u);
registry.remove<int>(e0);
registry.erase<int>(e0);
ASSERT_EQ(group.size(), 1u);
@@ -53,10 +53,10 @@ TEST(NonOwningGroup, Functionalities) {
ASSERT_EQ(cgroup.get<const char>(entity), '2');
}
ASSERT_EQ(*(group.data() + 0), e1);
ASSERT_EQ(group.data()[0u], e1);
registry.remove<char>(e0);
registry.remove<char>(e1);
registry.erase<char>(e0);
registry.erase<char>(e1);
ASSERT_EQ(group.begin(), group.end());
ASSERT_EQ(cgroup.begin(), cgroup.end());
@@ -90,7 +90,7 @@ TEST(NonOwningGroup, Invalid) {
ASSERT_TRUE(group.empty());
ASSERT_EQ(group.size(), 0u);
ASSERT_EQ(group.capacity(), 0u);
ASSERT_NO_THROW(group.shrink_to_fit());
ASSERT_NO_FATAL_FAILURE(group.shrink_to_fit());
ASSERT_EQ(group.data(), nullptr);
@@ -101,15 +101,6 @@ TEST(NonOwningGroup, Invalid) {
ASSERT_EQ(group.find(entity), group.end());
ASSERT_EQ(group.front(), entt::entity{entt::null});
ASSERT_EQ(group.back(), entt::entity{entt::null});
group.each([](const auto, const auto &) { FAIL(); });
group.each([](const auto &) { FAIL(); });
for([[maybe_unused]] auto all: group.each()) { FAIL(); }
for(auto first = group.each().rbegin(), last = group.each().rend(); first != last; ++first) { FAIL(); }
ASSERT_NO_THROW(group.sort([](const auto, const auto) { FAIL(), true; }));
ASSERT_NO_THROW(group.sort<const empty_type>());
}
TEST(NonOwningGroup, ElementAccess) {
@@ -168,6 +159,7 @@ TEST(NonOwningGroup, Empty) {
TEST(NonOwningGroup, Each) {
entt::registry registry;
auto group = registry.group(entt::get<int, char>);
auto iterable = group.each();
const auto e0 = registry.create();
registry.emplace<int>(e0, 0);
@@ -178,9 +170,10 @@ TEST(NonOwningGroup, Each) {
registry.emplace<char>(e1);
auto cgroup = std::as_const(registry).group_if_exists(entt::get<const int, const char>);
auto citerable = cgroup.each();
std::size_t cnt = 0;
for(auto first = cgroup.each().rbegin(), last = cgroup.each().rend(); first != last; ++first) {
for(auto first = citerable.rbegin(), last = citerable.rend(); first != last; ++first) {
static_assert(std::is_same_v<decltype(*first), std::tuple<entt::entity, const int &, const char &>>);
ASSERT_EQ(std::get<1>(*first), cnt++);
}
@@ -193,7 +186,8 @@ TEST(NonOwningGroup, Each) {
cgroup.each([&cnt](const int &, const char &) { --cnt; });
cgroup.each([&cnt](auto, const int &, const char &) { --cnt; });
for(auto &&[entt, iv, cv]: group.each()) {
// do not use iterable, make sure an iterable group works when created from a temporary
for(auto [entt, iv, cv]: registry.group(entt::get<int, char>).each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), char &>);
@@ -201,6 +195,12 @@ TEST(NonOwningGroup, Each) {
}
ASSERT_EQ(cnt, std::size_t{0});
auto it = iterable.begin();
auto rit = iterable.rbegin();
ASSERT_EQ((it++, ++it), iterable.end());
ASSERT_EQ((rit++, ++rit), iterable.rend());
}
TEST(NonOwningGroup, Sort) {
@@ -221,17 +221,17 @@ TEST(NonOwningGroup, Sort) {
registry.emplace<int>(e1, 1);
registry.emplace<int>(e2, 2);
ASSERT_EQ(*(group.data() + 0u), e0);
ASSERT_EQ(*(group.data() + 1u), e1);
ASSERT_EQ(*(group.data() + 2u), e2);
ASSERT_EQ(group.data()[0u], e0);
ASSERT_EQ(group.data()[1u], e1);
ASSERT_EQ(group.data()[2u], e2);
group.sort([](const entt::entity lhs, const entt::entity rhs) {
return entt::to_integral(lhs) < entt::to_integral(rhs);
});
ASSERT_EQ(*(group.data() + 0u), e2);
ASSERT_EQ(*(group.data() + 1u), e1);
ASSERT_EQ(*(group.data() + 2u), e0);
ASSERT_EQ(group.data()[0u], e2);
ASSERT_EQ(group.data()[1u], e1);
ASSERT_EQ(group.data()[2u], e0);
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)));
@@ -243,9 +243,25 @@ TEST(NonOwningGroup, Sort) {
return lhs > rhs;
});
ASSERT_EQ(*(group.data() + 0u), e0);
ASSERT_EQ(*(group.data() + 1u), e1);
ASSERT_EQ(*(group.data() + 2u), e2);
ASSERT_EQ(group.data()[0u], e0);
ASSERT_EQ(group.data()[1u], e1);
ASSERT_EQ(group.data()[2u], e2);
ASSERT_EQ((group.get<const int, unsigned int>(e0)), (std::make_tuple(0, 0u)));
ASSERT_EQ((group.get<const int, unsigned int>(e1)), (std::make_tuple(1, 1u)));
ASSERT_EQ((group.get<const int, unsigned int>(e2)), (std::make_tuple(2, 2u)));
ASSERT_FALSE(group.contains(e3));
group.sort<const int, unsigned int>([](const auto lhs, const auto rhs) {
static_assert(std::is_same_v<decltype(std::get<0>(lhs)), const int &>);
static_assert(std::is_same_v<decltype(std::get<1>(rhs)), unsigned int &>);
return std::get<0>(lhs) < std::get<0>(rhs);
});
ASSERT_EQ(group.data()[0u], e2);
ASSERT_EQ(group.data()[1u], e1);
ASSERT_EQ(group.data()[2u], e0);
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)));
@@ -348,12 +364,16 @@ TEST(NonOwningGroup, ConstNonConstAndAllInBetween) {
static_assert(std::is_same_v<decltype(group.get({})), std::tuple<int &, const char &>>);
static_assert(std::is_same_v<decltype(group.data()), const entt::entity *>);
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<const 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, const char>)), decltype(std::as_const(registry).group_if_exists(entt::get<const int, const char>))>);
group.each([](auto &&i, auto &&c) {
static_assert(std::is_same_v<decltype(i), int &>);
static_assert(std::is_same_v<decltype(c), const char &>);
});
for(auto &&[entt, iv, cv]: group.each()) {
for(auto [entt, iv, cv]: group.each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), const char &>);
@@ -380,7 +400,7 @@ TEST(NonOwningGroup, Find) {
registry.emplace<int>(e3);
registry.emplace<char>(e3);
registry.remove<int>(e1);
registry.erase<int>(e1);
ASSERT_NE(group.find(e0), group.end());
ASSERT_EQ(group.find(e1), group.end());
@@ -439,8 +459,8 @@ TEST(NonOwningGroup, ExcludedComponents) {
ASSERT_TRUE(group.empty());
registry.remove<char>(e1);
registry.remove<char>(e3);
registry.erase<char>(e1);
registry.erase<char>(e3);
for(const auto entity: group) {
ASSERT_TRUE(entity == e1 || entity == e3);
@@ -475,7 +495,7 @@ TEST(NonOwningGroup, EmptyAndNonEmptyTypes) {
ASSERT_TRUE(entity == e0 || entity == e1);
});
for(auto &&[entt, iv]: group.each()) {
for(auto [entt, iv]: group.each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>);
ASSERT_TRUE(entt == e0 || entt == e1);
@@ -496,7 +516,7 @@ TEST(NonOwningGroup, TrackEntitiesOnComponentDestruction) {
ASSERT_TRUE(group.empty());
ASSERT_TRUE(cgroup.empty());
registry.remove<char>(entity);
registry.erase<char>(entity);
ASSERT_FALSE(group.empty());
ASSERT_FALSE(cgroup.empty());
@@ -514,7 +534,7 @@ TEST(NonOwningGroup, EmptyTypes) {
ASSERT_EQ(entity, entt);
});
for(auto &&[entt, iv, cv]: registry.group(entt::get<int, char, empty_type>).each()) {
for(auto [entt, iv, cv]: registry.group(entt::get<int, char, empty_type>).each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), char &>);
@@ -526,7 +546,7 @@ TEST(NonOwningGroup, EmptyTypes) {
check = false;
});
for(auto &&[entt, iv, cv]: registry.group(entt::get<int, empty_type, char>).each()) {
for(auto [entt, iv, cv]: registry.group(entt::get<int, empty_type, char>).each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), char &>);
@@ -537,15 +557,16 @@ TEST(NonOwningGroup, EmptyTypes) {
ASSERT_EQ(entity, entt);
});
for(auto &&[entt, iv, cv]: registry.group(entt::get<empty_type, int, char>).each()) {
for(auto [entt, iv, cv]: registry.group(entt::get<empty_type, int, char>).each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), char &>);
ASSERT_EQ(entity, entt);
}
registry.group(entt::get<int, char, double>).each([](const auto, int, char, double) { FAIL(); });
ASSERT_EQ(registry.group(entt::get<int, char, double>).each().begin(), registry.group(entt::get<int, char, double>).each().end());
auto iterable = registry.group(entt::get<int, char, double>).each();
ASSERT_EQ(iterable.begin(), iterable.end());
}
TEST(NonOwningGroup, FrontBack) {
@@ -583,9 +604,35 @@ TEST(NonOwningGroup, SignalRace) {
TEST(NonOwningGroup, ExtendedGet) {
using type = decltype(std::declval<entt::registry>().group(entt::get<int, empty_type, char>).get({}));
static_assert(std::tuple_size_v<type> == 2u);
static_assert(std::is_same_v<std::tuple_element_t<0, type>, int &>);
static_assert(std::is_same_v<std::tuple_element_t<1, type>, char &>);
entt::registry registry;
const auto entity = registry.create();
registry.emplace<int>(entity, 42);
registry.emplace<char>(entity, 'c');
const auto tup = registry.group(entt::get<int, char>).get(entity);
ASSERT_EQ(std::get<0>(tup), 42);
ASSERT_EQ(std::get<1>(tup), 'c');
}
TEST(NonOwningGroup, IterableGroupAlgorithmCompatibility) {
entt::registry registry;
const auto entity = registry.create();
registry.emplace<int>(entity);
registry.emplace<char>(entity);
const auto group = registry.group(entt::get<int, char>);
const auto iterable = group.each();
const auto it = std::find_if(iterable.begin(), iterable.end(), [entity](auto args) { return std::get<0>(args) == entity; });
ASSERT_EQ(std::get<0>(*it), entity);
}
TEST(OwningGroup, Functionalities) {
@@ -603,10 +650,10 @@ TEST(OwningGroup, Functionalities) {
registry.emplace<char>(e1, '2');
ASSERT_FALSE(group.empty());
ASSERT_NO_THROW(group.begin()++);
ASSERT_NO_THROW(++cgroup.begin());
ASSERT_NO_THROW([](auto it) { return it++; }(group.rbegin()));
ASSERT_NO_THROW([](auto it) { return ++it; }(cgroup.rbegin()));
ASSERT_NO_FATAL_FAILURE(group.begin()++);
ASSERT_NO_FATAL_FAILURE(++cgroup.begin());
ASSERT_NO_FATAL_FAILURE([](auto it) { return it++; }(group.rbegin()));
ASSERT_NO_FATAL_FAILURE([](auto it) { return ++it; }(cgroup.rbegin()));
ASSERT_NE(group.begin(), group.end());
ASSERT_NE(cgroup.begin(), cgroup.end());
@@ -618,12 +665,12 @@ TEST(OwningGroup, Functionalities) {
ASSERT_EQ(group.size(), 2u);
registry.remove<int>(e0);
registry.erase<int>(e0);
ASSERT_EQ(group.size(), 1u);
ASSERT_EQ(*(cgroup.raw<const int>() + 0), 42);
ASSERT_EQ(*(group.raw<int>() + 0), 42);
ASSERT_EQ(cgroup.raw<const int>()[0u][0u], 42);
ASSERT_EQ(group.raw<int>()[0u][0u], 42);
for(auto entity: group) {
ASSERT_EQ(std::get<0>(cgroup.get<const int, const char>(entity)), 42);
@@ -631,11 +678,11 @@ TEST(OwningGroup, Functionalities) {
ASSERT_EQ(cgroup.get<const char>(entity), '2');
}
ASSERT_EQ(*(group.data() + 0), e1);
ASSERT_EQ(*(group.raw<int>() + 0), 42);
ASSERT_EQ(group.data()[0u], e1);
ASSERT_EQ(group.raw<int>()[0u][0u], 42);
registry.remove<char>(e0);
registry.remove<char>(e1);
registry.erase<char>(e0);
registry.erase<char>(e1);
ASSERT_EQ(group.begin(), group.end());
ASSERT_EQ(cgroup.begin(), cgroup.end());
@@ -673,12 +720,6 @@ TEST(OwningGroup, Invalid) {
ASSERT_EQ(group.find(entity), group.end());
ASSERT_EQ(group.front(), entt::entity{entt::null});
ASSERT_EQ(group.back(), entt::entity{entt::null});
group.each([](const auto, const auto &) { FAIL(); });
group.each([](const auto &) { FAIL(); });
for([[maybe_unused]] auto all: group.each()) { FAIL(); }
for(auto first = group.each().rbegin(), last = group.each().rend(); first != last; ++first) { FAIL(); }
}
TEST(OwningGroup, ElementAccess) {
@@ -737,6 +778,7 @@ TEST(OwningGroup, Empty) {
TEST(OwningGroup, Each) {
entt::registry registry;
auto group = registry.group<int>(entt::get<char>);
auto iterable = group.each();
const auto e0 = registry.create();
registry.emplace<int>(e0, 0);
@@ -747,9 +789,10 @@ TEST(OwningGroup, Each) {
registry.emplace<char>(e1);
auto cgroup = std::as_const(registry).group_if_exists<const int>(entt::get<const char>);
auto citerable = cgroup.each();
std::size_t cnt = 0;
for(auto first = cgroup.each().rbegin(), last = cgroup.each().rend(); first != last; ++first) {
for(auto first = citerable.rbegin(), last = citerable.rend(); first != last; ++first) {
static_assert(std::is_same_v<decltype(*first), std::tuple<entt::entity, const int &, const char &>>);
ASSERT_EQ(std::get<1>(*first), cnt++);
}
@@ -762,7 +805,8 @@ TEST(OwningGroup, Each) {
cgroup.each([&cnt](const int &, const char &) { --cnt; });
cgroup.each([&cnt](auto, const int &, const char &) { --cnt; });
for(auto &&[entt, iv, cv]: group.each()) {
// do not use iterable, make sure an iterable group works when created from a temporary
for(auto [entt, iv, cv]: registry.group<int>(entt::get<char>).each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), char &>);
@@ -770,6 +814,12 @@ TEST(OwningGroup, Each) {
}
ASSERT_EQ(cnt, std::size_t{0});
auto it = iterable.begin();
auto rit = iterable.rbegin();
ASSERT_EQ((it++, ++it), iterable.end());
ASSERT_EQ((rit++, ++rit), iterable.rend());
}
TEST(OwningGroup, SortOrdered) {
@@ -795,21 +845,21 @@ TEST(OwningGroup, SortOrdered) {
return group.get<boxed_int>(lhs).value < group.get<boxed_int>(rhs).value;
});
ASSERT_EQ(*(group.data() + 0u), entities[0]);
ASSERT_EQ(*(group.data() + 1u), entities[1]);
ASSERT_EQ(*(group.data() + 2u), entities[2]);
ASSERT_EQ(*(group.data() + 3u), entities[3]);
ASSERT_EQ(*(group.data() + 4u), entities[4]);
ASSERT_EQ(group.data()[0u], entities[0]);
ASSERT_EQ(group.data()[1u], entities[1]);
ASSERT_EQ(group.data()[2u], entities[2]);
ASSERT_EQ(group.data()[3u], entities[3]);
ASSERT_EQ(group.data()[4u], entities[4]);
ASSERT_EQ((group.raw<boxed_int>() + 0u)->value, 12);
ASSERT_EQ((group.raw<boxed_int>() + 1u)->value, 9);
ASSERT_EQ((group.raw<boxed_int>() + 2u)->value, 6);
ASSERT_EQ((group.raw<boxed_int>() + 3u)->value, 1);
ASSERT_EQ((group.raw<boxed_int>() + 4u)->value, 2);
ASSERT_EQ(group.raw<boxed_int>()[0u][0u].value, 12);
ASSERT_EQ(group.raw<boxed_int>()[0u][1u].value, 9);
ASSERT_EQ(group.raw<boxed_int>()[0u][2u].value, 6);
ASSERT_EQ(group.raw<boxed_int>()[0u][3u].value, 1);
ASSERT_EQ(group.raw<boxed_int>()[0u][4u].value, 2);
ASSERT_EQ(*(group.raw<char>() + 0u), 'a');
ASSERT_EQ(*(group.raw<char>() + 1u), 'b');
ASSERT_EQ(*(group.raw<char>() + 2u), 'c');
ASSERT_EQ(group.raw<char>()[0u][0u], 'a');
ASSERT_EQ(group.raw<char>()[0u][1u], 'b');
ASSERT_EQ(group.raw<char>()[0u][2u], 'c');
ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{12}, 'a')));
ASSERT_EQ((group.get<boxed_int, char>(entities[1])), (std::make_tuple(boxed_int{9}, 'b')));
@@ -842,21 +892,21 @@ TEST(OwningGroup, SortReverse) {
return lhs.value < rhs.value;
});
ASSERT_EQ(*(group.data() + 0u), entities[2]);
ASSERT_EQ(*(group.data() + 1u), entities[1]);
ASSERT_EQ(*(group.data() + 2u), entities[0]);
ASSERT_EQ(*(group.data() + 3u), entities[3]);
ASSERT_EQ(*(group.data() + 4u), entities[4]);
ASSERT_EQ(group.data()[0u], entities[2]);
ASSERT_EQ(group.data()[1u], entities[1]);
ASSERT_EQ(group.data()[2u], entities[0]);
ASSERT_EQ(group.data()[3u], entities[3]);
ASSERT_EQ(group.data()[4u], entities[4]);
ASSERT_EQ((group.raw<boxed_int>() + 0u)->value, 12);
ASSERT_EQ((group.raw<boxed_int>() + 1u)->value, 9);
ASSERT_EQ((group.raw<boxed_int>() + 2u)->value, 6);
ASSERT_EQ((group.raw<boxed_int>() + 3u)->value, 1);
ASSERT_EQ((group.raw<boxed_int>() + 4u)->value, 2);
ASSERT_EQ(group.raw<boxed_int>()[0u][0u].value, 12);
ASSERT_EQ(group.raw<boxed_int>()[0u][1u].value, 9);
ASSERT_EQ(group.raw<boxed_int>()[0u][2u].value, 6);
ASSERT_EQ(group.raw<boxed_int>()[0u][3u].value, 1);
ASSERT_EQ(group.raw<boxed_int>()[0u][4u].value, 2);
ASSERT_EQ(*(group.raw<char>() + 0u), 'c');
ASSERT_EQ(*(group.raw<char>() + 1u), 'b');
ASSERT_EQ(*(group.raw<char>() + 2u), 'a');
ASSERT_EQ(group.raw<char>()[0u][0u], 'c');
ASSERT_EQ(group.raw<char>()[0u][1u], 'b');
ASSERT_EQ(group.raw<char>()[0u][2u], 'a');
ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{6}, 'a')));
ASSERT_EQ((group.get<boxed_int, char>(entities[1])), (std::make_tuple(boxed_int{9}, 'b')));
@@ -891,31 +941,33 @@ TEST(OwningGroup, SortUnordered) {
registry.emplace<boxed_int>(entities[5], 4);
registry.emplace<boxed_int>(entities[6], 5);
group.sort<char>([](const auto lhs, const auto rhs) {
return lhs < rhs;
group.sort<boxed_int, char>([](const auto lhs, const auto rhs) {
static_assert(std::is_same_v<decltype(std::get<0>(lhs)), boxed_int &>);
static_assert(std::is_same_v<decltype(std::get<1>(rhs)), char &>);
return std::get<1>(lhs) < std::get<1>(rhs);
});
ASSERT_EQ(*(group.data() + 0u), entities[4]);
ASSERT_EQ(*(group.data() + 1u), entities[3]);
ASSERT_EQ(*(group.data() + 2u), entities[0]);
ASSERT_EQ(*(group.data() + 3u), entities[1]);
ASSERT_EQ(*(group.data() + 4u), entities[2]);
ASSERT_EQ(*(group.data() + 5u), entities[5]);
ASSERT_EQ(*(group.data() + 6u), entities[6]);
ASSERT_EQ(group.data()[0u], entities[4]);
ASSERT_EQ(group.data()[1u], entities[3]);
ASSERT_EQ(group.data()[2u], entities[0]);
ASSERT_EQ(group.data()[3u], entities[1]);
ASSERT_EQ(group.data()[4u], entities[2]);
ASSERT_EQ(group.data()[5u], entities[5]);
ASSERT_EQ(group.data()[6u], entities[6]);
ASSERT_EQ((group.raw<boxed_int>() + 0u)->value, 12);
ASSERT_EQ((group.raw<boxed_int>() + 1u)->value, 9);
ASSERT_EQ((group.raw<boxed_int>() + 2u)->value, 6);
ASSERT_EQ((group.raw<boxed_int>() + 3u)->value, 3);
ASSERT_EQ((group.raw<boxed_int>() + 4u)->value, 1);
ASSERT_EQ((group.raw<boxed_int>() + 5u)->value, 4);
ASSERT_EQ((group.raw<boxed_int>() + 6u)->value, 5);
ASSERT_EQ(group.raw<boxed_int>()[0u][0u].value, 12);
ASSERT_EQ(group.raw<boxed_int>()[0u][1u].value, 9);
ASSERT_EQ(group.raw<boxed_int>()[0u][2u].value, 6);
ASSERT_EQ(group.raw<boxed_int>()[0u][3u].value, 3);
ASSERT_EQ(group.raw<boxed_int>()[0u][4u].value, 1);
ASSERT_EQ(group.raw<boxed_int>()[0u][5u].value, 4);
ASSERT_EQ(group.raw<boxed_int>()[0u][6u].value, 5);
ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{6}, 'c')));
ASSERT_EQ((group.get<boxed_int, char>(entities[1])), (std::make_tuple(boxed_int{3}, 'b')));
ASSERT_EQ((group.get<boxed_int, char>(entities[2])), (std::make_tuple(boxed_int{1}, 'a')));
ASSERT_EQ((group.get<boxed_int, char>(entities[3])), (std::make_tuple(boxed_int{9}, 'd')));
ASSERT_EQ((group.get<boxed_int, char>(entities[4])), (std::make_tuple(boxed_int{12}, 'e')));
ASSERT_EQ(group.get<char>(group.data()[0u]), 'e');
ASSERT_EQ(group.get<char>(group.data()[1u]), 'd');
ASSERT_EQ(group.get<char>(group.data()[2u]), 'c');
ASSERT_EQ(group.get<char>(group.data()[3u]), 'b');
ASSERT_EQ(group.get<char>(group.data()[4u]), 'a');
ASSERT_FALSE(group.contains(entities[5]));
ASSERT_FALSE(group.contains(entities[6]));
@@ -940,15 +992,15 @@ TEST(OwningGroup, SortWithExclusionList) {
return lhs < rhs;
});
ASSERT_EQ(*(group.data() + 0u), entities[4]);
ASSERT_EQ(*(group.data() + 1u), entities[3]);
ASSERT_EQ(*(group.data() + 2u), entities[1]);
ASSERT_EQ(*(group.data() + 3u), entities[0]);
ASSERT_EQ(group.data()[0u], entities[4]);
ASSERT_EQ(group.data()[1u], entities[3]);
ASSERT_EQ(group.data()[2u], entities[1]);
ASSERT_EQ(group.data()[3u], entities[0]);
ASSERT_EQ((group.raw<boxed_int>() + 0u)->value, 4);
ASSERT_EQ((group.raw<boxed_int>() + 1u)->value, 3);
ASSERT_EQ((group.raw<boxed_int>() + 2u)->value, 1);
ASSERT_EQ((group.raw<boxed_int>() + 3u)->value, 0);
ASSERT_EQ(group.raw<boxed_int>()[0u][0u].value, 4);
ASSERT_EQ(group.raw<boxed_int>()[0u][1u].value, 3);
ASSERT_EQ(group.raw<boxed_int>()[0u][2u].value, 1);
ASSERT_EQ(group.raw<boxed_int>()[0u][3u].value, 0);
ASSERT_EQ(group.get<boxed_int>(entities[0]).value, 0);
ASSERT_EQ(group.get<boxed_int>(entities[1]).value, 1);
@@ -1014,8 +1066,12 @@ TEST(OwningGroup, ConstNonConstAndAllInBetween) {
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({})), std::tuple<int &, const char &, double &, const float &>>);
static_assert(std::is_same_v<decltype(group.data()), const entt::entity *>);
static_assert(std::is_same_v<decltype(group.raw<const char>()), const char *>);
static_assert(std::is_same_v<decltype(group.raw<int>()), int *>);
static_assert(std::is_same_v<decltype(group.raw<const char>()), const char * const *>);
static_assert(std::is_same_v<decltype(group.raw<int>()), int **>);
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<const 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<const char>)), decltype(std::as_const(registry).group_if_exists<const int>(entt::get<const char>))>);
group.each([](auto &&i, auto &&c, auto &&d, auto &&f) {
static_assert(std::is_same_v<decltype(i), int &>);
@@ -1024,7 +1080,7 @@ TEST(OwningGroup, ConstNonConstAndAllInBetween) {
static_assert(std::is_same_v<decltype(f), const float &>);
});
for(auto &&[entt, iv, cv, dv, fv]: group.each()) {
for(auto [entt, iv, cv, dv, fv]: group.each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), const char &>);
@@ -1053,7 +1109,7 @@ TEST(OwningGroup, Find) {
registry.emplace<int>(e3);
registry.emplace<char>(e3);
registry.remove<int>(e1);
registry.erase<int>(e1);
ASSERT_NE(group.find(e0), group.end());
ASSERT_EQ(group.find(e1), group.end());
@@ -1112,8 +1168,8 @@ TEST(OwningGroup, ExcludedComponents) {
ASSERT_TRUE(group.empty());
registry.remove<char>(e1);
registry.remove<double>(e3);
registry.erase<char>(e1);
registry.erase<double>(e3);
for(const auto entity: group) {
ASSERT_TRUE(entity == e1 || entity == e3);
@@ -1148,7 +1204,7 @@ TEST(OwningGroup, EmptyAndNonEmptyTypes) {
ASSERT_TRUE(entity == e0 || entity == e1);
});
for(auto &&[entt, iv]: group.each()) {
for(auto [entt, iv]: group.each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>);
ASSERT_TRUE(entt == e0 || entt == e1);
@@ -1169,7 +1225,7 @@ TEST(OwningGroup, TrackEntitiesOnComponentDestruction) {
ASSERT_TRUE(group.empty());
ASSERT_TRUE(cgroup.empty());
registry.remove<char>(entity);
registry.erase<char>(entity);
ASSERT_FALSE(group.empty());
ASSERT_FALSE(cgroup.empty());
@@ -1187,7 +1243,7 @@ TEST(OwningGroup, EmptyTypes) {
ASSERT_EQ(entity, entt);
});
for(auto &&[entt, iv, cv]: registry.group<int>(entt::get<char, empty_type>).each()) {
for(auto [entt, iv, cv]: registry.group<int>(entt::get<char, empty_type>).each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), char &>);
@@ -1199,7 +1255,7 @@ TEST(OwningGroup, EmptyTypes) {
check = false;
});
for(auto &&[entt, cv, iv]: registry.group<char>(entt::get<empty_type, int>).each()) {
for(auto [entt, cv, iv]: registry.group<char>(entt::get<empty_type, int>).each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(cv), char &>);
static_assert(std::is_same_v<decltype(iv), int &>);
@@ -1210,15 +1266,16 @@ TEST(OwningGroup, EmptyTypes) {
ASSERT_EQ(entity, entt);
});
for(auto &&[entt, iv, cv]: registry.group<empty_type>(entt::get<int, char>).each()) {
for(auto [entt, iv, cv]: registry.group<empty_type>(entt::get<int, char>).each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), char &>);
ASSERT_EQ(entity, entt);
}
registry.group<double>(entt::get<int, char>).each([](const auto, double, int, char) { FAIL(); });
ASSERT_EQ(registry.group<double>(entt::get<int, char>).each().begin(), registry.group<double>(entt::get<int, char>).each().end());
auto iterable = registry.group<double>(entt::get<int, char>).each();
ASSERT_EQ(iterable.begin(), iterable.end());
}
TEST(OwningGroup, FrontBack) {
@@ -1284,9 +1341,53 @@ TEST(OwningGroup, PreventEarlyOptOut) {
});
}
TEST(OwningGroup, SwappingValuesIsAllowed) {
entt::registry registry;
const auto group = registry.group<boxed_int>(entt::get<empty_type>);
for(std::size_t i{}; i < 2u; ++i) {
const auto entity = registry.create();
registry.emplace<boxed_int>(entity, static_cast<int>(i));
registry.emplace<empty_type>(entity);
}
registry.destroy(group.back());
// thanks to @andranik3949 for pointing out this missing test
registry.view<const boxed_int>().each([](const auto entity, const auto &value) {
ASSERT_EQ(entt::to_integral(entity), value.value);
});
}
TEST(OwningGroup, ExtendedGet) {
using type = decltype(std::declval<entt::registry>().group<int, empty_type>(entt::get<char>).get({}));
static_assert(std::tuple_size_v<type> == 2u);
static_assert(std::is_same_v<std::tuple_element_t<0, type>, int &>);
static_assert(std::is_same_v<std::tuple_element_t<1, type>, char &>);
entt::registry registry;
const auto entity = registry.create();
registry.emplace<int>(entity, 42);
registry.emplace<char>(entity, 'c');
const auto tup = registry.group<int>(entt::get<char>).get(entity);
ASSERT_EQ(std::get<0>(tup), 42);
ASSERT_EQ(std::get<1>(tup), 'c');
}
TEST(OwningGroup, IterableGroupAlgorithmCompatibility) {
entt::registry registry;
const auto entity = registry.create();
registry.emplace<int>(entity);
registry.emplace<char>(entity);
const auto group = registry.group<int>(entt::get<char>);
const auto iterable = group.each();
const auto it = std::find_if(iterable.begin(), iterable.end(), [entity](auto args) { return std::get<0>(args) == entity; });
ASSERT_EQ(std::get<0>(*it), entity);
}

View File

@@ -157,21 +157,22 @@ TEST(BasicHandle, Component) {
ASSERT_EQ(42, patched);
ASSERT_EQ('a', handle.replace<char>('a'));
ASSERT_TRUE((handle.has<int, char, double>()));
ASSERT_TRUE((handle.all_of<int, char, double>()));
ASSERT_EQ((std::make_tuple(42, 'a', .3)), (handle.get<int, char, double>()));
handle.remove<char, double>();
handle.erase<char, double>();
ASSERT_TRUE((registry.empty<char, double>()));
ASSERT_EQ(0u, (handle.remove_if_exists<char, double>()));
ASSERT_EQ(0u, (handle.remove<char, double>()));
handle.visit([](auto info) { ASSERT_EQ(entt::type_hash<int>::value(), info.hash()); });
handle.visit([](auto info) { ASSERT_EQ(entt::type_id<int>(), info); });
ASSERT_TRUE((handle.any<int, char, double>()));
ASSERT_FALSE((handle.has<int, char, double>()));
ASSERT_TRUE((handle.any_of<int, char, double>()));
ASSERT_FALSE((handle.all_of<int, char, double>()));
ASSERT_FALSE(handle.orphan());
handle.remove<int>();
ASSERT_EQ(1u, (handle.remove<int>()));
ASSERT_DEATH(handle.erase<int>(), "");
ASSERT_TRUE(registry.empty<int>());
ASSERT_TRUE(handle.orphan());
@@ -185,20 +186,6 @@ TEST(BasicHandle, Component) {
ASSERT_EQ(nullptr, std::get<1>(handle.try_get<int, char, double>()));
}
TEST(BasicHandle, RemoveAll) {
entt::registry registry;
const auto entity = registry.create();
entt::handle handle{registry, entity};
ASSERT_EQ(3, handle.emplace<int>(3));
ASSERT_EQ('c', handle.emplace_or_replace<char>('c'));
ASSERT_TRUE((handle.has<int, char>()));
handle.remove_all();
ASSERT_FALSE((handle.any<int, char>()));
}
TEST(BasicHandle, FromEntity) {
entt::registry registry;
const auto entity = registry.create();
@@ -210,7 +197,7 @@ TEST(BasicHandle, FromEntity) {
ASSERT_TRUE(handle);
ASSERT_EQ(entity, handle.entity());
ASSERT_TRUE((handle.has<int, char>()));
ASSERT_TRUE((handle.all_of<int, char>()));
ASSERT_EQ(handle.get<int>(), 42);
ASSERT_EQ(handle.get<char>(), 'c');
}

View File

@@ -41,19 +41,39 @@ TEST(Helper, Invoke) {
TEST(Helper, ToEntity) {
entt::registry registry;
const auto entity = registry.create();
const auto other = registry.create();
const entt::entity null = entt::null;
const int value = 42;
ASSERT_EQ(entt::to_entity(registry, 42), null);
ASSERT_EQ(entt::to_entity(registry, value), null);
const auto entity = registry.create();
registry.emplace<int>(entity);
while(registry.size<int>() < (ENTT_PACKED_PAGE - 1u)) {
registry.emplace<int>(registry.create(), value);
}
const auto other = registry.create();
const auto next = registry.create();
registry.emplace<int>(other);
registry.emplace<char>(other);
registry.emplace<int>(next);
ASSERT_EQ(entt::to_entity(registry, registry.get<int>(entity)), entity);
ASSERT_EQ(entt::to_entity(registry, registry.get<int>(other)), other);
ASSERT_EQ(entt::to_entity(registry, registry.get<char>(other)), other);
ASSERT_EQ(entt::to_entity(registry, registry.get<int>(next)), next);
registry.destroy(entity);
ASSERT_EQ(&registry.get<int>(entity) + ENTT_PACKED_PAGE - 1u, &registry.get<int>(other));
ASSERT_NE(&registry.get<int>(entity) + ENTT_PACKED_PAGE, &registry.get<int>(next));
ASSERT_EQ(entt::to_entity(registry, registry.get<int>(other)), other);
ASSERT_EQ(entt::to_entity(registry, registry.get<char>(other)), other);
registry.destroy(other);
ASSERT_EQ(entt::to_entity(registry, registry.get<int>(entity)), entity);
ASSERT_EQ(entt::to_entity(registry, registry.get<int>(next)), next);
ASSERT_EQ(&registry.get<int>(entity) + ENTT_PACKED_PAGE - 1u, &registry.get<int>(next));
ASSERT_EQ(entt::to_entity(registry, 42), null);
ASSERT_EQ(entt::to_entity(registry, value), null);
}

View File

@@ -11,7 +11,6 @@ TEST(Observer, Functionalities) {
ASSERT_EQ(observer.size(), 0u);
ASSERT_TRUE(observer.empty());
ASSERT_EQ(observer.data(), nullptr);
ASSERT_EQ(observer.begin(), observer.end());
const auto entity = registry.create();
@@ -19,7 +18,6 @@ TEST(Observer, Functionalities) {
ASSERT_EQ(observer.size(), 1u);
ASSERT_FALSE(observer.empty());
ASSERT_NE(observer.data(), nullptr);
ASSERT_EQ(*observer.data(), entity);
ASSERT_NE(observer.begin(), observer.end());
ASSERT_EQ(++observer.begin(), observer.end());
@@ -31,7 +29,7 @@ TEST(Observer, Functionalities) {
ASSERT_TRUE(observer.empty());
observer.disconnect();
registry.remove<int>(entity);
registry.erase<int>(entity);
registry.emplace<int>(entity);
ASSERT_EQ(observer.size(), 0u);
@@ -60,7 +58,7 @@ TEST(Observer, AllOf) {
ASSERT_FALSE(observer.empty());
registry.remove<int>(entity);
registry.erase<int>(entity);
ASSERT_TRUE(observer.empty());
@@ -69,7 +67,7 @@ TEST(Observer, AllOf) {
ASSERT_FALSE(observer.empty());
registry.remove<double>(entity);
registry.erase<double>(entity);
ASSERT_TRUE(observer.empty());
@@ -81,7 +79,7 @@ TEST(Observer, AllOf) {
observer.disconnect();
registry.emplace_or_replace<int>(entity);
registry.emplace_or_replace<char>(entity);
registry.remove_if_exists<float>(entity);
registry.erase<float>(entity);
ASSERT_TRUE(observer.empty());
}
@@ -100,17 +98,16 @@ TEST(Observer, AllOfFiltered) {
ASSERT_EQ(observer.size(), 0u);
ASSERT_TRUE(observer.empty());
ASSERT_EQ(observer.data(), nullptr);
registry.remove<int>(entity);
registry.erase<int>(entity);
registry.emplace<char>(entity);
registry.emplace<double>(entity);
registry.emplace<int>(entity);
ASSERT_TRUE(observer.empty());
registry.remove<int>(entity);
registry.remove<double>(entity);
registry.erase<int>(entity);
registry.erase<double>(entity);
registry.emplace<int>(entity);
ASSERT_EQ(observer.size(), 1u);
@@ -121,12 +118,12 @@ TEST(Observer, AllOfFiltered) {
ASSERT_TRUE(observer.empty());
registry.remove<double>(entity);
registry.erase<double>(entity);
ASSERT_TRUE(observer.empty());
observer.disconnect();
registry.remove<int>(entity);
registry.erase<int>(entity);
registry.emplace<int>(entity);
ASSERT_TRUE(observer.empty());
@@ -181,7 +178,6 @@ TEST(Observer, ObserveFiltered) {
ASSERT_EQ(observer.size(), 0u);
ASSERT_TRUE(observer.empty());
ASSERT_EQ(observer.data(), nullptr);
registry.emplace<char>(entity);
registry.emplace<double>(entity);
@@ -189,7 +185,7 @@ TEST(Observer, ObserveFiltered) {
ASSERT_TRUE(observer.empty());
registry.remove<double>(entity);
registry.erase<double>(entity);
registry.replace<int>(entity);
ASSERT_EQ(observer.size(), 1u);
@@ -200,7 +196,7 @@ TEST(Observer, ObserveFiltered) {
ASSERT_TRUE(observer.empty());
registry.remove<double>(entity);
registry.erase<double>(entity);
ASSERT_TRUE(observer.empty());
@@ -222,13 +218,13 @@ TEST(Observer, AllOfObserve) {
registry.emplace<int>(entity);
registry.emplace<char>(entity);
registry.replace<char>(entity);
registry.remove<int>(entity);
registry.erase<int>(entity);
ASSERT_EQ(observer.size(), 1u);
ASSERT_FALSE(observer.empty());
ASSERT_EQ(*observer.data(), entity);
registry.remove<char>(entity);
registry.erase<char>(entity);
registry.emplace<char>(entity);
ASSERT_TRUE(observer.empty());
@@ -256,7 +252,7 @@ TEST(Observer, CrossRulesCornerCase) {
ASSERT_TRUE(observer.empty());
registry.emplace<char>(entity);
registry.remove<int>(entity);
registry.erase<int>(entity);
ASSERT_FALSE(observer.empty());
}
@@ -323,7 +319,7 @@ TEST(Observer, MultipleFilters) {
ASSERT_FALSE(observer.empty());
ASSERT_EQ(*observer.data(), entity);
registry.remove<float>(entity);
registry.erase<float>(entity);
ASSERT_TRUE(observer.empty());
@@ -362,7 +358,7 @@ TEST(Observer, GroupCornerCase) {
ASSERT_FALSE(remove_observer.empty());
remove_observer.clear();
registry.remove<char>(entity);
registry.erase<char>(entity);
ASSERT_FALSE(add_observer.empty());
ASSERT_TRUE(remove_observer.empty());

View File

@@ -71,7 +71,7 @@ TEST(Organizer, EmplaceFreeFunction) {
ASSERT_EQ(graph[2u].children()[0u], 3u);
for(auto &&vertex: graph) {
ASSERT_NO_THROW(vertex.callback()(vertex.data(), registry));
ASSERT_NO_FATAL_FAILURE(vertex.callback()(vertex.data(), registry));
}
organizer.clear();
@@ -127,7 +127,7 @@ TEST(Organizer, EmplaceMemberFunction) {
ASSERT_EQ(graph[2u].children()[0u], 3u);
for(auto &&vertex: graph) {
ASSERT_NO_THROW(vertex.callback()(vertex.data(), registry));
ASSERT_NO_FATAL_FAILURE(vertex.callback()(vertex.data(), registry));
}
organizer.clear();
@@ -140,8 +140,6 @@ TEST(Organizer, EmplaceFreeFunctionWithPayload) {
entt::registry registry;
clazz instance;
// TODO
organizer.emplace<&clazz::ro_int_char_double>(instance, "t1");
organizer.emplace<&clazz::ro_int_with_payload>(instance, "t2");
organizer.emplace<&clazz::ro_char_with_payload, const clazz>(instance, "t3");
@@ -193,7 +191,7 @@ TEST(Organizer, EmplaceFreeFunctionWithPayload) {
ASSERT_EQ(graph[3u].children()[0u], 4u);
for(auto &&vertex: graph) {
ASSERT_NO_THROW(vertex.callback()(vertex.data(), registry));
ASSERT_NO_FATAL_FAILURE(vertex.callback()(vertex.data(), registry));
}
organizer.clear();
@@ -266,7 +264,7 @@ TEST(Organizer, EmplaceDirectFunction) {
ASSERT_EQ(graph[2u].children()[0u], 3u);
for(auto &&vertex: graph) {
ASSERT_NO_THROW(vertex.callback()(vertex.data(), registry));
ASSERT_NO_FATAL_FAILURE(vertex.callback()(vertex.data(), registry));
}
organizer.clear();
@@ -319,7 +317,7 @@ TEST(Organizer, SyncPoint) {
ASSERT_EQ(graph[4u].children()[0u], 5u);
for(auto &&vertex: graph) {
ASSERT_NO_THROW(vertex.callback()(vertex.data(), registry));
ASSERT_NO_FATAL_FAILURE(vertex.callback()(vertex.data(), registry));
}
}

View File

@@ -11,12 +11,10 @@ template<typename Entity>
struct PolyStorage: entt::type_list_cat_t<
decltype(as_type_list(std::declval<entt::Storage<Entity>>())),
entt::type_list<
void(const Entity *, const Entity *, void *),
void(entt::basic_registry<Entity> &, const Entity, const void *),
const void *(const Entity) const,
const Entity *() const,
const void *() const,
std::size_t() const,
void(entt::basic_registry<Entity> &, const Entity *, const void *, const std::size_t)
void(entt::basic_registry<Entity> &) const
>
> {
using entity_type = Entity;
@@ -24,36 +22,28 @@ struct PolyStorage: entt::type_list_cat_t<
template<typename Base>
struct type: entt::Storage<Entity>::template type<Base> {
static constexpr auto base = std::tuple_size_v<typename entt::poly_vtable<entt::Storage<Entity>>::type>;
static constexpr auto base = decltype(as_type_list(std::declval<entt::Storage<Entity>>()))::size;
void emplace(entt::basic_registry<entity_type> &owner, const entity_type entity, const void *instance) {
entt::poly_call<base + 0>(*this, owner, entity, instance);
void erase(entt::basic_registry<Entity> &owner, const entity_type *first, const entity_type *last) {
entt::poly_call<base + 0>(*this, first, last, &owner);
}
void emplace(entt::basic_registry<Entity> &owner, const entity_type entity, const void *instance) {
entt::poly_call<base + 1>(*this, owner, entity, instance);
}
const void * get(const entity_type entity) const {
return entt::poly_call<base + 1>(*this, entity);
return entt::poly_call<base + 2>(*this, entity);
}
const entity_type * data() const {
return entt::poly_call<base + 2>(*this);
}
const void * raw() const {
return entt::poly_call<base + 3>(*this);
}
size_type size() const {
return entt::poly_call<base + 4>(*this);
}
void insert(entt::basic_registry<Entity> &owner, const Entity *entity, const void *instance, const std::size_t length) {
entt::poly_call<base + 5>(*this, owner, entity, instance, length);
void copy_to(entt::basic_registry<Entity> &other) const {
entt::poly_call<base + 3>(*this, other);
}
};
template<typename Type>
struct members {
static void emplace(Type &self, entt::basic_registry<entity_type> &owner, const entity_type entity, const void *instance) {
static void emplace(Type &self, entt::basic_registry<Entity> &owner, const entity_type entity, const void *instance) {
self.emplace(owner, entity, *static_cast<const typename Type::value_type *>(instance));
}
@@ -61,9 +51,9 @@ struct PolyStorage: entt::type_list_cat_t<
return &self.get(entity);
}
static void insert(Type &self, entt::basic_registry<entity_type> &owner, const entity_type *entity, const void *instance, const size_type length) {
const auto *value = static_cast<const typename Type::value_type *>(instance);
self.insert(owner, entity, entity + length, value, value + length);
static void copy_to(const Type &self, entt::basic_registry<entity_type> &other) {
const entt::sparse_set &base = self;
other.template insert<typename Type::value_type>(base.rbegin(), base.rend(), self.rbegin());
}
};
@@ -71,12 +61,10 @@ struct PolyStorage: entt::type_list_cat_t<
using impl = entt::value_list_cat_t<
typename entt::Storage<Entity>::template impl<Type>,
entt::value_list<
&Type::template erase<const entity_type *>,
&members<Type>::emplace,
&members<Type>::get,
&Type::data,
entt::overload<const typename Type::value_type *() const ENTT_NOEXCEPT>(&Type::raw),
&Type::size,
&members<Type>::insert
&members<Type>::copy_to
>
>;
};
@@ -94,16 +82,16 @@ TEST(PolyStorage, CopyEntity) {
registry.emplace<int>(entity, 42);
registry.emplace<char>(entity, 'c');
ASSERT_TRUE((registry.has<int, char>(entity)));
ASSERT_FALSE((registry.any<int, char>(other)));
ASSERT_TRUE((registry.all_of<int, char>(entity)));
ASSERT_FALSE((registry.any_of<int, char>(other)));
registry.visit(entity, [&](const auto info) {
auto storage = registry.storage(info);
auto &&storage = registry.storage(info);
storage->emplace(registry, other, storage->get(entity));
});
ASSERT_TRUE((registry.has<int, char>(entity)));
ASSERT_TRUE((registry.has<int, char>(other)));
ASSERT_TRUE((registry.all_of<int, char>(entity)));
ASSERT_TRUE((registry.all_of<int, char>(other)));
ASSERT_EQ(registry.get<int>(entity), registry.get<int>(other));
ASSERT_EQ(registry.get<char>(entity), registry.get<char>(other));
@@ -118,20 +106,19 @@ TEST(PolyStorage, CopyRegistry) {
registry.insert<int>(std::begin(entities), std::end(entities), 42);
registry.insert<char>(std::begin(entities), std::end(entities), 'c');
other.prepare<int>();
other.prepare<char>();
ASSERT_EQ(registry.size(), 10u);
ASSERT_EQ(other.size(), 0u);
other.assign(registry.data(), registry.data() + registry.size(), registry.destroyed());
registry.visit([&](const auto info) {
auto storage = registry.storage(info);
other.storage(info)->insert(other, storage->data(), storage->raw(), storage->size());
});
other.assign(registry.data(), registry.data() + registry.size(), registry.released());
registry.visit([&](const auto info) { std::as_const(registry).storage(info)->copy_to(other); });
ASSERT_EQ(registry.size(), other.size());
ASSERT_EQ((registry.view<int, char>().size_hint()), (other.view<int, char>().size_hint()));
ASSERT_NE((other.view<int, char>().size_hint()), 0u);
for(const auto entity: registry.view<int, char>()) {
ASSERT_EQ((registry.get<int, char>(entity)), (other.get<int, char>(entity)));
}
}
TEST(PolyStorage, Constness) {
@@ -142,13 +129,14 @@ TEST(PolyStorage, Constness) {
entity[0] = registry.create();
registry.emplace<int>(entity[0], 42);
// cannot invoke erase on a const storage, let's copy the returned value
auto cstorage = cregistry.storage(entt::type_id<int>());
ASSERT_DEATH(cstorage->remove(registry, std::begin(entity), std::end(entity)), ".*");
ASSERT_TRUE(registry.has<int>(entity[0]));
ASSERT_DEATH(cstorage->erase(registry, std::begin(entity), std::end(entity)), "");
ASSERT_TRUE(registry.all_of<int>(entity[0]));
auto storage = registry.storage(entt::type_id<int>());
storage->remove(registry, std::begin(entity), std::end(entity));
auto &&storage = registry.storage(entt::type_id<int>());
storage->erase(registry, std::begin(entity), std::end(entity));
ASSERT_FALSE(registry.has<int>(entity[0]));
ASSERT_FALSE(registry.all_of<int>(entity[0]));
}

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,7 @@ TEST(Registry, NoEto) {
registry.emplace<empty_type>(entity);
registry.emplace<int>(entity, 42);
ASSERT_NE(registry.raw<empty_type>(), nullptr);
ASSERT_NE(registry.view<empty_type>().raw(), nullptr);
ASSERT_NE(registry.try_get<empty_type>(entity), nullptr);
ASSERT_EQ(registry.view<empty_type>().get(entity), std::as_const(registry).view<const empty_type>().get(entity));

View File

@@ -2,9 +2,17 @@
#include <algorithm>
#include <gtest/gtest.h>
#include <entt/core/type_info.hpp>
#include <entt/entity/component.hpp>
#include <entt/entity/registry.hpp>
#include <entt/entity/runtime_view.hpp>
struct stable_type { int value; };
template<>
struct entt::component_traits<stable_type>: basic_component_traits {
using in_place_delete = std::true_type;
};
TEST(RuntimeView, Functionalities) {
entt::registry registry;
@@ -32,8 +40,8 @@ TEST(RuntimeView, Functionalities) {
ASSERT_EQ(*it, e1);
ASSERT_EQ(++it, (view.end()));
ASSERT_NO_THROW((view.begin()++));
ASSERT_NO_THROW((++view.begin()));
ASSERT_NO_FATAL_FAILURE((view.begin()++));
ASSERT_NO_FATAL_FAILURE((++view.begin()));
ASSERT_NE(view.begin(), view.end());
ASSERT_EQ(view.size_hint(), 1u);
@@ -224,3 +232,43 @@ TEST(RuntimeView, ExcludedComponents) {
ASSERT_EQ(e0, entity);
});
}
TEST(RuntimeView, StableType) {
entt::registry registry;
const auto e0 = registry.create();
const auto e1 = registry.create();
const auto e2 = registry.create();
registry.emplace<int>(e0);
registry.emplace<int>(e1);
registry.emplace<int>(e2);
registry.emplace<stable_type>(e0);
registry.emplace<stable_type>(e1);
registry.remove<stable_type>(e1);
entt::id_type components[] = { entt::type_hash<int>::value(), entt::type_hash<stable_type>::value() };
auto view = registry.runtime_view(std::begin(components), std::end(components));
ASSERT_EQ(view.size_hint(), 2u);
ASSERT_TRUE(view.contains(e0));
ASSERT_FALSE(view.contains(e1));
ASSERT_EQ(*view.begin(), e0);
ASSERT_EQ(++view.begin(), view.end());
view.each([e0](const auto entt) {
ASSERT_EQ(e0, entt);
});
for(auto entt: view) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
ASSERT_EQ(e0, entt);
}
registry.compact();
ASSERT_EQ(view.size_hint(), 1u);
}

View File

@@ -144,7 +144,7 @@ TEST(Snapshot, Dump) {
ASSERT_EQ(registry.current(e1), v1);
ASSERT_EQ(registry.get<int>(e2), 3);
ASSERT_EQ(registry.get<char>(e3), '0');
ASSERT_TRUE(registry.has<a_component>(e3));
ASSERT_TRUE(registry.all_of<a_component>(e3));
ASSERT_TRUE(registry.empty<another_component>());
}
@@ -199,7 +199,7 @@ TEST(Snapshot, Partial) {
ASSERT_EQ(registry.get<int>(e0), 42);
ASSERT_EQ(registry.get<char>(e0), 'c');
ASSERT_FALSE(registry.has<double>(e0));
ASSERT_FALSE(registry.all_of<double>(e0));
ASSERT_EQ(registry.current(e1), v1);
ASSERT_EQ(registry.get<int>(e2), 3);
ASSERT_EQ(registry.get<char>(e3), '0');
@@ -286,7 +286,7 @@ TEST(Snapshot, Continuous) {
input_archive<storage_type> input{storage};
for(int i = 0; i < 10; ++i) {
src.create();
static_cast<void>(src.create());
}
src.clear();
@@ -342,7 +342,7 @@ TEST(Snapshot, Continuous) {
decltype(dst.size()) noncopyable_component_cnt{};
dst.each([&dst, &a_component_cnt](auto entt) {
ASSERT_TRUE(dst.has<a_component>(entt));
ASSERT_TRUE(dst.all_of<a_component>(entt));
++a_component_cnt;
});
@@ -549,8 +549,8 @@ TEST(Snapshot, SyncDataMembers) {
output_archive<storage_type> output{storage};
input_archive<storage_type> input{storage};
src.create();
src.create();
static_cast<void>(src.create());
static_cast<void>(src.create());
src.clear();
@@ -582,8 +582,8 @@ TEST(Snapshot, SyncDataMembers) {
ASSERT_FALSE(dst.valid(parent));
ASSERT_FALSE(dst.valid(child));
ASSERT_TRUE(dst.has<what_a_component>(loader.map(parent)));
ASSERT_TRUE(dst.has<what_a_component>(loader.map(child)));
ASSERT_TRUE(dst.all_of<what_a_component>(loader.map(parent)));
ASSERT_TRUE(dst.all_of<what_a_component>(loader.map(child)));
ASSERT_EQ(dst.get<what_a_component>(loader.map(parent)).bar, static_cast<entt::entity>(entt::null));

View File

@@ -5,8 +5,10 @@
#include <functional>
#include <type_traits>
#include <gtest/gtest.h>
#include <entt/entity/entity.hpp>
#include <entt/entity/sparse_set.hpp>
#include <entt/entity/fwd.hpp>
#include "throwing_allocator.hpp"
struct empty_type {};
struct boxed_int { int value; };
@@ -24,9 +26,12 @@ TEST(SparseSet, Functionalities) {
ASSERT_FALSE(set.contains(entt::entity{0}));
ASSERT_FALSE(set.contains(entt::entity{42}));
set.emplace(entt::entity{42});
set.reserve(0);
ASSERT_EQ(set.index(entt::entity{42}), 0u);
ASSERT_EQ(set.capacity(), 42u);
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.emplace(entt::entity{42}), 0u);
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 1u);
@@ -35,8 +40,11 @@ TEST(SparseSet, Functionalities) {
ASSERT_FALSE(set.contains(entt::entity{0}));
ASSERT_TRUE(set.contains(entt::entity{42}));
ASSERT_EQ(set.index(entt::entity{42}), 0u);
ASSERT_EQ(set.at(0u), entt::entity{42});
ASSERT_EQ(set.at(1u), static_cast<entt::entity>(entt::null));
ASSERT_EQ(set[0u], entt::entity{42});
set.remove(entt::entity{42});
set.erase(entt::entity{42});
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u);
@@ -44,83 +52,175 @@ TEST(SparseSet, Functionalities) {
ASSERT_EQ(set.begin(), set.end());
ASSERT_FALSE(set.contains(entt::entity{0}));
ASSERT_FALSE(set.contains(entt::entity{42}));
ASSERT_EQ(set.at(0u), static_cast<entt::entity>(entt::null));
ASSERT_EQ(set.at(1u), static_cast<entt::entity>(entt::null));
set.emplace(entt::entity{42});
ASSERT_EQ(set.emplace(entt::entity{42}), 0u);
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.index(entt::entity{42}), 0u);
ASSERT_EQ(set.at(0u), entt::entity{42});
ASSERT_EQ(set.at(1u), static_cast<entt::entity>(entt::null));
ASSERT_EQ(set[0u], entt::entity{42});
set.clear();
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u);
ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end());
ASSERT_EQ(set.begin(), set.end());
ASSERT_FALSE(set.contains(entt::entity{0}));
ASSERT_FALSE(set.contains(entt::entity{42}));
}
TEST(SparseSet, Contains) {
entt::sparse_set set{entt::deletion_policy::in_place};
set.emplace(entt::entity{0});
set.emplace(entt::entity{3});
set.emplace(entt::entity{42});
set.emplace(entt::entity{99});
set.emplace(entt::entity{1});
ASSERT_TRUE(set.contains(entt::entity{0}));
ASSERT_TRUE(set.contains(entt::entity{3}));
ASSERT_TRUE(set.contains(entt::entity{42}));
ASSERT_TRUE(set.contains(entt::entity{99}));
ASSERT_TRUE(set.contains(entt::entity{1}));
set.erase(entt::entity{0});
set.erase(entt::entity{3});
set.remove(entt::entity{42});
set.remove(entt::entity{99});
ASSERT_FALSE(set.contains(entt::entity{0}));
ASSERT_FALSE(set.contains(entt::entity{3}));
ASSERT_FALSE(set.contains(entt::entity{42}));
ASSERT_FALSE(set.contains(entt::entity{99}));
ASSERT_TRUE(set.contains(entt::entity{1}));
ASSERT_DEATH(static_cast<void>(set.contains(entt::null)), "");
ASSERT_DEATH(static_cast<void>(set.contains(entt::tombstone)), "");
ASSERT_DEATH(static_cast<void>(set.contains(entt::tombstone | entt::entity{1u})), "");
ASSERT_DEATH(static_cast<void>(set.contains(entt::null | entt::entity{1u})), "");
}
TEST(SparseSet, Move) {
entt::sparse_set set;
set.emplace(entt::entity{42});
ASSERT_TRUE(std::is_move_constructible_v<decltype(set)>);
ASSERT_TRUE(std::is_move_assignable_v<decltype(set)>);
entt::sparse_set other{std::move(set)};
ASSERT_TRUE(set.empty());
ASSERT_FALSE(other.empty());
ASSERT_EQ(set.at(0u), static_cast<entt::entity>(entt::null));
ASSERT_EQ(other.at(0u), entt::entity{42});
set = std::move(other);
ASSERT_FALSE(set.empty());
ASSERT_TRUE(other.empty());
ASSERT_EQ(set.at(0u), entt::entity{42});
ASSERT_EQ(other.at(0u), static_cast<entt::entity>(entt::null));
other = entt::sparse_set{};
other.emplace(entt::entity{3});
other = std::move(set);
ASSERT_TRUE(set.empty());
ASSERT_FALSE(other.empty());
ASSERT_EQ(other.index(entt::entity{42}), 0u);
other.clear();
ASSERT_TRUE(other.empty());
ASSERT_EQ(other.size(), 0u);
ASSERT_EQ(std::as_const(other).begin(), std::as_const(other).end());
ASSERT_EQ(other.begin(), other.end());
ASSERT_FALSE(other.contains(entt::entity{0}));
ASSERT_FALSE(other.contains(entt::entity{42}));
ASSERT_EQ(set.at(0u), static_cast<entt::entity>(entt::null));
ASSERT_EQ(other.at(0u), entt::entity{42});
}
TEST(SparseSet, Pagination) {
entt::sparse_set set;
constexpr auto entt_per_page = ENTT_PAGE_SIZE / sizeof(entt::entity);
ASSERT_EQ(set.extent(), 0u);
set.emplace(entt::entity{entt_per_page-1});
set.emplace(entt::entity{ENTT_SPARSE_PAGE-1u});
ASSERT_EQ(set.extent(), entt_per_page);
ASSERT_TRUE(set.contains(entt::entity{entt_per_page-1}));
ASSERT_EQ(set.extent(), ENTT_SPARSE_PAGE);
ASSERT_TRUE(set.contains(entt::entity{ENTT_SPARSE_PAGE-1u}));
set.emplace(entt::entity{entt_per_page});
set.emplace(entt::entity{ENTT_SPARSE_PAGE});
ASSERT_EQ(set.extent(), 2 * entt_per_page);
ASSERT_TRUE(set.contains(entt::entity{entt_per_page-1}));
ASSERT_TRUE(set.contains(entt::entity{entt_per_page}));
ASSERT_FALSE(set.contains(entt::entity{entt_per_page+1}));
ASSERT_EQ(set.extent(), 2 * ENTT_SPARSE_PAGE);
ASSERT_TRUE(set.contains(entt::entity{ENTT_SPARSE_PAGE-1u}));
ASSERT_TRUE(set.contains(entt::entity{ENTT_SPARSE_PAGE}));
ASSERT_FALSE(set.contains(entt::entity{ENTT_SPARSE_PAGE+1u}));
set.remove(entt::entity{entt_per_page-1});
set.erase(entt::entity{ENTT_SPARSE_PAGE-1u});
ASSERT_EQ(set.extent(), 2 * entt_per_page);
ASSERT_FALSE(set.contains(entt::entity{entt_per_page-1}));
ASSERT_TRUE(set.contains(entt::entity{entt_per_page}));
ASSERT_EQ(set.extent(), 2 * ENTT_SPARSE_PAGE);
ASSERT_FALSE(set.contains(entt::entity{ENTT_SPARSE_PAGE-1u}));
ASSERT_TRUE(set.contains(entt::entity{ENTT_SPARSE_PAGE}));
set.shrink_to_fit();
set.remove(entt::entity{entt_per_page});
set.erase(entt::entity{ENTT_SPARSE_PAGE});
ASSERT_EQ(set.extent(), 2 * entt_per_page);
ASSERT_FALSE(set.contains(entt::entity{entt_per_page-1}));
ASSERT_FALSE(set.contains(entt::entity{entt_per_page}));
ASSERT_EQ(set.extent(), 2 * ENTT_SPARSE_PAGE);
ASSERT_FALSE(set.contains(entt::entity{ENTT_SPARSE_PAGE-1u}));
ASSERT_FALSE(set.contains(entt::entity{ENTT_SPARSE_PAGE}));
set.shrink_to_fit();
ASSERT_EQ(set.extent(), 0u);
ASSERT_EQ(set.extent(), 2 * ENTT_SPARSE_PAGE);
}
TEST(SparseSet, Emplace) {
entt::sparse_set set{entt::deletion_policy::in_place};
entt::entity entities[2u];
entities[0u] = entt::entity{3};
entities[1u] = entt::entity{42};
ASSERT_TRUE(set.empty());
set.emplace(entities[0u]);
set.erase(entities[0u]);
set.emplace_back(entities[0u]);
set.emplace(entities[1u]);
ASSERT_DEATH(set.emplace_back(entities[1u]), "");
ASSERT_DEATH(set.emplace(entities[0u]), "");
ASSERT_EQ(set.at(0u), entities[1u]);
ASSERT_EQ(set.at(1u), entities[0u]);
ASSERT_EQ(set.index(entities[0u]), 1u);
ASSERT_EQ(set.index(entities[1u]), 0u);
set.erase(std::begin(entities), std::end(entities));
set.emplace(entities[1u]);
set.emplace_back(entities[0u]);
ASSERT_EQ(set.at(0u), entities[1u]);
ASSERT_EQ(set.at(1u), static_cast<entt::entity>(entt::null));
ASSERT_EQ(set.at(2u), entities[0u]);
ASSERT_EQ(set.index(entities[0u]), 2u);
ASSERT_EQ(set.index(entities[1u]), 0u);
}
TEST(SparseSet, Insert) {
entt::sparse_set set;
entt::entity entities[2];
entt::sparse_set set{entt::deletion_policy::in_place};
entt::entity entities[2u];
entities[0] = entt::entity{3};
entities[1] = entt::entity{42};
entities[0u] = entt::entity{3};
entities[1u] = entt::entity{42};
set.emplace(entt::entity{12});
set.insert(std::end(entities), std::end(entities));
set.insert(std::begin(entities), std::end(entities));
set.emplace(entt::entity{24});
ASSERT_TRUE(set.contains(entities[0]));
ASSERT_TRUE(set.contains(entities[1]));
ASSERT_TRUE(set.contains(entities[0u]));
ASSERT_TRUE(set.contains(entities[1u]));
ASSERT_FALSE(set.contains(entt::entity{0}));
ASSERT_FALSE(set.contains(entt::entity{9}));
ASSERT_TRUE(set.contains(entt::entity{12}));
@@ -129,46 +229,368 @@ TEST(SparseSet, Insert) {
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 4u);
ASSERT_EQ(set.index(entt::entity{12}), 0u);
ASSERT_EQ(set.index(entities[0]), 1u);
ASSERT_EQ(set.index(entities[1]), 2u);
ASSERT_EQ(set.index(entities[0u]), 1u);
ASSERT_EQ(set.index(entities[1u]), 2u);
ASSERT_EQ(set.index(entt::entity{24}), 3u);
ASSERT_EQ(set.data()[set.index(entt::entity{12})], entt::entity{12});
ASSERT_EQ(set.data()[set.index(entities[0])], entities[0]);
ASSERT_EQ(set.data()[set.index(entities[1])], entities[1]);
ASSERT_EQ(set.data()[set.index(entities[0u])], entities[0u]);
ASSERT_EQ(set.data()[set.index(entities[1u])], entities[1u]);
ASSERT_EQ(set.data()[set.index(entt::entity{24})], entt::entity{24});
set.erase(std::begin(entities), std::end(entities));
set.insert(std::rbegin(entities), std::rend(entities));
ASSERT_EQ(set.size(), 6u);
ASSERT_TRUE(set.at(1u) == entt::tombstone);
ASSERT_TRUE(set.at(2u) == entt::tombstone);
ASSERT_EQ(set.at(4u), entities[1u]);
ASSERT_EQ(set.at(5u), entities[0u]);
ASSERT_EQ(set.index(entities[0u]), 5u);
ASSERT_EQ(set.index(entities[1u]), 4u);
}
TEST(SparseSet, Erase) {
entt::sparse_set set;
entt::entity entities[3u];
entities[0u] = entt::entity{3};
entities[1u] = entt::entity{42};
entities[2u] = entt::entity{9};
ASSERT_EQ(set.policy(), entt::deletion_policy::swap_and_pop);
ASSERT_TRUE(set.empty());
ASSERT_DEATH(set.erase(std::begin(entities), std::end(entities)), "");
ASSERT_DEATH(set.erase(entities[1u]), "");
ASSERT_TRUE(set.empty());
set.insert(std::begin(entities), std::end(entities));
set.erase(set.begin(), set.end());
ASSERT_TRUE(set.empty());
set.insert(std::begin(entities), std::end(entities));
set.erase(entities, entities + 2u);
ASSERT_FALSE(set.empty());
ASSERT_EQ(*set.begin(), entities[2u]);
set.erase(entities[2u]);
ASSERT_DEATH(set.erase(entities[2u]), "");
ASSERT_TRUE(set.empty());
set.insert(std::begin(entities), std::end(entities));
std::swap(entities[1u], entities[2u]);
set.erase(entities, entities + 2u);
ASSERT_FALSE(set.empty());
ASSERT_EQ(*set.begin(), entities[2u]);
}
TEST(SparseSet, StableErase) {
entt::sparse_set set{entt::deletion_policy::in_place};
entt::entity entities[3u];
entities[0u] = entt::entity{3};
entities[1u] = entt::entity{42};
entities[2u] = entt::entity{9};
ASSERT_EQ(set.policy(), entt::deletion_policy::in_place);
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u);
ASSERT_DEATH(set.erase(std::begin(entities), std::end(entities)), "");
ASSERT_DEATH(set.erase(entities[1u]), "");
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u);
set.insert(std::begin(entities), std::end(entities));
set.erase(set.begin(), set.end());
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 3u);
ASSERT_TRUE(set.at(0u) == entt::tombstone);
ASSERT_TRUE(set.at(1u) == entt::tombstone);
ASSERT_TRUE(set.at(2u) == entt::tombstone);
ASSERT_EQ(set.slot(), 0u);
set.insert(std::begin(entities), std::end(entities));
set.erase(entities, entities + 2u);
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 6u);
ASSERT_EQ(*set.begin(), entities[2u]);
ASSERT_TRUE(set.at(3u) == entt::tombstone);
ASSERT_TRUE(set.at(4u) == entt::tombstone);
ASSERT_EQ(set.slot(), 4u);
set.erase(entities[2u]);
ASSERT_DEATH(set.erase(entities[2u]), "");
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 6u);
ASSERT_EQ(set.slot(), 5u);
set.insert(std::begin(entities), std::end(entities));
std::swap(entities[1u], entities[2u]);
set.erase(entities, entities + 2u);
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 9u);
ASSERT_TRUE(set.at(6u) == entt::tombstone);
ASSERT_EQ(set.at(7u), entities[2u]);
ASSERT_EQ(*++set.begin(), entities[2u]);
ASSERT_TRUE(set.at(8u) == entt::tombstone);
ASSERT_EQ(set.slot(), 8u);
set.compact();
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 1u);
ASSERT_EQ(*set.begin(), entities[2u]);
ASSERT_EQ(set.slot(), 1u);
set.clear();
ASSERT_EQ(set.size(), 0u);
ASSERT_EQ(set.slot(), 0u);
set.insert(std::begin(entities), std::end(entities));
set.erase(entities[2u]);
ASSERT_DEATH(set.erase(entities[2u]), "");
ASSERT_EQ(set.slot(), 2u);
set.erase(entities[0u]);
set.erase(entities[1u]);
ASSERT_DEATH(set.erase(entities, entities + 2u), "");
ASSERT_EQ(set.size(), 3u);
ASSERT_TRUE(*set.begin() == entt::tombstone);
ASSERT_EQ(set.slot(), 1u);
ASSERT_EQ(set.emplace(entities[0u]), 1u);
ASSERT_EQ(*++set.begin(), entities[0u]);
ASSERT_EQ(set.emplace(entities[1u]), 0u);
ASSERT_EQ(set.emplace(entities[2u]), 2u);
ASSERT_EQ(set.emplace(entt::entity{0}), 3u);
ASSERT_EQ(set.size(), 4u);
ASSERT_EQ(*set.begin(), entt::entity{0});
ASSERT_EQ(set.at(0u), entities[1u]);
ASSERT_EQ(set.at(1u), entities[0u]);
ASSERT_EQ(set.at(2u), entities[2u]);
}
TEST(SparseSet, Remove) {
entt::sparse_set set;
entt::entity entities[3];
entt::entity entities[3u];
entities[0] = entt::entity{3};
entities[1] = entt::entity{42};
entities[2] = entt::entity{9};
entities[0u] = entt::entity{3};
entities[1u] = entt::entity{42};
entities[2u] = entt::entity{9};
set.insert(std::begin(entities), std::end(entities));
set.remove(set.begin(), set.end());
ASSERT_EQ(set.policy(), entt::deletion_policy::swap_and_pop);
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.remove(std::begin(entities), std::end(entities)), 0u);
ASSERT_EQ(set.remove(entities[1u]), 0u);
ASSERT_TRUE(set.empty());
set.insert(std::begin(entities), std::end(entities));
set.remove(set.rbegin(), set.rend());
ASSERT_EQ(set.remove(set.begin(), set.end()), 3u);
ASSERT_TRUE(set.empty());
set.insert(std::begin(entities), std::end(entities));
set.remove(entities, entities + 2u);
ASSERT_EQ(set.remove(entities, entities + 2u), 2u);
ASSERT_FALSE(set.empty());
ASSERT_EQ(*set.begin(), entities[2u]);
ASSERT_EQ(set.remove(entities[2u]), 1u);
ASSERT_EQ(set.remove(entities[2u]), 0u);
ASSERT_TRUE(set.empty());
set.insert(entities, entities + 2u);
ASSERT_EQ(set.remove(std::begin(entities), std::end(entities)), 2u);
ASSERT_TRUE(set.empty());
set.insert(std::begin(entities), std::end(entities));
std::swap(entities[1u], entities[2u]);
ASSERT_EQ(set.remove(entities, entities + 2u), 2u);
ASSERT_FALSE(set.empty());
ASSERT_EQ(*set.begin(), entities[2u]);
}
TEST(SparseSet, StableRemove) {
entt::sparse_set set{entt::deletion_policy::in_place};
entt::entity entities[3u];
entities[0u] = entt::entity{3};
entities[1u] = entt::entity{42};
entities[2u] = entt::entity{9};
ASSERT_EQ(set.policy(), entt::deletion_policy::in_place);
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u);
ASSERT_EQ(set.remove(std::begin(entities), std::end(entities)), 0u);
ASSERT_EQ(set.remove(entities[1u]), 0u);
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u);
set.insert(std::begin(entities), std::end(entities));
ASSERT_EQ(set.remove(set.begin(), set.end()), 3u);
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 3u);
ASSERT_TRUE(set.at(0u) == entt::tombstone);
ASSERT_TRUE(set.at(1u) == entt::tombstone);
ASSERT_TRUE(set.at(2u) == entt::tombstone);
ASSERT_EQ(set.slot(), 0u);
set.insert(std::begin(entities), std::end(entities));
ASSERT_EQ(set.remove(entities, entities + 2u), 2u);
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 6u);
ASSERT_EQ(*set.begin(), entt::entity{9});
ASSERT_TRUE(set.at(3u) == entt::tombstone);
ASSERT_TRUE(set.at(4u) == entt::tombstone);
ASSERT_EQ(set.slot(), 4u);
ASSERT_EQ(set.remove(entities[2u]), 1u);
ASSERT_EQ(set.remove(entities[2u]), 0u);
ASSERT_EQ(set.remove(entities[2u]), 0u);
ASSERT_EQ(set.remove(entities[2u]), 0u);
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 6u);
ASSERT_TRUE(*set.begin() == entt::tombstone);
ASSERT_EQ(set.slot(), 5u);
set.insert(entities, entities + 2u);
ASSERT_EQ(set.remove(std::begin(entities), std::end(entities)), 2u);
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 8u);
ASSERT_TRUE(set.at(6u) == entt::tombstone);
ASSERT_TRUE(set.at(7u) == entt::tombstone);
ASSERT_EQ(set.slot(), 7u);
set.insert(std::begin(entities), std::end(entities));
std::swap(entities[1u], entities[2u]);
ASSERT_EQ(set.remove(entities, entities + 2u), 2u);
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 11u);
ASSERT_TRUE(set.at(8u) == entt::tombstone);
ASSERT_EQ(set.at(9u), entities[2u]);
ASSERT_EQ(*++set.begin(), entities[2u]);
ASSERT_TRUE(set.at(10u) == entt::tombstone);
ASSERT_EQ(set.slot(), 10u);
set.compact();
ASSERT_FALSE(set.empty());
ASSERT_EQ(*set.begin(), entt::entity{9});
ASSERT_EQ(set.size(), 1u);
ASSERT_EQ(*set.begin(), entities[2u]);
ASSERT_EQ(set.slot(), 1u);
set.clear();
ASSERT_EQ(set.size(), 0u);
ASSERT_EQ(set.slot(), 0u);
set.insert(std::begin(entities), std::end(entities));
std::swap(entities[1], entities[2]);
set.remove(entities, entities + 2u);
ASSERT_EQ(set.remove(entities[2u]), 1u);
ASSERT_EQ(set.remove(entities[2u]), 0u);
ASSERT_EQ(set.remove(entities[0u]), 1u);
ASSERT_EQ(set.remove(entities[1u]), 1u);
ASSERT_EQ(set.remove(entities, entities + 2u), 0u);
ASSERT_EQ(set.size(), 3u);
ASSERT_TRUE(*set.begin() == entt::tombstone);
ASSERT_EQ(set.slot(), 1u);
ASSERT_EQ(set.emplace(entities[0u]), 1u);
ASSERT_EQ(*++set.begin(), entities[0u]);
ASSERT_EQ(set.emplace(entities[1u]), 0u);
ASSERT_EQ(set.emplace(entities[2u]), 2u);
ASSERT_EQ(set.emplace(entt::entity{0}), 3u);
ASSERT_EQ(set.size(), 4u);
ASSERT_EQ(*set.begin(), entt::entity{0});
ASSERT_EQ(set.at(0u), entities[1u]);
ASSERT_EQ(set.at(1u), entities[0u]);
ASSERT_EQ(set.at(2u), entities[2u]);
}
TEST(SparseSet, Compact) {
entt::sparse_set set{entt::deletion_policy::in_place};
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u);
set.compact();
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u);
set.emplace(entt::entity{0});
set.compact();
ASSERT_FALSE(set.empty());
ASSERT_EQ(*set.begin(), entt::entity{42});
ASSERT_EQ(set.size(), 1u);
set.emplace(entt::entity{42});
set.erase(entt::entity{0});
ASSERT_EQ(set.size(), 2u);
ASSERT_EQ(set.index(entt::entity{42}), 1u);
set.compact();
ASSERT_EQ(set.size(), 1u);
ASSERT_EQ(set.index(entt::entity{42}), 0u);
set.emplace(entt::entity{0});
set.compact();
ASSERT_EQ(set.size(), 2u);
ASSERT_EQ(set.index(entt::entity{42}), 0u);
ASSERT_EQ(set.index(entt::entity{0}), 1u);
set.erase(entt::entity{0});
set.erase(entt::entity{42});
set.compact();
ASSERT_TRUE(set.empty());
}
TEST(SparseSet, Clear) {
entt::sparse_set set;
set.emplace(entt::entity{3});
set.emplace(entt::entity{42});
set.emplace(entt::entity{9});
ASSERT_FALSE(set.empty());
set.clear();
ASSERT_TRUE(set.empty());
}
TEST(SparseSet, Iterator) {
@@ -204,7 +626,7 @@ TEST(SparseSet, Iterator) {
ASSERT_EQ(end - (end - begin), set.begin());
ASSERT_EQ(end + (begin - end), set.begin());
ASSERT_EQ(begin[0], *set.begin());
ASSERT_EQ(begin[0u], *set.begin());
ASSERT_LT(begin, end);
ASSERT_LE(begin, set.begin());
@@ -249,7 +671,7 @@ TEST(SparseSet, ReverseIterator) {
ASSERT_EQ(end - (end - begin), set.rbegin());
ASSERT_EQ(end + (begin - end), set.rbegin());
ASSERT_EQ(begin[0], *set.rbegin());
ASSERT_EQ(begin[0u], *set.rbegin());
ASSERT_LT(begin, end);
ASSERT_LE(begin, set.rbegin());
@@ -487,3 +909,51 @@ TEST(SparseSet, CanModifyDuringIteration) {
const auto entity = *it;
(void)entity;
}
TEST(SparseSet, ThrowingAllocator) {
entt::basic_sparse_set<entt::entity, test::throwing_allocator<entt::entity>> set{};
test::throwing_allocator<entt::entity>::trigger_on_allocate = true;
// strong exception safety
ASSERT_THROW(set.reserve(1u), test::throwing_allocator<entt::entity>::exception_type);
ASSERT_EQ(set.capacity(), 0u);
ASSERT_EQ(set.extent(), 0u);
test::throwing_allocator<entt::entity>::trigger_on_allocate = true;
// strong exception safety
ASSERT_THROW(set.emplace(entt::entity{0}), test::throwing_allocator<entt::entity>::exception_type);
ASSERT_EQ(set.capacity(), 0u);
ASSERT_EQ(set.extent(), 0u);
set.emplace(entt::entity{0});
test::throwing_allocator<entt::entity>::trigger_on_allocate = true;
// strong exception safety
ASSERT_THROW(set.reserve(2u), test::throwing_allocator<entt::entity>::exception_type);
ASSERT_EQ(set.capacity(), 1u);
ASSERT_EQ(set.extent(), ENTT_SPARSE_PAGE);
ASSERT_TRUE(set.contains(entt::entity{0}));
entt::entity entities[2u]{entt::entity{1}, entt::entity{ENTT_SPARSE_PAGE}};
test::throwing_allocator<entt::entity>::trigger_after_allocate = true;
// basic exception safety
ASSERT_THROW(set.insert(std::begin(entities), std::end(entities)), test::throwing_allocator<entt::entity>::exception_type);
ASSERT_EQ(set.capacity(), 3u);
ASSERT_EQ(set.size(), 2u);
ASSERT_EQ(set.extent(), 2 * ENTT_SPARSE_PAGE);
ASSERT_TRUE(set.contains(entt::entity{0}));
ASSERT_TRUE(set.contains(entt::entity{1}));
ASSERT_FALSE(set.contains(entt::entity{ENTT_SPARSE_PAGE}));
set.emplace(entities[1u]);
ASSERT_TRUE(set.contains(entt::entity{ENTT_SPARSE_PAGE}));
// unnecessary but they test a bit of template machinery :)
set.clear();
set.shrink_to_fit();
set = decltype(set){};
}

View File

@@ -5,31 +5,59 @@
#include <type_traits>
#include <unordered_set>
#include <gtest/gtest.h>
#include <entt/entity/storage.hpp>
#include <entt/entity/component.hpp>
#include <entt/entity/fwd.hpp>
#include <entt/entity/storage.hpp>
#include "throwing_allocator.hpp"
#include "throwing_component.hpp"
struct empty_type {};
struct boxed_int { int value; };
struct stable_type { int value; };
struct update_from_destructor {
update_from_destructor(entt::storage<update_from_destructor> &ref, entt::entity other)
: storage{&ref},
target{other}
{}
update_from_destructor(update_from_destructor &&other) ENTT_NOEXCEPT
: storage{std::exchange(other.storage, nullptr)},
target{std::exchange(other.target, entt::null)}
{}
update_from_destructor & operator=(update_from_destructor &&other) ENTT_NOEXCEPT {
storage = std::exchange(other.storage, nullptr);
target = std::exchange(other.target, entt::null);
return *this;
}
~update_from_destructor() {
if(target != entt::null && storage->contains(target)) {
storage->erase(target);
}
}
private:
entt::storage<update_from_destructor> *storage{};
entt::entity target{entt::null};
};
template<>
struct entt::component_traits<stable_type>: basic_component_traits {
using in_place_delete = std::true_type;
};
bool operator==(const boxed_int &lhs, const boxed_int &rhs) {
return lhs.value == rhs.value;
}
struct throwing_component {
struct constructor_exception: std::exception {};
[[noreturn]] throwing_component() { throw constructor_exception{}; }
// necessary to disable the empty type optimization
int data;
};
TEST(Storage, Functionalities) {
entt::storage<int> pool;
pool.reserve(42);
ASSERT_EQ(pool.capacity(), 42u);
ASSERT_EQ(pool.capacity(), ENTT_PACKED_PAGE);
ASSERT_TRUE(pool.empty());
ASSERT_EQ(pool.size(), 0u);
ASSERT_EQ(std::as_const(pool).begin(), std::as_const(pool).end());
@@ -37,6 +65,11 @@ TEST(Storage, Functionalities) {
ASSERT_FALSE(pool.contains(entt::entity{0}));
ASSERT_FALSE(pool.contains(entt::entity{41}));
pool.reserve(0);
ASSERT_EQ(pool.capacity(), ENTT_PACKED_PAGE);
ASSERT_TRUE(pool.empty());
pool.emplace(entt::entity{41}, 3);
ASSERT_FALSE(pool.empty());
@@ -47,7 +80,7 @@ TEST(Storage, Functionalities) {
ASSERT_TRUE(pool.contains(entt::entity{41}));
ASSERT_EQ(pool.get(entt::entity{41}), 3);
pool.remove(entt::entity{41});
pool.erase(entt::entity{41});
ASSERT_TRUE(pool.empty());
ASSERT_EQ(pool.size(), 0u);
@@ -69,15 +102,45 @@ TEST(Storage, Functionalities) {
ASSERT_FALSE(pool.contains(entt::entity{0}));
ASSERT_FALSE(pool.contains(entt::entity{41}));
ASSERT_EQ(pool.capacity(), 42u);
ASSERT_EQ(pool.capacity(), ENTT_PACKED_PAGE);
pool.shrink_to_fit();
ASSERT_EQ(pool.capacity(), 0u);
}
(void)entt::storage<int>{std::move(pool)};
entt::storage<int> other;
TEST(Storage, Move) {
entt::storage<int> pool;
pool.emplace(entt::entity{3}, 3);
ASSERT_TRUE(std::is_move_constructible_v<decltype(pool)>);
ASSERT_TRUE(std::is_move_assignable_v<decltype(pool)>);
entt::storage<int> other{std::move(pool)};
ASSERT_TRUE(pool.empty());
ASSERT_FALSE(other.empty());
ASSERT_EQ(pool.at(0u), static_cast<entt::entity>(entt::null));
ASSERT_EQ(other.at(0u), entt::entity{3});
ASSERT_EQ(other.get(entt::entity{3}), 3);
pool = std::move(other);
ASSERT_FALSE(pool.empty());
ASSERT_TRUE(other.empty());
ASSERT_EQ(pool.at(0u), entt::entity{3});
ASSERT_EQ(pool.get(entt::entity{3}), 3);
ASSERT_EQ(other.at(0u), static_cast<entt::entity>(entt::null));
other = entt::storage<int>{};
other.emplace(entt::entity{42}, 42);
other = std::move(pool);
ASSERT_TRUE(pool.empty());
ASSERT_FALSE(other.empty());
ASSERT_EQ(pool.at(0u), static_cast<entt::entity>(entt::null));
ASSERT_EQ(other.at(0u), entt::entity{3});
ASSERT_EQ(other.get(entt::entity{3}), 3);
}
TEST(Storage, EmptyType) {
@@ -88,66 +151,415 @@ TEST(Storage, EmptyType) {
}
TEST(Storage, Insert) {
entt::storage<int> pool;
entt::entity entities[2];
entt::storage<stable_type> pool;
entt::entity entities[2u];
entities[0] = entt::entity{3};
entities[1] = entt::entity{42};
pool.insert(std::begin(entities), std::end(entities), {});
entities[0u] = entt::entity{3};
entities[1u] = entt::entity{42};
pool.insert(std::begin(entities), std::end(entities), stable_type{99});
ASSERT_TRUE(pool.contains(entities[0]));
ASSERT_TRUE(pool.contains(entities[1]));
ASSERT_TRUE(pool.contains(entities[0u]));
ASSERT_TRUE(pool.contains(entities[1u]));
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.get(entities[0]), 0);
ASSERT_EQ(pool.get(entities[1]), 0);
ASSERT_EQ(pool.get(entities[0u]).value, 99);
ASSERT_EQ(pool.get(entities[1u]).value, 99);
pool.erase(std::begin(entities), std::end(entities));
const stable_type values[2u] = { stable_type{42}, stable_type{3} };
pool.insert(std::rbegin(entities), std::rend(entities), std::begin(values));
ASSERT_EQ(pool.size(), 4u);
ASSERT_TRUE(pool.at(0u) == entt::tombstone);
ASSERT_TRUE(pool.at(1u) == entt::tombstone);
ASSERT_EQ(pool.at(2u), entities[1u]);
ASSERT_EQ(pool.at(3u), entities[0u]);
ASSERT_EQ(pool.index(entities[0u]), 3u);
ASSERT_EQ(pool.index(entities[1u]), 2u);
ASSERT_EQ(pool.get(entities[0u]).value, 3);
ASSERT_EQ(pool.get(entities[1u]).value, 42);
}
TEST(Storage, InsertEmptyType) {
entt::storage<empty_type> pool;
entt::entity entities[2];
entt::entity entities[2u];
entities[0] = entt::entity{3};
entities[1] = entt::entity{42};
entities[0u] = entt::entity{3};
entities[1u] = entt::entity{42};
pool.insert(std::begin(entities), std::end(entities));
ASSERT_TRUE(pool.contains(entities[0]));
ASSERT_TRUE(pool.contains(entities[1]));
ASSERT_TRUE(pool.contains(entities[0u]));
ASSERT_TRUE(pool.contains(entities[1u]));
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 2u);
}
TEST(Storage, Remove) {
TEST(Storage, Erase) {
entt::storage<int> pool;
entt::sparse_set &base = pool;
entt::entity entities[3u];
pool.emplace(entt::entity{3});
pool.emplace(entt::entity{42});
base.remove(base.begin(), base.end());
entities[0u] = entt::entity{3};
entities[1u] = entt::entity{42};
entities[2u] = entt::entity{9};
pool.emplace(entities[0u]);
pool.emplace(entities[1u]);
pool.emplace(entities[2u]);
pool.erase(std::begin(entities), std::end(entities));
ASSERT_DEATH(pool.erase(std::begin(entities), std::end(entities)), "");
ASSERT_TRUE(pool.empty());
pool.emplace(entt::entity{3}, 3);
pool.emplace(entt::entity{42}, 42);
pool.emplace(entt::entity{9}, 9);
base.remove(base.rbegin(), base.rbegin() + 2u);
pool.emplace(entities[0u], 0);
pool.emplace(entities[1u], 1);
pool.emplace(entities[2u], 2);
pool.erase(entities, entities + 2u);
ASSERT_FALSE(pool.empty());
ASSERT_EQ(*pool.begin(), 9);
ASSERT_EQ(*pool.begin(), 2);
pool.erase(entities[2u]);
ASSERT_DEATH(pool.erase(entities[2u]), "");
ASSERT_TRUE(pool.empty());
pool.emplace(entities[0u], 0);
pool.emplace(entities[1u], 1);
pool.emplace(entities[2u], 2);
std::swap(entities[1u], entities[2u]);
pool.erase(entities, entities + 2u);
ASSERT_FALSE(pool.empty());
ASSERT_EQ(*pool.begin(), 1);
}
TEST(Storage, StableErase) {
entt::storage<stable_type> pool;
entt::entity entities[3u];
ASSERT_DEATH([[maybe_unused]] auto &&value = pool.get(entt::tombstone), "");
ASSERT_DEATH([[maybe_unused]] auto &&value = pool.get(entt::null), "");
entities[0u] = entt::entity{3};
entities[1u] = entt::entity{42};
entities[2u] = entt::entity{9};
pool.emplace(entities[0u], stable_type{0});
pool.emplace(entities[1u], stable_type{1});
pool.emplace(entities[2u], stable_type{2});
pool.erase(std::begin(entities), std::end(entities));
ASSERT_DEATH(pool.erase(std::begin(entities), std::end(entities)), "");
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 3u);
ASSERT_TRUE(pool.at(2u) == entt::tombstone);
ASSERT_DEATH([[maybe_unused]] auto &&value = pool.get(entt::tombstone), "");
ASSERT_DEATH([[maybe_unused]] auto &&value = pool.get(entt::null), "");
ASSERT_DEATH([[maybe_unused]] auto &&value = pool.get(entities[1u]), "");
pool.emplace(entities[2u], stable_type{2});
pool.emplace(entities[0u], stable_type{0});
pool.emplace(entities[1u], stable_type{1});
ASSERT_EQ(pool.get(entities[0u]).value, 0);
ASSERT_EQ(pool.get(entities[1u]).value, 1);
ASSERT_EQ(pool.get(entities[2u]).value, 2);
ASSERT_EQ(pool.begin()->value, 2);
ASSERT_EQ(pool.index(entities[0u]), 1u);
ASSERT_EQ(pool.index(entities[1u]), 0u);
ASSERT_EQ(pool.index(entities[2u]), 2u);
pool.erase(entities, entities + 2u);
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 3u);
ASSERT_EQ(pool.begin()->value, 2);
ASSERT_EQ(pool.index(entities[2u]), 2u);
pool.erase(entities[2u]);
ASSERT_DEATH(pool.erase(entities[2u]), "");
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 3u);
ASSERT_FALSE(pool.contains(entities[0u]));
ASSERT_FALSE(pool.contains(entities[1u]));
ASSERT_FALSE(pool.contains(entities[2u]));
pool.emplace(entities[0u], stable_type{0});
pool.emplace(entities[1u], stable_type{1});
pool.emplace(entities[2u], stable_type{2});
std::swap(entities[1u], entities[2u]);
pool.erase(entities, entities + 2u);
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 3u);
ASSERT_TRUE(pool.contains(entities[2u]));
ASSERT_EQ(pool.index(entities[2u]), 0u);
ASSERT_EQ(pool.get(entities[2u]).value, 1);
pool.compact();
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 1u);
ASSERT_EQ(pool.begin()->value, 1);
pool.clear();
pool.emplace(entt::entity{3}, 3);
pool.emplace(entt::entity{42}, 42);
pool.emplace(entt::entity{9}, 9);
entt::entity entities[2]{entt::entity{3}, entt::entity{9}};
base.remove(std::begin(entities), std::end(entities));
ASSERT_EQ(pool.size(), 0u);
pool.emplace(entities[0u], stable_type{0});
pool.emplace(entities[1u], stable_type{2});
pool.emplace(entities[2u], stable_type{1});
pool.erase(entities[2u]);
ASSERT_DEATH(pool.erase(entities[2u]), "");
pool.erase(entities[0u]);
pool.erase(entities[1u]);
ASSERT_DEATH(pool.erase(entities, entities + 2u), "");
ASSERT_EQ(pool.size(), 3u);
ASSERT_TRUE(pool.at(2u) == entt::tombstone);
pool.emplace(entities[0u], stable_type{99});
ASSERT_EQ((++pool.begin())->value, 99);
pool.emplace(entities[1u], stable_type{2});
pool.emplace(entities[2u], stable_type{1});
pool.emplace(entt::entity{0}, stable_type{7});
ASSERT_EQ(pool.size(), 4u);
ASSERT_EQ(pool.begin()->value, 7);
ASSERT_EQ(pool.at(0u), entities[1u]);
ASSERT_EQ(pool.at(1u), entities[0u]);
ASSERT_EQ(pool.at(2u), entities[2u]);
ASSERT_EQ(pool.get(entities[0u]).value, 99);
ASSERT_EQ(pool.get(entities[1u]).value, 2);
ASSERT_EQ(pool.get(entities[2u]).value, 1);
}
TEST(Storage, Remove) {
entt::storage<int> pool;
entt::entity entities[3u];
entities[0u] = entt::entity{3};
entities[1u] = entt::entity{42};
entities[2u] = entt::entity{9};
pool.emplace(entities[0u]);
pool.emplace(entities[1u]);
pool.emplace(entities[2u]);
ASSERT_EQ(pool.remove(std::begin(entities), std::end(entities)), 3u);
ASSERT_EQ(pool.remove(std::begin(entities), std::end(entities)), 0u);
ASSERT_TRUE(pool.empty());
pool.emplace(entities[0u], 0);
pool.emplace(entities[1u], 1);
pool.emplace(entities[2u], 2);
ASSERT_EQ(pool.remove(entities, entities + 2u), 2u);
ASSERT_FALSE(pool.empty());
ASSERT_EQ(*pool.begin(), 2);
ASSERT_EQ(pool.remove(entities[2u]), 1u);
ASSERT_EQ(pool.remove(entities[2u]), 0u);
ASSERT_TRUE(pool.empty());
pool.emplace(entities[0u], 0);
pool.emplace(entities[1u], 1);
pool.emplace(entities[2u], 2);
std::swap(entities[1u], entities[2u]);
ASSERT_EQ(pool.remove(entities, entities + 2u), 2u);
ASSERT_FALSE(pool.empty());
ASSERT_EQ(*pool.begin(), 1);
}
TEST(Storage, StableRemove) {
entt::storage<stable_type> pool;
entt::entity entities[3u];
entities[0u] = entt::entity{3};
entities[1u] = entt::entity{42};
entities[2u] = entt::entity{9};
pool.emplace(entities[0u], stable_type{0});
pool.emplace(entities[1u], stable_type{1});
pool.emplace(entities[2u], stable_type{2});
ASSERT_EQ(pool.remove(std::begin(entities), std::end(entities)), 3u);
ASSERT_EQ(pool.remove(std::begin(entities), std::end(entities)), 0u);
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 3u);
ASSERT_TRUE(pool.at(2u) == entt::tombstone);
ASSERT_DEATH([[maybe_unused]] auto &&value = pool.get(entt::tombstone), "");
ASSERT_DEATH([[maybe_unused]] auto &&value = pool.get(entt::null), "");
ASSERT_DEATH([[maybe_unused]] auto &&value = pool.get(entities[1u]), "");
pool.emplace(entities[2u], stable_type{2});
pool.emplace(entities[0u], stable_type{0});
pool.emplace(entities[1u], stable_type{1});
ASSERT_EQ(pool.get(entities[0u]).value, 0);
ASSERT_EQ(pool.get(entities[1u]).value, 1);
ASSERT_EQ(pool.get(entities[2u]).value, 2);
ASSERT_EQ(pool.begin()->value, 2);
ASSERT_EQ(pool.index(entities[0u]), 1u);
ASSERT_EQ(pool.index(entities[1u]), 0u);
ASSERT_EQ(pool.index(entities[2u]), 2u);
ASSERT_EQ(pool.remove(entities, entities + 2u), 2u);
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 3u);
ASSERT_EQ(pool.begin()->value, 2);
ASSERT_EQ(pool.index(entities[2u]), 2u);
ASSERT_EQ(pool.remove(entities[2u]), 1u);
ASSERT_EQ(pool.remove(entities[2u]), 0u);
ASSERT_EQ(pool.remove(entities[2u]), 0u);
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 3u);
ASSERT_FALSE(pool.contains(entities[0u]));
ASSERT_FALSE(pool.contains(entities[1u]));
ASSERT_FALSE(pool.contains(entities[2u]));
pool.emplace(entities[0u], stable_type{0});
pool.emplace(entities[1u], stable_type{1});
pool.emplace(entities[2u], stable_type{2});
std::swap(entities[1u], entities[2u]);
ASSERT_EQ(pool.remove(entities, entities + 2u), 2u);
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 3u);
ASSERT_TRUE(pool.contains(entities[2u]));
ASSERT_EQ(pool.index(entities[2u]), 0u);
ASSERT_EQ(pool.get(entities[2u]).value, 1);
pool.compact();
ASSERT_FALSE(pool.empty());
ASSERT_EQ(*pool.begin(), 42);
ASSERT_EQ(pool.size(), 1u);
ASSERT_EQ(pool.begin()->value, 1);
pool.clear();
ASSERT_EQ(pool.size(), 0u);
pool.emplace(entities[0u], stable_type{0});
pool.emplace(entities[1u], stable_type{2});
pool.emplace(entities[2u], stable_type{1});
ASSERT_EQ(pool.remove(entities[2u]), 1u);
ASSERT_EQ(pool.remove(entities[2u]), 0u);
ASSERT_EQ(pool.remove(entities[0u]), 1u);
ASSERT_EQ(pool.remove(entities[1u]), 1u);
ASSERT_EQ(pool.remove(entities, entities + 2u), 0u);
ASSERT_EQ(pool.size(), 3u);
ASSERT_TRUE(pool.at(2u) == entt::tombstone);
pool.emplace(entities[0u], stable_type{99});
ASSERT_EQ((++pool.begin())->value, 99);
pool.emplace(entities[1u], stable_type{2});
pool.emplace(entities[2u], stable_type{1});
pool.emplace(entt::entity{0}, stable_type{7});
ASSERT_EQ(pool.size(), 4u);
ASSERT_EQ(pool.begin()->value, 7);
ASSERT_EQ(pool.at(0u), entities[1u]);
ASSERT_EQ(pool.at(1u), entities[0u]);
ASSERT_EQ(pool.at(2u), entities[2u]);
ASSERT_EQ(pool.get(entities[0u]).value, 99);
ASSERT_EQ(pool.get(entities[1u]).value, 2);
ASSERT_EQ(pool.get(entities[2u]).value, 1);
}
TEST(Storage, Compact) {
entt::storage<stable_type> pool;
ASSERT_TRUE(pool.empty());
ASSERT_EQ(pool.size(), 0u);
pool.compact();
ASSERT_TRUE(pool.empty());
ASSERT_EQ(pool.size(), 0u);
pool.emplace(entt::entity{0}, stable_type{0});
pool.compact();
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 1u);
pool.emplace(entt::entity{42}, stable_type{42});
pool.erase(entt::entity{0});
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.index(entt::entity{42}), 1u);
ASSERT_EQ(pool.get(entt::entity{42}).value, 42);
pool.compact();
ASSERT_EQ(pool.size(), 1u);
ASSERT_EQ(pool.index(entt::entity{42}), 0u);
ASSERT_EQ(pool.get(entt::entity{42}).value, 42);
pool.emplace(entt::entity{0}, stable_type{0});
pool.compact();
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.index(entt::entity{42}), 0u);
ASSERT_EQ(pool.index(entt::entity{0}), 1u);
ASSERT_EQ(pool.get(entt::entity{42}).value, 42);
ASSERT_EQ(pool.get(entt::entity{0}).value, 0);
pool.erase(entt::entity{0});
pool.erase(entt::entity{42});
pool.compact();
ASSERT_TRUE(pool.empty());
}
TEST(Storage, ShrinkToFit) {
entt::storage<int> pool;
for(std::size_t next{}; next < ENTT_PACKED_PAGE; ++next) {
pool.emplace(entt::entity(next));
}
pool.emplace(entt::entity{ENTT_PACKED_PAGE});
pool.erase(entt::entity{ENTT_PACKED_PAGE});
ASSERT_EQ(pool.capacity(), 2 * ENTT_PACKED_PAGE);
ASSERT_EQ(pool.size(), ENTT_PACKED_PAGE);
pool.shrink_to_fit();
ASSERT_EQ(pool.capacity(), ENTT_PACKED_PAGE);
ASSERT_EQ(pool.size(), ENTT_PACKED_PAGE);
pool.clear();
ASSERT_EQ(pool.capacity(), ENTT_PACKED_PAGE);
ASSERT_EQ(pool.size(), 0u);
pool.shrink_to_fit();
ASSERT_EQ(pool.capacity(), 0u);
ASSERT_EQ(pool.size(), 0u);
}
TEST(Storage, AggregatesMustWork) {
@@ -160,7 +572,7 @@ TEST(Storage, TypesFromStandardTemplateLibraryMustWork) {
// see #37 - this test shouldn't crash, that's all
entt::storage<std::unordered_set<int>> pool;
pool.emplace(entt::entity{0}).insert(42);
pool.remove(entt::entity{0});
pool.erase(entt::entity{0});
}
TEST(Storage, Iterator) {
@@ -196,7 +608,7 @@ TEST(Storage, Iterator) {
ASSERT_EQ(end - (end - begin), pool.begin());
ASSERT_EQ(end + (begin - end), pool.begin());
ASSERT_EQ(begin[0].value, pool.begin()->value);
ASSERT_EQ(begin[0u].value, pool.begin()->value);
ASSERT_LT(begin, end);
ASSERT_LE(begin, pool.begin());
@@ -238,7 +650,7 @@ TEST(Storage, ConstIterator) {
ASSERT_EQ(cend - (cend - cbegin), pool.cbegin());
ASSERT_EQ(cend + (cbegin - cend), pool.cbegin());
ASSERT_EQ(cbegin[0].value, pool.cbegin()->value);
ASSERT_EQ(cbegin[0u].value, pool.cbegin()->value);
ASSERT_LT(cbegin, cend);
ASSERT_LE(cbegin, pool.cbegin());
@@ -280,7 +692,7 @@ TEST(Storage, ReverseIterator) {
ASSERT_EQ(end - (end - begin), pool.rbegin());
ASSERT_EQ(end + (begin - end), pool.rbegin());
ASSERT_EQ(begin[0].value, pool.rbegin()->value);
ASSERT_EQ(begin[0u].value, pool.rbegin()->value);
ASSERT_LT(begin, end);
ASSERT_LE(begin, pool.rbegin());
@@ -322,7 +734,7 @@ TEST(Storage, ConstReverseIterator) {
ASSERT_EQ(cend - (cend - cbegin), pool.crbegin());
ASSERT_EQ(cend + (cbegin - cend), pool.crbegin());
ASSERT_EQ(cbegin[0].value, pool.crbegin()->value);
ASSERT_EQ(cbegin[0u].value, pool.crbegin()->value);
ASSERT_LT(cbegin, cend);
ASSERT_LE(cbegin, pool.crbegin());
@@ -342,9 +754,9 @@ TEST(Storage, Raw) {
ASSERT_EQ(std::as_const(pool).get(entt::entity{12}), 6);
ASSERT_EQ(pool.get(entt::entity{42}), 9);
ASSERT_EQ(pool.raw()[0u], 3);
ASSERT_EQ(std::as_const(pool).raw()[1u], 6);
ASSERT_EQ(pool.raw()[2u], 9);
ASSERT_EQ(pool.raw()[0u][0u], 3);
ASSERT_EQ(std::as_const(pool).raw()[0u][1u], 6);
ASSERT_EQ(pool.raw()[0u][2u], 9);
}
TEST(Storage, SortOrdered) {
@@ -352,7 +764,7 @@ TEST(Storage, SortOrdered) {
entt::entity entities[5u]{entt::entity{12}, entt::entity{42}, entt::entity{7}, entt::entity{3}, entt::entity{9}};
boxed_int values[5u]{{12}, {9}, {6}, {3}, {1}};
pool.insert(std::begin(entities), std::end(entities), std::begin(values), std::end(values));
pool.insert(std::begin(entities), std::end(entities), values);
pool.sort([](auto lhs, auto rhs) { return lhs.value < rhs.value; });
ASSERT_TRUE(std::equal(std::rbegin(entities), std::rend(entities), pool.entt::sparse_set::begin(), pool.entt::sparse_set::end()));
@@ -364,7 +776,7 @@ TEST(Storage, SortReverse) {
entt::entity entities[5u]{entt::entity{12}, entt::entity{42}, entt::entity{7}, entt::entity{3}, entt::entity{9}};
boxed_int values[5u]{{1}, {3}, {6}, {9}, {12}};
pool.insert(std::begin(entities), std::end(entities), std::begin(values), std::end(values));
pool.insert(std::begin(entities), std::end(entities), values);
pool.sort([](auto lhs, auto rhs) { return lhs.value < rhs.value; });
ASSERT_TRUE(std::equal(std::begin(entities), std::end(entities), pool.entt::sparse_set::begin(), pool.entt::sparse_set::end()));
@@ -376,7 +788,7 @@ TEST(Storage, SortUnordered) {
entt::entity entities[5u]{entt::entity{12}, entt::entity{42}, entt::entity{7}, entt::entity{3}, entt::entity{9}};
boxed_int values[5u]{{6}, {3}, {1}, {9}, {12}};
pool.insert(std::begin(entities), std::end(entities), std::begin(values), std::end(values));
pool.insert(std::begin(entities), std::end(entities), values);
pool.sort([](auto lhs, auto rhs) { return lhs.value < rhs.value; });
auto begin = pool.begin();
@@ -401,7 +813,7 @@ TEST(Storage, SortRange) {
entt::entity entities[5u]{entt::entity{12}, entt::entity{42}, entt::entity{7}, entt::entity{3}, entt::entity{9}};
boxed_int values[5u]{{3}, {6}, {1}, {9}, {12}};
pool.insert(std::begin(entities), std::end(entities), std::begin(values), std::end(values));
pool.insert(std::begin(entities), std::end(entities), values);
pool.sort_n(0u, [](auto lhs, auto rhs) { return lhs.value < rhs.value; });
ASSERT_TRUE(std::equal(std::rbegin(entities), std::rend(entities), pool.entt::sparse_set::begin(), pool.entt::sparse_set::end()));
@@ -409,9 +821,9 @@ TEST(Storage, SortRange) {
pool.sort_n(2u, [](auto lhs, auto rhs) { return lhs.value < rhs.value; });
ASSERT_EQ(pool.raw()[0u], boxed_int{6});
ASSERT_EQ(pool.raw()[1u], boxed_int{3});
ASSERT_EQ(pool.raw()[2u], boxed_int{1});
ASSERT_EQ(pool.raw()[0u][0u], boxed_int{6});
ASSERT_EQ(pool.raw()[0u][1u], boxed_int{3});
ASSERT_EQ(pool.raw()[0u][2u], boxed_int{1});
ASSERT_EQ(pool.data()[0u], entt::entity{42});
ASSERT_EQ(pool.data()[1u], entt::entity{12});
@@ -442,7 +854,7 @@ TEST(Storage, RespectDisjoint) {
entt::entity lhs_entities[3u]{entt::entity{3}, entt::entity{12}, entt::entity{42}};
int lhs_values[3u]{3, 6, 9};
lhs.insert(std::begin(lhs_entities), std::end(lhs_entities), std::begin(lhs_values), std::end(lhs_values));
lhs.insert(std::begin(lhs_entities), std::end(lhs_entities), lhs_values);
ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.entt::sparse_set::begin(), lhs.entt::sparse_set::end()));
ASSERT_TRUE(std::equal(std::rbegin(lhs_values), std::rend(lhs_values), lhs.begin(), lhs.end()));
@@ -459,11 +871,11 @@ TEST(Storage, RespectOverlap) {
entt::entity lhs_entities[3u]{entt::entity{3}, entt::entity{12}, entt::entity{42}};
int lhs_values[3u]{3, 6, 9};
lhs.insert(std::begin(lhs_entities), std::end(lhs_entities), std::begin(lhs_values), std::end(lhs_values));
lhs.insert(std::begin(lhs_entities), std::end(lhs_entities), lhs_values);
entt::entity rhs_entities[1u]{entt::entity{12}};
int rhs_values[1u]{6};
rhs.insert(std::begin(rhs_entities), std::end(rhs_entities), std::begin(rhs_values), std::end(rhs_values));
rhs.insert(std::begin(rhs_entities), std::end(rhs_entities), rhs_values);
ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.entt::sparse_set::begin(), lhs.entt::sparse_set::end()));
ASSERT_TRUE(std::equal(std::rbegin(lhs_values), std::rend(lhs_values), lhs.begin(), lhs.end()));
@@ -492,11 +904,11 @@ TEST(Storage, RespectOrdered) {
entt::entity lhs_entities[5u]{entt::entity{1}, entt::entity{2}, entt::entity{3}, entt::entity{4}, entt::entity{5}};
int lhs_values[5u]{1, 2, 3, 4, 5};
lhs.insert(std::begin(lhs_entities), std::end(lhs_entities), std::begin(lhs_values), std::end(lhs_values));
lhs.insert(std::begin(lhs_entities), std::end(lhs_entities), lhs_values);
entt::entity rhs_entities[6u]{entt::entity{6}, entt::entity{1}, entt::entity{2}, entt::entity{3}, entt::entity{4}, entt::entity{5}};
int rhs_values[6u]{6, 1, 2, 3, 4, 5};
rhs.insert(std::begin(rhs_entities), std::end(rhs_entities), std::begin(rhs_values), std::end(rhs_values));
rhs.insert(std::begin(rhs_entities), std::end(rhs_entities), rhs_values);
ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.entt::sparse_set::begin(), lhs.entt::sparse_set::end()));
ASSERT_TRUE(std::equal(std::rbegin(lhs_values), std::rend(lhs_values), lhs.begin(), lhs.end()));
@@ -516,11 +928,11 @@ TEST(Storage, RespectReverse) {
entt::entity lhs_entities[5u]{entt::entity{1}, entt::entity{2}, entt::entity{3}, entt::entity{4}, entt::entity{5}};
int lhs_values[5u]{1, 2, 3, 4, 5};
lhs.insert(std::begin(lhs_entities), std::end(lhs_entities), std::begin(lhs_values), std::end(lhs_values));
lhs.insert(std::begin(lhs_entities), std::end(lhs_entities), lhs_values);
entt::entity rhs_entities[6u]{entt::entity{5}, entt::entity{4}, entt::entity{3}, entt::entity{2}, entt::entity{1}, entt::entity{6}};
int rhs_values[6u]{5, 4, 3, 2, 1, 6};
rhs.insert(std::begin(rhs_entities), std::end(rhs_entities), std::begin(rhs_values), std::end(rhs_values));
rhs.insert(std::begin(rhs_entities), std::end(rhs_entities), rhs_values);
ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.entt::sparse_set::begin(), lhs.entt::sparse_set::end()));
ASSERT_TRUE(std::equal(std::rbegin(lhs_values), std::rend(lhs_values), lhs.begin(), lhs.end()));
@@ -555,11 +967,11 @@ TEST(Storage, RespectUnordered) {
entt::entity lhs_entities[5u]{entt::entity{1}, entt::entity{2}, entt::entity{3}, entt::entity{4}, entt::entity{5}};
int lhs_values[5u]{1, 2, 3, 4, 5};
lhs.insert(std::begin(lhs_entities), std::end(lhs_entities), std::begin(lhs_values), std::end(lhs_values));
lhs.insert(std::begin(lhs_entities), std::end(lhs_entities), lhs_values);
entt::entity rhs_entities[6u]{entt::entity{3}, entt::entity{2}, entt::entity{6}, entt::entity{1}, entt::entity{4}, entt::entity{5}};
int rhs_values[6u]{3, 2, 6, 1, 4, 5};
rhs.insert(std::begin(rhs_entities), std::end(rhs_entities), std::begin(rhs_values), std::end(rhs_values));
rhs.insert(std::begin(rhs_entities), std::end(rhs_entities), rhs_values);
ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.entt::sparse_set::begin(), lhs.entt::sparse_set::end()));
ASSERT_TRUE(std::equal(std::rbegin(lhs_values), std::rend(lhs_values), lhs.begin(), lhs.end()));
@@ -592,12 +1004,12 @@ TEST(Storage, CanModifyDuringIteration) {
entt::storage<int> pool;
pool.emplace(entt::entity{0}, 42);
ASSERT_EQ(pool.capacity(), 1u);
ASSERT_EQ(pool.capacity(), ENTT_PACKED_PAGE);
const auto it = pool.cbegin();
pool.reserve(2u);
pool.reserve(ENTT_PACKED_PAGE + 1u);
ASSERT_EQ(pool.capacity(), 2u);
ASSERT_EQ(pool.capacity(), 2 * ENTT_PACKED_PAGE);
// this should crash with asan enabled if we break the constraint
const auto entity = *it;
@@ -638,14 +1050,134 @@ TEST(Storage, MoveOnlyComponent) {
(void)pool;
}
TEST(Storage, ConstructorExceptionDoesNotAddToStorage) {
entt::storage<throwing_component> pool;
TEST(Storage, UpdateFromDestructor) {
static constexpr auto size = 10u;
auto test = [](const auto target) {
entt::storage<update_from_destructor> pool;
for(std::size_t next{}; next < size; ++next) {
const auto entity = entt::entity(next);
pool.emplace(entity, pool, entity == entt::entity(size/2) ? target : entity);
}
pool.erase(entt::entity(size/2));
ASSERT_EQ(pool.size(), size - 1u - (target != entt::null));
ASSERT_FALSE(pool.contains(entt::entity(size/2)));
ASSERT_FALSE(pool.contains(target));
pool.clear();
try {
pool.emplace(entt::entity{0});
} catch (const throwing_component::constructor_exception &) {
ASSERT_TRUE(pool.empty());
}
ASSERT_TRUE(pool.empty());
for(std::size_t next{}; next < size; ++next) {
ASSERT_FALSE(pool.contains(entt::entity(next)));
}
};
test(entt::entity(size - 1u));
test(entt::entity(size - 2u));
test(entt::entity{0u});
}
TEST(Storage, ThrowingComponent) {
entt::storage<test::throwing_component> pool;
test::throwing_component::trigger_on_value = 42;
// strong exception safety
ASSERT_THROW(pool.emplace(entt::entity{0}, test::throwing_component{42}), typename test::throwing_component::exception_type);
ASSERT_TRUE(pool.empty());
const entt::entity entities[2u]{entt::entity{42}, entt::entity{1}};
const test::throwing_component components[2u]{42, 1};
// basic exception safety
ASSERT_THROW(pool.insert(std::begin(entities), std::end(entities), test::throwing_component{42}), typename test::throwing_component::exception_type);
ASSERT_EQ(pool.size(), 0u);
ASSERT_FALSE(pool.contains(entt::entity{1}));
// basic exception safety
ASSERT_THROW(pool.insert(std::begin(entities), std::end(entities), std::begin(components)), typename test::throwing_component::exception_type);
ASSERT_EQ(pool.size(), 0u);
ASSERT_FALSE(pool.contains(entt::entity{1}));
// basic exception safety
ASSERT_THROW(pool.insert(std::rbegin(entities), std::rend(entities), std::rbegin(components)), typename test::throwing_component::exception_type);
ASSERT_EQ(pool.size(), 1u);
ASSERT_TRUE(pool.contains(entt::entity{1}));
ASSERT_EQ(pool.get(entt::entity{1}), 1);
pool.clear();
pool.emplace(entt::entity{1}, 1);
pool.emplace(entt::entity{42}, 42);
// basic exception safety
ASSERT_THROW(pool.erase(entt::entity{1}), typename test::throwing_component::exception_type);
ASSERT_EQ(pool.size(), 2u);
ASSERT_TRUE(pool.contains(entt::entity{42}));
ASSERT_TRUE(pool.contains(entt::entity{1}));
ASSERT_EQ(pool.at(0u), entt::entity{1});
ASSERT_EQ(pool.at(1u), entt::entity{42});
ASSERT_EQ(pool.get(entt::entity{42}), 42);
// the element may have been moved but it's still there
ASSERT_EQ(pool.get(entt::entity{1}), test::throwing_component::moved_from_value);
test::throwing_component::trigger_on_value = 99;
pool.erase(entt::entity{1});
ASSERT_EQ(pool.size(), 1u);
ASSERT_TRUE(pool.contains(entt::entity{42}));
ASSERT_FALSE(pool.contains(entt::entity{1}));
ASSERT_EQ(pool.at(0u), entt::entity{42});
ASSERT_EQ(pool.get(entt::entity{42}), 42);
}
TEST(Storage, ThrowingAllocator) {
entt::basic_storage<entt::entity, int, test::throwing_allocator<int>> pool;
test::throwing_allocator<int>::trigger_on_allocate = true;
// strong exception safety
ASSERT_THROW(pool.reserve(1u), test::throwing_allocator<int>::exception_type);
ASSERT_EQ(pool.capacity(), 0u);
test::throwing_allocator<int>::trigger_after_allocate = true;
// strong exception safety
ASSERT_THROW(pool.reserve(2 * ENTT_PACKED_PAGE), test::throwing_allocator<int>::exception_type);
ASSERT_EQ(pool.capacity(), 0u);
pool.shrink_to_fit();
test::throwing_allocator<int>::trigger_on_allocate = true;
// strong exception safety
ASSERT_THROW(pool.emplace(entt::entity{0}, 0), test::throwing_allocator<int>::exception_type);
ASSERT_FALSE(pool.contains(entt::entity{0}));
ASSERT_TRUE(pool.empty());
test::throwing_allocator<entt::entity>::trigger_on_allocate = true;
// strong exception safety
ASSERT_THROW(pool.emplace(entt::entity{0}, 0), test::throwing_allocator<entt::entity>::exception_type);
ASSERT_FALSE(pool.contains(entt::entity{0}));
ASSERT_TRUE(pool.empty());
pool.emplace(entt::entity{0}, 0);
const entt::entity entities[2u]{entt::entity{1}, entt::entity{ENTT_SPARSE_PAGE}};
test::throwing_allocator<entt::entity>::trigger_after_allocate = true;
// basic exception safety
ASSERT_THROW(pool.insert(std::begin(entities), std::end(entities), 0), test::throwing_allocator<entt::entity>::exception_type);
ASSERT_TRUE(pool.contains(entt::entity{1}));
ASSERT_FALSE(pool.contains(entt::entity{ENTT_SPARSE_PAGE}));
pool.erase(entt::entity{1});
const int components[2u]{1, ENTT_SPARSE_PAGE};
test::throwing_allocator<entt::entity>::trigger_on_allocate = true;
// basic exception safety
ASSERT_THROW(pool.insert(std::begin(entities), std::end(entities), std::begin(components)), test::throwing_allocator<entt::entity>::exception_type);
ASSERT_TRUE(pool.contains(entt::entity{1}));
ASSERT_FALSE(pool.contains(entt::entity{ENTT_SPARSE_PAGE}));
}

View File

@@ -0,0 +1,63 @@
#ifndef ENTT_ENTITY_THROWING_ALLOCATOR_HPP
#define ENTT_ENTITY_THROWING_ALLOCATOR_HPP
#include <cstddef>
#include <memory>
#include <type_traits>
namespace test {
template<typename Type>
class throwing_allocator {
template<typename Other>
friend class throwing_allocator;
struct test_exception {};
public:
using value_type = Type;
using pointer = value_type *;
using const_pointer = const value_type *;
using void_pointer = void *;
using const_void_pointer = const void *;
using propagate_on_container_move_assignment = std::true_type;
using exception_type = test_exception;
throwing_allocator() = default;
template<class Other>
throwing_allocator(const throwing_allocator<Other> &other)
: allocator{other.allocator}
{}
pointer allocate(std::size_t length) {
if(trigger_on_allocate) {
trigger_on_allocate = false;
throw test_exception{};
}
trigger_on_allocate = trigger_after_allocate;
trigger_after_allocate = false;
return allocator.allocate(length);
}
void deallocate(pointer mem, std::size_t length) {
allocator.deallocate(mem, length);
}
static inline bool trigger_on_allocate{};
static inline bool trigger_after_allocate{};
private:
std::allocator<Type> allocator;
};
}
#endif

View File

@@ -0,0 +1,52 @@
#ifndef ENTT_ENTITY_THROWING_COMPONENT_HPP
#define ENTT_ENTITY_THROWING_COMPONENT_HPP
namespace test {
class throwing_component {
struct test_exception {};
public:
using exception_type = test_exception;
static constexpr auto moved_from_value = -1;
throwing_component(int value)
: data{value}
{}
throwing_component(const throwing_component &other)
: data{other.data}
{
if(data == trigger_on_value) {
data = moved_from_value;
throw exception_type{};
}
}
throwing_component & operator=(const throwing_component &other) {
if(other.data == trigger_on_value) {
data = moved_from_value;
throw exception_type{};
}
data = other.data;
return *this;
}
operator int() const {
return data;
}
static inline int trigger_on_value{};
private:
int data{};
};
}
#endif

View File

@@ -1,11 +1,19 @@
#include <algorithm>
#include <tuple>
#include <utility>
#include <type_traits>
#include <gtest/gtest.h>
#include <entt/entity/component.hpp>
#include <entt/entity/registry.hpp>
#include <entt/entity/view.hpp>
struct empty_type {};
struct stable_type { int value; };
template<>
struct entt::component_traits<stable_type>: basic_component_traits {
using in_place_delete = std::true_type;
};
TEST(SingleComponentView, Functionalities) {
entt::registry registry;
@@ -20,10 +28,10 @@ TEST(SingleComponentView, Functionalities) {
registry.emplace<int>(e1);
registry.emplace<char>(e1);
ASSERT_NO_THROW(view.begin()++);
ASSERT_NO_THROW(++cview.begin());
ASSERT_NO_THROW([](auto it) { return it++; }(view.rbegin()));
ASSERT_NO_THROW([](auto it) { return ++it; }(cview.rbegin()));
ASSERT_NO_FATAL_FAILURE(view.begin()++);
ASSERT_NO_FATAL_FAILURE(++cview.begin());
ASSERT_NO_FATAL_FAILURE([](auto it) { return it++; }(view.rbegin()));
ASSERT_NO_FATAL_FAILURE([](auto it) { return ++it; }(cview.rbegin()));
ASSERT_NE(view.begin(), view.end());
ASSERT_NE(cview.begin(), cview.end());
@@ -43,14 +51,14 @@ TEST(SingleComponentView, Functionalities) {
ASSERT_TRUE(cview.get<const char>(entity) == '1' || std::get<const char &>(cview.get(entity)) == '2');
}
ASSERT_EQ(*(view.data() + 0), e1);
ASSERT_EQ(*(view.data() + 1), e0);
ASSERT_EQ(view.data()[0u], e1);
ASSERT_EQ(view.data()[1u], e0);
ASSERT_EQ(*(view.raw() + 0), '2');
ASSERT_EQ(*(cview.raw() + 1), '1');
ASSERT_EQ(view.raw()[0u][0u], '2');
ASSERT_EQ(cview.raw()[0u][1u], '1');
registry.remove<char>(e0);
registry.remove<char>(e1);
registry.erase<char>(e0);
registry.erase<char>(e1);
ASSERT_EQ(view.begin(), view.end());
ASSERT_EQ(view.rbegin(), view.rend());
@@ -63,7 +71,34 @@ TEST(SingleComponentView, Functionalities) {
ASSERT_FALSE(invalid);
}
TEST(SingleComponentView, Invalid) {
TEST(SingleComponentView, RawData) {
entt::registry registry;
auto view = registry.view<int>();
auto cview = std::as_const(registry).view<const int>();
const auto entity = registry.create();
ASSERT_EQ(view.size(), 0u);
ASSERT_EQ(cview.size(), 0u);
ASSERT_EQ(view.raw(), cview.raw());
ASSERT_EQ(view.data(), cview.data());
registry.emplace<int>(entity, 42);
ASSERT_NE(view.size(), 0u);
ASSERT_NE(cview.size(), 0u);
ASSERT_EQ(view.raw()[0u][0u], 42);
ASSERT_EQ(cview.raw()[0u][0u], 42);
ASSERT_EQ(view.data()[0u], entity);
ASSERT_EQ(cview.data()[0u], entity);
registry.destroy(entity);
ASSERT_EQ(view.size(), 0u);
ASSERT_EQ(cview.size(), 0u);
}
TEST(SingleComponentView, LazyTypeFromConstRegistry) {
entt::registry registry{};
auto eview = std::as_const(registry).view<const empty_type>();
auto cview = std::as_const(registry).view<const int>();
@@ -72,34 +107,21 @@ TEST(SingleComponentView, Invalid) {
registry.emplace<empty_type>(entity);
registry.emplace<int>(entity);
ASSERT_FALSE(cview);
ASSERT_FALSE(eview);
ASSERT_TRUE(cview);
ASSERT_TRUE(eview);
ASSERT_TRUE(cview.empty());
ASSERT_EQ(eview.size(), 0u);
ASSERT_NE(cview.raw(), nullptr);
ASSERT_NE(eview.data(), nullptr);
ASSERT_EQ(cview.raw(), nullptr);
ASSERT_EQ(eview.data(), nullptr);
ASSERT_FALSE(cview.empty());
ASSERT_EQ(eview.size(), 1u);
ASSERT_TRUE(cview.contains(entity));
ASSERT_EQ(cview.begin(), cview.end());
ASSERT_EQ(eview.rbegin(), eview.rend());
ASSERT_FALSE(cview.contains(entity));
ASSERT_EQ(eview.find(entity), eview.end());
ASSERT_EQ(cview.front(), entt::entity{entt::null});
ASSERT_EQ(eview.back(), entt::entity{entt::null});
cview.each([](const auto, const auto &) { FAIL(); });
cview.each([](const auto &) { FAIL(); });
eview.each([](const auto) { FAIL(); });
eview.each([]() { FAIL(); });
for([[maybe_unused]] auto all: cview.each()) { FAIL(); }
for(auto first = cview.each().rbegin(), last = cview.each().rend(); first != last; ++first) { FAIL(); }
for([[maybe_unused]] auto entt: eview.each()) { FAIL(); }
for(auto first = eview.each().rbegin(), last = eview.each().rend(); first != last; ++first) { FAIL(); }
ASSERT_NE(cview.begin(), cview.end());
ASSERT_NE(eview.rbegin(), eview.rend());
ASSERT_NE(eview.find(entity), eview.end());
ASSERT_EQ(cview.front(), entity);
ASSERT_EQ(eview.back(), entity);
}
TEST(SingleComponentView, ElementAccess) {
@@ -160,10 +182,14 @@ TEST(SingleComponentView, Each) {
registry.emplace<int>(registry.create(), 1);
auto view = registry.view<int>();
auto iterable = view.each();
auto cview = std::as_const(registry).view<const int>();
auto citerable = cview.each();
std::size_t cnt = 0;
for(auto first = cview.each().rbegin(), last = cview.each().rend(); first != last; ++first) {
for(auto first = citerable.rbegin(), last = citerable.rend(); first != last; ++first) {
static_assert(std::is_same_v<decltype(*first), std::tuple<entt::entity, const int &>>);
ASSERT_EQ(std::get<1>(*first), cnt++);
}
@@ -176,13 +202,20 @@ TEST(SingleComponentView, Each) {
cview.each([&cnt](const int &) { --cnt; });
cview.each([&cnt](auto, const int &) { --cnt; });
for(auto &&[entt, iv]: view.each()) {
// do not use iterable, make sure an iterable view works when created from a temporary
for(auto [entt, iv]: registry.view<int>().each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>);
ASSERT_EQ(iv, --cnt);
}
ASSERT_EQ(cnt, std::size_t{0});
auto it = iterable.begin();
auto rit = iterable.rbegin();
ASSERT_EQ((it++, ++it), iterable.end());
ASSERT_EQ((rit++, ++rit), iterable.rend());
}
TEST(SingleComponentView, ConstNonConstAndAllInBetween) {
@@ -198,15 +231,17 @@ TEST(SingleComponentView, ConstNonConstAndAllInBetween) {
ASSERT_EQ(view.size(), 1u);
ASSERT_EQ(cview.size(), 1u);
static_assert(std::is_same_v<typename decltype(view)::raw_type, int>);
static_assert(std::is_same_v<typename decltype(cview)::raw_type, const int>);
static_assert(std::is_same_v<decltype(view.raw()), int **>);
static_assert(std::is_same_v<decltype(cview.raw()), const int * const *>);
static_assert(std::is_same_v<decltype(view.get<int>({})), int &>);
static_assert(std::is_same_v<decltype(view.get({})), std::tuple<int &>>);
static_assert(std::is_same_v<decltype(view.raw()), int *>);
static_assert(std::is_same_v<decltype(view.raw()), int **>);
static_assert(std::is_same_v<decltype(cview.get<const int>({})), const int &>);
static_assert(std::is_same_v<decltype(cview.get({})), std::tuple<const int &>>);
static_assert(std::is_same_v<decltype(cview.raw()), const int *>);
static_assert(std::is_same_v<decltype(cview.raw()), const int * const *>);
static_assert(std::is_same_v<decltype(std::as_const(registry).view<int>()), decltype(cview)>);
view.each([](auto &&i) {
static_assert(std::is_same_v<decltype(i), int &>);
@@ -216,12 +251,12 @@ TEST(SingleComponentView, ConstNonConstAndAllInBetween) {
static_assert(std::is_same_v<decltype(i), const int &>);
});
for(auto &&[entt, iv]: view.each()) {
for(auto [entt, iv]: view.each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>);
}
for(auto &&[entt, iv]: cview.each()) {
for(auto [entt, iv]: cview.each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), const int &>);
}
@@ -240,17 +275,16 @@ TEST(SingleComponentView, ConstNonConstAndAllInBetweenWithEmptyType) {
ASSERT_EQ(view.size(), 1u);
ASSERT_EQ(cview.size(), 1u);
static_assert(std::is_same_v<typename decltype(view)::raw_type, empty_type>);
static_assert(std::is_same_v<typename decltype(cview)::raw_type, const empty_type>);
static_assert(std::is_same_v<decltype(view.get({})), std::tuple<>>);
static_assert(std::is_same_v<decltype(cview.get({})), std::tuple<>>);
for(auto &&[entt]: view.each()) {
static_assert(std::is_same_v<decltype(std::as_const(registry).view<empty_type>()), decltype(cview)>);
for(auto [entt]: view.each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
}
for(auto &&[entt]: cview.each()) {
for(auto [entt]: cview.each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
}
}
@@ -271,7 +305,7 @@ TEST(SingleComponentView, Find) {
const auto e3 = registry.create();
registry.emplace<int>(e3);
registry.remove<int>(e1);
registry.erase<int>(e1);
ASSERT_NE(view.find(e0), view.end());
ASSERT_EQ(view.find(e1), view.end());
@@ -315,7 +349,7 @@ TEST(SingleComponentView, EmptyTypes) {
check = false;
});
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>);
ASSERT_EQ(entity, entt);
}
@@ -329,7 +363,7 @@ TEST(SingleComponentView, EmptyTypes) {
check = false;
});
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(iv), int &>);
ASSERT_EQ(entity, entt);
@@ -355,10 +389,70 @@ TEST(SingleComponentView, FrontBack) {
TEST(SingleComponentView, DeductionGuide) {
entt::registry registry;
typename entt::storage_traits<entt::entity, int>::storage_type storage;
typename entt::storage_traits<entt::entity, int>::storage_type istorage;
typename entt::storage_traits<entt::entity, stable_type>::storage_type sstorage;
static_assert(std::is_same_v<decltype(entt::basic_view{storage}), entt::basic_view<entt::entity, entt::exclude_t<>, int>>);
static_assert(std::is_same_v<decltype(entt::basic_view{std::as_const(storage)}), entt::basic_view<entt::entity, entt::exclude_t<>, const int>>);
static_assert(std::is_same_v<entt::basic_view<entt::entity, entt::exclude_t<>, int>, decltype(entt::basic_view{istorage})>);
static_assert(std::is_same_v<entt::basic_view<entt::entity, entt::exclude_t<>, const int>, decltype(entt::basic_view{std::as_const(istorage)})>);
static_assert(std::is_same_v<entt::basic_view<entt::entity, entt::exclude_t<>, stable_type>, decltype(entt::basic_view{sstorage})>);
static_assert(std::is_base_of_v<entt::basic_view_impl<entt::packed_storage_policy, entt::entity, entt::exclude_t<>, int>, decltype(entt::basic_view{istorage})>);
static_assert(std::is_base_of_v<entt::basic_view_impl<entt::packed_storage_policy, entt::entity, entt::exclude_t<>, const int>, decltype(entt::basic_view{std::as_const(istorage)})>);
static_assert(std::is_base_of_v<entt::basic_view_impl<entt::stable_storage_policy, entt::entity, entt::exclude_t<>, stable_type>, decltype(entt::basic_view{sstorage})>);
}
TEST(SingleComponentView, IterableViewAlgorithmCompatibility) {
entt::registry registry;
const auto entity = registry.create();
registry.emplace<int>(entity);
const auto view = registry.view<int>();
const auto iterable = view.each();
const auto it = std::find_if(iterable.begin(), iterable.end(), [entity](auto args) { return std::get<0>(args) == entity; });
ASSERT_EQ(std::get<0>(*it), entity);
}
TEST(SingleComponentView, StableType) {
entt::registry registry;
auto view = registry.view<stable_type>();
const auto entity = registry.create();
const auto other = registry.create();
registry.emplace<stable_type>(entity);
registry.emplace<stable_type>(other);
registry.destroy(entity);
ASSERT_EQ(view.size_hint(), 2u);
ASSERT_FALSE(view.contains(entity));
ASSERT_TRUE(view.contains(other));
ASSERT_EQ(view.front(), other);
ASSERT_EQ(view.back(), other);
ASSERT_EQ(*view.begin(), other);
ASSERT_EQ(++view.begin(), view.end());
view.each([other](const auto entt, stable_type) {
ASSERT_EQ(other, entt);
});
view.each([check = true](stable_type) mutable {
ASSERT_TRUE(check);
check = false;
});
for(auto [entt, st]: view.each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(st), stable_type &>);
ASSERT_EQ(other, entt);
}
registry.compact();
ASSERT_EQ(view.size_hint(), 1u);
}
TEST(MultiComponentView, Functionalities) {
@@ -378,10 +472,10 @@ TEST(MultiComponentView, Functionalities) {
ASSERT_EQ(++view.begin(), (view.end()));
ASSERT_EQ(++view.rbegin(), (view.rend()));
ASSERT_NO_THROW((view.begin()++));
ASSERT_NO_THROW((++cview.begin()));
ASSERT_NO_THROW(view.rbegin()++);
ASSERT_NO_THROW(++cview.rbegin());
ASSERT_NO_FATAL_FAILURE((view.begin()++));
ASSERT_NO_FATAL_FAILURE((++cview.begin()));
ASSERT_NO_FATAL_FAILURE(view.rbegin()++);
ASSERT_NO_FATAL_FAILURE(++cview.rbegin());
ASSERT_NE(view.begin(), view.end());
ASSERT_NE(cview.begin(), cview.end());
@@ -402,7 +496,7 @@ TEST(MultiComponentView, Functionalities) {
ASSERT_FALSE(invalid);
}
TEST(MultiComponentView, Invalid) {
TEST(MultiComponentView, LazyTypesFromConstRegistry) {
entt::registry registry{};
auto view = std::as_const(registry).view<const empty_type, const int>();
@@ -410,22 +504,34 @@ TEST(MultiComponentView, Invalid) {
registry.emplace<empty_type>(entity);
registry.emplace<int>(entity);
ASSERT_FALSE(view);
ASSERT_TRUE(view);
ASSERT_EQ(view.size_hint(), 0u);
ASSERT_EQ(view.size_hint(), 1u);
ASSERT_TRUE(view.contains(entity));
ASSERT_EQ(view.begin(), view.end());
ASSERT_NE(view.begin(), view.end());
ASSERT_NE(view.find(entity), view.end());
ASSERT_EQ(view.front(), entity);
ASSERT_EQ(view.back(), entity);
}
ASSERT_FALSE(view.contains(entity));
ASSERT_EQ(view.find(entity), view.end());
ASSERT_EQ(view.front(), entt::entity{entt::null});
ASSERT_EQ(view.back(), entt::entity{entt::null});
TEST(MultiComponentView, LazyExcludedTypeFromConstRegistry) {
entt::registry registry;
view.each([](const auto, const auto &) { FAIL(); });
view.each([](const auto &) { FAIL(); });
auto entity = registry.create();
registry.emplace<int>(entity);
for([[maybe_unused]] auto all: view.each()) { FAIL(); }
for(auto first = view.each().rbegin(), last = view.each().rend(); first != last; ++first) { FAIL(); }
auto view = std::as_const(registry).view<const int>(entt::exclude<char>);
ASSERT_TRUE(view);
ASSERT_EQ(view.size_hint(), 1u);
ASSERT_TRUE(view.contains(entity));
ASSERT_NE(view.begin(), view.end());
ASSERT_NE(view.find(entity), view.end());
ASSERT_EQ(view.front(), entity);
ASSERT_EQ(view.back(), entity);
}
TEST(MultiComponentView, Iterator) {
@@ -454,6 +560,21 @@ TEST(MultiComponentView, Iterator) {
ASSERT_EQ(*begin, entity);
ASSERT_EQ(*begin.operator->(), entity);
registry.emplace<int>(registry.create());
registry.emplace<char>(registry.create());
const auto other = registry.create();
registry.emplace<int>(other);
registry.emplace<char>(other);
begin = view.begin();
ASSERT_EQ(*(begin++), other);
ASSERT_EQ(*(begin++), entity);
ASSERT_EQ(begin--, end);
ASSERT_EQ(*(begin--), entity);
ASSERT_EQ(*begin, other);
}
TEST(MultiComponentView, ReverseIterator) {
@@ -534,10 +655,14 @@ TEST(MultiComponentView, Each) {
registry.emplace<char>(e1);
auto view = registry.view<int, char>();
auto iterable = view.each();
auto cview = std::as_const(registry).view<const int, const char>();
auto citerable = cview.each();
std::size_t cnt = 0;
for(auto first = cview.each().rbegin(), last = cview.each().rend(); first != last; ++first) {
for(auto first = citerable.rbegin(), last = citerable.rend(); first != last; ++first) {
static_assert(std::is_same_v<decltype(*first), std::tuple<entt::entity, const int &, const char &>>);
ASSERT_EQ(std::get<1>(*first), cnt++);
}
@@ -550,7 +675,8 @@ TEST(MultiComponentView, Each) {
cview.each([&cnt](const int &, const char &) { --cnt; });
cview.each([&cnt](auto, const int &, const char &) { --cnt; });
for(auto &&[entt, iv, cv]: view.each()) {
// do not use iterable, make sure an iterable view works when created from a temporary
for(auto [entt, iv, cv]: registry.view<int, char>().each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), char &>);
@@ -558,6 +684,12 @@ TEST(MultiComponentView, Each) {
}
ASSERT_EQ(cnt, std::size_t{0});
auto it = iterable.begin();
auto rit = iterable.rbegin();
ASSERT_EQ((it++, ++it), iterable.end());
ASSERT_EQ((rit++, ++rit), iterable.rend());
}
TEST(MultiComponentView, EachWithSuggestedType) {
@@ -652,12 +784,16 @@ TEST(MultiComponentView, ConstNonConstAndAllInBetween) {
static_assert(std::is_same_v<decltype(view.get<int, const char>({})), std::tuple<int &, const char &>>);
static_assert(std::is_same_v<decltype(view.get({})), std::tuple<int &, const char &>>);
static_assert(std::is_same_v<decltype(std::as_const(registry).view<char, int>()), decltype(std::as_const(registry).view<const char, const int>())>);
static_assert(std::is_same_v<decltype(std::as_const(registry).view<char, const int>()), decltype(std::as_const(registry).view<const char, const int>())>);
static_assert(std::is_same_v<decltype(std::as_const(registry).view<const char, int>()), decltype(std::as_const(registry).view<const char, const int>())>);
view.each([](auto &&i, auto &&c) {
static_assert(std::is_same_v<decltype(i), int &>);
static_assert(std::is_same_v<decltype(c), const char &>);
});
for(auto &&[entt, iv, cv]: view.each()) {
for(auto [entt, iv, cv]: view.each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), const char &>);
@@ -684,7 +820,7 @@ TEST(MultiComponentView, Find) {
registry.emplace<int>(e3);
registry.emplace<char>(e3);
registry.remove<int>(e1);
registry.erase<int>(e1);
ASSERT_NE(view.find(e0), view.end());
ASSERT_EQ(view.find(e1), view.end());
@@ -740,8 +876,8 @@ TEST(MultiComponentView, ExcludedComponents) {
registry.emplace<char>(e0);
registry.emplace<char>(e2);
registry.remove<char>(e1);
registry.remove<char>(e3);
registry.erase<char>(e1);
registry.erase<char>(e3);
for(const auto entity: view) {
ASSERT_TRUE(entity == e1 || entity == e3);
@@ -760,81 +896,74 @@ TEST(MultiComponentView, EmptyTypes) {
const auto entity = registry.create();
registry.emplace<int>(entity);
registry.emplace<char>(entity);
registry.emplace<double>(entity);
registry.emplace<empty_type>(entity);
const auto other = registry.create();
registry.emplace<int>(other);
registry.emplace<char>(other);
registry.emplace<double>(other);
registry.emplace<empty_type>(other);
registry.view<int, char, empty_type>().each([entity](const auto entt, int, char) {
const auto ignored = registry.create();
registry.emplace<int>(ignored);
registry.emplace<char>(ignored);
registry.view<int, char, empty_type>(entt::exclude<double>).each([entity](const auto entt, int, char) {
ASSERT_EQ(entity, entt);
});
for(auto &&[entt, iv, cv]: registry.view<int, char, empty_type>().each()) {
for(auto [entt, iv, cv]: registry.view<int, char, empty_type>(entt::exclude<double>).each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), char &>);
ASSERT_EQ(entity, entt);
}
registry.view<int, empty_type, char>().each([check = true](int, char) mutable {
registry.view<int, empty_type, char>(entt::exclude<double>).each([check = true](int, char) mutable {
ASSERT_TRUE(check);
check = false;
});
for(auto &&[entt, iv, cv]: registry.view<int, empty_type, char>().each()) {
for(auto [entt, iv, cv]: registry.view<int, empty_type, char>(entt::exclude<double>).each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), char &>);
ASSERT_EQ(entity, entt);
}
registry.view<empty_type, int, char>().each([entity](const auto entt, int, char) {
registry.view<empty_type, int, char>(entt::exclude<double>).each([entity](const auto entt, int, char) {
ASSERT_EQ(entity, entt);
});
for(auto &&[entt, iv, cv]: registry.view<empty_type, int, char>().each()) {
for(auto [entt, iv, cv]: registry.view<empty_type, int, char>(entt::exclude<double>).each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), char &>);
ASSERT_EQ(entity, entt);
}
registry.view<empty_type, int, char>().each<empty_type>([entity](const auto entt, int, char) {
registry.view<empty_type, int, char>(entt::exclude<double>).each<empty_type>([entity](const auto entt, int, char) {
ASSERT_EQ(entity, entt);
});
for(auto &&[entt, iv, cv]: registry.view<empty_type, int, char>().each<empty_type>()) {
for(auto [entt, iv, cv]: registry.view<empty_type, int, char>(entt::exclude<double>).each<empty_type>()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), char &>);
ASSERT_EQ(entity, entt);
}
registry.view<int, empty_type, char>().each<empty_type>([check = true](int, char) mutable {
registry.view<int, empty_type, char>(entt::exclude<double>).each<empty_type>([check = true](int, char) mutable {
ASSERT_TRUE(check);
check = false;
});
for(auto &&[entt, iv, cv]: registry.view<int, empty_type, char>().each<empty_type>()) {
for(auto [entt, iv, cv]: registry.view<int, empty_type, char>(entt::exclude<double>).each<empty_type>()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), char &>);
ASSERT_EQ(entity, entt);
}
registry.view<int, char, double>().each([entity](const auto entt, int, char, double) {
ASSERT_EQ(entity, entt);
});
for(auto &&[entt, iv, cv, dv]: registry.view<int, char, double>().each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), char &>);
static_assert(std::is_same_v<decltype(dv), double &>);
ASSERT_EQ(entity, entt);
}
}
TEST(MultiComponentView, FrontBack) {
@@ -870,9 +999,118 @@ TEST(MultiComponentView, DeductionGuide) {
entt::registry registry;
typename entt::storage_traits<entt::entity, int>::storage_type istorage;
typename entt::storage_traits<entt::entity, double>::storage_type dstorage;
typename entt::storage_traits<entt::entity, stable_type>::storage_type sstorage;
static_assert(std::is_same_v<decltype(entt::basic_view{istorage, dstorage}), entt::basic_view<entt::entity, entt::exclude_t<>, int, double>>);
static_assert(std::is_same_v<decltype(entt::basic_view{std::as_const(istorage), dstorage}), entt::basic_view<entt::entity, entt::exclude_t<>, const int, double>>);
static_assert(std::is_same_v<decltype(entt::basic_view{istorage, std::as_const(dstorage)}), entt::basic_view<entt::entity, entt::exclude_t<>, int, const double>>);
static_assert(std::is_same_v<decltype(entt::basic_view{std::as_const(istorage), std::as_const(dstorage)}), entt::basic_view<entt::entity, entt::exclude_t<>, const int, const double>>);
static_assert(std::is_same_v<entt::basic_view<entt::entity, entt::exclude_t<>, int, double>, decltype(entt::basic_view{istorage, dstorage})>);
static_assert(std::is_same_v<entt::basic_view<entt::entity, entt::exclude_t<>, const int, double>, decltype(entt::basic_view{std::as_const(istorage), dstorage})>);
static_assert(std::is_same_v<entt::basic_view<entt::entity, entt::exclude_t<>, int, const double>, decltype(entt::basic_view{istorage, std::as_const(dstorage)})>);
static_assert(std::is_same_v<entt::basic_view<entt::entity, entt::exclude_t<>, const int, const double>, decltype(entt::basic_view{std::as_const(istorage), std::as_const(dstorage)})>);
static_assert(std::is_same_v<entt::basic_view<entt::entity, entt::exclude_t<>, int, stable_type>, decltype(entt::basic_view{istorage, sstorage})>);
static_assert(std::is_base_of_v<entt::basic_view_impl<entt::packed_storage_policy, entt::entity, entt::exclude_t<>, int, double>, decltype(entt::basic_view{istorage, dstorage})>);
static_assert(std::is_base_of_v<entt::basic_view_impl<entt::packed_storage_policy, entt::entity, entt::exclude_t<>, const int, double>, decltype(entt::basic_view{std::as_const(istorage), dstorage})>);
static_assert(std::is_base_of_v<entt::basic_view_impl<entt::packed_storage_policy, entt::entity, entt::exclude_t<>, int, const double>, decltype(entt::basic_view{istorage, std::as_const(dstorage)})>);
static_assert(std::is_base_of_v<entt::basic_view_impl<entt::packed_storage_policy, entt::entity, entt::exclude_t<>, const int, const double>, decltype(entt::basic_view{std::as_const(istorage), std::as_const(dstorage)})>);
static_assert(std::is_base_of_v<entt::basic_view_impl<entt::stable_storage_policy, entt::entity, entt::exclude_t<>, int, stable_type>, decltype(entt::basic_view{istorage, sstorage})>);
}
TEST(MultiComponentView, IterableViewAlgorithmCompatibility) {
entt::registry registry;
const auto entity = registry.create();
registry.emplace<int>(entity);
registry.emplace<char>(entity);
const auto view = registry.view<int, char>();
const auto iterable = view.each();
const auto it = std::find_if(iterable.begin(), iterable.end(), [entity](auto args) { return std::get<0>(args) == entity; });
ASSERT_EQ(std::get<0>(*it), entity);
}
TEST(MultiComponentView, StableType) {
entt::registry registry;
auto view = registry.view<int, stable_type>();
const auto entity = registry.create();
const auto other = registry.create();
registry.emplace<int>(entity);
registry.emplace<int>(other);
registry.emplace<stable_type>(entity);
registry.emplace<stable_type>(other);
registry.destroy(entity);
ASSERT_EQ(view.size_hint(), 1u);
view.use<stable_type>();
ASSERT_EQ(view.size_hint(), 2u);
ASSERT_FALSE(view.contains(entity));
ASSERT_TRUE(view.contains(other));
ASSERT_EQ(view.front(), other);
ASSERT_EQ(view.back(), other);
ASSERT_EQ(*view.begin(), other);
ASSERT_EQ(++view.begin(), view.end());
view.each([other](const auto entt, int, stable_type) {
ASSERT_EQ(other, entt);
});
view.each([check = true](int, stable_type) mutable {
ASSERT_TRUE(check);
check = false;
});
for(auto [entt, iv, st]: view.each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(st), stable_type &>);
ASSERT_EQ(other, entt);
}
registry.compact();
ASSERT_EQ(view.size_hint(), 1u);
}
TEST(View, Pipe) {
entt::registry registry;
const auto entity = registry.create();
const auto other = registry.create();
registry.emplace<int>(entity);
registry.emplace<char>(entity);
registry.emplace<double>(entity);
registry.emplace<empty_type>(entity);
registry.emplace<int>(other);
registry.emplace<char>(other);
registry.emplace<stable_type>(other);
const auto view1 = registry.view<int>(entt::exclude<double>);
const auto view2 = registry.view<const char>(entt::exclude<float>);
const auto view3 = registry.view<empty_type>();
const auto view4 = registry.view<stable_type>();
static_assert(std::is_same_v<entt::basic_view<entt::entity, entt::exclude_t<double, float>, int, const char>, decltype(view1 | view2)>);
static_assert(std::is_same_v<entt::basic_view<entt::entity, entt::exclude_t<float, double>, const char, int>, decltype(view2 | view1)>);
static_assert(std::is_same_v<decltype((view1 | view2) | view3), decltype(view1 | (view2 | view3))>);
static_assert(std::is_base_of_v<entt::basic_view_impl<entt::packed_storage_policy, entt::entity, entt::exclude_t<double, float>, int, const char>, decltype(view1 | view2)>);
static_assert(std::is_base_of_v<entt::basic_view_impl<entt::stable_storage_policy, entt::entity, entt::exclude_t<double, float>, int, stable_type, const char>, decltype(view1 | view4 | view2)>);
ASSERT_FALSE((view1 | view2).contains(entity));
ASSERT_TRUE((view1 | view2).contains(other));
ASSERT_TRUE((view2 | view3).contains(entity));
ASSERT_FALSE((view2 | view3).contains(other));
ASSERT_FALSE((view1 | view2 | view3).contains(entity));
ASSERT_FALSE((view1 | view2 | view3).contains(other));
ASSERT_FALSE((view1 | view4 | view2).contains(entity));
ASSERT_TRUE((view1 | view4 | view2).contains(other));
}

View File

@@ -1,420 +0,0 @@
#include <cstdint>
#include <iterator>
#include <type_traits>
#include <gtest/gtest.h>
#include <entt/entity/registry.hpp>
#include <entt/entity/view.hpp>
#include <entt/entity/view_pack.hpp>
struct empty_type {};
TEST(ViewPack, Construction) {
entt::registry registry;
const auto view1 = registry.view<int, const char>();
const auto view2 = registry.view<empty_type>();
const auto view3 = registry.view<double>();
static_assert(std::is_same_v<decltype(entt::view_pack{view1}), entt::view_pack<entt::view<entt::exclude_t<>, int, const char>>>);
static_assert(std::is_same_v<decltype(entt::view_pack{view1, view2, view3}), decltype(view1 | view2 | view3)>);
static_assert(std::is_same_v<
decltype(entt::view_pack{view1, view2, view3}),
entt::view_pack<
entt::view<entt::exclude_t<>, int, const char>,
entt::view<entt::exclude_t<>, empty_type>,
entt::view<entt::exclude_t<>, double>
>
>);
static_assert(std::is_same_v<decltype(entt::view_pack{view1, view2, view3}), decltype(entt::view_pack{view1} | view2 | view3)>);
static_assert(std::is_same_v<decltype(entt::view_pack{view1, view2, view3}), decltype(view1 | entt::view_pack{view2} | view3)>);
static_assert(std::is_same_v<decltype(entt::view_pack{view1, view2, view3}), decltype(view1 | view2 | entt::view_pack{view3})>);
}
TEST(ViewPack, Functionalities) {
entt::registry registry;
const auto pack = registry.view<int>() | registry.view<char>();
const auto cpack = registry.view<const int>() | registry.view<const char>();
const auto e0 = registry.create();
registry.emplace<char>(e0, '1');
const auto e1 = registry.create();
registry.emplace<int>(e1, 42);
registry.emplace<char>(e1, '2');
ASSERT_EQ(*pack.begin(), e1);
ASSERT_EQ(*pack.rbegin(), e1);
ASSERT_EQ(++pack.begin(), (pack.end()));
ASSERT_EQ(++pack.rbegin(), (pack.rend()));
ASSERT_NO_THROW((pack.begin()++));
ASSERT_NO_THROW((++cpack.begin()));
ASSERT_NO_THROW(pack.rbegin()++);
ASSERT_NO_THROW(++cpack.rbegin());
ASSERT_NE(pack.begin(), pack.end());
ASSERT_NE(cpack.begin(), cpack.end());
ASSERT_NE(pack.rbegin(), pack.rend());
ASSERT_NE(cpack.rbegin(), cpack.rend());
for(auto entity: pack) {
ASSERT_EQ(std::get<0>(cpack.get<const int, const char>(entity)), 42);
ASSERT_EQ(std::get<1>(pack.get<int, char>(entity)), '2');
ASSERT_EQ(cpack.get<const char>(entity), '2');
}
}
TEST(ViewPack, Iterator) {
entt::registry registry;
const auto entity = registry.create();
registry.emplace<int>(entity);
registry.emplace<char>(entity);
const auto pack = registry.view<int>() | registry.view<char>();
ASSERT_NE(pack.begin(), pack.end());
ASSERT_EQ(pack.begin()++, pack.begin());
ASSERT_EQ(++pack.begin(), pack.end());
ASSERT_EQ(*pack.begin(), entity);
}
TEST(ViewPack, ReverseIterator) {
entt::registry registry;
const auto entity = registry.create();
registry.emplace<int>(entity);
registry.emplace<char>(entity);
const auto pack = registry.view<int>() | registry.view<char>();
ASSERT_NE(pack.rbegin(), pack.rend());
ASSERT_EQ(pack.rbegin()++, pack.rbegin());
ASSERT_EQ(++pack.rbegin(), pack.rend());
ASSERT_EQ(*pack.rbegin(), entity);
}
TEST(ViewPack, Contains) {
entt::registry registry;
const auto e0 = registry.create();
registry.emplace<int>(e0);
registry.emplace<char>(e0);
const auto e1 = registry.create();
registry.emplace<int>(e1);
registry.emplace<char>(e1);
registry.destroy(e0);
const auto pack = registry.view<int>() | registry.view<char>();
ASSERT_FALSE(pack.contains(e0));
ASSERT_TRUE(pack.contains(e1));
}
TEST(ViewPack, Each) {
entt::registry registry;
const auto e0 = registry.create();
registry.emplace<int>(e0, 0);
registry.emplace<char>(e0);
const auto e1 = registry.create();
registry.emplace<int>(e1, 1);
registry.emplace<char>(e1);
auto pack = registry.view<int>() | registry.view<char>();
auto cpack = registry.view<const int>() | registry.view<const char>();
std::size_t cnt = 0;
for(auto first = cpack.each().rbegin(), last = cpack.each().rend(); first != last; ++first) {
static_assert(std::is_same_v<decltype(*first), std::tuple<entt::entity, const int &, const char &>>);
ASSERT_EQ(std::get<1>(*first), cnt++);
}
pack.each([&cnt](auto, int &, char &) { ++cnt; });
pack.each([&cnt](int &, char &) { ++cnt; });
ASSERT_EQ(cnt, std::size_t{6});
cpack.each([&cnt](const int &, const char &) { --cnt; });
cpack.each([&cnt](auto, const int &, const char &) { --cnt; });
for(auto &&[entt, iv, cv]: pack.each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), char &>);
ASSERT_EQ(iv, --cnt);
}
ASSERT_EQ(cnt, std::size_t{0});
}
TEST(ViewPack, EachWithHoles) {
entt::registry registry;
const auto e0 = registry.create();
const auto e1 = registry.create();
const auto e2 = registry.create();
registry.emplace<char>(e0, '0');
registry.emplace<char>(e1, '1');
registry.emplace<int>(e0, 0);
registry.emplace<int>(e2, 2);
auto pack = registry.view<char>() | registry.view<int>();
pack.each([e0](auto entity, const char &c, const int &i) {
ASSERT_EQ(entity, e0);
ASSERT_EQ(c, '0');
ASSERT_EQ(i, 0);
});
for(auto &&curr: pack.each()) {
ASSERT_EQ(std::get<0>(curr), e0);
ASSERT_EQ(std::get<1>(curr), '0');
ASSERT_EQ(std::get<2>(curr), 0);
}
}
TEST(ViewPack, ConstNonConstAndAllInBetween) {
entt::registry registry;
auto pack = registry.view<int, empty_type>() | registry.view<const char>();
const auto entity = registry.create();
registry.emplace<int>(entity, 0);
registry.emplace<empty_type>(entity);
registry.emplace<char>(entity, 'c');
static_assert(std::is_same_v<decltype(pack.get<int>({})), int &>);
static_assert(std::is_same_v<decltype(pack.get<const char>({})), const char &>);
static_assert(std::is_same_v<decltype(pack.get<int, const char>({})), std::tuple<int &, const char &>>);
static_assert(std::is_same_v<decltype(pack.get({})), std::tuple<int &, const char &>>);
pack.each([](auto &&i, auto &&c) {
static_assert(std::is_same_v<decltype(i), int &>);
static_assert(std::is_same_v<decltype(c), const char &>);
});
for(auto &&[entt, iv, cv]: pack.each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), const char &>);
}
}
TEST(ViewPack, Find) {
entt::registry registry;
auto pack = registry.view<int>() | registry.view<const char>();
const auto e0 = registry.create();
registry.emplace<int>(e0);
registry.emplace<char>(e0);
const auto e1 = registry.create();
registry.emplace<int>(e1);
registry.emplace<char>(e1);
const auto e2 = registry.create();
registry.emplace<int>(e2);
registry.emplace<char>(e2);
const auto e3 = registry.create();
registry.emplace<int>(e3);
registry.emplace<char>(e3);
registry.remove<int>(e1);
ASSERT_NE(pack.find(e0), pack.end());
ASSERT_EQ(pack.find(e1), pack.end());
ASSERT_NE(pack.find(e2), pack.end());
ASSERT_NE(pack.find(e3), pack.end());
auto it = pack.find(e2);
ASSERT_EQ(*it, e2);
ASSERT_EQ(*(++it), e3);
ASSERT_EQ(*(++it), e0);
ASSERT_EQ(++it, pack.end());
ASSERT_EQ(++pack.find(e0), pack.end());
const auto e4 = registry.create();
registry.destroy(e4);
const auto e5 = registry.create();
registry.emplace<int>(e5);
registry.emplace<char>(e5);
ASSERT_NE(pack.find(e5), pack.end());
ASSERT_EQ(pack.find(e4), pack.end());
}
TEST(ViewPack, FrontBack) {
entt::registry registry;
auto pack = registry.view<const int>() | registry.view<const char>();
ASSERT_EQ(pack.front(), static_cast<entt::entity>(entt::null));
ASSERT_EQ(pack.back(), static_cast<entt::entity>(entt::null));
const auto e0 = registry.create();
registry.emplace<int>(e0);
registry.emplace<char>(e0);
const auto e1 = registry.create();
registry.emplace<int>(e1);
registry.emplace<char>(e1);
const auto entity = registry.create();
registry.emplace<char>(entity);
ASSERT_EQ(pack.front(), e1);
ASSERT_EQ(pack.back(), e0);
}
TEST(ViewPack, ShortestPool) {
entt::registry registry;
entt::entity entities[4];
registry.create(std::begin(entities), std::end(entities));
registry.insert<int>(std::begin(entities), std::end(entities));
registry.insert<empty_type>(std::begin(entities), std::end(entities));
registry.insert<char>(std::rbegin(entities) + 1u, std::rend(entities) - 1u);
const auto tmp = registry.view<char>() | registry.view<empty_type>();
const auto pack = tmp | registry.view<const int>();
{
std::size_t next{};
for(const auto entt: pack) {
ASSERT_EQ(entt::to_integral(entt), ++next);
ASSERT_TRUE((registry.has<int, char>(entt)));
}
}
auto it = pack.begin();
ASSERT_EQ(*it++, entities[1u]);
ASSERT_EQ(++it, pack.end());
ASSERT_TRUE(it == pack.end());
ASSERT_FALSE(it != pack.end());
pack.each([&registry, next = 0u](const auto entt, auto &&cv, auto &&iv) mutable {
static_assert(std::is_same_v<decltype(entt), const entt::entity>);
static_assert(std::is_same_v<decltype(cv), char &>);
static_assert(std::is_same_v<decltype(iv), const int &>);
ASSERT_EQ(entt::to_integral(entt), ++next);
ASSERT_EQ(&cv, registry.try_get<char>(entt));
ASSERT_EQ(&iv, registry.try_get<int>(entt));
});
pack.each([](auto &&cv, auto &&iv) {
static_assert(std::is_same_v<decltype(cv), char &>);
static_assert(std::is_same_v<decltype(iv), const int &>);
});
auto eit = pack.each().begin();
ASSERT_EQ(std::get<0>(*eit++), entities[1u]);
static_assert(std::is_same_v<decltype(std::get<1>(*eit)), char &>);
static_assert(std::is_same_v<decltype(std::get<2>(*eit)), const int &>);
ASSERT_EQ(++eit, pack.each().end());
ASSERT_TRUE(eit == pack.each().end());
ASSERT_FALSE(eit != pack.each().end());
{
std::size_t next{};
for(const auto [entt, cv, iv]: pack.each()) {
static_assert(std::is_same_v<decltype(entt), const entt::entity>);
static_assert(std::is_same_v<decltype(cv), char &>);
static_assert(std::is_same_v<decltype(iv), const int &>);
ASSERT_EQ(entt::to_integral(entt), ++next);
ASSERT_EQ(&cv, registry.try_get<char>(entt));
ASSERT_EQ(&iv, registry.try_get<int>(entt));
}
}
}
TEST(ViewPack, LongestPool) {
entt::registry registry;
entt::entity entities[4];
registry.create(std::begin(entities), std::end(entities));
registry.insert<int>(std::begin(entities), std::end(entities));
registry.insert<empty_type>(std::begin(entities), std::end(entities));
registry.insert<char>(std::rbegin(entities) + 1u, std::rend(entities) - 1u);
const auto pack = registry.view<int>() | registry.view<empty_type>() | registry.view<const char>();
{
std::size_t next{2u};
for(const auto entt: pack) {
ASSERT_EQ(entt::to_integral(entt), next--);
ASSERT_TRUE((registry.has<int, char>(entt)));
}
}
auto it = pack.begin();
ASSERT_EQ(*it++, entities[2u]);
ASSERT_EQ(++it, pack.end());
ASSERT_TRUE(it == pack.end());
ASSERT_FALSE(it != pack.end());
pack.each([&registry, next = 2u](const auto entt, auto &&iv, auto &&cv) mutable {
static_assert(std::is_same_v<decltype(entt), const entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), const char &>);
ASSERT_EQ(entt::to_integral(entt), next--);
ASSERT_EQ(&iv, registry.try_get<int>(entt));
ASSERT_EQ(&cv, registry.try_get<char>(entt));
});
pack.each([](auto &&iv, auto &&cv) {
static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), const char &>);
});
auto eit = pack.each().begin();
ASSERT_EQ(std::get<0>(*eit++), entities[2u]);
static_assert(std::is_same_v<decltype(std::get<1>(*eit)), int &>);
static_assert(std::is_same_v<decltype(std::get<2>(*eit)), const char &>);
ASSERT_EQ(++eit, pack.each().end());
ASSERT_TRUE(eit == pack.each().end());
ASSERT_FALSE(eit != pack.each().end());
{
std::size_t next{2u};
for(const auto [entt, iv, cv]: pack.each()) {
static_assert(std::is_same_v<decltype(entt), const entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), const char &>);
ASSERT_EQ(entt::to_integral(entt), next--);
ASSERT_EQ(&iv, registry.try_get<int>(entt));
ASSERT_EQ(&cv, registry.try_get<char>(entt));
}
}
}
TEST(ViewPack, RepeatedType) {
entt::registry registry;
const auto entity = registry.create();
registry.emplace<int>(entity, 3);
const auto view = registry.view<int>();
const auto pack = view | view;
for(auto [entt, i1, i2]: pack.each()) {
ASSERT_EQ(entt, entity);
ASSERT_EQ(i1, 3);
ASSERT_EQ(i1, i2);
}
}

View File

@@ -1,3 +1,4 @@
#include <algorithm>
#include <gtest/gtest.h>
#include <entt/core/hashed_string.hpp>
#include <entt/meta/factory.hpp>
@@ -5,36 +6,42 @@
#include <entt/meta/resolve.hpp>
struct clazz_t {
clazz_t(): value{0} {}
void member(int i) { value = i; }
static void func() { c = 'd'; }
static inline char c = 'c';
int value = 0;
int value;
};
struct empty_t {
virtual ~empty_t() = default;
static void destroy(empty_t &) {
++counter;
virtual ~empty_t() {
++destructor_counter;
}
inline static int counter = 0;
static void destroy(empty_t &) {
++destroy_counter;
}
inline static int destroy_counter = 0;
inline static int destructor_counter = 0;
};
struct fat_t: empty_t {
fat_t() = default;
fat_t()
: value{.0, .0, .0, .0}
{}
fat_t(int *value)
: foo{value}, bar{value}
fat_t(double v1, double v2, double v3, double v4)
: value{v1, v2, v3, v4}
{}
bool operator==(const fat_t &other) const {
return foo == other.foo && bar == other.bar;
return std::equal(std::begin(value), std::end(value), std::begin(other.value), std::end(other.value));
}
int *foo{nullptr};
int *bar{nullptr};
double gnam[4];
double value[4];
};
struct not_comparable_t {
@@ -50,22 +57,36 @@ struct unmanageable_t {
};
struct MetaAny: ::testing::Test {
static void SetUpTestCase() {
void SetUp() override {
using namespace entt::literals;
entt::meta<double>().conv<int>();
entt::meta<empty_t>().dtor<&empty_t::destroy>();
entt::meta<fat_t>().base<empty_t>().dtor<&fat_t::destroy>();
entt::meta<double>()
.type("double"_hs)
.conv<int>();
entt::meta<empty_t>()
.type("empty"_hs)
.dtor<&empty_t::destroy>();
entt::meta<fat_t>()
.type("fat"_hs)
.base<empty_t>()
.dtor<&fat_t::destroy>();
entt::meta<clazz_t>()
.type("clazz"_hs)
.data<&clazz_t::value>("value"_hs)
.func<&clazz_t::member>("member"_hs)
.func<&clazz_t::func>("func"_hs);
empty_t::destroy_counter = 0;
empty_t::destructor_counter = 0;
}
void SetUp() override {
empty_t::counter = 0;
void TearDown() override {
for(auto type: entt::resolve()) {
type.reset();
}
}
};
@@ -81,8 +102,7 @@ TEST_F(MetaAny, SBO) {
}
TEST_F(MetaAny, NoSBO) {
int value = 42;
fat_t instance{&value};
fat_t instance{.1, .2, .3, .4};
entt::meta_any any{instance};
ASSERT_TRUE(any);
@@ -102,6 +122,14 @@ TEST_F(MetaAny, Empty) {
ASSERT_EQ(any.data(), nullptr);
ASSERT_EQ(any, entt::meta_any{});
ASSERT_NE(entt::meta_any{'c'}, any);
ASSERT_FALSE(any.as_ref());
ASSERT_FALSE(any.as_sequence_container());
ASSERT_FALSE(any.as_associative_container());
ASSERT_FALSE(std::as_const(any).as_ref());
ASSERT_FALSE(std::as_const(any).as_sequence_container());
ASSERT_FALSE(std::as_const(any).as_associative_container());
}
TEST_F(MetaAny, SBOInPlaceTypeConstruction) {
@@ -118,38 +146,72 @@ TEST_F(MetaAny, SBOInPlaceTypeConstruction) {
TEST_F(MetaAny, SBOAsRefConstruction) {
int value = 3;
int other = 42;
entt::meta_any any{std::ref(value)};
int compare = 42;
auto any = entt::forward_as_meta(value);
ASSERT_TRUE(any);
ASSERT_EQ(any.type(), entt::resolve<int>());
ASSERT_FALSE(any.try_cast<std::size_t>());
ASSERT_EQ(any.cast<int &>(), 3);
ASSERT_EQ(any.cast<const int &>(), 3);
ASSERT_EQ(any.cast<int>(), 3);
ASSERT_NE(any.data(), nullptr);
ASSERT_NE(std::as_const(any).data(), nullptr);
ASSERT_EQ(any, (entt::meta_any{std::ref(value)}));
ASSERT_NE(any, (entt::meta_any{std::ref(other)}));
ASSERT_EQ(any.data(), &value);
ASSERT_EQ(std::as_const(any).data(), &value);
ASSERT_EQ(any, entt::forward_as_meta(value));
ASSERT_NE(any, entt::forward_as_meta(compare));
ASSERT_NE(any, entt::meta_any{42});
ASSERT_EQ(entt::meta_any{3}, any);
any = entt::make_meta<int &>(value);
ASSERT_TRUE(any);
ASSERT_EQ(any.type(), entt::resolve<int>());
ASSERT_EQ(std::as_const(any).data(), &value);
auto other = any.as_ref();
ASSERT_TRUE(other);
ASSERT_EQ(any.type(), entt::resolve<int>());
ASSERT_EQ(any.cast<int>(), 3);
ASSERT_EQ(other.data(), any.data());
}
TEST_F(MetaAny, SBOAsConstRefConstruction) {
int value = 3;
int other = 42;
entt::meta_any any{std::cref(value)};
const int value = 3;
int compare = 42;
auto any = entt::forward_as_meta(value);
ASSERT_TRUE(any);
ASSERT_EQ(any.type(), entt::resolve<int>());
ASSERT_FALSE(any.try_cast<std::size_t>());
ASSERT_DEATH(any.cast<int &>() = 3, ".*");
ASSERT_DEATH(any.cast<int &>() = 3, "");
ASSERT_EQ(any.cast<const int &>(), 3);
ASSERT_EQ(any.cast<int>(), 3);
ASSERT_EQ(any.data(), nullptr);
ASSERT_NE(std::as_const(any).data(), nullptr);
ASSERT_EQ(any, (entt::meta_any{std::cref(value)}));
ASSERT_NE(any, (entt::meta_any{std::cref(other)}));
ASSERT_EQ(std::as_const(any).data(), &value);
ASSERT_EQ(any, entt::forward_as_meta(value));
ASSERT_NE(any, entt::forward_as_meta(compare));
ASSERT_NE(any, entt::meta_any{42});
ASSERT_EQ(entt::meta_any{3}, any);
any = entt::make_meta<const int &>(value);
ASSERT_TRUE(any);
ASSERT_EQ(any.type(), entt::resolve<int>());
ASSERT_EQ(std::as_const(any).data(), &value);
auto other = any.as_ref();
ASSERT_TRUE(other);
ASSERT_EQ(any.type(), entt::resolve<int>());
ASSERT_EQ(any.cast<int>(), 3);
ASSERT_EQ(other.data(), any.data());
}
TEST_F(MetaAny, SBOCopyConstruction) {
@@ -215,8 +277,7 @@ TEST_F(MetaAny, SBODirectAssignment) {
}
TEST_F(MetaAny, NoSBOInPlaceTypeConstruction) {
int value = 42;
fat_t instance{&value};
fat_t instance{.1, .2, .3, .4};
entt::meta_any any{std::in_place_type<fat_t>, instance};
ASSERT_TRUE(any);
@@ -229,42 +290,73 @@ TEST_F(MetaAny, NoSBOInPlaceTypeConstruction) {
}
TEST_F(MetaAny, NoSBOAsRefConstruction) {
int value = 3;
fat_t instance{&value};
entt::meta_any any{std::ref(instance)};
fat_t instance{.1, .2, .3, .4};
auto any = entt::forward_as_meta(instance);
ASSERT_TRUE(any);
ASSERT_EQ(any.type(), entt::resolve<fat_t>());
ASSERT_FALSE(any.try_cast<std::size_t>());
ASSERT_EQ(any.cast<fat_t &>(), instance);
ASSERT_EQ(any.cast<const fat_t &>(), instance);
ASSERT_EQ(any.cast<fat_t>(), instance);
ASSERT_NE(any.data(), nullptr);
ASSERT_NE(std::as_const(any).data(), nullptr);
ASSERT_EQ(any, (entt::meta_any{std::ref(instance)}));
ASSERT_EQ(any.data(), &instance);
ASSERT_EQ(std::as_const(any).data(), &instance);
ASSERT_EQ(any, entt::forward_as_meta(instance));
ASSERT_EQ(any, entt::meta_any{instance});
ASSERT_NE(entt::meta_any{fat_t{}}, any);
any = entt::make_meta<fat_t &>(instance);
ASSERT_TRUE(any);
ASSERT_EQ(any.type(), entt::resolve<fat_t>());
ASSERT_EQ(std::as_const(any).data(), &instance);
auto other = any.as_ref();
ASSERT_TRUE(other);
ASSERT_EQ(any.type(), entt::resolve<fat_t>());
ASSERT_EQ(any, entt::meta_any{instance});
ASSERT_EQ(other.data(), any.data());
}
TEST_F(MetaAny, NoSBOAsConstRefConstruction) {
int value = 3;
fat_t instance{&value};
entt::meta_any any{std::cref(instance)};
const fat_t instance{.1, .2, .3, .4};
auto any = entt::forward_as_meta(instance);
ASSERT_TRUE(any);
ASSERT_EQ(any.type(), entt::resolve<fat_t>());
ASSERT_FALSE(any.try_cast<std::size_t>());
ASSERT_DEATH(any.cast<fat_t &>() = {}, ".*");
ASSERT_DEATH(any.cast<fat_t &>() = {}, "");
ASSERT_EQ(any.cast<const fat_t &>(), instance);
ASSERT_EQ(any.cast<fat_t>(), instance);
ASSERT_EQ(any.data(), nullptr);
ASSERT_NE(std::as_const(any).data(), nullptr);
ASSERT_EQ(any, (entt::meta_any{std::cref(instance)}));
ASSERT_EQ(std::as_const(any).data(), &instance);
ASSERT_EQ(any, entt::forward_as_meta(instance));
ASSERT_EQ(any, entt::meta_any{instance});
ASSERT_NE(entt::meta_any{fat_t{}}, any);
any = entt::make_meta<const fat_t &>(instance);
ASSERT_TRUE(any);
ASSERT_EQ(any.type(), entt::resolve<fat_t>());
ASSERT_EQ(std::as_const(any).data(), &instance);
auto other = any.as_ref();
ASSERT_TRUE(other);
ASSERT_EQ(any.type(), entt::resolve<fat_t>());
ASSERT_EQ(any, entt::meta_any{instance});
ASSERT_EQ(other.data(), any.data());
}
TEST_F(MetaAny, NoSBOCopyConstruction) {
int value = 42;
fat_t instance{&value};
fat_t instance{.1, .2, .3, .4};
entt::meta_any any{instance};
entt::meta_any other{any};
@@ -277,8 +369,7 @@ TEST_F(MetaAny, NoSBOCopyConstruction) {
}
TEST_F(MetaAny, NoSBOCopyAssignment) {
int value = 42;
fat_t instance{&value};
fat_t instance{.1, .2, .3, .4};
entt::meta_any any{instance};
entt::meta_any other{3};
@@ -293,8 +384,7 @@ TEST_F(MetaAny, NoSBOCopyAssignment) {
}
TEST_F(MetaAny, NoSBOMoveConstruction) {
int value = 42;
fat_t instance{&value};
fat_t instance{.1, .2, .3, .4};
entt::meta_any any{instance};
entt::meta_any other{std::move(any)};
@@ -307,8 +397,7 @@ TEST_F(MetaAny, NoSBOMoveConstruction) {
}
TEST_F(MetaAny, NoSBOMoveAssignment) {
int value = 42;
fat_t instance{&value};
fat_t instance{.1, .2, .3, .4};
entt::meta_any any{instance};
entt::meta_any other{3};
@@ -323,13 +412,13 @@ TEST_F(MetaAny, NoSBOMoveAssignment) {
}
TEST_F(MetaAny, NoSBODirectAssignment) {
int value = 42;
fat_t instance{.1, .2, .3, .4};
entt::meta_any any{};
any = fat_t{&value};
any = instance;
ASSERT_FALSE(any.try_cast<std::size_t>());
ASSERT_EQ(any.cast<fat_t>(), fat_t{&value});
ASSERT_EQ(any, entt::meta_any{fat_t{&value}});
ASSERT_EQ(any.cast<fat_t>(), instance);
ASSERT_EQ(any, (entt::meta_any{fat_t{.1, .2, .3, .4}}));
ASSERT_NE(fat_t{}, any);
}
@@ -399,8 +488,7 @@ TEST_F(MetaAny, SBOMoveInvalidate) {
}
TEST_F(MetaAny, NoSBOMoveInvalidate) {
int value = 42;
fat_t instance{&value};
fat_t instance{.1, .2, .3, .4};
entt::meta_any any{instance};
entt::meta_any other{std::move(any)};
entt::meta_any valid = std::move(other);
@@ -421,15 +509,29 @@ TEST_F(MetaAny, VoidMoveInvalidate) {
}
TEST_F(MetaAny, SBODestruction) {
ASSERT_EQ(empty_t::counter, 0);
{ entt::meta_any any{empty_t{}}; }
ASSERT_EQ(empty_t::counter, 1);
{
entt::meta_any any{std::in_place_type<empty_t>};
any.emplace<empty_t>();
any = empty_t{};
entt::meta_any other{std::move(any)};
any = std::move(other);
}
ASSERT_EQ(empty_t::destroy_counter, 3);
ASSERT_EQ(empty_t::destructor_counter,6);
}
TEST_F(MetaAny, NoSBODestruction) {
ASSERT_EQ(fat_t::counter, 0);
{ entt::meta_any any{fat_t{}}; }
ASSERT_EQ(fat_t::counter, 1);
{
entt::meta_any any{std::in_place_type<fat_t>, 1., 2., 3., 4.};
any.emplace<fat_t>(1., 2., 3., 4.);
any = fat_t{1., 2., 3., 4.};
entt::meta_any other{std::move(any)};
any = std::move(other);
}
ASSERT_EQ(fat_t::destroy_counter, 3);
ASSERT_EQ(empty_t::destructor_counter, 4);
}
TEST_F(MetaAny, VoidDestruction) {
@@ -460,6 +562,18 @@ TEST_F(MetaAny, EmplaceVoid) {
ASSERT_EQ(any, (entt::meta_any{std::in_place_type<void>}));
}
TEST_F(MetaAny, Reset) {
entt::meta_any any{42};
ASSERT_TRUE(any);
ASSERT_EQ(any.type(), entt::resolve<int>());
any.reset();
ASSERT_FALSE(any);
ASSERT_EQ(any.type(), entt::meta_type{});
}
TEST_F(MetaAny, SBOSwap) {
entt::meta_any lhs{'c'};
entt::meta_any rhs{42};
@@ -473,14 +587,13 @@ TEST_F(MetaAny, SBOSwap) {
}
TEST_F(MetaAny, NoSBOSwap) {
int i{}, j{};
entt::meta_any lhs{fat_t{&i}};
entt::meta_any rhs{fat_t{&j}};
entt::meta_any lhs{fat_t{.1, .2, .3, .4}};
entt::meta_any rhs{fat_t{.4, .3, .2, .1}};
std::swap(lhs, rhs);
ASSERT_EQ(lhs.cast<fat_t>().foo, &j);
ASSERT_EQ(rhs.cast<fat_t>().bar, &i);
ASSERT_EQ(lhs.cast<fat_t>(), (fat_t{.4, .3, .2, .1}));
ASSERT_EQ(rhs.cast<fat_t>(), (fat_t{.1, .2, .3, .4}));
}
TEST_F(MetaAny, VoidSwap) {
@@ -494,8 +607,7 @@ TEST_F(MetaAny, VoidSwap) {
}
TEST_F(MetaAny, SBOWithNoSBOSwap) {
int value = 42;
entt::meta_any lhs{fat_t{&value}};
entt::meta_any lhs{fat_t{.1, .2, .3, .4}};
entt::meta_any rhs{'c'};
std::swap(lhs, rhs);
@@ -503,8 +615,7 @@ TEST_F(MetaAny, SBOWithNoSBOSwap) {
ASSERT_FALSE(lhs.try_cast<fat_t>());
ASSERT_EQ(lhs.cast<char>(), 'c');
ASSERT_FALSE(rhs.try_cast<char>());
ASSERT_EQ(rhs.cast<fat_t>().foo, &value);
ASSERT_EQ(rhs.cast<fat_t>().bar, &value);
ASSERT_EQ(rhs.cast<fat_t>(), (fat_t{.1, .2, .3, .4}));
}
TEST_F(MetaAny, SBOWithEmptySwap) {
@@ -533,37 +644,39 @@ TEST_F(MetaAny, SBOWithVoidSwap) {
}
TEST_F(MetaAny, NoSBOWithEmptySwap) {
int i{};
entt::meta_any lhs{fat_t{&i}};
entt::meta_any lhs{fat_t{.1, .2, .3, .4}};
entt::meta_any rhs{};
std::swap(lhs, rhs);
ASSERT_EQ(rhs.cast<fat_t>().bar, &i);
ASSERT_FALSE(lhs);
ASSERT_EQ(rhs.cast<fat_t>(), (fat_t{.1, .2, .3, .4}));
std::swap(lhs, rhs);
ASSERT_EQ(lhs.cast<fat_t>().bar, &i);
ASSERT_FALSE(rhs);
ASSERT_EQ(lhs.cast<fat_t>(), (fat_t{.1, .2, .3, .4}));
}
TEST_F(MetaAny, NoSBOWithVoidSwap) {
int i{};
entt::meta_any lhs{fat_t{&i}};
entt::meta_any lhs{fat_t{.1, .2, .3, .4}};
entt::meta_any rhs{std::in_place_type<void>};
std::swap(lhs, rhs);
ASSERT_EQ(rhs.cast<fat_t>().bar, &i);
ASSERT_EQ(lhs.type(), entt::resolve<void>());
ASSERT_EQ(rhs.cast<fat_t>(), (fat_t{.1, .2, .3, .4}));
std::swap(lhs, rhs);
ASSERT_EQ(lhs.cast<fat_t>().bar, &i);
ASSERT_EQ(rhs.type(), entt::resolve<void>());
ASSERT_EQ(lhs.cast<fat_t>(), (fat_t{.1, .2, .3, .4}));
}
TEST_F(MetaAny, AsRef) {
entt::meta_any any{42};
auto ref = as_ref(any);
auto cref = as_ref(std::as_const(any));
auto ref = any.as_ref();
auto cref = std::as_const(any).as_ref();
ASSERT_EQ(any.try_cast<int>(), any.data());
ASSERT_EQ(ref.try_cast<int>(), any.data());
@@ -585,7 +698,7 @@ TEST_F(MetaAny, AsRef) {
ASSERT_EQ(any.cast<const int &>(), 42);
ASSERT_EQ(ref.cast<int &>(), 42);
ASSERT_EQ(ref.cast<const int &>(), 42);
ASSERT_DEATH(cref.cast<int &>() = 3, ".*");
ASSERT_DEATH(cref.cast<int &>() = 3, "");
ASSERT_EQ(cref.cast<const int &>(), 42);
any.cast<int &>() = 3;
@@ -599,16 +712,16 @@ TEST_F(MetaAny, AsRef) {
ASSERT_EQ(ref.try_cast<int>(), nullptr);
ASSERT_EQ(cref.try_cast<int>(), any.data());
ref = as_ref(ref);
cref = as_ref(std::as_const(cref));
ref = ref.as_ref();
cref = std::as_const(cref).as_ref();
ASSERT_EQ(ref.try_cast<int>(), nullptr);
ASSERT_EQ(cref.try_cast<int>(), nullptr);
ASSERT_EQ(ref.try_cast<const int>(), any.data());
ASSERT_EQ(cref.try_cast<const int>(), any.data());
ASSERT_DEATH(ref.cast<int &>() = 3, ".*");
ASSERT_DEATH(cref.cast<int &>() = 3, ".*");
ASSERT_DEATH(ref.cast<int &>() = 3, "");
ASSERT_DEATH(cref.cast<int &>() = 3, "");
ASSERT_EQ(ref.cast<const int &>(), 3);
ASSERT_EQ(cref.cast<const int &>(), 3);
@@ -624,6 +737,14 @@ TEST_F(MetaAny, AsRef) {
ASSERT_EQ(cref.cast<const int &>(), 42);
ASSERT_NE(ref.try_cast<int>(), any.data());
ASSERT_NE(cref.try_cast<int>(), any.data());
any.emplace<void>();
ref = any.as_ref();
cref = std::as_const(any).as_ref();
ASSERT_TRUE(any);
ASSERT_FALSE(ref);
ASSERT_FALSE(cref);
}
TEST_F(MetaAny, Comparable) {
@@ -690,11 +811,11 @@ TEST_F(MetaAny, Cast) {
ASSERT_EQ(any.cast<fat_t &>(), fat_t{});
ASSERT_EQ(any.cast<fat_t>(), fat_t{});
ASSERT_EQ(any.cast<fat_t>().gnam[0u], 0.);
ASSERT_EQ(any.cast<fat_t>().value[0u], 0.);
any.cast<fat_t &>().gnam[0u] = 3.;
any.cast<fat_t &>().value[0u] = 3.;
ASSERT_EQ(any.cast<fat_t>().gnam[0u], 3.);
ASSERT_EQ(any.cast<fat_t>().value[0u], 3.);
}
TEST_F(MetaAny, Convert) {
@@ -731,8 +852,8 @@ TEST_F(MetaAny, ConstConvert) {
TEST_F(MetaAny, UnmanageableType) {
unmanageable_t instance;
entt::meta_any any{std::ref(instance)};
entt::meta_any other = any;
auto any = entt::forward_as_meta(instance);
entt::meta_any other = any.as_ref();
std::swap(any, other);
@@ -755,12 +876,12 @@ TEST_F(MetaAny, Invoke) {
using namespace entt::literals;
clazz_t instance;
entt::meta_any any{std::ref(instance)};
auto any = entt::forward_as_meta(instance);
ASSERT_TRUE(any.invoke("func"_hs));
ASSERT_TRUE(any.invoke("member"_hs, 42));
ASSERT_FALSE(std::as_const(any).invoke("member"_hs, 42));
ASSERT_FALSE(as_ref(std::as_const(any)).invoke("member"_hs, 42));
ASSERT_FALSE(std::as_const(any).as_ref().invoke("member"_hs, 42));
ASSERT_FALSE(any.invoke("non_existent"_hs, 42));
ASSERT_EQ(clazz_t::c, 'd');
@@ -771,7 +892,7 @@ TEST_F(MetaAny, SetGet) {
using namespace entt::literals;
clazz_t instance;
entt::meta_any any{std::ref(instance)};
auto any = entt::forward_as_meta(instance);
ASSERT_TRUE(any.set("value"_hs, 42));
@@ -779,7 +900,7 @@ TEST_F(MetaAny, SetGet) {
ASSERT_TRUE(value);
ASSERT_EQ(value, any.get("value"_hs));
ASSERT_EQ(value, as_ref(std::as_const(any)).get("value"_hs));
ASSERT_EQ(value, std::as_const(any).as_ref().get("value"_hs));
ASSERT_NE(value.try_cast<int>(), nullptr);
ASSERT_EQ(value.cast<int>(), 42);
ASSERT_EQ(instance.value, 42);
@@ -787,3 +908,40 @@ TEST_F(MetaAny, SetGet) {
ASSERT_FALSE(any.set("non_existent"_hs, 42));
ASSERT_FALSE(any.get("non_existent"_hs));
}
TEST_F(MetaAny, MakeMeta) {
int value = 42;
auto any = entt::make_meta<int>(value);
auto ref = entt::make_meta<int &>(value);
ASSERT_TRUE(any);
ASSERT_TRUE(ref);
ASSERT_EQ(any.cast<const int &>(), 42);
ASSERT_EQ(ref.cast<const int &>(), 42);
ASSERT_NE(any.data(), &value);
ASSERT_EQ(ref.data(), &value);
}
TEST_F(MetaAny, ForwardAsMeta) {
int value = 42;
auto any = entt::forward_as_meta(std::move(value));
auto ref = entt::forward_as_meta(value);
auto cref = entt::forward_as_meta(std::as_const(value));
ASSERT_TRUE(any);
ASSERT_TRUE(ref);
ASSERT_TRUE(cref);
ASSERT_NE(any.try_cast<int>(), nullptr);
ASSERT_NE(ref.try_cast<int>(), nullptr);
ASSERT_EQ(cref.try_cast<int>(), nullptr);
ASSERT_EQ(any.cast<const int &>(), 42);
ASSERT_EQ(ref.cast<const int &>(), 42);
ASSERT_EQ(cref.cast<const int &>(), 42);
ASSERT_NE(any.data(), &value);
ASSERT_EQ(ref.data(), &value);
}

View File

@@ -1,29 +1,57 @@
#include <gtest/gtest.h>
#include <entt/core/hashed_string.hpp>
#include <entt/meta/factory.hpp>
#include <entt/meta/meta.hpp>
#include <entt/meta/node.hpp>
#include <entt/meta/resolve.hpp>
struct base_t {};
struct derived_t: base_t {};
struct base_t {
base_t() = default;
int value;
};
struct derived_t: base_t {
derived_t() = default;
};
struct MetaBase: ::testing::Test {
static void SetUpTestCase() {
void SetUp() override {
using namespace entt::literals;
entt::meta<base_t>().type("base"_hs);
entt::meta<derived_t>().type("derived"_hs).base<base_t>();
entt::meta<derived_t>()
.type("derived"_hs)
.base<base_t>();
}
void TearDown() override {
for(auto type: entt::resolve()) {
type.reset();
}
}
};
TEST_F(MetaBase, Functionalities) {
using namespace entt::literals;
auto any = entt::resolve<derived_t>().construct();
any.cast<derived_t &>().value = 42;
auto as_derived = any.as_ref();
auto base = entt::resolve<derived_t>().base("base"_hs);
derived_t derived{};
ASSERT_TRUE(any.allow_cast<base_t &>());
ASSERT_TRUE(base);
ASSERT_EQ(base.parent(), entt::resolve("derived"_hs));
ASSERT_EQ(base.type(), entt::resolve<base_t>());
ASSERT_EQ(base.cast(&derived), static_cast<base_t *>(&derived));
ASSERT_FALSE(any.allow_cast<char>());
ASSERT_FALSE(as_derived.allow_cast<char>());
ASSERT_TRUE(any);
ASSERT_EQ(any.cast<base_t &>().value, as_derived.cast<derived_t &>().value);
any.cast<base_t &>().value = 3;
ASSERT_EQ(any.cast<const base_t &>().value, as_derived.cast<const derived_t &>().value);
}
TEST_F(MetaBase, ReRegistration) {
SetUp();
auto *node = entt::internal::meta_info<derived_t>::resolve();
ASSERT_NE(node->base, nullptr);
ASSERT_EQ(node->base->next, nullptr);
}

View File

@@ -6,9 +6,22 @@
#include <entt/meta/resolve.hpp>
struct MetaContainer: ::testing::Test {
static void SetUpTestCase() {
entt::meta<double>().conv<int>();
entt::meta<int>().conv<char>();
void SetUp() override {
using namespace entt::literals;
entt::meta<double>()
.type("double"_hs)
.conv<int>();
entt::meta<int>()
.type("int"_hs)
.conv<char>();
}
void TearDown() override {
for(auto type: entt::resolve()) {
type.reset();
}
}
};
@@ -44,7 +57,7 @@ TEST_F(MetaContainer, EmptyAssociativeContainer) {
TEST_F(MetaContainer, SequenceContainerIterator) {
std::vector<int> vec{2, 3, 4};
entt::meta_any any{std::ref(vec)};
auto any = entt::forward_as_meta(vec);
entt::meta_sequence_container::iterator first{};
auto view = any.as_sequence_container();
@@ -71,7 +84,7 @@ TEST_F(MetaContainer, SequenceContainerIterator) {
TEST_F(MetaContainer, AssociativeContainerIterator) {
std::map<int, char> map{{2, 'c'}, {3, 'd'}, {4, 'e'}};
entt::meta_any any{std::ref(map)};
auto any = entt::forward_as_meta(map);
entt::meta_associative_container::iterator first{};
auto view = any.as_associative_container();
@@ -98,7 +111,7 @@ TEST_F(MetaContainer, AssociativeContainerIterator) {
TEST_F(MetaContainer, StdVector) {
std::vector<int> vec{};
entt::meta_any any{std::ref(vec)};
auto any = entt::forward_as_meta(vec);
auto view = any.as_sequence_container();
@@ -141,7 +154,7 @@ TEST_F(MetaContainer, StdVector) {
TEST_F(MetaContainer, StdArray) {
std::array<int, 3> arr{};
entt::meta_any any{std::ref(arr)};
auto any = entt::forward_as_meta(arr);
auto view = any.as_sequence_container();
@@ -183,7 +196,7 @@ TEST_F(MetaContainer, StdArray) {
TEST_F(MetaContainer, StdMap) {
std::map<int, char> map{{2, 'c'}, {3, 'd'}, {4, 'e'}};
entt::meta_any any{std::ref(map)};
auto any = entt::forward_as_meta(map);
auto view = any.as_associative_container();
@@ -227,7 +240,7 @@ TEST_F(MetaContainer, StdMap) {
TEST_F(MetaContainer, StdSet) {
std::set<int> set{2, 3, 4};
entt::meta_any any{std::ref(set)};
auto any = entt::forward_as_meta(set);
auto view = any.as_associative_container();
@@ -259,9 +272,9 @@ TEST_F(MetaContainer, StdSet) {
ASSERT_EQ(view.size(), 4u);
ASSERT_EQ(view.find(0), view.end());
(*view.find(1)).first.cast<int &>() = 42;
ASSERT_EQ((*view.find(1)).first.cast<int>(), 1);
ASSERT_EQ((*view.find(1)).first.try_cast<int>(), nullptr);
ASSERT_NE((*view.find(1)).first.try_cast<const int>(), nullptr);
ASSERT_EQ((*view.find(1)).first.cast<const int &>(), 1);
ASSERT_TRUE(view.erase(1.));
ASSERT_TRUE(view.clear());
@@ -270,7 +283,7 @@ TEST_F(MetaContainer, StdSet) {
TEST_F(MetaContainer, ConstSequenceContainer) {
std::vector<int> vec{};
entt::meta_any any{std::cref(vec)};
auto any = entt::forward_as_meta(std::as_const(vec));
auto view = any.as_sequence_container();
@@ -288,7 +301,7 @@ TEST_F(MetaContainer, ConstSequenceContainer) {
ASSERT_EQ(view.size(), 1u);
ASSERT_NE(view.begin(), view.end());
ASSERT_DEATH(view[0].cast<int &>() = 2, ".*");
ASSERT_DEATH(view[0].cast<int &>() = 2, "");
ASSERT_EQ(view[0].cast<const int &>(), 42);
auto it = view.begin();
@@ -311,7 +324,7 @@ TEST_F(MetaContainer, ConstSequenceContainer) {
TEST_F(MetaContainer, ConstKeyValueAssociativeContainer) {
std::map<int, char> map{};
entt::meta_any any{std::cref(map)};
auto any = entt::forward_as_meta(std::as_const(map));
auto view = any.as_associative_container();
@@ -329,7 +342,7 @@ TEST_F(MetaContainer, ConstKeyValueAssociativeContainer) {
ASSERT_EQ(view.size(), 1u);
ASSERT_NE(view.begin(), view.end());
ASSERT_DEATH((*view.find(2)).second.cast<char &>() = 'a', ".*");
ASSERT_DEATH((*view.find(2)).second.cast<char &>() = 'a', "");
ASSERT_EQ((*view.find(2)).second.cast<const char &>(), 'c');
ASSERT_FALSE(view.insert(0, 'a'));
@@ -347,7 +360,7 @@ TEST_F(MetaContainer, ConstKeyValueAssociativeContainer) {
TEST_F(MetaContainer, ConstKeyOnlyAssociativeContainer) {
std::set<int> set{};
entt::meta_any any{std::cref(set)};
auto any = entt::forward_as_meta(std::as_const(set));
auto view = any.as_associative_container();
@@ -365,8 +378,9 @@ TEST_F(MetaContainer, ConstKeyOnlyAssociativeContainer) {
ASSERT_EQ(view.size(), 1u);
ASSERT_NE(view.begin(), view.end());
ASSERT_EQ((*view.find(2)).first.try_cast<int>(), nullptr);
ASSERT_NE((*view.find(2)).first.try_cast<const int>(), nullptr);
ASSERT_EQ((*view.find(2)).first.cast<int>(), 2);
ASSERT_EQ((*view.find(2)).first.cast<int &>(), 2);
ASSERT_EQ((*view.find(2)).first.cast<const int &>(), 2);
ASSERT_FALSE(view.insert(0));
@@ -388,15 +402,15 @@ TEST_F(MetaContainer, SequenceContainerConstMetaAny) {
ASSERT_TRUE(view);
ASSERT_EQ(view.value_type(), entt::resolve<int>());
ASSERT_DEATH(view[0].cast<int &>() = 2, ".*");
ASSERT_DEATH(view[0].cast<int &>() = 2, "");
ASSERT_EQ(view[0].cast<const int &>(), 42);
};
std::vector<int> vec{42};
test(vec);
test(std::ref(vec));
test(std::cref(vec));
test(entt::forward_as_meta(vec));
test(entt::forward_as_meta(std::as_const(vec)));
}
TEST_F(MetaContainer, KeyValueAssociativeContainerConstMetaAny) {
@@ -405,15 +419,15 @@ TEST_F(MetaContainer, KeyValueAssociativeContainerConstMetaAny) {
ASSERT_TRUE(view);
ASSERT_EQ(view.value_type(), (entt::resolve<std::pair<const int, char>>()));
ASSERT_DEATH((*view.find(2)).second.cast<char &>() = 'a', ".*");
ASSERT_DEATH((*view.find(2)).second.cast<char &>() = 'a', "");
ASSERT_EQ((*view.find(2)).second.cast<const char &>(), 'c');
};
std::map<int, char> map{{2, 'c'}};
test(map);
test(std::ref(map));
test(std::cref(map));
test(entt::forward_as_meta(map));
test(entt::forward_as_meta(std::as_const(map)));
}
TEST_F(MetaContainer, KeyOnlyAssociativeContainerConstMetaAny) {
@@ -422,14 +436,64 @@ TEST_F(MetaContainer, KeyOnlyAssociativeContainerConstMetaAny) {
ASSERT_TRUE(view);
ASSERT_EQ(view.value_type(), (entt::resolve<int>()));
ASSERT_EQ((*view.find(2)).first.try_cast<int>(), nullptr);
ASSERT_NE((*view.find(2)).first.try_cast<const int>(), nullptr);
ASSERT_EQ((*view.find(2)).first.cast<int>(), 2);
ASSERT_EQ((*view.find(2)).first.cast<int &>(), 2);
ASSERT_EQ((*view.find(2)).first.cast<const int &>(), 2);
};
std::set<int> set{2};
test(set);
test(std::ref(set));
test(std::cref(set));
test(entt::forward_as_meta(set));
test(entt::forward_as_meta(std::as_const(set)));
}
TEST_F(MetaContainer, StdVectorBool) {
using proxy_type = typename std::vector<bool>::reference;
using const_proxy_type = typename std::vector<bool>::const_reference;
std::vector<bool> vec{};
auto any = entt::forward_as_meta(vec);
auto cany = std::as_const(any).as_ref();
auto view = any.as_sequence_container();
auto cview = cany.as_sequence_container();
ASSERT_TRUE(view);
ASSERT_EQ(view.value_type(), entt::resolve<bool>());
ASSERT_EQ(view.size(), 0u);
ASSERT_EQ(view.begin(), view.end());
ASSERT_TRUE(view.resize(3u));
ASSERT_EQ(view.size(), 3u);
ASSERT_NE(view.begin(), view.end());
view[0].cast<proxy_type>() = true;
view[1].cast<proxy_type>() = true;
view[2].cast<proxy_type>() = false;
ASSERT_EQ(cview[1u].cast<const_proxy_type>(), true);
auto it = view.begin();
auto ret = view.insert(it, true);
ASSERT_TRUE(ret.second);
ASSERT_FALSE(view.insert(ret.first, 'c').second);
ASSERT_TRUE(view.insert(++ret.first, false).second);
ASSERT_EQ(view.size(), 5u);
ASSERT_EQ((*view.begin()).cast<proxy_type>(), true);
ASSERT_EQ((*++cview.begin()).cast<const_proxy_type>(), false);
it = view.begin();
ret = view.erase(it);
ASSERT_TRUE(ret.second);
ASSERT_EQ(view.size(), 4u);
ASSERT_EQ((*ret.first).cast<proxy_type>(), false);
ASSERT_TRUE(view.clear());
ASSERT_EQ(cview.size(), 0u);
}

View File

@@ -1,70 +1,59 @@
#include <utility>
#include <gtest/gtest.h>
#include <entt/core/hashed_string.hpp>
#include <entt/meta/factory.hpp>
#include <entt/meta/meta.hpp>
#include <entt/meta/node.hpp>
#include <entt/meta/resolve.hpp>
struct clazz_t {
int f() const {
return i;
}
static char g(const clazz_t &type) {
return type.c;
}
int i{};
char c{};
clazz_t() = default;
operator int() const { return value; }
int value;
};
double conv_to_double(const clazz_t &instance) {
return instance.value * 2.;
}
struct MetaConv: ::testing::Test {
static void SetUpTestCase() {
entt::meta<double>().conv<int>();
entt::meta<clazz_t>().conv<&clazz_t::f>().conv<&clazz_t::g>();
void SetUp() override {
using namespace entt::literals;
entt::meta<clazz_t>()
.type("clazz"_hs)
.conv<int>()
.conv<&conv_to_double>();
}
void TearDown() override {
for(auto type: entt::resolve()) {
type.reset();
}
}
};
TEST_F(MetaConv, Functionalities) {
auto conv = entt::resolve<double>().conv<int>();
double value = 3.;
auto any = entt::resolve<clazz_t>().construct();
any.cast<clazz_t &>().value = 42;
ASSERT_TRUE(conv);
ASSERT_EQ(conv.parent(), entt::resolve<double>());
ASSERT_EQ(conv.type(), entt::resolve<int>());
const auto as_int = std::as_const(any).allow_cast<int>();
const auto as_double = std::as_const(any).allow_cast<double>();
auto any = conv.convert(&value);
ASSERT_FALSE(any.allow_cast<char>());
ASSERT_TRUE(any);
ASSERT_EQ(any.type(), entt::resolve<int>());
ASSERT_EQ(any.cast<int>(), 3);
ASSERT_TRUE(as_int);
ASSERT_TRUE(as_double);
ASSERT_EQ(as_int.cast<int>(), any.cast<clazz_t &>().value);
ASSERT_EQ(as_double.cast<double>(), conv_to_double(any.cast<clazz_t &>()));
}
TEST_F(MetaConv, AsFreeFunctions) {
auto conv = entt::resolve<clazz_t>().conv<int>();
clazz_t clazz{42, 'c'};
TEST_F(MetaConv, ReRegistration) {
SetUp();
ASSERT_TRUE(conv);
ASSERT_EQ(conv.parent(), entt::resolve<clazz_t>());
ASSERT_EQ(conv.type(), entt::resolve<int>());
auto *node = entt::internal::meta_info<clazz_t>::resolve();
auto any = conv.convert(&clazz);
ASSERT_TRUE(any);
ASSERT_EQ(any.type(), entt::resolve<int>());
ASSERT_EQ(any.cast<int>(), 42);
}
TEST_F(MetaConv, AsMemberFunctions) {
auto conv = entt::resolve<clazz_t>().conv<char>();
clazz_t clazz{42, 'c'};
ASSERT_TRUE(conv);
ASSERT_EQ(conv.parent(), entt::resolve<clazz_t>());
ASSERT_EQ(conv.type(), entt::resolve<char>());
auto any = conv.convert(&clazz);
ASSERT_TRUE(any);
ASSERT_EQ(any.type(), entt::resolve<char>());
ASSERT_EQ(any.cast<char>(), 'c');
ASSERT_NE(node->conv, nullptr);
ASSERT_NE(node->conv->next, nullptr);
ASSERT_EQ(node->conv->next->next, nullptr);
}

View File

@@ -6,8 +6,8 @@
#include <entt/meta/meta.hpp>
#include <entt/meta/resolve.hpp>
struct base_t { char value{}; };
struct derived_t: base_t {};
struct base_t { base_t(): value{'c'} {} char value; };
struct derived_t: base_t { derived_t(): base_t{} {} };
struct clazz_t {
clazz_t(const base_t &other, int iv)
@@ -30,20 +30,35 @@ struct clazz_t {
char c{};
};
double double_factory() { return 42.; }
struct MetaCtor: ::testing::Test {
static void SetUpTestCase() {
void SetUp() override {
using namespace entt::literals;
entt::meta<double>().conv<int>();
entt::meta<derived_t>().base<base_t>();
entt::meta<double>()
.type("double"_hs)
.conv<int>()
.ctor<&double_factory>();
entt::meta<clazz_t>().type("clazz"_hs)
entt::meta<derived_t>()
.type("derived"_hs)
.base<base_t>();
entt::meta<clazz_t>()
.type("clazz"_hs)
.ctor<&entt::registry::emplace_or_replace<clazz_t, const int &, const char &>, entt::as_ref_t>()
.ctor<const base_t &, int>()
.ctor<const int &, char>().prop(3, false)
.ctor<entt::overload<clazz_t(int)>(&clazz_t::factory)>().prop('c', 42)
.ctor<entt::overload<clazz_t(base_t, int, int)>(&clazz_t::factory)>();
}
void TearDown() override {
for(auto type: entt::resolve()) {
type.reset();
}
}
};
TEST_F(MetaCtor, Functionalities) {
@@ -53,7 +68,7 @@ TEST_F(MetaCtor, Functionalities) {
ASSERT_TRUE(ctor);
ASSERT_EQ(ctor.parent(), entt::resolve("clazz"_hs));
ASSERT_EQ(ctor.size(), 2u);
ASSERT_EQ(ctor.arity(), 2u);
ASSERT_EQ(ctor.arg(0u), entt::resolve<int>());
ASSERT_EQ(ctor.arg(1u), entt::resolve<char>());
ASSERT_FALSE(ctor.arg(2u));
@@ -88,7 +103,7 @@ TEST_F(MetaCtor, Func) {
ASSERT_TRUE(ctor);
ASSERT_EQ(ctor.parent(), entt::resolve("clazz"_hs));
ASSERT_EQ(ctor.size(), 1u);
ASSERT_EQ(ctor.arity(), 1u);
ASSERT_EQ(ctor.arg(0u), entt::resolve<int>());
ASSERT_FALSE(ctor.arg(1u));
@@ -129,7 +144,7 @@ TEST_F(MetaCtor, InvalidArgs) {
}
TEST_F(MetaCtor, CastAndConvert) {
auto any = entt::resolve<clazz_t>().ctor<const base_t &, int>().invoke(derived_t{{'c'}}, 42.);
auto any = entt::resolve<clazz_t>().ctor<const base_t &, int>().invoke(derived_t{}, 42.);
ASSERT_TRUE(any);
ASSERT_EQ(any.cast<clazz_t>().i, 42);
@@ -138,9 +153,9 @@ TEST_F(MetaCtor, CastAndConvert) {
TEST_F(MetaCtor, ConstNonConstRefArgs) {
int ivalue = 42;
char cvalue = 'c';
const char cvalue = 'c';
auto any = entt::resolve<clazz_t>().ctor<int, char>().invoke(std::ref(ivalue), std::cref(cvalue));
auto any = entt::resolve<clazz_t>().ctor<int, char>().invoke(entt::forward_as_meta(ivalue), entt::forward_as_meta(cvalue));
ASSERT_TRUE(any);
ASSERT_EQ(any.cast<clazz_t>().i, 42);
@@ -161,7 +176,7 @@ TEST_F(MetaCtor, FuncInvalidArgs) {
}
TEST_F(MetaCtor, FuncCastAndConvert) {
auto any = entt::resolve<clazz_t>().ctor<base_t, int, int>().invoke(derived_t{{'c'}}, 3., 3);
auto any = entt::resolve<clazz_t>().ctor<base_t, int, int>().invoke(derived_t{}, 3., 3);
ASSERT_TRUE(any);
ASSERT_EQ(any.cast<clazz_t>().i, 9);
@@ -172,8 +187,8 @@ TEST_F(MetaCtor, FuncConstNonConstRefArgs) {
int ivalue = 42;
auto ctor = entt::resolve<clazz_t>().ctor<int>();
auto any = ctor.invoke(std::ref(ivalue));
auto other = ctor.invoke(std::cref(ivalue));
auto any = ctor.invoke(entt::forward_as_meta(ivalue));
auto other = ctor.invoke(entt::make_meta<const int &>(ivalue));
ASSERT_TRUE(any);
ASSERT_TRUE(other);
@@ -188,7 +203,7 @@ TEST_F(MetaCtor, ExternalMemberFunction) {
ASSERT_TRUE(ctor);
ASSERT_EQ(ctor.parent(), entt::resolve("clazz"_hs));
ASSERT_EQ(ctor.size(), 4u);
ASSERT_EQ(ctor.arity(), 4u);
ASSERT_EQ(ctor.arg(0u), entt::resolve<entt::registry>());
ASSERT_EQ(ctor.arg(1u), entt::resolve<entt::entity>());
ASSERT_EQ(ctor.arg(2u), entt::resolve<int>());
@@ -198,12 +213,76 @@ TEST_F(MetaCtor, ExternalMemberFunction) {
entt::registry registry;
const auto entity = registry.create();
ASSERT_FALSE(registry.has<clazz_t>(entity));
ASSERT_FALSE(registry.all_of<clazz_t>(entity));
const auto any = ctor.invoke(std::ref(registry), entity, 3, 'c');
const auto any = ctor.invoke(entt::forward_as_meta(registry), entity, 3, 'c');
ASSERT_TRUE(any);
ASSERT_TRUE(registry.has<clazz_t>(entity));
ASSERT_TRUE(registry.all_of<clazz_t>(entity));
ASSERT_EQ(registry.get<clazz_t>(entity).i, 3);
ASSERT_EQ(registry.get<clazz_t>(entity).c, 'c');
}
TEST_F(MetaCtor, ImplicitlyGeneratedDefaultConstructor) {
auto type = entt::resolve<int>();
int counter{};
for([[maybe_unused]] auto curr: type.ctor()) {
++counter;
}
// default constructor is implicitly generated
ASSERT_EQ(counter, 1);
ASSERT_TRUE(type.ctor<>());
ASSERT_EQ(type.ctor<>().arity(), 0u);
ASSERT_EQ(type.ctor<>().arg(0), entt::meta_type{});
auto any = type.ctor<>().invoke();
ASSERT_TRUE(any);
ASSERT_EQ(any.type(), entt::resolve<int>());
ASSERT_EQ(any.cast<int>(), 0);
}
TEST_F(MetaCtor, OverrideImplicitlyGeneratedDefaultConstructor) {
auto type = entt::resolve<double>();
int counter{};
for([[maybe_unused]] auto curr: type.ctor()) {
++counter;
}
// default constructor is implicitly generated
ASSERT_EQ(counter, 2);
ASSERT_TRUE(type.ctor<>());
auto any = type.ctor<>().invoke();
ASSERT_TRUE(any);
ASSERT_EQ(any.type(), entt::resolve<double>());
ASSERT_EQ(any.cast<double>(), 42.);
}
TEST_F(MetaCtor, NonDefaultConstructibleType) {
auto type = entt::resolve<clazz_t>();
int counter{};
for([[maybe_unused]] auto curr: type.ctor()) {
++counter;
}
// the implicitly generated default constructor doesn't exist
ASSERT_EQ(counter, 5);
ASSERT_FALSE(type.ctor<>());
}
TEST_F(MetaCtor, ReRegistration) {
SetUp();
auto *node = entt::internal::meta_info<double>::resolve();
ASSERT_NE(node->ctor, nullptr);
// default constructor is implicitly generated
ASSERT_NE(node->ctor->next, nullptr);
ASSERT_EQ(node->ctor->next->next, nullptr);
}

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