Compare commits

..

180 Commits

Author SHA1 Message Date
Michele Caini
dd6863f71d update single include file 2021-07-28 09:58:14 +02:00
Michele Caini
dc7c976518 Ready to cut v3.8.1 2021-07-28 09:56:40 +02:00
Michele Caini
3408556eea sparse_set: fix an issue with assuring pages properly on emplace (close #746) 2021-07-27 18:53:30 +02:00
Michele Caini
f94b9773da build system: test id type std::uint64_t on the CI, all platforms 2021-07-26 23:37:55 +02:00
Michele Caini
e0b3786d97 test: make tests for entity traits work when id_type is std::uint64_t 2021-07-26 23:37:50 +02:00
Michele Caini
4047cb01a8 test: make tests for hashed string work when id_type is std::uint64_t 2021-07-26 23:37:46 +02:00
Michele Caini
8cfd08b137 sparse set: make vs2017 work (more or less) fine when id_type is std::uint64_t 2021-07-26 23:37:42 +02:00
Michele Caini
311011672c entity: avoid UBs when id type is std::uint64_t (close #745) 2021-07-26 23:37:39 +02:00
Hussein Taher
6df19c833d snapshot: fix warning for discarding a nodiscard (#728) 2021-07-26 23:37:36 +02:00
Michele Caini
78d9e71888 test: get rid of inconsistent line (close #741) 2021-07-26 23:37:32 +02:00
Michele Caini
94131648dd type_traits: try to also please gcc :) 2021-07-26 23:37:12 +02:00
Michele Caini
151f180199 core: make is_equality_comparable[_v] work with iterators (close #739) 2021-07-26 23:37:02 +02:00
Michele Caini
bb8bfaf262 meta_any: avoid risky fallthrough in the vtable (close #736) 2021-07-26 23:36:51 +02:00
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
87 changed files with 19607 additions and 9334 deletions

View File

@@ -9,30 +9,42 @@ jobs:
strategy:
matrix:
compiler: [
g++-7, g++-8, g++-9, g++,
clang++-8, clang++-9, clang++
]
compiler:
- pkg: g++-7
exe: g++-7
- pkg: g++-8
exe: g++-8
- pkg: g++-9
exe: g++-9
- pkg: g++
exe: g++
- pkg: clang-8
exe: clang++-8
- pkg: clang-9
exe: clang++-9
- pkg: clang-10
exe: clang++-10
- pkg: clang
exe: clang++
id_type: [uint32, uint64]
include:
- id_type: uint64
id_type_option: -DENTT_BUILD_UINT64=ON
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install g++-7
if: ${{ matrix.compiler == 'g++-7' }}
run: sudo apt install g++-7
- name: Install g++-8
if: ${{ matrix.compiler == 'g++-8' }}
run: sudo apt install g++-8
- name: Install clang-8
if: ${{ matrix.compiler == 'clang++-8' }}
run: sudo apt install clang-8
- name: Install compiler
run: |
sudo apt-get update
sudo apt-get install ${{ matrix.compiler.pkg }} -y
- name: Compile tests
working-directory: build
env:
CXX: ${{ matrix.compiler }}
CXX: ${{ matrix.compiler.exe }}
run: |
cmake -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON ..
cmake -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON ${{ matrix.id_type_option }} ..
make -j4
- name: Run tests
working-directory: build
@@ -47,11 +59,14 @@ jobs:
matrix:
os: [windows-latest, windows-2016]
toolset: [clang-cl, default, v141]
id_type: [uint32, uint64]
include:
- toolset: clang-cl
toolset_option: -T"ClangCl"
- toolset: v141
toolset_option: -T"v141"
- id_type: uint64
id_type_option: -DENTT_BUILD_UINT64=ON
exclude:
- os: windows-2016
toolset: clang-cl
@@ -65,7 +80,7 @@ jobs:
- name: Compile tests
working-directory: build
run: |
cmake -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON ${{ matrix.toolset_option }} ..
cmake -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON ${{ matrix.id_type_option }} ${{ matrix.toolset_option }} ..
cmake --build . -j 4
- name: Run tests
working-directory: build
@@ -75,6 +90,14 @@ jobs:
macos:
timeout-minutes: 10
strategy:
matrix:
id_type: [uint32, uint64]
include:
- id_type: uint64
id_type_option: -DENTT_BUILD_UINT64=ON
runs-on: macOS-latest
steps:
@@ -82,7 +105,7 @@ jobs:
- name: Compile tests
working-directory: build
run: |
cmake -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON ..
cmake -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON ${{ matrix.id_type_option }} ..
make -j4
- name: Run tests
working-directory: build

View File

@@ -10,6 +10,10 @@ jobs:
strategy:
matrix:
compiler: [clang++]
id_type: [uint32, uint64]
include:
- id_type: uint64
id_type_option: -DENTT_BUILD_UINT64=ON
runs-on: ubuntu-latest
@@ -20,7 +24,7 @@ jobs:
env:
CXX: ${{ matrix.compiler }}
run: |
cmake -DENTT_USE_SANITIZER=ON -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON ..
cmake -DENTT_USE_SANITIZER=ON -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON ${{ matrix.id_type_option }} ..
make -j4
- name: Run tests
working-directory: build

1
.gitignore vendored
View File

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

View File

@@ -173,6 +173,7 @@ if(ENTT_BUILD_TESTING)
option(ENTT_BUILD_EXAMPLE "Build examples." OFF)
option(ENTT_BUILD_LIB "Build lib tests." OFF)
option(ENTT_BUILD_SNAPSHOT "Build snapshot test with Cereal." OFF)
option(ENTT_BUILD_UINT64 "Build using 64b entity identifiers" OFF)
include(CTest)
enable_testing()

44
TODO
View File

@@ -1,5 +1,4 @@
* 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
@@ -7,24 +6,31 @@
* custom pools example (multi instance, tables, enable/disable, and so on...)
WIP:
* HP: scheduler, use any (or poly?) instead of unique_ptr
* HP: resource, forward the id to the loader from the cache and if constexpr the call to load, update doc and describe customization points
* HP: 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
* HP: add user data to type_info
* HP: make pools available (registry/view/group), review operator| for views
* HP: any_vector for context variables
* HP: make const registry::view thread safe, switch to a view<T...>{registry} model (long term goal)
* HP: weak reference wrapper example with custom storage
* HP: paginate pools
* HP: headless (sparse set only) view
* HP: write documentation for custom storages and views!!
* 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: add exclude-only views to combine with packs
* HP: entity-aware observer, add observer functions aside observer class
* HP: any and the like: remove constructor that accepts reference wrapper, allow only in-place T&?
* 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
* pagination doesn't work nicely across boundaries probably, give it a look. RO operations are fine, adding components maybe not.
* snapshot: support for range-based archives
* page size 0 -> page less mode
* add example: 64 bit ids with 32 bits reserved for users' purposes

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,14 +60,23 @@ 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.<br/>
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
For performance reasons, `EnTT` doesn't use exceptions or any other control

View File

@@ -247,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/>
@@ -272,20 +280,17 @@ 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:

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,6 +19,7 @@
* [Sorting: is it possible?](#sorting-is-it-possible)
* [Helpers](#helpers)
* [Null entity](#null-entity)
* [Tombstone](#tombstone)
* [To entity](#to-entity)
* [Dependencies](#dependencies)
* [Invoke](#invoke)
@@ -26,6 +27,10 @@
* [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)
@@ -43,6 +48,7 @@
* [Nested groups](#nested-groups)
* [Types: const, non-const and all in between](#types-const-non-const-and-all-in-between)
* [Give me everything](#give-me-everything)
* [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)
@@ -63,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/>
@@ -82,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
@@ -104,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.
@@ -171,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:
@@ -197,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
@@ -278,7 +301,7 @@ 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.);
@@ -296,24 +319,24 @@ bool all = registry.all_of<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>();
@@ -616,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/>
@@ -631,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
@@ -916,6 +980,163 @@ 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/>
@@ -947,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));
});
```
@@ -957,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);
});
```
@@ -1002,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
@@ -1067,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
@@ -1118,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.
@@ -1246,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
@@ -1344,8 +1564,7 @@ registry during iterations to get the types iterated by the view itself.
### View pack
Views can be combined with each other to create new and more specific
objects.<br/>
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.
@@ -1368,8 +1587,8 @@ the above type order rules apply sequentially.
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
@@ -1757,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
@@ -1780,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
@@ -1853,8 +2112,8 @@ only the entities to which it's assigned are made available.<br/>
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
won't be available. Finally, the `sort` functionality will onlyaccepts callbacks
that require to return entities rather than 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) {
@@ -1869,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

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

@@ -112,6 +112,10 @@ I hope this list can grow much more in the future:
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

View File

@@ -210,9 +210,10 @@ 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{};
@@ -386,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) {

View File

@@ -312,13 +312,13 @@ 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 shape;
drawable instance{std::ref(shape)};
drawable instance{std::in_place_type<circle &>, shape};
```
Similarly, it's possible to create non-owning copies of `poly` from an existing

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 *);`

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,10 +37,17 @@
#endif
#ifdef ENTT_PAGE_SIZE
static_assert(ENTT_PAGE_SIZE && ((ENTT_PAGE_SIZE & (ENTT_PAGE_SIZE - 1)) == 0), "ENTT_PAGE_SIZE must be a power of two");
#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_PAGE_SIZE 4096
# 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
@@ -33,16 +56,16 @@ static_assert(ENTT_PAGE_SIZE && ((ENTT_PAGE_SIZE & (ENTT_PAGE_SIZE - 1)) == 0),
# 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,8 +3,8 @@
#define ENTT_VERSION_MAJOR 3
#define ENTT_VERSION_MINOR 7
#define ENTT_VERSION_PATCH 0
#define ENTT_VERSION_MINOR 8
#define ENTT_VERSION_PATCH 1
#endif

View File

@@ -5,10 +5,10 @@
#include <cstddef>
#include <functional>
#include <memory>
#include <new>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "../core/utility.hpp"
#include "fwd.hpp"
#include "type_info.hpp"
#include "type_traits.hpp"
@@ -24,14 +24,28 @@ namespace entt {
*/
template<std::size_t Len, std::size_t Align>
class basic_any {
enum class operation { COPY, MOVE, DTOR, COMP, ADDR, CADDR, REF, CREF, TYPE };
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<Len + !Len, Align>;
using vtable_type = const void *(const operation, const basic_any &, const void *);
using vtable_type = const void *(const operation, const basic_any &, void *);
template<typename 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) {
if constexpr(!std::is_function_v<Type> && is_equality_comparable_v<Type>) {
@@ -42,178 +56,148 @@ class basic_any {
}
template<typename Type>
static Type & as(const void *to) {
return *const_cast<Type *>(static_cast<const Type *>(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");
template<typename Type>
static const void * basic_vtable([[maybe_unused]] const operation op, [[maybe_unused]] const basic_any &from, [[maybe_unused]] const void *to) {
if constexpr(!std::is_void_v<Type>) {
if constexpr(std::is_lvalue_reference_v<Type>) {
using base_type = std::remove_const_t<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::COPY:
if constexpr(std::is_copy_constructible_v<base_type>) {
as<basic_any>(to).template emplace<base_type>(*static_cast<const base_type *>(from.instance));
}
break;
case operation::MOVE:
as<basic_any>(to).instance = from.instance;
[[fallthrough]];
case operation::DTOR:
break;
case operation::COMP:
return compare<base_type>(from.instance, to) ? to : nullptr;
case operation::ADDR:
return std::is_const_v<std::remove_reference_t<Type>> ? nullptr : from.instance;
case operation::CADDR:
return from.instance;
case operation::REF:
as<basic_any>(to).instance = from.instance;
as<basic_any>(to).vtable = basic_vtable<Type>;
break;
case operation::CREF:
as<basic_any>(to).instance = from.instance;
as<basic_any>(to).vtable = basic_vtable<const base_type &>;
break;
case operation::TYPE:
as<type_info>(to) = type_id<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 defined(__cpp_lib_launder) && __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:
if constexpr(std::is_copy_constructible_v<Type>) {
new (&as<basic_any>(to).storage) Type{std::as_const(*instance)};
as<basic_any>(to).vtable = from.vtable;
}
break;
case operation::MOVE:
new (&as<basic_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:
as<basic_any>(to).instance = instance;
as<basic_any>(to).vtable = basic_vtable<Type &>;
break;
case operation::CREF:
as<basic_any>(to).instance = instance;
as<basic_any>(to).vtable = basic_vtable<const Type &>;
break;
case operation::TYPE:
as<type_info>(to) = type_id<Type>();
break;
}
} else {
switch(op) {
case operation::COPY:
if constexpr(std::is_copy_constructible_v<Type>) {
as<basic_any>(to).instance = new Type{*static_cast<const Type *>(from.instance)};
as<basic_any>(to).vtable = from.vtable;
}
break;
case operation::MOVE:
as<basic_any>(to).instance = from.instance;
break;
case operation::DTOR:
if constexpr(std::is_array_v<Type>) {
delete[] static_cast<const Type *>(from.instance);
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 static_cast<const Type *>(from.instance);
delete instance;
}
break;
case operation::COMP:
return compare<Type>(from.instance, to) ? to : nullptr;
case operation::ADDR:
case operation::CADDR:
return from.instance;
case operation::REF:
as<basic_any>(to).instance = from.instance;
as<basic_any>(to).vtable = basic_vtable<Type &>;
break;
case operation::CREF:
as<basic_any>(to).instance = from.instance;
as<basic_any>(to).vtable = basic_vtable<const Type &>;
break;
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;
}
public:
/*! @brief Default constructor. */
basic_any() ENTT_NOEXCEPT
: basic_any{std::in_place_type<void>}
{}
/**
* @brief Constructs an any 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 basic_any(std::in_place_type_t<Type>, [[maybe_unused]] Args &&... args)
: instance{},
vtable{&basic_vtable<Type>}
{
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>) {
new (&storage) Type(std::forward<Args>(args)...);
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 {
instance = new Type(std::forward<Args>(args)...);
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. */
basic_any() ENTT_NOEXCEPT
: instance{},
vtable{&basic_vtable<void>},
mode{policy::OWNER}
{}
/**
* @brief Constructs an any that holds an unmanaged 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 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>()}
{
initialize<Type>(std::forward<Args>(args)...);
}
/**
* @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>
basic_any(std::reference_wrapper<Type> value) ENTT_NOEXCEPT
: basic_any{std::in_place_type<Type &>, value.get()}
{}
: 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::decay_t<Type>, basic_any>>>
basic_any(Type &&value)
: basic_any{std::in_place_type<std::decay_t<Type>>, std::forward<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.
*/
basic_any(const basic_any &other)
: basic_any{}
: instance{},
vtable{&basic_vtable<void>},
mode{policy::OWNER}
{
other.vtable(operation::COPY, other, this);
}
@@ -223,10 +207,11 @@ public:
* @param other The instance to move from.
*/
basic_any(basic_any &&other) ENTT_NOEXCEPT
: basic_any{}
: instance{},
vtable{other.vtable},
mode{other.mode}
{
other.vtable(operation::MOVE, other, this);
vtable = std::exchange(other.vtable, &basic_vtable<void>);
vtable(operation::MOVE, other, this);
}
/*! @brief Frees the internal storage, whatever it means. */
@@ -235,12 +220,51 @@ public:
}
/**
* @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.
*/
basic_any & operator=(basic_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;
}
@@ -249,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;
}
@@ -275,12 +299,15 @@ public:
*/
template<typename Type, typename... Args>
void emplace(Args &&... args) {
*this = basic_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() {
*this = basic_any{};
std::exchange(vtable, &basic_vtable<void>)(operation::DTOR, *this, nullptr);
mode = policy::OWNER;
}
/**
@@ -297,42 +324,35 @@ public:
* @return False if the two objects differ in their content, true otherwise.
*/
bool operator==(const basic_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(basic_any &lhs, basic_any &rhs) {
basic_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);
const basic_any *trampoline = &other;
return type() == other.type() && (vtable(operation::COMP, *this, &trampoline) || !other.data());
}
/**
* @brief Aliasing constructor.
* @return An any that shares a reference to an unmanaged object.
* @return A wrapper that shares a reference to an unmanaged object.
*/
[[nodiscard]] basic_any as_ref() ENTT_NOEXCEPT {
basic_any ref{};
vtable(operation::REF, *this, &ref);
return ref;
return basic_any{*this, (mode == policy::CREF ? policy::CREF : policy::REF)};
}
/*! @copydoc as_ref */
[[nodiscard]] basic_any as_ref() const ENTT_NOEXCEPT {
basic_any ref{};
vtable(operation::CREF, *this, &ref);
return ref;
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:
union { const void *instance; storage_type storage; };
vtable_type *vtable;
policy mode;
};
@@ -361,7 +381,7 @@ template<std::size_t Len, std::size_t Align>
template<typename Type, std::size_t Len, std::size_t Align>
Type any_cast(const basic_any<Len, Align> &data) 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);
}
@@ -370,8 +390,8 @@ Type any_cast(const basic_any<Len, Align> &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);
}
@@ -380,8 +400,8 @@ Type any_cast(basic_any<Len, Align> &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));
}
@@ -401,6 +421,35 @@ Type * any_cast(basic_any<Len, Align> *data) ENTT_NOEXCEPT {
}
/**
* @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

@@ -9,7 +9,7 @@
namespace entt {
template<std::size_t Len, std::size_t = alignof(typename std::aligned_storage_t<Len + !Len>)>
template<std::size_t Len = sizeof(double[2]), std::size_t = alignof(typename std::aligned_storage_t<Len + !Len>)>
class basic_any;
@@ -18,7 +18,7 @@ using id_type = ENTT_ID_TYPE;
/*! @brief Alias declaration for the most common use case. */
using any = basic_any<sizeof(double[2])>;
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

@@ -3,8 +3,9 @@
#include <cstddef>
#include <utility>
#include <iterator>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "fwd.hpp"
@@ -82,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;
/**
@@ -287,7 +288,7 @@ 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. */
@@ -421,64 +422,6 @@ 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>()) {
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.
*/
template<typename Type, typename = void>
struct is_equality_comparable: std::bool_constant<internal::is_equality_comparable<Type>(choice<2>)> {};
/**
* @brief Helper variable template.
* @tparam Type Potentially equality comparable type.
*/
template<class Type>
inline constexpr auto is_equality_comparable_v = is_equality_comparable<Type>::value;
/*! @brief Same as std::is_invocable, but with tuples. */
template<typename, typename>
struct is_applicable: std::false_type {};
@@ -495,11 +438,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...> {};
@@ -510,7 +453,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. */
@@ -537,14 +480,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 {};
@@ -555,52 +498,132 @@ 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
* hashable, false otherwise.
* @tparam Type Potentially hashable 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_std_hashable: std::false_type {};
template<typename Type, typename = void>
struct is_iterator: std::false_type {};
/*! @copydoc is_std_hashable */
template <typename Type>
struct is_std_hashable<Type, std::enable_if_t<std::is_convertible_v<decltype(std::declval<std::hash<Type>>()(std::declval<Type>())), std::size_t>>>
/*! @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 Potentially hashable type.
* @tparam Type The type to test.
*/
template <typename Type>
inline constexpr auto is_std_hashable_v = is_std_hashable<Type>::value;
template<typename Type>
inline constexpr bool is_iterator_v = is_iterator<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 of the
* required iterator type, false otherwise.
* @tparam Type The type to test.
* @tparam It Required iterator type.
*/
template<typename Type, typename = void>
struct is_empty: ENTT_IS_EMPTY(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 Potential empty type.
* @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;
/**
* @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>
inline constexpr auto is_empty_v = is_empty<Type>::value;
[[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(is_iterator_v<Type>) {
return true;
} else 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 The type to test.
*/
template<typename Type, typename = void>
struct is_equality_comparable: std::bool_constant<internal::is_equality_comparable<Type>(choice<2>)> {};
/**
* @brief Helper variable template.
* @tparam Type The type to test.
*/
template<class Type>
inline constexpr bool is_equality_comparable_v = is_equality_comparable<Type>::value;
/**

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) | (static_cast<entity_type>(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_void_v<decltype(std::declval<storage_type<Owned>>().get({}))>, 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_void_v<decltype(std::declval<storage_type<Owned>>().get({}))>, 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 {
[[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);
@@ -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,16 +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) {
Entity to_entity(const basic_registry<Entity> &reg, const Component &instance) {
const auto view = reg.template view<const Component>();
return *(view.data() + (&component - view.raw()));
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

@@ -191,7 +191,7 @@ class basic_observer {
template<std::size_t Index>
static void discard_if(basic_observer &obs, basic_registry<Entity> &, const Entity entt) {
if(obs.storage.contains(entt) && !(obs.storage.get(entt) &= (~(1 << Index)))) {
obs.storage.remove(entt);
obs.storage.erase(entt);
}
}
@@ -234,7 +234,7 @@ class basic_observer {
template<std::size_t Index>
static void discard_if(basic_observer &obs, basic_registry<Entity> &, const Entity entt) {
if(obs.storage.contains(entt) && !(obs.storage.get(entt) &= (~(1 << Index)))) {
obs.storage.remove(entt);
obs.storage.erase(entt);
}
}

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

@@ -16,6 +16,7 @@
#include "../core/fwd.hpp"
#include "../core/type_info.hpp"
#include "../core/type_traits.hpp"
#include "component.hpp"
#include "entity.hpp"
#include "fwd.hpp"
#include "group.hpp"
@@ -44,13 +45,14 @@ template<typename Entity>
class basic_registry {
using traits_type = entt_traits<Entity>;
using poly_storage_type = typename poly_storage_traits<Entity>::storage_type;
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>;
struct pool_data {
poly_storage_type poly;
std::unique_ptr<basic_sparse_set<Entity>> pool{};
std::unique_ptr<basic_common_type> pool{};
};
template<typename...>
@@ -58,8 +60,9 @@ class basic_registry {
template<typename... Exclude, typename... Get, typename... Owned>
struct group_handler<exclude_t<Exclude...>, get_t<Get...>, Owned...> {
static_assert(!std::disjunction_v<typename component_traits<Owned>::in_place_delete...>, "Groups do not support in-place delete");
static_assert(std::conjunction_v<std::is_same<Owned, std::remove_const_t<Owned>>..., std::is_same<Get, std::remove_const_t<Get>>..., std::is_same<Exclude, std::remove_const_t<Exclude>>...>, "One or more component types are invalid");
std::conditional_t<sizeof...(Owned) == 0, basic_sparse_set<Entity>, std::size_t> current{};
std::conditional_t<sizeof...(Owned) == 0, basic_common_type, std::size_t> current{};
template<typename Component>
void maybe_valid_if(basic_registry &owner, const Entity entt) {
@@ -83,9 +86,7 @@ class basic_registry {
void discard_if([[maybe_unused]] basic_registry &owner, const Entity entt) {
if constexpr(sizeof...(Owned) == 0) {
if(current.contains(entt)) {
current.remove(entt);
}
current.remove(entt);
} else {
if(const auto cpools = std::make_tuple(owner.assure<Owned>()...); std::get<0>(cpools)->contains(entt) && (std::get<0>(cpools)->index(entt) < current)) {
const auto pos = --current;
@@ -121,30 +122,29 @@ class basic_registry {
}
template<typename Component>
[[nodiscard]] const storage_type<Component> * pool_if_exists() const {
[[nodiscard]] const storage_type<Component> * pool_if_exists() const ENTT_NOEXCEPT {
static_assert(std::is_same_v<Component, std::decay_t<Component>>, "Non-decayed types not allowed");
const auto index = type_seq<Component>::value();
return (!(index < pools.size()) || !pools[index].pool) ? nullptr : static_cast<const storage_type<Component> *>(pools[index].pool.get());
}
Entity generate_identifier() {
// traits_type::entity_mask is reserved to allow for null identifiers
ENTT_ASSERT(static_cast<typename traits_type::entity_type>(entities.size()) < traits_type::entity_mask);
return entities.emplace_back(entity_type{static_cast<typename traits_type::entity_type>(entities.size())});
auto generate_identifier(const std::size_t pos) ENTT_NOEXCEPT {
ENTT_ASSERT(pos < traits_type::to_integral(null), "No entities available");
return traits_type::construct(static_cast<typename traits_type::entity_type>(pos), {});
}
Entity recycle_identifier() {
ENTT_ASSERT(available != null);
const auto curr = to_integral(available);
const auto version = to_integral(entities[curr]) & (traits_type::version_mask << traits_type::entity_shift);
available = entity_type{to_integral(entities[curr]) & traits_type::entity_mask};
return entities[curr] = entity_type{curr | version};
auto recycle_identifier() ENTT_NOEXCEPT {
ENTT_ASSERT(free_list != null, "No entities available");
const auto curr = traits_type::to_entity(free_list);
free_list = (tombstone | entities[curr]);
return (entities[curr] = traits_type::construct(curr, traits_type::to_version(entities[curr])));
}
void release_entity(const Entity entity, const typename traits_type::version_type version) {
const auto entt = to_integral(entity) & traits_type::entity_mask;
entities[entt] = entity_type{to_integral(available) | (typename traits_type::entity_type{version} << traits_type::entity_shift)};
available = entity_type{entt};
auto release_entity(const Entity entity, const typename traits_type::version_type version) {
const typename traits_type::version_type vers = version + (version == traits_type::to_version(tombstone));
entities[traits_type::to_entity(entity)] = traits_type::construct(traits_type::to_entity(free_list), vers);
free_list = (tombstone | entity);
return vers;
}
public:
@@ -163,7 +163,7 @@ public:
* @return The entity identifier without the version.
*/
[[nodiscard]] static entity_type entity(const entity_type entity) ENTT_NOEXCEPT {
return entity_type{to_integral(entity) & traits_type::entity_mask};
return traits_type::construct(traits_type::to_entity(entity), {});
}
/**
@@ -172,7 +172,7 @@ public:
* @return The version stored along with the given entity identifier.
*/
[[nodiscard]] static version_type version(const entity_type entity) ENTT_NOEXCEPT {
return version_type(to_integral(entity) >> traits_type::entity_shift);
return traits_type::to_version(entity);
}
/*! @brief Default constructor. */
@@ -201,13 +201,13 @@ public:
* empty and thus invalid element otherwise.
*/
poly_storage & storage(const type_info info) {
ENTT_ASSERT(info.seq() < pools.size() && pools[info.seq()].poly);
ENTT_ASSERT(info.seq() < pools.size() && pools[info.seq()].poly, "Storage not available");
return pools[info.seq()].poly;
}
/*! @copydoc storage */
const poly_storage & storage(const type_info info) const {
ENTT_ASSERT(info.seq() < pools.size() && pools[info.seq()].poly);
ENTT_ASSERT(info.seq() < pools.size() && pools[info.seq()].poly, "Storage not available");
return pools[info.seq()].poly;
}
@@ -237,8 +237,8 @@ public:
[[nodiscard]] size_type alive() const {
auto sz = entities.size();
for(auto curr = available; curr != null; --sz) {
curr = entities[to_integral(curr) & traits_type::entity_mask];
for(auto curr = free_list; curr != null; --sz) {
curr = entities[traits_type::to_entity(curr)];
}
return sz;
@@ -270,6 +270,7 @@ public:
* @brief Reserves enough space to store `count` pools.
* @param count Number of pools to reserve space for.
*/
[[deprecated("No longer supported")]]
void reserve_pools(const size_t count) {
pools.reserve(count);
}
@@ -341,15 +342,21 @@ public:
}
/**
* @brief Returns the head of the list of destroyed entities.
* @brief Returns the head of the list of released entities.
*
* This function is intended for use in conjunction with `assign`.<br/>
* The returned entity has an invalid identifier in all cases.
*
* @return The head of the list of destroyed entities.
* @return The head of the list of released entities.
*/
[[nodiscard]] entity_type released() const ENTT_NOEXCEPT {
return free_list;
}
/*! @copydoc released */
[[deprecated("Use ::released instead")]]
[[nodiscard]] entity_type destroyed() const ENTT_NOEXCEPT {
return available;
return released();
}
/**
@@ -358,7 +365,7 @@ public:
* @return True if the identifier is valid, false otherwise.
*/
[[nodiscard]] bool valid(const entity_type entity) const {
const auto pos = size_type(to_integral(entity) & traits_type::entity_mask);
const auto pos = size_type(traits_type::to_entity(entity));
return (pos < entities.size() && entities[pos] == entity);
}
@@ -374,8 +381,8 @@ public:
* @return Actual version for the given entity identifier.
*/
[[nodiscard]] version_type current(const entity_type entity) const {
const auto pos = size_type(to_integral(entity) & traits_type::entity_mask);
ENTT_ASSERT(pos < entities.size());
const auto pos = size_type(traits_type::to_entity(entity));
ENTT_ASSERT(pos < entities.size(), "Entity does not exist");
return version(entities[pos]);
}
@@ -389,8 +396,8 @@ public:
*
* @return A valid entity identifier.
*/
entity_type create() {
return available == null ? generate_identifier() : recycle_identifier();
[[nodiscard]] entity_type create() {
return (free_list == null) ? entities.emplace_back(generate_identifier(entities.size())) : recycle_identifier();
}
/**
@@ -399,33 +406,32 @@ public:
* @sa create
*
* If the requested entity isn't in use, the suggested identifier is created
* and returned. Otherwise, a new one will be generated for this purpose.
* and returned. Otherwise, a new identifier is generated.
*
* @param hint A desired entity identifier.
* @param hint Required entity identifier.
* @return A valid entity identifier.
*/
[[nodiscard]] entity_type create(const entity_type hint) {
ENTT_ASSERT(hint != null);
entity_type entt;
const auto length = entities.size();
if(const auto req = (to_integral(hint) & traits_type::entity_mask); !(req < entities.size())) {
entities.reserve(size_type(req) + 1u);
if(hint == null || hint == tombstone) {
return create();
} else if(const auto req = traits_type::to_entity(hint); !(req < length)) {
entities.resize(size_type(req) + 1u, null);
for(auto pos = entities.size(); pos < req; ++pos) {
release_entity(generate_identifier(), {});
for(auto pos = length; pos < req; ++pos) {
release_entity(generate_identifier(pos), {});
}
entt = entities.emplace_back(hint);
} else if(const auto curr = (to_integral(entities[req]) & traits_type::entity_mask); req == curr) {
entt = create();
return (entities[req] = hint);
} else if(const auto curr = traits_type::to_entity(entities[req]); req == curr) {
return create();
} else {
auto *it = &available;
for(; (to_integral(*it) & traits_type::entity_mask) != req; it = &entities[to_integral(*it) & traits_type::entity_mask]);
*it = entity_type{curr | (to_integral(*it) & (traits_type::version_mask << traits_type::entity_shift))};
entt = entities[req] = hint;
auto *it = &free_list;
for(; traits_type::to_entity(*it) != req; it = &entities[traits_type::to_entity(*it)]);
*it = traits_type::construct(curr, traits_type::to_version(*it));
return (entities[req] = hint);
}
return entt;
}
/**
@@ -439,12 +445,15 @@ public:
*/
template<typename It>
void create(It first, It last) {
for(; available != null && first != last; ++first) {
for(; free_list != null && first != last; ++first) {
*first = recycle_identifier();
}
for(; first != last; ++first) {
*first = generate_identifier();
const auto length = entities.size();
entities.resize(length + std::distance(first, last), null);
for(auto pos = length; first != last; ++first, ++pos) {
*first = entities[pos] = generate_identifier(pos);
}
}
@@ -467,43 +476,102 @@ public:
*/
template<typename It>
void assign(It first, It last, const entity_type destroyed) {
ENTT_ASSERT(!alive());
ENTT_ASSERT(!alive(), "Entities still alive");
entities.assign(first, last);
available = destroyed;
free_list = destroyed;
}
/**
* @brief Destroys an entity.
* @brief Releases an entity identifier.
*
* When an entity is destroyed, its version is updated and the identifier
* can be recycled at any time.
* The version is updated and the identifier can be recycled at any time.
*
* @sa remove_all
* @warning
* Attempting to use an invalid entity results in undefined behavior.
*
* @param entity A valid entity identifier.
* @return The version of the recycled entity.
*/
void destroy(const entity_type entity) {
destroy(entity, static_cast<typename traits_type::version_type>(version(entity) + 1u));
version_type release(const entity_type entity) {
return release(entity, version(entity) + 1u);
}
/**
* @brief Destroys an entity.
* @brief Releases an entity identifier.
*
* If the entity isn't already destroyed, the suggested version is used
* instead of the implicitly generated one.
* The suggested version or the valid version closest to the suggested one
* is used instead of the implicitly generated version.
*
* @sa remove_all
* @sa release
*
* @param entity A valid entity identifier.
* @param version A desired version upon destruction.
* @return The version actually assigned to the entity.
*/
void destroy(const entity_type entity, const version_type version) {
remove_all(entity);
release_entity(entity, version);
version_type release(const entity_type entity, const version_type version) {
ENTT_ASSERT(orphan(entity), "Non-orphan entity");
return release_entity(entity, version);
}
/**
* @brief Destroys all the entities in a range.
* @brief Releases all entity identifiers in a range.
*
* @sa release
*
* @tparam It Type of input iterator.
* @param first An iterator to the first element of the range of entities.
* @param last An iterator past the last element of the range of entities.
*/
template<typename It>
void release(It first, It last) {
for(; first != last; ++first) {
release(*first, version(*first) + 1u);
}
}
/**
* @brief Destroys an entity and releases its identifier.
*
* The version is updated and the identifier can be recycled at any time.
*
* @warning
* Adding or removing components to an entity that is being destroyed can
* result in undefined behavior.
*
* @warning
* Attempting to use an invalid entity results in undefined behavior.
*
* @param entity A valid entity identifier.
* @return The version of the recycled entity.
*/
version_type destroy(const entity_type entity) {
return destroy(entity, version(entity) + 1u);
}
/**
* @brief Destroys an entity and releases its identifier.
*
* The suggested version or the valid version closest to the suggested one
* is used instead of the implicitly generated version.
*
* @sa destroy
*
* @param entity A valid entity identifier.
* @param version A desired version upon destruction.
* @return The version actually assigned to the entity.
*/
version_type destroy(const entity_type entity, const version_type version) {
ENTT_ASSERT(valid(entity), "Invalid entity");
for(auto &&pdata: pools) {
pdata.pool && pdata.pool->remove(entity, this);
}
return release_entity(entity, version);
}
/**
* @brief Destroys all entities in a range and releases their identifiers.
*
* @sa destroy
*
@@ -513,8 +581,16 @@ public:
*/
template<typename It>
void destroy(It first, It last) {
for(; first != last; ++first) {
destroy(*first);
if constexpr(is_iterator_type_v<typename basic_common_type::iterator, It>) {
for(; first != last; ++first) {
destroy(*first, version(*first) + 1u);
}
} else {
for(auto &&pdata: pools) {
pdata.pool && pdata.pool->remove(first, last, this);
}
release(first, last);
}
}
@@ -537,7 +613,7 @@ public:
*/
template<typename Component, typename... Args>
decltype(auto) emplace(const entity_type entity, Args &&... args) {
ENTT_ASSERT(valid(entity));
ENTT_ASSERT(valid(entity), "Invalid entity");
return assure<Component>()->emplace(*this, entity, std::forward<Args>(args)...);
}
@@ -554,7 +630,7 @@ public:
*/
template<typename Component, typename It>
void insert(It first, It last, const Component &value = {}) {
ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }));
ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }), "Invalid entity");
assure<Component>()->insert(*this, first, last, value);
}
@@ -569,13 +645,12 @@ 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 components.
* @param to An iterator past the last element of the range of components.
*/
template<typename Component, typename EIt, typename CIt>
void insert(EIt first, EIt last, CIt from, CIt to) {
template<typename Component, typename EIt, typename CIt, typename = std::enable_if_t<std::is_same_v<std::decay_t<typename std::iterator_traits<CIt>::value_type>, Component>>>
void insert(EIt first, EIt last, CIt from) {
static_assert(std::is_constructible_v<Component, typename std::iterator_traits<CIt>::value_type>, "Invalid value type");
ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }));
assure<Component>()->insert(*this, first, last, from, to);
ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }), "Invalid entity");
assure<Component>()->insert(*this, first, last, from);
}
/**
@@ -600,7 +675,7 @@ public:
*/
template<typename Component, typename... Args>
decltype(auto) emplace_or_replace(const entity_type entity, Args &&... args) {
ENTT_ASSERT(valid(entity));
ENTT_ASSERT(valid(entity), "Invalid entity");
auto *cpool = assure<Component>();
return cpool->contains(entity)
@@ -634,7 +709,7 @@ public:
*/
template<typename Component, typename... Func>
decltype(auto) patch(const entity_type entity, Func &&... func) {
ENTT_ASSERT(valid(entity));
ENTT_ASSERT(valid(entity), "Invalid entity");
return assure<Component>()->patch(*this, entity, std::forward<Func>(func)...);
}
@@ -664,17 +739,17 @@ public:
* @brief Removes the given components from an entity.
*
* @warning
* Attempting to use an invalid entity or to remove a component from an
* entity that doesn't own it results in undefined behavior.
* Attempting to use an invalid entity results in undefined behavior.
*
* @tparam Component Types of components to remove.
* @param entity A valid entity identifier.
* @return The number of components actually removed.
*/
template<typename... Component>
void remove(const entity_type entity) {
ENTT_ASSERT(valid(entity));
size_type remove(const entity_type entity) {
ENTT_ASSERT(valid(entity), "Invalid entity");
static_assert(sizeof...(Component) > 0, "Provide one or more component types");
(assure<Component>()->remove(entity, this), ...);
return (assure<Component>()->remove(entity, this) + ... + size_type{});
}
/**
@@ -686,39 +761,83 @@ public:
* @tparam It Type of input iterator.
* @param first An iterator to the first element of the range of entities.
* @param last An iterator past the last element of the range of entities.
* @return The number of components actually removed.
*/
template<typename... Component, typename It>
void remove(It first, It last) {
ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }));
size_type remove(It first, It last) {
static_assert(sizeof...(Component) > 0, "Provide one or more component types");
(assure<Component>()->remove(first, last, this), ...);
const auto cpools = std::make_tuple(assure<Component>()...);
size_type count{};
for(; first != last; ++first) {
const auto entity = *first;
ENTT_ASSERT(valid(entity), "Invalid entity");
count += (std::get<storage_type<Component> *>(cpools)->remove(entity, this) + ...);
}
return count;
}
/**
* @brief Removes the given components from an entity.
*
* Equivalent to the following snippet (pseudocode):
*
* @code{.cpp}
* if(registry.all_of<Component>(entity)) { registry.remove<Component>(entity) }
* @endcode
*
* Prefer this function anyway because it has slightly better performance.
* @brief Erases the given components from an entity.
*
* @warning
* Attempting to use an invalid entity results in undefined behavior.
* Attempting to use an invalid entity or to erase a component from an
* entity that doesn't own it results in undefined behavior.
*
* @tparam Component Types of components to remove.
* @tparam Component Types of components to erase.
* @param entity A valid entity identifier.
* @return The number of components actually removed.
*/
template<typename... Component>
size_type remove_if_exists(const entity_type entity) {
ENTT_ASSERT(valid(entity));
void erase(const entity_type entity) {
ENTT_ASSERT(valid(entity), "Invalid entity");
static_assert(sizeof...(Component) > 0, "Provide one or more component types");
(assure<Component>()->erase(entity, this), ...);
}
return ([this, entity](auto *cpool) {
return cpool->contains(entity) ? (cpool->remove(entity, this), true) : false;
}(assure<Component>()) + ... + size_type{});
/**
* @brief Erases the given components from all the entities in a range.
*
* @sa erase
*
* @tparam Component Types of components to 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.
*/
template<typename... Component, typename It>
void erase(It first, It last) {
static_assert(sizeof...(Component) > 0, "Provide one or more component types");
const auto cpools = std::make_tuple(assure<Component>()...);
for(; first != last; ++first) {
const auto entity = *first;
ENTT_ASSERT(valid(entity), "Invalid entity");
(std::get<storage_type<Component> *>(cpools)->erase(entity, this), ...);
}
}
/**
* @brief Removes all tombstones from a registry or only the pools for the
* given components.
* @tparam Component Types of components for which to clear all tombstones.
*/
template<typename... Component>
void compact() {
if constexpr(sizeof...(Component) == 0) {
for(auto &&pdata: pools) {
pdata.pool && (pdata.pool->compact(), true);
}
} else {
(assure<Component>()->compact(), ...);
}
}
/*! @copydoc remove */
template<typename... Component>
[[deprecated("Use ::remove instead")]]
size_type remove_if_exists(const entity_type entity) {
return remove<Component...>(entity);
}
/**
@@ -735,14 +854,12 @@ public:
*
* @param entity A valid entity identifier.
*/
[[deprecated("Use ::destroy(entity)/::create(entity) instead")]]
void remove_all(const entity_type entity) {
ENTT_ASSERT(valid(entity));
entity_type wrap[1u]{entity};
ENTT_ASSERT(valid(entity), "Invalid entity");
for(auto pos = pools.size(); pos; --pos) {
if(auto &pdata = pools[pos-1]; pdata.pool && pdata.pool->contains(entity)) {
pdata.pool->remove(std::begin(wrap), std::end(wrap), this);
}
for(auto &&pdata: pools) {
pdata.pool && pdata.pool->remove(entity, this);
}
}
@@ -758,7 +875,7 @@ public:
*/
template<typename... Component>
[[nodiscard]] bool all_of(const entity_type entity) const {
ENTT_ASSERT(valid(entity));
ENTT_ASSERT(valid(entity), "Invalid entity");
return [entity](const auto *... cpool) { return ((cpool && cpool->contains(entity)) && ...); }(pool_if_exists<Component>()...);
}
@@ -775,7 +892,7 @@ public:
*/
template<typename... Component>
[[nodiscard]] bool any_of(const entity_type entity) const {
ENTT_ASSERT(valid(entity));
ENTT_ASSERT(valid(entity), "Invalid entity");
return [entity](const auto *... cpool) { return !((!cpool || !cpool->contains(entity)) && ...); }(pool_if_exists<Component>()...);
}
@@ -792,11 +909,11 @@ public:
*/
template<typename... Component>
[[nodiscard]] decltype(auto) get([[maybe_unused]] const entity_type entity) const {
ENTT_ASSERT(valid(entity));
ENTT_ASSERT(valid(entity), "Invalid entity");
if constexpr(sizeof...(Component) == 1) {
const auto *cpool = pool_if_exists<std::remove_const_t<Component>...>();
ENTT_ASSERT(cpool);
ENTT_ASSERT(cpool, "Storage not available");
return cpool->get(entity);
} else {
return std::forward_as_tuple(get<Component>(entity)...);
@@ -806,7 +923,7 @@ public:
/*! @copydoc get */
template<typename... Component>
[[nodiscard]] decltype(auto) get([[maybe_unused]] const entity_type entity) {
ENTT_ASSERT(valid(entity));
ENTT_ASSERT(valid(entity), "Invalid entity");
if constexpr(sizeof...(Component) == 1) {
return (const_cast<Component &>(assure<std::remove_const_t<Component>>()->get(entity)), ...);
@@ -839,7 +956,7 @@ public:
*/
template<typename Component, typename... Args>
[[nodiscard]] decltype(auto) get_or_emplace(const entity_type entity, Args &&... args) {
ENTT_ASSERT(valid(entity));
ENTT_ASSERT(valid(entity), "Invalid entity");
auto *cpool = assure<Component>();
return cpool->contains(entity) ? cpool->get(entity) : cpool->emplace(*this, entity, std::forward<Args>(args)...);
}
@@ -859,7 +976,7 @@ public:
*/
template<typename... Component>
[[nodiscard]] auto try_get([[maybe_unused]] const entity_type entity) const {
ENTT_ASSERT(valid(entity));
ENTT_ASSERT(valid(entity), "Invalid entity");
if constexpr(sizeof...(Component) == 1) {
const auto *cpool = pool_if_exists<std::remove_const_t<Component>...>();
@@ -872,7 +989,7 @@ public:
/*! @copydoc try_get */
template<typename... Component>
[[nodiscard]] auto try_get([[maybe_unused]] const entity_type entity) {
ENTT_ASSERT(valid(entity));
ENTT_ASSERT(valid(entity), "Invalid entity");
if constexpr(sizeof...(Component) == 1) {
return (const_cast<Component *>(std::as_const(*this).template try_get<Component>(entity)), ...);
@@ -888,17 +1005,13 @@ public:
template<typename... Component>
void clear() {
if constexpr(sizeof...(Component) == 0) {
for(auto pos = pools.size(); pos; --pos) {
if(auto &pdata = pools[pos-1]; pdata.pool) {
pdata.pool->clear(this);
}
for(auto &&pdata: pools) {
pdata.pool && (pdata.pool->clear(this), true);
}
each([this](const auto entity) { release_entity(entity, version(entity) + 1u); });
} else {
([this](auto *cpool) {
cpool->remove(cpool->basic_sparse_set<entity_type>::begin(), cpool->basic_sparse_set<entity_type>::end(), this);
}(assure<Component>()), ...);
(assure<Component>()->clear(this), ...);
}
}
@@ -921,13 +1034,13 @@ public:
*/
template<typename Func>
void each(Func func) const {
if(available == null) {
if(free_list == null) {
for(auto pos = entities.size(); pos; --pos) {
func(entities[pos-1]);
}
} else {
for(auto pos = entities.size(); pos; --pos) {
if(const auto entity = entities[pos - 1]; (to_integral(entity) & traits_type::entity_mask) == (pos - 1)) {
if(const auto entity = entities[pos - 1]; traits_type::to_entity(entity) == (pos - 1)) {
func(entity);
}
}
@@ -940,7 +1053,7 @@ public:
* @return True if the entity has no components assigned, false otherwise.
*/
[[nodiscard]] bool orphan(const entity_type entity) const {
ENTT_ASSERT(valid(entity));
ENTT_ASSERT(valid(entity), "Invalid entity");
return std::none_of(pools.cbegin(), pools.cend(), [entity](auto &&pdata) { return pdata.pool && pdata.pool->contains(entity); });
}
@@ -1075,9 +1188,8 @@ public:
* @return A newly created view.
*/
template<typename... Component, typename... Exclude>
[[nodiscard]] basic_view<Entity, exclude_t<Exclude...>, Component...> view(exclude_t<Exclude...> = {}) const {
[[nodiscard]] basic_view<Entity, exclude_t<Exclude...>, std::add_const_t<Component>...> view(exclude_t<Exclude...> = {}) const {
static_assert(sizeof...(Component) > 0, "Exclusion-only views are not supported");
static_assert((std::is_const_v<Component> && ...), "Invalid non-const type");
return { *assure<std::remove_const_t<Component>>()..., *assure<Exclude>()... };
}
@@ -1118,8 +1230,8 @@ public:
*/
template<typename ItComp, typename ItExcl = id_type *>
[[nodiscard]] basic_runtime_view<Entity> runtime_view(ItComp first, ItComp last, ItExcl from = {}, ItExcl to = {}) const {
std::vector<const basic_sparse_set<Entity> *> component(std::distance(first, last));
std::vector<const basic_sparse_set<Entity> *> filter(std::distance(from, to));
std::vector<const basic_common_type *> component(std::distance(first, last));
std::vector<const basic_common_type *> filter(std::distance(from, to));
std::transform(first, last, component.begin(), [this](const auto ctype) {
const auto it = std::find_if(pools.cbegin(), pools.cend(), [ctype](auto &&pdata) { return pdata.poly && pdata.poly->value_type().hash() == ctype; });
@@ -1203,7 +1315,7 @@ public:
const auto overlapping = (0u + ... + gdata.owned(type_hash<std::remove_const_t<Owned>>::value()));
const auto sz = overlapping + (0u + ... + gdata.get(type_hash<std::remove_const_t<Get>>::value())) + (0u + ... + gdata.exclude(type_hash<Exclude>::value()));
return !overlapping || ((sz == size) || (sz == gdata.size));
}));
}), "Conflicting groups");
const auto next = std::find_if_not(groups.cbegin(), groups.cend(), [size](const auto &gdata) {
return !(0u + ... + gdata.owned(type_hash<std::remove_const_t<Owned>>::value())) || (size > gdata.size);
@@ -1252,9 +1364,7 @@ public:
* @return A newly created group.
*/
template<typename... Owned, typename... Get, typename... Exclude>
[[nodiscard]] basic_group<Entity, exclude_t<Exclude...>, get_t<Get...>, Owned...> group_if_exists(get_t<Get...>, exclude_t<Exclude...> = {}) const {
static_assert(std::conjunction_v<std::is_const<Owned>..., std::is_const<Get>...>, "Invalid non-const type");
[[nodiscard]] basic_group<Entity, exclude_t<Exclude...>, get_t<std::add_const_t<Get>...>, std::add_const_t<Owned>...> group_if_exists(get_t<Get...>, exclude_t<Exclude...> = {}) const {
if(auto it = std::find_if(groups.cbegin(), groups.cend(), [](const auto &gdata) {
return gdata.size == (sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude))
&& (gdata.owned(type_hash<std::remove_const_t<Owned>>::value()) && ...)
@@ -1293,8 +1403,8 @@ public:
* @return A newly created group.
*/
template<typename... Owned, typename... Exclude>
[[nodiscard]] basic_group<Entity, exclude_t<Exclude...>, get_t<>, Owned...> group_if_exists(exclude_t<Exclude...> = {}) const {
return group_if_exists<Owned...>(get_t<>{}, exclude<Exclude...>);
[[nodiscard]] basic_group<Entity, exclude_t<Exclude...>, get_t<>, std::add_const_t<Owned>...> group_if_exists(exclude_t<Exclude...> = {}) const {
return group_if_exists<std::add_const_t<Owned>...>(get_t<>{}, exclude<Exclude...>);
}
/**
@@ -1370,7 +1480,7 @@ public:
*/
template<typename Component, typename Compare, typename Sort = std_sort, typename... Args>
void sort(Compare compare, Sort algo = Sort{}, Args &&... args) {
ENTT_ASSERT(sortable<Component>());
ENTT_ASSERT(sortable<Component>(), "Cannot sort owned storage");
assure<Component>()->sort(std::move(compare), std::move(algo), std::forward<Args>(args)...);
}
@@ -1409,7 +1519,7 @@ public:
*/
template<typename To, typename From>
void sort() {
ENTT_ASSERT(sortable<To>());
ENTT_ASSERT(sortable<To>(), "Cannot sort owned storage");
assure<To>()->respect(*assure<From>());
}
@@ -1523,9 +1633,9 @@ public:
* registry, a null pointer otherwise.
*/
template<typename Type>
[[nodiscard]] Type * try_ctx() const {
[[nodiscard]] std::add_const_t<Type> * try_ctx() const {
auto it = std::find_if(vars.cbegin(), vars.cend(), [type = type_id<Type>()](auto &&var) { return var.type() == type; });
return it == vars.cend() ? nullptr : any_cast<Type>(&*it);
return it == vars.cend() ? nullptr : any_cast<std::add_const_t<Type>>(&*it);
}
/*! @copydoc try_ctx */
@@ -1546,17 +1656,17 @@ public:
* @return A valid reference to the object in the context of the registry.
*/
template<typename Type>
[[nodiscard]] Type & ctx() const {
[[nodiscard]] std::add_const_t<Type> & ctx() const {
auto it = std::find_if(vars.cbegin(), vars.cend(), [type = type_id<Type>()](auto &&var) { return var.type() == type; });
ENTT_ASSERT(it != vars.cend());
return any_cast<Type &>(*it);
ENTT_ASSERT(it != vars.cend(), "Invalid instance");
return any_cast<std::add_const_t<Type> &>(*it);
}
/*! @copydoc ctx */
template<typename Type>
[[nodiscard]] Type & ctx() {
auto it = std::find_if(vars.begin(), vars.end(), [type = type_id<Type>()](auto &&var) { return var.type() == type; });
ENTT_ASSERT(it != vars.end());
ENTT_ASSERT(it != vars.end(), "Invalid instance");
return any_cast<Type &>(*it);
}
@@ -1593,7 +1703,7 @@ private:
mutable std::vector<pool_data> pools{};
std::vector<group_data> groups{};
std::vector<entity_type> entities{};
entity_type available{null};
entity_type free_list{tombstone};
};

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

@@ -55,7 +55,7 @@ class basic_snapshot {
while(begin != last) {
const auto entt = *(begin++);
((reg->template all_of<Component>(entt) ? ++size[Index] : size[Index]), ...);
((reg->template all_of<Component>(entt) ? ++size[Index] : 0u), ...);
}
(get<Component>(archive, size[Index], first, last), ...);
@@ -99,7 +99,7 @@ public:
archive(*first);
}
archive(reg->destroyed());
archive(reg->released());
return *this;
}
@@ -177,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 {
@@ -186,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));
}
}
@@ -204,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. */
@@ -273,7 +273,7 @@ public:
*/
const basic_snapshot_loader & orphans() const {
reg->orphans([this](const auto entt) {
reg->destroy(entt);
reg->release(entt);
});
return *this;
@@ -380,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);
}
}
}
@@ -448,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);
@@ -529,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,26 +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 constexpr auto page_size = ENTT_PAGE_SIZE;
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 &;
@@ -65,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;
}
@@ -136,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 {
@@ -144,62 +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) / page_size};
[[nodiscard]] static auto page(const Entity entt) ENTT_NOEXCEPT {
return static_cast<size_type>(traits_type::to_entity(entt) / sparse_page);
}
[[nodiscard]] auto offset(const Entity entt) const ENTT_NOEXCEPT {
return size_type{to_integral(entt) & (page_size - 1)};
[[nodiscard]] static auto offset(const Entity entt) ENTT_NOEXCEPT {
return static_cast<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[page_size]);
// null is safe in all cases for our purposes
for(auto *first = sparse[pos].get(), *last = first + page_size; 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);
}
}
protected:
/*! @brief Swaps two entities in the internal packed array. */
virtual void swap_at(const std::size_t, const std::size_t) {}
/**
* @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 Attempts to remove an entity from the internal packed array. */
virtual void swap_and_pop(const std::size_t, void *) {}
/**
* @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 = static_cast<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 = static_cast<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 : static_cast<size_type>(traits_type::to_entity(free_list));
}
/**
* @brief Increases the capacity of a sparse set.
@@ -210,7 +379,9 @@ public:
* @param cap Desired capacity.
*/
void reserve(const size_type cap) {
packed.reserve(cap);
if(cap > reserved) {
resize_packed(cap);
}
}
/**
@@ -219,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();
}
/**
@@ -244,7 +411,7 @@ public:
* @return Extent of the sparse set.
*/
[[nodiscard]] size_type extent() const ENTT_NOEXCEPT {
return sparse.size() * page_size;
return bucket * sparse_page;
}
/**
@@ -258,7 +425,7 @@ public:
* @return Number of elements.
*/
[[nodiscard]] size_type size() const ENTT_NOEXCEPT {
return packed.size();
return count;
}
/**
@@ -266,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;
}
/**
@@ -295,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)};
}
/**
@@ -310,7 +468,7 @@ public:
* internal packed array.
*/
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
return iterator{packed, {}};
return iterator{std::addressof(packed), {}};
}
/**
@@ -324,7 +482,7 @@ public:
* array.
*/
[[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
return packed.data();
return std::make_reverse_iterator(end());
}
/**
@@ -338,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());
}
/**
@@ -347,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();
}
@@ -356,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);
}
/**
@@ -372,9 +531,9 @@ 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 static_cast<size_type>(traits_type::to_entity(sparse[page(entt)][offset(entt)]));
}
/**
@@ -382,8 +541,8 @@ public:
* @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 {
return pos < packed.size() ? packed[pos] : null;
[[nodiscard]] entity_type at(const size_type pos) const ENTT_NOEXCEPT {
return pos < count ? packed[pos] : null;
}
/**
@@ -391,11 +550,34 @@ public:
* @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_ASSERT(pos < packed.size());
[[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++;
}
/**
* @brief Assigns an entity to a sparse set.
*
@@ -404,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 = static_cast<size_type>(traits_type::to_entity(free_list));
assure_page(page(entt))[offset(entt)] = traits_type::construct(static_cast<typename traits_type::entity_type>(pos));
free_list = std::exchange(packed[pos], entt);
return pos;
}
}
/**
@@ -424,56 +613,97 @@ 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, void *ud = nullptr) {
ENTT_ASSERT(contains(entt));
auto &ref = sparse[page(entt)][offset(entt)];
// last chance to use the entity for derived classes and mixins, if any
swap_and_pop(size_type{to_integral(ref)}, ud);
const auto other = packed.back();
sparse[page(other)][offset(other)] = ref;
// if it looks weird, imagine what the subtle bugs it prevents are
ENTT_ASSERT((packed.back() = entt, true));
packed[size_type{to_integral(ref)}] = other;
ref = null;
packed.pop_back();
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, void *ud = nullptr) {
void erase(It first, It last, void *ud = nullptr) {
for(; first != last; ++first) {
remove(*first, ud);
erase(*first, ud);
}
}
/**
* @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
*
@@ -488,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 = static_cast<size_type>(traits_type::to_entity(entt));
const auto to = static_cast<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]);
}
/**
@@ -520,18 +758,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) {
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]);
@@ -540,10 +780,8 @@ public:
const auto entt = packed[curr];
swap_at(next, idx);
sparse[page(entt)][offset(entt)] = entity_type{static_cast<typename traits_type::entity_type>(curr)};
curr = next;
next = idx;
sparse[page(entt)][offset(entt)] = traits_type::construct(static_cast<typename traits_type::entity_type>(curr));
curr = std::exchange(next, idx);
}
}
}
@@ -562,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)...);
}
/**
@@ -581,21 +819,20 @@ 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;
}
}
@@ -603,13 +840,27 @@ public:
* @brief Clears a sparse set.
* @param ud Optional user data that are forwarded as-is to derived classes.
*/
void clear(void *ud = nullptr) ENTT_NOEXCEPT {
remove(begin(), end(), ud);
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"
@@ -30,9 +31,7 @@ namespace entt {
* 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
@@ -47,27 +46,33 @@ namespace entt {
*
* @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 &;
@@ -75,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;
}
@@ -117,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 {
@@ -146,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 {
@@ -154,45 +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;
};
protected:
/**
* @copybrief basic_sparse_set::swap_at
* @param lhs A valid position of an entity within storage.
* @param rhs A valid position of an entity within storage.
*/
void swap_at(const std::size_t lhs, const std::size_t rhs) {
std::swap(instances[lhs], instances[rhs]);
[[nodiscard]] static auto page(const std::size_t pos) ENTT_NOEXCEPT {
return pos / packed_page;
}
/**
* @copybrief basic_sparse_set::swap_and_pop
* @param pos A valid position of an entity within storage.
*/
void swap_and_pop(const std::size_t pos, void *) {
auto other = std::move(instances.back());
instances[pos] = std::move(other);
instances.pop_back();
[[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(packed[page(lhs)][offset(lhs)], packed[page(rhs)][offset(rhs)]);
}
/*! @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);
}
/*! @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 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 *;
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.
@@ -204,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;
}
/**
@@ -243,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 */
@@ -254,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};
}
/**
@@ -269,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 */
@@ -279,7 +447,7 @@ public:
/*! @copydoc end */
[[nodiscard]] iterator end() ENTT_NOEXCEPT {
return iterator{instances, {}};
return iterator{std::addressof(packed), {}};
}
/**
@@ -292,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 */
@@ -302,7 +470,7 @@ public:
/*! @copydoc rbegin */
[[nodiscard]] reverse_iterator rbegin() ENTT_NOEXCEPT {
return instances.data();
return std::make_reverse_iterator(end());
}
/**
@@ -316,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 */
@@ -326,7 +494,7 @@ public:
/*! @copydoc rend */
[[nodiscard]] reverse_iterator rend() ENTT_NOEXCEPT {
return rbegin() + instances.size();
return std::make_reverse_iterator(begin());
}
/**
@@ -339,12 +507,13 @@ public:
* @param entt A valid entity identifier.
* @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));
}
@@ -366,29 +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 entity A valid entity identifier.
* @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 entity, Func &&... func) {
auto &&instance = instances[this->index(entity)];
(std::forward<Func>(func)(instance), ...);
return instance;
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;
}
/**
@@ -406,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;
}
}
}
/**
@@ -422,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;
}
}
}
/**
@@ -461,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)...);
}
}
@@ -491,20 +688,29 @@ 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 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. */
@@ -512,6 +718,14 @@ public:
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/**
* @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.
*
@@ -521,8 +735,8 @@ public:
*
* @param entt A valid entity identifier.
*/
void get([[maybe_unused]] const entity_type entt) const {
ENTT_ASSERT(this->contains(entt));
void get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT {
ENTT_ASSERT(underlying_type::contains(entt), "Storage does not contain entity");
}
/**
@@ -545,12 +759,12 @@ public:
/**
* @brief Updates the instance assigned to a given entity in-place.
* @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.
*/
template<typename... Func>
void patch([[maybe_unused]] const entity_type entity, Func &&... func) {
ENTT_ASSERT(this->contains(entity));
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)(), ...);
}
@@ -585,16 +799,19 @@ struct storage_adapter_mixin: Type {
/*! @brief Underlying entity identifier. */
using entity_type = typename Type::entity_type;
/*! @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)...);
}
/**
@@ -615,13 +832,13 @@ struct storage_adapter_mixin: Type {
/**
* @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, Func &&... func) {
return Type::patch(entity, std::forward<Func>(func)...);
decltype(auto) patch(basic_registry<entity_type> &, const entity_type entt, Func &&... func) {
return Type::patch(entt, std::forward<Func>(func)...);
}
};
@@ -632,17 +849,18 @@ struct storage_adapter_mixin: Type {
*/
template<typename Type>
class sigh_storage_mixin final: public Type {
/**
* @copybrief basic_sparse_set::swap_and_pop
* @param pos A valid position of an entity within storage.
* @param ud Optional user data that are forwarded as-is to derived classes.
*/
void swap_and_pop(const std::size_t pos, void *ud) final {
ENTT_ASSERT(ud != nullptr);
const auto entity = basic_sparse_set<typename Type::entity_type>::operator[](pos);
destruction.publish(*static_cast<basic_registry<typename Type::entity_type> *>(ud), entity);
// the position may have changed due to the actions of a listener
Type::swap_and_pop(this->index(entity), ud);
/*! @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:
@@ -651,6 +869,9 @@ public:
/*! @brief Underlying entity identifier. */
using entity_type = typename Type::entity_type;
/*! @brief Inherited constructors. */
using Type::Type;
/**
* @brief Returns a sink object.
*
@@ -720,15 +941,15 @@ public:
* @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(entity, std::forward<Args>(args)...);
construction.publish(owner, entity);
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);
}
/**
@@ -757,15 +978,15 @@ public:
* @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, Func &&... func) {
Type::patch(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:
@@ -776,17 +997,21 @@ 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 Allocator>
struct basic_storage: basic_storage_impl<Entity, Type, Allocator> {
using basic_storage_impl<Entity, Type, Allocator>::basic_storage_impl;
};
/**
* @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 {
@@ -799,17 +1024,17 @@ struct storage_traits {
* @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) {
[[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_void_v<decltype(container.get({}))>) {
return std::make_tuple();
} else {
return std::forward_as_tuple(container.get(entity));
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>) {
@@ -246,11 +310,11 @@ class basic_view<Entity, exclude_t<Exclude...>, Component...> final {
template<typename Comp, typename Func>
void traverse(Func func) const {
if constexpr(std::is_void_v<decltype(std::get<storage_type<Comp> *>(pools)->get({}))>) {
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) || ...))
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>().get({})))>) {
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));
@@ -260,11 +324,11 @@ class basic_view<Entity, exclude_t<Exclude...>, Component...> final {
} 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) || ...))
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>().get({})))>) {
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)...));
@@ -282,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{}
{}
@@ -296,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()}
@@ -328,7 +394,7 @@ public:
* @return An iterator to the first entity of the view.
*/
[[nodiscard]] iterator begin() const {
return iterator{view->begin(), view->end(), view->begin(), unchecked(view), filter};
return iterator{view->begin(), view->end(), view->begin(), pools_to_unchecked_array(), filter_to_array()};
}
/**
@@ -341,7 +407,7 @@ public:
* @return An iterator to the entity following the last entity of the view.
*/
[[nodiscard]] iterator end() const {
return iterator{view->begin(), view->end(), view->end(), unchecked(view), filter};
return iterator{view->begin(), view->end(), view->end(), pools_to_unchecked_array(), filter_to_array()};
}
/**
@@ -353,7 +419,7 @@ public:
* @return An iterator to the first entity of the reversed view.
*/
[[nodiscard]] reverse_iterator rbegin() const {
return reverse_iterator{view->rbegin(), view->rend(), view->rbegin(), unchecked(view), filter};
return reverse_iterator{view->rbegin(), view->rend(), view->rbegin(), pools_to_unchecked_array(), filter_to_array()};
}
/**
@@ -368,7 +434,7 @@ public:
* reversed view.
*/
[[nodiscard]] reverse_iterator rend() const {
return reverse_iterator{view->rbegin(), view->rend(), view->rend(), unchecked(view), filter};
return reverse_iterator{view->rbegin(), view->rend(), view->rend(), pools_to_unchecked_array(), filter_to_array()};
}
/**
@@ -398,7 +464,7 @@ public:
* iterator otherwise.
*/
[[nodiscard]] iterator find(const entity_type entt) const {
const auto it = iterator{view->begin(), view->end(), view->find(entt), unchecked(view), filter};
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();
}
@@ -416,7 +482,7 @@ public:
* @return True if the view contains the given entity, false otherwise.
*/
[[nodiscard]] bool contains(const entity_type entt) const {
return (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) && ...);
}
/**
@@ -436,7 +502,7 @@ 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)...);
@@ -480,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
*
@@ -518,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
*
@@ -548,7 +610,7 @@ public:
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;
};
@@ -584,34 +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;
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;
}
@@ -619,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);
}
@@ -631,36 +689,36 @@ class basic_view<Entity, exclude_t<>, Component> final {
std::tuple<It...> it;
};
iterable_view(storage_type &ref)
: pool{&ref}
{}
public:
using iterator = std::conditional_t<
std::is_void_v<decltype(std::declval<storage_type>().get({}))>,
iterable_view_iterator<typename basic_sparse_set<Entity>::iterator>,
iterable_view_iterator<typename basic_sparse_set<Entity>::iterator, decltype(std::declval<storage_type>().begin())>
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_void_v<decltype(std::declval<storage_type>().get({}))>,
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())>
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 iterator{pool->basic_sparse_set<entity_type>::begin(), pool->begin()};
return iterator{pool->basic_common_type::begin(), pool->begin()};
}
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
return iterator{pool->basic_sparse_set<entity_type>::end(), pool->end()};
return iterator{pool->basic_common_type::end(), pool->end()};
}
[[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
return reverse_iterator{pool->basic_sparse_set<entity_type>::rbegin(), pool->rbegin()};
return reverse_iterator{pool->basic_common_type::rbegin(), pool->rbegin()};
}
[[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
return reverse_iterator{pool->basic_sparse_set<entity_type>::rend(), pool->rend()};
return reverse_iterator{pool->basic_common_type::rend(), pool->rend()};
}
private:
@@ -668,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
: pools{}
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
: pools{&ref}
basic_view_impl(storage_type &ref) ENTT_NOEXCEPT
: pools{&ref},
filter{}
{}
/**
@@ -709,14 +769,10 @@ public:
}
/**
* @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 {
[[nodiscard]] auto raw() const ENTT_NOEXCEPT {
return std::get<0>(pools)->raw();
}
@@ -728,7 +784,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 std::get<0>(pools)->data();
}
@@ -741,7 +797,7 @@ public:
* @return An iterator to the first entity of the view.
*/
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
return std::get<0>(pools)->basic_sparse_set<entity_type>::begin();
return std::get<0>(pools)->basic_common_type::begin();
}
/**
@@ -754,7 +810,7 @@ public:
* @return An iterator to the entity following the last entity of the view.
*/
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
return std::get<0>(pools)->basic_sparse_set<entity_type>::end();
return std::get<0>(pools)->basic_common_type::end();
}
/**
@@ -766,7 +822,7 @@ public:
* @return An iterator to the first entity of the reversed view.
*/
[[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
return std::get<0>(pools)->basic_sparse_set<entity_type>::rbegin();
return std::get<0>(pools)->basic_common_type::rbegin();
}
/**
@@ -781,7 +837,7 @@ public:
* reversed view.
*/
[[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
return std::get<0>(pools)->basic_sparse_set<entity_type>::rend();
return std::get<0>(pools)->basic_common_type::rend();
}
/**
@@ -858,7 +914,7 @@ 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) {
return get_as_tuple(*std::get<0>(pools), entt);
@@ -950,13 +1006,29 @@ private:
};
/**
* @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;
};
/**
* @brief Deduction guide.
* @tparam Storage Type of storage classes used to create the view.
* @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>...>;

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"

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

@@ -110,12 +110,12 @@ private:
static internal::meta_prop_node node{
nullptr,
property,
property + 1u
property[0u],
property[1u]
};
entt::meta_any instance{std::forward<Key>(key)};
ENTT_ASSERT(!internal::find_if_not(&instance, *curr, &node));
ENTT_ASSERT(!internal::find_if_not(&instance, *curr, &node), "Duplicate key");
property[0u] = std::move(instance);
property[1u] = std::move(value);
@@ -191,7 +191,7 @@ struct meta_factory<Type> {
auto type(const id_type id = type_hash<Type>::value()) {
auto * const node = internal::meta_info<Type>::resolve();
ENTT_ASSERT(!internal::find_if_not(id, *internal::meta_context::global(), node));
ENTT_ASSERT(!internal::find_if_not(id, *internal::meta_context::global(), node), "Duplicate identifier");
node->id = id;
if(!internal::find_if(node, *internal::meta_context::global())) {
@@ -232,6 +232,63 @@ struct meta_factory<Type> {
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>{};
}
/**
* @brief Assigns a meta conversion function to a meta type.
*
@@ -263,40 +320,6 @@ struct meta_factory<Type> {
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));
}
};
if(!internal::find_if(&node, type->conv)) {
node.next = type->conv;
type->conv = &node;
}
return meta_factory<Type>{};
}
/**
* @brief Assigns a meta constructor to a meta type.
*
@@ -313,7 +336,7 @@ struct meta_factory<Type> {
template<auto Candidate, typename Policy = as_is_t>
auto ctor() ENTT_NOEXCEPT {
using descriptor = meta_function_helper_t<Type, decltype(Candidate)>;
static_assert(std::is_same_v<std::remove_cv_t<std::remove_reference_t<typename descriptor::return_type>>, Type>, "The function doesn't return an object of the required type");
static_assert(std::is_same_v<std::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{
@@ -395,7 +418,7 @@ struct meta_factory<Type> {
auto * const type = internal::meta_info<Type>::resolve();
type->dtor = [](void *instance) {
std::invoke(Func, *static_cast<Type *>(instance));
Func(*static_cast<Type *>(instance));
};
return meta_factory<Type>{};
@@ -434,7 +457,7 @@ struct meta_factory<Type> {
&meta_getter<Type, Data, Policy>
};
ENTT_ASSERT(!internal::find_if_not(id, type->data, &node));
ENTT_ASSERT(!internal::find_if_not(id, type->data, &node), "Duplicate identifier");
node.id = id;
if(!internal::find_if(&node, type->data)) {
@@ -483,7 +506,7 @@ struct meta_factory<Type> {
&meta_getter<Type, Getter, Policy>
};
ENTT_ASSERT(!internal::find_if_not(id, type->data, &node));
ENTT_ASSERT(!internal::find_if_not(id, type->data, &node), "Duplicate identifier");
node.id = id;
if(!internal::find_if(&node, type->data)) {
@@ -539,7 +562,7 @@ struct meta_factory<Type> {
internal::meta_func_node **it = &type->func;
for(; *it && (*it)->id != id; it = &(*it)->next);
for(; *it && (*it)->id == id && (*it)->arity < node.arity; it = &(*it)->next);
node.id = id;
node.next = *it;
*it = &node;

View File

@@ -2,7 +2,6 @@
#define ENTT_META_META_HPP
#include <array>
#include <cstddef>
#include <functional>
#include <iterator>
@@ -157,64 +156,53 @@ private:
/*! @brief Opaque wrapper for values of any type. */
class meta_any {
enum class operation { DTOR, REF, CREF, DEREF, CDEREF, SEQ, CSEQ, ASSOC, CASSOC };
enum class operation { DTOR, DEREF, SEQ, ASSOC };
using vtable_type = void(const operation, const any &, void *);
template<typename Type>
static void basic_vtable(const operation op, [[maybe_unused]] const any &from, [[maybe_unused]] void *to) {
if constexpr(!std::is_void_v<Type>) {
using base_type = std::remove_const_t<std::remove_reference_t<Type>>;
static void basic_vtable([[maybe_unused]] const operation op, [[maybe_unused]] const 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>) {
switch(op) {
case operation::DTOR:
if constexpr(!std::is_lvalue_reference_v<Type>) {
if(auto *curr = static_cast<internal::meta_type_node *>(to); curr->dtor) {
curr->dtor(const_cast<any &>(from).data());
}
if(auto *curr = static_cast<internal::meta_type_node *>(to); curr->dtor && from.owner()) {
curr->dtor(const_cast<any &>(from).data());
}
break;
case operation::REF:
case operation::CREF:
*static_cast<meta_any *>(to) = (op == operation::REF ? meta_any{std::ref(any_cast<Type &>(const_cast<any &>(from)))} : meta_any{std::cref(any_cast<const base_type &>(from))});
break;
case operation::DEREF:
case operation::CDEREF:
if constexpr(is_meta_pointer_like_v<base_type>) {
// for some reason vs2017 doesn't compile when using alias declarations with pointer_traits to get the element type
using element_type = std::remove_const_t<typename std::pointer_traits<std::remove_const_t<std::remove_reference_t<Type>>>::element_type>;
if constexpr(is_meta_pointer_like_v<Type>) {
using element_type = std::remove_const_t<typename std::pointer_traits<Type>::element_type>;
if constexpr(std::is_function_v<element_type>) {
*static_cast<meta_any *>(to) = any_cast<base_type>(from);
} else if constexpr(!std::is_same_v<element_type, void>) {
// for some reason vs2017 doesn't compile when using alias declarations with adl_meta_pointer_like
using adl_meta_pointer_like_type = adl_meta_pointer_like<std::remove_const_t<std::remove_reference_t<Type>>>;
if constexpr(std::is_lvalue_reference_v<decltype(adl_meta_pointer_like_type::dereference(std::declval<const base_type &>()))>) {
auto &&obj = adl_meta_pointer_like_type::dereference(any_cast<const base_type &>(from));
*static_cast<meta_any *>(to) = (op == operation::DEREF ? meta_any{std::ref(obj)} : meta_any{std::cref(obj)});
} else {
*static_cast<meta_any *>(to) = adl_meta_pointer_like_type::dereference(any_cast<const base_type &>(from));
}
*static_cast<meta_any *>(to) = any_cast<Type>(from);
} else if constexpr(!std::is_same_v<std::remove_const_t<typename std::pointer_traits<Type>::element_type>, void>) {
using in_place_type = decltype(adl_meta_pointer_like<Type>::dereference(any_cast<const Type &>(from)));
static_cast<meta_any *>(to)->emplace<in_place_type>(adl_meta_pointer_like<Type>::dereference(any_cast<const Type &>(from)));
}
}
break;
case operation::SEQ:
case operation::CSEQ:
if constexpr(is_complete_v<meta_sequence_container_traits<base_type>>) {
*static_cast<meta_sequence_container *>(to) = { std::in_place_type<base_type>, (op == operation::SEQ ? const_cast<any &>(from).as_ref() : from.as_ref()) };
if constexpr(is_complete_v<meta_sequence_container_traits<Type>>) {
*static_cast<meta_sequence_container *>(to) = { std::in_place_type<Type>, std::move(const_cast<any &>(from)) };
}
break;
case operation::ASSOC:
case operation::CASSOC:
if constexpr(is_complete_v<meta_associative_container_traits<base_type>>) {
*static_cast<meta_associative_container *>(to) = { std::in_place_type<base_type>, (op == operation::ASSOC ? const_cast<any &>(from).as_ref() : from.as_ref()) };
if constexpr(is_complete_v<meta_associative_container_traits<Type>>) {
*static_cast<meta_associative_container *>(to) = { std::in_place_type<Type>, std::move(const_cast<any &>(from)) };
}
break;
}
}
}
meta_any(const meta_any &other, any ref) ENTT_NOEXCEPT
: storage{std::move(ref)},
node{storage ? other.node : nullptr},
vtable{storage ? other.vtable : &basic_vtable<void>}
{}
public:
/*! @brief Default constructor. */
meta_any() ENTT_NOEXCEPT
@@ -224,36 +212,41 @@ public:
{}
/**
* @brief Constructs a meta 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 meta_any(std::in_place_type_t<Type>, Args &&... args)
: storage(std::in_place_type<Type>, std::forward<Args>(args)...),
: storage{std::in_place_type<Type>, std::forward<Args>(args)...},
node{internal::meta_info<Type>::resolve()},
vtable{&basic_vtable<Type>}
vtable{&basic_vtable<std::remove_const_t<std::remove_reference_t<Type>>>}
{}
/**
* @brief Constructs a meta 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>
meta_any(std::reference_wrapper<Type> value)
: meta_any{std::in_place_type<Type &>, value.get()}
{}
: meta_any{}
{
// invokes deprecated assignment operator (and avoids issues with vs2017)
*this = value;
}
/**
* @brief Constructs a meta 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>>, meta_any>>>
template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::decay_t<Type>, meta_any>>>
meta_any(Type &&value)
: meta_any{std::in_place_type<std::remove_cv_t<std::remove_reference_t<Type>>>, std::forward<Type>(value)}
: storage{std::forward<Type>(value)},
node{internal::meta_info<std::decay_t<Type>>::resolve()},
vtable{&basic_vtable<std::decay_t<Type>>}
{}
/**
@@ -267,25 +260,63 @@ public:
* @param other The instance to move from.
*/
meta_any(meta_any &&other) ENTT_NOEXCEPT
: meta_any{}
{
swap(*this, other);
}
: storage{std::move(other.storage)},
node{std::exchange(other.node, nullptr)},
vtable{std::exchange(other.vtable, &basic_vtable<void>)}
{}
/*! @brief Frees the internal storage, whatever it means. */
~meta_any() {
if(vtable) {
vtable(operation::DTOR, storage, node);
}
vtable(operation::DTOR, storage, node);
}
/**
* @brief Assignment operator.
* @param other The instance to assign from.
* @brief Copy assignment operator.
* @param other The instance to copy from.
* @return This meta any object.
*/
meta_any & operator=(meta_any other) {
swap(other, *this);
meta_any & operator=(const meta_any &other) {
std::exchange(vtable, other.vtable)(operation::DTOR, storage, node);
storage = other.storage;
node = other.node;
return *this;
}
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This meta any object.
*/
meta_any & operator=(meta_any &&other) ENTT_NOEXCEPT {
std::exchange(vtable, std::exchange(other.vtable, &basic_vtable<void>))(operation::DTOR, storage, node);
storage = std::move(other.storage);
node = std::exchange(other.node, nullptr);
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 meta any object.
*/
template<typename Type>
[[deprecated("Use std::in_place_type<T &>, entt::make_meta<T &>, emplace<Type &> or forward_as_meta instead")]]
meta_any & operator=(std::reference_wrapper<Type> value) {
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 meta any object.
*/
template<typename Type>
std::enable_if_t<!std::is_same_v<std::decay_t<Type>, meta_any>, meta_any &>
operator=(Type &&value) {
emplace<std::decay_t<Type>>(std::forward<Type>(value));
return *this;
}
@@ -316,7 +347,7 @@ public:
* @tparam Args Types of arguments to use to invoke the function.
* @param id Unique identifier.
* @param args Parameters to use to invoke the function.
* @return A meta any containing the returned value, if any.
* @return A wrapper containing the returned value, if any.
*/
template<typename... Args>
meta_any invoke(const id_type id, Args &&... args) const;
@@ -342,7 +373,7 @@ public:
/**
* @brief Gets the value of a given variable.
* @param id Unique identifier.
* @return A meta any containing the value of the underlying variable.
* @return A wrapper containing the value of the underlying variable.
*/
[[nodiscard]] meta_any get(const id_type id) const;
@@ -394,18 +425,18 @@ public:
*/
template<typename Type>
[[nodiscard]] Type cast() const {
auto * const actual = try_cast<std::remove_reference_t<Type>>();
ENTT_ASSERT(actual);
return static_cast<Type>(*actual);
auto * const instance = try_cast<std::remove_reference_t<Type>>();
ENTT_ASSERT(instance, "Invalid instance");
return static_cast<Type>(*instance);
}
/*! @copydoc cast */
template<typename Type>
[[nodiscard]] Type cast() {
// forces const on non-reference types to make them work also with wrappers for const references
auto * const actual = try_cast<std::conditional_t<std::is_reference_v<Type>, std::remove_reference_t<Type>, const Type>>();
ENTT_ASSERT(actual);
return static_cast<Type>(*actual);
auto * const instance = try_cast<std::remove_reference_t<const Type>>();
ENTT_ASSERT(instance, "Invalid instance");
return static_cast<Type>(*instance);
}
/**
@@ -434,7 +465,8 @@ public:
*/
template<typename Type>
bool allow_cast() {
if(try_cast<std::conditional_t<std::is_reference_v<Type>, std::remove_reference_t<Type>, const Type>>() != nullptr) {
// forces const on non-reference types to make them work also with wrappers for const references
if(try_cast<std::remove_reference_t<const Type>>() != nullptr) {
return true;
} else if(node) {
if(const auto * const conv = internal::meta_visit<&internal::meta_type_node::conv>([info = type_id<Type>()](const auto *curr) { return curr->type()->info == info; }, node); conv) {
@@ -454,12 +486,16 @@ public:
*/
template<typename Type, typename... Args>
void emplace(Args &&... args) {
*this = meta_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, storage, node);
storage.emplace<Type>(std::forward<Args>(args)...);
node = internal::meta_info<Type>::resolve();
}
/*! @brief Destroys contained object */
void reset() {
*this = meta_any{};
std::exchange(vtable, &basic_vtable<void>)(operation::DTOR, storage, node);
storage.reset();
node = nullptr;
}
/**
@@ -468,14 +504,14 @@ public:
*/
[[nodiscard]] meta_sequence_container as_sequence_container() ENTT_NOEXCEPT {
meta_sequence_container proxy;
vtable(operation::SEQ, storage, &proxy);
vtable(operation::SEQ, storage.as_ref(), &proxy);
return proxy;
}
/*! @copydoc as_sequence_container */
[[nodiscard]] meta_sequence_container as_sequence_container() const ENTT_NOEXCEPT {
meta_sequence_container proxy;
vtable(operation::CSEQ, storage, &proxy);
vtable(operation::SEQ, storage.as_ref(), &proxy);
return proxy;
}
@@ -485,32 +521,25 @@ public:
*/
[[nodiscard]] meta_associative_container as_associative_container() ENTT_NOEXCEPT {
meta_associative_container proxy;
vtable(operation::ASSOC, storage, &proxy);
vtable(operation::ASSOC, storage.as_ref(), &proxy);
return proxy;
}
/*! @copydoc as_associative_container */
[[nodiscard]] meta_associative_container as_associative_container() const ENTT_NOEXCEPT {
meta_associative_container proxy;
vtable(operation::CASSOC, storage, &proxy);
vtable(operation::ASSOC, storage.as_ref(), &proxy);
return proxy;
}
/**
* @brief Indirection operator for dereferencing opaque objects.
* @return A meta any that shares a reference to an unmanaged object if the
* @return A wrapper that shares a reference to an unmanaged object if the
* wrapped element is dereferenceable, an invalid meta any otherwise.
*/
[[nodiscard]] meta_any operator*() ENTT_NOEXCEPT {
meta_any ret{};
vtable(operation::DEREF, storage, &ret);
return ret;
}
/*! @copydoc operator* */
[[nodiscard]] meta_any operator*() const ENTT_NOEXCEPT {
meta_any ret{};
vtable(operation::CDEREF, storage, &ret);
vtable(operation::DEREF, storage, &ret);
return ret;
}
@@ -531,33 +560,17 @@ public:
return (!node && !other.node) || (node && other.node && node->info == other.node->info && storage == other.storage);
}
/**
* @brief Swaps two meta any objects.
* @param lhs A valid meta any object.
* @param rhs A valid meta any object.
*/
friend void swap(meta_any &lhs, meta_any &rhs) {
using std::swap;
swap(lhs.storage, rhs.storage);
swap(lhs.vtable, rhs.vtable);
swap(lhs.node, rhs.node);
}
/**
* @brief Aliasing constructor.
* @return A meta any that shares a reference to an unmanaged object.
* @return A wrapper that shares a reference to an unmanaged object.
*/
[[nodiscard]] meta_any as_ref() ENTT_NOEXCEPT {
meta_any ref{};
vtable(operation::REF, storage, &ref);
return ref;
return meta_any{*this, storage.as_ref()};
}
/*! @copydoc as_ref */
[[nodiscard]] meta_any as_ref() const ENTT_NOEXCEPT {
meta_any ref{};
vtable(operation::CREF, storage, &ref);
return ref;
return meta_any{*this, storage.as_ref()};
}
private:
@@ -569,8 +582,8 @@ private:
/**
* @brief Checks if two wrappers differ in their content.
* @param lhs A meta any object, either empty or not.
* @param rhs A meta any object, either empty or not.
* @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 meta_any &lhs, const meta_any &rhs) ENTT_NOEXCEPT {
@@ -578,6 +591,31 @@ private:
}
/**
* @brief Constructs a wrapper from a given type, passing it all arguments.
* @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.
* @return A properly initialized wrapper for an object of the given type.
*/
template<typename Type, typename... Args>
meta_any make_meta(Args &&... args) {
return meta_any{std::in_place_type<Type>, std::forward<Args>(args)...};
}
/**
* @brief Forwards its argument and avoids copies for lvalue references.
* @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<typename Type>
meta_any forward_as_meta(Type &&value) {
return meta_any{std::in_place_type<std::conditional_t<std::is_rvalue_reference_v<Type>, std::decay_t<Type>, Type>>, std::forward<Type>(value)};
}
/**
* @brief Opaque pointers to instances of any type.
*
@@ -613,11 +651,11 @@ struct meta_handle {
* @tparam Type Type of object to use to initialize the handle.
* @param value An instance of an object to use to initialize the handle.
*/
template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_reference_t<Type>>, meta_handle>>>
template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::decay_t<Type>, meta_handle>>>
meta_handle(Type &value) ENTT_NOEXCEPT
: meta_handle{}
{
if constexpr(std::is_same_v<std::remove_cv_t<std::remove_reference_t<Type>>, meta_any>) {
if constexpr(std::is_same_v<std::decay_t<Type>, meta_any>) {
any = value.as_ref();
} else {
any.emplace<Type &>(value);
@@ -634,7 +672,7 @@ struct meta_handle {
/**
* @brief Access operator for accessing the contained opaque object.
* @return A meta any that shares a reference to an unmanaged object.
* @return A wrapper that shares a reference to an unmanaged object.
*/
[[nodiscard]] meta_any * operator->() {
return &any;
@@ -665,18 +703,18 @@ struct meta_prop {
/**
* @brief Returns the stored key as a const reference.
* @return A meta any containing the key stored with the property.
* @return A wrapper containing the key stored with the property.
*/
[[nodiscard]] meta_any key() const {
return node->id->as_ref();
return node->id.as_ref();
}
/**
* @brief Returns the stored value by copy.
* @return A meta any containing the value stored with the property.
* @return A wrapper containing the value stored with the property.
*/
[[nodiscard]] meta_any value() const {
return *node->value;
return node->value;
}
/**
@@ -733,7 +771,7 @@ struct meta_ctor {
*
* @param args Parameters to use to construct the instance.
* @param sz Number of parameters to use to construct the instance.
* @return A meta any containing the new instance, if any.
* @return A wrapper containing the new instance, if any.
*/
[[nodiscard]] meta_any invoke(meta_any * const args, const size_type sz) const {
return sz == arity() ? node->invoke(args) : meta_any{};
@@ -746,12 +784,12 @@ struct meta_ctor {
*
* @tparam Args Types of arguments to use to construct the instance.
* @param args Parameters to use to construct the instance.
* @return A meta any containing the new instance, if any.
* @return A wrapper containing the new instance, if any.
*/
template<typename... Args>
[[nodiscard]] meta_any invoke([[maybe_unused]] Args &&... args) const {
std::array<meta_any, sizeof...(Args)> arguments{std::forward<Args>(args)...};
return invoke(arguments.data(), sizeof...(Args));
meta_any arguments[sizeof...(Args) + 1u]{std::forward<Args>(args)...};
return invoke(arguments, sizeof...(Args));
}
/**
@@ -768,7 +806,7 @@ struct meta_ctor {
* @return The property associated with the given key, if any.
*/
[[nodiscard]] meta_prop prop(meta_any key) const {
return internal::meta_visit<&node_type::prop>([&key](const auto *curr) { return *curr->id == key; }, node);
return internal::meta_visit<&node_type::prop>([&key](const auto *curr) { return curr->id == key; }, node);
}
/**
@@ -847,7 +885,7 @@ struct meta_data {
* member. Otherwise, invoking the getter results in an undefined behavior.
*
* @param instance An opaque instance of the underlying type.
* @return A meta any containing the value of the underlying variable.
* @return A wrapper containing the value of the underlying variable.
*/
[[nodiscard]] meta_any get(meta_handle instance) const {
return node->get(std::move(instance));
@@ -864,7 +902,7 @@ struct meta_data {
* @return The property associated with the given key, if any.
*/
[[nodiscard]] meta_prop prop(meta_any key) const {
return internal::meta_visit<&node_type::prop>([&key](const auto *curr) { return *curr->id == key; }, node);
return internal::meta_visit<&node_type::prop>([&key](const auto *curr) { return curr->id == key; }, node);
}
/**
@@ -950,7 +988,7 @@ struct meta_func {
* @param instance An opaque instance of the underlying type.
* @param args Parameters to use to invoke the function.
* @param sz Number of parameters to use to invoke the function.
* @return A meta any containing the returned value, if any.
* @return A wrapper containing the returned value, if any.
*/
meta_any invoke(meta_handle instance, meta_any * const args, const size_type sz) const {
return sz == arity() ? node->invoke(std::move(instance), args) : meta_any{};
@@ -964,12 +1002,12 @@ struct meta_func {
* @tparam Args Types of arguments to use to invoke the function.
* @param instance An opaque instance of the underlying type.
* @param args Parameters to use to invoke the function.
* @return A meta any containing the new instance, if any.
* @return A wrapper containing the new instance, if any.
*/
template<typename... Args>
meta_any invoke(meta_handle instance, Args &&... args) const {
std::array<meta_any, sizeof...(Args)> arguments{std::forward<Args>(args)...};
return invoke(std::move(instance), arguments.data(), sizeof...(Args));
meta_any arguments[sizeof...(Args) + 1u]{std::forward<Args>(args)...};
return invoke(std::move(instance), arguments, sizeof...(Args));
}
/*! @copydoc meta_ctor::prop */
@@ -983,7 +1021,7 @@ struct meta_func {
* @return The property associated with the given key, if any.
*/
[[nodiscard]] meta_prop prop(meta_any key) const {
return internal::meta_visit<&node_type::prop>([&key](const auto *curr) { return *curr->id == key; }, node);
return internal::meta_visit<&node_type::prop>([&key](const auto *curr) { return curr->id == key; }, node);
}
/**
@@ -1032,6 +1070,14 @@ class meta_type {
return nullptr;
}
template<auto... Member, typename Node>
void unregister_all(Node **curr) {
while(*curr) {
(unregister_all(&((*curr)->*Member)), ...);
*curr = std::exchange((*curr)->next, nullptr);
}
}
public:
/*! @brief Node type. */
using node_type = internal::meta_type_node;
@@ -1354,7 +1400,7 @@ public:
*
* @param args Parameters to use to construct the instance.
* @param sz Number of parameters to use to construct the instance.
* @return A meta any containing the new instance, if any.
* @return A wrapper containing the new instance, if any.
*/
[[nodiscard]] meta_any construct(meta_any * const args, const size_type sz) const {
meta_any ret{};
@@ -1369,12 +1415,12 @@ public:
*
* @tparam Args Types of arguments to use to construct the instance.
* @param args Parameters to use to construct the instance.
* @return A meta any containing the new instance, if any.
* @return A wrapper containing the new instance, if any.
*/
template<typename... Args>
[[nodiscard]] meta_any construct(Args &&... args) const {
std::array<meta_any, sizeof...(Args)> arguments{std::forward<Args>(args)...};
return construct(arguments.data(), sizeof...(Args));
meta_any arguments[sizeof...(Args) + 1u]{std::forward<Args>(args)...};
return construct(arguments, sizeof...(Args));
}
/**
@@ -1390,7 +1436,7 @@ public:
* @param instance An opaque instance of the underlying type.
* @param args Parameters to use to invoke the function.
* @param sz Number of parameters to use to invoke the function.
* @return A meta any containing the returned value, if any.
* @return A wrapper containing the returned value, if any.
*/
meta_any invoke(const id_type id, meta_handle instance, meta_any * const args, const size_type sz) const {
const internal::meta_func_node* candidate{};
@@ -1430,12 +1476,12 @@ public:
* @tparam Args Types of arguments to use to invoke the function.
* @param instance An opaque instance of the underlying type.
* @param args Parameters to use to invoke the function.
* @return A meta any containing the new instance, if any.
* @return A wrapper containing the new instance, if any.
*/
template<typename... Args>
meta_any invoke(const id_type id, meta_handle instance, Args &&... args) const {
std::array<meta_any, sizeof...(Args)> arguments{std::forward<Args>(args)...};
return invoke(id, std::move(instance), arguments.data(), sizeof...(Args));
meta_any arguments[sizeof...(Args) + 1u]{std::forward<Args>(args)...};
return invoke(id, std::move(instance), arguments, sizeof...(Args));
}
/**
@@ -1455,8 +1501,8 @@ public:
*/
template<typename Type>
bool set(const id_type id, meta_handle instance, Type &&value) const {
auto const candidate = data(id);
return candidate ? candidate.set(std::move(instance), std::forward<Type>(value)) : false;
const auto candidate = data(id);
return candidate && candidate.set(std::move(instance), std::forward<Type>(value));
}
/**
@@ -1467,10 +1513,10 @@ public:
*
* @param id Unique identifier.
* @param instance An opaque instance of the underlying type.
* @return A meta any containing the value of the underlying variable.
* @return A wrapper containing the value of the underlying variable.
*/
[[nodiscard]] meta_any get(const id_type id, meta_handle instance) const {
auto const candidate = data(id);
const auto candidate = data(id);
return candidate ? candidate.get(std::move(instance)) : meta_any{};
}
@@ -1491,7 +1537,7 @@ public:
* @return The property associated with the given key, if any.
*/
[[nodiscard]] meta_prop prop(meta_any key) const {
return internal::meta_visit<&node_type::prop>([&key](const auto *curr) { return *curr->id == key; }, node);
return internal::meta_visit<&node_type::prop>([&key](const auto *curr) { return curr->id == key; }, node);
}
/**
@@ -1522,33 +1568,19 @@ public:
* The type is also removed from the list of searchable types.
*/
void reset() ENTT_NOEXCEPT {
auto** it = internal::meta_context::global();
while(*it && *it != node) {
it = &(*it)->next;
}
if(*it) {
*it = (*it)->next;
}
const auto unregister_all = y_combinator{
[](auto &&self, auto **curr, auto... member) {
while(*curr) {
auto *prev = *curr;
(self(&(prev->*member)), ...);
*curr = prev->next;
prev->next = nullptr;
}
for(auto** it = internal::meta_context::global(); *it; it = &(*it)->next) {
if(*it == node) {
*it = (*it)->next;
break;
}
};
}
unregister_all(&node->prop);
unregister_all(&node->base);
unregister_all(&node->conv);
unregister_all(&node->ctor, &internal::meta_ctor_node::prop);
unregister_all(&node->data, &internal::meta_data_node::prop);
unregister_all(&node->func, &internal::meta_func_node::prop);
unregister_all<&internal::meta_ctor_node::prop>(&node->ctor);
unregister_all<&internal::meta_data_node::prop>(&node->data);
unregister_all<&internal::meta_func_node::prop>(&node->func);
node->id = {};
node->ctor = node->def_ctor;
@@ -1753,12 +1785,12 @@ struct meta_sequence_container::meta_sequence_container_proxy {
[[nodiscard]] static bool resize(any &container, size_type sz) {
auto * const cont = any_cast<Type>(&container);
return cont ? traits_type::resize(*cont, sz) : false;
return cont && traits_type::resize(*cont, sz);
}
[[nodiscard]] static bool clear(any &container) {
auto * const cont = any_cast<Type>(&container);
return cont ? traits_type::clear(*cont) : false;
return cont && traits_type::clear(*cont);
}
[[nodiscard]] static iterator begin(any &container) {
@@ -1920,10 +1952,12 @@ class meta_associative_container::meta_iterator {
++any_cast<It &>(const_cast<any &>(from));
break;
case operation::DEREF:
const auto &it = any_cast<const It &>(from);
if constexpr(KeyOnly) {
static_cast<std::pair<meta_any, meta_any> *>(to)->first = std::cref(*any_cast<const It &>(from));
static_cast<std::pair<meta_any, meta_any> *>(to)->first.emplace<decltype(*it)>(*it);
} else {
*static_cast<std::pair<meta_any, meta_any> *>(to) = std::make_pair<meta_any, meta_any>(std::cref(any_cast<const It &>(from)->first), std::ref(any_cast<const It &>(from)->second));
static_cast<std::pair<meta_any, meta_any> *>(to)->first.emplace<decltype((it->first))>(it->first);
static_cast<std::pair<meta_any, meta_any> *>(to)->second.emplace<decltype((it->second))>(it->second);
}
break;
}
@@ -2035,7 +2069,7 @@ struct meta_associative_container::meta_associative_container_proxy {
[[nodiscard]] static bool clear(any &container) {
auto * const cont = any_cast<Type>(&container);
return cont ? traits_type::clear(*cont) : false;
return cont && traits_type::clear(*cont);
}
[[nodiscard]] static iterator begin(any &container) {
@@ -2059,9 +2093,8 @@ struct meta_associative_container::meta_associative_container_proxy {
if constexpr(is_key_only_meta_associative_container_v<Type>) {
return traits_type::insert(*cont, key.cast<const typename Type::key_type &>());
} else {
if(value.allow_cast<const typename Type::mapped_type &>()) {
return traits_type::insert(*cont, key.cast<const typename Type::key_type &>(), value.cast<const typename Type::mapped_type &>());
}
return value.allow_cast<const typename Type::mapped_type &>()
&& traits_type::insert(*cont, key.cast<const typename Type::key_type &>(), value.cast<const typename Type::mapped_type &>());
}
}

View File

@@ -2,7 +2,6 @@
#define ENTT_META_NODE_HPP
#include <array>
#include <cstddef>
#include <type_traits>
#include <utility>
@@ -36,8 +35,8 @@ struct meta_type_node;
struct meta_prop_node {
meta_prop_node * next;
const meta_any * const id;
meta_any * const value;
const meta_any &id;
meta_any &value;
};
@@ -234,7 +233,7 @@ public:
meta_template_descriptor(),
std::rank_v<Type>,
[](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_pointer_t<Type>>>::resolve,
&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)
@@ -251,7 +250,8 @@ 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 {
return std::array<meta_type_node *, sizeof...(Args)>{{internal::meta_info<Args>::resolve()...}}[index];
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

@@ -2,7 +2,6 @@
#define ENTT_META_UTILITY_HPP
#include <array>
#include <cstddef>
#include <functional>
#include <type_traits>
@@ -159,25 +158,27 @@ template<typename Type, typename... Args, std::size_t... Index>
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)>>> || std::is_member_function_pointer_v<decltype(Data)>) {
using descriptor = meta_function_helper_t<Type, decltype(Data)>;
using data_type = type_list_element_t<!std::is_member_function_pointer_v<decltype(Data)>, typename descriptor::args_type>;
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) {
if(value.allow_cast<data_type>()) {
std::invoke(Data, *clazz, value.cast<data_type>());
return true;
}
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) {
if(value.allow_cast<data_type>()) {
std::invoke(Data, clazz) = value.cast<data_type>();
return true;
}
if(auto * const clazz = instance->try_cast<Type>(); clazz && value.allow_cast<data_type>()) {
clazz->*Data = value.cast<data_type>();
return true;
}
}
} else {
@@ -196,6 +197,29 @@ template<typename Type, auto Data>
}
/**
* @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.
@@ -206,41 +230,30 @@ template<typename Type, auto Data>
*/
template<typename Type, auto Data, typename Policy = as_is_t>
[[nodiscard]] meta_any meta_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::reference_wrapper{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)>) {
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 ? dispatch(std::invoke(Data, *clazz)) : meta_any{};
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)>>>) {
return meta_any{};
} else {
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 dispatch(std::invoke(Data, *clazz));
} else {
auto * fallback = instance->try_cast<const Type>();
return fallback ? dispatch(std::invoke(Data, *fallback)) : meta_any{};
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 dispatch(*Data);
return meta_dispatch<Policy>(*Data);
}
} else {
return dispatch(Data);
return meta_dispatch<Policy>(Data);
}
}
@@ -256,34 +269,38 @@ template<typename Type, auto Data, typename Policy = as_is_t>
* @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]] meta_any meta_invoke([[maybe_unused]] meta_handle instance, meta_any *args, std::index_sequence<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)>;
auto dispatch = [](auto &&... params) {
if constexpr(std::is_void_v<std::remove_cv_t<typename descriptor::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::reference_wrapper{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)...))};
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 {
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_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 dispatch(*clazz, (args+Index)->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 dispatch(*clazz, (args+Index)->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 dispatch((args+Index)->cast<type_list_element_t<Index, typename descriptor::args_type>>()...);
return invoke((args+Index)->cast<type_list_element_t<Index, typename descriptor::args_type>>()...);
}
}
@@ -291,6 +308,25 @@ template<typename Type, auto Candidate, typename Policy = as_is_t, std::size_t..
}
/**
* @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());
}
}
}

View File

@@ -206,16 +206,6 @@ public:
vtable{poly_vtable<Concept, Len, Align>::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>
basic_poly(std::reference_wrapper<Type> value)
: basic_poly{std::in_place_type<Type &>, value.get()}
{}
/**
* @brief Constructs a poly from a given value.
* @tparam Type Type of object to use to initialize the poly.

View File

@@ -202,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;
}

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) {
@@ -86,7 +84,7 @@ class scheduler {
if(process->rejected()) {
return true;
} else if(process->dead()) {
} else if(process->finished()) {
if(handler.next) {
handler = std::move(*handler.next);
// forces the process to exit the uninitialized state

View File

@@ -91,8 +91,6 @@ 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");
if(auto it = resources.find(id); it == resources.cend()) {
if(auto handle = temp<Loader>(std::forward<Args>(args)...); handle) {
return (resources[id] = std::move(handle));

View File

@@ -10,7 +10,7 @@ struct resource_cache;
template<typename>
struct resource_handle;
class resource_handle;
template<typename, typename>

View File

@@ -3,6 +3,7 @@
#include <memory>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "fwd.hpp"
@@ -24,7 +25,12 @@ namespace entt {
* @tparam Resource Type of resource managed by a handle.
*/
template<typename Resource>
struct resource_handle {
class resource_handle {
/*! @brief Resource handles are friends with each other. */
template<typename>
friend class resource_handle;
public:
/*! @brief Default constructor. */
resource_handle() ENTT_NOEXCEPT = default;
@@ -36,6 +42,78 @@ struct resource_handle {
: 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 @@ struct resource_handle {
* @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 @@ struct resource_handle {
* contains no resource at all.
*/
[[nodiscard]] const Resource * operator->() const ENTT_NOEXCEPT {
ENTT_ASSERT(static_cast<bool>(resource));
return resource.get();
}

View File

@@ -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.

View File

@@ -288,7 +288,7 @@ public:
* @return The value returned by the underlying function.
*/
Ret operator()(Args... args) const {
ENTT_ASSERT(static_cast<bool>(*this));
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

@@ -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

@@ -65,6 +65,14 @@ function(SETUP_TARGET TARGET_NAME)
NOMINMAX
${ARGN}
)
if(ENTT_BUILD_UINT64)
target_compile_definitions(
${TARGET_NAME}
PRIVATE
ENTT_ID_TYPE=std::uint64_t
)
endif()
endfunction()
add_library(odr OBJECT odr.cpp)

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

@@ -4,6 +4,21 @@
#include <gtest/gtest.h>
#include <entt/core/hashed_string.hpp>
template<typename>
struct foobar_t;
template<>
struct foobar_t<std::uint32_t> {
static constexpr auto value = 0xbf9cf968;
};
template<>
struct foobar_t<std::uint64_t> {
static constexpr auto value = 0x85944171f73967e8;
};
inline constexpr auto foobar_v = foobar_t<entt::id_type>::value;
TEST(BasicHashedString, DeductionGuide) {
static_assert(std::is_same_v<decltype(entt::basic_hashed_string{"foo"}), entt::hashed_string>);
static_assert(std::is_same_v<decltype(entt::basic_hashed_string{L"foo"}), entt::hashed_wstring>);
@@ -29,8 +44,8 @@ TEST(HashedString, Functionalities) {
entt::hashed_string hs{"foobar"};
ASSERT_EQ(static_cast<hash_type>(hs), 0xbf9cf968);
ASSERT_EQ(hs.value(), 0xbf9cf968);
ASSERT_EQ(static_cast<hash_type>(hs), foobar_v);
ASSERT_EQ(hs.value(), foobar_v);
ASSERT_EQ(foo_hs, "foo"_hs);
ASSERT_NE(bar_hs, "foo"_hs);
@@ -59,9 +74,9 @@ TEST(HashedString, Correctness) {
const char *foobar = "foobar";
std::string_view view{"foobar__", 6};
ASSERT_EQ(entt::hashed_string{foobar}, 0xbf9cf968);
ASSERT_EQ(entt::hashed_string::value(foobar), 0xbf9cf968);
ASSERT_EQ(entt::hashed_string::value(view.data(), view.size()), 0xbf9cf968);
ASSERT_EQ(entt::hashed_string{foobar}, foobar_v);
ASSERT_EQ(entt::hashed_string::value(foobar), foobar_v);
ASSERT_EQ(entt::hashed_string::value(view.data(), view.size()), foobar_v);
}
TEST(HashedString, Constexprness) {
@@ -69,13 +84,13 @@ TEST(HashedString, Constexprness) {
constexpr std::string_view view{"foobar__", 6};
static_assert(entt::hashed_string{"quux"} == "quux"_hs);
static_assert(entt::hashed_string{"foobar"} == 0xbf9cf968);
static_assert(entt::hashed_string{"foobar"} == foobar_v);
static_assert(entt::hashed_string::value("quux") == "quux"_hs);
static_assert(entt::hashed_string::value("foobar") == 0xbf9cf968);
static_assert(entt::hashed_string::value("foobar") == foobar_v);
static_assert(entt::hashed_string::value("quux", 4) == "quux"_hs);
static_assert(entt::hashed_string::value(view.data(), view.size()) == 0xbf9cf968);
static_assert(entt::hashed_string::value(view.data(), view.size()) == foobar_v);
}
TEST(HashedWString, Functionalities) {
@@ -98,8 +113,8 @@ TEST(HashedWString, Functionalities) {
entt::hashed_wstring hws{L"foobar"};
ASSERT_EQ(static_cast<hash_type>(hws), 0xbf9cf968);
ASSERT_EQ(hws.value(), 0xbf9cf968);
ASSERT_EQ(static_cast<hash_type>(hws), foobar_v);
ASSERT_EQ(hws.value(), foobar_v);
ASSERT_EQ(foo_hws, L"foo"_hws);
ASSERT_NE(bar_hws, L"foo"_hws);
@@ -118,9 +133,9 @@ TEST(HashedWString, Correctness) {
const wchar_t *foobar = L"foobar";
std::wstring_view view{L"foobar__", 6};
ASSERT_EQ(entt::hashed_wstring{foobar}, 0xbf9cf968);
ASSERT_EQ(entt::hashed_wstring::value(foobar), 0xbf9cf968);
ASSERT_EQ(entt::hashed_wstring::value(view.data(), view.size()), 0xbf9cf968);
ASSERT_EQ(entt::hashed_wstring{foobar}, foobar_v);
ASSERT_EQ(entt::hashed_wstring::value(foobar), foobar_v);
ASSERT_EQ(entt::hashed_wstring::value(view.data(), view.size()), foobar_v);
}
TEST(HashedWString, Constexprness) {
@@ -128,11 +143,11 @@ TEST(HashedWString, Constexprness) {
constexpr std::wstring_view view{L"foobar__", 6};
static_assert(entt::hashed_wstring{L"quux"} == L"quux"_hws);
static_assert(entt::hashed_wstring{L"foobar"} == 0xbf9cf968);
static_assert(entt::hashed_wstring{L"foobar"} == foobar_v);
static_assert(entt::hashed_wstring::value(L"quux") == L"quux"_hws);
static_assert(entt::hashed_wstring::value(L"foobar") == 0xbf9cf968);
static_assert(entt::hashed_wstring::value(L"foobar") == foobar_v);
static_assert(entt::hashed_wstring::value(L"quux", 4) == L"quux"_hws);
static_assert(entt::hashed_wstring::value(view.data(), view.size()) == 0xbf9cf968);
static_assert(entt::hashed_wstring::value(view.data(), view.size()) == foobar_v);
}

View File

@@ -1,3 +1,4 @@
#include <iterator>
#include <tuple>
#include <type_traits>
#include <unordered_map>
@@ -11,6 +12,11 @@ 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));
@@ -95,13 +101,14 @@ TEST(TypeTraits, IsEqualityComparable) {
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<std::vector<not_comparable>::iterator>);
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>);
}
@@ -115,13 +122,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, IsStdHashable) {
static_assert(entt::is_std_hashable_v<int>);
static_assert(!entt::is_std_hashable_v<not_comparable>);
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

@@ -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{~entt::id_type{}});
}
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>
@@ -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());
@@ -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_FATAL_FAILURE(group.sort([](const auto, const auto) { FAIL(), true; }));
ASSERT_NO_FATAL_FAILURE(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 &>);
@@ -202,11 +196,11 @@ TEST(NonOwningGroup, Each) {
ASSERT_EQ(cnt, std::size_t{0});
auto it = group.each().begin();
auto rit = group.each().rbegin();
auto it = iterable.begin();
auto rit = iterable.rbegin();
ASSERT_EQ((it++, ++it), group.each().end());
ASSERT_EQ((rit++, ++rit), group.each().rend());
ASSERT_EQ((it++, ++it), iterable.end());
ASSERT_EQ((rit++, ++rit), iterable.rend());
}
TEST(NonOwningGroup, Sort) {
@@ -227,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)));
@@ -249,9 +243,9 @@ 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)));
@@ -265,9 +259,9 @@ TEST(NonOwningGroup, Sort) {
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.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)));
@@ -370,6 +364,10 @@ 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 &>);
@@ -402,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());
@@ -461,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);
@@ -518,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());
@@ -566,8 +564,9 @@ TEST(NonOwningGroup, EmptyTypes) {
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) {
@@ -622,6 +621,20 @@ TEST(NonOwningGroup, ExtendedGet) {
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) {
entt::registry registry;
auto group = registry.group<int>(entt::get<char>);
@@ -652,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);
@@ -665,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());
@@ -707,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) {
@@ -771,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);
@@ -781,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++);
}
@@ -796,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 &>);
@@ -805,11 +815,11 @@ TEST(OwningGroup, Each) {
ASSERT_EQ(cnt, std::size_t{0});
auto it = group.each().begin();
auto rit = group.each().rbegin();
auto it = iterable.begin();
auto rit = iterable.rbegin();
ASSERT_EQ((it++, ++it), group.each().end());
ASSERT_EQ((rit++, ++rit), group.each().rend());
ASSERT_EQ((it++, ++it), iterable.end());
ASSERT_EQ((rit++, ++rit), iterable.rend());
}
TEST(OwningGroup, SortOrdered) {
@@ -835,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')));
@@ -882,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')));
@@ -937,27 +947,27 @@ TEST(OwningGroup, SortUnordered) {
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]));
@@ -982,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);
@@ -1056,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 &>);
@@ -1095,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());
@@ -1154,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);
@@ -1211,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());
@@ -1259,8 +1273,9 @@ TEST(OwningGroup, EmptyTypes) {
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) {
@@ -1362,3 +1377,17 @@ TEST(OwningGroup, ExtendedGet) {
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

@@ -160,10 +160,10 @@ TEST(BasicHandle, Component) {
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_id<int>(), info); });
@@ -171,7 +171,8 @@ TEST(BasicHandle, Component) {
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.all_of<int, char>()));
handle.remove_all();
ASSERT_FALSE((handle.any_of<int, char>()));
}
TEST(BasicHandle, FromEntity) {
entt::registry registry;
const auto entity = registry.create();

View File

@@ -41,19 +41,38 @@ 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_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

@@ -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");

View File

@@ -24,7 +24,7 @@ struct PolyStorage: entt::type_list_cat_t<
struct type: entt::Storage<Entity>::template type<Base> {
static constexpr auto base = decltype(as_type_list(std::declval<entt::Storage<Entity>>()))::size;
void remove(entt::basic_registry<Entity> &owner, const entity_type *first, const entity_type *last) {
void erase(entt::basic_registry<Entity> &owner, const entity_type *first, const entity_type *last) {
entt::poly_call<base + 0>(*this, first, last, &owner);
}
@@ -52,7 +52,8 @@ struct PolyStorage: entt::type_list_cat_t<
}
static void copy_to(const Type &self, entt::basic_registry<entity_type> &other) {
other.template insert<typename Type::value_type>(self.data(), self.data() + self.size(), self.raw(), self.raw() + self.size());
const entt::sparse_set &base = self;
other.template insert<typename Type::value_type>(base.rbegin(), base.rend(), self.rbegin());
}
};
@@ -60,7 +61,7 @@ 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 remove<const entity_type *>,
&Type::template erase<const entity_type *>,
&members<Type>::emplace,
&members<Type>::get,
&members<Type>::copy_to
@@ -108,7 +109,7 @@ TEST(PolyStorage, CopyRegistry) {
ASSERT_EQ(registry.size(), 10u);
ASSERT_EQ(other.size(), 0u);
other.assign(registry.data(), registry.data() + registry.size(), registry.destroyed());
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());
@@ -128,14 +129,14 @@ TEST(PolyStorage, Constness) {
entity[0] = registry.create();
registry.emplace<int>(entity[0], 42);
// cannot invoke remove on a const storage, let's copy the returned value
// 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_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));
storage->erase(registry, std::begin(entity), std::end(entity));
ASSERT_FALSE(registry.all_of<int>(entity[0]));
}

File diff suppressed because it is too large Load Diff

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;
@@ -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

@@ -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();
@@ -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();

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,7 +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.capacity(), 42u);
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.emplace(entt::entity{42}), 0u);
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 1u);
@@ -37,7 +44,7 @@ TEST(SparseSet, Functionalities) {
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);
@@ -48,7 +55,7 @@ TEST(SparseSet, Functionalities) {
ASSERT_EQ(set.at(0u), static_cast<entt::entity>(entt::null));
ASSERT_EQ(set.at(1u), static_cast<entt::entity>(entt::null));
set.emplace(entt::entity{42});
ASSERT_EQ(set.emplace(entt::entity{42}), 0u);
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.index(entt::entity{42}), 0u);
@@ -56,82 +63,177 @@ TEST(SparseSet, Functionalities) {
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_EQ(set.at(0u), static_cast<entt::entity>(entt::null));
ASSERT_FALSE(other.empty());
ASSERT_EQ(other.index(entt::entity{42}), 0u);
ASSERT_EQ(set.at(0u), static_cast<entt::entity>(entt::null));
ASSERT_EQ(other.at(0u), entt::entity{42});
ASSERT_EQ(other.at(1u), static_cast<entt::entity>(entt::null));
ASSERT_EQ(other[0u], entt::entity{42});
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}));
}
TEST(SparseSet, Pagination) {
entt::sparse_set set;
constexpr auto page_size = ENTT_PAGE_SIZE;
ASSERT_EQ(set.extent(), 0u);
set.emplace(entt::entity{page_size-1});
set.emplace(entt::entity{ENTT_SPARSE_PAGE-1u});
ASSERT_EQ(set.extent(), page_size);
ASSERT_TRUE(set.contains(entt::entity{page_size-1}));
ASSERT_EQ(set.extent(), ENTT_SPARSE_PAGE);
ASSERT_TRUE(set.contains(entt::entity{ENTT_SPARSE_PAGE-1u}));
set.emplace(entt::entity{page_size});
set.emplace(entt::entity{ENTT_SPARSE_PAGE});
ASSERT_EQ(set.extent(), 2 * page_size);
ASSERT_TRUE(set.contains(entt::entity{page_size-1}));
ASSERT_TRUE(set.contains(entt::entity{page_size}));
ASSERT_FALSE(set.contains(entt::entity{page_size+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{page_size-1});
set.erase(entt::entity{ENTT_SPARSE_PAGE-1u});
ASSERT_EQ(set.extent(), 2 * page_size);
ASSERT_FALSE(set.contains(entt::entity{page_size-1}));
ASSERT_TRUE(set.contains(entt::entity{page_size}));
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{page_size});
set.erase(entt::entity{ENTT_SPARSE_PAGE});
ASSERT_EQ(set.extent(), 2 * page_size);
ASSERT_FALSE(set.contains(entt::entity{page_size-1}));
ASSERT_FALSE(set.contains(entt::entity{page_size}));
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, EmplaceOutOfBounds) {
entt::sparse_set set{entt::deletion_policy::in_place};
entt::entity entities[2u]{entt::entity{0}, entt::entity{ENTT_SPARSE_PAGE}};
ASSERT_EQ(set.emplace(entities[0u]), 0u);
ASSERT_EQ(set.extent(), ENTT_SPARSE_PAGE);
set.erase(entities[0u]);
ASSERT_EQ(set.emplace(entities[1u]), 0u);
ASSERT_EQ(set.extent(), 2u * ENTT_SPARSE_PAGE);
}
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}));
@@ -140,46 +242,354 @@ 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.begin(), set.end());
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) {
@@ -229,7 +639,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());
@@ -274,7 +684,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());
@@ -512,3 +922,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;
@@ -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());
@@ -72,19 +80,17 @@ TEST(SingleComponentView, RawData) {
ASSERT_EQ(view.size(), 0u);
ASSERT_EQ(cview.size(), 0u);
ASSERT_EQ(view.raw(), nullptr);
ASSERT_EQ(cview.raw(), nullptr);
ASSERT_EQ(view.data(), nullptr);
ASSERT_EQ(cview.data(), nullptr);
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(), 42);
ASSERT_EQ(*cview.raw(), 42);
ASSERT_EQ(*view.data(), entity);
ASSERT_EQ(*cview.data(), entity);
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);
@@ -176,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++);
}
@@ -192,7 +202,8 @@ 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);
@@ -200,11 +211,11 @@ TEST(SingleComponentView, Each) {
ASSERT_EQ(cnt, std::size_t{0});
auto it = view.each().begin();
auto rit = view.each().rbegin();
auto it = iterable.begin();
auto rit = iterable.rbegin();
ASSERT_EQ((it++, ++it), view.each().end());
ASSERT_EQ((rit++, ++rit), view.each().rend());
ASSERT_EQ((it++, ++it), iterable.end());
ASSERT_EQ((rit++, ++rit), iterable.rend());
}
TEST(SingleComponentView, ConstNonConstAndAllInBetween) {
@@ -220,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 &>);
@@ -262,12 +275,11 @@ 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<>>);
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>);
}
@@ -293,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());
@@ -377,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) {
@@ -583,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++);
}
@@ -599,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 &>);
@@ -608,11 +685,11 @@ TEST(MultiComponentView, Each) {
ASSERT_EQ(cnt, std::size_t{0});
auto it = view.each().begin();
auto rit = view.each().rbegin();
auto it = iterable.begin();
auto rit = iterable.rbegin();
ASSERT_EQ((it++, ++it), view.each().end());
ASSERT_EQ((rit++, ++rit), view.each().rend());
ASSERT_EQ((it++, ++it), iterable.end());
ASSERT_EQ((rit++, ++rit), iterable.rend());
}
TEST(MultiComponentView, EachWithSuggestedType) {
@@ -707,6 +784,10 @@ 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 &>);
@@ -739,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());
@@ -795,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);
@@ -918,11 +999,81 @@ 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) {
@@ -937,15 +1088,20 @@ TEST(View, Pipe) {
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<decltype(view1 | view2), entt::basic_view<entt::entity, entt::exclude_t<double, float>, int, const char>>);
static_assert(std::is_same_v<decltype(view2 | view1), entt::basic_view<entt::entity, entt::exclude_t<float, double>, const char, int>>);
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));
@@ -954,4 +1110,7 @@ TEST(View, Pipe) {
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,3 +1,4 @@
#include <algorithm>
#include <gtest/gtest.h>
#include <entt/core/hashed_string.hpp>
#include <entt/meta/factory.hpp>
@@ -15,28 +16,32 @@ struct clazz_t {
};
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(): foo{}, bar{}, gnam{} {}
fat_t()
: value{.0, .0, .0, .0}
{}
fat_t(int *value)
: foo{value}, bar{value}, gnam{}
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;
int *bar;
double gnam[4];
double value[4];
};
struct not_comparable_t {
@@ -74,7 +79,8 @@ struct MetaAny: ::testing::Test {
.func<&clazz_t::member>("member"_hs)
.func<&clazz_t::func>("func"_hs);
empty_t::counter = 0;
empty_t::destroy_counter = 0;
empty_t::destructor_counter = 0;
}
void TearDown() override {
@@ -96,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);
@@ -141,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_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) {
@@ -238,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);
@@ -252,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_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};
@@ -300,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};
@@ -316,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)};
@@ -330,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};
@@ -346,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);
}
@@ -422,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);
@@ -444,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) {
@@ -508,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) {
@@ -529,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);
@@ -538,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) {
@@ -568,31 +644,33 @@ 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) {
@@ -733,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) {
@@ -774,7 +852,7 @@ TEST_F(MetaAny, ConstConvert) {
TEST_F(MetaAny, UnmanageableType) {
unmanageable_t instance;
entt::meta_any any{std::ref(instance)};
auto any = entt::forward_as_meta(instance);
entt::meta_any other = any.as_ref();
std::swap(any, other);
@@ -798,7 +876,7 @@ 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));
@@ -814,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));
@@ -830,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

