Compare commits
180 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd6863f71d | ||
|
|
dc7c976518 | ||
|
|
3408556eea | ||
|
|
f94b9773da | ||
|
|
e0b3786d97 | ||
|
|
4047cb01a8 | ||
|
|
8cfd08b137 | ||
|
|
311011672c | ||
|
|
6df19c833d | ||
|
|
78d9e71888 | ||
|
|
94131648dd | ||
|
|
151f180199 | ||
|
|
bb8bfaf262 | ||
|
|
2d5a3f24aa | ||
|
|
1639429edd | ||
|
|
f81abf4883 | ||
|
|
dcddb7d50e | ||
|
|
42a763031c | ||
|
|
0964ca5918 | ||
|
|
4e870b83cd | ||
|
|
0b19c9be0b | ||
|
|
ac655902a0 | ||
|
|
e4fb293b55 | ||
|
|
d3df64ef4b | ||
|
|
93e3d14f34 | ||
|
|
bc687d412d | ||
|
|
71e85b44b0 | ||
|
|
d4b59aff97 | ||
|
|
e3c21e1f3d | ||
|
|
e012250e0a | ||
|
|
c6bf82664c | ||
|
|
c61e98009f | ||
|
|
44bccaaad6 | ||
|
|
aebca14dea | ||
|
|
4576f27f6e | ||
|
|
0c213fca40 | ||
|
|
891f596191 | ||
|
|
eb2b724902 | ||
|
|
1f8c896181 | ||
|
|
12b39e1fbe | ||
|
|
206d31f27a | ||
|
|
6709331182 | ||
|
|
9460e04ea5 | ||
|
|
462bfea733 | ||
|
|
a2d61dfaeb | ||
|
|
538425e35c | ||
|
|
2e86c1f1a2 | ||
|
|
8aa4d46ce0 | ||
|
|
cdf67a1421 | ||
|
|
93d905d93d | ||
|
|
2c35203647 | ||
|
|
216871fe56 | ||
|
|
03511f39b1 | ||
|
|
5ad0832b22 | ||
|
|
d49e7ba4b2 | ||
|
|
a621b36389 | ||
|
|
d093df02ac | ||
|
|
2e1529e78d | ||
|
|
90be1db402 | ||
|
|
29265e4181 | ||
|
|
2689a7ef13 | ||
|
|
88b58cf23d | ||
|
|
e2e433480b | ||
|
|
b8bc3e4e94 | ||
|
|
c28f52b816 | ||
|
|
ca34309f75 | ||
|
|
ea614a0f3f | ||
|
|
3e7dc7af29 | ||
|
|
0b3e3fd19a | ||
|
|
d3372dc05c | ||
|
|
7d141cb183 | ||
|
|
755699c31b | ||
|
|
acf3d4cd74 | ||
|
|
92414c91b5 | ||
|
|
ff67f402bb | ||
|
|
f048a3f1d8 | ||
|
|
9d39cb51cf | ||
|
|
7624a9d34c | ||
|
|
0fe73fb6ed | ||
|
|
ae11652493 | ||
|
|
0ab46870ae | ||
|
|
a5381374b8 | ||
|
|
7fae655651 | ||
|
|
3d9959c8e5 | ||
|
|
bc86576aa8 | ||
|
|
7c2bce8baf | ||
|
|
b062bbf58d | ||
|
|
8e5a048913 | ||
|
|
ff642ffff7 | ||
|
|
656c12cebd | ||
|
|
48f3fcf3bd | ||
|
|
d797af695b | ||
|
|
75e0161c5f | ||
|
|
c0662816f1 | ||
|
|
415a31ce23 | ||
|
|
6e603e2e51 | ||
|
|
9af318a767 | ||
|
|
30827120f6 | ||
|
|
6194e12616 | ||
|
|
59a27fb652 | ||
|
|
f8ec57c2f6 | ||
|
|
844ef5e232 | ||
|
|
5bf3f7b77e | ||
|
|
00c0afd093 | ||
|
|
5228739c87 | ||
|
|
0f6d1268c7 | ||
|
|
b2a4515d2b | ||
|
|
3576c80d33 | ||
|
|
77afd2d36c | ||
|
|
e6ddd5d9c2 | ||
|
|
b9bf1a234e | ||
|
|
0ce9122449 | ||
|
|
dc41657872 | ||
|
|
64d9380031 | ||
|
|
eca7484e30 | ||
|
|
2b5c393a13 | ||
|
|
0817d416a3 | ||
|
|
ccff753305 | ||
|
|
11481a430a | ||
|
|
e252b22735 | ||
|
|
a32ca8eb1d | ||
|
|
1979a2279f | ||
|
|
1434f942dd | ||
|
|
955d325f07 | ||
|
|
3fac3fe2d7 | ||
|
|
6ff217e74e | ||
|
|
cd667fe34b | ||
|
|
3d6202ecfd | ||
|
|
c40f0ef2bb | ||
|
|
c4b169edd1 | ||
|
|
c6bba98828 | ||
|
|
e5d4f1bb58 | ||
|
|
cf522d60ca | ||
|
|
daf72a7c61 | ||
|
|
f116ad0594 | ||
|
|
e59d40834d | ||
|
|
d06328af7f | ||
|
|
baa9d7d836 | ||
|
|
b030df55ee | ||
|
|
64eb8a2d0d | ||
|
|
7d7c36e0c7 | ||
|
|
9ad5768050 | ||
|
|
1cafbcff38 | ||
|
|
4bbf93fd0c | ||
|
|
120918bc37 | ||
|
|
253448cffb | ||
|
|
26f19fb90e | ||
|
|
e8f982e909 | ||
|
|
0fa433187e | ||
|
|
b0069299ea | ||
|
|
375d5d3e9b | ||
|
|
2d9398ae1a | ||
|
|
fc69e91636 | ||
|
|
5c56cbd672 | ||
|
|
a42255158d | ||
|
|
f3d10a97df | ||
|
|
5d15a3d69f | ||
|
|
94292872dc | ||
|
|
5c8a1e7d10 | ||
|
|
e98d8426bb | ||
|
|
77f80cecf9 | ||
|
|
bce26a1499 | ||
|
|
453f1c6edc | ||
|
|
9f8a36f2c9 | ||
|
|
e6a5945463 | ||
|
|
60393fbc5f | ||
|
|
b9a925dbd4 | ||
|
|
5dbdb1bcb5 | ||
|
|
d55cefc086 | ||
|
|
5380e6d98b | ||
|
|
45cc24e0b8 | ||
|
|
76bf1791eb | ||
|
|
7e9a4c4b16 | ||
|
|
75cd5f169f | ||
|
|
10636c82a2 | ||
|
|
04a6729963 | ||
|
|
b5617398ad | ||
|
|
da14641ccb | ||
|
|
3997bd2396 | ||
|
|
c876350e05 |
57
.github/workflows/build.yml
vendored
57
.github/workflows/build.yml
vendored
@@ -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
|
||||
|
||||
6
.github/workflows/sanitizer.yml
vendored
6
.github/workflows/sanitizer.yml
vendored
@@ -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
1
.gitignore
vendored
@@ -7,6 +7,7 @@ conan/test_package/build
|
||||
.vscode
|
||||
.vs
|
||||
CMakeSettings.json
|
||||
cpp.hint
|
||||
|
||||
# Bazel
|
||||
/bazel-*
|
||||
|
||||
@@ -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
44
TODO
@@ -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
|
||||
|
||||
154
docs/doxy.in
154
docs/doxy.in
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -60,7 +60,8 @@ struct overloaded: Func... {
|
||||
* @tparam Func Types of function objects.
|
||||
*/
|
||||
template<class... Func>
|
||||
overloaded(Func...) -> overloaded<Func...>;
|
||||
overloaded(Func...)
|
||||
-> overloaded<Func...>;
|
||||
|
||||
|
||||
/**
|
||||
|
||||
34
src/entt/entity/component.hpp
Normal file
34
src/entt/entity/component.hpp
Normal 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
|
||||
@@ -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{};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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> ®, 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> ®, const Component &component) {
|
||||
Entity to_entity(const basic_registry<Entity> ®, 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>...>;
|
||||
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 &>());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -10,7 +10,7 @@ struct resource_cache;
|
||||
|
||||
|
||||
template<typename>
|
||||
struct resource_handle;
|
||||
class resource_handle;
|
||||
|
||||
|
||||
template<typename, typename>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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...)>;
|
||||
|
||||
|
||||
|
||||
@@ -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...)>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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, ®istry](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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(®istry.get<int>(entity) + ENTT_PACKED_PAGE - 1u, ®istry.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(®istry.get<int>(entity) + ENTT_PACKED_PAGE - 1u, ®istry.get<int>(next));
|
||||
|
||||
ASSERT_EQ(entt::to_entity(registry, 42), null);
|
||||
ASSERT_EQ(entt::to_entity(registry, value), null);
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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){};
|
||||
}
|
||||
|
||||
@@ -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}));
|
||||
}
|
||||
|
||||
63
test/entt/entity/throwing_allocator.hpp
Normal file
63
test/entt/entity/throwing_allocator.hpp
Normal 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
|
||||
52
test/entt/entity/throwing_component.hpp
Normal file
52
test/entt/entity/throwing_component.hpp
Normal 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
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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{};
|
||||
|
||||
@@ -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; });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user