Compare commits
378 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
1e07b981f0 | ||
|
|
756e909f5e | ||
|
|
2404392daf | ||
|
|
bf6576f5af | ||
|
|
158c2ab76e | ||
|
|
56fa50385e | ||
|
|
eb05ab5fab | ||
|
|
7f87b637d5 | ||
|
|
4b213adc75 | ||
|
|
7e7d5bbf17 | ||
|
|
e18c0a0c19 | ||
|
|
02b6ffc771 | ||
|
|
340d66c24c | ||
|
|
2cbf9d3620 | ||
|
|
11e6793544 | ||
|
|
d07e0336a2 | ||
|
|
7dbfb3d9ae | ||
|
|
02ef5db6e3 | ||
|
|
bb43bb5508 | ||
|
|
a771f083a7 | ||
|
|
67f8bce1dd | ||
|
|
3b6dd23598 | ||
|
|
66bbeae7da | ||
|
|
059334a861 | ||
|
|
f874c8309f | ||
|
|
27be812cc3 | ||
|
|
2adc2e97e1 | ||
|
|
dc4279a2d0 | ||
|
|
85bff4525a | ||
|
|
bd648c0745 | ||
|
|
d5e7005edd | ||
|
|
8913c7ea18 | ||
|
|
0e2afe4f98 | ||
|
|
a936f7cdbe | ||
|
|
44fa466618 | ||
|
|
b05d84e8e3 | ||
|
|
7787c1ddd7 | ||
|
|
52bfddd2e7 | ||
|
|
f59adcdc37 | ||
|
|
e996d3398f | ||
|
|
d73892d25a | ||
|
|
1ebf614d79 | ||
|
|
f9c995f03f | ||
|
|
2dcdd561f0 | ||
|
|
90f97aa8d7 | ||
|
|
5a4b067cee | ||
|
|
cabcc761c6 | ||
|
|
a40ac1c46c | ||
|
|
85ddfc4d21 | ||
|
|
037a35df1d | ||
|
|
64c753023e | ||
|
|
9d72ffb9fe | ||
|
|
4162d4fbc6 | ||
|
|
a4d16bffd3 | ||
|
|
bcaf1489c0 | ||
|
|
a85712e8aa | ||
|
|
0f95d02cdc | ||
|
|
c151d55237 | ||
|
|
785cd6bc11 | ||
|
|
8ec7e3cc97 | ||
|
|
e376493970 | ||
|
|
08e2322d79 | ||
|
|
2832767daa | ||
|
|
62e12ee0aa | ||
|
|
80e73c1089 | ||
|
|
4f93995bd7 | ||
|
|
03c7dac92f | ||
|
|
d0b93f565a | ||
|
|
1512fbae55 | ||
|
|
4d91afdf9f | ||
|
|
c239c3fea3 | ||
|
|
a0b431e8c4 | ||
|
|
005e03aeb3 | ||
|
|
6002d373e2 | ||
|
|
a53066424b | ||
|
|
38ab02ff88 | ||
|
|
fee0b29a0b | ||
|
|
2553695029 | ||
|
|
301939983f | ||
|
|
cd9ae1fbad | ||
|
|
54e9bc86cc | ||
|
|
a1dd4c28c3 | ||
|
|
6d3857f337 | ||
|
|
c4be3e731a | ||
|
|
ea4407b847 | ||
|
|
a62a83044f | ||
|
|
e7e7b06744 | ||
|
|
2222e31885 | ||
|
|
c3f7f83c55 | ||
|
|
86e18f68c4 | ||
|
|
cc7d9e03d9 | ||
|
|
c8639ae434 | ||
|
|
85cf32516e | ||
|
|
bc85817b07 | ||
|
|
1016394be5 | ||
|
|
de35cbf4af | ||
|
|
bb1acee36a | ||
|
|
c6a2ed78c3 | ||
|
|
7729958082 | ||
|
|
ccdaec86b3 | ||
|
|
42afeef993 | ||
|
|
3d06911886 | ||
|
|
98f929e41e | ||
|
|
94d4e0231e | ||
|
|
e60cdb2e3b | ||
|
|
550c021097 | ||
|
|
1aec7f71b8 | ||
|
|
07565927f2 | ||
|
|
fda78ede1b | ||
|
|
1249a4f8d3 | ||
|
|
4e631f1536 | ||
|
|
1757dbc225 | ||
|
|
8149e204d2 | ||
|
|
412f2ef63e | ||
|
|
ea33673daa | ||
|
|
169b816613 | ||
|
|
48edf077fc | ||
|
|
91db153710 | ||
|
|
ea1f010b1a | ||
|
|
935a852745 | ||
|
|
0eb6d7de3c | ||
|
|
3118cd4fa5 | ||
|
|
18832fcb37 | ||
|
|
612554f6af | ||
|
|
1c794591b9 | ||
|
|
a8d95b284d | ||
|
|
772c2dba2f | ||
|
|
9d1a210c97 | ||
|
|
b04b966db1 | ||
|
|
e2c61d90e9 | ||
|
|
d50655c4f7 | ||
|
|
5e4c63736b | ||
|
|
b90b71c8c0 | ||
|
|
722033e906 | ||
|
|
b6ec91fcd0 | ||
|
|
e94c0d003a | ||
|
|
5c46ccb37e | ||
|
|
fd989feea3 | ||
|
|
1b53e83dde | ||
|
|
3ebe827b51 | ||
|
|
b8266e1169 | ||
|
|
4efbe24607 | ||
|
|
e96ac1f6ff | ||
|
|
14915368c7 | ||
|
|
0ff5c18743 | ||
|
|
240814cc85 | ||
|
|
1912350cc6 | ||
|
|
146faa6008 | ||
|
|
e5fa67850b | ||
|
|
1377b51341 | ||
|
|
db67fb3539 | ||
|
|
6d216406f3 | ||
|
|
ab225eaec1 | ||
|
|
4d6d9e567d | ||
|
|
0fcf0142ba | ||
|
|
ab907e4fef | ||
|
|
1c1c3eb271 | ||
|
|
a03a569534 | ||
|
|
309fb0fa83 | ||
|
|
14c1431848 | ||
|
|
c3dc32415b | ||
|
|
44d6246278 | ||
|
|
5bbd7e03f9 | ||
|
|
f5f463b411 | ||
|
|
6eaefbe25c | ||
|
|
4b5c2c85a5 | ||
|
|
0f951cd322 | ||
|
|
cf0da32fd0 | ||
|
|
5a3085c42b | ||
|
|
42cc480bf5 | ||
|
|
2822eda858 | ||
|
|
6c804b5ca2 | ||
|
|
5149d1395e | ||
|
|
f75a2dab8d | ||
|
|
a045c88c61 | ||
|
|
a5fdf917df | ||
|
|
80a0e47f1a | ||
|
|
a304313ad4 | ||
|
|
05b1d5a4da | ||
|
|
edeef3541c | ||
|
|
cd929d8e65 | ||
|
|
b005971427 | ||
|
|
c35614fb63 | ||
|
|
18e16b09f2 | ||
|
|
b18b76c1f0 | ||
|
|
c9f65267ba | ||
|
|
cce52c673b | ||
|
|
fdde216f0e | ||
|
|
d1901a97d1 | ||
|
|
eb2077b95b | ||
|
|
fad3bdeed4 | ||
|
|
e1a537e547 | ||
|
|
ba098e1199 | ||
|
|
0ec755fccf | ||
|
|
997dd433b3 | ||
|
|
4aea1567db | ||
|
|
446c8df3b4 | ||
|
|
f4bd868d6a | ||
|
|
8490264af3 | ||
|
|
505cfdd193 | ||
|
|
39ecd1545c | ||
|
|
9b80c0028d | ||
|
|
35276b55af | ||
|
|
eb63dd6227 | ||
|
|
9f24b3c584 | ||
|
|
53e951d96d | ||
|
|
17dd386937 | ||
|
|
3eb00382e7 | ||
|
|
98b5f7a26a | ||
|
|
fb70ec1cb0 | ||
|
|
47ada87ba2 |
37
.github/workflows/build.yml
vendored
37
.github/workflows/build.yml
vendored
@@ -10,26 +10,51 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
compiler: [
|
||||
g++-8, g++-9, g++,
|
||||
clang++-8, clang++-9, clang++
|
||||
g++-7, g++-8, g++-9, g++,
|
||||
clang++-8, clang++-9, clang++-10, clang++
|
||||
]
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install g++-7
|
||||
if: ${{ matrix.compiler == 'g++-7' }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install g++-7 -y
|
||||
- name: Install g++-8
|
||||
if: ${{ matrix.compiler == 'g++-8' }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install g++-8 -y
|
||||
- name: Install clang-8
|
||||
if: ${{ matrix.compiler == 'clang++-8' }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install clang-8 -y
|
||||
- name: Install clang-9
|
||||
if: ${{ matrix.compiler == 'clang++-9' }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install clang-9 -y
|
||||
- name: Install clang-10
|
||||
if: ${{ matrix.compiler == 'clang++-10' }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install clang-10 -y
|
||||
- name: Compile tests
|
||||
working-directory: build
|
||||
env:
|
||||
CXX: ${{ matrix.compiler }}
|
||||
run: |
|
||||
cmake -DENTT_USE_ASAN=ON -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON ..
|
||||
cmake -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON ..
|
||||
make -j4
|
||||
- name: Run tests
|
||||
working-directory: build
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
run: ctest --timeout 5 -C Debug -j4
|
||||
run: ctest --timeout 10 -C Debug -j4
|
||||
|
||||
windows:
|
||||
timeout-minutes: 10
|
||||
@@ -62,7 +87,7 @@ jobs:
|
||||
working-directory: build
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
run: ctest --timeout 5 -C Debug -j4
|
||||
run: ctest --timeout 10 -C Debug -j4
|
||||
|
||||
macos:
|
||||
timeout-minutes: 10
|
||||
@@ -79,4 +104,4 @@ jobs:
|
||||
working-directory: build
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
run: ctest --timeout 5 -C Debug -j4
|
||||
run: ctest --timeout 10 -C Debug -j4
|
||||
|
||||
49
.github/workflows/coverage.yml
vendored
49
.github/workflows/coverage.yml
vendored
@@ -9,25 +9,30 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Compile tests
|
||||
working-directory: build
|
||||
env:
|
||||
CXXFLAGS: "-O0 --coverage -fno-inline -fno-inline-small-functions -fno-default-inline"
|
||||
CXX: g++
|
||||
run: |
|
||||
cmake -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON ..
|
||||
make -j4
|
||||
- name: Run tests
|
||||
working-directory: build
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
run: ctest --timeout 5 -C Debug -j4
|
||||
- name: Upload coverage to Codecov
|
||||
working-directory: build
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
run: |
|
||||
wget https://codecov.io/bash -O codecov
|
||||
chmod +x codecov
|
||||
./codecov -t $CODECOV_TOKEN -B $GITHUB_REF -s .
|
||||
- uses: actions/checkout@v2
|
||||
- name: Compile tests
|
||||
working-directory: build
|
||||
env:
|
||||
CXXFLAGS: "--coverage -fno-inline"
|
||||
CXX: g++
|
||||
run: |
|
||||
cmake -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON ..
|
||||
make -j4
|
||||
- name: Run tests
|
||||
working-directory: build
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
run: ctest --timeout 10 -C Debug -j4
|
||||
- name: Collect data
|
||||
working-directory: build
|
||||
run: |
|
||||
sudo apt install lcov
|
||||
lcov -c -d . -o coverage.info
|
||||
lcov -l coverage.info
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
file: build/coverage.info
|
||||
name: EnTT
|
||||
fail_ci_if_error: true
|
||||
|
||||
3
.github/workflows/deploy.yml
vendored
3
.github/workflows/deploy.yml
vendored
@@ -20,8 +20,7 @@ jobs:
|
||||
working-directory: build
|
||||
env:
|
||||
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
||||
run: |
|
||||
git clone https://$GITHUB_ACTOR:$PERSONAL_ACCESS_TOKEN@github.com/$GITHUB_ACTOR/$GH_REPO.git
|
||||
run: git clone https://$GITHUB_ACTOR:$PERSONAL_ACCESS_TOKEN@github.com/$GITHUB_ACTOR/$GH_REPO.git
|
||||
- name: Prepare formula
|
||||
working-directory: build
|
||||
run: |
|
||||
|
||||
29
.github/workflows/sanitizer.yml
vendored
Normal file
29
.github/workflows/sanitizer.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: sanitizer
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
|
||||
linux:
|
||||
timeout-minutes: 10
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
compiler: [clang++]
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Compile tests
|
||||
working-directory: build
|
||||
env:
|
||||
CXX: ${{ matrix.compiler }}
|
||||
run: |
|
||||
cmake -DENTT_USE_SANITIZER=ON -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON ..
|
||||
make -j4
|
||||
- name: Run tests
|
||||
working-directory: build
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
run: ctest --timeout 10 -C Debug -j4
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,6 +7,7 @@ conan/test_package/build
|
||||
.vscode
|
||||
.vs
|
||||
CMakeSettings.json
|
||||
cpp.hint
|
||||
|
||||
# Bazel
|
||||
/bazel-*
|
||||
|
||||
@@ -9,7 +9,6 @@ cc_library(
|
||||
copts = select({
|
||||
"@bazel_tools//src/conditions:windows": _msvc_copts,
|
||||
"@bazel_tools//src/conditions:windows_msvc": _msvc_copts,
|
||||
"@bazel_tools//src/conditions:windows_msys": _msvc_copts,
|
||||
"//conditions:default": _gcc_copts,
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -43,7 +43,7 @@ message(VERBOSE "* Copyright (c) 2017-2021 Michele Caini <michele.caini@gmail.co
|
||||
message(VERBOSE "*")
|
||||
|
||||
option(ENTT_USE_LIBCPP "Use libc++ by adding -stdlib=libc++ flag if availbale." ON)
|
||||
option(ENTT_USE_ASAN "Use address sanitizer by adding -fsanitize=address -fno-omit-frame-pointer flags" OFF)
|
||||
option(ENTT_USE_SANITIZER "Enable sanitizers by adding -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined flags" OFF)
|
||||
|
||||
#
|
||||
# Compiler stuff
|
||||
@@ -85,9 +85,9 @@ target_include_directories(
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
||||
)
|
||||
|
||||
if(ENTT_USE_ASAN)
|
||||
target_compile_options(EnTT INTERFACE $<$<CONFIG:Debug>:-fsanitize=address -fno-omit-frame-pointer>)
|
||||
target_link_libraries(EnTT INTERFACE $<$<CONFIG:Debug>:-fsanitize=address -fno-omit-frame-pointer>)
|
||||
if(ENTT_USE_SANITIZER)
|
||||
target_compile_options(EnTT INTERFACE $<$<CONFIG:Debug>:-fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined>)
|
||||
target_link_libraries(EnTT INTERFACE $<$<CONFIG:Debug>:-fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined>)
|
||||
endif()
|
||||
|
||||
if(ENTT_HAS_LIBCPP)
|
||||
@@ -96,6 +96,23 @@ endif()
|
||||
|
||||
target_compile_features(EnTT INTERFACE cxx_std_17)
|
||||
|
||||
#
|
||||
# Install pkg-config file
|
||||
#
|
||||
|
||||
set(EnTT_PKGCONFIG ${CMAKE_CURRENT_BINARY_DIR}/entt.pc)
|
||||
|
||||
configure_file(
|
||||
${EnTT_SOURCE_DIR}/cmake/in/entt.pc.in
|
||||
${EnTT_PKGCONFIG}
|
||||
@ONLY
|
||||
)
|
||||
|
||||
install(
|
||||
FILES ${EnTT_PKGCONFIG}
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig
|
||||
)
|
||||
|
||||
#
|
||||
# Install EnTT
|
||||
#
|
||||
@@ -186,6 +203,7 @@ add_custom_target(
|
||||
.github/workflows/build.yml
|
||||
.github/workflows/coverage.yml
|
||||
.github/workflows/deploy.yml
|
||||
.github/workflows/sanitizer.yml
|
||||
.github/FUNDING.yml
|
||||
AUTHORS
|
||||
CONTRIBUTING.md
|
||||
|
||||
209
README.md
209
README.md
@@ -3,10 +3,9 @@
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
[](https://github.com/skypjack/entt/releases)
|
||||
[](https://github.com/skypjack/entt/actions)
|
||||
[](https://codecov.io/gh/skypjack/entt)
|
||||
[](https://godbolt.org/z/cOUcm1)
|
||||
[](https://godbolt.org/z/zxW73f)
|
||||
[](http://entt.docsforge.com/)
|
||||
[](https://gitter.im/skypjack/entt)
|
||||
[](https://discord.gg/5BjPWBd)
|
||||
@@ -17,8 +16,7 @@ much more written in **modern C++**.<br/>
|
||||
[Among others](https://github.com/skypjack/entt/wiki/EnTT-in-Action), it's used
|
||||
in [**Minecraft**](https://minecraft.net/en-us/attribution/) by Mojang, the
|
||||
[**ArcGIS Runtime SDKs**](https://developers.arcgis.com/arcgis-runtime/) by Esri
|
||||
and the amazing [**Ragdoll**](https://ragdolldynamics.com/) Autodesk Maya
|
||||
plugin.<br/>
|
||||
and the amazing [**Ragdoll**](https://ragdolldynamics.com/).<br/>
|
||||
If you don't see your project in the list, please open an issue, submit a PR or
|
||||
add the [#entt](https://github.com/topics/entt) tag to your _topics_! :+1:
|
||||
|
||||
@@ -26,23 +24,21 @@ add the [#entt](https://github.com/topics/entt) tag to your _topics_! :+1:
|
||||
|
||||
Do you want to **keep up with changes** or do you have a **question** that
|
||||
doesn't require you to open an issue?<br/>
|
||||
Join the [gitter channel](https://gitter.im/skypjack/entt) or the
|
||||
[discord server](https://discord.gg/5BjPWBd) and meet other users like you. The
|
||||
more we are, the better for everyone.
|
||||
Join the [gitter channel](https://gitter.im/skypjack/entt) and the
|
||||
[discord server](https://discord.gg/5BjPWBd), meet other users like you. The
|
||||
more we are, the better for everyone.<br/>
|
||||
Don't forget to check the
|
||||
[FAQs](https://github.com/skypjack/entt/wiki/Frequently-Asked-Questions) and the
|
||||
[wiki](https://github.com/skypjack/entt/wiki) too. Your answers may already be
|
||||
there.
|
||||
|
||||
Wondering why your **debug build** is so slow on Windows or how to represent a
|
||||
**hierarchy** with components?<br/>
|
||||
Check out the
|
||||
[FAQ](https://github.com/skypjack/entt/wiki/Frequently-Asked-Questions) and the
|
||||
[wiki](https://github.com/skypjack/entt/wiki) if you have these or other doubts,
|
||||
your answers may already be there.
|
||||
Do you want to support `EnTT`? Consider becoming a
|
||||
[**sponsor**](https://github.com/users/skypjack/sponsorship).
|
||||
Many thanks to [these people](https://skypjack.github.io/sponsorship/) and
|
||||
**special** thanks to:
|
||||
|
||||
If you use `EnTT` and you want to say thanks or support the project, please
|
||||
**consider becoming a
|
||||
[sponsor](https://github.com/users/skypjack/sponsorship)**.<br/>
|
||||
You can help me make the difference.
|
||||
[Many thanks](https://skypjack.github.io/sponsorship/) to those who supported me
|
||||
and still support me today.
|
||||
[](https://mojang.com)
|
||||
[](https://img.ly/)
|
||||
|
||||
# Table of Contents
|
||||
|
||||
@@ -50,16 +46,16 @@ and still support me today.
|
||||
* [Code Example](#code-example)
|
||||
* [Motivation](#motivation)
|
||||
* [Performance](#performance)
|
||||
* [Build Instructions](#build-instructions)
|
||||
* [Integration](#integration)
|
||||
* [Requirements](#requirements)
|
||||
* [Library](#library)
|
||||
* [Documentation](#documentation)
|
||||
* [Tests](#tests)
|
||||
* [Packaging Tools](#packaging-tools)
|
||||
* [CMake](#cmake)
|
||||
* [Packaging Tools](#packaging-tools)
|
||||
* [pkg-config](#pkg-config)
|
||||
* [Documentation](#documentation)
|
||||
* [Tests](#tests)
|
||||
* [EnTT in Action](#entt-in-action)
|
||||
* [Contributors](#contributors)
|
||||
* [License](#license)
|
||||
* [Support](#support)
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
@@ -103,7 +99,7 @@ Here is a brief, yet incomplete list of what it offers today:
|
||||
Consider this list a work in progress as well as the project. The whole API is
|
||||
fully documented in-code for those who are brave enough to read it.
|
||||
|
||||
It is also known that `EnTT` (version 3) is used in **Minecraft**.<br/>
|
||||
It is also known that `EnTT` is used in **Minecraft**.<br/>
|
||||
Given that the game is available literally everywhere, I can confidently say
|
||||
that the library has been sufficiently tested on every platform that can come to
|
||||
mind.
|
||||
@@ -203,26 +199,7 @@ new features, mainly for fun.<br/>
|
||||
If you want to contribute and/or have suggestions, feel free to make a PR or
|
||||
open an issue to discuss your idea.
|
||||
|
||||
# Build Instructions
|
||||
|
||||
## Requirements
|
||||
|
||||
To be able to use `EnTT`, users must provide a full-featured compiler that
|
||||
supports at least C++17.<br/>
|
||||
The requirements below are mandatory to compile the tests and to extract the
|
||||
documentation:
|
||||
|
||||
* `CMake` version 3.7 or later.
|
||||
* `Doxygen` version 1.8 or later.
|
||||
|
||||
Alternatively, [Bazel](https://bazel.build) is also supported as a build system
|
||||
(credits to [zaucy](https://github.com/zaucy) who offered to maintain it).<br/>
|
||||
In the documentation below I'll still refer to `CMake`, this being the official
|
||||
build system of the library.
|
||||
|
||||
If you are looking for a C++14 version of `EnTT`, check out the git tag `cpp14`.
|
||||
|
||||
## Library
|
||||
# Integration
|
||||
|
||||
`EnTT` is a header-only library. This means that including the `entt.hpp` header
|
||||
is enough to include the library as a whole and use it. For those who are
|
||||
@@ -243,51 +220,33 @@ Use the line below to include only the entity-component system instead:
|
||||
Then pass the proper `-I` argument to the compiler to add the `src` directory to
|
||||
the include paths.
|
||||
|
||||
## Documentation
|
||||
## Requirements
|
||||
|
||||
The documentation is based on [doxygen](http://www.doxygen.nl/).
|
||||
To build it:
|
||||
To be able to use `EnTT`, users must provide a full-featured compiler that
|
||||
supports at least C++17.<br/>
|
||||
The requirements below are mandatory to compile the tests and to extract the
|
||||
documentation:
|
||||
|
||||
$ cd build
|
||||
$ cmake .. -DENTT_BUILD_DOCS=ON
|
||||
$ make
|
||||
* `CMake` version 3.7 or later.
|
||||
* `Doxygen` version 1.8 or later.
|
||||
|
||||
The API reference will be created in HTML format within the directory
|
||||
`build/docs/html`. To navigate it with your favorite browser:
|
||||
Alternatively, [Bazel](https://bazel.build) is also supported as a build system
|
||||
(credits to [zaucy](https://github.com/zaucy) who offered to maintain it).<br/>
|
||||
In the documentation below I'll still refer to `CMake`, this being the official
|
||||
build system of the library.
|
||||
|
||||
$ cd build
|
||||
$ your_favorite_browser docs/html/index.html
|
||||
## CMake
|
||||
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
The same version is also available [online](https://skypjack.github.io/entt/)
|
||||
for the latest release, that is the last stable tag. If you are looking for
|
||||
something more pleasing to the eye, consider reading the nice-looking version
|
||||
available on [docsforge](https://entt.docsforge.com/): same documentation, much
|
||||
more pleasant to read.<br/>
|
||||
Moreover, there exists a [wiki](https://github.com/skypjack/entt/wiki) dedicated
|
||||
to the project where users can find all related documentation pages.
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
To use `EnTT` from a `CMake` project, just link an existing target to the
|
||||
`EnTT::EnTT` alias.<br/>
|
||||
The library offers everything you need for locating (as in `find_package`),
|
||||
embedding (as in `add_subdirectory`), fetching (as in `FetchContent`) or using
|
||||
it in many of the ways that you can think of and that involve `CMake`.<br/>
|
||||
Covering all possible cases would require a treaty and not a simple README file,
|
||||
but I'm confident that anyone reading this section also knows what it's about
|
||||
and can use `EnTT` from a `CMake` project without problems.
|
||||
|
||||
## Tests
|
||||
|
||||
To compile and run the tests, `EnTT` requires *googletest*.<br/>
|
||||
`cmake` will download and compile the library before compiling anything else.
|
||||
In order to build the tests, set the CMake option `ENTT_BUILD_TESTING` to `ON`.
|
||||
|
||||
To build the most basic set of tests:
|
||||
|
||||
* `$ cd build`
|
||||
* `$ cmake -DENTT_BUILD_TESTING=ON ..`
|
||||
* `$ make`
|
||||
* `$ make test`
|
||||
|
||||
Note that benchmarks are not part of this set.
|
||||
|
||||
# Packaging Tools
|
||||
## Packaging Tools
|
||||
|
||||
`EnTT` is available for some of the most known packaging tools. In particular:
|
||||
|
||||
@@ -346,7 +305,58 @@ Note that benchmarks are not part of this set.
|
||||
[documentation](https://build2.org/build2-toolchain/doc/build2-toolchain-intro.xhtml#guide-repositories)
|
||||
for more details.
|
||||
|
||||
Consider this list a work in progress and help me to make it longer.
|
||||
Consider this list a work in progress and help me to make it longer if you like.
|
||||
|
||||
## pkg-config
|
||||
|
||||
`EnTT` also supports `pkg-config` (for some definition of _supports_ at least).
|
||||
A suitable file called `entt.pc` is generated and installed in a proper
|
||||
directory when running `CMake`.<br/>
|
||||
This should also make it easier to use with tools such as `Meson` or similar.
|
||||
|
||||
# Documentation
|
||||
|
||||
The documentation is based on [doxygen](http://www.doxygen.nl/). To build it:
|
||||
|
||||
$ cd build
|
||||
$ cmake .. -DENTT_BUILD_DOCS=ON
|
||||
$ make
|
||||
|
||||
The API reference will be created in HTML format within the directory
|
||||
`build/docs/html`. To navigate it with your favorite browser:
|
||||
|
||||
$ cd build
|
||||
$ your_favorite_browser docs/html/index.html
|
||||
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
The same version is also available [online](https://skypjack.github.io/entt/)
|
||||
for the latest release, that is the last stable tag. If you are looking for
|
||||
something more pleasing to the eye, consider reading the nice-looking version
|
||||
available on [docsforge](https://entt.docsforge.com/): same documentation, much
|
||||
more pleasant to read.<br/>
|
||||
Moreover, there exists a [wiki](https://github.com/skypjack/entt/wiki) dedicated
|
||||
to the project where users can find all related documentation pages.
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
|
||||
# Tests
|
||||
|
||||
To compile and run the tests, `EnTT` requires *googletest*.<br/>
|
||||
`cmake` will download and compile the library before compiling anything else.
|
||||
In order to build the tests, set the `CMake` option `ENTT_BUILD_TESTING` to
|
||||
`ON`.
|
||||
|
||||
To build the most basic set of tests:
|
||||
|
||||
* `$ cd build`
|
||||
* `$ cmake -DENTT_BUILD_TESTING=ON ..`
|
||||
* `$ make`
|
||||
* `$ make test`
|
||||
|
||||
Note that benchmarks are not part of this set.
|
||||
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
@@ -368,18 +378,14 @@ open an issue or a PR and I'll be glad to add them to the list.
|
||||
|
||||
# Contributors
|
||||
|
||||
`EnTT` was written initially as a faster alternative to other well known and
|
||||
open source entity-component systems. Nowadays this library is moving its first
|
||||
steps. Much more will come in the future and hopefully I'm going to work on it
|
||||
for a long time.<br/>
|
||||
Requests for features, PR, suggestions ad feedback are highly appreciated.
|
||||
Requests for features, PRs, suggestions ad feedback are highly appreciated.
|
||||
|
||||
If you find you can help me and want to contribute to the project with your
|
||||
experience or you do want to get part of the project for some other reasons,
|
||||
feel free to contact me directly (you can find the mail in the
|
||||
If you find you can help and want to contribute to the project with your
|
||||
experience or you do want to get part of the project for some other reason, feel
|
||||
free to contact me directly (you can find the mail in the
|
||||
[profile](https://github.com/skypjack)).<br/>
|
||||
I can't promise that each and every contribution will be accepted, but I can
|
||||
assure that I'll do my best to take them all seriously.
|
||||
assure that I'll do my best to take them all as soon as possible.
|
||||
|
||||
If you decide to participate, please see the guidelines for
|
||||
[contributing](CONTRIBUTING.md) before to create issues or pull
|
||||
@@ -402,16 +408,3 @@ Documentation released under
|
||||
[CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).<br/>
|
||||
All logos released under
|
||||
[CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/).
|
||||
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
# Support
|
||||
|
||||
If you want to support this project, you can
|
||||
[offer me](https://github.com/users/skypjack/sponsorship) an espresso.<br/>
|
||||
If you find that it's not enough, feel free to
|
||||
[help me](https://www.paypal.me/skypjack) the way you prefer.
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
|
||||
54
TODO
54
TODO
@@ -1,40 +1,36 @@
|
||||
* long term feature: shared_ptr less locator and resource cache
|
||||
* custom allocators and EnTT allocator-aware in general (long term feature, I don't actually need it at the moment) - see #22
|
||||
* debugging tools (#60): the issue online already contains interesting tips on this, look at it
|
||||
* work stealing job system (see #100) + mt scheduler based on const awareness for types
|
||||
* allow to replace std:: with custom implementations
|
||||
* add examples (and credits) from @alanjfs :)
|
||||
* static reflection, hint: template<> meta_type_t<Type>: meta_descriptor<name, func..., props..., etc...> (see #342)
|
||||
* update documentation for meta, it contains less than half of the actual feature
|
||||
|
||||
* custom pools example:
|
||||
- lockless fully concurrent ro/rw pool with free lists
|
||||
- multi instance
|
||||
- tables
|
||||
- enable/disable component
|
||||
- spatial query
|
||||
- runtime types pool
|
||||
- off-line/off-memory/remote
|
||||
- ...
|
||||
* custom pools example (multi instance, tables, enable/disable, and so on...)
|
||||
|
||||
WIP:
|
||||
* HP: paginate pools
|
||||
* HP: headless (sparse set only) view
|
||||
* HP: pass the registry to pools, basic poly storage should have only component member
|
||||
* HP: make view pack work also with groups, make packs input iterator only, add view adapter for external sources
|
||||
* HP: write documentation for custom storages and views!!
|
||||
* HP: any/poly: configurable sbo size, compile-time policies like sbo-required.
|
||||
* HP: registry: use a poly object for pools, no more pool_data type.
|
||||
* HP: make runtime views use opaque storage and therefore return also elements.
|
||||
* HP: poly: support for data members
|
||||
* remove view/storage dispatcher, add support to relax policy constraints on user request (eg view.use<T>())
|
||||
* improve perf for sparse_set/storage::insert/emplace/destroy/remove/...
|
||||
* custom allocators all over
|
||||
|
||||
WIP:
|
||||
* make value_type available from meta container types, otherwise we have to default construct a container to get it
|
||||
* make it possible to register externally managed pools with the registry (allow for system centric mode)
|
||||
* registry: switch to the udata/mixin model and get rid of poly storage, use pointer to sparse set only for pools, discard pool_data type.
|
||||
* it's now possible to have 0 as null entity/version, so we can finally switch to it
|
||||
* make pools available (registry/view/group), review operator| for views
|
||||
* page size: add per-pool size, allow for 0 sizes (old fully packed array)
|
||||
* compressed pair to exploit ebo in sparse set and the others
|
||||
* isolate view iterator, unwrap iterators in registry ::remove/::erase/::destroy to use the faster solution for non-view iterators
|
||||
* remove view each<T>(F), each<T>(), make view::use return a view and remove the mutable data member
|
||||
* resource, forward the id to the loader from the cache and if constexpr the call to load, update doc and describe customization points
|
||||
* make it possible to create views of the type `view<T, T>`, add get by index and such, allow to register custom pools by name with the registry
|
||||
* add user data to type_info
|
||||
* any_vector for context variables
|
||||
* make const registry::view thread safe, switch to a view<T...>{registry} model (long term goal)
|
||||
* weak reference wrapper example with custom storage
|
||||
* headless (sparse set only) view
|
||||
* write documentation for custom storages and views!!
|
||||
* make runtime views use opaque storage and therefore return also elements.
|
||||
* add exclude-only views to combine with packs
|
||||
* entity-aware observer, add observer functions aside observer class
|
||||
* deprecate non-owning groups in favor of owning views and view packs, introduce lazy owning views
|
||||
* view pack: plain function as an alias for operator|, reverse iterators, rbegin and rend
|
||||
* pagination doesn't work nicely across boundaries probably, give it a look. RO operations are fine, adding components maybe not.
|
||||
* add observer functions aside observer class
|
||||
* snapshot: support for range-based archives
|
||||
* update snapshot documentation to describe alternatives
|
||||
* page size 0 -> page less mode
|
||||
* add example: 64 bit ids with 32 bits reserved for users' purposes
|
||||
* add meta dynamic cast (search base for T in parent, we have the meta type already)
|
||||
* make meta base/conv node work with storage/any and deprecate/remove meta_base, meta_conv, ...
|
||||
|
||||
8
cmake/in/entt.pc.in
Normal file
8
cmake/in/entt.pc.in
Normal file
@@ -0,0 +1,8 @@
|
||||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
|
||||
|
||||
Name: EnTT
|
||||
Description: Gaming meets modern C++
|
||||
Url: https://github.com/skypjack/entt
|
||||
Version: @ENTT_VERSION@
|
||||
Cflags: -I${includedir}
|
||||
@@ -24,8 +24,9 @@ add_custom_target(
|
||||
md/links.md
|
||||
md/locator.md
|
||||
md/meta.md
|
||||
md/poly.md
|
||||
md/process.md
|
||||
md/references.md
|
||||
md/reference.md
|
||||
md/resource.md
|
||||
md/signal.md
|
||||
md/unreal.md
|
||||
|
||||
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,13 +60,22 @@ the library.<br/>
|
||||
By default, its type is `std::uint32_t`. However, users can define a different
|
||||
default type if necessary.
|
||||
|
||||
## ENTT_PAGE_SIZE
|
||||
## ENTT_SPARSE_PAGE
|
||||
|
||||
As is known, the ECS module of `EnTT` is based on _sparse sets_. What is less
|
||||
known perhaps is that these are paged to reduce memory consumption in some
|
||||
corner cases.<br/>
|
||||
The default size of a page is 32kB but users can adjust it if appropriate. In
|
||||
all case, the chosen value **must** be a power of 2.
|
||||
It's known that the ECS module of `EnTT` is based on _sparse sets_. What is less
|
||||
known perhaps is that the sparse arrays are paged to reduce memory usage.<br/>
|
||||
Default size of pages (that is, the number of elements they contain) is 4096 but
|
||||
users can adjust it if appropriate. In all case, the chosen value **must** be a
|
||||
power of 2.
|
||||
|
||||
## ENTT_PACKED_PAGE
|
||||
|
||||
Similar to sparse arrays, packed arrays of components are paginated as well. In
|
||||
However, int this case the aim isn't to reduce memory usage but to have pointer
|
||||
stability upon component creation.<br/>
|
||||
Default size of pages (that is, the number of elements they contain) is 1024 but
|
||||
users can adjust it if appropriate. In all case, the chosen value **must** be a
|
||||
power of 2.
|
||||
|
||||
## ENTT_ASSERT
|
||||
|
||||
|
||||
101
docs/md/core.md
101
docs/md/core.md
@@ -14,6 +14,8 @@
|
||||
* [Conflicts](#conflicts)
|
||||
* [Monostate](#monostate)
|
||||
* [Any as in any type](#any-as-in-any-type)
|
||||
* [Small buffer optimization](#small-buffer-optimization)
|
||||
* [Alignment requirement](#alignment-requirement)
|
||||
* [Type support](#type-support)
|
||||
* [Type info](#type-info)
|
||||
* [Almost unique identifiers](#almost-unique-identifiers)
|
||||
@@ -24,6 +26,7 @@
|
||||
* [Member class type](#member-class-type)
|
||||
* [Integral constant](#integral-constant)
|
||||
* [Tag](#tag)
|
||||
* [Type list and value list](#type-list-and-value-list)
|
||||
* [Utilities](#utilities)
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
@@ -244,8 +247,16 @@ entt::any any{0};
|
||||
entt::any in_place{std::in_place_type<int>, 42};
|
||||
```
|
||||
|
||||
The `any` class takes the burden of destroying the contained element when
|
||||
required, regardless of the storage strategy used for the specific object.<br/>
|
||||
Alternatively, the `make_any` function serves the same purpose but requires to
|
||||
always be explicit about the type:
|
||||
|
||||
```cpp
|
||||
entt::any any = entt::make_any<int>(42);
|
||||
```
|
||||
|
||||
In both cases, the `any` class takes the burden of destroying the contained
|
||||
element when required, regardless of the storage strategy used for the specific
|
||||
object.<br/>
|
||||
Furthermore, an instance of `any` is not tied to an actual type. Therefore, the
|
||||
wrapper will be reconfigured by assigning it an object of a different type than
|
||||
the one contained, so as to be able to handle the new instance.<br/>
|
||||
@@ -269,26 +280,23 @@ an opaque container for const and non-const references:
|
||||
```cpp
|
||||
int value = 42;
|
||||
|
||||
// reference construction
|
||||
entt::any any{std::ref(value)};
|
||||
entt::any cany{std::cref(value)};
|
||||
entt::any any{std::in_place_type<int &>(value)};
|
||||
entt::any cany = entt::make_any<const int &>(value);
|
||||
entt::any fwd = entt::forward_as_any(value);
|
||||
|
||||
// alias construction
|
||||
int value = 42;
|
||||
entt::any in_place{std::in_place_type<int &>, &value};
|
||||
any.emplace<const int &>(value);
|
||||
```
|
||||
|
||||
In other words, whenever `any` intercepts a `reference_wrapper` or is explicitly
|
||||
told that users want to construct an alias, it acts as a pointer to the original
|
||||
instance rather than making a copy of it or moving it internally. The contained
|
||||
object is never destroyed and users must ensure that its lifetime exceeds that
|
||||
of the container.<br/>
|
||||
In other words, whenever `any` is explicitly told to construct an _alias_, it
|
||||
acts as a pointer to the original instance rather than making a copy of it or
|
||||
moving it internally. The contained object is never destroyed and users must
|
||||
ensure that its lifetime exceeds that of the container.<br/>
|
||||
Similarly, it's possible to create non-owning copies of `any` from an existing
|
||||
object:
|
||||
|
||||
```cpp
|
||||
// aliasing constructor
|
||||
entt::any ref = as_ref(other);
|
||||
entt::any ref = other.as_ref();
|
||||
```
|
||||
|
||||
In this case, it doesn't matter if the original container actually holds an
|
||||
@@ -311,6 +319,48 @@ The only difference is that, in the case of `EnTT`, these won't raise exceptions
|
||||
but will only trigger an assert in debug mode, otherwise resulting in undefined
|
||||
behavior in case of misuse in release mode.
|
||||
|
||||
## Small buffer optimization
|
||||
|
||||
The `any` class uses a technique called _small buffer optimization_ to reduce
|
||||
the number of allocations where possible.<br/>
|
||||
The default reserved size for an instance of `any` is `sizeof(double[2])`.
|
||||
However, this is also configurable if needed. In fact, `any` is defined as an
|
||||
alias for `basic_any<Len>`, where `Len` is the size above.<br/>
|
||||
Users can easily set a custom size or define their own aliases:
|
||||
|
||||
```cpp
|
||||
using my_any = entt::basic_any<sizeof(double[4])>;
|
||||
```
|
||||
|
||||
This feature, in addition to allowing the choice of a size that best suits the
|
||||
needs of an application, also offers the possibility of forcing dynamic creation
|
||||
of objects during construction.<br/>
|
||||
In other terms, if the size is 0, `any` avoids the use of any optimization and
|
||||
always dynamically allocates objects (except for aliasing cases).
|
||||
|
||||
Note that the size of the internal storage as well as the alignment requirements
|
||||
are directly part of the type and therefore contribute to define different types
|
||||
that won't be able to interoperate with each other.
|
||||
|
||||
## Alignment requirement
|
||||
|
||||
The alignment requirement is optional and by default the most stringent (the
|
||||
largest) for any object whose size is at most equal to the one provided.<br/>
|
||||
The `basic_any` class template inspects the alignment requirements in each case,
|
||||
even when not provided and may decide not to use the small buffer optimization
|
||||
in order to meet them.
|
||||
|
||||
The alignment requirement is provided as an optional second parameter following
|
||||
the desired size for the internal storage:
|
||||
|
||||
```cpp
|
||||
using my_any = entt::basic_any<sizeof(double[4]), alignof(double[4])>;
|
||||
```
|
||||
|
||||
Note that the alignment requirements as well as the size of the internal storage
|
||||
are directly part of the type and therefore contribute to define different types
|
||||
that won't be able to interoperate with each other.
|
||||
|
||||
# Type support
|
||||
|
||||
`EnTT` provides some basic information about types of all kinds.<br/>
|
||||
@@ -571,6 +621,29 @@ registry.emplace<entt::tag<"enemy"_hs>>(entity);
|
||||
However, this isn't the only permitted use. Literally any value convertible to
|
||||
`id_type` is a good candidate, such as the named constants of an unscoped enum.
|
||||
|
||||
### Type list and value list
|
||||
|
||||
There is no respectable library where the much desired _type list_ can be
|
||||
missing.<br/>
|
||||
`EnTT` is no exception and provides (making extensive use of it internally) the
|
||||
`type_list` type, in addition to its `value_list` counterpart dedicated to
|
||||
non-type template parameters.
|
||||
|
||||
Here is a (possibly incomplete) list of the functionalities that come with a
|
||||
type list:
|
||||
|
||||
* `type_list_element[_t]` to get the N-th element of a type list.
|
||||
* `type_list_cast[_t]` and a handy `operator+` to concatenate type lists.
|
||||
* `type_list_unique[_t]` to remove duplicate types from a type list.
|
||||
* `type_list_contains[_v]` to know if a type list contains a given type.
|
||||
* `type_list_diff[_t]` to remove types from type lists.
|
||||
|
||||
I'm also pretty sure that more and more utilities will be added over time as
|
||||
needs become apparent.<br/>
|
||||
Many of these functionalities also exist in their version dedicated to value
|
||||
lists. We therefore have `value_list_element[_v]` as well as
|
||||
`value_list_cat[_t]`and so on.
|
||||
|
||||
# Utilities
|
||||
|
||||
It's not possible to escape the temptation to add utilities of some kind to a
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [Design decisions](#design-decisions)
|
||||
* [A bitset-free entity-component system](#a-bitset-free-entity-component-system)
|
||||
* [Type-less and bitset-free](#type-less-and-bitset-free)
|
||||
* [Build your own](#build-your-own)
|
||||
* [Pay per use](#pay-per-use)
|
||||
* [All or nothing](#all-or-nothing)
|
||||
* [Stateless systems](#stateless-systems)
|
||||
* [Vademecum](#vademecum)
|
||||
* [Pools](#pools)
|
||||
* [The Registry, the Entity and the Component](#the-registry-the-entity-and-the-component)
|
||||
@@ -19,12 +19,18 @@
|
||||
* [Sorting: is it possible?](#sorting-is-it-possible)
|
||||
* [Helpers](#helpers)
|
||||
* [Null entity](#null-entity)
|
||||
* [Tombstone](#tombstone)
|
||||
* [To entity](#to-entity)
|
||||
* [Dependencies](#dependencies)
|
||||
* [Invoke](#invoke)
|
||||
* [Handle](#handle)
|
||||
* [Context variables](#context-variables)
|
||||
* [Organizer](#organizer)
|
||||
* [Context variables](#context-variables)
|
||||
* [Aliased properties](#aliased-properties)
|
||||
* [In-place delete](#in-place-delete)
|
||||
* [Pointer stability](#pointer-stability)
|
||||
* [Hierarchies and the like](#hierarchies-and-the-like)
|
||||
* [Making the most of range-destroy](#making-the-most-of-range-destroy)
|
||||
* [Meet the runtime](#meet-the-runtime)
|
||||
* [Snapshot: complete vs continuous](#snapshot-complete-vs-continuous)
|
||||
* [Snapshot loader](#snapshot-loader)
|
||||
@@ -40,14 +46,15 @@
|
||||
* [Partial-owning groups](#partial-owning-groups)
|
||||
* [Non-owning groups](#non-owning-groups)
|
||||
* [Nested groups](#nested-groups)
|
||||
* [Invalid views and groups](#invalid-views-and-groups)
|
||||
* [Types: const, non-const and all in between](#types-const-non-const-and-all-in-between)
|
||||
* [Give me everything](#give-me-everything)
|
||||
* [Stable storage](#stable-storage)
|
||||
* [What is allowed and what is not](#what-is-allowed-and-what-is-not)
|
||||
* [More performance, more constraints](#more-performance-more-constraints)
|
||||
* [Empty type optimization](#empty-type-optimization)
|
||||
* [Multithreading](#multithreading)
|
||||
* [Iterators](#iterators)
|
||||
* [Const registry](#const-registry)
|
||||
* [Beyond this document](#beyond-this-document)
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
@@ -62,7 +69,7 @@ used mostly in game development.
|
||||
|
||||
# Design decisions
|
||||
|
||||
## A bitset-free entity-component system
|
||||
## Type-less and bitset-free
|
||||
|
||||
`EnTT` offers a _bitset-free_ entity-component system that doesn't require users
|
||||
to specify the set of components neither at compile-time nor at runtime.<br/>
|
||||
@@ -81,6 +88,23 @@ entt::registry<comp_0, comp_1, ..., comp_n> registry;
|
||||
Furthermore, it isn't necessary to announce the existence of a component type.
|
||||
When the time comes, just use it and that's all.
|
||||
|
||||
## Build your own
|
||||
|
||||
`EnTT` is designed as a container that can be used at any time just as a vector
|
||||
or any other tool would be used. It doesn't attempt in any way to take over on
|
||||
the user code base, nor to control its main loop or process scheduling.<br/>
|
||||
Unlike other more or less known models, it makes use of independent pools. This
|
||||
has some advantages and disadvantages. The main purpose is to provide a fully
|
||||
customizable tool, where users have the freedom to define pools and opaque
|
||||
proxies for types with specific requirements.
|
||||
|
||||
The library provides a default implementation for many things and a mixin model
|
||||
that allows users to completely replace or even just enrich the pool dedicated
|
||||
to one or more components.<br/>
|
||||
The built-in signal support is an example of that: defined as a mixin, it's
|
||||
easily disabled if not needed. Similarly, poly storage is another example of how
|
||||
everything is customizable down to the smallest detail.
|
||||
|
||||
## Pay per use
|
||||
|
||||
`EnTT` is entirely designed around the principle that users have to pay only for
|
||||
@@ -103,26 +127,17 @@ many others besides me.
|
||||
|
||||
## All or nothing
|
||||
|
||||
`EnTT` is such that at every moment a pair `(T *, size)` is available to
|
||||
directly access all the instances of a given component type `T`.<br/>
|
||||
This was a guideline and a design decision that influenced many choices, for
|
||||
better and for worse. I cannot say whether it will be useful or not to the
|
||||
reader, but it's worth to mention it since it's one of the corner stones of
|
||||
this library.
|
||||
`EnTT` is such that a `T**` pointer (or whatever a custom pool returns) is
|
||||
always available to directly access all the instances of a given component type
|
||||
`T`.<br/>
|
||||
I cannot say whether it will be useful or not to the reader, but it's worth to
|
||||
mention it since it's one of the corner stones of this library.
|
||||
|
||||
Many of the tools described below give the possibility to get this information
|
||||
and have been designed around this need.<br/>
|
||||
The rest is experimentation and the desire to invent something new, hoping to
|
||||
have succeeded.
|
||||
|
||||
## Stateless systems
|
||||
|
||||
`EnTT` is designed so that it can work with _stateless systems_. In other words,
|
||||
all systems can be free functions and there is no need to define them as classes
|
||||
(although nothing prevents users from doing so).<br/>
|
||||
This is possible because the main class with which the users will work provides
|
||||
all what is needed to act as the sole _source of truth_ of an application.
|
||||
|
||||
# Vademecum
|
||||
|
||||
The registry to store, the views and the groups to iterate. That's all.
|
||||
@@ -170,11 +185,8 @@ the alias `entt::registry` for `entt::basic_registry<entt::entity>`.
|
||||
|
||||
Entities are represented by _entity identifiers_. An entity identifier carries
|
||||
information about the entity itself and its version.<br/>
|
||||
User defined identifiers can be introduced by means of enum classes and custom
|
||||
types for which a specialization of `entt_traits` exists. For this purpose,
|
||||
`entt_traits` is also defined as a _sfinae-friendly_ class template. In theory,
|
||||
integral types can also be used as entity identifiers, even though this may
|
||||
break in future and isn't recommended in general.
|
||||
User defined identifiers can be introduced through enum classes and class types
|
||||
that define an `entity_type` member of type `std::uint32_t` or `std::uint64_t`.
|
||||
|
||||
A registry is used both to construct and to destroy entities:
|
||||
|
||||
@@ -196,10 +208,22 @@ auto view = registry.view<a_component, another_component>();
|
||||
registry.destroy(view.begin(), view.end());
|
||||
```
|
||||
|
||||
When an entity is destroyed, the registry can freely reuse it internally with a
|
||||
slightly different identifier. In particular, the version of an entity is
|
||||
increased after destruction (unless the overload that forces a version is used
|
||||
instead of the default one).<br/>
|
||||
In addition to offering an overload to force the version upon destruction. Note
|
||||
that this function removes all components from an entity before releasing its
|
||||
identifier. There exists also a _lighter_ alternative that only releases the
|
||||
elements without poking in any pool, for use with orphaned entities:
|
||||
|
||||
```cpp
|
||||
// releases an orphaned identifier
|
||||
registry.release(entity);
|
||||
```
|
||||
|
||||
As with the `destroy` function, also in this case entity ranges are supported
|
||||
and it's possible to force the version during release.
|
||||
|
||||
In both cases, when an identifier is released, the registry can freely reuse it
|
||||
internally. In particular, the version of an entity is increased (unless the
|
||||
overload that forces a version is used instead of the default one).<br/>
|
||||
Users can probe an identifier to know the information it carries:
|
||||
|
||||
```cpp
|
||||
@@ -277,42 +301,42 @@ registry.emplace_or_replace<position>(entity, 0., 0.);
|
||||
This is a slightly faster alternative for the following snippet:
|
||||
|
||||
```cpp
|
||||
if(registry.has<velocity>(entity)) {
|
||||
if(registry.all_of<velocity>(entity)) {
|
||||
registry.replace<velocity>(entity, 0., 0.);
|
||||
} else {
|
||||
registry.emplace<velocity>(entity, 0., 0.);
|
||||
}
|
||||
```
|
||||
|
||||
The `has` and `any` member functions may also be useful if in doubt about
|
||||
The `all_of` and `any_of` member functions may also be useful if in doubt about
|
||||
whether or not an entity has all the components in a set or any of them:
|
||||
|
||||
```cpp
|
||||
// true if entity has all the given components
|
||||
bool all = registry.has<position, velocity>(entity);
|
||||
bool all = registry.all_of<position, velocity>(entity);
|
||||
|
||||
// true if entity has at least one of the given components
|
||||
bool any = registry.any<position, velocity>(entity);
|
||||
bool any = registry.any_of<position, velocity>(entity);
|
||||
```
|
||||
|
||||
If the goal is to delete a component from an entity that owns it, the `remove`
|
||||
If the goal is to delete a component from an entity that owns it, the `erase`
|
||||
member function template is the way to go:
|
||||
|
||||
```cpp
|
||||
registry.erase<position>(entity);
|
||||
```
|
||||
|
||||
When in doubt whether the entity owns the component, use the `remove` member
|
||||
function instead. It behaves similarly to `erase` but it erases the component
|
||||
if and only if it exists, otherwise it returns safely to the caller:
|
||||
|
||||
```cpp
|
||||
registry.remove<position>(entity);
|
||||
```
|
||||
|
||||
When in doubt whether the entity owns the component, use the `remove_if_exists`
|
||||
member function instead. It behaves similarly to `remove` but it discards the
|
||||
component if and only if it exists, otherwise it returns safely to the caller:
|
||||
|
||||
```cpp
|
||||
registry.remove_if_exists<position>(entity);
|
||||
```
|
||||
|
||||
The `clear` member function works similarly and can be used to either:
|
||||
|
||||
* Remove all instances of the given components from the entities that own them:
|
||||
* Erases all instances of the given components from the entities that own them:
|
||||
|
||||
```cpp
|
||||
registry.clear<position>();
|
||||
@@ -615,11 +639,54 @@ const auto entity = registry.create();
|
||||
const bool null = (entity == entt::null);
|
||||
```
|
||||
|
||||
As for its integral form, the null entity only affects the entity part of an
|
||||
identifier and is instead completely transparent to its version.
|
||||
|
||||
Be aware that `entt::null` and entity 0 aren't the same thing. Likewise, a zero
|
||||
initialized entity isn't the same as `entt::null`. Therefore, although
|
||||
`entt::entity{}` is in some sense an alias for entity 0, none of them can be
|
||||
used to create a null entity.
|
||||
|
||||
### Tombstone
|
||||
|
||||
In addition to the null entity, `EnTT` also models the concept of _tombstone_
|
||||
with the `entt::tombstone` variable.<br/>
|
||||
Once created, the integral form of the two values is the same, although they
|
||||
affect different parts of an identifier. In fact, the tombstone uses only the
|
||||
version part and is completely transparent to the entity part.
|
||||
|
||||
Also in this case, the following expression always returns false:
|
||||
|
||||
```cpp
|
||||
registry.valid(entt::tombstone);
|
||||
```
|
||||
|
||||
Moreover, users cannot set set the tombstone version when releasing an entity:
|
||||
|
||||
```
|
||||
registry.destroy(entity, entt::tombstone);
|
||||
```
|
||||
|
||||
In this case, a different version number is implicitly generated.<br/>
|
||||
The type of a tombstone is internal and can change at any time. However, there
|
||||
exist implicit conversions from a tombstone to identifiers of any allowed type:
|
||||
|
||||
```cpp
|
||||
entt::entity null = entt::tombstone;
|
||||
```
|
||||
|
||||
Similarly, the tombstone can be compared to any other identifier:
|
||||
|
||||
```cpp
|
||||
const auto entity = registry.create();
|
||||
const bool tombstone = (entity == entt::tombstone);
|
||||
```
|
||||
|
||||
Be aware that `entt::tombstone` and entity 0 aren't the same thing. Likewise, a
|
||||
zero initialized entity isn't the same as `entt::tombstone`. Therefore, although
|
||||
`entt::entity{}` is in some sense an alias for entity 0, none of them can be
|
||||
used to create tombstones.
|
||||
|
||||
### To entity
|
||||
|
||||
Sometimes it's useful to get the entity from a component instance.<br/>
|
||||
@@ -630,9 +697,7 @@ instance of a component and returns the entity associated with the latter:
|
||||
const auto entity = entt::to_entity(registry, position);
|
||||
```
|
||||
|
||||
This utility doesn't perform any check on the validity of the component.
|
||||
Therefore, trying to take the entity of an invalid element or of an instance
|
||||
that isn't associated with the given registry can result in undefined behavior.
|
||||
A null entity is returned in case the component doesn't belong to the registry.
|
||||
|
||||
### Dependencies
|
||||
|
||||
@@ -713,39 +778,6 @@ This class is intended to simplify function signatures. In case of functions
|
||||
that take a registry and an entity and do most of their work on that entity,
|
||||
users might want to consider using handles, either const or non-const.
|
||||
|
||||
### Context variables
|
||||
|
||||
It is often convenient to assign context variables to a registry, so as to make
|
||||
it the only _source of truth_ of an application.<br/>
|
||||
This is possible by means of a member function named `set` to use to create a
|
||||
context variable from a given type. Either `ctx` or `try_ctx` can be used to
|
||||
retrieve the newly created instance, while `unset` is meant to clear the
|
||||
variable if needed.
|
||||
|
||||
Example of use:
|
||||
|
||||
```cpp
|
||||
// creates a new context variable initialized with the given values
|
||||
registry.set<my_type>(42, 'c');
|
||||
|
||||
// gets the context variable
|
||||
const auto &var = registry.ctx<my_type>();
|
||||
|
||||
// if in doubts, probe the registry to avoid assertions in case of errors
|
||||
if(auto *ptr = registry.try_ctx<my_type>(); ptr) {
|
||||
// uses the context variable associated with the registry, if any
|
||||
}
|
||||
|
||||
// unsets the context variable
|
||||
registry.unset<my_type>();
|
||||
```
|
||||
|
||||
The type of a context variable must be such that it's default constructible and
|
||||
can be moved. The `set` member function either creates a new instance of the
|
||||
context variable or overwrites an already existing one if any. The `try_ctx`
|
||||
member function returns a pointer to the context variable if it exists,
|
||||
otherwise it returns a null pointer.
|
||||
|
||||
### Organizer
|
||||
|
||||
The `organizer` class template offers minimal support (but sufficient in many
|
||||
@@ -880,6 +912,231 @@ for(auto &&node: graph) {
|
||||
The actual scheduling of the tasks is the responsibility of the user, who can
|
||||
use the preferred tool.
|
||||
|
||||
## Context variables
|
||||
|
||||
It is often convenient to assign context variables to a registry, so as to make
|
||||
it the only _source of truth_ of an application.<br/>
|
||||
This is possible by means of a member function named `set` to use to create a
|
||||
context variable from a given type. Either `ctx` or `try_ctx` can be used to
|
||||
retrieve the newly created instance, while `unset` is meant to clear the
|
||||
variable if needed:
|
||||
|
||||
```cpp
|
||||
// creates a new context variable initialized with the given values
|
||||
registry.set<my_type>(42, 'c');
|
||||
|
||||
// gets the context variable as a non-const reference from a non-const registry
|
||||
auto &var = registry.ctx<my_type>();
|
||||
|
||||
// gets the context variable as a const reference from either a const or a non-const registry
|
||||
const auto &cvar = registry.ctx<const my_type>();
|
||||
|
||||
// unsets the context variable
|
||||
registry.unset<my_type>();
|
||||
```
|
||||
|
||||
The type of a context variable must be such that it's default constructible and
|
||||
can be moved. The `set` member function either creates a new instance of the
|
||||
context variable or overwrites an already existing one if any.<br/>
|
||||
The `try_ctx` member function returns a pointer to the context variable if it
|
||||
exists, otherwise it returns a null pointer. As `ctx`, it supports both const
|
||||
and non-const types and requires a const one when used on a const registry:
|
||||
|
||||
```cpp
|
||||
if(auto *cptr = registry.try_ctx<const my_type>(); cptr) {
|
||||
// uses the context variable associated with the registry, if any
|
||||
}
|
||||
```
|
||||
|
||||
### Aliased properties
|
||||
|
||||
Context variables can also be used to create aliases for existing variables that
|
||||
aren't directly managed by the registry. In this case, it's also possible to
|
||||
make them read-only.<br/>
|
||||
To do that, the type used upon construction must be a reference type and an
|
||||
lvalue is necessarily provided as an argument:
|
||||
|
||||
```cpp
|
||||
time clock;
|
||||
registry.set<my_type &>(clock);
|
||||
```
|
||||
|
||||
Read-only aliased properties are created using const types instead:
|
||||
|
||||
```cpp
|
||||
registry.set<const my_type &>(clock);
|
||||
```
|
||||
|
||||
From the point of view of the user, there are no differences between a variable
|
||||
that is managed by the registry and an aliased property. However, read-only
|
||||
variables aren't accesible as non-const references:
|
||||
|
||||
```cpp
|
||||
// read-only variables only support const access
|
||||
const my_type *ptr = registry.try_ctx<const my_type>();
|
||||
const my_type &var = registry.ctx<const my_type>();
|
||||
```
|
||||
|
||||
Aliased properties can be unset and are overwritten when `set` is invoked, as it
|
||||
happens with standard variables.
|
||||
|
||||
## In-place delete
|
||||
|
||||
By default, `EnTT` keeps all pools compact when a component is removed. This is
|
||||
done through a swap-and-pop between the removed item and the one occupying the
|
||||
last position in the storage.<br/>
|
||||
Unfortunately, this also inevitably leads the components to change position
|
||||
within the storage, making direct access almost impossible (be it via pointer or
|
||||
index).
|
||||
|
||||
However, the underlying model with its independent pools helps introduce storage
|
||||
with different deletion policies, so that users can best choose type by
|
||||
type.<br/>
|
||||
In particular, the library offers out of the box support for in-place deletion,
|
||||
thus offering storage with completely stable pointers. To do so, it's required
|
||||
to specialize the `component_traits` class.<br/>
|
||||
The definition common to all components is the following:
|
||||
|
||||
```cpp
|
||||
struct basic_component_traits {
|
||||
using in_place_delete = std::false_type;
|
||||
using ignore_if_empty = ENTT_IGNORE_IF_EMPTY;
|
||||
};
|
||||
```
|
||||
|
||||
Where `in_place_delete` instructs the library on the deletion policy for a given
|
||||
type while `ignore_if_empty` selectively disables empty type optimization.<br/>
|
||||
The `component_traits` class template is _sfinae-friendly_, it supports single-
|
||||
and multi-type specializations as well as feature-based ones:
|
||||
|
||||
```cpp
|
||||
template<>
|
||||
struct entt::component_traits<position>: basic_component_traits {
|
||||
using in_place_delete = std::true_type;
|
||||
};
|
||||
```
|
||||
|
||||
This will ensure in-place deletion for the `position` component without further
|
||||
user intervention.<br/>
|
||||
Pools, views and groups will adapt accordingly when they detect a storage with a
|
||||
different deletion policy than the default. No specific action is required from
|
||||
the user once in-place deletion is enabled.
|
||||
|
||||
### Pointer stability
|
||||
|
||||
The ability to achieve pointer stability for one, several or all components is a
|
||||
direct consequence of the design of `EnTT` and of its default storage.<br/>
|
||||
In fact, although it contains what is commonly referred to as a _packed array_,
|
||||
the default storage is paged and doesn't suffer from invalidation of references
|
||||
when it runs out of space and has to reallocate.<br/>
|
||||
However, this isn't enough to ensure pointer stability in case of deletion. For
|
||||
this reason, a _stable_ deletion method is also offered. This one is such that
|
||||
the position of the elements is preserved by creating tombstones upon deletion
|
||||
rather than trying to fill the holes that are created.
|
||||
|
||||
For performance reasons, `EnTT` will also favor storage compaction in all cases,
|
||||
although often accessing a component occurs mostly randomly or traversing pools
|
||||
in a non-linear order on the user side (as in the case of a hierarchy).<br/>
|
||||
In other words, pointer stability is not automatic but is enabled on request. To
|
||||
have it at the project level and for all components, it's required to partially
|
||||
specialize the `component_traits` class for all possible types:
|
||||
|
||||
```cpp
|
||||
template<typename Type>
|
||||
struct entt::component_traits<Type>: basic_component_traits {
|
||||
using in_place_delete = std::true_type;
|
||||
};
|
||||
```
|
||||
|
||||
Because of how C++ works, this specialization will obviously have to be visible
|
||||
every time operations are performed on a storage.
|
||||
|
||||
### Hierarchies and the like
|
||||
|
||||
`EnTT` doesn't attempt in any way to offer built-in methods with hidden or
|
||||
unclear costs to facilitate the creation of hierarchies.<br/>
|
||||
There are various solutions to the problem, such as using the following class:
|
||||
|
||||
```cpp
|
||||
struct relationship {
|
||||
std::size_t children{};
|
||||
entt::entity first{entt::null};
|
||||
entt::entity prev{entt::null};
|
||||
entt::entity next{entt::null};
|
||||
entt::entity parent{entt::null};
|
||||
// ... other data members ...
|
||||
};
|
||||
```
|
||||
|
||||
However, it should be pointed out that the possibility of having stable pointers
|
||||
for one, many or all types solves the problem of hierarchies at the root in many
|
||||
cases.<br/>
|
||||
In fact, if a certain type of component is visited mainly in random order or
|
||||
according to hierarchical relationships, using direct pointers has many
|
||||
advantages:
|
||||
|
||||
```cpp
|
||||
struct transform {
|
||||
transform *parent;
|
||||
// ... other data members ...
|
||||
};
|
||||
|
||||
template<>
|
||||
struct entt::component_traits<transform>: basic_component_traits {
|
||||
using in_place_delete = std::true_type;
|
||||
};
|
||||
```
|
||||
|
||||
Furthermore, it's quite common for a group of elements to be created close in
|
||||
time and therefore fallback into adjacent positions, thus favoring locality even
|
||||
on random accesses. Locality that won't be sacrificed over time given the
|
||||
stability of storage positions, with undoubted performance advantages.<br/>
|
||||
Of course, the cost moves to linear iterations, where views and groups will have
|
||||
to identify (and discard) all tombstones. However, once considered the benefits,
|
||||
from performance to ease of use, and given the many optimizations that make this
|
||||
cost negligible, this is configured as one of the most convenient solutions and
|
||||
certainly something to take into consideration.
|
||||
|
||||
## Making the most of range-destroy
|
||||
|
||||
The range-destroy functionality offers an improved path under the hood. To
|
||||
understand it, let's try to describe what problem it tries to solve.<br/>
|
||||
This function accepts two iterators that point to the beginning and end of a
|
||||
range of entities. If the iterators are those returned from a view, this pair
|
||||
cannot be passed to the first storage asking to remove all entities and then to
|
||||
all other storage. This is because the range may be empty when passed to the
|
||||
second pool, as not all of those entities still own all the components iterated
|
||||
from the view itself.<br/>
|
||||
As a result, only one component is removed and no entities are destroyed.
|
||||
|
||||
To avoid this, in many cases the registry doesn't pass the range to all pools.
|
||||
Instead, it iterates the range and passes an entity at a time to all pools.<br/>
|
||||
It goes without saying that the latter is slightly slower than the former.
|
||||
|
||||
On the other side, the `destroy` function also uses `is_iterator_type` under the
|
||||
hood to detect _dangerous_ iterators. Whenever possible, it still chooses the
|
||||
fastest path.<br/>
|
||||
This means that performance will improve if, for example, two iterators returned
|
||||
from an `std::vector` are used or, more in general, with all iterators that are
|
||||
not part of `EnTT`.
|
||||
|
||||
Unfortunately, this risks falling into the error described above in some corner
|
||||
cases. In particular, where an iterator is used that is not defined by `EnTT`
|
||||
but which uses one of the latter _within_ it.<br/>
|
||||
It's quite unlikely to happen even in large software. However, the library
|
||||
offers a solution also in this case, so as to allow for custom iterators and
|
||||
better performance at the same time.<br/>
|
||||
In particular, it's necessary to either expose the member type `iterator_type`
|
||||
and declare that an iterator from `EnTT` is used internally or specialize the
|
||||
`is_iterator_type` class to drive the choice of the `destroy` function.<br/>
|
||||
In both cases, the aim is to not choose the optimized route if it can cause
|
||||
problems.
|
||||
|
||||
With a good chance, the last note can be ignored and there will never be a need
|
||||
to do the above even after writing millions of lines of code.<br/>
|
||||
However, it's good to know how to exploit the `destroy` function to get the best
|
||||
out of it.
|
||||
|
||||
## Meet the runtime
|
||||
|
||||
`EnTT` takes full advantage of what the language offers at compile-time.<br/>
|
||||
@@ -911,7 +1168,7 @@ copying an entity will be as easy as:
|
||||
|
||||
```cpp
|
||||
registry.visit(entity, [&](const auto info) {
|
||||
auto storage = registry.storage(info);
|
||||
auto &&storage = registry.storage(info);
|
||||
storage->emplace(registry, other, storage->get(entity));
|
||||
});
|
||||
```
|
||||
@@ -921,8 +1178,7 @@ Similarly, copying entire pools between different registries can look like this:
|
||||
|
||||
```cpp
|
||||
registry.visit([&](const auto info) {
|
||||
auto storage = registry.storage(info);
|
||||
other.storage(info)->insert(other, storage->data(), storage->raw(), storage->size());
|
||||
registry.storage(info)->copy_to(other);
|
||||
});
|
||||
```
|
||||
|
||||
@@ -966,7 +1222,7 @@ to use in which case mostly depends on the goal and there is not a golden rule
|
||||
for that.
|
||||
|
||||
The `entities` member function makes the snapshot serialize all entities (both
|
||||
those still alive and those destroyed) along with their versions.<br/>
|
||||
those still alive and those released) along with their versions.<br/>
|
||||
On the other hand, the `component` member function is a function template the
|
||||
aim of which is to store aside components. The presence of a template parameter
|
||||
list is a consequence of a couple of design choices from the past and in the
|
||||
@@ -1031,11 +1287,11 @@ The `component` member function restores all and only the components specified
|
||||
and assigns them to the right entities. Note that the template parameter list
|
||||
must be exactly the same used during the serialization.
|
||||
|
||||
The `orphans` member function literally destroys those entities that have no
|
||||
The `orphans` member function literally releases those entities that have no
|
||||
components attached. It's usually useless if the snapshot is a full dump of the
|
||||
source. However, in case all the entities are serialized but only few components
|
||||
are saved, it could happen that some of the entities have no components once
|
||||
restored. The best the users can do to deal with them is to destroy those
|
||||
restored. The best the users can do to deal with them is to release those
|
||||
entities and thus update their versions.
|
||||
|
||||
### Continuous loader
|
||||
@@ -1082,7 +1338,7 @@ In case the component contains entities itself (either as data members of type
|
||||
automatically. To do that, it's enough to specify the data members to update as
|
||||
shown in the example.
|
||||
|
||||
The `orphans` member function literally destroys those entities that have no
|
||||
The `orphans` member function literally releases those entities that have no
|
||||
components after a restore. It has exactly the same purpose described in the
|
||||
previous section and works the same way.
|
||||
|
||||
@@ -1210,12 +1466,12 @@ different in the two cases.
|
||||
Single component views are specialized in order to give a boost in terms of
|
||||
performance in all the situations. This kind of views can access the underlying
|
||||
data structures directly and avoid superfluous checks. There is nothing as fast
|
||||
as a single component view. In fact, they walk through a packed array of
|
||||
components and return them one at a time.<br/>
|
||||
Single component views offer a bunch of functionalities to get the number of
|
||||
entities they are going to return and a raw access to the entity list as well as
|
||||
to the component list. It's also possible to ask a view if it contains a given
|
||||
entity.<br/>
|
||||
as a single component view. In fact, they walk through a packed (actually paged)
|
||||
array of components and return them one at a time.<br/>
|
||||
Single component views also offer a bunch of functionalities to get the number
|
||||
of entities they are going to return and a raw access to the entity list as well
|
||||
as to the component list. It's also possible to ask a view if it contains a
|
||||
given entity.<br/>
|
||||
Refer to the inline documentation for all the details.
|
||||
|
||||
Multi component views iterate entities that have at least all the given
|
||||
@@ -1308,14 +1564,11 @@ registry during iterations to get the types iterated by the view itself.
|
||||
|
||||
### View pack
|
||||
|
||||
The view pack allows users to combine multiple views into a single _view-like_
|
||||
iterable object, while also giving them full control over which view should lead
|
||||
the iteration.<br/>
|
||||
This object returns all and only the entities present in all views. Its intended
|
||||
primary use is for custom storage and views, but it can also be very convenient
|
||||
in everyday use.
|
||||
Views are combined with each other to create new and more specific types.<br/>
|
||||
The type returned when combining multiple views together is itself a view, more
|
||||
in general a multi component one.
|
||||
|
||||
The creation of a view pack tries to mimic C++20 ranges:
|
||||
Combining different views tries to mimic C++20 ranges:
|
||||
|
||||
```cpp
|
||||
auto view = registry.view<position>();
|
||||
@@ -1324,54 +1577,18 @@ auto other = registry.view<velocity>();
|
||||
auto pack = view | other;
|
||||
```
|
||||
|
||||
The return type is a specialization of the class template `entt::view_pack`.
|
||||
This is nothing more than a _view-like_ iterable object that combines two or
|
||||
more views into a single instance.<br/>
|
||||
The first view used to create a pack will also be the same that will lead the
|
||||
iteration.
|
||||
|
||||
A view pack offers functionalities similar to those of a multi component view,
|
||||
especially with regard to the possibilities of iteration. In particular, it only
|
||||
returns entities if iterated directly:
|
||||
|
||||
```cpp
|
||||
for(auto entt: pack) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
On the other hand, both the (optional) entity and the components are returned
|
||||
when the `each` member function is used, be it with callback or to get an
|
||||
extended iterable object:
|
||||
|
||||
```cpp
|
||||
// with a callback
|
||||
pack.each([](const auto entt, auto &pos, auto &vel) { /* ... */ });
|
||||
|
||||
// with an extended iterable object
|
||||
for(auto [entt, pos, vel]: pack.each()) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Furthermore, the constness of the types returned by a view pack is directly
|
||||
inherited by the views that compose it:
|
||||
|
||||
```
|
||||
(registry.view<position>() | registry.view<const velocity>()).each([](auto &pos, const auto &vel) {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
Read also the dedicated section to know how a view pack is involved in the
|
||||
creation and use of custom storage and pools.
|
||||
The constness of the types is preserved and their order depends on the order in
|
||||
which the views are combined. Therefore, the pack in the example above will
|
||||
return an instance of `position` first and then one of `velocity`.<br/>
|
||||
Since combining views generates views, a chain can be of arbitrary length and
|
||||
the above type order rules apply sequentially.
|
||||
|
||||
### Runtime views
|
||||
|
||||
Runtime views iterate entities that have at least all the given components in
|
||||
their bags. During construction, these views look at the number of entities
|
||||
available for each component and pick up a reference to the smallest
|
||||
set of candidates in order to speed up iterations.<br/>
|
||||
available for each component and pick up a reference to the smallest set of
|
||||
candidates in order to speed up iterations.<br/>
|
||||
They offer more or less the same functionalities of a multi component view.
|
||||
However, they don't expose a `get` member function and users should refer to the
|
||||
registry that generated the view to access components. In particular, a runtime
|
||||
@@ -1666,32 +1883,6 @@ restrictive of them. To prevent users from having to remember which of their
|
||||
groups is the most restrictive, the registry class offers the `sortable` member
|
||||
function to know if a group can be sorted or not.
|
||||
|
||||
## Invalid views and groups
|
||||
|
||||
Views and groups as returned by a registry are generally valid. However, there
|
||||
are some exceptions where an invalid object might be returned.<br/>
|
||||
In these cases, they should be renewed as soon as possible. In fact, an invalid
|
||||
view or group contains a broken reference to one or more pools and this will
|
||||
never be fixed. The view or the group will continue to return no data, even if
|
||||
the pool for the pending reference is created in the registry in the meantime.
|
||||
|
||||
There is only one case in which an invalid object can be returned, that is when
|
||||
the view or the group is created from a constant reference to a registry in
|
||||
which the required pools haven't yet been created.<br/>
|
||||
Pools are typically created whenever any method is used on a non-const registry.
|
||||
This also means that creating views and groups from a non-const registry can
|
||||
never result in an invalid object.
|
||||
|
||||
It's also perfectly fine to use an invalid view or group, to invoke `each` on
|
||||
them or to iterate them like any other object. The only difference from a valid
|
||||
view or group is that the invalid ones will always appear as _empty_.<br/>
|
||||
In general, when views and groups are created on the fly and used at the same
|
||||
time, then discarded immediately afterwards, it doesn't matter whether or not
|
||||
they may be invalid. Therefore, this remains the recommended approach.
|
||||
|
||||
To know if a view or a group is properly initialized, both can be converted to
|
||||
bool explicitly and used in a guard.
|
||||
|
||||
## Types: const, non-const and all in between
|
||||
|
||||
The `registry` class offers two overloads when it comes to constructing views
|
||||
@@ -1785,20 +1976,58 @@ In general, all these functions can result in poor performance.<br/>
|
||||
entity. For similar reasons, `orphans` can be even slower. Both functions should
|
||||
not be used frequently to avoid the risk of a performance hit.
|
||||
|
||||
## Stable storage
|
||||
|
||||
Since it's possible to have completely stable storage in `EnTT`, it's also
|
||||
required that all views behave accordingly.<br/>
|
||||
In general, this aspect is quite transparent to the user who doesn't have to do
|
||||
anything in the vast majority of cases. In particular:
|
||||
|
||||
* Groups are incompatible with stable storage and will trigger a compile-time
|
||||
error if detected.
|
||||
|
||||
* Views detect the type of storage with the most stringent requirements when
|
||||
built and self-configure themselves to use the correct iteration policy.
|
||||
|
||||
* Views created as view packs adjust their policy by choosing the most stringent
|
||||
among those available.
|
||||
|
||||
The policy adopted doesn't emerge from the view type, although it's available
|
||||
through the `storage_policy` alias.<br/>
|
||||
However, this can affect the feature set offered by the view itself. In the case
|
||||
of storage that also support tombstones, all views (even single-component ones)
|
||||
will always behave as a multi-type views. Therefore, for example, it won't be
|
||||
possible to directly access the raw representation of entities and components.
|
||||
|
||||
In other words, the more generic version of a view will be provided in case of
|
||||
stable storage, even for single components, always supported by an appropriate
|
||||
iteration policy.<br/>
|
||||
The latter will be such that in no case will a tombstone be returned from the
|
||||
view itself, regardless of the iteration method. Similarly, no non-existent
|
||||
components will be accessed, which could result in an UB otherwise.
|
||||
|
||||
## What is allowed and what is not
|
||||
|
||||
Most of the _ECS_ available out there don't allow to create and destroy entities
|
||||
and components during iterations.<br/>
|
||||
and components during iterations, nor to have pointer stability.<br/>
|
||||
`EnTT` partially solves the problem with a few limitations:
|
||||
|
||||
* Creating entities and components is allowed during iterations in most cases.
|
||||
* Creating entities and components is allowed during iterations in most cases
|
||||
and it never invalidates already existing references.
|
||||
|
||||
* Deleting the current entity or removing its components is allowed during
|
||||
iterations. For all the other entities, destroying them or removing their
|
||||
components isn't allowed and can result in undefined behavior.
|
||||
iterations but it could invalidate references. For all the other entities,
|
||||
destroying them or removing their components isn't allowed and can result in
|
||||
undefined behavior.
|
||||
|
||||
In these cases, iterators aren't invalidated. To be clear, it doesn't mean that
|
||||
also references will continue to be valid.<br/>
|
||||
* If a type has stable pointers, it's possible to destroy any entity and any
|
||||
component, even if not currently iterated, without the risk of invalidating
|
||||
any references.
|
||||
|
||||
In other terms, iterators are never invalidated. Also, component references
|
||||
aren't invalidated when a new element is added while they could be invalidated
|
||||
upon destruction due to the _swap-and-pop_ policy, unless the type leading the
|
||||
iteration undergoes in-place deletion.<br/>
|
||||
Consider the following example:
|
||||
|
||||
```cpp
|
||||
@@ -1808,14 +2037,16 @@ registry.view<position>([&](const auto entity, auto &pos) {
|
||||
});
|
||||
```
|
||||
|
||||
The `each` member function won't break (because iterators aren't invalidated)
|
||||
but there are no guarantees on references. Use a common range-for loop and get
|
||||
components directly from the view or move the creation of components at the end
|
||||
of the function to avoid dangling pointers.
|
||||
The `each` member function won't break (because iterators remain valid) nor will
|
||||
any reference be invalidated. Instead, more attention should be paid to the
|
||||
destruction of entities or the removal of components.<br/>
|
||||
Use a common range-for loop and get components directly from the view or move
|
||||
the deletion of entities and components at the end of the function to avoid
|
||||
dangling pointers.
|
||||
|
||||
Iterators are invalidated instead and the behavior is undefined if an entity is
|
||||
modified or destroyed and it's not the one currently returned by the iterator
|
||||
nor a newly created one.<br/>
|
||||
For all types that don't offer stable pointers, iterators are also invalidated
|
||||
and the behavior is undefined if an entity is modified or destroyed and it's not
|
||||
the one currently returned by the iterator nor a newly created one.<br/>
|
||||
To work around it, possible approaches are:
|
||||
|
||||
* Store aside the entities and the components to be removed and perform the
|
||||
@@ -1878,11 +2109,11 @@ mentioning.
|
||||
|
||||
When an empty type is detected, it's not instantiated in any case. Therefore,
|
||||
only the entities to which it's assigned are made available.<br/>
|
||||
There doesn't exist a way to _iterate_ empty types. Views and groups will never
|
||||
return instances of empty types (for example, during a call to `each`) and some
|
||||
functions such as `try_get` or the raw access to the list of components aren't
|
||||
available for them. Finally, the `sort` functionality accepts only callbacks
|
||||
that require to return entities rather than components:
|
||||
There doesn't exist a way to _get_ empty types from a registry, views and groups
|
||||
will never return instances for them (for example, during a call to `each`) and
|
||||
some functions such as `try_get` or the raw access to the list of components
|
||||
aren't available for empty types. Finally, the `sort` functionality will only
|
||||
accepts callbacks that require to return entities rather than components:
|
||||
|
||||
```cpp
|
||||
registry.sort<empty_type>([](const entt::entity lhs, const entt::entity rhs) {
|
||||
@@ -1897,8 +2128,12 @@ it is assigned to.
|
||||
|
||||
More in general, none of the features offered by the library is affected, but
|
||||
for the ones that require to return actual instances.<br/>
|
||||
This optimization can be disabled by defining the `ENTT_NO_ETO` macro. In this
|
||||
case, empty types will be treated like all other types, no matter what.
|
||||
This optimization can be disabled for the whole application by defining the
|
||||
`ENTT_NO_ETO` macro. In this case, empty types will be treated like all other
|
||||
types, no matter what.<br/>
|
||||
Otherwise, users can specialize the `component_traits` template class and in
|
||||
particular the `ignore_if_empty` alias, disabling this optimization for some
|
||||
types only.
|
||||
|
||||
# Multithreading
|
||||
|
||||
@@ -1975,6 +2210,28 @@ both the entities and a list of references to their components by default sooner
|
||||
or later. Multi-pass guarantee won't break in any case and the performance
|
||||
should even benefit from it further.
|
||||
|
||||
## Const registry
|
||||
|
||||
Contrary to what the standard library containers offer, a const registry is
|
||||
generally but not completely thread safe.<br/>
|
||||
In particular, one (and only one) of its const member functions isn't fully
|
||||
thread safe. That is the `view` method.
|
||||
|
||||
The reason for this is easy to explain. To avoid requiring types to be
|
||||
_announced_ in advance, the registry lazily initializes the storage objects for
|
||||
the different components.<br/>
|
||||
In most cases, this isn't even necessary. The absence of a storage is itself the
|
||||
required information. However, when building a view, all pools must necessarily
|
||||
exist. This makes the `view` member function not thread safe even in its const
|
||||
overload, unless all pools already exist.
|
||||
|
||||
Fortunately, there is also a way to instantiate storage classes early when in
|
||||
doubt or when there are special requirements.<br/>
|
||||
Calling the `prepare` method is equivalent to _announcing_ the existence of a
|
||||
particular storage, to avoid running into problems. For those interested, there
|
||||
are also alternative approaches, such as a single threaded tick for the registry
|
||||
warm-up, but these are not always applicable.
|
||||
|
||||
# Beyond this document
|
||||
|
||||
There are many other features and functions not listed in this document.<br/>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ attached the new visible meta types, no matter where they are created.<br/>
|
||||
A context can also be reset and then associated again locally as:
|
||||
|
||||
```cpp
|
||||
entt::meta_ctx::bind{entt::meta_ctx{});
|
||||
entt::meta_ctx::bind(entt::meta_ctx{});
|
||||
```
|
||||
|
||||
This is allowed because local and global contexts are separated. Therefore, it's
|
||||
|
||||
@@ -43,8 +43,8 @@ I hope this list can grow much more in the future:
|
||||
* [The Machine](https://github.com/Kerndog73/The-Machine): a box pushing
|
||||
puzzler with logic gates and other cool stuff.
|
||||
[Check it out](https://indi-kernick.itch.io/the-machine-web-version).
|
||||
* [EnttPong 2.0](https://github.com/DomRe/EnttPong): a basic game made to
|
||||
showcase different parts of EnTT and C++17.
|
||||
* [EnTTPong](https://github.com/DomRe/EnttPong): a basic game made to showcase
|
||||
different parts of EnTT and C++17.
|
||||
* [Randballs](https://github.com/gale93/randballs): simple `SFML` and `EnTT`
|
||||
playground.
|
||||
* [EnTT Tower Defense](https://github.com/Daivuk/tddod): a data oriented tower
|
||||
@@ -64,6 +64,14 @@ I hope this list can grow much more in the future:
|
||||
* [DungeonSlayer](https://github.com/alohaeee/DungeonSlayer): 2D game made
|
||||
from scratch in C++.
|
||||
* [3DGame](https://github.com/kwarkGorny/3DGame): 2.5D top-down space shooter.
|
||||
* [Pulcher](https://github.com/AODQ/pulcher): 2D cross-platform game inspired
|
||||
by Quake.
|
||||
* [Destroid](https://github.com/tyrannicaltoucan/destroid): _one-bazillionth_
|
||||
arcade game about shooting dirty rocks in space, inspired by Asteroids.
|
||||
* [Wanderer](https://github.com/albin-johansson/wanderer): a 2D exploration
|
||||
based indie game.
|
||||
* [Spelunky® Classic remake](https://github.com/dbeef/spelunky-psp): A truly
|
||||
multiplatform experience with a rewrite from scratch.
|
||||
|
||||
* Engines and the like:
|
||||
* [Aether Engine](https://hadean.com/spatial-simulation/)
|
||||
@@ -88,8 +96,6 @@ I hope this list can grow much more in the future:
|
||||
[Wisp](https://teamwisp.github.io/product/) by
|
||||
[Team Wisp](https://teamwisp.github.io/): an advanced real-time ray tracing
|
||||
renderer built for the demands of video game artists.
|
||||
* [Apparently](https://github.com/JosiahWI/qub3d-libdeps)
|
||||
[Qub3d](https://qub3d.org/): because blocks should be open source.
|
||||
* [shiva](https://github.com/Milerius/shiva): modern C++ engine with
|
||||
modularity.
|
||||
* [ImGui/EnTT editor](https://github.com/Green-Sky/imgui_entt_entity_editor):
|
||||
@@ -104,6 +110,12 @@ I hope this list can grow much more in the future:
|
||||
Box Generator.
|
||||
* [Lina Engine](https://github.com/inanevin/LinaEngine): an open-source,
|
||||
modular, tiny and fast C++ game engine, aimed to develop 3D desktop games.
|
||||
* [Spike](https://github.com/FahimFuad/Spike): a powerful game engine which
|
||||
can run on a toaster.
|
||||
* [Helena Framework](https://github.com/NIKEA-SOFT/HelenaFramework): a modern
|
||||
framework in C++17 for backend development.
|
||||
* [Unity/EnTT](https://github.com/TongTungGiang/unity-entt): tech demo of a
|
||||
native simulation layer using `EnTT` and `Unity` as a rendering engine.
|
||||
|
||||
* Articles, videos and blog posts:
|
||||
* [Some posts](https://skypjack.github.io/tags/#entt) on my personal
|
||||
@@ -147,8 +159,10 @@ I hope this list can grow much more in the future:
|
||||
* [Creating Minecraft in One Week with C++ and Vulkan](https://vazgriz.com/189/creating-minecraft-in-one-week-with-c-and-vulkan/):
|
||||
a crack at recreating Minecraft in one week using a custom C++ engine and
|
||||
Vulkan ([code included](https://github.com/vazgriz/VoxelGame)).
|
||||
* [Ability Creator](https://www.erichildebrand.net/blog/ability-creator-project-retrospect).
|
||||
* [Ability Creator](https://www.erichildebrand.net/blog/ability-creator-project-retrospect):
|
||||
project retrospect by [Eric Hildebrand](https://www.erichildebrand.net/).
|
||||
* [EnTT Entity Component System Gaming Library](https://gamefromscratch.com/entt-entity-component-system-gaming-library/):
|
||||
`EnTT` on GameFromScratch.com.
|
||||
|
||||
* Any Other Business:
|
||||
* [ArcGIS Runtime SDKs](https://developers.arcgis.com/arcgis-runtime/) by
|
||||
|
||||
232
docs/md/meta.md
232
docs/md/meta.md
@@ -12,6 +12,8 @@
|
||||
* [Enjoy the runtime](#enjoy-the-runtime)
|
||||
* [Container support](#container-support)
|
||||
* [Pointer-like types](#pointer-like-types)
|
||||
* [Template information](#template-information)
|
||||
* [Implicitly generated default constructor](#implicitly-generated-default-constructor)
|
||||
* [Policies: the more, the less](#policies-the-more-the-less)
|
||||
* [Named constants and enums](#named-constants-and-enums)
|
||||
* [Properties and meta objects](#properties-and-meta-objects)
|
||||
@@ -22,11 +24,11 @@
|
||||
|
||||
# Introduction
|
||||
|
||||
Reflection (or rather, its lack) is a trending topic in the C++ world and, in
|
||||
the specific case of `EnTT`, a tool that can unlock a lot of other features. I
|
||||
Reflection (or rather, its lack) is a trending topic in the C++ world and a tool
|
||||
that can unlock a lot of interesting feature in the specific case of `EnTT`. I
|
||||
looked for a third-party library that met my needs on the subject, but I always
|
||||
came across some details that I didn't like: macros, being intrusive, too many
|
||||
allocations. In one word: unsatisfactory.<br/>
|
||||
allocations, and so on.<br/>
|
||||
I finally decided to write a built-in, non-intrusive and macro-free runtime
|
||||
reflection system for `EnTT`. Maybe I didn't do better than others or maybe yes,
|
||||
time will tell me, but at least I can model this tool around the library to
|
||||
@@ -44,7 +46,7 @@ compile-time or with custom functions.
|
||||
|
||||
That being said, the examples in the following sections are all based on the
|
||||
`hashed_string` class as provided by this library. Therefore, where an
|
||||
identifier is required, it's likely that a user defined literal is used as
|
||||
identifier is required, it's likely that an user defined literal is used as
|
||||
follows:
|
||||
|
||||
```cpp
|
||||
@@ -93,8 +95,8 @@ features to a reflected type so that the reflection system can use it correctly
|
||||
under the hood, but they don't want to also make the type _searchable_. In this
|
||||
case, it's sufficient not to invoke `type`.
|
||||
|
||||
A factory is such that all its member functions returns the factory itself or
|
||||
a decorated version of it. This object can be used to add the following:
|
||||
A factory is such that all its member functions return the factory itself or a
|
||||
decorated version of it. This object can be used to add the following:
|
||||
|
||||
* _Constructors_. Actual constructors can be assigned to a reflected type by
|
||||
specifying their list of arguments. Free functions (namely, factories) can be
|
||||
@@ -135,7 +137,7 @@ a decorated version of it. This object can be used to add the following:
|
||||
The function requires as an argument the identifier to give to the meta data
|
||||
once created. Users can then access meta data at runtime by searching for them
|
||||
by _name_.<br/>
|
||||
Data members can also be defined by means of a _setter_ and _getter_. Setters
|
||||
Data members can also be defined by means of a setter and getter pair. Setters
|
||||
and getters can be either free functions, class members or a mix of them, as
|
||||
long as they respect the required signatures. This approach is also convenient
|
||||
to create a read-only variable from a non-const data member:
|
||||
@@ -161,7 +163,9 @@ a decorated version of it. This object can be used to add the following:
|
||||
|
||||
The function requires as an argument the identifier to give to the meta
|
||||
function once created. Users can then access meta functions at runtime by
|
||||
searching for them by _name_.
|
||||
searching for them by _name_.<br/>
|
||||
Overloading of meta functions is supported. Overloaded functions are resolved
|
||||
at runtime by the reflection system according to the types of the arguments.
|
||||
|
||||
* _Base classes_. A base class is such that the underlying type is actually
|
||||
derived from it. In this case, the reflection system tracks the relationship
|
||||
@@ -206,19 +210,19 @@ Among the few relevant differences, `meta_any` adds support for containers and
|
||||
pointer-like types (see the following sections for more details), while `any`
|
||||
does not.<br/>
|
||||
Similar to `any`, this class can also be used to create _aliases_ for unmanaged
|
||||
objects either upon construction using `std::ref` and `std::cref` or from an
|
||||
existing instance by means of the `as_ref` function. However, unlike `any`,
|
||||
`meta_any` treats an empty instance and one initialized with `void` differently:
|
||||
objects either with `forward_as_meta` or using the `std::in_place_type<T &>`
|
||||
disambiguation tag, as well as from an existing object by means of the `as_ref`
|
||||
member function. However, unlike `any`, `meta_any` treats an empty instance and
|
||||
one initialized with `void` differently:
|
||||
|
||||
```cpp
|
||||
entt::meta_any empty{};
|
||||
entt::meta_any other{std::in_place_type<void>};
|
||||
```
|
||||
|
||||
While `any` treats both objects as empty, `meta_any` treats objects initialized
|
||||
with `void` as if they were _valid_ ones. This allows to differentiate between
|
||||
failed function calls and function calls that are successful but return
|
||||
nothing.<br/>
|
||||
While `any` considers both as empty, `meta_any` treats objects initialized with
|
||||
`void` as if they were _valid_ ones. This allows to differentiate between failed
|
||||
function calls and function calls that are successful but return nothing.<br/>
|
||||
Finally, the member functions `try_cast`, `cast` and `allow_cast` are used to
|
||||
cast the underlying object to a given type (either a reference or a value type)
|
||||
or to _convert_ a `meta_any` in such a way that a cast becomes viable for the
|
||||
@@ -228,9 +232,9 @@ resulting object. There is in fact no `any_cast` equivalent for `meta_any`.
|
||||
|
||||
Once the web of reflected types has been constructed, it's a matter of using it
|
||||
at runtime where required.<br/>
|
||||
All this has the great merit that, unlike the vast majority of the things
|
||||
present in this library and closely linked to the compile-time, the reflection
|
||||
system stands in fact as a non-intrusive tool for the runtime.
|
||||
All this has the great merit that the reflection system stands in fact as a
|
||||
non-intrusive tool for the runtime, unlike the vast majority of the things
|
||||
offered by this library and closely linked to the compile-time.
|
||||
|
||||
To search for a reflected type there are a few options:
|
||||
|
||||
@@ -238,11 +242,11 @@ To search for a reflected type there are a few options:
|
||||
// direct access to a reflected type
|
||||
auto by_type = entt::resolve<my_type>();
|
||||
|
||||
// lookup of a reflected type by identifier
|
||||
auto by_id = entt::resolve_id("reflected_type"_hs);
|
||||
// look up a reflected type by identifier
|
||||
auto by_id = entt::resolve("reflected_type"_hs);
|
||||
|
||||
// lookup of a reflected type by type id
|
||||
auto by_type_id = entt::resolve_type(entt::type_hash<my_type>::value());
|
||||
// look up a reflected type by type info
|
||||
auto by_type_id = entt::resolve(entt::type_id<my_type>());
|
||||
```
|
||||
|
||||
There exits also an overload of the `resolve` function to use to iterate all the
|
||||
@@ -308,24 +312,8 @@ The meta objects that compose a meta type are accessed in the following ways:
|
||||
auto base = entt::resolve<derived_type>().base("base"_hs);
|
||||
```
|
||||
|
||||
The returned type is `meta_base` and may be invalid if there is no meta base
|
||||
object associated with the given identifier.<br/>
|
||||
Meta bases aren't meant to be used directly, even though they are freely
|
||||
accessible. They expose only a few methods to use to know the meta type of the
|
||||
base class and to convert a raw pointer between types.
|
||||
|
||||
* _Meta conversion functions_. They are accessed by type:
|
||||
|
||||
```cpp
|
||||
auto conv = entt::resolve<double>().conv<int>();
|
||||
```
|
||||
|
||||
The returned type is `meta_conv` and may be invalid if there is no meta
|
||||
conversion function associated with the given type.<br/>
|
||||
The meta conversion functions are as thin as the meta bases and with a very
|
||||
similar interface. The sole difference is that they return a newly created
|
||||
instance wrapped in a `meta_any` object when they convert between different
|
||||
types.
|
||||
The returned type is `meta_type` and may be invalid if there is no meta base
|
||||
object associated with the given identifier.
|
||||
|
||||
All the objects thus obtained as well as the meta types can be explicitly
|
||||
converted to a boolean value to check if they are valid:
|
||||
@@ -337,7 +325,7 @@ if(auto func = entt::resolve<my_type>().func("member"_hs); func) {
|
||||
```
|
||||
|
||||
Furthermore, all them are also returned by specific overloads that provide the
|
||||
caller with iterable objects. As an example:
|
||||
caller with iterable ranges of top-level elements. As an example:
|
||||
|
||||
```cpp
|
||||
for(auto data = entt::resolve<my_type>().data()) {
|
||||
@@ -353,12 +341,12 @@ or may not be initialized, depending on whether a suitable constructor has been
|
||||
found or not.
|
||||
|
||||
There is no object that wraps the destructor of a meta type nor a `destroy`
|
||||
member function in its API. The reason is quickly explained: destructors are
|
||||
invoked implicitly by `meta_any` behind the scenes and users have not to deal
|
||||
with them explicitly. Furthermore, they have no name, cannot be searched and
|
||||
wouldn't have member functions to expose anyway.<br/>
|
||||
Therefore, exposing destructors would be pointless and would add nothing to the
|
||||
library itself.
|
||||
member function in its API. Destructors are invoked implicitly by `meta_any`
|
||||
behind the scenes and users have not to deal with them explicitly. Furthermore,
|
||||
they have no name, cannot be searched and wouldn't have member functions to
|
||||
expose anyway.<br/>
|
||||
Similarly, conversion functions aren't directly accessible. They are used
|
||||
internally by `meta_any` and the meta objects when needed.
|
||||
|
||||
Meta types and meta objects in general contain much more than what is said: a
|
||||
plethora of functions in addition to those listed whose purposes and uses go
|
||||
@@ -368,14 +356,14 @@ read the inline documentation to get the best out of this powerful tool.
|
||||
|
||||
## Container support
|
||||
|
||||
The meta module supports containers of all types out of the box.<br/>
|
||||
The runtime reflection system also supports containers of all types.<br/>
|
||||
Moreover, _containers_ doesn't necessarily mean those offered by the C++
|
||||
standard library. In fact, user defined data structures can also work with the
|
||||
meta system in many cases.
|
||||
|
||||
To make a container be recognized by the meta module, users are required to
|
||||
provide specializations for either the `meta_sequence_container_traits` class or
|
||||
the `meta_associative_container_traits` class, according with the actual _type_
|
||||
To make a container be recognized as such by the meta system, users are required
|
||||
to provide specializations for either the `meta_sequence_container_traits` class
|
||||
or the `meta_associative_container_traits` class, according with the actual type
|
||||
of the container.<br/>
|
||||
`EnTT` already exports the specializations for some common classes. In
|
||||
particular:
|
||||
@@ -399,7 +387,7 @@ object for a sequence container:
|
||||
|
||||
```cpp
|
||||
std::vector<int> vec{1, 2, 3};
|
||||
entt::meta_any any{std::ref(vec)};
|
||||
entt::meta_any any = entt::forward_as_meta(vec);
|
||||
|
||||
if(any.type().is_sequence_container()) {
|
||||
if(auto view = any.as_sequence_container(); view) {
|
||||
@@ -597,15 +585,14 @@ differences in behavior in the case of key-only containers. In particular:
|
||||
elements. Modifying the returned object will then directly modify the element
|
||||
inside the container.
|
||||
|
||||
Container support is deliberately minimal but theoretically sufficient to
|
||||
satisfy all needs.
|
||||
Container support is minimal but likely sufficient to satisfy all needs.
|
||||
|
||||
## Pointer-like types
|
||||
|
||||
As with containers, it's also possible to communicate to the meta system which
|
||||
types to consider _pointers_. This will allow to dereference instances of
|
||||
`meta_any`, obtaining light _references_ to the pointed objects that are also
|
||||
correctly associated with their meta types.<br/>
|
||||
`meta_any`, thus obtaining light _references_ to the pointed objects that are
|
||||
also correctly associated with their meta types.<br/>
|
||||
To make the meta system recognize a type as _pointer-like_, users can specialize
|
||||
the `is_meta_pointer_like` class. `EnTT` already exports the specializations for
|
||||
some common classes. In particular:
|
||||
@@ -616,7 +603,7 @@ some common classes. In particular:
|
||||
It's important to include the header file `pointer.hpp` to make these
|
||||
specializations available to the compiler when needed.<br/>
|
||||
The same file also contains many examples for the users that are interested in
|
||||
making their own containers available to the meta system.
|
||||
making their own pointer-like types available to the meta system.
|
||||
|
||||
When a type is recognized as a pointer-like one by the meta system, it's
|
||||
possible to dereference the instances of `meta_any` that contain these objects.
|
||||
@@ -640,7 +627,8 @@ query the meta type or verify that the returned object is valid. For example,
|
||||
invalid instances are returned when the wrapped object isn't a pointer-like
|
||||
type.<br/>
|
||||
Note that dereferencing a pointer-like object returns an instance of `meta_any`
|
||||
which refers to the pointed object and allows users to modify it directly.
|
||||
which refers to the pointed object and allows users to modify it directly
|
||||
(unless the returned element is const, of course).
|
||||
|
||||
In general, _dereferencing_ a pointer-like type boils down to a `*ptr`. However,
|
||||
`EnTT` also supports classes that don't offer an `operator*`. In particular:
|
||||
@@ -656,8 +644,8 @@ In general, _dereferencing_ a pointer-like type boils down to a `*ptr`. However,
|
||||
```
|
||||
|
||||
* When not in control of the type's namespace, it's possible to inject into the
|
||||
`entt` namespace a specialization of `adl_meta_pointer_like` class template to
|
||||
bypass the adl lookup as a whole:
|
||||
`entt` namespace a specialization of the `adl_meta_pointer_like` class
|
||||
template to bypass the adl lookup as a whole:
|
||||
|
||||
```cpp
|
||||
template<typename Type>
|
||||
@@ -671,6 +659,95 @@ In general, _dereferencing_ a pointer-like type boils down to a `*ptr`. However,
|
||||
In all other cases, that is, when dereferencing a pointer works as expected and
|
||||
regardless of the pointed type, no user intervention is required.
|
||||
|
||||
## Template information
|
||||
|
||||
Meta types also provide a minimal set of information about the nature of the
|
||||
original type in case it's a class template.<br/>
|
||||
By default, this works out of the box and requires no user action. However, it's
|
||||
important to include the header file `template.hpp` to make these information
|
||||
available to the compiler when needed.
|
||||
|
||||
Meta template information are easily found:
|
||||
|
||||
```cpp
|
||||
// this method returns true if the type is recognized as a class template specialization
|
||||
if(auto type = entt::resolve<std::shared_ptr<my_type>>(); type.is_template_specialization()) {
|
||||
// meta type of the class template conveniently wrapped by entt::meta_class_template_tag
|
||||
auto class_type = type.template_type();
|
||||
|
||||
// number of template arguments
|
||||
std::size_t arity = type.template_arity();
|
||||
|
||||
// meta type of the i-th argument
|
||||
auto arg_type = type.template_arg(0u);
|
||||
}
|
||||
```
|
||||
|
||||
Typically, when template information for a type are required, what the library
|
||||
provides is sufficient. However, there are some cases where a user may want more
|
||||
details or a different set of information.<br/>
|
||||
Consider the case of a class template that is meant to wrap function types:
|
||||
|
||||
```cpp
|
||||
template<typename>
|
||||
struct function_type;
|
||||
|
||||
template<typename Ret, typename... Args>
|
||||
struct function_type<Ret(Args...)> {};
|
||||
```
|
||||
|
||||
In this case, rather than the function type, the user might want the return type
|
||||
and unpacked arguments as if they were different template parameters for the
|
||||
original class template.<br/>
|
||||
To achieve this, users must enter the library internals and provide their own
|
||||
specialization for the class template `entt::meta_template_traits`, such as:
|
||||
|
||||
```cpp
|
||||
template<typename Ret, typename... Args>
|
||||
struct entt::meta_template_traits<function_type<Ret(Args...)>> {
|
||||
using class_type = meta_class_template_tag<function_type>;
|
||||
using args_type = type_list<Ret, Args...>;
|
||||
};
|
||||
```
|
||||
|
||||
The reflection system doesn't verify the accuracy of the information nor infer a
|
||||
correspondence between real types and meta types.<br/>
|
||||
Therefore, the specialization will be used as is and the information it contains
|
||||
will be associated with the appropriate type when required.
|
||||
|
||||
## Implicitly generated default constructor
|
||||
|
||||
In many cases, it's useful to be able to create objects of default constructible
|
||||
types through the reflection system, while not having to explicitly register the
|
||||
meta type or the default constructor.<br/>
|
||||
For example, in the case of primitive types like `int` or `char`, but not just
|
||||
them.
|
||||
|
||||
For this reason and only for default constructible types, default constructors
|
||||
are automatically defined and associated with their meta types, whether they are
|
||||
explicitly or implicitly generated.<br/>
|
||||
Therefore, it won't be necessary to do this in order to construct an integer
|
||||
from its meta type:
|
||||
|
||||
```cpp
|
||||
entt::meta<int>().ctor<>();
|
||||
```
|
||||
|
||||
Instead, just do this:
|
||||
|
||||
```cpp
|
||||
entt::resolve<int>().construct();
|
||||
```
|
||||
|
||||
Where the meta type can be for example the one returned from a meta container,
|
||||
useful for building keys without knowing or having to register the actual types.
|
||||
|
||||
In all cases, when users register custom defaul constructors, they are preferred
|
||||
both during searches and when the `construct` member function is invoked.<br/>
|
||||
However, the implicitly generated default constructor will always be returned,
|
||||
either if one is not explicitly specified or if all constructors are iterated
|
||||
for some reason (in this case, it will always be the last element).
|
||||
|
||||
## Policies: the more, the less
|
||||
|
||||
Policies are a kind of compile-time directives that can be used when registering
|
||||
@@ -693,33 +770,30 @@ There are a few alternatives available at the moment:
|
||||
|
||||
* The _as-void_ policy, associated with the type `entt::as_void_t`.<br/>
|
||||
Its purpose is to discard the return value of a meta object, whatever it is,
|
||||
thus making it appear as if its type were `void`.<br/>
|
||||
thus making it appear as if its type were `void`:
|
||||
```cpp
|
||||
entt::meta<my_type>().func<&my_type::member_function, entt::as_void_t>("member"_hs);
|
||||
```
|
||||
If the use with functions is obvious, it must be said that it's also possible
|
||||
to use this policy with constructors and data members. In the first case, the
|
||||
constructor will be invoked but the returned wrapper will actually be empty.
|
||||
In the second case, instead, the property will not be accessible for reading.
|
||||
|
||||
As an example of use:
|
||||
|
||||
```cpp
|
||||
entt::meta<my_type>().func<&my_type::member_function, entt::as_void_t>("member"_hs);
|
||||
```
|
||||
|
||||
* The _as-ref_ and _as-cref_ policies, associated with the types
|
||||
`entt::as_ref_t` and `entt::as_cref_t`.<br/>
|
||||
They allow to build wrappers that act as references to unmanaged objects.
|
||||
Accessing the object contained in the wrapper for which the _reference_ was
|
||||
requested will make it possible to directly access the instance used to
|
||||
initialize the wrapper itself.<br/>
|
||||
These policies work with constructors (for example, when objects are taken
|
||||
from an external container rather than created on demand), data members and
|
||||
functions in general (as long as their return types are lvalue references).
|
||||
|
||||
As an example of use:
|
||||
|
||||
initialize the wrapper itself:
|
||||
```cpp
|
||||
entt::meta<my_type>().data<&my_type::data_member, entt::as_ref_t>("member"_hs);
|
||||
```
|
||||
These policies work with constructors (for example, when objects are taken
|
||||
from an external container rather than created on demand), data members and
|
||||
functions in general.<br/>
|
||||
If on the one hand `as_cref_t` always forces the return type to be const,
|
||||
`as_ref_t` _adapts_ to the constness of the passed object and to that of the
|
||||
return type if any.
|
||||
|
||||
Some uses are rather trivial, but it's useful to note that there are some less
|
||||
obvious corner cases that can in turn be solved with the use of policies.
|
||||
@@ -744,8 +818,8 @@ Exporting constant values or elements from an enum is as simple as ever:
|
||||
|
||||
```cpp
|
||||
entt::meta<my_enum>()
|
||||
.data<my_enum::a_value>("a_value"_hs)
|
||||
.data<my_enum::another_value>("another_value"_hs);
|
||||
.data<my_enum::a_value>("a_value"_hs)
|
||||
.data<my_enum::another_value>("another_value"_hs);
|
||||
|
||||
entt::meta<int>().data<2048>("max_int"_hs);
|
||||
```
|
||||
@@ -859,10 +933,10 @@ the key and the value contained in the form of `meta_any` objects, respectively.
|
||||
|
||||
A type registered with the reflection system can also be unregistered. This
|
||||
means unregistering all its data members, member functions, conversion functions
|
||||
and so on. However, the base classes won't be unregistered, since they don't
|
||||
and so on. However, base classes aren't unregistered as well, since they don't
|
||||
necessarily depend on it. Similarly, implicitly generated types (as an example,
|
||||
the meta types implicitly generated for function parameters when needed) won't
|
||||
be unregistered.<br/>
|
||||
the meta types implicitly generated for function parameters when needed) aren't
|
||||
unregistered.<br/>
|
||||
Roughly speaking, unregistering a type means disconnecting all associated meta
|
||||
objects from it and making its identifier no longer visible. The underlying node
|
||||
will remain available though, as if it were implicitly generated:
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* [Fullfill a concept](#fullfill-a-concept)
|
||||
* [Inheritance](#inheritance)
|
||||
* [Static polymorphism in the wild](#static-polymorphism-in-the-wild)
|
||||
* [Storage size and alignment requirement](#storage-size-and-alignment-requirement)
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
@@ -236,7 +237,7 @@ For a deduced concept, inheritance is achieved in a few steps:
|
||||
```cpp
|
||||
struct DrawableAndErasable: entt::type_list<> {
|
||||
template<typename Base>
|
||||
struct type: typename Drawable::type<Base> {
|
||||
struct type: typename Drawable::template type<Base> {
|
||||
static constexpr auto base = std::tuple_size_v<typename entt::poly_vtable<Drawable>::type>;
|
||||
void erase() { entt::poly_call<base + 0>(*this); }
|
||||
};
|
||||
@@ -302,37 +303,57 @@ struct square {
|
||||
|
||||
// ...
|
||||
|
||||
drawable d{circle{}};
|
||||
d->draw();
|
||||
drawable instance{circle{}};
|
||||
instance->draw();
|
||||
|
||||
d = square{};
|
||||
d->draw();
|
||||
instance = square{};
|
||||
instance->draw();
|
||||
```
|
||||
|
||||
The `poly` class template offers a wide range of constructors, from the default
|
||||
one (which will return an uninitialized `poly` object) to the copy and move
|
||||
constructor, as well as the ability to create objects in-place.<br/>
|
||||
Among others, there is a constructor that allows users to wrap unmanaged objects
|
||||
in a `poly` instance (either const or non-const ones):
|
||||
constructors, as well as the ability to create objects in-place.<br/>
|
||||
Among others, there is also a constructor that allows users to wrap unmanaged
|
||||
objects in a `poly` instance (either const or non-const ones):
|
||||
|
||||
```cpp
|
||||
circle c;
|
||||
drawable d{std::ref(c)};
|
||||
circle shape;
|
||||
drawable instance{std::in_place_type<circle &>, shape};
|
||||
```
|
||||
|
||||
Similarly, it's possible to create non-owning copies of `poly` from an existing
|
||||
object:
|
||||
|
||||
```cpp
|
||||
drawable other = as_ref(d);
|
||||
drawable other = instance.as_ref();
|
||||
```
|
||||
|
||||
In both cases, although the interface of the `poly` object doesn't change, it
|
||||
won't construct any element or take care of destroying the referenced objects.
|
||||
|
||||
Note also how the underlying concept is accessed via a call to `operator->` and
|
||||
not directly as `d.draw()`.<br/>
|
||||
not directly as `instance.draw()`.<br/>
|
||||
This allows users to decouple the API of the wrapper from that of the concept.
|
||||
Therefore, where `d.data()` will invoke the `data` member function of the poly
|
||||
object, `d->data()` will map directly to the functionality exposed by the
|
||||
underlying concept.
|
||||
Therefore, where `instance.data()` will invoke the `data` member function of the
|
||||
poly object, `instance->data()` will map directly to the functionality exposed
|
||||
by the underlying concept.
|
||||
|
||||
# Storage size and alignment requirement
|
||||
|
||||
Under the hood, the `poly` class template makes use of `entt::any`. Therefore,
|
||||
it can take advantage of the possibility of defining at compile-time the size of
|
||||
the storage suitable for the small buffer optimization as well as the alignment
|
||||
requirements:
|
||||
|
||||
```cpp
|
||||
entt::basic_poly<Drawable, sizeof(double[4]), alignof(double[4])>
|
||||
```
|
||||
|
||||
The default size is `sizeof(double[2])`, which seems like a good compromise
|
||||
between a buffer that is too large and one unable to hold anything larger than
|
||||
an integer. The alignment requirement is optional instead and by default such
|
||||
that it's the most stringent (the largest) for any object whose size is at most
|
||||
equal to the one provided.<br/>
|
||||
It's worth noting that providing a size of 0 (which is an accepted value in all
|
||||
respects) will force the system to dynamically allocate the contained objects in
|
||||
all cases.
|
||||
|
||||
@@ -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 *);`
|
||||
|
||||
|
||||
@@ -28,10 +28,14 @@ I hope this list can grow much more in the future:
|
||||
ECS that uses sparse sets to keep track of entities in systems.
|
||||
* [EntityX](https://github.com/alecthomas/entityx): a bitset based ECS that
|
||||
uses a single large matrix of components indexed with entities.
|
||||
* [Polypropylene](https://github.com/pmbittner/Polypropylene): a hybrid
|
||||
solution between an ECS and dynamic mixins.
|
||||
|
||||
* C#
|
||||
* [Entitas](https://github.com/sschmid/Entitas-CSharp): the ECS framework for
|
||||
C# and Unity, where _reactive systems_ were invented.
|
||||
* [LeoECS](https://github.com/Leopotam/ecs): simple lightweight C# Entity
|
||||
Component System framework.
|
||||
* [Svelto.ECS](https://github.com/sebas77/Svelto.ECS): a very interesting
|
||||
platform agnostic and table based ECS framework.
|
||||
|
||||
@@ -23,7 +23,7 @@ loading, and so on.
|
||||
`EnTT` doesn't pretend to offer a _one-fits-all_ solution for the different
|
||||
cases. Instead, it offers a minimal and perhaps trivial cache that can be useful
|
||||
most of the time during prototyping and sometimes even in a production
|
||||
environment.<br/>
|
||||
environments.<br/>
|
||||
For those interested in the subject, the plan is to improve it considerably over
|
||||
time in terms of performance, memory usage and functionalities. Hoping to make
|
||||
it, of course, one step at a time.
|
||||
@@ -126,7 +126,7 @@ identifier and the parameters used to construct the resource as arguments:
|
||||
// uses the identifier declared above
|
||||
cache.load<my_loader>(identifier, 0);
|
||||
|
||||
// uses a const char * directly as an identifier
|
||||
// uses a hashed string directly
|
||||
cache.load<my_loader>("another/identifier"_hs, 42);
|
||||
```
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,8 +2,24 @@
|
||||
#define ENTT_CONFIG_CONFIG_H
|
||||
|
||||
|
||||
#ifndef ENTT_NOEXCEPT
|
||||
#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION)
|
||||
# define ENTT_NOEXCEPT noexcept
|
||||
# define ENTT_THROW throw
|
||||
# define ENTT_TRY try
|
||||
# define ENTT_CATCH catch(...)
|
||||
#else
|
||||
# define ENTT_NOEXCEPT
|
||||
# define ENTT_THROW
|
||||
# define ENTT_TRY if(true)
|
||||
# define ENTT_CATCH if(false)
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606L
|
||||
# include <new>
|
||||
# define ENTT_LAUNDER(expr) std::launder(expr)
|
||||
#else
|
||||
# define ENTT_LAUNDER(expr) expr
|
||||
#endif
|
||||
|
||||
|
||||
@@ -21,8 +37,17 @@
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef ENTT_PAGE_SIZE
|
||||
# define ENTT_PAGE_SIZE 4096
|
||||
#ifdef ENTT_SPARSE_PAGE
|
||||
static_assert(ENTT_SPARSE_PAGE && ((ENTT_SPARSE_PAGE & (ENTT_SPARSE_PAGE - 1)) == 0), "ENTT_SPARSE_PAGE must be a power of two");
|
||||
#else
|
||||
# define ENTT_SPARSE_PAGE 4096
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef ENTT_PACKED_PAGE
|
||||
static_assert(ENTT_PACKED_PAGE && ((ENTT_PACKED_PAGE & (ENTT_PACKED_PAGE - 1)) == 0), "ENTT_PACKED_PAGE must be a power of two");
|
||||
#else
|
||||
# define ENTT_PACKED_PAGE 1024
|
||||
#endif
|
||||
|
||||
|
||||
@@ -31,16 +56,16 @@
|
||||
# define ENTT_ASSERT(...) (void(0))
|
||||
#elif !defined ENTT_ASSERT
|
||||
# include <cassert>
|
||||
# define ENTT_ASSERT(condition) assert(condition)
|
||||
# define ENTT_ASSERT(condition, ...) assert(condition)
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef ENTT_NO_ETO
|
||||
#ifdef ENTT_NO_ETO
|
||||
# include <type_traits>
|
||||
# define ENTT_IS_EMPTY(Type) std::is_empty<Type>
|
||||
# define ENTT_IGNORE_IF_EMPTY std::false_type
|
||||
#else
|
||||
# include <type_traits>
|
||||
# define ENTT_IS_EMPTY(Type) std::false_type
|
||||
# define ENTT_IGNORE_IF_EMPTY std::true_type
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
#define ENTT_VERSION_MAJOR 3
|
||||
#define ENTT_VERSION_MINOR 6
|
||||
#define ENTT_VERSION_MINOR 8
|
||||
#define ENTT_VERSION_PATCH 0
|
||||
|
||||
|
||||
|
||||
@@ -2,11 +2,14 @@
|
||||
#define ENTT_CORE_ANY_HPP
|
||||
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <new>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include "../config/config.h"
|
||||
#include "../core/utility.hpp"
|
||||
#include "fwd.hpp"
|
||||
#include "type_info.hpp"
|
||||
#include "type_traits.hpp"
|
||||
|
||||
@@ -14,15 +17,34 @@
|
||||
namespace entt {
|
||||
|
||||
|
||||
/*! @brief A SBO friendly, type-safe container for single values of any type. */
|
||||
class any {
|
||||
enum class operation { COPY, MOVE, DTOR, COMP, ADDR, CADDR, REF, CREF, TYPE };
|
||||
/**
|
||||
* @brief A SBO friendly, type-safe container for single values of any type.
|
||||
* @tparam Len Size of the storage reserved for the small buffer optimization.
|
||||
* @tparam Align Optional alignment requirement.
|
||||
*/
|
||||
template<std::size_t Len, std::size_t Align>
|
||||
class basic_any {
|
||||
enum class operation: std::uint8_t { COPY, MOVE, DTOR, COMP, ADDR, CADDR, TYPE };
|
||||
enum class policy: std::uint8_t { OWNER, REF, CREF };
|
||||
|
||||
using storage_type = std::aligned_storage_t<sizeof(double[2]), alignof(double[2])>;
|
||||
using vtable_type = const void *(const operation, const any &, const void *);
|
||||
using storage_type = std::aligned_storage_t<Len + !Len, Align>;
|
||||
using vtable_type = const void *(const operation, const basic_any &, void *);
|
||||
|
||||
template<typename Type>
|
||||
static constexpr auto in_situ = sizeof(Type) <= sizeof(storage_type) && std::is_nothrow_move_constructible_v<Type>;
|
||||
static constexpr bool in_situ = Len && alignof(Type) <= alignof(storage_type) && sizeof(Type) <= sizeof(storage_type) && std::is_nothrow_move_constructible_v<Type>;
|
||||
|
||||
template<typename Type>
|
||||
[[nodiscard]] static constexpr policy type_to_policy() {
|
||||
if constexpr(std::is_lvalue_reference_v<Type>) {
|
||||
if constexpr(std::is_const_v<std::remove_reference_t<Type>>) {
|
||||
return policy::CREF;
|
||||
} else {
|
||||
return policy::REF;
|
||||
}
|
||||
} else {
|
||||
return policy::OWNER;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Type>
|
||||
[[nodiscard]] static bool compare(const void *lhs, const void *rhs) {
|
||||
@@ -33,186 +55,216 @@ class any {
|
||||
}
|
||||
}
|
||||
|
||||
static type_info & as_type_info(const void *data) {
|
||||
return *const_cast<type_info *>(static_cast<const type_info *>(data));
|
||||
}
|
||||
|
||||
static any & as_any(const void *data) {
|
||||
return *const_cast<any *>(static_cast<const any *>(data));
|
||||
}
|
||||
|
||||
template<typename Type>
|
||||
static const void * basic_vtable([[maybe_unused]] const operation op, [[maybe_unused]] const any &from, [[maybe_unused]] const void *to) {
|
||||
static const void * basic_vtable([[maybe_unused]] const operation op, [[maybe_unused]] const basic_any &from, [[maybe_unused]] void *to) {
|
||||
static_assert(std::is_same_v<std::remove_reference_t<std::remove_const_t<Type>>, Type>, "Invalid type");
|
||||
|
||||
if constexpr(!std::is_void_v<Type>) {
|
||||
if constexpr(std::is_lvalue_reference_v<Type>) {
|
||||
using base_type = std::remove_reference_t<Type>;
|
||||
const Type *instance = (in_situ<Type> && from.mode == policy::OWNER)
|
||||
? ENTT_LAUNDER(reinterpret_cast<const Type *>(&from.storage))
|
||||
: static_cast<const Type *>(from.instance);
|
||||
|
||||
switch(op) {
|
||||
case operation::REF:
|
||||
case operation::CREF:
|
||||
as_any(to).vtable = (op == operation::REF) ? basic_vtable<Type> : basic_vtable<const base_type &>;
|
||||
[[fallthrough]];
|
||||
case operation::COPY:
|
||||
case operation::MOVE:
|
||||
as_any(to).instance = from.instance;
|
||||
[[fallthrough]];
|
||||
case operation::DTOR:
|
||||
break;
|
||||
case operation::COMP:
|
||||
return compare<std::remove_const_t<base_type>>(from.instance, to) ? to : nullptr;
|
||||
case operation::ADDR:
|
||||
return std::is_const_v<base_type> ? nullptr : from.instance;
|
||||
case operation::CADDR:
|
||||
return from.instance;
|
||||
case operation::TYPE:
|
||||
as_type_info(to) = type_id<std::remove_const_t<base_type>>();
|
||||
break;
|
||||
switch(op) {
|
||||
case operation::COPY:
|
||||
if constexpr(std::is_copy_constructible_v<Type>) {
|
||||
static_cast<basic_any *>(to)->emplace<Type>(*instance);
|
||||
}
|
||||
break;
|
||||
case operation::MOVE:
|
||||
if constexpr(in_situ<Type>) {
|
||||
if(from.mode == policy::OWNER) {
|
||||
return new (&static_cast<basic_any *>(to)->storage) Type{std::move(*const_cast<Type *>(instance))};
|
||||
}
|
||||
}
|
||||
} else if constexpr(in_situ<Type>) {
|
||||
#if __cpp_lib_launder >= 201606L
|
||||
auto *instance = const_cast<Type *>(std::launder(reinterpret_cast<const Type *>(&from.storage)));
|
||||
#else
|
||||
auto *instance = const_cast<Type *>(reinterpret_cast<const Type *>(&from.storage));
|
||||
#endif
|
||||
|
||||
switch(op) {
|
||||
case operation::COPY:
|
||||
new (&as_any(to).storage) Type{std::as_const(*instance)};
|
||||
break;
|
||||
case operation::MOVE:
|
||||
new (&as_any(to).storage) Type{std::move(*instance)};
|
||||
[[fallthrough]];
|
||||
case operation::DTOR:
|
||||
instance->~Type();
|
||||
break;
|
||||
case operation::COMP:
|
||||
return compare<Type>(instance, to) ? to : nullptr;
|
||||
case operation::ADDR:
|
||||
case operation::CADDR:
|
||||
return instance;
|
||||
case operation::REF:
|
||||
case operation::CREF:
|
||||
as_any(to).vtable = (op == operation::REF) ? basic_vtable<Type &> : basic_vtable<const Type &>;
|
||||
as_any(to).instance = instance;
|
||||
break;
|
||||
case operation::TYPE:
|
||||
as_type_info(to) = type_id<Type>();
|
||||
break;
|
||||
return (static_cast<basic_any *>(to)->instance = std::exchange(const_cast<basic_any &>(from).instance, nullptr));
|
||||
case operation::DTOR:
|
||||
if(from.mode == policy::OWNER) {
|
||||
if constexpr(in_situ<Type>) {
|
||||
instance->~Type();
|
||||
} else if constexpr(std::is_array_v<Type>) {
|
||||
delete[] instance;
|
||||
} else {
|
||||
delete instance;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch(op) {
|
||||
case operation::COPY:
|
||||
as_any(to).instance = new Type{*static_cast<const Type *>(from.instance)};
|
||||
break;
|
||||
case operation::REF:
|
||||
case operation::CREF:
|
||||
as_any(to).vtable = (op == operation::REF) ? basic_vtable<Type &> : basic_vtable<const Type &>;
|
||||
[[fallthrough]];
|
||||
case operation::MOVE:
|
||||
as_any(to).instance = from.instance;
|
||||
break;
|
||||
case operation::DTOR:
|
||||
delete static_cast<const Type *>(from.instance);
|
||||
break;
|
||||
case operation::COMP:
|
||||
return compare<Type>(from.instance, to) ? to : nullptr;
|
||||
case operation::ADDR:
|
||||
case operation::CADDR:
|
||||
return from.instance;
|
||||
case operation::TYPE:
|
||||
as_type_info(to) = type_id<Type>();
|
||||
break;
|
||||
break;
|
||||
case operation::COMP:
|
||||
return compare<Type>(instance, (*static_cast<const basic_any **>(to))->data()) ? to : nullptr;
|
||||
case operation::ADDR:
|
||||
if(from.mode == policy::CREF) {
|
||||
return nullptr;
|
||||
}
|
||||
[[fallthrough]];
|
||||
case operation::CADDR:
|
||||
return instance;
|
||||
case operation::TYPE:
|
||||
*static_cast<type_info *>(to) = type_id<Type>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template<typename Type, typename... Args>
|
||||
void initialize([[maybe_unused]] Args &&... args) {
|
||||
if constexpr(!std::is_void_v<Type>) {
|
||||
if constexpr(std::is_lvalue_reference_v<Type>) {
|
||||
static_assert(sizeof...(Args) == 1u && (std::is_lvalue_reference_v<Args> && ...), "Invalid arguments");
|
||||
instance = (std::addressof(args), ...);
|
||||
} else if constexpr(in_situ<Type>) {
|
||||
if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v<Type>) {
|
||||
new (&storage) Type{std::forward<Args>(args)...};
|
||||
} else {
|
||||
new (&storage) Type(std::forward<Args>(args)...);
|
||||
}
|
||||
} else {
|
||||
if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v<Type>) {
|
||||
instance = new Type{std::forward<Args>(args)...};
|
||||
} else {
|
||||
instance = new Type(std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
basic_any(const basic_any &other, const policy pol) ENTT_NOEXCEPT
|
||||
: instance{other.data()},
|
||||
vtable{other.vtable},
|
||||
mode{pol}
|
||||
{}
|
||||
|
||||
public:
|
||||
/*! @brief Size of the internal storage. */
|
||||
static constexpr auto length = Len;
|
||||
/*! @brief Alignment requirement. */
|
||||
static constexpr auto alignment = Align;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
any() ENTT_NOEXCEPT
|
||||
: any{std::in_place_type<void>}
|
||||
basic_any() ENTT_NOEXCEPT
|
||||
: instance{},
|
||||
vtable{&basic_vtable<void>},
|
||||
mode{policy::OWNER}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Constructs an any by directly initializing the new object.
|
||||
* @brief Constructs a wrapper by directly initializing the new object.
|
||||
* @tparam Type Type of object to use to initialize the wrapper.
|
||||
* @tparam Args Types of arguments to use to construct the new instance.
|
||||
* @param args Parameters to use to construct the instance.
|
||||
*/
|
||||
template<typename Type, typename... Args>
|
||||
explicit any(std::in_place_type_t<Type>, [[maybe_unused]] Args &&... args)
|
||||
: vtable{&basic_vtable<Type>},
|
||||
instance{}
|
||||
explicit basic_any(std::in_place_type_t<Type>, Args &&... args)
|
||||
: instance{},
|
||||
vtable{&basic_vtable<std::remove_const_t<std::remove_reference_t<Type>>>},
|
||||
mode{type_to_policy<Type>()}
|
||||
{
|
||||
if constexpr(!std::is_void_v<Type>) {
|
||||
if constexpr(std::is_lvalue_reference_v<Type>) {
|
||||
static_assert(sizeof...(Args) == 1u && (std::is_pointer_v<std::remove_reference_t<Args>> && ...));
|
||||
ENTT_ASSERT(((args != nullptr) && ...));
|
||||
instance = (args, ...);
|
||||
} else if constexpr(in_situ<Type>) {
|
||||
new (&storage) Type{std::forward<Args>(args)...};
|
||||
} else {
|
||||
instance = new Type{std::forward<Args>(args)...};
|
||||
}
|
||||
}
|
||||
initialize<Type>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Constructs an any that holds an unmanaged object.
|
||||
* @brief Constructs a wrapper that holds an unmanaged object.
|
||||
* @tparam Type Type of object to use to initialize the wrapper.
|
||||
* @param value An instance of an object to use to initialize the wrapper.
|
||||
*/
|
||||
template<typename Type>
|
||||
any(std::reference_wrapper<Type> value) ENTT_NOEXCEPT
|
||||
: any{std::in_place_type<Type &>, &value.get()}
|
||||
{}
|
||||
basic_any(std::reference_wrapper<Type> value) ENTT_NOEXCEPT
|
||||
: basic_any{}
|
||||
{
|
||||
// invokes deprecated assignment operator (and avoids issues with vs2017)
|
||||
*this = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Constructs an any from a given value.
|
||||
* @brief Constructs a wrapper from a given value.
|
||||
* @tparam Type Type of object to use to initialize the wrapper.
|
||||
* @param value An instance of an object to use to initialize the wrapper.
|
||||
*/
|
||||
template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_reference_t<Type>>, any>>>
|
||||
any(Type &&value)
|
||||
: any{std::in_place_type<std::remove_cv_t<std::remove_reference_t<Type>>>, std::forward<Type>(value)}
|
||||
{}
|
||||
template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::decay_t<Type>, basic_any>>>
|
||||
basic_any(Type &&value)
|
||||
: instance{},
|
||||
vtable{&basic_vtable<std::decay_t<Type>>},
|
||||
mode{policy::OWNER}
|
||||
{
|
||||
initialize<std::decay_t<Type>>(std::forward<Type>(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy constructor.
|
||||
* @param other The instance to copy from.
|
||||
*/
|
||||
any(const any &other)
|
||||
: any{}
|
||||
basic_any(const basic_any &other)
|
||||
: instance{},
|
||||
vtable{&basic_vtable<void>},
|
||||
mode{policy::OWNER}
|
||||
{
|
||||
vtable = other.vtable;
|
||||
vtable(operation::COPY, other, this);
|
||||
other.vtable(operation::COPY, other, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
* @param other The instance to move from.
|
||||
*/
|
||||
any(any &&other) ENTT_NOEXCEPT
|
||||
: any{}
|
||||
basic_any(basic_any &&other) ENTT_NOEXCEPT
|
||||
: instance{},
|
||||
vtable{other.vtable},
|
||||
mode{other.mode}
|
||||
{
|
||||
vtable = std::exchange(other.vtable, &basic_vtable<void>);
|
||||
vtable(operation::MOVE, other, this);
|
||||
}
|
||||
|
||||
/*! @brief Frees the internal storage, whatever it means. */
|
||||
~any() {
|
||||
~basic_any() {
|
||||
vtable(operation::DTOR, *this, nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assignment operator.
|
||||
* @param other The instance to assign from.
|
||||
* @brief Copy assignment operator.
|
||||
* @param other The instance to copy from.
|
||||
* @return This any object.
|
||||
*/
|
||||
any & operator=(any other) {
|
||||
swap(*this, other);
|
||||
basic_any & operator=(const basic_any &other) {
|
||||
reset();
|
||||
other.vtable(operation::COPY, other, this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move assignment operator.
|
||||
* @param other The instance to move from.
|
||||
* @return This any object.
|
||||
*/
|
||||
basic_any & operator=(basic_any &&other) ENTT_NOEXCEPT {
|
||||
std::exchange(vtable, other.vtable)(operation::DTOR, *this, nullptr);
|
||||
other.vtable(operation::MOVE, other, this);
|
||||
mode = other.mode;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Value assignment operator.
|
||||
* @tparam Type Type of object to use to initialize the wrapper.
|
||||
* @param value An instance of an object to use to initialize the wrapper.
|
||||
* @return This any object.
|
||||
*/
|
||||
template<typename Type>
|
||||
[[deprecated("Use std::in_place_type<T &>, entt::make_any<T &>, emplace<Type &> or forward_as_any instead")]]
|
||||
basic_any & operator=(std::reference_wrapper<Type> value) ENTT_NOEXCEPT {
|
||||
emplace<Type &>(value.get());
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Value assignment operator.
|
||||
* @tparam Type Type of object to use to initialize the wrapper.
|
||||
* @param value An instance of an object to use to initialize the wrapper.
|
||||
* @return This any object.
|
||||
*/
|
||||
template<typename Type>
|
||||
std::enable_if_t<!std::is_same_v<std::decay_t<Type>, basic_any>, basic_any &>
|
||||
operator=(Type &&value) {
|
||||
emplace<std::decay_t<Type>>(std::forward<Type>(value));
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -221,7 +273,7 @@ public:
|
||||
* @return The type of the contained object, if any.
|
||||
*/
|
||||
[[nodiscard]] type_info type() const ENTT_NOEXCEPT {
|
||||
type_info info;
|
||||
type_info info{};
|
||||
vtable(operation::TYPE, *this, &info);
|
||||
return info;
|
||||
}
|
||||
@@ -247,7 +299,15 @@ public:
|
||||
*/
|
||||
template<typename Type, typename... Args>
|
||||
void emplace(Args &&... args) {
|
||||
*this = any{std::in_place_type<Type>, std::forward<Args>(args)...};
|
||||
std::exchange(vtable, &basic_vtable<std::remove_const_t<std::remove_reference_t<Type>>>)(operation::DTOR, *this, nullptr);
|
||||
mode = type_to_policy<Type>();
|
||||
initialize<Type>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/*! @brief Destroys contained object */
|
||||
void reset() {
|
||||
std::exchange(vtable, &basic_vtable<void>)(operation::DTOR, *this, nullptr);
|
||||
mode = policy::OWNER;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -263,54 +323,49 @@ public:
|
||||
* @param other Wrapper with which to compare.
|
||||
* @return False if the two objects differ in their content, true otherwise.
|
||||
*/
|
||||
bool operator==(const any &other) const ENTT_NOEXCEPT {
|
||||
return type() == other.type() && (vtable(operation::COMP, *this, other.data()) == other.data());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Swaps two any objects.
|
||||
* @param lhs A valid any object.
|
||||
* @param rhs A valid any object.
|
||||
*/
|
||||
friend void swap(any &lhs, any &rhs) {
|
||||
any tmp{};
|
||||
lhs.vtable(operation::MOVE, lhs, &tmp);
|
||||
rhs.vtable(operation::MOVE, rhs, &lhs);
|
||||
lhs.vtable(operation::MOVE, tmp, &rhs);
|
||||
std::swap(lhs.vtable, rhs.vtable);
|
||||
bool operator==(const basic_any &other) const ENTT_NOEXCEPT {
|
||||
const basic_any *trampoline = &other;
|
||||
return type() == other.type() && (vtable(operation::COMP, *this, &trampoline) || !other.data());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Aliasing constructor.
|
||||
* @param other A reference to an object that isn't necessarily initialized.
|
||||
* @return An any that shares a reference to an unmanaged object.
|
||||
* @return A wrapper that shares a reference to an unmanaged object.
|
||||
*/
|
||||
[[nodiscard]] friend any as_ref(any &other) ENTT_NOEXCEPT {
|
||||
any ref{};
|
||||
other.vtable(operation::REF, other, &ref);
|
||||
return ref;
|
||||
[[nodiscard]] basic_any as_ref() ENTT_NOEXCEPT {
|
||||
return basic_any{*this, (mode == policy::CREF ? policy::CREF : policy::REF)};
|
||||
}
|
||||
|
||||
/*! @copydoc as_ref */
|
||||
[[nodiscard]] friend any as_ref(const any &other) ENTT_NOEXCEPT {
|
||||
any ref{};
|
||||
other.vtable(operation::CREF, other, &ref);
|
||||
return ref;
|
||||
[[nodiscard]] basic_any as_ref() const ENTT_NOEXCEPT {
|
||||
return basic_any{*this, policy::CREF};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if a wrapper owns its object, false otherwise.
|
||||
* @return True if the wrapper owns its object, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool owner() const ENTT_NOEXCEPT {
|
||||
return (mode == policy::OWNER);
|
||||
}
|
||||
|
||||
private:
|
||||
vtable_type *vtable;
|
||||
union { const void *instance; storage_type storage; };
|
||||
vtable_type *vtable;
|
||||
policy mode;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Checks if two wrappers differ in their content.
|
||||
* @tparam Len Size of the storage reserved for the small buffer optimization.
|
||||
* @tparam Align Alignment requirement.
|
||||
* @param lhs A wrapper, either empty or not.
|
||||
* @param rhs A wrapper, either empty or not.
|
||||
* @return True if the two wrappers differ in their content, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] inline bool operator!=(const any &lhs, const any &rhs) ENTT_NOEXCEPT {
|
||||
template<std::size_t Len, std::size_t Align>
|
||||
[[nodiscard]] inline bool operator!=(const basic_any<Len, Align> &lhs, const basic_any<Len, Align> &rhs) ENTT_NOEXCEPT {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
@@ -318,49 +373,80 @@ private:
|
||||
/**
|
||||
* @brief Performs type-safe access to the contained object.
|
||||
* @tparam Type Type to which conversion is required.
|
||||
* @tparam Len Size of the storage reserved for the small buffer optimization.
|
||||
* @tparam Align Alignment requirement.
|
||||
* @param data Target any object.
|
||||
* @return The element converted to the requested type.
|
||||
*/
|
||||
template<typename Type>
|
||||
Type any_cast(const any &data) ENTT_NOEXCEPT {
|
||||
template<typename Type, std::size_t Len, std::size_t Align>
|
||||
Type any_cast(const basic_any<Len, Align> &data) ENTT_NOEXCEPT {
|
||||
const auto * const instance = any_cast<std::remove_reference_t<Type>>(&data);
|
||||
ENTT_ASSERT(instance);
|
||||
ENTT_ASSERT(instance, "Invalid instance");
|
||||
return static_cast<Type>(*instance);
|
||||
}
|
||||
|
||||
|
||||
/*! @copydoc any_cast */
|
||||
template<typename Type>
|
||||
Type any_cast(any &data) ENTT_NOEXCEPT {
|
||||
template<typename Type, std::size_t Len, std::size_t Align>
|
||||
Type any_cast(basic_any<Len, Align> &data) ENTT_NOEXCEPT {
|
||||
// forces const on non-reference types to make them work also with wrappers for const references
|
||||
auto * const instance = any_cast<std::conditional_t<std::is_reference_v<Type>, std::remove_reference_t<Type>, const Type>>(&data);
|
||||
ENTT_ASSERT(instance);
|
||||
auto * const instance = any_cast<std::remove_reference_t<const Type>>(&data);
|
||||
ENTT_ASSERT(instance, "Invalid instance");
|
||||
return static_cast<Type>(*instance);
|
||||
}
|
||||
|
||||
|
||||
/*! @copydoc any_cast */
|
||||
template<typename Type>
|
||||
Type any_cast(any &&data) ENTT_NOEXCEPT {
|
||||
template<typename Type, std::size_t Len, std::size_t Align>
|
||||
Type any_cast(basic_any<Len, Align> &&data) ENTT_NOEXCEPT {
|
||||
// forces const on non-reference types to make them work also with wrappers for const references
|
||||
auto * const instance = any_cast<std::conditional_t<std::is_reference_v<Type>, std::remove_reference_t<Type>, const Type>>(&data);
|
||||
ENTT_ASSERT(instance);
|
||||
auto * const instance = any_cast<std::remove_reference_t<const Type>>(&data);
|
||||
ENTT_ASSERT(instance, "Invalid instance");
|
||||
return static_cast<Type>(std::move(*instance));
|
||||
}
|
||||
|
||||
|
||||
/*! @copydoc any_cast */
|
||||
template<typename Type>
|
||||
const Type * any_cast(const any *data) ENTT_NOEXCEPT {
|
||||
template<typename Type, std::size_t Len, std::size_t Align>
|
||||
const Type * any_cast(const basic_any<Len, Align> *data) ENTT_NOEXCEPT {
|
||||
return (data->type() == type_id<Type>() ? static_cast<const Type *>(data->data()) : nullptr);
|
||||
}
|
||||
|
||||
|
||||
/*! @copydoc any_cast */
|
||||
template<typename Type>
|
||||
Type * any_cast(any *data) ENTT_NOEXCEPT {
|
||||
template<typename Type, std::size_t Len, std::size_t Align>
|
||||
Type * any_cast(basic_any<Len, Align> *data) ENTT_NOEXCEPT {
|
||||
// last attempt to make wrappers for const references return their values
|
||||
return (data->type() == type_id<Type>() ? static_cast<Type *>(static_cast<constness_as_t<any, Type> *>(data)->data()) : nullptr);
|
||||
return (data->type() == type_id<Type>() ? static_cast<Type *>(static_cast<constness_as_t<basic_any<Len, Align>, Type> *>(data)->data()) : nullptr);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Constructs a wrapper from a given type, passing it all arguments.
|
||||
* @tparam Type Type of object to use to initialize the wrapper.
|
||||
* @tparam Len Size of the storage reserved for the small buffer optimization.
|
||||
* @tparam Align Optional alignment requirement.
|
||||
* @tparam Args Types of arguments to use to construct the new instance.
|
||||
* @param args Parameters to use to construct the instance.
|
||||
* @return A properly initialized wrapper for an object of the given type.
|
||||
*/
|
||||
template<typename Type, std::size_t Len = basic_any<>::length, std::size_t Align = basic_any<Len>::alignment, typename... Args>
|
||||
basic_any<Len, Align> make_any(Args &&... args) {
|
||||
return basic_any<Len, Align>{std::in_place_type<Type>, std::forward<Args>(args)...};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Forwards its argument and avoids copies for lvalue references.
|
||||
* @tparam Len Size of the storage reserved for the small buffer optimization.
|
||||
* @tparam Align Optional alignment requirement.
|
||||
* @tparam Type Type of argument to use to construct the new instance.
|
||||
* @param value Parameter to use to construct the instance.
|
||||
* @return A properly initialized and not necessarily owning wrapper.
|
||||
*/
|
||||
template<std::size_t Len = basic_any<>::length, std::size_t Align = basic_any<Len>::alignment, typename Type>
|
||||
basic_any<Len, Align> forward_as_any(Type &&value) {
|
||||
return basic_any<Len, Align>{std::in_place_type<std::conditional_t<std::is_rvalue_reference_v<Type>, std::decay_t<Type>, Type>>, std::forward<Type>(value)};
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,16 +2,25 @@
|
||||
#define ENTT_CORE_FWD_HPP
|
||||
|
||||
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
template<std::size_t Len = sizeof(double[2]), std::size_t = alignof(typename std::aligned_storage_t<Len + !Len>)>
|
||||
class basic_any;
|
||||
|
||||
|
||||
/*! @brief Alias declaration for type identifiers. */
|
||||
using id_type = ENTT_ID_TYPE;
|
||||
|
||||
|
||||
/*! @brief Alias declaration for the most common use case. */
|
||||
using any = basic_any<>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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>;
|
||||
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ template<typename Type>
|
||||
auto value = pretty_function.substr(first, pretty_function.find_last_of(ENTT_PRETTY_FUNCTION_SUFFIX) - first);
|
||||
return value;
|
||||
#else
|
||||
return std::string_view{};
|
||||
return std::string_view{""};
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -97,6 +97,9 @@ struct ENTT_API type_seq final {
|
||||
static const id_type value = internal::type_seq::next();
|
||||
return value;
|
||||
}
|
||||
|
||||
/*! @copydoc value */
|
||||
[[nodiscard]] constexpr operator id_type() const ENTT_NOEXCEPT { return value(); }
|
||||
};
|
||||
|
||||
|
||||
@@ -118,6 +121,9 @@ struct type_hash final {
|
||||
return type_seq<Type>::value();
|
||||
#endif
|
||||
}
|
||||
|
||||
/*! @copydoc value */
|
||||
[[nodiscard]] constexpr operator id_type() const ENTT_NOEXCEPT { return value(); }
|
||||
};
|
||||
|
||||
|
||||
@@ -134,6 +140,9 @@ struct type_name final {
|
||||
[[nodiscard]] static constexpr std::string_view value() ENTT_NOEXCEPT {
|
||||
return internal::type_name<Type>(0);
|
||||
}
|
||||
|
||||
/*! @copydoc value */
|
||||
[[nodiscard]] constexpr operator std::string_view() const ENTT_NOEXCEPT { return value(); }
|
||||
};
|
||||
|
||||
|
||||
@@ -176,7 +185,7 @@ public:
|
||||
* @return True if the object is properly initialized, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT {
|
||||
return !name_value.empty();
|
||||
return name_value.data() != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -236,7 +245,7 @@ private:
|
||||
* @return The type info object for the given type.
|
||||
*/
|
||||
template<typename Type>
|
||||
type_info type_id() ENTT_NOEXCEPT {
|
||||
[[nodiscard]] type_info type_id() ENTT_NOEXCEPT {
|
||||
return type_info{
|
||||
type_seq<std::remove_cv_t<std::remove_reference_t<Type>>>::value(),
|
||||
type_hash<std::remove_cv_t<std::remove_reference_t<Type>>>::value(),
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
|
||||
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include <iterator>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include "../config/config.h"
|
||||
#include "fwd.hpp"
|
||||
|
||||
@@ -12,6 +13,32 @@
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Utility class to disambiguate overloaded functions.
|
||||
* @tparam N Number of choices available.
|
||||
*/
|
||||
template<std::size_t N>
|
||||
struct choice_t
|
||||
// Unfortunately, doxygen cannot parse such a construct.
|
||||
/*! @cond TURN_OFF_DOXYGEN */
|
||||
: choice_t<N-1>
|
||||
/*! @endcond */
|
||||
{};
|
||||
|
||||
|
||||
/*! @copybrief choice_t */
|
||||
template<>
|
||||
struct choice_t<0> {};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Variable template for the choice trick.
|
||||
* @tparam N Number of choices available.
|
||||
*/
|
||||
template<std::size_t N>
|
||||
inline constexpr choice_t<N> choice{};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Identity type trait.
|
||||
*
|
||||
@@ -56,7 +83,7 @@ struct size_of<Type, std::void_t<decltype(sizeof(Type))>>
|
||||
* @tparam Type The type of which to return the size.
|
||||
*/
|
||||
template<class Type>
|
||||
inline constexpr auto size_of_v = size_of<Type>::value;
|
||||
inline constexpr std::size_t size_of_v = size_of<Type>::value;
|
||||
|
||||
|
||||
/**
|
||||
@@ -93,32 +120,6 @@ template<id_type Value>
|
||||
using tag = integral_constant<Value>;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Utility class to disambiguate overloaded functions.
|
||||
* @tparam N Number of choices available.
|
||||
*/
|
||||
template<std::size_t N>
|
||||
struct choice_t
|
||||
// Unfortunately, doxygen cannot parse such a construct.
|
||||
/*! @cond TURN_OFF_DOXYGEN */
|
||||
: choice_t<N-1>
|
||||
/*! @endcond */
|
||||
{};
|
||||
|
||||
|
||||
/*! @copybrief choice_t */
|
||||
template<>
|
||||
struct choice_t<0> {};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Variable template for the choice trick.
|
||||
* @tparam N Number of choices available.
|
||||
*/
|
||||
template<std::size_t N>
|
||||
inline constexpr choice_t<N> choice{};
|
||||
|
||||
|
||||
/**
|
||||
* @brief A class to use to push around lists of types, nothing more.
|
||||
* @tparam Type Types provided by the type list.
|
||||
@@ -287,7 +288,32 @@ struct type_list_contains<type_list<Type...>, Other>: std::disjunction<std::is_s
|
||||
* @tparam Type Type to look for.
|
||||
*/
|
||||
template<class List, typename Type>
|
||||
inline constexpr auto type_list_contains_v = type_list_contains<List, Type>::value;
|
||||
inline constexpr bool type_list_contains_v = type_list_contains<List, Type>::value;
|
||||
|
||||
|
||||
/*! @brief Primary template isn't defined on purpose. */
|
||||
template<typename...>
|
||||
struct type_list_diff;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Computes the difference between two type lists.
|
||||
* @tparam Type Types provided by the first type list.
|
||||
* @tparam Other Types provided by the second type list.
|
||||
*/
|
||||
template<typename... Type, typename... Other>
|
||||
struct type_list_diff<type_list<Type...>, type_list<Other...>> {
|
||||
/*! @brief A type list that is the difference between the two type lists. */
|
||||
using type = type_list_cat_t<std::conditional_t<type_list_contains_v<type_list<Other...>, Type>, type_list<>, type_list<Type>>...>;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Helper type.
|
||||
* @tparam List Type lists between which to compute the difference.
|
||||
*/
|
||||
template<typename... List>
|
||||
using type_list_diff_t = typename type_list_diff<List...>::type;
|
||||
|
||||
|
||||
/**
|
||||
@@ -396,28 +422,66 @@ template<typename... List>
|
||||
using value_list_cat_t = typename value_list_cat<List...>::type;
|
||||
|
||||
|
||||
/**
|
||||
* @cond TURN_OFF_DOXYGEN
|
||||
* Internal details not to be documented.
|
||||
*/
|
||||
|
||||
|
||||
namespace internal {
|
||||
|
||||
|
||||
template<typename>
|
||||
[[nodiscard]] constexpr bool is_equality_comparable(...) { return false; }
|
||||
|
||||
|
||||
template<typename Type>
|
||||
[[nodiscard]] constexpr auto is_equality_comparable(choice_t<0>)
|
||||
-> decltype(std::declval<Type>() == std::declval<Type>()) { return true; }
|
||||
|
||||
|
||||
template<typename Type>
|
||||
[[nodiscard]] constexpr auto is_equality_comparable(choice_t<1>)
|
||||
-> decltype(std::declval<typename Type::value_type>(), std::declval<Type>() == std::declval<Type>()) {
|
||||
if constexpr(std::is_same_v<typename Type::value_type, Type>) {
|
||||
return is_equality_comparable<Type>(choice<0>);
|
||||
} else {
|
||||
return is_equality_comparable<typename Type::value_type>(choice<2>);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<typename Type>
|
||||
[[nodiscard]] constexpr auto is_equality_comparable(choice_t<2>)
|
||||
-> decltype(std::declval<typename Type::mapped_type>(), std::declval<Type>() == std::declval<Type>()) {
|
||||
return is_equality_comparable<typename Type::key_type>(choice<2>) && is_equality_comparable<typename Type::mapped_type>(choice<2>);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Internal details not to be documented.
|
||||
* @endcond
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @brief Provides the member constant `value` to true if a given type is
|
||||
* equality comparable, false otherwise.
|
||||
* @tparam Type Potentially equality comparable type.
|
||||
* @tparam Type The type to test.
|
||||
*/
|
||||
template<typename Type, typename = void>
|
||||
struct is_equality_comparable: std::false_type {};
|
||||
|
||||
|
||||
/*! @copydoc is_equality_comparable */
|
||||
template<typename Type>
|
||||
struct is_equality_comparable<Type, std::void_t<decltype(std::declval<Type>() == std::declval<Type>())>>
|
||||
: std::true_type
|
||||
{};
|
||||
struct is_equality_comparable: std::bool_constant<internal::is_equality_comparable<Type>(choice<2>)> {};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Helper variable template.
|
||||
* @tparam Type Potentially equality comparable type.
|
||||
* @tparam Type The type to test.
|
||||
*/
|
||||
template<class Type>
|
||||
inline constexpr auto is_equality_comparable_v = is_equality_comparable<Type>::value;
|
||||
inline constexpr bool is_equality_comparable_v = is_equality_comparable<Type>::value;
|
||||
|
||||
|
||||
/*! @brief Same as std::is_invocable, but with tuples. */
|
||||
@@ -436,11 +500,11 @@ struct is_applicable<Func, Tuple<Args...>>: std::is_invocable<Func, Args...> {};
|
||||
|
||||
|
||||
/**
|
||||
* @copybrief is_applicable
|
||||
* @tparam Func A valid function type.
|
||||
* @tparam Tuple Tuple-like type.
|
||||
* @tparam Args The list of arguments to use to probe the function type.
|
||||
*/
|
||||
* @copybrief is_applicable
|
||||
* @tparam Func A valid function type.
|
||||
* @tparam Tuple Tuple-like type.
|
||||
* @tparam Args The list of arguments to use to probe the function type.
|
||||
*/
|
||||
template<typename Func, template<typename...> class Tuple, typename... Args>
|
||||
struct is_applicable<Func, const Tuple<Args...>>: std::is_invocable<Func, Args...> {};
|
||||
|
||||
@@ -451,7 +515,7 @@ struct is_applicable<Func, const Tuple<Args...>>: std::is_invocable<Func, Args..
|
||||
* @tparam Args The list of arguments to use to probe the function type.
|
||||
*/
|
||||
template<typename Func, typename Args>
|
||||
inline constexpr auto is_applicable_v = is_applicable<Func, Args>::value;
|
||||
inline constexpr bool is_applicable_v = is_applicable<Func, Args>::value;
|
||||
|
||||
|
||||
/*! @brief Same as std::is_invocable_r, but with tuples for arguments. */
|
||||
@@ -478,14 +542,14 @@ struct is_applicable_r<Ret, Func, std::tuple<Args...>>: std::is_invocable_r<Ret,
|
||||
* @tparam Args The list of arguments to use to probe the function type.
|
||||
*/
|
||||
template<typename Ret, typename Func, typename Args>
|
||||
inline constexpr auto is_applicable_r_v = is_applicable_r<Ret, Func, Args>::value;
|
||||
inline constexpr bool is_applicable_r_v = is_applicable_r<Ret, Func, Args>::value;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Provides the member constant `value` to true if a given type is
|
||||
* complete, false otherwise.
|
||||
* @tparam Type Potential complete type.
|
||||
*/
|
||||
* @brief Provides the member constant `value` to true if a given type is
|
||||
* complete, false otherwise.
|
||||
* @tparam Type The type to test.
|
||||
*/
|
||||
template<typename Type, typename = void>
|
||||
struct is_complete: std::false_type {};
|
||||
|
||||
@@ -496,28 +560,68 @@ struct is_complete<Type, std::void_t<decltype(sizeof(Type))>>: std::true_type {}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Helper variable template.
|
||||
* @tparam Type Potential complete type.
|
||||
*/
|
||||
* @brief Helper variable template.
|
||||
* @tparam Type The type to test.
|
||||
*/
|
||||
template<typename Type>
|
||||
inline constexpr auto is_complete_v = is_complete<Type>::value;
|
||||
inline constexpr bool is_complete_v = is_complete<Type>::value;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Provides the member constant `value` to true if a given type is empty
|
||||
* and the empty type optimization is enabled, false otherwise.
|
||||
* @tparam Type Potential empty type.
|
||||
* @brief Provides the member constant `value` to true if a given type is an
|
||||
* iterator, false otherwise.
|
||||
* @tparam Type The type to test.
|
||||
*/
|
||||
template<typename Type, typename = void>
|
||||
struct is_empty: ENTT_IS_EMPTY(Type) {};
|
||||
struct is_iterator: std::false_type {};
|
||||
|
||||
|
||||
/*! @copydoc is_iterator */
|
||||
template<typename Type>
|
||||
struct is_iterator<Type, std::void_t<typename std::iterator_traits<Type>::iterator_category>>
|
||||
: std::true_type
|
||||
{};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Helper variable template.
|
||||
* @tparam Type Potential empty type.
|
||||
* @tparam Type The type to test.
|
||||
*/
|
||||
template<typename Type>
|
||||
inline constexpr auto is_empty_v = is_empty<Type>::value;
|
||||
inline constexpr bool is_iterator_v = is_iterator<Type>::value;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Provides the member constant `value` to true if a given type is of the
|
||||
* required iterator type, false otherwise.
|
||||
* @tparam Type The type to test.
|
||||
* @tparam It Required iterator type.
|
||||
*/
|
||||
template<typename Type, typename It, typename = void>
|
||||
struct is_iterator_type: std::false_type {};
|
||||
|
||||
|
||||
/*! @copydoc is_iterator_type */
|
||||
template<typename Type, typename It>
|
||||
struct is_iterator_type<Type, It, std::enable_if_t<is_iterator_v<Type> && std::is_same_v<Type, It>>>
|
||||
: std::true_type
|
||||
{};
|
||||
|
||||
|
||||
/*! @copydoc is_iterator_type */
|
||||
template<typename Type, typename It>
|
||||
struct is_iterator_type<Type, It, std::enable_if_t<!std::is_same_v<Type, It>, std::void_t<typename It::iterator_type>>>
|
||||
: is_iterator_type<Type, typename It::iterator_type>
|
||||
{};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Helper variable template.
|
||||
* @tparam Type The type to test.
|
||||
* @tparam It Required iterator type.
|
||||
*/
|
||||
template<typename Type, typename It>
|
||||
inline constexpr bool is_iterator_type_v = is_iterator_type<Type, It>::value;
|
||||
|
||||
|
||||
/**
|
||||
@@ -527,7 +631,7 @@ inline constexpr auto is_empty_v = is_empty<Type>::value;
|
||||
*/
|
||||
template<typename To, typename From>
|
||||
struct constness_as {
|
||||
/*! @brief The type resulting from the transcription of the constness */
|
||||
/*! @brief The type resulting from the transcription of the constness. */
|
||||
using type = std::remove_const_t<To>;
|
||||
};
|
||||
|
||||
@@ -535,7 +639,7 @@ struct constness_as {
|
||||
/*! @copydoc constness_as */
|
||||
template<typename To, typename From>
|
||||
struct constness_as<To, const From> {
|
||||
/*! @brief The type resulting from the transcription of the constness */
|
||||
/*! @brief The type resulting from the transcription of the constness. */
|
||||
using type = std::add_const_t<To>;
|
||||
};
|
||||
|
||||
|
||||
@@ -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) | (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_same_v<typename storage_type<Owned>::storage_category, empty_storage_tag>, type_list<>, type_list<decltype(std::declval<storage_type<Owned>>().end())>>...>
|
||||
>;
|
||||
using reverse_iterator = iterable_group_iterator<
|
||||
typename basic_sparse_set<Entity>::reverse_iterator,
|
||||
type_list_cat_t<std::conditional_t<std::is_same_v<typename storage_type<Owned>::storage_category, empty_storage_tag>, type_list<>, type_list<decltype(std::declval<storage_type<Owned>>().rbegin())>>...>
|
||||
>;
|
||||
|
||||
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
|
||||
return length ? iterator{
|
||||
std::get<0>(pools)->basic_sparse_set<Entity>::end() - *length,
|
||||
std::get<0>(pools)->basic_common_type::end() - *length,
|
||||
std::make_tuple((std::get<storage_type<Owned> *>(pools)->end() - *length)...),
|
||||
std::make_tuple(std::get<storage_type<Get> *>(pools)...)
|
||||
} : iterator{{}, std::make_tuple(decltype(std::get<storage_type<Owned> *>(pools)->end()){}...), std::make_tuple(std::get<storage_type<Get> *>(pools)...)};
|
||||
@@ -617,7 +613,7 @@ class basic_group<Entity, exclude_t<Exclude...>, get_t<Get...>, Owned...> final
|
||||
|
||||
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
|
||||
return length ? iterator{
|
||||
std::get<0>(pools)->basic_sparse_set<Entity>::end(),
|
||||
std::get<0>(pools)->basic_common_type::end(),
|
||||
std::make_tuple((std::get<storage_type<Owned> *>(pools)->end())...),
|
||||
std::make_tuple(std::get<storage_type<Get> *>(pools)...)
|
||||
} : iterator{{}, std::make_tuple(decltype(std::get<storage_type<Owned> *>(pools)->end()){}...), std::make_tuple(std::get<storage_type<Get> *>(pools)...)};
|
||||
@@ -625,7 +621,7 @@ class basic_group<Entity, exclude_t<Exclude...>, get_t<Get...>, Owned...> final
|
||||
|
||||
[[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
|
||||
return length ? reverse_iterator{
|
||||
std::get<0>(pools)->basic_sparse_set<Entity>::rbegin(),
|
||||
std::get<0>(pools)->basic_common_type::rbegin(),
|
||||
std::make_tuple((std::get<storage_type<Owned> *>(pools)->rbegin())...),
|
||||
std::make_tuple(std::get<storage_type<Get> *>(pools)...)
|
||||
} : reverse_iterator{{}, std::make_tuple(decltype(std::get<storage_type<Owned> *>(pools)->rbegin()){}...), std::make_tuple(std::get<storage_type<Get> *>(pools)...)};
|
||||
@@ -633,7 +629,7 @@ class basic_group<Entity, exclude_t<Exclude...>, get_t<Get...>, Owned...> final
|
||||
|
||||
[[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
|
||||
return length ? reverse_iterator{
|
||||
std::get<0>(pools)->basic_sparse_set<Entity>::rbegin() + *length,
|
||||
std::get<0>(pools)->basic_common_type::rbegin() + *length,
|
||||
std::make_tuple((std::get<storage_type<Owned> *>(pools)->rbegin() + *length)...),
|
||||
std::make_tuple(std::get<storage_type<Get> *>(pools)...)
|
||||
} : reverse_iterator{{}, std::make_tuple(decltype(std::get<storage_type<Owned> *>(pools)->rbegin()){}...), std::make_tuple(std::get<storage_type<Get> *>(pools)...)};
|
||||
@@ -655,9 +651,11 @@ public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Random access iterator type. */
|
||||
using iterator = typename basic_sparse_set<Entity>::iterator;
|
||||
using iterator = typename basic_common_type::iterator;
|
||||
/*! @brief Reversed iterator type. */
|
||||
using reverse_iterator = typename basic_sparse_set<Entity>::reverse_iterator;
|
||||
using reverse_iterator = typename basic_common_type::reverse_iterator;
|
||||
/*! @brief Iterable group type. */
|
||||
using iterable_group = iterable;
|
||||
|
||||
/*! @brief Default constructor to use to create empty, invalid groups. */
|
||||
basic_group() ENTT_NOEXCEPT
|
||||
@@ -681,11 +679,7 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Direct access to the list of components of a given pool.
|
||||
*
|
||||
* The returned pointer is such that range
|
||||
* `[raw<Component>(), raw<Component>() + size())` is always a valid range,
|
||||
* even if the container is empty.<br/>
|
||||
* @brief Direct access to the raw representation offered by the storage.
|
||||
*
|
||||
* @warning
|
||||
* This function is only available for owned types.
|
||||
@@ -694,10 +688,10 @@ public:
|
||||
* @return A pointer to the array of components.
|
||||
*/
|
||||
template<typename Component>
|
||||
[[nodiscard]] Component * raw() const ENTT_NOEXCEPT {
|
||||
static_assert((std::is_same_v<Component, Owned> || ...));
|
||||
[[nodiscard]] auto raw() const ENTT_NOEXCEPT {
|
||||
static_assert((std::is_same_v<Component, Owned> || ...), "Non-owned type");
|
||||
auto *cpool = std::get<storage_type<Component> *>(pools);
|
||||
return cpool ? cpool->raw() : nullptr;
|
||||
return cpool ? cpool->raw() : decltype(cpool->raw()){};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -708,7 +702,7 @@ public:
|
||||
*
|
||||
* @return A pointer to the array of entities.
|
||||
*/
|
||||
[[nodiscard]] const entity_type * data() const ENTT_NOEXCEPT {
|
||||
[[nodiscard]] auto data() const ENTT_NOEXCEPT {
|
||||
return *this ? std::get<0>(pools)->data() : nullptr;
|
||||
}
|
||||
|
||||
@@ -721,7 +715,7 @@ public:
|
||||
* @return An iterator to the first entity of the group.
|
||||
*/
|
||||
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
|
||||
return *this ? (std::get<0>(pools)->basic_sparse_set<entity_type>::end() - *length) : iterator{};
|
||||
return *this ? (std::get<0>(pools)->basic_common_type::end() - *length) : iterator{};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -735,7 +729,7 @@ public:
|
||||
* group.
|
||||
*/
|
||||
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
|
||||
return *this ? std::get<0>(pools)->basic_sparse_set<entity_type>::end() : iterator{};
|
||||
return *this ? std::get<0>(pools)->basic_common_type::end() : iterator{};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -747,7 +741,7 @@ public:
|
||||
* @return An iterator to the first entity of the reversed group.
|
||||
*/
|
||||
[[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
|
||||
return *this ? std::get<0>(pools)->basic_sparse_set<entity_type>::rbegin() : reverse_iterator{};
|
||||
return *this ? std::get<0>(pools)->basic_common_type::rbegin() : reverse_iterator{};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -762,7 +756,7 @@ public:
|
||||
* reversed group.
|
||||
*/
|
||||
[[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
|
||||
return *this ? (std::get<0>(pools)->basic_sparse_set<entity_type>::rbegin() + *length) : reverse_iterator{};
|
||||
return *this ? (std::get<0>(pools)->basic_common_type::rbegin() + *length) : reverse_iterator{};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -839,7 +833,7 @@ public:
|
||||
*/
|
||||
template<typename... Component>
|
||||
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
|
||||
ENTT_ASSERT(contains(entt));
|
||||
ENTT_ASSERT(contains(entt), "Group does not contain entity");
|
||||
|
||||
if constexpr(sizeof...(Component) == 0) {
|
||||
return std::tuple_cat(get_as_tuple(*std::get<storage_type<Owned> *>(pools), entt)..., get_as_tuple(*std::get<storage_type<Get> *>(pools), entt)...);
|
||||
@@ -940,7 +934,7 @@ public:
|
||||
template<typename... Component, typename Compare, typename Sort = std_sort, typename... Args>
|
||||
void sort(Compare compare, Sort algo = Sort{}, Args &&... args) const {
|
||||
auto *cpool = std::get<0>(pools);
|
||||
|
||||
|
||||
if constexpr(sizeof...(Component) == 0) {
|
||||
static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>, "Invalid comparison function");
|
||||
cpool->sort_n(*length, std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||
@@ -953,7 +947,7 @@ public:
|
||||
return compare(std::forward_as_tuple(std::get<storage_type<Component> *>(pools)->get(lhs)...), std::forward_as_tuple(std::get<storage_type<Component> *>(pools)->get(rhs)...));
|
||||
}, std::move(algo), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
|
||||
[this](auto *head, auto *... other) {
|
||||
for(auto next = *length; next; --next) {
|
||||
const auto pos = next - 1;
|
||||
|
||||
@@ -24,10 +24,14 @@ namespace entt {
|
||||
*/
|
||||
template<typename Entity, typename... Type>
|
||||
struct basic_handle {
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = std::remove_const_t<Entity>;
|
||||
/*! @brief Type of registry accepted by the handle. */
|
||||
using registry_type = constness_as_t<basic_registry<entity_type>, Entity>;
|
||||
using registry_type = constness_as_t<basic_registry<std::remove_const_t<Entity>>, Entity>;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = typename registry_type::entity_type;
|
||||
/*! @brief Underlying version type. */
|
||||
using version_type = typename registry_type::version_type;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename registry_type::size_type;
|
||||
|
||||
/*! @brief Constructs an invalid handle. */
|
||||
basic_handle() ENTT_NOEXCEPT
|
||||
@@ -126,7 +130,7 @@ struct basic_handle {
|
||||
* @sa basic_registry::destroy
|
||||
* @param version A desired version upon destruction.
|
||||
*/
|
||||
void destroy(const typename registry_type::version_type version) {
|
||||
void destroy(const version_type version) {
|
||||
reg->destroy(entt, version);
|
||||
}
|
||||
|
||||
@@ -190,29 +194,37 @@ struct basic_handle {
|
||||
* @brief Removes the given components from a handle.
|
||||
* @sa basic_registry::remove
|
||||
* @tparam Component Types of components to remove.
|
||||
*/
|
||||
template<typename... Component>
|
||||
void remove() const {
|
||||
static_assert(sizeof...(Type) == 0 || (type_list_contains_v<type_list<Type...>, Component> && ...), "Invalid type");
|
||||
reg->template remove<Component...>(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes the given components from a handle.
|
||||
* @sa basic_registry::remove_if_exists
|
||||
* @tparam Component Types of components to remove.
|
||||
* @return The number of components actually removed.
|
||||
*/
|
||||
template<typename... Component>
|
||||
decltype(auto) remove_if_exists() const {
|
||||
size_type remove() const {
|
||||
static_assert(sizeof...(Type) == 0 || (type_list_contains_v<type_list<Type...>, Component> && ...), "Invalid type");
|
||||
return reg->template remove_if_exists<Component...>(entt);
|
||||
return reg->template remove<Component...>(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Erases the given components from a handle.
|
||||
* @sa basic_registry::erase
|
||||
* @tparam Component Types of components to erase.
|
||||
*/
|
||||
template<typename... Component>
|
||||
void erase() const {
|
||||
static_assert(sizeof...(Type) == 0 || (type_list_contains_v<type_list<Type...>, Component> && ...), "Invalid type");
|
||||
reg->template erase<Component...>(entt);
|
||||
}
|
||||
|
||||
/*! @copydoc remove */
|
||||
template<typename... Component>
|
||||
[[deprecated("Use ::remove instead")]]
|
||||
size_type remove_if_exists() const {
|
||||
return remove<Component...>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes all the components from a handle and makes it orphaned.
|
||||
* @sa basic_registry::remove_all
|
||||
*/
|
||||
[[deprecated("No longer supported")]]
|
||||
void remove_all() const {
|
||||
static_assert(sizeof...(Type) == 0, "Invalid operation");
|
||||
reg->remove_all(entt);
|
||||
@@ -220,25 +232,25 @@ struct basic_handle {
|
||||
|
||||
/**
|
||||
* @brief Checks if a handle has all the given components.
|
||||
* @sa basic_registry::has
|
||||
* @sa basic_registry::all_of
|
||||
* @tparam Component Components for which to perform the check.
|
||||
* @return True if the handle has all the components, false otherwise.
|
||||
*/
|
||||
template<typename... Component>
|
||||
[[nodiscard]] decltype(auto) has() const {
|
||||
return reg->template has<Component...>(entt);
|
||||
[[nodiscard]] decltype(auto) all_of() const {
|
||||
return reg->template all_of<Component...>(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a handle has at least one of the given components.
|
||||
* @sa basic_registry::any
|
||||
* @sa basic_registry::any_of
|
||||
* @tparam Component Components for which to perform the check.
|
||||
* @return True if the handle has at least one of the given components,
|
||||
* false otherwise.
|
||||
*/
|
||||
template<typename... Component>
|
||||
[[nodiscard]] decltype(auto) any() const {
|
||||
return reg->template any<Component...>(entt);
|
||||
[[nodiscard]] decltype(auto) any_of() const {
|
||||
return reg->template any_of<Component...>(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -324,7 +336,8 @@ bool operator!=(const basic_handle<Type> &lhs, const basic_handle<Other> &rhs) E
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
basic_handle(basic_registry<Entity> &, Entity) -> basic_handle<Entity>;
|
||||
basic_handle(basic_registry<Entity> &, Entity)
|
||||
-> basic_handle<Entity>;
|
||||
|
||||
|
||||
/**
|
||||
@@ -332,7 +345,8 @@ basic_handle(basic_registry<Entity> &, Entity) -> basic_handle<Entity>;
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
basic_handle(const basic_registry<Entity> &, Entity) -> basic_handle<const Entity>;
|
||||
basic_handle(const basic_registry<Entity> &, Entity)
|
||||
-> basic_handle<const Entity>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "../core/fwd.hpp"
|
||||
#include "../core/type_traits.hpp"
|
||||
#include "../signal/delegate.hpp"
|
||||
#include "registry.hpp"
|
||||
@@ -48,19 +49,18 @@ private:
|
||||
|
||||
/**
|
||||
* @brief Deduction guide.
|
||||
*
|
||||
* It allows to deduce the constness of a registry directly from the instance
|
||||
* provided to the constructor.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
as_view(basic_registry<Entity> &) ENTT_NOEXCEPT -> as_view<Entity>;
|
||||
as_view(basic_registry<Entity> &) -> as_view<Entity>;
|
||||
|
||||
|
||||
/*! @copydoc as_view */
|
||||
/**
|
||||
* @brief Deduction guide.
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
as_view(const basic_registry<Entity> &) ENTT_NOEXCEPT -> as_view<const Entity>;
|
||||
as_view(const basic_registry<Entity> &) -> as_view<const Entity>;
|
||||
|
||||
|
||||
/**
|
||||
@@ -103,19 +103,18 @@ private:
|
||||
|
||||
/**
|
||||
* @brief Deduction guide.
|
||||
*
|
||||
* It allows to deduce the constness of a registry directly from the instance
|
||||
* provided to the constructor.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
as_group(basic_registry<Entity> &) ENTT_NOEXCEPT -> as_group<Entity>;
|
||||
as_group(basic_registry<Entity> &) -> as_group<Entity>;
|
||||
|
||||
|
||||
/*! @copydoc as_group */
|
||||
/**
|
||||
* @brief Deduction guide.
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
as_group(const basic_registry<Entity> &) ENTT_NOEXCEPT -> as_group<const Entity>;
|
||||
as_group(const basic_registry<Entity> &) -> as_group<const Entity>;
|
||||
|
||||
|
||||
|
||||
@@ -137,15 +136,29 @@ void invoke(basic_registry<Entity> ®, 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) {
|
||||
return *(reg.template data<Component>() + (&component - reg.template raw<Component>()));
|
||||
Entity to_entity(const basic_registry<Entity> ®, const Component &instance) {
|
||||
const auto view = reg.template view<const Component>();
|
||||
const auto *addr = std::addressof(instance);
|
||||
|
||||
for(auto it = view.rbegin(), last = view.rend(); it < last; it += ENTT_PACKED_PAGE) {
|
||||
if(const auto dist = (addr - std::addressof(view.template get<const Component>(*it))); dist >= 0 && dist < ENTT_PACKED_PAGE) {
|
||||
return *(it + dist);
|
||||
}
|
||||
}
|
||||
|
||||
return entt::null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -179,19 +179,19 @@ class basic_observer {
|
||||
struct matcher_handler<matcher<type_list<Reject...>, type_list<Require...>, AnyOf>> {
|
||||
template<std::size_t Index>
|
||||
static void maybe_valid_if(basic_observer &obs, basic_registry<Entity> ®, const Entity entt) {
|
||||
if(reg.template has<Require...>(entt) && !reg.template any<Reject...>(entt)) {
|
||||
if(!obs.view.contains(entt)) {
|
||||
obs.view.emplace(entt);
|
||||
if(reg.template all_of<Require...>(entt) && !reg.template any_of<Reject...>(entt)) {
|
||||
if(!obs.storage.contains(entt)) {
|
||||
obs.storage.emplace(entt);
|
||||
}
|
||||
|
||||
obs.view.get(entt) |= (1 << Index);
|
||||
obs.storage.get(entt) |= (1 << Index);
|
||||
}
|
||||
}
|
||||
|
||||
template<std::size_t Index>
|
||||
static void discard_if(basic_observer &obs, basic_registry<Entity> &, const Entity entt) {
|
||||
if(obs.view.contains(entt) && !(obs.view.get(entt) &= (~(1 << Index)))) {
|
||||
obs.view.remove(entt);
|
||||
if(obs.storage.contains(entt) && !(obs.storage.get(entt) &= (~(1 << Index)))) {
|
||||
obs.storage.erase(entt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,24 +217,24 @@ class basic_observer {
|
||||
static void maybe_valid_if(basic_observer &obs, basic_registry<Entity> ®, const Entity entt) {
|
||||
if([®, entt]() {
|
||||
if constexpr(sizeof...(Ignore) == 0) {
|
||||
return reg.template has<AllOf..., Require...>(entt) && !reg.template any<NoneOf..., Reject...>(entt);
|
||||
return reg.template all_of<AllOf..., Require...>(entt) && !reg.template any_of<NoneOf..., Reject...>(entt);
|
||||
} else {
|
||||
return reg.template has<AllOf..., Require...>(entt) && ((std::is_same_v<Ignore..., NoneOf> || !reg.template any<NoneOf>(entt)) && ...) && !reg.template any<Reject...>(entt);
|
||||
return reg.template all_of<AllOf..., Require...>(entt) && ((std::is_same_v<Ignore..., NoneOf> || !reg.template any_of<NoneOf>(entt)) && ...) && !reg.template any_of<Reject...>(entt);
|
||||
}
|
||||
}())
|
||||
{
|
||||
if(!obs.view.contains(entt)) {
|
||||
obs.view.emplace(entt);
|
||||
if(!obs.storage.contains(entt)) {
|
||||
obs.storage.emplace(entt);
|
||||
}
|
||||
|
||||
obs.view.get(entt) |= (1 << Index);
|
||||
obs.storage.get(entt) |= (1 << Index);
|
||||
}
|
||||
}
|
||||
|
||||
template<std::size_t Index>
|
||||
static void discard_if(basic_observer &obs, basic_registry<Entity> &, const Entity entt) {
|
||||
if(obs.view.contains(entt) && !(obs.view.get(entt) &= (~(1 << Index)))) {
|
||||
obs.view.remove(entt);
|
||||
if(obs.storage.contains(entt) && !(obs.storage.get(entt) &= (~(1 << Index)))) {
|
||||
obs.storage.erase(entt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,7 +281,7 @@ public:
|
||||
/*! @brief Default constructor. */
|
||||
basic_observer()
|
||||
: release{},
|
||||
view{}
|
||||
storage{}
|
||||
{}
|
||||
|
||||
/*! @brief Default copy constructor, deleted on purpose. */
|
||||
@@ -325,7 +325,7 @@ public:
|
||||
void connect(basic_registry<entity_type> ®, basic_collector<Matcher...>) {
|
||||
disconnect();
|
||||
connect<Matcher...>(reg, std::index_sequence_for<Matcher...>{});
|
||||
view.clear();
|
||||
storage.clear();
|
||||
}
|
||||
|
||||
/*! @brief Disconnects an observer from the registry it keeps track of. */
|
||||
@@ -341,7 +341,7 @@ public:
|
||||
* @return Number of elements.
|
||||
*/
|
||||
[[nodiscard]] size_type size() const ENTT_NOEXCEPT {
|
||||
return view.size();
|
||||
return storage.size();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -349,7 +349,7 @@ public:
|
||||
* @return True if the observer is empty, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool empty() const ENTT_NOEXCEPT {
|
||||
return view.empty();
|
||||
return storage.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -365,7 +365,7 @@ public:
|
||||
* @return A pointer to the array of entities.
|
||||
*/
|
||||
[[nodiscard]] const entity_type * data() const ENTT_NOEXCEPT {
|
||||
return view.data();
|
||||
return storage.data();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -377,7 +377,7 @@ public:
|
||||
* @return An iterator to the first entity of the observer.
|
||||
*/
|
||||
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
|
||||
return view.basic_sparse_set<entity_type>::begin();
|
||||
return storage.basic_sparse_set<entity_type>::begin();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -391,12 +391,12 @@ public:
|
||||
* observer.
|
||||
*/
|
||||
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
|
||||
return view.basic_sparse_set<entity_type>::end();
|
||||
return storage.basic_sparse_set<entity_type>::end();
|
||||
}
|
||||
|
||||
/*! @brief Clears the underlying container. */
|
||||
void clear() ENTT_NOEXCEPT {
|
||||
view.clear();
|
||||
storage.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -436,7 +436,7 @@ public:
|
||||
|
||||
private:
|
||||
delegate<void(basic_observer &)> release;
|
||||
basic_storage<entity_type, payload_type> view;
|
||||
basic_storage<entity_type, payload_type> storage;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -18,15 +18,7 @@ namespace entt {
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
struct Storage: type_list<
|
||||
type_info() const ENTT_NOEXCEPT,
|
||||
void(basic_registry<Entity> &, const Entity *, const Entity *)
|
||||
> {
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
|
||||
struct Storage: type_list<type_info() const ENTT_NOEXCEPT> {
|
||||
/**
|
||||
* @brief Concept definition.
|
||||
* @tparam Base Opaque base class from which to inherit.
|
||||
@@ -40,18 +32,6 @@ struct Storage: type_list<
|
||||
type_info value_type() const ENTT_NOEXCEPT {
|
||||
return poly_call<0>(*this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes entities from a storage.
|
||||
* @param owner The registry that issued the request.
|
||||
* @param first An iterator to the first element of the range of
|
||||
* entities.
|
||||
* @param last An iterator past the last element of the range of
|
||||
* entities.
|
||||
*/
|
||||
void remove(basic_registry<entity_type> &owner, const entity_type *first, const entity_type *last) {
|
||||
poly_call<1>(*this, owner, first, last);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -59,10 +39,7 @@ struct Storage: type_list<
|
||||
* @tparam Type Type for which to generate an implementation.
|
||||
*/
|
||||
template<typename Type>
|
||||
using impl = value_list<
|
||||
&type_id<typename Type::value_type>,
|
||||
&Type::template remove<const entity_type *>
|
||||
>;
|
||||
using impl = value_list<&type_id<typename Type::value_type>>;
|
||||
};
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ class basic_snapshot {
|
||||
while(first != last) {
|
||||
const auto entt = *(first++);
|
||||
|
||||
if(reg->template has<Component>(entt)) {
|
||||
if(reg->template all_of<Component>(entt)) {
|
||||
std::apply(archive, std::tuple_cat(std::make_tuple(entt), view.get(entt)));
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,7 @@ class basic_snapshot {
|
||||
|
||||
while(begin != last) {
|
||||
const auto entt = *(begin++);
|
||||
((reg->template has<Component>(entt) ? ++size[Index] : size[Index]), ...);
|
||||
((reg->template all_of<Component>(entt) ? ++size[Index] : size[Index]), ...);
|
||||
}
|
||||
|
||||
(get<Component>(archive, size[Index], first, last), ...);
|
||||
@@ -99,7 +99,7 @@ public:
|
||||
archive(*first);
|
||||
}
|
||||
|
||||
archive(reg->destroyed());
|
||||
archive(reg->released());
|
||||
|
||||
return *this;
|
||||
}
|
||||
@@ -117,8 +117,14 @@ public:
|
||||
*/
|
||||
template<typename... Component, typename Archive>
|
||||
const basic_snapshot & component(Archive &archive) const {
|
||||
(component<Component>(archive, reg->template data<Component>(), reg->template data<Component>() + reg->template size<Component>()), ...);
|
||||
return *this;
|
||||
if constexpr(sizeof...(Component) == 1u) {
|
||||
const auto view = reg->template view<const Component...>();
|
||||
(component<Component>(archive, view.data(), view.data() + view.size()), ...);
|
||||
return *this;
|
||||
} else {
|
||||
(component<Component>(archive), ...);
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,7 +177,7 @@ class basic_snapshot_loader {
|
||||
while(length--) {
|
||||
archive(entt);
|
||||
const auto entity = reg->valid(entt) ? entt : reg->create(entt);
|
||||
ENTT_ASSERT(entity == entt);
|
||||
ENTT_ASSERT(entity == entt, "Entity not available for use");
|
||||
reg->template emplace<Type>(entity);
|
||||
}
|
||||
} else {
|
||||
@@ -180,7 +186,7 @@ class basic_snapshot_loader {
|
||||
while(length--) {
|
||||
archive(entt, instance);
|
||||
const auto entity = reg->valid(entt) ? entt : reg->create(entt);
|
||||
ENTT_ASSERT(entity == entt);
|
||||
ENTT_ASSERT(entity == entt, "Entity not available for use");
|
||||
reg->template emplace<Type>(entity, std::move(instance));
|
||||
}
|
||||
}
|
||||
@@ -198,7 +204,7 @@ public:
|
||||
: reg{&source}
|
||||
{
|
||||
// restoring a snapshot as a whole requires a clean registry
|
||||
ENTT_ASSERT(reg->empty());
|
||||
ENTT_ASSERT(reg->empty(), "Registry must be empty");
|
||||
}
|
||||
|
||||
/*! @brief Default move constructor. */
|
||||
@@ -267,7 +273,7 @@ public:
|
||||
*/
|
||||
const basic_snapshot_loader & orphans() const {
|
||||
reg->orphans([this](const auto entt) {
|
||||
reg->destroy(entt);
|
||||
reg->release(entt);
|
||||
});
|
||||
|
||||
return *this;
|
||||
@@ -374,7 +380,7 @@ class basic_continuous_loader {
|
||||
const auto local = ref.second.first;
|
||||
|
||||
if(reg->valid(local)) {
|
||||
reg->template remove_if_exists<Component>(local);
|
||||
reg->template remove<Component>(local);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -442,7 +448,7 @@ public:
|
||||
for(decltype(length) pos{}; pos < length; ++pos) {
|
||||
archive(entt);
|
||||
|
||||
if(const auto entity = (to_integral(entt) & traits_type::entity_mask); entity == pos) {
|
||||
if(const auto entity = traits_type::to_entity(entt); entity == pos) {
|
||||
restore(entt);
|
||||
} else {
|
||||
destroy(entt);
|
||||
@@ -523,7 +529,7 @@ public:
|
||||
*/
|
||||
basic_continuous_loader & orphans() {
|
||||
reg->orphans([this](const auto entt) {
|
||||
reg->destroy(entt);
|
||||
reg->release(entt);
|
||||
});
|
||||
|
||||
return *this;
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
#define ENTT_ENTITY_SPARSE_SET_HPP
|
||||
|
||||
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include "../config/config.h"
|
||||
#include "../core/algorithm.hpp"
|
||||
#include "../core/fwd.hpp"
|
||||
#include "entity.hpp"
|
||||
#include "fwd.hpp"
|
||||
|
||||
@@ -17,6 +17,15 @@
|
||||
namespace entt {
|
||||
|
||||
|
||||
/*! @brief Sparse set deletion policy. */
|
||||
enum class deletion_policy: std::uint8_t {
|
||||
/*! @brief Swap-and-pop deletion policy. */
|
||||
swap_and_pop = 0u,
|
||||
/*! @brief In-place deletion policy. */
|
||||
in_place = 1u
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Basic sparse set implementation.
|
||||
*
|
||||
@@ -38,27 +47,27 @@ namespace entt {
|
||||
* a sparse set. Do not make assumption on the order in any case.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Allocator Type of allocator used to manage memory and elements.
|
||||
*/
|
||||
template<typename Entity>
|
||||
template<typename Entity, typename Allocator>
|
||||
class basic_sparse_set {
|
||||
static_assert(ENTT_PAGE_SIZE && ((ENTT_PAGE_SIZE & (ENTT_PAGE_SIZE - 1)) == 0), "ENTT_PAGE_SIZE must be a power of two");
|
||||
static constexpr auto entt_per_page = ENTT_PAGE_SIZE / sizeof(Entity);
|
||||
static constexpr auto growth_factor = 1.5;
|
||||
static constexpr auto sparse_page = ENTT_SPARSE_PAGE;
|
||||
|
||||
using traits_type = entt_traits<Entity>;
|
||||
using page_type = std::unique_ptr<Entity[]>;
|
||||
|
||||
class sparse_set_iterator final {
|
||||
friend class basic_sparse_set<Entity>;
|
||||
using alloc_traits = typename std::allocator_traits<Allocator>::template rebind_traits<Entity>;
|
||||
using alloc_pointer = typename alloc_traits::pointer;
|
||||
using alloc_const_pointer = typename alloc_traits::const_pointer;
|
||||
|
||||
using packed_type = std::vector<Entity>;
|
||||
using index_type = typename traits_type::difference_type;
|
||||
using bucket_alloc_traits = typename std::allocator_traits<Allocator>::template rebind_traits<alloc_pointer>;
|
||||
using bucket_alloc_pointer = typename bucket_alloc_traits::pointer;
|
||||
|
||||
sparse_set_iterator(const packed_type &ref, const index_type idx) ENTT_NOEXCEPT
|
||||
: packed{&ref}, index{idx}
|
||||
{}
|
||||
static_assert(alloc_traits::propagate_on_container_move_assignment::value);
|
||||
static_assert(bucket_alloc_traits::propagate_on_container_move_assignment::value);
|
||||
|
||||
public:
|
||||
using difference_type = index_type;
|
||||
struct sparse_set_iterator final {
|
||||
using difference_type = typename traits_type::difference_type;
|
||||
using value_type = Entity;
|
||||
using pointer = const value_type *;
|
||||
using reference = const value_type &;
|
||||
@@ -66,6 +75,11 @@ class basic_sparse_set {
|
||||
|
||||
sparse_set_iterator() ENTT_NOEXCEPT = default;
|
||||
|
||||
sparse_set_iterator(const alloc_const_pointer *ref, const difference_type idx) ENTT_NOEXCEPT
|
||||
: packed{ref},
|
||||
index{idx}
|
||||
{}
|
||||
|
||||
sparse_set_iterator & operator++() ENTT_NOEXCEPT {
|
||||
return --index, *this;
|
||||
}
|
||||
@@ -137,7 +151,7 @@ class basic_sparse_set {
|
||||
|
||||
[[nodiscard]] pointer operator->() const {
|
||||
const auto pos = size_type(index-1u);
|
||||
return &(*packed)[pos];
|
||||
return std::addressof((*packed)[pos]);
|
||||
}
|
||||
|
||||
[[nodiscard]] reference operator*() const {
|
||||
@@ -145,59 +159,216 @@ class basic_sparse_set {
|
||||
}
|
||||
|
||||
private:
|
||||
const packed_type *packed;
|
||||
index_type index;
|
||||
const alloc_const_pointer *packed;
|
||||
difference_type index;
|
||||
};
|
||||
|
||||
[[nodiscard]] auto page(const Entity entt) const ENTT_NOEXCEPT {
|
||||
return size_type{(to_integral(entt) & traits_type::entity_mask) / entt_per_page};
|
||||
[[nodiscard]] static auto page(const Entity entt) ENTT_NOEXCEPT {
|
||||
return size_type{traits_type::to_entity(entt) / sparse_page};
|
||||
}
|
||||
|
||||
[[nodiscard]] auto offset(const Entity entt) const ENTT_NOEXCEPT {
|
||||
return size_type{to_integral(entt) & (entt_per_page - 1)};
|
||||
[[nodiscard]] static auto offset(const Entity entt) ENTT_NOEXCEPT {
|
||||
return size_type{traits_type::to_entity(entt) & (sparse_page - 1)};
|
||||
}
|
||||
|
||||
[[nodiscard]] page_type & assure(const std::size_t pos) {
|
||||
if(!(pos < sparse.size())) {
|
||||
sparse.resize(pos+1);
|
||||
[[nodiscard]] auto assure_page(const std::size_t idx) {
|
||||
if(!(idx < bucket)) {
|
||||
const size_type sz = idx + 1u;
|
||||
const auto mem = bucket_alloc_traits::allocate(bucket_allocator, sz);
|
||||
|
||||
std::uninitialized_value_construct(mem + bucket, mem + sz);
|
||||
std::uninitialized_copy(sparse, sparse + bucket, mem);
|
||||
|
||||
std::destroy(sparse, sparse + bucket);
|
||||
bucket_alloc_traits::deallocate(bucket_allocator, sparse, bucket);
|
||||
|
||||
sparse = mem;
|
||||
bucket = sz;
|
||||
}
|
||||
|
||||
if(!sparse[pos]) {
|
||||
sparse[pos].reset(new entity_type[entt_per_page]);
|
||||
// null is safe in all cases for our purposes
|
||||
for(auto *first = sparse[pos].get(), *last = first + entt_per_page; first != last; ++first) {
|
||||
*first = null;
|
||||
if(!sparse[idx]) {
|
||||
sparse[idx] = alloc_traits::allocate(allocator, sparse_page);
|
||||
std::uninitialized_fill(sparse[idx], sparse[idx] + sparse_page, null);
|
||||
}
|
||||
|
||||
return sparse[idx];
|
||||
}
|
||||
|
||||
void resize_packed(const std::size_t req) {
|
||||
ENTT_ASSERT((req != reserved) && !(req < count), "Invalid request");
|
||||
const auto mem = alloc_traits::allocate(allocator, req);
|
||||
|
||||
std::uninitialized_copy(packed, packed + count, mem);
|
||||
std::uninitialized_fill(mem + count, mem + req, tombstone);
|
||||
|
||||
std::destroy(packed, packed + reserved);
|
||||
alloc_traits::deallocate(allocator, packed, reserved);
|
||||
|
||||
packed = mem;
|
||||
reserved = req;
|
||||
}
|
||||
|
||||
void release_memory() {
|
||||
if(packed) {
|
||||
for(size_type pos{}; pos < bucket; ++pos) {
|
||||
if(sparse[pos]) {
|
||||
std::destroy(sparse[pos], sparse[pos] + sparse_page);
|
||||
alloc_traits::deallocate(allocator, sparse[pos], sparse_page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sparse[pos];
|
||||
std::destroy(packed, packed + reserved);
|
||||
std::destroy(sparse, sparse + bucket);
|
||||
alloc_traits::deallocate(allocator, packed, reserved);
|
||||
bucket_alloc_traits::deallocate(bucket_allocator, sparse, bucket);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void swap_at(const std::size_t, const std::size_t) {}
|
||||
virtual void swap_and_pop(const std::size_t) {}
|
||||
virtual void clear_all() {}
|
||||
protected:
|
||||
/**
|
||||
* @brief Swaps two entities in the internal packed array.
|
||||
* @param lhs A valid position of an entity within storage.
|
||||
* @param rhs A valid position of an entity within storage.
|
||||
*/
|
||||
virtual void swap_at([[maybe_unused]] const std::size_t lhs, [[maybe_unused]] const std::size_t rhs) {}
|
||||
|
||||
/**
|
||||
* @brief Moves an entity in the internal packed array.
|
||||
* @param from A valid position of an entity within storage.
|
||||
* @param to A valid position of an entity within storage.
|
||||
*/
|
||||
virtual void move_and_pop([[maybe_unused]] const std::size_t from, [[maybe_unused]] const std::size_t to) {}
|
||||
|
||||
/**
|
||||
* @brief Attempts to erase an entity from the internal packed array.
|
||||
* @param entt A valid entity identifier.
|
||||
* @param ud Optional user data that are forwarded as-is to derived classes.
|
||||
*/
|
||||
virtual void swap_and_pop(const Entity entt, [[maybe_unused]] void *ud) {
|
||||
auto &ref = sparse[page(entt)][offset(entt)];
|
||||
const auto pos = size_type{traits_type::to_entity(ref)};
|
||||
ENTT_ASSERT(packed[pos] == entt, "Invalid entity identifier");
|
||||
auto &last = packed[--count];
|
||||
|
||||
packed[pos] = last;
|
||||
sparse[page(last)][offset(last)] = ref;
|
||||
// lazy self-assignment guard
|
||||
ref = null;
|
||||
// unnecessary but it helps to detect nasty bugs
|
||||
ENTT_ASSERT((last = tombstone, true), "");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Attempts to erase an entity from the internal packed array.
|
||||
* @param entt A valid entity identifier.
|
||||
* @param ud Optional user data that are forwarded as-is to derived classes.
|
||||
*/
|
||||
virtual void in_place_pop(const Entity entt, [[maybe_unused]] void *ud) {
|
||||
auto &ref = sparse[page(entt)][offset(entt)];
|
||||
const auto pos = size_type{traits_type::to_entity(ref)};
|
||||
ENTT_ASSERT(packed[pos] == entt, "Invalid entity identifier");
|
||||
|
||||
packed[pos] = std::exchange(free_list, traits_type::construct(static_cast<typename traits_type::entity_type>(pos)));
|
||||
// lazy self-assignment guard
|
||||
ref = null;
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Allocator type. */
|
||||
using allocator_type = typename alloc_traits::allocator_type;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Pointer type to contained entities. */
|
||||
using pointer = alloc_const_pointer;
|
||||
/*! @brief Random access iterator type. */
|
||||
using iterator = sparse_set_iterator;
|
||||
/*! @brief Reverse iterator type. */
|
||||
using reverse_iterator = const entity_type *;
|
||||
using reverse_iterator = std::reverse_iterator<iterator>;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
basic_sparse_set() = default;
|
||||
/**
|
||||
* @brief Constructs an empty container with the given policy and allocator.
|
||||
* @param pol Type of deletion policy.
|
||||
* @param alloc Allocator to use (possibly default-constructed).
|
||||
*/
|
||||
explicit basic_sparse_set(deletion_policy pol, const allocator_type &alloc = {})
|
||||
: allocator{alloc},
|
||||
bucket_allocator{alloc},
|
||||
sparse{bucket_alloc_traits::allocate(bucket_allocator, 0u)},
|
||||
packed{alloc_traits::allocate(allocator, 0u)},
|
||||
bucket{0u},
|
||||
count{0u},
|
||||
reserved{0u},
|
||||
free_list{tombstone},
|
||||
mode{pol}
|
||||
{}
|
||||
|
||||
/*! @brief Default move constructor. */
|
||||
basic_sparse_set(basic_sparse_set &&) = default;
|
||||
/**
|
||||
* @brief Constructs an empty container with the given allocator.
|
||||
* @param alloc Allocator to use (possibly default-constructed).
|
||||
*/
|
||||
explicit basic_sparse_set(const allocator_type &alloc = {})
|
||||
: basic_sparse_set{deletion_policy::swap_and_pop, alloc}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
* @param other The instance to move from.
|
||||
*/
|
||||
basic_sparse_set(basic_sparse_set &&other) ENTT_NOEXCEPT
|
||||
: allocator{std::move(other.allocator)},
|
||||
bucket_allocator{std::move(other.bucket_allocator)},
|
||||
sparse{std::exchange(other.sparse, bucket_alloc_pointer{})},
|
||||
packed{std::exchange(other.packed, alloc_pointer{})},
|
||||
bucket{std::exchange(other.bucket, 0u)},
|
||||
count{std::exchange(other.count, 0u)},
|
||||
reserved{std::exchange(other.reserved, 0u)},
|
||||
free_list{std::exchange(other.free_list, tombstone)},
|
||||
mode{other.mode}
|
||||
{}
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
virtual ~basic_sparse_set() = default;
|
||||
virtual ~basic_sparse_set() {
|
||||
release_memory();
|
||||
}
|
||||
|
||||
/*! @brief Default move assignment operator. @return This sparse set. */
|
||||
basic_sparse_set & operator=(basic_sparse_set &&) = default;
|
||||
/**
|
||||
* @brief Move assignment operator.
|
||||
* @param other The instance to move from.
|
||||
* @return This sparse set.
|
||||
*/
|
||||
basic_sparse_set & operator=(basic_sparse_set &&other) ENTT_NOEXCEPT {
|
||||
release_memory();
|
||||
|
||||
allocator = std::move(other.allocator);
|
||||
bucket_allocator = std::move(other.bucket_allocator);
|
||||
sparse = std::exchange(other.sparse, bucket_alloc_pointer{});
|
||||
packed = std::exchange(other.packed, alloc_pointer{});
|
||||
bucket = std::exchange(other.bucket, 0u);
|
||||
count = std::exchange(other.count, 0u);
|
||||
reserved = std::exchange(other.reserved, 0u);
|
||||
free_list = std::exchange(other.free_list, tombstone);
|
||||
mode = other.mode;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the deletion policy of a sparse set.
|
||||
* @return The deletion policy of the sparse set.
|
||||
*/
|
||||
[[nodiscard]] deletion_policy policy() const ENTT_NOEXCEPT {
|
||||
return mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the next slot available for insertion.
|
||||
* @return The next slot available for insertion.
|
||||
*/
|
||||
[[nodiscard]] size_type slot() const ENTT_NOEXCEPT {
|
||||
return free_list == null ? count : size_type{traits_type::to_entity(free_list)};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Increases the capacity of a sparse set.
|
||||
@@ -208,7 +379,9 @@ public:
|
||||
* @param cap Desired capacity.
|
||||
*/
|
||||
void reserve(const size_type cap) {
|
||||
packed.reserve(cap);
|
||||
if(cap > reserved) {
|
||||
resize_packed(cap);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -217,18 +390,14 @@ public:
|
||||
* @return Capacity of the sparse set.
|
||||
*/
|
||||
[[nodiscard]] size_type capacity() const ENTT_NOEXCEPT {
|
||||
return packed.capacity();
|
||||
return reserved;
|
||||
}
|
||||
|
||||
/*! @brief Requests the removal of unused capacity. */
|
||||
void shrink_to_fit() {
|
||||
// conservative approach
|
||||
if(packed.empty()) {
|
||||
sparse.clear();
|
||||
if(count < reserved) {
|
||||
resize_packed(count);
|
||||
}
|
||||
|
||||
sparse.shrink_to_fit();
|
||||
packed.shrink_to_fit();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -242,7 +411,7 @@ public:
|
||||
* @return Extent of the sparse set.
|
||||
*/
|
||||
[[nodiscard]] size_type extent() const ENTT_NOEXCEPT {
|
||||
return sparse.size() * entt_per_page;
|
||||
return bucket * sparse_page;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -256,7 +425,7 @@ public:
|
||||
* @return Number of elements.
|
||||
*/
|
||||
[[nodiscard]] size_type size() const ENTT_NOEXCEPT {
|
||||
return packed.size();
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -264,23 +433,15 @@ public:
|
||||
* @return True if the sparse set is empty, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool empty() const ENTT_NOEXCEPT {
|
||||
return packed.empty();
|
||||
return (count == size_type{});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Direct access to the internal packed array.
|
||||
*
|
||||
* The returned pointer is such that range `[data(), data() + size())` is
|
||||
* always a valid range, even if the container is empty.
|
||||
*
|
||||
* @note
|
||||
* Entities are in the reverse order as returned by the `begin`/`end`
|
||||
* iterators.
|
||||
*
|
||||
* @return A pointer to the internal packed array.
|
||||
*/
|
||||
[[nodiscard]] const entity_type * data() const ENTT_NOEXCEPT {
|
||||
return packed.data();
|
||||
[[nodiscard]] pointer data() const ENTT_NOEXCEPT {
|
||||
return packed;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -293,8 +454,7 @@ public:
|
||||
* @return An iterator to the first entity of the internal packed array.
|
||||
*/
|
||||
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
|
||||
const typename traits_type::difference_type pos = packed.size();
|
||||
return iterator{packed, pos};
|
||||
return iterator{std::addressof(packed), static_cast<typename traits_type::difference_type>(count)};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -308,7 +468,7 @@ public:
|
||||
* internal packed array.
|
||||
*/
|
||||
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
|
||||
return iterator{packed, {}};
|
||||
return iterator{std::addressof(packed), {}};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -322,7 +482,7 @@ public:
|
||||
* array.
|
||||
*/
|
||||
[[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
|
||||
return packed.data();
|
||||
return std::make_reverse_iterator(end());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -336,7 +496,7 @@ public:
|
||||
* reversed internal packed array.
|
||||
*/
|
||||
[[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
|
||||
return rbegin() + packed.size();
|
||||
return std::make_reverse_iterator(begin());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -345,7 +505,7 @@ public:
|
||||
* @return An iterator to the given entity if it's found, past the end
|
||||
* iterator otherwise.
|
||||
*/
|
||||
[[nodiscard]] iterator find(const entity_type entt) const {
|
||||
[[nodiscard]] iterator find(const entity_type entt) const ENTT_NOEXCEPT {
|
||||
return contains(entt) ? --(end() - index(entt)) : end();
|
||||
}
|
||||
|
||||
@@ -354,10 +514,11 @@ public:
|
||||
* @param entt A valid entity identifier.
|
||||
* @return True if the sparse set contains the entity, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool contains(const entity_type entt) const {
|
||||
[[nodiscard]] bool contains(const entity_type entt) const ENTT_NOEXCEPT {
|
||||
ENTT_ASSERT(entt != tombstone && entt != null, "Invalid entity");
|
||||
const auto curr = page(entt);
|
||||
// testing against null permits to avoid accessing the packed array
|
||||
return (curr < sparse.size() && sparse[curr] && sparse[curr][offset(entt)] != null);
|
||||
// testing versions permits to avoid accessing the packed array
|
||||
return (curr < bucket && sparse[curr] && sparse[curr][offset(entt)] != null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -370,9 +531,51 @@ public:
|
||||
* @param entt A valid entity identifier.
|
||||
* @return The position of the entity in the sparse set.
|
||||
*/
|
||||
[[nodiscard]] size_type index(const entity_type entt) const {
|
||||
ENTT_ASSERT(contains(entt));
|
||||
return size_type{to_integral(sparse[page(entt)][offset(entt)])};
|
||||
[[nodiscard]] size_type index(const entity_type entt) const ENTT_NOEXCEPT {
|
||||
ENTT_ASSERT(contains(entt), "Set does not contain entity");
|
||||
return size_type{traits_type::to_entity(sparse[page(entt)][offset(entt)])};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the entity at specified location, with bounds checking.
|
||||
* @param pos The position for which to return the entity.
|
||||
* @return The entity at specified location if any, a null entity otherwise.
|
||||
*/
|
||||
[[nodiscard]] entity_type at(const size_type pos) const ENTT_NOEXCEPT {
|
||||
return pos < count ? packed[pos] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the entity at specified location, without bounds checking.
|
||||
* @param pos The position for which to return the entity.
|
||||
* @return The entity at specified location.
|
||||
*/
|
||||
[[nodiscard]] entity_type operator[](const size_type pos) const ENTT_NOEXCEPT {
|
||||
ENTT_ASSERT(pos < count, "Position is out of bounds");
|
||||
return packed[pos];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Appends an entity to a sparse set.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to assign an entity that already belongs to the sparse set
|
||||
* results in undefined behavior.
|
||||
*
|
||||
* @param entt A valid entity identifier.
|
||||
* @return The slot used for insertion.
|
||||
*/
|
||||
size_type emplace_back(const entity_type entt) {
|
||||
ENTT_ASSERT(!contains(entt), "Set already contains entity");
|
||||
|
||||
if(count == reserved) {
|
||||
const size_type sz = static_cast<size_type>(reserved * growth_factor);
|
||||
resize_packed(sz + !(sz > reserved));
|
||||
}
|
||||
|
||||
assure_page(page(entt))[offset(entt)] = traits_type::construct(static_cast<typename traits_type::entity_type>(count));
|
||||
packed[count] = entt;
|
||||
return count++;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -383,11 +586,18 @@ public:
|
||||
* results in undefined behavior.
|
||||
*
|
||||
* @param entt A valid entity identifier.
|
||||
* @return The slot used for insertion.
|
||||
*/
|
||||
void emplace(const entity_type entt) {
|
||||
ENTT_ASSERT(!contains(entt));
|
||||
assure(page(entt))[offset(entt)] = entity_type{static_cast<typename traits_type::entity_type>(packed.size())};
|
||||
packed.push_back(entt);
|
||||
size_type emplace(const entity_type entt) {
|
||||
if(free_list == null) {
|
||||
return emplace_back(entt);
|
||||
} else {
|
||||
ENTT_ASSERT(!contains(entt), "Set already contains entity");
|
||||
const auto pos = size_type{traits_type::to_entity(free_list)};
|
||||
sparse[page(entt)][offset(entt)] = traits_type::construct(static_cast<typename traits_type::entity_type>(pos));
|
||||
free_list = std::exchange(packed[pos], entt);
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -403,58 +613,99 @@ public:
|
||||
*/
|
||||
template<typename It>
|
||||
void insert(It first, It last) {
|
||||
auto next = static_cast<typename traits_type::entity_type>(packed.size());
|
||||
packed.insert(packed.end(), first, last);
|
||||
reserve(count + std::distance(first, last));
|
||||
|
||||
for(; first != last; ++first) {
|
||||
ENTT_ASSERT(!contains(*first));
|
||||
assure(page(*first))[offset(*first)] = entity_type{next++};
|
||||
const auto entt = *first;
|
||||
ENTT_ASSERT(!contains(entt), "Set already contains entity");
|
||||
assure_page(page(entt))[offset(entt)] = traits_type::construct(static_cast<typename traits_type::entity_type>(count));
|
||||
packed[count++] = entt;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes an entity from a sparse set.
|
||||
* @brief Erases an entity from a sparse set.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to remove an entity that doesn't belong to the sparse set
|
||||
* Attempting to erase an entity that doesn't belong to the sparse set
|
||||
* results in undefined behavior.
|
||||
*
|
||||
* @param entt A valid entity identifier.
|
||||
* @param ud Optional user data that are forwarded as-is to derived classes.
|
||||
*/
|
||||
void remove(const entity_type entt) {
|
||||
ENTT_ASSERT(contains(entt));
|
||||
auto &ref = sparse[page(entt)][offset(entt)];
|
||||
const auto pos = size_type{to_integral(ref)};
|
||||
const auto other = packed.back();
|
||||
|
||||
sparse[page(other)][offset(other)] = ref;
|
||||
packed[pos] = other;
|
||||
ref = null;
|
||||
|
||||
packed.pop_back();
|
||||
swap_and_pop(pos);
|
||||
void erase(const entity_type entt, void *ud = nullptr) {
|
||||
ENTT_ASSERT(contains(entt), "Set does not contain entity");
|
||||
(mode == deletion_policy::in_place) ? in_place_pop(entt, ud) : swap_and_pop(entt, ud);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes multiple entities from a pool.
|
||||
* @brief Erases entities from a set.
|
||||
*
|
||||
* @sa erase
|
||||
*
|
||||
* @tparam It Type of input iterator.
|
||||
* @param first An iterator to the first element of the range of entities.
|
||||
* @param last An iterator past the last element of the range of entities.
|
||||
* @param ud Optional user data that are forwarded as-is to derived classes.
|
||||
*/
|
||||
template<typename It>
|
||||
void remove(It first, It last) {
|
||||
if(std::distance(first, last) == std::distance(packed.begin(), packed.end())) {
|
||||
// no validity check, let it be misused
|
||||
clear();
|
||||
} else {
|
||||
for(; first != last; ++first) {
|
||||
remove(*first);
|
||||
}
|
||||
void erase(It first, It last, void *ud = nullptr) {
|
||||
for(; first != last; ++first) {
|
||||
erase(*first, ud);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Swaps two entities in the internal packed array.
|
||||
* @brief Removes an entity from a sparse set if it exists.
|
||||
* @param entt A valid entity identifier.
|
||||
* @param ud Optional user data that are forwarded as-is to derived classes.
|
||||
* @return True if the entity is actually removed, false otherwise.
|
||||
*/
|
||||
bool remove(const entity_type entt, void *ud = nullptr) {
|
||||
return contains(entt) && (erase(entt, ud), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes entities from a sparse set if they exist.
|
||||
* @tparam It Type of input iterator.
|
||||
* @param first An iterator to the first element of the range of entities.
|
||||
* @param last An iterator past the last element of the range of entities.
|
||||
* @param ud Optional user data that are forwarded as-is to derived classes.
|
||||
* @return The number of entities actually removed.
|
||||
*/
|
||||
template<typename It>
|
||||
size_type remove(It first, It last, void *ud = nullptr) {
|
||||
size_type found{};
|
||||
|
||||
for(; first != last; ++first) {
|
||||
found += remove(*first, ud);
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
/*! @brief Removes all tombstones from the packed array of a sparse set. */
|
||||
void compact() {
|
||||
size_type next = count;
|
||||
for(; next && packed[next - 1u] == tombstone; --next);
|
||||
|
||||
for(auto *it = &free_list; *it != null && next; it = std::addressof(packed[traits_type::to_entity(*it)])) {
|
||||
if(const size_type pos = traits_type::to_entity(*it); pos < next) {
|
||||
--next;
|
||||
move_and_pop(next, pos);
|
||||
std::swap(packed[next], packed[pos]);
|
||||
sparse[page(packed[pos])][offset(packed[pos])] = traits_type::construct(static_cast<const typename traits_type::entity_type>(pos));
|
||||
*it = traits_type::construct(static_cast<typename traits_type::entity_type>(next));
|
||||
for(; next && packed[next - 1u] == tombstone; --next);
|
||||
}
|
||||
}
|
||||
|
||||
free_list = tombstone;
|
||||
count = next;
|
||||
}
|
||||
|
||||
/**
|
||||
* @copybrief swap_at
|
||||
*
|
||||
* For what it's worth, this function affects both the internal sparse array
|
||||
* and the internal packed array. Users should not care of that anyway.
|
||||
@@ -467,11 +718,19 @@ public:
|
||||
* @param rhs A valid entity identifier.
|
||||
*/
|
||||
void swap(const entity_type lhs, const entity_type rhs) {
|
||||
const auto from = index(lhs);
|
||||
const auto to = index(rhs);
|
||||
std::swap(sparse[page(lhs)][offset(lhs)], sparse[page(rhs)][offset(rhs)]);
|
||||
std::swap(packed[from], packed[to]);
|
||||
ENTT_ASSERT(contains(lhs), "Set does not contain entity");
|
||||
ENTT_ASSERT(contains(rhs), "Set does not contain entity");
|
||||
|
||||
auto &entt = sparse[page(lhs)][offset(lhs)];
|
||||
auto &other = sparse[page(rhs)][offset(rhs)];
|
||||
|
||||
const auto from = size_type{traits_type::to_entity(entt)};
|
||||
const auto to = size_type{traits_type::to_entity(other)};
|
||||
|
||||
// basic no-leak guarantee (with invalid state) if swapping throws
|
||||
swap_at(from, to);
|
||||
std::swap(entt, other);
|
||||
std::swap(packed[from], packed[to]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -499,27 +758,30 @@ public:
|
||||
* @tparam Compare Type of comparison function object.
|
||||
* @tparam Sort Type of sort function object.
|
||||
* @tparam Args Types of arguments to forward to the sort function object.
|
||||
* @param count Number of elements to sort.
|
||||
* @param length Number of elements to sort.
|
||||
* @param compare A valid comparison function object.
|
||||
* @param algo A valid sort function object.
|
||||
* @param args Arguments to forward to the sort function object, if any.
|
||||
*/
|
||||
template<typename Compare, typename Sort = std_sort, typename... Args>
|
||||
void sort_n(const size_type count, Compare compare, Sort algo = Sort{}, Args &&... args) {
|
||||
ENTT_ASSERT(!(count > size()));
|
||||
void sort_n(const size_type length, Compare compare, Sort algo = Sort{}, Args &&... args) {
|
||||
// basic no-leak guarantee (with invalid state) if sorting throws
|
||||
ENTT_ASSERT(!(length > count), "Length exceeds the number of elements");
|
||||
compact();
|
||||
|
||||
algo(packed.rend() - count, packed.rend(), std::move(compare), std::forward<Args>(args)...);
|
||||
algo(std::make_reverse_iterator(packed + length), std::make_reverse_iterator(packed), std::move(compare), std::forward<Args>(args)...);
|
||||
|
||||
for(size_type pos{}; pos < count; ++pos) {
|
||||
for(size_type pos{}; pos < length; ++pos) {
|
||||
auto curr = pos;
|
||||
auto next = index(packed[curr]);
|
||||
|
||||
while(curr != next) {
|
||||
swap_at(next, index(packed[next]));
|
||||
sparse[page(packed[curr])][offset(packed[curr])] = entity_type{static_cast<typename traits_type::entity_type>(curr)};
|
||||
const auto idx = index(packed[next]);
|
||||
const auto entt = packed[curr];
|
||||
|
||||
curr = next;
|
||||
next = index(packed[curr]);
|
||||
swap_at(next, idx);
|
||||
sparse[page(entt)][offset(entt)] = traits_type::construct(static_cast<typename traits_type::entity_type>(curr));
|
||||
curr = std::exchange(next, idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -538,7 +800,7 @@ public:
|
||||
*/
|
||||
template<typename Compare, typename Sort = std_sort, typename... Args>
|
||||
void sort(Compare compare, Sort algo = Sort{}, Args &&... args) {
|
||||
sort_n(size(), std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||
sort_n(count, std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -557,34 +819,48 @@ public:
|
||||
* @param other The sparse sets that imposes the order of the entities.
|
||||
*/
|
||||
void respect(const basic_sparse_set &other) {
|
||||
compact();
|
||||
|
||||
const auto to = other.end();
|
||||
auto from = other.begin();
|
||||
|
||||
size_type pos = packed.size() - 1;
|
||||
|
||||
while(pos && from != to) {
|
||||
for(size_type pos = count - 1; pos && from != to; ++from) {
|
||||
if(contains(*from)) {
|
||||
if(*from != packed[pos]) {
|
||||
// basic no-leak guarantee (with invalid state) if swapping throws
|
||||
swap(packed[pos], *from);
|
||||
}
|
||||
|
||||
--pos;
|
||||
}
|
||||
|
||||
++from;
|
||||
}
|
||||
}
|
||||
|
||||
/*! @brief Clears a sparse set. */
|
||||
void clear() ENTT_NOEXCEPT {
|
||||
sparse.clear();
|
||||
packed.clear();
|
||||
clear_all();
|
||||
/**
|
||||
* @brief Clears a sparse set.
|
||||
* @param ud Optional user data that are forwarded as-is to derived classes.
|
||||
*/
|
||||
void clear(void *ud = nullptr) {
|
||||
for(auto &&entity: *this) {
|
||||
if(entity != tombstone) {
|
||||
in_place_pop(entity, ud);
|
||||
}
|
||||
}
|
||||
|
||||
free_list = tombstone;
|
||||
count = 0u;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<page_type> sparse;
|
||||
std::vector<entity_type> packed;
|
||||
typename alloc_traits::allocator_type allocator;
|
||||
typename bucket_alloc_traits::allocator_type bucket_allocator;
|
||||
bucket_alloc_pointer sparse;
|
||||
alloc_pointer packed;
|
||||
std::size_t bucket;
|
||||
std::size_t count;
|
||||
std::size_t reserved;
|
||||
entity_type free_list;
|
||||
deletion_policy mode;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
#define ENTT_ENTITY_STORAGE_HPP
|
||||
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "../config/config.h"
|
||||
#include "../core/algorithm.hpp"
|
||||
#include "../core/fwd.hpp"
|
||||
#include "../core/type_traits.hpp"
|
||||
#include "../signal/sigh.hpp"
|
||||
#include "component.hpp"
|
||||
#include "entity.hpp"
|
||||
#include "fwd.hpp"
|
||||
#include "sparse_set.hpp"
|
||||
@@ -21,12 +22,6 @@
|
||||
namespace entt {
|
||||
|
||||
|
||||
/*! @brief Empty storage category tag. */
|
||||
struct empty_storage_tag {};
|
||||
/*! @brief Dense storage category tag. */
|
||||
struct dense_storage_tag: empty_storage_tag {};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Basic storage implementation.
|
||||
*
|
||||
@@ -36,9 +31,7 @@ struct dense_storage_tag: empty_storage_tag {};
|
||||
* to the entities.
|
||||
*
|
||||
* @note
|
||||
* Entities and objects have the same order. It's guaranteed both in case of raw
|
||||
* access (either to entities or objects) and when using random or input access
|
||||
* iterators.
|
||||
* Entities and objects have the same order.
|
||||
*
|
||||
* @note
|
||||
* Internal data structures arrange elements to maximize performance. There are
|
||||
@@ -53,27 +46,33 @@ struct dense_storage_tag: empty_storage_tag {};
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Type Type of objects assigned to the entities.
|
||||
* @tparam Allocator Type of allocator used to manage memory and elements.
|
||||
*/
|
||||
template<typename Entity, typename Type, typename = void>
|
||||
class basic_storage: public basic_sparse_set<Entity> {
|
||||
static_assert(std::is_move_constructible_v<Type> && std::is_move_assignable_v<Type>, "The managed type must be at least move constructible and assignable");
|
||||
template<typename Entity, typename Type, typename Allocator, typename = void>
|
||||
class basic_storage_impl: public basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>> {
|
||||
static constexpr auto packed_page = ENTT_PACKED_PAGE;
|
||||
|
||||
using underlying_type = basic_sparse_set<Entity>;
|
||||
using traits_type = entt_traits<Entity>;
|
||||
using comp_traits = component_traits<Type>;
|
||||
|
||||
using underlying_type = basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>>;
|
||||
using difference_type = typename entt_traits<Entity>::difference_type;
|
||||
|
||||
using alloc_traits = typename std::allocator_traits<Allocator>::template rebind_traits<Type>;
|
||||
using alloc_pointer = typename alloc_traits::pointer;
|
||||
using alloc_const_pointer = typename alloc_traits::const_pointer;
|
||||
|
||||
using bucket_alloc_traits = typename std::allocator_traits<Allocator>::template rebind_traits<alloc_pointer>;
|
||||
using bucket_alloc_pointer = typename bucket_alloc_traits::pointer;
|
||||
|
||||
using bucket_alloc_const_type = typename std::allocator_traits<Allocator>::template rebind_alloc<alloc_const_pointer>;
|
||||
using bucket_alloc_const_pointer = typename std::allocator_traits<bucket_alloc_const_type>::const_pointer;
|
||||
|
||||
static_assert(alloc_traits::propagate_on_container_move_assignment::value);
|
||||
static_assert(bucket_alloc_traits::propagate_on_container_move_assignment::value);
|
||||
|
||||
template<typename Value>
|
||||
class storage_iterator final {
|
||||
friend class basic_storage<Entity, Type>;
|
||||
|
||||
using instance_type = constness_as_t<std::vector<Type>, Value>;
|
||||
using index_type = typename traits_type::difference_type;
|
||||
|
||||
storage_iterator(instance_type &ref, const index_type idx) ENTT_NOEXCEPT
|
||||
: instances{&ref}, index{idx}
|
||||
{}
|
||||
|
||||
public:
|
||||
using difference_type = index_type;
|
||||
struct storage_iterator final {
|
||||
using difference_type = typename basic_storage_impl::difference_type;
|
||||
using value_type = Value;
|
||||
using pointer = value_type *;
|
||||
using reference = value_type &;
|
||||
@@ -81,6 +80,11 @@ class basic_storage: public basic_sparse_set<Entity> {
|
||||
|
||||
storage_iterator() ENTT_NOEXCEPT = default;
|
||||
|
||||
storage_iterator(bucket_alloc_pointer const *ref, const typename basic_storage_impl::difference_type idx) ENTT_NOEXCEPT
|
||||
: packed{ref},
|
||||
index{idx}
|
||||
{}
|
||||
|
||||
storage_iterator & operator++() ENTT_NOEXCEPT {
|
||||
return --index, *this;
|
||||
}
|
||||
@@ -123,7 +127,7 @@ class basic_storage: public basic_sparse_set<Entity> {
|
||||
|
||||
[[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT {
|
||||
const auto pos = size_type(index-value-1);
|
||||
return (*instances)[pos];
|
||||
return (*packed)[page(pos)][offset(pos)];
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator==(const storage_iterator &other) const ENTT_NOEXCEPT {
|
||||
@@ -152,7 +156,7 @@ class basic_storage: public basic_sparse_set<Entity> {
|
||||
|
||||
[[nodiscard]] pointer operator->() const ENTT_NOEXCEPT {
|
||||
const auto pos = size_type(index-1u);
|
||||
return &(*instances)[pos];
|
||||
return std::addressof((*packed)[page(pos)][offset(pos)]);
|
||||
}
|
||||
|
||||
[[nodiscard]] reference operator*() const ENTT_NOEXCEPT {
|
||||
@@ -160,41 +164,199 @@ class basic_storage: public basic_sparse_set<Entity> {
|
||||
}
|
||||
|
||||
private:
|
||||
instance_type *instances;
|
||||
index_type index;
|
||||
bucket_alloc_pointer const *packed;
|
||||
difference_type index;
|
||||
};
|
||||
|
||||
[[nodiscard]] static auto page(const std::size_t pos) ENTT_NOEXCEPT {
|
||||
return pos / packed_page;
|
||||
}
|
||||
|
||||
[[nodiscard]] static auto offset(const std::size_t pos) ENTT_NOEXCEPT {
|
||||
return pos & (packed_page - 1);
|
||||
}
|
||||
|
||||
void release_memory() {
|
||||
if(packed) {
|
||||
// no-throw stable erase iteration
|
||||
underlying_type::clear();
|
||||
|
||||
for(size_type pos{}; pos < bucket; ++pos) {
|
||||
alloc_traits::deallocate(allocator, packed[pos], packed_page);
|
||||
bucket_alloc_traits::destroy(bucket_allocator, std::addressof(packed[pos]));
|
||||
}
|
||||
|
||||
bucket_alloc_traits::deallocate(bucket_allocator, packed, bucket);
|
||||
}
|
||||
}
|
||||
|
||||
void assure_at_least(const std::size_t last) {
|
||||
if(const auto idx = page(last - 1u); !(idx < bucket)) {
|
||||
const size_type sz = idx + 1u;
|
||||
const auto mem = bucket_alloc_traits::allocate(bucket_allocator, sz);
|
||||
std::uninitialized_copy(packed, packed + bucket, mem);
|
||||
size_type pos{};
|
||||
|
||||
ENTT_TRY {
|
||||
for(pos = bucket; pos < sz; ++pos) {
|
||||
auto pg = alloc_traits::allocate(allocator, packed_page);
|
||||
bucket_alloc_traits::construct(bucket_allocator, std::addressof(mem[pos]), pg);
|
||||
}
|
||||
} ENTT_CATCH {
|
||||
for(auto next = bucket; next < pos; ++next) {
|
||||
alloc_traits::deallocate(allocator, mem[next], packed_page);
|
||||
}
|
||||
|
||||
std::destroy(mem, mem + pos);
|
||||
bucket_alloc_traits::deallocate(bucket_allocator, mem, sz);
|
||||
ENTT_THROW;
|
||||
}
|
||||
|
||||
std::destroy(packed, packed + bucket);
|
||||
bucket_alloc_traits::deallocate(bucket_allocator, packed, bucket);
|
||||
|
||||
packed = mem;
|
||||
bucket = sz;
|
||||
}
|
||||
}
|
||||
|
||||
void release_unused_pages() {
|
||||
if(const auto length = underlying_type::size() / packed_page; length < bucket) {
|
||||
const auto mem = bucket_alloc_traits::allocate(bucket_allocator, length);
|
||||
std::uninitialized_copy(packed, packed + length, mem);
|
||||
|
||||
for(auto pos = length; pos < bucket; ++pos) {
|
||||
alloc_traits::deallocate(allocator, packed[pos], packed_page);
|
||||
bucket_alloc_traits::destroy(bucket_allocator, std::addressof(packed[pos]));
|
||||
}
|
||||
|
||||
bucket_alloc_traits::deallocate(bucket_allocator, packed, bucket);
|
||||
|
||||
packed = mem;
|
||||
bucket = length;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
auto & push_at(const std::size_t pos, Args &&... args) {
|
||||
ENTT_ASSERT(pos < (bucket * packed_page), "Out of bounds index");
|
||||
auto *instance = std::addressof(packed[page(pos)][offset(pos)]);
|
||||
|
||||
if constexpr(std::is_aggregate_v<value_type>) {
|
||||
alloc_traits::construct(allocator, instance, Type{std::forward<Args>(args)...});
|
||||
} else {
|
||||
alloc_traits::construct(allocator, instance, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
return *instance;
|
||||
}
|
||||
|
||||
void pop_at(const std::size_t pos) {
|
||||
alloc_traits::destroy(allocator, std::addressof(packed[page(pos)][offset(pos)]));
|
||||
}
|
||||
|
||||
protected:
|
||||
/*! @copydoc basic_sparse_set::swap_at */
|
||||
void swap_at(const std::size_t lhs, const std::size_t rhs) final {
|
||||
std::swap(instances[lhs], instances[rhs]);
|
||||
std::swap(packed[page(lhs)][offset(lhs)], packed[page(rhs)][offset(rhs)]);
|
||||
}
|
||||
|
||||
void swap_and_pop(const std::size_t pos) final {
|
||||
auto other = std::move(instances.back());
|
||||
instances[pos] = std::move(other);
|
||||
instances.pop_back();
|
||||
/*! @copydoc basic_sparse_set::move_and_pop */
|
||||
void move_and_pop(const std::size_t from, const std::size_t to) final {
|
||||
push_at(to, std::move(packed[page(from)][offset(from)]));
|
||||
pop_at(from);
|
||||
}
|
||||
|
||||
void clear_all() ENTT_NOEXCEPT final {
|
||||
instances.clear();
|
||||
/*! @copydoc basic_sparse_set::swap_and_pop */
|
||||
void swap_and_pop(const Entity entt, void *ud) override {
|
||||
const auto pos = underlying_type::index(entt);
|
||||
const auto last = underlying_type::size() - 1u;
|
||||
auto &&elem = packed[page(pos)][offset(pos)];
|
||||
|
||||
// support for nosy destructors
|
||||
[[maybe_unused]] auto unused = std::move(elem);
|
||||
elem = std::move(packed[page(last)][offset(last)]);
|
||||
pop_at(last);
|
||||
|
||||
underlying_type::swap_and_pop(entt, ud);
|
||||
}
|
||||
|
||||
/*! @copydoc basic_sparse_set::in_place_pop */
|
||||
void in_place_pop(const Entity entt, void *ud) override {
|
||||
const auto pos = underlying_type::index(entt);
|
||||
underlying_type::in_place_pop(entt, ud);
|
||||
// support for nosy destructors
|
||||
pop_at(pos);
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Type of the objects associated with the entities. */
|
||||
/*! @brief Allocator type. */
|
||||
using allocator_type = typename alloc_traits::allocator_type;
|
||||
/*! @brief Type of the objects assigned to entities. */
|
||||
using value_type = Type;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Pointer type to contained elements. */
|
||||
using pointer = bucket_alloc_pointer;
|
||||
/*! @brief Constant pointer type to contained elements. */
|
||||
using const_pointer = bucket_alloc_const_pointer;
|
||||
/*! @brief Random access iterator type. */
|
||||
using iterator = storage_iterator<Type>;
|
||||
using iterator = storage_iterator<value_type>;
|
||||
/*! @brief Constant random access iterator type. */
|
||||
using const_iterator = storage_iterator<const Type>;
|
||||
using const_iterator = storage_iterator<const value_type>;
|
||||
/*! @brief Reverse iterator type. */
|
||||
using reverse_iterator = Type *;
|
||||
using reverse_iterator = std::reverse_iterator<iterator>;
|
||||
/*! @brief Constant reverse iterator type. */
|
||||
using const_reverse_iterator = const Type *;
|
||||
/*! @brief Storage category. */
|
||||
using storage_category = dense_storage_tag;
|
||||
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
|
||||
|
||||
/**
|
||||
* @brief Default constructor.
|
||||
* @param alloc Allocator to use (possibly default-constructed).
|
||||
*/
|
||||
explicit basic_storage_impl(const allocator_type &alloc = {})
|
||||
: underlying_type{deletion_policy{comp_traits::in_place_delete::value}, alloc},
|
||||
allocator{alloc},
|
||||
bucket_allocator{alloc},
|
||||
packed{bucket_alloc_traits::allocate(bucket_allocator, 0u)},
|
||||
bucket{}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
* @param other The instance to move from.
|
||||
*/
|
||||
basic_storage_impl(basic_storage_impl &&other) ENTT_NOEXCEPT
|
||||
: underlying_type{std::move(other)},
|
||||
allocator{std::move(other.allocator)},
|
||||
bucket_allocator{std::move(other.bucket_allocator)},
|
||||
packed{std::exchange(other.packed, bucket_alloc_pointer{})},
|
||||
bucket{std::exchange(other.bucket, 0u)}
|
||||
{}
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
~basic_storage_impl() override {
|
||||
release_memory();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move assignment operator.
|
||||
* @param other The instance to move from.
|
||||
* @return This sparse set.
|
||||
*/
|
||||
basic_storage_impl & operator=(basic_storage_impl &&other) ENTT_NOEXCEPT {
|
||||
release_memory();
|
||||
|
||||
underlying_type::operator=(std::move(other));
|
||||
|
||||
allocator = std::move(other.allocator);
|
||||
bucket_allocator = std::move(other.bucket_allocator);
|
||||
packed = std::exchange(other.packed, bucket_alloc_pointer{});
|
||||
bucket = std::exchange(other.bucket, 0u);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Increases the capacity of a storage.
|
||||
@@ -206,34 +368,38 @@ public:
|
||||
*/
|
||||
void reserve(const size_type cap) {
|
||||
underlying_type::reserve(cap);
|
||||
instances.reserve(cap);
|
||||
|
||||
if(cap > underlying_type::size()) {
|
||||
assure_at_least(cap);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the number of elements that a storage has currently
|
||||
* allocated space for.
|
||||
* @return Capacity of the storage.
|
||||
*/
|
||||
[[nodiscard]] size_type capacity() const ENTT_NOEXCEPT {
|
||||
return bucket * packed_page;
|
||||
}
|
||||
|
||||
/*! @brief Requests the removal of unused capacity. */
|
||||
void shrink_to_fit() {
|
||||
underlying_type::shrink_to_fit();
|
||||
instances.shrink_to_fit();
|
||||
release_unused_pages();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Direct access to the array of objects.
|
||||
*
|
||||
* The returned pointer is such that range `[raw(), raw() + size())` is
|
||||
* always a valid range, even if the container is empty.
|
||||
*
|
||||
* @note
|
||||
* Objects are in the reverse order as returned by the `begin`/`end`
|
||||
* iterators.
|
||||
*
|
||||
* @return A pointer to the array of objects.
|
||||
*/
|
||||
[[nodiscard]] const value_type * raw() const ENTT_NOEXCEPT {
|
||||
return instances.data();
|
||||
[[nodiscard]] const_pointer raw() const ENTT_NOEXCEPT {
|
||||
return packed;
|
||||
}
|
||||
|
||||
/*! @copydoc raw */
|
||||
[[nodiscard]] value_type * raw() ENTT_NOEXCEPT {
|
||||
return const_cast<value_type *>(std::as_const(*this).raw());
|
||||
[[nodiscard]] pointer raw() ENTT_NOEXCEPT {
|
||||
return packed;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,8 +411,8 @@ public:
|
||||
* @return An iterator to the first instance of the internal array.
|
||||
*/
|
||||
[[nodiscard]] const_iterator cbegin() const ENTT_NOEXCEPT {
|
||||
const typename traits_type::difference_type pos = underlying_type::size();
|
||||
return const_iterator{instances, pos};
|
||||
const difference_type pos = underlying_type::size();
|
||||
return const_iterator{std::addressof(packed), pos};
|
||||
}
|
||||
|
||||
/*! @copydoc cbegin */
|
||||
@@ -256,8 +422,8 @@ public:
|
||||
|
||||
/*! @copydoc begin */
|
||||
[[nodiscard]] iterator begin() ENTT_NOEXCEPT {
|
||||
const typename traits_type::difference_type pos = underlying_type::size();
|
||||
return iterator{instances, pos};
|
||||
const difference_type pos = underlying_type::size();
|
||||
return iterator{std::addressof(packed), pos};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -271,7 +437,7 @@ public:
|
||||
* internal array.
|
||||
*/
|
||||
[[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT {
|
||||
return const_iterator{instances, {}};
|
||||
return const_iterator{std::addressof(packed), {}};
|
||||
}
|
||||
|
||||
/*! @copydoc cend */
|
||||
@@ -281,7 +447,7 @@ public:
|
||||
|
||||
/*! @copydoc end */
|
||||
[[nodiscard]] iterator end() ENTT_NOEXCEPT {
|
||||
return iterator{instances, {}};
|
||||
return iterator{std::addressof(packed), {}};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -294,7 +460,7 @@ public:
|
||||
* @return An iterator to the first instance of the reversed internal array.
|
||||
*/
|
||||
[[nodiscard]] const_reverse_iterator crbegin() const ENTT_NOEXCEPT {
|
||||
return instances.data();
|
||||
return std::make_reverse_iterator(cend());
|
||||
}
|
||||
|
||||
/*! @copydoc crbegin */
|
||||
@@ -304,7 +470,7 @@ public:
|
||||
|
||||
/*! @copydoc rbegin */
|
||||
[[nodiscard]] reverse_iterator rbegin() ENTT_NOEXCEPT {
|
||||
return instances.data();
|
||||
return std::make_reverse_iterator(end());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -318,7 +484,7 @@ public:
|
||||
* reversed internal array.
|
||||
*/
|
||||
[[nodiscard]] const_reverse_iterator crend() const ENTT_NOEXCEPT {
|
||||
return crbegin() + instances.size();
|
||||
return std::make_reverse_iterator(cbegin());
|
||||
}
|
||||
|
||||
/*! @copydoc crend */
|
||||
@@ -328,25 +494,26 @@ public:
|
||||
|
||||
/*! @copydoc rend */
|
||||
[[nodiscard]] reverse_iterator rend() ENTT_NOEXCEPT {
|
||||
return rbegin() + instances.size();
|
||||
return std::make_reverse_iterator(begin());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the object associated with an entity.
|
||||
* @brief Returns the object assigned to an entity.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that doesn't belong to the storage results in
|
||||
* undefined behavior.
|
||||
*
|
||||
* @param entt A valid entity identifier.
|
||||
* @return The object associated with the entity.
|
||||
* @return The object assigned to the entity.
|
||||
*/
|
||||
[[nodiscard]] const value_type & get(const entity_type entt) const {
|
||||
return instances[underlying_type::index(entt)];
|
||||
[[nodiscard]] const value_type & get(const entity_type entt) const ENTT_NOEXCEPT {
|
||||
const auto idx = underlying_type::index(entt);
|
||||
return packed[page(idx)][offset(idx)];
|
||||
}
|
||||
|
||||
/*! @copydoc get */
|
||||
[[nodiscard]] value_type & get(const entity_type entt) {
|
||||
[[nodiscard]] value_type & get(const entity_type entt) ENTT_NOEXCEPT {
|
||||
return const_cast<value_type &>(std::as_const(*this).get(entt));
|
||||
}
|
||||
|
||||
@@ -368,15 +535,35 @@ public:
|
||||
*/
|
||||
template<typename... Args>
|
||||
value_type & emplace(const entity_type entt, Args &&... args) {
|
||||
if constexpr(std::is_aggregate_v<value_type>) {
|
||||
instances.push_back(Type{std::forward<Args>(args)...});
|
||||
} else {
|
||||
instances.emplace_back(std::forward<Args>(args)...);
|
||||
const auto pos = underlying_type::slot();
|
||||
assure_at_least(pos + 1u);
|
||||
|
||||
auto &value = push_at(pos, std::forward<Args>(args)...);
|
||||
|
||||
ENTT_TRY {
|
||||
[[maybe_unused]] const auto curr = underlying_type::emplace(entt);
|
||||
ENTT_ASSERT(pos == curr, "Misplaced component");
|
||||
} ENTT_CATCH {
|
||||
pop_at(pos);
|
||||
ENTT_THROW;
|
||||
}
|
||||
|
||||
// entity goes after component in case constructor throws
|
||||
underlying_type::emplace(entt);
|
||||
return instances.back();
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates the instance assigned to a given entity in-place.
|
||||
* @tparam Func Types of the function objects to invoke.
|
||||
* @param entt A valid entity identifier.
|
||||
* @param func Valid function objects.
|
||||
* @return A reference to the updated instance.
|
||||
*/
|
||||
template<typename... Func>
|
||||
decltype(auto) patch(const entity_type entt, Func &&... func) {
|
||||
const auto idx = underlying_type::index(entt);
|
||||
auto &&elem = packed[page(idx)][offset(idx)];
|
||||
(std::forward<Func>(func)(elem), ...);
|
||||
return elem;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -394,9 +581,20 @@ public:
|
||||
*/
|
||||
template<typename It>
|
||||
void insert(It first, It last, const value_type &value = {}) {
|
||||
instances.insert(instances.end(), std::distance(first, last), value);
|
||||
// entities go after components in case constructors throw
|
||||
underlying_type::insert(first, last);
|
||||
const auto cap = underlying_type::size() + std::distance(first, last);
|
||||
underlying_type::reserve(cap);
|
||||
assure_at_least(cap);
|
||||
|
||||
for(; first != last; ++first) {
|
||||
push_at(underlying_type::size(), value);
|
||||
|
||||
ENTT_TRY {
|
||||
underlying_type::emplace_back(*first);
|
||||
} ENTT_CATCH {
|
||||
pop_at(underlying_type::size());
|
||||
ENTT_THROW;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -410,13 +608,23 @@ public:
|
||||
* @param first An iterator to the first element of the range of entities.
|
||||
* @param last An iterator past the last element of the range of entities.
|
||||
* @param from An iterator to the first element of the range of objects.
|
||||
* @param to An iterator past the last element of the range of objects.
|
||||
*/
|
||||
template<typename EIt, typename CIt>
|
||||
void insert(EIt first, EIt last, CIt from, CIt to) {
|
||||
instances.insert(instances.end(), from, to);
|
||||
// entities go after components in case constructors throw
|
||||
underlying_type::insert(first, last);
|
||||
template<typename EIt, typename CIt, typename = std::enable_if_t<std::is_same_v<std::decay_t<typename std::iterator_traits<CIt>::value_type>, value_type>>>
|
||||
void insert(EIt first, EIt last, CIt from) {
|
||||
const auto cap = underlying_type::size() + std::distance(first, last);
|
||||
underlying_type::reserve(cap);
|
||||
assure_at_least(cap);
|
||||
|
||||
for(; first != last; ++first, ++from) {
|
||||
push_at(underlying_type::size(), *from);
|
||||
|
||||
ENTT_TRY {
|
||||
underlying_type::emplace_back(*first);
|
||||
} ENTT_CATCH {
|
||||
pop_at(underlying_type::size());
|
||||
ENTT_THROW;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -449,19 +657,20 @@ public:
|
||||
* @tparam Compare Type of comparison function object.
|
||||
* @tparam Sort Type of sort function object.
|
||||
* @tparam Args Types of arguments to forward to the sort function object.
|
||||
* @param count Number of elements to sort.
|
||||
* @param length Number of elements to sort.
|
||||
* @param compare A valid comparison function object.
|
||||
* @param algo A valid sort function object.
|
||||
* @param args Arguments to forward to the sort function object, if any.
|
||||
*/
|
||||
template<typename Compare, typename Sort = std_sort, typename... Args>
|
||||
void sort_n(const size_type count, Compare compare, Sort algo = Sort{}, Args &&... args) {
|
||||
void sort_n(const size_type length, Compare compare, Sort algo = Sort{}, Args &&... args) {
|
||||
if constexpr(std::is_invocable_v<Compare, const value_type &, const value_type &>) {
|
||||
underlying_type::sort_n(count, [this, compare = std::move(compare)](const auto lhs, const auto rhs) {
|
||||
return compare(std::as_const(instances[underlying_type::index(lhs)]), std::as_const(instances[underlying_type::index(rhs)]));
|
||||
underlying_type::sort_n(length, [this, compare = std::move(compare)](const auto lhs, const auto rhs) {
|
||||
const auto ilhs = underlying_type::index(lhs), irhs = underlying_type::index(rhs);
|
||||
return compare(std::as_const(packed[page(ilhs)][offset(ilhs)]), std::as_const(packed[page(irhs)][offset(irhs)]));
|
||||
}, std::move(algo), std::forward<Args>(args)...);
|
||||
} else {
|
||||
underlying_type::sort_n(count, std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||
underlying_type::sort_n(length, std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,28 +688,56 @@ public:
|
||||
*/
|
||||
template<typename Compare, typename Sort = std_sort, typename... Args>
|
||||
void sort(Compare compare, Sort algo = Sort{}, Args &&... args) {
|
||||
sort_n(this->size(), std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||
sort_n(underlying_type::size(), std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<value_type> instances;
|
||||
typename alloc_traits::allocator_type allocator;
|
||||
typename bucket_alloc_traits::allocator_type bucket_allocator;
|
||||
bucket_alloc_pointer packed;
|
||||
size_type bucket;
|
||||
};
|
||||
|
||||
|
||||
/*! @copydoc basic_storage */
|
||||
template<typename Entity, typename Type>
|
||||
class basic_storage<Entity, Type, std::enable_if_t<is_empty_v<Type>>>: public basic_sparse_set<Entity> {
|
||||
using underlying_type = basic_sparse_set<Entity>;
|
||||
/*! @copydoc basic_storage_impl */
|
||||
template<typename Entity, typename Type, typename Allocator>
|
||||
class basic_storage_impl<Entity, Type, Allocator, std::enable_if_t<component_traits<Type>::ignore_if_empty::value && std::is_empty_v<Type>>>
|
||||
: public basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>>
|
||||
{
|
||||
using comp_traits = component_traits<Type>;
|
||||
using underlying_type = basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>>;
|
||||
using alloc_traits = typename std::allocator_traits<Allocator>::template rebind_traits<Type>;
|
||||
|
||||
public:
|
||||
/*! @brief Type of the objects associated with the entities. */
|
||||
/*! @brief Allocator type. */
|
||||
using allocator_type = typename alloc_traits::allocator_type;
|
||||
/*! @brief Type of the objects assigned to entities. */
|
||||
using value_type = Type;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Storage category. */
|
||||
using storage_category = empty_storage_tag;
|
||||
|
||||
/**
|
||||
* @brief Default constructor.
|
||||
* @param alloc Allocator to use (possibly default-constructed).
|
||||
*/
|
||||
explicit basic_storage_impl(const allocator_type &alloc = {})
|
||||
: underlying_type{deletion_policy{comp_traits::in_place_delete::value}, alloc}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Fake get function.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that doesn't belong to the storage results in
|
||||
* undefined behavior.
|
||||
*
|
||||
* @param entt A valid entity identifier.
|
||||
*/
|
||||
void get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT {
|
||||
ENTT_ASSERT(underlying_type::contains(entt), "Storage does not contain entity");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns an entity to a storage and constructs its object.
|
||||
@@ -519,6 +756,18 @@ public:
|
||||
underlying_type::emplace(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates the instance assigned to a given entity in-place.
|
||||
* @tparam Func Types of the function objects to invoke.
|
||||
* @param entt A valid entity identifier.
|
||||
* @param func Valid function objects.
|
||||
*/
|
||||
template<typename... Func>
|
||||
void patch([[maybe_unused]] const entity_type entt, Func &&... func) {
|
||||
ENTT_ASSERT(underlying_type::contains(entt), "Storage does not contain entity");
|
||||
(std::forward<Func>(func)(), ...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns one or more entities to a storage.
|
||||
*
|
||||
@@ -545,71 +794,51 @@ template<typename Type>
|
||||
struct storage_adapter_mixin: Type {
|
||||
static_assert(std::is_same_v<typename Type::value_type, std::decay_t<typename Type::value_type>>, "Invalid object type");
|
||||
|
||||
/*! @brief Type of the objects associated with the entities. */
|
||||
/*! @brief Type of the objects assigned to entities. */
|
||||
using value_type = typename Type::value_type;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = typename Type::entity_type;
|
||||
/*! @brief Storage category. */
|
||||
using storage_category = typename Type::storage_category;
|
||||
|
||||
/*! @brief Inherited constructors. */
|
||||
using Type::Type;
|
||||
|
||||
/**
|
||||
* @brief Assigns entities to a storage.
|
||||
* @tparam Args Types of arguments to use to construct the object.
|
||||
* @param entity A valid entity identifier.
|
||||
* @param entt A valid entity identifier.
|
||||
* @param args Parameters to use to initialize the object.
|
||||
* @return A reference to the newly created object.
|
||||
*/
|
||||
template<typename... Args>
|
||||
decltype(auto) emplace(basic_registry<entity_type> &, const entity_type entity, Args &&... args) {
|
||||
return Type::emplace(entity, std::forward<Args>(args)...);
|
||||
decltype(auto) emplace(basic_registry<entity_type> &, const entity_type entt, Args &&... args) {
|
||||
return Type::emplace(entt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns entities to a storage.
|
||||
* @tparam It Type of input iterator.
|
||||
* @tparam Args Types of arguments to use to construct the objects
|
||||
* associated with the entities.
|
||||
* @tparam Args Types of arguments to use to construct the objects assigned
|
||||
* to the entities.
|
||||
* @param first An iterator to the first element of the range of entities.
|
||||
* @param last An iterator past the last element of the range of entities.
|
||||
* @param args Parameters to use to initialize the objects associated with
|
||||
* the entities.
|
||||
* @param args Parameters to use to initialize the objects assigned to the
|
||||
* entities.
|
||||
*/
|
||||
template<typename It, typename... Args>
|
||||
void insert(basic_registry<entity_type> &, It first, It last, Args &&... args) {
|
||||
Type::insert(first, last, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes entities from a storage.
|
||||
* @param entity A valid entity identifier.
|
||||
*/
|
||||
void remove(basic_registry<entity_type> &, const entity_type entity) {
|
||||
Type::remove(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copybrief remove
|
||||
* @tparam It Type of input iterator.
|
||||
* @param first An iterator to the first element of the range of entities.
|
||||
* @param last An iterator past the last element of the range of entities.
|
||||
*/
|
||||
template<typename It>
|
||||
void remove(basic_registry<entity_type> &, It first, It last) {
|
||||
Type::remove(first, last);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Patches the given instance for an entity.
|
||||
* @tparam Func Types of the function objects to invoke.
|
||||
* @param entity A valid entity identifier.
|
||||
* @param entt A valid entity identifier.
|
||||
* @param func Valid function objects.
|
||||
* @return A reference to the patched instance.
|
||||
*/
|
||||
template<typename... Func>
|
||||
decltype(auto) patch(basic_registry<entity_type> &, const entity_type entity, [[maybe_unused]] Func &&... func) {
|
||||
auto &instance = this->get(entity);
|
||||
(std::forward<Func>(func)(instance), ...);
|
||||
return instance;
|
||||
decltype(auto) patch(basic_registry<entity_type> &, const entity_type entt, Func &&... func) {
|
||||
return Type::patch(entt, std::forward<Func>(func)...);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -619,13 +848,29 @@ struct storage_adapter_mixin: Type {
|
||||
* @tparam Type The type of the underlying storage.
|
||||
*/
|
||||
template<typename Type>
|
||||
struct sigh_storage_mixin: Type {
|
||||
class sigh_storage_mixin final: public Type {
|
||||
/*! @copydoc basic_sparse_set::swap_and_pop */
|
||||
void swap_and_pop(const typename Type::entity_type entt, void *ud) final {
|
||||
ENTT_ASSERT(ud != nullptr, "Invalid pointer to registry");
|
||||
destruction.publish(*static_cast<basic_registry<typename Type::entity_type> *>(ud), entt);
|
||||
Type::swap_and_pop(entt, ud);
|
||||
}
|
||||
|
||||
/*! @copydoc basic_sparse_set::in_place_pop */
|
||||
void in_place_pop(const typename Type::entity_type entt, void *ud) final {
|
||||
ENTT_ASSERT(ud != nullptr, "Invalid pointer to registry");
|
||||
destruction.publish(*static_cast<basic_registry<typename Type::entity_type> *>(ud), entt);
|
||||
Type::in_place_pop(entt, ud);
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Underlying value type. */
|
||||
using value_type = typename Type::value_type;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = typename Type::entity_type;
|
||||
/*! @brief Storage category. */
|
||||
using storage_category = typename Type::storage_category;
|
||||
|
||||
/*! @brief Inherited constructors. */
|
||||
using Type::Type;
|
||||
|
||||
/**
|
||||
* @brief Returns a sink object.
|
||||
@@ -693,37 +938,34 @@ struct sigh_storage_mixin: Type {
|
||||
}
|
||||
|
||||
/**
|
||||
* @copybrief storage_adapter_mixin::emplace
|
||||
* @brief Assigns entities to a storage.
|
||||
* @tparam Args Types of arguments to use to construct the object.
|
||||
* @param owner The registry that issued the request.
|
||||
* @param entity A valid entity identifier.
|
||||
* @param entt A valid entity identifier.
|
||||
* @param args Parameters to use to initialize the object.
|
||||
* @return A reference to the newly created object.
|
||||
*/
|
||||
template<typename... Args>
|
||||
decltype(auto) emplace(basic_registry<entity_type> &owner, const entity_type entity, Args &&... args) {
|
||||
Type::emplace(owner, entity, std::forward<Args>(args)...);
|
||||
construction.publish(owner, entity);
|
||||
|
||||
if constexpr(!std::is_same_v<storage_category, empty_storage_tag>) {
|
||||
return this->get(entity);
|
||||
}
|
||||
decltype(auto) emplace(basic_registry<entity_type> &owner, const entity_type entt, Args &&... args) {
|
||||
Type::emplace(entt, std::forward<Args>(args)...);
|
||||
construction.publish(owner, entt);
|
||||
return this->get(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copybrief storage_adapter_mixin::insert
|
||||
* @brief Assigns entities to a storage.
|
||||
* @tparam It Type of input iterator.
|
||||
* @tparam Args Types of arguments to use to construct the objects
|
||||
* associated with the entities.
|
||||
* @tparam Args Types of arguments to use to construct the objects assigned
|
||||
* to the entities.
|
||||
* @param owner The registry that issued the request.
|
||||
* @param first An iterator to the first element of the range of entities.
|
||||
* @param last An iterator past the last element of the range of entities.
|
||||
* @param args Parameters to use to initialize the objects associated with
|
||||
* the entities.
|
||||
* @param args Parameters to use to initialize the objects assigned to the
|
||||
* entities.
|
||||
*/
|
||||
template<typename It, typename... Args>
|
||||
void insert(basic_registry<entity_type> &owner, It first, It last, Args &&... args) {
|
||||
Type::insert(owner, first, last, std::forward<Args>(args)...);
|
||||
Type::insert(first, last, std::forward<Args>(args)...);
|
||||
|
||||
if(!construction.empty()) {
|
||||
for(; first != last; ++first) {
|
||||
@@ -733,50 +975,18 @@ struct sigh_storage_mixin: Type {
|
||||
}
|
||||
|
||||
/**
|
||||
* @copybrief storage_adapter_mixin::remove
|
||||
* @param owner The registry that issued the request.
|
||||
* @param entity A valid entity identifier.
|
||||
*/
|
||||
void remove(basic_registry<entity_type> &owner, const entity_type entity) {
|
||||
destruction.publish(owner, entity);
|
||||
Type::remove(owner, entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copybrief storage_adapter_mixin::remove
|
||||
* @tparam It Type of input iterator.
|
||||
* @param owner The registry that issued the request.
|
||||
* @param first An iterator to the first element of the range of entities.
|
||||
* @param last An iterator past the last element of the range of entities.
|
||||
*/
|
||||
template<typename It>
|
||||
void remove(basic_registry<entity_type> &owner, It first, It last) {
|
||||
if(!destruction.empty()) {
|
||||
for(auto it = first; it != last; ++it) {
|
||||
destruction.publish(owner, *it);
|
||||
}
|
||||
}
|
||||
|
||||
Type::remove(owner, first, last);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copybrief storage_adapter_mixin::patch
|
||||
* @brief Patches the given instance for an entity.
|
||||
* @tparam Func Types of the function objects to invoke.
|
||||
* @param owner The registry that issued the request.
|
||||
* @param entity A valid entity identifier.
|
||||
* @param entt A valid entity identifier.
|
||||
* @param func Valid function objects.
|
||||
* @return A reference to the patched instance.
|
||||
*/
|
||||
template<typename... Func>
|
||||
decltype(auto) patch(basic_registry<entity_type> &owner, const entity_type entity, [[maybe_unused]] Func &&... func) {
|
||||
if constexpr(std::is_same_v<storage_category, empty_storage_tag>) {
|
||||
update.publish(owner, entity);
|
||||
} else {
|
||||
Type::patch(owner, entity, std::forward<Func>(func)...);
|
||||
update.publish(owner, entity);
|
||||
return this->get(entity);
|
||||
}
|
||||
decltype(auto) patch(basic_registry<entity_type> &owner, const entity_type entt, Func &&... func) {
|
||||
Type::patch(entt, std::forward<Func>(func)...);
|
||||
update.publish(owner, entt);
|
||||
return this->get(entt);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -787,41 +997,44 @@ private:
|
||||
|
||||
|
||||
/**
|
||||
* @brief Defines the component-to-storage conversion.
|
||||
*
|
||||
* Formally:
|
||||
*
|
||||
* * If the component type is a non-const one, the member typedef type is the
|
||||
* declared storage type.
|
||||
* * If the component type is a const one, the member typedef type is the
|
||||
* declared storage type, except it has a const-qualifier added.
|
||||
*
|
||||
* @brief Storage implementation dispatcher.
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Type Type of objects assigned to the entities.
|
||||
* @tparam Allocator Type of allocator used to manage memory and elements.
|
||||
*/
|
||||
template<typename Entity, typename Type, typename = void>
|
||||
struct storage_traits {
|
||||
/*! @brief Resulting type after component-to-storage conversion. */
|
||||
using storage_type = sigh_storage_mixin<storage_adapter_mixin<basic_storage<Entity, Type>>>;
|
||||
template<typename Entity, typename Type, typename Allocator>
|
||||
struct basic_storage: basic_storage_impl<Entity, Type, Allocator> {
|
||||
using basic_storage_impl<Entity, Type, Allocator>::basic_storage_impl;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Gets the element associated with an entity from a storage, if any.
|
||||
* @brief Provides a common way to access certain properties of storage types.
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Type Type of objects managed by the storage class.
|
||||
*/
|
||||
template<typename Entity, typename Type, typename = void>
|
||||
struct storage_traits {
|
||||
/*! @brief Resulting type after component-to-storage conversion. */
|
||||
using storage_type = sigh_storage_mixin<basic_storage<Entity, Type>>;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Gets the element assigned to an entity from a storage, if any.
|
||||
* @tparam Type Storage type.
|
||||
* @param container A valid instance of a storage class.
|
||||
* @param entity A valid entity identifier.
|
||||
* @param entt A valid entity identifier.
|
||||
* @return A possibly empty tuple containing the requested element.
|
||||
*/
|
||||
template<typename Type>
|
||||
[[nodiscard]] auto get_as_tuple([[maybe_unused]] Type &container, [[maybe_unused]] const typename Type::entity_type entity) {
|
||||
static_assert(std::is_same_v<std::remove_const_t<Type>, typename storage_traits<typename Type::entity_type, typename Type::value_type>::storage_type>);
|
||||
[[nodiscard]] auto get_as_tuple([[maybe_unused]] Type &container, [[maybe_unused]] const typename Type::entity_type entt) {
|
||||
static_assert(std::is_same_v<std::remove_const_t<Type>, typename storage_traits<typename Type::entity_type, typename Type::value_type>::storage_type>, "Invalid storage");
|
||||
|
||||
if constexpr(std::is_base_of_v<dense_storage_tag, typename Type::storage_category>) {
|
||||
return std::forward_as_tuple(container.get(entity));
|
||||
} else {
|
||||
static_assert(std::is_base_of_v<empty_storage_tag, typename Type::storage_category>, "Unknown storage category");
|
||||
if constexpr(std::is_void_v<decltype(container.get({}))>) {
|
||||
return std::make_tuple();
|
||||
} else {
|
||||
return std::forward_as_tuple(container.get(entt));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "../core/type_traits.hpp"
|
||||
#include "component.hpp"
|
||||
#include "entity.hpp"
|
||||
#include "fwd.hpp"
|
||||
#include "sparse_set.hpp"
|
||||
@@ -21,13 +22,154 @@ namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief View.
|
||||
* @cond TURN_OFF_DOXYGEN
|
||||
* Internal details not to be documented.
|
||||
*/
|
||||
|
||||
|
||||
namespace internal {
|
||||
|
||||
|
||||
template<typename Policy, typename It, std::size_t AllOf, std::size_t NoneOf>
|
||||
class view_iterator final {
|
||||
using basic_common_type = basic_sparse_set<typename std::iterator_traits<It>::value_type>;
|
||||
|
||||
[[nodiscard]] bool valid() const {
|
||||
const auto entt = *it;
|
||||
return Policy::accept(entt)
|
||||
&& std::apply([entt](const auto *... curr) { return (curr->contains(entt) && ...); }, pools)
|
||||
&& std::apply([entt](const auto *... curr) { return (!curr->contains(entt) && ...); }, filter);
|
||||
}
|
||||
|
||||
public:
|
||||
using iterator_type = It;
|
||||
using difference_type = typename std::iterator_traits<It>::difference_type;
|
||||
using value_type = typename std::iterator_traits<It>::value_type;
|
||||
using pointer = typename std::iterator_traits<It>::pointer;
|
||||
using reference = typename std::iterator_traits<It>::reference;
|
||||
using iterator_category = std::bidirectional_iterator_tag;
|
||||
|
||||
view_iterator() ENTT_NOEXCEPT
|
||||
: first{},
|
||||
last{},
|
||||
it{},
|
||||
pools{},
|
||||
filter{}
|
||||
{}
|
||||
|
||||
view_iterator(It from, It to, It curr, std::array<const basic_common_type *, AllOf> all_of, std::array<const basic_common_type *, NoneOf> none_of) ENTT_NOEXCEPT
|
||||
: first{from},
|
||||
last{to},
|
||||
it{curr},
|
||||
pools{all_of},
|
||||
filter{none_of}
|
||||
{
|
||||
if(it != last && !valid()) {
|
||||
++(*this);
|
||||
}
|
||||
}
|
||||
|
||||
view_iterator & operator++() ENTT_NOEXCEPT {
|
||||
while(++it != last && !valid());
|
||||
return *this;
|
||||
}
|
||||
|
||||
view_iterator operator++(int) ENTT_NOEXCEPT {
|
||||
view_iterator orig = *this;
|
||||
return ++(*this), orig;
|
||||
}
|
||||
|
||||
view_iterator & operator--() ENTT_NOEXCEPT {
|
||||
while(--it != first && !valid());
|
||||
return *this;
|
||||
}
|
||||
|
||||
view_iterator operator--(int) ENTT_NOEXCEPT {
|
||||
view_iterator orig = *this;
|
||||
return operator--(), orig;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator==(const view_iterator &other) const ENTT_NOEXCEPT {
|
||||
return other.it == it;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator!=(const view_iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
[[nodiscard]] pointer operator->() const {
|
||||
return &*it;
|
||||
}
|
||||
|
||||
[[nodiscard]] reference operator*() const {
|
||||
return *operator->();
|
||||
}
|
||||
|
||||
private:
|
||||
It first;
|
||||
It last;
|
||||
It it;
|
||||
std::array<const basic_common_type *, AllOf> pools;
|
||||
std::array<const basic_common_type *, NoneOf> filter;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Internal details not to be documented.
|
||||
* @endcond
|
||||
*/
|
||||
|
||||
|
||||
/*! @brief Stable storage policy, aimed at pointer stability. */
|
||||
struct stable_storage_policy {
|
||||
/**
|
||||
* @cond TURN_OFF_DOXYGEN
|
||||
* Internal details not to be documented.
|
||||
*/
|
||||
template<typename Entity>
|
||||
[[nodiscard]] static constexpr bool accept(const Entity entity) ENTT_NOEXCEPT {
|
||||
return entity != tombstone;
|
||||
}
|
||||
/**
|
||||
* Internal details not to be documented.
|
||||
* @endcond
|
||||
*/
|
||||
};
|
||||
|
||||
|
||||
/*! @brief Packed storage policy, aimed at faster linear iteration. */
|
||||
struct packed_storage_policy {
|
||||
/**
|
||||
* @cond TURN_OFF_DOXYGEN
|
||||
* Internal details not to be documented.
|
||||
*/
|
||||
template<typename Entity>
|
||||
[[nodiscard]] static constexpr bool accept(const Entity) ENTT_NOEXCEPT {
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Internal details not to be documented.
|
||||
* @endcond
|
||||
*/
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief View implementation.
|
||||
*
|
||||
* Primary template isn't defined on purpose. All the specializations give a
|
||||
* compile-time error, but for a few reasonable cases.
|
||||
*/
|
||||
template<typename...>
|
||||
class basic_view;
|
||||
class basic_view_impl;
|
||||
|
||||
|
||||
/*! @brief View implementation dispatcher. */
|
||||
template<typename...>
|
||||
struct basic_view;
|
||||
|
||||
|
||||
/**
|
||||
@@ -59,181 +201,103 @@ class basic_view;
|
||||
* Lifetime of a view must not overcome that of the registry that generated it.
|
||||
* In any other case, attempting to use a view results in undefined behavior.
|
||||
*
|
||||
* @tparam Policy Common (stricter) storage policy.
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Exclude Types of components used to filter the view.
|
||||
* @tparam Component Types of components iterated by the view.
|
||||
*/
|
||||
template<typename Entity, typename... Exclude, typename... Component>
|
||||
class basic_view<Entity, exclude_t<Exclude...>, Component...> final {
|
||||
template<typename Policy, typename Entity, typename... Exclude, typename... Component>
|
||||
class basic_view_impl<Policy, Entity, exclude_t<Exclude...>, Component...> {
|
||||
using basic_common_type = basic_sparse_set<Entity>;
|
||||
|
||||
template<typename Comp>
|
||||
using storage_type = constness_as_t<typename storage_traits<Entity, std::remove_const_t<Comp>>::storage_type, Comp>;
|
||||
|
||||
using unchecked_type = std::array<const basic_sparse_set<Entity> *, (sizeof...(Component) - 1)>;
|
||||
|
||||
template<typename It>
|
||||
class view_iterator final {
|
||||
friend class basic_view<Entity, exclude_t<Exclude...>, Component...>;
|
||||
|
||||
view_iterator(It from, It to, It curr, unchecked_type other, const std::tuple<const storage_type<Exclude> *...> &ignore) ENTT_NOEXCEPT
|
||||
: first{from},
|
||||
last{to},
|
||||
it{curr},
|
||||
unchecked{other},
|
||||
filter{ignore}
|
||||
{
|
||||
if(it != last && !valid()) {
|
||||
++(*this);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool valid() const {
|
||||
const auto entt = *it;
|
||||
|
||||
return std::all_of(unchecked.cbegin(), unchecked.cend(), [entt](const basic_sparse_set<Entity> *curr) { return curr->contains(entt); })
|
||||
&& !(std::get<const storage_type<Exclude> *>(filter)->contains(entt) || ...);
|
||||
}
|
||||
|
||||
public:
|
||||
using difference_type = typename std::iterator_traits<It>::difference_type;
|
||||
using value_type = typename std::iterator_traits<It>::value_type;
|
||||
using pointer = typename std::iterator_traits<It>::pointer;
|
||||
using reference = typename std::iterator_traits<It>::reference;
|
||||
using iterator_category = std::bidirectional_iterator_tag;
|
||||
|
||||
view_iterator() ENTT_NOEXCEPT
|
||||
: view_iterator{{}, {}, {}, {}, {}}
|
||||
{}
|
||||
|
||||
view_iterator & operator++() ENTT_NOEXCEPT {
|
||||
while(++it != last && !valid());
|
||||
return *this;
|
||||
}
|
||||
|
||||
view_iterator operator++(int) ENTT_NOEXCEPT {
|
||||
view_iterator orig = *this;
|
||||
return ++(*this), orig;
|
||||
}
|
||||
|
||||
view_iterator & operator--() ENTT_NOEXCEPT {
|
||||
while(--it != first && !valid());
|
||||
return *this;
|
||||
}
|
||||
|
||||
view_iterator operator--(int) ENTT_NOEXCEPT {
|
||||
view_iterator orig = *this;
|
||||
return operator--(), orig;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator==(const view_iterator &other) const ENTT_NOEXCEPT {
|
||||
return other.it == it;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator!=(const view_iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
[[nodiscard]] pointer operator->() const {
|
||||
return &*it;
|
||||
}
|
||||
|
||||
[[nodiscard]] reference operator*() const {
|
||||
return *operator->();
|
||||
}
|
||||
|
||||
private:
|
||||
It first;
|
||||
It last;
|
||||
It it;
|
||||
unchecked_type unchecked;
|
||||
std::tuple<const storage_type<Exclude> *...> filter;
|
||||
};
|
||||
|
||||
class iterable_view final {
|
||||
friend class basic_view<Entity, exclude_t<Exclude...>, Component...>;
|
||||
|
||||
class iterable final {
|
||||
template<typename It>
|
||||
class iterable_view_iterator final {
|
||||
friend class iterable_view;
|
||||
|
||||
iterable_view_iterator(It from, const basic_view &parent) ENTT_NOEXCEPT
|
||||
: it{from},
|
||||
view{parent}
|
||||
{}
|
||||
|
||||
public:
|
||||
struct iterable_iterator final {
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using value_type = decltype(std::tuple_cat(std::tuple<Entity>{}, std::declval<basic_view>().get({})));
|
||||
using value_type = decltype(std::tuple_cat(std::tuple<Entity>{}, std::declval<basic_view_impl>().get({})));
|
||||
using pointer = void;
|
||||
using reference = value_type;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
|
||||
iterable_view_iterator & operator++() ENTT_NOEXCEPT {
|
||||
iterable_iterator(It from, const basic_view_impl *parent) ENTT_NOEXCEPT
|
||||
: it{from},
|
||||
view{parent}
|
||||
{}
|
||||
|
||||
iterable_iterator & operator++() ENTT_NOEXCEPT {
|
||||
return ++it, *this;
|
||||
}
|
||||
|
||||
iterable_view_iterator operator++(int) ENTT_NOEXCEPT {
|
||||
iterable_view_iterator orig = *this;
|
||||
iterable_iterator operator++(int) ENTT_NOEXCEPT {
|
||||
iterable_iterator orig = *this;
|
||||
return ++(*this), orig;
|
||||
}
|
||||
|
||||
[[nodiscard]] reference operator*() const ENTT_NOEXCEPT {
|
||||
return std::tuple_cat(std::make_tuple(*it), view.get(*it));
|
||||
return std::tuple_cat(std::make_tuple(*it), view->get(*it));
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator==(const iterable_view_iterator &other) const ENTT_NOEXCEPT {
|
||||
[[nodiscard]] bool operator==(const iterable_iterator &other) const ENTT_NOEXCEPT {
|
||||
return other.it == it;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator!=(const iterable_view_iterator &other) const ENTT_NOEXCEPT {
|
||||
[[nodiscard]] bool operator!=(const iterable_iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
private:
|
||||
It it;
|
||||
const basic_view view;
|
||||
const basic_view_impl *view;
|
||||
};
|
||||
|
||||
iterable_view(const basic_view &parent)
|
||||
public:
|
||||
using iterator = iterable_iterator<internal::view_iterator<Policy, typename basic_common_type::iterator, sizeof...(Component) - 1u, sizeof...(Exclude)>>;
|
||||
using reverse_iterator = iterable_iterator<internal::view_iterator<Policy, typename basic_common_type::reverse_iterator, sizeof...(Component) - 1u, sizeof...(Exclude)>>;
|
||||
|
||||
iterable(const basic_view_impl &parent)
|
||||
: view{parent}
|
||||
{}
|
||||
|
||||
public:
|
||||
using iterator = iterable_view_iterator<view_iterator<typename basic_sparse_set<Entity>::iterator>>;
|
||||
using reverse_iterator = iterable_view_iterator<view_iterator<typename basic_sparse_set<Entity>::reverse_iterator>>;
|
||||
|
||||
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
|
||||
return { view.begin(), view };
|
||||
return { view.begin(), &view };
|
||||
}
|
||||
|
||||
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
|
||||
return { view.end(), view };
|
||||
return { view.end(), &view };
|
||||
}
|
||||
|
||||
[[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
|
||||
return { view.rbegin(), view };
|
||||
return { view.rbegin(), &view };
|
||||
}
|
||||
|
||||
[[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
|
||||
return { view.rend(), view };
|
||||
return { view.rend(), &view };
|
||||
}
|
||||
|
||||
private:
|
||||
const basic_view view;
|
||||
const basic_view_impl view;
|
||||
};
|
||||
|
||||
[[nodiscard]] const basic_sparse_set<Entity> * candidate() const ENTT_NOEXCEPT {
|
||||
return (std::min)({ static_cast<const basic_sparse_set<entity_type> *>(std::get<storage_type<Component> *>(pools))... }, [](const auto *lhs, const auto *rhs) {
|
||||
[[nodiscard]] const auto * candidate() const ENTT_NOEXCEPT {
|
||||
return (std::min)({ static_cast<const basic_common_type *>(std::get<storage_type<Component> *>(pools))... }, [](const auto *lhs, const auto *rhs) {
|
||||
return lhs->size() < rhs->size();
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] unchecked_type unchecked(const basic_sparse_set<Entity> *cpool) const {
|
||||
[[nodiscard]] auto pools_to_unchecked_array() const ENTT_NOEXCEPT {
|
||||
std::size_t pos{};
|
||||
unchecked_type other{};
|
||||
(static_cast<void>(std::get<storage_type<Component> *>(pools) == cpool ? nullptr : (other[pos] = std::get<storage_type<Component> *>(pools), other[pos++])), ...);
|
||||
std::array<const basic_common_type *, sizeof...(Component) - 1u> other{};
|
||||
(static_cast<void>(std::get<storage_type<Component> *>(pools) == view ? void() : void(other[pos++] = std::get<storage_type<Component> *>(pools))), ...);
|
||||
return other;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto filter_to_array() const ENTT_NOEXCEPT {
|
||||
return std::array<const basic_common_type *, sizeof...(Exclude)>{std::get<const storage_type<Exclude> *>(filter)...};
|
||||
}
|
||||
|
||||
template<typename Comp, typename It>
|
||||
[[nodiscard]] auto dispatch_get([[maybe_unused]] It &it, [[maybe_unused]] const Entity entt) const {
|
||||
if constexpr(std::is_same_v<typename std::iterator_traits<It>::value_type, typename storage_type<Comp>::value_type>) {
|
||||
@@ -245,35 +309,33 @@ class basic_view<Entity, exclude_t<Exclude...>, Component...> final {
|
||||
|
||||
template<typename Comp, typename Func>
|
||||
void traverse(Func func) const {
|
||||
if(*this) {
|
||||
if constexpr(std::is_same_v<typename storage_type<Comp>::storage_category, empty_storage_tag>) {
|
||||
for(const auto entt: static_cast<const basic_sparse_set<entity_type> &>(*std::get<storage_type<Comp> *>(pools))) {
|
||||
if(((std::is_same_v<Comp, Component> || std::get<storage_type<Component> *>(pools)->contains(entt)) && ...)
|
||||
&& !(std::get<const storage_type<Exclude> *>(filter)->contains(entt) || ...))
|
||||
{
|
||||
if constexpr(is_applicable_v<Func, decltype(std::tuple_cat(std::tuple<entity_type>{}, std::declval<basic_view>().get({})))>) {
|
||||
std::apply(func, std::tuple_cat(std::make_tuple(entt), get(entt)));
|
||||
} else {
|
||||
std::apply(func, get(entt));
|
||||
}
|
||||
if constexpr(std::is_void_v<decltype(std::get<storage_type<Comp> *>(pools)->get({}))>) {
|
||||
for(const auto entt: static_cast<const basic_common_type &>(*std::get<storage_type<Comp> *>(pools))) {
|
||||
if(Policy::accept(entt) && ((std::is_same_v<Comp, Component> || std::get<storage_type<Component> *>(pools)->contains(entt)) && ...)
|
||||
&& (!std::get<const storage_type<Exclude> *>(filter)->contains(entt) && ...))
|
||||
{
|
||||
if constexpr(is_applicable_v<Func, decltype(std::tuple_cat(std::tuple<entity_type>{}, std::declval<basic_view_impl>().get({})))>) {
|
||||
std::apply(func, std::tuple_cat(std::make_tuple(entt), get(entt)));
|
||||
} else {
|
||||
std::apply(func, get(entt));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
auto it = std::get<storage_type<Comp> *>(pools)->begin();
|
||||
}
|
||||
} else {
|
||||
auto it = std::get<storage_type<Comp> *>(pools)->begin();
|
||||
|
||||
for(const auto entt: static_cast<const basic_sparse_set<entity_type> &>(*std::get<storage_type<Comp> *>(pools))) {
|
||||
if(((std::is_same_v<Comp, Component> || std::get<storage_type<Component> *>(pools)->contains(entt)) && ...)
|
||||
&& !(std::get<const storage_type<Exclude> *>(filter)->contains(entt) || ...))
|
||||
{
|
||||
if constexpr(is_applicable_v<Func, decltype(std::tuple_cat(std::tuple<entity_type>{}, std::declval<basic_view>().get({})))>) {
|
||||
std::apply(func, std::tuple_cat(std::make_tuple(entt), dispatch_get<Component>(it, entt)...));
|
||||
} else {
|
||||
std::apply(func, std::tuple_cat(dispatch_get<Component>(it, entt)...));
|
||||
}
|
||||
for(const auto entt: static_cast<const basic_common_type &>(*std::get<storage_type<Comp> *>(pools))) {
|
||||
if(Policy::accept(entt) && ((std::is_same_v<Comp, Component> || std::get<storage_type<Component> *>(pools)->contains(entt)) && ...)
|
||||
&& (!std::get<const storage_type<Exclude> *>(filter)->contains(entt) && ...))
|
||||
{
|
||||
if constexpr(is_applicable_v<Func, decltype(std::tuple_cat(std::tuple<entity_type>{}, std::declval<basic_view_impl>().get({})))>) {
|
||||
std::apply(func, std::tuple_cat(std::make_tuple(entt), dispatch_get<Component>(it, entt)...));
|
||||
} else {
|
||||
std::apply(func, std::tuple_cat(dispatch_get<Component>(it, entt)...));
|
||||
}
|
||||
|
||||
++it;
|
||||
}
|
||||
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,12 +346,14 @@ public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Bidirectional iterator type. */
|
||||
using iterator = view_iterator<typename basic_sparse_set<entity_type>::iterator>;
|
||||
using iterator = internal::view_iterator<Policy, typename basic_common_type::iterator, sizeof...(Component) - 1u, sizeof...(Exclude)>;
|
||||
/*! @brief Reverse iterator type. */
|
||||
using reverse_iterator = view_iterator<typename basic_sparse_set<entity_type>::reverse_iterator>;
|
||||
using reverse_iterator = internal::view_iterator<Policy, typename basic_common_type::reverse_iterator, sizeof...(Component) - 1u, sizeof...(Exclude)>;
|
||||
/*! @brief Iterable view type. */
|
||||
using iterable_view = iterable;
|
||||
|
||||
/*! @brief Default constructor to use to create empty, invalid views. */
|
||||
basic_view() ENTT_NOEXCEPT
|
||||
basic_view_impl() ENTT_NOEXCEPT
|
||||
: view{}
|
||||
{}
|
||||
|
||||
@@ -298,7 +362,7 @@ public:
|
||||
* @param component The storage for the types to iterate.
|
||||
* @param epool The storage for the types used to filter the view.
|
||||
*/
|
||||
basic_view(storage_type<Component> &... component, const storage_type<Exclude> &... epool) ENTT_NOEXCEPT
|
||||
basic_view_impl(storage_type<Component> &... component, const storage_type<Exclude> &... epool) ENTT_NOEXCEPT
|
||||
: pools{&component...},
|
||||
filter{&epool...},
|
||||
view{candidate()}
|
||||
@@ -310,7 +374,7 @@ public:
|
||||
*/
|
||||
template<typename Comp>
|
||||
void use() const ENTT_NOEXCEPT {
|
||||
view = *this ? std::get<storage_type<Comp> *>(pools) : nullptr;
|
||||
view = std::get<storage_type<Comp> *>(pools);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -318,7 +382,7 @@ public:
|
||||
* @return Estimated number of entities iterated by the view.
|
||||
*/
|
||||
[[nodiscard]] size_type size_hint() const ENTT_NOEXCEPT {
|
||||
return *this ? view->size() : size_type{};
|
||||
return view->size();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -330,7 +394,7 @@ public:
|
||||
* @return An iterator to the first entity of the view.
|
||||
*/
|
||||
[[nodiscard]] iterator begin() const {
|
||||
return *this ? iterator{view->begin(), view->end(), view->begin(), unchecked(view), filter} : iterator{};
|
||||
return iterator{view->begin(), view->end(), view->begin(), pools_to_unchecked_array(), filter_to_array()};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -343,7 +407,7 @@ public:
|
||||
* @return An iterator to the entity following the last entity of the view.
|
||||
*/
|
||||
[[nodiscard]] iterator end() const {
|
||||
return *this ? iterator{view->begin(), view->end(), view->end(), unchecked(view), filter} : iterator{};
|
||||
return iterator{view->begin(), view->end(), view->end(), pools_to_unchecked_array(), filter_to_array()};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -355,7 +419,7 @@ public:
|
||||
* @return An iterator to the first entity of the reversed view.
|
||||
*/
|
||||
[[nodiscard]] reverse_iterator rbegin() const {
|
||||
return *this ? reverse_iterator{view->rbegin(), view->rend(), view->rbegin(), unchecked(view), filter} : reverse_iterator{};
|
||||
return reverse_iterator{view->rbegin(), view->rend(), view->rbegin(), pools_to_unchecked_array(), filter_to_array()};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -370,7 +434,7 @@ public:
|
||||
* reversed view.
|
||||
*/
|
||||
[[nodiscard]] reverse_iterator rend() const {
|
||||
return *this ? reverse_iterator{view->rbegin(), view->rend(), view->rend(), unchecked(view), filter} : reverse_iterator{};
|
||||
return reverse_iterator{view->rbegin(), view->rend(), view->rend(), pools_to_unchecked_array(), filter_to_array()};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -400,7 +464,7 @@ public:
|
||||
* iterator otherwise.
|
||||
*/
|
||||
[[nodiscard]] iterator find(const entity_type entt) const {
|
||||
const auto it = *this ? iterator{view->begin(), view->end(), view->find(entt), unchecked(view), filter} : end();
|
||||
const auto it = iterator{view->begin(), view->end(), view->find(entt), pools_to_unchecked_array(), filter_to_array()};
|
||||
return (it != end() && *it == entt) ? it : end();
|
||||
}
|
||||
|
||||
@@ -418,7 +482,7 @@ public:
|
||||
* @return True if the view contains the given entity, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool contains(const entity_type entt) const {
|
||||
return *this && (std::get<storage_type<Component> *>(pools)->contains(entt) && ...) && !(std::get<const storage_type<Exclude> *>(filter)->contains(entt) || ...);
|
||||
return (std::get<storage_type<Component> *>(pools)->contains(entt) && ...) && (!std::get<const storage_type<Exclude> *>(filter)->contains(entt) && ...);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -438,14 +502,15 @@ public:
|
||||
*/
|
||||
template<typename... Comp>
|
||||
[[nodiscard]] decltype(auto) get([[maybe_unused]] const entity_type entt) const {
|
||||
ENTT_ASSERT(contains(entt));
|
||||
ENTT_ASSERT(contains(entt), "View does not contain entity");
|
||||
|
||||
if constexpr(sizeof...(Comp) == 0) {
|
||||
return std::tuple_cat(get_as_tuple(*std::get<storage_type<Component> *>(pools), entt)...);
|
||||
} else if constexpr(sizeof...(Comp) == 1) {
|
||||
return (std::get<storage_type<Comp> *>(pools)->get(entt), ...);
|
||||
} else {
|
||||
return std::tuple_cat(get_as_tuple(*std::get<storage_type<Comp> *>(pools), entt)...); }
|
||||
return std::tuple_cat(get_as_tuple(*std::get<storage_type<Comp> *>(pools), entt)...);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -481,9 +546,7 @@ public:
|
||||
*
|
||||
* The pool of the suggested component is used to lead the iterations. The
|
||||
* returned entities will therefore respect the order of the pool associated
|
||||
* with that type.<br/>
|
||||
* It is no longer guaranteed that the performance is the best possible, but
|
||||
* there will be greater control over the order of iteration.
|
||||
* with that type.
|
||||
*
|
||||
* @sa each
|
||||
*
|
||||
@@ -519,9 +582,7 @@ public:
|
||||
*
|
||||
* The pool of the suggested component is used to lead the iterations. The
|
||||
* returned elements will therefore respect the order of the pool associated
|
||||
* with that type.<br/>
|
||||
* It is no longer guaranteed that the performance is the best possible, but
|
||||
* there will be greater control over the order of iteration.
|
||||
* with that type.
|
||||
*
|
||||
* @sa each
|
||||
*
|
||||
@@ -534,10 +595,22 @@ public:
|
||||
return iterable_view{*this};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Combines two views in a _more specific_ one (friend function).
|
||||
* @tparam Id A valid entity type (see entt_traits for more details).
|
||||
* @tparam ELhs Filter list of the first view.
|
||||
* @tparam CLhs Component list of the first view.
|
||||
* @tparam ERhs Filter list of the second view.
|
||||
* @tparam CRhs Component list of the second view.
|
||||
* @return A more specific view.
|
||||
*/
|
||||
template<typename Id, typename... ELhs, typename... CLhs, typename... ERhs, typename... CRhs>
|
||||
friend auto operator|(const basic_view<Id, exclude_t<ELhs...>, CLhs...> &, const basic_view<Id, exclude_t<ERhs...>, CRhs...> &);
|
||||
|
||||
private:
|
||||
const std::tuple<storage_type<Component> *...> pools;
|
||||
const std::tuple<const storage_type<Exclude> *...> filter;
|
||||
mutable const basic_sparse_set<entity_type> *view;
|
||||
mutable const basic_common_type *view;
|
||||
};
|
||||
|
||||
|
||||
@@ -573,38 +646,30 @@ private:
|
||||
* @tparam Component Type of component iterated by the view.
|
||||
*/
|
||||
template<typename Entity, typename Component>
|
||||
class basic_view<Entity, exclude_t<>, Component> final {
|
||||
class basic_view_impl<packed_storage_policy, Entity, exclude_t<>, Component> {
|
||||
using basic_common_type = basic_sparse_set<Entity>;
|
||||
using storage_type = constness_as_t<typename storage_traits<Entity, std::remove_const_t<Component>>::storage_type, Component>;
|
||||
|
||||
class iterable_view {
|
||||
friend class basic_view<Entity, exclude_t<>, Component>;
|
||||
|
||||
class iterable final {
|
||||
template<typename... It>
|
||||
class iterable_view_iterator {
|
||||
friend class iterable_view;
|
||||
|
||||
iterable_view_iterator() ENTT_NOEXCEPT
|
||||
: iterable_view_iterator{It{}...}
|
||||
{}
|
||||
|
||||
template<typename... Discard>
|
||||
iterable_view_iterator(It... from, Discard...) ENTT_NOEXCEPT
|
||||
: it{from...}
|
||||
{}
|
||||
|
||||
public:
|
||||
struct iterable_iterator final {
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using value_type = decltype(std::tuple_cat(std::tuple<Entity>{}, std::declval<basic_view>().get({})));
|
||||
using value_type = decltype(std::tuple_cat(std::tuple<Entity>{}, std::declval<basic_view_impl>().get({})));
|
||||
using pointer = void;
|
||||
using reference = value_type;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
|
||||
iterable_view_iterator & operator++() ENTT_NOEXCEPT {
|
||||
template<typename... Discard>
|
||||
iterable_iterator(It... from, Discard...) ENTT_NOEXCEPT
|
||||
: it{from...}
|
||||
{}
|
||||
|
||||
iterable_iterator & operator++() ENTT_NOEXCEPT {
|
||||
return (++std::get<It>(it), ...), *this;
|
||||
}
|
||||
|
||||
iterable_view_iterator operator++(int) ENTT_NOEXCEPT {
|
||||
iterable_view_iterator orig = *this;
|
||||
iterable_iterator operator++(int) ENTT_NOEXCEPT {
|
||||
iterable_iterator orig = *this;
|
||||
return ++(*this), orig;
|
||||
}
|
||||
|
||||
@@ -612,11 +677,11 @@ class basic_view<Entity, exclude_t<>, Component> final {
|
||||
return { *std::get<It>(it)... };
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator==(const iterable_view_iterator &other) const ENTT_NOEXCEPT {
|
||||
[[nodiscard]] bool operator==(const iterable_iterator &other) const ENTT_NOEXCEPT {
|
||||
return std::get<0>(other.it) == std::get<0>(it);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator!=(const iterable_view_iterator &other) const ENTT_NOEXCEPT {
|
||||
[[nodiscard]] bool operator!=(const iterable_iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
@@ -624,36 +689,36 @@ class basic_view<Entity, exclude_t<>, Component> final {
|
||||
std::tuple<It...> it;
|
||||
};
|
||||
|
||||
iterable_view(storage_type * const ref)
|
||||
: pool{ref}
|
||||
{}
|
||||
|
||||
public:
|
||||
using iterator = std::conditional_t<
|
||||
std::is_same_v<typename storage_type::storage_category, empty_storage_tag>,
|
||||
iterable_view_iterator<typename basic_sparse_set<Entity>::iterator>,
|
||||
iterable_view_iterator<typename basic_sparse_set<Entity>::iterator, decltype(std::declval<storage_type>().begin())>
|
||||
std::is_void_v<decltype(std::declval<storage_type>().get({}))>,
|
||||
iterable_iterator<typename basic_common_type::iterator>,
|
||||
iterable_iterator<typename basic_common_type::iterator, decltype(std::declval<storage_type>().begin())>
|
||||
>;
|
||||
using reverse_iterator = std::conditional_t<
|
||||
std::is_same_v<typename storage_type::storage_category, empty_storage_tag>,
|
||||
iterable_view_iterator<typename basic_sparse_set<Entity>::reverse_iterator>,
|
||||
iterable_view_iterator<typename basic_sparse_set<Entity>::reverse_iterator, decltype(std::declval<storage_type>().rbegin())>
|
||||
std::is_void_v<decltype(std::declval<storage_type>().get({}))>,
|
||||
iterable_iterator<typename basic_common_type::reverse_iterator>,
|
||||
iterable_iterator<typename basic_common_type::reverse_iterator, decltype(std::declval<storage_type>().rbegin())>
|
||||
>;
|
||||
|
||||
iterable(storage_type &ref)
|
||||
: pool{&ref}
|
||||
{}
|
||||
|
||||
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
|
||||
return pool ? iterator{pool->basic_sparse_set<entity_type>::begin(), pool->begin()} : iterator{};
|
||||
return iterator{pool->basic_common_type::begin(), pool->begin()};
|
||||
}
|
||||
|
||||
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
|
||||
return pool ? iterator{pool->basic_sparse_set<entity_type>::end(), pool->end()} : iterator{};
|
||||
return iterator{pool->basic_common_type::end(), pool->end()};
|
||||
}
|
||||
|
||||
[[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
|
||||
return pool ? reverse_iterator{pool->basic_sparse_set<entity_type>::rbegin(), pool->rbegin()} : reverse_iterator{};
|
||||
return reverse_iterator{pool->basic_common_type::rbegin(), pool->rbegin()};
|
||||
}
|
||||
|
||||
[[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
|
||||
return pool ? reverse_iterator{pool->basic_sparse_set<entity_type>::rend(), pool->rend()} : reverse_iterator{};
|
||||
return reverse_iterator{pool->basic_common_type::rend(), pool->rend()};
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -661,28 +726,30 @@ class basic_view<Entity, exclude_t<>, Component> final {
|
||||
};
|
||||
|
||||
public:
|
||||
/*! @brief Type of component iterated by the view. */
|
||||
using raw_type = Component;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Random access iterator type. */
|
||||
using iterator = typename basic_sparse_set<Entity>::iterator;
|
||||
using iterator = typename basic_common_type::iterator;
|
||||
/*! @brief Reversed iterator type. */
|
||||
using reverse_iterator = typename basic_sparse_set<Entity>::reverse_iterator;
|
||||
using reverse_iterator = typename basic_common_type::reverse_iterator;
|
||||
/*! @brief Iterable view type. */
|
||||
using iterable_view = iterable;
|
||||
|
||||
/*! @brief Default constructor to use to create empty, invalid views. */
|
||||
basic_view() ENTT_NOEXCEPT
|
||||
: pool{}
|
||||
basic_view_impl() ENTT_NOEXCEPT
|
||||
: pools{},
|
||||
filter{}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Constructs a single-type view from a storage class.
|
||||
* @param ref The storage for the type to iterate.
|
||||
*/
|
||||
basic_view(storage_type &ref) ENTT_NOEXCEPT
|
||||
: pool{&ref}
|
||||
basic_view_impl(storage_type &ref) ENTT_NOEXCEPT
|
||||
: pools{&ref},
|
||||
filter{}
|
||||
{}
|
||||
|
||||
/**
|
||||
@@ -690,7 +757,7 @@ public:
|
||||
* @return Number of entities that have the given component.
|
||||
*/
|
||||
[[nodiscard]] size_type size() const ENTT_NOEXCEPT {
|
||||
return *this ? pool->size() : size_type{};
|
||||
return std::get<0>(pools)->size();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -698,19 +765,15 @@ public:
|
||||
* @return True if the view is empty, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool empty() const ENTT_NOEXCEPT {
|
||||
return !*this || pool->empty();
|
||||
return std::get<0>(pools)->empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Direct access to the list of components.
|
||||
*
|
||||
* The returned pointer is such that range `[raw(), raw() + size())` is
|
||||
* always a valid range, even if the container is empty.
|
||||
*
|
||||
* @brief Direct access to the raw representation offered by the storage.
|
||||
* @return A pointer to the array of components.
|
||||
*/
|
||||
[[nodiscard]] raw_type * raw() const ENTT_NOEXCEPT {
|
||||
return *this ? pool->raw() : nullptr;
|
||||
[[nodiscard]] auto raw() const ENTT_NOEXCEPT {
|
||||
return std::get<0>(pools)->raw();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -721,8 +784,8 @@ public:
|
||||
*
|
||||
* @return A pointer to the array of entities.
|
||||
*/
|
||||
[[nodiscard]] const entity_type * data() const ENTT_NOEXCEPT {
|
||||
return *this ? pool->data() : nullptr;
|
||||
[[nodiscard]] auto data() const ENTT_NOEXCEPT {
|
||||
return std::get<0>(pools)->data();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -734,7 +797,7 @@ public:
|
||||
* @return An iterator to the first entity of the view.
|
||||
*/
|
||||
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
|
||||
return *this ? pool->basic_sparse_set<entity_type>::begin() : iterator{};
|
||||
return std::get<0>(pools)->basic_common_type::begin();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -747,7 +810,7 @@ public:
|
||||
* @return An iterator to the entity following the last entity of the view.
|
||||
*/
|
||||
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
|
||||
return *this ? pool->basic_sparse_set<entity_type>::end() : iterator{};
|
||||
return std::get<0>(pools)->basic_common_type::end();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -759,7 +822,7 @@ public:
|
||||
* @return An iterator to the first entity of the reversed view.
|
||||
*/
|
||||
[[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
|
||||
return *this ? pool->basic_sparse_set<entity_type>::rbegin() : reverse_iterator{};
|
||||
return std::get<0>(pools)->basic_common_type::rbegin();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -774,7 +837,7 @@ public:
|
||||
* reversed view.
|
||||
*/
|
||||
[[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
|
||||
return *this ? pool->basic_sparse_set<entity_type>::rend() : reverse_iterator{};
|
||||
return std::get<0>(pools)->basic_common_type::rend();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -804,7 +867,7 @@ public:
|
||||
* iterator otherwise.
|
||||
*/
|
||||
[[nodiscard]] iterator find(const entity_type entt) const {
|
||||
const auto it = *this ? pool->find(entt) : end();
|
||||
const auto it = std::get<0>(pools)->find(entt);
|
||||
return it != end() && *it == entt ? it : end();
|
||||
}
|
||||
|
||||
@@ -822,7 +885,7 @@ public:
|
||||
* @return True if the view is properly initialized, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT {
|
||||
return pool != nullptr;
|
||||
return std::get<0>(pools) != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -831,7 +894,7 @@ public:
|
||||
* @return True if the view contains the given entity, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool contains(const entity_type entt) const {
|
||||
return *this && pool->contains(entt);
|
||||
return std::get<0>(pools)->contains(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -851,17 +914,13 @@ public:
|
||||
*/
|
||||
template<typename... Comp>
|
||||
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
|
||||
ENTT_ASSERT(contains(entt));
|
||||
ENTT_ASSERT(contains(entt), "View does not contain entity");
|
||||
|
||||
if constexpr(sizeof...(Comp) == 0) {
|
||||
if constexpr(std::is_same_v<typename storage_type::storage_category, empty_storage_tag>) {
|
||||
return std::make_tuple();
|
||||
} else {
|
||||
return std::forward_as_tuple(pool->get(entt));
|
||||
}
|
||||
return get_as_tuple(*std::get<0>(pools), entt);
|
||||
} else {
|
||||
static_assert(std::is_same_v<Comp..., Component>, "Invalid component type");
|
||||
return pool->get(entt);
|
||||
return std::get<0>(pools)->get(entt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -889,14 +948,14 @@ public:
|
||||
*/
|
||||
template<typename Func>
|
||||
void each(Func func) const {
|
||||
if constexpr(std::is_same_v<typename storage_type::storage_category, empty_storage_tag>) {
|
||||
if constexpr(std::is_void_v<decltype(std::get<0>(pools)->get({}))>) {
|
||||
if constexpr(std::is_invocable_v<Func>) {
|
||||
for(auto pos = size(); pos; --pos) {
|
||||
func();
|
||||
}
|
||||
} else {
|
||||
for(auto &&component: *this) {
|
||||
func(component);
|
||||
for(auto entity: *this) {
|
||||
func(entity);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -905,10 +964,8 @@ public:
|
||||
std::apply(func, pack);
|
||||
}
|
||||
} else {
|
||||
if(*this) {
|
||||
for(auto &&component: *pool) {
|
||||
func(component);
|
||||
}
|
||||
for(auto &&component: *std::get<0>(pools)) {
|
||||
func(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -928,11 +985,40 @@ public:
|
||||
* @return An iterable object to use to _visit_ the view.
|
||||
*/
|
||||
[[nodiscard]] iterable_view each() const ENTT_NOEXCEPT {
|
||||
return iterable_view{pool};
|
||||
return iterable_view{*std::get<0>(pools)};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Combines two views in a _more specific_ one (friend function).
|
||||
* @tparam Id A valid entity type (see entt_traits for more details).
|
||||
* @tparam ELhs Filter list of the first view.
|
||||
* @tparam CLhs Component list of the first view.
|
||||
* @tparam ERhs Filter list of the second view.
|
||||
* @tparam CRhs Component list of the second view.
|
||||
* @return A more specific view.
|
||||
*/
|
||||
template<typename Id, typename... ELhs, typename... CLhs, typename... ERhs, typename... CRhs>
|
||||
friend auto operator|(const basic_view<Id, exclude_t<ELhs...>, CLhs...> &, const basic_view<Id, exclude_t<ERhs...>, CRhs...> &);
|
||||
|
||||
private:
|
||||
storage_type * const pool;
|
||||
const std::tuple<storage_type *> pools;
|
||||
const std::tuple<> filter;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief View implementation dispatcher.
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Exclude Types of components used to filter the view.
|
||||
* @tparam Component Types of components iterated by the view.
|
||||
*/
|
||||
template<typename Entity, typename... Exclude, typename... Component>
|
||||
struct basic_view<Entity, exclude_t<Exclude...>, Component...>
|
||||
: basic_view_impl<std::conditional_t<std::disjunction_v<typename component_traits<std::remove_const_t<Component>>::in_place_delete...>, stable_storage_policy, packed_storage_policy>, Entity, exclude_t<Exclude...>, Component...>
|
||||
{
|
||||
/*! @brief Most restrictive storage policy of all component types. */
|
||||
using storage_policy = std::conditional_t<std::disjunction_v<typename component_traits<std::remove_const_t<Component>>::in_place_delete...>, stable_storage_policy, packed_storage_policy>;
|
||||
using basic_view_impl<storage_policy, Entity, exclude_t<Exclude...>, Component...>::basic_view_impl;
|
||||
};
|
||||
|
||||
|
||||
@@ -942,10 +1028,28 @@ private:
|
||||
* @param storage The storage for the types to iterate.
|
||||
*/
|
||||
template<typename... Storage>
|
||||
basic_view(Storage &... storage) ENTT_NOEXCEPT
|
||||
basic_view(Storage &... storage)
|
||||
-> basic_view<std::common_type_t<typename Storage::entity_type...>, entt::exclude_t<>, constness_as_t<typename Storage::value_type, Storage>...>;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Combines two views in a _more specific_ one.
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam ELhs Filter list of the first view.
|
||||
* @tparam CLhs Component list of the first view.
|
||||
* @tparam ERhs Filter list of the second view.
|
||||
* @tparam CRhs Component list of the second view.
|
||||
* @param lhs A valid reference to the first view.
|
||||
* @param rhs A valid reference to the second view.
|
||||
* @return A more specific view.
|
||||
*/
|
||||
template<typename Entity, typename... ELhs, typename... CLhs, typename... ERhs, typename... CRhs>
|
||||
[[nodiscard]] auto operator|(const basic_view<Entity, exclude_t<ELhs...>, CLhs...> &lhs, const basic_view<Entity, exclude_t<ERhs...>, CRhs...> &rhs) {
|
||||
using view_type = basic_view<Entity, exclude_t<ELhs..., ERhs...>, CLhs..., CRhs...>;
|
||||
return std::apply([](auto *... storage) { return view_type{*storage...}; }, std::tuple_cat(lhs.pools, rhs.pools, lhs.filter, rhs.filter));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,435 +0,0 @@
|
||||
#ifndef ENTT_ENTITY_VIEW_PACK_HPP
|
||||
#define ENTT_ENTITY_VIEW_PACK_HPP
|
||||
|
||||
|
||||
#include <iterator>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include "../core/type_traits.hpp"
|
||||
#include "fwd.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief View pack.
|
||||
*
|
||||
* The view pack allows users to combine multiple views into a single iterable
|
||||
* object, while also giving them full control over which view should lead the
|
||||
* iteration.<br/>
|
||||
* This class returns all and only the entities present in all views. Its
|
||||
* intended primary use is for custom storage and views, but it can also be very
|
||||
* convenient in everyday use.
|
||||
*
|
||||
* @tparam Head Type of the leading view of the pack.
|
||||
* @tparam Tail Types of all other views of the pack.
|
||||
*/
|
||||
template<typename Head, typename... Tail>
|
||||
class view_pack {
|
||||
template<typename It>
|
||||
class view_pack_iterator final {
|
||||
friend class view_pack<Head, Tail...>;
|
||||
|
||||
view_pack_iterator(It from, It to, const std::tuple<Tail...> &other) ENTT_NOEXCEPT
|
||||
: it{from},
|
||||
last{to},
|
||||
tail{other}
|
||||
{
|
||||
if(it != last && !valid()) {
|
||||
++(*this);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool valid() const {
|
||||
return std::apply([entity = *it](auto &&... curr) { return (curr.contains(entity) && ...); }, tail);
|
||||
}
|
||||
|
||||
public:
|
||||
using difference_type = typename std::iterator_traits<It>::difference_type;
|
||||
using value_type = typename std::iterator_traits<It>::value_type;
|
||||
using pointer = typename std::iterator_traits<It>::pointer;
|
||||
using reference = typename std::iterator_traits<It>::reference;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
|
||||
view_pack_iterator & operator++() ENTT_NOEXCEPT {
|
||||
while(++it != last && !valid());
|
||||
return *this;
|
||||
}
|
||||
|
||||
view_pack_iterator operator++(int) ENTT_NOEXCEPT {
|
||||
view_pack_iterator orig = *this;
|
||||
return ++(*this), orig;
|
||||
}
|
||||
|
||||
[[nodiscard]] reference operator*() const {
|
||||
return *it;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator==(const view_pack_iterator &other) const ENTT_NOEXCEPT {
|
||||
return other.it == it;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator!=(const view_pack_iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
private:
|
||||
It it;
|
||||
const It last;
|
||||
const std::tuple<Tail...> tail;
|
||||
};
|
||||
|
||||
class iterable_view_pack final {
|
||||
friend class view_pack<Head, Tail...>;
|
||||
|
||||
using iterable_view = decltype(std::declval<Head>().each());
|
||||
|
||||
template<typename It>
|
||||
class iterable_view_pack_iterator final {
|
||||
friend class iterable_view_pack;
|
||||
|
||||
iterable_view_pack_iterator(It from, It to, const std::tuple<Tail...> &other) ENTT_NOEXCEPT
|
||||
: it{from},
|
||||
last{to},
|
||||
tail{other}
|
||||
{
|
||||
if(it != last && !valid()) {
|
||||
++(*this);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool valid() const {
|
||||
return std::apply([entity = std::get<0>(*it)](auto &&... curr) { return (curr.contains(entity) && ...); }, tail);
|
||||
}
|
||||
|
||||
public:
|
||||
using difference_type = typename std::iterator_traits<It>::difference_type;
|
||||
using value_type = decltype(std::tuple_cat(*std::declval<It>(), std::declval<Tail>().get({})...));
|
||||
using pointer = void;
|
||||
using reference = value_type;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
|
||||
iterable_view_pack_iterator & operator++() ENTT_NOEXCEPT {
|
||||
while(++it != last && !valid());
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterable_view_pack_iterator operator++(int) ENTT_NOEXCEPT {
|
||||
iterable_view_pack_iterator orig = *this;
|
||||
return ++(*this), orig;
|
||||
}
|
||||
|
||||
[[nodiscard]] reference operator*() const {
|
||||
return std::apply([value = *it](auto &&... curr) { return std::tuple_cat(value, curr.get(std::get<0>(value))...); }, tail);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator==(const iterable_view_pack_iterator &other) const ENTT_NOEXCEPT {
|
||||
return other.it == it;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator!=(const iterable_view_pack_iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
private:
|
||||
It it;
|
||||
const It last;
|
||||
const std::tuple<Tail...> tail;
|
||||
};
|
||||
|
||||
iterable_view_pack(const Head &first, const std::tuple<Tail...> &last)
|
||||
: iterable{first.each()},
|
||||
tail{last}
|
||||
{}
|
||||
|
||||
public:
|
||||
using iterator = iterable_view_pack_iterator<typename iterable_view::iterator>;
|
||||
using reverse_iterator = iterable_view_pack_iterator<typename iterable_view::reverse_iterator>;
|
||||
|
||||
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
|
||||
return { iterable.begin(), iterable.end(), tail };
|
||||
}
|
||||
|
||||
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
|
||||
return { iterable.end(), iterable.end(), tail };
|
||||
}
|
||||
|
||||
[[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
|
||||
return { iterable.rbegin(), iterable.rend(), tail };
|
||||
}
|
||||
|
||||
[[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
|
||||
return { iterable.rend(), iterable.rend(), tail };
|
||||
}
|
||||
|
||||
private:
|
||||
iterable_view iterable;
|
||||
std::tuple<Tail...> tail;
|
||||
};
|
||||
|
||||
public:
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = std::common_type_t<typename Head::entity_type, typename Tail::entity_type...>;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using size_type = std::common_type_t<typename Head::size_type, typename Tail::size_type...>;
|
||||
/*! @brief Input iterator type. */
|
||||
using iterator = view_pack_iterator<typename Head::iterator>;
|
||||
/*! @brief Reversed iterator type. */
|
||||
using reverse_iterator = view_pack_iterator<typename Head::reverse_iterator>;
|
||||
|
||||
/**
|
||||
* @brief Constructs a pack from a bunch of views.
|
||||
* @param first A reference to the leading view for the pack.
|
||||
* @param last References to the other views to use to construct the pack.
|
||||
*/
|
||||
view_pack(const Head &first, const Tail &... last)
|
||||
: head{first},
|
||||
tail{last...}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the first entity of the pack.
|
||||
*
|
||||
* The returned iterator points to the first entity of the pack. If the pack
|
||||
* is empty, the returned iterator will be equal to `end()`.
|
||||
*
|
||||
* @return An iterator to the first entity of the pack.
|
||||
*/
|
||||
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
|
||||
return { head.begin(), head.end(), tail };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator that is past the last entity of the pack.
|
||||
*
|
||||
* The returned iterator points to the entity following the last entity of
|
||||
* the pack. Attempting to dereference the returned iterator results in
|
||||
* undefined behavior.
|
||||
*
|
||||
* @return An iterator to the entity following the last entity of the pack.
|
||||
*/
|
||||
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
|
||||
return { head.end(), head.end(), tail };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the first entity of the pack.
|
||||
*
|
||||
* The returned iterator points to the first entity of the reversed pack. If
|
||||
* the pack is empty, the returned iterator will be equal to `rend()`.
|
||||
*
|
||||
* @return An iterator to the first entity of the pack.
|
||||
*/
|
||||
[[nodiscard]] reverse_iterator rbegin() const {
|
||||
return { head.rbegin(), head.rend(), tail };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator that is past the last entity of the reversed
|
||||
* pack.
|
||||
*
|
||||
* The returned iterator points to the entity following the last entity of
|
||||
* the reversed pack. Attempting to dereference the returned iterator
|
||||
* results in undefined behavior.
|
||||
*
|
||||
* @return An iterator to the entity following the last entity of the
|
||||
* reversed pack.
|
||||
*/
|
||||
[[nodiscard]] reverse_iterator rend() const {
|
||||
return { head.rend(), head.rend(), tail };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the first entity of the pack, if any.
|
||||
* @return The first entity of the pack if one exists, the null entity
|
||||
* otherwise.
|
||||
*/
|
||||
[[nodiscard]] entity_type front() const {
|
||||
const auto it = begin();
|
||||
return it != end() ? *it : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the last entity of the pack, if any.
|
||||
* @return The last entity of the pack if one exists, the null entity
|
||||
* otherwise.
|
||||
*/
|
||||
[[nodiscard]] entity_type back() const {
|
||||
const auto it = rbegin();
|
||||
return it != rend() ? *it : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Finds an entity.
|
||||
* @param entt A valid entity identifier.
|
||||
* @return An iterator to the given entity if it's found, past the end
|
||||
* iterator otherwise.
|
||||
*/
|
||||
[[nodiscard]] iterator find(const entity_type entt) const {
|
||||
iterator it{head.find(entt), head.end(), tail};
|
||||
return (it != end() && *it == entt) ? it : end();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a pack contains an entity.
|
||||
* @param entt A valid entity identifier.
|
||||
* @return True if the pack contains the given entity, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool contains(const entity_type entt) const {
|
||||
return head.contains(entt) && std::apply([entt](auto &&... curr) { return (curr.contains(entt) && ...); }, tail);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the components assigned to the given entity.
|
||||
*
|
||||
* Prefer this function instead of `registry::get` during iterations. It has
|
||||
* far better performance than its counterpart.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid component type results in a compilation
|
||||
* error. Attempting to use an entity that doesn't belong to the pack
|
||||
* results in undefined behavior.
|
||||
*
|
||||
* @tparam Comp Types of components to get.
|
||||
* @param entt A valid entity identifier.
|
||||
* @return The components assigned to the entity.
|
||||
*/
|
||||
template<typename... Comp>
|
||||
[[nodiscard]] decltype(auto) get([[maybe_unused]] const entity_type entt) const {
|
||||
ENTT_ASSERT(contains(entt));
|
||||
auto component = std::apply([this, entt](auto &&... curr) { return std::tuple_cat(head.get(entt), curr.get(entt)...); }, tail);
|
||||
|
||||
if constexpr(sizeof...(Comp) == 0) {
|
||||
return component;
|
||||
} else if constexpr(sizeof...(Comp) == 1) {
|
||||
return (std::get<Comp &>(component), ...);
|
||||
} else {
|
||||
return std::forward_as_tuple(std::get<Comp &>(component)...);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterates entities and components and applies the given function
|
||||
* object to them.
|
||||
*
|
||||
* The function object is invoked for each entity. It is provided with the
|
||||
* entity itself and a set of references to non-empty components. The
|
||||
* _constness_ of the components is as requested.<br/>
|
||||
* The signature of the function must be equivalent to one of the following
|
||||
* forms:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(const entity_type, Type &...);
|
||||
* void(Type &...);
|
||||
* @endcode
|
||||
*
|
||||
* @note
|
||||
* Empty types aren't explicitly instantiated and therefore they are never
|
||||
* returned during iterations.
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void each(Func func) const {
|
||||
for(auto &&value: head.each()) {
|
||||
if(std::apply([&value](auto &&... curr) { return (curr.contains(std::get<0>(value)) && ...); }, tail)) {
|
||||
auto args = std::apply([&value](auto &&... curr) { return std::tuple_cat(value, curr.get(std::get<0>(value))...); }, tail);
|
||||
|
||||
if constexpr(is_applicable_v<Func, decltype(args)>) {
|
||||
std::apply(func, args);
|
||||
} else {
|
||||
std::apply([&func](const auto, auto &&... component) { func(std::forward<decltype(component)>(component)...); }, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterable object to use to _visit_ the pack.
|
||||
*
|
||||
* The iterable object returns tuples that contain the current entity and a
|
||||
* set of references to its non-empty components. The _constness_ of the
|
||||
* components is as requested.
|
||||
*
|
||||
* @note
|
||||
* Empty types aren't explicitly instantiated and therefore they are never
|
||||
* returned during iterations.
|
||||
*
|
||||
* @return An iterable object to use to _visit_ the pack.
|
||||
*/
|
||||
[[nodiscard]] iterable_view_pack each() const ENTT_NOEXCEPT {
|
||||
return { head, tail };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a copy of the views stored by the pack.
|
||||
* @return A copy of the views stored by the pack.
|
||||
*/
|
||||
std::tuple<Head, Tail...> pack() const ENTT_NOEXCEPT {
|
||||
return std::apply([this](auto &&... curr) { return std::make_tuple(head, curr...); }, tail);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Appends a view to a pack.
|
||||
* @tparam Args View template arguments.
|
||||
* @param other A reference to a view to append to the pack.
|
||||
* @return The extended pack.
|
||||
*/
|
||||
template<typename... Args>
|
||||
[[nodiscard]] auto operator|(const basic_view<Args...> &other) const {
|
||||
return std::make_from_tuple<view_pack<Head, Tail..., basic_view<Args...>>>(std::tuple_cat(std::make_tuple(head), tail, std::make_tuple(other)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Appends a pack and therefore all its views to another pack.
|
||||
* @tparam Pack Types of views of the pack to append.
|
||||
* @param other A reference to the pack to append.
|
||||
* @return The extended pack.
|
||||
*/
|
||||
template<typename... Pack>
|
||||
[[nodiscard]] auto operator|(const view_pack<Pack...> &other) const {
|
||||
return std::make_from_tuple<view_pack<Head, Tail..., Pack...>>(std::tuple_cat(std::make_tuple(head), tail, other.pack()));
|
||||
}
|
||||
|
||||
private:
|
||||
Head head;
|
||||
std::tuple<Tail...> tail;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Combines two views in a pack.
|
||||
* @tparam Args Template arguments of the first view.
|
||||
* @tparam Other Template arguments of the second view.
|
||||
* @param lhs A reference to the first view with which to create the pack.
|
||||
* @param rhs A reference to the second view with which to create the pack.
|
||||
* @return A pack that combines the two views in a single iterable object.
|
||||
*/
|
||||
template<typename... Args, typename... Other>
|
||||
[[nodiscard]] auto operator|(const basic_view<Args...> &lhs, const basic_view<Other...> &rhs) {
|
||||
return view_pack{lhs, rhs};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Combines a view with a pack.
|
||||
* @tparam Args View template arguments.
|
||||
* @tparam Pack Types of views of the pack.
|
||||
* @param view A reference to the view to combine with the pack.
|
||||
* @param pack A reference to the pack to combine with the view.
|
||||
* @return The extended pack.
|
||||
*/
|
||||
template<typename... Args, typename... Pack>
|
||||
[[nodiscard]] auto operator|(const basic_view<Args...> &view, const view_pack<Pack...> &pack) {
|
||||
return view_pack{view} | pack;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "core/type_info.hpp"
|
||||
#include "core/type_traits.hpp"
|
||||
#include "core/utility.hpp"
|
||||
#include "entity/component.hpp"
|
||||
#include "entity/entity.hpp"
|
||||
#include "entity/group.hpp"
|
||||
#include "entity/handle.hpp"
|
||||
@@ -23,19 +24,20 @@
|
||||
#include "entity/storage.hpp"
|
||||
#include "entity/utility.hpp"
|
||||
#include "entity/view.hpp"
|
||||
#include "entity/view_pack.hpp"
|
||||
#include "locator/locator.hpp"
|
||||
#include "meta/adl_pointer.hpp"
|
||||
#include "meta/container.hpp"
|
||||
#include "meta/ctx.hpp"
|
||||
#include "meta/factory.hpp"
|
||||
#include "meta/internal.hpp"
|
||||
#include "meta/meta.hpp"
|
||||
#include "meta/node.hpp"
|
||||
#include "meta/pointer.hpp"
|
||||
#include "meta/policy.hpp"
|
||||
#include "meta/range.hpp"
|
||||
#include "meta/resolve.hpp"
|
||||
#include "meta/template.hpp"
|
||||
#include "meta/type_traits.hpp"
|
||||
#include "meta/utility.hpp"
|
||||
#include "platform/android-ndk-r17.hpp"
|
||||
#include "poly/poly.hpp"
|
||||
#include "process/process.hpp"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "core/fwd.hpp"
|
||||
#include "entity/fwd.hpp"
|
||||
#include "poly/fwd.hpp"
|
||||
#include "resource/fwd.hpp"
|
||||
#include "signal/fwd.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);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,10 @@ namespace entt {
|
||||
* @tparam Trait Traits associated with the underlying container.
|
||||
*/
|
||||
template<typename Container, template<typename> class... Trait>
|
||||
struct meta_container_traits: public Trait<Container>... {};
|
||||
struct meta_container_traits: public Trait<Container>... {
|
||||
/*! @brief Type of container. */
|
||||
using type = Container;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
@@ -33,21 +36,12 @@ struct meta_container_traits: public Trait<Container>... {};
|
||||
*/
|
||||
template<typename Container>
|
||||
struct basic_container {
|
||||
/*! @brief Iterator type of the container. */
|
||||
using iterator = typename Container::iterator;
|
||||
/*! @brief Iterator type of the container. */
|
||||
using const_iterator = typename Container::const_iterator;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename Container::size_type;
|
||||
/*! @brief Value type of the container. */
|
||||
using value_type = typename Container::value_type;
|
||||
|
||||
/**
|
||||
* @brief Returns the size of the given container.
|
||||
* @param cont The container for which to return the size.
|
||||
* @return The size of the given container.
|
||||
*/
|
||||
[[nodiscard]] static size_type size(const Container &cont) ENTT_NOEXCEPT {
|
||||
[[nodiscard]] static typename Container::size_type size(const Container &cont) ENTT_NOEXCEPT {
|
||||
return cont.size();
|
||||
}
|
||||
|
||||
@@ -56,7 +50,7 @@ struct basic_container {
|
||||
* @param cont The container for which to return the iterator.
|
||||
* @return An iterator to the first element of the given container.
|
||||
*/
|
||||
[[nodiscard]] static iterator begin(Container &cont) {
|
||||
[[nodiscard]] static typename Container::iterator begin(Container &cont) {
|
||||
return cont.begin();
|
||||
}
|
||||
|
||||
@@ -65,7 +59,7 @@ struct basic_container {
|
||||
* @param cont The container for which to return the iterator.
|
||||
* @return An iterator to the first element of the given container.
|
||||
*/
|
||||
[[nodiscard]] static const_iterator cbegin(const Container &cont) {
|
||||
[[nodiscard]] static typename Container::const_iterator cbegin(const Container &cont) {
|
||||
return cont.begin();
|
||||
}
|
||||
|
||||
@@ -74,7 +68,7 @@ struct basic_container {
|
||||
* @param cont The container for which to return the iterator.
|
||||
* @return An iterator past the last element of the given container.
|
||||
*/
|
||||
[[nodiscard]] static iterator end(Container &cont) {
|
||||
[[nodiscard]] static typename Container::iterator end(Container &cont) {
|
||||
return cont.end();
|
||||
}
|
||||
|
||||
@@ -83,7 +77,7 @@ struct basic_container {
|
||||
* @param cont The container for which to return the iterator.
|
||||
* @return An iterator past the last element of the given container.
|
||||
*/
|
||||
[[nodiscard]] static const_iterator cend(const Container &cont) {
|
||||
[[nodiscard]] static typename Container::const_iterator cend(const Container &cont) {
|
||||
return cont.end();
|
||||
}
|
||||
};
|
||||
@@ -95,9 +89,6 @@ struct basic_container {
|
||||
*/
|
||||
template<typename Container>
|
||||
struct basic_associative_container {
|
||||
/*! @brief Key type of the sequence container. */
|
||||
using key_type = typename Container::key_type;
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the element with key equivalent to the
|
||||
* given one, if any.
|
||||
@@ -105,12 +96,12 @@ struct basic_associative_container {
|
||||
* @param key The key of the element to search.
|
||||
* @return An iterator to the element with the given key, if any.
|
||||
*/
|
||||
[[nodiscard]] static typename Container::iterator find(Container &cont, const key_type &key) {
|
||||
[[nodiscard]] static typename Container::iterator find(Container &cont, const typename Container::key_type &key) {
|
||||
return cont.find(key);
|
||||
}
|
||||
|
||||
/*! @copydoc find */
|
||||
[[nodiscard]] static typename Container::const_iterator cfind(const Container &cont, const key_type &key) {
|
||||
[[nodiscard]] static typename Container::const_iterator cfind(const Container &cont, const typename Container::key_type &key) {
|
||||
return cont.find(key);
|
||||
}
|
||||
};
|
||||
@@ -165,12 +156,12 @@ struct basic_sequence_container {
|
||||
* @param pos The position of the element to return.
|
||||
* @return A reference to the requested element.
|
||||
*/
|
||||
[[nodiscard]] static typename Container::value_type & get(Container &cont, typename Container::size_type pos) {
|
||||
[[nodiscard]] static typename Container::reference get(Container &cont, typename Container::size_type pos) {
|
||||
return cont[pos];
|
||||
}
|
||||
|
||||
/*! @copydoc get */
|
||||
[[nodiscard]] static const typename Container::value_type & cget(const Container &cont, typename Container::size_type pos) {
|
||||
[[nodiscard]] static typename Container::const_reference cget(const Container &cont, typename Container::size_type pos) {
|
||||
return cont[pos];
|
||||
}
|
||||
};
|
||||
@@ -349,10 +340,7 @@ struct meta_associative_container_traits<std::map<Key, Value, Args...>>
|
||||
basic_dynamic_associative_container,
|
||||
dynamic_associative_key_value_container
|
||||
>
|
||||
{
|
||||
/*! @brief Mapped type of the sequence container. */
|
||||
using mapped_type = typename std::map<Key, Value, Args...>::mapped_type;
|
||||
};
|
||||
{};
|
||||
|
||||
|
||||
/**
|
||||
@@ -372,10 +360,7 @@ struct meta_associative_container_traits<std::unordered_map<Key, Value, Args...>
|
||||
basic_dynamic_associative_container,
|
||||
dynamic_associative_key_value_container
|
||||
>
|
||||
{
|
||||
/*! @brief Mapped type of the sequence container. */
|
||||
using mapped_type = typename std::unordered_map<Key, Value, Args...>::mapped_type;
|
||||
};
|
||||
{};
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
#define ENTT_META_FACTORY_HPP
|
||||
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
@@ -12,9 +10,10 @@
|
||||
#include "../core/fwd.hpp"
|
||||
#include "../core/type_info.hpp"
|
||||
#include "../core/type_traits.hpp"
|
||||
#include "internal.hpp"
|
||||
#include "meta.hpp"
|
||||
#include "node.hpp"
|
||||
#include "policy.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
@@ -29,165 +28,19 @@ namespace entt {
|
||||
namespace internal {
|
||||
|
||||
|
||||
template<typename, bool, bool>
|
||||
struct meta_function_helper;
|
||||
|
||||
|
||||
template<typename Ret, typename... Args, bool Const, bool Static>
|
||||
struct meta_function_helper<Ret(Args...), Const, Static> {
|
||||
using return_type = Ret;
|
||||
using args_type = type_list<Args...>;
|
||||
|
||||
static constexpr auto is_static = Static;
|
||||
static constexpr auto is_const = Const;
|
||||
|
||||
[[nodiscard]] static auto arg(typename internal::meta_func_node::size_type index) ENTT_NOEXCEPT {
|
||||
return std::array<meta_type_node *, sizeof...(Args)>{{meta_info<Args>::resolve()...}}[index];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename Type, typename Ret, typename... Args, typename Class>
|
||||
constexpr meta_function_helper<std::conditional_t<std::is_same_v<Type, Class>, Ret(Args...), Ret(Class &, Args...)>, true, !std::is_same_v<Type, Class>>
|
||||
to_meta_function_helper(Ret(Class:: *)(Args...) const);
|
||||
|
||||
|
||||
template<typename Type, typename Ret, typename... Args, typename Class>
|
||||
constexpr meta_function_helper<std::conditional_t<std::is_same_v<Type, Class>, Ret(Args...), Ret(Class &, Args...)>, false, !std::is_same_v<Type, Class>>
|
||||
to_meta_function_helper(Ret(Class:: *)(Args...));
|
||||
|
||||
|
||||
template<typename Type, typename Ret, typename... Args>
|
||||
constexpr meta_function_helper<Ret(Args...), false, true>
|
||||
to_meta_function_helper(Ret(*)(Args...));
|
||||
|
||||
|
||||
template<typename Type>
|
||||
constexpr void to_meta_function_helper(...);
|
||||
|
||||
|
||||
template<typename Type, typename Candidate>
|
||||
using meta_function_helper_t = decltype(to_meta_function_helper<Type>(std::declval<Candidate>()));
|
||||
|
||||
|
||||
template<typename Type, typename... Args, std::size_t... Index>
|
||||
[[nodiscard]] meta_any construct(meta_any * const args, std::index_sequence<Index...>) {
|
||||
if(((args+Index)->allow_cast<Args>() && ...)) {
|
||||
return Type{(args+Index)->cast<Args>()...};
|
||||
}
|
||||
|
||||
return {};
|
||||
template<typename Node>
|
||||
[[nodiscard]] bool find_if(const Node *candidate, const Node *node) ENTT_NOEXCEPT {
|
||||
return node && (node == candidate || find_if(candidate, node->next));
|
||||
}
|
||||
|
||||
|
||||
template<typename Type, auto Data>
|
||||
[[nodiscard]] bool setter([[maybe_unused]] meta_handle instance, [[maybe_unused]] meta_any value) {
|
||||
if constexpr(std::is_function_v<std::remove_reference_t<std::remove_pointer_t<decltype(Data)>>> || std::is_member_function_pointer_v<decltype(Data)>) {
|
||||
using helper_type = meta_function_helper_t<Type, decltype(Data)>;
|
||||
using data_type = type_list_element_t<!std::is_member_function_pointer_v<decltype(Data)>, typename helper_type::args_type>;
|
||||
|
||||
if(auto * const clazz = instance->try_cast<Type>(); clazz) {
|
||||
if(value.allow_cast<data_type>()) {
|
||||
std::invoke(Data, *clazz, value.cast<data_type>());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if constexpr(std::is_member_object_pointer_v<decltype(Data)>) {
|
||||
using data_type = std::remove_cv_t<std::remove_reference_t<decltype(std::declval<Type>().*Data)>>;
|
||||
|
||||
if constexpr(!std::is_array_v<data_type>) {
|
||||
if(auto * const clazz = instance->try_cast<Type>(); clazz) {
|
||||
if(value.allow_cast<data_type>()) {
|
||||
std::invoke(Data, clazz) = value.cast<data_type>();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
template<typename Id, typename Node>
|
||||
[[nodiscard]] bool find_if_not(const Id id, Node *node, const Node *owner) ENTT_NOEXCEPT {
|
||||
if constexpr(std::is_pointer_v<Id>) {
|
||||
return node && ((*node->id == *id && node != owner) || find_if_not(id, node->next, owner));
|
||||
} else {
|
||||
using data_type = std::remove_cv_t<std::remove_reference_t<decltype(*Data)>>;
|
||||
|
||||
if constexpr(!std::is_array_v<data_type>) {
|
||||
if(value.allow_cast<data_type>()) {
|
||||
*Data = value.cast<data_type>();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return node && ((node->id == id && node != owner) || find_if_not(id, node->next, owner));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
template<typename Type, auto Data, typename Policy>
|
||||
[[nodiscard]] meta_any getter([[maybe_unused]] meta_handle instance) {
|
||||
[[maybe_unused]] auto dispatch = [](auto &&value) {
|
||||
if constexpr(std::is_same_v<Policy, as_void_t>) {
|
||||
return meta_any{std::in_place_type<void>, std::forward<decltype(value)>(value)};
|
||||
} else if constexpr(std::is_same_v<Policy, as_ref_t>) {
|
||||
return meta_any{std::ref(std::forward<decltype(value)>(value))};
|
||||
} else if constexpr(std::is_same_v<Policy, as_cref_t>) {
|
||||
return meta_any{std::cref(std::forward<decltype(value)>(value))};
|
||||
} else {
|
||||
static_assert(std::is_same_v<Policy, as_is_t>, "Policy not supported");
|
||||
return meta_any{std::forward<decltype(value)>(value)};
|
||||
}
|
||||
};
|
||||
|
||||
if constexpr(std::is_function_v<std::remove_reference_t<std::remove_pointer_t<decltype(Data)>>> || std::is_member_function_pointer_v<decltype(Data)>) {
|
||||
auto * const clazz = instance->try_cast<std::conditional_t<std::is_invocable_v<decltype(Data), const Type *>, const Type, Type>>();
|
||||
return clazz ? dispatch(std::invoke(Data, *clazz)) : meta_any{};
|
||||
} else if constexpr(std::is_member_object_pointer_v<decltype(Data)>) {
|
||||
if constexpr(std::is_array_v<std::remove_cv_t<std::remove_reference_t<decltype(std::declval<Type>().*Data)>>>) {
|
||||
return meta_any{};
|
||||
} else {
|
||||
auto * const clazz = instance->try_cast<std::conditional_t<std::is_same_v<Policy, as_ref_t>, Type, const Type>>();
|
||||
return clazz ? dispatch(std::invoke(Data, clazz)) : meta_any{};
|
||||
}
|
||||
} else if constexpr(std::is_pointer_v<std::decay_t<decltype(Data)>>) {
|
||||
if constexpr(std::is_array_v<std::remove_pointer_t<decltype(Data)>>) {
|
||||
return meta_any{};
|
||||
} else {
|
||||
return dispatch(*Data);
|
||||
}
|
||||
} else {
|
||||
return dispatch(Data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<typename Type, auto Candidate, typename Policy, std::size_t... Index>
|
||||
[[nodiscard]] meta_any invoke([[maybe_unused]] meta_handle instance, meta_any *args, std::index_sequence<Index...>) {
|
||||
using helper_type = meta_function_helper_t<Type, decltype(Candidate)>;
|
||||
|
||||
auto dispatch = [](auto &&... params) {
|
||||
if constexpr(std::is_void_v<std::remove_cv_t<typename helper_type::return_type>> || std::is_same_v<Policy, as_void_t>) {
|
||||
std::invoke(Candidate, std::forward<decltype(params)>(params)...);
|
||||
return meta_any{std::in_place_type<void>};
|
||||
} else if constexpr(std::is_same_v<Policy, as_ref_t>) {
|
||||
return meta_any{std::ref(std::invoke(Candidate, std::forward<decltype(params)>(params)...))};
|
||||
} else if constexpr(std::is_same_v<Policy, as_cref_t>) {
|
||||
return meta_any{std::cref(std::invoke(Candidate, std::forward<decltype(params)>(params)...))};
|
||||
} else {
|
||||
static_assert(std::is_same_v<Policy, as_is_t>, "Policy not supported");
|
||||
return meta_any{std::invoke(Candidate, std::forward<decltype(params)>(params)...)};
|
||||
}
|
||||
};
|
||||
|
||||
if constexpr(std::is_invocable_v<decltype(Candidate), const Type &, type_list_element_t<Index, typename helper_type::args_type>...>) {
|
||||
if(const auto * const clazz = instance->try_cast<const Type>(); clazz && ((args+Index)->allow_cast<type_list_element_t<Index, typename helper_type::args_type>>() && ...)) {
|
||||
return dispatch(*clazz, (args+Index)->cast<type_list_element_t<Index, typename helper_type::args_type>>()...);
|
||||
}
|
||||
} else if constexpr(std::is_invocable_v<decltype(Candidate), Type &, type_list_element_t<Index, typename helper_type::args_type>...>) {
|
||||
if(auto * const clazz = instance->try_cast<Type>(); clazz && ((args+Index)->allow_cast<type_list_element_t<Index, typename helper_type::args_type>>() && ...)) {
|
||||
return dispatch(*clazz, (args+Index)->cast<type_list_element_t<Index, typename helper_type::args_type>>()...);
|
||||
}
|
||||
} else {
|
||||
if(((args+Index)->allow_cast<type_list_element_t<Index, typename helper_type::args_type>>() && ...)) {
|
||||
return dispatch((args+Index)->cast<type_list_element_t<Index, typename helper_type::args_type>>()...);
|
||||
}
|
||||
}
|
||||
|
||||
return meta_any{};
|
||||
}
|
||||
|
||||
|
||||
@@ -209,7 +62,7 @@ template<typename Type, auto Candidate, typename Policy, std::size_t... Index>
|
||||
* there are no subtle errors at runtime.
|
||||
*/
|
||||
template<typename...>
|
||||
class meta_factory;
|
||||
struct meta_factory;
|
||||
|
||||
|
||||
/**
|
||||
@@ -218,11 +71,8 @@ class meta_factory;
|
||||
* @tparam Spec Property specialization pack used to disambiguate overloads.
|
||||
*/
|
||||
template<typename Type, typename... Spec>
|
||||
class meta_factory<Type, Spec...>: public meta_factory<Type> {
|
||||
[[nodiscard]] bool exists(const meta_any &key, const internal::meta_prop_node *node) ENTT_NOEXCEPT {
|
||||
return node && (node->key() == key || exists(key, node->next));
|
||||
}
|
||||
|
||||
struct meta_factory<Type, Spec...>: public meta_factory<Type> {
|
||||
private:
|
||||
template<std::size_t Step = 0, std::size_t... Index, typename... Property, typename... Other>
|
||||
void unpack(std::index_sequence<Index...>, std::tuple<Property...> property, Other &&... other) {
|
||||
unroll<Step>(choice<3>, std::move(std::get<Index>(property))..., std::forward<Other>(other)...);
|
||||
@@ -254,27 +104,25 @@ class meta_factory<Type, Spec...>: public meta_factory<Type> {
|
||||
template<std::size_t>
|
||||
void unroll(choice_t<0>) {}
|
||||
|
||||
template<std::size_t = 0, typename Key, typename... Value>
|
||||
void assign(Key &&key, Value &&... value) {
|
||||
static const auto property{std::make_tuple(std::forward<Key>(key), std::forward<Value>(value)...)};
|
||||
template<std::size_t = 0, typename Key>
|
||||
void assign(Key &&key, meta_any value = {}) {
|
||||
static meta_any property[2u]{};
|
||||
|
||||
static internal::meta_prop_node node{
|
||||
nullptr,
|
||||
[]() -> meta_any {
|
||||
return std::get<0>(property);
|
||||
},
|
||||
[]() -> meta_any {
|
||||
if constexpr(sizeof...(Value) == 0) {
|
||||
return {};
|
||||
} else {
|
||||
return std::get<1>(property);
|
||||
}
|
||||
}
|
||||
property[0u],
|
||||
property[1u]
|
||||
};
|
||||
|
||||
ENTT_ASSERT(!exists(node.key(), *curr));
|
||||
node.next = *curr;
|
||||
*curr = &node;
|
||||
entt::meta_any instance{std::forward<Key>(key)};
|
||||
ENTT_ASSERT(!internal::find_if_not(&instance, *curr, &node), "Duplicate key");
|
||||
property[0u] = std::move(instance);
|
||||
property[1u] = std::move(value);
|
||||
|
||||
if(!internal::find_if(&node, *curr)) {
|
||||
node.next = *curr;
|
||||
*curr = &node;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -334,18 +182,7 @@ private:
|
||||
* @tparam Type Reflected type for which the factory was created.
|
||||
*/
|
||||
template<typename Type>
|
||||
class meta_factory<Type> {
|
||||
template<typename Node>
|
||||
bool exists(const Node *candidate, const Node *node) ENTT_NOEXCEPT {
|
||||
return node && (node == candidate || exists(candidate, node->next));
|
||||
}
|
||||
|
||||
template<typename Node>
|
||||
bool exists(const id_type id, const Node *node) ENTT_NOEXCEPT {
|
||||
return node && (node->id == id || exists(id, node->next));
|
||||
}
|
||||
|
||||
public:
|
||||
struct meta_factory<Type> {
|
||||
/**
|
||||
* @brief Makes a meta type _searchable_.
|
||||
* @param id Optional unique identifier.
|
||||
@@ -354,11 +191,13 @@ public:
|
||||
auto type(const id_type id = type_hash<Type>::value()) {
|
||||
auto * const node = internal::meta_info<Type>::resolve();
|
||||
|
||||
ENTT_ASSERT(!exists(id, *internal::meta_context::global()));
|
||||
ENTT_ASSERT(!exists(node, *internal::meta_context::global()));
|
||||
ENTT_ASSERT(!internal::find_if_not(id, *internal::meta_context::global(), node), "Duplicate identifier");
|
||||
node->id = id;
|
||||
node->next = *internal::meta_context::global();
|
||||
*internal::meta_context::global() = node;
|
||||
|
||||
if(!internal::find_if(node, *internal::meta_context::global())) {
|
||||
node->next = *internal::meta_context::global();
|
||||
*internal::meta_context::global() = node;
|
||||
}
|
||||
|
||||
return meta_factory<Type, Type>{&node->prop};
|
||||
}
|
||||
@@ -385,9 +224,67 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
ENTT_ASSERT(!exists(&node, type->base));
|
||||
node.next = type->base;
|
||||
type->base = &node;
|
||||
if(!internal::find_if(&node, type->base)) {
|
||||
node.next = type->base;
|
||||
type->base = &node;
|
||||
}
|
||||
|
||||
return meta_factory<Type>{};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns a meta conversion function to a meta type.
|
||||
*
|
||||
* Conversion functions can be either free functions or member
|
||||
* functions.<br/>
|
||||
* In case of free functions, they must accept a const reference to an
|
||||
* instance of the parent type as an argument. In case of member functions,
|
||||
* they should have no arguments at all.
|
||||
*
|
||||
* @tparam Candidate The actual function to use for the conversion.
|
||||
* @return A meta factory for the parent type.
|
||||
*/
|
||||
template<auto Candidate>
|
||||
std::enable_if_t<std::is_member_function_pointer_v<decltype(Candidate)>, meta_factory<Type>> conv() ENTT_NOEXCEPT {
|
||||
using conv_type = std::invoke_result_t<decltype(Candidate), Type &>;
|
||||
auto * const type = internal::meta_info<Type>::resolve();
|
||||
|
||||
static internal::meta_conv_node node{
|
||||
type,
|
||||
nullptr,
|
||||
&internal::meta_info<conv_type>::resolve,
|
||||
[](const void *instance) -> meta_any {
|
||||
return (static_cast<const Type *>(instance)->*Candidate)();
|
||||
}
|
||||
};
|
||||
|
||||
if(!internal::find_if(&node, type->conv)) {
|
||||
node.next = type->conv;
|
||||
type->conv = &node;
|
||||
}
|
||||
|
||||
return meta_factory<Type>{};
|
||||
}
|
||||
|
||||
/*! @copydoc conv */
|
||||
template<auto Candidate>
|
||||
std::enable_if_t<!std::is_member_function_pointer_v<decltype(Candidate)>, meta_factory<Type>> conv() ENTT_NOEXCEPT {
|
||||
using conv_type = std::invoke_result_t<decltype(Candidate), Type &>;
|
||||
auto * const type = internal::meta_info<Type>::resolve();
|
||||
|
||||
static internal::meta_conv_node node{
|
||||
type,
|
||||
nullptr,
|
||||
&internal::meta_info<conv_type>::resolve,
|
||||
[](const void *instance) -> meta_any {
|
||||
return Candidate(*static_cast<const Type *>(instance));
|
||||
}
|
||||
};
|
||||
|
||||
if(!internal::find_if(&node, type->conv)) {
|
||||
node.next = type->conv;
|
||||
type->conv = &node;
|
||||
}
|
||||
|
||||
return meta_factory<Type>{};
|
||||
}
|
||||
@@ -415,42 +312,10 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
ENTT_ASSERT(!exists(&node, type->conv));
|
||||
node.next = type->conv;
|
||||
type->conv = &node;
|
||||
|
||||
return meta_factory<Type>{};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns a meta conversion function to a meta type.
|
||||
*
|
||||
* Conversion functions can be either free functions or member
|
||||
* functions.<br/>
|
||||
* In case of free functions, they must accept a const reference to an
|
||||
* instance of the parent type as an argument. In case of member functions,
|
||||
* they should have no arguments at all.
|
||||
*
|
||||
* @tparam Candidate The actual function to use for the conversion.
|
||||
* @return A meta factory for the parent type.
|
||||
*/
|
||||
template<auto Candidate>
|
||||
auto conv() ENTT_NOEXCEPT {
|
||||
using conv_type = std::invoke_result_t<decltype(Candidate), Type &>;
|
||||
auto * const type = internal::meta_info<Type>::resolve();
|
||||
|
||||
static internal::meta_conv_node node{
|
||||
type,
|
||||
nullptr,
|
||||
&internal::meta_info<conv_type>::resolve,
|
||||
[](const void *instance) -> meta_any {
|
||||
return std::invoke(Candidate, *static_cast<const Type *>(instance));
|
||||
}
|
||||
};
|
||||
|
||||
ENTT_ASSERT(!exists(&node, type->conv));
|
||||
node.next = type->conv;
|
||||
type->conv = &node;
|
||||
if(!internal::find_if(&node, type->conv)) {
|
||||
node.next = type->conv;
|
||||
type->conv = &node;
|
||||
}
|
||||
|
||||
return meta_factory<Type>{};
|
||||
}
|
||||
@@ -470,24 +335,27 @@ public:
|
||||
*/
|
||||
template<auto Candidate, typename Policy = as_is_t>
|
||||
auto ctor() ENTT_NOEXCEPT {
|
||||
using helper_type = internal::meta_function_helper_t<Type, decltype(Candidate)>;
|
||||
static_assert(std::is_same_v<std::remove_cv_t<std::remove_reference_t<typename helper_type::return_type>>, Type>, "The function doesn't return an object of the required type");
|
||||
using descriptor = meta_function_helper_t<Type, decltype(Candidate)>;
|
||||
static_assert(std::is_same_v<std::decay_t<typename descriptor::return_type>, Type>, "The function doesn't return an object of the required type");
|
||||
auto * const type = internal::meta_info<Type>::resolve();
|
||||
|
||||
static internal::meta_ctor_node node{
|
||||
type,
|
||||
nullptr,
|
||||
nullptr,
|
||||
helper_type::args_type::size,
|
||||
&helper_type::arg,
|
||||
[](meta_any * const any) {
|
||||
return internal::invoke<Type, Candidate, Policy>({}, any, std::make_index_sequence<helper_type::args_type::size>{});
|
||||
descriptor::args_type::size,
|
||||
[](const typename internal::meta_ctor_node::size_type index) ENTT_NOEXCEPT {
|
||||
return meta_arg(typename descriptor::args_type{}, index);
|
||||
},
|
||||
[](meta_any * const args) {
|
||||
return meta_invoke<Type, Candidate, Policy>({}, args, std::make_index_sequence<descriptor::args_type::size>{});
|
||||
}
|
||||
};
|
||||
|
||||
ENTT_ASSERT(!exists(&node, type->ctor));
|
||||
node.next = type->ctor;
|
||||
type->ctor = &node;
|
||||
if(!internal::find_if(&node, type->ctor)) {
|
||||
node.next = type->ctor;
|
||||
type->ctor = &node;
|
||||
}
|
||||
|
||||
return meta_factory<Type, std::integral_constant<decltype(Candidate), Candidate>>{&node.prop};
|
||||
}
|
||||
@@ -504,23 +372,26 @@ public:
|
||||
*/
|
||||
template<typename... Args>
|
||||
auto ctor() ENTT_NOEXCEPT {
|
||||
using helper_type = internal::meta_function_helper_t<Type, Type(*)(Args...)>;
|
||||
using descriptor = meta_function_helper_t<Type, Type(*)(Args...)>;
|
||||
auto * const type = internal::meta_info<Type>::resolve();
|
||||
|
||||
static internal::meta_ctor_node node{
|
||||
type,
|
||||
nullptr,
|
||||
nullptr,
|
||||
helper_type::args_type::size,
|
||||
&helper_type::arg,
|
||||
[](meta_any * const any) {
|
||||
return internal::construct<Type, Args...>(any, std::make_index_sequence<helper_type::args_type::size>{});
|
||||
descriptor::args_type::size,
|
||||
[](const typename internal::meta_ctor_node::size_type index) ENTT_NOEXCEPT {
|
||||
return meta_arg(typename descriptor::args_type{}, index);
|
||||
},
|
||||
[](meta_any * const args) {
|
||||
return meta_construct<Type, Args...>(args, std::make_index_sequence<descriptor::args_type::size>{});
|
||||
}
|
||||
};
|
||||
|
||||
ENTT_ASSERT(!exists(&node, type->ctor));
|
||||
node.next = type->ctor;
|
||||
type->ctor = &node;
|
||||
if(!internal::find_if(&node, type->ctor)) {
|
||||
node.next = type->ctor;
|
||||
type->ctor = &node;
|
||||
}
|
||||
|
||||
return meta_factory<Type, Type(Args...)>{&node.prop};
|
||||
}
|
||||
@@ -546,12 +417,8 @@ public:
|
||||
static_assert(std::is_invocable_v<decltype(Func), Type &>, "The function doesn't accept an object of the type provided");
|
||||
auto * const type = internal::meta_info<Type>::resolve();
|
||||
|
||||
ENTT_ASSERT(!type->dtor);
|
||||
|
||||
type->dtor = [](void *instance) {
|
||||
if(instance) {
|
||||
std::invoke(Func, *static_cast<Type *>(instance));
|
||||
}
|
||||
Func(*static_cast<Type *>(instance));
|
||||
};
|
||||
|
||||
return meta_factory<Type>{};
|
||||
@@ -575,7 +442,7 @@ public:
|
||||
if constexpr(std::is_member_object_pointer_v<decltype(Data)>) {
|
||||
return data<Data, Data, Policy>(id);
|
||||
} else {
|
||||
using data_type = std::remove_pointer_t<std::decay_t<decltype(Data)>>;
|
||||
using data_type = std::remove_pointer_t<decltype(Data)>;
|
||||
auto * const type = internal::meta_info<Type>::resolve();
|
||||
|
||||
static internal::meta_data_node node{
|
||||
@@ -583,23 +450,20 @@ public:
|
||||
type,
|
||||
nullptr,
|
||||
nullptr,
|
||||
std::is_same_v<Type, data_type> || std::is_const_v<data_type>,
|
||||
true,
|
||||
&internal::meta_info<data_type>::resolve,
|
||||
[]() -> std::remove_cv_t<decltype(internal::meta_data_node::set)> {
|
||||
if constexpr(std::is_same_v<Type, data_type> || std::is_const_v<data_type>) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return &internal::setter<Type, Data>;
|
||||
}
|
||||
}(),
|
||||
&internal::getter<Type, Data, Policy>
|
||||
&meta_setter<Type, Data>,
|
||||
&meta_getter<Type, Data, Policy>
|
||||
};
|
||||
|
||||
ENTT_ASSERT(!exists(id, type->data));
|
||||
ENTT_ASSERT(!exists(&node, type->data));
|
||||
ENTT_ASSERT(!internal::find_if_not(id, type->data, &node), "Duplicate identifier");
|
||||
node.id = id;
|
||||
node.next = type->data;
|
||||
type->data = &node;
|
||||
|
||||
if(!internal::find_if(&node, type->data)) {
|
||||
node.next = type->data;
|
||||
type->data = &node;
|
||||
}
|
||||
|
||||
return meta_factory<Type, std::integral_constant<decltype(Data), Data>>{&node.prop};
|
||||
}
|
||||
@@ -635,23 +499,20 @@ public:
|
||||
type,
|
||||
nullptr,
|
||||
nullptr,
|
||||
std::is_same_v<decltype(Setter), std::nullptr_t> || (std::is_member_object_pointer_v<decltype(Setter)> && std::is_const_v<underlying_type>),
|
||||
false,
|
||||
&internal::meta_info<underlying_type>::resolve,
|
||||
[]() -> std::remove_cv_t<decltype(internal::meta_data_node::set)> {
|
||||
if constexpr(std::is_same_v<decltype(Setter), std::nullptr_t> || (std::is_member_object_pointer_v<decltype(Setter)> && std::is_const_v<underlying_type>)) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return &internal::setter<Type, Setter>;
|
||||
}
|
||||
}(),
|
||||
&internal::getter<Type, Getter, Policy>
|
||||
&meta_setter<Type, Setter>,
|
||||
&meta_getter<Type, Getter, Policy>
|
||||
};
|
||||
|
||||
ENTT_ASSERT(!exists(id, type->data));
|
||||
ENTT_ASSERT(!exists(&node, type->data));
|
||||
ENTT_ASSERT(!internal::find_if_not(id, type->data, &node), "Duplicate identifier");
|
||||
node.id = id;
|
||||
node.next = type->data;
|
||||
type->data = &node;
|
||||
|
||||
if(!internal::find_if(&node, type->data)) {
|
||||
node.next = type->data;
|
||||
type->data = &node;
|
||||
}
|
||||
|
||||
return meta_factory<Type, std::integral_constant<decltype(Setter), Setter>, std::integral_constant<decltype(Getter), Getter>>{&node.prop};
|
||||
}
|
||||
@@ -671,7 +532,7 @@ public:
|
||||
*/
|
||||
template<auto Candidate, typename Policy = as_is_t>
|
||||
auto func(const id_type id) ENTT_NOEXCEPT {
|
||||
using helper_type = internal::meta_function_helper_t<Type, decltype(Candidate)>;
|
||||
using descriptor = meta_function_helper_t<Type, decltype(Candidate)>;
|
||||
auto * const type = internal::meta_info<Type>::resolve();
|
||||
|
||||
static internal::meta_func_node node{
|
||||
@@ -679,21 +540,28 @@ public:
|
||||
type,
|
||||
nullptr,
|
||||
nullptr,
|
||||
helper_type::args_type::size,
|
||||
helper_type::is_const,
|
||||
helper_type::is_static,
|
||||
&internal::meta_info<std::conditional_t<std::is_same_v<Policy, as_void_t>, void, typename helper_type::return_type>>::resolve,
|
||||
&helper_type::arg,
|
||||
descriptor::args_type::size,
|
||||
descriptor::is_const,
|
||||
descriptor::is_static,
|
||||
&internal::meta_info<std::conditional_t<std::is_same_v<Policy, as_void_t>, void, typename descriptor::return_type>>::resolve,
|
||||
[](const typename internal::meta_func_node::size_type index) ENTT_NOEXCEPT {
|
||||
return meta_arg(typename descriptor::args_type{}, index);
|
||||
},
|
||||
[](meta_handle instance, meta_any *args) {
|
||||
return internal::invoke<Type, Candidate, Policy>(std::move(instance), args, std::make_index_sequence<helper_type::args_type::size>{});
|
||||
return meta_invoke<Type, Candidate, Policy>(std::move(instance), args, std::make_index_sequence<descriptor::args_type::size>{});
|
||||
}
|
||||
};
|
||||
|
||||
ENTT_ASSERT(!exists(&node, type->func));
|
||||
for(auto *it = &type->func; *it; it = &(*it)->next) {
|
||||
if(*it == &node) {
|
||||
*it = node.next;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
internal::meta_func_node **it = &type->func;
|
||||
for(; *it && (*it)->id != id; it = &(*it)->next);
|
||||
for(; *it && (*it)->id == id && (*it)->size < node.size; it = &(*it)->next);
|
||||
for(; *it && (*it)->id == id && (*it)->arity < node.arity; it = &(*it)->next);
|
||||
|
||||
node.id = id;
|
||||
node.next = *it;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,12 @@
|
||||
#ifndef ENTT_META_INTERNAL_HPP
|
||||
#define ENTT_META_INTERNAL_HPP
|
||||
#ifndef ENTT_META_NODE_HPP
|
||||
#define ENTT_META_NODE_HPP
|
||||
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include "../core/attribute.h"
|
||||
#include "../config/config.h"
|
||||
#include "../core/attribute.h"
|
||||
#include "../core/fwd.hpp"
|
||||
#include "../core/type_info.hpp"
|
||||
#include "../core/type_traits.hpp"
|
||||
@@ -19,6 +17,7 @@ namespace entt {
|
||||
|
||||
|
||||
class meta_any;
|
||||
class meta_type;
|
||||
struct meta_handle;
|
||||
|
||||
|
||||
@@ -36,8 +35,8 @@ struct meta_type_node;
|
||||
|
||||
struct meta_prop_node {
|
||||
meta_prop_node * next;
|
||||
meta_any(* const key)();
|
||||
meta_any(* const value)();
|
||||
const meta_any &id;
|
||||
meta_any &value;
|
||||
};
|
||||
|
||||
|
||||
@@ -62,8 +61,8 @@ struct meta_ctor_node {
|
||||
meta_type_node * const parent;
|
||||
meta_ctor_node * next;
|
||||
meta_prop_node * prop;
|
||||
const size_type size;
|
||||
meta_type_node *(* const arg)(size_type) ENTT_NOEXCEPT;
|
||||
const size_type arity;
|
||||
meta_type(* const arg)(const size_type) ENTT_NOEXCEPT;
|
||||
meta_any(* const invoke)(meta_any * const);
|
||||
};
|
||||
|
||||
@@ -73,6 +72,7 @@ struct meta_data_node {
|
||||
meta_type_node * const parent;
|
||||
meta_data_node * next;
|
||||
meta_prop_node * prop;
|
||||
const bool is_const;
|
||||
const bool is_static;
|
||||
meta_type_node *(* const type)() ENTT_NOEXCEPT;
|
||||
bool(* const set)(meta_handle, meta_any);
|
||||
@@ -86,15 +86,24 @@ struct meta_func_node {
|
||||
meta_type_node * const parent;
|
||||
meta_func_node * next;
|
||||
meta_prop_node * prop;
|
||||
const size_type size;
|
||||
const size_type arity;
|
||||
const bool is_const;
|
||||
const bool is_static;
|
||||
meta_type_node *(* const ret)() ENTT_NOEXCEPT;
|
||||
meta_type_node *(* const arg)(size_type) ENTT_NOEXCEPT;
|
||||
meta_type(* const arg)(const size_type) ENTT_NOEXCEPT;
|
||||
meta_any(* const invoke)(meta_handle, meta_any *);
|
||||
};
|
||||
|
||||
|
||||
struct meta_template_info {
|
||||
using size_type = std::size_t;
|
||||
const bool is_template_specialization;
|
||||
const size_type arity;
|
||||
meta_type_node *(* const type)() ENTT_NOEXCEPT;
|
||||
meta_type_node *(* const arg)(const size_type) ENTT_NOEXCEPT;
|
||||
};
|
||||
|
||||
|
||||
struct meta_type_node {
|
||||
using size_type = std::size_t;
|
||||
const type_info info;
|
||||
@@ -116,97 +125,35 @@ struct meta_type_node {
|
||||
const bool is_pointer_like;
|
||||
const bool is_sequence_container;
|
||||
const bool is_associative_container;
|
||||
const meta_template_info template_info;
|
||||
const size_type rank;
|
||||
size_type(* const extent)(size_type);
|
||||
size_type(* const extent)(const size_type) ENTT_NOEXCEPT ;
|
||||
meta_type_node *(* const remove_pointer)() ENTT_NOEXCEPT;
|
||||
meta_type_node *(* const remove_extent)() ENTT_NOEXCEPT;
|
||||
meta_ctor_node * const def_ctor;
|
||||
meta_ctor_node *ctor{nullptr};
|
||||
meta_base_node *base{nullptr};
|
||||
meta_conv_node *conv{nullptr};
|
||||
meta_ctor_node *ctor{nullptr};
|
||||
meta_data_node *data{nullptr};
|
||||
meta_func_node *func{nullptr};
|
||||
void(* dtor)(void *){nullptr};
|
||||
};
|
||||
|
||||
|
||||
template<typename Node>
|
||||
class meta_range {
|
||||
struct range_iterator {
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using value_type = Node;
|
||||
using pointer = value_type *;
|
||||
using reference = value_type &;
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
|
||||
range_iterator() ENTT_NOEXCEPT = default;
|
||||
|
||||
range_iterator(Node *head) ENTT_NOEXCEPT
|
||||
: node{head}
|
||||
{}
|
||||
|
||||
range_iterator & operator++() ENTT_NOEXCEPT {
|
||||
return node = node->next, *this;
|
||||
}
|
||||
|
||||
range_iterator operator++(int) ENTT_NOEXCEPT {
|
||||
range_iterator orig = *this;
|
||||
return ++(*this), orig;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator==(const range_iterator &other) const ENTT_NOEXCEPT {
|
||||
return other.node == node;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator!=(const range_iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
[[nodiscard]] pointer operator->() const ENTT_NOEXCEPT {
|
||||
return node;
|
||||
}
|
||||
|
||||
[[nodiscard]] reference operator*() const ENTT_NOEXCEPT {
|
||||
return *operator->();
|
||||
}
|
||||
|
||||
private:
|
||||
Node *node{nullptr};
|
||||
};
|
||||
|
||||
public:
|
||||
using iterator = range_iterator;
|
||||
|
||||
meta_range() ENTT_NOEXCEPT = default;
|
||||
|
||||
meta_range(Node *head)
|
||||
: node{head}
|
||||
{}
|
||||
|
||||
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
|
||||
return iterator{node};
|
||||
}
|
||||
|
||||
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
|
||||
return iterator{};
|
||||
}
|
||||
|
||||
private:
|
||||
Node *node{nullptr};
|
||||
};
|
||||
|
||||
|
||||
template<auto Member, typename Op>
|
||||
auto find_if(const Op &op, const meta_type_node *node)
|
||||
template<auto Member, typename Op, typename Node>
|
||||
auto meta_visit(const Op &op, const Node *node)
|
||||
-> std::decay_t<decltype(node->*Member)> {
|
||||
for(auto &&curr: meta_range{node->*Member}) {
|
||||
if(op(&curr)) {
|
||||
return &curr;
|
||||
for(auto *curr = node->*Member; curr; curr = curr->next) {
|
||||
if(op(curr)) {
|
||||
return curr;
|
||||
}
|
||||
}
|
||||
|
||||
for(auto &&curr: meta_range{node->base}) {
|
||||
if(auto *ret = find_if<Member>(op, curr.type()); ret) {
|
||||
return ret;
|
||||
if constexpr(std::is_same_v<Node, meta_type_node>) {
|
||||
for(auto *curr = node->base; curr; curr = curr->next) {
|
||||
if(auto *ret = meta_visit<Member>(op, curr->type()); ret) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,17 +161,53 @@ auto find_if(const Op &op, const meta_type_node *node)
|
||||
}
|
||||
|
||||
|
||||
template<typename... Args>
|
||||
meta_type_node * meta_arg_node(type_list<Args...>, const std::size_t index) ENTT_NOEXCEPT;
|
||||
|
||||
|
||||
template<typename Type>
|
||||
class ENTT_API meta_node {
|
||||
static_assert(std::is_same_v<Type, std::remove_cv_t<std::remove_reference_t<Type>>>, "Invalid type");
|
||||
|
||||
template<std::size_t... Index>
|
||||
[[nodiscard]] static auto extent(meta_type_node::size_type dim, std::index_sequence<Index...>) {
|
||||
[[nodiscard]] static auto extent(const meta_type_node::size_type dim, std::index_sequence<Index...>) ENTT_NOEXCEPT {
|
||||
meta_type_node::size_type ext{};
|
||||
((ext = (dim == Index ? std::extent_v<Type, Index> : ext)), ...);
|
||||
return ext;
|
||||
}
|
||||
|
||||
[[nodiscard]] static meta_ctor_node * meta_default_constructor([[maybe_unused]] meta_type_node *type) ENTT_NOEXCEPT {
|
||||
if constexpr(std::is_default_constructible_v<Type>) {
|
||||
static meta_ctor_node node{
|
||||
type,
|
||||
nullptr,
|
||||
nullptr,
|
||||
0u,
|
||||
nullptr,
|
||||
[](meta_any * const) { return meta_any{std::in_place_type<Type>}; }
|
||||
};
|
||||
|
||||
return &node;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] static meta_template_info meta_template_descriptor() ENTT_NOEXCEPT {
|
||||
if constexpr(is_complete_v<meta_template_traits<Type>>) {
|
||||
return {
|
||||
true,
|
||||
meta_template_traits<Type>::args_type::size,
|
||||
&meta_node<typename meta_template_traits<Type>::class_type>::resolve,
|
||||
[](const std::size_t index) ENTT_NOEXCEPT {
|
||||
return meta_arg_node(typename meta_template_traits<Type>::args_type{}, index);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return { false, 0u, nullptr, nullptr };
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
[[nodiscard]] static meta_type_node * resolve() ENTT_NOEXCEPT {
|
||||
static meta_type_node node{
|
||||
@@ -245,14 +228,15 @@ public:
|
||||
std::is_member_object_pointer_v<Type>,
|
||||
std::is_member_function_pointer_v<Type>,
|
||||
is_meta_pointer_like_v<Type>,
|
||||
has_meta_sequence_container_traits_v<Type>,
|
||||
has_meta_associative_container_traits_v<Type>,
|
||||
is_complete_v<meta_sequence_container_traits<Type>>,
|
||||
is_complete_v<meta_associative_container_traits<Type>>,
|
||||
meta_template_descriptor(),
|
||||
std::rank_v<Type>,
|
||||
[](meta_type_node::size_type dim) {
|
||||
return extent(dim, std::make_index_sequence<std::rank_v<Type>>{});
|
||||
},
|
||||
&meta_node<std::remove_cv_t<std::remove_pointer_t<Type>>>::resolve,
|
||||
&meta_node<std::remove_cv_t<std::remove_extent_t<Type>>>::resolve
|
||||
[](meta_type_node::size_type dim) ENTT_NOEXCEPT { return extent(dim, std::make_index_sequence<std::rank_v<Type>>{}); },
|
||||
&meta_node<std::remove_cv_t<std::remove_reference_t<std::remove_pointer_t<Type>>>>::resolve,
|
||||
&meta_node<std::remove_cv_t<std::remove_reference_t<std::remove_extent_t<Type>>>>::resolve,
|
||||
meta_default_constructor(&node),
|
||||
meta_default_constructor(&node)
|
||||
};
|
||||
|
||||
return &node;
|
||||
@@ -260,8 +244,15 @@ public:
|
||||
};
|
||||
|
||||
|
||||
template<typename... Type>
|
||||
struct meta_info: meta_node<std::remove_cv_t<std::remove_reference_t<Type>>...> {};
|
||||
template<typename Type>
|
||||
struct meta_info: meta_node<std::remove_cv_t<std::remove_reference_t<Type>>> {};
|
||||
|
||||
|
||||
template<typename... Args>
|
||||
meta_type_node * meta_arg_node(type_list<Args...>, const std::size_t index) ENTT_NOEXCEPT {
|
||||
meta_type_node *args[sizeof...(Args) + 1u]{nullptr, internal::meta_info<Args>::resolve()...};
|
||||
return args[index + 1u];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include "internal.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
@@ -12,9 +11,10 @@ namespace entt {
|
||||
|
||||
/**
|
||||
* @brief Iterable range to use to iterate all types of meta objects.
|
||||
* @tparam Type Type of meta objects iterated.
|
||||
* @tparam Type Type of meta objects returned.
|
||||
* @tparam Node Type of meta nodes iterated.
|
||||
*/
|
||||
template<typename Type>
|
||||
template<typename Type, typename Node = typename Type::node_type>
|
||||
class meta_range {
|
||||
struct range_iterator {
|
||||
using difference_type = std::ptrdiff_t;
|
||||
@@ -22,7 +22,7 @@ class meta_range {
|
||||
using pointer = void;
|
||||
using reference = value_type;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
using node_type = typename Type::node_type;
|
||||
using node_type = Node;
|
||||
|
||||
range_iterator() ENTT_NOEXCEPT = default;
|
||||
|
||||
@@ -31,16 +31,16 @@ class meta_range {
|
||||
{}
|
||||
|
||||
range_iterator & operator++() ENTT_NOEXCEPT {
|
||||
return ++it, *this;
|
||||
return (it = it->next), *this;
|
||||
}
|
||||
|
||||
range_iterator operator++(int) ENTT_NOEXCEPT {
|
||||
range_iterator orig = *this;
|
||||
return it++, orig;
|
||||
return ++(*this), orig;
|
||||
}
|
||||
|
||||
[[nodiscard]] reference operator*() const ENTT_NOEXCEPT {
|
||||
return it.operator->();
|
||||
return it;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator==(const range_iterator &other) const ENTT_NOEXCEPT {
|
||||
@@ -52,12 +52,12 @@ class meta_range {
|
||||
}
|
||||
|
||||
private:
|
||||
typename internal::meta_range<node_type>::iterator it{};
|
||||
node_type *it{};
|
||||
};
|
||||
|
||||
public:
|
||||
/*! @brief Node type. */
|
||||
using node_type = typename Type::node_type;
|
||||
using node_type = Node;
|
||||
/*! @brief Input iterator type. */
|
||||
using iterator = range_iterator;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "../core/type_info.hpp"
|
||||
#include "ctx.hpp"
|
||||
#include "meta.hpp"
|
||||
#include "node.hpp"
|
||||
#include "range.hpp"
|
||||
|
||||
|
||||
@@ -38,8 +39,13 @@ template<typename Type>
|
||||
* @return The meta type associated with the given identifier, if any.
|
||||
*/
|
||||
[[nodiscard]] inline meta_type resolve(const id_type id) ENTT_NOEXCEPT {
|
||||
internal::meta_range range{*internal::meta_context::global()};
|
||||
return std::find_if(range.begin(), range.end(), [id](const auto &curr) { return curr.id == id; }).operator->();
|
||||
for(auto *curr = *internal::meta_context::global(); curr; curr = curr->next) {
|
||||
if(curr->id == id) {
|
||||
return curr;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
@@ -50,8 +56,13 @@ template<typename Type>
|
||||
* @return The meta type associated with the given type info object, if any.
|
||||
*/
|
||||
[[nodiscard]] inline meta_type resolve(const type_info info) ENTT_NOEXCEPT {
|
||||
internal::meta_range range{*internal::meta_context::global()};
|
||||
return std::find_if(range.begin(), range.end(), [info](const auto &curr) { return curr.info == info; }).operator->();
|
||||
for(auto *curr = *internal::meta_context::global(); curr; curr = curr->next) {
|
||||
if(curr->info == info) {
|
||||
return curr;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
|
||||
33
src/entt/meta/template.hpp
Normal file
33
src/entt/meta/template.hpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef ENTT_META_TEMPLATE_HPP
|
||||
#define ENTT_META_TEMPLATE_HPP
|
||||
|
||||
|
||||
#include "../core/type_traits.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/*! @brief Utility class to disambiguate class templates. */
|
||||
template<template<typename...> typename>
|
||||
struct meta_class_template_tag {};
|
||||
|
||||
|
||||
/**
|
||||
* @brief General purpose traits class for generating meta template information.
|
||||
* @tparam Clazz Type of class template.
|
||||
* @tparam Args Types of template arguments.
|
||||
*/
|
||||
template<template<typename...> typename Clazz, typename... Args>
|
||||
struct meta_template_traits<Clazz<Args...>> {
|
||||
/*! @brief Wrapped class template. */
|
||||
using class_type = meta_class_template_tag<Clazz>;
|
||||
/*! @brief List of template arguments. */
|
||||
using args_type = type_list<Args...>;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -8,6 +8,14 @@
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Traits class template to be specialized to enable support for meta
|
||||
* template information.
|
||||
*/
|
||||
template<typename>
|
||||
struct meta_template_traits;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Traits class template to be specialized to enable support for meta
|
||||
* sequence containers.
|
||||
@@ -24,54 +32,6 @@ template<typename>
|
||||
struct meta_associative_container_traits;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Provides the member constant `value` to true if support for meta
|
||||
* sequence containers is enabled for the given type, false otherwise.
|
||||
* @tparam Type Potentially sequence container type.
|
||||
*/
|
||||
template<typename Type, typename = void>
|
||||
struct has_meta_sequence_container_traits: std::false_type {};
|
||||
|
||||
|
||||
/*! @copydoc has_meta_sequence_container_traits */
|
||||
template<typename Type>
|
||||
struct has_meta_sequence_container_traits<Type, std::void_t<typename meta_sequence_container_traits<Type>::value_type>>
|
||||
: std::true_type
|
||||
{};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Helper variable template.
|
||||
* @tparam Type Potentially sequence container type.
|
||||
*/
|
||||
template<typename Type>
|
||||
inline constexpr auto has_meta_sequence_container_traits_v = has_meta_sequence_container_traits<Type>::value;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Provides the member constant `value` to true if support for meta
|
||||
* associative containers is enabled for the given type, false otherwise.
|
||||
* @tparam Type Potentially associative container type.
|
||||
*/
|
||||
template<typename, typename = void>
|
||||
struct has_meta_associative_container_traits: std::false_type {};
|
||||
|
||||
|
||||
/*! @copydoc has_meta_associative_container_traits */
|
||||
template<typename Type>
|
||||
struct has_meta_associative_container_traits<Type, std::void_t<typename meta_associative_container_traits<Type>::key_type>>
|
||||
: std::true_type
|
||||
{};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Helper variable template.
|
||||
* @tparam Type Potentially associative container type.
|
||||
*/
|
||||
template<typename Type>
|
||||
inline constexpr auto has_meta_associative_container_traits_v = has_meta_associative_container_traits<Type>::value;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Provides the member constant `value` to true if a meta associative
|
||||
* container claims to wrap a key-only type, false otherwise.
|
||||
@@ -83,8 +43,8 @@ struct is_key_only_meta_associative_container: std::true_type {};
|
||||
|
||||
/*! @copydoc is_key_only_meta_associative_container */
|
||||
template<typename Type>
|
||||
struct is_key_only_meta_associative_container<Type, std::void_t<typename meta_associative_container_traits<Type>::mapped_type>>
|
||||
: std::false_type
|
||||
struct is_key_only_meta_associative_container<Type, std::void_t<typename meta_associative_container_traits<Type>::type::mapped_type>>
|
||||
: std::false_type
|
||||
{};
|
||||
|
||||
|
||||
|
||||
333
src/entt/meta/utility.hpp
Normal file
333
src/entt/meta/utility.hpp
Normal file
@@ -0,0 +1,333 @@
|
||||
#ifndef ENTT_META_UTILITY_HPP
|
||||
#define ENTT_META_UTILITY_HPP
|
||||
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include "../config/config.h"
|
||||
#include "../core/type_traits.hpp"
|
||||
#include "meta.hpp"
|
||||
#include "node.hpp"
|
||||
#include "policy.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/*! @brief Primary template isn't defined on purpose. */
|
||||
template<typename, typename>
|
||||
struct meta_function_descriptor;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Meta function descriptor.
|
||||
* @tparam Type Reflected type to which the meta function is associated.
|
||||
* @tparam Ret Function return type.
|
||||
* @tparam Class Actual owner of the member function.
|
||||
* @tparam Args Function arguments.
|
||||
*/
|
||||
template<typename Type, typename Ret, typename Class, typename... Args>
|
||||
struct meta_function_descriptor<Type, Ret(Class:: *)(Args...) const> {
|
||||
/*! @brief Meta function return type. */
|
||||
using return_type = Ret;
|
||||
/*! @brief Meta function arguments. */
|
||||
using args_type = std::conditional_t<std::is_same_v<Type, Class>, type_list<Args...>, type_list<const Class &, Args...>>;
|
||||
|
||||
/*! @brief True if the meta function is const, false otherwise. */
|
||||
static constexpr auto is_const = true;
|
||||
/*! @brief True if the meta function is static, false otherwise. */
|
||||
static constexpr auto is_static = !std::is_same_v<Type, Class>;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Meta function descriptor.
|
||||
* @tparam Type Reflected type to which the meta function is associated.
|
||||
* @tparam Ret Function return type.
|
||||
* @tparam Class Actual owner of the member function.
|
||||
* @tparam Args Function arguments.
|
||||
*/
|
||||
template<typename Type, typename Ret, typename Class, typename... Args>
|
||||
struct meta_function_descriptor<Type, Ret(Class:: *)(Args...)> {
|
||||
/*! @brief Meta function return type. */
|
||||
using return_type = Ret;
|
||||
/*! @brief Meta function arguments. */
|
||||
using args_type = std::conditional_t<std::is_same_v<Type, Class>, type_list<Args...>, type_list<Class &, Args...>>;
|
||||
|
||||
/*! @brief True if the meta function is const, false otherwise. */
|
||||
static constexpr auto is_const = false;
|
||||
/*! @brief True if the meta function is static, false otherwise. */
|
||||
static constexpr auto is_static = !std::is_same_v<Type, Class>;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Meta function descriptor.
|
||||
* @tparam Type Reflected type to which the meta function is associated.
|
||||
* @tparam Ret Function return type.
|
||||
* @tparam Args Function arguments.
|
||||
*/
|
||||
template<typename Type, typename Ret, typename... Args>
|
||||
struct meta_function_descriptor<Type, Ret(*)(Args...)> {
|
||||
/*! @brief Meta function return type. */
|
||||
using return_type = Ret;
|
||||
/*! @brief Meta function arguments. */
|
||||
using args_type = type_list<Args...>;
|
||||
|
||||
/*! @brief True if the meta function is const, false otherwise. */
|
||||
static constexpr auto is_const = false;
|
||||
/*! @brief True if the meta function is static, false otherwise. */
|
||||
static constexpr auto is_static = true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Meta function helper.
|
||||
*
|
||||
* Converts a function type to be associated with a reflected type into its meta
|
||||
* function descriptor.
|
||||
*
|
||||
* @tparam Type Reflected type to which the meta function is associated.
|
||||
* @tparam Candidate The actual function to associate with the reflected type.
|
||||
*/
|
||||
template<typename Type, typename Candidate>
|
||||
class meta_function_helper {
|
||||
template<typename Ret, typename... Args, typename Class>
|
||||
static constexpr meta_function_descriptor<Type, Ret(Class:: *)(Args...) const> get_rid_of_noexcept(Ret(Class:: *)(Args...) const);
|
||||
|
||||
template<typename Ret, typename... Args, typename Class>
|
||||
static constexpr meta_function_descriptor<Type, Ret(Class:: *)(Args...)> get_rid_of_noexcept(Ret(Class:: *)(Args...));
|
||||
|
||||
template<typename Ret, typename... Args>
|
||||
static constexpr meta_function_descriptor<Type, Ret(*)(Args...)> get_rid_of_noexcept(Ret(*)(Args...));
|
||||
|
||||
public:
|
||||
/*! @brief The meta function descriptor of the given function. */
|
||||
using type = decltype(get_rid_of_noexcept(std::declval<Candidate>()));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Helper type.
|
||||
* @tparam Type Reflected type to which the meta function is associated.
|
||||
* @tparam Candidate The actual function to associate with the reflected type.
|
||||
*/
|
||||
template<typename Type, typename Candidate>
|
||||
using meta_function_helper_t = typename meta_function_helper<Type, Candidate>::type;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Returns the meta type of the i-th element of a list of arguments.
|
||||
* @tparam Args Actual types of arguments.
|
||||
* @return The meta type of the i-th element of the list of arguments.
|
||||
*/
|
||||
template<typename... Args>
|
||||
[[nodiscard]] static meta_type meta_arg(type_list<Args...>, const std::size_t index) ENTT_NOEXCEPT {
|
||||
return internal::meta_arg_node(type_list<Args...>{}, index);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Constructs an instance given a list of erased parameters, if possible.
|
||||
* @tparam Type Actual type of the instance to construct.
|
||||
* @tparam Args Types of arguments expected.
|
||||
* @tparam Index Indexes to use to extract erased arguments from their list.
|
||||
* @param args Parameters to use to construct the instance.
|
||||
* @return A meta any containing the new instance, if any.
|
||||
*/
|
||||
template<typename Type, typename... Args, std::size_t... Index>
|
||||
[[nodiscard]] meta_any meta_construct(meta_any * const args, std::index_sequence<Index...>) {
|
||||
if(((args+Index)->allow_cast<Args>() && ...)) {
|
||||
return Type{(args+Index)->cast<Args>()...};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Sets the value of a given variable.
|
||||
* @tparam Type Reflected type to which the variable is associated.
|
||||
* @tparam Data The actual variable to set.
|
||||
* @param instance An opaque instance of the underlying type, if required.
|
||||
* @param value Parameter to use to set the variable.
|
||||
* @return True in case of success, false otherwise.
|
||||
*/
|
||||
template<typename Type, auto Data>
|
||||
[[nodiscard]] bool meta_setter([[maybe_unused]] meta_handle instance, [[maybe_unused]] meta_any value) {
|
||||
if constexpr(!std::is_same_v<decltype(Data), Type> && !std::is_same_v<decltype(Data), std::nullptr_t>) {
|
||||
if constexpr(std::is_function_v<std::remove_reference_t<std::remove_pointer_t<decltype(Data)>>>) {
|
||||
using data_type = type_list_element_t<1u, typename meta_function_helper_t<Type, decltype(Data)>::args_type>;
|
||||
|
||||
if(auto * const clazz = instance->try_cast<Type>(); clazz && value.allow_cast<data_type>()) {
|
||||
Data(*clazz, value.cast<data_type>());
|
||||
return true;
|
||||
}
|
||||
} else if constexpr(std::is_member_function_pointer_v<decltype(Data)>) {
|
||||
using data_type = type_list_element_t<0u, typename meta_function_helper_t<Type, decltype(Data)>::args_type>;
|
||||
|
||||
if(auto * const clazz = instance->try_cast<Type>(); clazz && value.allow_cast<data_type>()) {
|
||||
(clazz->*Data)(value.cast<data_type>());
|
||||
return true;
|
||||
}
|
||||
} else if constexpr(std::is_member_object_pointer_v<decltype(Data)>) {
|
||||
using data_type = std::remove_reference_t<decltype(std::declval<Type>().*Data)>;
|
||||
|
||||
if constexpr(!std::is_array_v<data_type> && !std::is_const_v<data_type>) {
|
||||
if(auto * const clazz = instance->try_cast<Type>(); clazz && value.allow_cast<data_type>()) {
|
||||
clazz->*Data = value.cast<data_type>();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
using data_type = std::remove_reference_t<decltype(*Data)>;
|
||||
|
||||
if constexpr(!std::is_array_v<data_type> && !std::is_const_v<data_type>) {
|
||||
if(value.allow_cast<data_type>()) {
|
||||
*Data = value.cast<data_type>();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Wraps a value depending on the given policy.
|
||||
* @tparam Policy Optional policy (no policy set by default).
|
||||
* @tparam Type Type of value to wrap.
|
||||
* @param value Value to wrap.
|
||||
* @return A meta any containing the returned value.
|
||||
*/
|
||||
template<typename Policy = as_is_t, typename Type>
|
||||
meta_any meta_dispatch(Type &&value) {
|
||||
if constexpr(std::is_same_v<Policy, as_void_t>) {
|
||||
return meta_any{std::in_place_type<void>, std::forward<Type>(value)};
|
||||
} else if constexpr(std::is_same_v<Policy, as_ref_t>) {
|
||||
return meta_any{std::in_place_type<Type>, std::forward<Type>(value)};
|
||||
} else if constexpr(std::is_same_v<Policy, as_cref_t>) {
|
||||
static_assert(std::is_lvalue_reference_v<Type>, "Invalid type");
|
||||
return meta_any{std::in_place_type<const std::remove_reference_t<Type> &>, std::as_const(value)};
|
||||
} else {
|
||||
static_assert(std::is_same_v<Policy, as_is_t>, "Policy not supported");
|
||||
return meta_any{std::forward<Type>(value)};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Gets the value of a given variable.
|
||||
* @tparam Type Reflected type to which the variable is associated.
|
||||
* @tparam Data The actual variable to get.
|
||||
* @tparam Policy Optional policy (no policy set by default).
|
||||
* @param instance An opaque instance of the underlying type, if required.
|
||||
* @return A meta any containing the value of the underlying variable.
|
||||
*/
|
||||
template<typename Type, auto Data, typename Policy = as_is_t>
|
||||
[[nodiscard]] meta_any meta_getter([[maybe_unused]] meta_handle instance) {
|
||||
if constexpr(std::is_function_v<std::remove_reference_t<std::remove_pointer_t<decltype(Data)>>>) {
|
||||
auto * const clazz = instance->try_cast<std::conditional_t<std::is_invocable_v<decltype(Data), const Type &>, const Type, Type>>();
|
||||
return clazz ? meta_dispatch<Policy>(Data(*clazz)) : meta_any{};
|
||||
} else if constexpr(std::is_member_function_pointer_v<decltype(Data)>) {
|
||||
auto * const clazz = instance->try_cast<std::conditional_t<std::is_invocable_v<decltype(Data), const Type &>, const Type, Type>>();
|
||||
return clazz ? meta_dispatch<Policy>((clazz->*Data)()) : meta_any{};
|
||||
} else if constexpr(std::is_member_object_pointer_v<decltype(Data)>) {
|
||||
if constexpr(!std::is_array_v<std::remove_cv_t<std::remove_reference_t<decltype(std::declval<Type>().*Data)>>>) {
|
||||
if(auto * clazz = instance->try_cast<Type>(); clazz) {
|
||||
return meta_dispatch<Policy>(clazz->*Data);
|
||||
} else if(auto * fallback = instance->try_cast<const Type>(); fallback) {
|
||||
return meta_dispatch<Policy>(fallback->*Data);
|
||||
}
|
||||
}
|
||||
|
||||
return meta_any{};
|
||||
} else if constexpr(std::is_pointer_v<decltype(Data)>) {
|
||||
if constexpr(std::is_array_v<std::remove_pointer_t<decltype(Data)>>) {
|
||||
return meta_any{};
|
||||
} else {
|
||||
return meta_dispatch<Policy>(*Data);
|
||||
}
|
||||
} else {
|
||||
return meta_dispatch<Policy>(Data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Invokes a function given a list of erased parameters, if possible.
|
||||
* @tparam Type Reflected type to which the function is associated.
|
||||
* @tparam Candidate The actual function to invoke.
|
||||
* @tparam Policy Optional policy (no policy set by default).
|
||||
* @tparam Index Indexes to use to extract erased arguments from their list.
|
||||
* @param instance An opaque instance of the underlying type, if required.
|
||||
* @param args Parameters to use to invoke the function.
|
||||
* @return A meta any containing the returned value, if any.
|
||||
*/
|
||||
template<typename Type, auto Candidate, typename Policy = as_is_t, std::size_t... Index>
|
||||
[[nodiscard]] std::enable_if_t<!std::is_invocable_v<decltype(Candidate)>, meta_any> meta_invoke([[maybe_unused]] meta_handle instance, meta_any *args, std::index_sequence<Index...>) {
|
||||
using descriptor = meta_function_helper_t<Type, decltype(Candidate)>;
|
||||
|
||||
const auto invoke = [](auto &&maybe_clazz, auto &&... other) {
|
||||
if constexpr(std::is_member_function_pointer_v<decltype(Candidate)>) {
|
||||
if constexpr(std::is_void_v<typename descriptor::return_type>) {
|
||||
(std::forward<decltype(maybe_clazz)>(maybe_clazz).*Candidate)(std::forward<decltype(other)>(other)...);
|
||||
return meta_any{std::in_place_type<void>};
|
||||
} else {
|
||||
return meta_dispatch<Policy>((std::forward<decltype(maybe_clazz)>(maybe_clazz).*Candidate)(std::forward<decltype(other)>(other)...));
|
||||
}
|
||||
} else {
|
||||
if constexpr(std::is_void_v<typename descriptor::return_type>) {
|
||||
Candidate(std::forward<decltype(maybe_clazz)>(maybe_clazz), std::forward<decltype(other)>(other)...);
|
||||
return meta_any{std::in_place_type<void>};
|
||||
} else {
|
||||
return meta_dispatch<Policy>(Candidate(std::forward<decltype(maybe_clazz)>(maybe_clazz), std::forward<decltype(other)>(other)...));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if constexpr(std::is_invocable_v<decltype(Candidate), const Type &, type_list_element_t<Index, typename descriptor::args_type>...>) {
|
||||
if(const auto * const clazz = instance->try_cast<const Type>(); clazz && ((args+Index)->allow_cast<type_list_element_t<Index, typename descriptor::args_type>>() && ...)) {
|
||||
return invoke(*clazz, (args+Index)->cast<type_list_element_t<Index, typename descriptor::args_type>>()...);
|
||||
}
|
||||
} else if constexpr(std::is_invocable_v<decltype(Candidate), Type &, type_list_element_t<Index, typename descriptor::args_type>...>) {
|
||||
if(auto * const clazz = instance->try_cast<Type>(); clazz && ((args+Index)->allow_cast<type_list_element_t<Index, typename descriptor::args_type>>() && ...)) {
|
||||
return invoke(*clazz, (args+Index)->cast<type_list_element_t<Index, typename descriptor::args_type>>()...);
|
||||
}
|
||||
} else {
|
||||
if(((args+Index)->allow_cast<type_list_element_t<Index, typename descriptor::args_type>>() && ...)) {
|
||||
return invoke((args+Index)->cast<type_list_element_t<Index, typename descriptor::args_type>>()...);
|
||||
}
|
||||
}
|
||||
|
||||
return meta_any{};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Invokes a function given a list of erased parameters, if possible.
|
||||
* @tparam Type Reflected type to which the function is associated.
|
||||
* @tparam Candidate The actual function to invoke.
|
||||
* @tparam Policy Optional policy (no policy set by default).
|
||||
* @tparam Index Indexes to use to extract erased arguments from their list.
|
||||
* @return A meta any containing the returned value, if any.
|
||||
*/
|
||||
template<typename Type, auto Candidate, typename Policy = as_is_t, std::size_t... Index>
|
||||
[[nodiscard]] std::enable_if_t<std::is_invocable_v<decltype(Candidate)>, meta_any> meta_invoke(meta_handle, meta_any *, std::index_sequence<Index...>) {
|
||||
if constexpr(std::is_void_v<decltype(Candidate())>) {
|
||||
Candidate();
|
||||
return meta_any{std::in_place_type<void>};
|
||||
} else {
|
||||
return meta_dispatch<Policy>(Candidate());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
26
src/entt/poly/fwd.hpp
Normal file
26
src/entt/poly/fwd.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef ENTT_POLY_FWD_HPP
|
||||
#define ENTT_POLY_FWD_HPP
|
||||
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
template<typename, std::size_t Len, std::size_t = alignof(typename std::aligned_storage_t<Len + !Len>)>
|
||||
class basic_poly;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Alias declaration for the most common use case.
|
||||
* @tparam Concept Concept descriptor.
|
||||
*/
|
||||
template<typename Concept>
|
||||
using poly = basic_poly<Concept, sizeof(double[2])>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "../core/any.hpp"
|
||||
#include "../core/type_info.hpp"
|
||||
#include "../core/type_traits.hpp"
|
||||
#include "fwd.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
@@ -44,29 +45,31 @@ struct poly_inspector {
|
||||
/**
|
||||
* @brief Static virtual table factory.
|
||||
* @tparam Concept Concept descriptor.
|
||||
* @tparam Len Size of the storage reserved for the small buffer optimization.
|
||||
* @tparam Align Alignment requirement.
|
||||
*/
|
||||
template<typename Concept>
|
||||
template<typename Concept, std::size_t Len, std::size_t Align>
|
||||
class poly_vtable {
|
||||
using inspector = typename Concept::template type<poly_inspector>;
|
||||
|
||||
template<typename Ret, typename... Args>
|
||||
static auto vtable_entry(Ret(*)(inspector &, Args...)) -> Ret(*)(any &, Args...);
|
||||
static auto vtable_entry(Ret(*)(inspector &, Args...)) -> Ret(*)(basic_any<Len, Align> &, Args...);
|
||||
|
||||
template<typename Ret, typename... Args>
|
||||
static auto vtable_entry(Ret(*)(const inspector &, Args...)) -> Ret(*)(const any &, Args...);
|
||||
static auto vtable_entry(Ret(*)(const inspector &, Args...)) -> Ret(*)(const basic_any<Len, Align> &, Args...);
|
||||
|
||||
template<typename Ret, typename... Args>
|
||||
static auto vtable_entry(Ret(*)(Args...)) -> Ret(*)(const any &, Args...);
|
||||
static auto vtable_entry(Ret(*)(Args...)) -> Ret(*)(const basic_any<Len, Align> &, Args...);
|
||||
|
||||
template<typename Ret, typename... Args>
|
||||
static auto vtable_entry(Ret(inspector:: *)(Args...)) -> Ret(*)(any &, Args...);
|
||||
static auto vtable_entry(Ret(inspector:: *)(Args...)) -> Ret(*)(basic_any<Len, Align> &, Args...);
|
||||
|
||||
template<typename Ret, typename... Args>
|
||||
static auto vtable_entry(Ret(inspector:: *)(Args...) const) -> Ret(*)(const any &, Args...);
|
||||
static auto vtable_entry(Ret(inspector:: *)(Args...) const) -> Ret(*)(const basic_any<Len, Align> &, Args...);
|
||||
|
||||
template<auto... Candidate>
|
||||
static auto make_vtable(value_list<Candidate...>)
|
||||
-> std::tuple<decltype(vtable_entry(Candidate))...>;
|
||||
-> decltype(std::make_tuple(vtable_entry(Candidate)...));
|
||||
|
||||
template<typename... Func>
|
||||
[[nodiscard]] static constexpr auto make_vtable(type_list<Func...>) {
|
||||
@@ -84,8 +87,8 @@ class poly_vtable {
|
||||
return std::invoke(Candidate, std::forward<Args>(args)...);
|
||||
};
|
||||
} else {
|
||||
entry = +[](Any &any, Args... args) -> Ret {
|
||||
return static_cast<Ret>(std::invoke(Candidate, any_cast<constness_as_t<Type, Any> &>(any), std::forward<Args>(args)...));
|
||||
entry = +[](Any &instance, Args... args) -> Ret {
|
||||
return static_cast<Ret>(std::invoke(Candidate, any_cast<constness_as_t<Type, Any> &>(instance), std::forward<Args>(args)...));
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -171,20 +174,22 @@ decltype(auto) poly_call(Poly &&self, Args &&... args) {
|
||||
* Moreover, the `poly` class template also works with unmanaged objects.
|
||||
*
|
||||
* @tparam Concept Concept descriptor.
|
||||
* @tparam Len Size of the storage reserved for the small buffer optimization.
|
||||
* @tparam Align Optional alignment requirement.
|
||||
*/
|
||||
template<typename Concept>
|
||||
class poly: private Concept::template type<poly_base<poly<Concept>>> {
|
||||
template<typename Concept, std::size_t Len, std::size_t Align>
|
||||
class basic_poly: private Concept::template type<poly_base<basic_poly<Concept, Len, Align>>> {
|
||||
/*! @brief A poly base is allowed to snoop into a poly object. */
|
||||
friend struct poly_base<poly<Concept>>;
|
||||
friend struct poly_base<basic_poly>;
|
||||
|
||||
using vtable_type = typename poly_vtable<Concept>::type;
|
||||
using vtable_type = typename poly_vtable<Concept, Len, Align>::type;
|
||||
|
||||
public:
|
||||
/*! @brief Concept type. */
|
||||
using concept_type = typename Concept::template type<poly_base<poly<Concept>>>;
|
||||
using concept_type = typename Concept::template type<poly_base<basic_poly>>;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
poly() ENTT_NOEXCEPT
|
||||
basic_poly() ENTT_NOEXCEPT
|
||||
: storage{},
|
||||
vtable{}
|
||||
{}
|
||||
@@ -196,19 +201,9 @@ public:
|
||||
* @param args Parameters to use to construct the instance.
|
||||
*/
|
||||
template<typename Type, typename... Args>
|
||||
explicit poly(std::in_place_type_t<Type>, Args &&... args)
|
||||
explicit basic_poly(std::in_place_type_t<Type>, Args &&... args)
|
||||
: storage{std::in_place_type<Type>, std::forward<Args>(args)...},
|
||||
vtable{poly_vtable<Concept>::template instance<std::remove_const_t<std::remove_reference_t<Type>>>()}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Constructs a poly that holds an unmanaged object.
|
||||
* @tparam Type Type of object to use to initialize the poly.
|
||||
* @param value An instance of an object to use to initialize the poly.
|
||||
*/
|
||||
template<typename Type>
|
||||
poly(std::reference_wrapper<Type> value)
|
||||
: poly{std::in_place_type<Type &>, &value.get()}
|
||||
vtable{poly_vtable<Concept, Len, Align>::template instance<std::remove_const_t<std::remove_reference_t<Type>>>()}
|
||||
{}
|
||||
|
||||
/**
|
||||
@@ -216,23 +211,23 @@ public:
|
||||
* @tparam Type Type of object to use to initialize the poly.
|
||||
* @param value An instance of an object to use to initialize the poly.
|
||||
*/
|
||||
template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_reference_t<Type>>, poly>>>
|
||||
poly(Type &&value) ENTT_NOEXCEPT
|
||||
: poly{std::in_place_type<std::remove_cv_t<std::remove_reference_t<Type>>>, std::forward<Type>(value)}
|
||||
template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_reference_t<Type>>, basic_poly>>>
|
||||
basic_poly(Type &&value) ENTT_NOEXCEPT
|
||||
: basic_poly{std::in_place_type<std::remove_cv_t<std::remove_reference_t<Type>>>, std::forward<Type>(value)}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Copy constructor.
|
||||
* @param other The instance to copy from.
|
||||
*/
|
||||
poly(const poly &other) = default;
|
||||
basic_poly(const basic_poly &other) = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
* @param other The instance to move from.
|
||||
*/
|
||||
poly(poly &&other) ENTT_NOEXCEPT
|
||||
: poly{}
|
||||
basic_poly(basic_poly &&other) ENTT_NOEXCEPT
|
||||
: basic_poly{}
|
||||
{
|
||||
swap(*this, other);
|
||||
}
|
||||
@@ -242,7 +237,7 @@ public:
|
||||
* @param other The instance to assign from.
|
||||
* @return This poly object.
|
||||
*/
|
||||
poly & operator=(poly other) {
|
||||
basic_poly & operator=(basic_poly other) {
|
||||
swap(other, *this);
|
||||
return *this;
|
||||
}
|
||||
@@ -276,8 +271,12 @@ public:
|
||||
*/
|
||||
template<typename Type, typename... Args>
|
||||
void emplace(Args &&... args) {
|
||||
storage.emplace<Type>(std::forward<Args>(args)...);
|
||||
vtable = poly_vtable<Concept>::template instance<Type>();
|
||||
*this = basic_poly{std::in_place_type<Type>, std::forward<Args>(args)...};
|
||||
}
|
||||
|
||||
/*! @brief Destroys contained object */
|
||||
void reset() {
|
||||
*this = basic_poly{};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -306,7 +305,7 @@ public:
|
||||
* @param lhs A valid poly object.
|
||||
* @param rhs A valid poly object.
|
||||
*/
|
||||
friend void swap(poly &lhs, poly &rhs) {
|
||||
friend void swap(basic_poly &lhs, basic_poly &rhs) {
|
||||
using std::swap;
|
||||
swap(lhs.storage, rhs.storage);
|
||||
swap(lhs.vtable, rhs.vtable);
|
||||
@@ -314,26 +313,24 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Aliasing constructor.
|
||||
* @param other A reference to an object that isn't necessarily initialized.
|
||||
* @return A poly that shares a reference to an unmanaged object.
|
||||
*/
|
||||
[[nodiscard]] friend poly as_ref(poly &other) ENTT_NOEXCEPT {
|
||||
poly ref;
|
||||
ref.storage = as_ref(other.storage);
|
||||
ref.vtable = other.vtable;
|
||||
[[nodiscard]] basic_poly as_ref() ENTT_NOEXCEPT {
|
||||
basic_poly ref = std::as_const(*this).as_ref();
|
||||
ref.storage = storage.as_ref();
|
||||
return ref;
|
||||
}
|
||||
|
||||
/*! @copydoc as_ref */
|
||||
[[nodiscard]] friend poly as_ref(const poly &other) ENTT_NOEXCEPT {
|
||||
poly ref;
|
||||
ref.storage = as_ref(other.storage);
|
||||
ref.vtable = other.vtable;
|
||||
[[nodiscard]] basic_poly as_ref() const ENTT_NOEXCEPT {
|
||||
basic_poly ref{};
|
||||
ref.storage = storage.as_ref();
|
||||
ref.vtable = vtable;
|
||||
return ref;
|
||||
}
|
||||
|
||||
private:
|
||||
any storage;
|
||||
basic_any<Len, Align> storage;
|
||||
const vtable_type *vtable;
|
||||
};
|
||||
|
||||
|
||||
@@ -78,7 +78,8 @@ class process {
|
||||
SUCCEEDED,
|
||||
FAILED,
|
||||
ABORTED,
|
||||
FINISHED
|
||||
FINISHED,
|
||||
REJECTED
|
||||
};
|
||||
|
||||
template<typename Target = Derived>
|
||||
@@ -201,7 +202,7 @@ public:
|
||||
* @brief Returns true if a process is already terminated.
|
||||
* @return True if the process is terminated, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool dead() const ENTT_NOEXCEPT {
|
||||
[[nodiscard]] bool finished() const ENTT_NOEXCEPT {
|
||||
return current == state::FINISHED;
|
||||
}
|
||||
|
||||
@@ -218,7 +219,7 @@ public:
|
||||
* @return True if the process terminated with errors, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool rejected() const ENTT_NOEXCEPT {
|
||||
return stopped;
|
||||
return current == state::REJECTED;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -248,13 +249,11 @@ public:
|
||||
break;
|
||||
case state::FAILED:
|
||||
next(std::integral_constant<state, state::FAILED>{});
|
||||
current = state::FINISHED;
|
||||
stopped = true;
|
||||
current = state::REJECTED;
|
||||
break;
|
||||
case state::ABORTED:
|
||||
next(std::integral_constant<state, state::ABORTED>{});
|
||||
current = state::FINISHED;
|
||||
stopped = true;
|
||||
current = state::REJECTED;
|
||||
break;
|
||||
default:
|
||||
// suppress warnings
|
||||
@@ -264,7 +263,6 @@ public:
|
||||
|
||||
private:
|
||||
state current{state::UNINITIALIZED};
|
||||
bool stopped{false};
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -57,9 +57,7 @@ class scheduler {
|
||||
struct continuation {
|
||||
continuation(process_handler *ref)
|
||||
: handler{ref}
|
||||
{
|
||||
ENTT_ASSERT(handler);
|
||||
}
|
||||
{}
|
||||
|
||||
template<typename Proc, typename... Args>
|
||||
continuation then(Args &&... args) {
|
||||
@@ -84,19 +82,19 @@ class scheduler {
|
||||
auto *process = static_cast<Proc *>(handler.instance.get());
|
||||
process->tick(delta, data);
|
||||
|
||||
auto dead = process->dead();
|
||||
|
||||
if(dead) {
|
||||
if(handler.next && !process->rejected()) {
|
||||
if(process->rejected()) {
|
||||
return true;
|
||||
} else if(process->finished()) {
|
||||
if(handler.next) {
|
||||
handler = std::move(*handler.next);
|
||||
// forces the process to exit the uninitialized state
|
||||
dead = handler.update(handler, {}, nullptr);
|
||||
} else {
|
||||
handler.instance.reset();
|
||||
return handler.update(handler, {}, nullptr);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return dead;
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename Proc>
|
||||
@@ -252,19 +250,17 @@ public:
|
||||
* @param data Optional data.
|
||||
*/
|
||||
void update(const Delta delta, void *data = nullptr) {
|
||||
bool clean = false;
|
||||
auto sz = handlers.size();
|
||||
|
||||
for(auto pos = handlers.size(); pos; --pos) {
|
||||
auto &handler = handlers[pos-1];
|
||||
const bool dead = handler.update(handler, delta, data);
|
||||
clean = clean || dead;
|
||||
|
||||
if(const auto dead = handler.update(handler, delta, data); dead) {
|
||||
std::swap(handler, handlers[--sz]);
|
||||
}
|
||||
}
|
||||
|
||||
if(clean) {
|
||||
handlers.erase(std::remove_if(handlers.begin(), handlers.end(), [](auto &handler) {
|
||||
return !handler.instance;
|
||||
}), handlers.end());
|
||||
}
|
||||
handlers.erase(handlers.begin() + sz, handlers.end());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#define ENTT_RESOURCE_CACHE_HPP
|
||||
|
||||
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
@@ -92,19 +91,15 @@ struct resource_cache {
|
||||
*/
|
||||
template<typename Loader, typename... Args>
|
||||
resource_handle<Resource> load(const id_type id, Args &&... args) {
|
||||
static_assert(std::is_base_of_v<resource_loader<Loader, Resource>, Loader>, "Invalid loader type");
|
||||
resource_handle<Resource> resource{};
|
||||
|
||||
if(auto it = resources.find(id); it == resources.cend()) {
|
||||
if(auto instance = Loader{}.get(std::forward<Args>(args)...); instance) {
|
||||
resources[id] = instance;
|
||||
resource = std::move(instance);
|
||||
if(auto handle = temp<Loader>(std::forward<Args>(args)...); handle) {
|
||||
return (resources[id] = std::move(handle));
|
||||
}
|
||||
} else {
|
||||
resource = it->second;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
return resource;
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,7 +144,7 @@ struct resource_cache {
|
||||
*/
|
||||
template<typename Loader, typename... Args>
|
||||
[[nodiscard]] resource_handle<Resource> temp(Args &&... args) const {
|
||||
return { Loader{}.get(std::forward<Args>(args)...) };
|
||||
return Loader{}.get(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -166,8 +161,11 @@ struct resource_cache {
|
||||
* @return A handle for the given resource.
|
||||
*/
|
||||
[[nodiscard]] resource_handle<Resource> handle(const id_type id) const {
|
||||
auto it = resources.find(id);
|
||||
return { it == resources.end() ? nullptr : it->second };
|
||||
if(auto it = resources.find(id); it != resources.cend()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -221,15 +219,15 @@ struct resource_cache {
|
||||
if constexpr(std::is_invocable_v<Func, id_type>) {
|
||||
func(curr->first);
|
||||
} else if constexpr(std::is_invocable_v<Func, resource_handle<Resource>>) {
|
||||
func(resource_handle{ curr->second });
|
||||
func(curr->second);
|
||||
} else {
|
||||
func(curr->first, resource_handle{ curr->second });
|
||||
func(curr->first, curr->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<id_type, std::shared_ptr<Resource>> resources;
|
||||
std::unordered_map<id_type, resource_handle<Resource>> resources;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include "../config/config.h"
|
||||
#include "fwd.hpp"
|
||||
@@ -25,17 +26,94 @@ namespace entt {
|
||||
*/
|
||||
template<typename Resource>
|
||||
class resource_handle {
|
||||
/*! @brief Resource handles are friends of their caches. */
|
||||
friend struct resource_cache<Resource>;
|
||||
|
||||
resource_handle(std::shared_ptr<Resource> res) ENTT_NOEXCEPT
|
||||
: resource{std::move(res)}
|
||||
{}
|
||||
/*! @brief Resource handles are friends with each other. */
|
||||
template<typename>
|
||||
friend class resource_handle;
|
||||
|
||||
public:
|
||||
/*! @brief Default constructor. */
|
||||
resource_handle() ENTT_NOEXCEPT = default;
|
||||
|
||||
/**
|
||||
* @brief Creates a handle from a shared pointer, namely a resource.
|
||||
* @param res A pointer to a properly initialized resource.
|
||||
*/
|
||||
resource_handle(std::shared_ptr<Resource> res) ENTT_NOEXCEPT
|
||||
: resource{std::move(res)}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Copy constructor.
|
||||
* @param other The instance to copy from.
|
||||
*/
|
||||
resource_handle(const resource_handle<Resource> &other) ENTT_NOEXCEPT = default;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
* @param other The instance to move from.
|
||||
*/
|
||||
resource_handle(resource_handle<Resource> &&other) ENTT_NOEXCEPT = default;
|
||||
|
||||
/**
|
||||
* @brief Copy constructs a handle which shares ownership of the resource.
|
||||
* @tparam Other Type of resource managed by the received handle.
|
||||
* @param other The handle to copy from.
|
||||
*/
|
||||
template<typename Other, typename = std::enable_if_t<!std::is_same_v<Other, Resource> && std::is_base_of_v<Resource, Other>>>
|
||||
resource_handle(const resource_handle<Other> &other) ENTT_NOEXCEPT
|
||||
: resource{other.resource}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Move constructs a handle which takes ownership of the resource.
|
||||
* @tparam Other Type of resource managed by the received handle.
|
||||
* @param other The handle to move from.
|
||||
*/
|
||||
template<typename Other, typename = std::enable_if_t<!std::is_same_v<Other, Resource> && std::is_base_of_v<Resource, Other>>>
|
||||
resource_handle(resource_handle<Other> &&other) ENTT_NOEXCEPT
|
||||
: resource{std::move(other.resource)}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Copy assignment operator.
|
||||
* @param other The instance to copy from.
|
||||
* @return This resource handle.
|
||||
*/
|
||||
resource_handle & operator=(const resource_handle<Resource> &other) ENTT_NOEXCEPT = default;
|
||||
|
||||
/**
|
||||
* @brief Move assignment operator.
|
||||
* @param other The instance to move from.
|
||||
* @return This resource handle.
|
||||
*/
|
||||
resource_handle & operator=(resource_handle<Resource> &&other) ENTT_NOEXCEPT = default;
|
||||
|
||||
/**
|
||||
* @brief Copy assignment operator from foreign handle.
|
||||
* @tparam Other Type of resource managed by the received handle.
|
||||
* @param other The handle to copy from.
|
||||
* @return This resource handle.
|
||||
*/
|
||||
template<typename Other>
|
||||
std::enable_if_t<!std::is_same_v<Other, Resource> && std::is_base_of_v<Resource, Other>, resource_handle &>
|
||||
operator=(const resource_handle<Other> &other) ENTT_NOEXCEPT {
|
||||
resource = other.resource;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move assignment operator from foreign handle.
|
||||
* @tparam Other Type of resource managed by the received handle.
|
||||
* @param other The handle to move from.
|
||||
* @return This resource handle.
|
||||
*/
|
||||
template<typename Other>
|
||||
std::enable_if_t<!std::is_same_v<Other, Resource> && std::is_base_of_v<Resource, Other>, resource_handle &>
|
||||
operator=(resource_handle<Other> &&other) ENTT_NOEXCEPT {
|
||||
resource = std::move(other.resource);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets a reference to the managed resource.
|
||||
*
|
||||
@@ -45,7 +123,7 @@ public:
|
||||
* @return A reference to the managed resource.
|
||||
*/
|
||||
[[nodiscard]] const Resource & get() const ENTT_NOEXCEPT {
|
||||
ENTT_ASSERT(static_cast<bool>(resource));
|
||||
ENTT_ASSERT(static_cast<bool>(resource), "Invalid resource");
|
||||
return *resource;
|
||||
}
|
||||
|
||||
@@ -84,7 +162,6 @@ public:
|
||||
* contains no resource at all.
|
||||
*/
|
||||
[[nodiscard]] const Resource * operator->() const ENTT_NOEXCEPT {
|
||||
ENTT_ASSERT(static_cast<bool>(resource));
|
||||
return resource.get();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
#define ENTT_RESOURCE_LOADER_HPP
|
||||
|
||||
|
||||
#include <memory>
|
||||
#include "fwd.hpp"
|
||||
#include "handle.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
@@ -15,14 +15,14 @@ namespace entt {
|
||||
* Resource loaders must inherit from this class and stay true to the CRTP
|
||||
* idiom. Moreover, a resource loader must expose a public, const member
|
||||
* function named `load` that accepts a variable number of arguments and returns
|
||||
* a shared pointer to the resource just created.<br/>
|
||||
* a handle to the resource just created.<br/>
|
||||
* As an example:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* struct my_resource {};
|
||||
*
|
||||
* struct my_loader: entt::resource_loader<my_loader, my_resource> {
|
||||
* std::shared_ptr<my_resource> load(int) const {
|
||||
* resource_handle<my_resource> load(int value) const {
|
||||
* // use the integer value somehow
|
||||
* return std::make_shared<my_resource>();
|
||||
* }
|
||||
@@ -44,7 +44,8 @@ namespace entt {
|
||||
template<typename Loader, typename Resource>
|
||||
class resource_loader {
|
||||
/*! @brief Resource loaders are friends of their caches. */
|
||||
friend struct resource_cache<Resource>;
|
||||
template<typename Other>
|
||||
friend struct resource_cache;
|
||||
|
||||
/**
|
||||
* @brief Loads the resource and returns it.
|
||||
@@ -53,7 +54,7 @@ class resource_loader {
|
||||
* @return The resource just loaded or an empty pointer in case of errors.
|
||||
*/
|
||||
template<typename... Args>
|
||||
[[nodiscard]] std::shared_ptr<Resource> get(Args &&... args) const {
|
||||
[[nodiscard]] resource_handle<Resource> get(Args &&... args) const {
|
||||
return static_cast<const Loader *>(this)->load(std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -288,7 +288,7 @@ public:
|
||||
* @return The value returned by the underlying function.
|
||||
*/
|
||||
Ret operator()(Args... args) const {
|
||||
ENTT_ASSERT(fn);
|
||||
ENTT_ASSERT(static_cast<bool>(*this), "Uninitialized delegate");
|
||||
return fn(data, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
@@ -335,7 +335,7 @@ template<typename Ret, typename... Args>
|
||||
* @tparam Candidate Function or member to connect to the delegate.
|
||||
*/
|
||||
template<auto Candidate>
|
||||
delegate(connect_arg_t<Candidate>) ENTT_NOEXCEPT
|
||||
delegate(connect_arg_t<Candidate>)
|
||||
-> delegate<std::remove_pointer_t<internal::function_pointer_t<decltype(Candidate)>>>;
|
||||
|
||||
|
||||
@@ -345,7 +345,7 @@ delegate(connect_arg_t<Candidate>) ENTT_NOEXCEPT
|
||||
* @tparam Type Type of class or type of payload.
|
||||
*/
|
||||
template<auto Candidate, typename Type>
|
||||
delegate(connect_arg_t<Candidate>, Type &&) ENTT_NOEXCEPT
|
||||
delegate(connect_arg_t<Candidate>, Type &&)
|
||||
-> delegate<std::remove_pointer_t<internal::function_pointer_t<decltype(Candidate), Type>>>;
|
||||
|
||||
|
||||
@@ -355,7 +355,7 @@ delegate(connect_arg_t<Candidate>, Type &&) ENTT_NOEXCEPT
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
*/
|
||||
template<typename Ret, typename... Args>
|
||||
delegate(Ret(*)(const void *, Args...), const void * = nullptr) ENTT_NOEXCEPT
|
||||
delegate(Ret(*)(const void *, Args...), const void * = nullptr)
|
||||
-> delegate<Ret(Args...)>;
|
||||
|
||||
|
||||
|
||||
@@ -101,6 +101,15 @@ class dispatcher {
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Default constructor. */
|
||||
dispatcher() = default;
|
||||
|
||||
/*! @brief Default move constructor. */
|
||||
dispatcher(dispatcher &&) = default;
|
||||
|
||||
/*! @brief Default move assignment operator. @return This dispatcher. */
|
||||
dispatcher & operator=(dispatcher &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Returns a sink object for the given event.
|
||||
*
|
||||
|
||||
@@ -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...)>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -58,7 +58,13 @@ function(SETUP_TARGET TARGET_NAME)
|
||||
)
|
||||
endif()
|
||||
|
||||
target_compile_definitions(${TARGET_NAME} PRIVATE ENTT_STANDALONE ${ARGN})
|
||||
target_compile_definitions(
|
||||
${TARGET_NAME}
|
||||
PRIVATE
|
||||
_ENABLE_EXTENDED_ALIGNED_STORAGE
|
||||
NOMINMAX
|
||||
${ARGN}
|
||||
)
|
||||
endfunction()
|
||||
|
||||
add_library(odr OBJECT odr.cpp)
|
||||
@@ -80,8 +86,8 @@ endfunction()
|
||||
|
||||
function(SETUP_PLUGIN_TEST TEST_NAME)
|
||||
add_library(_${TEST_NAME} MODULE $<TARGET_OBJECTS:odr> lib/${TEST_NAME}/plugin.cpp)
|
||||
SETUP_TARGET(_${TEST_NAME} NOMINMAX ${ARGVN})
|
||||
SETUP_BASIC_TEST(lib_${TEST_NAME} lib/${TEST_NAME}/main.cpp NOMINMAX PLUGIN="$<TARGET_FILE:_${TEST_NAME}>" ${ARGVN})
|
||||
SETUP_TARGET(_${TEST_NAME} ${ARGVN})
|
||||
SETUP_BASIC_TEST(lib_${TEST_NAME} lib/${TEST_NAME}/main.cpp PLUGIN="$<TARGET_FILE:_${TEST_NAME}>" ${ARGVN})
|
||||
target_include_directories(_${TEST_NAME} PRIVATE ${cr_INCLUDE_DIR})
|
||||
target_include_directories(lib_${TEST_NAME} PRIVATE ${cr_INCLUDE_DIR})
|
||||
target_link_libraries(lib_${TEST_NAME} PRIVATE ${CMAKE_DL_LIBS})
|
||||
@@ -97,6 +103,7 @@ endif()
|
||||
|
||||
if(ENTT_BUILD_EXAMPLE)
|
||||
SETUP_BASIC_TEST(custom_identifier example/custom_identifier.cpp)
|
||||
SETUP_BASIC_TEST(signal_less example/signal_less.cpp)
|
||||
endif()
|
||||
|
||||
# Test lib
|
||||
@@ -178,7 +185,6 @@ SETUP_BASIC_TEST(snapshot entt/entity/snapshot.cpp)
|
||||
SETUP_BASIC_TEST(sparse_set entt/entity/sparse_set.cpp)
|
||||
SETUP_BASIC_TEST(storage entt/entity/storage.cpp)
|
||||
SETUP_BASIC_TEST(view entt/entity/view.cpp)
|
||||
SETUP_BASIC_TEST(view_pack entt/entity/view_pack.cpp)
|
||||
|
||||
# Test locator
|
||||
|
||||
@@ -192,11 +198,13 @@ SETUP_BASIC_TEST(meta_container entt/meta/meta_container.cpp)
|
||||
SETUP_BASIC_TEST(meta_conv entt/meta/meta_conv.cpp)
|
||||
SETUP_BASIC_TEST(meta_ctor entt/meta/meta_ctor.cpp)
|
||||
SETUP_BASIC_TEST(meta_data entt/meta/meta_data.cpp)
|
||||
SETUP_BASIC_TEST(meta_dtor entt/meta/meta_dtor.cpp)
|
||||
SETUP_BASIC_TEST(meta_func entt/meta/meta_func.cpp)
|
||||
SETUP_BASIC_TEST(meta_handle entt/meta/meta_handle.cpp)
|
||||
SETUP_BASIC_TEST(meta_pointer entt/meta/meta_pointer.cpp)
|
||||
SETUP_BASIC_TEST(meta_prop entt/meta/meta_prop.cpp)
|
||||
SETUP_BASIC_TEST(meta_range entt/meta/meta_range.cpp)
|
||||
SETUP_BASIC_TEST(meta_template entt/meta/meta_template.cpp)
|
||||
SETUP_BASIC_TEST(meta_type entt/meta/meta_type.cpp)
|
||||
|
||||
# Test poly
|
||||
|
||||
@@ -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
@@ -34,6 +34,16 @@ TEST(HashedString, Functionalities) {
|
||||
|
||||
ASSERT_EQ(foo_hs, "foo"_hs);
|
||||
ASSERT_NE(bar_hs, "foo"_hs);
|
||||
|
||||
entt::hashed_string empty_hs{};
|
||||
|
||||
ASSERT_EQ(empty_hs, entt::hashed_string{});
|
||||
ASSERT_NE(empty_hs, foo_hs);
|
||||
|
||||
empty_hs = foo_hs;
|
||||
|
||||
ASSERT_NE(empty_hs, entt::hashed_string{});
|
||||
ASSERT_EQ(empty_hs, foo_hs);
|
||||
}
|
||||
|
||||
TEST(HashedString, Empty) {
|
||||
|
||||
@@ -5,17 +5,26 @@
|
||||
#include <entt/core/type_info.hpp>
|
||||
#include <entt/core/type_traits.hpp>
|
||||
|
||||
template<>
|
||||
struct entt::type_name<float> final {
|
||||
[[nodiscard]] static constexpr std::string_view value() ENTT_NOEXCEPT {
|
||||
return std::string_view{""};
|
||||
}
|
||||
};
|
||||
|
||||
TEST(TypeSeq, Functionalities) {
|
||||
ASSERT_EQ(entt::type_seq<int>::value(), entt::type_seq<int>::value());
|
||||
ASSERT_NE(entt::type_seq<int>::value(), entt::type_seq<char>::value());
|
||||
ASSERT_NE(entt::type_seq<int>::value(), entt::type_seq<int &&>::value());
|
||||
ASSERT_NE(entt::type_seq<int &>::value(), entt::type_seq<const int &>::value());
|
||||
ASSERT_EQ(static_cast<entt::id_type>(entt::type_seq<int>{}), entt::type_seq<int>::value());
|
||||
}
|
||||
|
||||
TEST(TypeHash, Functionalities) {
|
||||
ASSERT_NE(entt::type_hash<int>::value(), entt::type_hash<const int>::value());
|
||||
ASSERT_NE(entt::type_hash<int>::value(), entt::type_hash<char>::value());
|
||||
ASSERT_EQ(entt::type_hash<int>::value(), entt::type_hash<int>::value());
|
||||
ASSERT_EQ(static_cast<entt::id_type>(entt::type_hash<int>{}), entt::type_hash<int>::value());
|
||||
}
|
||||
|
||||
TEST(TypeName, Functionalities) {
|
||||
@@ -27,6 +36,8 @@ TEST(TypeName, Functionalities) {
|
||||
|
||||
ASSERT_TRUE(((entt::type_name<entt::type_list<entt::type_list<int, char>, double>>::value()) == std::string_view{"entt::type_list<entt::type_list<int, char>, double>"})
|
||||
|| ((entt::type_name<entt::type_list<entt::type_list<int, char>, double>>::value()) == std::string_view{"struct entt::type_list<struct entt::type_list<int,char>,double>"}));
|
||||
|
||||
ASSERT_EQ(static_cast<std::string_view>(entt::type_name<int>{}), entt::type_name<int>::value());
|
||||
}
|
||||
|
||||
TEST(TypeInfo, Functionalities) {
|
||||
@@ -40,7 +51,8 @@ TEST(TypeInfo, Functionalities) {
|
||||
ASSERT_NE(entt::type_id<int>(), entt::type_info{});
|
||||
ASSERT_NE(entt::type_id<int>(), entt::type_id<char>());
|
||||
|
||||
auto info = entt::type_id<int>();
|
||||
const auto info = entt::type_id<int>();
|
||||
const auto unnamed = entt::type_id<float>();
|
||||
entt::type_info empty{};
|
||||
|
||||
ASSERT_NE(info, empty);
|
||||
@@ -51,8 +63,9 @@ TEST(TypeInfo, Functionalities) {
|
||||
ASSERT_EQ(info.hash(), entt::type_hash<int>::value());
|
||||
ASSERT_EQ(info.name(), entt::type_name<int>::value());
|
||||
|
||||
ASSERT_FALSE(empty);
|
||||
ASSERT_TRUE(info);
|
||||
ASSERT_TRUE(unnamed);
|
||||
ASSERT_FALSE(empty);
|
||||
|
||||
empty = info;
|
||||
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
#include <iterator>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/config/config.h>
|
||||
#include <entt/core/hashed_string.hpp>
|
||||
#include <entt/core/type_traits.hpp>
|
||||
|
||||
struct not_comparable {
|
||||
bool operator==(const not_comparable &) const = delete;
|
||||
};
|
||||
|
||||
struct nlohmann_json_like {
|
||||
using value_type = nlohmann_json_like;
|
||||
bool operator==(const nlohmann_json_like &) const { return true; }
|
||||
};
|
||||
|
||||
TEST(TypeTraits, SizeOf) {
|
||||
static_assert(entt::size_of_v<void> == 0u);
|
||||
static_assert(entt::size_of_v<char> == sizeof(char));
|
||||
@@ -58,6 +70,12 @@ TEST(TypeTraits, TypeList) {
|
||||
static_assert(std::is_same_v<entt::type_list_element_t<0u, type>, int>);
|
||||
static_assert(std::is_same_v<entt::type_list_element_t<1u, type>, char>);
|
||||
static_assert(std::is_same_v<entt::type_list_element_t<0u, other>, double>);
|
||||
|
||||
static_assert(std::is_same_v<entt::type_list_diff_t<entt::type_list<int, char, double>, entt::type_list<float, bool>>, entt::type_list<int, char, double>>);
|
||||
static_assert(std::is_same_v<entt::type_list_diff_t<entt::type_list<int, char, double>, entt::type_list<int, char, double>>, entt::type_list<>>);
|
||||
static_assert(std::is_same_v<entt::type_list_diff_t<entt::type_list<int, char, double>, entt::type_list<int, char>>, entt::type_list<double>>);
|
||||
static_assert(std::is_same_v<entt::type_list_diff_t<entt::type_list<int, char, double>, entt::type_list<char, double>>, entt::type_list<int>>);
|
||||
static_assert(std::is_same_v<entt::type_list_diff_t<entt::type_list<int, char, double>, entt::type_list<char>>, entt::type_list<int, double>>);
|
||||
}
|
||||
|
||||
TEST(TypeTraits, ValueList) {
|
||||
@@ -79,6 +97,17 @@ TEST(TypeTraits, ValueList) {
|
||||
|
||||
TEST(TypeTraits, IsEqualityComparable) {
|
||||
static_assert(entt::is_equality_comparable_v<int>);
|
||||
static_assert(entt::is_equality_comparable_v<std::vector<int>>);
|
||||
static_assert(entt::is_equality_comparable_v<std::vector<std::vector<int>>>);
|
||||
static_assert(entt::is_equality_comparable_v<std::unordered_map<int, int>>);
|
||||
static_assert(entt::is_equality_comparable_v<std::unordered_map<int, std::unordered_map<int, char>>>);
|
||||
static_assert(entt::is_equality_comparable_v<nlohmann_json_like>);
|
||||
|
||||
static_assert(!entt::is_equality_comparable_v<not_comparable>);
|
||||
static_assert(!entt::is_equality_comparable_v<std::vector<not_comparable>>);
|
||||
static_assert(!entt::is_equality_comparable_v<std::vector<std::vector<not_comparable>>>);
|
||||
static_assert(!entt::is_equality_comparable_v<std::unordered_map<int, not_comparable>>);
|
||||
static_assert(!entt::is_equality_comparable_v<std::unordered_map<int, std::unordered_map<int, not_comparable>>>);
|
||||
static_assert(!entt::is_equality_comparable_v<void>);
|
||||
}
|
||||
|
||||
@@ -92,8 +121,28 @@ TEST(TypeTraits, IsApplicable) {
|
||||
}
|
||||
|
||||
TEST(TypeTraits, IsComplete) {
|
||||
static_assert(entt::is_complete_v<int>);
|
||||
static_assert(!entt::is_complete_v<void>);
|
||||
static_assert(entt::is_complete_v<int>);
|
||||
}
|
||||
|
||||
TEST(TypeTraits, IsIterator) {
|
||||
static_assert(!entt::is_iterator_v<void>);
|
||||
static_assert(!entt::is_iterator_v<int>);
|
||||
|
||||
static_assert(entt::is_iterator_v<int *>);
|
||||
static_assert(entt::is_iterator_v<std::vector<int>::iterator>);
|
||||
static_assert(entt::is_iterator_v<std::vector<int>::const_iterator>);
|
||||
static_assert(entt::is_iterator_v<std::vector<int>::reverse_iterator>);
|
||||
}
|
||||
|
||||
TEST(TypeTraits, IsIteratorType) {
|
||||
static_assert(!entt::is_iterator_type_v<void, std::vector<int>::iterator>);
|
||||
static_assert(!entt::is_iterator_type_v<std::vector<int>::iterator, std::vector<int>::const_iterator>);
|
||||
static_assert(!entt::is_iterator_type_v<std::vector<int>::iterator, int *>);
|
||||
|
||||
static_assert(entt::is_iterator_type_v<std::vector<int>::iterator, std::vector<int>::iterator>);
|
||||
static_assert(entt::is_iterator_type_v<std::vector<int>::iterator, std::reverse_iterator<std::vector<int>::iterator>>);
|
||||
static_assert(entt::is_iterator_type_v<std::vector<int>::iterator, std::reverse_iterator<std::reverse_iterator<std::vector<int>::iterator>>>);
|
||||
}
|
||||
|
||||
TEST(TypeTraits, ConstnessAs) {
|
||||
|
||||
@@ -27,11 +27,11 @@ TEST(Utility, Overload) {
|
||||
|
||||
functions instance;
|
||||
|
||||
ASSERT_NO_THROW(entt::overload<void(int)>(&functions::foo)(0));
|
||||
ASSERT_NO_THROW(entt::overload<void()>(&functions::foo)());
|
||||
ASSERT_NO_FATAL_FAILURE(entt::overload<void(int)>(&functions::foo)(0));
|
||||
ASSERT_NO_FATAL_FAILURE(entt::overload<void()>(&functions::foo)());
|
||||
|
||||
ASSERT_NO_THROW((instance.*entt::overload<void(int)>(&functions::bar))(0));
|
||||
ASSERT_NO_THROW((instance.*entt::overload<void()>(&functions::bar))());
|
||||
ASSERT_NO_FATAL_FAILURE((instance.*entt::overload<void(int)>(&functions::bar))(0));
|
||||
ASSERT_NO_FATAL_FAILURE((instance.*entt::overload<void()>(&functions::bar))());
|
||||
}
|
||||
|
||||
TEST(Utility, Overloaded) {
|
||||
|
||||
@@ -4,26 +4,97 @@
|
||||
#include <entt/entity/entity.hpp>
|
||||
#include <entt/entity/registry.hpp>
|
||||
|
||||
TEST(Entity, Traits) {
|
||||
using traits_type = entt::entt_traits<entt::entity>;
|
||||
entt::registry registry{};
|
||||
|
||||
registry.destroy(registry.create());
|
||||
const auto entity = registry.create();
|
||||
const auto other = registry.create();
|
||||
|
||||
ASSERT_EQ(entt::to_integral(entity), traits_type::to_integral(entity));
|
||||
ASSERT_NE(entt::to_integral(entity), entt::to_integral<entt::entity>(entt::null));
|
||||
ASSERT_NE(entt::to_integral(entity), entt::to_integral(entt::entity{}));
|
||||
|
||||
ASSERT_EQ(traits_type::to_entity(entity), 0u);
|
||||
ASSERT_EQ(traits_type::to_version(entity), 1u);
|
||||
ASSERT_EQ(traits_type::to_entity(other), 1u);
|
||||
ASSERT_EQ(traits_type::to_version(other), 0u);
|
||||
|
||||
ASSERT_EQ(traits_type::construct(traits_type::to_entity(entity), traits_type::to_version(entity)), entity);
|
||||
ASSERT_EQ(traits_type::construct(traits_type::to_entity(other), traits_type::to_version(other)), other);
|
||||
ASSERT_NE(traits_type::construct(traits_type::to_entity(entity), {}), entity);
|
||||
|
||||
ASSERT_EQ(traits_type::construct(), entt::tombstone | static_cast<entt::entity>(entt::null));
|
||||
ASSERT_EQ(traits_type::construct(), entt::null | static_cast<entt::entity>(entt::tombstone));
|
||||
|
||||
ASSERT_EQ(traits_type::construct(), static_cast<entt::entity>(entt::null));
|
||||
ASSERT_EQ(traits_type::construct(), static_cast<entt::entity>(entt::tombstone));
|
||||
ASSERT_EQ(traits_type::construct(), entt::entity{0xFFFFFFFF});
|
||||
}
|
||||
|
||||
TEST(Entity, Null) {
|
||||
using traits_type = entt::entt_traits<entt::entity>;
|
||||
constexpr entt::entity tombstone = entt::tombstone;
|
||||
constexpr entt::entity null = entt::null;
|
||||
|
||||
ASSERT_FALSE(entt::entity{} == entt::null);
|
||||
ASSERT_TRUE(entt::entity{traits_type::construct()} == entt::null);
|
||||
|
||||
ASSERT_TRUE(entt::null == entt::null);
|
||||
ASSERT_FALSE(entt::null != entt::null);
|
||||
|
||||
entt::registry registry{};
|
||||
const auto entity = registry.create();
|
||||
|
||||
ASSERT_EQ((entt::null | entity), (traits_type::construct(traits_type::to_entity(null), traits_type::to_version(entity))));
|
||||
ASSERT_EQ((entt::null | null), null);
|
||||
ASSERT_EQ((entt::null | tombstone), null);
|
||||
|
||||
registry.emplace<int>(entity, 42);
|
||||
|
||||
ASSERT_FALSE(entt::entity{} == entt::null);
|
||||
ASSERT_TRUE(entt::entity{traits_type::entity_mask} == entt::null);
|
||||
ASSERT_TRUE(entt::entity{~typename traits_type::entity_type{}} == entt::null);
|
||||
|
||||
ASSERT_TRUE(entt::null == entt::null);
|
||||
ASSERT_FALSE(entt::null != entt::null);
|
||||
|
||||
ASSERT_FALSE(entity == entt::null);
|
||||
ASSERT_FALSE(entt::null == entity);
|
||||
|
||||
ASSERT_TRUE(entity != entt::null);
|
||||
ASSERT_TRUE(entt::null != entity);
|
||||
|
||||
ASSERT_FALSE(registry.valid(entt::null));
|
||||
const entt::entity other = entt::null;
|
||||
|
||||
ASSERT_FALSE(registry.valid(other));
|
||||
ASSERT_NE(registry.create(other), other);
|
||||
}
|
||||
|
||||
TEST(Entity, Tombstone) {
|
||||
using traits_type = entt::entt_traits<entt::entity>;
|
||||
constexpr entt::entity tombstone = entt::tombstone;
|
||||
constexpr entt::entity null = entt::null;
|
||||
|
||||
ASSERT_FALSE(entt::entity{} == entt::tombstone);
|
||||
ASSERT_TRUE(entt::entity{traits_type::construct()} == entt::tombstone);
|
||||
|
||||
ASSERT_TRUE(entt::tombstone == entt::tombstone);
|
||||
ASSERT_FALSE(entt::tombstone != entt::tombstone);
|
||||
|
||||
entt::registry registry{};
|
||||
const auto entity = registry.create();
|
||||
|
||||
ASSERT_EQ((entt::tombstone | entity), (traits_type::construct(traits_type::to_entity(entity), traits_type::to_version(tombstone))));
|
||||
ASSERT_EQ((entt::tombstone | tombstone), tombstone);
|
||||
ASSERT_EQ((entt::tombstone | null), tombstone);
|
||||
|
||||
registry.emplace<int>(entity, 42);
|
||||
|
||||
ASSERT_FALSE(entity == entt::tombstone);
|
||||
ASSERT_FALSE(entt::tombstone == entity);
|
||||
|
||||
ASSERT_TRUE(entity != entt::tombstone);
|
||||
ASSERT_TRUE(entt::tombstone != entity);
|
||||
|
||||
const auto vers = traits_type::to_version(entt::tombstone);
|
||||
const auto other = traits_type::construct(traits_type::to_entity(entity), vers);
|
||||
|
||||
ASSERT_FALSE(registry.valid(entt::tombstone));
|
||||
ASSERT_NE(registry.destroy(entity, vers), vers);
|
||||
ASSERT_NE(registry.create(other), other);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <utility>
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/registry.hpp>
|
||||
#include <entt/entity/group.hpp>
|
||||
@@ -28,10 +28,10 @@ TEST(NonOwningGroup, Functionalities) {
|
||||
registry.emplace<char>(e1, '2');
|
||||
|
||||
ASSERT_FALSE(group.empty());
|
||||
ASSERT_NO_THROW(group.begin()++);
|
||||
ASSERT_NO_THROW(++cgroup.begin());
|
||||
ASSERT_NO_THROW([](auto it) { return it++; }(group.rbegin()));
|
||||
ASSERT_NO_THROW([](auto it) { return ++it; }(cgroup.rbegin()));
|
||||
ASSERT_NO_FATAL_FAILURE(group.begin()++);
|
||||
ASSERT_NO_FATAL_FAILURE(++cgroup.begin());
|
||||
ASSERT_NO_FATAL_FAILURE([](auto it) { return it++; }(group.rbegin()));
|
||||
ASSERT_NO_FATAL_FAILURE([](auto it) { return ++it; }(cgroup.rbegin()));
|
||||
|
||||
ASSERT_NE(group.begin(), group.end());
|
||||
ASSERT_NE(cgroup.begin(), cgroup.end());
|
||||
@@ -43,7 +43,7 @@ TEST(NonOwningGroup, Functionalities) {
|
||||
|
||||
ASSERT_EQ(group.size(), 2u);
|
||||
|
||||
registry.remove<int>(e0);
|
||||
registry.erase<int>(e0);
|
||||
|
||||
ASSERT_EQ(group.size(), 1u);
|
||||
|
||||
@@ -53,10 +53,10 @@ TEST(NonOwningGroup, Functionalities) {
|
||||
ASSERT_EQ(cgroup.get<const char>(entity), '2');
|
||||
}
|
||||
|
||||
ASSERT_EQ(*(group.data() + 0), e1);
|
||||
ASSERT_EQ(group.data()[0u], e1);
|
||||
|
||||
registry.remove<char>(e0);
|
||||
registry.remove<char>(e1);
|
||||
registry.erase<char>(e0);
|
||||
registry.erase<char>(e1);
|
||||
|
||||
ASSERT_EQ(group.begin(), group.end());
|
||||
ASSERT_EQ(cgroup.begin(), cgroup.end());
|
||||
@@ -90,7 +90,7 @@ TEST(NonOwningGroup, Invalid) {
|
||||
ASSERT_TRUE(group.empty());
|
||||
ASSERT_EQ(group.size(), 0u);
|
||||
ASSERT_EQ(group.capacity(), 0u);
|
||||
ASSERT_NO_THROW(group.shrink_to_fit());
|
||||
ASSERT_NO_FATAL_FAILURE(group.shrink_to_fit());
|
||||
|
||||
ASSERT_EQ(group.data(), nullptr);
|
||||
|
||||
@@ -101,15 +101,6 @@ TEST(NonOwningGroup, Invalid) {
|
||||
ASSERT_EQ(group.find(entity), group.end());
|
||||
ASSERT_EQ(group.front(), entt::entity{entt::null});
|
||||
ASSERT_EQ(group.back(), entt::entity{entt::null});
|
||||
|
||||
group.each([](const auto, const auto &) { FAIL(); });
|
||||
group.each([](const auto &) { FAIL(); });
|
||||
|
||||
for([[maybe_unused]] auto all: group.each()) { FAIL(); }
|
||||
for(auto first = group.each().rbegin(), last = group.each().rend(); first != last; ++first) { FAIL(); }
|
||||
|
||||
ASSERT_NO_THROW(group.sort([](const auto, const auto) { FAIL(), true; }));
|
||||
ASSERT_NO_THROW(group.sort<const empty_type>());
|
||||
}
|
||||
|
||||
TEST(NonOwningGroup, ElementAccess) {
|
||||
@@ -168,6 +159,7 @@ TEST(NonOwningGroup, Empty) {
|
||||
TEST(NonOwningGroup, Each) {
|
||||
entt::registry registry;
|
||||
auto group = registry.group(entt::get<int, char>);
|
||||
auto iterable = group.each();
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.emplace<int>(e0, 0);
|
||||
@@ -178,9 +170,10 @@ TEST(NonOwningGroup, Each) {
|
||||
registry.emplace<char>(e1);
|
||||
|
||||
auto cgroup = std::as_const(registry).group_if_exists(entt::get<const int, const char>);
|
||||
auto citerable = cgroup.each();
|
||||
std::size_t cnt = 0;
|
||||
|
||||
for(auto first = cgroup.each().rbegin(), last = cgroup.each().rend(); first != last; ++first) {
|
||||
for(auto first = citerable.rbegin(), last = citerable.rend(); first != last; ++first) {
|
||||
static_assert(std::is_same_v<decltype(*first), std::tuple<entt::entity, const int &, const char &>>);
|
||||
ASSERT_EQ(std::get<1>(*first), cnt++);
|
||||
}
|
||||
@@ -193,7 +186,8 @@ TEST(NonOwningGroup, Each) {
|
||||
cgroup.each([&cnt](const int &, const char &) { --cnt; });
|
||||
cgroup.each([&cnt](auto, const int &, const char &) { --cnt; });
|
||||
|
||||
for(auto &&[entt, iv, cv]: group.each()) {
|
||||
// do not use iterable, make sure an iterable group works when created from a temporary
|
||||
for(auto [entt, iv, cv]: registry.group(entt::get<int, char>).each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
static_assert(std::is_same_v<decltype(cv), char &>);
|
||||
@@ -201,6 +195,12 @@ TEST(NonOwningGroup, Each) {
|
||||
}
|
||||
|
||||
ASSERT_EQ(cnt, std::size_t{0});
|
||||
|
||||
auto it = iterable.begin();
|
||||
auto rit = iterable.rbegin();
|
||||
|
||||
ASSERT_EQ((it++, ++it), iterable.end());
|
||||
ASSERT_EQ((rit++, ++rit), iterable.rend());
|
||||
}
|
||||
|
||||
TEST(NonOwningGroup, Sort) {
|
||||
@@ -221,17 +221,17 @@ TEST(NonOwningGroup, Sort) {
|
||||
registry.emplace<int>(e1, 1);
|
||||
registry.emplace<int>(e2, 2);
|
||||
|
||||
ASSERT_EQ(*(group.data() + 0u), e0);
|
||||
ASSERT_EQ(*(group.data() + 1u), e1);
|
||||
ASSERT_EQ(*(group.data() + 2u), e2);
|
||||
ASSERT_EQ(group.data()[0u], e0);
|
||||
ASSERT_EQ(group.data()[1u], e1);
|
||||
ASSERT_EQ(group.data()[2u], e2);
|
||||
|
||||
group.sort([](const entt::entity lhs, const entt::entity rhs) {
|
||||
return entt::to_integral(lhs) < entt::to_integral(rhs);
|
||||
});
|
||||
|
||||
ASSERT_EQ(*(group.data() + 0u), e2);
|
||||
ASSERT_EQ(*(group.data() + 1u), e1);
|
||||
ASSERT_EQ(*(group.data() + 2u), e0);
|
||||
ASSERT_EQ(group.data()[0u], e2);
|
||||
ASSERT_EQ(group.data()[1u], e1);
|
||||
ASSERT_EQ(group.data()[2u], e0);
|
||||
|
||||
ASSERT_EQ((group.get<const int, unsigned int>(e0)), (std::make_tuple(0, 0u)));
|
||||
ASSERT_EQ((group.get<const int, unsigned int>(e1)), (std::make_tuple(1, 1u)));
|
||||
@@ -243,9 +243,25 @@ TEST(NonOwningGroup, Sort) {
|
||||
return lhs > rhs;
|
||||
});
|
||||
|
||||
ASSERT_EQ(*(group.data() + 0u), e0);
|
||||
ASSERT_EQ(*(group.data() + 1u), e1);
|
||||
ASSERT_EQ(*(group.data() + 2u), e2);
|
||||
ASSERT_EQ(group.data()[0u], e0);
|
||||
ASSERT_EQ(group.data()[1u], e1);
|
||||
ASSERT_EQ(group.data()[2u], e2);
|
||||
|
||||
ASSERT_EQ((group.get<const int, unsigned int>(e0)), (std::make_tuple(0, 0u)));
|
||||
ASSERT_EQ((group.get<const int, unsigned int>(e1)), (std::make_tuple(1, 1u)));
|
||||
ASSERT_EQ((group.get<const int, unsigned int>(e2)), (std::make_tuple(2, 2u)));
|
||||
|
||||
ASSERT_FALSE(group.contains(e3));
|
||||
|
||||
group.sort<const int, unsigned int>([](const auto lhs, const auto rhs) {
|
||||
static_assert(std::is_same_v<decltype(std::get<0>(lhs)), const int &>);
|
||||
static_assert(std::is_same_v<decltype(std::get<1>(rhs)), unsigned int &>);
|
||||
return std::get<0>(lhs) < std::get<0>(rhs);
|
||||
});
|
||||
|
||||
ASSERT_EQ(group.data()[0u], e2);
|
||||
ASSERT_EQ(group.data()[1u], e1);
|
||||
ASSERT_EQ(group.data()[2u], e0);
|
||||
|
||||
ASSERT_EQ((group.get<const int, unsigned int>(e0)), (std::make_tuple(0, 0u)));
|
||||
ASSERT_EQ((group.get<const int, unsigned int>(e1)), (std::make_tuple(1, 1u)));
|
||||
@@ -348,12 +364,16 @@ TEST(NonOwningGroup, ConstNonConstAndAllInBetween) {
|
||||
static_assert(std::is_same_v<decltype(group.get({})), std::tuple<int &, const char &>>);
|
||||
static_assert(std::is_same_v<decltype(group.data()), const entt::entity *>);
|
||||
|
||||
static_assert(std::is_same_v<decltype(std::as_const(registry).group_if_exists(entt::get<int, char>)), decltype(std::as_const(registry).group_if_exists(entt::get<const int, const char>))>);
|
||||
static_assert(std::is_same_v<decltype(std::as_const(registry).group_if_exists(entt::get<const int, char>)), decltype(std::as_const(registry).group_if_exists(entt::get<const int, const char>))>);
|
||||
static_assert(std::is_same_v<decltype(std::as_const(registry).group_if_exists(entt::get<int, const char>)), decltype(std::as_const(registry).group_if_exists(entt::get<const int, const char>))>);
|
||||
|
||||
group.each([](auto &&i, auto &&c) {
|
||||
static_assert(std::is_same_v<decltype(i), int &>);
|
||||
static_assert(std::is_same_v<decltype(c), const char &>);
|
||||
});
|
||||
|
||||
for(auto &&[entt, iv, cv]: group.each()) {
|
||||
for(auto [entt, iv, cv]: group.each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
static_assert(std::is_same_v<decltype(cv), const char &>);
|
||||
@@ -380,7 +400,7 @@ TEST(NonOwningGroup, Find) {
|
||||
registry.emplace<int>(e3);
|
||||
registry.emplace<char>(e3);
|
||||
|
||||
registry.remove<int>(e1);
|
||||
registry.erase<int>(e1);
|
||||
|
||||
ASSERT_NE(group.find(e0), group.end());
|
||||
ASSERT_EQ(group.find(e1), group.end());
|
||||
@@ -439,8 +459,8 @@ TEST(NonOwningGroup, ExcludedComponents) {
|
||||
|
||||
ASSERT_TRUE(group.empty());
|
||||
|
||||
registry.remove<char>(e1);
|
||||
registry.remove<char>(e3);
|
||||
registry.erase<char>(e1);
|
||||
registry.erase<char>(e3);
|
||||
|
||||
for(const auto entity: group) {
|
||||
ASSERT_TRUE(entity == e1 || entity == e3);
|
||||
@@ -475,7 +495,7 @@ TEST(NonOwningGroup, EmptyAndNonEmptyTypes) {
|
||||
ASSERT_TRUE(entity == e0 || entity == e1);
|
||||
});
|
||||
|
||||
for(auto &&[entt, iv]: group.each()) {
|
||||
for(auto [entt, iv]: group.each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
ASSERT_TRUE(entt == e0 || entt == e1);
|
||||
@@ -496,7 +516,7 @@ TEST(NonOwningGroup, TrackEntitiesOnComponentDestruction) {
|
||||
ASSERT_TRUE(group.empty());
|
||||
ASSERT_TRUE(cgroup.empty());
|
||||
|
||||
registry.remove<char>(entity);
|
||||
registry.erase<char>(entity);
|
||||
|
||||
ASSERT_FALSE(group.empty());
|
||||
ASSERT_FALSE(cgroup.empty());
|
||||
@@ -514,7 +534,7 @@ TEST(NonOwningGroup, EmptyTypes) {
|
||||
ASSERT_EQ(entity, entt);
|
||||
});
|
||||
|
||||
for(auto &&[entt, iv, cv]: registry.group(entt::get<int, char, empty_type>).each()) {
|
||||
for(auto [entt, iv, cv]: registry.group(entt::get<int, char, empty_type>).each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
static_assert(std::is_same_v<decltype(cv), char &>);
|
||||
@@ -526,7 +546,7 @@ TEST(NonOwningGroup, EmptyTypes) {
|
||||
check = false;
|
||||
});
|
||||
|
||||
for(auto &&[entt, iv, cv]: registry.group(entt::get<int, empty_type, char>).each()) {
|
||||
for(auto [entt, iv, cv]: registry.group(entt::get<int, empty_type, char>).each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
static_assert(std::is_same_v<decltype(cv), char &>);
|
||||
@@ -537,15 +557,16 @@ TEST(NonOwningGroup, EmptyTypes) {
|
||||
ASSERT_EQ(entity, entt);
|
||||
});
|
||||
|
||||
for(auto &&[entt, iv, cv]: registry.group(entt::get<empty_type, int, char>).each()) {
|
||||
for(auto [entt, iv, cv]: registry.group(entt::get<empty_type, int, char>).each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
static_assert(std::is_same_v<decltype(cv), char &>);
|
||||
ASSERT_EQ(entity, entt);
|
||||
}
|
||||
|
||||
registry.group(entt::get<int, char, double>).each([](const auto, int, char, double) { FAIL(); });
|
||||
ASSERT_EQ(registry.group(entt::get<int, char, double>).each().begin(), registry.group(entt::get<int, char, double>).each().end());
|
||||
auto iterable = registry.group(entt::get<int, char, double>).each();
|
||||
|
||||
ASSERT_EQ(iterable.begin(), iterable.end());
|
||||
}
|
||||
|
||||
TEST(NonOwningGroup, FrontBack) {
|
||||
@@ -583,9 +604,35 @@ TEST(NonOwningGroup, SignalRace) {
|
||||
|
||||
TEST(NonOwningGroup, ExtendedGet) {
|
||||
using type = decltype(std::declval<entt::registry>().group(entt::get<int, empty_type, char>).get({}));
|
||||
|
||||
static_assert(std::tuple_size_v<type> == 2u);
|
||||
static_assert(std::is_same_v<std::tuple_element_t<0, type>, int &>);
|
||||
static_assert(std::is_same_v<std::tuple_element_t<1, type>, char &>);
|
||||
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
|
||||
registry.emplace<int>(entity, 42);
|
||||
registry.emplace<char>(entity, 'c');
|
||||
|
||||
const auto tup = registry.group(entt::get<int, char>).get(entity);
|
||||
|
||||
ASSERT_EQ(std::get<0>(tup), 42);
|
||||
ASSERT_EQ(std::get<1>(tup), 'c');
|
||||
}
|
||||
|
||||
TEST(NonOwningGroup, IterableGroupAlgorithmCompatibility) {
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
|
||||
registry.emplace<int>(entity);
|
||||
registry.emplace<char>(entity);
|
||||
|
||||
const auto group = registry.group(entt::get<int, char>);
|
||||
const auto iterable = group.each();
|
||||
const auto it = std::find_if(iterable.begin(), iterable.end(), [entity](auto args) { return std::get<0>(args) == entity; });
|
||||
|
||||
ASSERT_EQ(std::get<0>(*it), entity);
|
||||
}
|
||||
|
||||
TEST(OwningGroup, Functionalities) {
|
||||
@@ -603,10 +650,10 @@ TEST(OwningGroup, Functionalities) {
|
||||
registry.emplace<char>(e1, '2');
|
||||
|
||||
ASSERT_FALSE(group.empty());
|
||||
ASSERT_NO_THROW(group.begin()++);
|
||||
ASSERT_NO_THROW(++cgroup.begin());
|
||||
ASSERT_NO_THROW([](auto it) { return it++; }(group.rbegin()));
|
||||
ASSERT_NO_THROW([](auto it) { return ++it; }(cgroup.rbegin()));
|
||||
ASSERT_NO_FATAL_FAILURE(group.begin()++);
|
||||
ASSERT_NO_FATAL_FAILURE(++cgroup.begin());
|
||||
ASSERT_NO_FATAL_FAILURE([](auto it) { return it++; }(group.rbegin()));
|
||||
ASSERT_NO_FATAL_FAILURE([](auto it) { return ++it; }(cgroup.rbegin()));
|
||||
|
||||
ASSERT_NE(group.begin(), group.end());
|
||||
ASSERT_NE(cgroup.begin(), cgroup.end());
|
||||
@@ -618,12 +665,12 @@ TEST(OwningGroup, Functionalities) {
|
||||
|
||||
ASSERT_EQ(group.size(), 2u);
|
||||
|
||||
registry.remove<int>(e0);
|
||||
registry.erase<int>(e0);
|
||||
|
||||
ASSERT_EQ(group.size(), 1u);
|
||||
|
||||
ASSERT_EQ(*(cgroup.raw<const int>() + 0), 42);
|
||||
ASSERT_EQ(*(group.raw<int>() + 0), 42);
|
||||
ASSERT_EQ(cgroup.raw<const int>()[0u][0u], 42);
|
||||
ASSERT_EQ(group.raw<int>()[0u][0u], 42);
|
||||
|
||||
for(auto entity: group) {
|
||||
ASSERT_EQ(std::get<0>(cgroup.get<const int, const char>(entity)), 42);
|
||||
@@ -631,11 +678,11 @@ TEST(OwningGroup, Functionalities) {
|
||||
ASSERT_EQ(cgroup.get<const char>(entity), '2');
|
||||
}
|
||||
|
||||
ASSERT_EQ(*(group.data() + 0), e1);
|
||||
ASSERT_EQ(*(group.raw<int>() + 0), 42);
|
||||
ASSERT_EQ(group.data()[0u], e1);
|
||||
ASSERT_EQ(group.raw<int>()[0u][0u], 42);
|
||||
|
||||
registry.remove<char>(e0);
|
||||
registry.remove<char>(e1);
|
||||
registry.erase<char>(e0);
|
||||
registry.erase<char>(e1);
|
||||
|
||||
ASSERT_EQ(group.begin(), group.end());
|
||||
ASSERT_EQ(cgroup.begin(), cgroup.end());
|
||||
@@ -673,12 +720,6 @@ TEST(OwningGroup, Invalid) {
|
||||
ASSERT_EQ(group.find(entity), group.end());
|
||||
ASSERT_EQ(group.front(), entt::entity{entt::null});
|
||||
ASSERT_EQ(group.back(), entt::entity{entt::null});
|
||||
|
||||
group.each([](const auto, const auto &) { FAIL(); });
|
||||
group.each([](const auto &) { FAIL(); });
|
||||
|
||||
for([[maybe_unused]] auto all: group.each()) { FAIL(); }
|
||||
for(auto first = group.each().rbegin(), last = group.each().rend(); first != last; ++first) { FAIL(); }
|
||||
}
|
||||
|
||||
TEST(OwningGroup, ElementAccess) {
|
||||
@@ -737,6 +778,7 @@ TEST(OwningGroup, Empty) {
|
||||
TEST(OwningGroup, Each) {
|
||||
entt::registry registry;
|
||||
auto group = registry.group<int>(entt::get<char>);
|
||||
auto iterable = group.each();
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.emplace<int>(e0, 0);
|
||||
@@ -747,9 +789,10 @@ TEST(OwningGroup, Each) {
|
||||
registry.emplace<char>(e1);
|
||||
|
||||
auto cgroup = std::as_const(registry).group_if_exists<const int>(entt::get<const char>);
|
||||
auto citerable = cgroup.each();
|
||||
std::size_t cnt = 0;
|
||||
|
||||
for(auto first = cgroup.each().rbegin(), last = cgroup.each().rend(); first != last; ++first) {
|
||||
for(auto first = citerable.rbegin(), last = citerable.rend(); first != last; ++first) {
|
||||
static_assert(std::is_same_v<decltype(*first), std::tuple<entt::entity, const int &, const char &>>);
|
||||
ASSERT_EQ(std::get<1>(*first), cnt++);
|
||||
}
|
||||
@@ -762,7 +805,8 @@ TEST(OwningGroup, Each) {
|
||||
cgroup.each([&cnt](const int &, const char &) { --cnt; });
|
||||
cgroup.each([&cnt](auto, const int &, const char &) { --cnt; });
|
||||
|
||||
for(auto &&[entt, iv, cv]: group.each()) {
|
||||
// do not use iterable, make sure an iterable group works when created from a temporary
|
||||
for(auto [entt, iv, cv]: registry.group<int>(entt::get<char>).each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
static_assert(std::is_same_v<decltype(cv), char &>);
|
||||
@@ -770,6 +814,12 @@ TEST(OwningGroup, Each) {
|
||||
}
|
||||
|
||||
ASSERT_EQ(cnt, std::size_t{0});
|
||||
|
||||
auto it = iterable.begin();
|
||||
auto rit = iterable.rbegin();
|
||||
|
||||
ASSERT_EQ((it++, ++it), iterable.end());
|
||||
ASSERT_EQ((rit++, ++rit), iterable.rend());
|
||||
}
|
||||
|
||||
TEST(OwningGroup, SortOrdered) {
|
||||
@@ -795,21 +845,21 @@ TEST(OwningGroup, SortOrdered) {
|
||||
return group.get<boxed_int>(lhs).value < group.get<boxed_int>(rhs).value;
|
||||
});
|
||||
|
||||
ASSERT_EQ(*(group.data() + 0u), entities[0]);
|
||||
ASSERT_EQ(*(group.data() + 1u), entities[1]);
|
||||
ASSERT_EQ(*(group.data() + 2u), entities[2]);
|
||||
ASSERT_EQ(*(group.data() + 3u), entities[3]);
|
||||
ASSERT_EQ(*(group.data() + 4u), entities[4]);
|
||||
ASSERT_EQ(group.data()[0u], entities[0]);
|
||||
ASSERT_EQ(group.data()[1u], entities[1]);
|
||||
ASSERT_EQ(group.data()[2u], entities[2]);
|
||||
ASSERT_EQ(group.data()[3u], entities[3]);
|
||||
ASSERT_EQ(group.data()[4u], entities[4]);
|
||||
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 0u)->value, 12);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 1u)->value, 9);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 2u)->value, 6);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 3u)->value, 1);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 4u)->value, 2);
|
||||
ASSERT_EQ(group.raw<boxed_int>()[0u][0u].value, 12);
|
||||
ASSERT_EQ(group.raw<boxed_int>()[0u][1u].value, 9);
|
||||
ASSERT_EQ(group.raw<boxed_int>()[0u][2u].value, 6);
|
||||
ASSERT_EQ(group.raw<boxed_int>()[0u][3u].value, 1);
|
||||
ASSERT_EQ(group.raw<boxed_int>()[0u][4u].value, 2);
|
||||
|
||||
ASSERT_EQ(*(group.raw<char>() + 0u), 'a');
|
||||
ASSERT_EQ(*(group.raw<char>() + 1u), 'b');
|
||||
ASSERT_EQ(*(group.raw<char>() + 2u), 'c');
|
||||
ASSERT_EQ(group.raw<char>()[0u][0u], 'a');
|
||||
ASSERT_EQ(group.raw<char>()[0u][1u], 'b');
|
||||
ASSERT_EQ(group.raw<char>()[0u][2u], 'c');
|
||||
|
||||
ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{12}, 'a')));
|
||||
ASSERT_EQ((group.get<boxed_int, char>(entities[1])), (std::make_tuple(boxed_int{9}, 'b')));
|
||||
@@ -842,21 +892,21 @@ TEST(OwningGroup, SortReverse) {
|
||||
return lhs.value < rhs.value;
|
||||
});
|
||||
|
||||
ASSERT_EQ(*(group.data() + 0u), entities[2]);
|
||||
ASSERT_EQ(*(group.data() + 1u), entities[1]);
|
||||
ASSERT_EQ(*(group.data() + 2u), entities[0]);
|
||||
ASSERT_EQ(*(group.data() + 3u), entities[3]);
|
||||
ASSERT_EQ(*(group.data() + 4u), entities[4]);
|
||||
ASSERT_EQ(group.data()[0u], entities[2]);
|
||||
ASSERT_EQ(group.data()[1u], entities[1]);
|
||||
ASSERT_EQ(group.data()[2u], entities[0]);
|
||||
ASSERT_EQ(group.data()[3u], entities[3]);
|
||||
ASSERT_EQ(group.data()[4u], entities[4]);
|
||||
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 0u)->value, 12);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 1u)->value, 9);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 2u)->value, 6);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 3u)->value, 1);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 4u)->value, 2);
|
||||
ASSERT_EQ(group.raw<boxed_int>()[0u][0u].value, 12);
|
||||
ASSERT_EQ(group.raw<boxed_int>()[0u][1u].value, 9);
|
||||
ASSERT_EQ(group.raw<boxed_int>()[0u][2u].value, 6);
|
||||
ASSERT_EQ(group.raw<boxed_int>()[0u][3u].value, 1);
|
||||
ASSERT_EQ(group.raw<boxed_int>()[0u][4u].value, 2);
|
||||
|
||||
ASSERT_EQ(*(group.raw<char>() + 0u), 'c');
|
||||
ASSERT_EQ(*(group.raw<char>() + 1u), 'b');
|
||||
ASSERT_EQ(*(group.raw<char>() + 2u), 'a');
|
||||
ASSERT_EQ(group.raw<char>()[0u][0u], 'c');
|
||||
ASSERT_EQ(group.raw<char>()[0u][1u], 'b');
|
||||
ASSERT_EQ(group.raw<char>()[0u][2u], 'a');
|
||||
|
||||
ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{6}, 'a')));
|
||||
ASSERT_EQ((group.get<boxed_int, char>(entities[1])), (std::make_tuple(boxed_int{9}, 'b')));
|
||||
@@ -891,31 +941,33 @@ TEST(OwningGroup, SortUnordered) {
|
||||
registry.emplace<boxed_int>(entities[5], 4);
|
||||
registry.emplace<boxed_int>(entities[6], 5);
|
||||
|
||||
group.sort<char>([](const auto lhs, const auto rhs) {
|
||||
return lhs < rhs;
|
||||
group.sort<boxed_int, char>([](const auto lhs, const auto rhs) {
|
||||
static_assert(std::is_same_v<decltype(std::get<0>(lhs)), boxed_int &>);
|
||||
static_assert(std::is_same_v<decltype(std::get<1>(rhs)), char &>);
|
||||
return std::get<1>(lhs) < std::get<1>(rhs);
|
||||
});
|
||||
|
||||
ASSERT_EQ(*(group.data() + 0u), entities[4]);
|
||||
ASSERT_EQ(*(group.data() + 1u), entities[3]);
|
||||
ASSERT_EQ(*(group.data() + 2u), entities[0]);
|
||||
ASSERT_EQ(*(group.data() + 3u), entities[1]);
|
||||
ASSERT_EQ(*(group.data() + 4u), entities[2]);
|
||||
ASSERT_EQ(*(group.data() + 5u), entities[5]);
|
||||
ASSERT_EQ(*(group.data() + 6u), entities[6]);
|
||||
ASSERT_EQ(group.data()[0u], entities[4]);
|
||||
ASSERT_EQ(group.data()[1u], entities[3]);
|
||||
ASSERT_EQ(group.data()[2u], entities[0]);
|
||||
ASSERT_EQ(group.data()[3u], entities[1]);
|
||||
ASSERT_EQ(group.data()[4u], entities[2]);
|
||||
ASSERT_EQ(group.data()[5u], entities[5]);
|
||||
ASSERT_EQ(group.data()[6u], entities[6]);
|
||||
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 0u)->value, 12);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 1u)->value, 9);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 2u)->value, 6);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 3u)->value, 3);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 4u)->value, 1);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 5u)->value, 4);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 6u)->value, 5);
|
||||
ASSERT_EQ(group.raw<boxed_int>()[0u][0u].value, 12);
|
||||
ASSERT_EQ(group.raw<boxed_int>()[0u][1u].value, 9);
|
||||
ASSERT_EQ(group.raw<boxed_int>()[0u][2u].value, 6);
|
||||
ASSERT_EQ(group.raw<boxed_int>()[0u][3u].value, 3);
|
||||
ASSERT_EQ(group.raw<boxed_int>()[0u][4u].value, 1);
|
||||
ASSERT_EQ(group.raw<boxed_int>()[0u][5u].value, 4);
|
||||
ASSERT_EQ(group.raw<boxed_int>()[0u][6u].value, 5);
|
||||
|
||||
ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{6}, 'c')));
|
||||
ASSERT_EQ((group.get<boxed_int, char>(entities[1])), (std::make_tuple(boxed_int{3}, 'b')));
|
||||
ASSERT_EQ((group.get<boxed_int, char>(entities[2])), (std::make_tuple(boxed_int{1}, 'a')));
|
||||
ASSERT_EQ((group.get<boxed_int, char>(entities[3])), (std::make_tuple(boxed_int{9}, 'd')));
|
||||
ASSERT_EQ((group.get<boxed_int, char>(entities[4])), (std::make_tuple(boxed_int{12}, 'e')));
|
||||
ASSERT_EQ(group.get<char>(group.data()[0u]), 'e');
|
||||
ASSERT_EQ(group.get<char>(group.data()[1u]), 'd');
|
||||
ASSERT_EQ(group.get<char>(group.data()[2u]), 'c');
|
||||
ASSERT_EQ(group.get<char>(group.data()[3u]), 'b');
|
||||
ASSERT_EQ(group.get<char>(group.data()[4u]), 'a');
|
||||
|
||||
ASSERT_FALSE(group.contains(entities[5]));
|
||||
ASSERT_FALSE(group.contains(entities[6]));
|
||||
@@ -940,15 +992,15 @@ TEST(OwningGroup, SortWithExclusionList) {
|
||||
return lhs < rhs;
|
||||
});
|
||||
|
||||
ASSERT_EQ(*(group.data() + 0u), entities[4]);
|
||||
ASSERT_EQ(*(group.data() + 1u), entities[3]);
|
||||
ASSERT_EQ(*(group.data() + 2u), entities[1]);
|
||||
ASSERT_EQ(*(group.data() + 3u), entities[0]);
|
||||
ASSERT_EQ(group.data()[0u], entities[4]);
|
||||
ASSERT_EQ(group.data()[1u], entities[3]);
|
||||
ASSERT_EQ(group.data()[2u], entities[1]);
|
||||
ASSERT_EQ(group.data()[3u], entities[0]);
|
||||
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 0u)->value, 4);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 1u)->value, 3);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 2u)->value, 1);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 3u)->value, 0);
|
||||
ASSERT_EQ(group.raw<boxed_int>()[0u][0u].value, 4);
|
||||
ASSERT_EQ(group.raw<boxed_int>()[0u][1u].value, 3);
|
||||
ASSERT_EQ(group.raw<boxed_int>()[0u][2u].value, 1);
|
||||
ASSERT_EQ(group.raw<boxed_int>()[0u][3u].value, 0);
|
||||
|
||||
ASSERT_EQ(group.get<boxed_int>(entities[0]).value, 0);
|
||||
ASSERT_EQ(group.get<boxed_int>(entities[1]).value, 1);
|
||||
@@ -1014,8 +1066,12 @@ TEST(OwningGroup, ConstNonConstAndAllInBetween) {
|
||||
static_assert(std::is_same_v<decltype(group.get<int, const char, double, const float>({})), std::tuple<int &, const char &, double &, const float &>>);
|
||||
static_assert(std::is_same_v<decltype(group.get({})), std::tuple<int &, const char &, double &, const float &>>);
|
||||
static_assert(std::is_same_v<decltype(group.data()), const entt::entity *>);
|
||||
static_assert(std::is_same_v<decltype(group.raw<const char>()), const char *>);
|
||||
static_assert(std::is_same_v<decltype(group.raw<int>()), int *>);
|
||||
static_assert(std::is_same_v<decltype(group.raw<const char>()), const char * const *>);
|
||||
static_assert(std::is_same_v<decltype(group.raw<int>()), int **>);
|
||||
|
||||
static_assert(std::is_same_v<decltype(std::as_const(registry).group_if_exists<int>(entt::get<char>)), decltype(std::as_const(registry).group_if_exists<const int>(entt::get<const char>))>);
|
||||
static_assert(std::is_same_v<decltype(std::as_const(registry).group_if_exists<const int>(entt::get<char>)), decltype(std::as_const(registry).group_if_exists<const int>(entt::get<const char>))>);
|
||||
static_assert(std::is_same_v<decltype(std::as_const(registry).group_if_exists<int>(entt::get<const char>)), decltype(std::as_const(registry).group_if_exists<const int>(entt::get<const char>))>);
|
||||
|
||||
group.each([](auto &&i, auto &&c, auto &&d, auto &&f) {
|
||||
static_assert(std::is_same_v<decltype(i), int &>);
|
||||
@@ -1024,7 +1080,7 @@ TEST(OwningGroup, ConstNonConstAndAllInBetween) {
|
||||
static_assert(std::is_same_v<decltype(f), const float &>);
|
||||
});
|
||||
|
||||
for(auto &&[entt, iv, cv, dv, fv]: group.each()) {
|
||||
for(auto [entt, iv, cv, dv, fv]: group.each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
static_assert(std::is_same_v<decltype(cv), const char &>);
|
||||
@@ -1053,7 +1109,7 @@ TEST(OwningGroup, Find) {
|
||||
registry.emplace<int>(e3);
|
||||
registry.emplace<char>(e3);
|
||||
|
||||
registry.remove<int>(e1);
|
||||
registry.erase<int>(e1);
|
||||
|
||||
ASSERT_NE(group.find(e0), group.end());
|
||||
ASSERT_EQ(group.find(e1), group.end());
|
||||
@@ -1112,8 +1168,8 @@ TEST(OwningGroup, ExcludedComponents) {
|
||||
|
||||
ASSERT_TRUE(group.empty());
|
||||
|
||||
registry.remove<char>(e1);
|
||||
registry.remove<double>(e3);
|
||||
registry.erase<char>(e1);
|
||||
registry.erase<double>(e3);
|
||||
|
||||
for(const auto entity: group) {
|
||||
ASSERT_TRUE(entity == e1 || entity == e3);
|
||||
@@ -1148,7 +1204,7 @@ TEST(OwningGroup, EmptyAndNonEmptyTypes) {
|
||||
ASSERT_TRUE(entity == e0 || entity == e1);
|
||||
});
|
||||
|
||||
for(auto &&[entt, iv]: group.each()) {
|
||||
for(auto [entt, iv]: group.each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
ASSERT_TRUE(entt == e0 || entt == e1);
|
||||
@@ -1169,7 +1225,7 @@ TEST(OwningGroup, TrackEntitiesOnComponentDestruction) {
|
||||
ASSERT_TRUE(group.empty());
|
||||
ASSERT_TRUE(cgroup.empty());
|
||||
|
||||
registry.remove<char>(entity);
|
||||
registry.erase<char>(entity);
|
||||
|
||||
ASSERT_FALSE(group.empty());
|
||||
ASSERT_FALSE(cgroup.empty());
|
||||
@@ -1187,7 +1243,7 @@ TEST(OwningGroup, EmptyTypes) {
|
||||
ASSERT_EQ(entity, entt);
|
||||
});
|
||||
|
||||
for(auto &&[entt, iv, cv]: registry.group<int>(entt::get<char, empty_type>).each()) {
|
||||
for(auto [entt, iv, cv]: registry.group<int>(entt::get<char, empty_type>).each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
static_assert(std::is_same_v<decltype(cv), char &>);
|
||||
@@ -1199,7 +1255,7 @@ TEST(OwningGroup, EmptyTypes) {
|
||||
check = false;
|
||||
});
|
||||
|
||||
for(auto &&[entt, cv, iv]: registry.group<char>(entt::get<empty_type, int>).each()) {
|
||||
for(auto [entt, cv, iv]: registry.group<char>(entt::get<empty_type, int>).each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(cv), char &>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
@@ -1210,15 +1266,16 @@ TEST(OwningGroup, EmptyTypes) {
|
||||
ASSERT_EQ(entity, entt);
|
||||
});
|
||||
|
||||
for(auto &&[entt, iv, cv]: registry.group<empty_type>(entt::get<int, char>).each()) {
|
||||
for(auto [entt, iv, cv]: registry.group<empty_type>(entt::get<int, char>).each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
static_assert(std::is_same_v<decltype(cv), char &>);
|
||||
ASSERT_EQ(entity, entt);
|
||||
}
|
||||
|
||||
registry.group<double>(entt::get<int, char>).each([](const auto, double, int, char) { FAIL(); });
|
||||
ASSERT_EQ(registry.group<double>(entt::get<int, char>).each().begin(), registry.group<double>(entt::get<int, char>).each().end());
|
||||
auto iterable = registry.group<double>(entt::get<int, char>).each();
|
||||
|
||||
ASSERT_EQ(iterable.begin(), iterable.end());
|
||||
}
|
||||
|
||||
TEST(OwningGroup, FrontBack) {
|
||||
@@ -1284,9 +1341,53 @@ TEST(OwningGroup, PreventEarlyOptOut) {
|
||||
});
|
||||
}
|
||||
|
||||
TEST(OwningGroup, SwappingValuesIsAllowed) {
|
||||
entt::registry registry;
|
||||
const auto group = registry.group<boxed_int>(entt::get<empty_type>);
|
||||
|
||||
for(std::size_t i{}; i < 2u; ++i) {
|
||||
const auto entity = registry.create();
|
||||
registry.emplace<boxed_int>(entity, static_cast<int>(i));
|
||||
registry.emplace<empty_type>(entity);
|
||||
}
|
||||
|
||||
registry.destroy(group.back());
|
||||
|
||||
// thanks to @andranik3949 for pointing out this missing test
|
||||
registry.view<const boxed_int>().each([](const auto entity, const auto &value) {
|
||||
ASSERT_EQ(entt::to_integral(entity), value.value);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(OwningGroup, ExtendedGet) {
|
||||
using type = decltype(std::declval<entt::registry>().group<int, empty_type>(entt::get<char>).get({}));
|
||||
|
||||
static_assert(std::tuple_size_v<type> == 2u);
|
||||
static_assert(std::is_same_v<std::tuple_element_t<0, type>, int &>);
|
||||
static_assert(std::is_same_v<std::tuple_element_t<1, type>, char &>);
|
||||
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
|
||||
registry.emplace<int>(entity, 42);
|
||||
registry.emplace<char>(entity, 'c');
|
||||
|
||||
const auto tup = registry.group<int>(entt::get<char>).get(entity);
|
||||
|
||||
ASSERT_EQ(std::get<0>(tup), 42);
|
||||
ASSERT_EQ(std::get<1>(tup), 'c');
|
||||
}
|
||||
|
||||
TEST(OwningGroup, IterableGroupAlgorithmCompatibility) {
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
|
||||
registry.emplace<int>(entity);
|
||||
registry.emplace<char>(entity);
|
||||
|
||||
const auto group = registry.group<int>(entt::get<char>);
|
||||
const auto iterable = group.each();
|
||||
const auto it = std::find_if(iterable.begin(), iterable.end(), [entity](auto args) { return std::get<0>(args) == entity; });
|
||||
|
||||
ASSERT_EQ(std::get<0>(*it), entity);
|
||||
}
|
||||
|
||||
@@ -157,21 +157,22 @@ TEST(BasicHandle, Component) {
|
||||
|
||||
ASSERT_EQ(42, patched);
|
||||
ASSERT_EQ('a', handle.replace<char>('a'));
|
||||
ASSERT_TRUE((handle.has<int, char, double>()));
|
||||
ASSERT_TRUE((handle.all_of<int, char, double>()));
|
||||
ASSERT_EQ((std::make_tuple(42, 'a', .3)), (handle.get<int, char, double>()));
|
||||
|
||||
handle.remove<char, double>();
|
||||
handle.erase<char, double>();
|
||||
|
||||
ASSERT_TRUE((registry.empty<char, double>()));
|
||||
ASSERT_EQ(0u, (handle.remove_if_exists<char, double>()));
|
||||
ASSERT_EQ(0u, (handle.remove<char, double>()));
|
||||
|
||||
handle.visit([](auto info) { ASSERT_EQ(entt::type_hash<int>::value(), info.hash()); });
|
||||
handle.visit([](auto info) { ASSERT_EQ(entt::type_id<int>(), info); });
|
||||
|
||||
ASSERT_TRUE((handle.any<int, char, double>()));
|
||||
ASSERT_FALSE((handle.has<int, char, double>()));
|
||||
ASSERT_TRUE((handle.any_of<int, char, double>()));
|
||||
ASSERT_FALSE((handle.all_of<int, char, double>()));
|
||||
ASSERT_FALSE(handle.orphan());
|
||||
|
||||
handle.remove<int>();
|
||||
ASSERT_EQ(1u, (handle.remove<int>()));
|
||||
ASSERT_DEATH(handle.erase<int>(), "");
|
||||
|
||||
ASSERT_TRUE(registry.empty<int>());
|
||||
ASSERT_TRUE(handle.orphan());
|
||||
@@ -185,20 +186,6 @@ TEST(BasicHandle, Component) {
|
||||
ASSERT_EQ(nullptr, std::get<1>(handle.try_get<int, char, double>()));
|
||||
}
|
||||
|
||||
TEST(BasicHandle, RemoveAll) {
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
entt::handle handle{registry, entity};
|
||||
|
||||
ASSERT_EQ(3, handle.emplace<int>(3));
|
||||
ASSERT_EQ('c', handle.emplace_or_replace<char>('c'));
|
||||
ASSERT_TRUE((handle.has<int, char>()));
|
||||
|
||||
handle.remove_all();
|
||||
|
||||
ASSERT_FALSE((handle.any<int, char>()));
|
||||
}
|
||||
|
||||
TEST(BasicHandle, FromEntity) {
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
@@ -210,7 +197,7 @@ TEST(BasicHandle, FromEntity) {
|
||||
|
||||
ASSERT_TRUE(handle);
|
||||
ASSERT_EQ(entity, handle.entity());
|
||||
ASSERT_TRUE((handle.has<int, char>()));
|
||||
ASSERT_TRUE((handle.all_of<int, char>()));
|
||||
ASSERT_EQ(handle.get<int>(), 42);
|
||||
ASSERT_EQ(handle.get<char>(), 'c');
|
||||
}
|
||||
|
||||
@@ -41,19 +41,39 @@ TEST(Helper, Invoke) {
|
||||
|
||||
TEST(Helper, ToEntity) {
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
const auto other = registry.create();
|
||||
const entt::entity null = entt::null;
|
||||
const int value = 42;
|
||||
|
||||
ASSERT_EQ(entt::to_entity(registry, 42), null);
|
||||
ASSERT_EQ(entt::to_entity(registry, value), null);
|
||||
|
||||
const auto entity = registry.create();
|
||||
registry.emplace<int>(entity);
|
||||
|
||||
while(registry.size<int>() < (ENTT_PACKED_PAGE - 1u)) {
|
||||
registry.emplace<int>(registry.create(), value);
|
||||
}
|
||||
|
||||
const auto other = registry.create();
|
||||
const auto next = registry.create();
|
||||
|
||||
registry.emplace<int>(other);
|
||||
registry.emplace<char>(other);
|
||||
registry.emplace<int>(next);
|
||||
|
||||
ASSERT_EQ(entt::to_entity(registry, registry.get<int>(entity)), entity);
|
||||
ASSERT_EQ(entt::to_entity(registry, registry.get<int>(other)), other);
|
||||
ASSERT_EQ(entt::to_entity(registry, registry.get<char>(other)), other);
|
||||
ASSERT_EQ(entt::to_entity(registry, registry.get<int>(next)), next);
|
||||
|
||||
registry.destroy(entity);
|
||||
ASSERT_EQ(®istry.get<int>(entity) + ENTT_PACKED_PAGE - 1u, ®istry.get<int>(other));
|
||||
ASSERT_NE(®istry.get<int>(entity) + ENTT_PACKED_PAGE, ®istry.get<int>(next));
|
||||
|
||||
ASSERT_EQ(entt::to_entity(registry, registry.get<int>(other)), other);
|
||||
ASSERT_EQ(entt::to_entity(registry, registry.get<char>(other)), other);
|
||||
registry.destroy(other);
|
||||
|
||||
ASSERT_EQ(entt::to_entity(registry, registry.get<int>(entity)), entity);
|
||||
ASSERT_EQ(entt::to_entity(registry, registry.get<int>(next)), next);
|
||||
|
||||
ASSERT_EQ(®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());
|
||||
|
||||
@@ -71,7 +71,7 @@ TEST(Organizer, EmplaceFreeFunction) {
|
||||
ASSERT_EQ(graph[2u].children()[0u], 3u);
|
||||
|
||||
for(auto &&vertex: graph) {
|
||||
ASSERT_NO_THROW(vertex.callback()(vertex.data(), registry));
|
||||
ASSERT_NO_FATAL_FAILURE(vertex.callback()(vertex.data(), registry));
|
||||
}
|
||||
|
||||
organizer.clear();
|
||||
@@ -127,7 +127,7 @@ TEST(Organizer, EmplaceMemberFunction) {
|
||||
ASSERT_EQ(graph[2u].children()[0u], 3u);
|
||||
|
||||
for(auto &&vertex: graph) {
|
||||
ASSERT_NO_THROW(vertex.callback()(vertex.data(), registry));
|
||||
ASSERT_NO_FATAL_FAILURE(vertex.callback()(vertex.data(), registry));
|
||||
}
|
||||
|
||||
organizer.clear();
|
||||
@@ -140,8 +140,6 @@ TEST(Organizer, EmplaceFreeFunctionWithPayload) {
|
||||
entt::registry registry;
|
||||
clazz instance;
|
||||
|
||||
// TODO
|
||||
|
||||
organizer.emplace<&clazz::ro_int_char_double>(instance, "t1");
|
||||
organizer.emplace<&clazz::ro_int_with_payload>(instance, "t2");
|
||||
organizer.emplace<&clazz::ro_char_with_payload, const clazz>(instance, "t3");
|
||||
@@ -193,7 +191,7 @@ TEST(Organizer, EmplaceFreeFunctionWithPayload) {
|
||||
ASSERT_EQ(graph[3u].children()[0u], 4u);
|
||||
|
||||
for(auto &&vertex: graph) {
|
||||
ASSERT_NO_THROW(vertex.callback()(vertex.data(), registry));
|
||||
ASSERT_NO_FATAL_FAILURE(vertex.callback()(vertex.data(), registry));
|
||||
}
|
||||
|
||||
organizer.clear();
|
||||
@@ -266,7 +264,7 @@ TEST(Organizer, EmplaceDirectFunction) {
|
||||
ASSERT_EQ(graph[2u].children()[0u], 3u);
|
||||
|
||||
for(auto &&vertex: graph) {
|
||||
ASSERT_NO_THROW(vertex.callback()(vertex.data(), registry));
|
||||
ASSERT_NO_FATAL_FAILURE(vertex.callback()(vertex.data(), registry));
|
||||
}
|
||||
|
||||
organizer.clear();
|
||||
@@ -319,7 +317,7 @@ TEST(Organizer, SyncPoint) {
|
||||
ASSERT_EQ(graph[4u].children()[0u], 5u);
|
||||
|
||||
for(auto &&vertex: graph) {
|
||||
ASSERT_NO_THROW(vertex.callback()(vertex.data(), registry));
|
||||
ASSERT_NO_FATAL_FAILURE(vertex.callback()(vertex.data(), registry));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,12 +11,10 @@ template<typename Entity>
|
||||
struct PolyStorage: entt::type_list_cat_t<
|
||||
decltype(as_type_list(std::declval<entt::Storage<Entity>>())),
|
||||
entt::type_list<
|
||||
void(const Entity *, const Entity *, void *),
|
||||
void(entt::basic_registry<Entity> &, const Entity, const void *),
|
||||
const void *(const Entity) const,
|
||||
const Entity *() const,
|
||||
const void *() const,
|
||||
std::size_t() const,
|
||||
void(entt::basic_registry<Entity> &, const Entity *, const void *, const std::size_t)
|
||||
void(entt::basic_registry<Entity> &) const
|
||||
>
|
||||
> {
|
||||
using entity_type = Entity;
|
||||
@@ -24,36 +22,28 @@ struct PolyStorage: entt::type_list_cat_t<
|
||||
|
||||
template<typename Base>
|
||||
struct type: entt::Storage<Entity>::template type<Base> {
|
||||
static constexpr auto base = std::tuple_size_v<typename entt::poly_vtable<entt::Storage<Entity>>::type>;
|
||||
static constexpr auto base = decltype(as_type_list(std::declval<entt::Storage<Entity>>()))::size;
|
||||
|
||||
void emplace(entt::basic_registry<entity_type> &owner, const entity_type entity, const void *instance) {
|
||||
entt::poly_call<base + 0>(*this, owner, entity, instance);
|
||||
void erase(entt::basic_registry<Entity> &owner, const entity_type *first, const entity_type *last) {
|
||||
entt::poly_call<base + 0>(*this, first, last, &owner);
|
||||
}
|
||||
|
||||
void emplace(entt::basic_registry<Entity> &owner, const entity_type entity, const void *instance) {
|
||||
entt::poly_call<base + 1>(*this, owner, entity, instance);
|
||||
}
|
||||
|
||||
const void * get(const entity_type entity) const {
|
||||
return entt::poly_call<base + 1>(*this, entity);
|
||||
return entt::poly_call<base + 2>(*this, entity);
|
||||
}
|
||||
|
||||
const entity_type * data() const {
|
||||
return entt::poly_call<base + 2>(*this);
|
||||
}
|
||||
|
||||
const void * raw() const {
|
||||
return entt::poly_call<base + 3>(*this);
|
||||
}
|
||||
|
||||
size_type size() const {
|
||||
return entt::poly_call<base + 4>(*this);
|
||||
}
|
||||
|
||||
void insert(entt::basic_registry<Entity> &owner, const Entity *entity, const void *instance, const std::size_t length) {
|
||||
entt::poly_call<base + 5>(*this, owner, entity, instance, length);
|
||||
void copy_to(entt::basic_registry<Entity> &other) const {
|
||||
entt::poly_call<base + 3>(*this, other);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Type>
|
||||
struct members {
|
||||
static void emplace(Type &self, entt::basic_registry<entity_type> &owner, const entity_type entity, const void *instance) {
|
||||
static void emplace(Type &self, entt::basic_registry<Entity> &owner, const entity_type entity, const void *instance) {
|
||||
self.emplace(owner, entity, *static_cast<const typename Type::value_type *>(instance));
|
||||
}
|
||||
|
||||
@@ -61,9 +51,9 @@ struct PolyStorage: entt::type_list_cat_t<
|
||||
return &self.get(entity);
|
||||
}
|
||||
|
||||
static void insert(Type &self, entt::basic_registry<entity_type> &owner, const entity_type *entity, const void *instance, const size_type length) {
|
||||
const auto *value = static_cast<const typename Type::value_type *>(instance);
|
||||
self.insert(owner, entity, entity + length, value, value + length);
|
||||
static void copy_to(const Type &self, entt::basic_registry<entity_type> &other) {
|
||||
const entt::sparse_set &base = self;
|
||||
other.template insert<typename Type::value_type>(base.rbegin(), base.rend(), self.rbegin());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -71,12 +61,10 @@ struct PolyStorage: entt::type_list_cat_t<
|
||||
using impl = entt::value_list_cat_t<
|
||||
typename entt::Storage<Entity>::template impl<Type>,
|
||||
entt::value_list<
|
||||
&Type::template erase<const entity_type *>,
|
||||
&members<Type>::emplace,
|
||||
&members<Type>::get,
|
||||
&Type::data,
|
||||
entt::overload<const typename Type::value_type *() const ENTT_NOEXCEPT>(&Type::raw),
|
||||
&Type::size,
|
||||
&members<Type>::insert
|
||||
&members<Type>::copy_to
|
||||
>
|
||||
>;
|
||||
};
|
||||
@@ -94,16 +82,16 @@ TEST(PolyStorage, CopyEntity) {
|
||||
registry.emplace<int>(entity, 42);
|
||||
registry.emplace<char>(entity, 'c');
|
||||
|
||||
ASSERT_TRUE((registry.has<int, char>(entity)));
|
||||
ASSERT_FALSE((registry.any<int, char>(other)));
|
||||
ASSERT_TRUE((registry.all_of<int, char>(entity)));
|
||||
ASSERT_FALSE((registry.any_of<int, char>(other)));
|
||||
|
||||
registry.visit(entity, [&](const auto info) {
|
||||
auto storage = registry.storage(info);
|
||||
auto &&storage = registry.storage(info);
|
||||
storage->emplace(registry, other, storage->get(entity));
|
||||
});
|
||||
|
||||
ASSERT_TRUE((registry.has<int, char>(entity)));
|
||||
ASSERT_TRUE((registry.has<int, char>(other)));
|
||||
ASSERT_TRUE((registry.all_of<int, char>(entity)));
|
||||
ASSERT_TRUE((registry.all_of<int, char>(other)));
|
||||
|
||||
ASSERT_EQ(registry.get<int>(entity), registry.get<int>(other));
|
||||
ASSERT_EQ(registry.get<char>(entity), registry.get<char>(other));
|
||||
@@ -118,20 +106,19 @@ TEST(PolyStorage, CopyRegistry) {
|
||||
registry.insert<int>(std::begin(entities), std::end(entities), 42);
|
||||
registry.insert<char>(std::begin(entities), std::end(entities), 'c');
|
||||
|
||||
other.prepare<int>();
|
||||
other.prepare<char>();
|
||||
|
||||
ASSERT_EQ(registry.size(), 10u);
|
||||
ASSERT_EQ(other.size(), 0u);
|
||||
|
||||
other.assign(registry.data(), registry.data() + registry.size(), registry.destroyed());
|
||||
|
||||
registry.visit([&](const auto info) {
|
||||
auto storage = registry.storage(info);
|
||||
other.storage(info)->insert(other, storage->data(), storage->raw(), storage->size());
|
||||
});
|
||||
other.assign(registry.data(), registry.data() + registry.size(), registry.released());
|
||||
registry.visit([&](const auto info) { std::as_const(registry).storage(info)->copy_to(other); });
|
||||
|
||||
ASSERT_EQ(registry.size(), other.size());
|
||||
ASSERT_EQ((registry.view<int, char>().size_hint()), (other.view<int, char>().size_hint()));
|
||||
ASSERT_NE((other.view<int, char>().size_hint()), 0u);
|
||||
|
||||
for(const auto entity: registry.view<int, char>()) {
|
||||
ASSERT_EQ((registry.get<int, char>(entity)), (other.get<int, char>(entity)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(PolyStorage, Constness) {
|
||||
@@ -142,13 +129,14 @@ TEST(PolyStorage, Constness) {
|
||||
entity[0] = registry.create();
|
||||
registry.emplace<int>(entity[0], 42);
|
||||
|
||||
// cannot invoke erase on a const storage, let's copy the returned value
|
||||
auto cstorage = cregistry.storage(entt::type_id<int>());
|
||||
|
||||
ASSERT_DEATH(cstorage->remove(registry, std::begin(entity), std::end(entity)), ".*");
|
||||
ASSERT_TRUE(registry.has<int>(entity[0]));
|
||||
ASSERT_DEATH(cstorage->erase(registry, std::begin(entity), std::end(entity)), "");
|
||||
ASSERT_TRUE(registry.all_of<int>(entity[0]));
|
||||
|
||||
auto storage = registry.storage(entt::type_id<int>());
|
||||
storage->remove(registry, std::begin(entity), std::end(entity));
|
||||
auto &&storage = registry.storage(entt::type_id<int>());
|
||||
storage->erase(registry, std::begin(entity), std::end(entity));
|
||||
|
||||
ASSERT_FALSE(registry.has<int>(entity[0]));
|
||||
ASSERT_FALSE(registry.all_of<int>(entity[0]));
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@ TEST(Registry, NoEto) {
|
||||
registry.emplace<empty_type>(entity);
|
||||
registry.emplace<int>(entity, 42);
|
||||
|
||||
ASSERT_NE(registry.raw<empty_type>(), nullptr);
|
||||
ASSERT_NE(registry.view<empty_type>().raw(), nullptr);
|
||||
ASSERT_NE(registry.try_get<empty_type>(entity), nullptr);
|
||||
ASSERT_EQ(registry.view<empty_type>().get(entity), std::as_const(registry).view<const empty_type>().get(entity));
|
||||
|
||||
|
||||
@@ -2,9 +2,17 @@
|
||||
#include <algorithm>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/type_info.hpp>
|
||||
#include <entt/entity/component.hpp>
|
||||
#include <entt/entity/registry.hpp>
|
||||
#include <entt/entity/runtime_view.hpp>
|
||||
|
||||
struct stable_type { int value; };
|
||||
|
||||
template<>
|
||||
struct entt::component_traits<stable_type>: basic_component_traits {
|
||||
using in_place_delete = std::true_type;
|
||||
};
|
||||
|
||||
TEST(RuntimeView, Functionalities) {
|
||||
entt::registry registry;
|
||||
|
||||
@@ -32,8 +40,8 @@ TEST(RuntimeView, Functionalities) {
|
||||
ASSERT_EQ(*it, e1);
|
||||
ASSERT_EQ(++it, (view.end()));
|
||||
|
||||
ASSERT_NO_THROW((view.begin()++));
|
||||
ASSERT_NO_THROW((++view.begin()));
|
||||
ASSERT_NO_FATAL_FAILURE((view.begin()++));
|
||||
ASSERT_NO_FATAL_FAILURE((++view.begin()));
|
||||
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
ASSERT_EQ(view.size_hint(), 1u);
|
||||
@@ -224,3 +232,43 @@ TEST(RuntimeView, ExcludedComponents) {
|
||||
ASSERT_EQ(e0, entity);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(RuntimeView, StableType) {
|
||||
entt::registry registry;
|
||||
|
||||
const auto e0 = registry.create();
|
||||
const auto e1 = registry.create();
|
||||
const auto e2 = registry.create();
|
||||
|
||||
registry.emplace<int>(e0);
|
||||
registry.emplace<int>(e1);
|
||||
registry.emplace<int>(e2);
|
||||
|
||||
registry.emplace<stable_type>(e0);
|
||||
registry.emplace<stable_type>(e1);
|
||||
|
||||
registry.remove<stable_type>(e1);
|
||||
|
||||
entt::id_type components[] = { entt::type_hash<int>::value(), entt::type_hash<stable_type>::value() };
|
||||
auto view = registry.runtime_view(std::begin(components), std::end(components));
|
||||
|
||||
ASSERT_EQ(view.size_hint(), 2u);
|
||||
ASSERT_TRUE(view.contains(e0));
|
||||
ASSERT_FALSE(view.contains(e1));
|
||||
|
||||
ASSERT_EQ(*view.begin(), e0);
|
||||
ASSERT_EQ(++view.begin(), view.end());
|
||||
|
||||
view.each([e0](const auto entt) {
|
||||
ASSERT_EQ(e0, entt);
|
||||
});
|
||||
|
||||
for(auto entt: view) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
ASSERT_EQ(e0, entt);
|
||||
}
|
||||
|
||||
registry.compact();
|
||||
|
||||
ASSERT_EQ(view.size_hint(), 1u);
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ TEST(Snapshot, Dump) {
|
||||
ASSERT_EQ(registry.current(e1), v1);
|
||||
ASSERT_EQ(registry.get<int>(e2), 3);
|
||||
ASSERT_EQ(registry.get<char>(e3), '0');
|
||||
ASSERT_TRUE(registry.has<a_component>(e3));
|
||||
ASSERT_TRUE(registry.all_of<a_component>(e3));
|
||||
|
||||
ASSERT_TRUE(registry.empty<another_component>());
|
||||
}
|
||||
@@ -199,7 +199,7 @@ TEST(Snapshot, Partial) {
|
||||
|
||||
ASSERT_EQ(registry.get<int>(e0), 42);
|
||||
ASSERT_EQ(registry.get<char>(e0), 'c');
|
||||
ASSERT_FALSE(registry.has<double>(e0));
|
||||
ASSERT_FALSE(registry.all_of<double>(e0));
|
||||
ASSERT_EQ(registry.current(e1), v1);
|
||||
ASSERT_EQ(registry.get<int>(e2), 3);
|
||||
ASSERT_EQ(registry.get<char>(e3), '0');
|
||||
@@ -286,7 +286,7 @@ TEST(Snapshot, Continuous) {
|
||||
input_archive<storage_type> input{storage};
|
||||
|
||||
for(int i = 0; i < 10; ++i) {
|
||||
src.create();
|
||||
static_cast<void>(src.create());
|
||||
}
|
||||
|
||||
src.clear();
|
||||
@@ -342,7 +342,7 @@ TEST(Snapshot, Continuous) {
|
||||
decltype(dst.size()) noncopyable_component_cnt{};
|
||||
|
||||
dst.each([&dst, &a_component_cnt](auto entt) {
|
||||
ASSERT_TRUE(dst.has<a_component>(entt));
|
||||
ASSERT_TRUE(dst.all_of<a_component>(entt));
|
||||
++a_component_cnt;
|
||||
});
|
||||
|
||||
@@ -549,8 +549,8 @@ TEST(Snapshot, SyncDataMembers) {
|
||||
output_archive<storage_type> output{storage};
|
||||
input_archive<storage_type> input{storage};
|
||||
|
||||
src.create();
|
||||
src.create();
|
||||
static_cast<void>(src.create());
|
||||
static_cast<void>(src.create());
|
||||
|
||||
src.clear();
|
||||
|
||||
@@ -582,8 +582,8 @@ TEST(Snapshot, SyncDataMembers) {
|
||||
ASSERT_FALSE(dst.valid(parent));
|
||||
ASSERT_FALSE(dst.valid(child));
|
||||
|
||||
ASSERT_TRUE(dst.has<what_a_component>(loader.map(parent)));
|
||||
ASSERT_TRUE(dst.has<what_a_component>(loader.map(child)));
|
||||
ASSERT_TRUE(dst.all_of<what_a_component>(loader.map(parent)));
|
||||
ASSERT_TRUE(dst.all_of<what_a_component>(loader.map(child)));
|
||||
|
||||
ASSERT_EQ(dst.get<what_a_component>(loader.map(parent)).bar, static_cast<entt::entity>(entt::null));
|
||||
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/entity.hpp>
|
||||
#include <entt/entity/sparse_set.hpp>
|
||||
#include <entt/entity/fwd.hpp>
|
||||
#include "throwing_allocator.hpp"
|
||||
|
||||
struct empty_type {};
|
||||
struct boxed_int { int value; };
|
||||
@@ -24,9 +26,12 @@ TEST(SparseSet, Functionalities) {
|
||||
ASSERT_FALSE(set.contains(entt::entity{0}));
|
||||
ASSERT_FALSE(set.contains(entt::entity{42}));
|
||||
|
||||
set.emplace(entt::entity{42});
|
||||
set.reserve(0);
|
||||
|
||||
ASSERT_EQ(set.index(entt::entity{42}), 0u);
|
||||
ASSERT_EQ(set.capacity(), 42u);
|
||||
ASSERT_TRUE(set.empty());
|
||||
|
||||
ASSERT_EQ(set.emplace(entt::entity{42}), 0u);
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 1u);
|
||||
@@ -35,8 +40,11 @@ TEST(SparseSet, Functionalities) {
|
||||
ASSERT_FALSE(set.contains(entt::entity{0}));
|
||||
ASSERT_TRUE(set.contains(entt::entity{42}));
|
||||
ASSERT_EQ(set.index(entt::entity{42}), 0u);
|
||||
ASSERT_EQ(set.at(0u), entt::entity{42});
|
||||
ASSERT_EQ(set.at(1u), static_cast<entt::entity>(entt::null));
|
||||
ASSERT_EQ(set[0u], entt::entity{42});
|
||||
|
||||
set.remove(entt::entity{42});
|
||||
set.erase(entt::entity{42});
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
@@ -44,83 +52,175 @@ TEST(SparseSet, Functionalities) {
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.contains(entt::entity{0}));
|
||||
ASSERT_FALSE(set.contains(entt::entity{42}));
|
||||
ASSERT_EQ(set.at(0u), static_cast<entt::entity>(entt::null));
|
||||
ASSERT_EQ(set.at(1u), static_cast<entt::entity>(entt::null));
|
||||
|
||||
set.emplace(entt::entity{42});
|
||||
ASSERT_EQ(set.emplace(entt::entity{42}), 0u);
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.index(entt::entity{42}), 0u);
|
||||
ASSERT_EQ(set.at(0u), entt::entity{42});
|
||||
ASSERT_EQ(set.at(1u), static_cast<entt::entity>(entt::null));
|
||||
ASSERT_EQ(set[0u], entt::entity{42});
|
||||
|
||||
set.clear();
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end());
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.contains(entt::entity{0}));
|
||||
ASSERT_FALSE(set.contains(entt::entity{42}));
|
||||
}
|
||||
|
||||
TEST(SparseSet, Contains) {
|
||||
entt::sparse_set set{entt::deletion_policy::in_place};
|
||||
|
||||
set.emplace(entt::entity{0});
|
||||
set.emplace(entt::entity{3});
|
||||
set.emplace(entt::entity{42});
|
||||
set.emplace(entt::entity{99});
|
||||
|
||||
set.emplace(entt::entity{1});
|
||||
|
||||
ASSERT_TRUE(set.contains(entt::entity{0}));
|
||||
ASSERT_TRUE(set.contains(entt::entity{3}));
|
||||
ASSERT_TRUE(set.contains(entt::entity{42}));
|
||||
ASSERT_TRUE(set.contains(entt::entity{99}));
|
||||
ASSERT_TRUE(set.contains(entt::entity{1}));
|
||||
|
||||
set.erase(entt::entity{0});
|
||||
set.erase(entt::entity{3});
|
||||
|
||||
set.remove(entt::entity{42});
|
||||
set.remove(entt::entity{99});
|
||||
|
||||
ASSERT_FALSE(set.contains(entt::entity{0}));
|
||||
ASSERT_FALSE(set.contains(entt::entity{3}));
|
||||
ASSERT_FALSE(set.contains(entt::entity{42}));
|
||||
ASSERT_FALSE(set.contains(entt::entity{99}));
|
||||
ASSERT_TRUE(set.contains(entt::entity{1}));
|
||||
|
||||
ASSERT_DEATH(static_cast<void>(set.contains(entt::null)), "");
|
||||
ASSERT_DEATH(static_cast<void>(set.contains(entt::tombstone)), "");
|
||||
ASSERT_DEATH(static_cast<void>(set.contains(entt::tombstone | entt::entity{1u})), "");
|
||||
ASSERT_DEATH(static_cast<void>(set.contains(entt::null | entt::entity{1u})), "");
|
||||
}
|
||||
|
||||
TEST(SparseSet, Move) {
|
||||
entt::sparse_set set;
|
||||
set.emplace(entt::entity{42});
|
||||
|
||||
ASSERT_TRUE(std::is_move_constructible_v<decltype(set)>);
|
||||
ASSERT_TRUE(std::is_move_assignable_v<decltype(set)>);
|
||||
|
||||
entt::sparse_set other{std::move(set)};
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_FALSE(other.empty());
|
||||
ASSERT_EQ(set.at(0u), static_cast<entt::entity>(entt::null));
|
||||
ASSERT_EQ(other.at(0u), entt::entity{42});
|
||||
|
||||
set = std::move(other);
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_TRUE(other.empty());
|
||||
ASSERT_EQ(set.at(0u), entt::entity{42});
|
||||
ASSERT_EQ(other.at(0u), static_cast<entt::entity>(entt::null));
|
||||
|
||||
other = entt::sparse_set{};
|
||||
other.emplace(entt::entity{3});
|
||||
other = std::move(set);
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_FALSE(other.empty());
|
||||
ASSERT_EQ(other.index(entt::entity{42}), 0u);
|
||||
|
||||
other.clear();
|
||||
|
||||
ASSERT_TRUE(other.empty());
|
||||
ASSERT_EQ(other.size(), 0u);
|
||||
ASSERT_EQ(std::as_const(other).begin(), std::as_const(other).end());
|
||||
ASSERT_EQ(other.begin(), other.end());
|
||||
ASSERT_FALSE(other.contains(entt::entity{0}));
|
||||
ASSERT_FALSE(other.contains(entt::entity{42}));
|
||||
ASSERT_EQ(set.at(0u), static_cast<entt::entity>(entt::null));
|
||||
ASSERT_EQ(other.at(0u), entt::entity{42});
|
||||
}
|
||||
|
||||
TEST(SparseSet, Pagination) {
|
||||
entt::sparse_set set;
|
||||
constexpr auto entt_per_page = ENTT_PAGE_SIZE / sizeof(entt::entity);
|
||||
|
||||
ASSERT_EQ(set.extent(), 0u);
|
||||
|
||||
set.emplace(entt::entity{entt_per_page-1});
|
||||
set.emplace(entt::entity{ENTT_SPARSE_PAGE-1u});
|
||||
|
||||
ASSERT_EQ(set.extent(), entt_per_page);
|
||||
ASSERT_TRUE(set.contains(entt::entity{entt_per_page-1}));
|
||||
ASSERT_EQ(set.extent(), ENTT_SPARSE_PAGE);
|
||||
ASSERT_TRUE(set.contains(entt::entity{ENTT_SPARSE_PAGE-1u}));
|
||||
|
||||
set.emplace(entt::entity{entt_per_page});
|
||||
set.emplace(entt::entity{ENTT_SPARSE_PAGE});
|
||||
|
||||
ASSERT_EQ(set.extent(), 2 * entt_per_page);
|
||||
ASSERT_TRUE(set.contains(entt::entity{entt_per_page-1}));
|
||||
ASSERT_TRUE(set.contains(entt::entity{entt_per_page}));
|
||||
ASSERT_FALSE(set.contains(entt::entity{entt_per_page+1}));
|
||||
ASSERT_EQ(set.extent(), 2 * ENTT_SPARSE_PAGE);
|
||||
ASSERT_TRUE(set.contains(entt::entity{ENTT_SPARSE_PAGE-1u}));
|
||||
ASSERT_TRUE(set.contains(entt::entity{ENTT_SPARSE_PAGE}));
|
||||
ASSERT_FALSE(set.contains(entt::entity{ENTT_SPARSE_PAGE+1u}));
|
||||
|
||||
set.remove(entt::entity{entt_per_page-1});
|
||||
set.erase(entt::entity{ENTT_SPARSE_PAGE-1u});
|
||||
|
||||
ASSERT_EQ(set.extent(), 2 * entt_per_page);
|
||||
ASSERT_FALSE(set.contains(entt::entity{entt_per_page-1}));
|
||||
ASSERT_TRUE(set.contains(entt::entity{entt_per_page}));
|
||||
ASSERT_EQ(set.extent(), 2 * ENTT_SPARSE_PAGE);
|
||||
ASSERT_FALSE(set.contains(entt::entity{ENTT_SPARSE_PAGE-1u}));
|
||||
ASSERT_TRUE(set.contains(entt::entity{ENTT_SPARSE_PAGE}));
|
||||
|
||||
set.shrink_to_fit();
|
||||
set.remove(entt::entity{entt_per_page});
|
||||
set.erase(entt::entity{ENTT_SPARSE_PAGE});
|
||||
|
||||
ASSERT_EQ(set.extent(), 2 * entt_per_page);
|
||||
ASSERT_FALSE(set.contains(entt::entity{entt_per_page-1}));
|
||||
ASSERT_FALSE(set.contains(entt::entity{entt_per_page}));
|
||||
ASSERT_EQ(set.extent(), 2 * ENTT_SPARSE_PAGE);
|
||||
ASSERT_FALSE(set.contains(entt::entity{ENTT_SPARSE_PAGE-1u}));
|
||||
ASSERT_FALSE(set.contains(entt::entity{ENTT_SPARSE_PAGE}));
|
||||
|
||||
set.shrink_to_fit();
|
||||
|
||||
ASSERT_EQ(set.extent(), 0u);
|
||||
ASSERT_EQ(set.extent(), 2 * ENTT_SPARSE_PAGE);
|
||||
}
|
||||
|
||||
TEST(SparseSet, Emplace) {
|
||||
entt::sparse_set set{entt::deletion_policy::in_place};
|
||||
entt::entity entities[2u];
|
||||
|
||||
entities[0u] = entt::entity{3};
|
||||
entities[1u] = entt::entity{42};
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
|
||||
set.emplace(entities[0u]);
|
||||
set.erase(entities[0u]);
|
||||
|
||||
set.emplace_back(entities[0u]);
|
||||
set.emplace(entities[1u]);
|
||||
|
||||
ASSERT_DEATH(set.emplace_back(entities[1u]), "");
|
||||
ASSERT_DEATH(set.emplace(entities[0u]), "");
|
||||
|
||||
ASSERT_EQ(set.at(0u), entities[1u]);
|
||||
ASSERT_EQ(set.at(1u), entities[0u]);
|
||||
ASSERT_EQ(set.index(entities[0u]), 1u);
|
||||
ASSERT_EQ(set.index(entities[1u]), 0u);
|
||||
|
||||
set.erase(std::begin(entities), std::end(entities));
|
||||
set.emplace(entities[1u]);
|
||||
set.emplace_back(entities[0u]);
|
||||
|
||||
ASSERT_EQ(set.at(0u), entities[1u]);
|
||||
ASSERT_EQ(set.at(1u), static_cast<entt::entity>(entt::null));
|
||||
ASSERT_EQ(set.at(2u), entities[0u]);
|
||||
ASSERT_EQ(set.index(entities[0u]), 2u);
|
||||
ASSERT_EQ(set.index(entities[1u]), 0u);
|
||||
}
|
||||
|
||||
TEST(SparseSet, Insert) {
|
||||
entt::sparse_set set;
|
||||
entt::entity entities[2];
|
||||
entt::sparse_set set{entt::deletion_policy::in_place};
|
||||
entt::entity entities[2u];
|
||||
|
||||
entities[0] = entt::entity{3};
|
||||
entities[1] = entt::entity{42};
|
||||
entities[0u] = entt::entity{3};
|
||||
entities[1u] = entt::entity{42};
|
||||
|
||||
set.emplace(entt::entity{12});
|
||||
set.insert(std::end(entities), std::end(entities));
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
set.emplace(entt::entity{24});
|
||||
|
||||
ASSERT_TRUE(set.contains(entities[0]));
|
||||
ASSERT_TRUE(set.contains(entities[1]));
|
||||
ASSERT_TRUE(set.contains(entities[0u]));
|
||||
ASSERT_TRUE(set.contains(entities[1u]));
|
||||
ASSERT_FALSE(set.contains(entt::entity{0}));
|
||||
ASSERT_FALSE(set.contains(entt::entity{9}));
|
||||
ASSERT_TRUE(set.contains(entt::entity{12}));
|
||||
@@ -129,46 +229,368 @@ TEST(SparseSet, Insert) {
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 4u);
|
||||
ASSERT_EQ(set.index(entt::entity{12}), 0u);
|
||||
ASSERT_EQ(set.index(entities[0]), 1u);
|
||||
ASSERT_EQ(set.index(entities[1]), 2u);
|
||||
ASSERT_EQ(set.index(entities[0u]), 1u);
|
||||
ASSERT_EQ(set.index(entities[1u]), 2u);
|
||||
ASSERT_EQ(set.index(entt::entity{24}), 3u);
|
||||
ASSERT_EQ(set.data()[set.index(entt::entity{12})], entt::entity{12});
|
||||
ASSERT_EQ(set.data()[set.index(entities[0])], entities[0]);
|
||||
ASSERT_EQ(set.data()[set.index(entities[1])], entities[1]);
|
||||
ASSERT_EQ(set.data()[set.index(entities[0u])], entities[0u]);
|
||||
ASSERT_EQ(set.data()[set.index(entities[1u])], entities[1u]);
|
||||
ASSERT_EQ(set.data()[set.index(entt::entity{24})], entt::entity{24});
|
||||
|
||||
set.erase(std::begin(entities), std::end(entities));
|
||||
set.insert(std::rbegin(entities), std::rend(entities));
|
||||
|
||||
ASSERT_EQ(set.size(), 6u);
|
||||
ASSERT_TRUE(set.at(1u) == entt::tombstone);
|
||||
ASSERT_TRUE(set.at(2u) == entt::tombstone);
|
||||
ASSERT_EQ(set.at(4u), entities[1u]);
|
||||
ASSERT_EQ(set.at(5u), entities[0u]);
|
||||
ASSERT_EQ(set.index(entities[0u]), 5u);
|
||||
ASSERT_EQ(set.index(entities[1u]), 4u);
|
||||
}
|
||||
|
||||
TEST(SparseSet, Erase) {
|
||||
entt::sparse_set set;
|
||||
entt::entity entities[3u];
|
||||
|
||||
entities[0u] = entt::entity{3};
|
||||
entities[1u] = entt::entity{42};
|
||||
entities[2u] = entt::entity{9};
|
||||
|
||||
ASSERT_EQ(set.policy(), entt::deletion_policy::swap_and_pop);
|
||||
ASSERT_TRUE(set.empty());
|
||||
|
||||
ASSERT_DEATH(set.erase(std::begin(entities), std::end(entities)), "");
|
||||
ASSERT_DEATH(set.erase(entities[1u]), "");
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
set.erase(set.begin(), set.end());
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
set.erase(entities, entities + 2u);
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(*set.begin(), entities[2u]);
|
||||
|
||||
set.erase(entities[2u]);
|
||||
|
||||
ASSERT_DEATH(set.erase(entities[2u]), "");
|
||||
ASSERT_TRUE(set.empty());
|
||||
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
std::swap(entities[1u], entities[2u]);
|
||||
set.erase(entities, entities + 2u);
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(*set.begin(), entities[2u]);
|
||||
}
|
||||
|
||||
TEST(SparseSet, StableErase) {
|
||||
entt::sparse_set set{entt::deletion_policy::in_place};
|
||||
entt::entity entities[3u];
|
||||
|
||||
entities[0u] = entt::entity{3};
|
||||
entities[1u] = entt::entity{42};
|
||||
entities[2u] = entt::entity{9};
|
||||
|
||||
ASSERT_EQ(set.policy(), entt::deletion_policy::in_place);
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
|
||||
ASSERT_DEATH(set.erase(std::begin(entities), std::end(entities)), "");
|
||||
ASSERT_DEATH(set.erase(entities[1u]), "");
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
set.erase(set.begin(), set.end());
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 3u);
|
||||
ASSERT_TRUE(set.at(0u) == entt::tombstone);
|
||||
ASSERT_TRUE(set.at(1u) == entt::tombstone);
|
||||
ASSERT_TRUE(set.at(2u) == entt::tombstone);
|
||||
ASSERT_EQ(set.slot(), 0u);
|
||||
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
set.erase(entities, entities + 2u);
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 6u);
|
||||
ASSERT_EQ(*set.begin(), entities[2u]);
|
||||
ASSERT_TRUE(set.at(3u) == entt::tombstone);
|
||||
ASSERT_TRUE(set.at(4u) == entt::tombstone);
|
||||
ASSERT_EQ(set.slot(), 4u);
|
||||
|
||||
set.erase(entities[2u]);
|
||||
|
||||
ASSERT_DEATH(set.erase(entities[2u]), "");
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 6u);
|
||||
ASSERT_EQ(set.slot(), 5u);
|
||||
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
std::swap(entities[1u], entities[2u]);
|
||||
set.erase(entities, entities + 2u);
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 9u);
|
||||
ASSERT_TRUE(set.at(6u) == entt::tombstone);
|
||||
ASSERT_EQ(set.at(7u), entities[2u]);
|
||||
ASSERT_EQ(*++set.begin(), entities[2u]);
|
||||
ASSERT_TRUE(set.at(8u) == entt::tombstone);
|
||||
ASSERT_EQ(set.slot(), 8u);
|
||||
|
||||
set.compact();
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 1u);
|
||||
ASSERT_EQ(*set.begin(), entities[2u]);
|
||||
ASSERT_EQ(set.slot(), 1u);
|
||||
|
||||
set.clear();
|
||||
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_EQ(set.slot(), 0u);
|
||||
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
set.erase(entities[2u]);
|
||||
|
||||
ASSERT_DEATH(set.erase(entities[2u]), "");
|
||||
ASSERT_EQ(set.slot(), 2u);
|
||||
|
||||
set.erase(entities[0u]);
|
||||
set.erase(entities[1u]);
|
||||
|
||||
ASSERT_DEATH(set.erase(entities, entities + 2u), "");
|
||||
ASSERT_EQ(set.size(), 3u);
|
||||
ASSERT_TRUE(*set.begin() == entt::tombstone);
|
||||
ASSERT_EQ(set.slot(), 1u);
|
||||
|
||||
ASSERT_EQ(set.emplace(entities[0u]), 1u);
|
||||
ASSERT_EQ(*++set.begin(), entities[0u]);
|
||||
|
||||
ASSERT_EQ(set.emplace(entities[1u]), 0u);
|
||||
ASSERT_EQ(set.emplace(entities[2u]), 2u);
|
||||
ASSERT_EQ(set.emplace(entt::entity{0}), 3u);
|
||||
|
||||
ASSERT_EQ(set.size(), 4u);
|
||||
ASSERT_EQ(*set.begin(), entt::entity{0});
|
||||
ASSERT_EQ(set.at(0u), entities[1u]);
|
||||
ASSERT_EQ(set.at(1u), entities[0u]);
|
||||
ASSERT_EQ(set.at(2u), entities[2u]);
|
||||
}
|
||||
|
||||
TEST(SparseSet, Remove) {
|
||||
entt::sparse_set set;
|
||||
entt::entity entities[3];
|
||||
entt::entity entities[3u];
|
||||
|
||||
entities[0] = entt::entity{3};
|
||||
entities[1] = entt::entity{42};
|
||||
entities[2] = entt::entity{9};
|
||||
entities[0u] = entt::entity{3};
|
||||
entities[1u] = entt::entity{42};
|
||||
entities[2u] = entt::entity{9};
|
||||
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
set.remove(set.begin(), set.end());
|
||||
ASSERT_EQ(set.policy(), entt::deletion_policy::swap_and_pop);
|
||||
ASSERT_TRUE(set.empty());
|
||||
|
||||
ASSERT_EQ(set.remove(std::begin(entities), std::end(entities)), 0u);
|
||||
ASSERT_EQ(set.remove(entities[1u]), 0u);
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
set.remove(set.rbegin(), set.rend());
|
||||
|
||||
ASSERT_EQ(set.remove(set.begin(), set.end()), 3u);
|
||||
ASSERT_TRUE(set.empty());
|
||||
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
set.remove(entities, entities + 2u);
|
||||
|
||||
ASSERT_EQ(set.remove(entities, entities + 2u), 2u);
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(*set.begin(), entities[2u]);
|
||||
|
||||
ASSERT_EQ(set.remove(entities[2u]), 1u);
|
||||
ASSERT_EQ(set.remove(entities[2u]), 0u);
|
||||
ASSERT_TRUE(set.empty());
|
||||
|
||||
set.insert(entities, entities + 2u);
|
||||
|
||||
ASSERT_EQ(set.remove(std::begin(entities), std::end(entities)), 2u);
|
||||
ASSERT_TRUE(set.empty());
|
||||
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
std::swap(entities[1u], entities[2u]);
|
||||
|
||||
ASSERT_EQ(set.remove(entities, entities + 2u), 2u);
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(*set.begin(), entities[2u]);
|
||||
}
|
||||
|
||||
TEST(SparseSet, StableRemove) {
|
||||
entt::sparse_set set{entt::deletion_policy::in_place};
|
||||
entt::entity entities[3u];
|
||||
|
||||
entities[0u] = entt::entity{3};
|
||||
entities[1u] = entt::entity{42};
|
||||
entities[2u] = entt::entity{9};
|
||||
|
||||
ASSERT_EQ(set.policy(), entt::deletion_policy::in_place);
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
|
||||
ASSERT_EQ(set.remove(std::begin(entities), std::end(entities)), 0u);
|
||||
ASSERT_EQ(set.remove(entities[1u]), 0u);
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
|
||||
ASSERT_EQ(set.remove(set.begin(), set.end()), 3u);
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 3u);
|
||||
ASSERT_TRUE(set.at(0u) == entt::tombstone);
|
||||
ASSERT_TRUE(set.at(1u) == entt::tombstone);
|
||||
ASSERT_TRUE(set.at(2u) == entt::tombstone);
|
||||
ASSERT_EQ(set.slot(), 0u);
|
||||
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
|
||||
ASSERT_EQ(set.remove(entities, entities + 2u), 2u);
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 6u);
|
||||
ASSERT_EQ(*set.begin(), entt::entity{9});
|
||||
ASSERT_TRUE(set.at(3u) == entt::tombstone);
|
||||
ASSERT_TRUE(set.at(4u) == entt::tombstone);
|
||||
ASSERT_EQ(set.slot(), 4u);
|
||||
|
||||
ASSERT_EQ(set.remove(entities[2u]), 1u);
|
||||
ASSERT_EQ(set.remove(entities[2u]), 0u);
|
||||
ASSERT_EQ(set.remove(entities[2u]), 0u);
|
||||
ASSERT_EQ(set.remove(entities[2u]), 0u);
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 6u);
|
||||
ASSERT_TRUE(*set.begin() == entt::tombstone);
|
||||
ASSERT_EQ(set.slot(), 5u);
|
||||
|
||||
set.insert(entities, entities + 2u);
|
||||
|
||||
ASSERT_EQ(set.remove(std::begin(entities), std::end(entities)), 2u);
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 8u);
|
||||
ASSERT_TRUE(set.at(6u) == entt::tombstone);
|
||||
ASSERT_TRUE(set.at(7u) == entt::tombstone);
|
||||
ASSERT_EQ(set.slot(), 7u);
|
||||
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
std::swap(entities[1u], entities[2u]);
|
||||
|
||||
ASSERT_EQ(set.remove(entities, entities + 2u), 2u);
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 11u);
|
||||
ASSERT_TRUE(set.at(8u) == entt::tombstone);
|
||||
ASSERT_EQ(set.at(9u), entities[2u]);
|
||||
ASSERT_EQ(*++set.begin(), entities[2u]);
|
||||
ASSERT_TRUE(set.at(10u) == entt::tombstone);
|
||||
ASSERT_EQ(set.slot(), 10u);
|
||||
|
||||
set.compact();
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(*set.begin(), entt::entity{9});
|
||||
ASSERT_EQ(set.size(), 1u);
|
||||
ASSERT_EQ(*set.begin(), entities[2u]);
|
||||
ASSERT_EQ(set.slot(), 1u);
|
||||
|
||||
set.clear();
|
||||
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_EQ(set.slot(), 0u);
|
||||
|
||||
set.insert(std::begin(entities), std::end(entities));
|
||||
std::swap(entities[1], entities[2]);
|
||||
set.remove(entities, entities + 2u);
|
||||
|
||||
ASSERT_EQ(set.remove(entities[2u]), 1u);
|
||||
ASSERT_EQ(set.remove(entities[2u]), 0u);
|
||||
|
||||
ASSERT_EQ(set.remove(entities[0u]), 1u);
|
||||
ASSERT_EQ(set.remove(entities[1u]), 1u);
|
||||
ASSERT_EQ(set.remove(entities, entities + 2u), 0u);
|
||||
|
||||
ASSERT_EQ(set.size(), 3u);
|
||||
ASSERT_TRUE(*set.begin() == entt::tombstone);
|
||||
ASSERT_EQ(set.slot(), 1u);
|
||||
|
||||
ASSERT_EQ(set.emplace(entities[0u]), 1u);
|
||||
ASSERT_EQ(*++set.begin(), entities[0u]);
|
||||
|
||||
ASSERT_EQ(set.emplace(entities[1u]), 0u);
|
||||
ASSERT_EQ(set.emplace(entities[2u]), 2u);
|
||||
ASSERT_EQ(set.emplace(entt::entity{0}), 3u);
|
||||
|
||||
ASSERT_EQ(set.size(), 4u);
|
||||
ASSERT_EQ(*set.begin(), entt::entity{0});
|
||||
ASSERT_EQ(set.at(0u), entities[1u]);
|
||||
ASSERT_EQ(set.at(1u), entities[0u]);
|
||||
ASSERT_EQ(set.at(2u), entities[2u]);
|
||||
}
|
||||
|
||||
TEST(SparseSet, Compact) {
|
||||
entt::sparse_set set{entt::deletion_policy::in_place};
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
|
||||
set.compact();
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
|
||||
set.emplace(entt::entity{0});
|
||||
set.compact();
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(*set.begin(), entt::entity{42});
|
||||
ASSERT_EQ(set.size(), 1u);
|
||||
|
||||
set.emplace(entt::entity{42});
|
||||
set.erase(entt::entity{0});
|
||||
|
||||
ASSERT_EQ(set.size(), 2u);
|
||||
ASSERT_EQ(set.index(entt::entity{42}), 1u);
|
||||
|
||||
set.compact();
|
||||
|
||||
ASSERT_EQ(set.size(), 1u);
|
||||
ASSERT_EQ(set.index(entt::entity{42}), 0u);
|
||||
|
||||
set.emplace(entt::entity{0});
|
||||
set.compact();
|
||||
|
||||
ASSERT_EQ(set.size(), 2u);
|
||||
ASSERT_EQ(set.index(entt::entity{42}), 0u);
|
||||
ASSERT_EQ(set.index(entt::entity{0}), 1u);
|
||||
|
||||
set.erase(entt::entity{0});
|
||||
set.erase(entt::entity{42});
|
||||
set.compact();
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
}
|
||||
|
||||
TEST(SparseSet, Clear) {
|
||||
entt::sparse_set set;
|
||||
|
||||
set.emplace(entt::entity{3});
|
||||
set.emplace(entt::entity{42});
|
||||
set.emplace(entt::entity{9});
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
|
||||
set.clear();
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
}
|
||||
|
||||
TEST(SparseSet, Iterator) {
|
||||
@@ -204,7 +626,7 @@ TEST(SparseSet, Iterator) {
|
||||
ASSERT_EQ(end - (end - begin), set.begin());
|
||||
ASSERT_EQ(end + (begin - end), set.begin());
|
||||
|
||||
ASSERT_EQ(begin[0], *set.begin());
|
||||
ASSERT_EQ(begin[0u], *set.begin());
|
||||
|
||||
ASSERT_LT(begin, end);
|
||||
ASSERT_LE(begin, set.begin());
|
||||
@@ -249,7 +671,7 @@ TEST(SparseSet, ReverseIterator) {
|
||||
ASSERT_EQ(end - (end - begin), set.rbegin());
|
||||
ASSERT_EQ(end + (begin - end), set.rbegin());
|
||||
|
||||
ASSERT_EQ(begin[0], *set.rbegin());
|
||||
ASSERT_EQ(begin[0u], *set.rbegin());
|
||||
|
||||
ASSERT_LT(begin, end);
|
||||
ASSERT_LE(begin, set.rbegin());
|
||||
@@ -487,3 +909,51 @@ TEST(SparseSet, CanModifyDuringIteration) {
|
||||
const auto entity = *it;
|
||||
(void)entity;
|
||||
}
|
||||
|
||||
TEST(SparseSet, ThrowingAllocator) {
|
||||
entt::basic_sparse_set<entt::entity, test::throwing_allocator<entt::entity>> set{};
|
||||
|
||||
test::throwing_allocator<entt::entity>::trigger_on_allocate = true;
|
||||
|
||||
// strong exception safety
|
||||
ASSERT_THROW(set.reserve(1u), test::throwing_allocator<entt::entity>::exception_type);
|
||||
ASSERT_EQ(set.capacity(), 0u);
|
||||
ASSERT_EQ(set.extent(), 0u);
|
||||
|
||||
test::throwing_allocator<entt::entity>::trigger_on_allocate = true;
|
||||
|
||||
// strong exception safety
|
||||
ASSERT_THROW(set.emplace(entt::entity{0}), test::throwing_allocator<entt::entity>::exception_type);
|
||||
ASSERT_EQ(set.capacity(), 0u);
|
||||
ASSERT_EQ(set.extent(), 0u);
|
||||
|
||||
set.emplace(entt::entity{0});
|
||||
test::throwing_allocator<entt::entity>::trigger_on_allocate = true;
|
||||
|
||||
// strong exception safety
|
||||
ASSERT_THROW(set.reserve(2u), test::throwing_allocator<entt::entity>::exception_type);
|
||||
ASSERT_EQ(set.capacity(), 1u);
|
||||
ASSERT_EQ(set.extent(), ENTT_SPARSE_PAGE);
|
||||
ASSERT_TRUE(set.contains(entt::entity{0}));
|
||||
|
||||
entt::entity entities[2u]{entt::entity{1}, entt::entity{ENTT_SPARSE_PAGE}};
|
||||
test::throwing_allocator<entt::entity>::trigger_after_allocate = true;
|
||||
|
||||
// basic exception safety
|
||||
ASSERT_THROW(set.insert(std::begin(entities), std::end(entities)), test::throwing_allocator<entt::entity>::exception_type);
|
||||
ASSERT_EQ(set.capacity(), 3u);
|
||||
ASSERT_EQ(set.size(), 2u);
|
||||
ASSERT_EQ(set.extent(), 2 * ENTT_SPARSE_PAGE);
|
||||
ASSERT_TRUE(set.contains(entt::entity{0}));
|
||||
ASSERT_TRUE(set.contains(entt::entity{1}));
|
||||
ASSERT_FALSE(set.contains(entt::entity{ENTT_SPARSE_PAGE}));
|
||||
|
||||
set.emplace(entities[1u]);
|
||||
|
||||
ASSERT_TRUE(set.contains(entt::entity{ENTT_SPARSE_PAGE}));
|
||||
|
||||
// unnecessary but they test a bit of template machinery :)
|
||||
set.clear();
|
||||
set.shrink_to_fit();
|
||||
set = decltype(set){};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -20,10 +28,10 @@ TEST(SingleComponentView, Functionalities) {
|
||||
registry.emplace<int>(e1);
|
||||
registry.emplace<char>(e1);
|
||||
|
||||
ASSERT_NO_THROW(view.begin()++);
|
||||
ASSERT_NO_THROW(++cview.begin());
|
||||
ASSERT_NO_THROW([](auto it) { return it++; }(view.rbegin()));
|
||||
ASSERT_NO_THROW([](auto it) { return ++it; }(cview.rbegin()));
|
||||
ASSERT_NO_FATAL_FAILURE(view.begin()++);
|
||||
ASSERT_NO_FATAL_FAILURE(++cview.begin());
|
||||
ASSERT_NO_FATAL_FAILURE([](auto it) { return it++; }(view.rbegin()));
|
||||
ASSERT_NO_FATAL_FAILURE([](auto it) { return ++it; }(cview.rbegin()));
|
||||
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
ASSERT_NE(cview.begin(), cview.end());
|
||||
@@ -43,14 +51,14 @@ TEST(SingleComponentView, Functionalities) {
|
||||
ASSERT_TRUE(cview.get<const char>(entity) == '1' || std::get<const char &>(cview.get(entity)) == '2');
|
||||
}
|
||||
|
||||
ASSERT_EQ(*(view.data() + 0), e1);
|
||||
ASSERT_EQ(*(view.data() + 1), e0);
|
||||
ASSERT_EQ(view.data()[0u], e1);
|
||||
ASSERT_EQ(view.data()[1u], e0);
|
||||
|
||||
ASSERT_EQ(*(view.raw() + 0), '2');
|
||||
ASSERT_EQ(*(cview.raw() + 1), '1');
|
||||
ASSERT_EQ(view.raw()[0u][0u], '2');
|
||||
ASSERT_EQ(cview.raw()[0u][1u], '1');
|
||||
|
||||
registry.remove<char>(e0);
|
||||
registry.remove<char>(e1);
|
||||
registry.erase<char>(e0);
|
||||
registry.erase<char>(e1);
|
||||
|
||||
ASSERT_EQ(view.begin(), view.end());
|
||||
ASSERT_EQ(view.rbegin(), view.rend());
|
||||
@@ -63,7 +71,34 @@ TEST(SingleComponentView, Functionalities) {
|
||||
ASSERT_FALSE(invalid);
|
||||
}
|
||||
|
||||
TEST(SingleComponentView, Invalid) {
|
||||
TEST(SingleComponentView, RawData) {
|
||||
entt::registry registry;
|
||||
auto view = registry.view<int>();
|
||||
auto cview = std::as_const(registry).view<const int>();
|
||||
|
||||
const auto entity = registry.create();
|
||||
|
||||
ASSERT_EQ(view.size(), 0u);
|
||||
ASSERT_EQ(cview.size(), 0u);
|
||||
ASSERT_EQ(view.raw(), cview.raw());
|
||||
ASSERT_EQ(view.data(), cview.data());
|
||||
|
||||
registry.emplace<int>(entity, 42);
|
||||
|
||||
ASSERT_NE(view.size(), 0u);
|
||||
ASSERT_NE(cview.size(), 0u);
|
||||
ASSERT_EQ(view.raw()[0u][0u], 42);
|
||||
ASSERT_EQ(cview.raw()[0u][0u], 42);
|
||||
ASSERT_EQ(view.data()[0u], entity);
|
||||
ASSERT_EQ(cview.data()[0u], entity);
|
||||
|
||||
registry.destroy(entity);
|
||||
|
||||
ASSERT_EQ(view.size(), 0u);
|
||||
ASSERT_EQ(cview.size(), 0u);
|
||||
}
|
||||
|
||||
TEST(SingleComponentView, LazyTypeFromConstRegistry) {
|
||||
entt::registry registry{};
|
||||
auto eview = std::as_const(registry).view<const empty_type>();
|
||||
auto cview = std::as_const(registry).view<const int>();
|
||||
@@ -72,34 +107,21 @@ TEST(SingleComponentView, Invalid) {
|
||||
registry.emplace<empty_type>(entity);
|
||||
registry.emplace<int>(entity);
|
||||
|
||||
ASSERT_FALSE(cview);
|
||||
ASSERT_FALSE(eview);
|
||||
ASSERT_TRUE(cview);
|
||||
ASSERT_TRUE(eview);
|
||||
|
||||
ASSERT_TRUE(cview.empty());
|
||||
ASSERT_EQ(eview.size(), 0u);
|
||||
ASSERT_NE(cview.raw(), nullptr);
|
||||
ASSERT_NE(eview.data(), nullptr);
|
||||
|
||||
ASSERT_EQ(cview.raw(), nullptr);
|
||||
ASSERT_EQ(eview.data(), nullptr);
|
||||
ASSERT_FALSE(cview.empty());
|
||||
ASSERT_EQ(eview.size(), 1u);
|
||||
ASSERT_TRUE(cview.contains(entity));
|
||||
|
||||
ASSERT_EQ(cview.begin(), cview.end());
|
||||
ASSERT_EQ(eview.rbegin(), eview.rend());
|
||||
|
||||
ASSERT_FALSE(cview.contains(entity));
|
||||
ASSERT_EQ(eview.find(entity), eview.end());
|
||||
ASSERT_EQ(cview.front(), entt::entity{entt::null});
|
||||
ASSERT_EQ(eview.back(), entt::entity{entt::null});
|
||||
|
||||
cview.each([](const auto, const auto &) { FAIL(); });
|
||||
cview.each([](const auto &) { FAIL(); });
|
||||
|
||||
eview.each([](const auto) { FAIL(); });
|
||||
eview.each([]() { FAIL(); });
|
||||
|
||||
for([[maybe_unused]] auto all: cview.each()) { FAIL(); }
|
||||
for(auto first = cview.each().rbegin(), last = cview.each().rend(); first != last; ++first) { FAIL(); }
|
||||
|
||||
for([[maybe_unused]] auto entt: eview.each()) { FAIL(); }
|
||||
for(auto first = eview.each().rbegin(), last = eview.each().rend(); first != last; ++first) { FAIL(); }
|
||||
ASSERT_NE(cview.begin(), cview.end());
|
||||
ASSERT_NE(eview.rbegin(), eview.rend());
|
||||
ASSERT_NE(eview.find(entity), eview.end());
|
||||
ASSERT_EQ(cview.front(), entity);
|
||||
ASSERT_EQ(eview.back(), entity);
|
||||
}
|
||||
|
||||
TEST(SingleComponentView, ElementAccess) {
|
||||
@@ -160,10 +182,14 @@ TEST(SingleComponentView, Each) {
|
||||
registry.emplace<int>(registry.create(), 1);
|
||||
|
||||
auto view = registry.view<int>();
|
||||
auto iterable = view.each();
|
||||
|
||||
auto cview = std::as_const(registry).view<const int>();
|
||||
auto citerable = cview.each();
|
||||
|
||||
std::size_t cnt = 0;
|
||||
|
||||
for(auto first = cview.each().rbegin(), last = cview.each().rend(); first != last; ++first) {
|
||||
for(auto first = citerable.rbegin(), last = citerable.rend(); first != last; ++first) {
|
||||
static_assert(std::is_same_v<decltype(*first), std::tuple<entt::entity, const int &>>);
|
||||
ASSERT_EQ(std::get<1>(*first), cnt++);
|
||||
}
|
||||
@@ -176,13 +202,20 @@ TEST(SingleComponentView, Each) {
|
||||
cview.each([&cnt](const int &) { --cnt; });
|
||||
cview.each([&cnt](auto, const int &) { --cnt; });
|
||||
|
||||
for(auto &&[entt, iv]: view.each()) {
|
||||
// do not use iterable, make sure an iterable view works when created from a temporary
|
||||
for(auto [entt, iv]: registry.view<int>().each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
ASSERT_EQ(iv, --cnt);
|
||||
}
|
||||
|
||||
ASSERT_EQ(cnt, std::size_t{0});
|
||||
|
||||
auto it = iterable.begin();
|
||||
auto rit = iterable.rbegin();
|
||||
|
||||
ASSERT_EQ((it++, ++it), iterable.end());
|
||||
ASSERT_EQ((rit++, ++rit), iterable.rend());
|
||||
}
|
||||
|
||||
TEST(SingleComponentView, ConstNonConstAndAllInBetween) {
|
||||
@@ -198,15 +231,17 @@ TEST(SingleComponentView, ConstNonConstAndAllInBetween) {
|
||||
ASSERT_EQ(view.size(), 1u);
|
||||
ASSERT_EQ(cview.size(), 1u);
|
||||
|
||||
static_assert(std::is_same_v<typename decltype(view)::raw_type, int>);
|
||||
static_assert(std::is_same_v<typename decltype(cview)::raw_type, const int>);
|
||||
static_assert(std::is_same_v<decltype(view.raw()), int **>);
|
||||
static_assert(std::is_same_v<decltype(cview.raw()), const int * const *>);
|
||||
|
||||
static_assert(std::is_same_v<decltype(view.get<int>({})), int &>);
|
||||
static_assert(std::is_same_v<decltype(view.get({})), std::tuple<int &>>);
|
||||
static_assert(std::is_same_v<decltype(view.raw()), int *>);
|
||||
static_assert(std::is_same_v<decltype(view.raw()), int **>);
|
||||
static_assert(std::is_same_v<decltype(cview.get<const int>({})), const int &>);
|
||||
static_assert(std::is_same_v<decltype(cview.get({})), std::tuple<const int &>>);
|
||||
static_assert(std::is_same_v<decltype(cview.raw()), const int *>);
|
||||
static_assert(std::is_same_v<decltype(cview.raw()), const int * const *>);
|
||||
|
||||
static_assert(std::is_same_v<decltype(std::as_const(registry).view<int>()), decltype(cview)>);
|
||||
|
||||
view.each([](auto &&i) {
|
||||
static_assert(std::is_same_v<decltype(i), int &>);
|
||||
@@ -216,12 +251,12 @@ TEST(SingleComponentView, ConstNonConstAndAllInBetween) {
|
||||
static_assert(std::is_same_v<decltype(i), const int &>);
|
||||
});
|
||||
|
||||
for(auto &&[entt, iv]: view.each()) {
|
||||
for(auto [entt, iv]: view.each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
}
|
||||
|
||||
for(auto &&[entt, iv]: cview.each()) {
|
||||
for(auto [entt, iv]: cview.each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), const int &>);
|
||||
}
|
||||
@@ -240,17 +275,16 @@ TEST(SingleComponentView, ConstNonConstAndAllInBetweenWithEmptyType) {
|
||||
ASSERT_EQ(view.size(), 1u);
|
||||
ASSERT_EQ(cview.size(), 1u);
|
||||
|
||||
static_assert(std::is_same_v<typename decltype(view)::raw_type, empty_type>);
|
||||
static_assert(std::is_same_v<typename decltype(cview)::raw_type, const empty_type>);
|
||||
|
||||
static_assert(std::is_same_v<decltype(view.get({})), std::tuple<>>);
|
||||
static_assert(std::is_same_v<decltype(cview.get({})), std::tuple<>>);
|
||||
|
||||
for(auto &&[entt]: view.each()) {
|
||||
static_assert(std::is_same_v<decltype(std::as_const(registry).view<empty_type>()), decltype(cview)>);
|
||||
|
||||
for(auto [entt]: view.each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
}
|
||||
|
||||
for(auto &&[entt]: cview.each()) {
|
||||
for(auto [entt]: cview.each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
}
|
||||
}
|
||||
@@ -271,7 +305,7 @@ TEST(SingleComponentView, Find) {
|
||||
const auto e3 = registry.create();
|
||||
registry.emplace<int>(e3);
|
||||
|
||||
registry.remove<int>(e1);
|
||||
registry.erase<int>(e1);
|
||||
|
||||
ASSERT_NE(view.find(e0), view.end());
|
||||
ASSERT_EQ(view.find(e1), view.end());
|
||||
@@ -315,7 +349,7 @@ TEST(SingleComponentView, EmptyTypes) {
|
||||
check = false;
|
||||
});
|
||||
|
||||
for(auto &&[entt]: registry.view<empty_type>().each()) {
|
||||
for(auto [entt]: registry.view<empty_type>().each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
ASSERT_EQ(entity, entt);
|
||||
}
|
||||
@@ -329,7 +363,7 @@ TEST(SingleComponentView, EmptyTypes) {
|
||||
check = false;
|
||||
});
|
||||
|
||||
for(auto &&[entt, iv]: registry.view<int>().each()) {
|
||||
for(auto [entt, iv]: registry.view<int>().each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
ASSERT_EQ(entity, entt);
|
||||
@@ -355,10 +389,70 @@ TEST(SingleComponentView, FrontBack) {
|
||||
|
||||
TEST(SingleComponentView, DeductionGuide) {
|
||||
entt::registry registry;
|
||||
typename entt::storage_traits<entt::entity, int>::storage_type storage;
|
||||
typename entt::storage_traits<entt::entity, int>::storage_type istorage;
|
||||
typename entt::storage_traits<entt::entity, stable_type>::storage_type sstorage;
|
||||
|
||||
static_assert(std::is_same_v<decltype(entt::basic_view{storage}), entt::basic_view<entt::entity, entt::exclude_t<>, int>>);
|
||||
static_assert(std::is_same_v<decltype(entt::basic_view{std::as_const(storage)}), entt::basic_view<entt::entity, entt::exclude_t<>, const int>>);
|
||||
static_assert(std::is_same_v<entt::basic_view<entt::entity, entt::exclude_t<>, int>, decltype(entt::basic_view{istorage})>);
|
||||
static_assert(std::is_same_v<entt::basic_view<entt::entity, entt::exclude_t<>, const int>, decltype(entt::basic_view{std::as_const(istorage)})>);
|
||||
static_assert(std::is_same_v<entt::basic_view<entt::entity, entt::exclude_t<>, stable_type>, decltype(entt::basic_view{sstorage})>);
|
||||
|
||||
static_assert(std::is_base_of_v<entt::basic_view_impl<entt::packed_storage_policy, entt::entity, entt::exclude_t<>, int>, decltype(entt::basic_view{istorage})>);
|
||||
static_assert(std::is_base_of_v<entt::basic_view_impl<entt::packed_storage_policy, entt::entity, entt::exclude_t<>, const int>, decltype(entt::basic_view{std::as_const(istorage)})>);
|
||||
static_assert(std::is_base_of_v<entt::basic_view_impl<entt::stable_storage_policy, entt::entity, entt::exclude_t<>, stable_type>, decltype(entt::basic_view{sstorage})>);
|
||||
}
|
||||
|
||||
TEST(SingleComponentView, IterableViewAlgorithmCompatibility) {
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
|
||||
registry.emplace<int>(entity);
|
||||
|
||||
const auto view = registry.view<int>();
|
||||
const auto iterable = view.each();
|
||||
const auto it = std::find_if(iterable.begin(), iterable.end(), [entity](auto args) { return std::get<0>(args) == entity; });
|
||||
|
||||
ASSERT_EQ(std::get<0>(*it), entity);
|
||||
}
|
||||
|
||||
TEST(SingleComponentView, StableType) {
|
||||
entt::registry registry;
|
||||
auto view = registry.view<stable_type>();
|
||||
|
||||
const auto entity = registry.create();
|
||||
const auto other = registry.create();
|
||||
|
||||
registry.emplace<stable_type>(entity);
|
||||
registry.emplace<stable_type>(other);
|
||||
registry.destroy(entity);
|
||||
|
||||
ASSERT_EQ(view.size_hint(), 2u);
|
||||
ASSERT_FALSE(view.contains(entity));
|
||||
ASSERT_TRUE(view.contains(other));
|
||||
|
||||
ASSERT_EQ(view.front(), other);
|
||||
ASSERT_EQ(view.back(), other);
|
||||
|
||||
ASSERT_EQ(*view.begin(), other);
|
||||
ASSERT_EQ(++view.begin(), view.end());
|
||||
|
||||
view.each([other](const auto entt, stable_type) {
|
||||
ASSERT_EQ(other, entt);
|
||||
});
|
||||
|
||||
view.each([check = true](stable_type) mutable {
|
||||
ASSERT_TRUE(check);
|
||||
check = false;
|
||||
});
|
||||
|
||||
for(auto [entt, st]: view.each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(st), stable_type &>);
|
||||
ASSERT_EQ(other, entt);
|
||||
}
|
||||
|
||||
registry.compact();
|
||||
|
||||
ASSERT_EQ(view.size_hint(), 1u);
|
||||
}
|
||||
|
||||
TEST(MultiComponentView, Functionalities) {
|
||||
@@ -378,10 +472,10 @@ TEST(MultiComponentView, Functionalities) {
|
||||
ASSERT_EQ(++view.begin(), (view.end()));
|
||||
ASSERT_EQ(++view.rbegin(), (view.rend()));
|
||||
|
||||
ASSERT_NO_THROW((view.begin()++));
|
||||
ASSERT_NO_THROW((++cview.begin()));
|
||||
ASSERT_NO_THROW(view.rbegin()++);
|
||||
ASSERT_NO_THROW(++cview.rbegin());
|
||||
ASSERT_NO_FATAL_FAILURE((view.begin()++));
|
||||
ASSERT_NO_FATAL_FAILURE((++cview.begin()));
|
||||
ASSERT_NO_FATAL_FAILURE(view.rbegin()++);
|
||||
ASSERT_NO_FATAL_FAILURE(++cview.rbegin());
|
||||
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
ASSERT_NE(cview.begin(), cview.end());
|
||||
@@ -402,7 +496,7 @@ TEST(MultiComponentView, Functionalities) {
|
||||
ASSERT_FALSE(invalid);
|
||||
}
|
||||
|
||||
TEST(MultiComponentView, Invalid) {
|
||||
TEST(MultiComponentView, LazyTypesFromConstRegistry) {
|
||||
entt::registry registry{};
|
||||
auto view = std::as_const(registry).view<const empty_type, const int>();
|
||||
|
||||
@@ -410,22 +504,34 @@ TEST(MultiComponentView, Invalid) {
|
||||
registry.emplace<empty_type>(entity);
|
||||
registry.emplace<int>(entity);
|
||||
|
||||
ASSERT_FALSE(view);
|
||||
ASSERT_TRUE(view);
|
||||
|
||||
ASSERT_EQ(view.size_hint(), 0u);
|
||||
ASSERT_EQ(view.size_hint(), 1u);
|
||||
ASSERT_TRUE(view.contains(entity));
|
||||
|
||||
ASSERT_EQ(view.begin(), view.end());
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
ASSERT_NE(view.find(entity), view.end());
|
||||
ASSERT_EQ(view.front(), entity);
|
||||
ASSERT_EQ(view.back(), entity);
|
||||
}
|
||||
|
||||
ASSERT_FALSE(view.contains(entity));
|
||||
ASSERT_EQ(view.find(entity), view.end());
|
||||
ASSERT_EQ(view.front(), entt::entity{entt::null});
|
||||
ASSERT_EQ(view.back(), entt::entity{entt::null});
|
||||
TEST(MultiComponentView, LazyExcludedTypeFromConstRegistry) {
|
||||
entt::registry registry;
|
||||
|
||||
view.each([](const auto, const auto &) { FAIL(); });
|
||||
view.each([](const auto &) { FAIL(); });
|
||||
auto entity = registry.create();
|
||||
registry.emplace<int>(entity);
|
||||
|
||||
for([[maybe_unused]] auto all: view.each()) { FAIL(); }
|
||||
for(auto first = view.each().rbegin(), last = view.each().rend(); first != last; ++first) { FAIL(); }
|
||||
auto view = std::as_const(registry).view<const int>(entt::exclude<char>);
|
||||
|
||||
ASSERT_TRUE(view);
|
||||
|
||||
ASSERT_EQ(view.size_hint(), 1u);
|
||||
ASSERT_TRUE(view.contains(entity));
|
||||
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
ASSERT_NE(view.find(entity), view.end());
|
||||
ASSERT_EQ(view.front(), entity);
|
||||
ASSERT_EQ(view.back(), entity);
|
||||
}
|
||||
|
||||
TEST(MultiComponentView, Iterator) {
|
||||
@@ -454,6 +560,21 @@ TEST(MultiComponentView, Iterator) {
|
||||
|
||||
ASSERT_EQ(*begin, entity);
|
||||
ASSERT_EQ(*begin.operator->(), entity);
|
||||
|
||||
registry.emplace<int>(registry.create());
|
||||
registry.emplace<char>(registry.create());
|
||||
|
||||
const auto other = registry.create();
|
||||
registry.emplace<int>(other);
|
||||
registry.emplace<char>(other);
|
||||
|
||||
begin = view.begin();
|
||||
|
||||
ASSERT_EQ(*(begin++), other);
|
||||
ASSERT_EQ(*(begin++), entity);
|
||||
ASSERT_EQ(begin--, end);
|
||||
ASSERT_EQ(*(begin--), entity);
|
||||
ASSERT_EQ(*begin, other);
|
||||
}
|
||||
|
||||
TEST(MultiComponentView, ReverseIterator) {
|
||||
@@ -534,10 +655,14 @@ TEST(MultiComponentView, Each) {
|
||||
registry.emplace<char>(e1);
|
||||
|
||||
auto view = registry.view<int, char>();
|
||||
auto iterable = view.each();
|
||||
|
||||
auto cview = std::as_const(registry).view<const int, const char>();
|
||||
auto citerable = cview.each();
|
||||
|
||||
std::size_t cnt = 0;
|
||||
|
||||
for(auto first = cview.each().rbegin(), last = cview.each().rend(); first != last; ++first) {
|
||||
for(auto first = citerable.rbegin(), last = citerable.rend(); first != last; ++first) {
|
||||
static_assert(std::is_same_v<decltype(*first), std::tuple<entt::entity, const int &, const char &>>);
|
||||
ASSERT_EQ(std::get<1>(*first), cnt++);
|
||||
}
|
||||
@@ -550,7 +675,8 @@ TEST(MultiComponentView, Each) {
|
||||
cview.each([&cnt](const int &, const char &) { --cnt; });
|
||||
cview.each([&cnt](auto, const int &, const char &) { --cnt; });
|
||||
|
||||
for(auto &&[entt, iv, cv]: view.each()) {
|
||||
// do not use iterable, make sure an iterable view works when created from a temporary
|
||||
for(auto [entt, iv, cv]: registry.view<int, char>().each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
static_assert(std::is_same_v<decltype(cv), char &>);
|
||||
@@ -558,6 +684,12 @@ TEST(MultiComponentView, Each) {
|
||||
}
|
||||
|
||||
ASSERT_EQ(cnt, std::size_t{0});
|
||||
|
||||
auto it = iterable.begin();
|
||||
auto rit = iterable.rbegin();
|
||||
|
||||
ASSERT_EQ((it++, ++it), iterable.end());
|
||||
ASSERT_EQ((rit++, ++rit), iterable.rend());
|
||||
}
|
||||
|
||||
TEST(MultiComponentView, EachWithSuggestedType) {
|
||||
@@ -652,12 +784,16 @@ TEST(MultiComponentView, ConstNonConstAndAllInBetween) {
|
||||
static_assert(std::is_same_v<decltype(view.get<int, const char>({})), std::tuple<int &, const char &>>);
|
||||
static_assert(std::is_same_v<decltype(view.get({})), std::tuple<int &, const char &>>);
|
||||
|
||||
static_assert(std::is_same_v<decltype(std::as_const(registry).view<char, int>()), decltype(std::as_const(registry).view<const char, const int>())>);
|
||||
static_assert(std::is_same_v<decltype(std::as_const(registry).view<char, const int>()), decltype(std::as_const(registry).view<const char, const int>())>);
|
||||
static_assert(std::is_same_v<decltype(std::as_const(registry).view<const char, int>()), decltype(std::as_const(registry).view<const char, const int>())>);
|
||||
|
||||
view.each([](auto &&i, auto &&c) {
|
||||
static_assert(std::is_same_v<decltype(i), int &>);
|
||||
static_assert(std::is_same_v<decltype(c), const char &>);
|
||||
});
|
||||
|
||||
for(auto &&[entt, iv, cv]: view.each()) {
|
||||
for(auto [entt, iv, cv]: view.each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
static_assert(std::is_same_v<decltype(cv), const char &>);
|
||||
@@ -684,7 +820,7 @@ TEST(MultiComponentView, Find) {
|
||||
registry.emplace<int>(e3);
|
||||
registry.emplace<char>(e3);
|
||||
|
||||
registry.remove<int>(e1);
|
||||
registry.erase<int>(e1);
|
||||
|
||||
ASSERT_NE(view.find(e0), view.end());
|
||||
ASSERT_EQ(view.find(e1), view.end());
|
||||
@@ -740,8 +876,8 @@ TEST(MultiComponentView, ExcludedComponents) {
|
||||
|
||||
registry.emplace<char>(e0);
|
||||
registry.emplace<char>(e2);
|
||||
registry.remove<char>(e1);
|
||||
registry.remove<char>(e3);
|
||||
registry.erase<char>(e1);
|
||||
registry.erase<char>(e3);
|
||||
|
||||
for(const auto entity: view) {
|
||||
ASSERT_TRUE(entity == e1 || entity == e3);
|
||||
@@ -760,81 +896,74 @@ TEST(MultiComponentView, EmptyTypes) {
|
||||
const auto entity = registry.create();
|
||||
registry.emplace<int>(entity);
|
||||
registry.emplace<char>(entity);
|
||||
registry.emplace<double>(entity);
|
||||
registry.emplace<empty_type>(entity);
|
||||
|
||||
const auto other = registry.create();
|
||||
registry.emplace<int>(other);
|
||||
registry.emplace<char>(other);
|
||||
registry.emplace<double>(other);
|
||||
registry.emplace<empty_type>(other);
|
||||
|
||||
registry.view<int, char, empty_type>().each([entity](const auto entt, int, char) {
|
||||
const auto ignored = registry.create();
|
||||
registry.emplace<int>(ignored);
|
||||
registry.emplace<char>(ignored);
|
||||
|
||||
registry.view<int, char, empty_type>(entt::exclude<double>).each([entity](const auto entt, int, char) {
|
||||
ASSERT_EQ(entity, entt);
|
||||
});
|
||||
|
||||
for(auto &&[entt, iv, cv]: registry.view<int, char, empty_type>().each()) {
|
||||
for(auto [entt, iv, cv]: registry.view<int, char, empty_type>(entt::exclude<double>).each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
static_assert(std::is_same_v<decltype(cv), char &>);
|
||||
ASSERT_EQ(entity, entt);
|
||||
}
|
||||
|
||||
registry.view<int, empty_type, char>().each([check = true](int, char) mutable {
|
||||
registry.view<int, empty_type, char>(entt::exclude<double>).each([check = true](int, char) mutable {
|
||||
ASSERT_TRUE(check);
|
||||
check = false;
|
||||
});
|
||||
|
||||
for(auto &&[entt, iv, cv]: registry.view<int, empty_type, char>().each()) {
|
||||
for(auto [entt, iv, cv]: registry.view<int, empty_type, char>(entt::exclude<double>).each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
static_assert(std::is_same_v<decltype(cv), char &>);
|
||||
ASSERT_EQ(entity, entt);
|
||||
}
|
||||
|
||||
registry.view<empty_type, int, char>().each([entity](const auto entt, int, char) {
|
||||
registry.view<empty_type, int, char>(entt::exclude<double>).each([entity](const auto entt, int, char) {
|
||||
ASSERT_EQ(entity, entt);
|
||||
});
|
||||
|
||||
for(auto &&[entt, iv, cv]: registry.view<empty_type, int, char>().each()) {
|
||||
for(auto [entt, iv, cv]: registry.view<empty_type, int, char>(entt::exclude<double>).each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
static_assert(std::is_same_v<decltype(cv), char &>);
|
||||
ASSERT_EQ(entity, entt);
|
||||
}
|
||||
|
||||
registry.view<empty_type, int, char>().each<empty_type>([entity](const auto entt, int, char) {
|
||||
registry.view<empty_type, int, char>(entt::exclude<double>).each<empty_type>([entity](const auto entt, int, char) {
|
||||
ASSERT_EQ(entity, entt);
|
||||
});
|
||||
|
||||
for(auto &&[entt, iv, cv]: registry.view<empty_type, int, char>().each<empty_type>()) {
|
||||
for(auto [entt, iv, cv]: registry.view<empty_type, int, char>(entt::exclude<double>).each<empty_type>()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
static_assert(std::is_same_v<decltype(cv), char &>);
|
||||
ASSERT_EQ(entity, entt);
|
||||
}
|
||||
|
||||
registry.view<int, empty_type, char>().each<empty_type>([check = true](int, char) mutable {
|
||||
registry.view<int, empty_type, char>(entt::exclude<double>).each<empty_type>([check = true](int, char) mutable {
|
||||
ASSERT_TRUE(check);
|
||||
check = false;
|
||||
});
|
||||
|
||||
for(auto &&[entt, iv, cv]: registry.view<int, empty_type, char>().each<empty_type>()) {
|
||||
for(auto [entt, iv, cv]: registry.view<int, empty_type, char>(entt::exclude<double>).each<empty_type>()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
static_assert(std::is_same_v<decltype(cv), char &>);
|
||||
ASSERT_EQ(entity, entt);
|
||||
}
|
||||
|
||||
registry.view<int, char, double>().each([entity](const auto entt, int, char, double) {
|
||||
ASSERT_EQ(entity, entt);
|
||||
});
|
||||
|
||||
for(auto &&[entt, iv, cv, dv]: registry.view<int, char, double>().each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
static_assert(std::is_same_v<decltype(cv), char &>);
|
||||
static_assert(std::is_same_v<decltype(dv), double &>);
|
||||
ASSERT_EQ(entity, entt);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(MultiComponentView, FrontBack) {
|
||||
@@ -870,9 +999,118 @@ TEST(MultiComponentView, DeductionGuide) {
|
||||
entt::registry registry;
|
||||
typename entt::storage_traits<entt::entity, int>::storage_type istorage;
|
||||
typename entt::storage_traits<entt::entity, double>::storage_type dstorage;
|
||||
typename entt::storage_traits<entt::entity, stable_type>::storage_type sstorage;
|
||||
|
||||
static_assert(std::is_same_v<decltype(entt::basic_view{istorage, dstorage}), entt::basic_view<entt::entity, entt::exclude_t<>, int, double>>);
|
||||
static_assert(std::is_same_v<decltype(entt::basic_view{std::as_const(istorage), dstorage}), entt::basic_view<entt::entity, entt::exclude_t<>, const int, double>>);
|
||||
static_assert(std::is_same_v<decltype(entt::basic_view{istorage, std::as_const(dstorage)}), entt::basic_view<entt::entity, entt::exclude_t<>, int, const double>>);
|
||||
static_assert(std::is_same_v<decltype(entt::basic_view{std::as_const(istorage), std::as_const(dstorage)}), entt::basic_view<entt::entity, entt::exclude_t<>, const int, const double>>);
|
||||
static_assert(std::is_same_v<entt::basic_view<entt::entity, entt::exclude_t<>, int, double>, decltype(entt::basic_view{istorage, dstorage})>);
|
||||
static_assert(std::is_same_v<entt::basic_view<entt::entity, entt::exclude_t<>, const int, double>, decltype(entt::basic_view{std::as_const(istorage), dstorage})>);
|
||||
static_assert(std::is_same_v<entt::basic_view<entt::entity, entt::exclude_t<>, int, const double>, decltype(entt::basic_view{istorage, std::as_const(dstorage)})>);
|
||||
static_assert(std::is_same_v<entt::basic_view<entt::entity, entt::exclude_t<>, const int, const double>, decltype(entt::basic_view{std::as_const(istorage), std::as_const(dstorage)})>);
|
||||
static_assert(std::is_same_v<entt::basic_view<entt::entity, entt::exclude_t<>, int, stable_type>, decltype(entt::basic_view{istorage, sstorage})>);
|
||||
|
||||
static_assert(std::is_base_of_v<entt::basic_view_impl<entt::packed_storage_policy, entt::entity, entt::exclude_t<>, int, double>, decltype(entt::basic_view{istorage, dstorage})>);
|
||||
static_assert(std::is_base_of_v<entt::basic_view_impl<entt::packed_storage_policy, entt::entity, entt::exclude_t<>, const int, double>, decltype(entt::basic_view{std::as_const(istorage), dstorage})>);
|
||||
static_assert(std::is_base_of_v<entt::basic_view_impl<entt::packed_storage_policy, entt::entity, entt::exclude_t<>, int, const double>, decltype(entt::basic_view{istorage, std::as_const(dstorage)})>);
|
||||
static_assert(std::is_base_of_v<entt::basic_view_impl<entt::packed_storage_policy, entt::entity, entt::exclude_t<>, const int, const double>, decltype(entt::basic_view{std::as_const(istorage), std::as_const(dstorage)})>);
|
||||
static_assert(std::is_base_of_v<entt::basic_view_impl<entt::stable_storage_policy, entt::entity, entt::exclude_t<>, int, stable_type>, decltype(entt::basic_view{istorage, sstorage})>);
|
||||
}
|
||||
|
||||
TEST(MultiComponentView, IterableViewAlgorithmCompatibility) {
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
|
||||
registry.emplace<int>(entity);
|
||||
registry.emplace<char>(entity);
|
||||
|
||||
const auto view = registry.view<int, char>();
|
||||
const auto iterable = view.each();
|
||||
const auto it = std::find_if(iterable.begin(), iterable.end(), [entity](auto args) { return std::get<0>(args) == entity; });
|
||||
|
||||
ASSERT_EQ(std::get<0>(*it), entity);
|
||||
}
|
||||
|
||||
TEST(MultiComponentView, StableType) {
|
||||
entt::registry registry;
|
||||
auto view = registry.view<int, stable_type>();
|
||||
|
||||
const auto entity = registry.create();
|
||||
const auto other = registry.create();
|
||||
|
||||
registry.emplace<int>(entity);
|
||||
registry.emplace<int>(other);
|
||||
registry.emplace<stable_type>(entity);
|
||||
registry.emplace<stable_type>(other);
|
||||
registry.destroy(entity);
|
||||
|
||||
ASSERT_EQ(view.size_hint(), 1u);
|
||||
|
||||
view.use<stable_type>();
|
||||
|
||||
ASSERT_EQ(view.size_hint(), 2u);
|
||||
ASSERT_FALSE(view.contains(entity));
|
||||
ASSERT_TRUE(view.contains(other));
|
||||
|
||||
ASSERT_EQ(view.front(), other);
|
||||
ASSERT_EQ(view.back(), other);
|
||||
|
||||
ASSERT_EQ(*view.begin(), other);
|
||||
ASSERT_EQ(++view.begin(), view.end());
|
||||
|
||||
view.each([other](const auto entt, int, stable_type) {
|
||||
ASSERT_EQ(other, entt);
|
||||
});
|
||||
|
||||
view.each([check = true](int, stable_type) mutable {
|
||||
ASSERT_TRUE(check);
|
||||
check = false;
|
||||
});
|
||||
|
||||
for(auto [entt, iv, st]: view.each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
static_assert(std::is_same_v<decltype(st), stable_type &>);
|
||||
ASSERT_EQ(other, entt);
|
||||
}
|
||||
|
||||
registry.compact();
|
||||
|
||||
ASSERT_EQ(view.size_hint(), 1u);
|
||||
}
|
||||
|
||||
TEST(View, Pipe) {
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
const auto other = registry.create();
|
||||
|
||||
registry.emplace<int>(entity);
|
||||
registry.emplace<char>(entity);
|
||||
registry.emplace<double>(entity);
|
||||
registry.emplace<empty_type>(entity);
|
||||
|
||||
registry.emplace<int>(other);
|
||||
registry.emplace<char>(other);
|
||||
registry.emplace<stable_type>(other);
|
||||
|
||||
const auto view1 = registry.view<int>(entt::exclude<double>);
|
||||
const auto view2 = registry.view<const char>(entt::exclude<float>);
|
||||
const auto view3 = registry.view<empty_type>();
|
||||
const auto view4 = registry.view<stable_type>();
|
||||
|
||||
static_assert(std::is_same_v<entt::basic_view<entt::entity, entt::exclude_t<double, float>, int, const char>, decltype(view1 | view2)>);
|
||||
static_assert(std::is_same_v<entt::basic_view<entt::entity, entt::exclude_t<float, double>, const char, int>, decltype(view2 | view1)>);
|
||||
static_assert(std::is_same_v<decltype((view1 | view2) | view3), decltype(view1 | (view2 | view3))>);
|
||||
|
||||
static_assert(std::is_base_of_v<entt::basic_view_impl<entt::packed_storage_policy, entt::entity, entt::exclude_t<double, float>, int, const char>, decltype(view1 | view2)>);
|
||||
static_assert(std::is_base_of_v<entt::basic_view_impl<entt::stable_storage_policy, entt::entity, entt::exclude_t<double, float>, int, stable_type, const char>, decltype(view1 | view4 | view2)>);
|
||||
|
||||
ASSERT_FALSE((view1 | view2).contains(entity));
|
||||
ASSERT_TRUE((view1 | view2).contains(other));
|
||||
|
||||
ASSERT_TRUE((view2 | view3).contains(entity));
|
||||
ASSERT_FALSE((view2 | view3).contains(other));
|
||||
|
||||
ASSERT_FALSE((view1 | view2 | view3).contains(entity));
|
||||
ASSERT_FALSE((view1 | view2 | view3).contains(other));
|
||||
|
||||
ASSERT_FALSE((view1 | view4 | view2).contains(entity));
|
||||
ASSERT_TRUE((view1 | view4 | view2).contains(other));
|
||||
}
|
||||
|
||||
@@ -1,420 +0,0 @@
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <type_traits>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/registry.hpp>
|
||||
#include <entt/entity/view.hpp>
|
||||
#include <entt/entity/view_pack.hpp>
|
||||
|
||||
struct empty_type {};
|
||||
|
||||
TEST(ViewPack, Construction) {
|
||||
entt::registry registry;
|
||||
|
||||
const auto view1 = registry.view<int, const char>();
|
||||
const auto view2 = registry.view<empty_type>();
|
||||
const auto view3 = registry.view<double>();
|
||||
|
||||
static_assert(std::is_same_v<decltype(entt::view_pack{view1}), entt::view_pack<entt::view<entt::exclude_t<>, int, const char>>>);
|
||||
static_assert(std::is_same_v<decltype(entt::view_pack{view1, view2, view3}), decltype(view1 | view2 | view3)>);
|
||||
|
||||
static_assert(std::is_same_v<
|
||||
decltype(entt::view_pack{view1, view2, view3}),
|
||||
entt::view_pack<
|
||||
entt::view<entt::exclude_t<>, int, const char>,
|
||||
entt::view<entt::exclude_t<>, empty_type>,
|
||||
entt::view<entt::exclude_t<>, double>
|
||||
>
|
||||
>);
|
||||
|
||||
static_assert(std::is_same_v<decltype(entt::view_pack{view1, view2, view3}), decltype(entt::view_pack{view1} | view2 | view3)>);
|
||||
static_assert(std::is_same_v<decltype(entt::view_pack{view1, view2, view3}), decltype(view1 | entt::view_pack{view2} | view3)>);
|
||||
static_assert(std::is_same_v<decltype(entt::view_pack{view1, view2, view3}), decltype(view1 | view2 | entt::view_pack{view3})>);
|
||||
}
|
||||
|
||||
TEST(ViewPack, Functionalities) {
|
||||
entt::registry registry;
|
||||
const auto pack = registry.view<int>() | registry.view<char>();
|
||||
const auto cpack = registry.view<const int>() | registry.view<const char>();
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.emplace<char>(e0, '1');
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.emplace<int>(e1, 42);
|
||||
registry.emplace<char>(e1, '2');
|
||||
|
||||
ASSERT_EQ(*pack.begin(), e1);
|
||||
ASSERT_EQ(*pack.rbegin(), e1);
|
||||
ASSERT_EQ(++pack.begin(), (pack.end()));
|
||||
ASSERT_EQ(++pack.rbegin(), (pack.rend()));
|
||||
|
||||
ASSERT_NO_THROW((pack.begin()++));
|
||||
ASSERT_NO_THROW((++cpack.begin()));
|
||||
ASSERT_NO_THROW(pack.rbegin()++);
|
||||
ASSERT_NO_THROW(++cpack.rbegin());
|
||||
|
||||
ASSERT_NE(pack.begin(), pack.end());
|
||||
ASSERT_NE(cpack.begin(), cpack.end());
|
||||
ASSERT_NE(pack.rbegin(), pack.rend());
|
||||
ASSERT_NE(cpack.rbegin(), cpack.rend());
|
||||
|
||||
for(auto entity: pack) {
|
||||
ASSERT_EQ(std::get<0>(cpack.get<const int, const char>(entity)), 42);
|
||||
ASSERT_EQ(std::get<1>(pack.get<int, char>(entity)), '2');
|
||||
ASSERT_EQ(cpack.get<const char>(entity), '2');
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ViewPack, Iterator) {
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
registry.emplace<int>(entity);
|
||||
registry.emplace<char>(entity);
|
||||
|
||||
const auto pack = registry.view<int>() | registry.view<char>();
|
||||
|
||||
ASSERT_NE(pack.begin(), pack.end());
|
||||
ASSERT_EQ(pack.begin()++, pack.begin());
|
||||
ASSERT_EQ(++pack.begin(), pack.end());
|
||||
ASSERT_EQ(*pack.begin(), entity);
|
||||
}
|
||||
|
||||
TEST(ViewPack, ReverseIterator) {
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
registry.emplace<int>(entity);
|
||||
registry.emplace<char>(entity);
|
||||
|
||||
const auto pack = registry.view<int>() | registry.view<char>();
|
||||
|
||||
ASSERT_NE(pack.rbegin(), pack.rend());
|
||||
ASSERT_EQ(pack.rbegin()++, pack.rbegin());
|
||||
ASSERT_EQ(++pack.rbegin(), pack.rend());
|
||||
ASSERT_EQ(*pack.rbegin(), entity);
|
||||
}
|
||||
|
||||
TEST(ViewPack, Contains) {
|
||||
entt::registry registry;
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.emplace<int>(e0);
|
||||
registry.emplace<char>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.emplace<int>(e1);
|
||||
registry.emplace<char>(e1);
|
||||
|
||||
registry.destroy(e0);
|
||||
|
||||
const auto pack = registry.view<int>() | registry.view<char>();
|
||||
|
||||
ASSERT_FALSE(pack.contains(e0));
|
||||
ASSERT_TRUE(pack.contains(e1));
|
||||
}
|
||||
|
||||
TEST(ViewPack, Each) {
|
||||
entt::registry registry;
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.emplace<int>(e0, 0);
|
||||
registry.emplace<char>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.emplace<int>(e1, 1);
|
||||
registry.emplace<char>(e1);
|
||||
|
||||
auto pack = registry.view<int>() | registry.view<char>();
|
||||
auto cpack = registry.view<const int>() | registry.view<const char>();
|
||||
std::size_t cnt = 0;
|
||||
|
||||
for(auto first = cpack.each().rbegin(), last = cpack.each().rend(); first != last; ++first) {
|
||||
static_assert(std::is_same_v<decltype(*first), std::tuple<entt::entity, const int &, const char &>>);
|
||||
ASSERT_EQ(std::get<1>(*first), cnt++);
|
||||
}
|
||||
|
||||
pack.each([&cnt](auto, int &, char &) { ++cnt; });
|
||||
pack.each([&cnt](int &, char &) { ++cnt; });
|
||||
|
||||
ASSERT_EQ(cnt, std::size_t{6});
|
||||
|
||||
cpack.each([&cnt](const int &, const char &) { --cnt; });
|
||||
cpack.each([&cnt](auto, const int &, const char &) { --cnt; });
|
||||
|
||||
for(auto &&[entt, iv, cv]: pack.each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
static_assert(std::is_same_v<decltype(cv), char &>);
|
||||
ASSERT_EQ(iv, --cnt);
|
||||
}
|
||||
|
||||
ASSERT_EQ(cnt, std::size_t{0});
|
||||
}
|
||||
|
||||
TEST(ViewPack, EachWithHoles) {
|
||||
entt::registry registry;
|
||||
|
||||
const auto e0 = registry.create();
|
||||
const auto e1 = registry.create();
|
||||
const auto e2 = registry.create();
|
||||
|
||||
registry.emplace<char>(e0, '0');
|
||||
registry.emplace<char>(e1, '1');
|
||||
|
||||
registry.emplace<int>(e0, 0);
|
||||
registry.emplace<int>(e2, 2);
|
||||
|
||||
auto pack = registry.view<char>() | registry.view<int>();
|
||||
|
||||
pack.each([e0](auto entity, const char &c, const int &i) {
|
||||
ASSERT_EQ(entity, e0);
|
||||
ASSERT_EQ(c, '0');
|
||||
ASSERT_EQ(i, 0);
|
||||
});
|
||||
|
||||
for(auto &&curr: pack.each()) {
|
||||
ASSERT_EQ(std::get<0>(curr), e0);
|
||||
ASSERT_EQ(std::get<1>(curr), '0');
|
||||
ASSERT_EQ(std::get<2>(curr), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ViewPack, ConstNonConstAndAllInBetween) {
|
||||
entt::registry registry;
|
||||
auto pack = registry.view<int, empty_type>() | registry.view<const char>();
|
||||
|
||||
const auto entity = registry.create();
|
||||
registry.emplace<int>(entity, 0);
|
||||
registry.emplace<empty_type>(entity);
|
||||
registry.emplace<char>(entity, 'c');
|
||||
|
||||
static_assert(std::is_same_v<decltype(pack.get<int>({})), int &>);
|
||||
static_assert(std::is_same_v<decltype(pack.get<const char>({})), const char &>);
|
||||
static_assert(std::is_same_v<decltype(pack.get<int, const char>({})), std::tuple<int &, const char &>>);
|
||||
static_assert(std::is_same_v<decltype(pack.get({})), std::tuple<int &, const char &>>);
|
||||
|
||||
pack.each([](auto &&i, auto &&c) {
|
||||
static_assert(std::is_same_v<decltype(i), int &>);
|
||||
static_assert(std::is_same_v<decltype(c), const char &>);
|
||||
});
|
||||
|
||||
for(auto &&[entt, iv, cv]: pack.each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
static_assert(std::is_same_v<decltype(cv), const char &>);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ViewPack, Find) {
|
||||
entt::registry registry;
|
||||
auto pack = registry.view<int>() | registry.view<const char>();
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.emplace<int>(e0);
|
||||
registry.emplace<char>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.emplace<int>(e1);
|
||||
registry.emplace<char>(e1);
|
||||
|
||||
const auto e2 = registry.create();
|
||||
registry.emplace<int>(e2);
|
||||
registry.emplace<char>(e2);
|
||||
|
||||
const auto e3 = registry.create();
|
||||
registry.emplace<int>(e3);
|
||||
registry.emplace<char>(e3);
|
||||
|
||||
registry.remove<int>(e1);
|
||||
|
||||
ASSERT_NE(pack.find(e0), pack.end());
|
||||
ASSERT_EQ(pack.find(e1), pack.end());
|
||||
ASSERT_NE(pack.find(e2), pack.end());
|
||||
ASSERT_NE(pack.find(e3), pack.end());
|
||||
|
||||
auto it = pack.find(e2);
|
||||
|
||||
ASSERT_EQ(*it, e2);
|
||||
ASSERT_EQ(*(++it), e3);
|
||||
ASSERT_EQ(*(++it), e0);
|
||||
ASSERT_EQ(++it, pack.end());
|
||||
ASSERT_EQ(++pack.find(e0), pack.end());
|
||||
|
||||
const auto e4 = registry.create();
|
||||
registry.destroy(e4);
|
||||
const auto e5 = registry.create();
|
||||
registry.emplace<int>(e5);
|
||||
registry.emplace<char>(e5);
|
||||
|
||||
ASSERT_NE(pack.find(e5), pack.end());
|
||||
ASSERT_EQ(pack.find(e4), pack.end());
|
||||
}
|
||||
|
||||
TEST(ViewPack, FrontBack) {
|
||||
entt::registry registry;
|
||||
auto pack = registry.view<const int>() | registry.view<const char>();
|
||||
|
||||
ASSERT_EQ(pack.front(), static_cast<entt::entity>(entt::null));
|
||||
ASSERT_EQ(pack.back(), static_cast<entt::entity>(entt::null));
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.emplace<int>(e0);
|
||||
registry.emplace<char>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.emplace<int>(e1);
|
||||
registry.emplace<char>(e1);
|
||||
|
||||
const auto entity = registry.create();
|
||||
registry.emplace<char>(entity);
|
||||
|
||||
ASSERT_EQ(pack.front(), e1);
|
||||
ASSERT_EQ(pack.back(), e0);
|
||||
}
|
||||
|
||||
TEST(ViewPack, ShortestPool) {
|
||||
entt::registry registry;
|
||||
entt::entity entities[4];
|
||||
|
||||
registry.create(std::begin(entities), std::end(entities));
|
||||
|
||||
registry.insert<int>(std::begin(entities), std::end(entities));
|
||||
registry.insert<empty_type>(std::begin(entities), std::end(entities));
|
||||
registry.insert<char>(std::rbegin(entities) + 1u, std::rend(entities) - 1u);
|
||||
|
||||
const auto tmp = registry.view<char>() | registry.view<empty_type>();
|
||||
const auto pack = tmp | registry.view<const int>();
|
||||
|
||||
{
|
||||
std::size_t next{};
|
||||
|
||||
for(const auto entt: pack) {
|
||||
ASSERT_EQ(entt::to_integral(entt), ++next);
|
||||
ASSERT_TRUE((registry.has<int, char>(entt)));
|
||||
}
|
||||
}
|
||||
|
||||
auto it = pack.begin();
|
||||
|
||||
ASSERT_EQ(*it++, entities[1u]);
|
||||
ASSERT_EQ(++it, pack.end());
|
||||
ASSERT_TRUE(it == pack.end());
|
||||
ASSERT_FALSE(it != pack.end());
|
||||
|
||||
pack.each([®istry, next = 0u](const auto entt, auto &&cv, auto &&iv) mutable {
|
||||
static_assert(std::is_same_v<decltype(entt), const entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(cv), char &>);
|
||||
static_assert(std::is_same_v<decltype(iv), const int &>);
|
||||
ASSERT_EQ(entt::to_integral(entt), ++next);
|
||||
ASSERT_EQ(&cv, registry.try_get<char>(entt));
|
||||
ASSERT_EQ(&iv, registry.try_get<int>(entt));
|
||||
});
|
||||
|
||||
pack.each([](auto &&cv, auto &&iv) {
|
||||
static_assert(std::is_same_v<decltype(cv), char &>);
|
||||
static_assert(std::is_same_v<decltype(iv), const int &>);
|
||||
});
|
||||
|
||||
auto eit = pack.each().begin();
|
||||
|
||||
ASSERT_EQ(std::get<0>(*eit++), entities[1u]);
|
||||
static_assert(std::is_same_v<decltype(std::get<1>(*eit)), char &>);
|
||||
static_assert(std::is_same_v<decltype(std::get<2>(*eit)), const int &>);
|
||||
ASSERT_EQ(++eit, pack.each().end());
|
||||
ASSERT_TRUE(eit == pack.each().end());
|
||||
ASSERT_FALSE(eit != pack.each().end());
|
||||
|
||||
{
|
||||
std::size_t next{};
|
||||
|
||||
for(const auto [entt, cv, iv]: pack.each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), const entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(cv), char &>);
|
||||
static_assert(std::is_same_v<decltype(iv), const int &>);
|
||||
ASSERT_EQ(entt::to_integral(entt), ++next);
|
||||
ASSERT_EQ(&cv, registry.try_get<char>(entt));
|
||||
ASSERT_EQ(&iv, registry.try_get<int>(entt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ViewPack, LongestPool) {
|
||||
entt::registry registry;
|
||||
entt::entity entities[4];
|
||||
|
||||
registry.create(std::begin(entities), std::end(entities));
|
||||
|
||||
registry.insert<int>(std::begin(entities), std::end(entities));
|
||||
registry.insert<empty_type>(std::begin(entities), std::end(entities));
|
||||
registry.insert<char>(std::rbegin(entities) + 1u, std::rend(entities) - 1u);
|
||||
|
||||
const auto pack = registry.view<int>() | registry.view<empty_type>() | registry.view<const char>();
|
||||
|
||||
{
|
||||
std::size_t next{2u};
|
||||
|
||||
for(const auto entt: pack) {
|
||||
ASSERT_EQ(entt::to_integral(entt), next--);
|
||||
ASSERT_TRUE((registry.has<int, char>(entt)));
|
||||
}
|
||||
}
|
||||
|
||||
auto it = pack.begin();
|
||||
|
||||
ASSERT_EQ(*it++, entities[2u]);
|
||||
ASSERT_EQ(++it, pack.end());
|
||||
ASSERT_TRUE(it == pack.end());
|
||||
ASSERT_FALSE(it != pack.end());
|
||||
|
||||
pack.each([®istry, next = 2u](const auto entt, auto &&iv, auto &&cv) mutable {
|
||||
static_assert(std::is_same_v<decltype(entt), const entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
static_assert(std::is_same_v<decltype(cv), const char &>);
|
||||
ASSERT_EQ(entt::to_integral(entt), next--);
|
||||
ASSERT_EQ(&iv, registry.try_get<int>(entt));
|
||||
ASSERT_EQ(&cv, registry.try_get<char>(entt));
|
||||
});
|
||||
|
||||
pack.each([](auto &&iv, auto &&cv) {
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
static_assert(std::is_same_v<decltype(cv), const char &>);
|
||||
});
|
||||
|
||||
auto eit = pack.each().begin();
|
||||
|
||||
ASSERT_EQ(std::get<0>(*eit++), entities[2u]);
|
||||
static_assert(std::is_same_v<decltype(std::get<1>(*eit)), int &>);
|
||||
static_assert(std::is_same_v<decltype(std::get<2>(*eit)), const char &>);
|
||||
ASSERT_EQ(++eit, pack.each().end());
|
||||
ASSERT_TRUE(eit == pack.each().end());
|
||||
ASSERT_FALSE(eit != pack.each().end());
|
||||
|
||||
{
|
||||
std::size_t next{2u};
|
||||
|
||||
for(const auto [entt, iv, cv]: pack.each()) {
|
||||
static_assert(std::is_same_v<decltype(entt), const entt::entity>);
|
||||
static_assert(std::is_same_v<decltype(iv), int &>);
|
||||
static_assert(std::is_same_v<decltype(cv), const char &>);
|
||||
ASSERT_EQ(entt::to_integral(entt), next--);
|
||||
ASSERT_EQ(&iv, registry.try_get<int>(entt));
|
||||
ASSERT_EQ(&cv, registry.try_get<char>(entt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ViewPack, RepeatedType) {
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
|
||||
registry.emplace<int>(entity, 3);
|
||||
|
||||
const auto view = registry.view<int>();
|
||||
const auto pack = view | view;
|
||||
|
||||
for(auto [entt, i1, i2]: pack.each()) {
|
||||
ASSERT_EQ(entt, entity);
|
||||
ASSERT_EQ(i1, 3);
|
||||
ASSERT_EQ(i1, i2);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <algorithm>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/hashed_string.hpp>
|
||||
#include <entt/meta/factory.hpp>
|
||||
@@ -5,36 +6,42 @@
|
||||
#include <entt/meta/resolve.hpp>
|
||||
|
||||
struct clazz_t {
|
||||
clazz_t(): value{0} {}
|
||||
|
||||
void member(int i) { value = i; }
|
||||
static void func() { c = 'd'; }
|
||||
|
||||
static inline char c = 'c';
|
||||
int value = 0;
|
||||
int value;
|
||||
};
|
||||
|
||||
struct empty_t {
|
||||
virtual ~empty_t() = default;
|
||||
static void destroy(empty_t &) {
|
||||
++counter;
|
||||
virtual ~empty_t() {
|
||||
++destructor_counter;
|
||||
}
|
||||
|
||||
inline static int counter = 0;
|
||||
static void destroy(empty_t &) {
|
||||
++destroy_counter;
|
||||
}
|
||||
|
||||
inline static int destroy_counter = 0;
|
||||
inline static int destructor_counter = 0;
|
||||
};
|
||||
|
||||
struct fat_t: empty_t {
|
||||
fat_t() = default;
|
||||
fat_t()
|
||||
: value{.0, .0, .0, .0}
|
||||
{}
|
||||
|
||||
fat_t(int *value)
|
||||
: foo{value}, bar{value}
|
||||
fat_t(double v1, double v2, double v3, double v4)
|
||||
: value{v1, v2, v3, v4}
|
||||
{}
|
||||
|
||||
bool operator==(const fat_t &other) const {
|
||||
return foo == other.foo && bar == other.bar;
|
||||
return std::equal(std::begin(value), std::end(value), std::begin(other.value), std::end(other.value));
|
||||
}
|
||||
|
||||
int *foo{nullptr};
|
||||
int *bar{nullptr};
|
||||
double gnam[4];
|
||||
double value[4];
|
||||
};
|
||||
|
||||
struct not_comparable_t {
|
||||
@@ -50,22 +57,36 @@ struct unmanageable_t {
|
||||
};
|
||||
|
||||
struct MetaAny: ::testing::Test {
|
||||
static void SetUpTestCase() {
|
||||
void SetUp() override {
|
||||
using namespace entt::literals;
|
||||
|
||||
entt::meta<double>().conv<int>();
|
||||
entt::meta<empty_t>().dtor<&empty_t::destroy>();
|
||||
entt::meta<fat_t>().base<empty_t>().dtor<&fat_t::destroy>();
|
||||
entt::meta<double>()
|
||||
.type("double"_hs)
|
||||
.conv<int>();
|
||||
|
||||
entt::meta<empty_t>()
|
||||
.type("empty"_hs)
|
||||
.dtor<&empty_t::destroy>();
|
||||
|
||||
entt::meta<fat_t>()
|
||||
.type("fat"_hs)
|
||||
.base<empty_t>()
|
||||
.dtor<&fat_t::destroy>();
|
||||
|
||||
entt::meta<clazz_t>()
|
||||
.type("clazz"_hs)
|
||||
.data<&clazz_t::value>("value"_hs)
|
||||
.func<&clazz_t::member>("member"_hs)
|
||||
.func<&clazz_t::func>("func"_hs);
|
||||
|
||||
empty_t::destroy_counter = 0;
|
||||
empty_t::destructor_counter = 0;
|
||||
}
|
||||
|
||||
void SetUp() override {
|
||||
empty_t::counter = 0;
|
||||
void TearDown() override {
|
||||
for(auto type: entt::resolve()) {
|
||||
type.reset();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -81,8 +102,7 @@ TEST_F(MetaAny, SBO) {
|
||||
}
|
||||
|
||||
TEST_F(MetaAny, NoSBO) {
|
||||
int value = 42;
|
||||
fat_t instance{&value};
|
||||
fat_t instance{.1, .2, .3, .4};
|
||||
entt::meta_any any{instance};
|
||||
|
||||
ASSERT_TRUE(any);
|
||||
@@ -102,6 +122,14 @@ TEST_F(MetaAny, Empty) {
|
||||
ASSERT_EQ(any.data(), nullptr);
|
||||
ASSERT_EQ(any, entt::meta_any{});
|
||||
ASSERT_NE(entt::meta_any{'c'}, any);
|
||||
|
||||
ASSERT_FALSE(any.as_ref());
|
||||
ASSERT_FALSE(any.as_sequence_container());
|
||||
ASSERT_FALSE(any.as_associative_container());
|
||||
|
||||
ASSERT_FALSE(std::as_const(any).as_ref());
|
||||
ASSERT_FALSE(std::as_const(any).as_sequence_container());
|
||||
ASSERT_FALSE(std::as_const(any).as_associative_container());
|
||||
}
|
||||
|
||||
TEST_F(MetaAny, SBOInPlaceTypeConstruction) {
|
||||
@@ -118,38 +146,72 @@ TEST_F(MetaAny, SBOInPlaceTypeConstruction) {
|
||||
|
||||
TEST_F(MetaAny, SBOAsRefConstruction) {
|
||||
int value = 3;
|
||||
int other = 42;
|
||||
entt::meta_any any{std::ref(value)};
|
||||
int compare = 42;
|
||||
auto any = entt::forward_as_meta(value);
|
||||
|
||||
ASSERT_TRUE(any);
|
||||
ASSERT_EQ(any.type(), entt::resolve<int>());
|
||||
|
||||
ASSERT_FALSE(any.try_cast<std::size_t>());
|
||||
ASSERT_EQ(any.cast<int &>(), 3);
|
||||
ASSERT_EQ(any.cast<const int &>(), 3);
|
||||
ASSERT_EQ(any.cast<int>(), 3);
|
||||
ASSERT_NE(any.data(), nullptr);
|
||||
ASSERT_NE(std::as_const(any).data(), nullptr);
|
||||
ASSERT_EQ(any, (entt::meta_any{std::ref(value)}));
|
||||
ASSERT_NE(any, (entt::meta_any{std::ref(other)}));
|
||||
ASSERT_EQ(any.data(), &value);
|
||||
ASSERT_EQ(std::as_const(any).data(), &value);
|
||||
|
||||
ASSERT_EQ(any, entt::forward_as_meta(value));
|
||||
ASSERT_NE(any, entt::forward_as_meta(compare));
|
||||
|
||||
ASSERT_NE(any, entt::meta_any{42});
|
||||
ASSERT_EQ(entt::meta_any{3}, any);
|
||||
|
||||
any = entt::make_meta<int &>(value);
|
||||
|
||||
ASSERT_TRUE(any);
|
||||
ASSERT_EQ(any.type(), entt::resolve<int>());
|
||||
ASSERT_EQ(std::as_const(any).data(), &value);
|
||||
|
||||
auto other = any.as_ref();
|
||||
|
||||
ASSERT_TRUE(other);
|
||||
ASSERT_EQ(any.type(), entt::resolve<int>());
|
||||
ASSERT_EQ(any.cast<int>(), 3);
|
||||
ASSERT_EQ(other.data(), any.data());
|
||||
}
|
||||
|
||||
TEST_F(MetaAny, SBOAsConstRefConstruction) {
|
||||
int value = 3;
|
||||
int other = 42;
|
||||
entt::meta_any any{std::cref(value)};
|
||||
const int value = 3;
|
||||
int compare = 42;
|
||||
auto any = entt::forward_as_meta(value);
|
||||
|
||||
ASSERT_TRUE(any);
|
||||
ASSERT_EQ(any.type(), entt::resolve<int>());
|
||||
|
||||
ASSERT_FALSE(any.try_cast<std::size_t>());
|
||||
ASSERT_DEATH(any.cast<int &>() = 3, ".*");
|
||||
ASSERT_DEATH(any.cast<int &>() = 3, "");
|
||||
ASSERT_EQ(any.cast<const int &>(), 3);
|
||||
ASSERT_EQ(any.cast<int>(), 3);
|
||||
ASSERT_EQ(any.data(), nullptr);
|
||||
ASSERT_NE(std::as_const(any).data(), nullptr);
|
||||
ASSERT_EQ(any, (entt::meta_any{std::cref(value)}));
|
||||
ASSERT_NE(any, (entt::meta_any{std::cref(other)}));
|
||||
ASSERT_EQ(std::as_const(any).data(), &value);
|
||||
|
||||
ASSERT_EQ(any, entt::forward_as_meta(value));
|
||||
ASSERT_NE(any, entt::forward_as_meta(compare));
|
||||
|
||||
ASSERT_NE(any, entt::meta_any{42});
|
||||
ASSERT_EQ(entt::meta_any{3}, any);
|
||||
|
||||
any = entt::make_meta<const int &>(value);
|
||||
|
||||
ASSERT_TRUE(any);
|
||||
ASSERT_EQ(any.type(), entt::resolve<int>());
|
||||
ASSERT_EQ(std::as_const(any).data(), &value);
|
||||
|
||||
auto other = any.as_ref();
|
||||
|
||||
ASSERT_TRUE(other);
|
||||
ASSERT_EQ(any.type(), entt::resolve<int>());
|
||||
ASSERT_EQ(any.cast<int>(), 3);
|
||||
ASSERT_EQ(other.data(), any.data());
|
||||
}
|
||||
|
||||
TEST_F(MetaAny, SBOCopyConstruction) {
|
||||
@@ -215,8 +277,7 @@ TEST_F(MetaAny, SBODirectAssignment) {
|
||||
}
|
||||
|
||||
TEST_F(MetaAny, NoSBOInPlaceTypeConstruction) {
|
||||
int value = 42;
|
||||
fat_t instance{&value};
|
||||
fat_t instance{.1, .2, .3, .4};
|
||||
entt::meta_any any{std::in_place_type<fat_t>, instance};
|
||||
|
||||
ASSERT_TRUE(any);
|
||||
@@ -229,42 +290,73 @@ TEST_F(MetaAny, NoSBOInPlaceTypeConstruction) {
|
||||
}
|
||||
|
||||
TEST_F(MetaAny, NoSBOAsRefConstruction) {
|
||||
int value = 3;
|
||||
fat_t instance{&value};
|
||||
entt::meta_any any{std::ref(instance)};
|
||||
fat_t instance{.1, .2, .3, .4};
|
||||
auto any = entt::forward_as_meta(instance);
|
||||
|
||||
ASSERT_TRUE(any);
|
||||
ASSERT_EQ(any.type(), entt::resolve<fat_t>());
|
||||
|
||||
ASSERT_FALSE(any.try_cast<std::size_t>());
|
||||
ASSERT_EQ(any.cast<fat_t &>(), instance);
|
||||
ASSERT_EQ(any.cast<const fat_t &>(), instance);
|
||||
ASSERT_EQ(any.cast<fat_t>(), instance);
|
||||
ASSERT_NE(any.data(), nullptr);
|
||||
ASSERT_NE(std::as_const(any).data(), nullptr);
|
||||
ASSERT_EQ(any, (entt::meta_any{std::ref(instance)}));
|
||||
ASSERT_EQ(any.data(), &instance);
|
||||
ASSERT_EQ(std::as_const(any).data(), &instance);
|
||||
|
||||
ASSERT_EQ(any, entt::forward_as_meta(instance));
|
||||
|
||||
ASSERT_EQ(any, entt::meta_any{instance});
|
||||
ASSERT_NE(entt::meta_any{fat_t{}}, any);
|
||||
|
||||
any = entt::make_meta<fat_t &>(instance);
|
||||
|
||||
ASSERT_TRUE(any);
|
||||
ASSERT_EQ(any.type(), entt::resolve<fat_t>());
|
||||
ASSERT_EQ(std::as_const(any).data(), &instance);
|
||||
|
||||
auto other = any.as_ref();
|
||||
|
||||
ASSERT_TRUE(other);
|
||||
ASSERT_EQ(any.type(), entt::resolve<fat_t>());
|
||||
ASSERT_EQ(any, entt::meta_any{instance});
|
||||
ASSERT_EQ(other.data(), any.data());
|
||||
}
|
||||
|
||||
TEST_F(MetaAny, NoSBOAsConstRefConstruction) {
|
||||
int value = 3;
|
||||
fat_t instance{&value};
|
||||
entt::meta_any any{std::cref(instance)};
|
||||
const fat_t instance{.1, .2, .3, .4};
|
||||
auto any = entt::forward_as_meta(instance);
|
||||
|
||||
ASSERT_TRUE(any);
|
||||
ASSERT_EQ(any.type(), entt::resolve<fat_t>());
|
||||
|
||||
ASSERT_FALSE(any.try_cast<std::size_t>());
|
||||
ASSERT_DEATH(any.cast<fat_t &>() = {}, ".*");
|
||||
ASSERT_DEATH(any.cast<fat_t &>() = {}, "");
|
||||
ASSERT_EQ(any.cast<const fat_t &>(), instance);
|
||||
ASSERT_EQ(any.cast<fat_t>(), instance);
|
||||
ASSERT_EQ(any.data(), nullptr);
|
||||
ASSERT_NE(std::as_const(any).data(), nullptr);
|
||||
ASSERT_EQ(any, (entt::meta_any{std::cref(instance)}));
|
||||
ASSERT_EQ(std::as_const(any).data(), &instance);
|
||||
|
||||
ASSERT_EQ(any, entt::forward_as_meta(instance));
|
||||
|
||||
ASSERT_EQ(any, entt::meta_any{instance});
|
||||
ASSERT_NE(entt::meta_any{fat_t{}}, any);
|
||||
|
||||
any = entt::make_meta<const fat_t &>(instance);
|
||||
|
||||
ASSERT_TRUE(any);
|
||||
ASSERT_EQ(any.type(), entt::resolve<fat_t>());
|
||||
ASSERT_EQ(std::as_const(any).data(), &instance);
|
||||
|
||||
auto other = any.as_ref();
|
||||
|
||||
ASSERT_TRUE(other);
|
||||
ASSERT_EQ(any.type(), entt::resolve<fat_t>());
|
||||
ASSERT_EQ(any, entt::meta_any{instance});
|
||||
ASSERT_EQ(other.data(), any.data());
|
||||
}
|
||||
|
||||
TEST_F(MetaAny, NoSBOCopyConstruction) {
|
||||
int value = 42;
|
||||
fat_t instance{&value};
|
||||
fat_t instance{.1, .2, .3, .4};
|
||||
entt::meta_any any{instance};
|
||||
entt::meta_any other{any};
|
||||
|
||||
@@ -277,8 +369,7 @@ TEST_F(MetaAny, NoSBOCopyConstruction) {
|
||||
}
|
||||
|
||||
TEST_F(MetaAny, NoSBOCopyAssignment) {
|
||||
int value = 42;
|
||||
fat_t instance{&value};
|
||||
fat_t instance{.1, .2, .3, .4};
|
||||
entt::meta_any any{instance};
|
||||
entt::meta_any other{3};
|
||||
|
||||
@@ -293,8 +384,7 @@ TEST_F(MetaAny, NoSBOCopyAssignment) {
|
||||
}
|
||||
|
||||
TEST_F(MetaAny, NoSBOMoveConstruction) {
|
||||
int value = 42;
|
||||
fat_t instance{&value};
|
||||
fat_t instance{.1, .2, .3, .4};
|
||||
entt::meta_any any{instance};
|
||||
entt::meta_any other{std::move(any)};
|
||||
|
||||
@@ -307,8 +397,7 @@ TEST_F(MetaAny, NoSBOMoveConstruction) {
|
||||
}
|
||||
|
||||
TEST_F(MetaAny, NoSBOMoveAssignment) {
|
||||
int value = 42;
|
||||
fat_t instance{&value};
|
||||
fat_t instance{.1, .2, .3, .4};
|
||||
entt::meta_any any{instance};
|
||||
entt::meta_any other{3};
|
||||
|
||||
@@ -323,13 +412,13 @@ TEST_F(MetaAny, NoSBOMoveAssignment) {
|
||||
}
|
||||
|
||||
TEST_F(MetaAny, NoSBODirectAssignment) {
|
||||
int value = 42;
|
||||
fat_t instance{.1, .2, .3, .4};
|
||||
entt::meta_any any{};
|
||||
any = fat_t{&value};
|
||||
any = instance;
|
||||
|
||||
ASSERT_FALSE(any.try_cast<std::size_t>());
|
||||
ASSERT_EQ(any.cast<fat_t>(), fat_t{&value});
|
||||
ASSERT_EQ(any, entt::meta_any{fat_t{&value}});
|
||||
ASSERT_EQ(any.cast<fat_t>(), instance);
|
||||
ASSERT_EQ(any, (entt::meta_any{fat_t{.1, .2, .3, .4}}));
|
||||
ASSERT_NE(fat_t{}, any);
|
||||
}
|
||||
|
||||
@@ -399,8 +488,7 @@ TEST_F(MetaAny, SBOMoveInvalidate) {
|
||||
}
|
||||
|
||||
TEST_F(MetaAny, NoSBOMoveInvalidate) {
|
||||
int value = 42;
|
||||
fat_t instance{&value};
|
||||
fat_t instance{.1, .2, .3, .4};
|
||||
entt::meta_any any{instance};
|
||||
entt::meta_any other{std::move(any)};
|
||||
entt::meta_any valid = std::move(other);
|
||||
@@ -421,15 +509,29 @@ TEST_F(MetaAny, VoidMoveInvalidate) {
|
||||
}
|
||||
|
||||
TEST_F(MetaAny, SBODestruction) {
|
||||
ASSERT_EQ(empty_t::counter, 0);
|
||||
{ entt::meta_any any{empty_t{}}; }
|
||||
ASSERT_EQ(empty_t::counter, 1);
|
||||
{
|
||||
entt::meta_any any{std::in_place_type<empty_t>};
|
||||
any.emplace<empty_t>();
|
||||
any = empty_t{};
|
||||
entt::meta_any other{std::move(any)};
|
||||
any = std::move(other);
|
||||
}
|
||||
|
||||
ASSERT_EQ(empty_t::destroy_counter, 3);
|
||||
ASSERT_EQ(empty_t::destructor_counter,6);
|
||||
}
|
||||
|
||||
TEST_F(MetaAny, NoSBODestruction) {
|
||||
ASSERT_EQ(fat_t::counter, 0);
|
||||
{ entt::meta_any any{fat_t{}}; }
|
||||
ASSERT_EQ(fat_t::counter, 1);
|
||||
{
|
||||
entt::meta_any any{std::in_place_type<fat_t>, 1., 2., 3., 4.};
|
||||
any.emplace<fat_t>(1., 2., 3., 4.);
|
||||
any = fat_t{1., 2., 3., 4.};
|
||||
entt::meta_any other{std::move(any)};
|
||||
any = std::move(other);
|
||||
}
|
||||
|
||||
ASSERT_EQ(fat_t::destroy_counter, 3);
|
||||
ASSERT_EQ(empty_t::destructor_counter, 4);
|
||||
}
|
||||
|
||||
TEST_F(MetaAny, VoidDestruction) {
|
||||
@@ -460,6 +562,18 @@ TEST_F(MetaAny, EmplaceVoid) {
|
||||
ASSERT_EQ(any, (entt::meta_any{std::in_place_type<void>}));
|
||||
}
|
||||
|
||||
TEST_F(MetaAny, Reset) {
|
||||
entt::meta_any any{42};
|
||||
|
||||
ASSERT_TRUE(any);
|
||||
ASSERT_EQ(any.type(), entt::resolve<int>());
|
||||
|
||||
any.reset();
|
||||
|
||||
ASSERT_FALSE(any);
|
||||
ASSERT_EQ(any.type(), entt::meta_type{});
|
||||
}
|
||||
|
||||
TEST_F(MetaAny, SBOSwap) {
|
||||
entt::meta_any lhs{'c'};
|
||||
entt::meta_any rhs{42};
|
||||
@@ -473,14 +587,13 @@ TEST_F(MetaAny, SBOSwap) {
|
||||
}
|
||||
|
||||
TEST_F(MetaAny, NoSBOSwap) {
|
||||
int i{}, j{};
|
||||
entt::meta_any lhs{fat_t{&i}};
|
||||
entt::meta_any rhs{fat_t{&j}};
|
||||
entt::meta_any lhs{fat_t{.1, .2, .3, .4}};
|
||||
entt::meta_any rhs{fat_t{.4, .3, .2, .1}};
|
||||
|
||||
std::swap(lhs, rhs);
|
||||
|
||||
ASSERT_EQ(lhs.cast<fat_t>().foo, &j);
|
||||
ASSERT_EQ(rhs.cast<fat_t>().bar, &i);
|
||||
ASSERT_EQ(lhs.cast<fat_t>(), (fat_t{.4, .3, .2, .1}));
|
||||
ASSERT_EQ(rhs.cast<fat_t>(), (fat_t{.1, .2, .3, .4}));
|
||||
}
|
||||
|
||||
TEST_F(MetaAny, VoidSwap) {
|
||||
@@ -494,8 +607,7 @@ TEST_F(MetaAny, VoidSwap) {
|
||||
}
|
||||
|
||||
TEST_F(MetaAny, SBOWithNoSBOSwap) {
|
||||
int value = 42;
|
||||
entt::meta_any lhs{fat_t{&value}};
|
||||
entt::meta_any lhs{fat_t{.1, .2, .3, .4}};
|
||||
entt::meta_any rhs{'c'};
|
||||
|
||||
std::swap(lhs, rhs);
|
||||
@@ -503,8 +615,7 @@ TEST_F(MetaAny, SBOWithNoSBOSwap) {
|
||||
ASSERT_FALSE(lhs.try_cast<fat_t>());
|
||||
ASSERT_EQ(lhs.cast<char>(), 'c');
|
||||
ASSERT_FALSE(rhs.try_cast<char>());
|
||||
ASSERT_EQ(rhs.cast<fat_t>().foo, &value);
|
||||
ASSERT_EQ(rhs.cast<fat_t>().bar, &value);
|
||||
ASSERT_EQ(rhs.cast<fat_t>(), (fat_t{.1, .2, .3, .4}));
|
||||
}
|
||||
|
||||
TEST_F(MetaAny, SBOWithEmptySwap) {
|
||||
@@ -533,37 +644,39 @@ TEST_F(MetaAny, SBOWithVoidSwap) {
|
||||
}
|
||||
|
||||
TEST_F(MetaAny, NoSBOWithEmptySwap) {
|
||||
int i{};
|
||||
entt::meta_any lhs{fat_t{&i}};
|
||||
entt::meta_any lhs{fat_t{.1, .2, .3, .4}};
|
||||
entt::meta_any rhs{};
|
||||
|
||||
std::swap(lhs, rhs);
|
||||
|
||||
ASSERT_EQ(rhs.cast<fat_t>().bar, &i);
|
||||
ASSERT_FALSE(lhs);
|
||||
ASSERT_EQ(rhs.cast<fat_t>(), (fat_t{.1, .2, .3, .4}));
|
||||
|
||||
std::swap(lhs, rhs);
|
||||
|
||||
ASSERT_EQ(lhs.cast<fat_t>().bar, &i);
|
||||
ASSERT_FALSE(rhs);
|
||||
ASSERT_EQ(lhs.cast<fat_t>(), (fat_t{.1, .2, .3, .4}));
|
||||
}
|
||||
|
||||
TEST_F(MetaAny, NoSBOWithVoidSwap) {
|
||||
int i{};
|
||||
entt::meta_any lhs{fat_t{&i}};
|
||||
entt::meta_any lhs{fat_t{.1, .2, .3, .4}};
|
||||
entt::meta_any rhs{std::in_place_type<void>};
|
||||
|
||||
std::swap(lhs, rhs);
|
||||
|
||||
ASSERT_EQ(rhs.cast<fat_t>().bar, &i);
|
||||
ASSERT_EQ(lhs.type(), entt::resolve<void>());
|
||||
ASSERT_EQ(rhs.cast<fat_t>(), (fat_t{.1, .2, .3, .4}));
|
||||
|
||||
std::swap(lhs, rhs);
|
||||
|
||||
ASSERT_EQ(lhs.cast<fat_t>().bar, &i);
|
||||
ASSERT_EQ(rhs.type(), entt::resolve<void>());
|
||||
ASSERT_EQ(lhs.cast<fat_t>(), (fat_t{.1, .2, .3, .4}));
|
||||
}
|
||||
|
||||
TEST_F(MetaAny, AsRef) {
|
||||
entt::meta_any any{42};
|
||||
auto ref = as_ref(any);
|
||||
auto cref = as_ref(std::as_const(any));
|
||||
auto ref = any.as_ref();
|
||||
auto cref = std::as_const(any).as_ref();
|
||||
|
||||
ASSERT_EQ(any.try_cast<int>(), any.data());
|
||||
ASSERT_EQ(ref.try_cast<int>(), any.data());
|
||||
@@ -585,7 +698,7 @@ TEST_F(MetaAny, AsRef) {
|
||||
ASSERT_EQ(any.cast<const int &>(), 42);
|
||||
ASSERT_EQ(ref.cast<int &>(), 42);
|
||||
ASSERT_EQ(ref.cast<const int &>(), 42);
|
||||
ASSERT_DEATH(cref.cast<int &>() = 3, ".*");
|
||||
ASSERT_DEATH(cref.cast<int &>() = 3, "");
|
||||
ASSERT_EQ(cref.cast<const int &>(), 42);
|
||||
|
||||
any.cast<int &>() = 3;
|
||||
@@ -599,16 +712,16 @@ TEST_F(MetaAny, AsRef) {
|
||||
ASSERT_EQ(ref.try_cast<int>(), nullptr);
|
||||
ASSERT_EQ(cref.try_cast<int>(), any.data());
|
||||
|
||||
ref = as_ref(ref);
|
||||
cref = as_ref(std::as_const(cref));
|
||||
ref = ref.as_ref();
|
||||
cref = std::as_const(cref).as_ref();
|
||||
|
||||
ASSERT_EQ(ref.try_cast<int>(), nullptr);
|
||||
ASSERT_EQ(cref.try_cast<int>(), nullptr);
|
||||
ASSERT_EQ(ref.try_cast<const int>(), any.data());
|
||||
ASSERT_EQ(cref.try_cast<const int>(), any.data());
|
||||
|
||||
ASSERT_DEATH(ref.cast<int &>() = 3, ".*");
|
||||
ASSERT_DEATH(cref.cast<int &>() = 3, ".*");
|
||||
ASSERT_DEATH(ref.cast<int &>() = 3, "");
|
||||
ASSERT_DEATH(cref.cast<int &>() = 3, "");
|
||||
|
||||
ASSERT_EQ(ref.cast<const int &>(), 3);
|
||||
ASSERT_EQ(cref.cast<const int &>(), 3);
|
||||
@@ -624,6 +737,14 @@ TEST_F(MetaAny, AsRef) {
|
||||
ASSERT_EQ(cref.cast<const int &>(), 42);
|
||||
ASSERT_NE(ref.try_cast<int>(), any.data());
|
||||
ASSERT_NE(cref.try_cast<int>(), any.data());
|
||||
|
||||
any.emplace<void>();
|
||||
ref = any.as_ref();
|
||||
cref = std::as_const(any).as_ref();
|
||||
|
||||
ASSERT_TRUE(any);
|
||||
ASSERT_FALSE(ref);
|
||||
ASSERT_FALSE(cref);
|
||||
}
|
||||
|
||||
TEST_F(MetaAny, Comparable) {
|
||||
@@ -690,11 +811,11 @@ TEST_F(MetaAny, Cast) {
|
||||
ASSERT_EQ(any.cast<fat_t &>(), fat_t{});
|
||||
ASSERT_EQ(any.cast<fat_t>(), fat_t{});
|
||||
|
||||
ASSERT_EQ(any.cast<fat_t>().gnam[0u], 0.);
|
||||
ASSERT_EQ(any.cast<fat_t>().value[0u], 0.);
|
||||
|
||||
any.cast<fat_t &>().gnam[0u] = 3.;
|
||||
any.cast<fat_t &>().value[0u] = 3.;
|
||||
|
||||
ASSERT_EQ(any.cast<fat_t>().gnam[0u], 3.);
|
||||
ASSERT_EQ(any.cast<fat_t>().value[0u], 3.);
|
||||
}
|
||||
|
||||
TEST_F(MetaAny, Convert) {
|
||||
@@ -731,8 +852,8 @@ TEST_F(MetaAny, ConstConvert) {
|
||||
|
||||
TEST_F(MetaAny, UnmanageableType) {
|
||||
unmanageable_t instance;
|
||||
entt::meta_any any{std::ref(instance)};
|
||||
entt::meta_any other = any;
|
||||
auto any = entt::forward_as_meta(instance);
|
||||
entt::meta_any other = any.as_ref();
|
||||
|
||||
std::swap(any, other);
|
||||
|
||||
@@ -755,12 +876,12 @@ TEST_F(MetaAny, Invoke) {
|
||||
using namespace entt::literals;
|
||||
|
||||
clazz_t instance;
|
||||
entt::meta_any any{std::ref(instance)};
|
||||
auto any = entt::forward_as_meta(instance);
|
||||
|
||||
ASSERT_TRUE(any.invoke("func"_hs));
|
||||
ASSERT_TRUE(any.invoke("member"_hs, 42));
|
||||
ASSERT_FALSE(std::as_const(any).invoke("member"_hs, 42));
|
||||
ASSERT_FALSE(as_ref(std::as_const(any)).invoke("member"_hs, 42));
|
||||
ASSERT_FALSE(std::as_const(any).as_ref().invoke("member"_hs, 42));
|
||||
ASSERT_FALSE(any.invoke("non_existent"_hs, 42));
|
||||
|
||||
ASSERT_EQ(clazz_t::c, 'd');
|
||||
@@ -771,7 +892,7 @@ TEST_F(MetaAny, SetGet) {
|
||||
using namespace entt::literals;
|
||||
|
||||
clazz_t instance;
|
||||
entt::meta_any any{std::ref(instance)};
|
||||
auto any = entt::forward_as_meta(instance);
|
||||
|
||||
ASSERT_TRUE(any.set("value"_hs, 42));
|
||||
|
||||
@@ -779,7 +900,7 @@ TEST_F(MetaAny, SetGet) {
|
||||
|
||||
ASSERT_TRUE(value);
|
||||
ASSERT_EQ(value, any.get("value"_hs));
|
||||
ASSERT_EQ(value, as_ref(std::as_const(any)).get("value"_hs));
|
||||
ASSERT_EQ(value, std::as_const(any).as_ref().get("value"_hs));
|
||||
ASSERT_NE(value.try_cast<int>(), nullptr);
|
||||
ASSERT_EQ(value.cast<int>(), 42);
|
||||
ASSERT_EQ(instance.value, 42);
|
||||
@@ -787,3 +908,40 @@ TEST_F(MetaAny, SetGet) {
|
||||
ASSERT_FALSE(any.set("non_existent"_hs, 42));
|
||||
ASSERT_FALSE(any.get("non_existent"_hs));
|
||||
}
|
||||
|
||||
TEST_F(MetaAny, MakeMeta) {
|
||||
int value = 42;
|
||||
auto any = entt::make_meta<int>(value);
|
||||
auto ref = entt::make_meta<int &>(value);
|
||||
|
||||
ASSERT_TRUE(any);
|
||||
ASSERT_TRUE(ref);
|
||||
|
||||
ASSERT_EQ(any.cast<const int &>(), 42);
|
||||
ASSERT_EQ(ref.cast<const int &>(), 42);
|
||||
|
||||
ASSERT_NE(any.data(), &value);
|
||||
ASSERT_EQ(ref.data(), &value);
|
||||
}
|
||||
|
||||
TEST_F(MetaAny, ForwardAsMeta) {
|
||||
int value = 42;
|
||||
auto any = entt::forward_as_meta(std::move(value));
|
||||
auto ref = entt::forward_as_meta(value);
|
||||
auto cref = entt::forward_as_meta(std::as_const(value));
|
||||
|
||||
ASSERT_TRUE(any);
|
||||
ASSERT_TRUE(ref);
|
||||
ASSERT_TRUE(cref);
|
||||
|
||||
ASSERT_NE(any.try_cast<int>(), nullptr);
|
||||
ASSERT_NE(ref.try_cast<int>(), nullptr);
|
||||
ASSERT_EQ(cref.try_cast<int>(), nullptr);
|
||||
|
||||
ASSERT_EQ(any.cast<const int &>(), 42);
|
||||
ASSERT_EQ(ref.cast<const int &>(), 42);
|
||||
ASSERT_EQ(cref.cast<const int &>(), 42);
|
||||
|
||||
ASSERT_NE(any.data(), &value);
|
||||
ASSERT_EQ(ref.data(), &value);
|
||||
}
|
||||
|
||||
@@ -1,29 +1,57 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/hashed_string.hpp>
|
||||
#include <entt/meta/factory.hpp>
|
||||
#include <entt/meta/meta.hpp>
|
||||
#include <entt/meta/node.hpp>
|
||||
#include <entt/meta/resolve.hpp>
|
||||
|
||||
struct base_t {};
|
||||
struct derived_t: base_t {};
|
||||
struct base_t {
|
||||
base_t() = default;
|
||||
int value;
|
||||
};
|
||||
|
||||
struct derived_t: base_t {
|
||||
derived_t() = default;
|
||||
};
|
||||
|
||||
struct MetaBase: ::testing::Test {
|
||||
static void SetUpTestCase() {
|
||||
void SetUp() override {
|
||||
using namespace entt::literals;
|
||||
|
||||
entt::meta<base_t>().type("base"_hs);
|
||||
entt::meta<derived_t>().type("derived"_hs).base<base_t>();
|
||||
entt::meta<derived_t>()
|
||||
.type("derived"_hs)
|
||||
.base<base_t>();
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
for(auto type: entt::resolve()) {
|
||||
type.reset();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(MetaBase, Functionalities) {
|
||||
using namespace entt::literals;
|
||||
auto any = entt::resolve<derived_t>().construct();
|
||||
any.cast<derived_t &>().value = 42;
|
||||
auto as_derived = any.as_ref();
|
||||
|
||||
auto base = entt::resolve<derived_t>().base("base"_hs);
|
||||
derived_t derived{};
|
||||
ASSERT_TRUE(any.allow_cast<base_t &>());
|
||||
|
||||
ASSERT_TRUE(base);
|
||||
ASSERT_EQ(base.parent(), entt::resolve("derived"_hs));
|
||||
ASSERT_EQ(base.type(), entt::resolve<base_t>());
|
||||
ASSERT_EQ(base.cast(&derived), static_cast<base_t *>(&derived));
|
||||
ASSERT_FALSE(any.allow_cast<char>());
|
||||
ASSERT_FALSE(as_derived.allow_cast<char>());
|
||||
|
||||
ASSERT_TRUE(any);
|
||||
ASSERT_EQ(any.cast<base_t &>().value, as_derived.cast<derived_t &>().value);
|
||||
|
||||
any.cast<base_t &>().value = 3;
|
||||
|
||||
ASSERT_EQ(any.cast<const base_t &>().value, as_derived.cast<const derived_t &>().value);
|
||||
}
|
||||
|
||||
TEST_F(MetaBase, ReRegistration) {
|
||||
SetUp();
|
||||
|
||||
auto *node = entt::internal::meta_info<derived_t>::resolve();
|
||||
|
||||
ASSERT_NE(node->base, nullptr);
|
||||
ASSERT_EQ(node->base->next, nullptr);
|
||||
}
|
||||
|
||||
@@ -6,9 +6,22 @@
|
||||
#include <entt/meta/resolve.hpp>
|
||||
|
||||
struct MetaContainer: ::testing::Test {
|
||||
static void SetUpTestCase() {
|
||||
entt::meta<double>().conv<int>();
|
||||
entt::meta<int>().conv<char>();
|
||||
void SetUp() override {
|
||||
using namespace entt::literals;
|
||||
|
||||
entt::meta<double>()
|
||||
.type("double"_hs)
|
||||
.conv<int>();
|
||||
|
||||
entt::meta<int>()
|
||||
.type("int"_hs)
|
||||
.conv<char>();
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
for(auto type: entt::resolve()) {
|
||||
type.reset();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -44,7 +57,7 @@ TEST_F(MetaContainer, EmptyAssociativeContainer) {
|
||||
|
||||
TEST_F(MetaContainer, SequenceContainerIterator) {
|
||||
std::vector<int> vec{2, 3, 4};
|
||||
entt::meta_any any{std::ref(vec)};
|
||||
auto any = entt::forward_as_meta(vec);
|
||||
entt::meta_sequence_container::iterator first{};
|
||||
auto view = any.as_sequence_container();
|
||||
|
||||
@@ -71,7 +84,7 @@ TEST_F(MetaContainer, SequenceContainerIterator) {
|
||||
|
||||
TEST_F(MetaContainer, AssociativeContainerIterator) {
|
||||
std::map<int, char> map{{2, 'c'}, {3, 'd'}, {4, 'e'}};
|
||||
entt::meta_any any{std::ref(map)};
|
||||
auto any = entt::forward_as_meta(map);
|
||||
entt::meta_associative_container::iterator first{};
|
||||
auto view = any.as_associative_container();
|
||||
|
||||
@@ -98,7 +111,7 @@ TEST_F(MetaContainer, AssociativeContainerIterator) {
|
||||
|
||||
TEST_F(MetaContainer, StdVector) {
|
||||
std::vector<int> vec{};
|
||||
entt::meta_any any{std::ref(vec)};
|
||||
auto any = entt::forward_as_meta(vec);
|
||||
|
||||
auto view = any.as_sequence_container();
|
||||
|
||||
@@ -141,7 +154,7 @@ TEST_F(MetaContainer, StdVector) {
|
||||
|
||||
TEST_F(MetaContainer, StdArray) {
|
||||
std::array<int, 3> arr{};
|
||||
entt::meta_any any{std::ref(arr)};
|
||||
auto any = entt::forward_as_meta(arr);
|
||||
|
||||
auto view = any.as_sequence_container();
|
||||
|
||||
@@ -183,7 +196,7 @@ TEST_F(MetaContainer, StdArray) {
|
||||
|
||||
TEST_F(MetaContainer, StdMap) {
|
||||
std::map<int, char> map{{2, 'c'}, {3, 'd'}, {4, 'e'}};
|
||||
entt::meta_any any{std::ref(map)};
|
||||
auto any = entt::forward_as_meta(map);
|
||||
|
||||
auto view = any.as_associative_container();
|
||||
|
||||
@@ -227,7 +240,7 @@ TEST_F(MetaContainer, StdMap) {
|
||||
|
||||
TEST_F(MetaContainer, StdSet) {
|
||||
std::set<int> set{2, 3, 4};
|
||||
entt::meta_any any{std::ref(set)};
|
||||
auto any = entt::forward_as_meta(set);
|
||||
|
||||
auto view = any.as_associative_container();
|
||||
|
||||
@@ -259,9 +272,9 @@ TEST_F(MetaContainer, StdSet) {
|
||||
ASSERT_EQ(view.size(), 4u);
|
||||
ASSERT_EQ(view.find(0), view.end());
|
||||
|
||||
(*view.find(1)).first.cast<int &>() = 42;
|
||||
|
||||
ASSERT_EQ((*view.find(1)).first.cast<int>(), 1);
|
||||
ASSERT_EQ((*view.find(1)).first.try_cast<int>(), nullptr);
|
||||
ASSERT_NE((*view.find(1)).first.try_cast<const int>(), nullptr);
|
||||
ASSERT_EQ((*view.find(1)).first.cast<const int &>(), 1);
|
||||
|
||||
ASSERT_TRUE(view.erase(1.));
|
||||
ASSERT_TRUE(view.clear());
|
||||
@@ -270,7 +283,7 @@ TEST_F(MetaContainer, StdSet) {
|
||||
|
||||
TEST_F(MetaContainer, ConstSequenceContainer) {
|
||||
std::vector<int> vec{};
|
||||
entt::meta_any any{std::cref(vec)};
|
||||
auto any = entt::forward_as_meta(std::as_const(vec));
|
||||
|
||||
auto view = any.as_sequence_container();
|
||||
|
||||
@@ -288,7 +301,7 @@ TEST_F(MetaContainer, ConstSequenceContainer) {
|
||||
ASSERT_EQ(view.size(), 1u);
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
|
||||
ASSERT_DEATH(view[0].cast<int &>() = 2, ".*");
|
||||
ASSERT_DEATH(view[0].cast<int &>() = 2, "");
|
||||
ASSERT_EQ(view[0].cast<const int &>(), 42);
|
||||
|
||||
auto it = view.begin();
|
||||
@@ -311,7 +324,7 @@ TEST_F(MetaContainer, ConstSequenceContainer) {
|
||||
|
||||
TEST_F(MetaContainer, ConstKeyValueAssociativeContainer) {
|
||||
std::map<int, char> map{};
|
||||
entt::meta_any any{std::cref(map)};
|
||||
auto any = entt::forward_as_meta(std::as_const(map));
|
||||
|
||||
auto view = any.as_associative_container();
|
||||
|
||||
@@ -329,7 +342,7 @@ TEST_F(MetaContainer, ConstKeyValueAssociativeContainer) {
|
||||
ASSERT_EQ(view.size(), 1u);
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
|
||||
ASSERT_DEATH((*view.find(2)).second.cast<char &>() = 'a', ".*");
|
||||
ASSERT_DEATH((*view.find(2)).second.cast<char &>() = 'a', "");
|
||||
ASSERT_EQ((*view.find(2)).second.cast<const char &>(), 'c');
|
||||
|
||||
ASSERT_FALSE(view.insert(0, 'a'));
|
||||
@@ -347,7 +360,7 @@ TEST_F(MetaContainer, ConstKeyValueAssociativeContainer) {
|
||||
|
||||
TEST_F(MetaContainer, ConstKeyOnlyAssociativeContainer) {
|
||||
std::set<int> set{};
|
||||
entt::meta_any any{std::cref(set)};
|
||||
auto any = entt::forward_as_meta(std::as_const(set));
|
||||
|
||||
auto view = any.as_associative_container();
|
||||
|
||||
@@ -365,8 +378,9 @@ TEST_F(MetaContainer, ConstKeyOnlyAssociativeContainer) {
|
||||
ASSERT_EQ(view.size(), 1u);
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
|
||||
ASSERT_EQ((*view.find(2)).first.try_cast<int>(), nullptr);
|
||||
ASSERT_NE((*view.find(2)).first.try_cast<const int>(), nullptr);
|
||||
ASSERT_EQ((*view.find(2)).first.cast<int>(), 2);
|
||||
ASSERT_EQ((*view.find(2)).first.cast<int &>(), 2);
|
||||
ASSERT_EQ((*view.find(2)).first.cast<const int &>(), 2);
|
||||
|
||||
ASSERT_FALSE(view.insert(0));
|
||||
@@ -388,15 +402,15 @@ TEST_F(MetaContainer, SequenceContainerConstMetaAny) {
|
||||
|
||||
ASSERT_TRUE(view);
|
||||
ASSERT_EQ(view.value_type(), entt::resolve<int>());
|
||||
ASSERT_DEATH(view[0].cast<int &>() = 2, ".*");
|
||||
ASSERT_DEATH(view[0].cast<int &>() = 2, "");
|
||||
ASSERT_EQ(view[0].cast<const int &>(), 42);
|
||||
};
|
||||
|
||||
std::vector<int> vec{42};
|
||||
|
||||
test(vec);
|
||||
test(std::ref(vec));
|
||||
test(std::cref(vec));
|
||||
test(entt::forward_as_meta(vec));
|
||||
test(entt::forward_as_meta(std::as_const(vec)));
|
||||
}
|
||||
|
||||
TEST_F(MetaContainer, KeyValueAssociativeContainerConstMetaAny) {
|
||||
@@ -405,15 +419,15 @@ TEST_F(MetaContainer, KeyValueAssociativeContainerConstMetaAny) {
|
||||
|
||||
ASSERT_TRUE(view);
|
||||
ASSERT_EQ(view.value_type(), (entt::resolve<std::pair<const int, char>>()));
|
||||
ASSERT_DEATH((*view.find(2)).second.cast<char &>() = 'a', ".*");
|
||||
ASSERT_DEATH((*view.find(2)).second.cast<char &>() = 'a', "");
|
||||
ASSERT_EQ((*view.find(2)).second.cast<const char &>(), 'c');
|
||||
};
|
||||
|
||||
std::map<int, char> map{{2, 'c'}};
|
||||
|
||||
test(map);
|
||||
test(std::ref(map));
|
||||
test(std::cref(map));
|
||||
test(entt::forward_as_meta(map));
|
||||
test(entt::forward_as_meta(std::as_const(map)));
|
||||
}
|
||||
|
||||
TEST_F(MetaContainer, KeyOnlyAssociativeContainerConstMetaAny) {
|
||||
@@ -422,14 +436,64 @@ TEST_F(MetaContainer, KeyOnlyAssociativeContainerConstMetaAny) {
|
||||
|
||||
ASSERT_TRUE(view);
|
||||
ASSERT_EQ(view.value_type(), (entt::resolve<int>()));
|
||||
|
||||
ASSERT_EQ((*view.find(2)).first.try_cast<int>(), nullptr);
|
||||
ASSERT_NE((*view.find(2)).first.try_cast<const int>(), nullptr);
|
||||
ASSERT_EQ((*view.find(2)).first.cast<int>(), 2);
|
||||
ASSERT_EQ((*view.find(2)).first.cast<int &>(), 2);
|
||||
ASSERT_EQ((*view.find(2)).first.cast<const int &>(), 2);
|
||||
};
|
||||
|
||||
std::set<int> set{2};
|
||||
|
||||
test(set);
|
||||
test(std::ref(set));
|
||||
test(std::cref(set));
|
||||
test(entt::forward_as_meta(set));
|
||||
test(entt::forward_as_meta(std::as_const(set)));
|
||||
}
|
||||
|
||||
TEST_F(MetaContainer, StdVectorBool) {
|
||||
using proxy_type = typename std::vector<bool>::reference;
|
||||
using const_proxy_type = typename std::vector<bool>::const_reference;
|
||||
|
||||
std::vector<bool> vec{};
|
||||
auto any = entt::forward_as_meta(vec);
|
||||
auto cany = std::as_const(any).as_ref();
|
||||
|
||||
auto view = any.as_sequence_container();
|
||||
auto cview = cany.as_sequence_container();
|
||||
|
||||
ASSERT_TRUE(view);
|
||||
ASSERT_EQ(view.value_type(), entt::resolve<bool>());
|
||||
|
||||
ASSERT_EQ(view.size(), 0u);
|
||||
ASSERT_EQ(view.begin(), view.end());
|
||||
ASSERT_TRUE(view.resize(3u));
|
||||
ASSERT_EQ(view.size(), 3u);
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
|
||||
view[0].cast<proxy_type>() = true;
|
||||
view[1].cast<proxy_type>() = true;
|
||||
view[2].cast<proxy_type>() = false;
|
||||
|
||||
ASSERT_EQ(cview[1u].cast<const_proxy_type>(), true);
|
||||
|
||||
auto it = view.begin();
|
||||
auto ret = view.insert(it, true);
|
||||
|
||||
ASSERT_TRUE(ret.second);
|
||||
ASSERT_FALSE(view.insert(ret.first, 'c').second);
|
||||
ASSERT_TRUE(view.insert(++ret.first, false).second);
|
||||
|
||||
ASSERT_EQ(view.size(), 5u);
|
||||
ASSERT_EQ((*view.begin()).cast<proxy_type>(), true);
|
||||
ASSERT_EQ((*++cview.begin()).cast<const_proxy_type>(), false);
|
||||
|
||||
it = view.begin();
|
||||
ret = view.erase(it);
|
||||
|
||||
ASSERT_TRUE(ret.second);
|
||||
ASSERT_EQ(view.size(), 4u);
|
||||
ASSERT_EQ((*ret.first).cast<proxy_type>(), false);
|
||||
|
||||
ASSERT_TRUE(view.clear());
|
||||
ASSERT_EQ(cview.size(), 0u);
|
||||
}
|
||||
|
||||
@@ -1,70 +1,59 @@
|
||||
#include <utility>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/hashed_string.hpp>
|
||||
#include <entt/meta/factory.hpp>
|
||||
#include <entt/meta/meta.hpp>
|
||||
#include <entt/meta/node.hpp>
|
||||
#include <entt/meta/resolve.hpp>
|
||||
|
||||
struct clazz_t {
|
||||
int f() const {
|
||||
return i;
|
||||
}
|
||||
|
||||
static char g(const clazz_t &type) {
|
||||
return type.c;
|
||||
}
|
||||
|
||||
int i{};
|
||||
char c{};
|
||||
clazz_t() = default;
|
||||
operator int() const { return value; }
|
||||
int value;
|
||||
};
|
||||
|
||||
double conv_to_double(const clazz_t &instance) {
|
||||
return instance.value * 2.;
|
||||
}
|
||||
|
||||
struct MetaConv: ::testing::Test {
|
||||
static void SetUpTestCase() {
|
||||
entt::meta<double>().conv<int>();
|
||||
entt::meta<clazz_t>().conv<&clazz_t::f>().conv<&clazz_t::g>();
|
||||
void SetUp() override {
|
||||
using namespace entt::literals;
|
||||
|
||||
entt::meta<clazz_t>()
|
||||
.type("clazz"_hs)
|
||||
.conv<int>()
|
||||
.conv<&conv_to_double>();
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
for(auto type: entt::resolve()) {
|
||||
type.reset();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(MetaConv, Functionalities) {
|
||||
auto conv = entt::resolve<double>().conv<int>();
|
||||
double value = 3.;
|
||||
auto any = entt::resolve<clazz_t>().construct();
|
||||
any.cast<clazz_t &>().value = 42;
|
||||
|
||||
ASSERT_TRUE(conv);
|
||||
ASSERT_EQ(conv.parent(), entt::resolve<double>());
|
||||
ASSERT_EQ(conv.type(), entt::resolve<int>());
|
||||
const auto as_int = std::as_const(any).allow_cast<int>();
|
||||
const auto as_double = std::as_const(any).allow_cast<double>();
|
||||
|
||||
auto any = conv.convert(&value);
|
||||
ASSERT_FALSE(any.allow_cast<char>());
|
||||
|
||||
ASSERT_TRUE(any);
|
||||
ASSERT_EQ(any.type(), entt::resolve<int>());
|
||||
ASSERT_EQ(any.cast<int>(), 3);
|
||||
ASSERT_TRUE(as_int);
|
||||
ASSERT_TRUE(as_double);
|
||||
|
||||
ASSERT_EQ(as_int.cast<int>(), any.cast<clazz_t &>().value);
|
||||
ASSERT_EQ(as_double.cast<double>(), conv_to_double(any.cast<clazz_t &>()));
|
||||
}
|
||||
|
||||
TEST_F(MetaConv, AsFreeFunctions) {
|
||||
auto conv = entt::resolve<clazz_t>().conv<int>();
|
||||
clazz_t clazz{42, 'c'};
|
||||
TEST_F(MetaConv, ReRegistration) {
|
||||
SetUp();
|
||||
|
||||
ASSERT_TRUE(conv);
|
||||
ASSERT_EQ(conv.parent(), entt::resolve<clazz_t>());
|
||||
ASSERT_EQ(conv.type(), entt::resolve<int>());
|
||||
auto *node = entt::internal::meta_info<clazz_t>::resolve();
|
||||
|
||||
auto any = conv.convert(&clazz);
|
||||
|
||||
ASSERT_TRUE(any);
|
||||
ASSERT_EQ(any.type(), entt::resolve<int>());
|
||||
ASSERT_EQ(any.cast<int>(), 42);
|
||||
}
|
||||
|
||||
TEST_F(MetaConv, AsMemberFunctions) {
|
||||
auto conv = entt::resolve<clazz_t>().conv<char>();
|
||||
clazz_t clazz{42, 'c'};
|
||||
|
||||
ASSERT_TRUE(conv);
|
||||
ASSERT_EQ(conv.parent(), entt::resolve<clazz_t>());
|
||||
ASSERT_EQ(conv.type(), entt::resolve<char>());
|
||||
|
||||
auto any = conv.convert(&clazz);
|
||||
|
||||
ASSERT_TRUE(any);
|
||||
ASSERT_EQ(any.type(), entt::resolve<char>());
|
||||
ASSERT_EQ(any.cast<char>(), 'c');
|
||||
ASSERT_NE(node->conv, nullptr);
|
||||
ASSERT_NE(node->conv->next, nullptr);
|
||||
ASSERT_EQ(node->conv->next->next, nullptr);
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
#include <entt/meta/meta.hpp>
|
||||
#include <entt/meta/resolve.hpp>
|
||||
|
||||
struct base_t { char value{}; };
|
||||
struct derived_t: base_t {};
|
||||
struct base_t { base_t(): value{'c'} {} char value; };
|
||||
struct derived_t: base_t { derived_t(): base_t{} {} };
|
||||
|
||||
struct clazz_t {
|
||||
clazz_t(const base_t &other, int iv)
|
||||
@@ -30,20 +30,35 @@ struct clazz_t {
|
||||
char c{};
|
||||
};
|
||||
|
||||
double double_factory() { return 42.; }
|
||||
|
||||
struct MetaCtor: ::testing::Test {
|
||||
static void SetUpTestCase() {
|
||||
void SetUp() override {
|
||||
using namespace entt::literals;
|
||||
|
||||
entt::meta<double>().conv<int>();
|
||||
entt::meta<derived_t>().base<base_t>();
|
||||
entt::meta<double>()
|
||||
.type("double"_hs)
|
||||
.conv<int>()
|
||||
.ctor<&double_factory>();
|
||||
|
||||
entt::meta<clazz_t>().type("clazz"_hs)
|
||||
entt::meta<derived_t>()
|
||||
.type("derived"_hs)
|
||||
.base<base_t>();
|
||||
|
||||
entt::meta<clazz_t>()
|
||||
.type("clazz"_hs)
|
||||
.ctor<&entt::registry::emplace_or_replace<clazz_t, const int &, const char &>, entt::as_ref_t>()
|
||||
.ctor<const base_t &, int>()
|
||||
.ctor<const int &, char>().prop(3, false)
|
||||
.ctor<entt::overload<clazz_t(int)>(&clazz_t::factory)>().prop('c', 42)
|
||||
.ctor<entt::overload<clazz_t(base_t, int, int)>(&clazz_t::factory)>();
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
for(auto type: entt::resolve()) {
|
||||
type.reset();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(MetaCtor, Functionalities) {
|
||||
@@ -53,7 +68,7 @@ TEST_F(MetaCtor, Functionalities) {
|
||||
|
||||
ASSERT_TRUE(ctor);
|
||||
ASSERT_EQ(ctor.parent(), entt::resolve("clazz"_hs));
|
||||
ASSERT_EQ(ctor.size(), 2u);
|
||||
ASSERT_EQ(ctor.arity(), 2u);
|
||||
ASSERT_EQ(ctor.arg(0u), entt::resolve<int>());
|
||||
ASSERT_EQ(ctor.arg(1u), entt::resolve<char>());
|
||||
ASSERT_FALSE(ctor.arg(2u));
|
||||
@@ -88,7 +103,7 @@ TEST_F(MetaCtor, Func) {
|
||||
|
||||
ASSERT_TRUE(ctor);
|
||||
ASSERT_EQ(ctor.parent(), entt::resolve("clazz"_hs));
|
||||
ASSERT_EQ(ctor.size(), 1u);
|
||||
ASSERT_EQ(ctor.arity(), 1u);
|
||||
ASSERT_EQ(ctor.arg(0u), entt::resolve<int>());
|
||||
ASSERT_FALSE(ctor.arg(1u));
|
||||
|
||||
@@ -129,7 +144,7 @@ TEST_F(MetaCtor, InvalidArgs) {
|
||||
}
|
||||
|
||||
TEST_F(MetaCtor, CastAndConvert) {
|
||||
auto any = entt::resolve<clazz_t>().ctor<const base_t &, int>().invoke(derived_t{{'c'}}, 42.);
|
||||
auto any = entt::resolve<clazz_t>().ctor<const base_t &, int>().invoke(derived_t{}, 42.);
|
||||
|
||||
ASSERT_TRUE(any);
|
||||
ASSERT_EQ(any.cast<clazz_t>().i, 42);
|
||||
@@ -138,9 +153,9 @@ TEST_F(MetaCtor, CastAndConvert) {
|
||||
|
||||
TEST_F(MetaCtor, ConstNonConstRefArgs) {
|
||||
int ivalue = 42;
|
||||
char cvalue = 'c';
|
||||
const char cvalue = 'c';
|
||||
|
||||
auto any = entt::resolve<clazz_t>().ctor<int, char>().invoke(std::ref(ivalue), std::cref(cvalue));
|
||||
auto any = entt::resolve<clazz_t>().ctor<int, char>().invoke(entt::forward_as_meta(ivalue), entt::forward_as_meta(cvalue));
|
||||
|
||||
ASSERT_TRUE(any);
|
||||
ASSERT_EQ(any.cast<clazz_t>().i, 42);
|
||||
@@ -161,7 +176,7 @@ TEST_F(MetaCtor, FuncInvalidArgs) {
|
||||
}
|
||||
|
||||
TEST_F(MetaCtor, FuncCastAndConvert) {
|
||||
auto any = entt::resolve<clazz_t>().ctor<base_t, int, int>().invoke(derived_t{{'c'}}, 3., 3);
|
||||
auto any = entt::resolve<clazz_t>().ctor<base_t, int, int>().invoke(derived_t{}, 3., 3);
|
||||
|
||||
ASSERT_TRUE(any);
|
||||
ASSERT_EQ(any.cast<clazz_t>().i, 9);
|
||||
@@ -172,8 +187,8 @@ TEST_F(MetaCtor, FuncConstNonConstRefArgs) {
|
||||
int ivalue = 42;
|
||||
auto ctor = entt::resolve<clazz_t>().ctor<int>();
|
||||
|
||||
auto any = ctor.invoke(std::ref(ivalue));
|
||||
auto other = ctor.invoke(std::cref(ivalue));
|
||||
auto any = ctor.invoke(entt::forward_as_meta(ivalue));
|
||||
auto other = ctor.invoke(entt::make_meta<const int &>(ivalue));
|
||||
|
||||
ASSERT_TRUE(any);
|
||||
ASSERT_TRUE(other);
|
||||
@@ -188,7 +203,7 @@ TEST_F(MetaCtor, ExternalMemberFunction) {
|
||||
|
||||
ASSERT_TRUE(ctor);
|
||||
ASSERT_EQ(ctor.parent(), entt::resolve("clazz"_hs));
|
||||
ASSERT_EQ(ctor.size(), 4u);
|
||||
ASSERT_EQ(ctor.arity(), 4u);
|
||||
ASSERT_EQ(ctor.arg(0u), entt::resolve<entt::registry>());
|
||||
ASSERT_EQ(ctor.arg(1u), entt::resolve<entt::entity>());
|
||||
ASSERT_EQ(ctor.arg(2u), entt::resolve<int>());
|
||||
@@ -198,12 +213,76 @@ TEST_F(MetaCtor, ExternalMemberFunction) {
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
|
||||
ASSERT_FALSE(registry.has<clazz_t>(entity));
|
||||
ASSERT_FALSE(registry.all_of<clazz_t>(entity));
|
||||
|
||||
const auto any = ctor.invoke(std::ref(registry), entity, 3, 'c');
|
||||
const auto any = ctor.invoke(entt::forward_as_meta(registry), entity, 3, 'c');
|
||||
|
||||
ASSERT_TRUE(any);
|
||||
ASSERT_TRUE(registry.has<clazz_t>(entity));
|
||||
ASSERT_TRUE(registry.all_of<clazz_t>(entity));
|
||||
ASSERT_EQ(registry.get<clazz_t>(entity).i, 3);
|
||||
ASSERT_EQ(registry.get<clazz_t>(entity).c, 'c');
|
||||
}
|
||||
|
||||
TEST_F(MetaCtor, ImplicitlyGeneratedDefaultConstructor) {
|
||||
auto type = entt::resolve<int>();
|
||||
int counter{};
|
||||
|
||||
for([[maybe_unused]] auto curr: type.ctor()) {
|
||||
++counter;
|
||||
}
|
||||
|
||||
// default constructor is implicitly generated
|
||||
ASSERT_EQ(counter, 1);
|
||||
ASSERT_TRUE(type.ctor<>());
|
||||
ASSERT_EQ(type.ctor<>().arity(), 0u);
|
||||
ASSERT_EQ(type.ctor<>().arg(0), entt::meta_type{});
|
||||
|
||||
auto any = type.ctor<>().invoke();
|
||||
|
||||
ASSERT_TRUE(any);
|
||||
ASSERT_EQ(any.type(), entt::resolve<int>());
|
||||
ASSERT_EQ(any.cast<int>(), 0);
|
||||
}
|
||||
|
||||
TEST_F(MetaCtor, OverrideImplicitlyGeneratedDefaultConstructor) {
|
||||
auto type = entt::resolve<double>();
|
||||
int counter{};
|
||||
|
||||
for([[maybe_unused]] auto curr: type.ctor()) {
|
||||
++counter;
|
||||
}
|
||||
|
||||
// default constructor is implicitly generated
|
||||
ASSERT_EQ(counter, 2);
|
||||
ASSERT_TRUE(type.ctor<>());
|
||||
|
||||
auto any = type.ctor<>().invoke();
|
||||
|
||||
ASSERT_TRUE(any);
|
||||
ASSERT_EQ(any.type(), entt::resolve<double>());
|
||||
ASSERT_EQ(any.cast<double>(), 42.);
|
||||
}
|
||||
|
||||
TEST_F(MetaCtor, NonDefaultConstructibleType) {
|
||||
auto type = entt::resolve<clazz_t>();
|
||||
int counter{};
|
||||
|
||||
for([[maybe_unused]] auto curr: type.ctor()) {
|
||||
++counter;
|
||||
}
|
||||
|
||||
// the implicitly generated default constructor doesn't exist
|
||||
ASSERT_EQ(counter, 5);
|
||||
ASSERT_FALSE(type.ctor<>());
|
||||
}
|
||||
|
||||
TEST_F(MetaCtor, ReRegistration) {
|
||||
SetUp();
|
||||
|
||||
auto *node = entt::internal::meta_info<double>::resolve();
|
||||
|
||||
ASSERT_NE(node->ctor, nullptr);
|
||||
// default constructor is implicitly generated
|
||||
ASSERT_NE(node->ctor->next, nullptr);
|
||||
ASSERT_EQ(node->ctor->next->next, nullptr);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user