@@ -57,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();
@@ -84,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();
@@ -111,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();
@@ -154,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();
@@ -196,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();
@@ -240,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();
@@ -283,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();
@@ -324,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();
@@ -360,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();
@@ -409,8 +409,8 @@ TEST_F(MetaContainer, SequenceContainerConstMetaAny) {
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) {
@@ -426,8 +426,8 @@ TEST_F(MetaContainer, KeyValueAssociativeContainerConstMetaAny) {
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) {
@@ -446,8 +446,8 @@ TEST_F(MetaContainer, KeyOnlyAssociativeContainerConstMetaAny) {
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) {
@@ -455,7 +455,7 @@ TEST_F(MetaContainer, StdVectorBool) {
using const_proxy_type = typename std::vector<bool>::const_reference;
std::vector<bool> vec{};
entt::meta_any any{std::ref(vec)};
auto any = entt::forward_as_meta(vec);
auto cany = std::as_const(any).as_ref();
auto view = any.as_sequence_container();

View File

@@ -153,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);
@@ -187,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);
@@ -215,7 +215,7 @@ TEST_F(MetaCtor, ExternalMemberFunction) {
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.all_of<clazz_t>(entity));

View File

@@ -309,11 +309,11 @@ TEST_F(MetaData, SetByRef) {
int value{42};
ASSERT_EQ(any.cast<clazz_t>().i, 0);
ASSERT_TRUE(entt::resolve<clazz_t>().data("i"_hs).set(any, std::ref(value)));
ASSERT_TRUE(entt::resolve<clazz_t>().data("i"_hs).set(any, entt::make_meta<int &>(value)));
ASSERT_EQ(any.cast<clazz_t>().i, 42);
value = 3;
entt::meta_any wrapper{std::ref(value)};
auto wrapper = entt::make_meta<int &>(value);
ASSERT_TRUE(entt::resolve<clazz_t>().data("i"_hs).set(any, wrapper.as_ref()));
ASSERT_EQ(any.cast<clazz_t>().i, 3);
@@ -326,11 +326,11 @@ TEST_F(MetaData, SetByConstRef) {
int value{42};
ASSERT_EQ(any.cast<clazz_t>().i, 0);
ASSERT_TRUE(entt::resolve<clazz_t>().data("i"_hs).set(any, std::cref(value)));
ASSERT_TRUE(entt::resolve<clazz_t>().data("i"_hs).set(any, entt::make_meta<const int &>(value)));
ASSERT_EQ(any.cast<clazz_t>().i, 42);
value = 3;
entt::meta_any wrapper{std::cref(value)};
auto wrapper = entt::make_meta<const int &>(value);
ASSERT_TRUE(entt::resolve<clazz_t>().data("i"_hs).set(any, wrapper.as_ref()));
ASSERT_EQ(any.cast<clazz_t>().i, 3);

View File

@@ -74,8 +74,8 @@ TEST_F(MetaDtor, AsRefConstruction) {
ASSERT_EQ(clazz_t::counter, 0);
clazz_t instance{};
entt::meta_any any{std::ref(instance)};
entt::meta_any cany{std::cref(instance)};
auto any = entt::forward_as_meta(instance);
auto cany = entt::make_meta<const clazz_t &>(instance);
auto cref = cany.as_ref();
auto ref = any.as_ref();

View File

@@ -332,7 +332,7 @@ TEST_F(MetaFunc, ArgsByRef) {
entt::meta_any any{3};
int value = 4;
ASSERT_EQ(func.invoke({}, std::ref(value)).cast<int>(), 8);
ASSERT_EQ(func.invoke({}, entt::forward_as_meta(value)).cast<int>(), 8);
ASSERT_EQ(func.invoke({}, any.as_ref()).cast<int>(), 6);
ASSERT_EQ(any.cast<int>(), 6);
ASSERT_EQ(value, 8);
@@ -346,7 +346,7 @@ TEST_F(MetaFunc, ArgsByConstRef) {
entt::meta_any any{2};
int value = 3;
ASSERT_TRUE(func.invoke(instance, std::cref(value)));
ASSERT_TRUE(func.invoke(instance, entt::make_meta<const int &>(value)));
ASSERT_EQ(func_t::value, 9);
ASSERT_TRUE(func.invoke(instance, std::as_const(any).as_ref()));
@@ -434,7 +434,7 @@ TEST_F(MetaFunc, ExternalMemberFunction) {
ASSERT_FALSE(registry.all_of<func_t>(entity));
func.invoke({}, std::ref(registry), entity);
func.invoke({}, entt::forward_as_meta(registry), entity);
ASSERT_TRUE(registry.all_of<func_t>(entity));
}

View File

@@ -42,7 +42,7 @@ TEST_F(MetaHandle, Functionalities) {
ASSERT_TRUE(handle->invoke("incr"_hs));
ASSERT_EQ(instance.value, 1);
entt::meta_any any{std::ref(instance)};
auto any = entt::forward_as_meta(instance);
handle = entt::meta_handle{any};
ASSERT_FALSE(std::as_const(handle)->invoke("decr"_hs));

View File

@@ -103,25 +103,36 @@ TEST(MetaPointerLike, DereferenceOperatorConstType) {
ASSERT_EQ(deref.cast<const int &>(), 42);
}
TEST(MetaPointerLike, DereferenceOperatorConstAny) {
auto test = [](const entt::meta_any any) {
auto deref = *any;
ASSERT_TRUE(deref);
ASSERT_FALSE(deref.type().is_pointer());
ASSERT_FALSE(deref.type().is_pointer_like());
ASSERT_EQ(deref.type(), entt::resolve<int>());
ASSERT_EQ(deref.try_cast<int>(), nullptr);
ASSERT_NE(deref.try_cast<const int>(), nullptr);
ASSERT_DEATH(deref.cast<int &>() = 0, "");
ASSERT_EQ(deref.cast<const int &>(), 42);
};
TEST(MetaPointerLike, DereferenceOperatorConstAnyNonConstType) {
int value = 42;
const entt::meta_any any{&value};
auto deref = *any;
test(&value);
test(&std::as_const(value));
ASSERT_TRUE(deref);
ASSERT_FALSE(deref.type().is_pointer());
ASSERT_FALSE(deref.type().is_pointer_like());
ASSERT_EQ(deref.type(), entt::resolve<int>());
ASSERT_NE(deref.try_cast<int>(), nullptr);
ASSERT_NE(deref.try_cast<const int>(), nullptr);
ASSERT_EQ(deref.cast<int &>(), 42);
ASSERT_EQ(deref.cast<const int &>(), 42);
}
TEST(MetaPointerLike, DereferenceOperatorConstAnyConstType) {
const int value = 42;
const entt::meta_any any{&value};
auto deref = *any;
ASSERT_TRUE(deref);
ASSERT_FALSE(deref.type().is_pointer());
ASSERT_FALSE(deref.type().is_pointer_like());
ASSERT_EQ(deref.type(), entt::resolve<int>());
ASSERT_EQ(deref.try_cast<int>(), nullptr);
ASSERT_NE(deref.try_cast<const int>(), nullptr);
ASSERT_DEATH(deref.cast<int &>() = 0, "");
ASSERT_EQ(deref.cast<const int &>(), 42);
}
TEST(MetaPointerLike, DereferenceOperatorRawPointer) {
@@ -182,7 +193,7 @@ TEST(MetaPointerLike, PointerToConstMoveOnlyType) {
TEST(MetaPointerLike, AsRef) {
int value = 0;
int * ptr = &value;
entt::meta_any any{std::ref(ptr)};
entt::meta_any any{entt::forward_as_meta(ptr)};
ASSERT_TRUE(any.type().is_pointer());
ASSERT_TRUE(any.type().is_pointer_like());
@@ -203,8 +214,8 @@ TEST(MetaPointerLike, AsRef) {
TEST(MetaPointerLike, AsConstRef) {
int value = 42;
int * ptr = &value;
entt::meta_any any{std::cref(ptr)};
int * const ptr = &value;
entt::meta_any any{entt::forward_as_meta(ptr)};
ASSERT_TRUE(any.type().is_pointer());
ASSERT_TRUE(any.type().is_pointer_like());
@@ -302,14 +313,17 @@ TEST(MetaPointerLike, DereferencePointerToFunction) {
ASSERT_EQ(any.cast<int(*)()>()(), 42);
};
test(entt::meta_any{&test_function});
test(*entt::meta_any{&test_function});
test(**entt::meta_any{&test_function});
entt::meta_any func{&test_function};
test(func);
test(*func);
test(**func);
test(*std::as_const(func));
}
TEST(MetaPointerLike, DereferenceSelfPointer) {
self_ptr obj{42};
entt::meta_any any{std::ref(obj)};
entt::meta_any any{entt::forward_as_meta(obj)};
entt::meta_any deref = *any;
ASSERT_TRUE(deref);
@@ -333,3 +347,14 @@ TEST(MetaPointerLike, DereferenceProxyPointer) {
ASSERT_EQ(value, 42);
}
TEST(MetaPointerLike, DereferenceArray) {
entt::meta_any array{std::in_place_type<int[3]>};
entt::meta_any array_of_array{std::in_place_type<int[3][3]>};
ASSERT_EQ(array.type(), entt::resolve<int[3]>());
ASSERT_EQ(array_of_array.type(), entt::resolve<int[3][3]>());
ASSERT_FALSE(*array);
ASSERT_FALSE(*array_of_array);
}

View File

@@ -1,3 +1,4 @@
#include <string.h>
#include <gtest/gtest.h>
#include <entt/core/hashed_string.hpp>
#include <entt/meta/factory.hpp>
@@ -18,7 +19,8 @@ struct MetaProp: ::testing::Test {
entt::meta<base_2_t>()
.type("base_2"_hs)
.prop("bool"_hs, false);
.prop("bool"_hs, false)
.prop("char[]"_hs, "char[]");
entt::meta<derived_t>()
.type("derived"_hs)
@@ -57,6 +59,17 @@ TEST_F(MetaProp, FromBase) {
ASSERT_EQ(prop_int.value().cast<int>(), 42);
}
TEST_F(MetaProp, DeducedArrayType) {
using namespace entt::literals;
auto prop = entt::resolve<base_2_t>().prop("char[]"_hs);
ASSERT_TRUE(prop);
ASSERT_EQ(prop.key(), "char[]"_hs);
ASSERT_EQ(prop.value().type(), entt::resolve<const char *>());
ASSERT_EQ(strcmp(prop.value().cast<const char *>(), "char[]"), 0);
}
TEST_F(MetaProp, ReRegistration) {
using namespace entt::literals;

View File

@@ -48,7 +48,7 @@ TEST(PolyDeduced, Functionalities) {
entt::poly<Deduced> empty{};
entt::poly<Deduced> in_place{std::in_place_type<impl>, 3};
entt::poly<Deduced> alias{std::ref(instance)};
entt::poly<Deduced> alias{std::in_place_type<impl &>, instance};
entt::poly<Deduced> value{impl{}};
ASSERT_FALSE(empty);
@@ -134,7 +134,7 @@ TEST(PolyDeduced, Owned) {
TEST(PolyDeduced, Reference) {
impl instance{};
entt::poly<Deduced> poly{std::ref(instance)};
entt::poly<Deduced> poly{std::in_place_type<impl &>, instance};
ASSERT_TRUE(poly);
ASSERT_NE(poly.data(), nullptr);
@@ -158,7 +158,7 @@ TEST(PolyDeduced, Reference) {
TEST(PolyDeduced, ConstReference) {
impl instance{};
entt::poly<Deduced> poly{std::cref(instance)};
entt::poly<Deduced> poly{std::in_place_type<const impl &>, instance};
ASSERT_TRUE(poly);
ASSERT_EQ(poly.data(), nullptr);

View File

@@ -54,7 +54,7 @@ TEST(PolyDefined, Functionalities) {
entt::poly<Defined> empty{};
entt::poly<Defined> in_place{std::in_place_type<impl>, 3};
entt::poly<Defined> alias{std::ref(instance)};
entt::poly<Defined> alias{std::in_place_type<impl &>, instance};
entt::poly<Defined> value{impl{}};
ASSERT_FALSE(empty);
@@ -140,7 +140,7 @@ TEST(PolyDefined, Owned) {
TEST(PolyDefined, Reference) {
impl instance{};
entt::poly<Defined> poly{std::ref(instance)};
entt::poly<Defined> poly{std::in_place_type<impl &>, instance};
ASSERT_TRUE(poly);
ASSERT_NE(poly.data(), nullptr);
@@ -164,7 +164,7 @@ TEST(PolyDefined, Reference) {
TEST(PolyDefined, ConstReference) {
impl instance{};
entt::poly<Defined> poly{std::cref(instance)};
entt::poly<Defined> poly{std::in_place_type<const impl &>, instance};
ASSERT_TRUE(poly);
ASSERT_EQ(poly.data(), nullptr);

View File

@@ -46,7 +46,7 @@ TEST(Process, Basics) {
fake_process<int> process{};
ASSERT_FALSE(process.alive());
ASSERT_FALSE(process.dead());
ASSERT_FALSE(process.finished());
ASSERT_FALSE(process.paused());
ASSERT_FALSE(process.rejected());
@@ -57,42 +57,42 @@ TEST(Process, Basics) {
process.unpause();
ASSERT_FALSE(process.alive());
ASSERT_FALSE(process.dead());
ASSERT_FALSE(process.finished());
ASSERT_FALSE(process.paused());
ASSERT_FALSE(process.rejected());
process.tick(0);
ASSERT_TRUE(process.alive());
ASSERT_FALSE(process.dead());
ASSERT_FALSE(process.finished());
ASSERT_FALSE(process.paused());
ASSERT_FALSE(process.rejected());
process.pause();
ASSERT_TRUE(process.alive());
ASSERT_FALSE(process.dead());
ASSERT_FALSE(process.finished());
ASSERT_TRUE(process.paused());
ASSERT_FALSE(process.rejected());
process.unpause();
ASSERT_TRUE(process.alive());
ASSERT_FALSE(process.dead());
ASSERT_FALSE(process.finished());
ASSERT_FALSE(process.paused());
ASSERT_FALSE(process.rejected());
process.fail();
ASSERT_FALSE(process.alive());
ASSERT_FALSE(process.dead());
ASSERT_FALSE(process.finished());
ASSERT_FALSE(process.paused());
ASSERT_FALSE(process.rejected());
process.tick(0);
ASSERT_FALSE(process.alive());
ASSERT_FALSE(process.dead());
ASSERT_FALSE(process.finished());
ASSERT_FALSE(process.paused());
ASSERT_TRUE(process.rejected());
}
@@ -106,7 +106,7 @@ TEST(Process, Succeeded) {
process.tick({});
ASSERT_FALSE(process.alive());
ASSERT_TRUE(process.dead());
ASSERT_TRUE(process.finished());
ASSERT_FALSE(process.paused());
ASSERT_FALSE(process.rejected());
@@ -126,7 +126,7 @@ TEST(Process, Fail) {
process.tick(0);
ASSERT_FALSE(process.alive());
ASSERT_FALSE(process.dead());
ASSERT_FALSE(process.finished());
ASSERT_FALSE(process.paused());
ASSERT_TRUE(process.rejected());
@@ -147,7 +147,7 @@ TEST(Process, Data) {
process.tick({}, &value);
ASSERT_FALSE(process.alive());
ASSERT_TRUE(process.dead());
ASSERT_TRUE(process.finished());
ASSERT_FALSE(process.paused());
ASSERT_FALSE(process.rejected());
@@ -167,7 +167,7 @@ TEST(Process, AbortNextTick) {
process.tick(0);
ASSERT_FALSE(process.alive());
ASSERT_FALSE(process.dead());
ASSERT_FALSE(process.finished());
ASSERT_FALSE(process.paused());
ASSERT_TRUE(process.rejected());
@@ -185,7 +185,7 @@ TEST(Process, AbortImmediately) {
process.abort(true);
ASSERT_FALSE(process.alive());
ASSERT_FALSE(process.dead());
ASSERT_FALSE(process.finished());
ASSERT_FALSE(process.paused());
ASSERT_TRUE(process.rejected());
@@ -209,7 +209,7 @@ TEST(ProcessAdaptor, Resolved) {
process.tick(0);
process.tick(0);
ASSERT_TRUE(process.dead());
ASSERT_TRUE(process.finished());
ASSERT_TRUE(updated);
}
@@ -243,6 +243,6 @@ TEST(ProcessAdaptor, Data) {
process.tick(0);
process.tick(0, &value);
ASSERT_TRUE(process.dead());
ASSERT_TRUE(process.finished());
ASSERT_EQ(value, 42);
}

View File

@@ -4,15 +4,20 @@
#include <entt/resource/cache.hpp>
struct resource { int value; };
struct derived_resource: resource {};
struct loader: entt::resource_loader<loader, resource> {
std::shared_ptr<resource> load(int value) const {
return std::shared_ptr<resource>(new resource{ value });
template<typename Resource>
struct loader: entt::resource_loader<loader<Resource>, Resource> {
std::shared_ptr<Resource> load(int value) const {
auto res = std::shared_ptr<Resource>(new Resource);
res->value = value;
return res;
}
};
struct broken_loader: entt::resource_loader<broken_loader, resource> {
std::shared_ptr<resource> load(int) const {
template<typename Resource>
struct broken_loader: entt::resource_loader<broken_loader<Resource>, Resource> {
std::shared_ptr<Resource> load(int) const {
return nullptr;
}
};
@@ -28,16 +33,16 @@ TEST(Resource, Functionalities) {
ASSERT_FALSE(cache.contains(hs1));
ASSERT_FALSE(cache.contains(hs2));
ASSERT_FALSE(cache.load<broken_loader>(hs1, 42));
ASSERT_FALSE(cache.reload<broken_loader>(hs1, 42));
ASSERT_FALSE(cache.load<broken_loader<resource>>(hs1, 42));
ASSERT_FALSE(cache.reload<broken_loader<resource>>(hs1, 42));
ASSERT_EQ(cache.size(), 0u);
ASSERT_TRUE(cache.empty());
ASSERT_FALSE(cache.contains(hs1));
ASSERT_FALSE(cache.contains(hs2));
ASSERT_TRUE(cache.load<loader>(hs1, 42));
ASSERT_TRUE(cache.reload<loader>(hs1, 42));
ASSERT_TRUE(cache.load<loader<resource>>(hs1, 42));
ASSERT_TRUE(cache.reload<loader<resource>>(hs1, 42));
ASSERT_NE(cache.size(), 0u);
ASSERT_FALSE(cache.empty());
@@ -45,8 +50,8 @@ TEST(Resource, Functionalities) {
ASSERT_FALSE(cache.contains(hs2));
ASSERT_EQ((*cache.handle(hs1)).value, 42);
ASSERT_TRUE(cache.load<loader>(hs1, 42));
ASSERT_TRUE(cache.load<loader>(hs2, 42));
ASSERT_TRUE(cache.load<loader<resource>>(hs1, 42));
ASSERT_TRUE(cache.load<loader<resource>>(hs2, 42));
ASSERT_NE(cache.size(), 0u);
ASSERT_FALSE(cache.empty());
@@ -61,7 +66,7 @@ TEST(Resource, Functionalities) {
ASSERT_TRUE(cache.contains(hs2));
ASSERT_EQ(cache.handle(hs2)->value, 42);
ASSERT_TRUE(cache.load<loader>(hs1, 42));
ASSERT_TRUE(cache.load<loader<resource>>(hs1, 42));
ASSERT_NO_FATAL_FAILURE(cache.clear());
ASSERT_EQ(cache.size(), 0u);
@@ -69,7 +74,7 @@ TEST(Resource, Functionalities) {
ASSERT_FALSE(cache.contains(hs1));
ASSERT_FALSE(cache.contains(hs2));
ASSERT_TRUE(cache.load<loader>(hs1, 42));
ASSERT_TRUE(cache.load<loader<resource>>(hs1, 42));
ASSERT_NE(cache.size(), 0u);
ASSERT_FALSE(cache.empty());
@@ -83,7 +88,7 @@ TEST(Resource, Functionalities) {
ASSERT_EQ(cache.size(), 0u);
ASSERT_TRUE(cache.empty());
ASSERT_TRUE(cache.temp<loader>(42));
ASSERT_TRUE(cache.temp<loader<resource>>(42));
ASSERT_TRUE(cache.empty());
ASSERT_FALSE(entt::resource_handle<resource>{});
@@ -97,7 +102,7 @@ TEST(Resource, MutableHandle) {
entt::resource_cache<resource> cache;
constexpr auto hs = entt::hashed_string{"res"};
auto handle = cache.load<loader>(hs, 0);
auto handle = cache.load<loader<resource>>(hs, 0);
ASSERT_TRUE(handle);
@@ -109,11 +114,50 @@ TEST(Resource, MutableHandle) {
ASSERT_EQ(cache.handle(hs)->value, 4);
}
TEST(Resource, HandleCast) {
using namespace entt::literals;
entt::resource_cache<resource> cache;
auto handle = cache.load<loader<derived_resource>>("resource"_hs, 0);
auto resource = std::make_shared<derived_resource>();
entt::resource_handle<derived_resource> other{resource};
ASSERT_TRUE(handle);
ASSERT_TRUE(other);
ASSERT_NE(&*handle, &*other);
ASSERT_EQ(resource.use_count(), 2u);
auto temp = std::move(handle);
handle = other;
ASSERT_TRUE(handle);
ASSERT_TRUE(other);
ASSERT_TRUE(temp);
ASSERT_EQ(&*handle, &*other);
ASSERT_EQ(resource.use_count(), 3u);
temp = std::move(other);
ASSERT_TRUE(handle);
ASSERT_FALSE(other);
ASSERT_TRUE(temp);
ASSERT_EQ(&*handle, &*temp);
ASSERT_EQ(resource.use_count(), 3u);
temp = handle = {};
ASSERT_FALSE(handle);
ASSERT_FALSE(other);
ASSERT_FALSE(temp);
ASSERT_EQ(resource.use_count(), 1u);
}
TEST(Resource, Each) {
using namespace entt::literals;
entt::resource_cache<resource> cache;
cache.load<loader>("resource"_hs, 0);
cache.load<loader<resource>>("resource"_hs, 0);
cache.each([](entt::resource_handle<resource> res) {
++res->value;

View File

@@ -4,17 +4,18 @@
#include <entt/entity/registry.hpp>
struct entity_id {
using entity_type = typename entt::entt_traits<entt::entity>::entity_type;
using entity_type = std::uint32_t;
static constexpr auto null = entt::null;
entity_id(entity_type value = entt::null)
constexpr entity_id(entity_type value = null) ENTT_NOEXCEPT
: entt{value}
{}
entity_id(const entity_id &other)
constexpr entity_id(const entity_id &other) ENTT_NOEXCEPT
: entt{other.entt}
{}
operator entity_type() const {
constexpr operator entity_type() const ENTT_NOEXCEPT {
return entt;
}
@@ -22,9 +23,6 @@ private:
entity_type entt;
};
template<>
struct entt::entt_traits<entity_id>: entt::entt_traits<entt::entity> {};
TEST(Example, CustomIdentifier) {
entt::basic_registry<entity_id> registry{};
entity_id entity{};

View File

@@ -35,7 +35,7 @@ TEST(Example, SignalLess) {
// literally a test for storage_adapter_mixin
registry.emplace<int>(entity[0], 0);
registry.remove<int>(entity[0]);
registry.erase<int>(entity[0]);
registry.insert<int>(std::begin(entity), std::end(entity), 3);
registry.patch<int>(entity[0], [](auto &value) { value = 42; });