Compare commits
531 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e07482e26 | ||
|
|
6bb4800ecd | ||
|
|
37e1ac71b0 | ||
|
|
869f96816c | ||
|
|
35a7008444 | ||
|
|
67f80ee111 | ||
|
|
083a58753b | ||
|
|
b652357a5c | ||
|
|
49a52140b0 | ||
|
|
e37f84a227 | ||
|
|
50fc83d478 | ||
|
|
a3da1c54eb | ||
|
|
b8471e3cf1 | ||
|
|
4bd645d5c6 | ||
|
|
83446c0694 | ||
|
|
b4c30fc07b | ||
|
|
8325f5bbae | ||
|
|
9eb1196526 | ||
|
|
5fe5d4b6fc | ||
|
|
34af44b8d3 | ||
|
|
643a20fec3 | ||
|
|
576b931090 | ||
|
|
2cc1f044df | ||
|
|
c651392643 | ||
|
|
9a669176ac | ||
|
|
b902831db8 | ||
|
|
90a1de383a | ||
|
|
83f9d42d31 | ||
|
|
94136531a5 | ||
|
|
b8b1e6ba62 | ||
|
|
d6911337f3 | ||
|
|
d2cbb72490 | ||
|
|
c835b4d363 | ||
|
|
ece4480200 | ||
|
|
57f8d90e38 | ||
|
|
da2b2701fe | ||
|
|
6d7fba3abf | ||
|
|
90798c161b | ||
|
|
65e270ff57 | ||
|
|
4912e19281 | ||
|
|
6402564f44 | ||
|
|
a06d5dd7bb | ||
|
|
15e7486ca3 | ||
|
|
ac462cfce9 | ||
|
|
d921306dd8 | ||
|
|
05417705ed | ||
|
|
1c7d9e3d75 | ||
|
|
97dd928ca4 | ||
|
|
03949f7d60 | ||
|
|
effe477351 | ||
|
|
12ac858242 | ||
|
|
eec1937f52 | ||
|
|
906cd48fb7 | ||
|
|
78beecf6f1 | ||
|
|
06a0646541 | ||
|
|
0499a6c6f9 | ||
|
|
0e62f2ee85 | ||
|
|
2ba4c36af0 | ||
|
|
688e6ad79f | ||
|
|
f8ee0ce185 | ||
|
|
a226e06ae8 | ||
|
|
e0e51b51b7 | ||
|
|
9e9e3d43a8 | ||
|
|
7691a9bdad | ||
|
|
22020cae49 | ||
|
|
6cd910b0f6 | ||
|
|
b5e9811063 | ||
|
|
7668c7dd00 | ||
|
|
d71f743e85 | ||
|
|
73ef0222ac | ||
|
|
6152387096 | ||
|
|
b93ffe3b44 | ||
|
|
478eb7c5b5 | ||
|
|
6c40d8bd8b | ||
|
|
7026fe4d5a | ||
|
|
9a4491dc50 | ||
|
|
0d835e5014 | ||
|
|
29b5ffb58d | ||
|
|
a179ec485b | ||
|
|
f220a577e0 | ||
|
|
b22b1d4794 | ||
|
|
d2d19e3fbc | ||
|
|
19ef290499 | ||
|
|
f6584b45ed | ||
|
|
bad97a8b5b | ||
|
|
7116df15d6 | ||
|
|
ffbb018d12 | ||
|
|
a4dcdfc403 | ||
|
|
87d901b1ae | ||
|
|
9126651cb9 | ||
|
|
3996ce8ce5 | ||
|
|
6340423b32 | ||
|
|
0d921dc1fc | ||
|
|
25afea4f9c | ||
|
|
ba46bf3af5 | ||
|
|
416b4885f9 | ||
|
|
9a10ee0efa | ||
|
|
d63b78f0e7 | ||
|
|
99411241f6 | ||
|
|
941fb1349b | ||
|
|
a470925305 | ||
|
|
e3ac292af1 | ||
|
|
260b9a8d0d | ||
|
|
530407f9b2 | ||
|
|
52e7b50e9b | ||
|
|
23aa097d0a | ||
|
|
0bea153aa0 | ||
|
|
00be58e65d | ||
|
|
a5cc985bc2 | ||
|
|
afba54fb5e | ||
|
|
46150a2da4 | ||
|
|
842988c2ad | ||
|
|
ebad34ae51 | ||
|
|
bceb359438 | ||
|
|
19a32134ab | ||
|
|
e44a71daf2 | ||
|
|
573750a43d | ||
|
|
dd4c73b41f | ||
|
|
094326220f | ||
|
|
729a071284 | ||
|
|
ee7857a560 | ||
|
|
4cba604712 | ||
|
|
18a62797d9 | ||
|
|
5d4ac62c9e | ||
|
|
2ef8c41b48 | ||
|
|
891831a96a | ||
|
|
11d18fa1af | ||
|
|
ad69cbc681 | ||
|
|
10995de7bd | ||
|
|
c4d78c06eb | ||
|
|
eb252321a7 | ||
|
|
04dd1447ee | ||
|
|
b14edbe1f4 | ||
|
|
c697e52c0c | ||
|
|
b6911da8f0 | ||
|
|
038448865b | ||
|
|
9e44fdbc55 | ||
|
|
e088f0f31e | ||
|
|
6101feda10 | ||
|
|
7a3fa68468 | ||
|
|
16a11638a7 | ||
|
|
43932492a7 | ||
|
|
8d08ffc4ae | ||
|
|
a5a4ee4bfe | ||
|
|
e66f3a80e8 | ||
|
|
e69efacb47 | ||
|
|
528b361e34 | ||
|
|
a77bb3d341 | ||
|
|
6b17385ad7 | ||
|
|
33fc01ef4b | ||
|
|
482324c0bd | ||
|
|
0ab13a3a70 | ||
|
|
699fb73726 | ||
|
|
ddba29cc09 | ||
|
|
1cdb9ef47c | ||
|
|
ade3305b4c | ||
|
|
bd2bd37bc4 | ||
|
|
ee6f55193e | ||
|
|
1868df61ea | ||
|
|
dfa1fc7b09 | ||
|
|
a52b0495f1 | ||
|
|
ce00eee29f | ||
|
|
858bfade6a | ||
|
|
4535cf9274 | ||
|
|
84d1fa54ec | ||
|
|
7cf4721efa | ||
|
|
6578838587 | ||
|
|
46b7c4717f | ||
|
|
52bcf63554 | ||
|
|
37ec97550e | ||
|
|
55e20abea8 | ||
|
|
ec7d81dc7a | ||
|
|
e93387b719 | ||
|
|
050b476fc3 | ||
|
|
5b5ffd7eaf | ||
|
|
39bf5fdc05 | ||
|
|
09ff43ef0a | ||
|
|
5a02aeaa89 | ||
|
|
2a3a91af42 | ||
|
|
d4a64e93e0 | ||
|
|
c126b64892 | ||
|
|
526814b9ed | ||
|
|
bc3b790f13 | ||
|
|
1fb0765443 | ||
|
|
c4b85f9bda | ||
|
|
161b5530b9 | ||
|
|
e03979ebdc | ||
|
|
356c0ab594 | ||
|
|
85152bac34 | ||
|
|
c832888d56 | ||
|
|
cac1b87b44 | ||
|
|
30f71b2ed6 | ||
|
|
9f8e9a1916 | ||
|
|
8519fe57ae | ||
|
|
1442a3853c | ||
|
|
2658ddf868 | ||
|
|
f95eecfcc6 | ||
|
|
1e598cca31 | ||
|
|
f3eaeb96f0 | ||
|
|
91fac8049c | ||
|
|
b1325a7514 | ||
|
|
150b83b4f3 | ||
|
|
3a3a5dc071 | ||
|
|
9b76a5bd66 | ||
|
|
850a1f9155 | ||
|
|
ef6c8a38d0 | ||
|
|
e228cb6648 | ||
|
|
3a53cac607 | ||
|
|
df4bd57c82 | ||
|
|
de737fc72d | ||
|
|
ccea7a5783 | ||
|
|
5b84e1528a | ||
|
|
7382addfe4 | ||
|
|
b8dd6822ac | ||
|
|
8c47b85b9b | ||
|
|
4d2666a0e3 | ||
|
|
fc18eaa292 | ||
|
|
22199cdd87 | ||
|
|
76820f25c6 | ||
|
|
bac4984496 | ||
|
|
1414c5feac | ||
|
|
c344f63154 | ||
|
|
77ea28bef2 | ||
|
|
bf63b15e07 | ||
|
|
ce05504278 | ||
|
|
95b90974a2 | ||
|
|
1adbef7352 | ||
|
|
3109928dcd | ||
|
|
24dd63789c | ||
|
|
27225e6b5e | ||
|
|
2994978fdb | ||
|
|
ffd864dc72 | ||
|
|
53a2c88d4b | ||
|
|
444ae73ffb | ||
|
|
9f5f4b9c91 | ||
|
|
ffd2af0257 | ||
|
|
b983845745 | ||
|
|
cfcc6e1029 | ||
|
|
6fea4377b2 | ||
|
|
e01230e425 | ||
|
|
f66db81edd | ||
|
|
a47471d0ae | ||
|
|
06b5a91cd3 | ||
|
|
8aacf3497e | ||
|
|
7fc5e304e3 | ||
|
|
65536febd5 | ||
|
|
60f7910840 | ||
|
|
829c3c612e | ||
|
|
355c7b59aa | ||
|
|
6508cdc823 | ||
|
|
9ef9f603a3 | ||
|
|
e76f881c67 | ||
|
|
4292d2ce86 | ||
|
|
ae927c5600 | ||
|
|
57e2eed771 | ||
|
|
ca807b892f | ||
|
|
9bc015d10e | ||
|
|
59b204c3bc | ||
|
|
4ac2472a62 | ||
|
|
054b9c2402 | ||
|
|
96f267cbfd | ||
|
|
2018163107 | ||
|
|
253b5b1fb7 | ||
|
|
0be2f1b295 | ||
|
|
a8838fc611 | ||
|
|
d02636e370 | ||
|
|
f3c1b09188 | ||
|
|
7bac69f360 | ||
|
|
5a3fdd225e | ||
|
|
4660f58653 | ||
|
|
5fabca3616 | ||
|
|
edb74dae87 | ||
|
|
31b833b46a | ||
|
|
21bc1c51ba | ||
|
|
4f6cab9a45 | ||
|
|
33626fa47c | ||
|
|
9fcc1e17f2 | ||
|
|
a5f48f8af8 | ||
|
|
3dfa526ec6 | ||
|
|
dc28066017 | ||
|
|
2f2edfbde8 | ||
|
|
3beac98499 | ||
|
|
f70ce9dd26 | ||
|
|
e301b9d399 | ||
|
|
42f92ecd7a | ||
|
|
27eb0d04b9 | ||
|
|
112d13c127 | ||
|
|
16afc9d0f8 | ||
|
|
5b4889b46b | ||
|
|
f7eed0e2af | ||
|
|
cce287e8a6 | ||
|
|
9a001ebacc | ||
|
|
79ab4c02bb | ||
|
|
b6de70a3a6 | ||
|
|
15cafe2d78 | ||
|
|
90197abc5c | ||
|
|
2147436a2a | ||
|
|
8ef0c66966 | ||
|
|
2779ad6c6f | ||
|
|
f30fe3c746 | ||
|
|
f71496693f | ||
|
|
e4cd5dbf1f | ||
|
|
ffd5defcb6 | ||
|
|
0b81421124 | ||
|
|
0f73b9e07f | ||
|
|
d3078699d6 | ||
|
|
cc39f9f891 | ||
|
|
70608df843 | ||
|
|
e7f2c6b5ba | ||
|
|
420fc9aa24 | ||
|
|
200012fd41 | ||
|
|
e3c858278b | ||
|
|
1ec7c3afa4 | ||
|
|
6ab0b60f12 | ||
|
|
5ec38b44b0 | ||
|
|
39c2294d43 | ||
|
|
3b92481133 | ||
|
|
6b8d24d2f5 | ||
|
|
62d6ff61ab | ||
|
|
f7c056145a | ||
|
|
451f54b036 | ||
|
|
ef378de883 | ||
|
|
2f2b63da4a | ||
|
|
01d8f2c7e6 | ||
|
|
330d553ffb | ||
|
|
f1d0895eb1 | ||
|
|
4eaf6415b7 | ||
|
|
f0650bf64f | ||
|
|
44856c6207 | ||
|
|
dcbf6e43c7 | ||
|
|
0d22da672d | ||
|
|
a62471b9b8 | ||
|
|
f673f2c5bc | ||
|
|
a7caae5c17 | ||
|
|
d0deefd0d7 | ||
|
|
9810da6982 | ||
|
|
cb93a3bee3 | ||
|
|
aeacc3e39d | ||
|
|
e1c861cc8a | ||
|
|
37ee05a576 | ||
|
|
07a2bd817d | ||
|
|
78c171e0c2 | ||
|
|
1ae436cb08 | ||
|
|
b0d3a84d1e | ||
|
|
1acd9ca981 | ||
|
|
72467b6b9c | ||
|
|
862c85387c | ||
|
|
eeeca3e21c | ||
|
|
4ee4af7fd4 | ||
|
|
d131cc1871 | ||
|
|
bd21ee318d | ||
|
|
02e7fc0bca | ||
|
|
51ba46970e | ||
|
|
9d65b44ab7 | ||
|
|
350d51a92d | ||
|
|
2a8202caa7 | ||
|
|
9e4591554f | ||
|
|
e98ecfd1f9 | ||
|
|
1cbf5c4359 | ||
|
|
0446faeb6f | ||
|
|
0417ea23c3 | ||
|
|
de9886e011 | ||
|
|
770e57c361 | ||
|
|
d80a00701d | ||
|
|
62d3cd8795 | ||
|
|
ad9ec22fd1 | ||
|
|
293985ec10 | ||
|
|
105598ac8e | ||
|
|
ff93f2220a | ||
|
|
33fddcb289 | ||
|
|
6a09bf2c54 | ||
|
|
2913f5bbbe | ||
|
|
671bdb09b8 | ||
|
|
353a0d14a4 | ||
|
|
e0fd83211b | ||
|
|
2c94d9cc37 | ||
|
|
f7dec412ec | ||
|
|
f7c756d215 | ||
|
|
617635a989 | ||
|
|
ea3b2c4713 | ||
|
|
15455a7e68 | ||
|
|
ad5cedc08c | ||
|
|
328f0c6da8 | ||
|
|
8ef818f581 | ||
|
|
48dace64e4 | ||
|
|
ab5837c7c4 | ||
|
|
9fbdbc1844 | ||
|
|
54129cecc2 | ||
|
|
69c514d1a4 | ||
|
|
7047d12eba | ||
|
|
8ab61637f0 | ||
|
|
7e0bd92593 | ||
|
|
99d9a9f44e | ||
|
|
9c55111e14 | ||
|
|
b1d6ba57ad | ||
|
|
47e9330646 | ||
|
|
e16a3d503c | ||
|
|
dc9c93b347 | ||
|
|
faceff620a | ||
|
|
a5a6a58556 | ||
|
|
45fdab27c9 | ||
|
|
8b360479f4 | ||
|
|
b170f2f69a | ||
|
|
7f1abab9ae | ||
|
|
2ec7bc878e | ||
|
|
e97d9bb095 | ||
|
|
2f3b02e870 | ||
|
|
11b7a45c32 | ||
|
|
fb10d2f9c2 | ||
|
|
60bb7f66cf | ||
|
|
ad5143169a | ||
|
|
d68c90a429 | ||
|
|
8a51549395 | ||
|
|
9f997fe1fa | ||
|
|
00f1f6d86b | ||
|
|
be72728000 | ||
|
|
f558126854 | ||
|
|
08ed1500ac | ||
|
|
f0926f1cc7 | ||
|
|
fe3f6aa22b | ||
|
|
1325f75e81 | ||
|
|
4b1d3a7b6f | ||
|
|
404afcdfd6 | ||
|
|
99793180f1 | ||
|
|
c91f9beddb | ||
|
|
c9c5ccaa6d | ||
|
|
da42de7ac8 | ||
|
|
3c9a6ad218 | ||
|
|
f0389d6b47 | ||
|
|
76eab21738 | ||
|
|
3b32c57d94 | ||
|
|
d119032cb9 | ||
|
|
397a04b49c | ||
|
|
d32c8e9457 | ||
|
|
ebb2974ca7 | ||
|
|
cbb1131a5d | ||
|
|
d9de7fe3b0 | ||
|
|
24ba692884 | ||
|
|
960bbbde29 | ||
|
|
eb79b79c35 | ||
|
|
8212ed6d87 | ||
|
|
c639cb5285 | ||
|
|
a88fd1c669 | ||
|
|
b053f23d15 | ||
|
|
9d32a89491 | ||
|
|
cb9a147fff | ||
|
|
c9fddedbf1 | ||
|
|
530bbbe4c5 | ||
|
|
73e5a9f45b | ||
|
|
72d4e947b6 | ||
|
|
41750c3639 | ||
|
|
52b36f38a0 | ||
|
|
58ceb454dd | ||
|
|
9e5a2db4c5 | ||
|
|
781f283c89 | ||
|
|
f1cd085727 | ||
|
|
3aba855bd6 | ||
|
|
7e3abb27a4 | ||
|
|
f0c11daa37 | ||
|
|
167c174f4d | ||
|
|
0173060eaf | ||
|
|
4edd25ff5d | ||
|
|
2a4e098645 | ||
|
|
7c5019c30f | ||
|
|
c13fe3feb6 | ||
|
|
0ced60e712 | ||
|
|
d0764d5854 | ||
|
|
f7905e3bc2 | ||
|
|
3e770792a4 | ||
|
|
8f9934a7f4 | ||
|
|
b921e7d595 | ||
|
|
a9cb2d2efd | ||
|
|
fe519b3acf | ||
|
|
30cc4a1668 | ||
|
|
a71e4d8970 | ||
|
|
66e7141415 | ||
|
|
6f0cdc864f | ||
|
|
4f9deaaf09 | ||
|
|
380745a814 | ||
|
|
ce4e335412 | ||
|
|
274b8181d1 | ||
|
|
767bdcc2ce | ||
|
|
c00cde8bcc | ||
|
|
5859a18b3a | ||
|
|
375124642d | ||
|
|
af9e6dea68 | ||
|
|
78f2267b06 | ||
|
|
0a34f22240 | ||
|
|
b0651fcaed | ||
|
|
d59faa2a08 | ||
|
|
6a3bc37dc1 | ||
|
|
2ea7796b26 | ||
|
|
cf0057bac4 | ||
|
|
fafb325a88 | ||
|
|
6b69b4671f | ||
|
|
eaa95ee840 | ||
|
|
a86ba1fdf6 | ||
|
|
09e0d2d15b | ||
|
|
6d9fa6418d | ||
|
|
6010f5b7a6 | ||
|
|
9c164e1cea | ||
|
|
880fba8dce | ||
|
|
17d57ee49a | ||
|
|
234949a627 | ||
|
|
ff34671526 | ||
|
|
6e22983d98 | ||
|
|
18254c30ec | ||
|
|
922c955239 | ||
|
|
618a325057 | ||
|
|
d81cb7f866 | ||
|
|
f71a4d5381 | ||
|
|
282cb60a66 | ||
|
|
58dbac4422 | ||
|
|
53e228465b | ||
|
|
06426e4fd7 | ||
|
|
c55a97c24d | ||
|
|
0d61289bf3 | ||
|
|
bf10cbc70b | ||
|
|
2d945e426b | ||
|
|
13250887fa | ||
|
|
3507c22968 | ||
|
|
cc3f98ebcd | ||
|
|
4116e2d6ac | ||
|
|
48eab6b4a7 | ||
|
|
25866b5369 | ||
|
|
c4dd06fa45 | ||
|
|
4846d211e0 | ||
|
|
a586ad1237 | ||
|
|
b701c9c464 | ||
|
|
d0f20ed2bf | ||
|
|
0f64a2f3b0 |
12
.github/FUNDING.yml
vendored
Normal file
12
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # [skypjack] available only for those enrolled in the github sponsor program
|
||||
patreon: skypjack
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: https://www.paypal.me/skypjack
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1 +1,8 @@
|
||||
*.user
|
||||
conan/test_package/build
|
||||
|
||||
# IDEs
|
||||
.idea
|
||||
|
||||
# Bazel
|
||||
/bazel-*
|
||||
|
||||
66
.travis.yml
66
.travis.yml
@@ -2,15 +2,40 @@ language: cpp
|
||||
dist: trusty
|
||||
sudo: false
|
||||
|
||||
env:
|
||||
global:
|
||||
- CONAN_USERNAME="skypjack"
|
||||
- CONAN_PACKAGE_NAME="entt"
|
||||
- CONAN_HEADER_ONLY="True"
|
||||
- NON_CONAN_DEPLOYMENT="True"
|
||||
|
||||
conan-buildsteps: &conan-buildsteps
|
||||
before_install:
|
||||
# use this step if you desire to manipulate CONAN variables programmatically
|
||||
- NON_CONAN_DEPLOYMENT="False"
|
||||
install:
|
||||
- chmod +x ./conan/ci/install.sh
|
||||
- ./conan/ci/install.sh
|
||||
script:
|
||||
- chmod +x ./conan/ci/build.sh
|
||||
- ./conan/ci/build.sh
|
||||
# the following are dummies to overwrite default build steps
|
||||
before_script:
|
||||
- true
|
||||
after_success:
|
||||
- true
|
||||
if: tag IS present
|
||||
conan-linux: &conan-linux
|
||||
os: linux
|
||||
sudo: required
|
||||
language: python
|
||||
python: "3.6"
|
||||
services:
|
||||
- docker
|
||||
<<: *conan-buildsteps
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
compiler: gcc
|
||||
addons:
|
||||
apt:
|
||||
sources: ['ubuntu-toolchain-r-test']
|
||||
packages: ['g++-6']
|
||||
env: COMPILER=g++-6
|
||||
- os: linux
|
||||
compiler: gcc
|
||||
addons:
|
||||
@@ -22,22 +47,11 @@ matrix:
|
||||
compiler: clang
|
||||
addons:
|
||||
apt:
|
||||
sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-4.0']
|
||||
packages: ['clang-4.0', 'libstdc++-4.9-dev']
|
||||
env: COMPILER=clang++-4.0
|
||||
- os: linux
|
||||
compiler: clang
|
||||
addons:
|
||||
apt:
|
||||
sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-5.0']
|
||||
packages: ['clang-5.0', 'libstdc++-4.9-dev']
|
||||
env: COMPILER=clang++-5.0
|
||||
sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-6.0']
|
||||
packages: ['clang-6.0', 'g++-7']
|
||||
env: COMPILER=clang++-6.0
|
||||
- os: osx
|
||||
osx_image: xcode8.3
|
||||
compiler: clang
|
||||
env: COMPILER=clang++
|
||||
- os: osx
|
||||
osx_image: xcode9.4
|
||||
osx_image: xcode10
|
||||
compiler: clang
|
||||
env: COMPILER=clang++
|
||||
- os: linux
|
||||
@@ -53,6 +67,9 @@ matrix:
|
||||
- pip install --user cpp-coveralls
|
||||
after_success:
|
||||
- coveralls --gcov gcov-7 --gcov-options '\-lp' --root ${TRAVIS_BUILD_DIR} --build-root ${TRAVIS_BUILD_DIR}/build --extension cpp --extension hpp --exclude deps --include src
|
||||
# Conan testing and uploading
|
||||
- <<: *conan-linux
|
||||
env: CONAN_GCC_VERSIONS=8 CONAN_DOCKER_IMAGE=conanio/gcc8
|
||||
|
||||
notifications:
|
||||
email:
|
||||
@@ -69,11 +86,12 @@ install:
|
||||
|
||||
script:
|
||||
- mkdir -p build && cd build
|
||||
- cmake .. && make -j4
|
||||
- CTEST_OUTPUT_ON_FAILURE=1 make test
|
||||
- cmake -DBUILD_TESTING=ON -DBUILD_LIB=ON .. && make -j4
|
||||
- CTEST_OUTPUT_ON_FAILURE=1 ctest --timeout 5 -C Debug -j4
|
||||
|
||||
deploy:
|
||||
provider: script
|
||||
script: scripts/update_packages.sh $TRAVIS_TAG
|
||||
on:
|
||||
tags: true
|
||||
condition: “$NON_CONAN_DEPLOYMENT = “True”
|
||||
|
||||
38
AUTHORS
38
AUTHORS
@@ -1,11 +1,37 @@
|
||||
# Author
|
||||
|
||||
Michele Caini aka skypjack
|
||||
skypjack
|
||||
|
||||
# Contributors
|
||||
|
||||
Paolo Monteverde aka morbo84
|
||||
David Nerjes aka DavidHamburg
|
||||
Indi Kernick aka Kerndog73
|
||||
Malte Müller-Rowold aka m-waka
|
||||
Richard Caseres aka richardbmx
|
||||
BenediktConze
|
||||
bjadamson
|
||||
ceeac
|
||||
ColinH
|
||||
corystegel
|
||||
Croydon
|
||||
dbacchet
|
||||
dBagrat
|
||||
djarek
|
||||
DonKult
|
||||
drglove
|
||||
eugeneko
|
||||
gale83
|
||||
ghost
|
||||
Green-Sky
|
||||
Kerndog73
|
||||
Lawrencemm
|
||||
mhammerc
|
||||
Milerius
|
||||
morbo84
|
||||
m-waka
|
||||
NixAJ
|
||||
Paolo-Oliverio
|
||||
pgruenbacher
|
||||
prowolf
|
||||
The5-1
|
||||
vblanco20-1
|
||||
willtunnels
|
||||
WizardIke
|
||||
w1th0utnam3
|
||||
zaucy
|
||||
|
||||
9
BUILD.bazel
Normal file
9
BUILD.bazel
Normal file
@@ -0,0 +1,9 @@
|
||||
load("//bazel:copts.bzl", "entt_copts")
|
||||
|
||||
cc_library(
|
||||
name = "entt",
|
||||
visibility = ["//visibility:public"],
|
||||
strip_include_prefix = "src",
|
||||
hdrs = glob(["src/**/*.h", "src/**/*.hpp"]),
|
||||
copts = entt_copts,
|
||||
)
|
||||
@@ -16,7 +16,7 @@ endif()
|
||||
# Project configuration
|
||||
#
|
||||
|
||||
project(EnTT VERSION 2.7.2)
|
||||
project(EnTT VERSION 3.1.1)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
@@ -31,7 +31,7 @@ set(PROJECT_AUTHOR_EMAIL "michele.caini@gmail.com")
|
||||
|
||||
message("*")
|
||||
message("* ${PROJECT_NAME} v${PROJECT_VERSION} (${CMAKE_BUILD_TYPE})")
|
||||
message("* Copyright (c) 2017-2018 ${PROJECT_AUTHOR} <${PROJECT_AUTHOR_EMAIL}>")
|
||||
message("* Copyright (c) 2017-2019 ${PROJECT_AUTHOR} <${PROJECT_AUTHOR_EMAIL}>")
|
||||
message("*")
|
||||
|
||||
option(USE_LIBCPP "Use libc++ by adding -stdlib=libc++ flag if availbale." ON)
|
||||
@@ -52,7 +52,7 @@ if(NOT MSVC AND USE_LIBCPP)
|
||||
|
||||
check_cxx_source_compiles("
|
||||
#include<type_traits>
|
||||
int main() { return std::is_same<int, int>::value ? 0 : 1; }
|
||||
int main() { return std::is_same_v<int, char>; }
|
||||
" HAS_LIBCPP)
|
||||
|
||||
if(NOT HAS_LIBCPP)
|
||||
@@ -68,6 +68,8 @@ endif()
|
||||
|
||||
add_library(EnTT INTERFACE)
|
||||
|
||||
configure_file(${EnTT_SOURCE_DIR}/cmake/in/version.h.in ${EnTT_SOURCE_DIR}/src/entt/config/version.h @ONLY)
|
||||
|
||||
target_include_directories(
|
||||
EnTT INTERFACE
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src>
|
||||
@@ -100,7 +102,7 @@ if(HAS_LIBCPP)
|
||||
target_compile_options(EnTT BEFORE INTERFACE -stdlib=libc++)
|
||||
endif()
|
||||
|
||||
target_compile_features(EnTT INTERFACE cxx_std_14)
|
||||
target_compile_features(EnTT INTERFACE cxx_std_17)
|
||||
|
||||
#
|
||||
# Install EnTT
|
||||
@@ -140,7 +142,6 @@ configure_package_config_file(
|
||||
${CUSTOM_INSTALL_CONFIGDIR}/EnTTConfig.cmake
|
||||
INSTALL_DESTINATION ${CUSTOM_INSTALL_CONFIGDIR}
|
||||
PATH_VARS CMAKE_INSTALL_INCLUDEDIR
|
||||
NO_CHECK_REQUIRED_COMPONENTS_MACRO
|
||||
)
|
||||
|
||||
write_basic_package_version_file(
|
||||
@@ -162,7 +163,7 @@ export(PACKAGE EnTT)
|
||||
# Tests
|
||||
#
|
||||
|
||||
option(BUILD_TESTING "Enable testing with ctest." ON)
|
||||
option(BUILD_TESTING "Enable testing with ctest." OFF)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
@@ -180,10 +181,13 @@ if(BUILD_TESTING)
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} --build . WORKING_DIRECTORY ${GOOGLETEST_DEPS_DIR})
|
||||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||
add_subdirectory(${GOOGLETEST_DEPS_DIR}/src ${GOOGLETEST_DEPS_DIR}/build)
|
||||
target_compile_features(gmock_main PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_FEATURES>)
|
||||
target_compile_features(gmock PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_FEATURES>)
|
||||
add_library(GTest::Main ALIAS gtest_main)
|
||||
endif()
|
||||
|
||||
option(BUILD_BENCHMARK "Build benchmark." OFF)
|
||||
option(BUILD_LIB "Build lib example." OFF)
|
||||
option(BUILD_MOD "Build mod example." OFF)
|
||||
option(BUILD_SNAPSHOT "Build snapshot example." OFF)
|
||||
|
||||
@@ -214,10 +218,9 @@ add_custom_target(
|
||||
SOURCES
|
||||
appveyor.yml
|
||||
AUTHORS
|
||||
CONTRIBUTING.md
|
||||
LICENSE
|
||||
README.md
|
||||
TODO
|
||||
.travis.yml
|
||||
docs/CONTRIBUTING.md
|
||||
docs/extra.dox
|
||||
)
|
||||
|
||||
@@ -22,8 +22,8 @@ How to do it mostly depends on the type of contribution:
|
||||
If you are unable to find an open issue addressing the problem, feel free to
|
||||
[open a new one](https://github.com/skypjack/entt/issues/new). **Please**, do
|
||||
not forget to carefully describe how to reproduce the problem, then add all
|
||||
the informaion about the system on which you are experiencing it and point out
|
||||
the version of `EnTT` you are using (tag or commit).
|
||||
the information about the system on which you are experiencing it and point
|
||||
out the version of `EnTT` you are using (tag or commit).
|
||||
|
||||
* If you found a bug and you wrote a patch to fix it, open a new
|
||||
[pull request](https://github.com/skypjack/entt/pulls) with your code.
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2018 Michele Caini
|
||||
Copyright (c) 2017-2019 Michele Caini
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
42
TODO
42
TODO
@@ -1,15 +1,33 @@
|
||||
* long term feature: templated generic vm
|
||||
* long term feature: shared_ptr less locator
|
||||
* long term feature: shared_ptr less resource cache
|
||||
* custom allocators and EnTT allocator-aware in general (long term feature, I don't actually need it at the moment) - see #22
|
||||
* scene management (I prefer the concept of spaces, that is a kind of scene anyway)
|
||||
* review doc: separate it in multiple md/dox files, reduce the readme to a minimum and provide users with links to the online documentation on gh-pages
|
||||
* debugging tools (#60): the issue online already contains interesting tips on this, look at it
|
||||
* define basic reactive systems (track entities to which component is attached, track entities from which component is removed, and so on)
|
||||
* define systems as composable mixins (initializazion, reactive, update, whatever) with flexible auto-detected arguments (registry, views, etc)
|
||||
* create dedicated flat map based on types implementation (sort of "type map") for types to use within the registry and so on...
|
||||
* registry::create with a "hint" on the entity identifier to use, it should ease combining multiple registries
|
||||
* optimize for empty components, it would be a mid improvement in terms of memory usage
|
||||
* add some lazy iterative sorters like "single bubble sort loop"
|
||||
* can we do more for shared libraries? who knows... see #144
|
||||
* runner proposal: https://en.wikipedia.org/wiki/Fork%E2%80%93join_model https://slide-rs.github.io/specs/03_dispatcher.html
|
||||
* work stealing job system (see #100)
|
||||
* reflection system (maybe)
|
||||
* C++17. That's all.
|
||||
* AOB
|
||||
- mt scheduler based on const awareness for types
|
||||
* meta: sort of meta view based on meta stuff to iterate entities, void * and meta info objects
|
||||
* allow for built-in parallel each if possible
|
||||
* allow to replace std:: with custom implementations
|
||||
* remove runtime views, welcome reflection and what about snapshot?
|
||||
* types defined at runtime that refer to the same compile-time type (but to different pools) are possible, the library is almost there
|
||||
* add opaque input iterators to views and groups that return tuples <entity, T &...> (proxy), multi-pass guaranteed
|
||||
* add fast lane for raw iterations, extend mt doc to describe allowed add/remove with pre-allocations on fast lanes
|
||||
* registry.each<T...>(first, last) by iterators, entities/components guaranteed
|
||||
* multi component registry::remove and some others?
|
||||
* built-in support for dual (or N-) buffering
|
||||
* allow for custom stomp functions
|
||||
* deprecate/replace snapshot
|
||||
|
||||
TODO
|
||||
* custom (decoupled) pools ==> double buffering, shared components, multi-model
|
||||
* make meta work across boundaries
|
||||
- inline variables are fine here, only the head represents a problem
|
||||
- we should always resolve by looking into the list of types when working across boundaries, no direct resolve
|
||||
* nested groups: AB/ABC/ABCD/... (hints: sort, check functions)
|
||||
* snapshot rework/deprecation
|
||||
* create(hint: entity) -> force-create
|
||||
* assign<T...>(first, last)
|
||||
* use unordered_map for named pools and context variables:
|
||||
* use direct access (pool-like) also for context variables
|
||||
* improves multi-stomp
|
||||
|
||||
41
WORKSPACE
Normal file
41
WORKSPACE
Normal file
@@ -0,0 +1,41 @@
|
||||
workspace(name = "com_github_skypjack_entt")
|
||||
|
||||
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||
|
||||
http_archive(
|
||||
name = "com_google_googletest",
|
||||
strip_prefix = "googletest-release-1.8.1",
|
||||
url = "https://github.com/google/googletest/archive/release-1.8.1.tar.gz",
|
||||
sha256 = "9bf1fe5182a604b4135edc1a425ae356c9ad15e9b23f9f12a02e80184c3a249c",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "org_duktape",
|
||||
url = "https://duktape.org/duktape-2.4.0.tar.xz",
|
||||
strip_prefix = "duktape-2.4.0",
|
||||
sha256 = "86a89307d1633b5cedb2c6e56dc86e92679fc34b05be551722d8cc69ab0771fc",
|
||||
build_file_content = """
|
||||
cc_library(
|
||||
name = "duktape",
|
||||
visibility = ["//visibility:public"],
|
||||
strip_include_prefix = "src",
|
||||
hdrs = ["src/duktape.h", "src/duk_config.h"],
|
||||
srcs = ["src/duktape.c", "src/duktape.h", "src/duk_config.h"],
|
||||
)
|
||||
""",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "bazelregistry_cereal",
|
||||
strip_prefix = "cereal-8629f40d932d57c5337d4557327f6f22436211b7",
|
||||
url = "https://github.com/bazelregistry/cereal/archive/8629f40d932d57c5337d4557327f6f22436211b7.zip",
|
||||
sha256 = "c983a7a2e16b153c3de022a0818d2f4836e510a3fc3bea9d3703de79f58a90a6",
|
||||
)
|
||||
|
||||
# This is for bazelregistry_cereal
|
||||
http_archive(
|
||||
name = "bazelregistry_rapidjson",
|
||||
strip_prefix = "rapidjson-6b980984dacf689be8f65be823203b967c69da04",
|
||||
url = "https://github.com/bazelregistry/rapidjson/archive/6b980984dacf689be8f65be823203b967c69da04.zip",
|
||||
sha256 = "82187ba8de53bab3b4fb9e56d0bae81a96fa27a115e5a6ce14c70f0ef9338965",
|
||||
)
|
||||
34
appveyor.yml
34
appveyor.yml
@@ -1,20 +1,42 @@
|
||||
# can use variables like {build} and {branch}
|
||||
version: 1.0.{build}
|
||||
|
||||
image: Visual Studio 2017
|
||||
skip_tags: true
|
||||
|
||||
image:
|
||||
- Visual Studio 2019
|
||||
- Visual Studio 2017
|
||||
|
||||
environment:
|
||||
BUILD_DIR: "%APPVEYOR_BUILD_FOLDER%\\build"
|
||||
|
||||
platform:
|
||||
- Win32
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
|
||||
configuration:
|
||||
- Release
|
||||
- Debug
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
for:
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- image: Visual Studio 2019
|
||||
environment:
|
||||
CMAKE_GENERATOR: "Visual Studio 16 2019"
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- image: Visual Studio 2017
|
||||
environment:
|
||||
CMAKE_GENERATOR: "Visual Studio 15 2017"
|
||||
|
||||
before_build:
|
||||
- cd %BUILD_DIR%
|
||||
- cmake .. -DCMAKE_CXX_FLAGS=/D_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING -G"Visual Studio 15 2017"
|
||||
- cmake .. -DBUILD_TESTING=ON -DBUILD_LIB=ON -DCMAKE_CXX_FLAGS=/W1 -G"%CMAKE_GENERATOR%"
|
||||
|
||||
after_build:
|
||||
- ctest --timeout 5 -C Debug -j4
|
||||
|
||||
build:
|
||||
parallel: true
|
||||
|
||||
1
bazel/BUILD.bazel
Normal file
1
bazel/BUILD.bazel
Normal file
@@ -0,0 +1 @@
|
||||
exports_files(["copts.bzl"], visibility = ["//:__subpackages__"])
|
||||
10
bazel/copts.bzl
Normal file
10
bazel/copts.bzl
Normal file
@@ -0,0 +1,10 @@
|
||||
_msvc_copts = ["/std:c++17"]
|
||||
_gcc_copts = ["-std=c++17"]
|
||||
|
||||
entt_copts = select({
|
||||
# Windows
|
||||
"@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,
|
||||
})
|
||||
11
cmake/in/version.h.in
Normal file
11
cmake/in/version.h.in
Normal file
@@ -0,0 +1,11 @@
|
||||
#ifndef ENTT_CONFIG_VERSION_H
|
||||
#define ENTT_CONFIG_VERSION_H
|
||||
|
||||
|
||||
#define ENTT_VERSION "@PROJECT_VERSION@"
|
||||
#define ENTT_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
|
||||
#define ENTT_VERSION_MINOR @PROJECT_VERSION_MINOR@
|
||||
#define ENTT_VERSION_PATCH @PROJECT_VERSION_PATCH@
|
||||
|
||||
|
||||
#endif // ENTT_CONFIG_VERSION_H
|
||||
45
conan/build.py
Normal file
45
conan/build.py
Normal file
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
from cpt.packager import ConanMultiPackager
|
||||
import os
|
||||
|
||||
if __name__ == "__main__":
|
||||
login_username = os.getenv("CONAN_LOGIN_USERNAME")
|
||||
username = os.getenv("CONAN_USERNAME")
|
||||
tag_version = os.getenv("CONAN_PACKAGE_VERSION", os.getenv("TRAVIS_TAG"))
|
||||
package_version = tag_version.replace("v", "")
|
||||
package_name_unset = "SET-CONAN_PACKAGE_NAME-OR-CONAN_REFERENCE"
|
||||
package_name = os.getenv("CONAN_PACKAGE_NAME", package_name_unset)
|
||||
reference = "{}/{}".format(package_name, package_version)
|
||||
channel = os.getenv("CONAN_CHANNEL", "stable")
|
||||
upload = os.getenv("CONAN_UPLOAD")
|
||||
stable_branch_pattern = os.getenv("CONAN_STABLE_BRANCH_PATTERN", r"v\d+\.\d+\.\d+.*")
|
||||
test_folder = os.getenv("CPT_TEST_FOLDER", os.path.join("conan", "test_package"))
|
||||
upload_only_when_stable = os.getenv("CONAN_UPLOAD_ONLY_WHEN_STABLE", True)
|
||||
header_only = os.getenv("CONAN_HEADER_ONLY", False)
|
||||
pure_c = os.getenv("CONAN_PURE_C", False)
|
||||
|
||||
disable_shared = os.getenv("CONAN_DISABLE_SHARED_BUILD", "False")
|
||||
if disable_shared == "True" and package_name == package_name_unset:
|
||||
raise Exception("CONAN_DISABLE_SHARED_BUILD: True is only supported when you define CONAN_PACKAGE_NAME")
|
||||
|
||||
builder = ConanMultiPackager(username=username,
|
||||
reference=reference,
|
||||
channel=channel,
|
||||
login_username=login_username,
|
||||
upload=upload,
|
||||
stable_branch_pattern=stable_branch_pattern,
|
||||
upload_only_when_stable=upload_only_when_stable,
|
||||
test_folder=test_folder)
|
||||
if header_only == "False":
|
||||
builder.add_common_builds(pure_c=pure_c)
|
||||
else:
|
||||
builder.add()
|
||||
|
||||
filtered_builds = []
|
||||
for settings, options, env_vars, build_requires, reference in builder.items:
|
||||
if disable_shared == "False" or not options["{}:shared".format(package_name)]:
|
||||
filtered_builds.append([settings, options, env_vars, build_requires])
|
||||
builder.builds = filtered_builds
|
||||
|
||||
builder.run()
|
||||
7
conan/ci/build.sh
Normal file
7
conan/ci/build.sh
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
set -x
|
||||
|
||||
conan user
|
||||
python conan/build.py
|
||||
6
conan/ci/install.sh
Normal file
6
conan/ci/install.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
set -x
|
||||
|
||||
pip install -U conan_package_tools conan
|
||||
13
conan/test_package/CMakeLists.txt
Normal file
13
conan/test_package/CMakeLists.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
cmake_minimum_required(VERSION 3.7.2)
|
||||
project(test_package)
|
||||
|
||||
set(CMAKE_VERBOSE_MAKEFILE TRUE)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
|
||||
conan_basic_setup()
|
||||
|
||||
add_executable(${PROJECT_NAME} test_package.cpp)
|
||||
target_link_libraries(${PROJECT_NAME} ${CONAN_LIBS})
|
||||
19
conan/test_package/conanfile.py
Normal file
19
conan/test_package/conanfile.py
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from conans import ConanFile, CMake
|
||||
import os
|
||||
|
||||
|
||||
class TestPackageConan(ConanFile):
|
||||
settings = "os", "compiler", "build_type", "arch"
|
||||
generators = "cmake"
|
||||
|
||||
def build(self):
|
||||
cmake = CMake(self)
|
||||
cmake.configure()
|
||||
cmake.build()
|
||||
|
||||
def test(self):
|
||||
bin_path = os.path.join("bin", "test_package")
|
||||
self.run(bin_path, run_environment=True)
|
||||
56
conan/test_package/test_package.cpp
Normal file
56
conan/test_package/test_package.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#include <entt/entt.hpp>
|
||||
#include <cstdint>
|
||||
|
||||
struct position {
|
||||
float x;
|
||||
float y;
|
||||
};
|
||||
|
||||
struct velocity {
|
||||
float dx;
|
||||
float dy;
|
||||
};
|
||||
|
||||
void update(entt::registry ®istry) {
|
||||
auto view = registry.view<position, velocity>();
|
||||
|
||||
for(auto entity: view) {
|
||||
// gets only the components that are going to be used ...
|
||||
|
||||
auto &vel = view.get<velocity>(entity);
|
||||
|
||||
vel.dx = 0.;
|
||||
vel.dy = 0.;
|
||||
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
void update(std::uint64_t dt, entt::registry ®istry) {
|
||||
registry.view<position, velocity>().each([dt](auto &pos, auto &vel) {
|
||||
// gets all the components of the view at once ...
|
||||
|
||||
pos.x += vel.dx * dt;
|
||||
pos.y += vel.dy * dt;
|
||||
|
||||
// ...
|
||||
});
|
||||
}
|
||||
|
||||
int main() {
|
||||
entt::registry registry;
|
||||
std::uint64_t dt = 16;
|
||||
|
||||
for(auto i = 0; i < 10; ++i) {
|
||||
auto entity = registry.create();
|
||||
registry.assign<position>(entity, i * 1.f, i * 1.f);
|
||||
if(i % 2 == 0) { registry.assign<velocity>(entity, i * .1f, i * .1f); }
|
||||
}
|
||||
|
||||
update(dt, registry);
|
||||
update(registry);
|
||||
|
||||
// ...
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
27
conanfile.py
Normal file
27
conanfile.py
Normal file
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
from conans import ConanFile
|
||||
|
||||
|
||||
class EnttConan(ConanFile):
|
||||
name = "entt"
|
||||
description = "Gaming meets modern C++ - a fast and reliable entity-component system (ECS) and much more "
|
||||
topics = ("conan," "entt", "gaming", "entity", "ecs")
|
||||
url = "https://github.com/skypjack/entt"
|
||||
homepage = url
|
||||
author = "Michele Caini <michele.caini@gmail.com>"
|
||||
license = "MIT"
|
||||
exports = ["LICENSE"]
|
||||
exports_sources = ["src/*"]
|
||||
no_copy_source = True
|
||||
|
||||
def package(self):
|
||||
self.copy(pattern="LICENSE", dst="licenses")
|
||||
self.copy(pattern="*", dst="include", src="src", keep_path=True)
|
||||
|
||||
def package_info(self):
|
||||
if not self.in_local_cache:
|
||||
self.cpp_info.includedirs = ["src"]
|
||||
|
||||
def package_id(self):
|
||||
self.info.header_only()
|
||||
@@ -20,3 +20,19 @@ install(
|
||||
DIRECTORY ${DOXY_OUTPUT_DIRECTORY}/html
|
||||
DESTINATION share/${PROJECT_NAME}-${PROJECT_VERSION}/
|
||||
)
|
||||
|
||||
add_custom_target(
|
||||
docs_aob
|
||||
SOURCES
|
||||
dox/extra.dox
|
||||
md/core.md
|
||||
md/entity.md
|
||||
md/faq.md
|
||||
md/lib.md
|
||||
md/links.md
|
||||
md/locator.md
|
||||
md/meta.md
|
||||
md/process.md
|
||||
md/resource.md
|
||||
md/signal.md
|
||||
)
|
||||
|
||||
@@ -780,7 +780,9 @@ WARN_LOGFILE =
|
||||
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
|
||||
# Note: If this tag is empty the current directory is searched.
|
||||
|
||||
INPUT = @DOXY_SOURCE_DIRECTORY@ @DOXY_DOCS_DIRECTORY@ @PROJECT_SOURCE_DIR@/README.md
|
||||
INPUT = @DOXY_SOURCE_DIRECTORY@ \
|
||||
@DOXY_DOCS_DIRECTORY@ \
|
||||
@PROJECT_SOURCE_DIR@/README.md
|
||||
|
||||
# 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
|
||||
@@ -805,9 +807,7 @@ INPUT_ENCODING = UTF-8
|
||||
# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
|
||||
# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf.
|
||||
|
||||
FILE_PATTERNS = *.cpp \
|
||||
*.c++ \
|
||||
*.h \
|
||||
FILE_PATTERNS = *.h \
|
||||
*.hpp \
|
||||
*.md \
|
||||
*.dox
|
||||
|
||||
184
docs/md/core.md
Normal file
184
docs/md/core.md
Normal file
@@ -0,0 +1,184 @@
|
||||
# Crash Course: core functionalities
|
||||
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
# Table of Contents
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [Compile-time identifiers](#compile-time-identifiers)
|
||||
* [Runtime identifiers](#runtime-identifiers)
|
||||
* [Hashed strings](#hashed-strings)
|
||||
* [Wide characters](wide-characters)
|
||||
* [Conflicts](#conflicts)
|
||||
* [Monostate](#monostate)
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
|
||||
# Introduction
|
||||
|
||||
`EnTT` comes with a bunch of core functionalities mostly used by the other parts
|
||||
of the library itself.<br/>
|
||||
Hardly users will include these features in their code, but it's worth
|
||||
describing what `EnTT` offers so as not to reinvent the wheel in case of need.
|
||||
|
||||
# Compile-time identifiers
|
||||
|
||||
Sometimes it's useful to be able to give unique identifiers to types at
|
||||
compile-time.<br/>
|
||||
There are plenty of different solutions out there and I could have used one of
|
||||
them. However, I decided to spend my time to define a compact and versatile tool
|
||||
that fully embraces what the modern C++ has to offer.
|
||||
|
||||
The _result of my efforts_ is the `identifier` class template:
|
||||
|
||||
```cpp
|
||||
#include <ident.hpp>
|
||||
|
||||
// defines the identifiers for the given types
|
||||
using id = entt::identifier<a_type, another_type>;
|
||||
|
||||
// ...
|
||||
|
||||
switch(a_type_identifier) {
|
||||
case id::type<a_type>:
|
||||
// ...
|
||||
break;
|
||||
case id::type<another_type>:
|
||||
// ...
|
||||
break;
|
||||
default:
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
This is all what the class template has to offer: a `type` inline variable that
|
||||
contains a numerical identifier for the given type. It can be used in any
|
||||
context where constant expressions are required.
|
||||
|
||||
As long as the list remains unchanged, identifiers are also guaranteed to be the
|
||||
same for every run. In case they have been used in a production environment and
|
||||
a type has to be removed, one can just use a placeholder to left the other
|
||||
identifiers unchanged:
|
||||
|
||||
```cpp
|
||||
template<typename> struct ignore_type {};
|
||||
|
||||
using id = entt::identifier<
|
||||
a_type_still_valid,
|
||||
ignore_type<a_type_no_longer_valid>,
|
||||
another_type_still_valid
|
||||
>;
|
||||
```
|
||||
|
||||
A bit ugly to see, but it works at least.
|
||||
|
||||
# Runtime identifiers
|
||||
|
||||
Sometimes it's useful to be able to give unique identifiers to types at
|
||||
runtime.<br/>
|
||||
There are plenty of different solutions out there and I could have used one of
|
||||
them. In fact, I adapted the most common one to my requirements and used it
|
||||
extensively within the entire library.
|
||||
|
||||
It's the `family` class. Here is an example of use directly from the
|
||||
entity-component system:
|
||||
|
||||
```cpp
|
||||
using component_family = entt::family<struct internal_registry_component_family>;
|
||||
|
||||
// ...
|
||||
|
||||
template<typename Component>
|
||||
component_type component() const noexcept {
|
||||
return component_family::type<Component>;
|
||||
}
|
||||
```
|
||||
|
||||
This is all what a _family_ has to offer: a `type` inline variable that contains
|
||||
a numerical identifier for the given type.
|
||||
|
||||
Please, note that identifiers aren't guaranteed to be the same for every run.
|
||||
Indeed it mostly depends on the flow of execution.
|
||||
|
||||
# Hashed strings
|
||||
|
||||
A hashed string is a zero overhead unique identifier. Users can use
|
||||
human-readable identifiers in the codebase while using their numeric
|
||||
counterparts at runtime, thus without affecting performance.<br/>
|
||||
The class has an implicit `constexpr` constructor that chews a bunch of
|
||||
characters. Once created, all what one can do with it is getting back the
|
||||
original string or converting it into a number.<br/>
|
||||
The good part is that a hashed string can be used wherever a constant expression
|
||||
is required and no _string-to-number_ conversion will take place at runtime if
|
||||
used carefully.
|
||||
|
||||
Example of use:
|
||||
|
||||
```cpp
|
||||
auto load(entt::hashed_string::hash_type resource) {
|
||||
// uses the numeric representation of the resource to load and return it
|
||||
}
|
||||
|
||||
auto resource = load(entt::hashed_string{"gui/background"});
|
||||
```
|
||||
|
||||
There is also a _user defined literal_ dedicated to hashed strings to make them
|
||||
more user-friendly:
|
||||
|
||||
```cpp
|
||||
constexpr auto str = "text"_hs;
|
||||
```
|
||||
|
||||
## Wide characters
|
||||
|
||||
The hashed string has a design that is close to that of an `std::basic_string`.
|
||||
It means that `hashed_string` is nothing more than an alias for
|
||||
`basic_hashed_string<char>`. For those who want to use the C++ type for wide
|
||||
character representation, there exists also the alias `hashed_wstring` for
|
||||
`basic_hashed_string<wchar_t>`.<br/>
|
||||
In this case, the user defined literal to use to create hashed strings on the
|
||||
fly is `_hws`:
|
||||
|
||||
```cpp
|
||||
constexpr auto str = "text"_hws;
|
||||
```
|
||||
|
||||
Note that the hash type of the `hashed_wstring` is the same of its counterpart.
|
||||
|
||||
## Conflicts
|
||||
|
||||
The hashed string class uses internally FNV-1a to compute the numeric
|
||||
counterpart of a string. Because of the _pigeonhole principle_, conflicts are
|
||||
possible. This is a fact.<br/>
|
||||
There is no silver bullet to solve the problem of conflicts when dealing with
|
||||
hashing functions. In this case, the best solution seemed to be to give up.
|
||||
That's all.<br/>
|
||||
After all, human-readable unique identifiers aren't something strictly defined
|
||||
and over which users have not the control. Choosing a slightly different
|
||||
identifier is probably the best solution to make the conflict disappear in this
|
||||
case.
|
||||
|
||||
# Monostate
|
||||
|
||||
The monostate pattern is often presented as an alternative to a singleton based
|
||||
configuration system. This is exactly its purpose in `EnTT`. Moreover, this
|
||||
implementation is thread safe by design (hopefully).<br/>
|
||||
Keys are represented by hashed strings, values are basic types like `int`s or
|
||||
`bool`s. Values of different types can be associated to each key, even more than
|
||||
one at a time. Because of this, users must pay attention to use the same type
|
||||
both during an assignment and when they try to read back their data. Otherwise,
|
||||
they will probably incur in unexpected results.
|
||||
|
||||
Example of use:
|
||||
|
||||
```cpp
|
||||
entt::monostate<entt::hashed_string{"mykey"}>{} = true;
|
||||
entt::monostate<"mykey"_hs>{} = 42;
|
||||
|
||||
// ...
|
||||
|
||||
const bool b = entt::monostate<"mykey"_hs>{};
|
||||
const int i = entt::monostate<entt::hashed_string{"mykey"}>{};
|
||||
```
|
||||
1686
docs/md/entity.md
Normal file
1686
docs/md/entity.md
Normal file
File diff suppressed because it is too large
Load Diff
163
docs/md/faq.md
Normal file
163
docs/md/faq.md
Normal file
@@ -0,0 +1,163 @@
|
||||
# Frequently Asked Questions
|
||||
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
# Table of Contents
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [FAQ](#faq)
|
||||
* [Why is my debug build on Windows so slow?](#why-is-my-debug-build-on-windows-so-slow)
|
||||
* [How can I represent hierarchies with my components?](#how-can-i-represent-hierarchies-with-my-components)
|
||||
* [Custom entity identifiers: yay or nay?](#custom-entity-identifiers-yay-or-nay)
|
||||
* [Warning C4307: integral constant overflow](#warning-C4307-integral-constant-overflow)
|
||||
* [Warning C4003: the min, the max and the macro](#warning-C4003-the-min-the-max-and-the-macro)
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
|
||||
# Introduction
|
||||
|
||||
This is a constantly updated section where I'll try to put the answers to the
|
||||
most frequently asked questions.<br/>
|
||||
If you don't find your answer here, there are two cases: nobody has done it yet
|
||||
or this section needs updating. In both cases, try to
|
||||
[open a new issue](https://github.com/skypjack/entt/issues/new) or enter the
|
||||
[gitter channel](https://gitter.im/skypjack/entt) and ask your question.
|
||||
Probably someone already has an answer for you and we can then integrate this
|
||||
part of the documentation.
|
||||
|
||||
# FAQ
|
||||
|
||||
## Why is my debug build on Windows so slow?
|
||||
|
||||
`EnTT` is an experimental project that I also use to keep me up-to-date with the
|
||||
latest revision of the language and the standard library. For this reason, it's
|
||||
likely that some classes you're working with are using standard containers under
|
||||
the hood.<br/>
|
||||
Unfortunately, it's known that the standard containers aren't particularly
|
||||
performing in debugging (the reasons for this go beyond this document) and are
|
||||
even less so on Windows apparently. Fortunately this can also be mitigated a
|
||||
lot, achieving good results in many cases.
|
||||
|
||||
First of all, there are two things to do in a Windows project:
|
||||
|
||||
* Disable the [`/JMC`](https://docs.microsoft.com/cpp/build/reference/jmc)
|
||||
option (_Just My Code_ debugging), available starting in Visual Studio 2017
|
||||
version 15.8.
|
||||
|
||||
* Set the [`_ITERATOR_DEBUG_LEVEL`](https://docs.microsoft.com/cpp/standard-library/iterator-debug-level)
|
||||
macro to 0. This will disable checked iterators and iterator debugging.
|
||||
|
||||
Moreover, the macro `ENTT_DISABLE_ASSERT` should be defined to disable internal
|
||||
checks made by `EnTT` in debug. These are asserts introduced to help the users,
|
||||
but require to access to the underlying containers and therefore risk ruining
|
||||
the performance in some cases.
|
||||
|
||||
With these changes, debug performance should increase enough for most cases. If
|
||||
you want something more, you can can also switch to an optimization level `O0`
|
||||
or preferably `O1`.
|
||||
|
||||
## How can I represent hierarchies with my components?
|
||||
|
||||
This is one of the first questions that anyone makes when starting to work with
|
||||
the entity-component-system architectural pattern.<br/>
|
||||
There are several approaches to the problem and what’s the best one depends
|
||||
mainly on the real problem one is facing. In all cases, how to do it doesn't
|
||||
strictly depend on the library in use, but the latter can certainly allow or
|
||||
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.
|
||||
|
||||
## Custom entity identifiers: yay or nay?
|
||||
|
||||
Custom entity identifiers are definitely a good idea in two cases at least:
|
||||
|
||||
* If `std::uint32_t` isn't large enough as an underlying type.
|
||||
* If you want to avoid conflicts when using multiple registries.
|
||||
|
||||
These identifiers are nothing more than enum classes with some salt.<br/>
|
||||
To simplify the creation of new identifiers, `EnTT` provides the macro
|
||||
`ENTT_OPAQUE_TYPE` that accepts two arguments:
|
||||
|
||||
* The name you want to give to the new identifier (watch out for namespaces).
|
||||
* The underlying type to use (either `std::uint16_t`, `std::uint32_t`
|
||||
or `std::uint64_t`).
|
||||
|
||||
In fact, this is the definition of `entt::entity`:
|
||||
|
||||
```cpp
|
||||
ENTT_OPAQUE_TYPE(entity, std::uint32_t)
|
||||
```
|
||||
|
||||
The use of this macro is highly recommended, so as not to run into problems if
|
||||
the requirements for the identifiers should change in the future.
|
||||
|
||||
## Warning C4307: integral constant overflow
|
||||
|
||||
According to [this](https://github.com/skypjack/entt/issues/121) issue, using a
|
||||
hashed string under VS could generate a warning.<br/>
|
||||
First of all, I want to reassure you: it's expected and harmless. However, it
|
||||
can be annoying.
|
||||
|
||||
To suppress it and if you don't want to suppress all the other warnings as well,
|
||||
here is a workaround in the form of a macro:
|
||||
|
||||
```cpp
|
||||
#if defined(_MSC_VER)
|
||||
#define HS(str)\
|
||||
__pragma(warning(push))\
|
||||
__pragma(warning(disable:4307))\
|
||||
entt::hashed_string{str}\
|
||||
__pragma(warning(pop))
|
||||
#else
|
||||
#define HS(str) entt::hashed_string{str}
|
||||
#endif
|
||||
```
|
||||
|
||||
With an example of use included:
|
||||
|
||||
```cpp
|
||||
constexpr auto identifier = HS("my/resource/identifier");
|
||||
```
|
||||
|
||||
Thanks to [huwpascoe](https://github.com/huwpascoe) for the courtesy.
|
||||
|
||||
## Warning C4003: the min, the max and the macro
|
||||
|
||||
On Windows, a header file defines two macros `min` and `max` which may result in
|
||||
conflicts with their counterparts in the standard library and therefore in
|
||||
errors during compilation.
|
||||
|
||||
It's a pretty big problem but fortunately it's not a problem of `EnTT` and there
|
||||
is a fairly simple solution to it.<br/>
|
||||
It consists in defining the `NOMINMAX` macro before to include any other header
|
||||
so as to get rid of the extra definitions:
|
||||
|
||||
```cpp
|
||||
#define NOMINMAX
|
||||
```
|
||||
|
||||
Please refer to [this](https://github.com/skypjack/entt/issues/96) issue for
|
||||
more details.
|
||||
155
docs/md/lib.md
Normal file
155
docs/md/lib.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# Push EnTT across boundaries
|
||||
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
# Table of Contents
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [Named types and traits class](#named-types-and-traits-class)
|
||||
* [Do not mix types](#do-not-mix-types)
|
||||
* [Macros, macros everywhere](#macros-macros-everywhere)
|
||||
* [Conflicts](#conflicts)
|
||||
* [Allocations: the dark side of the force](#allocations-the-dark-side-of-the-force)
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
|
||||
# Introduction
|
||||
|
||||
`EnTT` has historically had a limit when used across boundaries on Windows in
|
||||
general and on GNU/Linux when default visibility was set to _hidden_. The
|
||||
limitation is due mainly to a custom utility used to assign unique, sequential
|
||||
identifiers to different types. Unfortunately, this tool is used by several core
|
||||
classes (the `registry` among the others) that are thus almost unusable across
|
||||
boundaries.<br/>
|
||||
The reasons for that are beyond the purposes of this document. However, the good
|
||||
news is that `EnTT` also offers now a way to overcome this limit and to push
|
||||
things across boundaries without problems when needed.
|
||||
|
||||
# Named types and traits class
|
||||
|
||||
To allow a type to work properly across boundaries when used by a class that
|
||||
requires to assign unique identifiers to types, users must specialize a class
|
||||
template to literally give a compile-time name to the type itself.<br/>
|
||||
The name of the class template is `name_type_traits` and the specialization must
|
||||
be such that it exposes a static constexpr data member named `value` having type
|
||||
either `ENTT_ID_TYPE` or `entt::hashed_string::hash_type`. Its value is the user
|
||||
defined unique identifier assigned to the specific type.<br/>
|
||||
Identifiers are not to be sequentially generated in this case.
|
||||
|
||||
As an example:
|
||||
|
||||
```cpp
|
||||
struct my_type { /* ... */ };
|
||||
|
||||
template<>
|
||||
struct entt::named_type_traits<my_type> {
|
||||
static constexpr auto value = "my_type"_hs;
|
||||
};
|
||||
```
|
||||
|
||||
Because of the rules of the language, the specialization must reside in the
|
||||
global namespace or in the `entt` namespace. There is no way to change this rule
|
||||
unfortunately, because it doesn't depend on the library itself.
|
||||
|
||||
The good aspect of this approach is that it's not intrusive at all. The other
|
||||
way around was in fact forcing users to inherit all their classes from a common
|
||||
base. Something to avoid, at least from my point of view.<br/>
|
||||
However, despite the fact that it's not intrusive, it would be great if it was
|
||||
also easier to use and a bit less error-prone. This is why a bunch of macros
|
||||
exist to ease defining named types.
|
||||
|
||||
## Do not mix types
|
||||
|
||||
Someone might think that this trick is valid only for the types to push across
|
||||
boundaries. This isn't how things work. In fact, the problem is more complex
|
||||
than that.<br/>
|
||||
As a rule of thumb, users should never mix named and non-named types. Whenever
|
||||
a type is given a name, all the types must be given a name. As an example,
|
||||
consider the `registry` class template: in case it is pushed across boundaries,
|
||||
all the types of components should be assigned a name to avoid subtle bugs.
|
||||
|
||||
Indeed, this constraint can be relaxed in many cases. However, it is difficult
|
||||
to define a general rule to follow that is not the most stringent, unless users
|
||||
know exactly what they are doing. Therefore, I won't elaborate on giving further
|
||||
details on the topic.
|
||||
|
||||
# Macros, macros everywhere
|
||||
|
||||
The library comes with a set of predefined macros to use to declare named types
|
||||
or export already existing ones. In particular:
|
||||
|
||||
* `ENTT_NAMED_TYPE` can be used to assign a name to already existing types. This
|
||||
macro must be used in the global namespace even when the types to be named are
|
||||
not.
|
||||
|
||||
```cpp
|
||||
ENTT_NAMED_TYPE(my_type)
|
||||
ENTT_NAMED_TYPE(ns::another_type)
|
||||
```
|
||||
|
||||
* `ENTT_NAMED_STRUCT` can be used to define and export a struct at the same
|
||||
time. It accepts also an optional namespace in which to define the given type.
|
||||
This macro must be used in the global namespace.
|
||||
|
||||
```cpp
|
||||
ENTT_NAMED_STRUCT(my_type, { /* struct definition */})
|
||||
ENTT_NAMED_STRUCT(ns, another_type, { /* struct definition */})
|
||||
```
|
||||
|
||||
* `ENTT_NAMED_CLASS` can be used to define and export a class at the same
|
||||
time. It accepts also an optional namespace in which to define the given type.
|
||||
This macro must be used in the global namespace.
|
||||
|
||||
```cpp
|
||||
ENTT_NAMED_CLASS(my_type, { /* class definition */})
|
||||
ENTT_NAMED_CLASS(ns, another_type, { /* class definition */})
|
||||
```
|
||||
|
||||
Nested namespaces are supported out of the box as well in all cases. As an
|
||||
example:
|
||||
|
||||
```cpp
|
||||
ENTT_NAMED_STRUCT(nested::ns, my_type, { /* struct definition */})
|
||||
```
|
||||
|
||||
These macros can be used to avoid specializing the `named_type_traits` class
|
||||
template. In all cases, the name of the class is used also as a seed to generate
|
||||
the compile-time unique identifier.
|
||||
|
||||
## Conflicts
|
||||
|
||||
When using macros, unique identifiers are 32/64 bit integers generated by
|
||||
hashing strings during compilation. Therefore, conflicts are rare but still
|
||||
possible. In case of conflicts, everything simply will get broken at runtime and
|
||||
the strangest things will probably take place.<br/>
|
||||
Unfortunately, there is no safe way to prevent it. If this happens, it will be
|
||||
enough to give a different value to one of the conflicting types to solve the
|
||||
problem. To do this, users can either assign a different name to the class or
|
||||
directly define a specialization for the `named_type_traits` class template.
|
||||
|
||||
# Allocations: the dark side of the force
|
||||
|
||||
As long as `EnTT` won't support custom allocators, another problem with
|
||||
allocations will remain alive instead. This is in fact easily solved, or at
|
||||
least it is if one knows it.
|
||||
|
||||
To allow users to add types dynamically, the library makes extensive use of type
|
||||
erasure techniques and dynamic allocations for pools (whether they are for
|
||||
components, events or anything else). The problem occurs when, for example, a
|
||||
registry is created on one side of a boundary and a pool is dynamically created
|
||||
on the other side. In the best case, everything will crash at the exit, while at
|
||||
worst it will do so at runtime.<br/>
|
||||
To avoid problems, the pools must be generated from the same side of the
|
||||
boundary where the object that owns them is also created. As an example, when
|
||||
the registry is created in the main executable and used across boundaries for a
|
||||
given type of component, the pool for that type must be created before passing
|
||||
around the registry itself. To do this is fortunately quite easy, since it is
|
||||
sufficient to invoke any of the methods that involve the given type (continuing
|
||||
the example with the registry, a call to `reserve` or `size` is more than
|
||||
enough).
|
||||
|
||||
Maybe one day some dedicated methods will be added that do nothing but create a
|
||||
pool for a given type. Until now it has been preferred to keep the API cleaner
|
||||
as they are not strictly necessary.
|
||||
100
docs/md/links.md
Normal file
100
docs/md/links.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# EnTT in Action
|
||||
|
||||
`EnTT` is widely used in private and commercial applications. I cannot even
|
||||
mention most of them because of some signatures I put on some documents time
|
||||
ago. Fortunately, there are also people who took the time to implement open
|
||||
source projects based on `EnTT` and did not hold back when it came to
|
||||
documenting them.
|
||||
|
||||
Below an incomplete list of games, applications and articles that can be used as
|
||||
a reference. Where I put the word _apparently_ means that the use of `EnTT` is
|
||||
documented but the authors didn't make explicit announcements or contacted me
|
||||
directly.
|
||||
|
||||
I hope this list can grow much more in the future:
|
||||
|
||||
* Games:
|
||||
* [Minecraft](https://minecraft.net/en-us/attribution/) by
|
||||
[Mojang](https://mojang.com/): of course, **that** Minecraft, see the
|
||||
open source attributions page for more details.
|
||||
* [Face Smash](https://play.google.com/store/apps/details?id=com.gamee.facesmash):
|
||||
a game to play with your face.
|
||||
* [EnTT Pacman](https://github.com/Kerndog73/EnTT-Pacman): an example of how
|
||||
to make Pacman with `EnTT`.
|
||||
* [Wacman](https://github.com/carlfindahl/wacman): a pacman clone with OpenGL.
|
||||
* [Classic Tower Defence](https://github.com/kerndog73/Classic-Tower-Defence):
|
||||
a tiny little tower defence game featuring a homemade font.
|
||||
[Check it out](https://indi-kernick.itch.io/classic-tower-defence).
|
||||
* [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](https://github.com/reworks/EnttPong): an example of how to make
|
||||
Pong with `EnTT`.
|
||||
* [Randballs](https://github.com/gale93/randballs): simple `SFML` and `EnTT`
|
||||
playground.
|
||||
* [EnTT Tower Defense](https://github.com/Daivuk/tddod): a data oriented tower
|
||||
defense example.
|
||||
* [EnTT Breakout](https://github.com/vblanco20-1/entt-breakout): simple
|
||||
example of a breakout game, using `SDL` and `EnTT`.
|
||||
* [Arcade puzzle game with EnTT](https://github.com/MasonRG/ArcadePuzzleGame):
|
||||
arcade puzzle game made in C++ using the `SDL2` and `EnTT` libraries.
|
||||
* [Snake with EnTT](https://github.com/MasonRG/SnakeGame): simple snake game
|
||||
made in C++ with the `SDL2` and `EnTT` libraries.
|
||||
|
||||
* Engines and the like:
|
||||
* [Apparently](https://teamwisp.github.io/credits/)
|
||||
[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.
|
||||
* [starlight](https://github.com/DomRe/starlight): game programming framework
|
||||
using `Allegro`, `Lua` and modern C++.
|
||||
* [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.
|
||||
* [NovusCore](https://github.com/novuscore/NovusCore): A modern take on World
|
||||
of Warcraft emulation.
|
||||
* [ImGui/EnTT editor](https://github.com/Green-Sky/imgui_entt_entity_editor):
|
||||
A drop-in, single-file entity editor for `EnTT` that uses `ImGui` as
|
||||
graphical backend (with
|
||||
[demo code](https://github.com/Green-Sky/imgui_entt_entity_editor_demo)).
|
||||
|
||||
* Articles and blog posts
|
||||
* [Some posts](https://skypjack.github.io/tags/#entt) on my personal
|
||||
[blog](https://skypjack.github.io/) are about `EnTT`, for those who want to
|
||||
know **more** on this project.
|
||||
* [Space Battle: Huge edition](http://victor.madtriangles.com/code%20experiment/2018/06/11/post-ecs-battle-huge.html):
|
||||
huge space battle built entirely from scratch.
|
||||
* [Space Battle](https://github.com/vblanco20-1/ECS_SpaceBattle): huge space
|
||||
battle built on `UE4`.
|
||||
* [Experimenting with ECS in UE4](http://victor.madtriangles.com/code%20experiment/2018/03/25/post-ue4-ecs-battle.html):
|
||||
interesting article about `UE4` and `EnTT`.
|
||||
* [Implementing ECS architecture in UE4](https://forums.unrealengine.com/development-discussion/c-gameplay-programming/1449913-implementing-ecs-architecture-in-ue4-giant-space-battle):
|
||||
giant space battle.
|
||||
* [Conan Adventures (SFML and EnTT in C++)](https://leinnan.github.io/blog/conan-adventuressfml-and-entt-in-c.html):
|
||||
create projects in modern C++ using `SFML`, `EnTT`, `Conan` and `CMake`.
|
||||
|
||||
* Any Other Business:
|
||||
* The [ArcGIS Runtime SDKs](https://developers.arcgis.com/arcgis-runtime/)
|
||||
by [Esri](https://www.esri.com/): they use `EnTT` for the internal ECS and
|
||||
the cross platform C++ rendering engine. The SDKs are utilized by a lot of
|
||||
enterprise custom apps, as well as by Esri for its own public applications
|
||||
such as
|
||||
[Explorer](https://play.google.com/store/apps/details?id=com.esri.explorer),
|
||||
[Collector](https://play.google.com/store/apps/details?id=com.esri.arcgis.collector)
|
||||
and
|
||||
[Navigator](https://play.google.com/store/apps/details?id=com.esri.navigator).
|
||||
* [Apparently](https://www.linkedin.com/in/skypjack/)
|
||||
[NIO](https://www.nio.io/): there was a collaboration to make some changes
|
||||
to `EnTT`, at the time used for internal projects.
|
||||
* [Apparently](https://www.linkedin.com/jobs/view/architekt-c%2B%2B-at-tieto-1219512333/)
|
||||
[Tieto](https://www.tieto.com/): they published a job post where `EnTT` was
|
||||
listed on their software stack.
|
||||
* [MatchOneEntt](https://github.com/mhaemmerle/MatchOneEntt): port of
|
||||
[Match One](https://github.com/sschmid/Match-One) for `Entitas-CSharp`.
|
||||
* GitHub contains also
|
||||
[many other examples](https://github.com/search?o=desc&q=%22skypjack%2Fentt%22&s=indexed&type=Code)
|
||||
of use of `EnTT` from which to take inspiration if interested.
|
||||
|
||||
If you know of other resources out there that are about `EnTT`, feel free to
|
||||
open an issue or a PR and I'll be glad to add them to this page.
|
||||
75
docs/md/locator.md
Normal file
75
docs/md/locator.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Crash Course: service locator
|
||||
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
# Table of Contents
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [Service locator](#service-locator)
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
|
||||
# Introduction
|
||||
|
||||
Usually service locators are tightly bound to the services they expose and it's
|
||||
hard to define a general purpose solution. This template based implementation
|
||||
tries to fill the gap and to get rid of the burden of defining a different
|
||||
specific locator for each application.<br/>
|
||||
This class is tiny, partially unsafe and thus risky to use. Moreover it doesn't
|
||||
fit probably most of the scenarios in which a service locator is required. Look
|
||||
at it as a small tool that can sometimes be useful if users know how to handle
|
||||
it.
|
||||
|
||||
# Service locator
|
||||
|
||||
The API is straightforward. The basic idea is that services are implemented by
|
||||
means of interfaces and rely on polymorphism.<br/>
|
||||
The locator is instantiated with the base type of the service if any and a
|
||||
concrete implementation is provided along with all the parameters required to
|
||||
initialize it. As an example:
|
||||
|
||||
```cpp
|
||||
// the service has no base type, a locator is used to treat it as a kind of singleton
|
||||
entt::service_locator<my_service>::set(params...);
|
||||
|
||||
// sets up an opaque service
|
||||
entt::service_locator<audio_interface>::set<audio_implementation>(params...);
|
||||
|
||||
// resets (destroys) the service
|
||||
entt::service_locator<audio_interface>::reset();
|
||||
```
|
||||
|
||||
The locator can also be queried to know if an active service is currently set
|
||||
and to retrieve it if necessary (either as a pointer or as a reference):
|
||||
|
||||
```cpp
|
||||
// no service currently set
|
||||
auto empty = entt::service_locator<audio_interface>::empty();
|
||||
|
||||
// gets a (possibly empty) shared pointer to the service ...
|
||||
std::shared_ptr<audio_interface> ptr = entt::service_locator<audio_interface>::get();
|
||||
|
||||
// ... or a reference, but it's undefined behaviour if the service isn't set yet
|
||||
audio_interface &ref = entt::service_locator<audio_interface>::ref();
|
||||
```
|
||||
|
||||
A common use is to wrap the different locators in a container class, creating
|
||||
aliases for the various services:
|
||||
|
||||
```cpp
|
||||
struct locator {
|
||||
using camera = entt::service_locator<camera_interface>;
|
||||
using audio = entt::service_locator<audio_interface>;
|
||||
// ...
|
||||
};
|
||||
|
||||
// ...
|
||||
|
||||
void init() {
|
||||
locator::camera::set<camera_null>();
|
||||
locator::audio::set<audio_implementation>(params...);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
511
docs/md/meta.md
Normal file
511
docs/md/meta.md
Normal file
@@ -0,0 +1,511 @@
|
||||
# Crash Course: runtime reflection system
|
||||
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
# Table of Contents
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [Names and identifiers](#names-and-identifiers)
|
||||
* [Reflection in a nutshell](#reflection-in-a-nutshell)
|
||||
* [Any as in any type](#any-as-in-any-type)
|
||||
* [Enjoy the runtime](#enjoy-the-runtime)
|
||||
* [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)
|
||||
* [Unregister types](#unregister-types)
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
|
||||
# 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
|
||||
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/>
|
||||
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
|
||||
which it belongs and not vice versa.
|
||||
|
||||
# Names and identifiers
|
||||
|
||||
The meta system doesn't force the user to use the tools provided by the library
|
||||
when it comes to working with names and identifiers. It does this by offering an
|
||||
API that works with opaque identifiers that may or may not be generated by means
|
||||
of a hashed string.<br/>
|
||||
This means that users can assign any type of identifier to the meta objects, as
|
||||
long as they are numeric. It doesn't matter if they are generated at runtime, at
|
||||
compile-time or with custom functions.
|
||||
|
||||
However, 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
|
||||
follows:
|
||||
|
||||
```cpp
|
||||
auto factory = entt::reflect<my_type>("reflected_type"_hs);
|
||||
```
|
||||
|
||||
For what it's worth, this is likely completely equivalent to:
|
||||
|
||||
```cpp
|
||||
auto factory = entt::reflect<my_type>(42);
|
||||
```
|
||||
|
||||
Obviously, human-readable identifiers are more convenient to use and highly
|
||||
recommended.
|
||||
|
||||
# Reflection in a nutshell
|
||||
|
||||
Reflection always starts from real types (users cannot reflect imaginary types
|
||||
and it would not make much sense, we wouldn't be talking about reflection
|
||||
anymore).<br/>
|
||||
To _reflect_ a type, the library provides the `reflect` function:
|
||||
|
||||
```cpp
|
||||
auto factory = entt::reflect<my_type>("reflected_type"_hs);
|
||||
```
|
||||
|
||||
It accepts the type to reflect as a template parameter and an optional
|
||||
identifier as an argument. Identifiers are important because users can retrieve
|
||||
meta types at runtime by searching for them by _name_. However, there are cases
|
||||
in which users can be interested in adding features to a reflected type so that
|
||||
the reflection system can use it correctly under the hood, but they don't want
|
||||
to allow searching the type by _name_.<br/>
|
||||
In both cases, the returned value is a factory object to use to continue
|
||||
building the meta type.
|
||||
|
||||
A factory is such that all its member functions returns the factory itself.
|
||||
It can be used to extend the reflected type and 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
|
||||
used as well, as long as the return type is the expected one. From a client's
|
||||
point of view, nothing changes if a constructor is a free function or an
|
||||
actual constructor.<br/>
|
||||
Use the `ctor` member function for this purpose:
|
||||
|
||||
```cpp
|
||||
entt::reflect<my_type>("reflected"_hs).ctor<int, char>().ctor<&factory>();
|
||||
```
|
||||
|
||||
* _Destructors_. Free functions can be set as destructors of reflected types.
|
||||
The purpose is to give users the ability to free up resources that require
|
||||
special treatment before an object is actually destroyed.<br/>
|
||||
Use the `dtor` member function for this purpose:
|
||||
|
||||
```cpp
|
||||
entt::reflect<my_type>("reflected"_hs).dtor<&destroy>();
|
||||
```
|
||||
|
||||
A function should neither delete nor explicitly invoke the destructor of a
|
||||
given instance.
|
||||
|
||||
* _Data members_. Both real data members of the underlying type and static and
|
||||
global variables, as well as constants of any kind, can be attached to a meta
|
||||
type. From a client's point of view, all the variables associated with the
|
||||
reflected type will appear as if they were part of the type itself.<br/>
|
||||
Use the `data` member function for this purpose:
|
||||
|
||||
```cpp
|
||||
entt::reflect<my_type>("reflected"_hs)
|
||||
.data<&my_type::static_variable>("static"_hs)
|
||||
.data<&my_type::data_member>("member"_hs)
|
||||
.data<&global_variable>("global"_hs);
|
||||
```
|
||||
|
||||
This 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 be set also by means of a couple of functions, namely a
|
||||
setter and a getter. Setters and getters can be either free functions, member
|
||||
functions or mixed ones, as long as they respect the required signatures.<br/>
|
||||
Refer to the inline documentation for all the details.
|
||||
|
||||
* _Member functions_. Both real member functions of the underlying type and free
|
||||
functions can be attached to a meta type. From a client's point of view, all
|
||||
the functions associated with the reflected type will appear as if they were
|
||||
part of the type itself.<br/>
|
||||
Use the `func` member function for this purpose:
|
||||
|
||||
```cpp
|
||||
entt::reflect<my_type>("reflected"_hs)
|
||||
.func<&my_type::static_function>("static"_hs)
|
||||
.func<&my_type::member_function>("member"_hs)
|
||||
.func<&free_function>("free"_hs);
|
||||
```
|
||||
|
||||
This 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_.
|
||||
|
||||
* _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
|
||||
and allows for implicit casts at runtime when required.<br/>
|
||||
Use the `base` member function for this purpose:
|
||||
|
||||
```cpp
|
||||
entt::reflect<derived_type>("derived"_hs).base<base_type>();
|
||||
```
|
||||
|
||||
From now on, wherever a `base_type` is required, an instance of `derived_type`
|
||||
will also be accepted.
|
||||
|
||||
* _Conversion functions_. Actual types can be converted, this is a fact. Just
|
||||
think of the relationship between a `double` and an `int` to see it. Similar
|
||||
to bases, conversion functions allow users to define conversions that will be
|
||||
implicitly performed by the reflection system when required.<br/>
|
||||
Use the `conv` member function for this purpose:
|
||||
|
||||
```cpp
|
||||
entt::reflect<double>().conv<int>();
|
||||
```
|
||||
|
||||
That's all, everything users need to create meta types and enjoy the reflection
|
||||
system. At first glance it may not seem that much, but users usually learn to
|
||||
appreciate it over time.<br/>
|
||||
Also, do not forget what these few lines hide under the hood: a built-in,
|
||||
non-intrusive and macro-free system for reflection in C++. Features that are
|
||||
definitely worth the price, at least for me.
|
||||
|
||||
## Any as in any type
|
||||
|
||||
The reflection system comes with its own meta any type. It may seem redundant
|
||||
since C++17 introduced `std::any`, but it is not.<br/>
|
||||
In fact, the _type_ returned by an `std::any` is a const reference to an
|
||||
`std::type_info`, an implementation defined class that's not something everyone
|
||||
wants to see in a software. Furthermore, the class `std::type_info` suffers from
|
||||
some design flaws and there is even no way to _convert_ an `std::type_info` into
|
||||
a meta type, thus linking the two worlds.
|
||||
|
||||
A meta any object provides an API similar to that of its most famous counterpart
|
||||
and serves the same purpose of being an opaque container for any type of
|
||||
value.<br/>
|
||||
It minimizes the allocations required, which are almost absent thanks to _SBO_
|
||||
techniques. In fact, unless users deal with _fat types_ and create instances of
|
||||
them though the reflection system, allocations are at zero.
|
||||
|
||||
A meta any object can be created by any other object or as an empty container
|
||||
to initialize later:
|
||||
|
||||
```cpp
|
||||
// a meta any object that contains an int
|
||||
entt::meta_any any{0};
|
||||
|
||||
// an empty meta any object
|
||||
entt::meta_any empty{};
|
||||
```
|
||||
|
||||
It takes the burden of destroying the contained instance when required.<br/>
|
||||
Moreover, it can be used as an opaque container for unmanaged objects if needed:
|
||||
|
||||
```cpp
|
||||
int value;
|
||||
entt::meta_any any{entt::as_alias, value};
|
||||
```
|
||||
|
||||
In this case, the contained instance is never destroyed and users must ensure
|
||||
that the lifetime of the object exceeds that of the container.
|
||||
|
||||
A meta any object has a `type` member function that returns the meta type of the
|
||||
contained value, if any. The member functions `try_cast`, `cast` and `convert`
|
||||
are used to know if the underlying object has a given type as a base or if it
|
||||
can be converted implicitly to it.
|
||||
|
||||
## Enjoy the runtime
|
||||
|
||||
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.
|
||||
|
||||
To search for a reflected type there are two options: by type or by _name_. In
|
||||
both cases, the search can be done by means of the `resolve` function:
|
||||
|
||||
```cpp
|
||||
// search for a reflected type by type
|
||||
auto by_type = entt::resolve<my_type>();
|
||||
|
||||
// search for a reflected type by name
|
||||
auto by_name = entt::resolve("reflected_type"_hs);
|
||||
```
|
||||
|
||||
There exits also a third overload of the `resolve` function to use to iterate
|
||||
all the reflected types at once:
|
||||
|
||||
```cpp
|
||||
resolve([](auto type) {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
In all cases, the returned value is an instance of `meta_type`. This type of
|
||||
objects offer an API to know the _runtime identifier_ of the type, to iterate
|
||||
all the meta objects associated with them and even to build or destroy instances
|
||||
of the underlying type.<br/>
|
||||
Refer to the inline documentation for all the details.
|
||||
|
||||
The meta objects that compose a meta type are accessed in the following ways:
|
||||
|
||||
* _Meta constructors_. They are accessed by types of arguments:
|
||||
|
||||
```cpp
|
||||
auto ctor = entt::resolve<my_type>().ctor<int, char>();
|
||||
```
|
||||
|
||||
The returned type is `meta_ctor` and may be invalid if there is no constructor
|
||||
that accepts the supplied arguments or at least some types from which they are
|
||||
derived or to which they can be converted.<br/>
|
||||
A meta constructor offers an API to know the number of arguments, the expected
|
||||
meta types and to invoke it, therefore to construct a new instance of the
|
||||
underlying type.
|
||||
|
||||
* _Meta destructor_. It's returned by a dedicated function:
|
||||
|
||||
```cpp
|
||||
auto dtor = entt::resolve<my_type>().dtor();
|
||||
```
|
||||
|
||||
The returned type is `meta_dtor` and may be invalid if there is no custom
|
||||
destructor set for the given meta type.<br/>
|
||||
All what a meta destructor has to offer is a way to invoke it on a given
|
||||
instance. Be aware that the result may not be what is expected.
|
||||
|
||||
* _Meta data_. They are accessed by _name_:
|
||||
|
||||
```cpp
|
||||
auto data = entt::resolve<my_type>().data("member"_hs);
|
||||
```
|
||||
|
||||
The returned type is `meta_data` and may be invalid if there is no meta data
|
||||
object associated with the given identifier.<br/>
|
||||
A meta data object offers an API to query the underlying type (ie to know if
|
||||
it's a const or a static one), to get the meta type of the variable and to set
|
||||
or get the contained value.
|
||||
|
||||
* _Meta functions_. They are accessed by _name_:
|
||||
|
||||
```cpp
|
||||
auto func = entt::resolve<my_type>().func("member"_hs);
|
||||
```
|
||||
|
||||
The returned type is `meta_func` and may be invalid if there is no meta
|
||||
function object associated with the given identifier.<br/>
|
||||
A meta function object offers an API to query the underlying type (ie to know
|
||||
if it's a const or a static function), to know the number of arguments, the
|
||||
meta return type and the meta types of the parameters. In addition, a meta
|
||||
function object can be used to invoke the underlying function and then get the
|
||||
return value in the form of meta any object.
|
||||
|
||||
* _Meta bases_. They are accessed through the _name_ of the base types:
|
||||
|
||||
```cpp
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
```cpp
|
||||
auto func = entt::resolve<my_type>().func("member"_hs);
|
||||
|
||||
if(func) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Furthermore, all meta objects with the exception of meta destructors can be
|
||||
iterated through an overload that accepts a callback through which to return
|
||||
them. As an example:
|
||||
|
||||
```cpp
|
||||
entt::resolve<my_type>().data([](auto data) {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
A meta type can also be used to `construct` or `destroy` actual instances of the
|
||||
underlying type.<br/>
|
||||
In particular, the `construct` member function accepts a variable number of
|
||||
arguments and searches for a match. It returns a `meta_any` object that may or
|
||||
may not be initialized, depending on whether a suitable constructor has been
|
||||
found or not. On the other side, the `destroy` member function accepts instances
|
||||
of `meta_any` as well as actual objects by reference and invokes the registered
|
||||
destructor if any.<br/>
|
||||
Be aware that the result of a call to `destroy` may not be what is expected. The
|
||||
purpose is to give users the ability to free up resources that require special
|
||||
treatment and **not** to actually destroy instances.
|
||||
|
||||
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
|
||||
unfortunately beyond the scope of this document.<br/>
|
||||
I invite anyone interested in the subject to look at the code, experiment and
|
||||
read the official documentation to get the best out of this powerful tool.
|
||||
|
||||
## Policies: the more, the less
|
||||
|
||||
Policies are a kind of compile-time directives that can be used when recording
|
||||
reflection information.<br/>
|
||||
Their purpose is to require slightly different behavior than the default in some
|
||||
specific cases. For example, when reading a given data member, its value is
|
||||
returned wrapped in a `meta_any` object which, by default, makes a copy of it.
|
||||
For large objects or if the caller wants to access the original instance, this
|
||||
behavior isn't desirable. Policies are there to offer a solution to this and
|
||||
other problems.
|
||||
|
||||
There are a few alternatives available at the moment:
|
||||
|
||||
* The _as-is_ policy, associated with the type `entt::as_is_t`.<br/>
|
||||
This is the default policy. In general, it should never be used explicitly,
|
||||
since it's implicitly selected if no other policy is specified.<br/>
|
||||
In this case, the return values of the functions as well as the properties
|
||||
exposed as data members are always returned by copy in a dedicated wrapper and
|
||||
therefore associated with their original meta types.
|
||||
|
||||
* 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/>
|
||||
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::reflect<my_type>("reflected"_hs)
|
||||
.func<&my_type::member_function, entt::as_void_t>("member"_hs);
|
||||
```
|
||||
|
||||
* The _as-alias_ policy, associated with the type `entt::as_alias_t`.<br/>
|
||||
It allows to build wrappers that act as aliases for the objects used to
|
||||
initialize them. Modifying the object contained in the wrapper for which the
|
||||
_aliasing_ was requested will make it possible to directly modify the instance
|
||||
used to initialize the wrapper itself.<br/>
|
||||
This policy works 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:
|
||||
|
||||
```cpp
|
||||
entt::reflect<my_type>("reflected"_hs)
|
||||
.data<&my_type::data_member, entt::as_alias_t>("member"_hs);
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## Named constants and enums
|
||||
|
||||
A special mention should be made for constant values and enums. It wouldn't be
|
||||
necessary, but it will help distracted readers.
|
||||
|
||||
As mentioned, the `data` member function can be used to reflect constants of any
|
||||
type among the other things.<br/>
|
||||
This allows users to create meta types for enums that will work exactly like any
|
||||
other meta type built from a class. Similarly, arithmetic types can be enriched
|
||||
with constants of special meaning where required.<br/>
|
||||
Personally, I find it very useful not to export what is the difference between
|
||||
enums and classes in C++ directly in the space of the reflected types.
|
||||
|
||||
All the values thus exported will appear to users as if they were constant data
|
||||
members of the reflected types.
|
||||
|
||||
Exporting constant values or elements from an enum is as simple as ever:
|
||||
|
||||
```cpp
|
||||
entt::reflect<my_enum>()
|
||||
.data<my_enum::a_value>("a_value"_hs)
|
||||
.data<my_enum::another_value>("another_value"_hs);
|
||||
|
||||
entt::reflect<int>().data<2048>("max_int"_hs);
|
||||
```
|
||||
|
||||
It goes without saying that accessing them is trivial as well. It's a matter of
|
||||
doing the following, as with any other data member of a meta type:
|
||||
|
||||
```cpp
|
||||
auto value = entt::resolve<my_enum>().data("a_value"_hs).get({}).cast<my_enum>();
|
||||
auto max = entt::resolve<int>().data("max_int"_hs).get({}).cast<int>();
|
||||
```
|
||||
|
||||
As a side note, remember that all this happens behind the scenes without any
|
||||
allocation because of the small object optimization performed by the meta any
|
||||
class.
|
||||
|
||||
## Properties and meta objects
|
||||
|
||||
Sometimes (for example, when it comes to creating an editor) it might be useful
|
||||
to be able to attach properties to the meta objects created. Fortunately, this
|
||||
is possible for most of them.<br/>
|
||||
To attach a property to a meta object, no matter what as long as it supports
|
||||
properties, it is sufficient to provide an object at the time of construction
|
||||
such that `std::get<0>` and `std::get<1>` are valid for it. In other terms, the
|
||||
properties are nothing more than key/value pairs users can put in an
|
||||
`std::pair`. As an example:
|
||||
|
||||
```cpp
|
||||
entt::reflect<my_type>("reflected"_hs, std::make_pair("tooltip"_hs, "message"));
|
||||
```
|
||||
|
||||
The meta objects that support properties offer then a couple of member functions
|
||||
named `prop` to iterate them at once and to search a specific property by key:
|
||||
|
||||
```cpp
|
||||
// iterate all the properties of a meta type
|
||||
entt::resolve<my_type>().prop([](auto prop) {
|
||||
// ...
|
||||
});
|
||||
|
||||
// search for a given property by name
|
||||
auto prop = entt::resolve<my_type>().prop("tooltip"_hs);
|
||||
```
|
||||
|
||||
Meta properties are objects having a fairly poor interface, all in all. They
|
||||
only provide the `key` and the `value` member functions to be used to retrieve
|
||||
the key and the value contained in the form of meta any objects, respectively.
|
||||
|
||||
## Unregister types
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
To unregister a type, users can use the `unregister` function from the global
|
||||
namespace:
|
||||
|
||||
```cpp
|
||||
entt::unregister<my_type>();
|
||||
```
|
||||
|
||||
This function returns a boolean value that is true if the type is actually
|
||||
registered with the reflection system, false otherwise.<br/>
|
||||
The type can be re-registered later with a completely different name and form.
|
||||
208
docs/md/process.md
Normal file
208
docs/md/process.md
Normal file
@@ -0,0 +1,208 @@
|
||||
# Crash Course: cooperative scheduler
|
||||
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
# Table of Contents
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [The process](#the-process)
|
||||
* [Adaptor](#adaptor)
|
||||
* [The scheduler](#the-scheduler)
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
|
||||
# Introduction
|
||||
|
||||
Sometimes processes are a useful tool to work around the strict definition of a
|
||||
system and introduce logic in a different way, usually without resorting to the
|
||||
introduction of other components.
|
||||
|
||||
`EnTT` offers a minimal support to this paradigm by introducing a few classes
|
||||
that users can use to define and execute cooperative processes.
|
||||
|
||||
# The process
|
||||
|
||||
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):
|
||||
|
||||
* `void update(Delta, void *);`
|
||||
|
||||
It's invoked once per tick until a process is explicitly aborted or it
|
||||
terminates either with or without errors. Even though it's not mandatory to
|
||||
declare this member function, as a rule of thumb each process should at
|
||||
least define it to work properly. The `void *` parameter is an opaque pointer
|
||||
to user data (if any) forwarded directly to the process during an update.
|
||||
|
||||
* `void init();`
|
||||
|
||||
It's invoked when the process joins the running queue of a scheduler. This
|
||||
happens as soon as it's attached to the scheduler if the process is a top
|
||||
level one, otherwise when it replaces its parent if the process is a
|
||||
continuation.
|
||||
|
||||
* `void succeeded();`
|
||||
|
||||
It's invoked in case of success, immediately after an update and during the
|
||||
same tick.
|
||||
|
||||
* `void failed();`
|
||||
|
||||
It's invoked in case of errors, immediately after an update and during the
|
||||
same tick.
|
||||
|
||||
* `void aborted();`
|
||||
|
||||
It's invoked only if a process is explicitly aborted. There is no guarantee
|
||||
that it executes in the same tick, this depends solely on whether the
|
||||
process is aborted immediately or not.
|
||||
|
||||
Derived classes can also change the internal state of a process by invoking
|
||||
`succeed` and `fail`, as well as `pause` and `unpause` the process itself. All
|
||||
these are protected member functions made available to be able to manage the
|
||||
life cycle of a process from a derived class.
|
||||
|
||||
Here is a minimal example for the sake of curiosity:
|
||||
|
||||
```cpp
|
||||
struct my_process: entt::process<my_process, std::uint32_t> {
|
||||
using delta_type = std::uint32_t;
|
||||
|
||||
void update(delta_type delta, void *) {
|
||||
remaining -= std::min(remaining, delta);
|
||||
|
||||
// ...
|
||||
|
||||
if(!remaining) {
|
||||
succeed();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
delta_type remaining{1000u};
|
||||
};
|
||||
```
|
||||
|
||||
## Adaptor
|
||||
|
||||
Lambdas and functors can't be used directly with a scheduler for they are not
|
||||
properly defined processes with managed life cycles.<br/>
|
||||
This class helps in filling the gap and turning lambdas and functors into
|
||||
full featured processes usable by a scheduler.
|
||||
|
||||
The function call operator has a signature similar to the one of the `update`
|
||||
function of a process but for the fact that it receives two extra arguments to
|
||||
call whenever a process is terminated with success or with an error:
|
||||
|
||||
```cpp
|
||||
void(Delta delta, void *data, auto succeed, auto fail);
|
||||
```
|
||||
|
||||
Parameters have the following meaning:
|
||||
|
||||
* `delta` is the elapsed time.
|
||||
* `data` is an opaque pointer to user data if any, `nullptr` otherwise.
|
||||
* `succeed` is a function to call when a process terminates with success.
|
||||
* `fail` is a function to call when a process terminates with errors.
|
||||
|
||||
Both `succeed` and `fail` accept no parameters at all.
|
||||
|
||||
Note that usually users shouldn't worry about creating adaptors at all. A
|
||||
scheduler creates them internally each and every time a lambda or a functor is
|
||||
used as a process.
|
||||
|
||||
# The scheduler
|
||||
|
||||
A cooperative scheduler runs different processes and helps managing their life
|
||||
cycles.
|
||||
|
||||
Each process is invoked once per tick. If it terminates, it's removed
|
||||
automatically from the scheduler and it's never invoked again. Otherwise it's
|
||||
a good candidate to run one more time the next tick.<br/>
|
||||
A process can also have a child. In this case, the parent process is replaced
|
||||
with its child when it terminates and only if it returns with success. In case
|
||||
of errors, both the parent process and its child are discarded. This way, it's
|
||||
easy to create chain of processes to run sequentially.
|
||||
|
||||
Using a scheduler is straightforward. To create it, users must provide only the
|
||||
type for the elapsed times and no arguments at all:
|
||||
|
||||
```cpp
|
||||
entt::scheduler<std::uint32_t> scheduler;
|
||||
```
|
||||
|
||||
It has member functions to query its internal data structures, like `empty` or
|
||||
`size`, as well as a `clear` utility to reset it to a clean state:
|
||||
|
||||
```cpp
|
||||
// checks if there are processes still running
|
||||
const auto empty = scheduler.empty();
|
||||
|
||||
// gets the number of processes still running
|
||||
entt::scheduler<std::uint32_t>::size_type size = scheduler.size();
|
||||
|
||||
// resets the scheduler to its initial state and discards all the processes
|
||||
scheduler.clear();
|
||||
```
|
||||
|
||||
To attach a process to a scheduler there are mainly two ways:
|
||||
|
||||
* If the process inherits from the `process` class template, it's enough to
|
||||
indicate its type and submit all the parameters required to construct it to
|
||||
the `attach` member function:
|
||||
|
||||
```cpp
|
||||
scheduler.attach<my_process>("foobar");
|
||||
```
|
||||
|
||||
* Otherwise, in case of a lambda or a functor, it's enough to provide an
|
||||
instance of the class to the `attach` member function:
|
||||
|
||||
```cpp
|
||||
scheduler.attach([](auto...){ /* ... */ });
|
||||
```
|
||||
|
||||
In both cases, the return value is an opaque object that offers a `then` member
|
||||
function to use to create chains of processes to run sequentially.<br/>
|
||||
As a minimal example of use:
|
||||
|
||||
```cpp
|
||||
// schedules a task in the form of a lambda function
|
||||
scheduler.attach([](auto delta, void *, auto succeed, auto fail) {
|
||||
// ...
|
||||
})
|
||||
// appends a child in the form of another lambda function
|
||||
.then([](auto delta, void *, auto succeed, auto fail) {
|
||||
// ...
|
||||
})
|
||||
// appends a child in the form of a process class
|
||||
.then<my_process>();
|
||||
```
|
||||
|
||||
To update a scheduler and therefore all its processes, the `update` member
|
||||
function is the way to go:
|
||||
|
||||
```cpp
|
||||
// updates all the processes, no user data are provided
|
||||
scheduler.update(delta);
|
||||
|
||||
// updates all the processes and provides them with custom data
|
||||
scheduler.update(delta, &data);
|
||||
```
|
||||
|
||||
In addition to these functions, the scheduler offers an `abort` member function
|
||||
that can be used to discard all the running processes at once:
|
||||
|
||||
```cpp
|
||||
// aborts all the processes abruptly ...
|
||||
scheduler.abort(true);
|
||||
|
||||
// ... or gracefully during the next tick
|
||||
scheduler.abort();
|
||||
```
|
||||
238
docs/md/resource.md
Normal file
238
docs/md/resource.md
Normal file
@@ -0,0 +1,238 @@
|
||||
# Crash Course: resource management
|
||||
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
# Table of Contents
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [The resource, the loader and the cache](#the-resource-the-loader-and-the-cache)
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
|
||||
# Introduction
|
||||
|
||||
Resource management is usually one of the most critical part of a software like
|
||||
a game. Solutions are often tuned to the particular application. There exist
|
||||
several approaches and all of them are perfectly fine as long as they fit the
|
||||
requirements of the piece of software in which they are used.<br/>
|
||||
Examples are loading everything on start, loading on request, predictive
|
||||
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/>
|
||||
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.
|
||||
|
||||
# The resource, the loader and the cache
|
||||
|
||||
There are three main actors in the model: the resource, the loader and the
|
||||
cache.
|
||||
|
||||
The _resource_ is whatever users want it to be. An image, a video, an audio,
|
||||
whatever. There are no limits.<br/>
|
||||
As a minimal example:
|
||||
|
||||
```cpp
|
||||
struct my_resource { const int value; };
|
||||
```
|
||||
|
||||
A _loader_ is a class the aim of which is to load a specific resource. It has to
|
||||
inherit directly from the dedicated base class as in the following example:
|
||||
|
||||
```cpp
|
||||
struct my_loader final: entt::resource_loader<my_loader, my_resource> {
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
Where `my_resource` is the type of resources it creates.<br/>
|
||||
A resource loader must also expose a public const member function named `load`
|
||||
that accepts a variable number of arguments and returns a shared pointer to a
|
||||
resource.<br/>
|
||||
As an example:
|
||||
|
||||
```cpp
|
||||
struct my_loader: entt::resource_loader<my_loader, my_resource> {
|
||||
std::shared_ptr<my_resource> load(int value) const {
|
||||
// ...
|
||||
return std::shared_ptr<my_resource>(new my_resource{ value });
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
In general, resource loaders should not have a state or retain data of any type.
|
||||
They should let the cache manage their resources instead.<br/>
|
||||
As a side note, base class and CRTP idiom aren't strictly required with the
|
||||
current implementation. One could argue that a cache can easily work with
|
||||
loaders of any type. However, future changes won't be breaking ones by forcing
|
||||
the use of a base class today and that's why the model is already in its place.
|
||||
|
||||
Finally, a cache is a specialization of a class template tailored to a specific
|
||||
resource:
|
||||
|
||||
```cpp
|
||||
using my_resource_cache = entt::resource_cache<my_resource>;
|
||||
|
||||
// ...
|
||||
|
||||
my_resource_cache cache{};
|
||||
```
|
||||
|
||||
The idea is to create different caches for different types of resources and to
|
||||
manage each one independently in the most appropriate way.<br/>
|
||||
As a (very) trivial example, audio tracks can survive in most of the scenes of
|
||||
an application while meshes can be associated with a single scene and then
|
||||
discarded when users leave it.
|
||||
|
||||
A cache offers a set of basic functionalities to query its internal state and to
|
||||
_organize_ it:
|
||||
|
||||
```cpp
|
||||
// gets the number of resources managed by a cache
|
||||
const auto size = cache.size();
|
||||
|
||||
// checks if a cache contains at least a valid resource
|
||||
const auto empty = cache.empty();
|
||||
|
||||
// clears a cache and discards its content
|
||||
cache.clear();
|
||||
```
|
||||
|
||||
Besides these member functions, a cache contains what is needed to load, use and
|
||||
discard resources of the given type.<br/>
|
||||
Before to explore this part of the interface, it makes sense to mention how
|
||||
resources are identified. The type of the identifiers to use is defined as:
|
||||
|
||||
```cpp
|
||||
entt::resource_cache<resource>::resource_type
|
||||
```
|
||||
|
||||
Where `resource_type` is an alias for `entt::hashed_string::hash_type`.
|
||||
Therefore, resource identifiers are created explicitly as in the following
|
||||
example:
|
||||
|
||||
```cpp
|
||||
constexpr auto identifier = entt::resource_cache<resource>::resource_type{"my/resource/identifier"_hs};
|
||||
// this is equivalent to the following
|
||||
constexpr auto hs = entt::hashed_string{"my/resource/identifier"};
|
||||
```
|
||||
|
||||
The class `hashed_string` is described in a dedicated section, so I won't go in
|
||||
details here.
|
||||
|
||||
Resources are loaded and thus stored in a cache through the `load` member
|
||||
function. It accepts the loader to use as a template parameter, the resource
|
||||
identifier and the parameters used to construct the resource as arguments:
|
||||
|
||||
```cpp
|
||||
// uses the identifier declared above
|
||||
cache.load<my_loader>(identifier, 0);
|
||||
|
||||
// uses a const char * directly as an identifier
|
||||
cache.load<my_loader>("another/identifier"_hs, 42);
|
||||
```
|
||||
|
||||
The function returns a handle to the resource, whether it already exists or is
|
||||
loaded. In case the loader returns an invalid pointer, the handle is invalid as
|
||||
well and therefore it can be easily used with an `if` statement:
|
||||
|
||||
```cpp
|
||||
if(auto handle = cache.load<my_loader>("another/identifier"_hs, 42); handle) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Before trying to load a resource, the `contains` member function can be used to
|
||||
know if a cache already contains a specific resource:
|
||||
|
||||
```cpp
|
||||
auto exists = cache.contains("my/identifier"_hs);
|
||||
```
|
||||
|
||||
There exists also a member function to use to force a reload of an already
|
||||
existing resource if needed:
|
||||
|
||||
```cpp
|
||||
auto handle = cache.reload<my_loader>("another/identifier"_hs, 42);
|
||||
```
|
||||
|
||||
As above, the function returns a handle to the resource that is invalid in case
|
||||
of errors. The `reload` member function is a kind of alias of the following
|
||||
snippet:
|
||||
|
||||
```cpp
|
||||
cache.discard(identifier);
|
||||
cache.load<my_loader>(identifier, 42);
|
||||
```
|
||||
|
||||
Where the `discard` member function is used to get rid of a resource if loaded.
|
||||
In case the cache doesn't contain a resource for the given identifier, `discard`
|
||||
does nothing and returns immediately.
|
||||
|
||||
So far, so good. Resources are finally loaded and stored within the cache.<br/>
|
||||
They are returned to users in the form of handles. To get one of them later on:
|
||||
|
||||
```cpp
|
||||
auto handle = cache.handle("my/identifier"_hs);
|
||||
```
|
||||
|
||||
The idea behind a handle is the same of the flyweight pattern. In other terms,
|
||||
resources aren't copied around. Instead, instances are shared between handles.
|
||||
Users of a resource own a handle that guarantees that a resource isn't destroyed
|
||||
until all the handles are destroyed, even if the resource itself is removed from
|
||||
the cache.<br/>
|
||||
Handles are tiny objects both movable and copyable. They return the contained
|
||||
resource as a const reference on request:
|
||||
|
||||
* By means of the `get` member function:
|
||||
|
||||
```cpp
|
||||
const auto &resource = handle.get();
|
||||
```
|
||||
|
||||
* Using the proper cast operator:
|
||||
|
||||
```cpp
|
||||
const auto &resource = handle;
|
||||
```
|
||||
|
||||
* Through the dereference operator:
|
||||
|
||||
```cpp
|
||||
const auto &resource = *handle;
|
||||
```
|
||||
|
||||
The resource can also be accessed directly using the arrow operator if required:
|
||||
|
||||
```cpp
|
||||
auto value = handle->value;
|
||||
```
|
||||
|
||||
To test if a handle is still valid, the cast operator to `bool` allows users to
|
||||
use it in a guard:
|
||||
|
||||
```cpp
|
||||
if(handle) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Finally, in case there is the need to load a resource and thus to get a handle
|
||||
without storing the resource itself in the cache, users can rely on the `temp`
|
||||
member function template.<br/>
|
||||
The declaration is similar to that of `load`, a (possibly invalid) handle for
|
||||
the resource is returned also in this case:
|
||||
|
||||
```cpp
|
||||
if(auto handle = cache.temp<my_loader>(42); handle) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Do not forget to test the handle for validity. Otherwise, getting a reference to
|
||||
the resource it points may result in undefined behavior.
|
||||
479
docs/md/signal.md
Normal file
479
docs/md/signal.md
Normal file
@@ -0,0 +1,479 @@
|
||||
# Crash Course: events, signals and everything in between
|
||||
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
# Table of Contents
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [Delegate](#delegate)
|
||||
* [Signals](#signals)
|
||||
* [Event dispatcher](#event-dispatcher)
|
||||
* [Event emitter](#event-emitter)
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
|
||||
# Introduction
|
||||
|
||||
Signals are usually a core part of games and software architectures in
|
||||
general.<br/>
|
||||
Roughly speaking, they help to decouple the various parts of a system while
|
||||
allowing them to communicate with each other somehow.
|
||||
|
||||
The so called _modern C++_ comes with a tool that can be useful in these terms,
|
||||
the `std::function`. As an example, it can be used to create delegates.<br/>
|
||||
However, there is no guarantee that an `std::function` does not perform
|
||||
allocations under the hood and this could be problematic sometimes. Furthermore,
|
||||
it solves a problem but may not adapt well to other requirements that may arise
|
||||
from time to time.
|
||||
|
||||
In case that the flexibility and power of an `std::function` isn't required or
|
||||
if the price to pay for them is too high,` EnTT` offers a complete set of
|
||||
lightweight classes to solve the same and many other problems.
|
||||
|
||||
# Delegate
|
||||
|
||||
A delegate can be used as a general purpose invoker with no memory overhead for
|
||||
free functions and members provided along with an instance on which to invoke
|
||||
them.<br/>
|
||||
It does not claim to be a drop-in replacement for an `std::function`, so do not
|
||||
expect to use it whenever an `std::function` fits well. That said, it's most
|
||||
likely even a better fit than an `std::function` in a lot of cases, so expect to
|
||||
use it quite a lot anyway.
|
||||
|
||||
The interface is trivial. It offers a default constructor to create empty
|
||||
delegates:
|
||||
|
||||
```cpp
|
||||
entt::delegate<int(int)> delegate{};
|
||||
```
|
||||
|
||||
All what is needed to create an instance is to specify the type of the function
|
||||
the delegate will _contain_, that is the signature of the free function or the
|
||||
member one wants to assign to it.
|
||||
|
||||
Attempting to use an empty delegate by invoking its function call operator
|
||||
results in undefined behavior or most likely a crash. Before to use a delegate,
|
||||
it must be initialized.<br/>
|
||||
There exists a bunch of overloads of the `connect` member function to do that.
|
||||
As an example of use:
|
||||
|
||||
```cpp
|
||||
int f(int i) { return i; }
|
||||
|
||||
struct my_struct {
|
||||
int f(const int &i) { return i }
|
||||
};
|
||||
|
||||
// bind a free function to the delegate
|
||||
delegate.connect<&f>();
|
||||
|
||||
// bind a member function to the delegate
|
||||
my_struct instance;
|
||||
delegate.connect<&my_struct::f>(instance);
|
||||
```
|
||||
|
||||
The delegate class accepts also data members, if needed. In this case, the
|
||||
function type of the delegate is such that the parameter list is empty and the
|
||||
value of the data member is at least convertible to the return type.
|
||||
|
||||
Free functions having type equivalent to `void(T &, args...)` are accepted as
|
||||
well. In this case, `T &` is considered a payload and the function will receive
|
||||
it back every time it's invoked. In other terms, this works just fine with the
|
||||
above definition:
|
||||
|
||||
```cpp
|
||||
void g(const char &c, int i) { /* ... */ }
|
||||
const char c = 'c';
|
||||
|
||||
delegate.connect<&g>(c);
|
||||
delegate(42);
|
||||
```
|
||||
|
||||
The function `g` will be invoked with a reference to `c` and `42`. However, the
|
||||
function type of the delegate is still `void(int)`. This is also the signature
|
||||
of its function call operator.
|
||||
|
||||
Another interesting aspect of the delegate class is that it accepts also
|
||||
functions with a list of parameters that is shorter than that of the function
|
||||
type used to specialize the delegate itself.<br/>
|
||||
The following code is therefore perfectly valid:
|
||||
|
||||
```cpp
|
||||
void g() { /* ... */ }
|
||||
delegate.connect<&g>();
|
||||
delegate(42);
|
||||
```
|
||||
|
||||
Where the function type of the delegate is `void(int)` as above. It goes without
|
||||
saying that the extra arguments are silently discarded internally.<br/>v
|
||||
This is a nice-to-have feature in a lot of cases, as an example when the
|
||||
`delegate` class is used as a building block of a signal-slot system.
|
||||
|
||||
To create and initialize a delegate at once, there are a few specialized
|
||||
constructors. Because of the rules of the language, the listener is provided by
|
||||
means of the `entt::connect_arg` variable template:
|
||||
|
||||
```cpp
|
||||
entt::delegate<int(int)> func{entt::connect_arg<&f>};
|
||||
```
|
||||
|
||||
Aside `connect`, a `disconnect` counterpart isn't provided. Instead, there
|
||||
exists a `reset` member function to use to clear a delegate.<br/>
|
||||
To know if a delegate is empty, it can be used explicitly in every conditional
|
||||
statement:
|
||||
|
||||
```cpp
|
||||
if(delegate) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Finally, to invoke a delegate, the function call operator is the way to go as
|
||||
already shown in the examples above:
|
||||
|
||||
```cpp
|
||||
auto ret = delegate(42);
|
||||
```
|
||||
|
||||
In all cases, the listeners don't have to strictly follow the signature of the
|
||||
delegate. As long as a listener can be invoked with the given arguments to yield
|
||||
a result that is convertible to the given result type, everything works just
|
||||
fine.
|
||||
|
||||
# Signals
|
||||
|
||||
Signal handlers work with references to classes, function pointers and pointers
|
||||
to members. Listeners can be any kind of objects and users are in charge of
|
||||
connecting and disconnecting them from a signal to avoid crashes due to
|
||||
different lifetimes. On the other side, performance shouldn't be affected that
|
||||
much by the presence of such a signal handler.<br/>
|
||||
Signals make use of delegates internally and therefore they undergo the same
|
||||
rules and offer similar functionalities. It may be a good idea to consult the
|
||||
documentation of the `delegate` class for further information.
|
||||
|
||||
A signal handler can be used as a private data member without exposing any
|
||||
_publish_ functionality to the clients of a class. The basic idea is to impose a
|
||||
clear separation between the signal itself and the `sink` class, that is a tool
|
||||
to be used to connect and disconnect listeners on the fly.
|
||||
|
||||
The API of a signal handler is straightforward. The most important thing is that
|
||||
it comes in two forms: with and without a collector. In case a signal is
|
||||
provided with a collector, all the values returned by the listeners can be
|
||||
literally _collected_ and used later by the caller. Otherwise it works just like
|
||||
a plain signal that emits events from time to time.<br/>
|
||||
To create instances of signal handlers it is sufficient to provide the type of
|
||||
function to which they refer:
|
||||
|
||||
```cpp
|
||||
entt::sigh<void(int, char)> signal;
|
||||
```
|
||||
|
||||
Signals offer all the basic functionalities required to know how many listeners
|
||||
they contain (`size`) or if they contain at least a listener (`empty`), as well
|
||||
as a function to use to swap handlers (`swap`).
|
||||
|
||||
Besides them, there are member functions to use both to connect and disconnect
|
||||
listeners in all their forms by means of a sink:
|
||||
|
||||
```cpp
|
||||
void foo(int, char) { /* ... */ }
|
||||
|
||||
struct listener {
|
||||
void bar(const int &, char) { /* ... */ }
|
||||
};
|
||||
|
||||
// ...
|
||||
|
||||
entt::sink sink{signal};
|
||||
listener instance;
|
||||
|
||||
sink.connect<&foo>();
|
||||
sink.connect<&listener::bar>(instance);
|
||||
|
||||
// ...
|
||||
|
||||
// disconnects a free function
|
||||
sink.disconnect<&foo>();
|
||||
|
||||
// disconnect a member function of an instance
|
||||
sink.disconnect<&listener::bar>(instance);
|
||||
|
||||
// disconnect all the member functions of an instance, if any
|
||||
sink.disconnect(instance);
|
||||
|
||||
// discards all the listeners at once
|
||||
sink.disconnect();
|
||||
```
|
||||
|
||||
As shown above, the listeners don't have to strictly follow the signature of the
|
||||
signal. As long as a listener can be invoked with the given arguments to yield a
|
||||
result that is convertible to the given return type, everything works just
|
||||
fine.<br/>
|
||||
The `connect` member function returns by default a `connection` object to be
|
||||
used as an alternative to break a connection by means of its `release` member
|
||||
function. A `scoped_connection` can also be created from a connection. In this
|
||||
case, the link is broken automatically as soon as the object goes out of scope.
|
||||
|
||||
Once listeners are attached (or even if there are no listeners at all), events
|
||||
and data in general can be published through a signal by means of the `publish`
|
||||
member function:
|
||||
|
||||
```cpp
|
||||
signal.publish(42, 'c');
|
||||
```
|
||||
|
||||
To collect data, the `collect` member function should be used instead. Below is
|
||||
a minimal example to show how to use it:
|
||||
|
||||
```cpp
|
||||
int f() { return 0; }
|
||||
int g() { return 1; }
|
||||
|
||||
// ...
|
||||
|
||||
entt::sigh<int()> signal;
|
||||
entt::sink sink{signal};
|
||||
|
||||
sink.connect<&f>();
|
||||
sink.connect<&g>();
|
||||
|
||||
std::vector<int> vec{};
|
||||
signal.collect([&vec](int value) { vec.push_back(value); });
|
||||
|
||||
assert(vec[0] == 0);
|
||||
assert(vec[1] == 1);
|
||||
```
|
||||
|
||||
A collector must expose a function operator that accepts as an argument a type
|
||||
to which the return type of the listeners can be converted. Moreover, it can
|
||||
optionally return a boolean value that is true to stop collecting data, false
|
||||
otherwise. This way one can avoid calling all the listeners in case it isn't
|
||||
necessary.<br/>
|
||||
Functors can also be used in place of a lambda. Since the collector is copied
|
||||
when invoking the `collect` member function, `std::ref` is the way to go in this
|
||||
case:
|
||||
|
||||
```cpp
|
||||
struct my_collector {
|
||||
std::vector<int> vec{};
|
||||
|
||||
bool operator()(int v) noexcept {
|
||||
vec.push_back(v);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// ...
|
||||
|
||||
my_collector collector;
|
||||
signal.collect(std::ref(collector));
|
||||
```
|
||||
|
||||
# Event dispatcher
|
||||
|
||||
The event dispatcher class is designed so as to be used in a loop. It allows
|
||||
users both to trigger immediate events or to queue events to be published all
|
||||
together once per tick.<br/>
|
||||
This class shares part of its API with the one of the signal handler, but it
|
||||
doesn't require that all the types of events are specified when declared:
|
||||
|
||||
```cpp
|
||||
// define a general purpose dispatcher
|
||||
entt::dispatcher dispatcher{};
|
||||
```
|
||||
|
||||
In order to register an instance of a class to a dispatcher, its type must
|
||||
expose one or more member functions the arguments of which are such that
|
||||
`const E &` can be converted to them for each type of event `E`, no matter what
|
||||
the return value is.<br/>
|
||||
The name of the member function aimed to receive the event must be provided to
|
||||
the `connect` member function of the sink in charge for the specific event:
|
||||
|
||||
```cpp
|
||||
struct an_event { int value; };
|
||||
struct another_event {};
|
||||
|
||||
struct listener {
|
||||
void receive(const an_event &) { /* ... */ }
|
||||
void method(const another_event &) { /* ... */ }
|
||||
};
|
||||
|
||||
// ...
|
||||
|
||||
listener listener;
|
||||
dispatcher.sink<an_event>().connect<&listener::receive>(listener);
|
||||
dispatcher.sink<another_event>().connect<&listener::method>(listener);
|
||||
```
|
||||
|
||||
The `disconnect` member function follows the same pattern and can be used to
|
||||
remove one listener at a time or all of them at once:
|
||||
|
||||
```cpp
|
||||
dispatcher.sink<an_event>().disconnect<&listener::receive>(listener);
|
||||
dispatcher.sink<another_event>().disconnect(listener);
|
||||
```
|
||||
|
||||
The `trigger` member function serves the purpose of sending an immediate event
|
||||
to all the listeners registered so far. It offers a convenient approach that
|
||||
relieves users from having to create the event itself. Instead, it's enough to
|
||||
specify the type of event and provide all the parameters required to construct
|
||||
it.<br/>
|
||||
As an example:
|
||||
|
||||
```cpp
|
||||
dispatcher.trigger<an_event>(42);
|
||||
dispatcher.trigger<another_event>();
|
||||
```
|
||||
|
||||
Listeners are invoked immediately, order of execution isn't guaranteed. This
|
||||
method can be used to push around urgent messages like an _is terminating_
|
||||
notification on a mobile app.
|
||||
|
||||
On the other hand, the `enqueue` member function queues messages together and
|
||||
allows to maintain control over the moment they are sent to listeners. The
|
||||
signature of this method is more or less the same of `trigger`:
|
||||
|
||||
```cpp
|
||||
dispatcher.enqueue<an_event>(42);
|
||||
dispatcher.enqueue<another_event>();
|
||||
```
|
||||
|
||||
Events are stored aside until the `update` member function is invoked, then all
|
||||
the messages that are still pending are sent to the listeners at once:
|
||||
|
||||
```cpp
|
||||
// emits all the events of the given type at once
|
||||
dispatcher.update<my_event>();
|
||||
|
||||
// emits all the events queued so far at once
|
||||
dispatcher.update();
|
||||
```
|
||||
|
||||
This way users can embed the dispatcher in a loop and literally dispatch events
|
||||
once per tick to their systems.
|
||||
|
||||
# Event emitter
|
||||
|
||||
A general purpose event emitter thought mainly for those cases where it comes to
|
||||
working with asynchronous stuff.<br/>
|
||||
Originally designed to fit the requirements of
|
||||
[`uvw`](https://github.com/skypjack/uvw) (a wrapper for `libuv` written in
|
||||
modern C++), it was adapted later to be included in this library.
|
||||
|
||||
To create a custom emitter type, derived classes must inherit directly from the
|
||||
base class as:
|
||||
|
||||
```cpp
|
||||
struct my_emitter: emitter<my_emitter> {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
The full list of accepted types of events isn't required. Handlers are created
|
||||
internally on the fly and thus each type of event is accepted by default.
|
||||
|
||||
Whenever an event is published, an emitter provides the listeners with a
|
||||
reference to itself along with a const reference to the event. Therefore
|
||||
listeners have an handy way to work with it without incurring in the need of
|
||||
capturing a reference to the emitter itself.<br/>
|
||||
In addition, an opaque object is returned each time a connection is established
|
||||
between an emitter and a listener, allowing the caller to disconnect them at a
|
||||
later time.<br/>
|
||||
The opaque object used to handle connections is both movable and copyable. On
|
||||
the other side, an event emitter is movable but not copyable by default.
|
||||
|
||||
To create new instances of an emitter, no arguments are required:
|
||||
|
||||
```cpp
|
||||
my_emitter emitter{};
|
||||
```
|
||||
|
||||
Listeners must be movable and callable objects (free functions, lambdas,
|
||||
functors, `std::function`s, whatever) whose function type is:
|
||||
|
||||
```cpp
|
||||
void(const Event &, my_emitter &)
|
||||
```
|
||||
|
||||
Where `Event` is the type of event they want to listen.<br/>
|
||||
There are two ways to attach a listener to an event emitter that differ
|
||||
slightly from each other:
|
||||
|
||||
* To register a long-lived listener, use the `on` member function. It is meant
|
||||
to register a listener designed to be invoked more than once for the given
|
||||
event type.<br/>
|
||||
As an example:
|
||||
|
||||
```cpp
|
||||
auto conn = emitter.on<my_event>([](const my_event &event, my_emitter &emitter) {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
The connection object can be freely discarded. Otherwise, it can be used later
|
||||
to disconnect the listener if required.
|
||||
|
||||
* To register a short-lived listener, use the `once` member function. It is
|
||||
meant to register a listener designed to be invoked only once for the given
|
||||
event type. The listener is automatically disconnected after the first
|
||||
invocation.<br/>
|
||||
As an example:
|
||||
|
||||
```cpp
|
||||
auto conn = emitter.once<my_event>([](const my_event &event, my_emitter &emitter) {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
The connection object can be freely discarded. Otherwise, it can be used later
|
||||
to disconnect the listener if required.
|
||||
|
||||
In both cases, the connection object can be used with the `erase` member
|
||||
function:
|
||||
|
||||
```cpp
|
||||
emitter.erase(conn);
|
||||
```
|
||||
|
||||
There are also two member functions to use either to disconnect all the
|
||||
listeners for a given type of event or to clear the emitter:
|
||||
|
||||
```cpp
|
||||
// removes all the listener for the specific event
|
||||
emitter.clear<my_event>();
|
||||
|
||||
// removes all the listeners registered so far
|
||||
emitter.clear();
|
||||
```
|
||||
|
||||
To send an event to all the listeners that are interested in it, the `publish`
|
||||
member function offers a convenient approach that relieves users from having to
|
||||
create the event:
|
||||
|
||||
```cpp
|
||||
struct my_event { int i; };
|
||||
|
||||
// ...
|
||||
|
||||
emitter.publish<my_event>(42);
|
||||
```
|
||||
|
||||
Finally, the `empty` member function tests if there exists at least either a
|
||||
listener registered with the event emitter or to a given type of event:
|
||||
|
||||
```cpp
|
||||
bool empty;
|
||||
|
||||
// checks if there is any listener registered for the specific event
|
||||
empty = emitter.empty<my_event>();
|
||||
|
||||
// checks it there are listeners registered with the event emitter
|
||||
empty = emitter.empty();
|
||||
```
|
||||
|
||||
In general, the event emitter is a handy tool when the derived classes _wrap_
|
||||
asynchronous operations, because it introduces a _nice-to-have_ model based on
|
||||
events and listeners that kindly hides the complexity behind the scenes. However
|
||||
it is not limited to such uses.
|
||||
299
scripts/amalgamate.py
Normal file
299
scripts/amalgamate.py
Normal file
@@ -0,0 +1,299 @@
|
||||
#!/usr/bin/env python
|
||||
# coding=utf-8
|
||||
|
||||
# amalgamate.py - Amalgamate C source and header files.
|
||||
# Copyright (c) 2012, Erik Edlund <erik.edlund@32767.se>
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
#
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# * Neither the name of Erik Edlund, nor the names of its contributors may
|
||||
# be used to endorse or promote products derived from this software without
|
||||
# specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import argparse
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
class Amalgamation(object):
|
||||
|
||||
# Prepends self.source_path to file_path if needed.
|
||||
def actual_path(self, file_path):
|
||||
if not os.path.isabs(file_path):
|
||||
file_path = os.path.join(self.source_path, file_path)
|
||||
return file_path
|
||||
|
||||
# Search included file_path in self.include_paths and
|
||||
# in source_dir if specified.
|
||||
def find_included_file(self, file_path, source_dir):
|
||||
search_dirs = self.include_paths[:]
|
||||
if source_dir:
|
||||
search_dirs.insert(0, source_dir)
|
||||
|
||||
for search_dir in search_dirs:
|
||||
search_path = os.path.join(search_dir, file_path)
|
||||
if os.path.isfile(self.actual_path(search_path)):
|
||||
return search_path
|
||||
return None
|
||||
|
||||
def __init__(self, args):
|
||||
with open(args.config, 'r') as f:
|
||||
config = json.loads(f.read())
|
||||
for key in config:
|
||||
setattr(self, key, config[key])
|
||||
|
||||
self.verbose = args.verbose == "yes"
|
||||
self.prologue = args.prologue
|
||||
self.source_path = args.source_path
|
||||
self.included_files = []
|
||||
|
||||
# Generate the amalgamation and write it to the target file.
|
||||
def generate(self):
|
||||
amalgamation = ""
|
||||
|
||||
if self.prologue:
|
||||
with open(self.prologue, 'r') as f:
|
||||
amalgamation += datetime.datetime.now().strftime(f.read())
|
||||
|
||||
if self.verbose:
|
||||
print("Config:")
|
||||
print(" target = {0}".format(self.target))
|
||||
print(" working_dir = {0}".format(os.getcwd()))
|
||||
print(" include_paths = {0}".format(self.include_paths))
|
||||
print("Creating amalgamation:")
|
||||
for file_path in self.sources:
|
||||
# Do not check the include paths while processing the source
|
||||
# list, all given source paths must be correct.
|
||||
# actual_path = self.actual_path(file_path)
|
||||
print(" - processing \"{0}\"".format(file_path))
|
||||
t = TranslationUnit(file_path, self, True)
|
||||
amalgamation += t.content
|
||||
|
||||
with open(self.target, 'w') as f:
|
||||
f.write(amalgamation)
|
||||
|
||||
print("...done!\n")
|
||||
if self.verbose:
|
||||
print("Files processed: {0}".format(self.sources))
|
||||
print("Files included: {0}".format(self.included_files))
|
||||
print("")
|
||||
|
||||
|
||||
def _is_within(match, matches):
|
||||
for m in matches:
|
||||
if match.start() > m.start() and \
|
||||
match.end() < m.end():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class TranslationUnit(object):
|
||||
# // C++ comment.
|
||||
cpp_comment_pattern = re.compile(r"//.*?\n")
|
||||
|
||||
# /* C comment. */
|
||||
c_comment_pattern = re.compile(r"/\*.*?\*/", re.S)
|
||||
|
||||
# "complex \"stri\\\ng\" value".
|
||||
string_pattern = re.compile("[^']" r'".*?(?<=[^\\])"', re.S)
|
||||
|
||||
# Handle simple include directives. Support for advanced
|
||||
# directives where macros and defines needs to expanded is
|
||||
# not a concern right now.
|
||||
include_pattern = re.compile(
|
||||
r'#\s*include\s+(<|")(?P<path>.*?)("|>)', re.S)
|
||||
|
||||
# #pragma once
|
||||
pragma_once_pattern = re.compile(r'#\s*pragma\s+once', re.S)
|
||||
|
||||
# Search for pattern in self.content, add the match to
|
||||
# contexts if found and update the index accordingly.
|
||||
def _search_content(self, index, pattern, contexts):
|
||||
match = pattern.search(self.content, index)
|
||||
if match:
|
||||
contexts.append(match)
|
||||
return match.end()
|
||||
return index + 2
|
||||
|
||||
# Return all the skippable contexts, i.e., comments and strings
|
||||
def _find_skippable_contexts(self):
|
||||
# Find contexts in the content in which a found include
|
||||
# directive should not be processed.
|
||||
skippable_contexts = []
|
||||
|
||||
# Walk through the content char by char, and try to grab
|
||||
# skippable contexts using regular expressions when found.
|
||||
i = 1
|
||||
content_len = len(self.content)
|
||||
while i < content_len:
|
||||
j = i - 1
|
||||
current = self.content[i]
|
||||
previous = self.content[j]
|
||||
|
||||
if current == '"':
|
||||
# String value.
|
||||
i = self._search_content(j, self.string_pattern,
|
||||
skippable_contexts)
|
||||
elif current == '*' and previous == '/':
|
||||
# C style comment.
|
||||
i = self._search_content(j, self.c_comment_pattern,
|
||||
skippable_contexts)
|
||||
elif current == '/' and previous == '/':
|
||||
# C++ style comment.
|
||||
i = self._search_content(j, self.cpp_comment_pattern,
|
||||
skippable_contexts)
|
||||
else:
|
||||
# Skip to the next char.
|
||||
i += 1
|
||||
|
||||
return skippable_contexts
|
||||
|
||||
# Returns True if the match is within list of other matches
|
||||
|
||||
# Removes pragma once from content
|
||||
def _process_pragma_once(self):
|
||||
content_len = len(self.content)
|
||||
if content_len < len("#include <x>"):
|
||||
return 0
|
||||
|
||||
# Find contexts in the content in which a found include
|
||||
# directive should not be processed.
|
||||
skippable_contexts = self._find_skippable_contexts()
|
||||
|
||||
pragmas = []
|
||||
pragma_once_match = self.pragma_once_pattern.search(self.content)
|
||||
while pragma_once_match:
|
||||
if not _is_within(pragma_once_match, skippable_contexts):
|
||||
pragmas.append(pragma_once_match)
|
||||
|
||||
pragma_once_match = self.pragma_once_pattern.search(self.content,
|
||||
pragma_once_match.end())
|
||||
|
||||
# Handle all collected pragma once directives.
|
||||
prev_end = 0
|
||||
tmp_content = ''
|
||||
for pragma_match in pragmas:
|
||||
tmp_content += self.content[prev_end:pragma_match.start()]
|
||||
prev_end = pragma_match.end()
|
||||
tmp_content += self.content[prev_end:]
|
||||
self.content = tmp_content
|
||||
|
||||
# Include all trivial #include directives into self.content.
|
||||
def _process_includes(self):
|
||||
content_len = len(self.content)
|
||||
if content_len < len("#include <x>"):
|
||||
return 0
|
||||
|
||||
# Find contexts in the content in which a found include
|
||||
# directive should not be processed.
|
||||
skippable_contexts = self._find_skippable_contexts()
|
||||
|
||||
# Search for include directives in the content, collect those
|
||||
# which should be included into the content.
|
||||
includes = []
|
||||
include_match = self.include_pattern.search(self.content)
|
||||
while include_match:
|
||||
if not _is_within(include_match, skippable_contexts):
|
||||
include_path = include_match.group("path")
|
||||
search_same_dir = include_match.group(1) == '"'
|
||||
found_included_path = self.amalgamation.find_included_file(
|
||||
include_path, self.file_dir if search_same_dir else None)
|
||||
if found_included_path:
|
||||
includes.append((include_match, found_included_path))
|
||||
|
||||
include_match = self.include_pattern.search(self.content,
|
||||
include_match.end())
|
||||
|
||||
# Handle all collected include directives.
|
||||
prev_end = 0
|
||||
tmp_content = ''
|
||||
for include in includes:
|
||||
include_match, found_included_path = include
|
||||
tmp_content += self.content[prev_end:include_match.start()]
|
||||
tmp_content += "// {0}\n".format(include_match.group(0))
|
||||
if found_included_path not in self.amalgamation.included_files:
|
||||
t = TranslationUnit(found_included_path, self.amalgamation, False)
|
||||
tmp_content += t.content
|
||||
prev_end = include_match.end()
|
||||
tmp_content += self.content[prev_end:]
|
||||
self.content = tmp_content
|
||||
|
||||
return len(includes)
|
||||
|
||||
# Make all content processing
|
||||
def _process(self):
|
||||
if not self.is_root:
|
||||
self._process_pragma_once()
|
||||
self._process_includes()
|
||||
|
||||
def __init__(self, file_path, amalgamation, is_root):
|
||||
self.file_path = file_path
|
||||
self.file_dir = os.path.dirname(file_path)
|
||||
self.amalgamation = amalgamation
|
||||
self.is_root = is_root
|
||||
|
||||
self.amalgamation.included_files.append(self.file_path)
|
||||
|
||||
actual_path = self.amalgamation.actual_path(file_path)
|
||||
if not os.path.isfile(actual_path):
|
||||
raise IOError("File not found: \"{0}\"".format(file_path))
|
||||
with open(actual_path, 'r') as f:
|
||||
self.content = f.read()
|
||||
self._process()
|
||||
|
||||
|
||||
def main():
|
||||
description = "Amalgamate C source and header files."
|
||||
usage = " ".join([
|
||||
"amalgamate.py",
|
||||
"[-v]",
|
||||
"-c path/to/config.json",
|
||||
"-s path/to/source/dir",
|
||||
"[-p path/to/prologue.(c|h)]"
|
||||
])
|
||||
argsparser = argparse.ArgumentParser(
|
||||
description=description, usage=usage)
|
||||
|
||||
argsparser.add_argument("-v", "--verbose", dest="verbose",
|
||||
choices=["yes", "no"], metavar="", help="be verbose")
|
||||
|
||||
argsparser.add_argument("-c", "--config", dest="config",
|
||||
required=True, metavar="", help="path to a JSON config file")
|
||||
|
||||
argsparser.add_argument("-s", "--source", dest="source_path",
|
||||
required=True, metavar="", help="source code path")
|
||||
|
||||
argsparser.add_argument("-p", "--prologue", dest="prologue",
|
||||
required=False, metavar="", help="path to a C prologue file")
|
||||
|
||||
amalgamation = Amalgamation(argsparser.parse_args())
|
||||
amalgamation.generate()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
8
scripts/config.json
Normal file
8
scripts/config.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"project": "entt",
|
||||
"target": "single_include/entt/entt.hpp",
|
||||
"sources": [
|
||||
"src/entt/entt.hpp"
|
||||
],
|
||||
"include_paths": ["src"]
|
||||
}
|
||||
16388
single_include/entt/entt.hpp
Normal file
16388
single_include/entt/entt.hpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -12,5 +12,36 @@
|
||||
#endif // ENTT_HS_SUFFIX
|
||||
|
||||
|
||||
#ifndef ENTT_HWS_SUFFIX
|
||||
#define ENTT_HWS_SUFFIX _hws
|
||||
#endif // ENTT_HWS_SUFFIX
|
||||
|
||||
|
||||
#ifndef ENTT_NO_ATOMIC
|
||||
#include <atomic>
|
||||
#define ENTT_MAYBE_ATOMIC(Type) std::atomic<Type>
|
||||
#else // ENTT_NO_ATOMIC
|
||||
#define ENTT_MAYBE_ATOMIC(Type) Type
|
||||
#endif // ENTT_NO_ATOMIC
|
||||
|
||||
|
||||
#ifndef ENTT_ID_TYPE
|
||||
#include <cstdint>
|
||||
#define ENTT_ID_TYPE std::uint32_t
|
||||
#endif // ENTT_ID_TYPE
|
||||
|
||||
|
||||
#ifndef ENTT_PAGE_SIZE
|
||||
#define ENTT_PAGE_SIZE 32768
|
||||
#endif // ENTT_PAGE_SIZE
|
||||
|
||||
|
||||
#ifndef ENTT_DISABLE_ASSERT
|
||||
#include <cassert>
|
||||
#define ENTT_ASSERT(condition) assert(condition)
|
||||
#else // ENTT_DISABLE_ASSERT
|
||||
#define ENTT_ASSERT(...) ((void)0)
|
||||
#endif // ENTT_DISABLE_ASSERT
|
||||
|
||||
|
||||
#endif // ENTT_CONFIG_CONFIG_H
|
||||
|
||||
11
src/entt/config/version.h
Normal file
11
src/entt/config/version.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#ifndef ENTT_CONFIG_VERSION_H
|
||||
#define ENTT_CONFIG_VERSION_H
|
||||
|
||||
|
||||
#define ENTT_VERSION "3.1.1"
|
||||
#define ENTT_VERSION_MAJOR 3
|
||||
#define ENTT_VERSION_MINOR 1
|
||||
#define ENTT_VERSION_PATCH 1
|
||||
|
||||
|
||||
#endif // ENTT_CONFIG_VERSION_H
|
||||
@@ -2,9 +2,11 @@
|
||||
#define ENTT_CORE_ALGORITHM_HPP
|
||||
|
||||
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include "utility.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
@@ -18,7 +20,7 @@ namespace entt {
|
||||
* This class fills the gap by wrapping some flavors of `std::sort` in a
|
||||
* function object.
|
||||
*/
|
||||
struct StdSort final {
|
||||
struct std_sort {
|
||||
/**
|
||||
* @brief Sorts the elements in a range.
|
||||
*
|
||||
@@ -40,7 +42,7 @@ struct StdSort final {
|
||||
|
||||
|
||||
/*! @brief Function object for performing insertion sort. */
|
||||
struct InsertionSort final {
|
||||
struct insertion_sort {
|
||||
/**
|
||||
* @brief Sorts the elements in a range.
|
||||
*
|
||||
@@ -54,51 +56,81 @@ struct InsertionSort final {
|
||||
*/
|
||||
template<typename It, typename Compare = std::less<>>
|
||||
void operator()(It first, It last, Compare compare = Compare{}) const {
|
||||
auto it = first + 1;
|
||||
if(first < last) {
|
||||
for(auto it = first+1; it < last; ++it) {
|
||||
auto value = std::move(*it);
|
||||
auto pre = it;
|
||||
|
||||
while(it != last) {
|
||||
auto value = *it;
|
||||
auto pre = it;
|
||||
for(; pre > first && compare(value, *(pre-1)); --pre) {
|
||||
*pre = std::move(*(pre-1));
|
||||
}
|
||||
|
||||
while(pre != first && compare(value, *(pre-1))) {
|
||||
*pre = *(pre-1);
|
||||
--pre;
|
||||
*pre = std::move(value);
|
||||
}
|
||||
|
||||
*pre = value;
|
||||
++it;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*! @brief Function object for performing bubble sort (single iteration). */
|
||||
struct OneShotBubbleSort final {
|
||||
/**
|
||||
* @brief Function object for performing LSD radix sort.
|
||||
* @tparam Bit Number of bits processed per pass.
|
||||
* @tparam N Maximum number of bits to sort.
|
||||
*/
|
||||
template<std::size_t Bit, std::size_t N>
|
||||
struct radix_sort {
|
||||
static_assert((N % Bit) == 0);
|
||||
|
||||
/**
|
||||
* @brief Tries to sort the elements in a range.
|
||||
* @brief Sorts the elements in a range.
|
||||
*
|
||||
* Performs a single iteration to sort the elements in a range using the
|
||||
* given binary comparison function. The range may not be completely sorted
|
||||
* after running this function.
|
||||
* Sorts the elements in a range using the given _getter_ to access the
|
||||
* actual data to be sorted.
|
||||
*
|
||||
* This implementation is inspired by the online book
|
||||
* [Physically Based Rendering](http://www.pbr-book.org/3ed-2018/Primitives_and_Intersection_Acceleration/Bounding_Volume_Hierarchies.html#RadixSort).
|
||||
*
|
||||
* @tparam It Type of random access iterator.
|
||||
* @tparam Compare Type of comparison function object.
|
||||
* @tparam Getter Type of _getter_ function object.
|
||||
* @param first An iterator to the first element of the range to sort.
|
||||
* @param last An iterator past the last element of the range to sort.
|
||||
* @param compare A valid comparison function object.
|
||||
* @param getter A valid _getter_ function object.
|
||||
*/
|
||||
template<typename It, typename Compare = std::less<>>
|
||||
void operator()(It first, It last, Compare compare = Compare{}) const {
|
||||
if(first != last) {
|
||||
auto it = first++;
|
||||
template<typename It, typename Getter = identity>
|
||||
void operator()(It first, It last, Getter getter = Getter{}) const {
|
||||
if(first < last) {
|
||||
static constexpr auto mask = (1 << Bit) - 1;
|
||||
static constexpr auto buckets = 1 << Bit;
|
||||
static constexpr auto passes = N / Bit;
|
||||
|
||||
while(first != last) {
|
||||
if(compare(*first, *it)) {
|
||||
using std::swap;
|
||||
std::swap(*first, *it);
|
||||
}
|
||||
using value_type = typename std::iterator_traits<It>::value_type;
|
||||
std::vector<value_type> aux(std::distance(first, last));
|
||||
|
||||
it = first++;
|
||||
auto part = [getter = std::move(getter)](auto from, auto to, auto out, auto start) {
|
||||
std::size_t index[buckets]{};
|
||||
std::size_t count[buckets]{};
|
||||
|
||||
std::for_each(from, to, [&getter, &count, start](const value_type &item) {
|
||||
++count[(getter(item) >> start) & mask];
|
||||
});
|
||||
|
||||
std::for_each(std::next(std::begin(index)), std::end(index), [index = std::begin(index), count = std::begin(count)](auto &item) mutable {
|
||||
item = *(index++) + *(count++);
|
||||
});
|
||||
|
||||
std::for_each(from, to, [&getter, &out, &index, start](value_type &item) {
|
||||
out[index[(getter(item) >> start) & mask]++] = std::move(item);
|
||||
});
|
||||
};
|
||||
|
||||
for(std::size_t pass = 0; pass < (passes & ~1); pass += 2) {
|
||||
part(first, last, aux.begin(), pass * Bit);
|
||||
part(aux.begin(), aux.end(), first, (pass + 1) * Bit);
|
||||
}
|
||||
|
||||
if constexpr(passes & 1) {
|
||||
part(first, last, aux.begin(), (passes - 1) * Bit);
|
||||
std::move(aux.begin(), aux.end(), first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
|
||||
|
||||
#include <type_traits>
|
||||
#include <cstddef>
|
||||
#include <atomic>
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
@@ -19,34 +17,24 @@ namespace entt {
|
||||
* identifiers.
|
||||
*/
|
||||
template<typename...>
|
||||
class Family {
|
||||
static std::atomic<std::size_t> identifier;
|
||||
class family {
|
||||
inline static ENTT_MAYBE_ATOMIC(ENTT_ID_TYPE) identifier;
|
||||
|
||||
template<typename...>
|
||||
static std::size_t family() ENTT_NOEXCEPT {
|
||||
static const std::size_t value = identifier.fetch_add(1);
|
||||
return value;
|
||||
}
|
||||
// clang (since version 9) started to complain if auto is used instead of ENTT_ID_TYPE
|
||||
inline static const ENTT_ID_TYPE inner = identifier++;
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using family_type = std::size_t;
|
||||
using family_type = ENTT_ID_TYPE;
|
||||
|
||||
/**
|
||||
* @brief Returns an unique identifier for the given type.
|
||||
* @return Statically generated unique identifier for the given type.
|
||||
*/
|
||||
/*! @brief Statically generated unique identifier for the given type. */
|
||||
template<typename... Type>
|
||||
inline static family_type type() ENTT_NOEXCEPT {
|
||||
return family<std::decay_t<Type>...>();
|
||||
}
|
||||
// at the time I'm writing, clang crashes during compilation if auto is used instead of family_type
|
||||
inline static const family_type type = inner<std::decay_t<Type>...>;
|
||||
};
|
||||
|
||||
|
||||
template<typename... Types>
|
||||
std::atomic<std::size_t> Family<Types...>::identifier{};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
@@ -11,72 +10,170 @@ namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Zero overhead resource identifier.
|
||||
* @cond TURN_OFF_DOXYGEN
|
||||
* Internal details not to be documented.
|
||||
*/
|
||||
|
||||
|
||||
namespace internal {
|
||||
|
||||
|
||||
template<typename>
|
||||
struct fnv1a_traits;
|
||||
|
||||
|
||||
template<>
|
||||
struct fnv1a_traits<std::uint32_t> {
|
||||
static constexpr std::uint32_t offset = 2166136261;
|
||||
static constexpr std::uint32_t prime = 16777619;
|
||||
};
|
||||
|
||||
|
||||
template<>
|
||||
struct fnv1a_traits<std::uint64_t> {
|
||||
static constexpr std::uint64_t offset = 14695981039346656037ull;
|
||||
static constexpr std::uint64_t prime = 1099511628211ull;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Internal details not to be documented.
|
||||
* @endcond TURN_OFF_DOXYGEN
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @brief Zero overhead unique identifier.
|
||||
*
|
||||
* A hashed string is a compile-time tool that allows users to use
|
||||
* human-readable identifers in the codebase while using their numeric
|
||||
* counterparts at runtime.<br/>
|
||||
* Because of that, a hashed string can also be used in constant expressions if
|
||||
* required.
|
||||
*
|
||||
* @tparam Char Character type.
|
||||
*/
|
||||
class HashedString final {
|
||||
struct ConstCharWrapper final {
|
||||
template<typename Char>
|
||||
class basic_hashed_string {
|
||||
using traits_type = internal::fnv1a_traits<ENTT_ID_TYPE>;
|
||||
|
||||
struct const_wrapper {
|
||||
// non-explicit constructor on purpose
|
||||
constexpr ConstCharWrapper(const char *str) ENTT_NOEXCEPT: str{str} {}
|
||||
const char *str;
|
||||
constexpr const_wrapper(const Char *curr) ENTT_NOEXCEPT: str{curr} {}
|
||||
const Char *str;
|
||||
};
|
||||
|
||||
static constexpr std::uint64_t offset = 14695981039346656037ull;
|
||||
static constexpr std::uint64_t prime = 1099511628211ull;
|
||||
|
||||
// Fowler–Noll–Vo hash function v. 1a - the good
|
||||
static constexpr std::uint64_t helper(std::uint64_t partial, const char *str) ENTT_NOEXCEPT {
|
||||
return str[0] == 0 ? partial : helper((partial^str[0])*prime, str+1);
|
||||
static constexpr ENTT_ID_TYPE helper(ENTT_ID_TYPE partial, const Char *curr) ENTT_NOEXCEPT {
|
||||
return curr[0] == 0 ? partial : helper((partial^curr[0])*traits_type::prime, curr+1);
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Character type. */
|
||||
using value_type = Char;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using hash_type = std::uint64_t;
|
||||
using hash_type = ENTT_ID_TYPE;
|
||||
|
||||
/**
|
||||
* @brief Constructs a hashed string from an array of const chars.
|
||||
* @brief Returns directly the numeric representation of a string.
|
||||
*
|
||||
* Forcing template resolution avoids implicit conversions. An
|
||||
* human-readable identifier can be anything but a plain, old bunch of
|
||||
* characters.<br/>
|
||||
* Example of use:
|
||||
* @code{.cpp}
|
||||
* HashedString sh{"my.png"};
|
||||
* const auto value = basic_hashed_string<char>::to_value("my.png");
|
||||
* @endcode
|
||||
*
|
||||
* @tparam N Number of characters of the identifier.
|
||||
* @param str Human-readable identifer.
|
||||
* @return The numeric representation of the string.
|
||||
*/
|
||||
template <std::size_t N>
|
||||
constexpr HashedString(const char (&str)[N]) ENTT_NOEXCEPT
|
||||
: hash{helper(offset, str)}, str{str}
|
||||
template<std::size_t N>
|
||||
static constexpr hash_type to_value(const value_type (&str)[N]) ENTT_NOEXCEPT {
|
||||
return helper(traits_type::offset, str);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns directly the numeric representation of a string.
|
||||
* @param wrapper Helps achieving the purpose by relying on overloading.
|
||||
* @return The numeric representation of the string.
|
||||
*/
|
||||
static hash_type to_value(const_wrapper wrapper) ENTT_NOEXCEPT {
|
||||
return helper(traits_type::offset, wrapper.str);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns directly the numeric representation of a string view.
|
||||
* @param str Human-readable identifer.
|
||||
* @param size Length of the string to hash.
|
||||
* @return The numeric representation of the string.
|
||||
*/
|
||||
static hash_type to_value(const value_type *str, std::size_t size) ENTT_NOEXCEPT {
|
||||
ENTT_ID_TYPE partial{traits_type::offset};
|
||||
while(size--) { partial = (partial^(str++)[0])*traits_type::prime; }
|
||||
return partial;
|
||||
}
|
||||
|
||||
/*! @brief Constructs an empty hashed string. */
|
||||
constexpr basic_hashed_string() ENTT_NOEXCEPT
|
||||
: str{nullptr}, hash{}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Constructs a hashed string from an array of const characters.
|
||||
*
|
||||
* Forcing template resolution avoids implicit conversions. An
|
||||
* human-readable identifier can be anything but a plain, old bunch of
|
||||
* characters.<br/>
|
||||
* Example of use:
|
||||
* @code{.cpp}
|
||||
* basic_hashed_string<char> hs{"my.png"};
|
||||
* @endcode
|
||||
*
|
||||
* @tparam N Number of characters of the identifier.
|
||||
* @param curr Human-readable identifer.
|
||||
*/
|
||||
template<std::size_t N>
|
||||
constexpr basic_hashed_string(const value_type (&curr)[N]) ENTT_NOEXCEPT
|
||||
: str{curr}, hash{helper(traits_type::offset, curr)}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Explicit constructor on purpose to avoid constructing a hashed
|
||||
* string directly from a `const char *`.
|
||||
*
|
||||
* string directly from a `const value_type *`.
|
||||
* @param wrapper Helps achieving the purpose by relying on overloading.
|
||||
*/
|
||||
explicit constexpr HashedString(ConstCharWrapper wrapper) ENTT_NOEXCEPT
|
||||
: hash{helper(offset, wrapper.str)}, str{wrapper.str}
|
||||
explicit constexpr basic_hashed_string(const_wrapper wrapper) ENTT_NOEXCEPT
|
||||
: str{wrapper.str}, hash{helper(traits_type::offset, wrapper.str)}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Returns the human-readable representation of a hashed string.
|
||||
* @return The string used to initialize the instance.
|
||||
*/
|
||||
constexpr operator const char *() const ENTT_NOEXCEPT { return str; }
|
||||
constexpr const value_type * data() const ENTT_NOEXCEPT {
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the numeric representation of a hashed string.
|
||||
* @return The numeric representation of the instance.
|
||||
*/
|
||||
constexpr hash_type value() const ENTT_NOEXCEPT {
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the human-readable representation of a hashed string.
|
||||
* @return The string used to initialize the instance.
|
||||
*/
|
||||
constexpr operator const value_type *() const ENTT_NOEXCEPT { return str; }
|
||||
|
||||
/*! @copydoc value */
|
||||
constexpr operator hash_type() const ENTT_NOEXCEPT { return hash; }
|
||||
|
||||
/**
|
||||
@@ -84,27 +181,52 @@ public:
|
||||
* @param other Hashed string with which to compare.
|
||||
* @return True if the two hashed strings are identical, false otherwise.
|
||||
*/
|
||||
constexpr bool operator==(const HashedString &other) const ENTT_NOEXCEPT {
|
||||
constexpr bool operator==(const basic_hashed_string &other) const ENTT_NOEXCEPT {
|
||||
return hash == other.hash;
|
||||
}
|
||||
|
||||
private:
|
||||
const hash_type hash;
|
||||
const char *str;
|
||||
const value_type *str;
|
||||
hash_type hash;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Deduction guide.
|
||||
*
|
||||
* It allows to deduce the character type of the hashed string directly from a
|
||||
* human-readable identifer provided to the constructor.
|
||||
*
|
||||
* @tparam Char Character type.
|
||||
* @tparam N Number of characters of the identifier.
|
||||
* @param str Human-readable identifer.
|
||||
*/
|
||||
template<typename Char, std::size_t N>
|
||||
basic_hashed_string(const Char (&str)[N]) ENTT_NOEXCEPT
|
||||
-> basic_hashed_string<Char>;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Compares two hashed strings.
|
||||
* @tparam Char Character type.
|
||||
* @param lhs A valid hashed string.
|
||||
* @param rhs A valid hashed string.
|
||||
* @return True if the two hashed strings are identical, false otherwise.
|
||||
*/
|
||||
constexpr bool operator!=(const HashedString &lhs, const HashedString &rhs) ENTT_NOEXCEPT {
|
||||
template<typename Char>
|
||||
constexpr bool operator!=(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) ENTT_NOEXCEPT {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
|
||||
/*! @brief Aliases for common character types. */
|
||||
using hashed_string = basic_hashed_string<char>;
|
||||
|
||||
|
||||
/*! @brief Aliases for common character types. */
|
||||
using hashed_wstring = basic_hashed_string<wchar_t>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -113,8 +235,18 @@ constexpr bool operator!=(const HashedString &lhs, const HashedString &rhs) ENTT
|
||||
* @param str The literal without its suffix.
|
||||
* @return A properly initialized hashed string.
|
||||
*/
|
||||
constexpr entt::HashedString operator"" ENTT_HS_SUFFIX(const char *str, std::size_t) ENTT_NOEXCEPT {
|
||||
return entt::HashedString{str};
|
||||
constexpr entt::hashed_string operator"" ENTT_HS_SUFFIX(const char *str, std::size_t) ENTT_NOEXCEPT {
|
||||
return entt::hashed_string{str};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief User defined literal for hashed wstrings.
|
||||
* @param str The literal without its suffix.
|
||||
* @return A properly initialized hashed wstring.
|
||||
*/
|
||||
constexpr entt::hashed_wstring operator"" ENTT_HWS_SUFFIX(const wchar_t *str, std::size_t) ENTT_NOEXCEPT {
|
||||
return entt::hashed_wstring{str};
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,44 +2,15 @@
|
||||
#define ENTT_CORE_IDENT_HPP
|
||||
|
||||
|
||||
#include <type_traits>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
namespace internal {
|
||||
|
||||
|
||||
/**
|
||||
* @cond TURN_OFF_DOXYGEN
|
||||
* Internal details not to be documented.
|
||||
*/
|
||||
|
||||
|
||||
template<typename...>
|
||||
struct IsPartOf;
|
||||
|
||||
template<typename Type, typename Current, typename... Other>
|
||||
struct IsPartOf<Type, Current, Other...>: std::conditional_t<std::is_same<Type, Current>::value, std::true_type, IsPartOf<Type, Other...>> {};
|
||||
|
||||
template<typename Type>
|
||||
struct IsPartOf<Type>: std::false_type {};
|
||||
|
||||
|
||||
/**
|
||||
* Internal details not to be documented.
|
||||
* @endcond TURN_OFF_DOXYGEN
|
||||
*/
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Types identifiers.
|
||||
*
|
||||
@@ -51,13 +22,13 @@ struct IsPartOf<Type>: std::false_type {};
|
||||
* Identifiers are constant expression and can be used in any context where such
|
||||
* an expression is required. As an example:
|
||||
* @code{.cpp}
|
||||
* using ID = entt::Identifier<AType, AnotherType>;
|
||||
* using id = entt::identifier<a_type, another_type>;
|
||||
*
|
||||
* switch(aTypeIdentifier) {
|
||||
* case ID::get<AType>():
|
||||
* switch(a_type_identifier) {
|
||||
* case id::type<a_type>:
|
||||
* // ...
|
||||
* break;
|
||||
* case ID::get<AnotherType>():
|
||||
* case id::type<another_type>:
|
||||
* // ...
|
||||
* break;
|
||||
* default:
|
||||
@@ -68,33 +39,22 @@ struct IsPartOf<Type>: std::false_type {};
|
||||
* @tparam Types List of types for which to generate identifiers.
|
||||
*/
|
||||
template<typename... Types>
|
||||
class Identifier final {
|
||||
class identifier {
|
||||
using tuple_type = std::tuple<std::decay_t<Types>...>;
|
||||
|
||||
template<typename Type, std::size_t... Indexes>
|
||||
static constexpr std::size_t get(std::index_sequence<Indexes...>) ENTT_NOEXCEPT {
|
||||
static_assert(internal::IsPartOf<Type, Types...>::value, "!");
|
||||
|
||||
std::size_t max{};
|
||||
using accumulator_type = std::size_t[];
|
||||
accumulator_type accumulator = { (max = std::is_same<Type, std::tuple_element_t<Indexes, tuple_type>>::value ? Indexes : max)... };
|
||||
(void)accumulator;
|
||||
return max;
|
||||
static constexpr ENTT_ID_TYPE get(std::index_sequence<Indexes...>) ENTT_NOEXCEPT {
|
||||
static_assert(std::disjunction_v<std::is_same<Type, Types>...>);
|
||||
return (0 + ... + (std::is_same_v<Type, std::tuple_element_t<Indexes, tuple_type>> ? ENTT_ID_TYPE(Indexes) : ENTT_ID_TYPE{}));
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using identifier_type = std::size_t;
|
||||
using identifier_type = ENTT_ID_TYPE;
|
||||
|
||||
/**
|
||||
* @brief Returns the identifier associated with a given type.
|
||||
* @tparam Type of which to return the identifier.
|
||||
* @return The identifier associated with the given type.
|
||||
*/
|
||||
/*! @brief Statically generated unique identifier for the given type. */
|
||||
template<typename Type>
|
||||
static constexpr identifier_type get() ENTT_NOEXCEPT {
|
||||
return get<std::decay_t<Type>>(std::make_index_sequence<sizeof...(Types)>{});
|
||||
}
|
||||
static constexpr identifier_type type = get<std::decay_t<Type>>(std::make_index_sequence<sizeof...(Types)>{});
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
#define ENTT_CORE_MONOSTATE_HPP
|
||||
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include "family.hpp"
|
||||
#include "hashed_string.hpp"
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
namespace entt {
|
||||
@@ -22,8 +20,8 @@ namespace entt {
|
||||
* both during an assignment and when they try to read back their data.
|
||||
* Otherwise, they can incur in unexpected results.
|
||||
*/
|
||||
template<HashedString::hash_type>
|
||||
struct Monostate {
|
||||
template<ENTT_ID_TYPE>
|
||||
struct monostate {
|
||||
/**
|
||||
* @brief Assigns a value of a specific type to a given key.
|
||||
* @tparam Type Type of the value to assign.
|
||||
@@ -31,7 +29,7 @@ struct Monostate {
|
||||
*/
|
||||
template<typename Type>
|
||||
void operator=(Type val) const ENTT_NOEXCEPT {
|
||||
Monostate::value<Type> = val;
|
||||
value<Type> = val;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,18 +39,21 @@ struct Monostate {
|
||||
*/
|
||||
template<typename Type>
|
||||
operator Type() const ENTT_NOEXCEPT {
|
||||
return Monostate::value<Type>;
|
||||
return value<Type>;
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename Type>
|
||||
static std::atomic<Type> value;
|
||||
inline static ENTT_MAYBE_ATOMIC(Type) value{};
|
||||
};
|
||||
|
||||
|
||||
template<HashedString::hash_type ID>
|
||||
template<typename Type>
|
||||
std::atomic<Type> Monostate<ID>::value{};
|
||||
/**
|
||||
* @brief Helper variable template.
|
||||
* @tparam Value Value used to differentiate between different variables.
|
||||
*/
|
||||
template<ENTT_ID_TYPE Value>
|
||||
inline monostate<Value> monostate_v = {};
|
||||
|
||||
|
||||
}
|
||||
|
||||
277
src/entt/core/type_traits.hpp
Normal file
277
src/entt/core/type_traits.hpp
Normal file
@@ -0,0 +1,277 @@
|
||||
#ifndef ENTT_CORE_TYPE_TRAITS_HPP
|
||||
#define ENTT_CORE_TYPE_TRAITS_HPP
|
||||
|
||||
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "../core/hashed_string.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/*! @brief A class to use to push around lists of types, nothing more. */
|
||||
template<typename...>
|
||||
struct type_list {};
|
||||
|
||||
|
||||
/*! @brief Primary template isn't defined on purpose. */
|
||||
template<typename>
|
||||
struct type_list_size;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Compile-time number of elements in a type list.
|
||||
* @tparam Type Types provided by the type list.
|
||||
*/
|
||||
template<typename... Type>
|
||||
struct type_list_size<type_list<Type...>>
|
||||
: std::integral_constant<std::size_t, sizeof...(Type)>
|
||||
{};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Helper variable template.
|
||||
* @tparam List Type list.
|
||||
*/
|
||||
template<class List>
|
||||
constexpr auto type_list_size_v = type_list_size<List>::value;
|
||||
|
||||
|
||||
/*! @brief Primary template isn't defined on purpose. */
|
||||
template<typename...>
|
||||
struct type_list_cat;
|
||||
|
||||
|
||||
/*! @brief Concatenates multiple type lists. */
|
||||
template<>
|
||||
struct type_list_cat<> {
|
||||
/*! @brief A type list composed by the types of all the type lists. */
|
||||
using type = type_list<>;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Concatenates multiple type lists.
|
||||
* @tparam Type Types provided by the first type list.
|
||||
* @tparam Other Types provided by the second type list.
|
||||
* @tparam List Other type lists, if any.
|
||||
*/
|
||||
template<typename... Type, typename... Other, typename... List>
|
||||
struct type_list_cat<type_list<Type...>, type_list<Other...>, List...> {
|
||||
/*! @brief A type list composed by the types of all the type lists. */
|
||||
using type = typename type_list_cat<type_list<Type..., Other...>, List...>::type;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Concatenates multiple type lists.
|
||||
* @tparam Type Types provided by the type list.
|
||||
*/
|
||||
template<typename... Type>
|
||||
struct type_list_cat<type_list<Type...>> {
|
||||
/*! @brief A type list composed by the types of all the type lists. */
|
||||
using type = type_list<Type...>;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Helper type.
|
||||
* @tparam List Type lists to concatenate.
|
||||
*/
|
||||
template<typename... List>
|
||||
using type_list_cat_t = typename type_list_cat<List...>::type;
|
||||
|
||||
|
||||
/*! @brief Primary template isn't defined on purpose. */
|
||||
template<typename>
|
||||
struct type_list_unique;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Removes duplicates types from a type list.
|
||||
* @tparam Type One of the types provided by the given type list.
|
||||
* @tparam Other The other types provided by the given type list.
|
||||
*/
|
||||
template<typename Type, typename... Other>
|
||||
struct type_list_unique<type_list<Type, Other...>> {
|
||||
/*! @brief A type list without duplicate types. */
|
||||
using type = std::conditional_t<
|
||||
std::disjunction_v<std::is_same<Type, Other>...>,
|
||||
typename type_list_unique<type_list<Other...>>::type,
|
||||
type_list_cat_t<type_list<Type>, typename type_list_unique<type_list<Other...>>::type>
|
||||
>;
|
||||
};
|
||||
|
||||
|
||||
/*! @brief Removes duplicates types from a type list. */
|
||||
template<>
|
||||
struct type_list_unique<type_list<>> {
|
||||
/*! @brief A type list without duplicate types. */
|
||||
using type = type_list<>;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Helper type.
|
||||
* @tparam Type A type list.
|
||||
*/
|
||||
template<typename Type>
|
||||
using type_list_unique_t = typename type_list_unique<Type>::type;
|
||||
|
||||
|
||||
/*! @brief Traits class used mainly to push things across boundaries. */
|
||||
template<typename>
|
||||
struct named_type_traits;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Specialization used to get rid of constness.
|
||||
* @tparam Type Named type.
|
||||
*/
|
||||
template<typename Type>
|
||||
struct named_type_traits<const Type>
|
||||
: named_type_traits<Type>
|
||||
{};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Helper type.
|
||||
* @tparam Type Potentially named type.
|
||||
*/
|
||||
template<typename Type>
|
||||
using named_type_traits_t = typename named_type_traits<Type>::type;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Provides the member constant `value` to true if a given type has a
|
||||
* name. In all other cases, `value` is false.
|
||||
*/
|
||||
template<typename, typename = std::void_t<>>
|
||||
struct is_named_type: std::false_type {};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Provides the member constant `value` to true if a given type has a
|
||||
* name. In all other cases, `value` is false.
|
||||
* @tparam Type Potentially named type.
|
||||
*/
|
||||
template<typename Type>
|
||||
struct is_named_type<Type, std::void_t<named_type_traits_t<std::decay_t<Type>>>>: std::true_type {};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Helper variable template.
|
||||
* @tparam Type Potentially named type.
|
||||
*/
|
||||
template<class Type>
|
||||
constexpr auto is_named_type_v = is_named_type<Type>::value;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Defines an enum class to use for opaque identifiers and a dedicate
|
||||
* `to_integer` function to convert the identifiers to their underlying type.
|
||||
* @param clazz The name to use for the enum class.
|
||||
* @param type The underlying type for the enum class.
|
||||
*/
|
||||
#define ENTT_OPAQUE_TYPE(clazz, type)\
|
||||
enum class clazz: type {};\
|
||||
constexpr auto to_integer(const clazz id) ENTT_NOEXCEPT {\
|
||||
return std::underlying_type_t<clazz>(id);\
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Utility macro to deal with an issue of MSVC.
|
||||
*
|
||||
* See _msvc-doesnt-expand-va-args-correctly_ on SO for all the details.
|
||||
*
|
||||
* @param args Argument to expand.
|
||||
*/
|
||||
#define ENTT_EXPAND(args) args
|
||||
|
||||
|
||||
/**
|
||||
* @brief Makes an already existing type a named type.
|
||||
*
|
||||
* The current definition contains a workaround for Clang 6 because it fails to
|
||||
* deduce correctly the type to use to specialize the class template.<br/>
|
||||
* With a compiler that fully supports C++17 and works fine with deduction
|
||||
* guides, the following should be fine instead:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* std::integral_constant<ENTT_ID_TYPE, entt::basic_hashed_string{#type}>
|
||||
* @endcode
|
||||
*
|
||||
* In order to support even sligthly older compilers, I prefer to stick to the
|
||||
* implementation below.
|
||||
*
|
||||
* @param type Type to assign a name to.
|
||||
*/
|
||||
#define ENTT_NAMED_TYPE(type)\
|
||||
template<>\
|
||||
struct entt::named_type_traits<type>\
|
||||
: std::integral_constant<ENTT_ID_TYPE, entt::basic_hashed_string<std::remove_cv_t<std::remove_pointer_t<std::decay_t<decltype(#type)>>>>{#type}>\
|
||||
{\
|
||||
static_assert(std::is_same_v<std::decay_t<type>, type>);\
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Defines a named type (to use for structs).
|
||||
* @param clazz Name of the type to define.
|
||||
* @param body Body of the type to define.
|
||||
*/
|
||||
#define ENTT_NAMED_STRUCT_ONLY(clazz, body)\
|
||||
struct clazz body;\
|
||||
ENTT_NAMED_TYPE(clazz)
|
||||
|
||||
|
||||
/**
|
||||
* @brief Defines a named type (to use for structs).
|
||||
* @param ns Namespace where to define the named type.
|
||||
* @param clazz Name of the type to define.
|
||||
* @param body Body of the type to define.
|
||||
*/
|
||||
#define ENTT_NAMED_STRUCT_WITH_NAMESPACE(ns, clazz, body)\
|
||||
namespace ns { struct clazz body; }\
|
||||
ENTT_NAMED_TYPE(ns::clazz)
|
||||
|
||||
|
||||
/*! @brief Utility function to simulate macro overloading. */
|
||||
#define ENTT_NAMED_STRUCT_OVERLOAD(_1, _2, _3, FUNC, ...) FUNC
|
||||
/*! @brief Defines a named type (to use for structs). */
|
||||
#define ENTT_NAMED_STRUCT(...) ENTT_EXPAND(ENTT_NAMED_STRUCT_OVERLOAD(__VA_ARGS__, ENTT_NAMED_STRUCT_WITH_NAMESPACE, ENTT_NAMED_STRUCT_ONLY,)(__VA_ARGS__))
|
||||
|
||||
|
||||
/**
|
||||
* @brief Defines a named type (to use for classes).
|
||||
* @param clazz Name of the type to define.
|
||||
* @param body Body of the type to define.
|
||||
*/
|
||||
#define ENTT_NAMED_CLASS_ONLY(clazz, body)\
|
||||
class clazz body;\
|
||||
ENTT_NAMED_TYPE(clazz)
|
||||
|
||||
|
||||
/**
|
||||
* @brief Defines a named type (to use for classes).
|
||||
* @param ns Namespace where to define the named type.
|
||||
* @param clazz Name of the type to define.
|
||||
* @param body Body of the type to define.
|
||||
*/
|
||||
#define ENTT_NAMED_CLASS_WITH_NAMESPACE(ns, clazz, body)\
|
||||
namespace ns { class clazz body; }\
|
||||
ENTT_NAMED_TYPE(ns::clazz)
|
||||
|
||||
|
||||
/*! @brief Utility function to simulate macro overloading. */
|
||||
#define ENTT_NAMED_CLASS_MACRO(_1, _2, _3, FUNC, ...) FUNC
|
||||
/*! @brief Defines a named type (to use for classes). */
|
||||
#define ENTT_NAMED_CLASS(...) ENTT_EXPAND(ENTT_NAMED_CLASS_MACRO(__VA_ARGS__, ENTT_NAMED_CLASS_WITH_NAMESPACE, ENTT_NAMED_CLASS_ONLY,)(__VA_ARGS__))
|
||||
|
||||
|
||||
#endif // ENTT_CORE_TYPE_TRAITS_HPP
|
||||
50
src/entt/core/utility.hpp
Normal file
50
src/entt/core/utility.hpp
Normal file
@@ -0,0 +1,50 @@
|
||||
#ifndef ENTT_CORE_UTILITY_HPP
|
||||
#define ENTT_CORE_UTILITY_HPP
|
||||
|
||||
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/*! @brief Identity function object (waiting for C++20). */
|
||||
struct identity {
|
||||
/**
|
||||
* @brief Returns its argument unchanged.
|
||||
* @tparam Type Type of the argument.
|
||||
* @param value The actual argument.
|
||||
* @return The submitted value as-is.
|
||||
*/
|
||||
template<class Type>
|
||||
constexpr Type && operator()(Type &&value) const ENTT_NOEXCEPT {
|
||||
return std::forward<Type>(value);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Constant utility to disambiguate overloaded member functions.
|
||||
* @tparam Type Function type of the desired overload.
|
||||
* @tparam Class Type of class to which the member functions belong.
|
||||
* @param member A valid pointer to a member function.
|
||||
* @return Pointer to the member function.
|
||||
*/
|
||||
template<typename Type, typename Class>
|
||||
constexpr auto overload(Type Class:: *member) ENTT_NOEXCEPT { return member; }
|
||||
|
||||
|
||||
/**
|
||||
* @brief Constant utility to disambiguate overloaded functions.
|
||||
* @tparam Type Function type of the desired overload.
|
||||
* @param func A valid pointer to a function.
|
||||
* @return Pointer to the function.
|
||||
*/
|
||||
template<typename Type>
|
||||
constexpr auto overload(Type *func) ENTT_NOEXCEPT { return func; }
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_CORE_UTILITY_HPP
|
||||
@@ -4,67 +4,96 @@
|
||||
|
||||
#include <cassert>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "registry.hpp"
|
||||
#include "entity.hpp"
|
||||
#include "fwd.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Dedicated to those who aren't confident with entity-component systems.
|
||||
* @brief Dedicated to those who aren't confident with the
|
||||
* entity-component-system architecture.
|
||||
*
|
||||
* Tiny wrapper around a registry, for all those users that aren't confident
|
||||
* with entity-component systems and prefer to iterate objects directly.
|
||||
* with entity-component-system architecture and prefer to iterate objects
|
||||
* directly.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
struct Actor {
|
||||
struct basic_actor {
|
||||
/*! @brief Type of registry used internally. */
|
||||
using registry_type = Registry<Entity>;
|
||||
using registry_type = basic_registry<Entity>;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
|
||||
/**
|
||||
* @brief Constructs an actor by using the given registry.
|
||||
* @param reg An entity-component system properly initialized.
|
||||
*/
|
||||
Actor(Registry<Entity> ®)
|
||||
: reg{reg}, entt{reg.create()}
|
||||
basic_actor() ENTT_NOEXCEPT
|
||||
: entt{entt::null}, reg{nullptr}
|
||||
{}
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
virtual ~Actor() {
|
||||
reg.destroy(entt);
|
||||
}
|
||||
|
||||
/*! @brief Default copy constructor. */
|
||||
Actor(const Actor &) = default;
|
||||
/*! @brief Default move constructor. */
|
||||
Actor(Actor &&) = default;
|
||||
|
||||
/*! @brief Default copy assignment operator. @return This actor. */
|
||||
Actor & operator=(const Actor &) = default;
|
||||
/*! @brief Default move assignment operator. @return This actor. */
|
||||
Actor & operator=(Actor &&) = default;
|
||||
/**
|
||||
* @brief Constructs an actor from a given registry.
|
||||
* @param ref An instance of the registry class.
|
||||
*/
|
||||
explicit basic_actor(registry_type &ref)
|
||||
: entt{ref.create()}, reg{&ref}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Assigns the given tag to an actor.
|
||||
*
|
||||
* A new instance of the given tag is created and initialized with the
|
||||
* arguments provided (the tag must have a proper constructor or be of
|
||||
* aggregate type). Then the tag is removed from its previous owner (if any)
|
||||
* and assigned to the actor.
|
||||
*
|
||||
* @tparam Tag Type of the tag to create.
|
||||
* @tparam Args Types of arguments to use to construct the tag.
|
||||
* @param args Parameters to use to initialize the tag.
|
||||
* @return A reference to the newly created tag.
|
||||
* @brief Constructs an actor from a given entity.
|
||||
* @param entity A valid entity identifier.
|
||||
* @param ref An instance of the registry class.
|
||||
*/
|
||||
template<typename Tag, typename... Args>
|
||||
Tag & assign(tag_t, Args &&... args) {
|
||||
return (reg.template remove<Tag>(), reg.template assign<Tag>(tag_t{}, entt, std::forward<Args>(args)...));
|
||||
explicit basic_actor(entity_type entity, registry_type &ref)
|
||||
: entt{entity}, reg{&ref}
|
||||
{
|
||||
ENTT_ASSERT(ref.valid(entity));
|
||||
}
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
virtual ~basic_actor() {
|
||||
if(*this) {
|
||||
reg->destroy(entt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*
|
||||
* After actor move construction, instances that have been moved from are
|
||||
* placed in a valid but unspecified state. It's highly discouraged to
|
||||
* continue using them.
|
||||
*
|
||||
* @param other The instance to move from.
|
||||
*/
|
||||
basic_actor(basic_actor &&other)
|
||||
: entt{other.entt}, reg{other.reg}
|
||||
{
|
||||
other.entt = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move assignment operator.
|
||||
*
|
||||
* After actor move assignment, instances that have been moved from are
|
||||
* placed in a valid but unspecified state. It's highly discouraged to
|
||||
* continue using them.
|
||||
*
|
||||
* @param other The instance to move from.
|
||||
* @return This actor.
|
||||
*/
|
||||
basic_actor & operator=(basic_actor &&other) {
|
||||
if(this != &other) {
|
||||
auto tmp{std::move(other)};
|
||||
std::swap(reg, tmp.reg);
|
||||
std::swap(entt, tmp.entt);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,18 +111,8 @@ struct Actor {
|
||||
* @return A reference to the newly created component.
|
||||
*/
|
||||
template<typename Component, typename... Args>
|
||||
Component & assign(Args &&... args) {
|
||||
return reg.template accommodate<Component>(entt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes the given tag from an actor.
|
||||
* @tparam Tag Type of the tag to remove.
|
||||
*/
|
||||
template<typename Tag>
|
||||
void remove(tag_t) {
|
||||
assert(has<Tag>(tag_t{}));
|
||||
reg.template remove<Tag>();
|
||||
decltype(auto) assign(Args &&... args) {
|
||||
return reg->template assign_or_replace<Component>(entt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,109 +121,86 @@ struct Actor {
|
||||
*/
|
||||
template<typename Component>
|
||||
void remove() {
|
||||
reg.template remove<Component>(entt);
|
||||
reg->template remove<Component>(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if an actor owns the given tag.
|
||||
* @tparam Tag Type of the tag for which to perform the check.
|
||||
* @return True if the actor owns the tag, false otherwise.
|
||||
* @brief Checks if an actor has the given components.
|
||||
* @tparam Component Components for which to perform the check.
|
||||
* @return True if the actor has all the components, false otherwise.
|
||||
*/
|
||||
template<typename Tag>
|
||||
bool has(tag_t) const ENTT_NOEXCEPT {
|
||||
return (reg.template has<Tag>() && (reg.template attachee<Tag>() == entt));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if an actor has the given component.
|
||||
* @tparam Component Type of the component for which to perform the check.
|
||||
* @return True if the actor has the component, false otherwise.
|
||||
*/
|
||||
template<typename Component>
|
||||
template<typename... Component>
|
||||
bool has() const ENTT_NOEXCEPT {
|
||||
return reg.template has<Component>(entt);
|
||||
return (reg->template has<Component>(entt) && ...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the given tag for an actor.
|
||||
* @tparam Tag Type of the tag to get.
|
||||
* @return A reference to the instance of the tag owned by the actor.
|
||||
* @brief Returns references to the given components for an actor.
|
||||
* @tparam Component Types of components to get.
|
||||
* @return References to the components owned by the actor.
|
||||
*/
|
||||
template<typename Tag>
|
||||
const Tag & get(tag_t) const ENTT_NOEXCEPT {
|
||||
assert(has<Tag>(tag_t{}));
|
||||
return reg.template get<Tag>();
|
||||
template<typename... Component>
|
||||
decltype(auto) get() const ENTT_NOEXCEPT {
|
||||
return std::as_const(*reg).template get<Component...>(entt);
|
||||
}
|
||||
|
||||
/*! @copydoc get */
|
||||
template<typename... Component>
|
||||
decltype(auto) get() ENTT_NOEXCEPT {
|
||||
return reg->template get<Component...>(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the given tag for an actor.
|
||||
* @tparam Tag Type of the tag to get.
|
||||
* @return A reference to the instance of the tag owned by the actor.
|
||||
* @brief Returns pointers to the given components for an actor.
|
||||
* @tparam Component Types of components to get.
|
||||
* @return Pointers to the components owned by the actor.
|
||||
*/
|
||||
template<typename Tag>
|
||||
inline Tag & get(tag_t) ENTT_NOEXCEPT {
|
||||
return const_cast<Tag &>(const_cast<const Actor *>(this)->get<Tag>(tag_t{}));
|
||||
template<typename... Component>
|
||||
auto try_get() const ENTT_NOEXCEPT {
|
||||
return std::as_const(*reg).template try_get<Component...>(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the given component for an actor.
|
||||
* @tparam Component Type of the component to get.
|
||||
* @return A reference to the instance of the component owned by the actor.
|
||||
*/
|
||||
template<typename Component>
|
||||
const Component & get() const ENTT_NOEXCEPT {
|
||||
return reg.template get<Component>(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the given component for an actor.
|
||||
* @tparam Component Type of the component to get.
|
||||
* @return A reference to the instance of the component owned by the actor.
|
||||
*/
|
||||
template<typename Component>
|
||||
inline Component & get() ENTT_NOEXCEPT {
|
||||
return const_cast<Component &>(const_cast<const Actor *>(this)->get<Component>());
|
||||
/*! @copydoc try_get */
|
||||
template<typename... Component>
|
||||
auto try_get() ENTT_NOEXCEPT {
|
||||
return reg->template try_get<Component...>(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the underlying registry.
|
||||
* @return A reference to the underlying registry.
|
||||
*/
|
||||
inline const registry_type & registry() const ENTT_NOEXCEPT {
|
||||
return reg;
|
||||
const registry_type & backend() const ENTT_NOEXCEPT {
|
||||
return *reg;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the underlying registry.
|
||||
* @return A reference to the underlying registry.
|
||||
*/
|
||||
inline registry_type & registry() ENTT_NOEXCEPT {
|
||||
return const_cast<registry_type &>(const_cast<const Actor *>(this)->registry());
|
||||
/*! @copydoc backend */
|
||||
registry_type & backend() ENTT_NOEXCEPT {
|
||||
return const_cast<registry_type &>(std::as_const(*this).backend());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the entity associated with an actor.
|
||||
* @return The entity associated with the actor.
|
||||
*/
|
||||
inline entity_type entity() const ENTT_NOEXCEPT {
|
||||
entity_type entity() const ENTT_NOEXCEPT {
|
||||
return entt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if an actor refers to a valid entity or not.
|
||||
* @return True if the actor refers to a valid entity, false otherwise.
|
||||
*/
|
||||
explicit operator bool() const ENTT_NOEXCEPT {
|
||||
return reg && reg->valid(entt);
|
||||
}
|
||||
|
||||
private:
|
||||
registry_type ®
|
||||
Entity entt;
|
||||
entity_type entt;
|
||||
registry_type *reg;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Default actor class.
|
||||
*
|
||||
* The default actor is the best choice for almost all the applications.<br/>
|
||||
* Users should have a really good reason to choose something different.
|
||||
*/
|
||||
using DefaultActor = Actor<DefaultRegistry::entity_type>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,14 +2,100 @@
|
||||
#define ENTT_ENTITY_ENTITY_HPP
|
||||
|
||||
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "entt_traits.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
namespace internal {
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
template<typename>
|
||||
struct entt_traits;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Entity traits for a 16 bits entity identifier.
|
||||
*
|
||||
* A 16 bits entity identifier guarantees:
|
||||
*
|
||||
* * 12 bits for the entity number (up to 4k entities).
|
||||
* * 4 bit for the version (resets in [0-15]).
|
||||
*/
|
||||
template<>
|
||||
struct entt_traits<std::uint16_t> {
|
||||
/*! @brief Underlying entity type. */
|
||||
using entity_type = std::uint16_t;
|
||||
/*! @brief Underlying version type. */
|
||||
using version_type = std::uint8_t;
|
||||
/*! @brief Difference type. */
|
||||
using difference_type = std::int32_t;
|
||||
|
||||
/*! @brief Mask to use to get the entity number out of an identifier. */
|
||||
static constexpr std::uint16_t entity_mask = 0xFFF;
|
||||
/*! @brief Mask to use to get the version out of an identifier. */
|
||||
static constexpr std::uint16_t version_mask = 0xF;
|
||||
/*! @brief Extent of the entity number within an identifier. */
|
||||
static constexpr auto entity_shift = 12;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @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 std::uint32_t entity_mask = 0xFFFFF;
|
||||
/*! @brief Mask to use to get the version out of an identifier. */
|
||||
static constexpr std::uint32_t version_mask = 0xFFF;
|
||||
/*! @brief Extent of the entity number within an identifier. */
|
||||
static constexpr auto entity_shift = 20;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @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 std::uint64_t entity_mask = 0xFFFFFFFF;
|
||||
/*! @brief Mask to use to get the version out of an identifier. */
|
||||
static constexpr std::uint64_t version_mask = 0xFFFFFFFF;
|
||||
/*! @brief Extent of the entity number within an identifier. */
|
||||
static constexpr auto entity_shift = 32;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
@@ -18,47 +104,51 @@ namespace internal {
|
||||
*/
|
||||
|
||||
|
||||
template<typename Entity>
|
||||
static constexpr auto null = ~typename entt_traits<Entity>::entity_type{};
|
||||
namespace internal {
|
||||
|
||||
|
||||
struct Null {
|
||||
explicit constexpr Null() = default;
|
||||
class null {
|
||||
template<typename Entity>
|
||||
using traits_type = entt_traits<std::underlying_type_t<Entity>>;
|
||||
|
||||
public:
|
||||
template<typename Entity>
|
||||
constexpr operator Entity() const ENTT_NOEXCEPT {
|
||||
return null<Entity>;
|
||||
return Entity{traits_type<Entity>::entity_mask};
|
||||
}
|
||||
|
||||
constexpr bool operator==(Null) const ENTT_NOEXCEPT {
|
||||
constexpr bool operator==(null) const ENTT_NOEXCEPT {
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr bool operator!=(Null) const ENTT_NOEXCEPT {
|
||||
constexpr bool operator!=(null) const ENTT_NOEXCEPT {
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename Entity>
|
||||
constexpr bool operator==(const Entity entity) const ENTT_NOEXCEPT {
|
||||
return entity == null<Entity>;
|
||||
return (to_integer(entity) & traits_type<Entity>::entity_mask) == to_integer(static_cast<Entity>(*this));
|
||||
}
|
||||
|
||||
template<typename Entity>
|
||||
constexpr bool operator!=(const Entity entity) const ENTT_NOEXCEPT {
|
||||
return entity != null<Entity>;
|
||||
return !(entity == *this);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename Entity>
|
||||
constexpr bool operator==(const Entity entity, Null null) ENTT_NOEXCEPT {
|
||||
return null == entity;
|
||||
constexpr bool operator==(const Entity entity, null other) ENTT_NOEXCEPT {
|
||||
return other == entity;
|
||||
}
|
||||
|
||||
|
||||
template<typename Entity>
|
||||
constexpr bool operator!=(const Entity entity, Null null) ENTT_NOEXCEPT {
|
||||
return null != entity;
|
||||
constexpr bool operator!=(const Entity entity, null other) ENTT_NOEXCEPT {
|
||||
return other != entity;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -68,17 +158,14 @@ constexpr bool operator!=(const Entity entity, Null null) ENTT_NOEXCEPT {
|
||||
*/
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Null entity.
|
||||
* @brief Compile-time constant for null entities.
|
||||
*
|
||||
* There exist implicit conversions from this variable to entity identifiers of
|
||||
* any allowed type. Similarly, there exist comparision operators between the
|
||||
* null entity and any other entity identifier.
|
||||
*/
|
||||
constexpr auto null = internal::Null{};
|
||||
constexpr auto null = internal::null{};
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
#ifndef ENTT_ENTITY_ENTT_TRAITS_HPP
|
||||
#define ENTT_ENTITY_ENTT_TRAITS_HPP
|
||||
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
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.
|
||||
*/
|
||||
template<typename>
|
||||
struct entt_traits;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Entity traits for a 16 bits entity identifier.
|
||||
*
|
||||
* A 16 bits entity identifier guarantees:
|
||||
*
|
||||
* * 12 bits for the entity number (up to 4k entities).
|
||||
* * 4 bit for the version (resets in [0-15]).
|
||||
*/
|
||||
template<>
|
||||
struct entt_traits<std::uint16_t> {
|
||||
/*! @brief Underlying entity type. */
|
||||
using entity_type = std::uint16_t;
|
||||
/*! @brief Underlying version type. */
|
||||
using version_type = std::uint8_t;
|
||||
/*! @brief Difference type. */
|
||||
using difference_type = std::int32_t;
|
||||
|
||||
/*! @brief Mask to use to get the entity number out of an identifier. */
|
||||
static constexpr auto entity_mask = 0xFFF;
|
||||
/*! @brief Mask to use to get the version out of an identifier. */
|
||||
static constexpr auto version_mask = 0xF;
|
||||
/*! @brief Extent of the entity number within an identifier. */
|
||||
static constexpr auto entity_shift = 12;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @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 auto entity_mask = 0xFFFFF;
|
||||
/*! @brief Mask to use to get the version out of an identifier. */
|
||||
static constexpr auto version_mask = 0xFFF;
|
||||
/*! @brief Extent of the entity number within an identifier. */
|
||||
static constexpr auto entity_shift = 20;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @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 auto entity_mask = 0xFFFFFFFF;
|
||||
/*! @brief Mask to use to get the version out of an identifier. */
|
||||
static constexpr auto version_mask = 0xFFFFFFFF;
|
||||
/*! @brief Extent of the entity number within an identifier. */
|
||||
static constexpr auto entity_shift = 32;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_ENTT_TRAITS_HPP
|
||||
100
src/entt/entity/fwd.hpp
Normal file
100
src/entt/entity/fwd.hpp
Normal file
@@ -0,0 +1,100 @@
|
||||
#ifndef ENTT_ENTITY_FWD_HPP
|
||||
#define ENTT_ENTITY_FWD_HPP
|
||||
|
||||
|
||||
#include <cstdint>
|
||||
#include "../config/config.h"
|
||||
#include "../core/type_traits.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
/*! @class basic_registry */
|
||||
template <typename>
|
||||
class basic_registry;
|
||||
|
||||
/*! @class basic_view */
|
||||
template<typename, typename...>
|
||||
class basic_view;
|
||||
|
||||
/*! @class basic_runtime_view */
|
||||
template<typename>
|
||||
class basic_runtime_view;
|
||||
|
||||
/*! @class basic_group */
|
||||
template<typename...>
|
||||
class basic_group;
|
||||
|
||||
/*! @class basic_observer */
|
||||
template<typename>
|
||||
class basic_observer;
|
||||
|
||||
/*! @class basic_actor */
|
||||
template <typename>
|
||||
struct basic_actor;
|
||||
|
||||
/*! @class basic_prototype */
|
||||
template<typename>
|
||||
class basic_prototype;
|
||||
|
||||
/*! @class basic_snapshot */
|
||||
template<typename>
|
||||
class basic_snapshot;
|
||||
|
||||
/*! @class basic_snapshot_loader */
|
||||
template<typename>
|
||||
class basic_snapshot_loader;
|
||||
|
||||
/*! @class basic_continuous_loader */
|
||||
template<typename>
|
||||
class basic_continuous_loader;
|
||||
|
||||
/*! @brief Alias declaration for the most common use case. */
|
||||
ENTT_OPAQUE_TYPE(entity, ENTT_ID_TYPE)
|
||||
|
||||
/*! @brief Alias declaration for the most common use case. */
|
||||
ENTT_OPAQUE_TYPE(component, ENTT_ID_TYPE)
|
||||
|
||||
/*! @brief Alias declaration for the most common use case. */
|
||||
using registry = basic_registry<entity>;
|
||||
|
||||
/*! @brief Alias declaration for the most common use case. */
|
||||
using observer = basic_observer<entity>;
|
||||
|
||||
/*! @brief Alias declaration for the most common use case. */
|
||||
using actor = basic_actor<entity>;
|
||||
|
||||
/*! @brief Alias declaration for the most common use case. */
|
||||
using prototype = basic_prototype<entity>;
|
||||
|
||||
/*! @brief Alias declaration for the most common use case. */
|
||||
using snapshot = basic_snapshot<entity>;
|
||||
|
||||
/*! @brief Alias declaration for the most common use case. */
|
||||
using snapshot_loader = basic_snapshot_loader<entity>;
|
||||
|
||||
/*! @brief Alias declaration for the most common use case. */
|
||||
using continuous_loader = basic_continuous_loader<entity>;
|
||||
|
||||
/**
|
||||
* @brief Alias declaration for the most common use case.
|
||||
* @tparam Component Types of components iterated by the view.
|
||||
*/
|
||||
template<typename... Types>
|
||||
using view = basic_view<entity, Types...>;
|
||||
|
||||
/*! @brief Alias declaration for the most common use case. */
|
||||
using runtime_view = basic_runtime_view<entity>;
|
||||
|
||||
/**
|
||||
* @brief Alias declaration for the most common use case.
|
||||
* @tparam Types Types of components iterated by the group.
|
||||
*/
|
||||
template<typename... Types>
|
||||
using group = basic_group<entity, Types...>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_FWD_HPP
|
||||
857
src/entt/entity/group.hpp
Normal file
857
src/entt/entity/group.hpp
Normal file
@@ -0,0 +1,857 @@
|
||||
#ifndef ENTT_ENTITY_GROUP_HPP
|
||||
#define ENTT_ENTITY_GROUP_HPP
|
||||
|
||||
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "../core/type_traits.hpp"
|
||||
#include "sparse_set.hpp"
|
||||
#include "storage.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "fwd.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Group.
|
||||
*
|
||||
* 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_group;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Non-owning group.
|
||||
*
|
||||
* A non-owning group returns all the entities and only the entities that have
|
||||
* at least the given components. Moreover, it's guaranteed that the entity list
|
||||
* is tightly packed in memory for fast iterations.<br/>
|
||||
* In general, non-owning groups don't stay true to the order of any set of
|
||||
* components unless users explicitly sort them.
|
||||
*
|
||||
* @b Important
|
||||
*
|
||||
* Iterators aren't invalidated if:
|
||||
*
|
||||
* * New instances of the given components are created and assigned to entities.
|
||||
* * The entity currently pointed is modified (as an example, if one of the
|
||||
* given components is removed from the entity to which the iterator points).
|
||||
* * The entity currently pointed is destroyed.
|
||||
*
|
||||
* In all the other cases, modifying the pools of the given components in any
|
||||
* way invalidates all the iterators and using them results in undefined
|
||||
* behavior.
|
||||
*
|
||||
* @note
|
||||
* Groups share references to the underlying data structures of the registry
|
||||
* that generated them. Therefore any change to the entities and to the
|
||||
* components made by means of the registry are immediately reflected by all the
|
||||
* groups.<br/>
|
||||
* Moreover, sorting a non-owning group affects all the instance of the same
|
||||
* group (it means that users don't have to call `sort` on each instance to sort
|
||||
* all of them because they share the set of entities).
|
||||
*
|
||||
* @warning
|
||||
* Lifetime of a group must overcome the one of the registry that generated it.
|
||||
* In any other case, attempting to use a group results in undefined behavior.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Exclude Types of components used to filter the group.
|
||||
* @tparam Get Type of component observed by the group.
|
||||
* @tparam Other Other types of components observed by the group.
|
||||
*/
|
||||
template<typename Entity, typename... Exclude, typename Get, typename... Other>
|
||||
class basic_group<Entity, exclude_t<Exclude...>, get_t<Get, Other...>> {
|
||||
/*! @brief A registry is allowed to create groups. */
|
||||
friend class basic_registry<Entity>;
|
||||
|
||||
template<typename Component>
|
||||
using pool_type = std::conditional_t<std::is_const_v<Component>, const storage<Entity, std::remove_const_t<Component>>, storage<Entity, Component>>;
|
||||
|
||||
// we could use pool_type<Type> *..., but vs complains about it and refuses to compile for unknown reasons (most likely a bug)
|
||||
basic_group(sparse_set<Entity> *ref, storage<Entity, std::remove_const_t<Get>> *get, storage<Entity, std::remove_const_t<Other>> *... other) ENTT_NOEXCEPT
|
||||
: handler{ref},
|
||||
pools{get, other...}
|
||||
{}
|
||||
|
||||
template<typename Func, typename... Weak>
|
||||
void traverse(Func func, type_list<Weak...>) const {
|
||||
for(const auto entt: *handler) {
|
||||
if constexpr(std::is_invocable_v<Func, decltype(get<Weak>({}))...>) {
|
||||
func(std::get<pool_type<Weak> *>(pools)->get(entt)...);
|
||||
} else {
|
||||
func(entt, std::get<pool_type<Weak> *>(pools)->get(entt)...);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = typename sparse_set<Entity>::entity_type;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename sparse_set<Entity>::size_type;
|
||||
/*! @brief Input iterator type. */
|
||||
using iterator_type = typename sparse_set<Entity>::iterator_type;
|
||||
|
||||
/**
|
||||
* @brief Returns the number of existing components of the given type.
|
||||
* @tparam Component Type of component of which to return the size.
|
||||
* @return Number of existing components of the given type.
|
||||
*/
|
||||
template<typename Component>
|
||||
size_type size() const ENTT_NOEXCEPT {
|
||||
return std::get<pool_type<Component> *>(pools)->size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the number of entities that have the given components.
|
||||
* @return Number of entities that have the given components.
|
||||
*/
|
||||
size_type size() const ENTT_NOEXCEPT {
|
||||
return handler->size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the number of elements that a group has currently
|
||||
* allocated space for.
|
||||
* @return Capacity of the group.
|
||||
*/
|
||||
size_type capacity() const ENTT_NOEXCEPT {
|
||||
return handler->capacity();
|
||||
}
|
||||
|
||||
/*! @brief Requests the removal of unused capacity. */
|
||||
void shrink_to_fit() {
|
||||
handler->shrink_to_fit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks whether the pool of a given component is empty.
|
||||
* @tparam Component Type of component in which one is interested.
|
||||
* @return True if the pool of the given component is empty, false
|
||||
* otherwise.
|
||||
*/
|
||||
template<typename Component>
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
return std::get<pool_type<Component> *>(pools)->empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks whether the group is empty.
|
||||
* @return True if the group is empty, false otherwise.
|
||||
*/
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
return handler->empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Direct access to the list of components of a given pool.
|
||||
*
|
||||
* The returned pointer is such that range
|
||||
* `[raw<Component>(), raw<Component>() + size<Component>()]` is always a
|
||||
* valid range, even if the container is empty.
|
||||
*
|
||||
* @note
|
||||
* There are no guarantees on the order of the components. Use `begin` and
|
||||
* `end` if you want to iterate the group in the expected order.
|
||||
*
|
||||
* @tparam Component Type of component in which one is interested.
|
||||
* @return A pointer to the array of components.
|
||||
*/
|
||||
template<typename Component>
|
||||
Component * raw() const ENTT_NOEXCEPT {
|
||||
return std::get<pool_type<Component> *>(pools)->raw();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Direct access to the list of entities of a given pool.
|
||||
*
|
||||
* The returned pointer is such that range
|
||||
* `[data<Component>(), data<Component>() + size<Component>()]` is always a
|
||||
* valid range, even if the container is empty.
|
||||
*
|
||||
* @note
|
||||
* There are no guarantees on the order of the entities. Use `begin` and
|
||||
* `end` if you want to iterate the group in the expected order.
|
||||
*
|
||||
* @tparam Component Type of component in which one is interested.
|
||||
* @return A pointer to the array of entities.
|
||||
*/
|
||||
template<typename Component>
|
||||
const entity_type * data() const ENTT_NOEXCEPT {
|
||||
return std::get<pool_type<Component> *>(pools)->data();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Direct access to the list of entities.
|
||||
*
|
||||
* The returned pointer is such that range `[data(), data() + size()]` is
|
||||
* always a valid range, even if the container is empty.
|
||||
*
|
||||
* @note
|
||||
* There are no guarantees on the order of the entities. Use `begin` and
|
||||
* `end` if you want to iterate the group in the expected order.
|
||||
*
|
||||
* @return A pointer to the array of entities.
|
||||
*/
|
||||
const entity_type * data() const ENTT_NOEXCEPT {
|
||||
return handler->data();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the first entity that has the given
|
||||
* components.
|
||||
*
|
||||
* The returned iterator points to the first entity that has the given
|
||||
* components. If the group is empty, the returned iterator will be equal to
|
||||
* `end()`.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed to the underlying data
|
||||
* structures.
|
||||
*
|
||||
* @return An iterator to the first entity that has the given components.
|
||||
*/
|
||||
iterator_type begin() const ENTT_NOEXCEPT {
|
||||
return handler->begin();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator that is past the last entity that has the
|
||||
* given components.
|
||||
*
|
||||
* The returned iterator points to the entity following the last entity that
|
||||
* has the given components. Attempting to dereference the returned iterator
|
||||
* results in undefined behavior.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed to the underlying data
|
||||
* structures.
|
||||
*
|
||||
* @return An iterator to the entity following the last entity that has the
|
||||
* given components.
|
||||
*/
|
||||
iterator_type end() const ENTT_NOEXCEPT {
|
||||
return handler->end();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
iterator_type find(const entity_type entt) const ENTT_NOEXCEPT {
|
||||
const auto it = handler->find(entt);
|
||||
return it != end() && *it == entt ? it : end();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the identifier that occupies the given position.
|
||||
* @param pos Position of the element to return.
|
||||
* @return The identifier that occupies the given position.
|
||||
*/
|
||||
entity_type operator[](const size_type pos) const ENTT_NOEXCEPT {
|
||||
return begin()[pos];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a group contains an entity.
|
||||
* @param entt A valid entity identifier.
|
||||
* @return True if the group contains the given entity, false otherwise.
|
||||
*/
|
||||
bool contains(const entity_type entt) const ENTT_NOEXCEPT {
|
||||
return find(entt) != end();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 companion function.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid component type results in a compilation
|
||||
* error. Attempting to use an entity that doesn't belong to the group
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* group doesn't contain the given entity.
|
||||
*
|
||||
* @tparam Component Types of components to get.
|
||||
* @param entt A valid entity identifier.
|
||||
* @return The components assigned to the entity.
|
||||
*/
|
||||
template<typename... Component>
|
||||
decltype(auto) get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT {
|
||||
ENTT_ASSERT(contains(entt));
|
||||
|
||||
if constexpr(sizeof...(Component) == 1) {
|
||||
return (std::get<pool_type<Component> *>(pools)->get(entt), ...);
|
||||
} else {
|
||||
return std::tuple<decltype(get<Component>(entt))...>{get<Component>(entt)...};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 all its 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, Get &, Other &...);
|
||||
* void(Get &, Other &...);
|
||||
* @endcode
|
||||
*
|
||||
* @note
|
||||
* Empty types aren't explicitly instantiated. Therefore, temporary objects
|
||||
* are returned during iterations. They can be caught only by copy or with
|
||||
* const references.
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void each(Func func) const {
|
||||
traverse(std::move(func), type_list<Get, Other...>{});
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*
|
||||
* @sa each
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void less(Func func) const {
|
||||
using get_type_list = std::conditional_t<std::is_empty_v<Get>, type_list<>, type_list<Get>>;
|
||||
using other_type_list = type_list_cat_t<std::conditional_t<std::is_empty_v<Other>, type_list<>, type_list<Other>>...>;
|
||||
traverse(std::move(func), type_list_cat_t<get_type_list, other_type_list>{});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sort a group according to the given comparison function.
|
||||
*
|
||||
* Sort the group so that iterating it with a couple of iterators returns
|
||||
* entities and components in the expected order. See `begin` and `end` for
|
||||
* more details.
|
||||
*
|
||||
* The comparison function object must return `true` if the first element
|
||||
* is _less_ than the second one, `false` otherwise. The signature of the
|
||||
* comparison function should be equivalent to one of the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* bool(std::tuple<Component &...>, std::tuple<Component &...>);
|
||||
* bool(const Component &..., const Component &...);
|
||||
* bool(const Entity, const Entity);
|
||||
* @endcode
|
||||
*
|
||||
* Where `Component` are such that they are iterated by the group.<br/>
|
||||
* Moreover, the comparison function object shall induce a
|
||||
* _strict weak ordering_ on the values.
|
||||
*
|
||||
* The sort function oject must offer a member function template
|
||||
* `operator()` that accepts three arguments:
|
||||
*
|
||||
* * An iterator to the first element of the range to sort.
|
||||
* * An iterator past the last element of the range to sort.
|
||||
* * A comparison function to use to compare the elements.
|
||||
*
|
||||
* The comparison function object received by the sort function object
|
||||
* hasn't necessarily the type of the one passed along with the other
|
||||
* parameters to this member function.
|
||||
*
|
||||
* @note
|
||||
* Attempting to iterate elements using a raw pointer returned by a call to
|
||||
* either `data` or `raw` gives no guarantees on the order, even though
|
||||
* `sort` has been invoked.
|
||||
*
|
||||
* @tparam Component Optional types of components to compare.
|
||||
* @tparam Compare Type of comparison function object.
|
||||
* @tparam Sort Type of sort function object.
|
||||
* @tparam Args Types of arguments to forward to the sort function object.
|
||||
* @param compare A valid comparison function object.
|
||||
* @param algo A valid sort function object.
|
||||
* @param args Arguments to forward to the sort function object, if any.
|
||||
*/
|
||||
template<typename... Component, typename Compare, typename Sort = std_sort, typename... Args>
|
||||
void sort(Compare compare, Sort algo = Sort{}, Args &&... args) {
|
||||
if constexpr(sizeof...(Component) == 0) {
|
||||
static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>);
|
||||
handler->sort(handler->begin(), handler->end(), std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||
} else {
|
||||
handler->sort(handler->begin(), handler->end(), [this, compare = std::move(compare)](const entity_type lhs, const entity_type rhs) {
|
||||
// useless this-> used to suppress a warning with clang
|
||||
return compare(this->get<Component...>(lhs), this->get<Component...>(rhs));
|
||||
}, std::move(algo), std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sort the shared pool of entities according to the given component.
|
||||
*
|
||||
* Non-owning groups of the same type share with the registry a pool of
|
||||
* entities with its own order that doesn't depend on the order of any pool
|
||||
* of components. Users can order the underlying data structure so that it
|
||||
* respects the order of the pool of the given component.
|
||||
*
|
||||
* @note
|
||||
* The shared pool of entities and thus its order is affected by the changes
|
||||
* to each and every pool that it tracks. Therefore changes to those pools
|
||||
* can quickly ruin the order imposed to the pool of entities shared between
|
||||
* the non-owning groups.
|
||||
*
|
||||
* @tparam Component Type of component to use to impose the order.
|
||||
*/
|
||||
template<typename Component>
|
||||
void sort() const {
|
||||
handler->respect(*std::get<pool_type<Component> *>(pools));
|
||||
}
|
||||
|
||||
private:
|
||||
sparse_set<entity_type> *handler;
|
||||
const std::tuple<pool_type<Get> *, pool_type<Other> *...> pools;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Owning group.
|
||||
*
|
||||
* Owning groups return all the entities and only the entities that have at
|
||||
* least the given components. Moreover:
|
||||
*
|
||||
* * It's guaranteed that the entity list is tightly packed in memory for fast
|
||||
* iterations.
|
||||
* * It's guaranteed that the lists of owned components are tightly packed in
|
||||
* memory for even faster iterations and to allow direct access.
|
||||
* * They stay true to the order of the owned components and all the owned
|
||||
* components have the same order in memory.
|
||||
*
|
||||
* The more types of components are owned by a group, the faster it is to
|
||||
* iterate them.
|
||||
*
|
||||
* @b Important
|
||||
*
|
||||
* Iterators aren't invalidated if:
|
||||
*
|
||||
* * New instances of the given components are created and assigned to entities.
|
||||
* * The entity currently pointed is modified (as an example, if one of the
|
||||
* given components is removed from the entity to which the iterator points).
|
||||
* * The entity currently pointed is destroyed.
|
||||
*
|
||||
* In all the other cases, modifying the pools of the given components in any
|
||||
* way invalidates all the iterators and using them results in undefined
|
||||
* behavior.
|
||||
*
|
||||
* @note
|
||||
* Groups share references to the underlying data structures of the registry
|
||||
* that generated them. Therefore any change to the entities and to the
|
||||
* components made by means of the registry are immediately reflected by all the
|
||||
* groups.
|
||||
* Moreover, sorting an owning group affects all the instance of the same group
|
||||
* (it means that users don't have to call `sort` on each instance to sort all
|
||||
* of them because they share the underlying data structure).
|
||||
*
|
||||
* @warning
|
||||
* Lifetime of a group must overcome the one of the registry that generated it.
|
||||
* In any other case, attempting to use a group results in undefined behavior.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Exclude Types of components used to filter the group.
|
||||
* @tparam Get Types of components observed by the group.
|
||||
* @tparam Owned Type of component owned by the group.
|
||||
* @tparam Other Other types of components owned by the group.
|
||||
*/
|
||||
template<typename Entity, typename... Exclude, typename... Get, typename Owned, typename... Other>
|
||||
class basic_group<Entity, exclude_t<Exclude...>, get_t<Get...>, Owned, Other...> {
|
||||
/*! @brief A registry is allowed to create groups. */
|
||||
friend class basic_registry<Entity>;
|
||||
|
||||
template<typename Component>
|
||||
using pool_type = std::conditional_t<std::is_const_v<Component>, const storage<Entity, std::remove_const_t<Component>>, storage<Entity, Component>>;
|
||||
|
||||
template<typename Component>
|
||||
using component_iterator_type = decltype(std::declval<pool_type<Component>>().begin());
|
||||
|
||||
// we could use pool_type<Type> *..., but vs complains about it and refuses to compile for unknown reasons (most likely a bug)
|
||||
basic_group(const typename basic_registry<Entity>::size_type *sz, storage<Entity, std::remove_const_t<Owned>> *owned, storage<Entity, std::remove_const_t<Other>> *... other, storage<Entity, std::remove_const_t<Get>> *... get) ENTT_NOEXCEPT
|
||||
: length{sz},
|
||||
pools{owned, other..., get...}
|
||||
{}
|
||||
|
||||
template<typename Func, typename... Strong, typename... Weak>
|
||||
void traverse(Func func, type_list<Strong...>, type_list<Weak...>) const {
|
||||
[[maybe_unused]] auto raw = std::make_tuple((std::get<pool_type<Strong> *>(pools)->end() - *length)...);
|
||||
[[maybe_unused]] auto data = std::get<pool_type<Owned> *>(pools)->sparse_set<entity_type>::end() - *length;
|
||||
|
||||
for(auto next = *length; next; --next) {
|
||||
if constexpr(std::is_invocable_v<Func, decltype(get<Strong>({}))..., decltype(get<Weak>({}))...>) {
|
||||
if constexpr(sizeof...(Weak) == 0) {
|
||||
func(*(std::get<component_iterator_type<Strong>>(raw)++)...);
|
||||
} else {
|
||||
const auto entt = *(data++);
|
||||
func(*(std::get<component_iterator_type<Strong>>(raw)++)..., std::get<pool_type<Weak> *>(pools)->get(entt)...);
|
||||
}
|
||||
} else {
|
||||
const auto entt = *(data++);
|
||||
func(entt, *(std::get<component_iterator_type<Strong>>(raw)++)..., std::get<pool_type<Weak> *>(pools)->get(entt)...);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = typename sparse_set<Entity>::entity_type;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename sparse_set<Entity>::size_type;
|
||||
/*! @brief Input iterator type. */
|
||||
using iterator_type = typename sparse_set<Entity>::iterator_type;
|
||||
|
||||
/**
|
||||
* @brief Returns the number of existing components of the given type.
|
||||
* @tparam Component Type of component of which to return the size.
|
||||
* @return Number of existing components of the given type.
|
||||
*/
|
||||
template<typename Component>
|
||||
size_type size() const ENTT_NOEXCEPT {
|
||||
return std::get<pool_type<Component> *>(pools)->size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the number of entities that have the given components.
|
||||
* @return Number of entities that have the given components.
|
||||
*/
|
||||
size_type size() const ENTT_NOEXCEPT {
|
||||
return *length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks whether the pool of a given component is empty.
|
||||
* @tparam Component Type of component in which one is interested.
|
||||
* @return True if the pool of the given component is empty, false
|
||||
* otherwise.
|
||||
*/
|
||||
template<typename Component>
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
return std::get<pool_type<Component> *>(pools)->empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks whether the group is empty.
|
||||
* @return True if the group is empty, false otherwise.
|
||||
*/
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
return !*length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Direct access to the list of components of a given pool.
|
||||
*
|
||||
* The returned pointer is such that range
|
||||
* `[raw<Component>(), raw<Component>() + size<Component>()]` is always a
|
||||
* valid range, even if the container is empty.<br/>
|
||||
* Moreover, in case the group owns the given component, the range
|
||||
* `[raw<Component>(), raw<Component>() + size()]` is such that it contains
|
||||
* the instances that are part of the group itself.
|
||||
*
|
||||
* @note
|
||||
* There are no guarantees on the order of the components. Use `begin` and
|
||||
* `end` if you want to iterate the group in the expected order.
|
||||
*
|
||||
* @tparam Component Type of component in which one is interested.
|
||||
* @return A pointer to the array of components.
|
||||
*/
|
||||
template<typename Component>
|
||||
Component * raw() const ENTT_NOEXCEPT {
|
||||
return std::get<pool_type<Component> *>(pools)->raw();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Direct access to the list of entities of a given pool.
|
||||
*
|
||||
* The returned pointer is such that range
|
||||
* `[data<Component>(), data<Component>() + size<Component>()]` is always a
|
||||
* valid range, even if the container is empty.<br/>
|
||||
* Moreover, in case the group owns the given component, the range
|
||||
* `[data<Component>(), data<Component>() + size()]` is such that it
|
||||
* contains the entities that are part of the group itself.
|
||||
*
|
||||
* @note
|
||||
* There are no guarantees on the order of the entities. Use `begin` and
|
||||
* `end` if you want to iterate the group in the expected order.
|
||||
*
|
||||
* @tparam Component Type of component in which one is interested.
|
||||
* @return A pointer to the array of entities.
|
||||
*/
|
||||
template<typename Component>
|
||||
const entity_type * data() const ENTT_NOEXCEPT {
|
||||
return std::get<pool_type<Component> *>(pools)->data();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Direct access to the list of entities.
|
||||
*
|
||||
* The returned pointer is such that range `[data(), data() + size()]` is
|
||||
* always a valid range, even if the container is empty.
|
||||
*
|
||||
* @note
|
||||
* There are no guarantees on the order of the entities. Use `begin` and
|
||||
* `end` if you want to iterate the group in the expected order.
|
||||
*
|
||||
* @return A pointer to the array of entities.
|
||||
*/
|
||||
const entity_type * data() const ENTT_NOEXCEPT {
|
||||
return std::get<pool_type<Owned> *>(pools)->data();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the first entity that has the given
|
||||
* components.
|
||||
*
|
||||
* The returned iterator points to the first entity that has the given
|
||||
* components. If the group is empty, the returned iterator will be equal to
|
||||
* `end()`.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed to the underlying data
|
||||
* structures.
|
||||
*
|
||||
* @return An iterator to the first entity that has the given components.
|
||||
*/
|
||||
iterator_type begin() const ENTT_NOEXCEPT {
|
||||
return std::get<pool_type<Owned> *>(pools)->sparse_set<entity_type>::end() - *length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator that is past the last entity that has the
|
||||
* given components.
|
||||
*
|
||||
* The returned iterator points to the entity following the last entity that
|
||||
* has the given components. Attempting to dereference the returned iterator
|
||||
* results in undefined behavior.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed to the underlying data
|
||||
* structures.
|
||||
*
|
||||
* @return An iterator to the entity following the last entity that has the
|
||||
* given components.
|
||||
*/
|
||||
iterator_type end() const ENTT_NOEXCEPT {
|
||||
return std::get<pool_type<Owned> *>(pools)->sparse_set<entity_type>::end();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
iterator_type find(const entity_type entt) const ENTT_NOEXCEPT {
|
||||
const auto it = std::get<pool_type<Owned> *>(pools)->find(entt);
|
||||
return it != end() && it >= begin() && *it == entt ? it : end();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the identifier that occupies the given position.
|
||||
* @param pos Position of the element to return.
|
||||
* @return The identifier that occupies the given position.
|
||||
*/
|
||||
entity_type operator[](const size_type pos) const ENTT_NOEXCEPT {
|
||||
return begin()[pos];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a group contains an entity.
|
||||
* @param entt A valid entity identifier.
|
||||
* @return True if the group contains the given entity, false otherwise.
|
||||
*/
|
||||
bool contains(const entity_type entt) const ENTT_NOEXCEPT {
|
||||
return find(entt) != end();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 companion function.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid component type results in a compilation
|
||||
* error. Attempting to use an entity that doesn't belong to the group
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* group doesn't contain the given entity.
|
||||
*
|
||||
* @tparam Component Types of components to get.
|
||||
* @param entt A valid entity identifier.
|
||||
* @return The components assigned to the entity.
|
||||
*/
|
||||
template<typename... Component>
|
||||
decltype(auto) get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT {
|
||||
ENTT_ASSERT(contains(entt));
|
||||
|
||||
if constexpr(sizeof...(Component) == 1) {
|
||||
return (std::get<pool_type<Component> *>(pools)->get(entt), ...);
|
||||
} else {
|
||||
return std::tuple<decltype(get<Component>(entt))...>{get<Component>(entt)...};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 all its 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, Owned &, Other &..., Get &...);
|
||||
* void(Owned &, Other &..., Get &...);
|
||||
* @endcode
|
||||
*
|
||||
* @note
|
||||
* Empty types aren't explicitly instantiated. Therefore, temporary objects
|
||||
* are returned during iterations. They can be caught only by copy or with
|
||||
* const references.
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void each(Func func) const {
|
||||
traverse(std::move(func), type_list<Owned, Other...>{}, type_list<Get...>{});
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*
|
||||
* @sa each
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void less(Func func) const {
|
||||
using owned_type_list = std::conditional_t<std::is_empty_v<Owned>, type_list<>, type_list<Owned>>;
|
||||
using other_type_list = type_list_cat_t<std::conditional_t<std::is_empty_v<Other>, type_list<>, type_list<Other>>...>;
|
||||
using get_type_list = type_list_cat_t<std::conditional_t<std::is_empty_v<Get>, type_list<>, type_list<Get>>...>;
|
||||
traverse(std::move(func), type_list_cat_t<owned_type_list, other_type_list>{}, get_type_list{});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sort a group according to the given comparison function.
|
||||
*
|
||||
* Sort the group so that iterating it with a couple of iterators returns
|
||||
* entities and components in the expected order. See `begin` and `end` for
|
||||
* more details.
|
||||
*
|
||||
* The comparison function object must return `true` if the first element
|
||||
* is _less_ than the second one, `false` otherwise. The signature of the
|
||||
* comparison function should be equivalent to one of the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* bool(std::tuple<Component &...>, std::tuple<Component &...>);
|
||||
* bool(const Component &, const Component &);
|
||||
* bool(const Entity, const Entity);
|
||||
* @endcode
|
||||
*
|
||||
* Where `Component` are either owned types or not but still such that they
|
||||
* are iterated by the group.<br/>
|
||||
* Moreover, the comparison function object shall induce a
|
||||
* _strict weak ordering_ on the values.
|
||||
*
|
||||
* The sort function oject must offer a member function template
|
||||
* `operator()` that accepts three arguments:
|
||||
*
|
||||
* * An iterator to the first element of the range to sort.
|
||||
* * An iterator past the last element of the range to sort.
|
||||
* * A comparison function to use to compare the elements.
|
||||
*
|
||||
* The comparison function object received by the sort function object
|
||||
* hasn't necessarily the type of the one passed along with the other
|
||||
* parameters to this member function.
|
||||
*
|
||||
* @note
|
||||
* Attempting to iterate elements using a raw pointer returned by a call to
|
||||
* either `data` or `raw` gives no guarantees on the order, even though
|
||||
* `sort` has been invoked.
|
||||
*
|
||||
* @tparam Component Optional types of components to compare.
|
||||
* @tparam Compare Type of comparison function object.
|
||||
* @tparam Sort Type of sort function object.
|
||||
* @tparam Args Types of arguments to forward to the sort function object.
|
||||
* @param compare A valid comparison function object.
|
||||
* @param algo A valid sort function object.
|
||||
* @param args Arguments to forward to the sort function object, if any.
|
||||
*/
|
||||
template<typename... Component, typename Compare, typename Sort = std_sort, typename... Args>
|
||||
void sort(Compare compare, Sort algo = Sort{}, Args &&... args) {
|
||||
auto *cpool = std::get<pool_type<Owned> *>(pools);
|
||||
|
||||
if constexpr(sizeof...(Component) == 0) {
|
||||
static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>);
|
||||
cpool->sort(cpool->end()-*length, cpool->end(), std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||
} else {
|
||||
cpool->sort(cpool->end()-*length, cpool->end(), [this, compare = std::move(compare)](const entity_type lhs, const entity_type rhs) {
|
||||
// useless this-> used to suppress a warning with clang
|
||||
return compare(this->get<Component...>(lhs), this->get<Component...>(rhs));
|
||||
}, std::move(algo), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
for(auto next = *length; next; --next) {
|
||||
const auto pos = next - 1;
|
||||
const auto entt = cpool->data()[pos];
|
||||
(std::get<pool_type<Other> *>(pools)->swap(pos, std::get<pool_type<Other> *>(pools)->index(entt)), ...);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const typename basic_registry<Entity>::size_type *length;
|
||||
const std::tuple<pool_type<Owned> *, pool_type<Other> *..., pool_type<Get> *...> pools;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_GROUP_HPP
|
||||
@@ -3,100 +3,133 @@
|
||||
|
||||
|
||||
#include <type_traits>
|
||||
#include "../core/hashed_string.hpp"
|
||||
#include "../config/config.h"
|
||||
#include "../signal/sigh.hpp"
|
||||
#include "registry.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Dependency function prototype.
|
||||
*
|
||||
* A _dependency function_ is a built-in listener to use to automatically assign
|
||||
* components to an entity when a type has a dependency on some other types.
|
||||
*
|
||||
* This is a prototype function to use to create dependencies.<br/>
|
||||
* It isn't intended for direct use, although nothing forbids using it freely.
|
||||
*
|
||||
* @brief Converts a registry to a view.
|
||||
* @tparam Const Constness of the accepted registry.
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Component Types of components to assign to an entity if triggered.
|
||||
* @param registry A valid reference to a registry.
|
||||
* @param entity A valid entity identifier.
|
||||
*/
|
||||
template<typename Entity, typename... Component>
|
||||
void dependency(Registry<Entity> ®istry, const Entity entity) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { ((registry.template has<Component>(entity) ? void() : (registry.template assign<Component>(entity), void())), 0)... };
|
||||
(void)accumulator;
|
||||
}
|
||||
template<bool Const, typename Entity>
|
||||
struct as_view {
|
||||
/*! @brief Type of registry to convert. */
|
||||
using registry_type = std::conditional_t<Const, const entt::basic_registry<Entity>, entt::basic_registry<Entity>>;
|
||||
|
||||
/**
|
||||
* @brief Constructs a converter for a given registry.
|
||||
* @param source A valid reference to a registry.
|
||||
*/
|
||||
as_view(registry_type &source) ENTT_NOEXCEPT: reg{source} {}
|
||||
|
||||
/**
|
||||
* @brief Conversion function from a registry to a view.
|
||||
* @tparam Component Type of components used to construct the view.
|
||||
* @return A newly created view.
|
||||
*/
|
||||
template<typename... Component>
|
||||
operator entt::basic_view<Entity, Component...>() const {
|
||||
return reg.template view<Component...>();
|
||||
}
|
||||
|
||||
private:
|
||||
registry_type ®
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Connects a dependency function to the given sink.
|
||||
* @brief Deduction guide.
|
||||
*
|
||||
* A _dependency function_ is a built-in listener to use to automatically assign
|
||||
* components to an entity when a type has a dependency on some other types.
|
||||
* It allows to deduce the constness of a registry directly from the instance
|
||||
* provided to the constructor.
|
||||
*
|
||||
* The following adds components `AType` and `AnotherType` whenever `MyType` is
|
||||
* assigned to an entity:
|
||||
* @code{.cpp}
|
||||
* entt::DefaultRegistry registry;
|
||||
* entt::dependency<AType, AnotherType>(registry.construction<MyType>());
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Dependency Types of components to assign to an entity if triggered.
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @param sink A sink object properly initialized.
|
||||
*/
|
||||
template<typename... Dependency, typename Entity>
|
||||
void dependency(Sink<void(Registry<Entity> &, const Entity)> sink) {
|
||||
sink.template connect<dependency<Entity, Dependency...>>();
|
||||
}
|
||||
template<typename Entity>
|
||||
as_view(basic_registry<Entity> &) ENTT_NOEXCEPT -> as_view<false, Entity>;
|
||||
|
||||
|
||||
/*! @copydoc as_view */
|
||||
template<typename Entity>
|
||||
as_view(const basic_registry<Entity> &) ENTT_NOEXCEPT -> as_view<true, Entity>;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Disconnects a dependency function from the given sink.
|
||||
*
|
||||
* A _dependency function_ is a built-in listener to use to automatically assign
|
||||
* components to an entity when a type has a dependency on some other types.
|
||||
*
|
||||
* The following breaks the dependency between the component `MyType` and the
|
||||
* components `AType` and `AnotherType`:
|
||||
* @code{.cpp}
|
||||
* entt::DefaultRegistry registry;
|
||||
* entt::dependency<AType, AnotherType>(entt::break_t{}, registry.construction<MyType>());
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Dependency Types of components used to create the dependency.
|
||||
* @brief Converts a registry to a group.
|
||||
* @tparam Const Constness of the accepted registry.
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @param sink A sink object properly initialized.
|
||||
*/
|
||||
template<typename... Dependency, typename Entity>
|
||||
void dependency(break_t, Sink<void(Registry<Entity> &, const Entity)> sink) {
|
||||
sink.template disconnect<dependency<Entity, Dependency...>>();
|
||||
}
|
||||
template<bool Const, typename Entity>
|
||||
struct as_group {
|
||||
/*! @brief Type of registry to convert. */
|
||||
using registry_type = std::conditional_t<Const, const entt::basic_registry<Entity>, entt::basic_registry<Entity>>;
|
||||
|
||||
/**
|
||||
* @brief Constructs a converter for a given registry.
|
||||
* @param source A valid reference to a registry.
|
||||
*/
|
||||
as_group(registry_type &source) ENTT_NOEXCEPT: reg{source} {}
|
||||
|
||||
/**
|
||||
* @brief Conversion function from a registry to a group.
|
||||
* @tparam Exclude Types of components used to filter the group.
|
||||
* @tparam Get Types of components observed by the group.
|
||||
* @tparam Owned Types of components owned by the group.
|
||||
* @return A newly created group.
|
||||
*/
|
||||
template<typename Exclude, typename Get, typename... Owned>
|
||||
operator entt::basic_group<Entity, Exclude, Get, Owned...>() const {
|
||||
return reg.template group<Owned...>(Get{}, Exclude{});
|
||||
}
|
||||
|
||||
private:
|
||||
registry_type ®
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Alias template to ease the assignment of labels to entities.
|
||||
* @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<false, Entity>;
|
||||
|
||||
|
||||
/*! @copydoc as_group */
|
||||
template<typename Entity>
|
||||
as_group(const basic_registry<Entity> &) ENTT_NOEXCEPT -> as_group<true, Entity>;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Alias template to ease the assignment of tags to entities.
|
||||
*
|
||||
* If used in combination with hashed strings, it simplifies the assignment of
|
||||
* labels to entities and the use of labels in general where a type would be
|
||||
* tags to entities and the use of tags in general where a type would be
|
||||
* required otherwise.<br/>
|
||||
* As an example and where the user defined literal for hashed strings hasn't
|
||||
* been changed:
|
||||
* @code{.cpp}
|
||||
* entt::DefaultRegistry registry;
|
||||
* registry.assign<entt::label<"enemy"_hs>>(entity);
|
||||
* entt::registry registry;
|
||||
* registry.assign<entt::tag<"enemy"_hs>>(entity);
|
||||
* @endcode
|
||||
*
|
||||
* @note
|
||||
* Tags are empty components and therefore candidates for the empty component
|
||||
* optimization.
|
||||
*
|
||||
* @tparam Value The numeric representation of an instance of hashed string.
|
||||
*/
|
||||
template<typename HashedString::hash_type Value>
|
||||
using label = std::integral_constant<typename HashedString::hash_type, Value>;
|
||||
template<ENTT_ID_TYPE Value>
|
||||
using tag = std::integral_constant<ENTT_ID_TYPE, Value>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
438
src/entt/entity/observer.hpp
Normal file
438
src/entt/entity/observer.hpp
Normal file
@@ -0,0 +1,438 @@
|
||||
#ifndef ENTT_ENTITY_OBSERVER_HPP
|
||||
#define ENTT_ENTITY_OBSERVER_HPP
|
||||
|
||||
|
||||
#include <limits>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "../core/type_traits.hpp"
|
||||
#include "registry.hpp"
|
||||
#include "storage.hpp"
|
||||
#include "entity.hpp"
|
||||
#include "fwd.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/*! @brief Grouping matcher. */
|
||||
template<typename...>
|
||||
struct matcher {};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Collector.
|
||||
*
|
||||
* Primary template isn't defined on purpose. All the specializations give a
|
||||
* compile-time error, but for a few reasonable cases.
|
||||
*/
|
||||
template<typename...>
|
||||
struct basic_collector;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Collector.
|
||||
*
|
||||
* A collector contains a set of rules (literally, matchers) to use to track
|
||||
* entities.<br/>
|
||||
* Its main purpose is to generate a descriptor that allows an observer to know
|
||||
* how to connect to a registry.
|
||||
*/
|
||||
template<>
|
||||
struct basic_collector<> {
|
||||
/**
|
||||
* @brief Adds a grouping matcher to the collector.
|
||||
* @tparam AllOf Types of components tracked by the matcher.
|
||||
* @tparam NoneOf Types of components used to filter out entities.
|
||||
* @return The updated collector.
|
||||
*/
|
||||
template<typename... AllOf, typename... NoneOf>
|
||||
static constexpr auto group(exclude_t<NoneOf...> = {}) ENTT_NOEXCEPT {
|
||||
return basic_collector<matcher<matcher<type_list<>, type_list<>>, type_list<NoneOf...>, type_list<AllOf...>>>{};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds an observing matcher to the collector.
|
||||
* @tparam AnyOf Type of component for which changes should be detected.
|
||||
* @return The updated collector.
|
||||
*/
|
||||
template<typename AnyOf>
|
||||
static constexpr auto replace() ENTT_NOEXCEPT {
|
||||
return basic_collector<matcher<matcher<type_list<>, type_list<>>, AnyOf>>{};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Collector.
|
||||
* @copydetails basic_collector<>
|
||||
* @tparam AnyOf Types of components for which changes should be detected.
|
||||
* @tparam Matcher Types of grouping matchers.
|
||||
*/
|
||||
template<typename... Reject, typename... Require, typename... Rule, typename... Other>
|
||||
struct basic_collector<matcher<matcher<type_list<Reject...>, type_list<Require...>>, Rule...>, Other...> {
|
||||
/**
|
||||
* @brief Adds a grouping matcher to the collector.
|
||||
* @tparam AllOf Types of components tracked by the matcher.
|
||||
* @tparam NoneOf Types of components used to filter out entities.
|
||||
* @return The updated collector.
|
||||
*/
|
||||
template<typename... AllOf, typename... NoneOf>
|
||||
static constexpr auto group(exclude_t<NoneOf...> = {}) ENTT_NOEXCEPT {
|
||||
using first = matcher<matcher<type_list<Reject...>, type_list<Require...>>, Rule...>;
|
||||
return basic_collector<first, Other..., matcher<matcher<type_list<>, type_list<>>, type_list<NoneOf...>, type_list<AllOf...>>>{};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds an observing matcher to the collector.
|
||||
* @tparam AnyOf Type of component for which changes should be detected.
|
||||
* @return The updated collector.
|
||||
*/
|
||||
template<typename AnyOf>
|
||||
static constexpr auto replace() ENTT_NOEXCEPT {
|
||||
using first = matcher<matcher<type_list<Reject...>, type_list<Require...>>, Rule...>;
|
||||
return basic_collector<first, Other..., matcher<matcher<type_list<>, type_list<>>, AnyOf>>{};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates the filter of the last added matcher.
|
||||
* @tparam AllOf Types of components required by the matcher.
|
||||
* @tparam NoneOf Types of components used to filter out entities.
|
||||
* @return The updated collector.
|
||||
*/
|
||||
template<typename... AllOf, typename... NoneOf>
|
||||
static constexpr auto where(exclude_t<NoneOf...> = {}) ENTT_NOEXCEPT {
|
||||
return basic_collector<matcher<matcher<type_list<Reject..., NoneOf...>, type_list<Require..., AllOf...>>, Rule...>, Other...>{};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*! @brief Variable template used to ease the definition of collectors. */
|
||||
constexpr basic_collector<> collector{};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Observer.
|
||||
*
|
||||
* An observer returns all the entities and only the entities that fit the
|
||||
* requirements of at least one matcher. Moreover, it's guaranteed that the
|
||||
* entity list is tightly packed in memory for fast iterations.<br/>
|
||||
* In general, observers don't stay true to the order of any set of components.
|
||||
*
|
||||
* Observers work mainly with two types of matchers, provided through a
|
||||
* collector:
|
||||
*
|
||||
* * Observing matcher: an observer will return at least all the living entities
|
||||
* for which one or more of the given components have been explicitly
|
||||
* replaced and not yet destroyed.
|
||||
* * Grouping matcher: an observer will return at least all the living entities
|
||||
* that would have entered the given group if it existed and that would have
|
||||
* not yet left it.
|
||||
*
|
||||
* If an entity respects the requirements of multiple matchers, it will be
|
||||
* returned once and only once by the observer in any case.
|
||||
*
|
||||
* Matchers support also filtering by means of a _where_ clause that accepts
|
||||
* both a list of types and an exclusion list.<br/>
|
||||
* Whenever a matcher finds that an entity matches its requirements, the
|
||||
* condition of the filter is verified before to register the entity itself.
|
||||
* Moreover, a registered entity isn't returned by the observer if the condition
|
||||
* set by the filter is broken in the meantime.
|
||||
*
|
||||
* @b Important
|
||||
*
|
||||
* Iterators aren't invalidated if:
|
||||
*
|
||||
* * New instances of the given components are created and assigned to entities.
|
||||
* * The entity currently pointed is modified (as an example, if one of the
|
||||
* given components is removed from the entity to which the iterator points).
|
||||
* * The entity currently pointed is destroyed.
|
||||
*
|
||||
* In all the other cases, modifying the pools of the given components in any
|
||||
* way invalidates all the iterators and using them results in undefined
|
||||
* behavior.
|
||||
*
|
||||
* @warning
|
||||
* Lifetime of an observer doesn't necessarily have to overcome the one of the
|
||||
* registry to which it is connected. However, the observer must be disconnected
|
||||
* from the registry before being destroyed to avoid crashes due to dangling
|
||||
* pointers.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
class basic_observer {
|
||||
using payload_type = std::uint32_t;
|
||||
|
||||
template<typename>
|
||||
struct matcher_handler;
|
||||
|
||||
template<typename... Reject, typename... Require, typename AnyOf>
|
||||
struct matcher_handler<matcher<matcher<type_list<Reject...>, type_list<Require...>>, AnyOf>> {
|
||||
template<std::size_t Index>
|
||||
static void maybe_valid_if(basic_observer &obs, const Entity entt, const basic_registry<Entity> ®) {
|
||||
if(reg.template has<Require...>(entt) && !(reg.template has<Reject>(entt) || ...)) {
|
||||
auto *comp = obs.view.try_get(entt);
|
||||
(comp ? *comp : obs.view.construct(entt)) |= (1 << Index);
|
||||
}
|
||||
}
|
||||
|
||||
template<std::size_t Index>
|
||||
static void discard_if(basic_observer &obs, const Entity entt) {
|
||||
if(auto *value = obs.view.try_get(entt); value && !(*value &= (~(1 << Index)))) {
|
||||
obs.view.destroy(entt);
|
||||
}
|
||||
}
|
||||
|
||||
template<std::size_t Index>
|
||||
static void connect(basic_observer &obs, basic_registry<Entity> ®) {
|
||||
(reg.template on_destroy<Require>().template connect<&discard_if<Index>>(obs), ...);
|
||||
(reg.template on_construct<Reject>().template connect<&discard_if<Index>>(obs), ...);
|
||||
reg.template on_replace<AnyOf>().template connect<&maybe_valid_if<Index>>(obs);
|
||||
reg.template on_destroy<AnyOf>().template connect<&discard_if<Index>>(obs);
|
||||
}
|
||||
|
||||
static void disconnect(basic_observer &obs, basic_registry<Entity> ®) {
|
||||
(reg.template on_destroy<Require>().disconnect(obs), ...);
|
||||
(reg.template on_construct<Reject>().disconnect(obs), ...);
|
||||
reg.template on_replace<AnyOf>().disconnect(obs);
|
||||
reg.template on_destroy<AnyOf>().disconnect(obs);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Reject, typename... Require, typename... NoneOf, typename... AllOf>
|
||||
struct matcher_handler<matcher<matcher<type_list<Reject...>, type_list<Require...>>, type_list<NoneOf...>, type_list<AllOf...>>> {
|
||||
template<std::size_t Index>
|
||||
static void maybe_valid_if(basic_observer &obs, const Entity entt, const basic_registry<Entity> ®) {
|
||||
if(reg.template has<AllOf...>(entt) && !(reg.template has<NoneOf>(entt) || ...)
|
||||
&& reg.template has<Require...>(entt) && !(reg.template has<Reject>(entt) || ...))
|
||||
{
|
||||
auto *comp = obs.view.try_get(entt);
|
||||
(comp ? *comp : obs.view.construct(entt)) |= (1 << Index);
|
||||
}
|
||||
}
|
||||
|
||||
template<std::size_t Index>
|
||||
static void discard_if(basic_observer &obs, const Entity entt) {
|
||||
if(auto *value = obs.view.try_get(entt); value && !(*value &= (~(1 << Index)))) {
|
||||
obs.view.destroy(entt);
|
||||
}
|
||||
}
|
||||
|
||||
template<std::size_t Index>
|
||||
static void connect(basic_observer &obs, basic_registry<Entity> ®) {
|
||||
(reg.template on_destroy<Require>().template connect<&discard_if<Index>>(obs), ...);
|
||||
(reg.template on_construct<Reject>().template connect<&discard_if<Index>>(obs), ...);
|
||||
(reg.template on_construct<AllOf>().template connect<&maybe_valid_if<Index>>(obs), ...);
|
||||
(reg.template on_destroy<NoneOf>().template connect<&maybe_valid_if<Index>>(obs), ...);
|
||||
(reg.template on_destroy<AllOf>().template connect<&discard_if<Index>>(obs), ...);
|
||||
(reg.template on_construct<NoneOf>().template connect<&discard_if<Index>>(obs), ...);
|
||||
}
|
||||
|
||||
static void disconnect(basic_observer &obs, basic_registry<Entity> ®) {
|
||||
(reg.template on_destroy<Require>().disconnect(obs), ...);
|
||||
(reg.template on_construct<Reject>().disconnect(obs), ...);
|
||||
(reg.template on_construct<AllOf>().disconnect(obs), ...);
|
||||
(reg.template on_destroy<NoneOf>().disconnect(obs), ...);
|
||||
(reg.template on_destroy<AllOf>().disconnect(obs), ...);
|
||||
(reg.template on_construct<NoneOf>().disconnect(obs), ...);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Matcher>
|
||||
static void disconnect(basic_observer &obs, basic_registry<Entity> ®) {
|
||||
(matcher_handler<Matcher>::disconnect(obs, reg), ...);
|
||||
}
|
||||
|
||||
template<typename... Matcher, std::size_t... Index>
|
||||
void connect(basic_registry<Entity> ®, std::index_sequence<Index...>) {
|
||||
static_assert(sizeof...(Matcher) < std::numeric_limits<payload_type>::digits);
|
||||
(matcher_handler<Matcher>::template connect<Index>(*this, reg), ...);
|
||||
release = &basic_observer::disconnect<Matcher...>;
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Input iterator type. */
|
||||
using iterator_type = typename sparse_set<Entity>::iterator_type;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
basic_observer() ENTT_NOEXCEPT
|
||||
: target{}, release{}, view{}
|
||||
{}
|
||||
|
||||
/*! @brief Default copy constructor, deleted on purpose. */
|
||||
basic_observer(const basic_observer &) = delete;
|
||||
/*! @brief Default move constructor, deleted on purpose. */
|
||||
basic_observer(basic_observer &&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Creates an observer and connects it to a given registry.
|
||||
* @tparam Matcher Types of matchers to use to initialize the observer.
|
||||
* @param reg A valid reference to a registry.
|
||||
*/
|
||||
template<typename... Matcher>
|
||||
basic_observer(basic_registry<entity_type> ®, basic_collector<Matcher...>) ENTT_NOEXCEPT
|
||||
: target{®},
|
||||
release{},
|
||||
view{}
|
||||
{
|
||||
connect<Matcher...>(reg, std::make_index_sequence<sizeof...(Matcher)>{});
|
||||
}
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
~basic_observer() = default;
|
||||
|
||||
/**
|
||||
* @brief Default copy assignment operator, deleted on purpose.
|
||||
* @return This observer.
|
||||
*/
|
||||
basic_observer & operator=(const basic_observer &) = delete;
|
||||
|
||||
/**
|
||||
* @brief Default move assignment operator, deleted on purpose.
|
||||
* @return This observer.
|
||||
*/
|
||||
basic_observer & operator=(basic_observer &&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Connects an observer to a given registry.
|
||||
* @tparam Matcher Types of matchers to use to initialize the observer.
|
||||
* @param reg A valid reference to a registry.
|
||||
*/
|
||||
template<typename... Matcher>
|
||||
void connect(basic_registry<entity_type> ®, basic_collector<Matcher...>) {
|
||||
disconnect();
|
||||
connect<Matcher...>(reg, std::make_index_sequence<sizeof...(Matcher)>{});
|
||||
target = ®
|
||||
view.reset();
|
||||
}
|
||||
|
||||
/*! @brief Disconnects an observer from the registry it keeps track of. */
|
||||
void disconnect() {
|
||||
if(release) {
|
||||
release(*this, *target);
|
||||
release = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the number of elements in an observer.
|
||||
* @return Number of elements.
|
||||
*/
|
||||
size_type size() const ENTT_NOEXCEPT {
|
||||
return view.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks whether an observer is empty.
|
||||
* @return True if the observer is empty, false otherwise.
|
||||
*/
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
return view.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Direct access to the list of entities of the observer.
|
||||
*
|
||||
* The returned pointer is such that range `[data(), data() + size()]` is
|
||||
* always a valid range, even if the container is empty.
|
||||
*
|
||||
* @note
|
||||
* There are no guarantees on the order of the entities. Use `begin` and
|
||||
* `end` if you want to iterate the observer in the expected order.
|
||||
*
|
||||
* @return A pointer to the array of entities.
|
||||
*/
|
||||
const entity_type * data() const ENTT_NOEXCEPT {
|
||||
return view.data();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the first entity of the observer.
|
||||
*
|
||||
* The returned iterator points to the first entity of the observer. If the
|
||||
* container is empty, the returned iterator will be equal to `end()`.
|
||||
*
|
||||
* @return An iterator to the first entity of the observer.
|
||||
*/
|
||||
iterator_type begin() const ENTT_NOEXCEPT {
|
||||
return view.sparse_set<entity_type>::begin();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator that is past the last entity of the observer.
|
||||
*
|
||||
* The returned iterator points to the entity following the last entity of
|
||||
* the observer. Attempting to dereference the returned iterator results in
|
||||
* undefined behavior.
|
||||
*
|
||||
* @return An iterator to the entity following the last entity of the
|
||||
* observer.
|
||||
*/
|
||||
iterator_type end() const ENTT_NOEXCEPT {
|
||||
return view.sparse_set<entity_type>::end();
|
||||
}
|
||||
|
||||
/*! @brief Resets the underlying container. */
|
||||
void clear() {
|
||||
view.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterates entities and applies the given function object to them,
|
||||
* then clears the observer.
|
||||
*
|
||||
* The function object is invoked for each entity.<br/>
|
||||
* The signature of the function must be equivalent to the following form:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(const entity_type);
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void each(Func func) const {
|
||||
static_assert(std::is_invocable_v<Func, entity_type>);
|
||||
std::for_each(begin(), end(), std::move(func));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterates entities and applies the given function object to them,
|
||||
* then clears the observer.
|
||||
*
|
||||
* The function object is invoked for each entity.<br/>
|
||||
* The signature of the function must be equivalent to the following form:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(const entity_type);
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void each(Func func) {
|
||||
std::as_const(*this).each(std::move(func));
|
||||
clear();
|
||||
}
|
||||
|
||||
private:
|
||||
basic_registry<entity_type> *target;
|
||||
void(* release)(basic_observer &, basic_registry<entity_type> &);
|
||||
storage<entity_type, payload_type> view;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_OBSERVER_HPP
|
||||
@@ -1,496 +0,0 @@
|
||||
#ifndef ENTT_ENTITY_PROTOTYPE_HPP
|
||||
#define ENTT_ENTITY_PROTOTYPE_HPP
|
||||
|
||||
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include "../config/config.h"
|
||||
#include "registry.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Prototype container for _concepts_.
|
||||
*
|
||||
* A prototype is used to define a _concept_ in terms of components.<br/>
|
||||
* Prototypes act as templates for those specific types of an application which
|
||||
* users would otherwise define through a series of component assignments to
|
||||
* entities. In other words, prototypes can be used to assign components to
|
||||
* entities of a registry at once.
|
||||
*
|
||||
* @note
|
||||
* Components used along with prototypes must be copy constructible. Prototypes
|
||||
* wrap component types with custom types, so they do not interfere with other
|
||||
* users of the registry they were built with.
|
||||
*
|
||||
* @warning
|
||||
* Prototypes directly use their underlying registries to store entities and
|
||||
* components for their purposes. Users must ensure that the lifetime of a
|
||||
* registry and its contents exceed that of the prototypes that use it.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
class Prototype final {
|
||||
using basic_fn_type = void(const Prototype &, Registry<Entity> &, const Entity);
|
||||
using component_type = typename Registry<Entity>::component_type;
|
||||
|
||||
template<typename Component>
|
||||
struct Wrapper { Component component; };
|
||||
|
||||
struct Handler {
|
||||
basic_fn_type *accommodate;
|
||||
basic_fn_type *assign;
|
||||
};
|
||||
|
||||
void release() {
|
||||
if(registry->valid(entity)) {
|
||||
registry->destroy(entity);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Registry type. */
|
||||
using registry_type = Registry<Entity>;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
|
||||
/**
|
||||
* @brief Constructs a prototype that is bound to a given registry.
|
||||
* @param registry A valid reference to a registry.
|
||||
*/
|
||||
Prototype(Registry<Entity> ®istry)
|
||||
: registry{®istry},
|
||||
entity{registry.create()}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Releases all its resources.
|
||||
*/
|
||||
~Prototype() {
|
||||
release();
|
||||
}
|
||||
|
||||
/*! @brief Copying a prototype isn't allowed. */
|
||||
Prototype(const Prototype &) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*
|
||||
* After prototype move construction, instances that have been moved from
|
||||
* are placed in a valid but unspecified state. It's highly discouraged to
|
||||
* continue using them.
|
||||
*
|
||||
* @param other The instance to move from.
|
||||
*/
|
||||
Prototype(Prototype &&other)
|
||||
: handlers{std::move(other.handlers)},
|
||||
registry{other.registry},
|
||||
entity{other.entity}
|
||||
{
|
||||
other.entity = ~entity_type{};
|
||||
}
|
||||
|
||||
/*! @brief Copying a prototype isn't allowed. @return This Prototype. */
|
||||
Prototype & operator=(const Prototype &) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move assignment operator.
|
||||
*
|
||||
* After prototype move assignment, instances that have been moved from are
|
||||
* placed in a valid but unspecified state. It's highly discouraged to
|
||||
* continue using them.
|
||||
*
|
||||
* @param other The instance to move from.
|
||||
* @return This Prototype.
|
||||
*/
|
||||
Prototype & operator=(Prototype &&other) {
|
||||
if(this != &other) {
|
||||
auto tmp{std::move(other)};
|
||||
handlers.swap(tmp.handlers);
|
||||
std::swap(registry, tmp.registry);
|
||||
std::swap(entity, tmp.entity);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns to or replaces the given component of a prototype.
|
||||
* @tparam Component Type of component to assign or replace.
|
||||
* @tparam Args Types of arguments to use to construct the component.
|
||||
* @param args Parameters to use to initialize the component.
|
||||
* @return A reference to the newly created component.
|
||||
*/
|
||||
template<typename Component, typename... Args>
|
||||
Component & set(Args &&... args) {
|
||||
basic_fn_type *accommodate = [](const Prototype &prototype, Registry<Entity> &other, const Entity dst) {
|
||||
const auto &wrapper = prototype.registry->template get<Wrapper<Component>>(prototype.entity);
|
||||
other.template accommodate<Component>(dst, wrapper.component);
|
||||
};
|
||||
|
||||
basic_fn_type *assign = [](const Prototype &prototype, Registry<Entity> &other, const Entity dst) {
|
||||
if(!other.template has<Component>(dst)) {
|
||||
const auto &wrapper = prototype.registry->template get<Wrapper<Component>>(prototype.entity);
|
||||
other.template accommodate<Component>(dst, wrapper.component);
|
||||
}
|
||||
};
|
||||
|
||||
handlers[registry->template type<Component>()] = Handler{accommodate, assign};
|
||||
auto &wrapper = registry->template accommodate<Wrapper<Component>>(entity, Component{std::forward<Args>(args)...});
|
||||
return wrapper.component;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes the given component from a prototype.
|
||||
* @tparam Component Type of component to remove.
|
||||
*/
|
||||
template<typename Component>
|
||||
void unset() ENTT_NOEXCEPT {
|
||||
registry->template reset<Wrapper<Component>>(entity);
|
||||
handlers.erase(registry->template type<Component>());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a prototype owns all the given components.
|
||||
* @tparam Component Components for which to perform the check.
|
||||
* @return True if the prototype owns all the components, false otherwise.
|
||||
*/
|
||||
template<typename... Component>
|
||||
bool has() const ENTT_NOEXCEPT {
|
||||
return registry->template has<Wrapper<Component>...>(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the given component.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to get a component from a prototype that doesn't own it
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* prototype doesn't own an instance of the given component.
|
||||
*
|
||||
* @tparam Component Type of component to get.
|
||||
* @return A reference to the component owned by the prototype.
|
||||
*/
|
||||
template<typename Component>
|
||||
const Component & get() const ENTT_NOEXCEPT {
|
||||
return registry->template get<Wrapper<Component>>(entity).component;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the given component.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to get a component from a prototype that doesn't own it
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* prototype doesn't own an instance of the given component.
|
||||
*
|
||||
* @tparam Component Type of component to get.
|
||||
* @return A reference to the component owned by the prototype.
|
||||
*/
|
||||
template<typename Component>
|
||||
inline Component & get() ENTT_NOEXCEPT {
|
||||
return const_cast<Component &>(const_cast<const Prototype *>(this)->get<Component>());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the given components.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to get components from a prototype that doesn't own them
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* prototype doesn't own instances of the given components.
|
||||
*
|
||||
* @tparam Component Type of components to get.
|
||||
* @return References to the components owned by the prototype.
|
||||
*/
|
||||
template<typename... Component>
|
||||
inline std::enable_if_t<(sizeof...(Component) > 1), std::tuple<const Component &...>>
|
||||
get() const ENTT_NOEXCEPT {
|
||||
return std::tuple<const Component &...>{get<Component>()...};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the given components.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to get components from a prototype that doesn't own them
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* prototype doesn't own instances of the given components.
|
||||
*
|
||||
* @tparam Component Type of components to get.
|
||||
* @return References to the components owned by the prototype.
|
||||
*/
|
||||
template<typename... Component>
|
||||
inline std::enable_if_t<(sizeof...(Component) > 1), std::tuple<Component &...>>
|
||||
get() ENTT_NOEXCEPT {
|
||||
return std::tuple<Component &...>{get<Component>()...};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a new entity using a given prototype.
|
||||
*
|
||||
* Utility shortcut, equivalent to the following snippet:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* const auto entity = registry.create();
|
||||
* prototype(registry, entity);
|
||||
* @endcode
|
||||
*
|
||||
* @note
|
||||
* The registry may or may not be different from the one already used by
|
||||
* the prototype. There is also an overload that directly uses the
|
||||
* underlying registry.
|
||||
*
|
||||
* @param other A valid reference to a registry.
|
||||
* @return A valid entity identifier.
|
||||
*/
|
||||
entity_type create(registry_type &other) const {
|
||||
const auto entity = other.create();
|
||||
assign(other, entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a new entity using a given prototype.
|
||||
*
|
||||
* Utility shortcut, equivalent to the following snippet:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* const auto entity = registry.create();
|
||||
* prototype(entity);
|
||||
* @endcode
|
||||
*
|
||||
* @note
|
||||
* This overload directly uses the underlying registry as a working space.
|
||||
* Therefore, the components of the prototype and of the entity will share
|
||||
* the same registry.
|
||||
*
|
||||
* @return A valid entity identifier.
|
||||
*/
|
||||
inline entity_type create() const {
|
||||
return create(*registry);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns the components of a prototype to a given entity.
|
||||
*
|
||||
* Assigning a prototype to an entity won't overwrite existing components
|
||||
* under any circumstances.<br/>
|
||||
* In other words, only those components that the entity doesn't own yet are
|
||||
* copied over. All the other components remain unchanged.
|
||||
*
|
||||
* @note
|
||||
* The registry may or may not be different from the one already used by
|
||||
* the prototype. There is also an overload that directly uses the
|
||||
* underlying registry.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity.
|
||||
*
|
||||
* @param other A valid reference to a registry.
|
||||
* @param dst A valid entity identifier.
|
||||
*/
|
||||
void assign(registry_type &other, const entity_type dst) const {
|
||||
for(auto &handler: handlers) {
|
||||
handler.second.assign(*this, other, dst);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns the components of a prototype to a given entity.
|
||||
*
|
||||
* Assigning a prototype to an entity won't overwrite existing components
|
||||
* under any circumstances.<br/>
|
||||
* In other words, only those components that the entity doesn't own yet are
|
||||
* copied over. All the other components remain unchanged.
|
||||
*
|
||||
* @note
|
||||
* This overload directly uses the underlying registry as a working space.
|
||||
* Therefore, the components of the prototype and of the entity will share
|
||||
* the same registry.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity.
|
||||
*
|
||||
* @param dst A valid entity identifier.
|
||||
*/
|
||||
inline void assign(const entity_type dst) const {
|
||||
assign(*registry, dst);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns or replaces the components of a prototype for an entity.
|
||||
*
|
||||
* Existing components are overwritten, if any. All the other components
|
||||
* will be copied over to the target entity.
|
||||
*
|
||||
* @note
|
||||
* The registry may or may not be different from the one already used by
|
||||
* the prototype. There is also an overload that directly uses the
|
||||
* underlying registry.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity.
|
||||
*
|
||||
* @param other A valid reference to a registry.
|
||||
* @param dst A valid entity identifier.
|
||||
*/
|
||||
void accommodate(registry_type &other, const entity_type dst) const {
|
||||
for(auto &handler: handlers) {
|
||||
handler.second.accommodate(*this, other, dst);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns or replaces the components of a prototype for an entity.
|
||||
*
|
||||
* Existing components are overwritten, if any. All the other components
|
||||
* will be copied over to the target entity.
|
||||
*
|
||||
* @note
|
||||
* This overload directly uses the underlying registry as a working space.
|
||||
* Therefore, the components of the prototype and of the entity will share
|
||||
* the same registry.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity.
|
||||
*
|
||||
* @param dst A valid entity identifier.
|
||||
*/
|
||||
inline void accommodate(const entity_type dst) const {
|
||||
accommodate(*registry, dst);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns the components of a prototype to an entity.
|
||||
*
|
||||
* Assigning a prototype to an entity won't overwrite existing components
|
||||
* under any circumstances.<br/>
|
||||
* In other words, only the components that the entity doesn't own yet are
|
||||
* copied over. All the other components remain unchanged.
|
||||
*
|
||||
* @note
|
||||
* The registry may or may not be different from the one already used by
|
||||
* the prototype. There is also an overload that directly uses the
|
||||
* underlying registry.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity.
|
||||
*
|
||||
* @param other A valid reference to a registry.
|
||||
* @param dst A valid entity identifier.
|
||||
*/
|
||||
inline void operator()(registry_type &other, const entity_type dst) const ENTT_NOEXCEPT {
|
||||
assign(other, dst);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns the components of a prototype to an entity.
|
||||
*
|
||||
* Assigning a prototype to an entity won't overwrite existing components
|
||||
* under any circumstances.<br/>
|
||||
* In other words, only the components that the entity doesn't own yet are
|
||||
* copied over. All the other components remain unchanged.
|
||||
*
|
||||
* @note
|
||||
* This overload directly uses the underlying registry as a working space.
|
||||
* Therefore, the components of the prototype and of the entity will share
|
||||
* the same registry.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity.
|
||||
*
|
||||
* @param dst A valid entity identifier.
|
||||
*/
|
||||
inline void operator()(const entity_type dst) const ENTT_NOEXCEPT {
|
||||
assign(*registry, dst);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a new entity using a given prototype.
|
||||
*
|
||||
* Utility shortcut, equivalent to the following snippet:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* const auto entity = registry.create();
|
||||
* prototype(registry, entity);
|
||||
* @endcode
|
||||
*
|
||||
* @note
|
||||
* The registry may or may not be different from the one already used by
|
||||
* the prototype. There is also an overload that directly uses the
|
||||
* underlying registry.
|
||||
*
|
||||
* @param other A valid reference to a registry.
|
||||
* @return A valid entity identifier.
|
||||
*/
|
||||
inline entity_type operator()(registry_type &other) const ENTT_NOEXCEPT {
|
||||
return create(other);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a new entity using a given prototype.
|
||||
*
|
||||
* Utility shortcut, equivalent to the following snippet:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* const auto entity = registry.create();
|
||||
* prototype(entity);
|
||||
* @endcode
|
||||
*
|
||||
* @note
|
||||
* This overload directly uses the underlying registry as a working space.
|
||||
* Therefore, the components of the prototype and of the entity will share
|
||||
* the same registry.
|
||||
*
|
||||
* @return A valid entity identifier.
|
||||
*/
|
||||
inline entity_type operator()() const ENTT_NOEXCEPT {
|
||||
return create(*registry);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<component_type, Handler> handlers;
|
||||
Registry<Entity> *registry;
|
||||
entity_type entity;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Default prototype
|
||||
*
|
||||
* The default prototype is the best choice for almost all the
|
||||
* applications.<br/>
|
||||
* Users should have a really good reason to choose something different.
|
||||
*/
|
||||
using DefaultPrototype = Prototype<DefaultRegistry::entity_type>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_PROTOTYPE_HPP
|
||||
File diff suppressed because it is too large
Load Diff
257
src/entt/entity/runtime_view.hpp
Normal file
257
src/entt/entity/runtime_view.hpp
Normal file
@@ -0,0 +1,257 @@
|
||||
#ifndef ENTT_ENTITY_RUNTIME_VIEW_HPP
|
||||
#define ENTT_ENTITY_RUNTIME_VIEW_HPP
|
||||
|
||||
|
||||
#include <iterator>
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "sparse_set.hpp"
|
||||
#include "entity.hpp"
|
||||
#include "fwd.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Runtime view.
|
||||
*
|
||||
* Runtime views iterate over those entities that have at least all the given
|
||||
* components in their bags. During initialization, a runtime view looks at the
|
||||
* number of entities available for each component and picks up a reference to
|
||||
* the smallest set of candidate entities in order to get a performance boost
|
||||
* when iterate.<br/>
|
||||
* Order of elements during iterations are highly dependent on the order of the
|
||||
* underlying data structures. See sparse_set and its specializations for more
|
||||
* details.
|
||||
*
|
||||
* @b Important
|
||||
*
|
||||
* Iterators aren't invalidated if:
|
||||
*
|
||||
* * New instances of the given components are created and assigned to entities.
|
||||
* * The entity currently pointed is modified (as an example, if one of the
|
||||
* given components is removed from the entity to which the iterator points).
|
||||
* * The entity currently pointed is destroyed.
|
||||
*
|
||||
* In all the other cases, modifying the pools of the given components in any
|
||||
* way invalidates all the iterators and using them results in undefined
|
||||
* behavior.
|
||||
*
|
||||
* @note
|
||||
* Views share references to the underlying data structures of the registry that
|
||||
* generated them. Therefore any change to the entities and to the components
|
||||
* made by means of the registry are immediately reflected by the views, unless
|
||||
* a pool was missing when the view was built (in this case, the view won't
|
||||
* have a valid reference and won't be updated accordingly).
|
||||
*
|
||||
* @warning
|
||||
* Lifetime of a view must overcome the one of the registry that generated it.
|
||||
* In any other case, attempting to use a view results in undefined behavior.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
class basic_runtime_view {
|
||||
/*! @brief A registry is allowed to create views. */
|
||||
friend class basic_registry<Entity>;
|
||||
|
||||
using underlying_iterator_type = typename sparse_set<Entity>::iterator_type;
|
||||
using traits_type = entt_traits<std::underlying_type_t<Entity>>;
|
||||
|
||||
class iterator {
|
||||
friend class basic_runtime_view<Entity>;
|
||||
|
||||
iterator(underlying_iterator_type first, underlying_iterator_type last, const sparse_set<Entity> * const *others, const sparse_set<Entity> * const *length) ENTT_NOEXCEPT
|
||||
: begin{first},
|
||||
end{last},
|
||||
from{others},
|
||||
to{length}
|
||||
{
|
||||
if(begin != end && !valid()) {
|
||||
++(*this);
|
||||
}
|
||||
}
|
||||
|
||||
bool valid() const ENTT_NOEXCEPT {
|
||||
return std::all_of(from, to, [entt = *begin](const auto *view) {
|
||||
return view->has(entt);
|
||||
});
|
||||
}
|
||||
|
||||
public:
|
||||
using difference_type = typename underlying_iterator_type::difference_type;
|
||||
using value_type = typename underlying_iterator_type::value_type;
|
||||
using pointer = typename underlying_iterator_type::pointer;
|
||||
using reference = typename underlying_iterator_type::reference;
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
|
||||
iterator() ENTT_NOEXCEPT = default;
|
||||
|
||||
iterator & operator++() ENTT_NOEXCEPT {
|
||||
return (++begin != end && !valid()) ? ++(*this) : *this;
|
||||
}
|
||||
|
||||
iterator operator++(int) ENTT_NOEXCEPT {
|
||||
iterator orig = *this;
|
||||
return ++(*this), orig;
|
||||
}
|
||||
|
||||
bool operator==(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return other.begin == begin;
|
||||
}
|
||||
|
||||
bool operator!=(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
pointer operator->() const ENTT_NOEXCEPT {
|
||||
return begin.operator->();
|
||||
}
|
||||
|
||||
reference operator*() const ENTT_NOEXCEPT {
|
||||
return *operator->();
|
||||
}
|
||||
|
||||
private:
|
||||
underlying_iterator_type begin;
|
||||
underlying_iterator_type end;
|
||||
const sparse_set<Entity> * const *from;
|
||||
const sparse_set<Entity> * const *to;
|
||||
};
|
||||
|
||||
basic_runtime_view(std::vector<const sparse_set<Entity> *> others) ENTT_NOEXCEPT
|
||||
: pools{std::move(others)}
|
||||
{
|
||||
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());
|
||||
}
|
||||
|
||||
bool valid() const ENTT_NOEXCEPT {
|
||||
return !pools.empty() && pools.front();
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Input iterator type. */
|
||||
using iterator_type = iterator;
|
||||
|
||||
/**
|
||||
* @brief Estimates the number of entities that have the given components.
|
||||
* @return Estimated number of entities that have the given components.
|
||||
*/
|
||||
size_type size() const ENTT_NOEXCEPT {
|
||||
return valid() ? pools.front()->size() : size_type{};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the view is definitely empty.
|
||||
* @return True if the view is definitely empty, false otherwise.
|
||||
*/
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
return !valid() || pools.front()->empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the first entity that has the given
|
||||
* components.
|
||||
*
|
||||
* The returned iterator points to the first entity that has the given
|
||||
* components. If the view is empty, the returned iterator will be equal to
|
||||
* `end()`.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed to the underlying data
|
||||
* structures.
|
||||
*
|
||||
* @return An iterator to the first entity that has the given components.
|
||||
*/
|
||||
iterator_type begin() const ENTT_NOEXCEPT {
|
||||
iterator_type it{};
|
||||
|
||||
if(valid()) {
|
||||
const auto &pool = *pools.front();
|
||||
const auto * const *data = pools.data();
|
||||
it = { pool.begin(), pool.end(), data + 1, data + pools.size() };
|
||||
}
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator that is past the last entity that has the
|
||||
* given components.
|
||||
*
|
||||
* The returned iterator points to the entity following the last entity that
|
||||
* has the given components. Attempting to dereference the returned iterator
|
||||
* results in undefined behavior.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed to the underlying data
|
||||
* structures.
|
||||
*
|
||||
* @return An iterator to the entity following the last entity that has the
|
||||
* given components.
|
||||
*/
|
||||
iterator_type end() const ENTT_NOEXCEPT {
|
||||
iterator_type it{};
|
||||
|
||||
if(valid()) {
|
||||
const auto &pool = *pools.front();
|
||||
it = { pool.end(), pool.end(), nullptr, nullptr };
|
||||
}
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a view contains an entity.
|
||||
* @param entt A valid entity identifier.
|
||||
* @return True if the view contains the given entity, false otherwise.
|
||||
*/
|
||||
bool contains(const entity_type entt) const ENTT_NOEXCEPT {
|
||||
return valid() && std::all_of(pools.cbegin(), pools.cend(), [entt](const auto *view) {
|
||||
return view->find(entt) != view->end();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterates entities and applies the given function object to them.
|
||||
*
|
||||
* The function object is invoked for each entity. It is provided only with
|
||||
* the entity itself. To get the components, users can use the registry with
|
||||
* which the view was built.<br/>
|
||||
* The signature of the function should be equivalent to the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(const entity_type);
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void each(Func func) const {
|
||||
std::for_each(begin(), end(), func);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<const sparse_set<Entity> *> pools;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_RUNTIME_VIEW_HPP
|
||||
@@ -5,57 +5,54 @@
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include <cassert>
|
||||
#include <iterator>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include "../config/config.h"
|
||||
#include "entt_traits.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "entity.hpp"
|
||||
#include "fwd.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Forward declaration of the registry class.
|
||||
*/
|
||||
template<typename>
|
||||
class Registry;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Utility class to create snapshots from a registry.
|
||||
*
|
||||
* A _snapshot_ can be either a dump of the entire registry or a narrower
|
||||
* selection of components and tags of interest.<br/>
|
||||
* selection of components of interest.<br/>
|
||||
* This type can be used in both cases if provided with a correctly configured
|
||||
* output archive.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
class Snapshot final {
|
||||
class basic_snapshot {
|
||||
/*! @brief A registry is allowed to create snapshots. */
|
||||
friend class Registry<Entity>;
|
||||
friend class basic_registry<Entity>;
|
||||
|
||||
using follow_fn_type = Entity(const Registry<Entity> &, const Entity);
|
||||
using follow_fn_type = Entity(const basic_registry<Entity> &, const Entity);
|
||||
using traits_type = entt_traits<std::underlying_type_t<Entity>>;
|
||||
|
||||
Snapshot(const Registry<Entity> ®istry, Entity seed, follow_fn_type *follow) ENTT_NOEXCEPT
|
||||
: registry{registry},
|
||||
seed{seed},
|
||||
follow{follow}
|
||||
basic_snapshot(const basic_registry<Entity> *source, Entity init, follow_fn_type *fn) ENTT_NOEXCEPT
|
||||
: reg{source},
|
||||
seed{init},
|
||||
follow{fn}
|
||||
{}
|
||||
|
||||
template<typename Component, typename Archive, typename It>
|
||||
void get(Archive &archive, std::size_t sz, It first, It last) const {
|
||||
archive(static_cast<Entity>(sz));
|
||||
archive(typename traits_type::entity_type(sz));
|
||||
|
||||
while(first != last) {
|
||||
const auto entity = *(first++);
|
||||
const auto entt = *(first++);
|
||||
|
||||
if(registry.template has<Component>(entity)) {
|
||||
archive(entity, registry.template get<Component>(entity));
|
||||
if(reg->template has<Component>(entt)) {
|
||||
if constexpr(std::is_empty_v<Component>) {
|
||||
archive(entt);
|
||||
} else {
|
||||
archive(entt, reg->template get<Component>(entt));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,27 +63,19 @@ class Snapshot final {
|
||||
auto begin = first;
|
||||
|
||||
while(begin != last) {
|
||||
const auto entity = *(begin++);
|
||||
using accumulator_type = std::size_t[];
|
||||
accumulator_type accumulator = { (registry.template has<Component>(entity) ? ++size[Indexes] : size[Indexes])... };
|
||||
(void)accumulator;
|
||||
const auto entt = *(begin++);
|
||||
((reg->template has<Component>(entt) ? ++size[Indexes] : size[Indexes]), ...);
|
||||
}
|
||||
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { (get<Component>(archive, size[Indexes], first, last), 0)... };
|
||||
(void)accumulator;
|
||||
(get<Component>(archive, size[Indexes], first, last), ...);
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Copying a snapshot isn't allowed. */
|
||||
Snapshot(const Snapshot &) = delete;
|
||||
/*! @brief Default move constructor. */
|
||||
Snapshot(Snapshot &&) = default;
|
||||
basic_snapshot(basic_snapshot &&) = default;
|
||||
|
||||
/*! @brief Copying a snapshot isn't allowed. @return This snapshot. */
|
||||
Snapshot & operator=(const Snapshot &) = delete;
|
||||
/*! @brief Default move assignment operator. @return This snapshot. */
|
||||
Snapshot & operator=(Snapshot &&) = default;
|
||||
basic_snapshot & operator=(basic_snapshot &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Puts aside all the entities that are still in use.
|
||||
@@ -99,9 +88,9 @@ public:
|
||||
* @return An object of this type to continue creating the snapshot.
|
||||
*/
|
||||
template<typename Archive>
|
||||
const Snapshot & entities(Archive &archive) const {
|
||||
archive(static_cast<Entity>(registry.alive()));
|
||||
registry.each([&archive](const auto entity) { archive(entity); });
|
||||
const basic_snapshot & entities(Archive &archive) const {
|
||||
archive(typename traits_type::entity_type(reg->alive()));
|
||||
reg->each([&archive](const auto entt) { archive(entt); });
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -116,45 +105,23 @@ public:
|
||||
* @return An object of this type to continue creating the snapshot.
|
||||
*/
|
||||
template<typename Archive>
|
||||
const Snapshot & destroyed(Archive &archive) const {
|
||||
auto size = registry.size() - registry.alive();
|
||||
archive(static_cast<Entity>(size));
|
||||
auto curr = seed;
|
||||
const basic_snapshot & destroyed(Archive &archive) const {
|
||||
auto size = reg->size() - reg->alive();
|
||||
archive(typename traits_type::entity_type(size));
|
||||
|
||||
for(; size; --size) {
|
||||
if(size) {
|
||||
auto curr = seed;
|
||||
archive(curr);
|
||||
curr = follow(registry, curr);
|
||||
|
||||
for(--size; size; --size) {
|
||||
curr = follow(*reg, curr);
|
||||
archive(curr);
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Puts aside the given component.
|
||||
*
|
||||
* Each instance is serialized together with the entity to which it belongs.
|
||||
* Entities are serialized along with their versions.
|
||||
*
|
||||
* @tparam Component Type of component to serialize.
|
||||
* @tparam Archive Type of output archive.
|
||||
* @param archive A valid reference to an output archive.
|
||||
* @return An object of this type to continue creating the snapshot.
|
||||
*/
|
||||
template<typename Component, typename Archive>
|
||||
const Snapshot & component(Archive &archive) const {
|
||||
const auto sz = registry.template size<Component>();
|
||||
const auto *entities = registry.template data<Component>();
|
||||
|
||||
archive(static_cast<Entity>(sz));
|
||||
|
||||
for(std::remove_const_t<decltype(sz)> i{}; i < sz; ++i) {
|
||||
const auto entity = entities[i];
|
||||
archive(entity, registry.template get<Component>(entity));
|
||||
};
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Puts aside the given components.
|
||||
*
|
||||
@@ -167,11 +134,26 @@ public:
|
||||
* @return An object of this type to continue creating the snapshot.
|
||||
*/
|
||||
template<typename... Component, typename Archive>
|
||||
std::enable_if_t<(sizeof...(Component) > 1), const Snapshot &>
|
||||
component(Archive &archive) const {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (component<Component>(archive), 0)... };
|
||||
(void)accumulator;
|
||||
const basic_snapshot & component(Archive &archive) const {
|
||||
if constexpr(sizeof...(Component) == 1) {
|
||||
const auto sz = reg->template size<Component...>();
|
||||
const auto *entities = reg->template data<Component...>();
|
||||
|
||||
archive(typename traits_type::entity_type(sz));
|
||||
|
||||
for(std::remove_const_t<decltype(sz)> pos{}; pos < sz; ++pos) {
|
||||
const auto entt = entities[pos];
|
||||
|
||||
if constexpr(std::is_empty_v<Component...>) {
|
||||
archive(entt);
|
||||
} else {
|
||||
archive(entt, reg->template get<Component...>(entt));
|
||||
}
|
||||
};
|
||||
} else {
|
||||
(component<Component>(archive), ...);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -190,58 +172,13 @@ public:
|
||||
* @return An object of this type to continue creating the snapshot.
|
||||
*/
|
||||
template<typename... Component, typename Archive, typename It>
|
||||
const Snapshot & component(Archive &archive, It first, It last) const {
|
||||
const basic_snapshot & component(Archive &archive, It first, It last) const {
|
||||
component<Component...>(archive, first, last, std::make_index_sequence<sizeof...(Component)>{});
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Puts aside the given tag.
|
||||
*
|
||||
* Each instance is serialized together with the entity to which it belongs.
|
||||
* Entities are serialized along with their versions.
|
||||
*
|
||||
* @tparam Tag Type of tag to serialize.
|
||||
* @tparam Archive Type of output archive.
|
||||
* @param archive A valid reference to an output archive.
|
||||
* @return An object of this type to continue creating the snapshot.
|
||||
*/
|
||||
template<typename Tag, typename Archive>
|
||||
const Snapshot & tag(Archive &archive) const {
|
||||
const bool has = registry.template has<Tag>();
|
||||
|
||||
// numerical length is forced for tags to facilitate loading
|
||||
archive(has ? Entity(1): Entity{});
|
||||
|
||||
if(has) {
|
||||
archive(registry.template attachee<Tag>(), registry.template get<Tag>());
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Puts aside the given tags.
|
||||
*
|
||||
* Each instance is serialized together with the entity to which it belongs.
|
||||
* Entities are serialized along with their versions.
|
||||
*
|
||||
* @tparam Tag Types of tags to serialize.
|
||||
* @tparam Archive Type of output archive.
|
||||
* @param archive A valid reference to an output archive.
|
||||
* @return An object of this type to continue creating the snapshot.
|
||||
*/
|
||||
template<typename... Tag, typename Archive>
|
||||
std::enable_if_t<(sizeof...(Tag) > 1), const Snapshot &>
|
||||
tag(Archive &archive) const {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (tag<Tag>(archive), 0)... };
|
||||
(void)accumulator;
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
const Registry<Entity> ®istry;
|
||||
const basic_registry<Entity> *reg;
|
||||
const Entity seed;
|
||||
follow_fn_type *follow;
|
||||
};
|
||||
@@ -258,57 +195,61 @@ private:
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
class SnapshotLoader final {
|
||||
class basic_snapshot_loader {
|
||||
/*! @brief A registry is allowed to create snapshot loaders. */
|
||||
friend class Registry<Entity>;
|
||||
friend class basic_registry<Entity>;
|
||||
|
||||
using assure_fn_type = void(Registry<Entity> &, const Entity, const bool);
|
||||
using force_fn_type = void(basic_registry<Entity> &, const Entity, const bool);
|
||||
using traits_type = entt_traits<std::underlying_type_t<Entity>>;
|
||||
|
||||
SnapshotLoader(Registry<Entity> ®istry, assure_fn_type *assure_fn) ENTT_NOEXCEPT
|
||||
: registry{registry},
|
||||
assure_fn{assure_fn}
|
||||
basic_snapshot_loader(basic_registry<Entity> *source, force_fn_type *fn) ENTT_NOEXCEPT
|
||||
: reg{source},
|
||||
force{fn}
|
||||
{
|
||||
// restore a snapshot as a whole requires a clean registry
|
||||
assert(!registry.capacity());
|
||||
// to restore a snapshot as a whole requires a clean registry
|
||||
ENTT_ASSERT(reg->empty());
|
||||
}
|
||||
|
||||
template<typename Archive>
|
||||
void assure(Archive &archive, bool destroyed) const {
|
||||
Entity length{};
|
||||
void assure(Archive &archive, bool discard) const {
|
||||
typename traits_type::entity_type length{};
|
||||
archive(length);
|
||||
|
||||
while(length--) {
|
||||
Entity entity{};
|
||||
archive(entity);
|
||||
assure_fn(registry, entity, destroyed);
|
||||
Entity entt{};
|
||||
archive(entt);
|
||||
force(*reg, entt, discard);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Type, typename Archive, typename... Args>
|
||||
void assign(Archive &archive, Args... args) const {
|
||||
Entity length{};
|
||||
typename traits_type::entity_type length{};
|
||||
archive(length);
|
||||
|
||||
while(length--) {
|
||||
Entity entity{};
|
||||
Type instance{};
|
||||
archive(entity, instance);
|
||||
static constexpr auto destroyed = false;
|
||||
assure_fn(registry, entity, destroyed);
|
||||
registry.template assign<Type>(args..., entity, static_cast<const Type &>(instance));
|
||||
static constexpr auto discard = false;
|
||||
Entity entt{};
|
||||
|
||||
if constexpr(std::is_empty_v<Type>) {
|
||||
archive(entt);
|
||||
force(*reg, entt, discard);
|
||||
reg->template assign<Type>(args..., entt);
|
||||
} else {
|
||||
Type instance{};
|
||||
archive(entt, instance);
|
||||
force(*reg, entt, discard);
|
||||
reg->template assign<Type>(args..., entt, std::as_const(instance));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Copying a snapshot loader isn't allowed. */
|
||||
SnapshotLoader(const SnapshotLoader &) = delete;
|
||||
/*! @brief Default move constructor. */
|
||||
SnapshotLoader(SnapshotLoader &&) = default;
|
||||
basic_snapshot_loader(basic_snapshot_loader &&) = default;
|
||||
|
||||
/*! @brief Copying a snapshot loader isn't allowed. @return This loader. */
|
||||
SnapshotLoader & operator=(const SnapshotLoader &) = delete;
|
||||
/*! @brief Default move assignment operator. @return This loader. */
|
||||
SnapshotLoader & operator=(SnapshotLoader &&) = default;
|
||||
basic_snapshot_loader & operator=(basic_snapshot_loader &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Restores entities that were in use during serialization.
|
||||
@@ -321,9 +262,9 @@ public:
|
||||
* @return A valid loader to continue restoring data.
|
||||
*/
|
||||
template<typename Archive>
|
||||
const SnapshotLoader & entities(Archive &archive) const {
|
||||
static constexpr auto destroyed = false;
|
||||
assure(archive, destroyed);
|
||||
const basic_snapshot_loader & entities(Archive &archive) const {
|
||||
static constexpr auto discard = false;
|
||||
assure(archive, discard);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -338,9 +279,9 @@ public:
|
||||
* @return A valid loader to continue restoring data.
|
||||
*/
|
||||
template<typename Archive>
|
||||
const SnapshotLoader & destroyed(Archive &archive) const {
|
||||
static constexpr auto destroyed = true;
|
||||
assure(archive, destroyed);
|
||||
const basic_snapshot_loader & destroyed(Archive &archive) const {
|
||||
static constexpr auto discard = true;
|
||||
assure(archive, discard);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -358,55 +299,32 @@ public:
|
||||
* @return A valid loader to continue restoring data.
|
||||
*/
|
||||
template<typename... Component, typename Archive>
|
||||
const SnapshotLoader & component(Archive &archive) const {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (assign<Component>(archive), 0)... };
|
||||
(void)accumulator;
|
||||
const basic_snapshot_loader & component(Archive &archive) const {
|
||||
(assign<Component>(archive), ...);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Restores tags and assigns them to the right entities.
|
||||
*
|
||||
* The template parameter list must be exactly the same used during
|
||||
* serialization. In the event that the entity to which the tag is assigned
|
||||
* doesn't exist yet, the loader will take care to create it with the
|
||||
* version it originally had.
|
||||
*
|
||||
* @tparam Tag Types of tags to restore.
|
||||
* @tparam Archive Type of input archive.
|
||||
* @param archive A valid reference to an input archive.
|
||||
* @return A valid loader to continue restoring data.
|
||||
*/
|
||||
template<typename... Tag, typename Archive>
|
||||
const SnapshotLoader & tag(Archive &archive) const {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (assign<Tag>(archive, tag_t{}), 0)... };
|
||||
(void)accumulator;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destroys those entities that have neither components nor tags.
|
||||
* @brief Destroys those entities that have no components.
|
||||
*
|
||||
* In case all the entities were serialized but only part of the components
|
||||
* and tags was saved, it could happen that some of the entities have
|
||||
* neither components nor tags once restored.<br/>
|
||||
* was saved, it could happen that some of the entities have no components
|
||||
* once restored.<br/>
|
||||
* This functions helps to identify and destroy those entities.
|
||||
*
|
||||
* @return A valid loader to continue restoring data.
|
||||
*/
|
||||
const SnapshotLoader & orphans() const {
|
||||
registry.orphans([this](const auto entity) {
|
||||
registry.destroy(entity);
|
||||
const basic_snapshot_loader & orphans() const {
|
||||
reg->orphans([this](const auto entt) {
|
||||
reg->destroy(entt);
|
||||
});
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
Registry<Entity> ®istry;
|
||||
assure_fn_type *assure_fn;
|
||||
basic_registry<Entity> *reg;
|
||||
force_fn_type *force;
|
||||
};
|
||||
|
||||
|
||||
@@ -427,63 +345,55 @@ private:
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
class ContinuousLoader final {
|
||||
using traits_type = entt_traits<Entity>;
|
||||
class basic_continuous_loader {
|
||||
using traits_type = entt_traits<std::underlying_type_t<Entity>>;
|
||||
|
||||
void destroy(Entity entity) {
|
||||
const auto it = remloc.find(entity);
|
||||
void destroy(Entity entt) {
|
||||
const auto it = remloc.find(entt);
|
||||
|
||||
if(it == remloc.cend()) {
|
||||
const auto local = registry.create();
|
||||
remloc.emplace(entity, std::make_pair(local, true));
|
||||
registry.destroy(local);
|
||||
const auto local = reg->create();
|
||||
remloc.emplace(entt, std::make_pair(local, true));
|
||||
reg->destroy(local);
|
||||
}
|
||||
}
|
||||
|
||||
void restore(Entity entity) {
|
||||
const auto it = remloc.find(entity);
|
||||
void restore(Entity entt) {
|
||||
const auto it = remloc.find(entt);
|
||||
|
||||
if(it == remloc.cend()) {
|
||||
const auto local = registry.create();
|
||||
remloc.emplace(entity, std::make_pair(local, true));
|
||||
const auto local = reg->create();
|
||||
remloc.emplace(entt, std::make_pair(local, true));
|
||||
} else {
|
||||
remloc[entity].first =
|
||||
registry.valid(remloc[entity].first)
|
||||
? remloc[entity].first
|
||||
: registry.create();
|
||||
|
||||
remloc[entt].first = reg->valid(remloc[entt].first) ? remloc[entt].first : reg->create();
|
||||
// set the dirty flag
|
||||
remloc[entity].second = true;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Type, typename Member>
|
||||
std::enable_if_t<std::is_same<Member, Entity>::value>
|
||||
update(Type &instance, Member Type:: *member) {
|
||||
instance.*member = map(instance.*member);
|
||||
}
|
||||
|
||||
template<typename Type, typename Member>
|
||||
std::enable_if_t<std::is_same<typename std::iterator_traits<typename Member::iterator>::value_type, Entity>::value>
|
||||
update(Type &instance, Member Type:: *member) {
|
||||
for(auto &entity: instance.*member) {
|
||||
entity = map(entity);
|
||||
remloc[entt].second = true;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Other, typename Type, typename Member>
|
||||
std::enable_if_t<!std::is_same<Other, Type>::value>
|
||||
update(Other &, Member Type:: *) {}
|
||||
void update(Other &instance, Member Type:: *member) {
|
||||
if constexpr(!std::is_same_v<Other, Type>) {
|
||||
return;
|
||||
} else if constexpr(std::is_same_v<Member, Entity>) {
|
||||
instance.*member = map(instance.*member);
|
||||
} else {
|
||||
// maybe a container? let's try...
|
||||
for(auto &entt: instance.*member) {
|
||||
entt = map(entt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Archive>
|
||||
void assure(Archive &archive, void(ContinuousLoader:: *member)(Entity)) {
|
||||
Entity length{};
|
||||
void assure(Archive &archive, void(basic_continuous_loader:: *member)(Entity)) {
|
||||
typename traits_type::entity_type length{};
|
||||
archive(length);
|
||||
|
||||
while(length--) {
|
||||
Entity entity{};
|
||||
archive(entity);
|
||||
(this->*member)(entity);
|
||||
Entity entt{};
|
||||
archive(entt);
|
||||
(this->*member)(entt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,29 +402,31 @@ class ContinuousLoader final {
|
||||
for(auto &&ref: remloc) {
|
||||
const auto local = ref.second.first;
|
||||
|
||||
if(registry.valid(local)) {
|
||||
registry.template reset<Component>(local);
|
||||
if(reg->valid(local)) {
|
||||
reg->template reset<Component>(local);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Other, typename Archive, typename Func, typename... Type, typename... Member>
|
||||
void assign(Archive &archive, Func func, Member Type:: *... member) {
|
||||
Entity length{};
|
||||
template<typename Other, typename Archive, typename... Type, typename... Member>
|
||||
void assign(Archive &archive, [[maybe_unused]] Member Type:: *... member) {
|
||||
typename traits_type::entity_type length{};
|
||||
archive(length);
|
||||
|
||||
while(length--) {
|
||||
Entity entity{};
|
||||
Other instance{};
|
||||
Entity entt{};
|
||||
|
||||
archive(entity, instance);
|
||||
restore(entity);
|
||||
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (update(instance, member), 0)... };
|
||||
(void)accumulator;
|
||||
|
||||
func(map(entity), instance);
|
||||
if constexpr(std::is_empty_v<Other>) {
|
||||
archive(entt);
|
||||
restore(entt);
|
||||
reg->template assign_or_replace<Other>(map(entt));
|
||||
} else {
|
||||
Other instance{};
|
||||
archive(entt, instance);
|
||||
(update(instance, member), ...);
|
||||
restore(entt);
|
||||
reg->template assign_or_replace<Other>(map(entt), std::as_const(instance));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -524,21 +436,17 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Constructs a loader that is bound to a given registry.
|
||||
* @param registry A valid reference to a registry.
|
||||
* @param source A valid reference to a registry.
|
||||
*/
|
||||
ContinuousLoader(Registry<entity_type> ®istry) ENTT_NOEXCEPT
|
||||
: registry{registry}
|
||||
basic_continuous_loader(basic_registry<entity_type> &source) ENTT_NOEXCEPT
|
||||
: reg{&source}
|
||||
{}
|
||||
|
||||
/*! @brief Copying a snapshot loader isn't allowed. */
|
||||
ContinuousLoader(const ContinuousLoader &) = delete;
|
||||
/*! @brief Default move constructor. */
|
||||
ContinuousLoader(ContinuousLoader &&) = default;
|
||||
basic_continuous_loader(basic_continuous_loader &&) = default;
|
||||
|
||||
/*! @brief Copying a snapshot loader isn't allowed. @return This loader. */
|
||||
ContinuousLoader & operator=(const ContinuousLoader &) = delete;
|
||||
/*! @brief Default move assignment operator. @return This loader. */
|
||||
ContinuousLoader & operator=(ContinuousLoader &&) = default;
|
||||
basic_continuous_loader & operator=(basic_continuous_loader &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Restores entities that were in use during serialization.
|
||||
@@ -551,8 +459,8 @@ public:
|
||||
* @return A non-const reference to this loader.
|
||||
*/
|
||||
template<typename Archive>
|
||||
ContinuousLoader & entities(Archive &archive) {
|
||||
assure(archive, &ContinuousLoader::restore);
|
||||
basic_continuous_loader & entities(Archive &archive) {
|
||||
assure(archive, &basic_continuous_loader::restore);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -567,8 +475,8 @@ public:
|
||||
* @return A non-const reference to this loader.
|
||||
*/
|
||||
template<typename Archive>
|
||||
ContinuousLoader & destroyed(Archive &archive) {
|
||||
assure(archive, &ContinuousLoader::destroy);
|
||||
basic_continuous_loader & destroyed(Archive &archive) {
|
||||
assure(archive, &basic_continuous_loader::destroy);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -592,45 +500,9 @@ public:
|
||||
* @return A non-const reference to this loader.
|
||||
*/
|
||||
template<typename... Component, typename Archive, typename... Type, typename... Member>
|
||||
ContinuousLoader & component(Archive &archive, Member Type:: *... member) {
|
||||
auto apply = [this](const auto entity, const auto &component) {
|
||||
registry.template accommodate<std::decay_t<decltype(component)>>(entity, component);
|
||||
};
|
||||
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (reset<Component>(), assign<Component>(archive, apply, member...), 0)... };
|
||||
(void)accumulator;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Restores tags and assigns them to the right entities.
|
||||
*
|
||||
* The template parameter list must be exactly the same used during
|
||||
* serialization. In the event that the entity to which the tag is assigned
|
||||
* doesn't exist yet, the loader will take care to create a local
|
||||
* counterpart for it.<br/>
|
||||
* Members can be either data members of type entity_type or containers of
|
||||
* entities. In both cases, the loader will visit them and update the
|
||||
* entities by replacing each one with its local counterpart.
|
||||
*
|
||||
* @tparam Tag Type of tag to restore.
|
||||
* @tparam Archive Type of input archive.
|
||||
* @tparam Type Types of components to update with local counterparts.
|
||||
* @tparam Member Types of members to update with their local counterparts.
|
||||
* @param archive A valid reference to an input archive.
|
||||
* @param member Members to update with their local counterparts.
|
||||
* @return A non-const reference to this loader.
|
||||
*/
|
||||
template<typename... Tag, typename Archive, typename... Type, typename... Member>
|
||||
ContinuousLoader & tag(Archive &archive, Member Type:: *... member) {
|
||||
auto apply = [this](const auto entity, const auto &tag) {
|
||||
registry.template assign<std::decay_t<decltype(tag)>>(tag_t{}, entity, tag);
|
||||
};
|
||||
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (registry.template remove<Tag>(), assign<Tag>(archive, apply, member...), 0)... };
|
||||
(void)accumulator;
|
||||
basic_continuous_loader & component(Archive &archive, Member Type:: *... member) {
|
||||
(reset<Component>(), ...);
|
||||
(assign<Component>(archive, member...), ...);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -642,7 +514,7 @@ public:
|
||||
*
|
||||
* @return A non-const reference to this loader.
|
||||
*/
|
||||
ContinuousLoader & shrink() {
|
||||
basic_continuous_loader & shrink() {
|
||||
auto it = remloc.begin();
|
||||
|
||||
while(it != remloc.cend()) {
|
||||
@@ -653,8 +525,8 @@ public:
|
||||
dirty = false;
|
||||
++it;
|
||||
} else {
|
||||
if(registry.valid(local)) {
|
||||
registry.destroy(local);
|
||||
if(reg->valid(local)) {
|
||||
reg->destroy(local);
|
||||
}
|
||||
|
||||
it = remloc.erase(it);
|
||||
@@ -665,18 +537,18 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destroys those entities that have neither components nor tags.
|
||||
* @brief Destroys those entities that have no components.
|
||||
*
|
||||
* In case all the entities were serialized but only part of the components
|
||||
* and tags was saved, it could happen that some of the entities have
|
||||
* neither components nor tags once restored.<br/>
|
||||
* was saved, it could happen that some of the entities have no components
|
||||
* once restored.<br/>
|
||||
* This functions helps to identify and destroy those entities.
|
||||
*
|
||||
* @return A non-const reference to this loader.
|
||||
*/
|
||||
ContinuousLoader & orphans() {
|
||||
registry.orphans([this](const auto entity) {
|
||||
registry.destroy(entity);
|
||||
basic_continuous_loader & orphans() {
|
||||
reg->orphans([this](const auto entt) {
|
||||
reg->destroy(entt);
|
||||
});
|
||||
|
||||
return *this;
|
||||
@@ -684,33 +556,32 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Tests if a loader knows about a given entity.
|
||||
* @param entity An entity identifier.
|
||||
* @param entt An entity identifier.
|
||||
* @return True if `entity` is managed by the loader, false otherwise.
|
||||
*/
|
||||
bool has(entity_type entity) const ENTT_NOEXCEPT {
|
||||
return (remloc.find(entity) != remloc.cend());
|
||||
bool has(entity_type entt) const ENTT_NOEXCEPT {
|
||||
return (remloc.find(entt) != remloc.cend());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the identifier to which an entity refers.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that isn't managed by the loader results in
|
||||
* undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* loader doesn't knows about the entity.
|
||||
*
|
||||
* @param entity An entity identifier.
|
||||
* @return The identifier to which `entity` refers in the target registry.
|
||||
* @param entt An entity identifier.
|
||||
* @return The local identifier if any, the null entity otherwise.
|
||||
*/
|
||||
entity_type map(entity_type entity) const ENTT_NOEXCEPT {
|
||||
assert(has(entity));
|
||||
return remloc.find(entity)->second.first;
|
||||
entity_type map(entity_type entt) const ENTT_NOEXCEPT {
|
||||
const auto it = remloc.find(entt);
|
||||
entity_type other = null;
|
||||
|
||||
if(it != remloc.cend()) {
|
||||
other = it->second.first;
|
||||
}
|
||||
|
||||
return other;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<Entity, std::pair<Entity, bool>> remloc;
|
||||
Registry<Entity> ®istry;
|
||||
std::unordered_map<entity_type, std::pair<entity_type, bool>> remloc;
|
||||
basic_registry<entity_type> *reg;
|
||||
};
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
704
src/entt/entity/storage.hpp
Normal file
704
src/entt/entity/storage.hpp
Normal file
@@ -0,0 +1,704 @@
|
||||
#ifndef ENTT_ENTITY_STORAGE_HPP
|
||||
#define ENTT_ENTITY_STORAGE_HPP
|
||||
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <numeric>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "../core/algorithm.hpp"
|
||||
#include "sparse_set.hpp"
|
||||
#include "entity.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Basic storage implementation.
|
||||
*
|
||||
* This class is a refinement of a sparse set that associates an object to an
|
||||
* entity. The main purpose of this class is to extend sparse sets to store
|
||||
* components in a registry. It guarantees fast access both to the elements and
|
||||
* 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.
|
||||
*
|
||||
* @note
|
||||
* Internal data structures arrange elements to maximize performance. Because of
|
||||
* that, there are no guarantees that elements have the expected order when
|
||||
* iterate directly the internal packed array (see `raw` and `size` member
|
||||
* functions for that). Use `begin` and `end` instead.
|
||||
*
|
||||
* @warning
|
||||
* Empty types aren't explicitly instantiated. Temporary objects are returned in
|
||||
* place of the instances of the components and raw access isn't available for
|
||||
* them.
|
||||
*
|
||||
* @sa sparse_set<Entity>
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Type Type of objects assigned to the entities.
|
||||
*/
|
||||
template<typename Entity, typename Type, typename = std::void_t<>>
|
||||
class basic_storage: public sparse_set<Entity> {
|
||||
using underlying_type = sparse_set<Entity>;
|
||||
using traits_type = entt_traits<std::underlying_type_t<Entity>>;
|
||||
|
||||
template<bool Const>
|
||||
class iterator {
|
||||
friend class basic_storage<Entity, Type>;
|
||||
|
||||
using instance_type = std::conditional_t<Const, const std::vector<Type>, std::vector<Type>>;
|
||||
using index_type = typename traits_type::difference_type;
|
||||
|
||||
iterator(instance_type *ref, const index_type idx) ENTT_NOEXCEPT
|
||||
: instances{ref}, index{idx}
|
||||
{}
|
||||
|
||||
public:
|
||||
using difference_type = index_type;
|
||||
using value_type = Type;
|
||||
using pointer = std::conditional_t<Const, const value_type *, value_type *>;
|
||||
using reference = std::conditional_t<Const, const value_type &, value_type &>;
|
||||
using iterator_category = std::random_access_iterator_tag;
|
||||
|
||||
iterator() ENTT_NOEXCEPT = default;
|
||||
|
||||
iterator & operator++() ENTT_NOEXCEPT {
|
||||
return --index, *this;
|
||||
}
|
||||
|
||||
iterator operator++(int) ENTT_NOEXCEPT {
|
||||
iterator orig = *this;
|
||||
return ++(*this), orig;
|
||||
}
|
||||
|
||||
iterator & operator--() ENTT_NOEXCEPT {
|
||||
return ++index, *this;
|
||||
}
|
||||
|
||||
iterator operator--(int) ENTT_NOEXCEPT {
|
||||
iterator orig = *this;
|
||||
return --(*this), orig;
|
||||
}
|
||||
|
||||
iterator & operator+=(const difference_type value) ENTT_NOEXCEPT {
|
||||
index -= value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
|
||||
return iterator{instances, index-value};
|
||||
}
|
||||
|
||||
iterator & operator-=(const difference_type value) ENTT_NOEXCEPT {
|
||||
return (*this += -value);
|
||||
}
|
||||
|
||||
iterator operator-(const difference_type value) const ENTT_NOEXCEPT {
|
||||
return (*this + -value);
|
||||
}
|
||||
|
||||
difference_type operator-(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return other.index - index;
|
||||
}
|
||||
|
||||
reference operator[](const difference_type value) const ENTT_NOEXCEPT {
|
||||
const auto pos = size_type(index-value-1);
|
||||
return (*instances)[pos];
|
||||
}
|
||||
|
||||
bool operator==(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return other.index == index;
|
||||
}
|
||||
|
||||
bool operator!=(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
bool operator<(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return index > other.index;
|
||||
}
|
||||
|
||||
bool operator>(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return index < other.index;
|
||||
}
|
||||
|
||||
bool operator<=(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this > other);
|
||||
}
|
||||
|
||||
bool operator>=(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this < other);
|
||||
}
|
||||
|
||||
pointer operator->() const ENTT_NOEXCEPT {
|
||||
const auto pos = size_type(index-1);
|
||||
return &(*instances)[pos];
|
||||
}
|
||||
|
||||
reference operator*() const ENTT_NOEXCEPT {
|
||||
return *operator->();
|
||||
}
|
||||
|
||||
private:
|
||||
instance_type *instances;
|
||||
index_type index;
|
||||
};
|
||||
|
||||
public:
|
||||
/*! @brief Type of the objects associated with the entities. */
|
||||
using object_type = Type;
|
||||
/*! @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_type = iterator<false>;
|
||||
/*! @brief Constant random access iterator type. */
|
||||
using const_iterator_type = iterator<true>;
|
||||
|
||||
/**
|
||||
* @brief Increases the capacity of a storage.
|
||||
*
|
||||
* If the new capacity is greater than the current capacity, new storage is
|
||||
* allocated, otherwise the method does nothing.
|
||||
*
|
||||
* @param cap Desired capacity.
|
||||
*/
|
||||
void reserve(const size_type cap) {
|
||||
underlying_type::reserve(cap);
|
||||
instances.reserve(cap);
|
||||
}
|
||||
|
||||
/*! @brief Requests the removal of unused capacity. */
|
||||
void shrink_to_fit() {
|
||||
underlying_type::shrink_to_fit();
|
||||
instances.shrink_to_fit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
* There are no guarantees on the order, even though either `sort` or
|
||||
* `respect` has been previously invoked. Internal data structures arrange
|
||||
* elements to maximize performance. Accessing them directly gives a
|
||||
* performance boost but less guarantees. Use `begin` and `end` if you want
|
||||
* to iterate the storage in the expected order.
|
||||
*
|
||||
* @return A pointer to the array of objects.
|
||||
*/
|
||||
const object_type * raw() const ENTT_NOEXCEPT {
|
||||
return instances.data();
|
||||
}
|
||||
|
||||
/*! @copydoc raw */
|
||||
object_type * raw() ENTT_NOEXCEPT {
|
||||
return const_cast<object_type *>(std::as_const(*this).raw());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the beginning.
|
||||
*
|
||||
* The returned iterator points to the first instance of the given type. If
|
||||
* the storage is empty, the returned iterator will be equal to `end()`.
|
||||
*
|
||||
* @note
|
||||
* Random access iterators stay true to the order imposed by a call to
|
||||
* either `sort` or `respect`.
|
||||
*
|
||||
* @return An iterator to the first instance of the given type.
|
||||
*/
|
||||
const_iterator_type cbegin() const ENTT_NOEXCEPT {
|
||||
const typename traits_type::difference_type pos = underlying_type::size();
|
||||
return const_iterator_type{&instances, pos};
|
||||
}
|
||||
|
||||
/*! @copydoc cbegin */
|
||||
const_iterator_type begin() const ENTT_NOEXCEPT {
|
||||
return cbegin();
|
||||
}
|
||||
|
||||
/*! @copydoc begin */
|
||||
iterator_type begin() ENTT_NOEXCEPT {
|
||||
const typename traits_type::difference_type pos = underlying_type::size();
|
||||
return iterator_type{&instances, pos};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the end.
|
||||
*
|
||||
* The returned iterator points to the element following the last instance
|
||||
* of the given type. Attempting to dereference the returned iterator
|
||||
* results in undefined behavior.
|
||||
*
|
||||
* @note
|
||||
* Random access iterators stay true to the order imposed by a call to
|
||||
* either `sort` or `respect`.
|
||||
*
|
||||
* @return An iterator to the element following the last instance of the
|
||||
* given type.
|
||||
*/
|
||||
const_iterator_type cend() const ENTT_NOEXCEPT {
|
||||
return const_iterator_type{&instances, {}};
|
||||
}
|
||||
|
||||
/*! @copydoc cend */
|
||||
const_iterator_type end() const ENTT_NOEXCEPT {
|
||||
return cend();
|
||||
}
|
||||
|
||||
/*! @copydoc end */
|
||||
iterator_type end() ENTT_NOEXCEPT {
|
||||
return iterator_type{&instances, {}};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the object associated with an entity.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that doesn't belong to the storage results in
|
||||
* undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* storage doesn't contain the given entity.
|
||||
*
|
||||
* @param entt A valid entity identifier.
|
||||
* @return The object associated with the entity.
|
||||
*/
|
||||
const object_type & get(const entity_type entt) const ENTT_NOEXCEPT {
|
||||
return instances[underlying_type::index(entt)];
|
||||
}
|
||||
|
||||
/*! @copydoc get */
|
||||
object_type & get(const entity_type entt) ENTT_NOEXCEPT {
|
||||
return const_cast<object_type &>(std::as_const(*this).get(entt));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a pointer to the object associated with an entity, if any.
|
||||
* @param entt A valid entity identifier.
|
||||
* @return The object associated with the entity, if any.
|
||||
*/
|
||||
const object_type * try_get(const entity_type entt) const ENTT_NOEXCEPT {
|
||||
return underlying_type::has(entt) ? (instances.data() + underlying_type::index(entt)) : nullptr;
|
||||
}
|
||||
|
||||
/*! @copydoc try_get */
|
||||
object_type * try_get(const entity_type entt) ENTT_NOEXCEPT {
|
||||
return const_cast<object_type *>(std::as_const(*this).try_get(entt));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns an entity to a storage and constructs its object.
|
||||
*
|
||||
* This version accept both types that can be constructed in place directly
|
||||
* and types like aggregates that do not work well with a placement new as
|
||||
* performed usually under the hood during an _emplace back_.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that already belongs to the storage results
|
||||
* in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* storage already contains the given entity.
|
||||
*
|
||||
* @tparam Args Types of arguments to use to construct the object.
|
||||
* @param entt A valid entity identifier.
|
||||
* @param args Parameters to use to construct an object for the entity.
|
||||
* @return The object associated with the entity.
|
||||
*/
|
||||
template<typename... Args>
|
||||
object_type & construct(const entity_type entt, Args &&... args) {
|
||||
if constexpr(std::is_aggregate_v<object_type>) {
|
||||
instances.emplace_back(Type{std::forward<Args>(args)...});
|
||||
} else {
|
||||
instances.emplace_back(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// entity goes after component in case constructor throws
|
||||
underlying_type::construct(entt);
|
||||
return instances.back();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns one or more entities to a storage and default constructs
|
||||
* their objects.
|
||||
*
|
||||
* The object type must be at least move and default insertable.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to assign an entity that already belongs to the storage
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* storage already contains the given entity.
|
||||
*
|
||||
* @tparam It Type of forward iterator.
|
||||
* @param first An iterator to the first element of the range of entities.
|
||||
* @param last An iterator past the last element of the range of entities.
|
||||
* @return An iterator to the list of instances just created and sorted the
|
||||
* same of the entities.
|
||||
*/
|
||||
template<typename It>
|
||||
iterator_type batch(It first, It last) {
|
||||
instances.resize(instances.size() + std::distance(first, last));
|
||||
// entity goes after component in case constructor throws
|
||||
underlying_type::batch(first, last);
|
||||
return begin();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns one or more entities to a storage and copy constructs
|
||||
* their objects.
|
||||
*
|
||||
* The object type must be at least move and copy insertable.
|
||||
*
|
||||
* @sa batch
|
||||
*
|
||||
* @tparam It Type of forward 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 value The value to initialize the new objects with.
|
||||
* @return An iterator to the list of instances just created and sorted the
|
||||
* same of the entities.
|
||||
*/
|
||||
template<typename It>
|
||||
iterator_type batch(It first, It last, const object_type &value) {
|
||||
instances.resize(instances.size() + std::distance(first, last), value);
|
||||
// entity goes after component in case constructor throws
|
||||
underlying_type::batch(first, last);
|
||||
return begin();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes an entity from a storage and destroys its object.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that doesn't belong to the storage results in
|
||||
* undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* storage doesn't contain the given entity.
|
||||
*
|
||||
* @param entt A valid entity identifier.
|
||||
*/
|
||||
void destroy(const entity_type entt) {
|
||||
auto other = std::move(instances.back());
|
||||
instances[underlying_type::index(entt)] = std::move(other);
|
||||
instances.pop_back();
|
||||
underlying_type::destroy(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Swaps entities and objects in the internal packed arrays.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to swap entities that don't belong to the sparse set results
|
||||
* in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* sparse set doesn't contain the given entities.
|
||||
*
|
||||
* @param lhs A valid position within the sparse set.
|
||||
* @param rhs A valid position within the sparse set.
|
||||
*/
|
||||
void swap(const size_type lhs, const size_type rhs) ENTT_NOEXCEPT override {
|
||||
ENTT_ASSERT(lhs < instances.size());
|
||||
ENTT_ASSERT(rhs < instances.size());
|
||||
std::swap(instances[lhs], instances[rhs]);
|
||||
underlying_type::swap(lhs, rhs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sort elements according to the given comparison function.
|
||||
*
|
||||
* Sort the elements so that iterating the range with a couple of iterators
|
||||
* returns them in the expected order. See `begin` and `end` for more
|
||||
* details.
|
||||
*
|
||||
* The comparison function object must return `true` if the first element
|
||||
* is _less_ than the second one, `false` otherwise. The signature of the
|
||||
* comparison function should be equivalent to one of the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* bool(const Entity, const Entity);
|
||||
* bool(const Type &, const Type &);
|
||||
* @endcode
|
||||
*
|
||||
* Moreover, the comparison function object shall induce a
|
||||
* _strict weak ordering_ on the values.
|
||||
*
|
||||
* The sort function oject must offer a member function template
|
||||
* `operator()` that accepts three arguments:
|
||||
*
|
||||
* * An iterator to the first element of the range to sort.
|
||||
* * An iterator past the last element of the range to sort.
|
||||
* * A comparison function to use to compare the elements.
|
||||
*
|
||||
* The comparison function object received by the sort function object
|
||||
* hasn't necessarily the type of the one passed along with the other
|
||||
* parameters to this member function.
|
||||
*
|
||||
* @note
|
||||
* Attempting to iterate elements using a raw pointer returned by a call to
|
||||
* either `data` or `raw` gives no guarantees on the order, even though
|
||||
* `sort` has been invoked.
|
||||
*
|
||||
* @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 first An iterator to the first element of the range to sort.
|
||||
* @param last An iterator past the last element of the range 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(iterator_type first, iterator_type last, Compare compare, Sort algo = Sort{}, Args &&... args) {
|
||||
ENTT_ASSERT(!(first > last));
|
||||
|
||||
const auto from = underlying_type::begin() + std::distance(begin(), first);
|
||||
const auto to = from + std::distance(first, last);
|
||||
|
||||
if constexpr(std::is_invocable_v<Compare, const object_type &, const object_type &>) {
|
||||
static_assert(!std::is_empty_v<object_type>);
|
||||
|
||||
underlying_type::sort(from, to, [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)]));
|
||||
}, std::move(algo), std::forward<Args>(args)...);
|
||||
} else {
|
||||
underlying_type::sort(from, to, std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
/*! @brief Resets a storage. */
|
||||
void reset() {
|
||||
underlying_type::reset();
|
||||
instances.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<object_type> instances;
|
||||
};
|
||||
|
||||
|
||||
/*! @copydoc basic_storage */
|
||||
template<typename Entity, typename Type>
|
||||
class basic_storage<Entity, Type, std::enable_if_t<std::is_empty_v<Type>>>: public sparse_set<Entity> {
|
||||
using traits_type = entt_traits<std::underlying_type_t<Entity>>;
|
||||
using underlying_type = sparse_set<Entity>;
|
||||
|
||||
class iterator {
|
||||
friend class basic_storage<Entity, Type>;
|
||||
|
||||
using index_type = typename traits_type::difference_type;
|
||||
|
||||
iterator(const index_type idx) ENTT_NOEXCEPT
|
||||
: index{idx}
|
||||
{}
|
||||
|
||||
public:
|
||||
using difference_type = index_type;
|
||||
using value_type = Type;
|
||||
using pointer = const value_type *;
|
||||
using reference = value_type;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
|
||||
iterator() ENTT_NOEXCEPT = default;
|
||||
|
||||
iterator & operator++() ENTT_NOEXCEPT {
|
||||
return --index, *this;
|
||||
}
|
||||
|
||||
iterator operator++(int) ENTT_NOEXCEPT {
|
||||
iterator orig = *this;
|
||||
return ++(*this), orig;
|
||||
}
|
||||
|
||||
iterator & operator--() ENTT_NOEXCEPT {
|
||||
return ++index, *this;
|
||||
}
|
||||
|
||||
iterator operator--(int) ENTT_NOEXCEPT {
|
||||
iterator orig = *this;
|
||||
return --(*this), orig;
|
||||
}
|
||||
|
||||
iterator & operator+=(const difference_type value) ENTT_NOEXCEPT {
|
||||
index -= value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
|
||||
return iterator{index-value};
|
||||
}
|
||||
|
||||
iterator & operator-=(const difference_type value) ENTT_NOEXCEPT {
|
||||
return (*this += -value);
|
||||
}
|
||||
|
||||
iterator operator-(const difference_type value) const ENTT_NOEXCEPT {
|
||||
return (*this + -value);
|
||||
}
|
||||
|
||||
difference_type operator-(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return other.index - index;
|
||||
}
|
||||
|
||||
reference operator[](const difference_type) const ENTT_NOEXCEPT {
|
||||
return {};
|
||||
}
|
||||
|
||||
bool operator==(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return other.index == index;
|
||||
}
|
||||
|
||||
bool operator!=(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
bool operator<(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return index > other.index;
|
||||
}
|
||||
|
||||
bool operator>(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return index < other.index;
|
||||
}
|
||||
|
||||
bool operator<=(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this > other);
|
||||
}
|
||||
|
||||
bool operator>=(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this < other);
|
||||
}
|
||||
|
||||
pointer operator->() const ENTT_NOEXCEPT {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
reference operator*() const ENTT_NOEXCEPT {
|
||||
return {};
|
||||
}
|
||||
|
||||
private:
|
||||
index_type index;
|
||||
};
|
||||
|
||||
public:
|
||||
/*! @brief Type of the objects associated with the entities. */
|
||||
using object_type = Type;
|
||||
/*! @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_type = iterator;
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the beginning.
|
||||
*
|
||||
* The returned iterator points to the first instance of the given type. If
|
||||
* the storage is empty, the returned iterator will be equal to `end()`.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed by a call to either `sort`
|
||||
* or `respect`.
|
||||
*
|
||||
* @return An iterator to the first instance of the given type.
|
||||
*/
|
||||
iterator_type cbegin() const ENTT_NOEXCEPT {
|
||||
const typename traits_type::difference_type pos = underlying_type::size();
|
||||
return iterator_type{pos};
|
||||
}
|
||||
|
||||
/*! @copydoc cbegin */
|
||||
iterator_type begin() const ENTT_NOEXCEPT {
|
||||
return cbegin();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the end.
|
||||
*
|
||||
* The returned iterator points to the element following the last instance
|
||||
* of the given type. Attempting to dereference the returned iterator
|
||||
* results in undefined behavior.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed by a call to either `sort`
|
||||
* or `respect`.
|
||||
*
|
||||
* @return An iterator to the element following the last instance of the
|
||||
* given type.
|
||||
*/
|
||||
iterator_type cend() const ENTT_NOEXCEPT {
|
||||
return iterator_type{};
|
||||
}
|
||||
|
||||
/*! @copydoc cend */
|
||||
iterator_type end() const ENTT_NOEXCEPT {
|
||||
return cend();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the object associated with an entity.
|
||||
*
|
||||
* @note
|
||||
* Empty types aren't explicitly instantiated. Therefore, this function
|
||||
* always returns a temporary object.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that doesn't belong to the storage results in
|
||||
* undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* storage doesn't contain the given entity.
|
||||
*
|
||||
* @param entt A valid entity identifier.
|
||||
* @return The object associated with the entity.
|
||||
*/
|
||||
object_type get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT {
|
||||
ENTT_ASSERT(underlying_type::has(entt));
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns one or more entities to a storage.
|
||||
*
|
||||
* The object type must be at least default constructible.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to assign an entity that already belongs to the storage
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* storage already contains the given entity.
|
||||
*
|
||||
* @tparam It Type of forward iterator.
|
||||
* @param first An iterator to the first element of the range of entities.
|
||||
* @param last An iterator past the last element of the range of entities.
|
||||
* @return An iterator to the list of instances just created and sorted the
|
||||
* same of the entities.
|
||||
*/
|
||||
template<typename It>
|
||||
iterator_type batch(It first, It last, const object_type & = {}) {
|
||||
underlying_type::batch(first, last);
|
||||
return begin();
|
||||
}
|
||||
};
|
||||
|
||||
/*! @copydoc basic_storage */
|
||||
template<typename Entity, typename Type>
|
||||
struct storage: basic_storage<Entity, Type> {};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_STORAGE_HPP
|
||||
@@ -2,23 +2,42 @@
|
||||
#define ENTT_ENTITY_UTILITY_HPP
|
||||
|
||||
|
||||
#include "../core/type_traits.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/*! @brief Tag class type used to disambiguate overloads. */
|
||||
struct tag_t final {};
|
||||
/**
|
||||
* @brief Alias for exclusion lists.
|
||||
* @tparam Type List of types.
|
||||
*/
|
||||
template<typename... Type>
|
||||
struct exclude_t: type_list<Type...> {};
|
||||
|
||||
|
||||
/*! @brief Persistent view type used to disambiguate overloads. */
|
||||
struct persistent_t final {};
|
||||
/**
|
||||
* @brief Variable template for exclusion lists.
|
||||
* @tparam Type List of types.
|
||||
*/
|
||||
template<typename... Type>
|
||||
constexpr exclude_t<Type...> exclude{};
|
||||
|
||||
|
||||
/*! @brief Raw view type used to disambiguate overloads. */
|
||||
struct raw_t final {};
|
||||
/**
|
||||
* @brief Alias for lists of observed components.
|
||||
* @tparam Type List of types.
|
||||
*/
|
||||
template<typename... Type>
|
||||
struct get_t: type_list<Type...>{};
|
||||
|
||||
|
||||
/*! @brief Break type used to disambiguate overloads. */
|
||||
struct break_t final {};
|
||||
/**
|
||||
* @brief Variable template for lists of observed components.
|
||||
* @tparam Type List of types.
|
||||
*/
|
||||
template<typename... Type>
|
||||
constexpr get_t<Type...> get{};
|
||||
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,17 +3,24 @@
|
||||
#include "core/hashed_string.hpp"
|
||||
#include "core/ident.hpp"
|
||||
#include "core/monostate.hpp"
|
||||
#include "core/type_traits.hpp"
|
||||
#include "core/utility.hpp"
|
||||
#include "entity/actor.hpp"
|
||||
#include "entity/entity.hpp"
|
||||
#include "entity/entt_traits.hpp"
|
||||
#include "entity/group.hpp"
|
||||
#include "entity/helper.hpp"
|
||||
#include "entity/prototype.hpp"
|
||||
#include "entity/observer.hpp"
|
||||
#include "entity/registry.hpp"
|
||||
#include "entity/runtime_view.hpp"
|
||||
#include "entity/snapshot.hpp"
|
||||
#include "entity/sparse_set.hpp"
|
||||
#include "entity/storage.hpp"
|
||||
#include "entity/utility.hpp"
|
||||
#include "entity/view.hpp"
|
||||
#include "locator/locator.hpp"
|
||||
#include "meta/factory.hpp"
|
||||
#include "meta/meta.hpp"
|
||||
#include "meta/policy.hpp"
|
||||
#include "process/process.hpp"
|
||||
#include "process/scheduler.hpp"
|
||||
#include "resource/cache.hpp"
|
||||
|
||||
3
src/entt/fwd.hpp
Normal file
3
src/entt/fwd.hpp
Normal file
@@ -0,0 +1,3 @@
|
||||
#include "entity/fwd.hpp"
|
||||
#include "resource/fwd.hpp"
|
||||
#include "signal/fwd.hpp"
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <cassert>
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
@@ -23,20 +22,20 @@ namespace entt {
|
||||
* @tparam Service Type of service managed by the locator.
|
||||
*/
|
||||
template<typename Service>
|
||||
struct ServiceLocator final {
|
||||
struct service_locator {
|
||||
/*! @brief Type of service offered. */
|
||||
using service_type = Service;
|
||||
|
||||
/*! @brief Default constructor, deleted on purpose. */
|
||||
ServiceLocator() = delete;
|
||||
service_locator() = delete;
|
||||
/*! @brief Default destructor, deleted on purpose. */
|
||||
~ServiceLocator() = delete;
|
||||
~service_locator() = delete;
|
||||
|
||||
/**
|
||||
* @brief Tests if a valid service implementation is set.
|
||||
* @return True if the service is set, false otherwise.
|
||||
*/
|
||||
inline static bool empty() ENTT_NOEXCEPT {
|
||||
static bool empty() ENTT_NOEXCEPT {
|
||||
return !static_cast<bool>(service);
|
||||
}
|
||||
|
||||
@@ -50,7 +49,7 @@ struct ServiceLocator final {
|
||||
*
|
||||
* @return A reference to the service implementation currently set, if any.
|
||||
*/
|
||||
inline static std::weak_ptr<Service> get() ENTT_NOEXCEPT {
|
||||
static std::weak_ptr<Service> get() ENTT_NOEXCEPT {
|
||||
return service;
|
||||
}
|
||||
|
||||
@@ -68,7 +67,7 @@ struct ServiceLocator final {
|
||||
*
|
||||
* @return A reference to the service implementation currently set, if any.
|
||||
*/
|
||||
inline static Service & ref() ENTT_NOEXCEPT {
|
||||
static Service & ref() ENTT_NOEXCEPT {
|
||||
return *service;
|
||||
}
|
||||
|
||||
@@ -79,7 +78,7 @@ struct ServiceLocator final {
|
||||
* @param args Parameters to use to construct the service.
|
||||
*/
|
||||
template<typename Impl = Service, typename... Args>
|
||||
inline static void set(Args &&... args) {
|
||||
static void set(Args &&... args) {
|
||||
service = std::make_shared<Impl>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
@@ -87,8 +86,8 @@ struct ServiceLocator final {
|
||||
* @brief Sets or replaces a service.
|
||||
* @param ptr Service to use to replace the current one.
|
||||
*/
|
||||
inline static void set(std::shared_ptr<Service> ptr) {
|
||||
assert(static_cast<bool>(ptr));
|
||||
static void set(std::shared_ptr<Service> ptr) {
|
||||
ENTT_ASSERT(static_cast<bool>(ptr));
|
||||
service = std::move(ptr);
|
||||
}
|
||||
|
||||
@@ -97,19 +96,15 @@ struct ServiceLocator final {
|
||||
*
|
||||
* The service is no longer valid after a reset.
|
||||
*/
|
||||
inline static void reset() {
|
||||
static void reset() {
|
||||
service.reset();
|
||||
}
|
||||
|
||||
private:
|
||||
static std::shared_ptr<Service> service;
|
||||
inline static std::shared_ptr<Service> service = nullptr;
|
||||
};
|
||||
|
||||
|
||||
template<typename Service>
|
||||
std::shared_ptr<Service> ServiceLocator<Service>::service{};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
928
src/entt/meta/factory.hpp
Normal file
928
src/entt/meta/factory.hpp
Normal file
@@ -0,0 +1,928 @@
|
||||
#ifndef ENTT_META_FACTORY_HPP
|
||||
#define ENTT_META_FACTORY_HPP
|
||||
|
||||
|
||||
#include <tuple>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "policy.hpp"
|
||||
#include "meta.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @cond TURN_OFF_DOXYGEN
|
||||
* Internal details not to be documented.
|
||||
*/
|
||||
|
||||
|
||||
namespace internal {
|
||||
|
||||
|
||||
template<typename>
|
||||
struct meta_function_helper;
|
||||
|
||||
|
||||
template<typename Ret, typename... Args>
|
||||
struct meta_function_helper<Ret(Args...)> {
|
||||
using return_type = std::remove_cv_t<std::remove_reference_t<Ret>>;
|
||||
using args_type = std::tuple<std::remove_cv_t<std::remove_reference_t<Args>>...>;
|
||||
|
||||
static constexpr auto size = sizeof...(Args);
|
||||
static constexpr auto is_const = false;
|
||||
|
||||
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 Ret, typename... Args>
|
||||
struct meta_function_helper<Ret(Args...) const>: meta_function_helper<Ret(Args...)> {
|
||||
static constexpr auto is_const = true;
|
||||
};
|
||||
|
||||
|
||||
template<typename Ret, typename... Args, typename Class>
|
||||
constexpr meta_function_helper<Ret(Args...)>
|
||||
to_meta_function_helper(Ret(Class:: *)(Args...));
|
||||
|
||||
|
||||
template<typename Ret, typename... Args, typename Class>
|
||||
constexpr meta_function_helper<Ret(Args...) const>
|
||||
to_meta_function_helper(Ret(Class:: *)(Args...) const);
|
||||
|
||||
|
||||
template<typename Ret, typename... Args>
|
||||
constexpr meta_function_helper<Ret(Args...)>
|
||||
to_meta_function_helper(Ret(*)(Args...));
|
||||
|
||||
|
||||
template<typename Candidate>
|
||||
using meta_function_helper_t = decltype(to_meta_function_helper(std::declval<Candidate>()));
|
||||
|
||||
|
||||
template<typename Type, typename... Args, std::size_t... Indexes>
|
||||
meta_any construct(meta_any * const args, std::index_sequence<Indexes...>) {
|
||||
[[maybe_unused]] auto direct = std::make_tuple((args+Indexes)->try_cast<Args>()...);
|
||||
meta_any any{};
|
||||
|
||||
if(((std::get<Indexes>(direct) || (args+Indexes)->convert<Args>()) && ...)) {
|
||||
any = Type{(std::get<Indexes>(direct) ? *std::get<Indexes>(direct) : (args+Indexes)->cast<Args>())...};
|
||||
}
|
||||
|
||||
return any;
|
||||
}
|
||||
|
||||
|
||||
template<bool Const, typename Type, auto Data>
|
||||
bool setter([[maybe_unused]] meta_handle handle, [[maybe_unused]] meta_any index, [[maybe_unused]] meta_any value) {
|
||||
bool accepted = false;
|
||||
|
||||
if constexpr(!Const) {
|
||||
if constexpr(std::is_function_v<std::remove_pointer_t<decltype(Data)>> || std::is_member_function_pointer_v<decltype(Data)>) {
|
||||
using helper_type = meta_function_helper_t<decltype(Data)>;
|
||||
using data_type = std::tuple_element_t<!std::is_member_function_pointer_v<decltype(Data)>, typename helper_type::args_type>;
|
||||
static_assert(std::is_invocable_v<decltype(Data), Type &, data_type>);
|
||||
auto *direct = value.try_cast<data_type>();
|
||||
auto *clazz = handle.data<Type>();
|
||||
|
||||
if(clazz && (direct || value.convert<data_type>())) {
|
||||
std::invoke(Data, *clazz, direct ? *direct : value.cast<data_type>());
|
||||
accepted = 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)>>;
|
||||
static_assert(std::is_invocable_v<decltype(Data), Type *>);
|
||||
auto *clazz = handle.data<Type>();
|
||||
|
||||
if constexpr(std::is_array_v<data_type>) {
|
||||
using underlying_type = std::remove_extent_t<data_type>;
|
||||
auto *direct = value.try_cast<underlying_type>();
|
||||
auto *idx = index.try_cast<std::size_t>();
|
||||
|
||||
if(clazz && idx && (direct || value.convert<underlying_type>())) {
|
||||
std::invoke(Data, clazz)[*idx] = direct ? *direct : value.cast<underlying_type>();
|
||||
accepted = true;
|
||||
}
|
||||
} else {
|
||||
auto *direct = value.try_cast<data_type>();
|
||||
|
||||
if(clazz && (direct || value.convert<data_type>())) {
|
||||
std::invoke(Data, clazz) = (direct ? *direct : value.cast<data_type>());
|
||||
accepted = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
static_assert(std::is_pointer_v<decltype(Data)>);
|
||||
using data_type = std::remove_cv_t<std::remove_reference_t<decltype(*Data)>>;
|
||||
|
||||
if constexpr(std::is_array_v<data_type>) {
|
||||
using underlying_type = std::remove_extent_t<data_type>;
|
||||
auto *direct = value.try_cast<underlying_type>();
|
||||
auto *idx = index.try_cast<std::size_t>();
|
||||
|
||||
if(idx && (direct || value.convert<underlying_type>())) {
|
||||
(*Data)[*idx] = (direct ? *direct : value.cast<underlying_type>());
|
||||
accepted = true;
|
||||
}
|
||||
} else {
|
||||
auto *direct = value.try_cast<data_type>();
|
||||
|
||||
if(direct || value.convert<data_type>()) {
|
||||
*Data = (direct ? *direct : value.cast<data_type>());
|
||||
accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return accepted;
|
||||
}
|
||||
|
||||
|
||||
template<typename Type, auto Data, typename Policy>
|
||||
meta_any getter([[maybe_unused]] meta_handle handle, [[maybe_unused]] meta_any index) {
|
||||
auto dispatch = [](auto &&value) {
|
||||
if constexpr(std::is_same_v<Policy, as_void_t>) {
|
||||
return meta_any{std::in_place_type<void>};
|
||||
} else if constexpr(std::is_same_v<Policy, as_alias_t>) {
|
||||
return meta_any{as_alias, std::forward<decltype(value)>(value)};
|
||||
} else {
|
||||
static_assert(std::is_same_v<Policy, as_is_t>);
|
||||
return meta_any{std::forward<decltype(value)>(value)};
|
||||
}
|
||||
};
|
||||
|
||||
if constexpr(std::is_function_v<std::remove_pointer_t<decltype(Data)>> || std::is_member_function_pointer_v<decltype(Data)>) {
|
||||
static_assert(std::is_invocable_v<decltype(Data), Type &>);
|
||||
auto *clazz = handle.data<Type>();
|
||||
return clazz ? dispatch(std::invoke(Data, *clazz)) : meta_any{};
|
||||
} 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)>>;
|
||||
static_assert(std::is_invocable_v<decltype(Data), Type *>);
|
||||
auto *clazz = handle.data<Type>();
|
||||
|
||||
if constexpr(std::is_array_v<data_type>) {
|
||||
auto *idx = index.try_cast<std::size_t>();
|
||||
return (clazz && idx) ? dispatch(std::invoke(Data, clazz)[*idx]) : meta_any{};
|
||||
} else {
|
||||
return clazz ? dispatch(std::invoke(Data, clazz)) : meta_any{};
|
||||
}
|
||||
} else {
|
||||
static_assert(std::is_pointer_v<std::decay_t<decltype(Data)>>);
|
||||
|
||||
if constexpr(std::is_array_v<std::remove_pointer_t<decltype(Data)>>) {
|
||||
auto *idx = index.try_cast<std::size_t>();
|
||||
return idx ? dispatch((*Data)[*idx]) : meta_any{};
|
||||
} else {
|
||||
return dispatch(*Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<typename Type, auto Candidate, typename Policy, std::size_t... Indexes>
|
||||
meta_any invoke([[maybe_unused]] meta_handle handle, meta_any *args, std::index_sequence<Indexes...>) {
|
||||
using helper_type = meta_function_helper_t<decltype(Candidate)>;
|
||||
|
||||
auto dispatch = [](auto *... args) {
|
||||
if constexpr(std::is_void_v<typename helper_type::return_type> || std::is_same_v<Policy, as_void_t>) {
|
||||
std::invoke(Candidate, *args...);
|
||||
return meta_any{std::in_place_type<void>};
|
||||
} else if constexpr(std::is_same_v<Policy, as_alias_t>) {
|
||||
return meta_any{as_alias, std::invoke(Candidate, *args...)};
|
||||
} else {
|
||||
static_assert(std::is_same_v<Policy, as_is_t>);
|
||||
return meta_any{std::invoke(Candidate, *args...)};
|
||||
}
|
||||
};
|
||||
|
||||
[[maybe_unused]] const auto direct = std::make_tuple([](meta_any *any, auto *instance) {
|
||||
using arg_type = std::remove_reference_t<decltype(*instance)>;
|
||||
|
||||
if(!instance && any->convert<arg_type>()) {
|
||||
instance = any->try_cast<arg_type>();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}(args+Indexes, (args+Indexes)->try_cast<std::tuple_element_t<Indexes, typename helper_type::args_type>>())...);
|
||||
|
||||
if constexpr(std::is_function_v<std::remove_pointer_t<decltype(Candidate)>>) {
|
||||
return (std::get<Indexes>(direct) && ...) ? dispatch(std::get<Indexes>(direct)...) : meta_any{};
|
||||
} else {
|
||||
auto *clazz = handle.data<Type>();
|
||||
return (clazz && (std::get<Indexes>(direct) && ...)) ? dispatch(clazz, std::get<Indexes>(direct)...) : meta_any{};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Internal details not to be documented.
|
||||
* @endcond TURN_OFF_DOXYGEN
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @brief A meta factory to be used for reflection purposes.
|
||||
*
|
||||
* A meta factory is an utility class used to reflect types, data and functions
|
||||
* of all sorts. This class ensures that the underlying web of types is built
|
||||
* correctly and performs some checks in debug mode to ensure that there are no
|
||||
* subtle errors at runtime.
|
||||
*
|
||||
* @tparam Type Reflected type for which the factory was created.
|
||||
*/
|
||||
template<typename Type>
|
||||
class meta_factory {
|
||||
static_assert(std::is_same_v<Type, std::decay_t<Type>>);
|
||||
|
||||
template<typename Node>
|
||||
bool duplicate(const ENTT_ID_TYPE identifier, const Node *node) ENTT_NOEXCEPT {
|
||||
return node ? node->identifier == identifier || duplicate(identifier, node->next) : false;
|
||||
}
|
||||
|
||||
bool duplicate(const meta_any &key, const internal::meta_prop_node *node) ENTT_NOEXCEPT {
|
||||
return node ? node->key() == key || duplicate(key, node->next) : false;
|
||||
}
|
||||
|
||||
template<typename>
|
||||
internal::meta_prop_node * properties() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template<typename Owner, typename Property, typename... Other>
|
||||
internal::meta_prop_node * properties(Property &&property, Other &&... other) {
|
||||
static std::remove_cv_t<std::remove_reference_t<Property>> prop{};
|
||||
|
||||
static internal::meta_prop_node node{
|
||||
nullptr,
|
||||
[]() -> meta_any {
|
||||
return std::as_const(std::get<0>(prop));
|
||||
},
|
||||
[]() -> meta_any {
|
||||
return std::as_const(std::get<1>(prop));
|
||||
},
|
||||
[]() ENTT_NOEXCEPT -> meta_prop {
|
||||
return &node;
|
||||
}
|
||||
};
|
||||
|
||||
prop = std::forward<Property>(property);
|
||||
node.next = properties<Owner>(std::forward<Other>(other)...);
|
||||
ENTT_ASSERT(!duplicate(meta_any{std::get<0>(prop)}, node.next));
|
||||
return &node;
|
||||
}
|
||||
|
||||
void unregister_prop(internal::meta_prop_node **prop) {
|
||||
while(*prop) {
|
||||
auto *node = *prop;
|
||||
*prop = node->next;
|
||||
node->next = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void unregister_dtor() {
|
||||
if(auto node = internal::meta_info<Type>::type->dtor; node) {
|
||||
internal::meta_info<Type>::type->dtor = nullptr;
|
||||
*node->underlying = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template<auto Member>
|
||||
auto unregister_all(int)
|
||||
-> decltype((internal::meta_info<Type>::type->*Member)->prop, void()) {
|
||||
while(internal::meta_info<Type>::type->*Member) {
|
||||
auto node = internal::meta_info<Type>::type->*Member;
|
||||
internal::meta_info<Type>::type->*Member = node->next;
|
||||
unregister_prop(&node->prop);
|
||||
node->next = nullptr;
|
||||
*node->underlying = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template<auto Member>
|
||||
void unregister_all(char) {
|
||||
while(internal::meta_info<Type>::type->*Member) {
|
||||
auto node = internal::meta_info<Type>::type->*Member;
|
||||
internal::meta_info<Type>::type->*Member = node->next;
|
||||
node->next = nullptr;
|
||||
*node->underlying = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Default constructor. */
|
||||
meta_factory() ENTT_NOEXCEPT = default;
|
||||
|
||||
/**
|
||||
* @brief Extends a meta type by assigning it an identifier and properties.
|
||||
* @tparam Property Types of properties to assign to the meta type.
|
||||
* @param identifier Unique identifier.
|
||||
* @param property Properties to assign to the meta type.
|
||||
* @return A meta factory for the parent type.
|
||||
*/
|
||||
template<typename... Property>
|
||||
meta_factory type(const ENTT_ID_TYPE identifier, Property &&... property) ENTT_NOEXCEPT {
|
||||
ENTT_ASSERT(!internal::meta_info<Type>::type);
|
||||
auto *node = internal::meta_info<Type>::resolve();
|
||||
node->identifier = identifier;
|
||||
node->next = internal::meta_info<>::type;
|
||||
node->prop = properties<Type>(std::forward<Property>(property)...);
|
||||
ENTT_ASSERT(!duplicate(identifier, node->next));
|
||||
internal::meta_info<Type>::type = node;
|
||||
internal::meta_info<>::type = node;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns a meta base to a meta type.
|
||||
*
|
||||
* A reflected base class must be a real base class of the reflected type.
|
||||
*
|
||||
* @tparam Base Type of the base class to assign to the meta type.
|
||||
* @return A meta factory for the parent type.
|
||||
*/
|
||||
template<typename Base>
|
||||
meta_factory base() ENTT_NOEXCEPT {
|
||||
static_assert(std::is_base_of_v<Base, Type>);
|
||||
auto * const type = internal::meta_info<Type>::resolve();
|
||||
|
||||
static internal::meta_base_node node{
|
||||
&internal::meta_info<Type>::template base<Base>,
|
||||
type,
|
||||
nullptr,
|
||||
&internal::meta_info<Base>::resolve,
|
||||
[](void *instance) ENTT_NOEXCEPT -> void * {
|
||||
return static_cast<Base *>(static_cast<Type *>(instance));
|
||||
},
|
||||
[]() ENTT_NOEXCEPT -> meta_base {
|
||||
return &node;
|
||||
}
|
||||
};
|
||||
|
||||
node.next = type->base;
|
||||
ENTT_ASSERT((!internal::meta_info<Type>::template base<Base>));
|
||||
internal::meta_info<Type>::template base<Base> = &node;
|
||||
type->base = &node;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns a meta conversion function to a meta type.
|
||||
*
|
||||
* The given type must be such that an instance of the reflected type can be
|
||||
* converted to it.
|
||||
*
|
||||
* @tparam To Type of the conversion function to assign to the meta type.
|
||||
* @return A meta factory for the parent type.
|
||||
*/
|
||||
template<typename To>
|
||||
meta_factory conv() ENTT_NOEXCEPT {
|
||||
static_assert(std::is_convertible_v<Type, To>);
|
||||
auto * const type = internal::meta_info<Type>::resolve();
|
||||
|
||||
static internal::meta_conv_node node{
|
||||
&internal::meta_info<Type>::template conv<To>,
|
||||
type,
|
||||
nullptr,
|
||||
&internal::meta_info<To>::resolve,
|
||||
[](const void *instance) -> meta_any {
|
||||
return static_cast<To>(*static_cast<const Type *>(instance));
|
||||
},
|
||||
[]() ENTT_NOEXCEPT -> meta_conv {
|
||||
return &node;
|
||||
}
|
||||
};
|
||||
|
||||
node.next = type->conv;
|
||||
ENTT_ASSERT((!internal::meta_info<Type>::template conv<To>));
|
||||
internal::meta_info<Type>::template conv<To> = &node;
|
||||
type->conv = &node;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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>
|
||||
meta_factory 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{
|
||||
&internal::meta_info<Type>::template conv<conv_type>,
|
||||
type,
|
||||
nullptr,
|
||||
&internal::meta_info<conv_type>::resolve,
|
||||
[](const void *instance) -> meta_any {
|
||||
return std::invoke(Candidate, *static_cast<const Type *>(instance));
|
||||
},
|
||||
[]() ENTT_NOEXCEPT -> meta_conv {
|
||||
return &node;
|
||||
}
|
||||
};
|
||||
|
||||
node.next = type->conv;
|
||||
ENTT_ASSERT((!internal::meta_info<Type>::template conv<conv_type>));
|
||||
internal::meta_info<Type>::template conv<conv_type> = &node;
|
||||
type->conv = &node;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns a meta constructor to a meta type.
|
||||
*
|
||||
* Free functions can be assigned to meta types in the role of constructors.
|
||||
* All that is required is that they return an instance of the underlying
|
||||
* type.<br/>
|
||||
* From a client's point of view, nothing changes if a constructor of a meta
|
||||
* type is a built-in one or a free function.
|
||||
*
|
||||
* @tparam Func The actual function to use as a constructor.
|
||||
* @tparam Policy Optional policy (no policy set by default).
|
||||
* @tparam Property Types of properties to assign to the meta data.
|
||||
* @param property Properties to assign to the meta data.
|
||||
* @return A meta factory for the parent type.
|
||||
*/
|
||||
template<auto Func, typename Policy = as_is_t, typename... Property>
|
||||
meta_factory ctor(Property &&... property) ENTT_NOEXCEPT {
|
||||
using helper_type = internal::meta_function_helper_t<decltype(Func)>;
|
||||
static_assert(std::is_same_v<typename helper_type::return_type, Type>);
|
||||
auto * const type = internal::meta_info<Type>::resolve();
|
||||
|
||||
static internal::meta_ctor_node node{
|
||||
&internal::meta_info<Type>::template ctor<typename helper_type::args_type>,
|
||||
type,
|
||||
nullptr,
|
||||
nullptr,
|
||||
helper_type::size,
|
||||
&helper_type::arg,
|
||||
[](meta_any * const any) {
|
||||
return internal::invoke<Type, Func, Policy>({}, any, std::make_index_sequence<helper_type::size>{});
|
||||
},
|
||||
[]() ENTT_NOEXCEPT -> meta_ctor {
|
||||
return &node;
|
||||
}
|
||||
};
|
||||
|
||||
node.next = type->ctor;
|
||||
node.prop = properties<typename helper_type::args_type>(std::forward<Property>(property)...);
|
||||
ENTT_ASSERT((!internal::meta_info<Type>::template ctor<typename helper_type::args_type>));
|
||||
internal::meta_info<Type>::template ctor<typename helper_type::args_type> = &node;
|
||||
type->ctor = &node;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns a meta constructor to a meta type.
|
||||
*
|
||||
* A meta constructor is uniquely identified by the types of its arguments
|
||||
* and is such that there exists an actual constructor of the underlying
|
||||
* type that can be invoked with parameters whose types are those given.
|
||||
*
|
||||
* @tparam Args Types of arguments to use to construct an instance.
|
||||
* @tparam Property Types of properties to assign to the meta data.
|
||||
* @param property Properties to assign to the meta data.
|
||||
* @return A meta factory for the parent type.
|
||||
*/
|
||||
template<typename... Args, typename... Property>
|
||||
meta_factory ctor(Property &&... property) ENTT_NOEXCEPT {
|
||||
using helper_type = internal::meta_function_helper_t<Type(*)(Args...)>;
|
||||
auto * const type = internal::meta_info<Type>::resolve();
|
||||
|
||||
static internal::meta_ctor_node node{
|
||||
&internal::meta_info<Type>::template ctor<typename helper_type::args_type>,
|
||||
type,
|
||||
nullptr,
|
||||
nullptr,
|
||||
helper_type::size,
|
||||
&helper_type::arg,
|
||||
[](meta_any * const any) {
|
||||
return internal::construct<Type, std::remove_cv_t<std::remove_reference_t<Args>>...>(any, std::make_index_sequence<helper_type::size>{});
|
||||
},
|
||||
[]() ENTT_NOEXCEPT -> meta_ctor {
|
||||
return &node;
|
||||
}
|
||||
};
|
||||
|
||||
node.next = type->ctor;
|
||||
node.prop = properties<typename helper_type::args_type>(std::forward<Property>(property)...);
|
||||
ENTT_ASSERT((!internal::meta_info<Type>::template ctor<typename helper_type::args_type>));
|
||||
internal::meta_info<Type>::template ctor<typename helper_type::args_type> = &node;
|
||||
type->ctor = &node;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns a meta destructor to a meta type.
|
||||
*
|
||||
* Free functions can be assigned to meta types in the role of destructors.
|
||||
* The signature of the function should identical to the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(Type &);
|
||||
* @endcode
|
||||
*
|
||||
* The purpose is to give users the ability to free up resources that
|
||||
* require special treatment before an object is actually destroyed.
|
||||
*
|
||||
* @tparam Func The actual function to use as a destructor.
|
||||
* @return A meta factory for the parent type.
|
||||
*/
|
||||
template<auto Func>
|
||||
meta_factory dtor() ENTT_NOEXCEPT {
|
||||
static_assert(std::is_invocable_v<decltype(Func), Type &>);
|
||||
auto * const type = internal::meta_info<Type>::resolve();
|
||||
|
||||
static internal::meta_dtor_node node{
|
||||
&internal::meta_info<Type>::template dtor<Func>,
|
||||
type,
|
||||
[](meta_handle handle) {
|
||||
return handle.type() == internal::meta_info<Type>::resolve()->meta()
|
||||
? (std::invoke(Func, *handle.data<Type>()), true)
|
||||
: false;
|
||||
},
|
||||
[]() ENTT_NOEXCEPT -> meta_dtor {
|
||||
return &node;
|
||||
}
|
||||
};
|
||||
|
||||
ENTT_ASSERT(!internal::meta_info<Type>::type->dtor);
|
||||
ENTT_ASSERT((!internal::meta_info<Type>::template dtor<Func>));
|
||||
internal::meta_info<Type>::template dtor<Func> = &node;
|
||||
internal::meta_info<Type>::type->dtor = &node;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns a meta data to a meta type.
|
||||
*
|
||||
* Both data members and static and global variables, as well as constants
|
||||
* of any kind, can be assigned to a meta type.<br/>
|
||||
* From a client's point of view, all the variables associated with the
|
||||
* reflected object will appear as if they were part of the type itself.
|
||||
*
|
||||
* @tparam Data The actual variable to attach to the meta type.
|
||||
* @tparam Policy Optional policy (no policy set by default).
|
||||
* @tparam Property Types of properties to assign to the meta data.
|
||||
* @param identifier Unique identifier.
|
||||
* @param property Properties to assign to the meta data.
|
||||
* @return A meta factory for the parent type.
|
||||
*/
|
||||
template<auto Data, typename Policy = as_is_t, typename... Property>
|
||||
meta_factory data(const ENTT_ID_TYPE identifier, Property &&... property) ENTT_NOEXCEPT {
|
||||
auto * const type = internal::meta_info<Type>::resolve();
|
||||
internal::meta_data_node *curr = nullptr;
|
||||
|
||||
if constexpr(std::is_same_v<Type, decltype(Data)>) {
|
||||
static_assert(std::is_same_v<Policy, as_is_t>);
|
||||
|
||||
static internal::meta_data_node node{
|
||||
&internal::meta_info<Type>::template data<Data>,
|
||||
{},
|
||||
type,
|
||||
nullptr,
|
||||
nullptr,
|
||||
true,
|
||||
true,
|
||||
&internal::meta_info<Type>::resolve,
|
||||
[](meta_handle, meta_any, meta_any) { return false; },
|
||||
[](meta_handle, meta_any) -> meta_any { return Data; },
|
||||
[]() ENTT_NOEXCEPT -> meta_data {
|
||||
return &node;
|
||||
}
|
||||
};
|
||||
|
||||
node.prop = properties<std::integral_constant<Type, Data>>(std::forward<Property>(property)...);
|
||||
curr = &node;
|
||||
} else if constexpr(std::is_member_object_pointer_v<decltype(Data)>) {
|
||||
using data_type = std::remove_reference_t<decltype(std::declval<Type>().*Data)>;
|
||||
|
||||
static internal::meta_data_node node{
|
||||
&internal::meta_info<Type>::template data<Data>,
|
||||
{},
|
||||
type,
|
||||
nullptr,
|
||||
nullptr,
|
||||
std::is_const_v<data_type>,
|
||||
!std::is_member_object_pointer_v<decltype(Data)>,
|
||||
&internal::meta_info<data_type>::resolve,
|
||||
&internal::setter<std::is_const_v<data_type>, Type, Data>,
|
||||
&internal::getter<Type, Data, Policy>,
|
||||
[]() ENTT_NOEXCEPT -> meta_data {
|
||||
return &node;
|
||||
}
|
||||
};
|
||||
|
||||
node.prop = properties<std::integral_constant<decltype(Data), Data>>(std::forward<Property>(property)...);
|
||||
curr = &node;
|
||||
} else {
|
||||
static_assert(std::is_pointer_v<std::decay_t<decltype(Data)>>);
|
||||
using data_type = std::remove_pointer_t<std::decay_t<decltype(Data)>>;
|
||||
|
||||
static internal::meta_data_node node{
|
||||
&internal::meta_info<Type>::template data<Data>,
|
||||
{},
|
||||
type,
|
||||
nullptr,
|
||||
nullptr,
|
||||
std::is_const_v<data_type>,
|
||||
!std::is_member_object_pointer_v<decltype(Data)>,
|
||||
&internal::meta_info<data_type>::resolve,
|
||||
&internal::setter<std::is_const_v<data_type>, Type, Data>,
|
||||
&internal::getter<Type, Data, Policy>,
|
||||
[]() ENTT_NOEXCEPT -> meta_data {
|
||||
return &node;
|
||||
}
|
||||
};
|
||||
|
||||
node.prop = properties<std::integral_constant<decltype(Data), Data>>(std::forward<Property>(property)...);
|
||||
curr = &node;
|
||||
}
|
||||
|
||||
curr->identifier = identifier;
|
||||
curr->next = type->data;
|
||||
ENTT_ASSERT(!duplicate(identifier, curr->next));
|
||||
ENTT_ASSERT((!internal::meta_info<Type>::template data<Data>));
|
||||
internal::meta_info<Type>::template data<Data> = curr;
|
||||
type->data = curr;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns a meta data to a meta type by means of its setter and
|
||||
* getter.
|
||||
*
|
||||
* Setters and getters can be either free functions, member functions or a
|
||||
* mix of them.<br/>
|
||||
* In case of free functions, setters and getters must accept a reference to
|
||||
* an instance of the parent type as their first argument. A setter has then
|
||||
* an extra argument of a type convertible to that of the parameter to
|
||||
* set.<br/>
|
||||
* In case of member functions, getters have no arguments at all, while
|
||||
* setters has an argument of a type convertible to that of the parameter to
|
||||
* set.
|
||||
*
|
||||
* @tparam Setter The actual function to use as a setter.
|
||||
* @tparam Getter The actual function to use as a getter.
|
||||
* @tparam Policy Optional policy (no policy set by default).
|
||||
* @tparam Property Types of properties to assign to the meta data.
|
||||
* @param identifier Unique identifier.
|
||||
* @param property Properties to assign to the meta data.
|
||||
* @return A meta factory for the parent type.
|
||||
*/
|
||||
template<auto Setter, auto Getter, typename Policy = as_is_t, typename... Property>
|
||||
meta_factory data(const ENTT_ID_TYPE identifier, Property &&... property) ENTT_NOEXCEPT {
|
||||
using owner_type = std::tuple<std::integral_constant<decltype(Setter), Setter>, std::integral_constant<decltype(Getter), Getter>>;
|
||||
using underlying_type = std::invoke_result_t<decltype(Getter), Type &>;
|
||||
static_assert(std::is_invocable_v<decltype(Setter), Type &, underlying_type>);
|
||||
auto * const type = internal::meta_info<Type>::resolve();
|
||||
|
||||
static internal::meta_data_node node{
|
||||
&internal::meta_info<Type>::template data<Setter, Getter>,
|
||||
{},
|
||||
type,
|
||||
nullptr,
|
||||
nullptr,
|
||||
false,
|
||||
false,
|
||||
&internal::meta_info<underlying_type>::resolve,
|
||||
&internal::setter<false, Type, Setter>,
|
||||
&internal::getter<Type, Getter, Policy>,
|
||||
[]() ENTT_NOEXCEPT -> meta_data {
|
||||
return &node;
|
||||
}
|
||||
};
|
||||
|
||||
node.identifier = identifier;
|
||||
node.next = type->data;
|
||||
node.prop = properties<owner_type>(std::forward<Property>(property)...);
|
||||
ENTT_ASSERT(!duplicate(identifier, node.next));
|
||||
ENTT_ASSERT((!internal::meta_info<Type>::template data<Setter, Getter>));
|
||||
internal::meta_info<Type>::template data<Setter, Getter> = &node;
|
||||
type->data = &node;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns a meta funcion to a meta type.
|
||||
*
|
||||
* Both member functions and free functions can be assigned to a meta
|
||||
* type.<br/>
|
||||
* From a client's point of view, all the functions associated with the
|
||||
* reflected object will appear as if they were part of the type itself.
|
||||
*
|
||||
* @tparam Candidate The actual function to attach to the meta type.
|
||||
* @tparam Policy Optional policy (no policy set by default).
|
||||
* @tparam Property Types of properties to assign to the meta function.
|
||||
* @param identifier Unique identifier.
|
||||
* @param property Properties to assign to the meta function.
|
||||
* @return A meta factory for the parent type.
|
||||
*/
|
||||
template<auto Candidate, typename Policy = as_is_t, typename... Property>
|
||||
meta_factory func(const ENTT_ID_TYPE identifier, Property &&... property) ENTT_NOEXCEPT {
|
||||
using owner_type = std::integral_constant<decltype(Candidate), Candidate>;
|
||||
using helper_type = internal::meta_function_helper_t<decltype(Candidate)>;
|
||||
auto * const type = internal::meta_info<Type>::resolve();
|
||||
|
||||
static internal::meta_func_node node{
|
||||
&internal::meta_info<Type>::template func<Candidate>,
|
||||
{},
|
||||
type,
|
||||
nullptr,
|
||||
nullptr,
|
||||
helper_type::size,
|
||||
helper_type::is_const,
|
||||
!std::is_member_function_pointer_v<decltype(Candidate)>,
|
||||
&internal::meta_info<std::conditional_t<std::is_same_v<Policy, as_void_t>, void, typename helper_type::return_type>>::resolve,
|
||||
&helper_type::arg,
|
||||
[](meta_handle handle, meta_any *any) {
|
||||
return internal::invoke<Type, Candidate, Policy>(handle, any, std::make_index_sequence<helper_type::size>{});
|
||||
},
|
||||
[]() ENTT_NOEXCEPT -> meta_func {
|
||||
return &node;
|
||||
}
|
||||
};
|
||||
|
||||
node.identifier = identifier;
|
||||
node.next = type->func;
|
||||
node.prop = properties<owner_type>(std::forward<Property>(property)...);
|
||||
ENTT_ASSERT(!duplicate(identifier, node.next));
|
||||
ENTT_ASSERT((!internal::meta_info<Type>::template func<Candidate>));
|
||||
internal::meta_info<Type>::template func<Candidate> = &node;
|
||||
type->func = &node;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Unregisters a meta type and all its parts.
|
||||
*
|
||||
* This function unregisters a meta type and all its data members, member
|
||||
* functions and properties, as well as its constructors, destructors and
|
||||
* conversion functions if any.<br/>
|
||||
* Base classes aren't unregistered but the link between the two types is
|
||||
* removed.
|
||||
*
|
||||
* @return True if the meta type exists, false otherwise.
|
||||
*/
|
||||
bool unregister() ENTT_NOEXCEPT {
|
||||
const auto registered = internal::meta_info<Type>::type;
|
||||
|
||||
if(registered) {
|
||||
if(auto *curr = internal::meta_info<>::type; curr == internal::meta_info<Type>::type) {
|
||||
internal::meta_info<>::type = internal::meta_info<Type>::type->next;
|
||||
} else {
|
||||
while(curr && curr->next != internal::meta_info<Type>::type) {
|
||||
curr = curr->next;
|
||||
}
|
||||
|
||||
if(curr) {
|
||||
curr->next = internal::meta_info<Type>::type->next;
|
||||
}
|
||||
}
|
||||
|
||||
unregister_prop(&internal::meta_info<Type>::type->prop);
|
||||
unregister_all<&internal::meta_type_node::base>(0);
|
||||
unregister_all<&internal::meta_type_node::conv>(0);
|
||||
unregister_all<&internal::meta_type_node::ctor>(0);
|
||||
unregister_all<&internal::meta_type_node::data>(0);
|
||||
unregister_all<&internal::meta_type_node::func>(0);
|
||||
unregister_dtor();
|
||||
|
||||
internal::meta_info<Type>::type->identifier = {};
|
||||
internal::meta_info<Type>::type->next = nullptr;
|
||||
internal::meta_info<Type>::type = nullptr;
|
||||
}
|
||||
|
||||
return registered;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Utility function to use for reflection.
|
||||
*
|
||||
* This is the point from which everything starts.<br/>
|
||||
* By invoking this function with a type that is not yet reflected, a meta type
|
||||
* is created to which it will be possible to attach data and functions through
|
||||
* a dedicated factory.
|
||||
*
|
||||
* @tparam Type Type to reflect.
|
||||
* @tparam Property Types of properties to assign to the reflected type.
|
||||
* @param identifier Unique identifier.
|
||||
* @param property Properties to assign to the reflected type.
|
||||
* @return A meta factory for the given type.
|
||||
*/
|
||||
template<typename Type, typename... Property>
|
||||
inline meta_factory<Type> reflect(const ENTT_ID_TYPE identifier, Property &&... property) ENTT_NOEXCEPT {
|
||||
return meta_factory<Type>{}.type(identifier, std::forward<Property>(property)...);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Utility function to use for reflection.
|
||||
*
|
||||
* This is the point from which everything starts.<br/>
|
||||
* By invoking this function with a type that is not yet reflected, a meta type
|
||||
* is created to which it will be possible to attach data and functions through
|
||||
* a dedicated factory.
|
||||
*
|
||||
* @tparam Type Type to reflect.
|
||||
* @return A meta factory for the given type.
|
||||
*/
|
||||
template<typename Type>
|
||||
inline meta_factory<Type> reflect() ENTT_NOEXCEPT {
|
||||
return meta_factory<Type>{};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Utility function to unregister a type.
|
||||
*
|
||||
* This function unregisters a type and all its data members, member functions
|
||||
* and properties, as well as its constructors, destructors and conversion
|
||||
* functions if any.<br/>
|
||||
* Base classes aren't unregistered but the link between the two types is
|
||||
* removed.
|
||||
*
|
||||
* @tparam Type Type to unregister.
|
||||
* @return True if the type to unregister exists, false otherwise.
|
||||
*/
|
||||
template<typename Type>
|
||||
inline bool unregister() ENTT_NOEXCEPT {
|
||||
return meta_factory<Type>{}.unregister();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Returns the meta type associated with a given type.
|
||||
* @tparam Type Type to use to search for a meta type.
|
||||
* @return The meta type associated with the given type, if any.
|
||||
*/
|
||||
template<typename Type>
|
||||
inline meta_type resolve() ENTT_NOEXCEPT {
|
||||
return internal::meta_info<Type>::resolve()->meta();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Returns the meta type associated with a given identifier.
|
||||
* @param identifier Unique identifier.
|
||||
* @return The meta type associated with the given identifier, if any.
|
||||
*/
|
||||
inline meta_type resolve(const ENTT_ID_TYPE identifier) ENTT_NOEXCEPT {
|
||||
const auto *curr = internal::find_if([identifier](auto *node) {
|
||||
return node->identifier == identifier;
|
||||
}, internal::meta_info<>::type);
|
||||
|
||||
return curr ? curr->meta() : meta_type{};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Iterates all the reflected types.
|
||||
* @tparam Op Type of the function object to invoke.
|
||||
* @param op A valid function object.
|
||||
*/
|
||||
template<typename Op>
|
||||
inline std::enable_if_t<std::is_invocable_v<Op, meta_type>, void>
|
||||
resolve(Op op) ENTT_NOEXCEPT {
|
||||
internal::iterate([op = std::move(op)](auto *node) {
|
||||
op(node->meta());
|
||||
}, internal::meta_info<>::type);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_META_FACTORY_HPP
|
||||
2119
src/entt/meta/meta.hpp
Normal file
2119
src/entt/meta/meta.hpp
Normal file
File diff suppressed because it is too large
Load Diff
27
src/entt/meta/policy.hpp
Normal file
27
src/entt/meta/policy.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef ENTT_META_POLICY_HPP
|
||||
#define ENTT_META_POLICY_HPP
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/*! @brief Empty class type used to request the _as alias_ policy. */
|
||||
struct as_alias_t {};
|
||||
|
||||
|
||||
/*! @brief Disambiguation tag. */
|
||||
constexpr as_alias_t as_alias;
|
||||
|
||||
|
||||
/*! @brief Empty class type used to request the _as-is_ policy. */
|
||||
struct as_is_t {};
|
||||
|
||||
|
||||
/*! @brief Empty class type used to request the _as void_ policy. */
|
||||
struct as_void_t {};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_META_POLICY_HPP
|
||||
@@ -2,9 +2,8 @@
|
||||
#define ENTT_PROCESS_PROCESS_HPP
|
||||
|
||||
|
||||
#include <type_traits>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
@@ -31,12 +30,13 @@ namespace entt {
|
||||
* update.
|
||||
*
|
||||
* * @code{.cpp}
|
||||
* void init(void *);
|
||||
* void init();
|
||||
* @endcode
|
||||
*
|
||||
* It's invoked at the first tick, immediately before an update. The `void *`
|
||||
* parameter is an opaque pointer to user data (if any) forwarded directly to
|
||||
* the process during an update.
|
||||
* It's invoked when the process joins the running queue of a scheduler. This
|
||||
* happens as soon as it's attached to the scheduler if the process is a top
|
||||
* level one, otherwise when it replaces its parent if the process is a
|
||||
* continuation.
|
||||
*
|
||||
* * @code{.cpp}
|
||||
* void succeeded();
|
||||
@@ -64,14 +64,14 @@ namespace entt {
|
||||
* `succeed` and `fail` protected member functions and even pause or unpause the
|
||||
* process itself.
|
||||
*
|
||||
* @sa Scheduler
|
||||
* @sa scheduler
|
||||
*
|
||||
* @tparam Derived Actual type of process that extends the class template.
|
||||
* @tparam Delta Type to use to provide elapsed time.
|
||||
*/
|
||||
template<typename Derived, typename Delta>
|
||||
class Process {
|
||||
enum class State: unsigned int {
|
||||
class process {
|
||||
enum class state: unsigned int {
|
||||
UNINITIALIZED = 0,
|
||||
RUNNING,
|
||||
PAUSED,
|
||||
@@ -81,41 +81,41 @@ class Process {
|
||||
FINISHED
|
||||
};
|
||||
|
||||
template<State state>
|
||||
using tag = std::integral_constant<State, state>;
|
||||
template<state value>
|
||||
using state_value_t = std::integral_constant<state, value>;
|
||||
|
||||
template<typename Target = Derived>
|
||||
auto tick(int, tag<State::UNINITIALIZED>, void *data)
|
||||
-> decltype(std::declval<Target>().init(data)) {
|
||||
static_cast<Target *>(this)->init(data);
|
||||
auto tick(int, state_value_t<state::UNINITIALIZED>)
|
||||
-> decltype(std::declval<Target>().init()) {
|
||||
static_cast<Target *>(this)->init();
|
||||
}
|
||||
|
||||
template<typename Target = Derived>
|
||||
auto tick(int, tag<State::RUNNING>, Delta delta, void *data)
|
||||
auto tick(int, state_value_t<state::RUNNING>, Delta delta, void *data)
|
||||
-> decltype(std::declval<Target>().update(delta, data)) {
|
||||
static_cast<Target *>(this)->update(delta, data);
|
||||
}
|
||||
|
||||
template<typename Target = Derived>
|
||||
auto tick(int, tag<State::SUCCEEDED>)
|
||||
auto tick(int, state_value_t<state::SUCCEEDED>)
|
||||
-> decltype(std::declval<Target>().succeeded()) {
|
||||
static_cast<Target *>(this)->succeeded();
|
||||
}
|
||||
|
||||
template<typename Target = Derived>
|
||||
auto tick(int, tag<State::FAILED>)
|
||||
auto tick(int, state_value_t<state::FAILED>)
|
||||
-> decltype(std::declval<Target>().failed()) {
|
||||
static_cast<Target *>(this)->failed();
|
||||
}
|
||||
|
||||
template<typename Target = Derived>
|
||||
auto tick(int, tag<State::ABORTED>)
|
||||
auto tick(int, state_value_t<state::ABORTED>)
|
||||
-> decltype(std::declval<Target>().aborted()) {
|
||||
static_cast<Target *>(this)->aborted();
|
||||
}
|
||||
|
||||
template<State S, typename... Args>
|
||||
void tick(char, tag<S>, Args &&...) const ENTT_NOEXCEPT {}
|
||||
template<state value, typename... Args>
|
||||
void tick(char, state_value_t<value>, Args &&...) const ENTT_NOEXCEPT {}
|
||||
|
||||
protected:
|
||||
/**
|
||||
@@ -126,7 +126,7 @@ protected:
|
||||
*/
|
||||
void succeed() ENTT_NOEXCEPT {
|
||||
if(alive()) {
|
||||
current = State::SUCCEEDED;
|
||||
current = state::SUCCEEDED;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ protected:
|
||||
*/
|
||||
void fail() ENTT_NOEXCEPT {
|
||||
if(alive()) {
|
||||
current = State::FAILED;
|
||||
current = state::FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,8 +149,8 @@ protected:
|
||||
* running.
|
||||
*/
|
||||
void pause() ENTT_NOEXCEPT {
|
||||
if(current == State::RUNNING) {
|
||||
current = State::PAUSED;
|
||||
if(current == state::RUNNING) {
|
||||
current = state::PAUSED;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,8 +161,8 @@ protected:
|
||||
* paused.
|
||||
*/
|
||||
void unpause() ENTT_NOEXCEPT {
|
||||
if(current == State::PAUSED) {
|
||||
current = State::RUNNING;
|
||||
if(current == state::PAUSED) {
|
||||
current = state::RUNNING;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,8 +171,8 @@ public:
|
||||
using delta_type = Delta;
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
virtual ~Process() ENTT_NOEXCEPT {
|
||||
static_assert(std::is_base_of<Process, Derived>::value, "!");
|
||||
virtual ~process() ENTT_NOEXCEPT {
|
||||
static_assert(std::is_base_of_v<process, Derived>);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,7 +185,7 @@ public:
|
||||
*/
|
||||
void abort(const bool immediately = false) ENTT_NOEXCEPT {
|
||||
if(alive()) {
|
||||
current = State::ABORTED;
|
||||
current = state::ABORTED;
|
||||
|
||||
if(immediately) {
|
||||
tick(0);
|
||||
@@ -198,7 +198,7 @@ public:
|
||||
* @return True if the process is still alive, false otherwise.
|
||||
*/
|
||||
bool alive() const ENTT_NOEXCEPT {
|
||||
return current == State::RUNNING || current == State::PAUSED;
|
||||
return current == state::RUNNING || current == state::PAUSED;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -206,7 +206,7 @@ public:
|
||||
* @return True if the process is terminated, false otherwise.
|
||||
*/
|
||||
bool dead() const ENTT_NOEXCEPT {
|
||||
return current == State::FINISHED;
|
||||
return current == state::FINISHED;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -214,7 +214,7 @@ public:
|
||||
* @return True if the process is paused, false otherwise.
|
||||
*/
|
||||
bool paused() const ENTT_NOEXCEPT {
|
||||
return current == State::PAUSED;
|
||||
return current == state::PAUSED;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -232,12 +232,13 @@ public:
|
||||
*/
|
||||
void tick(const Delta delta, void *data = nullptr) {
|
||||
switch (current) {
|
||||
case State::UNINITIALIZED:
|
||||
tick(0, tag<State::UNINITIALIZED>{}, data);
|
||||
current = State::RUNNING;
|
||||
// no break on purpose, tasks are executed immediately
|
||||
case State::RUNNING:
|
||||
tick(0, tag<State::RUNNING>{}, delta, data);
|
||||
case state::UNINITIALIZED:
|
||||
tick(0, state_value_t<state::UNINITIALIZED>{});
|
||||
current = state::RUNNING;
|
||||
break;
|
||||
case state::RUNNING:
|
||||
tick(0, state_value_t<state::RUNNING>{}, delta, data);
|
||||
break;
|
||||
default:
|
||||
// suppress warnings
|
||||
break;
|
||||
@@ -245,18 +246,18 @@ public:
|
||||
|
||||
// if it's dead, it must be notified and removed immediately
|
||||
switch(current) {
|
||||
case State::SUCCEEDED:
|
||||
tick(0, tag<State::SUCCEEDED>{});
|
||||
current = State::FINISHED;
|
||||
case state::SUCCEEDED:
|
||||
tick(0, state_value_t<state::SUCCEEDED>{});
|
||||
current = state::FINISHED;
|
||||
break;
|
||||
case State::FAILED:
|
||||
tick(0, tag<State::FAILED>{});
|
||||
current = State::FINISHED;
|
||||
case state::FAILED:
|
||||
tick(0, state_value_t<state::FAILED>{});
|
||||
current = state::FINISHED;
|
||||
stopped = true;
|
||||
break;
|
||||
case State::ABORTED:
|
||||
tick(0, tag<State::ABORTED>{});
|
||||
current = State::FINISHED;
|
||||
case state::ABORTED:
|
||||
tick(0, state_value_t<state::ABORTED>{});
|
||||
current = state::FINISHED;
|
||||
stopped = true;
|
||||
break;
|
||||
default:
|
||||
@@ -266,7 +267,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
State current{State::UNINITIALIZED};
|
||||
state current{state::UNINITIALIZED};
|
||||
bool stopped{false};
|
||||
};
|
||||
|
||||
@@ -304,21 +305,21 @@ private:
|
||||
* create them internally each and avery time a lambda or a functor is used as
|
||||
* a process.
|
||||
*
|
||||
* @sa Process
|
||||
* @sa Scheduler
|
||||
* @sa process
|
||||
* @sa scheduler
|
||||
*
|
||||
* @tparam Func Actual type of process.
|
||||
* @tparam Delta Type to use to provide elapsed time.
|
||||
*/
|
||||
template<typename Func, typename Delta>
|
||||
struct ProcessAdaptor: Process<ProcessAdaptor<Func, Delta>, Delta>, private Func {
|
||||
struct process_adaptor: process<process_adaptor<Func, Delta>, Delta>, private Func {
|
||||
/**
|
||||
* @brief Constructs a process adaptor from a lambda or a functor.
|
||||
* @tparam Args Types of arguments to use to initialize the actual process.
|
||||
* @param args Parameters to use to initialize the actual process.
|
||||
*/
|
||||
template<typename... Args>
|
||||
ProcessAdaptor(Args &&... args)
|
||||
process_adaptor(Args &&... args)
|
||||
: Func{std::forward<Args>(args)...}
|
||||
{}
|
||||
|
||||
|
||||
@@ -30,23 +30,23 @@ namespace entt {
|
||||
* @code{.cpp}
|
||||
* scheduler.attach([](auto delta, void *, auto succeed, auto fail) {
|
||||
* // code
|
||||
* }).then<MyProcess>(arguments...);
|
||||
* }).then<my_process>(arguments...);
|
||||
* @endcode
|
||||
*
|
||||
* In order to invoke all scheduled processes, call the `update` member function
|
||||
* passing it the elapsed time to forward to the tasks.
|
||||
*
|
||||
* @sa Process
|
||||
* @sa process
|
||||
*
|
||||
* @tparam Delta Type to use to provide elapsed time.
|
||||
*/
|
||||
template<typename Delta>
|
||||
class Scheduler final {
|
||||
struct ProcessHandler final {
|
||||
class scheduler {
|
||||
struct process_handler {
|
||||
using instance_type = std::unique_ptr<void, void(*)(void *)>;
|
||||
using update_fn_type = bool(ProcessHandler &, Delta, void *);
|
||||
using abort_fn_type = void(ProcessHandler &, bool);
|
||||
using next_type = std::unique_ptr<ProcessHandler>;
|
||||
using update_fn_type = bool(process_handler &, Delta, void *);
|
||||
using abort_fn_type = void(process_handler &, bool);
|
||||
using next_type = std::unique_ptr<process_handler>;
|
||||
|
||||
instance_type instance;
|
||||
update_fn_type *update;
|
||||
@@ -54,30 +54,33 @@ class Scheduler final {
|
||||
next_type next;
|
||||
};
|
||||
|
||||
struct Then final {
|
||||
Then(ProcessHandler *handler)
|
||||
: handler{handler}
|
||||
{}
|
||||
struct continuation {
|
||||
continuation(process_handler *ref)
|
||||
: handler{ref}
|
||||
{
|
||||
ENTT_ASSERT(handler);
|
||||
}
|
||||
|
||||
template<typename Proc, typename... Args>
|
||||
decltype(auto) then(Args &&... args) && {
|
||||
static_assert(std::is_base_of<Process<Proc, Delta>, Proc>::value, "!");
|
||||
handler = Scheduler::then<Proc>(handler, std::forward<Args>(args)...);
|
||||
return std::move(*this);
|
||||
continuation then(Args &&... args) {
|
||||
static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>);
|
||||
auto proc = typename process_handler::instance_type{new Proc{std::forward<Args>(args)...}, &scheduler::deleter<Proc>};
|
||||
handler->next.reset(new process_handler{std::move(proc), &scheduler::update<Proc>, &scheduler::abort<Proc>, nullptr});
|
||||
handler = handler->next.get();
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename Func>
|
||||
decltype(auto) then(Func &&func) && {
|
||||
using Proc = ProcessAdaptor<std::decay_t<Func>, Delta>;
|
||||
return std::move(*this).template then<Proc>(std::forward<Func>(func));
|
||||
continuation then(Func &&func) {
|
||||
return then<process_adaptor<std::decay_t<Func>, Delta>>(std::forward<Func>(func));
|
||||
}
|
||||
|
||||
private:
|
||||
ProcessHandler *handler;
|
||||
process_handler *handler;
|
||||
};
|
||||
|
||||
template<typename Proc>
|
||||
static bool update(ProcessHandler &handler, const Delta delta, void *data) {
|
||||
static bool update(process_handler &handler, const Delta delta, void *data) {
|
||||
auto *process = static_cast<Proc *>(handler.instance.get());
|
||||
process->tick(delta, data);
|
||||
|
||||
@@ -86,7 +89,8 @@ class Scheduler final {
|
||||
if(dead) {
|
||||
if(handler.next && !process->rejected()) {
|
||||
handler = std::move(*handler.next);
|
||||
dead = handler.update(handler, delta, data);
|
||||
// forces the process to exit the uninitialized state
|
||||
dead = handler.update(handler, {}, nullptr);
|
||||
} else {
|
||||
handler.instance.reset();
|
||||
}
|
||||
@@ -96,7 +100,7 @@ class Scheduler final {
|
||||
}
|
||||
|
||||
template<typename Proc>
|
||||
static void abort(ProcessHandler &handler, const bool immediately) {
|
||||
static void abort(process_handler &handler, const bool immediately) {
|
||||
static_cast<Proc *>(handler.instance.get())->abort(immediately);
|
||||
}
|
||||
|
||||
@@ -105,33 +109,18 @@ class Scheduler final {
|
||||
delete static_cast<Proc *>(proc);
|
||||
}
|
||||
|
||||
template<typename Proc, typename... Args>
|
||||
static auto then(ProcessHandler *handler, Args &&... args) {
|
||||
if(handler) {
|
||||
auto proc = typename ProcessHandler::instance_type{new Proc{std::forward<Args>(args)...}, &Scheduler::deleter<Proc>};
|
||||
handler->next.reset(new ProcessHandler{std::move(proc), &Scheduler::update<Proc>, &Scheduler::abort<Proc>, nullptr});
|
||||
handler = handler->next.get();
|
||||
}
|
||||
|
||||
return handler;
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename std::vector<ProcessHandler>::size_type;
|
||||
using size_type = typename std::vector<process_handler>::size_type;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
Scheduler() ENTT_NOEXCEPT = default;
|
||||
scheduler() ENTT_NOEXCEPT = default;
|
||||
|
||||
/*! @brief Copying a scheduler isn't allowed. */
|
||||
Scheduler(const Scheduler &) = delete;
|
||||
/*! @brief Default move constructor. */
|
||||
Scheduler(Scheduler &&) = default;
|
||||
scheduler(scheduler &&) = default;
|
||||
|
||||
/*! @brief Copying a scheduler isn't allowed. @return This scheduler. */
|
||||
Scheduler & operator=(const Scheduler &) = delete;
|
||||
/*! @brief Default move assignment operator. @return This scheduler. */
|
||||
Scheduler & operator=(Scheduler &&) = default;
|
||||
scheduler & operator=(scheduler &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Number of processes currently scheduled.
|
||||
@@ -170,13 +159,13 @@ public:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* // schedules a task in the form of a process class
|
||||
* scheduler.attach<MyProcess>(arguments...)
|
||||
* scheduler.attach<my_process>(arguments...)
|
||||
* // appends a child in the form of a lambda function
|
||||
* .then([](auto delta, void *, auto succeed, auto fail) {
|
||||
* // code
|
||||
* })
|
||||
* // appends a child in the form of another process class
|
||||
* .then<MyOtherProcess>();
|
||||
* .then<my_other_process>();
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Proc Type of process to schedule.
|
||||
@@ -186,13 +175,12 @@ public:
|
||||
*/
|
||||
template<typename Proc, typename... Args>
|
||||
auto attach(Args &&... args) {
|
||||
static_assert(std::is_base_of<Process<Proc, Delta>, Proc>::value, "!");
|
||||
|
||||
auto proc = typename ProcessHandler::instance_type{new Proc{std::forward<Args>(args)...}, &Scheduler::deleter<Proc>};
|
||||
ProcessHandler handler{std::move(proc), &Scheduler::update<Proc>, &Scheduler::abort<Proc>, nullptr};
|
||||
handlers.push_back(std::move(handler));
|
||||
|
||||
return Then{&handlers.back()};
|
||||
static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>);
|
||||
auto proc = typename process_handler::instance_type{new Proc{std::forward<Args>(args)...}, &scheduler::deleter<Proc>};
|
||||
process_handler handler{std::move(proc), &scheduler::update<Proc>, &scheduler::abort<Proc>, nullptr};
|
||||
// forces the process to exit the uninitialized state
|
||||
handler.update(handler, {}, nullptr);
|
||||
return continuation{&handlers.emplace_back(std::move(handler))};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -204,12 +192,13 @@ public:
|
||||
* following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(Delta delta, auto succeed, auto fail);
|
||||
* void(Delta delta, void *data, auto succeed, auto fail);
|
||||
* @endcode
|
||||
*
|
||||
* Where:
|
||||
*
|
||||
* * `delta` is the elapsed time.
|
||||
* * `data` is an opaque pointer to user data if any, `nullptr` otherwise.
|
||||
* * `succeed` is a function to call when a process terminates with success.
|
||||
* * `fail` is a function to call when a process terminates with errors.
|
||||
*
|
||||
@@ -236,10 +225,10 @@ public:
|
||||
* // code
|
||||
* })
|
||||
* // appends a child in the form of a process class
|
||||
* .then<MyProcess>(arguments...);
|
||||
* .then<my_process>(arguments...);
|
||||
* @endcode
|
||||
*
|
||||
* @sa ProcessAdaptor
|
||||
* @sa process_adaptor
|
||||
*
|
||||
* @tparam Func Type of process to schedule.
|
||||
* @param func Either a lambda or a functor to use as a process.
|
||||
@@ -247,7 +236,7 @@ public:
|
||||
*/
|
||||
template<typename Func>
|
||||
auto attach(Func &&func) {
|
||||
using Proc = ProcessAdaptor<std::decay_t<Func>, Delta>;
|
||||
using Proc = process_adaptor<std::decay_t<Func>, Delta>;
|
||||
return attach<Proc>(std::forward<Func>(func));
|
||||
}
|
||||
|
||||
@@ -301,7 +290,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<ProcessHandler> handlers{};
|
||||
std::vector<process_handler> handlers{};
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include "../config/config.h"
|
||||
#include "../core/hashed_string.hpp"
|
||||
#include "handle.hpp"
|
||||
#include "loader.hpp"
|
||||
#include "fwd.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
@@ -26,27 +26,23 @@ namespace entt {
|
||||
* @tparam Resource Type of resources managed by a cache.
|
||||
*/
|
||||
template<typename Resource>
|
||||
class ResourceCache {
|
||||
using container_type = std::unordered_map<HashedString::hash_type, std::shared_ptr<Resource>>;
|
||||
class resource_cache {
|
||||
using container_type = std::unordered_map<ENTT_ID_TYPE, std::shared_ptr<Resource>>;
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename container_type::size_type;
|
||||
/*! @brief Type of resources managed by a cache. */
|
||||
using resource_type = HashedString;
|
||||
using resource_type = ENTT_ID_TYPE;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
ResourceCache() = default;
|
||||
resource_cache() = default;
|
||||
|
||||
/*! @brief Copying a cache isn't allowed. */
|
||||
ResourceCache(const ResourceCache &) ENTT_NOEXCEPT = delete;
|
||||
/*! @brief Default move constructor. */
|
||||
ResourceCache(ResourceCache &&) ENTT_NOEXCEPT = default;
|
||||
resource_cache(resource_cache &&) = default;
|
||||
|
||||
/*! @brief Copying a cache isn't allowed. @return This cache. */
|
||||
ResourceCache & operator=(const ResourceCache &) ENTT_NOEXCEPT = delete;
|
||||
/*! @brief Default move assignment operator. @return This cache. */
|
||||
ResourceCache & operator=(ResourceCache &&) ENTT_NOEXCEPT = default;
|
||||
resource_cache & operator=(resource_cache &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Number of resources managed by a cache.
|
||||
@@ -86,24 +82,31 @@ public:
|
||||
* If the identifier is already present in the cache, this function does
|
||||
* nothing and the arguments are simply discarded.
|
||||
*
|
||||
* @warning
|
||||
* If the resource cannot be loaded correctly, the returned handle will be
|
||||
* invalid and any use of it will result in undefined behavior.
|
||||
*
|
||||
* @tparam Loader Type of loader to use to load the resource if required.
|
||||
* @tparam Args Types of arguments to use to load the resource if required.
|
||||
* @param id Unique resource identifier.
|
||||
* @param args Arguments to use to load the resource if required.
|
||||
* @return True if the resource is ready to use, false otherwise.
|
||||
* @return A handle for the given resource.
|
||||
*/
|
||||
template<typename Loader, typename... Args>
|
||||
bool load(const resource_type id, Args &&... args) {
|
||||
static_assert(std::is_base_of<ResourceLoader<Loader, Resource>, Loader>::value, "!");
|
||||
resource_handle<Resource> load(const resource_type id, Args &&... args) {
|
||||
static_assert(std::is_base_of_v<resource_loader<Loader, Resource>, Loader>);
|
||||
resource_handle<Resource> handle{};
|
||||
|
||||
bool loaded = true;
|
||||
|
||||
if(resources.find(id) == resources.cend()) {
|
||||
std::shared_ptr<Resource> resource = Loader{}.get(std::forward<Args>(args)...);
|
||||
loaded = (static_cast<bool>(resource) ? (resources[id] = std::move(resource), loaded) : false);
|
||||
if(auto it = resources.find(id); it == resources.cend()) {
|
||||
if(auto resource = Loader{}.get(std::forward<Args>(args)...); resource) {
|
||||
resources[id] = resource;
|
||||
handle = std::move(resource);
|
||||
}
|
||||
} else {
|
||||
handle = it->second;
|
||||
}
|
||||
|
||||
return loaded;
|
||||
return handle;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,14 +122,18 @@ public:
|
||||
* Arguments are forwarded directly to the loader in order to construct
|
||||
* properly the requested resource.
|
||||
*
|
||||
* @warning
|
||||
* If the resource cannot be loaded correctly, the returned handle will be
|
||||
* invalid and any use of it will result in undefined behavior.
|
||||
*
|
||||
* @tparam Loader Type of loader to use to load the resource.
|
||||
* @tparam Args Types of arguments to use to load the resource.
|
||||
* @param id Unique resource identifier.
|
||||
* @param args Arguments to use to load the resource.
|
||||
* @return True if the resource is ready to use, false otherwise.
|
||||
* @return A handle for the given resource.
|
||||
*/
|
||||
template<typename Loader, typename... Args>
|
||||
bool reload(const resource_type id, Args &&... args) {
|
||||
resource_handle<Resource> reload(const resource_type id, Args &&... args) {
|
||||
return (discard(id), load<Loader>(id, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
@@ -143,7 +150,7 @@ public:
|
||||
* @return A handle for the given resource.
|
||||
*/
|
||||
template<typename Loader, typename... Args>
|
||||
ResourceHandle<Resource> temp(Args &&... args) const {
|
||||
resource_handle<Resource> temp(Args &&... args) const {
|
||||
return { Loader{}.get(std::forward<Args>(args)...) };
|
||||
}
|
||||
|
||||
@@ -155,12 +162,12 @@ public:
|
||||
* cache contains the resource itself. Otherwise the returned handle is
|
||||
* uninitialized and accessing it results in undefined behavior.
|
||||
*
|
||||
* @sa ResourceHandle
|
||||
* @sa resource_handle
|
||||
*
|
||||
* @param id Unique resource identifier.
|
||||
* @return A handle for the given resource.
|
||||
*/
|
||||
ResourceHandle<Resource> handle(const resource_type id) const {
|
||||
resource_handle<Resource> handle(const resource_type id) const {
|
||||
auto it = resources.find(id);
|
||||
return { it == resources.end() ? nullptr : it->second };
|
||||
}
|
||||
@@ -183,13 +190,46 @@ public:
|
||||
* @param id Unique resource identifier.
|
||||
*/
|
||||
void discard(const resource_type id) ENTT_NOEXCEPT {
|
||||
auto it = resources.find(id);
|
||||
|
||||
if(it != resources.end()) {
|
||||
if(auto it = resources.find(id); it != resources.end()) {
|
||||
resources.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterates all resources.
|
||||
*
|
||||
* The function object is invoked for each element. It is provided with
|
||||
* either the resource identifier, the resource handle or both of them.<br/>
|
||||
* The signature of the function must be equivalent to one of the following
|
||||
* forms:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(const resource_type);
|
||||
* void(resource_handle<Resource>);
|
||||
* void(const resource_type, resource_handle<Resource>);
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template <typename Func>
|
||||
void each(Func func) const {
|
||||
auto begin = resources.begin();
|
||||
auto end = resources.end();
|
||||
|
||||
while(begin != end) {
|
||||
auto curr = begin++;
|
||||
|
||||
if constexpr(std::is_invocable_v<Func, resource_type>) {
|
||||
func(curr->first);
|
||||
} else if constexpr(std::is_invocable_v<Func, resource_handle<Resource>>) {
|
||||
func(resource_handle{ curr->second });
|
||||
} else {
|
||||
func(curr->first, resource_handle{ curr->second });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
container_type resources;
|
||||
};
|
||||
|
||||
27
src/entt/resource/fwd.hpp
Normal file
27
src/entt/resource/fwd.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef ENTT_RESOURCE_FWD_HPP
|
||||
#define ENTT_RESOURCE_FWD_HPP
|
||||
|
||||
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/*! @class resource_cache */
|
||||
template<typename>
|
||||
class resource_cache;
|
||||
|
||||
/*! @class resource_handle */
|
||||
template<typename>
|
||||
class resource_handle;
|
||||
|
||||
/*! @class resource_loader */
|
||||
template<typename, typename>
|
||||
class resource_loader;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_RESOURCE_FWD_HPP
|
||||
@@ -4,17 +4,13 @@
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <cassert>
|
||||
#include "../config/config.h"
|
||||
#include "fwd.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
template<typename Resource>
|
||||
class ResourceCache;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Shared resource handle.
|
||||
*
|
||||
@@ -28,24 +24,17 @@ class ResourceCache;
|
||||
* @tparam Resource Type of resource managed by a handle.
|
||||
*/
|
||||
template<typename Resource>
|
||||
class ResourceHandle final {
|
||||
class resource_handle {
|
||||
/*! @brief Resource handles are friends of their caches. */
|
||||
friend class ResourceCache<Resource>;
|
||||
friend class resource_cache<Resource>;
|
||||
|
||||
ResourceHandle(std::shared_ptr<Resource> res) ENTT_NOEXCEPT
|
||||
resource_handle(std::shared_ptr<Resource> res) ENTT_NOEXCEPT
|
||||
: resource{std::move(res)}
|
||||
{}
|
||||
|
||||
public:
|
||||
/*! @brief Default copy constructor. */
|
||||
ResourceHandle(const ResourceHandle &) ENTT_NOEXCEPT = default;
|
||||
/*! @brief Default move constructor. */
|
||||
ResourceHandle(ResourceHandle &&) ENTT_NOEXCEPT = default;
|
||||
|
||||
/*! @brief Default copy assignment operator. @return This handle. */
|
||||
ResourceHandle & operator=(const ResourceHandle &) ENTT_NOEXCEPT = default;
|
||||
/*! @brief Default move assignment operator. @return This handle. */
|
||||
ResourceHandle & operator=(ResourceHandle &&) ENTT_NOEXCEPT = default;
|
||||
/*! @brief Default constructor. */
|
||||
resource_handle() ENTT_NOEXCEPT = default;
|
||||
|
||||
/**
|
||||
* @brief Gets a reference to the managed resource.
|
||||
@@ -58,34 +47,29 @@ public:
|
||||
* @return A reference to the managed resource.
|
||||
*/
|
||||
const Resource & get() const ENTT_NOEXCEPT {
|
||||
assert(static_cast<bool>(resource));
|
||||
ENTT_ASSERT(static_cast<bool>(resource));
|
||||
return *resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Casts a handle and gets a reference to the managed resource.
|
||||
*
|
||||
* @warning
|
||||
* The behavior is undefined if the handle doesn't contain a resource.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* handle is empty.
|
||||
*/
|
||||
inline operator const Resource &() const ENTT_NOEXCEPT { return get(); }
|
||||
/*! @copydoc get */
|
||||
Resource & get() ENTT_NOEXCEPT {
|
||||
return const_cast<Resource &>(std::as_const(*this).get());
|
||||
}
|
||||
|
||||
/*! @copydoc get */
|
||||
operator const Resource & () const ENTT_NOEXCEPT { return get(); }
|
||||
|
||||
/*! @copydoc get */
|
||||
operator Resource & () ENTT_NOEXCEPT { return get(); }
|
||||
|
||||
/*! @copydoc get */
|
||||
const Resource & operator *() const ENTT_NOEXCEPT { return get(); }
|
||||
|
||||
/*! @copydoc get */
|
||||
Resource & operator *() ENTT_NOEXCEPT { return get(); }
|
||||
|
||||
/**
|
||||
* @brief Dereferences a handle to obtain the managed resource.
|
||||
*
|
||||
* @warning
|
||||
* The behavior is undefined if the handle doesn't contain a resource.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* handle is empty.
|
||||
*
|
||||
* @return A reference to the managed resource.
|
||||
*/
|
||||
inline const Resource & operator *() const ENTT_NOEXCEPT { return get(); }
|
||||
|
||||
/**
|
||||
* @brief Gets a pointer to the managed resource from a handle.
|
||||
* @brief Gets a pointer to the managed resource.
|
||||
*
|
||||
* @warning
|
||||
* The behavior is undefined if the handle doesn't contain a resource.<br/>
|
||||
@@ -95,13 +79,19 @@ public:
|
||||
* @return A pointer to the managed resource or `nullptr` if the handle
|
||||
* contains no resource at all.
|
||||
*/
|
||||
inline const Resource * operator ->() const ENTT_NOEXCEPT {
|
||||
assert(static_cast<bool>(resource));
|
||||
const Resource * operator->() const ENTT_NOEXCEPT {
|
||||
ENTT_ASSERT(static_cast<bool>(resource));
|
||||
return resource.get();
|
||||
}
|
||||
|
||||
/*! @copydoc operator-> */
|
||||
Resource * operator->() ENTT_NOEXCEPT {
|
||||
return const_cast<Resource *>(std::as_const(*this).operator->());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if the handle contains a resource, false otherwise.
|
||||
* @brief Returns true if a handle contains a resource, false otherwise.
|
||||
* @return True if the handle contains a resource, false otherwise.
|
||||
*/
|
||||
explicit operator bool() const { return static_cast<bool>(resource); }
|
||||
|
||||
|
||||
@@ -3,15 +3,12 @@
|
||||
|
||||
|
||||
#include <memory>
|
||||
#include "fwd.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
template<typename Resource>
|
||||
class ResourceCache;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Base class for resource loaders.
|
||||
*
|
||||
@@ -22,12 +19,12 @@ class ResourceCache;
|
||||
* As an example:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* struct MyResource {};
|
||||
* struct my_resource {};
|
||||
*
|
||||
* struct MyLoader: entt::ResourceLoader<MyLoader, MyResource> {
|
||||
* std::shared_ptr<MyResource> load(int) const {
|
||||
* struct my_loader: entt::resource_loader<my_loader, my_resource> {
|
||||
* std::shared_ptr<my_resource> load(int) const {
|
||||
* // use the integer value somehow
|
||||
* return std::make_shared<MyResource>();
|
||||
* return std::make_shared<my_resource>();
|
||||
* }
|
||||
* };
|
||||
* @endcode
|
||||
@@ -45,10 +42,16 @@ class ResourceCache;
|
||||
* @tparam Resource Type of resource for which to use the loader.
|
||||
*/
|
||||
template<typename Loader, typename Resource>
|
||||
class ResourceLoader {
|
||||
class resource_loader {
|
||||
/*! @brief Resource loaders are friends of their caches. */
|
||||
friend class ResourceCache<Resource>;
|
||||
friend class resource_cache<Resource>;
|
||||
|
||||
/**
|
||||
* @brief Loads the resource and returns it.
|
||||
* @tparam Args Types of arguments for the loader.
|
||||
* @param args Arguments for the loader.
|
||||
* @return The resource just loaded or an empty pointer in case of errors.
|
||||
*/
|
||||
template<typename... Args>
|
||||
std::shared_ptr<Resource> get(Args &&... args) const {
|
||||
return static_cast<const Loader *>(this)->load(std::forward<Args>(args)...);
|
||||
|
||||
@@ -2,13 +2,80 @@
|
||||
#define ENTT_SIGNAL_DELEGATE_HPP
|
||||
|
||||
|
||||
#include <tuple>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @cond TURN_OFF_DOXYGEN
|
||||
* Internal details not to be documented.
|
||||
*/
|
||||
|
||||
|
||||
namespace internal {
|
||||
|
||||
|
||||
template<typename Ret, typename... Args>
|
||||
auto to_function_pointer(Ret(*)(Args...)) -> Ret(*)(Args...);
|
||||
|
||||
|
||||
template<typename Ret, typename... Args, typename Type, typename Payload, typename = std::enable_if_t<std::is_convertible_v<Payload &, Type &>>>
|
||||
auto to_function_pointer(Ret(*)(Type &, Args...), Payload &) -> Ret(*)(Args...);
|
||||
|
||||
|
||||
template<typename Class, typename Ret, typename... Args>
|
||||
auto to_function_pointer(Ret(Class:: *)(Args...), const Class &) -> Ret(*)(Args...);
|
||||
|
||||
|
||||
template<typename Class, typename Ret, typename... Args>
|
||||
auto to_function_pointer(Ret(Class:: *)(Args...) const, const Class &) -> Ret(*)(Args...);
|
||||
|
||||
|
||||
template<typename Class, typename Type>
|
||||
auto to_function_pointer(Type Class:: *, const Class &) -> Type(*)();
|
||||
|
||||
|
||||
template<typename>
|
||||
struct function_extent;
|
||||
|
||||
|
||||
template<typename Ret, typename... Args>
|
||||
struct function_extent<Ret(*)(Args...)> {
|
||||
static constexpr auto value = sizeof...(Args);
|
||||
};
|
||||
|
||||
|
||||
template<typename Func>
|
||||
constexpr auto function_extent_v = function_extent<Func>::value;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Internal details not to be documented.
|
||||
* @endcond TURN_OFF_DOXYGEN
|
||||
*/
|
||||
|
||||
|
||||
/*! @brief Used to wrap a function or a member of a specified type. */
|
||||
template<auto>
|
||||
struct connect_arg_t {};
|
||||
|
||||
|
||||
/*! @brief Constant of type connect_arg_t used to disambiguate calls. */
|
||||
template<auto Func>
|
||||
constexpr connect_arg_t<Func> connect_arg{};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Basic delegate implementation.
|
||||
*
|
||||
@@ -16,129 +83,224 @@ namespace entt {
|
||||
* compile-time error unless the template parameter is a function type.
|
||||
*/
|
||||
template<typename>
|
||||
class Delegate;
|
||||
class delegate;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Utility class to send around functions and member functions.
|
||||
* @brief Utility class to use to send around functions and members.
|
||||
*
|
||||
* Unmanaged delegate for function pointers and member functions. Users of this
|
||||
* class are in charge of disconnecting instances before deleting them.
|
||||
* Unmanaged delegate for function pointers and members. Users of this class are
|
||||
* in charge of disconnecting instances before deleting them.
|
||||
*
|
||||
* A delegate can be used as general purpose invoker with no memory overhead for
|
||||
* free functions and member functions provided along with an instance on which
|
||||
* to invoke them.
|
||||
* free functions (with or without payload) and members provided along with an
|
||||
* instance on which to invoke them.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
*/
|
||||
template<typename Ret, typename... Args>
|
||||
class Delegate<Ret(Args...)> final {
|
||||
using proto_fn_type = Ret(void *, Args...);
|
||||
using stub_type = std::pair<void *, proto_fn_type *>;
|
||||
class delegate<Ret(Args...)> {
|
||||
using proto_fn_type = Ret(const void *, std::tuple<Args &&...>);
|
||||
|
||||
template<Ret(*Function)(Args...)>
|
||||
static Ret proto(void *, Args... args) {
|
||||
return (Function)(args...);
|
||||
template<auto Function, std::size_t... Index>
|
||||
void connect(std::index_sequence<Index...>) ENTT_NOEXCEPT {
|
||||
static_assert(std::is_invocable_r_v<Ret, decltype(Function), std::tuple_element_t<Index, std::tuple<Args...>>...>);
|
||||
data = nullptr;
|
||||
|
||||
fn = [](const void *, std::tuple<Args &&...> args) -> Ret {
|
||||
// Ret(...) makes void(...) eat the return values to avoid errors
|
||||
return Ret(std::invoke(Function, std::forward<std::tuple_element_t<Index, std::tuple<Args...>>>(std::get<Index>(args))...));
|
||||
};
|
||||
}
|
||||
|
||||
template<typename Class, Ret(Class:: *Member)(Args...)>
|
||||
static Ret proto(void *instance, Args... args) {
|
||||
return (static_cast<Class *>(instance)->*Member)(args...);
|
||||
template<auto Candidate, typename Type, std::size_t... Index>
|
||||
void connect(Type &value_or_instance, std::index_sequence<Index...>) ENTT_NOEXCEPT {
|
||||
static_assert(std::is_invocable_r_v<Ret, decltype(Candidate), Type &, std::tuple_element_t<Index, std::tuple<Args...>>...>);
|
||||
data = &value_or_instance;
|
||||
|
||||
fn = [](const void *payload, std::tuple<Args &&...> args) -> Ret {
|
||||
Type *curr = nullptr;
|
||||
|
||||
if constexpr(std::is_const_v<Type>) {
|
||||
curr = static_cast<Type *>(payload);
|
||||
} else {
|
||||
curr = static_cast<Type *>(const_cast<void *>(payload));
|
||||
}
|
||||
|
||||
// Ret(...) makes void(...) eat the return values to avoid errors
|
||||
return Ret(std::invoke(Candidate, *curr, std::forward<std::tuple_element_t<Index, std::tuple<Args...>>>(std::get<Index>(args))...));
|
||||
};
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Function type of the delegate. */
|
||||
using function_type = Ret(Args...);
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
Delegate() ENTT_NOEXCEPT
|
||||
: stub{}
|
||||
delegate() ENTT_NOEXCEPT
|
||||
: fn{nullptr}, data{nullptr}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Checks whether a delegate actually stores a listener.
|
||||
* @return True if the delegate is empty, false otherwise.
|
||||
*/
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
// no need to test also stub.first
|
||||
return !stub.second;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Binds a free function to a delegate.
|
||||
* @brief Constructs a delegate and connects a free function to it.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<Ret(*Function)(Args...)>
|
||||
void connect() ENTT_NOEXCEPT {
|
||||
stub = std::make_pair(nullptr, &proto<Function>);
|
||||
template<auto Function>
|
||||
delegate(connect_arg_t<Function>) ENTT_NOEXCEPT
|
||||
: delegate{}
|
||||
{
|
||||
connect<Function>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a member function for a given instance to a delegate.
|
||||
*
|
||||
* The delegate isn't responsible for the connected object. Users must
|
||||
* guarantee that the lifetime of the instance overcomes the one of the
|
||||
* delegate.
|
||||
*
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the delegate.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
* @brief Constructs a delegate and connects a member for a given instance
|
||||
* or a free function with payload.
|
||||
* @tparam Candidate Member or free function to connect to the delegate.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
* @param value_or_instance A valid reference that fits the purpose.
|
||||
*/
|
||||
template<typename Class, Ret(Class:: *Member)(Args...)>
|
||||
void connect(Class *instance) ENTT_NOEXCEPT {
|
||||
stub = std::make_pair(instance, &proto<Class, Member>);
|
||||
template<auto Candidate, typename Type>
|
||||
delegate(connect_arg_t<Candidate>, Type &value_or_instance) ENTT_NOEXCEPT
|
||||
: delegate{}
|
||||
{
|
||||
connect<Candidate>(value_or_instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function to a delegate.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<auto Function>
|
||||
void connect() ENTT_NOEXCEPT {
|
||||
constexpr auto extent = internal::function_extent_v<decltype(internal::to_function_pointer(std::declval<decltype(Function)>()))>;
|
||||
connect<Function>(std::make_index_sequence<extent>{});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a member function for a given instance or a free function
|
||||
* with payload to a delegate.
|
||||
*
|
||||
* The delegate isn't responsible for the connected object or the payload.
|
||||
* Users must always guarantee that the lifetime of the instance overcomes
|
||||
* the one of the delegate.<br/>
|
||||
* When used to connect a free function with payload, its signature must be
|
||||
* such that the instance is the first argument before the ones used to
|
||||
* define the delegate itself.
|
||||
*
|
||||
* @tparam Candidate Member or free function to connect to the delegate.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
* @param value_or_instance A valid reference that fits the purpose.
|
||||
*/
|
||||
template<auto Candidate, typename Type>
|
||||
void connect(Type &value_or_instance) ENTT_NOEXCEPT {
|
||||
constexpr auto extent = internal::function_extent_v<decltype(internal::to_function_pointer(std::declval<decltype(Candidate)>(), value_or_instance))>;
|
||||
connect<Candidate>(value_or_instance, std::make_index_sequence<extent>{});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resets a delegate.
|
||||
*
|
||||
* After a reset, a delegate can be safely invoked with no effect.
|
||||
* After a reset, a delegate cannot be invoked anymore.
|
||||
*/
|
||||
void reset() ENTT_NOEXCEPT {
|
||||
stub.second = nullptr;
|
||||
fn = nullptr;
|
||||
data = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the instance or the payload linked to a delegate, if any.
|
||||
* @return An opaque pointer to the underlying data.
|
||||
*/
|
||||
const void * instance() const ENTT_NOEXCEPT {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Triggers a delegate.
|
||||
*
|
||||
* The delegate invokes the underlying function and returns the result.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to trigger an invalid delegate results in undefined
|
||||
* behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* delegate has not yet been set.
|
||||
*
|
||||
* @param args Arguments to use to invoke the underlying function.
|
||||
* @return The value returned by the underlying function.
|
||||
*/
|
||||
Ret operator()(Args... args) const {
|
||||
return stub.second(stub.first, args...);
|
||||
ENTT_ASSERT(fn);
|
||||
return fn(data, std::forward_as_tuple(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the contents of the two delegates are different.
|
||||
*
|
||||
* Two delegates are identical if they contain the same listener.
|
||||
*
|
||||
* @param other Delegate with which to compare.
|
||||
* @return True if the two delegates are identical, false otherwise.
|
||||
* @brief Checks whether a delegate actually stores a listener.
|
||||
* @return False if the delegate is empty, true otherwise.
|
||||
*/
|
||||
bool operator==(const Delegate<Ret(Args...)> &other) const ENTT_NOEXCEPT {
|
||||
return stub.first == other.stub.first && stub.second == other.stub.second;
|
||||
explicit operator bool() const ENTT_NOEXCEPT {
|
||||
// no need to test also data
|
||||
return fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compares the contents of two delegates.
|
||||
* @param other Delegate with which to compare.
|
||||
* @return False if the two contents differ, true otherwise.
|
||||
*/
|
||||
bool operator==(const delegate<Ret(Args...)> &other) const ENTT_NOEXCEPT {
|
||||
return fn == other.fn && data == other.data;
|
||||
}
|
||||
|
||||
private:
|
||||
stub_type stub;
|
||||
proto_fn_type *fn;
|
||||
const void *data;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Checks if the contents of the two delegates are different.
|
||||
*
|
||||
* Two delegates are identical if they contain the same listener.
|
||||
*
|
||||
* @brief Compares the contents of two delegates.
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
* @param lhs A valid delegate object.
|
||||
* @param rhs A valid delegate object.
|
||||
* @return True if the two delegates are different, false otherwise.
|
||||
* @return True if the two contents differ, false otherwise.
|
||||
*/
|
||||
template<typename Ret, typename... Args>
|
||||
bool operator!=(const Delegate<Ret(Args...)> &lhs, const Delegate<Ret(Args...)> &rhs) ENTT_NOEXCEPT {
|
||||
bool operator!=(const delegate<Ret(Args...)> &lhs, const delegate<Ret(Args...)> &rhs) ENTT_NOEXCEPT {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Deduction guide.
|
||||
*
|
||||
* It allows to deduce the function type of the delegate directly from a
|
||||
* function provided to the constructor.
|
||||
*
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<auto Function>
|
||||
delegate(connect_arg_t<Function>) ENTT_NOEXCEPT
|
||||
-> delegate<std::remove_pointer_t<decltype(internal::to_function_pointer(Function))>>;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Deduction guide.
|
||||
*
|
||||
* It allows to deduce the function type of the delegate directly from a member
|
||||
* or a free function with payload provided to the constructor.
|
||||
*
|
||||
* @param value_or_instance A valid reference that fits the purpose.
|
||||
* @tparam Candidate Member or free function to connect to the delegate.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
*/
|
||||
template<auto Candidate, typename Type>
|
||||
delegate(connect_arg_t<Candidate>, Type &value_or_instance) ENTT_NOEXCEPT
|
||||
-> delegate<std::remove_pointer_t<decltype(internal::to_function_pointer(Candidate, value_or_instance))>>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <cstdint>
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "../core/family.hpp"
|
||||
#include "../core/type_traits.hpp"
|
||||
#include "sigh.hpp"
|
||||
|
||||
|
||||
@@ -22,77 +21,109 @@ namespace entt {
|
||||
* A dispatcher can be used either to trigger an immediate event or to enqueue
|
||||
* events to be published all together once per tick.<br/>
|
||||
* Listeners are provided in the form of member functions. For each event of
|
||||
* type `Event`, listeners must have the following function type:
|
||||
* @code{.cpp}
|
||||
* void(const Event &)
|
||||
* @endcode
|
||||
* type `Event`, listeners are such that they can be invoked with an argument of
|
||||
* type `const Event &`, no matter what the return type is.
|
||||
*
|
||||
* Member functions named `receive` are automatically detected and registered or
|
||||
* unregistered by the dispatcher. The type of the instances is `Class *` (a
|
||||
* naked pointer). It means that users must guarantee that the lifetimes of the
|
||||
* instances overcome the one of the dispatcher itself to avoid crashes.
|
||||
* The types of the instances are `Class &`. Users must guarantee that the
|
||||
* lifetimes of the objects overcome the one of the dispatcher itself to avoid
|
||||
* crashes.
|
||||
*/
|
||||
class Dispatcher final {
|
||||
using event_family = Family<struct InternalDispatcherEventFamily>;
|
||||
class dispatcher {
|
||||
using event_family = family<struct internal_dispatcher_event_family>;
|
||||
|
||||
template<typename Class, typename Event>
|
||||
using instance_type = typename SigH<void(const Event &)>::template instance_type<Class>;
|
||||
using instance_type = typename sigh<void(const Event &)>::template instance_type<Class>;
|
||||
|
||||
struct BaseSignalWrapper {
|
||||
virtual ~BaseSignalWrapper() = default;
|
||||
struct base_wrapper {
|
||||
virtual ~base_wrapper() = default;
|
||||
virtual void publish() = 0;
|
||||
};
|
||||
|
||||
template<typename Event>
|
||||
struct SignalWrapper final: BaseSignalWrapper {
|
||||
using sink_type = typename SigH<void(const Event &)>::sink_type;
|
||||
struct signal_wrapper: base_wrapper {
|
||||
using signal_type = sigh<void(const Event &)>;
|
||||
using sink_type = typename signal_type::sink_type;
|
||||
|
||||
void publish() override {
|
||||
const auto &curr = current++;
|
||||
for(const auto &event: events[current]) {
|
||||
signal.publish(event);
|
||||
}
|
||||
|
||||
events[current++].clear();
|
||||
current %= std::extent<decltype(events)>::value;
|
||||
std::for_each(events[curr].cbegin(), events[curr].cend(), [this](const auto &event) { signal.publish(event); });
|
||||
events[curr].clear();
|
||||
}
|
||||
|
||||
inline sink_type sink() ENTT_NOEXCEPT {
|
||||
return signal.sink();
|
||||
sink_type sink() ENTT_NOEXCEPT {
|
||||
return entt::sink{signal};
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void trigger(Args &&... args) {
|
||||
void trigger(Args &&... args) {
|
||||
signal.publish({ std::forward<Args>(args)... });
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void enqueue(Args &&... args) {
|
||||
events[current].push_back({ std::forward<Args>(args)... });
|
||||
void enqueue(Args &&... args) {
|
||||
events[current].emplace_back(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
private:
|
||||
SigH<void(const Event &)> signal{};
|
||||
signal_type signal{};
|
||||
std::vector<Event> events[2];
|
||||
int current{};
|
||||
};
|
||||
|
||||
struct wrapper_data {
|
||||
std::unique_ptr<base_wrapper> wrapper;
|
||||
ENTT_ID_TYPE runtime_type;
|
||||
};
|
||||
|
||||
template<typename Event>
|
||||
SignalWrapper<Event> & wrapper() {
|
||||
const auto type = event_family::type<Event>();
|
||||
static auto type() ENTT_NOEXCEPT {
|
||||
if constexpr(is_named_type_v<Event>) {
|
||||
return named_type_traits<Event>::value;
|
||||
} else {
|
||||
return event_family::type<Event>;
|
||||
}
|
||||
}
|
||||
|
||||
if(!(type < wrappers.size())) {
|
||||
wrappers.resize(type + 1);
|
||||
template<typename Event>
|
||||
signal_wrapper<Event> & assure() {
|
||||
const auto wtype = type<Event>();
|
||||
wrapper_data *wdata = nullptr;
|
||||
|
||||
if constexpr(is_named_type_v<Event>) {
|
||||
const auto it = std::find_if(wrappers.begin(), wrappers.end(), [wtype](const auto &candidate) {
|
||||
return candidate.wrapper && candidate.runtime_type == wtype;
|
||||
});
|
||||
|
||||
wdata = (it == wrappers.cend() ? &wrappers.emplace_back() : &(*it));
|
||||
} else {
|
||||
if(!(wtype < wrappers.size())) {
|
||||
wrappers.resize(wtype+1);
|
||||
}
|
||||
|
||||
wdata = &wrappers[wtype];
|
||||
|
||||
if(wdata->wrapper && wdata->runtime_type != wtype) {
|
||||
wrappers.emplace_back();
|
||||
std::swap(wrappers[wtype], wrappers.back());
|
||||
wdata = &wrappers[wtype];
|
||||
}
|
||||
}
|
||||
|
||||
if(!wrappers[type]) {
|
||||
wrappers[type] = std::make_unique<SignalWrapper<Event>>();
|
||||
if(!wdata->wrapper) {
|
||||
wdata->wrapper = std::make_unique<signal_wrapper<Event>>();
|
||||
wdata->runtime_type = wtype;
|
||||
}
|
||||
|
||||
return static_cast<SignalWrapper<Event> &>(*wrappers[type]);
|
||||
return static_cast<signal_wrapper<Event> &>(*wdata->wrapper);
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Type of sink for the given event. */
|
||||
template<typename Event>
|
||||
using sink_type = typename SignalWrapper<Event>::sink_type;
|
||||
using sink_type = typename signal_wrapper<Event>::sink_type;
|
||||
|
||||
/**
|
||||
* @brief Returns a sink object for the given event.
|
||||
@@ -101,19 +132,19 @@ public:
|
||||
*
|
||||
* The function type for a listener is:
|
||||
* @code{.cpp}
|
||||
* void(const Event &)
|
||||
* void(const Event &);
|
||||
* @endcode
|
||||
*
|
||||
* The order of invocation of the listeners isn't guaranteed.
|
||||
*
|
||||
* @sa SigH::Sink
|
||||
* @sa sink
|
||||
*
|
||||
* @tparam Event Type of event of which to get the sink.
|
||||
* @return A temporary sink object.
|
||||
*/
|
||||
template<typename Event>
|
||||
inline sink_type<Event> sink() ENTT_NOEXCEPT {
|
||||
return wrapper<Event>().sink();
|
||||
sink_type<Event> sink() ENTT_NOEXCEPT {
|
||||
return assure<Event>().sink();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,8 +158,22 @@ public:
|
||||
* @param args Arguments to use to construct the event.
|
||||
*/
|
||||
template<typename Event, typename... Args>
|
||||
inline void trigger(Args &&... args) {
|
||||
wrapper<Event>().trigger(std::forward<Args>(args)...);
|
||||
void trigger(Args &&... args) {
|
||||
assure<Event>().trigger(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Triggers an immediate event of the given type.
|
||||
*
|
||||
* All the listeners registered for the given type are immediately notified.
|
||||
* The event is discarded after the execution.
|
||||
*
|
||||
* @tparam Event Type of event to trigger.
|
||||
* @param event An instance of the given type of event.
|
||||
*/
|
||||
template<typename Event>
|
||||
void trigger(Event &&event) {
|
||||
assure<std::decay_t<Event>>().trigger(std::forward<Event>(event));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,13 +182,27 @@ public:
|
||||
* An event of the given type is queued. No listener is invoked. Use the
|
||||
* `update` member function to notify listeners when ready.
|
||||
*
|
||||
* @tparam Event Type of event to trigger.
|
||||
* @tparam Event Type of event to enqueue.
|
||||
* @tparam Args Types of arguments to use to construct the event.
|
||||
* @param args Arguments to use to construct the event.
|
||||
*/
|
||||
template<typename Event, typename... Args>
|
||||
inline void enqueue(Args &&... args) {
|
||||
wrapper<Event>().enqueue(std::forward<Args>(args)...);
|
||||
void enqueue(Args &&... args) {
|
||||
assure<Event>().enqueue(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enqueues an event of the given type.
|
||||
*
|
||||
* An event of the given type is queued. No listener is invoked. Use the
|
||||
* `update` member function to notify listeners when ready.
|
||||
*
|
||||
* @tparam Event Type of event to enqueue.
|
||||
* @param event An instance of the given type of event.
|
||||
*/
|
||||
template<typename Event>
|
||||
void enqueue(Event &&event) {
|
||||
assure<std::decay_t<Event>>().enqueue(std::forward<Event>(event));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,8 +215,8 @@ public:
|
||||
* @tparam Event Type of events to send.
|
||||
*/
|
||||
template<typename Event>
|
||||
inline void update() {
|
||||
wrapper<Event>().publish();
|
||||
void update() {
|
||||
assure<Event>().publish();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,18 +226,18 @@ public:
|
||||
* delivered to the registered listeners. It's responsibility of the users
|
||||
* to reduce at a minimum the time spent in the bodies of the listeners.
|
||||
*/
|
||||
inline void update() const {
|
||||
void update() const {
|
||||
for(auto pos = wrappers.size(); pos; --pos) {
|
||||
auto &wrapper = wrappers[pos-1];
|
||||
auto &wdata = wrappers[pos-1];
|
||||
|
||||
if(wrapper) {
|
||||
wrapper->publish();
|
||||
if(wdata.wrapper) {
|
||||
wdata.wrapper->publish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<BaseSignalWrapper>> wrappers;
|
||||
std::vector<wrapper_data> wrappers;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include "../config/config.h"
|
||||
#include "../core/family.hpp"
|
||||
#include "../core/type_traits.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
@@ -23,11 +23,11 @@ namespace entt {
|
||||
* The emitter class template follows the CRTP idiom. To create a custom emitter
|
||||
* type, derived classes must inherit directly from the base class as:
|
||||
*
|
||||
* ```cpp
|
||||
* struct MyEmitter: Emitter<MyEmitter> {
|
||||
* @code{.cpp}
|
||||
* struct my_emitter: emitter<my_emitter> {
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
* @endcode
|
||||
*
|
||||
* Handlers for the type of events are created internally on the fly. It's not
|
||||
* required to specify in advance the full list of accepted types.<br/>
|
||||
@@ -39,17 +39,17 @@ namespace entt {
|
||||
* @tparam Derived Actual type of emitter that extends the class template.
|
||||
*/
|
||||
template<typename Derived>
|
||||
class Emitter {
|
||||
using handler_family = Family<struct InternalEmitterHandlerFamily>;
|
||||
class emitter {
|
||||
using handler_family = family<struct internal_emitter_handler_family>;
|
||||
|
||||
struct BaseHandler {
|
||||
virtual ~BaseHandler() = default;
|
||||
struct base_handler {
|
||||
virtual ~base_handler() = default;
|
||||
virtual bool empty() const ENTT_NOEXCEPT = 0;
|
||||
virtual void clear() ENTT_NOEXCEPT = 0;
|
||||
};
|
||||
|
||||
template<typename Event>
|
||||
struct Handler final: BaseHandler {
|
||||
struct event_handler: base_handler {
|
||||
using listener_type = std::function<void(const Event &, Derived &)>;
|
||||
using element_type = std::pair<bool, listener_type>;
|
||||
using container_type = std::list<element_type>;
|
||||
@@ -58,27 +58,27 @@ class Emitter {
|
||||
bool empty() const ENTT_NOEXCEPT override {
|
||||
auto pred = [](auto &&element) { return element.first; };
|
||||
|
||||
return std::all_of(onceL.cbegin(), onceL.cend(), pred) &&
|
||||
std::all_of(onL.cbegin(), onL.cend(), pred);
|
||||
return std::all_of(once_list.cbegin(), once_list.cend(), pred) &&
|
||||
std::all_of(on_list.cbegin(), on_list.cend(), pred);
|
||||
}
|
||||
|
||||
void clear() ENTT_NOEXCEPT override {
|
||||
if(publishing) {
|
||||
auto func = [](auto &&element) { element.first = true; };
|
||||
std::for_each(onceL.begin(), onceL.end(), func);
|
||||
std::for_each(onL.begin(), onL.end(), func);
|
||||
std::for_each(once_list.begin(), once_list.end(), func);
|
||||
std::for_each(on_list.begin(), on_list.end(), func);
|
||||
} else {
|
||||
onceL.clear();
|
||||
onL.clear();
|
||||
once_list.clear();
|
||||
on_list.clear();
|
||||
}
|
||||
}
|
||||
|
||||
inline connection_type once(listener_type listener) {
|
||||
return onceL.emplace(onceL.cend(), false, std::move(listener));
|
||||
connection_type once(listener_type listener) {
|
||||
return once_list.emplace(once_list.cend(), false, std::move(listener));
|
||||
}
|
||||
|
||||
inline connection_type on(listener_type listener) {
|
||||
return onL.emplace(onL.cend(), false, std::move(listener));
|
||||
connection_type on(listener_type listener) {
|
||||
return on_list.emplace(on_list.cend(), false, std::move(listener));
|
||||
}
|
||||
|
||||
void erase(connection_type conn) ENTT_NOEXCEPT {
|
||||
@@ -86,14 +86,14 @@ class Emitter {
|
||||
|
||||
if(!publishing) {
|
||||
auto pred = [](auto &&element) { return element.first; };
|
||||
onceL.remove_if(pred);
|
||||
onL.remove_if(pred);
|
||||
once_list.remove_if(pred);
|
||||
on_list.remove_if(pred);
|
||||
}
|
||||
}
|
||||
|
||||
void publish(const Event &event, Derived &ref) {
|
||||
container_type currentL;
|
||||
onceL.swap(currentL);
|
||||
container_type swap_list;
|
||||
once_list.swap(swap_list);
|
||||
|
||||
auto func = [&event, &ref](auto &&element) {
|
||||
return element.first ? void() : element.second(event, ref);
|
||||
@@ -101,39 +101,71 @@ class Emitter {
|
||||
|
||||
publishing = true;
|
||||
|
||||
std::for_each(onL.rbegin(), onL.rend(), func);
|
||||
std::for_each(currentL.rbegin(), currentL.rend(), func);
|
||||
std::for_each(on_list.rbegin(), on_list.rend(), func);
|
||||
std::for_each(swap_list.rbegin(), swap_list.rend(), func);
|
||||
|
||||
publishing = false;
|
||||
|
||||
onL.remove_if([](auto &&element) { return element.first; });
|
||||
on_list.remove_if([](auto &&element) { return element.first; });
|
||||
}
|
||||
|
||||
private:
|
||||
bool publishing{false};
|
||||
container_type onceL{};
|
||||
container_type onL{};
|
||||
container_type once_list{};
|
||||
container_type on_list{};
|
||||
};
|
||||
|
||||
struct handler_data {
|
||||
std::unique_ptr<base_handler> handler;
|
||||
ENTT_ID_TYPE runtime_type;
|
||||
};
|
||||
|
||||
template<typename Event>
|
||||
Handler<Event> & handler() ENTT_NOEXCEPT {
|
||||
const std::size_t family = handler_family::type<Event>();
|
||||
static auto type() ENTT_NOEXCEPT {
|
||||
if constexpr(is_named_type_v<Event>) {
|
||||
return named_type_traits<Event>::value;
|
||||
} else {
|
||||
return handler_family::type<Event>;
|
||||
}
|
||||
}
|
||||
|
||||
if(!(family < handlers.size())) {
|
||||
handlers.resize(family+1);
|
||||
template<typename Event>
|
||||
event_handler<Event> * assure() const ENTT_NOEXCEPT {
|
||||
const auto htype = type<Event>();
|
||||
handler_data *hdata = nullptr;
|
||||
|
||||
if constexpr(is_named_type_v<Event>) {
|
||||
const auto it = std::find_if(handlers.begin(), handlers.end(), [htype](const auto &candidate) {
|
||||
return candidate.handler && candidate.runtime_type == htype;
|
||||
});
|
||||
|
||||
hdata = (it == handlers.cend() ? &handlers.emplace_back() : &(*it));
|
||||
} else {
|
||||
if(!(htype < handlers.size())) {
|
||||
handlers.resize(htype+1);
|
||||
}
|
||||
|
||||
hdata = &handlers[htype];
|
||||
|
||||
if(hdata->handler && hdata->runtime_type != htype) {
|
||||
handlers.emplace_back();
|
||||
std::swap(handlers[htype], handlers.back());
|
||||
hdata = &handlers[htype];
|
||||
}
|
||||
}
|
||||
|
||||
if(!handlers[family]) {
|
||||
handlers[family] = std::make_unique<Handler<Event>>();
|
||||
if(!hdata->handler) {
|
||||
hdata->handler = std::make_unique<event_handler<Event>>();
|
||||
hdata->runtime_type = htype;
|
||||
}
|
||||
|
||||
return static_cast<Handler<Event> &>(*handlers[family]);
|
||||
return static_cast<event_handler<Event> *>(hdata->handler.get());
|
||||
}
|
||||
|
||||
public:
|
||||
/** @brief Type of listeners accepted for the given event. */
|
||||
template<typename Event>
|
||||
using Listener = typename Handler<Event>::listener_type;
|
||||
using listener = typename event_handler<Event>::listener_type;
|
||||
|
||||
/**
|
||||
* @brief Generic connection type for events.
|
||||
@@ -145,56 +177,35 @@ public:
|
||||
* @tparam Event Type of event for which the connection is created.
|
||||
*/
|
||||
template<typename Event>
|
||||
struct Connection final: private Handler<Event>::connection_type {
|
||||
struct connection: private event_handler<Event>::connection_type {
|
||||
/** @brief Event emitters are friend classes of connections. */
|
||||
friend class Emitter;
|
||||
friend class emitter;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
Connection() ENTT_NOEXCEPT = default;
|
||||
connection() ENTT_NOEXCEPT = default;
|
||||
|
||||
/**
|
||||
* @brief Creates a connection that wraps its underlying instance.
|
||||
* @param conn A connection object to wrap.
|
||||
*/
|
||||
Connection(typename Handler<Event>::connection_type conn)
|
||||
: Handler<Event>::connection_type{std::move(conn)}
|
||||
connection(typename event_handler<Event>::connection_type conn)
|
||||
: event_handler<Event>::connection_type{std::move(conn)}
|
||||
{}
|
||||
|
||||
/*! @brief Default copy constructor. */
|
||||
Connection(const Connection &) = default;
|
||||
/*! @brief Default move constructor. */
|
||||
Connection(Connection &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Default copy assignment operator.
|
||||
* @return This connection.
|
||||
*/
|
||||
Connection & operator=(const Connection &) = default;
|
||||
|
||||
/**
|
||||
* @brief Default move assignment operator.
|
||||
* @return This connection.
|
||||
*/
|
||||
Connection & operator=(Connection &&) = default;
|
||||
};
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
Emitter() ENTT_NOEXCEPT = default;
|
||||
emitter() ENTT_NOEXCEPT = default;
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
virtual ~Emitter() ENTT_NOEXCEPT {
|
||||
static_assert(std::is_base_of<Emitter<Derived>, Derived>::value, "!");
|
||||
virtual ~emitter() ENTT_NOEXCEPT {
|
||||
static_assert(std::is_base_of_v<emitter<Derived>, Derived>);
|
||||
}
|
||||
|
||||
/*! @brief Copying an emitter isn't allowed. */
|
||||
Emitter(const Emitter &) = delete;
|
||||
/*! @brief Default move constructor. */
|
||||
Emitter(Emitter &&) = default;
|
||||
emitter(emitter &&) = default;
|
||||
|
||||
/*! @brief Copying an emitter isn't allowed. @return This emitter. */
|
||||
Emitter & operator=(const Emitter &) = delete;
|
||||
/*! @brief Default move assignment operator. @return This emitter. */
|
||||
Emitter & operator=(Emitter &&) = default;
|
||||
emitter & operator=(emitter &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Emits the given event.
|
||||
@@ -209,7 +220,7 @@ public:
|
||||
*/
|
||||
template<typename Event, typename... Args>
|
||||
void publish(Args &&... args) {
|
||||
handler<Event>().publish({ std::forward<Args>(args)... }, *static_cast<Derived *>(this));
|
||||
assure<Event>()->publish({ std::forward<Args>(args)... }, *static_cast<Derived *>(this));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,12 +240,12 @@ public:
|
||||
* instances for later uses.
|
||||
*
|
||||
* @tparam Event Type of event to which to connect the listener.
|
||||
* @param listener The listener to register.
|
||||
* @param instance The listener to register.
|
||||
* @return Connection object that can be used to disconnect the listener.
|
||||
*/
|
||||
template<typename Event>
|
||||
Connection<Event> on(Listener<Event> listener) {
|
||||
return handler<Event>().on(std::move(listener));
|
||||
connection<Event> on(listener<Event> instance) {
|
||||
return assure<Event>()->on(std::move(instance));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -254,12 +265,12 @@ public:
|
||||
* instances for later uses.
|
||||
*
|
||||
* @tparam Event Type of event to which to connect the listener.
|
||||
* @param listener The listener to register.
|
||||
* @param instance The listener to register.
|
||||
* @return Connection object that can be used to disconnect the listener.
|
||||
*/
|
||||
template<typename Event>
|
||||
Connection<Event> once(Listener<Event> listener) {
|
||||
return handler<Event>().once(std::move(listener));
|
||||
connection<Event> once(listener<Event> instance) {
|
||||
return assure<Event>()->once(std::move(instance));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,8 +283,8 @@ public:
|
||||
* @param conn A valid connection.
|
||||
*/
|
||||
template<typename Event>
|
||||
void erase(Connection<Event> conn) ENTT_NOEXCEPT {
|
||||
handler<Event>().erase(std::move(conn));
|
||||
void erase(connection<Event> conn) ENTT_NOEXCEPT {
|
||||
assure<Event>()->erase(std::move(conn));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -286,7 +297,7 @@ public:
|
||||
*/
|
||||
template<typename Event>
|
||||
void clear() ENTT_NOEXCEPT {
|
||||
handler<Event>().clear();
|
||||
assure<Event>()->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -296,8 +307,8 @@ public:
|
||||
* results in undefined behavior.
|
||||
*/
|
||||
void clear() ENTT_NOEXCEPT {
|
||||
std::for_each(handlers.begin(), handlers.end(), [](auto &&handler) {
|
||||
return handler ? handler->clear() : void();
|
||||
std::for_each(handlers.begin(), handlers.end(), [](auto &&hdata) {
|
||||
return hdata.handler ? hdata.handler->clear() : void();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -308,11 +319,7 @@ public:
|
||||
*/
|
||||
template<typename Event>
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
const std::size_t family = handler_family::type<Event>();
|
||||
|
||||
return (!(family < handlers.size()) ||
|
||||
!handlers[family] ||
|
||||
static_cast<Handler<Event> &>(*handlers[family]).empty());
|
||||
return assure<Event>()->empty();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -320,13 +327,13 @@ public:
|
||||
* @return True if there are no listeners registered, false otherwise.
|
||||
*/
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
return std::all_of(handlers.cbegin(), handlers.cend(), [](auto &&handler) {
|
||||
return !handler || handler->empty();
|
||||
return std::all_of(handlers.cbegin(), handlers.cend(), [](auto &&hdata) {
|
||||
return !hdata.handler || hdata.handler->empty();
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<BaseHandler>> handlers{};
|
||||
mutable std::vector<handler_data> handlers{};
|
||||
};
|
||||
|
||||
|
||||
|
||||
27
src/entt/signal/fwd.hpp
Normal file
27
src/entt/signal/fwd.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef ENTT_SIGNAL_FWD_HPP
|
||||
#define ENTT_SIGNAL_FWD_HPP
|
||||
|
||||
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/*! @class delegate */
|
||||
template<typename>
|
||||
class delegate;
|
||||
|
||||
/*! @class sink */
|
||||
template<typename>
|
||||
class sink;
|
||||
|
||||
/*! @class sigh */
|
||||
template<typename>
|
||||
class sigh;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_SIGNAL_FWD_HPP
|
||||
@@ -5,90 +5,18 @@
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "delegate.hpp"
|
||||
#include "fwd.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
namespace internal {
|
||||
|
||||
|
||||
/**
|
||||
* @cond TURN_OFF_DOXYGEN
|
||||
* Internal details not to be documented.
|
||||
*/
|
||||
|
||||
|
||||
template<typename, typename>
|
||||
struct Invoker;
|
||||
|
||||
|
||||
template<typename Ret, typename... Args, typename Collector>
|
||||
struct Invoker<Ret(Args...), Collector> {
|
||||
using proto_fn_type = Ret(void *, Args...);
|
||||
using call_type = std::pair<void *, proto_fn_type *>;
|
||||
|
||||
virtual ~Invoker() = default;
|
||||
|
||||
bool invoke(Collector &collector, proto_fn_type *proto, void *instance, Args... args) const {
|
||||
return collector(proto(instance, args...));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename... Args, typename Collector>
|
||||
struct Invoker<void(Args...), Collector> {
|
||||
using proto_fn_type = void(void *, Args...);
|
||||
using call_type = std::pair<void *, proto_fn_type *>;
|
||||
|
||||
virtual ~Invoker() = default;
|
||||
|
||||
bool invoke(Collector &, proto_fn_type *proto, void *instance, Args... args) const {
|
||||
return (proto(instance, args...), true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename Ret>
|
||||
struct NullCollector final {
|
||||
using result_type = Ret;
|
||||
bool operator()(result_type) const ENTT_NOEXCEPT { return true; }
|
||||
};
|
||||
|
||||
|
||||
template<>
|
||||
struct NullCollector<void> final {
|
||||
using result_type = void;
|
||||
bool operator()() const ENTT_NOEXCEPT { return true; }
|
||||
};
|
||||
|
||||
|
||||
template<typename>
|
||||
struct DefaultCollector;
|
||||
|
||||
|
||||
template<typename Ret, typename... Args>
|
||||
struct DefaultCollector<Ret(Args...)> final {
|
||||
using collector_type = NullCollector<Ret>;
|
||||
};
|
||||
|
||||
|
||||
template<typename Function>
|
||||
using DefaultCollectorType = typename DefaultCollector<Function>::collector_type;
|
||||
|
||||
|
||||
/**
|
||||
* Internal details not to be documented.
|
||||
* @endcond TURN_OFF_DOXYGEN
|
||||
*/
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Sink implementation.
|
||||
* @brief Sink class.
|
||||
*
|
||||
* Primary template isn't defined on purpose. All the specializations give a
|
||||
* compile-time error unless the template parameter is a function type.
|
||||
@@ -96,172 +24,46 @@ using DefaultCollectorType = typename DefaultCollector<Function>::collector_type
|
||||
* @tparam Function A valid function type.
|
||||
*/
|
||||
template<typename Function>
|
||||
class Sink;
|
||||
class sink;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Unmanaged signal handler declaration.
|
||||
* @brief Unmanaged signal handler.
|
||||
*
|
||||
* Primary template isn't defined on purpose. All the specializations give a
|
||||
* compile-time error unless the template parameter is a function type.
|
||||
*
|
||||
* @tparam Function A valid function type.
|
||||
* @tparam Collector Type of collector to use, if any.
|
||||
*/
|
||||
template<typename Function, typename Collector = internal::DefaultCollectorType<Function>>
|
||||
class SigH;
|
||||
template<typename Function>
|
||||
class sigh;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Sink implementation.
|
||||
* @brief Unmanaged signal handler.
|
||||
*
|
||||
* A sink is an opaque object used to connect listeners to signals.<br/>
|
||||
* The function type for a listener is the one of the signal to which it
|
||||
* belongs.
|
||||
*
|
||||
* The clear separation between a signal and a sink permits to store the
|
||||
* former as private data member without exposing the publish functionality
|
||||
* to the users of a class.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
*/
|
||||
template<typename Ret, typename... Args>
|
||||
class Sink<Ret(Args...)> final {
|
||||
/*! @brief A signal is allowed to create sinks. */
|
||||
template<typename, typename>
|
||||
friend class SigH;
|
||||
|
||||
using proto_fn_type = Ret(void *, Args...);
|
||||
using call_type = std::pair<void *, proto_fn_type *>;
|
||||
|
||||
template<Ret(*Function)(Args...)>
|
||||
static Ret proto(void *, Args... args) {
|
||||
return (Function)(args...);
|
||||
}
|
||||
|
||||
template<typename Class, Ret(Class:: *Member)(Args... args)>
|
||||
static Ret proto(void *instance, Args... args) {
|
||||
return (static_cast<Class *>(instance)->*Member)(args...);
|
||||
}
|
||||
|
||||
Sink(std::vector<call_type> &calls) ENTT_NOEXCEPT
|
||||
: calls{calls}
|
||||
{}
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Connects a free function to a signal.
|
||||
*
|
||||
* The signal handler performs checks to avoid multiple connections for
|
||||
* free functions.
|
||||
*
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<Ret(*Function)(Args...)>
|
||||
void connect() {
|
||||
disconnect<Function>();
|
||||
calls.emplace_back(nullptr, &proto<Function>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a member function for a given instance to a signal.
|
||||
*
|
||||
* The signal isn't responsible for the connected object. Users must
|
||||
* guarantee that the lifetime of the instance overcomes the one of the
|
||||
* signal. On the other side, the signal handler performs checks to
|
||||
* avoid multiple connections for the same member function of a given
|
||||
* instance.
|
||||
*
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template <typename Class, Ret(Class:: *Member)(Args...) = &Class::receive>
|
||||
void connect(Class *instance) {
|
||||
disconnect<Class, Member>(instance);
|
||||
calls.emplace_back(instance, &proto<Class, Member>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a free function from a signal.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<Ret(*Function)(Args...)>
|
||||
void disconnect() {
|
||||
call_type target{nullptr, &proto<Function>};
|
||||
calls.erase(std::remove(calls.begin(), calls.end(), std::move(target)), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects the given member function from a signal.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class, Ret(Class:: *Member)(Args...)>
|
||||
void disconnect(Class *instance) {
|
||||
call_type target{instance, &proto<Class, Member>};
|
||||
calls.erase(std::remove(calls.begin(), calls.end(), std::move(target)), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes all existing connections for the given instance.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class>
|
||||
void disconnect(Class *instance) {
|
||||
auto func = [instance](const call_type &call) { return call.first == instance; };
|
||||
calls.erase(std::remove_if(calls.begin(), calls.end(), std::move(func)), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects all the listeners from a signal.
|
||||
*/
|
||||
void disconnect() {
|
||||
calls.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<call_type> &calls;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Unmanaged signal handler definition.
|
||||
*
|
||||
* Unmanaged signal handler. It works directly with naked pointers to classes
|
||||
* and pointers to member functions as well as pointers to free functions. Users
|
||||
* of this class are in charge of disconnecting instances before deleting them.
|
||||
* It works directly with references to classes and pointers to member functions
|
||||
* as well as pointers to free functions. Users of this class are in charge of
|
||||
* disconnecting instances before deleting them.
|
||||
*
|
||||
* This class serves mainly two purposes:
|
||||
*
|
||||
* * Creating signals used later to notify a bunch of listeners.
|
||||
* * Creating signals to use later to notify a bunch of listeners.
|
||||
* * Collecting results from a set of functions like in a voting system.
|
||||
*
|
||||
* The default collector does nothing. To properly collect data, define and use
|
||||
* a class that has a call operator the signature of which is `bool(Param)` and:
|
||||
*
|
||||
* * `Param` is a type to which `Ret` can be converted.
|
||||
* * The return type is true if the handler must stop collecting data, false
|
||||
* otherwise.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
* @tparam Collector Type of collector to use, if any.
|
||||
*/
|
||||
template<typename Ret, typename... Args, typename Collector>
|
||||
class SigH<Ret(Args...), Collector> final: private internal::Invoker<Ret(Args...), Collector> {
|
||||
using call_type = typename internal::Invoker<Ret(Args...), Collector>::call_type;
|
||||
template<typename Ret, typename... Args>
|
||||
class sigh<Ret(Args...)> {
|
||||
/*! @brief A sink is allowed to modify a signal. */
|
||||
friend class sink<Ret(Args...)>;
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename std::vector<call_type>::size_type;
|
||||
/*! @brief Collector type. */
|
||||
using collector_type = Collector;
|
||||
using size_type = typename std::vector<delegate<Ret(Args...)>>::size_type;
|
||||
/*! @brief Sink type. */
|
||||
using sink_type = Sink<Ret(Args...)>;
|
||||
using sink_type = entt::sink<Ret(Args...)>;
|
||||
|
||||
/**
|
||||
* @brief Instance type when it comes to connecting member functions.
|
||||
@@ -286,19 +88,6 @@ public:
|
||||
return calls.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a sink object for the given signal.
|
||||
*
|
||||
* A sink is an opaque object used to connect listeners to signals.<br/>
|
||||
* The function type for a listener is the one of the signal to which it
|
||||
* belongs. The order of invocation of the listeners isn't guaranteed.
|
||||
*
|
||||
* @return A temporary sink object.
|
||||
*/
|
||||
sink_type sink() ENTT_NOEXCEPT {
|
||||
return { calls };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Triggers a signal.
|
||||
*
|
||||
@@ -308,72 +97,346 @@ public:
|
||||
*/
|
||||
void publish(Args... args) const {
|
||||
for(auto pos = calls.size(); pos; --pos) {
|
||||
auto &call = calls[pos-1];
|
||||
call.second(call.first, args...);
|
||||
calls[pos-1](args...);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Collects return values from the listeners.
|
||||
*
|
||||
* The collector must expose a call operator with the following properties:
|
||||
*
|
||||
* * The return type is either `void` or such that it's convertible to
|
||||
* `bool`. In the second case, a true value will stop the iteration.
|
||||
* * The list of parameters is empty if `Ret` is `void`, otherwise it
|
||||
* contains a single element such that `Ret` is convertible to it.
|
||||
*
|
||||
* @tparam Func Type of collector to use, if any.
|
||||
* @param func A valid function object.
|
||||
* @param args Arguments to use to invoke listeners.
|
||||
* @return An instance of the collector filled with collected data.
|
||||
*/
|
||||
collector_type collect(Args... args) const {
|
||||
collector_type collector;
|
||||
template<typename Func>
|
||||
void collect(Func func, Args... args) const {
|
||||
bool stop = false;
|
||||
|
||||
for(auto &&call: calls) {
|
||||
if(!this->invoke(collector, call.second, call.first, args...)) {
|
||||
break;
|
||||
for(auto pos = calls.size(); pos && !stop; --pos) {
|
||||
if constexpr(std::is_void_v<Ret>) {
|
||||
if constexpr(std::is_invocable_r_v<bool, Func>) {
|
||||
calls[pos-1](args...);
|
||||
stop = func();
|
||||
} else {
|
||||
calls[pos-1](args...);
|
||||
func();
|
||||
}
|
||||
} else {
|
||||
if constexpr(std::is_invocable_r_v<bool, Func, Ret>) {
|
||||
stop = func(calls[pos-1](args...));
|
||||
} else {
|
||||
func(calls[pos-1](args...));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return collector;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Swaps listeners between the two signals.
|
||||
* @param lhs A valid signal object.
|
||||
* @param rhs A valid signal object.
|
||||
*/
|
||||
friend void swap(SigH &lhs, SigH &rhs) {
|
||||
using std::swap;
|
||||
swap(lhs.calls, rhs.calls);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the contents of the two signals are identical.
|
||||
*
|
||||
* Two signals are identical if they have the same size and the same
|
||||
* listeners registered exactly in the same order.
|
||||
*
|
||||
* @param other Signal with which to compare.
|
||||
* @return True if the two signals are identical, false otherwise.
|
||||
*/
|
||||
bool operator==(const SigH &other) const ENTT_NOEXCEPT {
|
||||
return std::equal(calls.cbegin(), calls.cend(), other.calls.cbegin(), other.calls.cend());
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<call_type> calls;
|
||||
std::vector<delegate<Ret(Args...)>> calls;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Checks if the contents of the two signals are different.
|
||||
* @brief Connection class.
|
||||
*
|
||||
* Two signals are identical if they have the same size and the same
|
||||
* listeners registered exactly in the same order.
|
||||
* Opaque object the aim of which is to allow users to release an already
|
||||
* estabilished connection without having to keep a reference to the signal or
|
||||
* the sink that generated it.
|
||||
*/
|
||||
class connection {
|
||||
/*! @brief A sink is allowed to create connection objects. */
|
||||
template<typename>
|
||||
friend class sink;
|
||||
|
||||
connection(delegate<void(void *)> fn, void *ref)
|
||||
: disconnect{fn}, signal{ref}
|
||||
{}
|
||||
|
||||
public:
|
||||
/*! @brief Default constructor. */
|
||||
connection() = default;
|
||||
|
||||
/*! @brief Default copy constructor. */
|
||||
connection(const connection &) = default;
|
||||
|
||||
/**
|
||||
* @brief Default move constructor.
|
||||
* @param other The instance to move from.
|
||||
*/
|
||||
connection(connection &&other)
|
||||
: connection{}
|
||||
{
|
||||
std::swap(disconnect, other.disconnect);
|
||||
std::swap(signal, other.signal);
|
||||
}
|
||||
|
||||
/*! @brief Default copy assignment operator. @return This connection. */
|
||||
connection & operator=(const connection &) = default;
|
||||
|
||||
/**
|
||||
* @brief Default move assignment operator.
|
||||
* @param other The instance to move from.
|
||||
* @return This connection.
|
||||
*/
|
||||
connection & operator=(connection &&other) {
|
||||
if(this != &other) {
|
||||
auto tmp{std::move(other)};
|
||||
disconnect = tmp.disconnect;
|
||||
signal = tmp.signal;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks whether a connection is properly initialized.
|
||||
* @return True if the connection is properly initialized, false otherwise.
|
||||
*/
|
||||
explicit operator bool() const ENTT_NOEXCEPT {
|
||||
return static_cast<bool>(disconnect);
|
||||
}
|
||||
|
||||
/*! @brief Breaks the connection. */
|
||||
void release() {
|
||||
if(disconnect) {
|
||||
disconnect(signal);
|
||||
disconnect.reset();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
delegate<void(void *)> disconnect;
|
||||
void *signal{};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Scoped connection class.
|
||||
*
|
||||
* Opaque object the aim of which is to allow users to release an already
|
||||
* estabilished connection without having to keep a reference to the signal or
|
||||
* the sink that generated it.<br/>
|
||||
* A scoped connection automatically breaks the link between the two objects
|
||||
* when it goes out of scope.
|
||||
*/
|
||||
struct scoped_connection: private connection {
|
||||
/*! @brief Default constructor. */
|
||||
scoped_connection() = default;
|
||||
|
||||
/**
|
||||
* @brief Constructs a scoped connection from a basic connection.
|
||||
* @param conn A valid connection object.
|
||||
*/
|
||||
scoped_connection(const connection &conn)
|
||||
: connection{conn}
|
||||
{}
|
||||
|
||||
/*! @brief Default copy constructor, deleted on purpose. */
|
||||
scoped_connection(const scoped_connection &) = delete;
|
||||
|
||||
/*! @brief Default move constructor. */
|
||||
scoped_connection(scoped_connection &&) = default;
|
||||
|
||||
/*! @brief Automatically breaks the link on destruction. */
|
||||
~scoped_connection() {
|
||||
connection::release();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Default copy assignment operator, deleted on purpose.
|
||||
* @return This scoped connection.
|
||||
*/
|
||||
scoped_connection & operator=(const scoped_connection &) = delete;
|
||||
|
||||
/**
|
||||
* @brief Default move assignment operator.
|
||||
* @return This scoped connection.
|
||||
*/
|
||||
scoped_connection & operator=(scoped_connection &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Copies a connection.
|
||||
* @param other The connection object to copy.
|
||||
* @return This scoped connection.
|
||||
*/
|
||||
scoped_connection & operator=(const connection &other) {
|
||||
static_cast<connection &>(*this) = other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Moves a connection.
|
||||
* @param other The connection object to move.
|
||||
* @return This scoped connection.
|
||||
*/
|
||||
scoped_connection & operator=(connection &&other) {
|
||||
static_cast<connection &>(*this) = std::move(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
using connection::operator bool;
|
||||
using connection::release;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Sink class.
|
||||
*
|
||||
* A sink is used to connect listeners to signals and to disconnect them.<br/>
|
||||
* The function type for a listener is the one of the signal to which it
|
||||
* belongs.
|
||||
*
|
||||
* The clear separation between a signal and a sink permits to store the former
|
||||
* as private data member without exposing the publish functionality to the
|
||||
* users of the class.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
* @param lhs A valid signal object.
|
||||
* @param rhs A valid signal object.
|
||||
* @return True if the two signals are different, false otherwise.
|
||||
*/
|
||||
template<typename Ret, typename... Args>
|
||||
bool operator!=(const SigH<Ret(Args...)> &lhs, const SigH<Ret(Args...)> &rhs) ENTT_NOEXCEPT {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
class sink<Ret(Args...)> {
|
||||
using signal_type = sigh<Ret(Args...)>;
|
||||
|
||||
template<auto Candidate, typename Type>
|
||||
static void release(Type &value_or_instance, void *signal) {
|
||||
sink{*static_cast<signal_type *>(signal)}.disconnect<Candidate>(value_or_instance);
|
||||
}
|
||||
|
||||
template<auto Function>
|
||||
static void release(void *signal) {
|
||||
sink{*static_cast<signal_type *>(signal)}.disconnect<Function>();
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a sink that is allowed to modify a given signal.
|
||||
* @param ref A valid reference to a signal object.
|
||||
*/
|
||||
sink(sigh<Ret(Args...)> &ref) ENTT_NOEXCEPT
|
||||
: signal{&ref}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Returns false if at least a listener is connected to the sink.
|
||||
* @return True if the sink has no listeners connected, false otherwise.
|
||||
*/
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
return signal->calls.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function to a signal.
|
||||
*
|
||||
* The signal handler performs checks to avoid multiple connections for free
|
||||
* functions.
|
||||
*
|
||||
* @tparam Function A valid free function pointer.
|
||||
* @return A properly initialized connection object.
|
||||
*/
|
||||
template<auto Function>
|
||||
connection connect() {
|
||||
disconnect<Function>();
|
||||
delegate<void(void *)> conn{};
|
||||
conn.template connect<&release<Function>>();
|
||||
signal->calls.emplace_back(delegate<Ret(Args...)>{connect_arg<Function>});
|
||||
return { std::move(conn), signal };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a member function or a free function with payload to a
|
||||
* signal.
|
||||
*
|
||||
* The signal isn't responsible for the connected object or the payload.
|
||||
* Users must always guarantee that the lifetime of the instance overcomes
|
||||
* the one of the delegate. On the other side, the signal handler performs
|
||||
* checks to avoid multiple connections for the same function.<br/>
|
||||
* When used to connect a free function with payload, its signature must be
|
||||
* such that the instance is the first argument before the ones used to
|
||||
* define the delegate itself.
|
||||
*
|
||||
* @tparam Candidate Member or free function to connect to the signal.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
* @param value_or_instance A valid reference that fits the purpose.
|
||||
* @return A properly initialized connection object.
|
||||
*/
|
||||
template<auto Candidate, typename Type>
|
||||
connection connect(Type &value_or_instance) {
|
||||
disconnect<Candidate>(value_or_instance);
|
||||
delegate<void(void *)> conn{};
|
||||
conn.template connect<&sink::release<Candidate, Type>>(value_or_instance);
|
||||
signal->calls.emplace_back(delegate<Ret(Args...)>{connect_arg<Candidate>, value_or_instance});
|
||||
return { std::move(conn), signal };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a free function from a signal.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<auto Function>
|
||||
void disconnect() {
|
||||
auto &calls = signal->calls;
|
||||
delegate<Ret(Args...)> delegate{};
|
||||
delegate.template connect<Function>();
|
||||
calls.erase(std::remove(calls.begin(), calls.end(), delegate), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a member function or a free function with payload from
|
||||
* a signal.
|
||||
* @tparam Candidate Member or free function to disconnect from the signal.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
* @param value_or_instance A valid reference that fits the purpose.
|
||||
*/
|
||||
template<auto Candidate, typename Type>
|
||||
void disconnect(Type &value_or_instance) {
|
||||
auto &calls = signal->calls;
|
||||
delegate<Ret(Args...)> delegate{};
|
||||
delegate.template connect<Candidate>(value_or_instance);
|
||||
calls.erase(std::remove(calls.begin(), calls.end(), delegate), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects member functions or free functions based on an
|
||||
* instance or specific payload.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
* @param value_or_instance A valid reference that fits the purpose.
|
||||
*/
|
||||
template<typename Type>
|
||||
void disconnect(const Type &value_or_instance) {
|
||||
auto &calls = signal->calls;
|
||||
calls.erase(std::remove_if(calls.begin(), calls.end(), [&value_or_instance](const auto &delegate) {
|
||||
return delegate.instance() == &value_or_instance;
|
||||
}), calls.end());
|
||||
}
|
||||
|
||||
/*! @brief Disconnects all the listeners from a signal. */
|
||||
void disconnect() {
|
||||
signal->calls.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
signal_type *signal;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Deduction guide.
|
||||
*
|
||||
* It allows to deduce the function type of a sink directly from the signal it
|
||||
* refers to.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
*/
|
||||
template<typename Ret, typename... Args>
|
||||
sink(sigh<Ret(Args...)> &) ENTT_NOEXCEPT -> sink<Ret(Args...)>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
0
test/BUILD.bazel
Normal file
0
test/BUILD.bazel
Normal file
@@ -5,11 +5,16 @@
|
||||
include_directories($<TARGET_PROPERTY:EnTT,INTERFACE_INCLUDE_DIRECTORIES>)
|
||||
add_compile_options($<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_OPTIONS>)
|
||||
|
||||
macro(SETUP_LIBRARY_TARGET LIB_TARGET)
|
||||
set_target_properties(${LIB_TARGET} PROPERTIES CXX_EXTENSIONS OFF)
|
||||
target_compile_definitions(${LIB_TARGET} PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_DEFINITIONS>)
|
||||
target_compile_features(${LIB_TARGET} PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_FEATURES>)
|
||||
target_compile_options(${LIB_TARGET} PRIVATE $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-pedantic -Wall>)
|
||||
target_compile_options(${LIB_TARGET} PRIVATE $<$<CXX_COMPILER_ID:MSVC>:/EHsc>)
|
||||
endmacro()
|
||||
|
||||
add_library(odr OBJECT odr.cpp)
|
||||
set_target_properties(odr PROPERTIES CXX_EXTENSIONS OFF)
|
||||
target_compile_definitions(odr PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_DEFINITIONS>)
|
||||
target_compile_features(odr PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_FEATURES>)
|
||||
target_compile_options(odr PRIVATE $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-pedantic -Wall>)
|
||||
SETUP_LIBRARY_TARGET(odr)
|
||||
|
||||
macro(SETUP_AND_ADD_TEST TEST_NAME TEST_SOURCE)
|
||||
add_executable(${TEST_NAME} $<TARGET_OBJECTS:odr> ${TEST_SOURCE})
|
||||
@@ -18,6 +23,7 @@ macro(SETUP_AND_ADD_TEST TEST_NAME TEST_SOURCE)
|
||||
target_compile_definitions(${TEST_NAME} PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_DEFINITIONS>)
|
||||
target_compile_features(${TEST_NAME} PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_FEATURES>)
|
||||
target_compile_options(${TEST_NAME} PRIVATE $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-pedantic -Wall>)
|
||||
target_compile_options(${TEST_NAME} PRIVATE $<$<CXX_COMPILER_ID:MSVC>:/EHsc>)
|
||||
add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME})
|
||||
endmacro()
|
||||
|
||||
@@ -27,6 +33,26 @@ if(BUILD_BENCHMARK)
|
||||
SETUP_AND_ADD_TEST(benchmark benchmark/benchmark.cpp)
|
||||
endif()
|
||||
|
||||
# Test lib
|
||||
|
||||
if(BUILD_LIB)
|
||||
add_library(a_module_shared SHARED lib/a_module.cpp)
|
||||
add_library(a_module_static STATIC lib/a_module.cpp)
|
||||
add_library(another_module_shared SHARED lib/another_module.cpp)
|
||||
add_library(another_module_static STATIC lib/another_module.cpp)
|
||||
|
||||
SETUP_LIBRARY_TARGET(a_module_shared)
|
||||
SETUP_LIBRARY_TARGET(a_module_static)
|
||||
SETUP_LIBRARY_TARGET(another_module_shared)
|
||||
SETUP_LIBRARY_TARGET(another_module_static)
|
||||
|
||||
SETUP_AND_ADD_TEST(lib_shared lib/lib.cpp)
|
||||
target_link_libraries(lib_shared PRIVATE a_module_shared another_module_shared)
|
||||
|
||||
SETUP_AND_ADD_TEST(lib_static lib/lib.cpp)
|
||||
target_link_libraries(lib_static PRIVATE a_module_static another_module_static)
|
||||
endif()
|
||||
|
||||
# Test mod
|
||||
|
||||
if(BUILD_MOD)
|
||||
@@ -61,22 +87,31 @@ SETUP_AND_ADD_TEST(family entt/core/family.cpp)
|
||||
SETUP_AND_ADD_TEST(hashed_string entt/core/hashed_string.cpp)
|
||||
SETUP_AND_ADD_TEST(ident entt/core/ident.cpp)
|
||||
SETUP_AND_ADD_TEST(monostate entt/core/monostate.cpp)
|
||||
SETUP_AND_ADD_TEST(type_traits entt/core/type_traits.cpp)
|
||||
SETUP_AND_ADD_TEST(utility entt/core/utility.cpp)
|
||||
|
||||
# Test entity
|
||||
|
||||
SETUP_AND_ADD_TEST(actor entt/entity/actor.cpp)
|
||||
SETUP_AND_ADD_TEST(entity entt/entity/entity.cpp)
|
||||
SETUP_AND_ADD_TEST(group entt/entity/group.cpp)
|
||||
SETUP_AND_ADD_TEST(helper entt/entity/helper.cpp)
|
||||
SETUP_AND_ADD_TEST(prototype entt/entity/prototype.cpp)
|
||||
SETUP_AND_ADD_TEST(observer entt/entity/observer.cpp)
|
||||
SETUP_AND_ADD_TEST(registry entt/entity/registry.cpp)
|
||||
SETUP_AND_ADD_TEST(runtime_view entt/entity/runtime_view.cpp)
|
||||
SETUP_AND_ADD_TEST(snapshot entt/entity/snapshot.cpp)
|
||||
SETUP_AND_ADD_TEST(sparse_set entt/entity/sparse_set.cpp)
|
||||
SETUP_AND_ADD_TEST(storage entt/entity/storage.cpp)
|
||||
SETUP_AND_ADD_TEST(view entt/entity/view.cpp)
|
||||
|
||||
# Test locator
|
||||
|
||||
SETUP_AND_ADD_TEST(locator entt/locator/locator.cpp)
|
||||
|
||||
# Test meta
|
||||
|
||||
SETUP_AND_ADD_TEST(meta entt/meta/meta.cpp)
|
||||
|
||||
# Test process
|
||||
|
||||
SETUP_AND_ADD_TEST(process entt/process/process.cpp)
|
||||
|
||||
11
test/benchmark/BUILD.bazel
Normal file
11
test/benchmark/BUILD.bazel
Normal file
@@ -0,0 +1,11 @@
|
||||
load("//bazel:copts.bzl", "entt_copts")
|
||||
|
||||
cc_binary(
|
||||
name = "benchmark",
|
||||
srcs = ["benchmark.cpp"],
|
||||
copts = entt_copts,
|
||||
deps = [
|
||||
"//:entt",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
0
test/entt/BUILD.bazel
Normal file
0
test/entt/BUILD.bazel
Normal file
71
test/entt/core/BUILD.bazel
Normal file
71
test/entt/core/BUILD.bazel
Normal file
@@ -0,0 +1,71 @@
|
||||
load("//bazel:copts.bzl", "entt_copts")
|
||||
|
||||
cc_test(
|
||||
name = "algorithm",
|
||||
srcs = ["algorithm.cpp"],
|
||||
copts = entt_copts,
|
||||
deps = [
|
||||
"//:entt",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "family",
|
||||
srcs = ["family.cpp"],
|
||||
copts = entt_copts,
|
||||
deps = [
|
||||
"//:entt",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "hashed_string",
|
||||
srcs = ["hashed_string.cpp"],
|
||||
copts = entt_copts,
|
||||
deps = [
|
||||
"//:entt",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "ident",
|
||||
srcs = ["ident.cpp"],
|
||||
copts = entt_copts,
|
||||
deps = [
|
||||
"//:entt",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "monostate",
|
||||
srcs = ["monostate.cpp"],
|
||||
copts = entt_copts,
|
||||
deps = [
|
||||
"//:entt",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "type_traits",
|
||||
srcs = ["type_traits.cpp"],
|
||||
copts = entt_copts,
|
||||
deps = [
|
||||
"//:entt",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "utility",
|
||||
srcs = ["utility.cpp"],
|
||||
copts = entt_copts,
|
||||
deps = [
|
||||
"//:entt",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
@@ -2,10 +2,14 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/algorithm.hpp>
|
||||
|
||||
struct boxed_int {
|
||||
int value;
|
||||
};
|
||||
|
||||
TEST(Algorithm, StdSort) {
|
||||
// well, I'm pretty sure it works, it's std::sort!!
|
||||
std::array<int, 5> arr{{4, 1, 3, 2, 0}};
|
||||
entt::StdSort sort;
|
||||
entt::std_sort sort;
|
||||
|
||||
sort(arr.begin(), arr.end());
|
||||
|
||||
@@ -14,9 +18,23 @@ TEST(Algorithm, StdSort) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Algorithm, StdSortBoxedInt) {
|
||||
// well, I'm pretty sure it works, it's std::sort!!
|
||||
std::array<boxed_int, 6> arr{{{4}, {1}, {3}, {2}, {0}, {6}}};
|
||||
entt::std_sort sort;
|
||||
|
||||
sort(arr.begin(), arr.end(), [](const auto &lhs, const auto &rhs) {
|
||||
return lhs.value > rhs.value;
|
||||
});
|
||||
|
||||
for(typename decltype(arr)::size_type i = 0; i < (arr.size() - 1); ++i) {
|
||||
ASSERT_GT(arr[i].value, arr[i+1].value);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Algorithm, InsertionSort) {
|
||||
std::array<int, 5> arr{{4, 1, 3, 2, 0}};
|
||||
entt::InsertionSort sort;
|
||||
entt::insertion_sort sort;
|
||||
|
||||
sort(arr.begin(), arr.end());
|
||||
|
||||
@@ -25,16 +43,55 @@ TEST(Algorithm, InsertionSort) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Algorithm, OneShotBubbleSort) {
|
||||
std::array<int, 5> arr{{4, 1, 3, 2, 0}};
|
||||
entt::OneShotBubbleSort sort;
|
||||
TEST(Algorithm, InsertionSortBoxedInt) {
|
||||
std::array<boxed_int, 6> arr{{{4}, {1}, {3}, {2}, {0}, {6}}};
|
||||
entt::insertion_sort sort;
|
||||
|
||||
sort(arr.begin(), arr.end());
|
||||
sort(arr.begin(), arr.end());
|
||||
sort(arr.begin(), arr.end());
|
||||
sort(arr.begin(), arr.end());
|
||||
sort(arr.begin(), arr.end(), [](const auto &lhs, const auto &rhs) {
|
||||
return lhs.value > rhs.value;
|
||||
});
|
||||
|
||||
for(typename decltype(arr)::size_type i = 0; i < (arr.size() - 1); ++i) {
|
||||
ASSERT_GT(arr[i].value, arr[i+1].value);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Algorithm, InsertionSortEmptyContainer) {
|
||||
std::vector<int> vec{};
|
||||
entt::insertion_sort sort;
|
||||
// this should crash with asan enabled if we break the constraint
|
||||
sort(vec.begin(), vec.end());
|
||||
}
|
||||
|
||||
TEST(Algorithm, RadixSort) {
|
||||
std::array<uint32_t, 5> arr{{4, 1, 3, 2, 0}};
|
||||
entt::radix_sort<8, 32> sort;
|
||||
|
||||
sort(arr.begin(), arr.end(), [](const auto &value) {
|
||||
return value;
|
||||
});
|
||||
|
||||
for(typename decltype(arr)::size_type i = 0; i < (arr.size() - 1); ++i) {
|
||||
ASSERT_LT(arr[i], arr[i+1]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Algorithm, RadixSortBoxedInt) {
|
||||
std::array<boxed_int, 6> arr{{{4}, {1}, {3}, {2}, {0}, {6}}};
|
||||
entt::radix_sort<2, 6> sort;
|
||||
|
||||
sort(arr.rbegin(), arr.rend(), [](const auto &instance) {
|
||||
return instance.value;
|
||||
});
|
||||
|
||||
for(typename decltype(arr)::size_type i = 0; i < (arr.size() - 1); ++i) {
|
||||
ASSERT_GT(arr[i].value, arr[i+1].value);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Algorithm, RadixSortEmptyContainer) {
|
||||
std::vector<int> vec{};
|
||||
entt::radix_sort<8, 32> sort;
|
||||
// this should crash with asan enabled if we break the constraint
|
||||
sort(vec.begin(), vec.end());
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/family.hpp>
|
||||
|
||||
using my_family = entt::Family<struct MyFamily>;
|
||||
using your_family = entt::Family<struct YourFamily>;
|
||||
using a_family = entt::family<struct a_family_type>;
|
||||
using another_family = entt::family<struct another_family_type>;
|
||||
|
||||
TEST(Family, Functionalities) {
|
||||
auto myFamilyType = my_family::type<struct MyFamilyType>();
|
||||
auto mySameFamilyType = my_family::type<struct MyFamilyType>();
|
||||
auto myOtherFamilyType = my_family::type<struct MyOtherFamilyType>();
|
||||
auto yourFamilyType = your_family::type<struct YourFamilyType>();
|
||||
auto t1 = a_family::type<int>;
|
||||
auto t2 = a_family::type<int>;
|
||||
auto t3 = a_family::type<char>;
|
||||
auto t4 = another_family::type<double>;
|
||||
|
||||
ASSERT_EQ(myFamilyType, mySameFamilyType);
|
||||
ASSERT_NE(myFamilyType, myOtherFamilyType);
|
||||
ASSERT_EQ(myFamilyType, yourFamilyType);
|
||||
ASSERT_EQ(t1, t2);
|
||||
ASSERT_NE(t1, t3);
|
||||
ASSERT_EQ(t1, t4);
|
||||
}
|
||||
|
||||
TEST(Family, Uniqueness) {
|
||||
ASSERT_EQ(my_family::type<int>(), my_family::type<int &>());
|
||||
ASSERT_EQ(my_family::type<int>(), my_family::type<int &&>());
|
||||
ASSERT_EQ(my_family::type<int>(), my_family::type<const int &>());
|
||||
ASSERT_EQ(a_family::type<int>, a_family::type<int &>);
|
||||
ASSERT_EQ(a_family::type<int>, a_family::type<int &&>);
|
||||
ASSERT_EQ(a_family::type<int>, a_family::type<const int &>);
|
||||
}
|
||||
|
||||
@@ -1,34 +1,128 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/hashed_string.hpp>
|
||||
|
||||
TEST(HashedString, Functionalities) {
|
||||
using hash_type = entt::HashedString::hash_type;
|
||||
using hash_type = entt::hashed_string::hash_type;
|
||||
|
||||
const char *bar = "bar";
|
||||
|
||||
auto fooHs = entt::HashedString{"foo"};
|
||||
auto barHs = entt::HashedString{bar};
|
||||
auto foo_hs = entt::hashed_string{"foo"};
|
||||
auto bar_hs = entt::hashed_string{bar};
|
||||
|
||||
ASSERT_NE(static_cast<hash_type>(fooHs), static_cast<hash_type>(barHs));
|
||||
ASSERT_EQ(static_cast<const char *>(fooHs), "foo");
|
||||
ASSERT_EQ(static_cast<const char *>(barHs), bar);
|
||||
ASSERT_NE(static_cast<hash_type>(foo_hs), static_cast<hash_type>(bar_hs));
|
||||
ASSERT_STREQ(static_cast<const char *>(foo_hs), "foo");
|
||||
ASSERT_STREQ(static_cast<const char *>(bar_hs), bar);
|
||||
ASSERT_STREQ(foo_hs.data(), "foo");
|
||||
ASSERT_STREQ(bar_hs.data(), bar);
|
||||
|
||||
ASSERT_EQ(fooHs, fooHs);
|
||||
ASSERT_NE(fooHs, barHs);
|
||||
ASSERT_EQ(foo_hs, foo_hs);
|
||||
ASSERT_NE(foo_hs, bar_hs);
|
||||
|
||||
entt::HashedString hs{"foobar"};
|
||||
entt::hashed_string hs{"foobar"};
|
||||
|
||||
ASSERT_EQ(static_cast<hash_type>(hs), 0x85944171f73967e8);
|
||||
ASSERT_EQ(static_cast<hash_type>(hs), 0xbf9cf968);
|
||||
ASSERT_EQ(hs.value(), 0xbf9cf968);
|
||||
|
||||
ASSERT_EQ(fooHs, "foo"_hs);
|
||||
ASSERT_NE(barHs, "foo"_hs);
|
||||
ASSERT_EQ(foo_hs, "foo"_hs);
|
||||
ASSERT_NE(bar_hs, "foo"_hs);
|
||||
}
|
||||
|
||||
TEST(HashedString, Empty) {
|
||||
using hash_type = entt::hashed_string::hash_type;
|
||||
|
||||
entt::hashed_string hs{};
|
||||
|
||||
ASSERT_EQ(static_cast<hash_type>(hs), hash_type{});
|
||||
ASSERT_EQ(static_cast<const char *>(hs), nullptr);
|
||||
}
|
||||
|
||||
TEST(HashedString, Constexprness) {
|
||||
using hash_type = entt::HashedString::hash_type;
|
||||
using hash_type = entt::hashed_string::hash_type;
|
||||
// how would you test a constexpr otherwise?
|
||||
(void)std::integral_constant<hash_type, entt::HashedString{"quux"}>{};
|
||||
(void)std::integral_constant<hash_type, entt::hashed_string{"quux"}>{};
|
||||
(void)std::integral_constant<hash_type, "quux"_hs>{};
|
||||
ASSERT_TRUE(true);
|
||||
}
|
||||
|
||||
TEST(HashedString, ToValue) {
|
||||
using hash_type = entt::hashed_string::hash_type;
|
||||
|
||||
const char *foobar = "foobar";
|
||||
|
||||
ASSERT_EQ(entt::hashed_string::to_value(foobar), 0xbf9cf968);
|
||||
// how would you test a constexpr otherwise?
|
||||
(void)std::integral_constant<hash_type, entt::hashed_string::to_value("quux")>{};
|
||||
}
|
||||
|
||||
TEST(HashedString, StringView) {
|
||||
std::string str{"__foobar__"};
|
||||
std::string_view view{str.data()+2, 6};
|
||||
ASSERT_EQ(entt::hashed_string::to_value(view.data(), view.size()), 0xbf9cf968);
|
||||
}
|
||||
|
||||
TEST(HashedWString, Functionalities) {
|
||||
using hash_type = entt::hashed_wstring::hash_type;
|
||||
|
||||
const wchar_t *bar = L"bar";
|
||||
|
||||
auto foo_hws = entt::hashed_wstring{L"foo"};
|
||||
auto bar_hws = entt::hashed_wstring{bar};
|
||||
|
||||
ASSERT_NE(static_cast<hash_type>(foo_hws), static_cast<hash_type>(bar_hws));
|
||||
ASSERT_STREQ(static_cast<const wchar_t *>(foo_hws), L"foo");
|
||||
ASSERT_STREQ(static_cast<const wchar_t *>(bar_hws), bar);
|
||||
ASSERT_STREQ(foo_hws.data(), L"foo");
|
||||
ASSERT_STREQ(bar_hws.data(), bar);
|
||||
|
||||
ASSERT_EQ(foo_hws, foo_hws);
|
||||
ASSERT_NE(foo_hws, bar_hws);
|
||||
|
||||
entt::hashed_wstring hws{L"foobar"};
|
||||
|
||||
ASSERT_EQ(static_cast<hash_type>(hws), 0xbf9cf968);
|
||||
ASSERT_EQ(hws.value(), 0xbf9cf968);
|
||||
|
||||
ASSERT_EQ(foo_hws, L"foo"_hws);
|
||||
ASSERT_NE(bar_hws, L"foo"_hws);
|
||||
}
|
||||
|
||||
TEST(HashedWString, Empty) {
|
||||
using hash_type = entt::hashed_wstring::hash_type;
|
||||
|
||||
entt::hashed_wstring hws{};
|
||||
|
||||
ASSERT_EQ(static_cast<hash_type>(hws), hash_type{});
|
||||
ASSERT_EQ(static_cast<const wchar_t *>(hws), nullptr);
|
||||
}
|
||||
|
||||
TEST(HashedWString, Constexprness) {
|
||||
using hash_type = entt::hashed_wstring::hash_type;
|
||||
// how would you test a constexpr otherwise?
|
||||
(void)std::integral_constant<hash_type, entt::hashed_wstring{L"quux"}>{};
|
||||
(void)std::integral_constant<hash_type, L"quux"_hws>{};
|
||||
ASSERT_TRUE(true);
|
||||
}
|
||||
|
||||
TEST(HashedWString, ToValue) {
|
||||
using hash_type = entt::hashed_wstring::hash_type;
|
||||
|
||||
const wchar_t *foobar = L"foobar";
|
||||
|
||||
ASSERT_EQ(entt::hashed_wstring::to_value(foobar), 0xbf9cf968);
|
||||
// how would you test a constexpr otherwise?
|
||||
(void)std::integral_constant<hash_type, entt::hashed_wstring::to_value(L"quux")>{};
|
||||
}
|
||||
|
||||
TEST(HashedWString, StringView) {
|
||||
std::wstring str{L"__foobar__"};
|
||||
std::wstring_view view{str.data()+2, 6};
|
||||
ASSERT_EQ(entt::hashed_wstring::to_value(view.data(), view.size()), 0xbf9cf968);
|
||||
}
|
||||
|
||||
TEST(BasicHashedString, DeductionGuide) {
|
||||
static_assert(std::is_same_v<decltype(entt::basic_hashed_string{"foo"}), entt::hashed_string>);
|
||||
static_assert(std::is_same_v<decltype(entt::basic_hashed_string{L"foo"}), entt::hashed_wstring>);
|
||||
}
|
||||
|
||||
@@ -2,31 +2,31 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/ident.hpp>
|
||||
|
||||
struct AType {};
|
||||
struct AnotherType {};
|
||||
struct a_type {};
|
||||
struct another_type {};
|
||||
|
||||
TEST(Identifier, Uniqueness) {
|
||||
using ID = entt::Identifier<AType, AnotherType>;
|
||||
constexpr AType anInstance;
|
||||
constexpr AnotherType anotherInstance;
|
||||
using id = entt::identifier<a_type, another_type>;
|
||||
constexpr a_type an_instance;
|
||||
constexpr another_type another_instance;
|
||||
|
||||
ASSERT_NE(ID::get<AType>(), ID::get<AnotherType>());
|
||||
ASSERT_EQ(ID::get<AType>(), ID::get<decltype(anInstance)>());
|
||||
ASSERT_NE(ID::get<AType>(), ID::get<decltype(anotherInstance)>());
|
||||
ASSERT_EQ(ID::get<AType>(), ID::get<AType>());
|
||||
ASSERT_EQ(ID::get<AnotherType>(), ID::get<AnotherType>());
|
||||
ASSERT_NE(id::type<a_type>, id::type<another_type>);
|
||||
ASSERT_EQ(id::type<a_type>, id::type<decltype(an_instance)>);
|
||||
ASSERT_NE(id::type<a_type>, id::type<decltype(another_instance)>);
|
||||
ASSERT_EQ(id::type<a_type>, id::type<a_type>);
|
||||
ASSERT_EQ(id::type<another_type>, id::type<another_type>);
|
||||
|
||||
// test uses in constant expressions
|
||||
switch(ID::get<AnotherType>()) {
|
||||
case ID::get<AType>():
|
||||
switch(id::type<another_type>) {
|
||||
case id::type<a_type>:
|
||||
FAIL();
|
||||
case ID::get<AnotherType>():
|
||||
case id::type<another_type>:
|
||||
SUCCEED();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Identifier, SingleType) {
|
||||
using ID = entt::Identifier<AType>;
|
||||
std::integral_constant<ID::identifier_type, ID::get<AType>()> ic;
|
||||
using id = entt::identifier<a_type>;
|
||||
std::integral_constant<id::identifier_type, id::type<a_type>> ic;
|
||||
(void)ic;
|
||||
}
|
||||
|
||||
@@ -3,18 +3,18 @@
|
||||
#include <entt/core/monostate.hpp>
|
||||
|
||||
TEST(Monostate, Functionalities) {
|
||||
const bool bPre = entt::Monostate<entt::HashedString{"foobar"}>{};
|
||||
const int iPre = entt::Monostate<"foobar"_hs>{};
|
||||
const bool b_pre = entt::monostate<entt::hashed_string{"foobar"}>{};
|
||||
const int i_pre = entt::monostate<"foobar"_hs>{};
|
||||
|
||||
ASSERT_FALSE(bPre);
|
||||
ASSERT_EQ(iPre, int{});
|
||||
ASSERT_FALSE(b_pre);
|
||||
ASSERT_EQ(i_pre, int{});
|
||||
|
||||
entt::Monostate<"foobar"_hs>{} = true;
|
||||
entt::Monostate<"foobar"_hs>{} = 42;
|
||||
entt::monostate<"foobar"_hs>{} = true;
|
||||
entt::monostate_v<"foobar"_hs> = 42;
|
||||
|
||||
const bool &bPost = entt::Monostate<"foobar"_hs>{};
|
||||
const int &iPost = entt::Monostate<entt::HashedString{"foobar"}>{};
|
||||
const bool &b_post = entt::monostate<"foobar"_hs>{};
|
||||
const int &i_post = entt::monostate_v<entt::hashed_string{"foobar"}>;
|
||||
|
||||
ASSERT_TRUE(bPost);
|
||||
ASSERT_EQ(iPost, 42);
|
||||
ASSERT_TRUE(b_post);
|
||||
ASSERT_EQ(i_post, 42);
|
||||
}
|
||||
|
||||
14
test/entt/core/type_traits.cpp
Normal file
14
test/entt/core/type_traits.cpp
Normal file
@@ -0,0 +1,14 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/type_traits.hpp>
|
||||
|
||||
TEST(TypeList, Functionalities) {
|
||||
using type = entt::type_list<int, char>;
|
||||
using other = entt::type_list<double>;
|
||||
|
||||
ASSERT_EQ(entt::type_list_size_v<type>, 2u);
|
||||
ASSERT_EQ(entt::type_list_size_v<other>, 1u);
|
||||
ASSERT_TRUE((std::is_same_v<entt::type_list_cat_t<type, other, type, other>, entt::type_list<int, char, double, int, char, double>>));
|
||||
ASSERT_TRUE((std::is_same_v<entt::type_list_cat_t<type, other>, entt::type_list<int, char, double>>));
|
||||
ASSERT_TRUE((std::is_same_v<entt::type_list_cat_t<type, type>, entt::type_list<int, char, int, char>>));
|
||||
ASSERT_TRUE((std::is_same_v<entt::type_list_unique_t<entt::type_list_cat_t<type, type>>, entt::type_list<int, char>>));
|
||||
}
|
||||
26
test/entt/core/utility.cpp
Normal file
26
test/entt/core/utility.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/utility.hpp>
|
||||
|
||||
struct Functions {
|
||||
static void foo(int) {}
|
||||
static void foo() {}
|
||||
|
||||
void bar(int) {}
|
||||
void bar() {}
|
||||
};
|
||||
|
||||
TEST(Utility, Identity) {
|
||||
entt::identity identity;
|
||||
int value = 42;
|
||||
|
||||
ASSERT_EQ(identity(value), value);
|
||||
ASSERT_EQ(&identity(value), &value);
|
||||
}
|
||||
|
||||
TEST(Utility, Overload) {
|
||||
ASSERT_EQ(entt::overload<void(int)>(&Functions::foo), static_cast<void(*)(int)>(&Functions::foo));
|
||||
ASSERT_EQ(entt::overload<void()>(&Functions::foo), static_cast<void(*)()>(&Functions::foo));
|
||||
|
||||
ASSERT_EQ(entt::overload<void(int)>(&Functions::bar), static_cast<void(Functions:: *)(int)>(&Functions::bar));
|
||||
ASSERT_EQ(entt::overload<void()>(&Functions::bar), static_cast<void(Functions:: *)()>(&Functions::bar));
|
||||
}
|
||||
121
test/entt/entity/BUILD.bazel
Normal file
121
test/entt/entity/BUILD.bazel
Normal file
@@ -0,0 +1,121 @@
|
||||
load("//bazel:copts.bzl", "entt_copts")
|
||||
|
||||
cc_test(
|
||||
name = "actor",
|
||||
copts = entt_copts,
|
||||
srcs = ["actor.cpp"],
|
||||
deps = [
|
||||
"//:entt",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "entity",
|
||||
copts = entt_copts,
|
||||
srcs = ["entity.cpp"],
|
||||
deps = [
|
||||
"//:entt",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "group",
|
||||
copts = entt_copts,
|
||||
srcs = ["group.cpp"],
|
||||
deps = [
|
||||
"//:entt",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "helper",
|
||||
copts = entt_copts,
|
||||
srcs = ["helper.cpp"],
|
||||
deps = [
|
||||
"//:entt",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "observer",
|
||||
copts = entt_copts,
|
||||
srcs = ["observer.cpp"],
|
||||
deps = [
|
||||
"//:entt",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "prototype",
|
||||
copts = entt_copts,
|
||||
srcs = ["prototype.cpp"],
|
||||
deps = [
|
||||
"//:entt",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "registry",
|
||||
copts = entt_copts,
|
||||
srcs = ["registry.cpp"],
|
||||
deps = [
|
||||
"//:entt",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "runtime_view",
|
||||
copts = entt_copts,
|
||||
srcs = ["runtime_view.cpp"],
|
||||
deps = [
|
||||
"//:entt",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "snapshot",
|
||||
copts = entt_copts,
|
||||
srcs = ["snapshot.cpp"],
|
||||
deps = [
|
||||
"//:entt",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "sparse_set",
|
||||
copts = entt_copts,
|
||||
srcs = ["sparse_set.cpp"],
|
||||
deps = [
|
||||
"//:entt",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "storage",
|
||||
copts = entt_copts,
|
||||
srcs = ["storage.cpp"],
|
||||
deps = [
|
||||
"//:entt",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "view",
|
||||
copts = entt_copts,
|
||||
srcs = ["view.cpp"],
|
||||
deps = [
|
||||
"//:entt",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
@@ -3,66 +3,82 @@
|
||||
#include <entt/entity/actor.hpp>
|
||||
#include <entt/entity/registry.hpp>
|
||||
|
||||
struct ActorComponent final {};
|
||||
struct ActorTag final {};
|
||||
|
||||
TEST(Actor, Component) {
|
||||
entt::DefaultRegistry registry;
|
||||
entt::DefaultActor actor{registry};
|
||||
const auto &cactor = actor;
|
||||
entt::registry registry;
|
||||
entt::actor actor{registry};
|
||||
|
||||
ASSERT_EQ(®istry, &actor.registry());
|
||||
ASSERT_EQ(®istry, &cactor.registry());
|
||||
ASSERT_TRUE(registry.empty<ActorComponent>());
|
||||
ASSERT_EQ(®istry, &actor.backend());
|
||||
ASSERT_EQ(®istry, &std::as_const(actor).backend());
|
||||
ASSERT_TRUE(registry.empty<int>());
|
||||
ASSERT_FALSE(registry.empty());
|
||||
ASSERT_FALSE(actor.has<ActorComponent>());
|
||||
ASSERT_FALSE(actor.has<int>());
|
||||
|
||||
const auto &component = actor.assign<ActorComponent>();
|
||||
const auto &cint = actor.assign<int>();
|
||||
const auto &cchar = actor.assign<char>();
|
||||
|
||||
ASSERT_EQ(&component, &actor.get<ActorComponent>());
|
||||
ASSERT_EQ(&component, &cactor.get<ActorComponent>());
|
||||
ASSERT_FALSE(registry.empty<ActorComponent>());
|
||||
ASSERT_EQ(&cint, &actor.get<int>());
|
||||
ASSERT_EQ(&cchar, &std::as_const(actor).get<char>());
|
||||
ASSERT_EQ(&cint, &std::get<0>(actor.get<int, char>()));
|
||||
ASSERT_EQ(&cchar, &std::get<1>(actor.get<int, char>()));
|
||||
ASSERT_EQ(&cint, std::get<0>(actor.try_get<int, char, double>()));
|
||||
ASSERT_EQ(&cchar, std::get<1>(actor.try_get<int, char, double>()));
|
||||
ASSERT_EQ(nullptr, std::get<2>(actor.try_get<int, char, double>()));
|
||||
ASSERT_EQ(nullptr, actor.try_get<double>());
|
||||
ASSERT_EQ(&cchar, actor.try_get<char>());
|
||||
ASSERT_EQ(&cint, actor.try_get<int>());
|
||||
|
||||
ASSERT_FALSE(registry.empty<int>());
|
||||
ASSERT_FALSE(registry.empty());
|
||||
ASSERT_TRUE(actor.has<ActorComponent>());
|
||||
ASSERT_TRUE((actor.has<int, char>()));
|
||||
ASSERT_FALSE(actor.has<double>());
|
||||
|
||||
actor.remove<ActorComponent>();
|
||||
actor.remove<int>();
|
||||
|
||||
ASSERT_TRUE(registry.empty<ActorComponent>());
|
||||
ASSERT_TRUE(registry.empty<int>());
|
||||
ASSERT_FALSE(registry.empty());
|
||||
ASSERT_FALSE(actor.has<ActorComponent>());
|
||||
ASSERT_FALSE(actor.has<int>());
|
||||
}
|
||||
|
||||
TEST(Actor, Tag) {
|
||||
entt::DefaultRegistry registry;
|
||||
entt::DefaultActor actor{registry};
|
||||
const auto &cactor = actor;
|
||||
TEST(Actor, FromEntity) {
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
|
||||
ASSERT_EQ(®istry, &actor.registry());
|
||||
ASSERT_EQ(®istry, &cactor.registry());
|
||||
ASSERT_FALSE(registry.has<ActorTag>());
|
||||
ASSERT_FALSE(actor.has<ActorTag>(entt::tag_t{}));
|
||||
registry.assign<int>(entity, 42);
|
||||
registry.assign<char>(entity, 'c');
|
||||
|
||||
const auto &tag = actor.assign<ActorTag>(entt::tag_t{});
|
||||
entt::actor actor{entity, registry};
|
||||
|
||||
ASSERT_EQ(&tag, &actor.get<ActorTag>(entt::tag_t{}));
|
||||
ASSERT_EQ(&tag, &cactor.get<ActorTag>(entt::tag_t{}));
|
||||
ASSERT_TRUE(registry.has<ActorTag>());
|
||||
ASSERT_FALSE(registry.empty());
|
||||
ASSERT_TRUE(actor.has<ActorTag>(entt::tag_t{}));
|
||||
|
||||
actor.remove<ActorTag>(entt::tag_t{});
|
||||
|
||||
ASSERT_FALSE(registry.has<ActorTag>());
|
||||
ASSERT_FALSE(registry.empty());
|
||||
ASSERT_FALSE(actor.has<ActorTag>(entt::tag_t{}));
|
||||
ASSERT_TRUE(actor);
|
||||
ASSERT_EQ(entity, actor.entity());
|
||||
ASSERT_TRUE((actor.has<int, char>()));
|
||||
ASSERT_EQ(actor.get<int>(), 42);
|
||||
ASSERT_EQ(actor.get<char>(), 'c');
|
||||
}
|
||||
|
||||
TEST(Actor, EntityLifetime) {
|
||||
entt::DefaultRegistry registry;
|
||||
auto *actor = new entt::DefaultActor{registry};
|
||||
actor->assign<ActorComponent>();
|
||||
entt::registry registry;
|
||||
entt::actor actor{};
|
||||
|
||||
ASSERT_FALSE(registry.empty<ActorComponent>());
|
||||
ASSERT_FALSE(actor);
|
||||
|
||||
actor = entt::actor{registry};
|
||||
actor.assign<int>();
|
||||
|
||||
ASSERT_TRUE(actor);
|
||||
ASSERT_FALSE(registry.empty<int>());
|
||||
ASSERT_FALSE(registry.empty());
|
||||
|
||||
registry.destroy(actor.entity());
|
||||
|
||||
ASSERT_FALSE(actor);
|
||||
}
|
||||
|
||||
TEST(Actor, ActorLifetime) {
|
||||
entt::registry registry;
|
||||
auto *actor = new entt::actor{registry};
|
||||
actor->assign<int>();
|
||||
|
||||
ASSERT_FALSE(registry.empty<int>());
|
||||
ASSERT_FALSE(registry.empty());
|
||||
|
||||
registry.each([actor](const auto entity) {
|
||||
@@ -71,6 +87,6 @@ TEST(Actor, EntityLifetime) {
|
||||
|
||||
delete actor;
|
||||
|
||||
ASSERT_TRUE(registry.empty<ActorComponent>());
|
||||
ASSERT_TRUE(registry.empty<int>());
|
||||
ASSERT_TRUE(registry.empty());
|
||||
}
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/entity.hpp>
|
||||
#include <entt/entity/registry.hpp>
|
||||
|
||||
template<bool>
|
||||
struct S {};
|
||||
|
||||
TEST(Traits, Null) {
|
||||
entt::DefaultRegistry registry{};
|
||||
TEST(Entity, Null) {
|
||||
using traits_type = entt::entt_traits<std::underlying_type_t<entt::entity>>;
|
||||
|
||||
entt::registry registry{};
|
||||
const auto entity = registry.create();
|
||||
|
||||
registry.assign<int>(entity, 42);
|
||||
|
||||
ASSERT_TRUE(~typename entt::DefaultRegistry::entity_type{} == 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);
|
||||
|
||||
985
test/entt/entity/group.cpp
Normal file
985
test/entt/entity/group.cpp
Normal file
@@ -0,0 +1,985 @@
|
||||
#include <utility>
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/helper.hpp>
|
||||
#include <entt/entity/registry.hpp>
|
||||
#include <entt/entity/group.hpp>
|
||||
|
||||
struct empty_type {};
|
||||
struct boxed_int { int value; };
|
||||
|
||||
TEST(NonOwningGroup, Functionalities) {
|
||||
entt::registry registry;
|
||||
auto group = registry.group(entt::get<int, char>);
|
||||
auto cgroup = std::as_const(registry).group(entt::get<const int, const char>);
|
||||
|
||||
ASSERT_TRUE(group.empty());
|
||||
ASSERT_TRUE(group.empty<int>());
|
||||
ASSERT_TRUE(cgroup.empty<const char>());
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<char>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<int>(e1);
|
||||
registry.assign<char>(e1);
|
||||
|
||||
ASSERT_FALSE(group.empty());
|
||||
ASSERT_FALSE(group.empty<int>());
|
||||
ASSERT_FALSE(cgroup.empty<const char>());
|
||||
ASSERT_NO_THROW((group.begin()++));
|
||||
ASSERT_NO_THROW((++cgroup.begin()));
|
||||
|
||||
ASSERT_NE(group.begin(), group.end());
|
||||
ASSERT_NE(cgroup.begin(), cgroup.end());
|
||||
ASSERT_EQ(group.size(), typename decltype(group)::size_type{1});
|
||||
ASSERT_EQ(group.size<int>(), typename decltype(group)::size_type{1});
|
||||
ASSERT_EQ(cgroup.size<const char>(), typename decltype(group)::size_type{2});
|
||||
|
||||
registry.assign<int>(e0);
|
||||
|
||||
ASSERT_EQ(group.size(), typename decltype(group)::size_type{2});
|
||||
ASSERT_EQ(group.size<int>(), typename decltype(group)::size_type{2});
|
||||
ASSERT_EQ(cgroup.size<const char>(), typename decltype(group)::size_type{2});
|
||||
|
||||
registry.remove<int>(e0);
|
||||
|
||||
ASSERT_EQ(group.size(), typename decltype(group)::size_type{1});
|
||||
ASSERT_EQ(group.size<int>(), typename decltype(group)::size_type{1});
|
||||
ASSERT_EQ(cgroup.size<const char>(), typename decltype(group)::size_type{2});
|
||||
|
||||
registry.get<char>(e0) = '1';
|
||||
registry.get<char>(e1) = '2';
|
||||
registry.get<int>(e1) = 42;
|
||||
|
||||
for(auto entity: group) {
|
||||
ASSERT_EQ(std::get<0>(cgroup.get<const int, const char>(entity)), 42);
|
||||
ASSERT_EQ(std::get<1>(group.get<int, char>(entity)), '2');
|
||||
ASSERT_EQ(cgroup.get<const char>(entity), '2');
|
||||
}
|
||||
|
||||
ASSERT_EQ(*(group.data() + 0), e1);
|
||||
|
||||
ASSERT_EQ(*(group.data<int>() + 0), e1);
|
||||
ASSERT_EQ(*(group.data<char>() + 0), e0);
|
||||
ASSERT_EQ(*(cgroup.data<const char>() + 1), e1);
|
||||
|
||||
ASSERT_EQ(*(group.raw<int>() + 0), 42);
|
||||
ASSERT_EQ(*(group.raw<char>() + 0), '1');
|
||||
ASSERT_EQ(*(cgroup.raw<const char>() + 1), '2');
|
||||
|
||||
registry.remove<char>(e0);
|
||||
registry.remove<char>(e1);
|
||||
|
||||
ASSERT_EQ(group.begin(), group.end());
|
||||
ASSERT_EQ(cgroup.begin(), cgroup.end());
|
||||
ASSERT_TRUE(group.empty());
|
||||
|
||||
ASSERT_TRUE(group.capacity());
|
||||
|
||||
group.shrink_to_fit();
|
||||
|
||||
ASSERT_FALSE(group.capacity());
|
||||
}
|
||||
|
||||
TEST(NonOwningGroup, ElementAccess) {
|
||||
entt::registry registry;
|
||||
auto group = registry.group(entt::get<int, char>);
|
||||
auto cgroup = std::as_const(registry).group(entt::get<const int, const char>);
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<int>(e0);
|
||||
registry.assign<char>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<int>(e1);
|
||||
registry.assign<char>(e1);
|
||||
|
||||
for(typename decltype(group)::size_type i{}; i < group.size(); ++i) {
|
||||
ASSERT_EQ(group[i], i ? e0 : e1);
|
||||
ASSERT_EQ(cgroup[i], i ? e0 : e1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(NonOwningGroup, Contains) {
|
||||
entt::registry registry;
|
||||
auto group = registry.group(entt::get<int, char>);
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<int>(e0);
|
||||
registry.assign<char>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<int>(e1);
|
||||
registry.assign<char>(e1);
|
||||
|
||||
registry.destroy(e0);
|
||||
|
||||
ASSERT_FALSE(group.contains(e0));
|
||||
ASSERT_TRUE(group.contains(e1));
|
||||
}
|
||||
|
||||
TEST(NonOwningGroup, Empty) {
|
||||
entt::registry registry;
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<double>(e0);
|
||||
registry.assign<int>(e0);
|
||||
registry.assign<float>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<char>(e1);
|
||||
registry.assign<float>(e1);
|
||||
|
||||
ASSERT_TRUE(registry.group(entt::get<char, int, float>).empty());
|
||||
ASSERT_TRUE(registry.group(entt::get<double, char, int, float>).empty());
|
||||
}
|
||||
|
||||
TEST(NonOwningGroup, Each) {
|
||||
entt::registry registry;
|
||||
auto group = registry.group(entt::get<int, char>);
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<int>(e0);
|
||||
registry.assign<char>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<int>(e1);
|
||||
registry.assign<char>(e1);
|
||||
|
||||
auto cgroup = std::as_const(registry).group(entt::get<const int, const char>);
|
||||
std::size_t cnt = 0;
|
||||
|
||||
group.each([&cnt](auto, int &, char &) { ++cnt; });
|
||||
group.each([&cnt](int &, char &) { ++cnt; });
|
||||
|
||||
ASSERT_EQ(cnt, std::size_t{4});
|
||||
|
||||
cgroup.each([&cnt](auto, const int &, const char &) { --cnt; });
|
||||
cgroup.each([&cnt](const int &, const char &) { --cnt; });
|
||||
|
||||
ASSERT_EQ(cnt, std::size_t{0});
|
||||
}
|
||||
|
||||
TEST(NonOwningGroup, Sort) {
|
||||
entt::registry registry;
|
||||
auto group = registry.group(entt::get<const int, unsigned int>);
|
||||
|
||||
const auto e0 = registry.create();
|
||||
const auto e1 = registry.create();
|
||||
const auto e2 = registry.create();
|
||||
|
||||
registry.assign<unsigned int>(e0, 0u);
|
||||
registry.assign<unsigned int>(e1, 1u);
|
||||
registry.assign<unsigned int>(e2, 2u);
|
||||
|
||||
registry.assign<int>(e0, 0);
|
||||
registry.assign<int>(e1, 1);
|
||||
registry.assign<int>(e2, 2);
|
||||
|
||||
ASSERT_EQ(*(group.raw<unsigned int>() + 0u), 0u);
|
||||
ASSERT_EQ(*(group.raw<unsigned int>() + 1u), 1u);
|
||||
ASSERT_EQ(*(group.raw<unsigned int>() + 2u), 2u);
|
||||
|
||||
ASSERT_EQ(*(group.raw<const int>() + 0u), 0);
|
||||
ASSERT_EQ(*(group.raw<const int>() + 1u), 1);
|
||||
ASSERT_EQ(*(group.raw<const int>() + 2u), 2);
|
||||
|
||||
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 std::underlying_type_t<entt::entity>(lhs) < std::underlying_type_t<entt::entity>(rhs);
|
||||
});
|
||||
|
||||
ASSERT_EQ(*(group.raw<unsigned int>() + 0u), 0u);
|
||||
ASSERT_EQ(*(group.raw<unsigned int>() + 1u), 1u);
|
||||
ASSERT_EQ(*(group.raw<unsigned int>() + 2u), 2u);
|
||||
|
||||
ASSERT_EQ(*(group.raw<const int>() + 0u), 0);
|
||||
ASSERT_EQ(*(group.raw<const int>() + 1u), 1);
|
||||
ASSERT_EQ(*(group.raw<const int>() + 2u), 2);
|
||||
|
||||
ASSERT_EQ(*(group.data() + 0u), e2);
|
||||
ASSERT_EQ(*(group.data() + 1u), e1);
|
||||
ASSERT_EQ(*(group.data() + 2u), e0);
|
||||
|
||||
group.sort<const int>([](const int lhs, const int rhs) {
|
||||
return lhs > rhs;
|
||||
});
|
||||
|
||||
ASSERT_EQ(*(group.data() + 0u), e0);
|
||||
ASSERT_EQ(*(group.data() + 1u), e1);
|
||||
ASSERT_EQ(*(group.data() + 2u), e2);
|
||||
}
|
||||
|
||||
TEST(NonOwningGroup, SortAsAPool) {
|
||||
entt::registry registry;
|
||||
auto group = registry.group(entt::get<const int, unsigned int>);
|
||||
|
||||
const auto e0 = registry.create();
|
||||
const auto e1 = registry.create();
|
||||
const auto e2 = registry.create();
|
||||
|
||||
auto uval = 0u;
|
||||
auto ival = 0;
|
||||
|
||||
registry.assign<unsigned int>(e0, uval++);
|
||||
registry.assign<unsigned int>(e1, uval++);
|
||||
registry.assign<unsigned int>(e2, uval++);
|
||||
|
||||
registry.assign<int>(e0, ival++);
|
||||
registry.assign<int>(e1, ival++);
|
||||
registry.assign<int>(e2, ival++);
|
||||
|
||||
for(auto entity: group) {
|
||||
ASSERT_EQ(group.get<unsigned int>(entity), --uval);
|
||||
ASSERT_EQ(group.get<const int>(entity), --ival);
|
||||
}
|
||||
|
||||
registry.sort<unsigned int>(std::less<unsigned int>{});
|
||||
group.sort<unsigned int>();
|
||||
|
||||
for(auto entity: group) {
|
||||
ASSERT_EQ(group.get<unsigned int>(entity), uval++);
|
||||
ASSERT_EQ(group.get<const int>(entity), ival++);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(NonOwningGroup, IndexRebuiltOnDestroy) {
|
||||
entt::registry registry;
|
||||
auto group = registry.group(entt::get<int, unsigned int>);
|
||||
|
||||
const auto e0 = registry.create();
|
||||
const auto e1 = registry.create();
|
||||
|
||||
registry.assign<unsigned int>(e0, 0u);
|
||||
registry.assign<unsigned int>(e1, 1u);
|
||||
|
||||
registry.assign<int>(e0, 0);
|
||||
registry.assign<int>(e1, 1);
|
||||
|
||||
registry.destroy(e0);
|
||||
registry.assign<int>(registry.create(), 42);
|
||||
|
||||
ASSERT_EQ(group.size(), typename decltype(group)::size_type{1});
|
||||
ASSERT_EQ(group[{}], e1);
|
||||
ASSERT_EQ(group.get<int>(e1), 1);
|
||||
ASSERT_EQ(group.get<unsigned int>(e1), 1u);
|
||||
|
||||
group.each([e1](auto entity, auto ivalue, auto uivalue) {
|
||||
ASSERT_EQ(entity, e1);
|
||||
ASSERT_EQ(ivalue, 1);
|
||||
ASSERT_EQ(uivalue, 1u);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(NonOwningGroup, ConstNonConstAndAllInBetween) {
|
||||
entt::registry registry;
|
||||
auto group = registry.group(entt::get<int, const char, std::true_type>);
|
||||
|
||||
ASSERT_EQ(group.size(), decltype(group.size()){0});
|
||||
|
||||
const auto entity = registry.create();
|
||||
registry.assign<int>(entity, 0);
|
||||
registry.assign<char>(entity, 'c');
|
||||
registry.assign<std::true_type>(entity);
|
||||
|
||||
ASSERT_EQ(group.size(), decltype(group.size()){1});
|
||||
|
||||
ASSERT_TRUE((std::is_same_v<decltype(group.get<int>({})), int &>));
|
||||
ASSERT_TRUE((std::is_same_v<decltype(group.get<const char>({})), const char &>));
|
||||
ASSERT_TRUE((std::is_same_v<decltype(group.get<std::true_type>({})), std::true_type>));
|
||||
ASSERT_TRUE((std::is_same_v<decltype(group.get<int, const char, std::true_type>({})), std::tuple<int &, const char &, std::true_type>>));
|
||||
ASSERT_TRUE((std::is_same_v<decltype(group.raw<const char>()), const char *>));
|
||||
ASSERT_TRUE((std::is_same_v<decltype(group.raw<int>()), int *>));
|
||||
|
||||
group.each([](auto &&i, auto &&c, auto &&e) {
|
||||
ASSERT_TRUE((std::is_same_v<decltype(i), int &>));
|
||||
ASSERT_TRUE((std::is_same_v<decltype(c), const char &>));
|
||||
ASSERT_TRUE((std::is_same_v<decltype(e), std::true_type &&>));
|
||||
});
|
||||
}
|
||||
|
||||
TEST(NonOwningGroup, Find) {
|
||||
entt::registry registry;
|
||||
auto group = registry.group(entt::get<int, const char>);
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<int>(e0);
|
||||
registry.assign<char>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<int>(e1);
|
||||
registry.assign<char>(e1);
|
||||
|
||||
const auto e2 = registry.create();
|
||||
registry.assign<int>(e2);
|
||||
registry.assign<char>(e2);
|
||||
|
||||
const auto e3 = registry.create();
|
||||
registry.assign<int>(e3);
|
||||
registry.assign<char>(e3);
|
||||
|
||||
registry.remove<int>(e1);
|
||||
|
||||
ASSERT_NE(group.find(e0), group.end());
|
||||
ASSERT_EQ(group.find(e1), group.end());
|
||||
ASSERT_NE(group.find(e2), group.end());
|
||||
ASSERT_NE(group.find(e3), group.end());
|
||||
|
||||
auto it = group.find(e2);
|
||||
|
||||
ASSERT_EQ(*it, e2);
|
||||
ASSERT_EQ(*(++it), e3);
|
||||
ASSERT_EQ(*(++it), e0);
|
||||
ASSERT_EQ(++it, group.end());
|
||||
ASSERT_EQ(++group.find(e0), group.end());
|
||||
|
||||
const auto e4 = registry.create();
|
||||
registry.destroy(e4);
|
||||
const auto e5 = registry.create();
|
||||
registry.assign<int>(e5);
|
||||
registry.assign<char>(e5);
|
||||
|
||||
ASSERT_NE(group.find(e5), group.end());
|
||||
ASSERT_EQ(group.find(e4), group.end());
|
||||
}
|
||||
|
||||
TEST(NonOwningGroup, ExcludedComponents) {
|
||||
entt::registry registry;
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<int>(e0, 0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<int>(e1, 1);
|
||||
registry.assign<char>(e1);
|
||||
|
||||
const auto group = registry.group(entt::get<int>, entt::exclude<char>);
|
||||
|
||||
const auto e2 = registry.create();
|
||||
registry.assign<int>(e2, 2);
|
||||
|
||||
const auto e3 = registry.create();
|
||||
registry.assign<int>(e3, 3);
|
||||
registry.assign<char>(e3);
|
||||
|
||||
for(const auto entity: group) {
|
||||
if(entity == e0) {
|
||||
ASSERT_EQ(group.get<int>(e0), 0);
|
||||
} else if(entity == e2) {
|
||||
ASSERT_EQ(group.get<int>(e2), 2);
|
||||
} else {
|
||||
FAIL();
|
||||
}
|
||||
}
|
||||
|
||||
registry.assign<char>(e0);
|
||||
registry.assign<char>(e2);
|
||||
|
||||
ASSERT_TRUE(group.empty());
|
||||
|
||||
registry.remove<char>(e1);
|
||||
registry.remove<char>(e3);
|
||||
|
||||
for(const auto entity: group) {
|
||||
if(entity == e1) {
|
||||
ASSERT_EQ(group.get<int>(e1), 1);
|
||||
} else if(entity == e3) {
|
||||
ASSERT_EQ(group.get<int>(e3), 3);
|
||||
} else {
|
||||
FAIL();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(NonOwningGroup, EmptyAndNonEmptyTypes) {
|
||||
entt::registry registry;
|
||||
const auto group = registry.group(entt::get<int, empty_type>);
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<empty_type>(e0);
|
||||
registry.assign<int>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<empty_type>(e1);
|
||||
registry.assign<int>(e1);
|
||||
|
||||
registry.assign<int>(registry.create());
|
||||
|
||||
for(const auto entity: group) {
|
||||
ASSERT_TRUE(entity == e0 || entity == e1);
|
||||
}
|
||||
|
||||
group.each([e0, e1](const auto entity, const int &, empty_type) {
|
||||
ASSERT_TRUE(entity == e0 || entity == e1);
|
||||
});
|
||||
|
||||
ASSERT_EQ(group.size(), typename decltype(group)::size_type{2});
|
||||
}
|
||||
|
||||
TEST(NonOwningGroup, TrackEntitiesOnComponentDestruction) {
|
||||
entt::registry registry;
|
||||
const auto group = registry.group(entt::get<int>, entt::exclude<char>);
|
||||
const auto cgroup = std::as_const(registry).group(entt::get<const int>, entt::exclude<char>);
|
||||
|
||||
const auto entity = registry.create();
|
||||
registry.assign<int>(entity);
|
||||
registry.assign<char>(entity);
|
||||
|
||||
ASSERT_TRUE(group.empty());
|
||||
ASSERT_TRUE(cgroup.empty());
|
||||
|
||||
registry.remove<char>(entity);
|
||||
|
||||
ASSERT_FALSE(group.empty());
|
||||
ASSERT_FALSE(cgroup.empty());
|
||||
}
|
||||
|
||||
TEST(NonOwningGroup, Less) {
|
||||
entt::registry registry;
|
||||
const auto entity = std::get<0>(registry.create<int, entt::tag<"empty"_hs>>());
|
||||
registry.create<char>();
|
||||
|
||||
registry.group(entt::get<int, char, entt::tag<"empty"_hs>>).less([entity](const auto entt, int, char) {
|
||||
ASSERT_EQ(entity, entt);
|
||||
});
|
||||
|
||||
registry.group(entt::get<int, entt::tag<"empty"_hs>, char>).less([check = true](int, char) mutable {
|
||||
ASSERT_TRUE(check);
|
||||
check = false;
|
||||
});
|
||||
|
||||
registry.group(entt::get<entt::tag<"empty"_hs>, int, char>).less([entity](const auto entt, int, char) {
|
||||
ASSERT_EQ(entity, entt);
|
||||
});
|
||||
|
||||
registry.group(entt::get<int, char, double>).less([entity](const auto entt, int, char, double) {
|
||||
ASSERT_EQ(entity, entt);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(OwningGroup, Functionalities) {
|
||||
entt::registry registry;
|
||||
auto group = registry.group<int>(entt::get<char>);
|
||||
auto cgroup = std::as_const(registry).group<const int>(entt::get<const char>);
|
||||
|
||||
ASSERT_TRUE(group.empty());
|
||||
ASSERT_TRUE(group.empty<int>());
|
||||
ASSERT_TRUE(cgroup.empty<const char>());
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<char>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<int>(e1);
|
||||
registry.assign<char>(e1);
|
||||
|
||||
ASSERT_FALSE(group.empty());
|
||||
ASSERT_FALSE(group.empty<int>());
|
||||
ASSERT_FALSE(cgroup.empty<const char>());
|
||||
ASSERT_NO_THROW((group.begin()++));
|
||||
ASSERT_NO_THROW((++cgroup.begin()));
|
||||
|
||||
ASSERT_NE(group.begin(), group.end());
|
||||
ASSERT_NE(cgroup.begin(), cgroup.end());
|
||||
ASSERT_EQ(group.size(), typename decltype(group)::size_type{1});
|
||||
ASSERT_EQ(group.size<int>(), typename decltype(group)::size_type{1});
|
||||
ASSERT_EQ(cgroup.size<const char>(), typename decltype(group)::size_type{2});
|
||||
|
||||
registry.assign<int>(e0);
|
||||
|
||||
ASSERT_EQ(group.size(), typename decltype(group)::size_type{2});
|
||||
ASSERT_EQ(group.size<int>(), typename decltype(group)::size_type{2});
|
||||
ASSERT_EQ(cgroup.size<const char>(), typename decltype(group)::size_type{2});
|
||||
|
||||
registry.remove<int>(e0);
|
||||
|
||||
ASSERT_EQ(group.size(), typename decltype(group)::size_type{1});
|
||||
ASSERT_EQ(group.size<int>(), typename decltype(group)::size_type{1});
|
||||
ASSERT_EQ(cgroup.size<const char>(), typename decltype(group)::size_type{2});
|
||||
|
||||
registry.get<char>(e0) = '1';
|
||||
registry.get<char>(e1) = '2';
|
||||
registry.get<int>(e1) = 42;
|
||||
|
||||
ASSERT_EQ(*(cgroup.raw<const int>() + 0), 42);
|
||||
ASSERT_EQ(*(group.raw<int>() + 0), 42);
|
||||
|
||||
for(auto entity: group) {
|
||||
ASSERT_EQ(std::get<0>(cgroup.get<const int, const char>(entity)), 42);
|
||||
ASSERT_EQ(std::get<1>(group.get<int, char>(entity)), '2');
|
||||
ASSERT_EQ(cgroup.get<const char>(entity), '2');
|
||||
}
|
||||
|
||||
ASSERT_EQ(*(group.data() + 0), e1);
|
||||
|
||||
ASSERT_EQ(*(group.data<int>() + 0), e1);
|
||||
ASSERT_EQ(*(group.data<char>() + 0), e0);
|
||||
ASSERT_EQ(*(cgroup.data<const char>() + 1), e1);
|
||||
|
||||
ASSERT_EQ(*(group.raw<int>() + 0), 42);
|
||||
ASSERT_EQ(*(group.raw<char>() + 0), '1');
|
||||
ASSERT_EQ(*(cgroup.raw<const char>() + 1), '2');
|
||||
|
||||
registry.remove<char>(e0);
|
||||
registry.remove<char>(e1);
|
||||
|
||||
ASSERT_EQ(group.begin(), group.end());
|
||||
ASSERT_EQ(cgroup.begin(), cgroup.end());
|
||||
ASSERT_TRUE(group.empty());
|
||||
}
|
||||
|
||||
TEST(OwningGroup, ElementAccess) {
|
||||
entt::registry registry;
|
||||
auto group = registry.group<int>(entt::get<char>);
|
||||
auto cgroup = std::as_const(registry).group<const int>(entt::get<const char>);
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<int>(e0);
|
||||
registry.assign<char>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<int>(e1);
|
||||
registry.assign<char>(e1);
|
||||
|
||||
for(typename decltype(group)::size_type i{}; i < group.size(); ++i) {
|
||||
ASSERT_EQ(group[i], i ? e0 : e1);
|
||||
ASSERT_EQ(cgroup[i], i ? e0 : e1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(OwningGroup, Contains) {
|
||||
entt::registry registry;
|
||||
auto group = registry.group<int>(entt::get<char>);
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<int>(e0);
|
||||
registry.assign<char>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<int>(e1);
|
||||
registry.assign<char>(e1);
|
||||
|
||||
registry.destroy(e0);
|
||||
|
||||
ASSERT_FALSE(group.contains(e0));
|
||||
ASSERT_TRUE(group.contains(e1));
|
||||
}
|
||||
|
||||
TEST(OwningGroup, Empty) {
|
||||
entt::registry registry;
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<double>(e0);
|
||||
registry.assign<int>(e0);
|
||||
registry.assign<float>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<char>(e1);
|
||||
registry.assign<float>(e1);
|
||||
|
||||
ASSERT_TRUE((registry.group<char, int>(entt::get<float>).empty()));
|
||||
ASSERT_TRUE((registry.group<double, float>(entt::get<char, int>).empty()));
|
||||
}
|
||||
|
||||
TEST(OwningGroup, Each) {
|
||||
entt::registry registry;
|
||||
auto group = registry.group<int>(entt::get<char>);
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<int>(e0);
|
||||
registry.assign<char>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<int>(e1);
|
||||
registry.assign<char>(e1);
|
||||
|
||||
auto cgroup = std::as_const(registry).group<const int>(entt::get<const char>);
|
||||
std::size_t cnt = 0;
|
||||
|
||||
group.each([&cnt](auto, int &, char &) { ++cnt; });
|
||||
group.each([&cnt](int &, char &) { ++cnt; });
|
||||
|
||||
ASSERT_EQ(cnt, std::size_t{4});
|
||||
|
||||
cgroup.each([&cnt](auto, const int &, const char &) { --cnt; });
|
||||
cgroup.each([&cnt](const int &, const char &) { --cnt; });
|
||||
|
||||
ASSERT_EQ(cnt, std::size_t{0});
|
||||
}
|
||||
|
||||
TEST(OwningGroup, SortOrdered) {
|
||||
entt::registry registry;
|
||||
auto group = registry.group<boxed_int, char>();
|
||||
|
||||
entt::entity entities[5] = {
|
||||
registry.create(),
|
||||
registry.create(),
|
||||
registry.create(),
|
||||
registry.create(),
|
||||
registry.create()
|
||||
};
|
||||
|
||||
registry.assign<boxed_int>(entities[0], 12);
|
||||
registry.assign<char>(entities[0], 'a');
|
||||
|
||||
registry.assign<boxed_int>(entities[1], 9);
|
||||
registry.assign<char>(entities[1], 'b');
|
||||
|
||||
registry.assign<boxed_int>(entities[2], 6);
|
||||
registry.assign<char>(entities[2], 'c');
|
||||
|
||||
registry.assign<boxed_int>(entities[3], 1);
|
||||
registry.assign<boxed_int>(entities[4], 2);
|
||||
|
||||
group.sort([&group](const entt::entity lhs, const entt::entity rhs) {
|
||||
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.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<char>() + 0u), 'a');
|
||||
ASSERT_EQ(*(group.raw<char>() + 1u), 'b');
|
||||
ASSERT_EQ(*(group.raw<char>() + 2u), 'c');
|
||||
}
|
||||
|
||||
TEST(OwningGroup, SortReverse) {
|
||||
entt::registry registry;
|
||||
auto group = registry.group<boxed_int, char>();
|
||||
|
||||
entt::entity entities[5] = {
|
||||
registry.create(),
|
||||
registry.create(),
|
||||
registry.create(),
|
||||
registry.create(),
|
||||
registry.create()
|
||||
};
|
||||
|
||||
registry.assign<boxed_int>(entities[0], 6);
|
||||
registry.assign<char>(entities[0], 'a');
|
||||
|
||||
registry.assign<boxed_int>(entities[1], 9);
|
||||
registry.assign<char>(entities[1], 'b');
|
||||
|
||||
registry.assign<boxed_int>(entities[2], 12);
|
||||
registry.assign<char>(entities[2], 'c');
|
||||
|
||||
registry.assign<boxed_int>(entities[3], 1);
|
||||
registry.assign<boxed_int>(entities[4], 2);
|
||||
|
||||
group.sort<boxed_int>([](const auto &lhs, const auto &rhs) {
|
||||
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.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<char>() + 0u), 'c');
|
||||
ASSERT_EQ(*(group.raw<char>() + 1u), 'b');
|
||||
ASSERT_EQ(*(group.raw<char>() + 2u), 'a');
|
||||
}
|
||||
|
||||
TEST(OwningGroup, SortUnordered) {
|
||||
entt::registry registry;
|
||||
auto group = registry.group<boxed_int>(entt::get<char>);
|
||||
|
||||
entt::entity entities[7] = {
|
||||
registry.create(),
|
||||
registry.create(),
|
||||
registry.create(),
|
||||
registry.create(),
|
||||
registry.create(),
|
||||
registry.create(),
|
||||
registry.create()
|
||||
};
|
||||
|
||||
registry.assign<boxed_int>(entities[0], 6);
|
||||
registry.assign<char>(entities[0], 'c');
|
||||
|
||||
registry.assign<boxed_int>(entities[1], 3);
|
||||
registry.assign<char>(entities[1], 'b');
|
||||
|
||||
registry.assign<boxed_int>(entities[2], 1);
|
||||
registry.assign<char>(entities[2], 'a');
|
||||
|
||||
registry.assign<boxed_int>(entities[3], 9);
|
||||
registry.assign<char>(entities[3], 'd');
|
||||
|
||||
registry.assign<boxed_int>(entities[4], 12);
|
||||
registry.assign<char>(entities[4], 'e');
|
||||
|
||||
registry.assign<boxed_int>(entities[5], 4);
|
||||
registry.assign<boxed_int>(entities[6], 5);
|
||||
|
||||
group.sort<char>([](const auto lhs, const auto rhs) {
|
||||
return lhs < 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.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<char>() + 0u), 'c');
|
||||
ASSERT_EQ(*(group.raw<char>() + 1u), 'b');
|
||||
ASSERT_EQ(*(group.raw<char>() + 2u), 'a');
|
||||
ASSERT_EQ(*(group.raw<char>() + 3u), 'd');
|
||||
ASSERT_EQ(*(group.raw<char>() + 4u), 'e');
|
||||
}
|
||||
|
||||
TEST(OwningGroup, IndexRebuiltOnDestroy) {
|
||||
entt::registry registry;
|
||||
auto group = registry.group<int>(entt::get<unsigned int>);
|
||||
|
||||
const auto e0 = registry.create();
|
||||
const auto e1 = registry.create();
|
||||
|
||||
registry.assign<unsigned int>(e0, 0u);
|
||||
registry.assign<unsigned int>(e1, 1u);
|
||||
|
||||
registry.assign<int>(e0, 0);
|
||||
registry.assign<int>(e1, 1);
|
||||
|
||||
registry.destroy(e0);
|
||||
registry.assign<int>(registry.create(), 42);
|
||||
|
||||
ASSERT_EQ(group.size(), typename decltype(group)::size_type{1});
|
||||
ASSERT_EQ(group[{}], e1);
|
||||
ASSERT_EQ(group.get<int>(e1), 1);
|
||||
ASSERT_EQ(group.get<unsigned int>(e1), 1u);
|
||||
|
||||
group.each([e1](auto entity, auto ivalue, auto uivalue) {
|
||||
ASSERT_EQ(entity, e1);
|
||||
ASSERT_EQ(ivalue, 1);
|
||||
ASSERT_EQ(uivalue, 1u);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(OwningGroup, ConstNonConstAndAllInBetween) {
|
||||
entt::registry registry;
|
||||
auto group = registry.group<int, const char>(entt::get<double, const float, std::true_type>);
|
||||
|
||||
ASSERT_EQ(group.size(), decltype(group.size()){0});
|
||||
|
||||
const auto entity = registry.create();
|
||||
registry.assign<int>(entity, 0);
|
||||
registry.assign<char>(entity, 'c');
|
||||
registry.assign<double>(entity, 0.);
|
||||
registry.assign<float>(entity, 0.f);
|
||||
registry.assign<std::true_type>(entity);
|
||||
|
||||
ASSERT_EQ(group.size(), decltype(group.size()){1});
|
||||
|
||||
ASSERT_TRUE((std::is_same_v<decltype(group.get<int>({})), int &>));
|
||||
ASSERT_TRUE((std::is_same_v<decltype(group.get<const char>({})), const char &>));
|
||||
ASSERT_TRUE((std::is_same_v<decltype(group.get<double>({})), double &>));
|
||||
ASSERT_TRUE((std::is_same_v<decltype(group.get<const float>({})), const float &>));
|
||||
ASSERT_TRUE((std::is_same_v<decltype(group.get<std::true_type>({})), std::true_type>));
|
||||
ASSERT_TRUE((std::is_same_v<decltype(group.get<int, const char, double, const float, std::true_type>({})), std::tuple<int &, const char &, double &, const float &, std::true_type>>));
|
||||
ASSERT_TRUE((std::is_same_v<decltype(group.raw<const float>()), const float *>));
|
||||
ASSERT_TRUE((std::is_same_v<decltype(group.raw<double>()), double *>));
|
||||
ASSERT_TRUE((std::is_same_v<decltype(group.raw<const char>()), const char *>));
|
||||
ASSERT_TRUE((std::is_same_v<decltype(group.raw<int>()), int *>));
|
||||
|
||||
group.each([](auto &&i, auto &&c, auto &&d, auto &&f, auto &&e) {
|
||||
ASSERT_TRUE((std::is_same_v<decltype(i), int &>));
|
||||
ASSERT_TRUE((std::is_same_v<decltype(c), const char &>));
|
||||
ASSERT_TRUE((std::is_same_v<decltype(d), double &>));
|
||||
ASSERT_TRUE((std::is_same_v<decltype(f), const float &>));
|
||||
ASSERT_TRUE((std::is_same_v<decltype(e), std::true_type &&>));
|
||||
});
|
||||
}
|
||||
|
||||
TEST(OwningGroup, Find) {
|
||||
entt::registry registry;
|
||||
auto group = registry.group<int>(entt::get<const char>);
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<int>(e0);
|
||||
registry.assign<char>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<int>(e1);
|
||||
registry.assign<char>(e1);
|
||||
|
||||
const auto e2 = registry.create();
|
||||
registry.assign<int>(e2);
|
||||
registry.assign<char>(e2);
|
||||
|
||||
const auto e3 = registry.create();
|
||||
registry.assign<int>(e3);
|
||||
registry.assign<char>(e3);
|
||||
|
||||
registry.remove<int>(e1);
|
||||
|
||||
ASSERT_NE(group.find(e0), group.end());
|
||||
ASSERT_EQ(group.find(e1), group.end());
|
||||
ASSERT_NE(group.find(e2), group.end());
|
||||
ASSERT_NE(group.find(e3), group.end());
|
||||
|
||||
auto it = group.find(e2);
|
||||
|
||||
ASSERT_EQ(*it, e2);
|
||||
ASSERT_EQ(*(++it), e3);
|
||||
ASSERT_EQ(*(++it), e0);
|
||||
ASSERT_EQ(++it, group.end());
|
||||
ASSERT_EQ(++group.find(e0), group.end());
|
||||
|
||||
const auto e4 = registry.create();
|
||||
registry.destroy(e4);
|
||||
const auto e5 = registry.create();
|
||||
registry.assign<int>(e5);
|
||||
registry.assign<char>(e5);
|
||||
|
||||
ASSERT_NE(group.find(e5), group.end());
|
||||
ASSERT_EQ(group.find(e4), group.end());
|
||||
}
|
||||
|
||||
TEST(OwningGroup, ExcludedComponents) {
|
||||
entt::registry registry;
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<int>(e0, 0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<int>(e1, 1);
|
||||
registry.assign<char>(e1);
|
||||
|
||||
const auto group = registry.group<int>(entt::exclude<char, double>);
|
||||
|
||||
const auto e2 = registry.create();
|
||||
registry.assign<int>(e2, 2);
|
||||
|
||||
const auto e3 = registry.create();
|
||||
registry.assign<int>(e3, 3);
|
||||
registry.assign<double>(e3);
|
||||
|
||||
for(const auto entity: group) {
|
||||
if(entity == e0) {
|
||||
ASSERT_EQ(group.get<int>(e0), 0);
|
||||
} else if(entity == e2) {
|
||||
ASSERT_EQ(group.get<int>(e2), 2);
|
||||
} else {
|
||||
FAIL();
|
||||
}
|
||||
}
|
||||
|
||||
registry.assign<char>(e0);
|
||||
registry.assign<double>(e2);
|
||||
|
||||
ASSERT_TRUE(group.empty());
|
||||
|
||||
registry.remove<char>(e1);
|
||||
registry.remove<double>(e3);
|
||||
|
||||
for(const auto entity: group) {
|
||||
if(entity == e1) {
|
||||
ASSERT_EQ(group.get<int>(e1), 1);
|
||||
} else if(entity == e3) {
|
||||
ASSERT_EQ(group.get<int>(e3), 3);
|
||||
} else {
|
||||
FAIL();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(OwningGroup, EmptyAndNonEmptyTypes) {
|
||||
entt::registry registry;
|
||||
const auto group = registry.group<empty_type>(entt::get<int>);
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<empty_type>(e0);
|
||||
registry.assign<int>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<empty_type>(e1);
|
||||
registry.assign<int>(e1);
|
||||
|
||||
registry.assign<int>(registry.create());
|
||||
|
||||
for(const auto entity: group) {
|
||||
ASSERT_TRUE(entity == e0 || entity == e1);
|
||||
}
|
||||
|
||||
group.each([e0, e1](const auto entity, empty_type, const int &) {
|
||||
ASSERT_TRUE(entity == e0 || entity == e1);
|
||||
});
|
||||
|
||||
ASSERT_EQ(group.size(), typename decltype(group)::size_type{2});
|
||||
}
|
||||
|
||||
TEST(OwningGroup, TrackEntitiesOnComponentDestruction) {
|
||||
entt::registry registry;
|
||||
const auto group = registry.group<int>(entt::exclude<char>);
|
||||
const auto cgroup = std::as_const(registry).group<const int>(entt::exclude<char>);
|
||||
|
||||
const auto entity = registry.create();
|
||||
registry.assign<int>(entity);
|
||||
registry.assign<char>(entity);
|
||||
|
||||
ASSERT_TRUE(group.empty());
|
||||
ASSERT_TRUE(cgroup.empty());
|
||||
|
||||
registry.remove<char>(entity);
|
||||
|
||||
ASSERT_FALSE(group.empty());
|
||||
ASSERT_FALSE(cgroup.empty());
|
||||
}
|
||||
|
||||
TEST(OwningGroup, Less) {
|
||||
entt::registry registry;
|
||||
const auto entity = std::get<0>(registry.create<int, entt::tag<"empty"_hs>>());
|
||||
registry.create<char>();
|
||||
|
||||
registry.group<int>(entt::get<char, entt::tag<"empty"_hs>>).less([entity](const auto entt, int, char) {
|
||||
ASSERT_EQ(entity, entt);
|
||||
});
|
||||
|
||||
registry.group<char>(entt::get<entt::tag<"empty"_hs>, int>).less([check = true](int, char) mutable {
|
||||
ASSERT_TRUE(check);
|
||||
check = false;
|
||||
});
|
||||
|
||||
registry.group<entt::tag<"empty"_hs>>(entt::get<int, char>).less([entity](const auto entt, int, char) {
|
||||
ASSERT_EQ(entity, entt);
|
||||
});
|
||||
|
||||
registry.group<double>(entt::get<int, char>).less([entity](const auto entt, double, int, char) {
|
||||
ASSERT_EQ(entity, entt);
|
||||
});
|
||||
}
|
||||
@@ -2,72 +2,46 @@
|
||||
#include <entt/core/hashed_string.hpp>
|
||||
#include <entt/entity/helper.hpp>
|
||||
#include <entt/entity/registry.hpp>
|
||||
#include <entt/core/type_traits.hpp>
|
||||
|
||||
TEST(Helper, Dependency) {
|
||||
entt::DefaultRegistry registry;
|
||||
const auto entity = registry.create();
|
||||
entt::dependency<double, float>(registry.construction<int>());
|
||||
TEST(Helper, AsView) {
|
||||
entt::registry registry;
|
||||
const entt::registry cregistry;
|
||||
|
||||
ASSERT_FALSE(registry.has<double>(entity));
|
||||
ASSERT_FALSE(registry.has<float>(entity));
|
||||
|
||||
registry.assign<char>(entity);
|
||||
|
||||
ASSERT_FALSE(registry.has<double>(entity));
|
||||
ASSERT_FALSE(registry.has<float>(entity));
|
||||
|
||||
registry.assign<int>(entity);
|
||||
|
||||
ASSERT_TRUE(registry.has<double>(entity));
|
||||
ASSERT_TRUE(registry.has<float>(entity));
|
||||
ASSERT_EQ(registry.get<double>(entity), .0);
|
||||
ASSERT_EQ(registry.get<float>(entity), .0f);
|
||||
|
||||
registry.get<double>(entity) = .3;
|
||||
registry.get<float>(entity) = .1f;
|
||||
registry.remove<int>(entity);
|
||||
registry.assign<int>(entity);
|
||||
|
||||
ASSERT_EQ(registry.get<double>(entity), .3);
|
||||
ASSERT_EQ(registry.get<float>(entity), .1f);
|
||||
|
||||
registry.remove<int>(entity);
|
||||
registry.remove<float>(entity);
|
||||
registry.assign<int>(entity);
|
||||
|
||||
ASSERT_TRUE(registry.has<float>(entity));
|
||||
ASSERT_EQ(registry.get<double>(entity), .3);
|
||||
ASSERT_EQ(registry.get<float>(entity), .0f);
|
||||
|
||||
registry.remove<int>(entity);
|
||||
registry.remove<double>(entity);
|
||||
registry.remove<float>(entity);
|
||||
entt::dependency<double, float>(entt::break_t{}, registry.construction<int>());
|
||||
registry.assign<int>(entity);
|
||||
|
||||
ASSERT_FALSE(registry.has<double>(entity));
|
||||
ASSERT_FALSE(registry.has<float>(entity));
|
||||
([](entt::view<int, char>) {})(entt::as_view{registry});
|
||||
([](entt::view<const int, char>) {})(entt::as_view{registry});
|
||||
([](entt::view<const double>) {})(entt::as_view{cregistry});
|
||||
}
|
||||
|
||||
TEST(Helper, Label) {
|
||||
entt::DefaultRegistry registry;
|
||||
TEST(Helper, AsGroup) {
|
||||
entt::registry registry;
|
||||
const entt::registry cregistry;
|
||||
|
||||
([](entt::group<entt::exclude_t<int>, entt::get_t<char>, double>) {})(entt::as_group{registry});
|
||||
([](entt::group<entt::exclude_t<const int>, entt::get_t<char>, double>) {})(entt::as_group{registry});
|
||||
([](entt::group<entt::exclude_t<const int>, entt::get_t<const char>, double>) {})(entt::as_group{registry});
|
||||
([](entt::group<entt::exclude_t<const int>, entt::get_t<const char>, const double>) {})(entt::as_group{registry});
|
||||
}
|
||||
|
||||
TEST(Helper, Tag) {
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
registry.assign<entt::label<"foobar"_hs>>(entity);
|
||||
registry.assign<entt::tag<"foobar"_hs>>(entity);
|
||||
registry.assign<int>(entity, 42);
|
||||
int counter{};
|
||||
|
||||
ASSERT_FALSE(registry.has<entt::label<"barfoo"_hs>>(entity));
|
||||
ASSERT_TRUE(registry.has<entt::label<"foobar"_hs>>(entity));
|
||||
ASSERT_FALSE(registry.has<entt::tag<"barfoo"_hs>>(entity));
|
||||
ASSERT_TRUE(registry.has<entt::tag<"foobar"_hs>>(entity));
|
||||
|
||||
for(auto entity: registry.view<int, entt::label<"foobar"_hs>>()) {
|
||||
(void)entity;
|
||||
for(auto entt: registry.view<int, entt::tag<"foobar"_hs>>()) {
|
||||
(void)entt;
|
||||
++counter;
|
||||
}
|
||||
|
||||
ASSERT_NE(counter, 0);
|
||||
|
||||
for(auto entity: registry.view<entt::label<"foobar"_hs>>()) {
|
||||
(void)entity;
|
||||
for(auto entt: registry.view<entt::tag<"foobar"_hs>>()) {
|
||||
(void)entt;
|
||||
--counter;
|
||||
}
|
||||
|
||||
|
||||
283
test/entt/entity/observer.cpp
Normal file
283
test/entt/entity/observer.cpp
Normal file
@@ -0,0 +1,283 @@
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/observer.hpp>
|
||||
#include <entt/entity/registry.hpp>
|
||||
|
||||
TEST(Observer, Functionalities) {
|
||||
entt::registry registry;
|
||||
entt::observer observer{registry, entt::collector.group<int>()};
|
||||
|
||||
ASSERT_EQ(observer.size(), entt::observer::size_type{});
|
||||
ASSERT_TRUE(observer.empty());
|
||||
ASSERT_EQ(observer.data(), nullptr);
|
||||
ASSERT_EQ(observer.begin(), observer.end());
|
||||
|
||||
const auto entity = std::get<0>(registry.create<int>());
|
||||
|
||||
ASSERT_EQ(observer.size(), entt::observer::size_type{1});
|
||||
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());
|
||||
ASSERT_EQ(*observer.begin(), entity);
|
||||
|
||||
observer.clear();
|
||||
|
||||
ASSERT_EQ(observer.size(), entt::observer::size_type{});
|
||||
ASSERT_TRUE(observer.empty());
|
||||
|
||||
observer.disconnect();
|
||||
registry.remove<int>(entity);
|
||||
registry.assign<int>(entity);
|
||||
|
||||
ASSERT_EQ(observer.size(), entt::observer::size_type{});
|
||||
ASSERT_TRUE(observer.empty());
|
||||
}
|
||||
|
||||
TEST(Observer, AllOf) {
|
||||
constexpr auto collector = entt::collector
|
||||
.group<int, char>(entt::exclude<float>)
|
||||
.group<int, double>();
|
||||
|
||||
entt::registry registry;
|
||||
entt::observer observer{registry, collector};
|
||||
const auto entity = registry.create();
|
||||
|
||||
ASSERT_TRUE(observer.empty());
|
||||
|
||||
registry.assign<int>(entity);
|
||||
registry.assign<char>(entity);
|
||||
|
||||
ASSERT_EQ(observer.size(), entt::observer::size_type{1});
|
||||
ASSERT_FALSE(observer.empty());
|
||||
ASSERT_EQ(*observer.data(), entity);
|
||||
|
||||
registry.assign<double>(entity);
|
||||
|
||||
ASSERT_FALSE(observer.empty());
|
||||
|
||||
registry.remove<int>(entity);
|
||||
|
||||
ASSERT_TRUE(observer.empty());
|
||||
|
||||
registry.assign<float>(entity);
|
||||
registry.assign<int>(entity);
|
||||
|
||||
ASSERT_FALSE(observer.empty());
|
||||
|
||||
registry.remove<double>(entity);
|
||||
|
||||
ASSERT_TRUE(observer.empty());
|
||||
|
||||
registry.assign<double>(entity);
|
||||
observer.clear();
|
||||
|
||||
ASSERT_TRUE(observer.empty());
|
||||
|
||||
observer.disconnect();
|
||||
registry.assign_or_replace<int>(entity);
|
||||
registry.assign_or_replace<char>(entity);
|
||||
registry.reset<float>(entity);
|
||||
|
||||
ASSERT_TRUE(observer.empty());
|
||||
}
|
||||
|
||||
TEST(Observer, AllOfFiltered) {
|
||||
constexpr auto collector = entt::collector
|
||||
.group<int>().where<char>(entt::exclude<double>);
|
||||
|
||||
entt::registry registry;
|
||||
entt::observer observer{registry, collector};
|
||||
const auto entity = registry.create();
|
||||
|
||||
ASSERT_TRUE(observer.empty());
|
||||
|
||||
registry.assign<int>(entity);
|
||||
|
||||
ASSERT_EQ(observer.size(), entt::observer::size_type{});
|
||||
ASSERT_TRUE(observer.empty());
|
||||
ASSERT_EQ(observer.data(), nullptr);
|
||||
|
||||
registry.remove<int>(entity);
|
||||
registry.assign<char>(entity);
|
||||
registry.assign<double>(entity);
|
||||
registry.assign<int>(entity);
|
||||
|
||||
ASSERT_TRUE(observer.empty());
|
||||
|
||||
registry.remove<int>(entity);
|
||||
registry.remove<double>(entity);
|
||||
registry.assign<int>(entity);
|
||||
|
||||
ASSERT_EQ(observer.size(), entt::observer::size_type{1});
|
||||
ASSERT_FALSE(observer.empty());
|
||||
ASSERT_EQ(*observer.data(), entity);
|
||||
|
||||
registry.assign<double>(entity);
|
||||
|
||||
ASSERT_TRUE(observer.empty());
|
||||
|
||||
registry.remove<double>(entity);
|
||||
|
||||
ASSERT_TRUE(observer.empty());
|
||||
|
||||
observer.disconnect();
|
||||
registry.remove<int>(entity);
|
||||
registry.assign<int>(entity);
|
||||
|
||||
ASSERT_TRUE(observer.empty());
|
||||
}
|
||||
|
||||
TEST(Observer, Observe) {
|
||||
entt::registry registry;
|
||||
entt::observer observer{registry, entt::collector.replace<int>().replace<char>()};
|
||||
const auto entity = registry.create();
|
||||
|
||||
ASSERT_TRUE(observer.empty());
|
||||
|
||||
registry.assign<int>(entity);
|
||||
registry.assign<char>(entity);
|
||||
|
||||
ASSERT_TRUE(observer.empty());
|
||||
|
||||
registry.assign_or_replace<int>(entity);
|
||||
|
||||
ASSERT_EQ(observer.size(), entt::observer::size_type{1});
|
||||
ASSERT_FALSE(observer.empty());
|
||||
ASSERT_EQ(*observer.data(), entity);
|
||||
|
||||
observer.clear();
|
||||
registry.replace<char>(entity);
|
||||
|
||||
ASSERT_FALSE(observer.empty());
|
||||
|
||||
observer.clear();
|
||||
|
||||
ASSERT_TRUE(observer.empty());
|
||||
|
||||
observer.disconnect();
|
||||
registry.assign_or_replace<int>(entity);
|
||||
registry.assign_or_replace<char>(entity);
|
||||
|
||||
ASSERT_TRUE(observer.empty());
|
||||
}
|
||||
|
||||
TEST(Observer, ObserveFiltered) {
|
||||
constexpr auto collector = entt::collector
|
||||
.replace<int>().where<char>(entt::exclude<double>);
|
||||
|
||||
entt::registry registry;
|
||||
entt::observer observer{registry, collector};
|
||||
const auto entity = registry.create();
|
||||
|
||||
ASSERT_TRUE(observer.empty());
|
||||
|
||||
registry.assign<int>(entity);
|
||||
registry.replace<int>(entity);
|
||||
|
||||
ASSERT_EQ(observer.size(), entt::observer::size_type{});
|
||||
ASSERT_TRUE(observer.empty());
|
||||
ASSERT_EQ(observer.data(), nullptr);
|
||||
|
||||
registry.assign<char>(entity);
|
||||
registry.assign<double>(entity);
|
||||
registry.replace<int>(entity);
|
||||
|
||||
ASSERT_TRUE(observer.empty());
|
||||
|
||||
registry.remove<double>(entity);
|
||||
registry.replace<int>(entity);
|
||||
|
||||
ASSERT_EQ(observer.size(), entt::observer::size_type{1});
|
||||
ASSERT_FALSE(observer.empty());
|
||||
ASSERT_EQ(*observer.data(), entity);
|
||||
|
||||
registry.assign<double>(entity);
|
||||
|
||||
ASSERT_TRUE(observer.empty());
|
||||
|
||||
registry.remove<double>(entity);
|
||||
|
||||
ASSERT_TRUE(observer.empty());
|
||||
|
||||
observer.disconnect();
|
||||
registry.replace<int>(entity);
|
||||
|
||||
ASSERT_TRUE(observer.empty());
|
||||
}
|
||||
|
||||
TEST(Observer, AllOfObserve) {
|
||||
entt::registry registry;
|
||||
entt::observer observer{};
|
||||
const auto entity = registry.create();
|
||||
|
||||
observer.connect(registry, entt::collector.group<int>().replace<char>());
|
||||
|
||||
ASSERT_TRUE(observer.empty());
|
||||
|
||||
registry.assign<int>(entity);
|
||||
registry.assign<char>(entity);
|
||||
registry.replace<char>(entity);
|
||||
registry.remove<int>(entity);
|
||||
|
||||
ASSERT_EQ(observer.size(), entt::observer::size_type{1});
|
||||
ASSERT_FALSE(observer.empty());
|
||||
ASSERT_EQ(*observer.data(), entity);
|
||||
|
||||
registry.remove<char>(entity);
|
||||
registry.assign<char>(entity);
|
||||
|
||||
ASSERT_TRUE(observer.empty());
|
||||
|
||||
registry.replace<char>(entity);
|
||||
observer.clear();
|
||||
|
||||
ASSERT_TRUE(observer.empty());
|
||||
|
||||
observer.disconnect();
|
||||
registry.assign_or_replace<int>(entity);
|
||||
registry.assign_or_replace<char>(entity);
|
||||
|
||||
ASSERT_TRUE(observer.empty());
|
||||
}
|
||||
|
||||
TEST(Observer, CrossRulesCornerCase) {
|
||||
entt::registry registry;
|
||||
entt::observer observer{registry, entt::collector.group<int>().group<char>()};
|
||||
const auto entity = registry.create();
|
||||
|
||||
registry.assign<int>(entity);
|
||||
observer.clear();
|
||||
|
||||
ASSERT_TRUE(observer.empty());
|
||||
|
||||
registry.assign<char>(entity);
|
||||
registry.remove<int>(entity);
|
||||
|
||||
ASSERT_FALSE(observer.empty());
|
||||
}
|
||||
|
||||
TEST(Observer, Each) {
|
||||
entt::registry registry;
|
||||
entt::observer observer{registry, entt::collector.group<int>()};
|
||||
const auto entity = std::get<0>(registry.create<int>());
|
||||
|
||||
ASSERT_FALSE(observer.empty());
|
||||
ASSERT_EQ(observer.size(), entt::observer::size_type{1});
|
||||
|
||||
std::as_const(observer).each([entity](const auto entt) {
|
||||
ASSERT_EQ(entity, entt);
|
||||
});
|
||||
|
||||
ASSERT_FALSE(observer.empty());
|
||||
ASSERT_EQ(observer.size(), entt::observer::size_type{1});
|
||||
|
||||
observer.each([entity](const auto entt) {
|
||||
ASSERT_EQ(entity, entt);
|
||||
});
|
||||
|
||||
ASSERT_TRUE(observer.empty());
|
||||
ASSERT_EQ(observer.size(), entt::observer::size_type{});
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/prototype.hpp>
|
||||
#include <entt/entity/registry.hpp>
|
||||
|
||||
TEST(Prototype, SameRegistry) {
|
||||
entt::DefaultRegistry registry;
|
||||
entt::DefaultPrototype prototype{registry};
|
||||
const auto &cprototype = prototype;
|
||||
|
||||
ASSERT_FALSE(registry.empty());
|
||||
ASSERT_FALSE((prototype.has<int, char>()));
|
||||
|
||||
ASSERT_EQ(prototype.set<int>(2), 2);
|
||||
ASSERT_EQ(prototype.set<int>(3), 3);
|
||||
ASSERT_EQ(prototype.set<char>('c'), 'c');
|
||||
|
||||
ASSERT_EQ(prototype.get<int>(), 3);
|
||||
ASSERT_EQ(cprototype.get<char>(), 'c');
|
||||
ASSERT_EQ(std::get<0>(prototype.get<int, char>()), 3);
|
||||
ASSERT_EQ(std::get<1>(cprototype.get<int, char>()), 'c');
|
||||
|
||||
const auto e0 = prototype.create();
|
||||
|
||||
ASSERT_TRUE((prototype.has<int, char>()));
|
||||
ASSERT_FALSE(registry.orphan(e0));
|
||||
|
||||
const auto e1 = prototype();
|
||||
prototype(e0);
|
||||
|
||||
ASSERT_FALSE(registry.orphan(e0));
|
||||
ASSERT_FALSE(registry.orphan(e1));
|
||||
|
||||
ASSERT_TRUE((registry.has<int, char>(e0)));
|
||||
ASSERT_TRUE((registry.has<int, char>(e1)));
|
||||
|
||||
registry.remove<int>(e0);
|
||||
registry.remove<int>(e1);
|
||||
prototype.unset<int>();
|
||||
|
||||
ASSERT_FALSE((prototype.has<int, char>()));
|
||||
ASSERT_FALSE((prototype.has<int>()));
|
||||
ASSERT_TRUE((prototype.has<char>()));
|
||||
|
||||
prototype(e0);
|
||||
prototype(e1);
|
||||
|
||||
ASSERT_FALSE(registry.has<int>(e0));
|
||||
ASSERT_FALSE(registry.has<int>(e1));
|
||||
|
||||
ASSERT_EQ(registry.get<char>(e0), 'c');
|
||||
ASSERT_EQ(registry.get<char>(e1), 'c');
|
||||
|
||||
registry.get<char>(e0) = '*';
|
||||
prototype.assign(e0);
|
||||
|
||||
ASSERT_EQ(registry.get<char>(e0), '*');
|
||||
|
||||
registry.get<char>(e1) = '*';
|
||||
prototype.accommodate(e1);
|
||||
|
||||
ASSERT_EQ(registry.get<char>(e1), 'c');
|
||||
}
|
||||
|
||||
TEST(Prototype, OtherRegistry) {
|
||||
entt::DefaultRegistry registry;
|
||||
entt::DefaultRegistry repository;
|
||||
entt::DefaultPrototype prototype{repository};
|
||||
const auto &cprototype = prototype;
|
||||
|
||||
ASSERT_TRUE(registry.empty());
|
||||
ASSERT_FALSE((prototype.has<int, char>()));
|
||||
|
||||
ASSERT_EQ(prototype.set<int>(2), 2);
|
||||
ASSERT_EQ(prototype.set<int>(3), 3);
|
||||
ASSERT_EQ(prototype.set<char>('c'), 'c');
|
||||
|
||||
ASSERT_EQ(prototype.get<int>(), 3);
|
||||
ASSERT_EQ(cprototype.get<char>(), 'c');
|
||||
ASSERT_EQ(std::get<0>(prototype.get<int, char>()), 3);
|
||||
ASSERT_EQ(std::get<1>(cprototype.get<int, char>()), 'c');
|
||||
|
||||
const auto e0 = prototype.create(registry);
|
||||
|
||||
ASSERT_TRUE((prototype.has<int, char>()));
|
||||
ASSERT_FALSE(registry.orphan(e0));
|
||||
|
||||
const auto e1 = prototype(registry);
|
||||
prototype(registry, e0);
|
||||
|
||||
ASSERT_FALSE(registry.orphan(e0));
|
||||
ASSERT_FALSE(registry.orphan(e1));
|
||||
|
||||
ASSERT_TRUE((registry.has<int, char>(e0)));
|
||||
ASSERT_TRUE((registry.has<int, char>(e1)));
|
||||
|
||||
registry.remove<int>(e0);
|
||||
registry.remove<int>(e1);
|
||||
prototype.unset<int>();
|
||||
|
||||
ASSERT_FALSE((prototype.has<int, char>()));
|
||||
ASSERT_FALSE((prototype.has<int>()));
|
||||
ASSERT_TRUE((prototype.has<char>()));
|
||||
|
||||
prototype(registry, e0);
|
||||
prototype(registry, e1);
|
||||
|
||||
ASSERT_FALSE(registry.has<int>(e0));
|
||||
ASSERT_FALSE(registry.has<int>(e1));
|
||||
|
||||
ASSERT_EQ(registry.get<char>(e0), 'c');
|
||||
ASSERT_EQ(registry.get<char>(e1), 'c');
|
||||
|
||||
registry.get<char>(e0) = '*';
|
||||
prototype.assign(registry, e0);
|
||||
|
||||
ASSERT_EQ(registry.get<char>(e0), '*');
|
||||
|
||||
registry.get<char>(e1) = '*';
|
||||
prototype.accommodate(registry, e1);
|
||||
|
||||
ASSERT_EQ(registry.get<char>(e1), 'c');
|
||||
}
|
||||
|
||||
TEST(Prototype, RAII) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
{
|
||||
entt::DefaultPrototype prototype{registry};
|
||||
prototype.set<int>(0);
|
||||
|
||||
ASSERT_FALSE(registry.empty());
|
||||
}
|
||||
|
||||
ASSERT_TRUE(registry.empty());
|
||||
}
|
||||
|
||||
TEST(Prototype, MoveConstructionAssignment) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
entt::DefaultPrototype prototype{registry};
|
||||
prototype.set<int>(0);
|
||||
auto other{std::move(prototype)};
|
||||
const auto e0 = other();
|
||||
|
||||
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{2});
|
||||
ASSERT_TRUE(registry.has<int>(e0));
|
||||
|
||||
prototype = std::move(other);
|
||||
const auto e1 = prototype();
|
||||
|
||||
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{3});
|
||||
ASSERT_TRUE(registry.has<int>(e1));
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user