Compare commits
576 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
bd83fba6cd | ||
|
|
15b9255a25 | ||
|
|
6794d21487 | ||
|
|
118c4432ec | ||
|
|
ccda429bf1 | ||
|
|
590937d2a0 | ||
|
|
241827dd80 | ||
|
|
77b4e0b4bd | ||
|
|
6a53cb32d8 | ||
|
|
f12ff3b15e | ||
|
|
69ad8676b7 | ||
|
|
d0fd756f6b | ||
|
|
7f8ab67e9e | ||
|
|
0e68bb3d2c | ||
|
|
d5b3933752 | ||
|
|
0b7206a92d | ||
|
|
e875f306fd | ||
|
|
8ed5e5ee28 | ||
|
|
948b0d40f6 | ||
|
|
48f4feb7a7 | ||
|
|
ddc0a32bbc | ||
|
|
10a7c54364 | ||
|
|
a66fa9d844 | ||
|
|
a16f2ac15c | ||
|
|
b699797a40 | ||
|
|
a6e9520d06 | ||
|
|
f8310b1296 | ||
|
|
353bf99cd5 | ||
|
|
a478e4acc9 | ||
|
|
d810e0ba7d | ||
|
|
1e51ffdb72 | ||
|
|
7da1d1fc64 | ||
|
|
957697c383 | ||
|
|
107eb72225 | ||
|
|
810b77f9da | ||
|
|
5e3bc2049b | ||
|
|
229500347d | ||
|
|
1f461db0a1 | ||
|
|
6c55aafee3 | ||
|
|
d57e55b719 | ||
|
|
bf772e5fe5 | ||
|
|
413f3356ce | ||
|
|
f147326fe0 | ||
|
|
30c59644b6 | ||
|
|
3fa5acf2e6 | ||
|
|
3e6ded8823 | ||
|
|
612017aaa2 | ||
|
|
ef57d7e7b6 | ||
|
|
a8d0db5036 | ||
|
|
ad6b5f8fc1 | ||
|
|
e3cb6a0aec | ||
|
|
cbf18a7dc4 | ||
|
|
01559410a9 | ||
|
|
f2ab94fa7f | ||
|
|
d7394a8369 | ||
|
|
9feef11d6f | ||
|
|
dc4e5ddc3c | ||
|
|
8600781bb6 | ||
|
|
bdc7bbdc9d | ||
|
|
73badef594 | ||
|
|
9474e6c08c | ||
|
|
653ded0e6f | ||
|
|
e34bec7dee | ||
|
|
610b560fb5 | ||
|
|
0a03ddb8a7 | ||
|
|
f31790631a | ||
|
|
e07128760e | ||
|
|
dd02ae313d | ||
|
|
d14b0b6843 | ||
|
|
f763c8a777 | ||
|
|
4231b040d8 | ||
|
|
67c587e673 | ||
|
|
3978e24fd8 | ||
|
|
c45de0c032 | ||
|
|
ff935efea1 | ||
|
|
808464f47d | ||
|
|
c986a6c4dd | ||
|
|
17c0479343 | ||
|
|
d93238912a | ||
|
|
d6ef0956e6 | ||
|
|
05db0aad29 | ||
|
|
89bbdfa1fe | ||
|
|
871bda6198 | ||
|
|
20732c9206 | ||
|
|
2a34a3ebb6 | ||
|
|
3f04247a53 | ||
|
|
ca0a1f8f8b | ||
|
|
d8a9f0ca12 | ||
|
|
c2116b841e | ||
|
|
700cf69f18 | ||
|
|
ed5dee5218 | ||
|
|
b224dfdfac | ||
|
|
e7da68547f | ||
|
|
9a785ceb2e | ||
|
|
fca1cd5a1c | ||
|
|
24b862e32e | ||
|
|
2ec9043cf2 | ||
|
|
1102d63469 | ||
|
|
b89f39d78c | ||
|
|
7ba479c9c9 | ||
|
|
8ad6a2980c | ||
|
|
d3b6ed78d9 | ||
|
|
8bd5605c2a | ||
|
|
08dc2fcf33 | ||
|
|
caa8d16371 | ||
|
|
bce92b3d85 | ||
|
|
67858bf300 | ||
|
|
7157e7e77d | ||
|
|
cf5074bdc5 | ||
|
|
fda44063ce | ||
|
|
f9becda02c | ||
|
|
fb9fc952c6 | ||
|
|
18451edfe9 | ||
|
|
8c73cac72f | ||
|
|
c54cedf14b | ||
|
|
8ef4cdc9c3 | ||
|
|
c0213e84f6 | ||
|
|
29de6d89d4 | ||
|
|
c9bf38ce36 | ||
|
|
338eb75bab | ||
|
|
31b1b453b0 | ||
|
|
aaf0e145eb | ||
|
|
089b3e13fd | ||
|
|
e9da2ce12a | ||
|
|
92048ac17b | ||
|
|
5e8561a578 | ||
|
|
d2f5e13074 | ||
|
|
820178f006 | ||
|
|
0a36a91e6d | ||
|
|
5013a92795 | ||
|
|
d81ecfec32 | ||
|
|
e99d7e2c3c | ||
|
|
d417984ff3 | ||
|
|
d38b3e641b | ||
|
|
28ce491dd5 | ||
|
|
c260d72125 | ||
|
|
d1d1b3156d | ||
|
|
472064b751 | ||
|
|
95ab9a0b70 | ||
|
|
4b03f6a039 | ||
|
|
c3460727fa | ||
|
|
2cc1850212 | ||
|
|
2d7443acaf | ||
|
|
13d0b0940c | ||
|
|
c101797924 | ||
|
|
83b55f8e3f | ||
|
|
b3b6362cd9 | ||
|
|
fc9af32d5f | ||
|
|
4cd1025011 | ||
|
|
5233fe8abc | ||
|
|
041e31ea78 | ||
|
|
7a3e881099 | ||
|
|
631bf42f84 | ||
|
|
1f704a7019 | ||
|
|
d295c88474 | ||
|
|
1dd9da4dff | ||
|
|
f2eb0c8427 | ||
|
|
c8ba11faf8 | ||
|
|
a2e243d992 | ||
|
|
c588fff5ca | ||
|
|
87f9599fea | ||
|
|
0459599b1d | ||
|
|
9447b1a696 | ||
|
|
0ccb7443c2 | ||
|
|
02cf27091f | ||
|
|
fdfbd04503 | ||
|
|
866c18200a | ||
|
|
c1cada49d4 | ||
|
|
7bf550a75f | ||
|
|
9c540c03aa | ||
|
|
b3df46db19 | ||
|
|
7ca615a1c1 | ||
|
|
c83db557a6 | ||
|
|
d54594f11d | ||
|
|
434e38608f | ||
|
|
871f090ca0 | ||
|
|
d1d235e025 | ||
|
|
e822a5fd53 | ||
|
|
7b82a4ae50 | ||
|
|
c532e9f2eb | ||
|
|
3fd034816e | ||
|
|
bb4b868c79 | ||
|
|
3b3da11a36 | ||
|
|
f2cbb5306b | ||
|
|
94ede1b324 | ||
|
|
0367248338 | ||
|
|
936db30e58 | ||
|
|
4822f0dd11 | ||
|
|
456d220829 | ||
|
|
b459ba6ea7 | ||
|
|
a19ef9bd16 | ||
|
|
59cec88a28 | ||
|
|
3ebc75af80 | ||
|
|
4dce474e03 | ||
|
|
31a18da578 | ||
|
|
8c499850fc | ||
|
|
6b6998a247 | ||
|
|
a6cb0fc856 | ||
|
|
e36b93e87b | ||
|
|
1e3723b8bb | ||
|
|
412372289e | ||
|
|
96f7e66073 | ||
|
|
6040f8f263 | ||
|
|
9761b6e14a | ||
|
|
cb49910ed2 | ||
|
|
62bd742673 | ||
|
|
42d0a3d734 | ||
|
|
f0f8681455 | ||
|
|
c801afddcb | ||
|
|
20e0e1333e | ||
|
|
a6b373fec4 | ||
|
|
41c77720bb | ||
|
|
92e6340120 | ||
|
|
1221f63cbd | ||
|
|
0f24418891 | ||
|
|
f477c0ab87 | ||
|
|
9358691901 | ||
|
|
cd343ba598 | ||
|
|
50069d3743 | ||
|
|
1e03f27f23 | ||
|
|
36bb55a9ce | ||
|
|
451e4050db | ||
|
|
367fd3e87f | ||
|
|
a67a2e12fd | ||
|
|
292978daf0 | ||
|
|
85a4a76a14 | ||
|
|
9d0ab7ed70 | ||
|
|
3d5b6a5e0b | ||
|
|
ab20372093 | ||
|
|
ab887f30e4 | ||
|
|
6cb6a8c25f | ||
|
|
9d1d2aca0a | ||
|
|
75cb2cd1f7 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,2 +1,5 @@
|
||||
# QtCreator
|
||||
*.user
|
||||
conan/test_package/build
|
||||
|
||||
# IDEs
|
||||
.idea
|
||||
|
||||
72
.travis.yml
72
.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.1
|
||||
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,5 +86,12 @@ install:
|
||||
|
||||
script:
|
||||
- mkdir -p build && cd build
|
||||
- cmake .. && make -j4
|
||||
- CTEST_OUTPUT_ON_FAILURE=1 make test
|
||||
- cmake -DBUILD_TESTING=ON -DCMAKE_BUILD_TYPE=Release .. && make -j4
|
||||
- CTEST_OUTPUT_ON_FAILURE=1 ctest -j4
|
||||
|
||||
deploy:
|
||||
provider: script
|
||||
script: scripts/update_packages.sh $TRAVIS_TAG
|
||||
on:
|
||||
tags: true
|
||||
condition: “$NON_CONAN_DEPLOYMENT = “True”
|
||||
|
||||
34
AUTHORS
34
AUTHORS
@@ -1,7 +1,37 @@
|
||||
# Author
|
||||
|
||||
Michele Caini aka skypjack
|
||||
skypjack
|
||||
|
||||
# Contributors
|
||||
|
||||
Paolo Monteverde aka morbo84
|
||||
BenediktConze
|
||||
bjadamson
|
||||
ceeac
|
||||
ColinH
|
||||
corystegel
|
||||
Croydon
|
||||
dbacchet
|
||||
dBagrat
|
||||
djarek
|
||||
DonKult
|
||||
drglove
|
||||
eugeneko
|
||||
gale83
|
||||
ghost
|
||||
Green-Sky
|
||||
Milerius
|
||||
morbo84
|
||||
m-waka
|
||||
Kerndog73
|
||||
Paolo-Oliverio
|
||||
pgruenbacher
|
||||
prowolf
|
||||
vblanco20-1
|
||||
willtunnels
|
||||
WizardIke
|
||||
w1th0utnam3
|
||||
|
||||
# Special thanks for patrons
|
||||
|
||||
Arn
|
||||
tpisto
|
||||
|
||||
192
CMakeLists.txt
192
CMakeLists.txt
@@ -2,7 +2,7 @@
|
||||
# EnTT
|
||||
#
|
||||
|
||||
cmake_minimum_required(VERSION 3.2)
|
||||
cmake_minimum_required(VERSION 3.7.2)
|
||||
|
||||
#
|
||||
# Building in-tree is not allowed (we take care of your craziness).
|
||||
@@ -16,7 +16,9 @@ endif()
|
||||
# Project configuration
|
||||
#
|
||||
|
||||
project(entt VERSION 2.2.0)
|
||||
project(EnTT VERSION 3.0.0)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Debug)
|
||||
@@ -29,58 +31,165 @@ set(PROJECT_AUTHOR_EMAIL "michele.caini@gmail.com")
|
||||
|
||||
message("*")
|
||||
message("* ${PROJECT_NAME} v${PROJECT_VERSION} (${CMAKE_BUILD_TYPE})")
|
||||
message("* Copyright (c) 2017 ${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)
|
||||
option(USE_ASAN "Use address sanitizer by adding -fsanitize=address -fno-omit-frame-pointer flags" OFF)
|
||||
option(USE_COMPILE_OPTIONS "Use compile options from EnTT." ON)
|
||||
|
||||
#
|
||||
# Compiler stuff
|
||||
#
|
||||
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
if(NOT MSVC AND USE_LIBCPP)
|
||||
include(CheckCXXSourceCompiles)
|
||||
include(CMakePushCheckState)
|
||||
|
||||
if(NOT MSVC)
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic -Wall")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DRELEASE")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g -DDEBUG")
|
||||
cmake_push_check_state()
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
# it seems that -O3 ruins the performance when using clang ...
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
|
||||
else()
|
||||
# ... on the other side, GCC is incredibly comfortable with it.
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
|
||||
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -stdlib=libc++")
|
||||
|
||||
check_cxx_source_compiles("
|
||||
#include<type_traits>
|
||||
int main() { return std::is_same_v<int, int> ? 0 : 1; }
|
||||
" HAS_LIBCPP)
|
||||
|
||||
if(NOT HAS_LIBCPP)
|
||||
message(WARNING "The option USE_LIBCPP is set (by default) but libc++ is not available. The flag will not be added to the target.")
|
||||
endif()
|
||||
|
||||
cmake_pop_check_state()
|
||||
endif()
|
||||
|
||||
#
|
||||
# CMake configuration
|
||||
# Add EnTT target
|
||||
#
|
||||
|
||||
set(PROJECT_CMAKE_IN ${entt_SOURCE_DIR}/cmake/in)
|
||||
set(PROJECT_DEPS_DIR ${entt_SOURCE_DIR}/deps)
|
||||
set(PROJECT_SRC_DIR ${entt_SOURCE_DIR}/src)
|
||||
add_library(EnTT INTERFACE)
|
||||
|
||||
set(PROJECT_RUNTIME_OUTPUT_DIRECTORY bin)
|
||||
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>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
||||
)
|
||||
|
||||
target_compile_definitions(
|
||||
EnTT
|
||||
INTERFACE $<$<AND:$<CONFIG:Debug>,$<NOT:$<CXX_COMPILER_ID:MSVC>>>:DEBUG>
|
||||
INTERFACE $<$<AND:$<CONFIG:Release>,$<NOT:$<CXX_COMPILER_ID:MSVC>>>:RELEASE>
|
||||
)
|
||||
|
||||
if(USE_ASAN)
|
||||
target_compile_options(EnTT INTERFACE $<$<AND:$<CONFIG:Debug>,$<NOT:$<CXX_COMPILER_ID:MSVC>>>:-fsanitize=address -fno-omit-frame-pointer>)
|
||||
target_link_libraries(EnTT INTERFACE $<$<AND:$<CONFIG:Debug>,$<NOT:$<CXX_COMPILER_ID:MSVC>>>:-fsanitize=address -fno-omit-frame-pointer>)
|
||||
endif()
|
||||
|
||||
if(USE_COMPILE_OPTIONS)
|
||||
target_compile_options(
|
||||
EnTT
|
||||
INTERFACE $<$<AND:$<CONFIG:Debug>,$<NOT:$<CXX_COMPILER_ID:MSVC>>>:-O0 -g>
|
||||
# it seems that -O3 ruins a bit the performance when using clang ...
|
||||
INTERFACE $<$<AND:$<CONFIG:Release>,$<CXX_COMPILER_ID:Clang>>:-O2>
|
||||
# ... on the other side, GCC is incredibly comfortable with it.
|
||||
INTERFACE $<$<AND:$<CONFIG:Release>,$<CXX_COMPILER_ID:GNU>>:-O3>
|
||||
)
|
||||
endif()
|
||||
|
||||
if(HAS_LIBCPP)
|
||||
target_compile_options(EnTT BEFORE INTERFACE -stdlib=libc++)
|
||||
endif()
|
||||
|
||||
target_compile_features(EnTT INTERFACE cxx_std_17)
|
||||
|
||||
#
|
||||
# Install EnTT
|
||||
#
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
|
||||
set(CUSTOM_INSTALL_CONFIGDIR cmake)
|
||||
else()
|
||||
set(CUSTOM_INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/entt)
|
||||
endif()
|
||||
|
||||
install(DIRECTORY src/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
install(TARGETS EnTT EXPORT EnTTTargets)
|
||||
|
||||
export(EXPORT EnTTTargets FILE ${EnTT_BINARY_DIR}/EnTTTargets.cmake)
|
||||
|
||||
install(
|
||||
EXPORT EnTTTargets
|
||||
FILE EnTTTargets.cmake
|
||||
DESTINATION ${CUSTOM_INSTALL_CONFIGDIR}
|
||||
)
|
||||
|
||||
#
|
||||
# Build tree package config file
|
||||
#
|
||||
|
||||
configure_file(cmake/in/EnTTBuildConfig.cmake.in EnTTConfig.cmake @ONLY)
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
|
||||
#
|
||||
# Install tree package config file
|
||||
#
|
||||
|
||||
configure_package_config_file(
|
||||
cmake/in/EnTTConfig.cmake.in
|
||||
${CUSTOM_INSTALL_CONFIGDIR}/EnTTConfig.cmake
|
||||
INSTALL_DESTINATION ${CUSTOM_INSTALL_CONFIGDIR}
|
||||
PATH_VARS CMAKE_INSTALL_INCLUDEDIR
|
||||
)
|
||||
|
||||
write_basic_package_version_file(
|
||||
${EnTT_BINARY_DIR}/EnTTConfigVersion.cmake
|
||||
VERSION ${PROJECT_VERSION}
|
||||
COMPATIBILITY AnyNewerVersion
|
||||
)
|
||||
|
||||
install(
|
||||
FILES
|
||||
${EnTT_BINARY_DIR}/${CUSTOM_INSTALL_CONFIGDIR}/EnTTConfig.cmake
|
||||
${EnTT_BINARY_DIR}/EnTTConfigVersion.cmake
|
||||
DESTINATION ${CUSTOM_INSTALL_CONFIGDIR}
|
||||
)
|
||||
|
||||
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)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
# gtest, gtest_main, gmock and gmock_main targets are available from now on
|
||||
set(GOOGLETEST_DEPS_DIR ${PROJECT_DEPS_DIR}/googletest)
|
||||
configure_file(${PROJECT_CMAKE_IN}/googletest.in ${GOOGLETEST_DEPS_DIR}/CMakeLists.txt)
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . WORKING_DIRECTORY ${GOOGLETEST_DEPS_DIR})
|
||||
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)
|
||||
option(FIND_GTEST_PACKAGE "Enable finding gtest package." OFF)
|
||||
|
||||
if(FIND_GTEST_PACKAGE)
|
||||
find_package(GTest REQUIRED)
|
||||
else()
|
||||
# gtest, gtest_main, gmock and gmock_main targets are available from now on
|
||||
set(GOOGLETEST_DEPS_DIR ${EnTT_SOURCE_DIR}/deps/googletest)
|
||||
configure_file(${EnTT_SOURCE_DIR}/cmake/in/googletest.in ${GOOGLETEST_DEPS_DIR}/CMakeLists.txt)
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . WORKING_DIRECTORY ${GOOGLETEST_DEPS_DIR})
|
||||
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)
|
||||
|
||||
enable_testing()
|
||||
add_subdirectory(test)
|
||||
@@ -90,8 +199,27 @@ endif()
|
||||
# Documentation
|
||||
#
|
||||
|
||||
find_package(Doxygen 1.8)
|
||||
option(BUILD_DOCS "Enable building with documentation." OFF)
|
||||
|
||||
if(DOXYGEN_FOUND)
|
||||
add_subdirectory(docs)
|
||||
if(BUILD_DOCS)
|
||||
find_package(Doxygen 1.8)
|
||||
|
||||
if(DOXYGEN_FOUND)
|
||||
add_subdirectory(docs)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
#
|
||||
# AOB
|
||||
#
|
||||
|
||||
add_custom_target(
|
||||
entt_aob
|
||||
SOURCES
|
||||
appveyor.yml
|
||||
AUTHORS
|
||||
LICENSE
|
||||
README.md
|
||||
TODO
|
||||
.travis.yml
|
||||
)
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 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
|
||||
|
||||
28
TODO
Normal file
28
TODO
Normal file
@@ -0,0 +1,28 @@
|
||||
* 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
|
||||
* debugging tools (#60): the issue online already contains interesting tips on this, look at it
|
||||
* 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)
|
||||
* 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?
|
||||
* empty components model allows for shared components and prefabs unity-like
|
||||
- each with entity return the shared component multiple times, one per entity that refers to it
|
||||
- each components only return actual component, so shared components are returned only once
|
||||
* types defined at runtime that refer to the same compile-time type (but to different pools) are possible, the library is almost there
|
||||
* add take functionality, eg registry.take(entity, other); where it takes the entity and all its components from registry and move them to other
|
||||
* 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
|
||||
* review sparse set to allow customization (mix pack in the spec, base is position only)
|
||||
- non-owning groups can iterate pages and skip empty ones, this should mitigate the lack of the packed array
|
||||
* review 64 bit id: user defined area + dedicated member on the registry to set it
|
||||
* early out in views using bitmasks with bloom filter like access based on modulus
|
||||
- standard each, use bitmask to speed up the whole thing and avoid accessing the pools to test for the page
|
||||
- iterator based each with a couple of iterators passed from outside (use bitmask + has)
|
||||
* stable component handle that isn't affected by reallocations
|
||||
* multi component registry::remove and some others?
|
||||
* move CONTRIBUTING.md within doc (for GitHub)
|
||||
* reactive systems
|
||||
@@ -14,7 +14,10 @@ configuration:
|
||||
|
||||
before_build:
|
||||
- cd %BUILD_DIR%
|
||||
- cmake .. -G"Visual Studio 15 2017"
|
||||
- cmake .. -DBUILD_TESTING=ON -DBUILD_LIB=ON -DCMAKE_CXX_FLAGS=/W1 -G"Visual Studio 15 2017"
|
||||
|
||||
after_build:
|
||||
- ctest -C Release -j4
|
||||
|
||||
build:
|
||||
parallel: true
|
||||
|
||||
6
cmake/in/EnTTBuildConfig.cmake.in
Normal file
6
cmake/in/EnTTBuildConfig.cmake.in
Normal file
@@ -0,0 +1,6 @@
|
||||
set(ENTT_VERSION "@PROJECT_VERSION@")
|
||||
set(ENTT_INCLUDE_DIRS "@CMAKE_CURRENT_SOURCE_DIR@/src")
|
||||
|
||||
if(NOT CMAKE_VERSION VERSION_LESS "3.0")
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/EnTTTargets.cmake")
|
||||
endif()
|
||||
11
cmake/in/EnTTConfig.cmake.in
Normal file
11
cmake/in/EnTTConfig.cmake.in
Normal file
@@ -0,0 +1,11 @@
|
||||
set(ENTT_VERSION "@PROJECT_VERSION@")
|
||||
|
||||
@PACKAGE_INIT@
|
||||
|
||||
set_and_check(ENTT_INCLUDE_DIRS "@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@")
|
||||
|
||||
if(NOT CMAKE_VERSION VERSION_LESS "3.0")
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/EnTTTargets.cmake")
|
||||
endif()
|
||||
|
||||
check_required_components("@PROJECT_NAME@")
|
||||
19
cmake/in/cereal.in
Normal file
19
cmake/in/cereal.in
Normal file
@@ -0,0 +1,19 @@
|
||||
project(cereal-download NONE)
|
||||
cmake_minimum_required(VERSION 3.2)
|
||||
|
||||
include(ExternalProject)
|
||||
|
||||
ExternalProject_Add(
|
||||
cereal
|
||||
GIT_REPOSITORY https://github.com/USCiLab/cereal.git
|
||||
GIT_TAG v1.2.2
|
||||
DOWNLOAD_DIR ${CEREAL_DEPS_DIR}
|
||||
TMP_DIR ${CEREAL_DEPS_DIR}/tmp
|
||||
STAMP_DIR ${CEREAL_DEPS_DIR}/stamp
|
||||
SOURCE_DIR ${CEREAL_DEPS_DIR}/src
|
||||
BINARY_DIR ${CEREAL_DEPS_DIR}/build
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
TEST_COMMAND ""
|
||||
)
|
||||
19
cmake/in/duktape.in
Normal file
19
cmake/in/duktape.in
Normal file
@@ -0,0 +1,19 @@
|
||||
project(duktape-download NONE)
|
||||
cmake_minimum_required(VERSION 3.2)
|
||||
|
||||
include(ExternalProject)
|
||||
|
||||
ExternalProject_Add(
|
||||
duktape
|
||||
GIT_REPOSITORY https://github.com/svaarala/duktape-releases.git
|
||||
GIT_TAG v2.2.0
|
||||
DOWNLOAD_DIR ${DUKTAPE_DEPS_DIR}
|
||||
TMP_DIR ${DUKTAPE_DEPS_DIR}/tmp
|
||||
STAMP_DIR ${DUKTAPE_DEPS_DIR}/stamp
|
||||
SOURCE_DIR ${DUKTAPE_DEPS_DIR}/src
|
||||
BINARY_DIR ${DUKTAPE_DEPS_DIR}/build
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
TEST_COMMAND ""
|
||||
)
|
||||
@@ -6,7 +6,7 @@ include(ExternalProject)
|
||||
ExternalProject_Add(
|
||||
googletest
|
||||
GIT_REPOSITORY https://github.com/google/googletest.git
|
||||
GIT_TAG release-1.8.0
|
||||
GIT_TAG master
|
||||
DOWNLOAD_DIR ${GOOGLETEST_DEPS_DIR}
|
||||
TMP_DIR ${GOOGLETEST_DEPS_DIR}/tmp
|
||||
STAMP_DIR ${GOOGLETEST_DEPS_DIR}/stamp
|
||||
|
||||
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;
|
||||
}
|
||||
23
conanfile.py
Normal file
23
conanfile.py
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/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_id(self):
|
||||
self.info.header_only()
|
||||
@@ -2,26 +2,38 @@
|
||||
# Doxygen configuration (documentation)
|
||||
#
|
||||
|
||||
set(TARGET_DOCS docs)
|
||||
|
||||
set(DOXY_IN_FILE doxy.in)
|
||||
|
||||
set(DOXY_SOURCE_DIRECTORY ${PROJECT_SRC_DIR})
|
||||
set(DOXY_SOURCE_DIRECTORY ${EnTT_SOURCE_DIR}/src)
|
||||
set(DOXY_DOCS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
set(DOXY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
set(DOXY_CFG_FILE doxy.cfg)
|
||||
|
||||
configure_file(${DOXY_IN_FILE} ${DOXY_CFG_FILE} @ONLY)
|
||||
configure_file(doxy.in doxy.cfg @ONLY)
|
||||
|
||||
add_custom_target(
|
||||
${TARGET_DOCS}
|
||||
COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/${DOXY_CFG_FILE}
|
||||
WORKING_DIRECTORY ${entt_SOURCE_DIR}
|
||||
docs ALL
|
||||
COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxy.cfg
|
||||
WORKING_DIRECTORY ${EnTT_SOURCE_DIR}
|
||||
VERBATIM
|
||||
SOURCES ${DOXY_IN_FILE}
|
||||
SOURCES doxy.in
|
||||
)
|
||||
|
||||
install(
|
||||
DIRECTORY ${DOXY_OUTPUT_DIRECTORY}/html
|
||||
DESTINATION share/${PROJECT_NAME}-${PROJECT_VERSION}/
|
||||
)
|
||||
|
||||
add_custom_target(
|
||||
docs_aob
|
||||
SOURCES
|
||||
md/CONTRIBUTING.md
|
||||
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
|
||||
dox/extra.dox
|
||||
)
|
||||
|
||||
395
docs/LICENSE
395
docs/LICENSE
@@ -1,395 +0,0 @@
|
||||
Attribution 4.0 International
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
||||
does not provide legal services or legal advice. Distribution of
|
||||
Creative Commons public licenses does not create a lawyer-client or
|
||||
other relationship. Creative Commons makes its licenses and related
|
||||
information available on an "as-is" basis. Creative Commons gives no
|
||||
warranties regarding its licenses, any material licensed under their
|
||||
terms and conditions, or any related information. Creative Commons
|
||||
disclaims all liability for damages resulting from their use to the
|
||||
fullest extent possible.
|
||||
|
||||
Using Creative Commons Public Licenses
|
||||
|
||||
Creative Commons public licenses provide a standard set of terms and
|
||||
conditions that creators and other rights holders may use to share
|
||||
original works of authorship and other material subject to copyright
|
||||
and certain other rights specified in the public license below. The
|
||||
following considerations are for informational purposes only, are not
|
||||
exhaustive, and do not form part of our licenses.
|
||||
|
||||
Considerations for licensors: Our public licenses are
|
||||
intended for use by those authorized to give the public
|
||||
permission to use material in ways otherwise restricted by
|
||||
copyright and certain other rights. Our licenses are
|
||||
irrevocable. Licensors should read and understand the terms
|
||||
and conditions of the license they choose before applying it.
|
||||
Licensors should also secure all rights necessary before
|
||||
applying our licenses so that the public can reuse the
|
||||
material as expected. Licensors should clearly mark any
|
||||
material not subject to the license. This includes other CC-
|
||||
licensed material, or material used under an exception or
|
||||
limitation to copyright. More considerations for licensors:
|
||||
wiki.creativecommons.org/Considerations_for_licensors
|
||||
|
||||
Considerations for the public: By using one of our public
|
||||
licenses, a licensor grants the public permission to use the
|
||||
licensed material under specified terms and conditions. If
|
||||
the licensor's permission is not necessary for any reason--for
|
||||
example, because of any applicable exception or limitation to
|
||||
copyright--then that use is not regulated by the license. Our
|
||||
licenses grant only permissions under copyright and certain
|
||||
other rights that a licensor has authority to grant. Use of
|
||||
the licensed material may still be restricted for other
|
||||
reasons, including because others have copyright or other
|
||||
rights in the material. A licensor may make special requests,
|
||||
such as asking that all changes be marked or described.
|
||||
Although not required by our licenses, you are encouraged to
|
||||
respect those requests where reasonable. More_considerations
|
||||
for the public:
|
||||
wiki.creativecommons.org/Considerations_for_licensees
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Attribution 4.0 International Public License
|
||||
|
||||
By exercising the Licensed Rights (defined below), You accept and agree
|
||||
to be bound by the terms and conditions of this Creative Commons
|
||||
Attribution 4.0 International Public License ("Public License"). To the
|
||||
extent this Public License may be interpreted as a contract, You are
|
||||
granted the Licensed Rights in consideration of Your acceptance of
|
||||
these terms and conditions, and the Licensor grants You such rights in
|
||||
consideration of benefits the Licensor receives from making the
|
||||
Licensed Material available under these terms and conditions.
|
||||
|
||||
|
||||
Section 1 -- Definitions.
|
||||
|
||||
a. Adapted Material means material subject to Copyright and Similar
|
||||
Rights that is derived from or based upon the Licensed Material
|
||||
and in which the Licensed Material is translated, altered,
|
||||
arranged, transformed, or otherwise modified in a manner requiring
|
||||
permission under the Copyright and Similar Rights held by the
|
||||
Licensor. For purposes of this Public License, where the Licensed
|
||||
Material is a musical work, performance, or sound recording,
|
||||
Adapted Material is always produced where the Licensed Material is
|
||||
synched in timed relation with a moving image.
|
||||
|
||||
b. Adapter's License means the license You apply to Your Copyright
|
||||
and Similar Rights in Your contributions to Adapted Material in
|
||||
accordance with the terms and conditions of this Public License.
|
||||
|
||||
c. Copyright and Similar Rights means copyright and/or similar rights
|
||||
closely related to copyright including, without limitation,
|
||||
performance, broadcast, sound recording, and Sui Generis Database
|
||||
Rights, without regard to how the rights are labeled or
|
||||
categorized. For purposes of this Public License, the rights
|
||||
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
||||
Rights.
|
||||
|
||||
d. Effective Technological Measures means those measures that, in the
|
||||
absence of proper authority, may not be circumvented under laws
|
||||
fulfilling obligations under Article 11 of the WIPO Copyright
|
||||
Treaty adopted on December 20, 1996, and/or similar international
|
||||
agreements.
|
||||
|
||||
e. Exceptions and Limitations means fair use, fair dealing, and/or
|
||||
any other exception or limitation to Copyright and Similar Rights
|
||||
that applies to Your use of the Licensed Material.
|
||||
|
||||
f. Licensed Material means the artistic or literary work, database,
|
||||
or other material to which the Licensor applied this Public
|
||||
License.
|
||||
|
||||
g. Licensed Rights means the rights granted to You subject to the
|
||||
terms and conditions of this Public License, which are limited to
|
||||
all Copyright and Similar Rights that apply to Your use of the
|
||||
Licensed Material and that the Licensor has authority to license.
|
||||
|
||||
h. Licensor means the individual(s) or entity(ies) granting rights
|
||||
under this Public License.
|
||||
|
||||
i. Share means to provide material to the public by any means or
|
||||
process that requires permission under the Licensed Rights, such
|
||||
as reproduction, public display, public performance, distribution,
|
||||
dissemination, communication, or importation, and to make material
|
||||
available to the public including in ways that members of the
|
||||
public may access the material from a place and at a time
|
||||
individually chosen by them.
|
||||
|
||||
j. Sui Generis Database Rights means rights other than copyright
|
||||
resulting from Directive 96/9/EC of the European Parliament and of
|
||||
the Council of 11 March 1996 on the legal protection of databases,
|
||||
as amended and/or succeeded, as well as other essentially
|
||||
equivalent rights anywhere in the world.
|
||||
|
||||
k. You means the individual or entity exercising the Licensed Rights
|
||||
under this Public License. Your has a corresponding meaning.
|
||||
|
||||
|
||||
Section 2 -- Scope.
|
||||
|
||||
a. License grant.
|
||||
|
||||
1. Subject to the terms and conditions of this Public License,
|
||||
the Licensor hereby grants You a worldwide, royalty-free,
|
||||
non-sublicensable, non-exclusive, irrevocable license to
|
||||
exercise the Licensed Rights in the Licensed Material to:
|
||||
|
||||
a. reproduce and Share the Licensed Material, in whole or
|
||||
in part; and
|
||||
|
||||
b. produce, reproduce, and Share Adapted Material.
|
||||
|
||||
2. Exceptions and Limitations. For the avoidance of doubt, where
|
||||
Exceptions and Limitations apply to Your use, this Public
|
||||
License does not apply, and You do not need to comply with
|
||||
its terms and conditions.
|
||||
|
||||
3. Term. The term of this Public License is specified in Section
|
||||
6(a).
|
||||
|
||||
4. Media and formats; technical modifications allowed. The
|
||||
Licensor authorizes You to exercise the Licensed Rights in
|
||||
all media and formats whether now known or hereafter created,
|
||||
and to make technical modifications necessary to do so. The
|
||||
Licensor waives and/or agrees not to assert any right or
|
||||
authority to forbid You from making technical modifications
|
||||
necessary to exercise the Licensed Rights, including
|
||||
technical modifications necessary to circumvent Effective
|
||||
Technological Measures. For purposes of this Public License,
|
||||
simply making modifications authorized by this Section 2(a)
|
||||
(4) never produces Adapted Material.
|
||||
|
||||
5. Downstream recipients.
|
||||
|
||||
a. Offer from the Licensor -- Licensed Material. Every
|
||||
recipient of the Licensed Material automatically
|
||||
receives an offer from the Licensor to exercise the
|
||||
Licensed Rights under the terms and conditions of this
|
||||
Public License.
|
||||
|
||||
b. No downstream restrictions. You may not offer or impose
|
||||
any additional or different terms or conditions on, or
|
||||
apply any Effective Technological Measures to, the
|
||||
Licensed Material if doing so restricts exercise of the
|
||||
Licensed Rights by any recipient of the Licensed
|
||||
Material.
|
||||
|
||||
6. No endorsement. Nothing in this Public License constitutes or
|
||||
may be construed as permission to assert or imply that You
|
||||
are, or that Your use of the Licensed Material is, connected
|
||||
with, or sponsored, endorsed, or granted official status by,
|
||||
the Licensor or others designated to receive attribution as
|
||||
provided in Section 3(a)(1)(A)(i).
|
||||
|
||||
b. Other rights.
|
||||
|
||||
1. Moral rights, such as the right of integrity, are not
|
||||
licensed under this Public License, nor are publicity,
|
||||
privacy, and/or other similar personality rights; however, to
|
||||
the extent possible, the Licensor waives and/or agrees not to
|
||||
assert any such rights held by the Licensor to the limited
|
||||
extent necessary to allow You to exercise the Licensed
|
||||
Rights, but not otherwise.
|
||||
|
||||
2. Patent and trademark rights are not licensed under this
|
||||
Public License.
|
||||
|
||||
3. To the extent possible, the Licensor waives any right to
|
||||
collect royalties from You for the exercise of the Licensed
|
||||
Rights, whether directly or through a collecting society
|
||||
under any voluntary or waivable statutory or compulsory
|
||||
licensing scheme. In all other cases the Licensor expressly
|
||||
reserves any right to collect such royalties.
|
||||
|
||||
|
||||
Section 3 -- License Conditions.
|
||||
|
||||
Your exercise of the Licensed Rights is expressly made subject to the
|
||||
following conditions.
|
||||
|
||||
a. Attribution.
|
||||
|
||||
1. If You Share the Licensed Material (including in modified
|
||||
form), You must:
|
||||
|
||||
a. retain the following if it is supplied by the Licensor
|
||||
with the Licensed Material:
|
||||
|
||||
i. identification of the creator(s) of the Licensed
|
||||
Material and any others designated to receive
|
||||
attribution, in any reasonable manner requested by
|
||||
the Licensor (including by pseudonym if
|
||||
designated);
|
||||
|
||||
ii. a copyright notice;
|
||||
|
||||
iii. a notice that refers to this Public License;
|
||||
|
||||
iv. a notice that refers to the disclaimer of
|
||||
warranties;
|
||||
|
||||
v. a URI or hyperlink to the Licensed Material to the
|
||||
extent reasonably practicable;
|
||||
|
||||
b. indicate if You modified the Licensed Material and
|
||||
retain an indication of any previous modifications; and
|
||||
|
||||
c. indicate the Licensed Material is licensed under this
|
||||
Public License, and include the text of, or the URI or
|
||||
hyperlink to, this Public License.
|
||||
|
||||
2. You may satisfy the conditions in Section 3(a)(1) in any
|
||||
reasonable manner based on the medium, means, and context in
|
||||
which You Share the Licensed Material. For example, it may be
|
||||
reasonable to satisfy the conditions by providing a URI or
|
||||
hyperlink to a resource that includes the required
|
||||
information.
|
||||
|
||||
3. If requested by the Licensor, You must remove any of the
|
||||
information required by Section 3(a)(1)(A) to the extent
|
||||
reasonably practicable.
|
||||
|
||||
4. If You Share Adapted Material You produce, the Adapter's
|
||||
License You apply must not prevent recipients of the Adapted
|
||||
Material from complying with this Public License.
|
||||
|
||||
|
||||
Section 4 -- Sui Generis Database Rights.
|
||||
|
||||
Where the Licensed Rights include Sui Generis Database Rights that
|
||||
apply to Your use of the Licensed Material:
|
||||
|
||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
||||
to extract, reuse, reproduce, and Share all or a substantial
|
||||
portion of the contents of the database;
|
||||
|
||||
b. if You include all or a substantial portion of the database
|
||||
contents in a database in which You have Sui Generis Database
|
||||
Rights, then the database in which You have Sui Generis Database
|
||||
Rights (but not its individual contents) is Adapted Material; and
|
||||
|
||||
c. You must comply with the conditions in Section 3(a) if You Share
|
||||
all or a substantial portion of the contents of the database.
|
||||
|
||||
For the avoidance of doubt, this Section 4 supplements and does not
|
||||
replace Your obligations under this Public License where the Licensed
|
||||
Rights include other Copyright and Similar Rights.
|
||||
|
||||
|
||||
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
||||
|
||||
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
||||
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
||||
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
||||
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
||||
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
||||
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
||||
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
||||
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
||||
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
||||
|
||||
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
||||
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
||||
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
||||
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
||||
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
||||
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
||||
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
||||
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
||||
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
||||
|
||||
c. The disclaimer of warranties and limitation of liability provided
|
||||
above shall be interpreted in a manner that, to the extent
|
||||
possible, most closely approximates an absolute disclaimer and
|
||||
waiver of all liability.
|
||||
|
||||
|
||||
Section 6 -- Term and Termination.
|
||||
|
||||
a. This Public License applies for the term of the Copyright and
|
||||
Similar Rights licensed here. However, if You fail to comply with
|
||||
this Public License, then Your rights under this Public License
|
||||
terminate automatically.
|
||||
|
||||
b. Where Your right to use the Licensed Material has terminated under
|
||||
Section 6(a), it reinstates:
|
||||
|
||||
1. automatically as of the date the violation is cured, provided
|
||||
it is cured within 30 days of Your discovery of the
|
||||
violation; or
|
||||
|
||||
2. upon express reinstatement by the Licensor.
|
||||
|
||||
For the avoidance of doubt, this Section 6(b) does not affect any
|
||||
right the Licensor may have to seek remedies for Your violations
|
||||
of this Public License.
|
||||
|
||||
c. For the avoidance of doubt, the Licensor may also offer the
|
||||
Licensed Material under separate terms or conditions or stop
|
||||
distributing the Licensed Material at any time; however, doing so
|
||||
will not terminate this Public License.
|
||||
|
||||
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
||||
License.
|
||||
|
||||
|
||||
Section 7 -- Other Terms and Conditions.
|
||||
|
||||
a. The Licensor shall not be bound by any additional or different
|
||||
terms or conditions communicated by You unless expressly agreed.
|
||||
|
||||
b. Any arrangements, understandings, or agreements regarding the
|
||||
Licensed Material not stated herein are separate from and
|
||||
independent of the terms and conditions of this Public License.
|
||||
|
||||
|
||||
Section 8 -- Interpretation.
|
||||
|
||||
a. For the avoidance of doubt, this Public License does not, and
|
||||
shall not be interpreted to, reduce, limit, restrict, or impose
|
||||
conditions on any use of the Licensed Material that could lawfully
|
||||
be made without permission under this Public License.
|
||||
|
||||
b. To the extent possible, if any provision of this Public License is
|
||||
deemed unenforceable, it shall be automatically reformed to the
|
||||
minimum extent necessary to make it enforceable. If the provision
|
||||
cannot be reformed, it shall be severed from this Public License
|
||||
without affecting the enforceability of the remaining terms and
|
||||
conditions.
|
||||
|
||||
c. No term or condition of this Public License will be waived and no
|
||||
failure to comply consented to unless expressly agreed to by the
|
||||
Licensor.
|
||||
|
||||
d. Nothing in this Public License constitutes or may be interpreted
|
||||
as a limitation upon, or waiver of, any privileges and immunities
|
||||
that apply to the Licensor or You, including from the legal
|
||||
processes of any jurisdiction or authority.
|
||||
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons is not a party to its public
|
||||
licenses. Notwithstanding, Creative Commons may elect to apply one of
|
||||
its public licenses to material it publishes and in those instances
|
||||
will be considered the “Licensor.” The text of the Creative Commons
|
||||
public licenses is dedicated to the public domain under the CC0 Public
|
||||
Domain Dedication. Except for the limited purpose of indicating that
|
||||
material is shared under a Creative Commons public license or as
|
||||
otherwise permitted by the Creative Commons policies published at
|
||||
creativecommons.org/policies, Creative Commons does not authorize the
|
||||
use of the trademark "Creative Commons" or any other trademark or logo
|
||||
of Creative Commons without its prior written consent including,
|
||||
without limitation, in connection with any unauthorized modifications
|
||||
to any of its public licenses or any other arrangements,
|
||||
understandings, or agreements concerning use of licensed material. For
|
||||
the avoidance of doubt, this paragraph does not form part of the
|
||||
public licenses.
|
||||
|
||||
Creative Commons may be contacted at creativecommons.org.
|
||||
@@ -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
|
||||
|
||||
43
docs/md/CONTRIBUTING.md
Normal file
43
docs/md/CONTRIBUTING.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Contributing
|
||||
|
||||
First of all, thank you very much for taking the time to contribute to the
|
||||
`EnTT` framework.<br/>
|
||||
How to do it mostly depends on the type of contribution:
|
||||
|
||||
* If you have a question, **please** ensure there isn't already an answer for
|
||||
you by searching on GitHub under
|
||||
[issues](https://github.com/skypjack/entt/issues). Do not forget to search
|
||||
also through the closed ones. If you are unable to find a proper answer, feel
|
||||
free to [open a new issue](https://github.com/skypjack/entt/issues/new).
|
||||
Usually, questions are marked as such and closed in a few days.
|
||||
|
||||
* If you want to fix a typo in the inline documentation or in the README file,
|
||||
if you want to add some new sections or if you want to help me with the
|
||||
language by reviewing what I wrote so far (I'm not a native speaker after
|
||||
all), **please** open a new
|
||||
[pull request](https://github.com/skypjack/entt/pulls) with your changes.
|
||||
|
||||
* If you found a bug, **please** ensure there isn't already an answer for you by
|
||||
searching on GitHub under [issues](https://github.com/skypjack/entt/issues).
|
||||
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 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.
|
||||
**Please**, add some tests to avoid regressions in future if possible, it
|
||||
would be really appreciated. Note that the `EnTT` framework has a
|
||||
[coverage at 100%](https://coveralls.io/github/skypjack/entt?branch=master)
|
||||
(at least it was at 100% at the time I wrote this file) and this is the reason
|
||||
for which you can be confident with using it in a production environment.
|
||||
|
||||
* If you want to propose a new feature and you know how to code it, **please**
|
||||
do not issue directly a pull request. Before to do it,
|
||||
[create a new issue](https://github.com/skypjack/entt/issues/new) to discuss
|
||||
your proposal. Other users could be interested in your idea and the discussion
|
||||
that will follow can refine it and therefore give us a better solution.
|
||||
|
||||
* If you want to request a new feature, I'm available for hiring. Take a look at
|
||||
[my profile](https://github.com/skypjack) and feel free to write me.
|
||||
167
docs/md/core.md
Normal file
167
docs/md/core.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# 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)
|
||||
* [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;
|
||||
```
|
||||
|
||||
## 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"}>{};
|
||||
```
|
||||
1549
docs/md/entity.md
Normal file
1549
docs/md/entity.md
Normal file
File diff suppressed because it is too large
Load Diff
55
docs/md/faq.md
Normal file
55
docs/md/faq.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# 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)
|
||||
<!--
|
||||
@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`.
|
||||
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.
|
||||
93
docs/md/links.md
Normal file
93
docs/md/links.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# 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`.
|
||||
* [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`.
|
||||
|
||||
* Engines/Frameworks:
|
||||
* [The Forge](https://github.com/ConfettiFX/The-Forge) by
|
||||
[Confett](http://www.confettispecialfx.com/): a cross-platform rendering
|
||||
framework.
|
||||
* [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.
|
||||
|
||||
* Emulators:
|
||||
* [NovusCore](https://github.com/novuscore/NovusCore): A modern take on World
|
||||
of Warcraft emulation.
|
||||
|
||||
* 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.
|
||||
* [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...);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
414
docs/md/meta.md
Normal file
414
docs/md/meta.md
Normal file
@@ -0,0 +1,414 @@
|
||||
# Crash Course: reflection system
|
||||
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
# Table of Contents
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [Reflection in a nutshell](#reflection-in-a-nutshell)
|
||||
* [Any as in any type](#any-as-in-any-type)
|
||||
* [Enjoy the runtime](#enjoy-the-runtime)
|
||||
* [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.
|
||||
|
||||
# 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");
|
||||
```
|
||||
|
||||
It accepts the type to reflect as a template parameter and an optional name as
|
||||
an argument. Names 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").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").dtor<&destroy>();
|
||||
```
|
||||
|
||||
* _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")
|
||||
.data<&my_type::static_variable>("static")
|
||||
.data<&my_type::data_member>("member")
|
||||
.data<&global_variable>("global");
|
||||
```
|
||||
|
||||
This function requires as an argument the name 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")
|
||||
.func<&my_type::static_function>("static")
|
||||
.func<&my_type::member_function>("member")
|
||||
.func<&free_function>("free");
|
||||
```
|
||||
|
||||
This function requires as an argument the name 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").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 can be constructed or assigned by copy and move and it takes the burden of
|
||||
destroying the contained object when required.<br/>
|
||||
A meta any object has a `type` member function that returns the meta type of the
|
||||
contained value, if any. The member functions `can_cast` and `can_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. Similarly, `cast` and `convert` do what they promise
|
||||
and return the expected value.
|
||||
|
||||
# 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");
|
||||
```
|
||||
|
||||
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 name_ 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");
|
||||
```
|
||||
|
||||
The returned type is `meta_data` and may be invalid if there is no meta data
|
||||
object associated with the given name.<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");
|
||||
```
|
||||
|
||||
The returned type is `meta_func` and may be invalid if there is no meta
|
||||
function object associated with the given name.<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");
|
||||
```
|
||||
|
||||
The returned type is `meta_base` and may be invalid if there is no meta base
|
||||
object associated with the given name.<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");
|
||||
|
||||
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 or a default one.<br/>
|
||||
Be aware that the result of a call to `destroy` may not be what is expected.
|
||||
|
||||
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.
|
||||
|
||||
# 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")
|
||||
.data<my_enum::another_value>("another_value");
|
||||
|
||||
entt::reflect<int>().data<2048>("max_int");
|
||||
```
|
||||
|
||||
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").get({}).cast<my_enum>();
|
||||
auto max = entt::resolve<int>().data("max_int").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 (ie 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", 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.
|
||||
447
docs/md/signal.md
Normal file
447
docs/md/signal.md
Normal file
@@ -0,0 +1,447 @@
|
||||
# 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 potential of an `std::function` are not
|
||||
required or where you are looking for something different, `EnTT` offers a full
|
||||
set of classes to solve completely different 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. However, it can be used
|
||||
to send opaque delegates around to be used to invoke functions as needed.
|
||||
|
||||
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 function 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.<br/>
|
||||
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 pointer to `c` and `42`. However, the
|
||||
function type of the delegate is still `void(int)`, mainly because this is also
|
||||
the signature of its function call operator.
|
||||
|
||||
To create and initialize a delegate at once, there are also some 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
|
||||
usual:
|
||||
|
||||
```cpp
|
||||
auto ret = delegate(42);
|
||||
```
|
||||
|
||||
As shown above, listeners do not 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.
|
||||
|
||||
Probably too much small and pretty poor of functionalities, but the delegate
|
||||
class can help in a lot of cases and it has shown that it is worth keeping it
|
||||
within the library.
|
||||
|
||||
# Signals
|
||||
|
||||
Signal handlers work with naked pointers, function pointers and pointers to
|
||||
member functions. 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/>
|
||||
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 its _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
|
||||
associated 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/>
|
||||
|
||||
**Note**: collectors are allowed only in case of function types whose the return
|
||||
type isn't `void` for obvious reasons.
|
||||
|
||||
To create instances of signal handlers there exist mainly two ways:
|
||||
|
||||
```cpp
|
||||
// no collector type
|
||||
entt::sigh<void(int, char)> signal;
|
||||
|
||||
// explicit collector type
|
||||
entt::sigh<void(int, char), my_collector<bool>> collector;
|
||||
```
|
||||
|
||||
As expected, they offer all the basic functionalities required to know how many
|
||||
listeners they contain (`size`) or if they contain at least a listener (`empty`)
|
||||
and even to swap two signal 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) { /* ... */ }
|
||||
};
|
||||
|
||||
// ...
|
||||
|
||||
listener instance;
|
||||
|
||||
signal.sink().connect<&foo>();
|
||||
signal.sink().connect<&listener::bar>(&instance);
|
||||
|
||||
// ...
|
||||
|
||||
// disconnects a free function
|
||||
signal.sink().disconnect<&foo>();
|
||||
|
||||
// disconnect a member function of an instance
|
||||
signal.sink().disconnect<&listener::bar>(&instance);
|
||||
|
||||
// discards all the listeners at once
|
||||
signal.sink().disconnect();
|
||||
```
|
||||
|
||||
As shown above, listeners do not 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 result type, everything works just fine.
|
||||
|
||||
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
|
||||
struct my_collector {
|
||||
std::vector<int> vec{};
|
||||
|
||||
bool operator()(int v) noexcept {
|
||||
vec.push_back(v);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
int f() { return 0; }
|
||||
int g() { return 1; }
|
||||
|
||||
// ...
|
||||
|
||||
entt::sigh<int(), my_collector<int>> signal;
|
||||
|
||||
signal.sink().connect<&f>();
|
||||
signal.sink().connect<&g>();
|
||||
|
||||
my_collector collector = signal.collect();
|
||||
|
||||
assert(collector.vec[0] == 0);
|
||||
assert(collector.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 has to
|
||||
return a boolean value that is false to stop collecting data, true otherwise.
|
||||
This way one can avoid calling all the listeners in case it isn't necessary.
|
||||
|
||||
# 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 that works with naked pointers
|
||||
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
|
||||
selectively remove listeners:
|
||||
|
||||
```cpp
|
||||
dispatcher.sink<an_event>().disconnect<&listener::receive>(&listener);
|
||||
dispatcher.sink<another_event>().disconnect<&listener::method>(&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"]
|
||||
}
|
||||
60
scripts/update_homebrew.sh
Executable file
60
scripts/update_homebrew.sh
Executable file
@@ -0,0 +1,60 @@
|
||||
#!/bin/sh
|
||||
|
||||
# only argument should be the version to upgrade to
|
||||
if [ $# != 1 ]
|
||||
then
|
||||
echo "Expected a version tag like v2.7.1"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION="$1"
|
||||
URL="https://github.com/skypjack/entt/archive/$VERSION.tar.gz"
|
||||
FORMULA="entt.rb"
|
||||
|
||||
echo "Updating homebrew package to $VERSION"
|
||||
|
||||
echo "Cloning..."
|
||||
git clone https://github.com/skypjack/homebrew-entt.git
|
||||
if [ $? != 0 ]
|
||||
then
|
||||
exit 1
|
||||
fi
|
||||
cd homebrew-entt
|
||||
|
||||
# download the repo at the version
|
||||
# exit with error messages if curl fails
|
||||
echo "Curling..."
|
||||
curl "$URL" --location --fail --silent --show-error --output archive.tar.gz
|
||||
if [ $? != 0 ]
|
||||
then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# compute sha256 hash
|
||||
echo "Hashing..."
|
||||
HASH="$(openssl sha256 archive.tar.gz | cut -d " " -f 2)"
|
||||
|
||||
# delete the archive
|
||||
rm archive.tar.gz
|
||||
|
||||
echo "Sedding..."
|
||||
|
||||
# change the url in the formula file
|
||||
# the slashes in the URL must be escaped
|
||||
ESCAPED_URL="$(sed -e 's/[\/&]/\\&/g' <<< "$URL")"
|
||||
sed -i -e '/url/s/".*"/"'$ESCAPED_URL'"/' $FORMULA
|
||||
|
||||
# change the hash in the formula file
|
||||
sed -i -e '/sha256/s/".*"/"'$HASH'"/' $FORMULA
|
||||
|
||||
# delete temporary file created by sed
|
||||
rm "$FORMULA-e"
|
||||
|
||||
# update remote repo
|
||||
echo "Gitting..."
|
||||
git add entt.rb
|
||||
git commit -m "Update to $VERSION"
|
||||
git push origin master
|
||||
|
||||
# out of homebrew-entt dir
|
||||
cd ..
|
||||
3
scripts/update_packages.sh
Executable file
3
scripts/update_packages.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
scripts/update_homebrew.sh $1
|
||||
15427
single_include/entt/entt.hpp
Normal file
15427
single_include/entt/entt.hpp
Normal file
File diff suppressed because it is too large
Load Diff
44
src/entt/config/config.h
Normal file
44
src/entt/config/config.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#ifndef ENTT_CONFIG_CONFIG_H
|
||||
#define ENTT_CONFIG_CONFIG_H
|
||||
|
||||
|
||||
#ifndef ENTT_NOEXCEPT
|
||||
#define ENTT_NOEXCEPT noexcept
|
||||
#endif // ENTT_NOEXCEPT
|
||||
|
||||
|
||||
#ifndef ENTT_HS_SUFFIX
|
||||
#define ENTT_HS_SUFFIX _hs
|
||||
#endif // ENTT_HS_SUFFIX
|
||||
|
||||
|
||||
#ifndef ENTT_NO_ATOMIC
|
||||
#include <atomic>
|
||||
template<typename Type>
|
||||
using maybe_atomic_t = std::atomic<Type>;
|
||||
#else // ENTT_NO_ATOMIC
|
||||
template<typename Type>
|
||||
using maybe_atomic_t = 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
|
||||
|
||||
|
||||
#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.0.0"
|
||||
#define ENTT_VERSION_MAJOR 3
|
||||
#define ENTT_VERSION_MINOR 0
|
||||
#define ENTT_VERSION_PATCH 0
|
||||
|
||||
|
||||
#endif // ENTT_CONFIG_VERSION_H
|
||||
78
src/entt/core/algorithm.hpp
Normal file
78
src/entt/core/algorithm.hpp
Normal file
@@ -0,0 +1,78 @@
|
||||
#ifndef ENTT_CORE_ALGORITHM_HPP
|
||||
#define ENTT_CORE_ALGORITHM_HPP
|
||||
|
||||
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Function object to wrap `std::sort` in a class type.
|
||||
*
|
||||
* Unfortunately, `std::sort` cannot be passed as template argument to a class
|
||||
* template or a function template.<br/>
|
||||
* This class fills the gap by wrapping some flavors of `std::sort` in a
|
||||
* function object.
|
||||
*/
|
||||
struct std_sort {
|
||||
/**
|
||||
* @brief Sorts the elements in a range.
|
||||
*
|
||||
* Sorts the elements in a range using the given binary comparison function.
|
||||
*
|
||||
* @tparam It Type of random access iterator.
|
||||
* @tparam Compare Type of comparison function object.
|
||||
* @tparam Args Types of arguments to forward to the sort function.
|
||||
* @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 args Arguments to forward to the sort function, if any.
|
||||
*/
|
||||
template<typename It, typename Compare = std::less<>, typename... Args>
|
||||
void operator()(It first, It last, Compare compare = Compare{}, Args &&... args) const {
|
||||
std::sort(std::forward<Args>(args)..., std::move(first), std::move(last), std::move(compare));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*! @brief Function object for performing insertion sort. */
|
||||
struct insertion_sort {
|
||||
/**
|
||||
* @brief Sorts the elements in a range.
|
||||
*
|
||||
* Sorts the elements in a range using the given binary comparison function.
|
||||
*
|
||||
* @tparam It Type of random access iterator.
|
||||
* @tparam Compare Type of comparison 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.
|
||||
*/
|
||||
template<typename It, typename Compare = std::less<>>
|
||||
void operator()(It first, It last, Compare compare = Compare{}) const {
|
||||
if(first != last) {
|
||||
auto it = first + 1;
|
||||
|
||||
while(it != last) {
|
||||
auto pre = it++;
|
||||
auto value = *pre;
|
||||
|
||||
while(pre-- != first && compare(value, *pre)) {
|
||||
*(pre+1) = *pre;
|
||||
}
|
||||
|
||||
*(pre+1) = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_CORE_ALGORITHM_HPP
|
||||
@@ -2,8 +2,8 @@
|
||||
#define ENTT_CORE_FAMILY_HPP
|
||||
|
||||
|
||||
#include<type_traits>
|
||||
#include<cstddef>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
namespace entt {
|
||||
@@ -17,30 +17,20 @@ namespace entt {
|
||||
* identifiers.
|
||||
*/
|
||||
template<typename...>
|
||||
class Family {
|
||||
static std::size_t identifier() noexcept {
|
||||
static std::size_t value = 0;
|
||||
return value++;
|
||||
}
|
||||
class family {
|
||||
inline static maybe_atomic_t<ENTT_ID_TYPE> identifier;
|
||||
|
||||
template<typename...>
|
||||
static std::size_t family() noexcept {
|
||||
static const std::size_t value = identifier();
|
||||
return value;
|
||||
}
|
||||
inline static const auto 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>
|
||||
static family_type type() noexcept {
|
||||
return family<std::decay_t<Type>...>();
|
||||
}
|
||||
// at the time I'm writing, clang crashes during compilation if auto is used in place of family_type here
|
||||
inline static const family_type type = inner<std::decay_t<Type>...>;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -2,14 +2,51 @@
|
||||
#define ENTT_CORE_HASHED_STRING_HPP
|
||||
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
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
|
||||
@@ -17,24 +54,69 @@ namespace entt {
|
||||
* Because of that, a hashed string can also be used in constant expressions if
|
||||
* required.
|
||||
*/
|
||||
class HashedString final {
|
||||
struct ConstCharWrapper final {
|
||||
class hashed_string {
|
||||
using traits_type = internal::fnv1a_traits<ENTT_ID_TYPE>;
|
||||
|
||||
struct const_wrapper {
|
||||
// non-explicit constructor on purpose
|
||||
constexpr ConstCharWrapper(const char *str) noexcept: str{str} {}
|
||||
constexpr const_wrapper(const char *curr) ENTT_NOEXCEPT: str{curr} {}
|
||||
const char *str;
|
||||
};
|
||||
|
||||
static constexpr std::uint64_t offset = 14695981039346656037u;
|
||||
static constexpr std::uint64_t prime = 1099511628211u;
|
||||
|
||||
// Fowler–Noll–Vo hash function v. 1a - the good
|
||||
static constexpr std::uint64_t helper(std::uint64_t partial, const char *str) noexcept {
|
||||
return str[0] == 0 ? partial : helper((partial^str[0])*prime, str+1);
|
||||
inline 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 Unsigned integer type. */
|
||||
using hash_type = std::uint64_t;
|
||||
using hash_type = ENTT_ID_TYPE;
|
||||
|
||||
/**
|
||||
* @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}
|
||||
* const auto value = hashed_string::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>
|
||||
inline static constexpr hash_type to_value(const char (&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.
|
||||
*/
|
||||
inline 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.
|
||||
*/
|
||||
inline static hash_type to_value(const char *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 hashed_string() ENTT_NOEXCEPT
|
||||
: str{nullptr}, hash{}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Constructs a hashed string from an array of const chars.
|
||||
@@ -44,51 +126,63 @@ public:
|
||||
* characters.<br/>
|
||||
* Example of use:
|
||||
* @code{.cpp}
|
||||
* HashedString sh{"my.png"};
|
||||
* hashed_string hs{"my.png"};
|
||||
* @endcode
|
||||
*
|
||||
* @tparam N Number of characters of the identifier.
|
||||
* @param str Human-readable identifer.
|
||||
* @param curr Human-readable identifer.
|
||||
*/
|
||||
template <std::size_t N>
|
||||
constexpr HashedString(const char (&str)[N]) noexcept
|
||||
: hash{helper(offset, str)}, str{str}
|
||||
template<std::size_t N>
|
||||
constexpr hashed_string(const char (&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 *`.
|
||||
*
|
||||
* @param wrapper Helps achieving the purpose by relying on overloading.
|
||||
*/
|
||||
explicit constexpr HashedString(ConstCharWrapper wrapper) noexcept
|
||||
: hash{helper(offset, wrapper.str)}, str{wrapper.str}
|
||||
explicit constexpr 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 noexcept { return str; }
|
||||
constexpr const char * data() const ENTT_NOEXCEPT {
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the numeric representation of a hashed string.
|
||||
* @return The numeric representation of the instance.
|
||||
*/
|
||||
constexpr operator hash_type() const noexcept { return hash; }
|
||||
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 char *() const ENTT_NOEXCEPT { return str; }
|
||||
|
||||
/*! @copydoc value */
|
||||
constexpr operator hash_type() const ENTT_NOEXCEPT { return hash; }
|
||||
|
||||
/**
|
||||
* @brief Compares two hashed strings.
|
||||
* @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 noexcept {
|
||||
constexpr bool operator==(const hashed_string &other) const ENTT_NOEXCEPT {
|
||||
return hash == other.hash;
|
||||
}
|
||||
|
||||
private:
|
||||
const hash_type hash;
|
||||
const char *str;
|
||||
hash_type hash;
|
||||
};
|
||||
|
||||
|
||||
@@ -98,7 +192,7 @@ private:
|
||||
* @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) noexcept {
|
||||
constexpr bool operator!=(const hashed_string &lhs, const hashed_string &rhs) ENTT_NOEXCEPT {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
@@ -106,4 +200,14 @@ constexpr bool operator!=(const HashedString &lhs, const HashedString &rhs) noex
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief User defined literal for hashed strings.
|
||||
* @param str The literal without its suffix.
|
||||
* @return A properly initialized hashed string.
|
||||
*/
|
||||
constexpr entt::hashed_string operator"" ENTT_HS_SUFFIX(const char *str, std::size_t) ENTT_NOEXCEPT {
|
||||
return entt::hashed_string{str};
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_CORE_HASHED_STRING_HPP
|
||||
|
||||
@@ -2,72 +2,33 @@
|
||||
#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 {
|
||||
|
||||
|
||||
template<typename... Types>
|
||||
struct Identifier final: Identifier<Types>... {
|
||||
using identifier_type = std::size_t;
|
||||
|
||||
template<std::size_t... Indexes>
|
||||
constexpr Identifier(std::index_sequence<Indexes...>)
|
||||
: Identifier<Types>{std::index_sequence<Indexes>{}}...
|
||||
{}
|
||||
|
||||
template<typename Type>
|
||||
constexpr std::size_t get() const {
|
||||
return Identifier<std::decay_t<Type>>::get();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename Type>
|
||||
struct Identifier<Type> {
|
||||
using identifier_type = std::size_t;
|
||||
|
||||
template<std::size_t Index>
|
||||
constexpr Identifier(std::index_sequence<Index>)
|
||||
: index{Index}
|
||||
{}
|
||||
|
||||
constexpr std::size_t get() const {
|
||||
return index;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::size_t index;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Types identifers.
|
||||
* @brief Types identifiers.
|
||||
*
|
||||
* Variable template used to generate identifiers at compile-time for the given
|
||||
* types. Use the `constexpr` `get` member function to know what's the
|
||||
* identifier associated to the specific type.
|
||||
* types. Use the `get` member function to know what's the identifier associated
|
||||
* to the specific type.
|
||||
*
|
||||
* @note
|
||||
* Identifiers are constant expression and can be used in any context where such
|
||||
* an expression is required. As an example:
|
||||
* @code{.cpp}
|
||||
* constexpr auto identifiers = entt::ident<AType, AnotherType>;
|
||||
* using id = entt::identifier<a_type, another_type>;
|
||||
*
|
||||
* switch(aTypeIdentifier) {
|
||||
* case identifers.get<AType>():
|
||||
* switch(a_type_identifier) {
|
||||
* case id::type<a_type>:
|
||||
* // ...
|
||||
* break;
|
||||
* case identifers.get<AnotherType>():
|
||||
* case id::type<another_type>:
|
||||
* // ...
|
||||
* break;
|
||||
* default:
|
||||
@@ -75,19 +36,26 @@ private:
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @note
|
||||
* In case of single type list, `get` isn't a member function template:
|
||||
* @code{.cpp}
|
||||
* func(std::integral_constant<
|
||||
* entt::ident<AType>::identifier_type,
|
||||
* entt::ident<AType>::get()
|
||||
* >{});
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Types List of types for which to generate identifiers.
|
||||
*/
|
||||
template<typename... Types>
|
||||
constexpr auto ident = Identifier<std::decay_t<Types>...>{std::make_index_sequence<sizeof...(Types)>{}};
|
||||
class identifier {
|
||||
using tuple_type = std::tuple<std::decay_t<Types>...>;
|
||||
|
||||
template<typename Type, std::size_t... Indexes>
|
||||
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 = ENTT_ID_TYPE;
|
||||
|
||||
/*! @brief Statically generated unique identifier for the given type. */
|
||||
template<typename Type>
|
||||
static constexpr identifier_type type = get<std::decay_t<Type>>(std::make_index_sequence<sizeof...(Types)>{});
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
63
src/entt/core/monostate.hpp
Normal file
63
src/entt/core/monostate.hpp
Normal file
@@ -0,0 +1,63 @@
|
||||
#ifndef ENTT_CORE_MONOSTATE_HPP
|
||||
#define ENTT_CORE_MONOSTATE_HPP
|
||||
|
||||
|
||||
#include <cassert>
|
||||
#include "../config/config.h"
|
||||
#include "hashed_string.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Minimal implementation of the monostate pattern.
|
||||
*
|
||||
* A minimal, yet complete configuration system built on top of the monostate
|
||||
* pattern. Thread safe by design, it works only with basic types like `int`s or
|
||||
* `bool`s.<br/>
|
||||
* Multiple types and therefore more than one value can be associated with a
|
||||
* single key. 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 can incur in unexpected results.
|
||||
*/
|
||||
template<hashed_string::hash_type>
|
||||
struct monostate {
|
||||
/**
|
||||
* @brief Assigns a value of a specific type to a given key.
|
||||
* @tparam Type Type of the value to assign.
|
||||
* @param val User data to assign to the given key.
|
||||
*/
|
||||
template<typename Type>
|
||||
void operator=(Type val) const ENTT_NOEXCEPT {
|
||||
value<Type> = val;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets a value of a specific type for a given key.
|
||||
* @tparam Type Type of the value to get.
|
||||
* @return Stored value, if any.
|
||||
*/
|
||||
template<typename Type>
|
||||
operator Type() const ENTT_NOEXCEPT {
|
||||
return value<Type>;
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename Type>
|
||||
inline static maybe_atomic_t<Type> value{};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Helper variable template.
|
||||
* @tparam Value Value used to differentiate between different variables.
|
||||
*/
|
||||
template<hashed_string::hash_type Value>
|
||||
inline monostate<Value> monostate_v = {};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_CORE_MONOSTATE_HPP
|
||||
228
src/entt/core/type_traits.hpp
Normal file
228
src/entt/core/type_traits.hpp
Normal file
@@ -0,0 +1,228 @@
|
||||
#ifndef ENTT_CORE_TYPE_TRAITS_HPP
|
||||
#define ENTT_CORE_TYPE_TRAITS_HPP
|
||||
|
||||
|
||||
#include <type_traits>
|
||||
#include "../core/hashed_string.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief A class to use to push around lists of types, nothing more.
|
||||
* @tparam Type Types provided by the given type list.
|
||||
*/
|
||||
template<typename... Type>
|
||||
struct type_list {
|
||||
/*! @brief Unsigned integer type. */
|
||||
static constexpr auto size = sizeof...(Type);
|
||||
};
|
||||
|
||||
|
||||
/*! @brief Primary template isn't defined on purpose. */
|
||||
template<typename...>
|
||||
struct type_list_cat;
|
||||
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*
|
||||
* True if a given type has a name, false otherwise.
|
||||
*
|
||||
* @tparam Type Potentially named type.
|
||||
*/
|
||||
template<class Type>
|
||||
constexpr auto is_named_type_v = is_named_type<Type>::value;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @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.
|
||||
* @param type Type to assign a name to.
|
||||
*/
|
||||
#define ENTT_NAMED_TYPE(type)\
|
||||
template<>\
|
||||
struct entt::named_type_traits<type>\
|
||||
: std::integral_constant<typename entt::hashed_string::hash_type, entt::hashed_string::to_value(#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
|
||||
32
src/entt/core/utility.hpp
Normal file
32
src/entt/core/utility.hpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#ifndef ENTT_CORE_UTILITY_HPP
|
||||
#define ENTT_CORE_UTILITY_HPP
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @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) { 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) { return func; }
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_CORE_UTILITY_HPP
|
||||
@@ -2,8 +2,13 @@
|
||||
#define ENTT_ENTITY_ACTOR_HPP
|
||||
|
||||
|
||||
#include <cassert>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "registry.hpp"
|
||||
#include "entity.hpp"
|
||||
#include "fwd.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
@@ -16,37 +21,61 @@ namespace entt {
|
||||
* with entity-component systems and prefer to iterate objects directly.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Delta Type to use to provide elapsed time.
|
||||
*/
|
||||
template<typename Entity, typename Delta>
|
||||
struct Actor {
|
||||
template<typename Entity>
|
||||
struct basic_actor {
|
||||
/*! @brief Type of registry used internally. */
|
||||
using registry_type = Registry<Entity>;
|
||||
/*! @brief Type used to provide elapsed time. */
|
||||
using delta_type = Delta;
|
||||
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.
|
||||
* @param ref An entity-component system properly initialized.
|
||||
*/
|
||||
Actor(Registry<Entity> ®)
|
||||
: reg{reg}, entity{reg.create()}
|
||||
basic_actor(registry_type &ref)
|
||||
: reg{&ref}, entt{ref.create()}
|
||||
{}
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
virtual ~Actor() {
|
||||
reg.destroy(entity);
|
||||
virtual ~basic_actor() {
|
||||
reg->destroy(entt);
|
||||
}
|
||||
|
||||
/*! @brief Default copy constructor. */
|
||||
Actor(const Actor &) = default;
|
||||
/*! @brief Default move constructor. */
|
||||
Actor(Actor &&) = default;
|
||||
/**
|
||||
* @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)
|
||||
: reg{other.reg}, entt{other.entt}
|
||||
{
|
||||
other.entt = null;
|
||||
}
|
||||
|
||||
/*! @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 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns the given component to an actor.
|
||||
@@ -63,8 +92,8 @@ struct Actor {
|
||||
* @return A reference to the newly created component.
|
||||
*/
|
||||
template<typename Component, typename... Args>
|
||||
Component & set(Args&&... args) {
|
||||
return reg.template accomodate<Component>(entity, std::forward<Args>(args)...);
|
||||
decltype(auto) assign(Args &&... args) {
|
||||
return reg->template assign_or_replace<Component>(entt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,8 +101,8 @@ struct Actor {
|
||||
* @tparam Component Type of the component to remove.
|
||||
*/
|
||||
template<typename Component>
|
||||
void unset() {
|
||||
reg.template remove<Component>(entity);
|
||||
void remove() {
|
||||
reg->template remove<Component>(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,70 +111,69 @@ struct Actor {
|
||||
* @return True if the actor has the component, false otherwise.
|
||||
*/
|
||||
template<typename Component>
|
||||
bool has() const noexcept {
|
||||
return reg.template has<Component>(entity);
|
||||
bool has() const ENTT_NOEXCEPT {
|
||||
return reg->template has<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 entity.
|
||||
* @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 Component>
|
||||
const Component & get() const noexcept {
|
||||
return reg.template get<Component>(entity);
|
||||
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 component for an actor.
|
||||
* @tparam Component Type of the component to get.
|
||||
* @return A reference to the instance of the component owned by the entity.
|
||||
* @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 Component>
|
||||
Component & get() noexcept {
|
||||
return const_cast<Component &>(const_cast<const Actor *>(this)->get<Component>());
|
||||
template<typename... Component>
|
||||
auto try_get() const ENTT_NOEXCEPT {
|
||||
return std::as_const(*reg).template try_get<Component...>(entt);
|
||||
}
|
||||
|
||||
/*! @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
|
||||
* @return A reference to the underlying registry.
|
||||
*/
|
||||
const registry_type & registry() const noexcept {
|
||||
return reg;
|
||||
inline const registry_type & backend() const ENTT_NOEXCEPT {
|
||||
return *reg;
|
||||
}
|
||||
|
||||
/*! @copydoc backend */
|
||||
inline registry_type & backend() ENTT_NOEXCEPT {
|
||||
return const_cast<registry_type &>(std::as_const(*this).backend());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the underlying registry.
|
||||
* @return A reference to the underlying registry
|
||||
* @brief Returns the entity associated with an actor.
|
||||
* @return The entity associated with the actor.
|
||||
*/
|
||||
registry_type & registry() noexcept {
|
||||
return const_cast<registry_type &>(const_cast<const Actor *>(this)->registry());
|
||||
inline entity_type entity() const ENTT_NOEXCEPT {
|
||||
return entt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates an actor, whatever it means to update it.
|
||||
* @param delta Elapsed time.
|
||||
*/
|
||||
virtual void update(delta_type delta) = 0;
|
||||
|
||||
private:
|
||||
registry_type ®
|
||||
Entity entity;
|
||||
registry_type *reg;
|
||||
Entity entt;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*
|
||||
* @tparam Delta Type to use to provide elapsed time.
|
||||
*/
|
||||
template<typename Delta>
|
||||
using DefaultActor = Actor<std::uint32_t, Delta>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
169
src/entt/entity/entity.hpp
Normal file
169
src/entt/entity/entity.hpp
Normal file
@@ -0,0 +1,169 @@
|
||||
#ifndef ENTT_ENTITY_ENTITY_HPP
|
||||
#define ENTT_ENTITY_ENTITY_HPP
|
||||
|
||||
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
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 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;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @cond TURN_OFF_DOXYGEN
|
||||
* Internal details not to be documented.
|
||||
*/
|
||||
|
||||
|
||||
namespace internal {
|
||||
|
||||
|
||||
struct null {
|
||||
template<typename Entity>
|
||||
constexpr operator Entity() const ENTT_NOEXCEPT {
|
||||
using traits_type = entt_traits<Entity>;
|
||||
return traits_type::entity_mask | (traits_type::version_mask << traits_type::entity_shift);
|
||||
}
|
||||
|
||||
constexpr bool operator==(null) const ENTT_NOEXCEPT {
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr bool operator!=(null) const ENTT_NOEXCEPT {
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename Entity>
|
||||
constexpr bool operator==(const Entity entity) const ENTT_NOEXCEPT {
|
||||
return entity == static_cast<Entity>(*this);
|
||||
}
|
||||
|
||||
template<typename Entity>
|
||||
constexpr bool operator!=(const Entity entity) const ENTT_NOEXCEPT {
|
||||
return entity != static_cast<Entity>(*this);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename Entity>
|
||||
constexpr bool operator==(const Entity entity, null other) ENTT_NOEXCEPT {
|
||||
return other == entity;
|
||||
}
|
||||
|
||||
|
||||
template<typename Entity>
|
||||
constexpr bool operator!=(const Entity entity, null other) ENTT_NOEXCEPT {
|
||||
return other != entity;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Internal details not to be documented.
|
||||
* @endcond TURN_OFF_DOXYGEN
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @brief Null entity.
|
||||
*
|
||||
* 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{};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_ENTITY_HPP
|
||||
89
src/entt/entity/fwd.hpp
Normal file
89
src/entt/entity/fwd.hpp
Normal file
@@ -0,0 +1,89 @@
|
||||
#ifndef ENTT_ENTITY_FWD_HPP
|
||||
#define ENTT_ENTITY_FWD_HPP
|
||||
|
||||
|
||||
#include <cstdint>
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
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_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. */
|
||||
using entity = std::uint32_t;
|
||||
|
||||
/*! @brief Alias declaration for the most common use case. */
|
||||
using registry = basic_registry<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
|
||||
779
src/entt/entity/group.hpp
Normal file
779
src/entt/entity/group.hpp
Normal file
@@ -0,0 +1,779 @@
|
||||
#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 "fwd.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Alias for lists of observed components.
|
||||
* @tparam Type List of types.
|
||||
*/
|
||||
template<typename... Type>
|
||||
struct get_t: type_list<Type...> {};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Variable template for lists of observed components.
|
||||
* @tparam Type List of types.
|
||||
*/
|
||||
template<typename... Type>
|
||||
constexpr get_t<Type...> get{};
|
||||
|
||||
|
||||
/**
|
||||
* @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).
|
||||
*
|
||||
* 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 Get Types of components observed by the group.
|
||||
*/
|
||||
template<typename Entity, typename... Get>
|
||||
class basic_group<Entity, get_t<Get...>> {
|
||||
static_assert(sizeof...(Get) > 0);
|
||||
|
||||
/*! @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<Get> *..., but vs complains about it and refuses to compile for unknown reasons (likely a bug)
|
||||
basic_group(sparse_set<Entity> *ref, storage<Entity, std::remove_const_t<Get>> *... get) ENTT_NOEXCEPT
|
||||
: handler{ref},
|
||||
pools{get...}
|
||||
{}
|
||||
|
||||
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 &...);
|
||||
* void(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>
|
||||
inline void each(Func func) const {
|
||||
for(const auto entt: *handler) {
|
||||
if constexpr(std::is_invocable_v<Func, decltype(get<Get>({}))...>) {
|
||||
func(std::get<pool_type<Get> *>(pools)->get(entt)...);
|
||||
} else {
|
||||
func(entt, std::get<pool_type<Get> *>(pools)->get(entt)...);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @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> *...> 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).
|
||||
*
|
||||
* 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 Get Types of components observed by the group.
|
||||
* @tparam Owned Types of components owned by the group.
|
||||
*/
|
||||
template<typename Entity, typename... Get, typename... Owned>
|
||||
class basic_group<Entity, get_t<Get...>, Owned...> {
|
||||
static_assert(sizeof...(Get) + sizeof...(Owned) > 0);
|
||||
|
||||
/*! @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());
|
||||
|
||||
template<typename Component>
|
||||
decltype(auto) from_index(const typename sparse_set<Entity>::size_type index) {
|
||||
static_assert(!std::is_empty_v<Component>);
|
||||
|
||||
if constexpr(std::disjunction_v<std::is_same<Component, Owned>...>) {
|
||||
return std::as_const(*std::get<pool_type<Component> *>(pools)).raw()[index];
|
||||
} else {
|
||||
return std::as_const(*std::get<pool_type<Component> *>(pools)).get(data()[index]);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Component>
|
||||
inline auto swap(int, pool_type<Component> *cpool, const std::size_t lhs, const std::size_t rhs)
|
||||
-> decltype(cpool->raw(), void()) {
|
||||
std::swap(cpool->raw()[lhs], cpool->raw()[rhs]);
|
||||
cpool->swap(lhs, rhs);
|
||||
}
|
||||
|
||||
template<typename Component>
|
||||
inline void swap(char, pool_type<Component> *cpool, const std::size_t lhs, const std::size_t rhs) {
|
||||
cpool->swap(lhs, rhs);
|
||||
}
|
||||
|
||||
// we could use pool_type<Type> *..., but vs complains about it and refuses to compile for unknown reasons (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<Get>> *... get) ENTT_NOEXCEPT
|
||||
: length{sz},
|
||||
pools{owned..., get...}
|
||||
{}
|
||||
|
||||
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<0>(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<0>(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<0>(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<0>(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 &..., Get &...);
|
||||
* void(Owned &..., 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>
|
||||
inline void each(Func func) const {
|
||||
auto raw = std::make_tuple((std::get<pool_type<Owned> *>(pools)->end() - *length)...);
|
||||
[[maybe_unused]] auto data = std::get<0>(pools)->sparse_set<entity_type>::end() - *length;
|
||||
|
||||
for(auto next = *length; next; --next) {
|
||||
if constexpr(std::is_invocable_v<Func, decltype(get<Owned>({}))..., decltype(get<Get>({}))...>) {
|
||||
if constexpr(sizeof...(Get) == 0) {
|
||||
func(*(std::get<component_iterator_type<Owned>>(raw)++)...);
|
||||
} else {
|
||||
const auto entt = *(data++);
|
||||
func(*(std::get<component_iterator_type<Owned>>(raw)++)..., std::get<pool_type<Get> *>(pools)->get(entt)...);
|
||||
}
|
||||
} else {
|
||||
const auto entt = *(data++);
|
||||
func(entt, *(std::get<component_iterator_type<Owned>>(raw)++)..., std::get<pool_type<Get> *>(pools)->get(entt)...);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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(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) {
|
||||
std::vector<size_type> copy(*length);
|
||||
std::iota(copy.begin(), copy.end(), 0);
|
||||
|
||||
if constexpr(sizeof...(Component) == 0) {
|
||||
algo(copy.rbegin(), copy.rend(), [compare = std::move(compare), data = data()](const auto lhs, const auto rhs) {
|
||||
return compare(data[lhs], data[rhs]);
|
||||
}, std::forward<Args>(args)...);
|
||||
} else {
|
||||
algo(copy.rbegin(), copy.rend(), [compare = std::move(compare), this](const auto lhs, const auto rhs) {
|
||||
// useless this-> used to suppress a warning with clang
|
||||
return compare(this->from_index<Component>(lhs)..., this->from_index<Component>(rhs)...);
|
||||
}, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
for(size_type pos = 0, last = copy.size(); pos < last; ++pos) {
|
||||
auto curr = pos;
|
||||
auto next = copy[curr];
|
||||
|
||||
while(curr != next) {
|
||||
const auto lhs = copy[curr];
|
||||
const auto rhs = copy[next];
|
||||
(swap<Owned>(0, std::get<pool_type<Owned> *>(pools), lhs, rhs), ...);
|
||||
copy[curr] = curr;
|
||||
curr = next;
|
||||
next = copy[curr];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const typename basic_registry<Entity>::size_type *length;
|
||||
const std::tuple<pool_type<Owned> *..., pool_type<Get> *...> pools;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_GROUP_HPP
|
||||
211
src/entt/entity/helper.hpp
Normal file
211
src/entt/entity/helper.hpp
Normal file
@@ -0,0 +1,211 @@
|
||||
#ifndef ENTT_ENTITY_HELPER_HPP
|
||||
#define ENTT_ENTITY_HELPER_HPP
|
||||
|
||||
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "../core/hashed_string.hpp"
|
||||
#include "../signal/sigh.hpp"
|
||||
#include "registry.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @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).
|
||||
*/
|
||||
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>
|
||||
inline operator entt::basic_view<Entity, Component...>() const {
|
||||
return reg.template view<Component...>();
|
||||
}
|
||||
|
||||
private:
|
||||
registry_type ®
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Deduction guide.
|
||||
*
|
||||
* It allows to deduce the constness of a registry directly from the instance
|
||||
* provided to the constructor.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
as_view(basic_registry<Entity> &) ENTT_NOEXCEPT -> as_view<false, Entity>;
|
||||
|
||||
|
||||
/*! @copydoc as_view */
|
||||
template<typename Entity>
|
||||
as_view(const basic_registry<Entity> &) ENTT_NOEXCEPT -> as_view<true, Entity>;
|
||||
|
||||
|
||||
/**
|
||||
* @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).
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @note
|
||||
* Unfortunately, only full owning groups are supported because of an issue
|
||||
* with msvc that doesn't manage to correctly deduce types.
|
||||
*
|
||||
* @tparam Owned Types of components owned by the group.
|
||||
* @return A newly created group.
|
||||
*/
|
||||
template<typename... Owned>
|
||||
inline operator entt::basic_group<Entity, get_t<>, Owned...>() const {
|
||||
return reg.template group<Owned...>();
|
||||
}
|
||||
|
||||
private:
|
||||
registry_type ®
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @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 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.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Component Type of component that triggers the dependency handler.
|
||||
* @tparam Dependency Types of components to assign to an entity if triggered.
|
||||
* @param reg A valid reference to a registry.
|
||||
* @param entt A valid entity identifier.
|
||||
*/
|
||||
template<typename Entity, typename Component, typename... Dependency>
|
||||
void dependency(basic_registry<Entity> ®, const Entity entt, const Component &) {
|
||||
((reg.template has<Dependency>(entt) ? void() : (reg.template assign<Dependency>(entt), void())), ...);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Connects a dependency function to 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 adds components `a_type` and `another_type` whenever `my_type`
|
||||
* is assigned to an entity:
|
||||
* @code{.cpp}
|
||||
* entt::registry registry;
|
||||
* entt::connect<a_type, another_type>(registry.construction<my_type>());
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Dependency Types of components to assign to an entity if triggered.
|
||||
* @tparam Component Type of component that triggers the dependency handler.
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @param sink A sink object properly initialized.
|
||||
*/
|
||||
template<typename... Dependency, typename Component, typename Entity>
|
||||
inline void connect(sink<void(basic_registry<Entity> &, const Entity, Component &)> sink) {
|
||||
sink.template connect<dependency<Entity, Component, Dependency...>>();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @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 `my_type` and the
|
||||
* components `a_type` and `another_type`:
|
||||
* @code{.cpp}
|
||||
* entt::registry registry;
|
||||
* entt::disconnect<a_type, another_type>(registry.construction<my_type>());
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Dependency Types of components used to create the dependency.
|
||||
* @tparam Component Type of component that triggers the dependency handler.
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @param sink A sink object properly initialized.
|
||||
*/
|
||||
template<typename... Dependency, typename Component, typename Entity>
|
||||
inline void disconnect(sink<void(basic_registry<Entity> &, const Entity, Component &)> sink) {
|
||||
sink.template disconnect<dependency<Entity, Component, Dependency...>>();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Alias template to ease the assignment of tags to entities.
|
||||
*
|
||||
* If used in combination with hashed strings, it simplifies the assignment of
|
||||
* 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::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 hashed_string::hash_type Value>
|
||||
using tag = std::integral_constant<typename hashed_string::hash_type, Value>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_HELPER_HPP
|
||||
483
src/entt/entity/prototype.hpp
Normal file
483
src/entt/entity/prototype.hpp
Normal file
@@ -0,0 +1,483 @@
|
||||
#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"
|
||||
#include "entity.hpp"
|
||||
#include "fwd.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 basic_prototype {
|
||||
using basic_fn_type = void(const basic_prototype &, basic_registry<Entity> &, const Entity);
|
||||
using component_type = typename basic_registry<Entity>::component_type;
|
||||
|
||||
template<typename Component>
|
||||
struct component_wrapper { Component component; };
|
||||
|
||||
struct component_handler {
|
||||
basic_fn_type *assign_or_replace;
|
||||
basic_fn_type *assign;
|
||||
};
|
||||
|
||||
void release() {
|
||||
if(reg->valid(entity)) {
|
||||
reg->destroy(entity);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Registry type. */
|
||||
using registry_type = basic_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 ref A valid reference to a registry.
|
||||
*/
|
||||
basic_prototype(registry_type &ref)
|
||||
: reg{&ref},
|
||||
entity{ref.create()}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Releases all its resources.
|
||||
*/
|
||||
~basic_prototype() {
|
||||
release();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
basic_prototype(basic_prototype &&other)
|
||||
: handlers{std::move(other.handlers)},
|
||||
reg{other.reg},
|
||||
entity{other.entity}
|
||||
{
|
||||
other.entity = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
basic_prototype & operator=(basic_prototype &&other) {
|
||||
if(this != &other) {
|
||||
auto tmp{std::move(other)};
|
||||
handlers.swap(tmp.handlers);
|
||||
std::swap(reg, tmp.reg);
|
||||
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) {
|
||||
component_handler handler;
|
||||
|
||||
handler.assign_or_replace = [](const basic_prototype &proto, registry_type &other, const Entity dst) {
|
||||
const auto &wrapper = proto.reg->template get<component_wrapper<Component>>(proto.entity);
|
||||
other.template assign_or_replace<Component>(dst, wrapper.component);
|
||||
};
|
||||
|
||||
handler.assign = [](const basic_prototype &proto, registry_type &other, const Entity dst) {
|
||||
if(!other.template has<Component>(dst)) {
|
||||
const auto &wrapper = proto.reg->template get<component_wrapper<Component>>(proto.entity);
|
||||
other.template assign<Component>(dst, wrapper.component);
|
||||
}
|
||||
};
|
||||
|
||||
handlers[reg->template type<Component>()] = handler;
|
||||
auto &wrapper = reg->template assign_or_replace<component_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 {
|
||||
reg->template reset<component_wrapper<Component>>(entity);
|
||||
handlers.erase(reg->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 reg->template has<component_wrapper<Component>...>(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns references to the given components.
|
||||
*
|
||||
* @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 Types of components to get.
|
||||
* @return References to the components owned by the prototype.
|
||||
*/
|
||||
template<typename... Component>
|
||||
decltype(auto) get() const ENTT_NOEXCEPT {
|
||||
if constexpr(sizeof...(Component) == 1) {
|
||||
return (std::as_const(*reg).template get<component_wrapper<Component...>>(entity).component);
|
||||
} else {
|
||||
return std::tuple<std::add_const_t<Component> &...>{get<Component>()...};
|
||||
}
|
||||
}
|
||||
|
||||
/*! @copydoc get */
|
||||
template<typename... Component>
|
||||
inline decltype(auto) get() ENTT_NOEXCEPT {
|
||||
if constexpr(sizeof...(Component) == 1) {
|
||||
return (const_cast<Component &>(std::as_const(*this).template get<Component>()), ...);
|
||||
} else {
|
||||
return std::tuple<Component &...>{get<Component>()...};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns pointers to the given components.
|
||||
* @tparam Component Types of components to get.
|
||||
* @return Pointers to the components owned by the prototype.
|
||||
*/
|
||||
template<typename... Component>
|
||||
auto try_get() const ENTT_NOEXCEPT {
|
||||
if constexpr(sizeof...(Component) == 1) {
|
||||
const auto *wrapper = reg->template try_get<component_wrapper<Component...>>(entity);
|
||||
return wrapper ? &wrapper->component : nullptr;
|
||||
} else {
|
||||
return std::tuple<std::add_const_t<Component> *...>{try_get<Component>()...};
|
||||
}
|
||||
}
|
||||
|
||||
/*! @copydoc try_get */
|
||||
template<typename... Component>
|
||||
inline auto try_get() ENTT_NOEXCEPT {
|
||||
if constexpr(sizeof...(Component) == 1) {
|
||||
return (const_cast<Component *>(std::as_const(*this).template try_get<Component>()), ...);
|
||||
} else {
|
||||
return std::tuple<Component *...>{try_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 entt = other.create();
|
||||
assign(other, entt);
|
||||
return entt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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(*reg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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(*reg, 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 assign_or_replace(registry_type &other, const entity_type dst) const {
|
||||
for(auto &handler: handlers) {
|
||||
handler.second.assign_or_replace(*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 assign_or_replace(const entity_type dst) const {
|
||||
assign_or_replace(*reg, 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(*reg, 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(*reg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the underlying registry.
|
||||
* @return A reference to the underlying registry.
|
||||
*/
|
||||
inline const registry_type & backend() const ENTT_NOEXCEPT {
|
||||
return *reg;
|
||||
}
|
||||
|
||||
/*! @copydoc backend */
|
||||
inline registry_type & backend() ENTT_NOEXCEPT {
|
||||
return const_cast<registry_type &>(std::as_const(*this).backend());
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<component_type, component_handler> handlers;
|
||||
registry_type *reg;
|
||||
entity_type entity;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_PROTOTYPE_HPP
|
||||
File diff suppressed because it is too large
Load Diff
275
src/entt/entity/runtime_view.hpp
Normal file
275
src/entt/entity/runtime_view.hpp
Normal file
@@ -0,0 +1,275 @@
|
||||
#ifndef ENTT_ENTITY_RUNTIME_VIEW_HPP
|
||||
#define ENTT_ENTITY_RUNTIME_VIEW_HPP
|
||||
|
||||
|
||||
#include <iterator>
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <algorithm>
|
||||
#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).
|
||||
*
|
||||
* 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 extent_type = typename sparse_set<Entity>::size_type;
|
||||
using traits_type = entt_traits<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, extent_type ext) ENTT_NOEXCEPT
|
||||
: begin{first},
|
||||
end{last},
|
||||
from{others},
|
||||
to{length},
|
||||
extent{ext}
|
||||
{
|
||||
if(begin != end && !valid()) {
|
||||
++(*this);
|
||||
}
|
||||
}
|
||||
|
||||
bool valid() const ENTT_NOEXCEPT {
|
||||
const auto entt = *begin;
|
||||
const auto sz = size_type(entt & traits_type::entity_mask);
|
||||
|
||||
return sz < extent && std::all_of(from, to, [entt](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;
|
||||
}
|
||||
|
||||
inline bool operator!=(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
pointer operator->() const ENTT_NOEXCEPT {
|
||||
return begin.operator->();
|
||||
}
|
||||
|
||||
inline 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;
|
||||
extent_type extent;
|
||||
};
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
extent_type min() const ENTT_NOEXCEPT {
|
||||
extent_type extent{};
|
||||
|
||||
if(valid()) {
|
||||
const auto it = std::min_element(pools.cbegin(), pools.cend(), [](const auto *lhs, const auto *rhs) {
|
||||
return lhs->extent() < rhs->extent();
|
||||
});
|
||||
|
||||
extent = (*it)->extent();
|
||||
}
|
||||
|
||||
return extent;
|
||||
}
|
||||
|
||||
inline bool valid() const ENTT_NOEXCEPT {
|
||||
return !pools.empty() && pools.front();
|
||||
}
|
||||
|
||||
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 = 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(), min() };
|
||||
}
|
||||
|
||||
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, min() };
|
||||
}
|
||||
|
||||
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->has(entt) && view->data()[view->get(entt)] == entt;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
589
src/entt/entity/snapshot.hpp
Normal file
589
src/entt/entity/snapshot.hpp
Normal file
@@ -0,0 +1,589 @@
|
||||
#ifndef ENTT_ENTITY_SNAPSHOT_HPP
|
||||
#define ENTT_ENTITY_SNAPSHOT_HPP
|
||||
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include <iterator>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include "../config/config.h"
|
||||
#include "entity.hpp"
|
||||
#include "fwd.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @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 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 basic_snapshot {
|
||||
/*! @brief A registry is allowed to create snapshots. */
|
||||
friend class basic_registry<Entity>;
|
||||
|
||||
using follow_fn_type = Entity(const basic_registry<Entity> &, const Entity);
|
||||
|
||||
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));
|
||||
|
||||
while(first != last) {
|
||||
const auto entt = *(first++);
|
||||
|
||||
if(reg->template has<Component>(entt)) {
|
||||
if constexpr(std::is_empty_v<Component>) {
|
||||
archive(entt);
|
||||
} else {
|
||||
archive(entt, reg->template get<Component>(entt));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... Component, typename Archive, typename It, std::size_t... Indexes>
|
||||
void component(Archive &archive, It first, It last, std::index_sequence<Indexes...>) const {
|
||||
std::array<std::size_t, sizeof...(Indexes)> size{};
|
||||
auto begin = first;
|
||||
|
||||
while(begin != last) {
|
||||
const auto entt = *(begin++);
|
||||
((reg->template has<Component>(entt) ? ++size[Indexes] : size[Indexes]), ...);
|
||||
}
|
||||
|
||||
(get<Component>(archive, size[Indexes], first, last), ...);
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Default move constructor. */
|
||||
basic_snapshot(basic_snapshot &&) = default;
|
||||
|
||||
/*! @brief Default move assignment operator. @return This snapshot. */
|
||||
basic_snapshot & operator=(basic_snapshot &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Puts aside all the entities that are still in use.
|
||||
*
|
||||
* Entities are serialized along with their versions. Destroyed entities are
|
||||
* not taken in consideration by this function.
|
||||
*
|
||||
* @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 Archive>
|
||||
const basic_snapshot & entities(Archive &archive) const {
|
||||
archive(static_cast<Entity>(reg->alive()));
|
||||
reg->each([&archive](const auto entt) { archive(entt); });
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Puts aside destroyed entities.
|
||||
*
|
||||
* Entities are serialized along with their versions. Entities that are
|
||||
* still in use are not taken in consideration by this function.
|
||||
*
|
||||
* @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 Archive>
|
||||
const basic_snapshot & destroyed(Archive &archive) const {
|
||||
auto size = reg->size() - reg->alive();
|
||||
archive(static_cast<Entity>(size));
|
||||
|
||||
if(size) {
|
||||
auto curr = seed;
|
||||
archive(curr);
|
||||
|
||||
for(--size; size; --size) {
|
||||
curr = follow(*reg, curr);
|
||||
archive(curr);
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Puts aside the given components.
|
||||
*
|
||||
* Each instance is serialized together with the entity to which it belongs.
|
||||
* Entities are serialized along with their versions.
|
||||
*
|
||||
* @tparam Component Types of components 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 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(static_cast<Entity>(sz));
|
||||
|
||||
for(std::remove_const_t<decltype(sz)> i{}; i < sz; ++i) {
|
||||
const auto entt = entities[i];
|
||||
|
||||
if constexpr(std::is_empty_v<Component...>) {
|
||||
archive(entt);
|
||||
} else {
|
||||
archive(entt, reg->template get<Component...>(entt));
|
||||
}
|
||||
};
|
||||
} else {
|
||||
(component<Component>(archive), ...);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Puts aside the given components for the entities in a range.
|
||||
*
|
||||
* Each instance is serialized together with the entity to which it belongs.
|
||||
* Entities are serialized along with their versions.
|
||||
*
|
||||
* @tparam Component Types of components to serialize.
|
||||
* @tparam Archive Type of output archive.
|
||||
* @tparam It Type of input iterator.
|
||||
* @param archive A valid reference to an output archive.
|
||||
* @param first An iterator to the first element of the range to serialize.
|
||||
* @param last An iterator past the last element of the range to serialize.
|
||||
* @return An object of this type to continue creating the snapshot.
|
||||
*/
|
||||
template<typename... Component, typename Archive, typename It>
|
||||
const basic_snapshot & component(Archive &archive, It first, It last) const {
|
||||
component<Component...>(archive, first, last, std::make_index_sequence<sizeof...(Component)>{});
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
const basic_registry<Entity> *reg;
|
||||
const Entity seed;
|
||||
follow_fn_type *follow;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Utility class to restore a snapshot as a whole.
|
||||
*
|
||||
* A snapshot loader requires that the destination registry be empty and loads
|
||||
* all the data at once while keeping intact the identifiers that the entities
|
||||
* originally had.<br/>
|
||||
* An example of use is the implementation of a save/restore utility.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
class basic_snapshot_loader {
|
||||
/*! @brief A registry is allowed to create snapshot loaders. */
|
||||
friend class basic_registry<Entity>;
|
||||
|
||||
using force_fn_type = void(basic_registry<Entity> &, const Entity, const bool);
|
||||
|
||||
basic_snapshot_loader(basic_registry<Entity> *source, force_fn_type *fn) ENTT_NOEXCEPT
|
||||
: reg{source},
|
||||
force{fn}
|
||||
{
|
||||
// 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{};
|
||||
archive(length);
|
||||
|
||||
while(length--) {
|
||||
Entity entt{};
|
||||
archive(entt);
|
||||
force(*reg, entt, destroyed);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Type, typename Archive, typename... Args>
|
||||
void assign(Archive &archive, Args... args) const {
|
||||
Entity length{};
|
||||
archive(length);
|
||||
|
||||
while(length--) {
|
||||
static constexpr auto destroyed = false;
|
||||
Entity entt{};
|
||||
|
||||
if constexpr(std::is_empty_v<Type>) {
|
||||
archive(entt);
|
||||
force(*reg, entt, destroyed);
|
||||
reg->template assign<Type>(args..., entt);
|
||||
} else {
|
||||
Type instance{};
|
||||
archive(entt, instance);
|
||||
force(*reg, entt, destroyed);
|
||||
reg->template assign<Type>(args..., entt, std::as_const(instance));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Default move constructor. */
|
||||
basic_snapshot_loader(basic_snapshot_loader &&) = default;
|
||||
|
||||
/*! @brief Default move assignment operator. @return This loader. */
|
||||
basic_snapshot_loader & operator=(basic_snapshot_loader &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Restores entities that were in use during serialization.
|
||||
*
|
||||
* This function restores the entities that were in use during serialization
|
||||
* and gives them the versions they originally had.
|
||||
*
|
||||
* @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 Archive>
|
||||
const basic_snapshot_loader & entities(Archive &archive) const {
|
||||
static constexpr auto destroyed = false;
|
||||
assure(archive, destroyed);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Restores entities that were destroyed during serialization.
|
||||
*
|
||||
* This function restores the entities that were destroyed during
|
||||
* serialization and gives them the versions they originally had.
|
||||
*
|
||||
* @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 Archive>
|
||||
const basic_snapshot_loader & destroyed(Archive &archive) const {
|
||||
static constexpr auto destroyed = true;
|
||||
assure(archive, destroyed);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Restores components 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 component is
|
||||
* assigned doesn't exist yet, the loader will take care to create it with
|
||||
* the version it originally had.
|
||||
*
|
||||
* @tparam Component Types of components 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... Component, typename Archive>
|
||||
const basic_snapshot_loader & component(Archive &archive) const {
|
||||
(assign<Component>(archive), ...);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destroys those entities that have no components.
|
||||
*
|
||||
* In case all the entities were serialized but only part of the components
|
||||
* 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 basic_snapshot_loader & orphans() const {
|
||||
reg->orphans([this](const auto entt) {
|
||||
reg->destroy(entt);
|
||||
});
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
basic_registry<Entity> *reg;
|
||||
force_fn_type *force;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Utility class for _continuous loading_.
|
||||
*
|
||||
* A _continuous loader_ is designed to load data from a source registry to a
|
||||
* (possibly) non-empty destination. The loader can accomodate in a registry
|
||||
* more than one snapshot in a sort of _continuous loading_ that updates the
|
||||
* destination one step at a time.<br/>
|
||||
* Identifiers that entities originally had are not transferred to the target.
|
||||
* Instead, the loader maps remote identifiers to local ones while restoring a
|
||||
* snapshot.<br/>
|
||||
* An example of use is the implementation of a client-server applications with
|
||||
* the requirement of transferring somehow parts of the representation side to
|
||||
* side.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
class basic_continuous_loader {
|
||||
using traits_type = entt_traits<Entity>;
|
||||
|
||||
void destroy(Entity entt) {
|
||||
const auto it = remloc.find(entt);
|
||||
|
||||
if(it == remloc.cend()) {
|
||||
const auto local = reg->create();
|
||||
remloc.emplace(entt, std::make_pair(local, true));
|
||||
reg->destroy(local);
|
||||
}
|
||||
}
|
||||
|
||||
void restore(Entity entt) {
|
||||
const auto it = remloc.find(entt);
|
||||
|
||||
if(it == remloc.cend()) {
|
||||
const auto local = reg->create();
|
||||
remloc.emplace(entt, std::make_pair(local, true));
|
||||
} else {
|
||||
remloc[entt].first = reg->valid(remloc[entt].first) ? remloc[entt].first : reg->create();
|
||||
// set the dirty flag
|
||||
remloc[entt].second = true;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Other, typename Type, typename Member>
|
||||
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(basic_continuous_loader:: *member)(Entity)) {
|
||||
Entity length{};
|
||||
archive(length);
|
||||
|
||||
while(length--) {
|
||||
Entity entt{};
|
||||
archive(entt);
|
||||
(this->*member)(entt);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Component>
|
||||
void reset() {
|
||||
for(auto &&ref: remloc) {
|
||||
const auto local = ref.second.first;
|
||||
|
||||
if(reg->valid(local)) {
|
||||
reg->template reset<Component>(local);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Other, typename Archive, typename... Type, typename... Member>
|
||||
void assign(Archive &archive, [[maybe_unused]] Member Type:: *... member) {
|
||||
Entity length{};
|
||||
archive(length);
|
||||
|
||||
while(length--) {
|
||||
Entity entt{};
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
|
||||
/**
|
||||
* @brief Constructs a loader that is bound to a given registry.
|
||||
* @param source A valid reference to a registry.
|
||||
*/
|
||||
basic_continuous_loader(basic_registry<entity_type> &source) ENTT_NOEXCEPT
|
||||
: reg{&source}
|
||||
{}
|
||||
|
||||
/*! @brief Default move constructor. */
|
||||
basic_continuous_loader(basic_continuous_loader &&) = default;
|
||||
|
||||
/*! @brief Default move assignment operator. @return This loader. */
|
||||
basic_continuous_loader & operator=(basic_continuous_loader &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Restores entities that were in use during serialization.
|
||||
*
|
||||
* This function restores the entities that were in use during serialization
|
||||
* and creates local counterparts for them if required.
|
||||
*
|
||||
* @tparam Archive Type of input archive.
|
||||
* @param archive A valid reference to an input archive.
|
||||
* @return A non-const reference to this loader.
|
||||
*/
|
||||
template<typename Archive>
|
||||
basic_continuous_loader & entities(Archive &archive) {
|
||||
assure(archive, &basic_continuous_loader::restore);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Restores entities that were destroyed during serialization.
|
||||
*
|
||||
* This function restores the entities that were destroyed during
|
||||
* serialization and creates local counterparts for them if required.
|
||||
*
|
||||
* @tparam Archive Type of input archive.
|
||||
* @param archive A valid reference to an input archive.
|
||||
* @return A non-const reference to this loader.
|
||||
*/
|
||||
template<typename Archive>
|
||||
basic_continuous_loader & destroyed(Archive &archive) {
|
||||
assure(archive, &basic_continuous_loader::destroy);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Restores components 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 component 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 Component Type of component 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... Component, typename Archive, typename... Type, typename... Member>
|
||||
basic_continuous_loader & component(Archive &archive, Member Type:: *... member) {
|
||||
(reset<Component>(), ...);
|
||||
(assign<Component>(archive, member...), ...);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Helps to purge entities that no longer have a conterpart.
|
||||
*
|
||||
* Users should invoke this member function after restoring each snapshot,
|
||||
* unless they know exactly what they are doing.
|
||||
*
|
||||
* @return A non-const reference to this loader.
|
||||
*/
|
||||
basic_continuous_loader & shrink() {
|
||||
auto it = remloc.begin();
|
||||
|
||||
while(it != remloc.cend()) {
|
||||
const auto local = it->second.first;
|
||||
bool &dirty = it->second.second;
|
||||
|
||||
if(dirty) {
|
||||
dirty = false;
|
||||
++it;
|
||||
} else {
|
||||
if(reg->valid(local)) {
|
||||
reg->destroy(local);
|
||||
}
|
||||
|
||||
it = remloc.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destroys those entities that have no components.
|
||||
*
|
||||
* In case all the entities were serialized but only part of the components
|
||||
* 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.
|
||||
*/
|
||||
basic_continuous_loader & orphans() {
|
||||
reg->orphans([this](const auto entt) {
|
||||
reg->destroy(entt);
|
||||
});
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Tests if a loader knows about a given entity.
|
||||
* @param entt An entity identifier.
|
||||
* @return True if `entity` is managed by the loader, false otherwise.
|
||||
*/
|
||||
bool has(entity_type entt) const ENTT_NOEXCEPT {
|
||||
return (remloc.find(entt) != remloc.cend());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the identifier to which an entity refers.
|
||||
* @param entt An entity identifier.
|
||||
* @return The local identifier if any, the null entity otherwise.
|
||||
*/
|
||||
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;
|
||||
basic_registry<Entity> *reg;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_SNAPSHOT_HPP
|
||||
@@ -3,26 +3,18 @@
|
||||
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <cstddef>
|
||||
#include <cassert>
|
||||
#include "traits.hpp"
|
||||
#include "../config/config.h"
|
||||
#include "entity.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Sparse set.
|
||||
*
|
||||
* Primary template isn't defined on purpose. All the specializations give a
|
||||
* compile-time error, but for a few reasonable cases.
|
||||
*/
|
||||
template<typename...>
|
||||
class SparseSet;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Basic sparse set implementation.
|
||||
*
|
||||
@@ -30,9 +22,9 @@ class SparseSet;
|
||||
* Two arrays: an _external_ one and an _internal_ one; a _sparse_ one and a
|
||||
* _packed_ one; one used for direct access through contiguous memory, the other
|
||||
* one used to get the data through an extra level of indirection.<br/>
|
||||
* This is largely used by the Registry to offer users the fastest access ever
|
||||
* to the components. View and PersistentView are entirely designed around
|
||||
* sparse sets.
|
||||
* This is largely used by the registry to offer users the fastest access ever
|
||||
* to the components. Views and groups in general are almost entirely designed
|
||||
* around sparse sets.
|
||||
*
|
||||
* This type of data structure is widely documented in the literature and on the
|
||||
* web. This is nothing more than a customized implementation suitable for the
|
||||
@@ -51,69 +43,233 @@ class SparseSet;
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
class SparseSet<Entity> {
|
||||
class sparse_set {
|
||||
using traits_type = entt_traits<Entity>;
|
||||
|
||||
struct Iterator {
|
||||
using value_type = Entity;
|
||||
static_assert(ENTT_PAGE_SIZE && ((ENTT_PAGE_SIZE & (ENTT_PAGE_SIZE - 1)) == 0));
|
||||
static constexpr auto entt_per_page = ENTT_PAGE_SIZE / sizeof(typename entt_traits<Entity>::entity_type);
|
||||
|
||||
Iterator(const std::vector<Entity> *direct, std::size_t pos)
|
||||
: direct{direct}, pos{pos}
|
||||
class iterator {
|
||||
friend class sparse_set<Entity>;
|
||||
|
||||
using direct_type = const std::vector<Entity>;
|
||||
using index_type = typename traits_type::difference_type;
|
||||
|
||||
iterator(direct_type *ref, const index_type idx) ENTT_NOEXCEPT
|
||||
: direct{ref}, index{idx}
|
||||
{}
|
||||
|
||||
Iterator & operator++() noexcept {
|
||||
return --pos, *this;
|
||||
public:
|
||||
using difference_type = index_type;
|
||||
using value_type = Entity;
|
||||
using pointer = const value_type *;
|
||||
using reference = const value_type &;
|
||||
using iterator_category = std::random_access_iterator_tag;
|
||||
|
||||
iterator() ENTT_NOEXCEPT = default;
|
||||
|
||||
iterator & operator++() ENTT_NOEXCEPT {
|
||||
return --index, *this;
|
||||
}
|
||||
|
||||
Iterator operator++(int) noexcept {
|
||||
Iterator orig = *this;
|
||||
iterator operator++(int) ENTT_NOEXCEPT {
|
||||
iterator orig = *this;
|
||||
return ++(*this), orig;
|
||||
}
|
||||
|
||||
bool operator==(const Iterator &other) const noexcept {
|
||||
return other.pos == pos && other.direct == direct;
|
||||
iterator & operator--() ENTT_NOEXCEPT {
|
||||
return ++index, *this;
|
||||
}
|
||||
|
||||
bool operator!=(const Iterator &other) const noexcept {
|
||||
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{direct, index-value};
|
||||
}
|
||||
|
||||
inline iterator & operator-=(const difference_type value) ENTT_NOEXCEPT {
|
||||
return (*this += -value);
|
||||
}
|
||||
|
||||
inline 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 (*direct)[pos];
|
||||
}
|
||||
|
||||
bool operator==(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return other.index == index;
|
||||
}
|
||||
|
||||
inline bool operator!=(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
value_type operator*() const noexcept {
|
||||
return (*direct)[pos-1];
|
||||
bool operator<(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return index > other.index;
|
||||
}
|
||||
|
||||
bool operator>(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return index < other.index;
|
||||
}
|
||||
|
||||
inline bool operator<=(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this > other);
|
||||
}
|
||||
|
||||
inline bool operator>=(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this < other);
|
||||
}
|
||||
|
||||
pointer operator->() const ENTT_NOEXCEPT {
|
||||
const auto pos = size_type(index-1);
|
||||
return &(*direct)[pos];
|
||||
}
|
||||
|
||||
inline reference operator*() const ENTT_NOEXCEPT {
|
||||
return *operator->();
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<Entity> *direct;
|
||||
std::size_t pos;
|
||||
direct_type *direct;
|
||||
index_type index;
|
||||
};
|
||||
|
||||
static constexpr Entity in_use = 1 << traits_type::entity_shift;
|
||||
void assure(const std::size_t page) {
|
||||
if(!(page < reverse.size())) {
|
||||
reverse.resize(page+1);
|
||||
}
|
||||
|
||||
if(!reverse[page].first) {
|
||||
reverse[page].first = std::make_unique<entity_type[]>(entt_per_page);
|
||||
// null is safe in all cases for our purposes
|
||||
std::fill_n(reverse[page].first.get(), entt_per_page, null);
|
||||
}
|
||||
}
|
||||
|
||||
auto index(const Entity entt) const ENTT_NOEXCEPT {
|
||||
const auto identifier = entt & traits_type::entity_mask;
|
||||
const auto page = size_type(identifier / entt_per_page);
|
||||
const auto offset = size_type(identifier & (entt_per_page - 1));
|
||||
return std::make_pair(page, offset);
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
/*! @brief Entity dependent position type. */
|
||||
using pos_type = entity_type;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Input iterator type. */
|
||||
using iterator_type = Iterator;
|
||||
/*! @brief Random access iterator type. */
|
||||
using iterator_type = iterator;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
SparseSet() noexcept = default;
|
||||
sparse_set() = default;
|
||||
|
||||
/**
|
||||
* @brief Copy constructor.
|
||||
* @param other The instance to copy from.
|
||||
*/
|
||||
sparse_set(const sparse_set &other)
|
||||
: reverse{},
|
||||
direct{other.direct}
|
||||
{
|
||||
for(size_type i = {}, last = other.reverse.size(); i < last; ++i) {
|
||||
if(other.reverse[i].first) {
|
||||
assure(i);
|
||||
std::copy_n(other.reverse[i].first.get(), entt_per_page, reverse[i].first.get());
|
||||
reverse[i].second = other.reverse[i].second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*! @brief Default move constructor. */
|
||||
sparse_set(sparse_set &&) = default;
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
virtual ~SparseSet() noexcept = default;
|
||||
virtual ~sparse_set() ENTT_NOEXCEPT = default;
|
||||
|
||||
/*! @brief Copying a sparse set isn't allowed. */
|
||||
SparseSet(const SparseSet &) = delete;
|
||||
/*! @brief Default move constructor. */
|
||||
SparseSet(SparseSet &&) = default;
|
||||
/**
|
||||
* @brief Copy assignment operator.
|
||||
* @param other The instance to copy from.
|
||||
* @return This sparse set.
|
||||
*/
|
||||
sparse_set & operator=(const sparse_set &other) {
|
||||
if(&other != this) {
|
||||
auto tmp{other};
|
||||
*this = std::move(tmp);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/*! @brief Copying a sparse set isn't allowed. @return This sparse set. */
|
||||
SparseSet & operator=(const SparseSet &) = delete;
|
||||
/*! @brief Default move assignment operator. @return This sparse set. */
|
||||
SparseSet & operator=(SparseSet &&) = default;
|
||||
sparse_set & operator=(sparse_set &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Increases the capacity of a sparse set.
|
||||
*
|
||||
* If the new capacity is greater than the current capacity, new storage is
|
||||
* allocated, otherwise the method does nothing.
|
||||
*
|
||||
* @param cap Desired capacity.
|
||||
*/
|
||||
virtual void reserve(const size_type cap) {
|
||||
direct.reserve(cap);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the number of elements that a sparse set has currently
|
||||
* allocated space for.
|
||||
* @return Capacity of the sparse set.
|
||||
*/
|
||||
size_type capacity() const ENTT_NOEXCEPT {
|
||||
return direct.capacity();
|
||||
}
|
||||
|
||||
/*! @brief Requests the removal of unused capacity. */
|
||||
virtual void shrink_to_fit() {
|
||||
while(!reverse.empty() && !reverse.back().second) {
|
||||
reverse.pop_back();
|
||||
}
|
||||
|
||||
for(auto &&data: reverse) {
|
||||
if(!data.second) {
|
||||
data.first.reset();
|
||||
}
|
||||
}
|
||||
|
||||
reverse.shrink_to_fit();
|
||||
direct.shrink_to_fit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the extent of a sparse set.
|
||||
*
|
||||
* The extent of a sparse set is also the size of the internal sparse array.
|
||||
* There is no guarantee that the internal packed array has the same size.
|
||||
* Usually the size of the internal sparse array is equal or greater than
|
||||
* the one of the internal packed array.
|
||||
*
|
||||
* @return Extent of the sparse set.
|
||||
*/
|
||||
size_type extent() const ENTT_NOEXCEPT {
|
||||
return reverse.size() * entt_per_page;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the number of elements in a sparse set.
|
||||
@@ -125,7 +281,7 @@ public:
|
||||
*
|
||||
* @return Number of elements.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
size_type size() const ENTT_NOEXCEPT {
|
||||
return direct.size();
|
||||
}
|
||||
|
||||
@@ -133,7 +289,7 @@ public:
|
||||
* @brief Checks whether a sparse set is empty.
|
||||
* @return True if the sparse set is empty, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
return direct.empty();
|
||||
}
|
||||
|
||||
@@ -144,7 +300,7 @@ public:
|
||||
* always a valid range, even if the container is empty.
|
||||
*
|
||||
* @note
|
||||
* There are no guarantees on the order, even though `sort` has been
|
||||
* There are no guarantees on the order, even though `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 sparse set
|
||||
@@ -152,52 +308,63 @@ public:
|
||||
*
|
||||
* @return A pointer to the internal packed array.
|
||||
*/
|
||||
const entity_type * data() const noexcept {
|
||||
const entity_type * data() const ENTT_NOEXCEPT {
|
||||
return direct.data();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the beginning.
|
||||
*
|
||||
* The returned iterator points to the first element of the internal packed
|
||||
* The returned iterator points to the first entity of the internal packed
|
||||
* array. If the sparse set is empty, the returned iterator will be equal to
|
||||
* `end()`.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed by a call to `sort`.
|
||||
* Input iterators stay true to the order imposed by a call to `respect`.
|
||||
*
|
||||
* @return An iterator to the first element of the internal packed array.
|
||||
* @return An iterator to the first entity of the internal packed array.
|
||||
*/
|
||||
iterator_type begin() const noexcept {
|
||||
return Iterator{&direct, direct.size()};
|
||||
iterator_type begin() const ENTT_NOEXCEPT {
|
||||
const typename traits_type::difference_type pos = direct.size();
|
||||
return iterator_type{&direct, pos};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the end.
|
||||
*
|
||||
* The returned iterator points to the element following the last element in
|
||||
* The returned iterator points to the element following the last entity in
|
||||
* the internal packed array. Attempting to dereference the returned
|
||||
* iterator results in undefined behavior.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed by a call to `sort`.
|
||||
* Input iterators stay true to the order imposed by a call to `respect`.
|
||||
*
|
||||
* @return An iterator to the element following the last element of the
|
||||
* @return An iterator to the element following the last entity of the
|
||||
* internal packed array.
|
||||
*/
|
||||
iterator_type end() const noexcept {
|
||||
return Iterator{&direct, 0};
|
||||
iterator_type end() const ENTT_NOEXCEPT {
|
||||
return iterator_type{&direct, {}};
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 {
|
||||
return has(entt) ? --(end() - get(entt)) : end();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a sparse set contains an entity.
|
||||
* @param entity A valid entity identifier.
|
||||
* @param entt A valid entity identifier.
|
||||
* @return True if the sparse set contains the entity, false otherwise.
|
||||
*/
|
||||
bool has(entity_type entity) const noexcept {
|
||||
const auto entt = entity & traits_type::entity_mask;
|
||||
// the in-use control bit permits to avoid accessing the direct vector
|
||||
return (entt < reverse.size()) && (reverse[entt] & in_use);
|
||||
bool has(const entity_type entt) const ENTT_NOEXCEPT {
|
||||
auto [page, offset] = index(entt);
|
||||
// testing against null permits to avoid accessing the direct vector
|
||||
return (page < reverse.size() && reverse[page].second && reverse[page].first[offset] != null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,14 +376,13 @@ public:
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* sparse set doesn't contain the given entity.
|
||||
*
|
||||
* @param entity A valid entity identifier.
|
||||
* @param entt A valid entity identifier.
|
||||
* @return The position of the entity in the sparse set.
|
||||
*/
|
||||
pos_type get(entity_type entity) const noexcept {
|
||||
assert(has(entity));
|
||||
const auto entt = entity & traits_type::entity_mask;
|
||||
// we must get rid of the in-use bit for it's not part of the position
|
||||
return reverse[entt] & ~in_use;
|
||||
size_type get(const entity_type entt) const ENTT_NOEXCEPT {
|
||||
ENTT_ASSERT(has(entt));
|
||||
auto [page, offset] = index(entt);
|
||||
return size_type(reverse[page].first[offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,20 +394,41 @@ public:
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* sparse set already contains the given entity.
|
||||
*
|
||||
* @param entity A valid entity identifier.
|
||||
* @param entt A valid entity identifier.
|
||||
*/
|
||||
void construct(entity_type entity) {
|
||||
assert(!has(entity));
|
||||
const auto entt = entity & traits_type::entity_mask;
|
||||
void construct(const entity_type entt) {
|
||||
ENTT_ASSERT(!has(entt));
|
||||
auto [page, offset] = index(entt);
|
||||
assure(page);
|
||||
reverse[page].first[offset] = entity_type(direct.size());
|
||||
reverse[page].second++;
|
||||
direct.push_back(entt);
|
||||
}
|
||||
|
||||
if(!(entt < reverse.size())) {
|
||||
reverse.resize(entt+1, pos_type{});
|
||||
}
|
||||
/**
|
||||
* @brief Assigns one or more entities to a sparse set.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to assign an entity that already belongs to the sparse set
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* sparse set 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.
|
||||
*/
|
||||
template<typename It>
|
||||
void batch(It first, It last) {
|
||||
std::for_each(first, last, [next = entity_type(direct.size()), this](const auto entt) mutable {
|
||||
ENTT_ASSERT(!has(entt));
|
||||
auto [page, offset] = index(entt);
|
||||
assure(page);
|
||||
reverse[page].first[offset] = next++;
|
||||
reverse[page].second++;
|
||||
});
|
||||
|
||||
// we exploit the fact that pos_type is equal to entity_type and pos has
|
||||
// traits_type::version_mask bits unused we can use to mark it as in-use
|
||||
reverse[entt] = pos_type(direct.size()) | in_use;
|
||||
direct.emplace_back(entity);
|
||||
direct.insert(direct.end(), first, last);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -253,19 +440,16 @@ public:
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* sparse set doesn't contain the given entity.
|
||||
*
|
||||
* @param entity A valid entity identifier.
|
||||
* @param entt A valid entity identifier.
|
||||
*/
|
||||
virtual void destroy(entity_type entity) {
|
||||
assert(has(entity));
|
||||
const auto entt = entity & traits_type::entity_mask;
|
||||
const auto back = direct.back() & traits_type::entity_mask;
|
||||
const auto pos = reverse[entt] & ~in_use;
|
||||
// the order matters: if back and entt are the same (for the sparse set
|
||||
// has size 1), switching the two lines below doesn't work as expected
|
||||
reverse[back] = pos | in_use;
|
||||
reverse[entt] = pos;
|
||||
// swapping isn't required here, we are getting rid of the last element
|
||||
direct[pos] = direct.back();
|
||||
virtual void destroy(const entity_type entt) {
|
||||
ENTT_ASSERT(has(entt));
|
||||
auto [from_page, from_offset] = index(entt);
|
||||
auto [to_page, to_offset] = index(direct.back());
|
||||
direct[size_type(reverse[from_page].first[from_offset])] = direct.back();
|
||||
reverse[to_page].first[to_offset] = reverse[from_page].first[from_offset];
|
||||
reverse[from_page].first[from_offset] = null;
|
||||
reverse[from_page].second--;
|
||||
direct.pop_back();
|
||||
}
|
||||
|
||||
@@ -281,50 +465,20 @@ public:
|
||||
* 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 entity identifier.
|
||||
* @param rhs A valid entity identifier.
|
||||
* @param lhs A valid position within the sparse set.
|
||||
* @param rhs A valid position within the sparse set.
|
||||
*/
|
||||
virtual void swap(entity_type lhs, entity_type rhs) {
|
||||
assert(has(lhs));
|
||||
assert(has(rhs));
|
||||
auto &le = reverse[lhs & traits_type::entity_mask];
|
||||
auto &re = reverse[rhs & traits_type::entity_mask];
|
||||
// we must get rid of the in-use bit for it's not part of the position
|
||||
std::swap(direct[le & ~in_use], direct[re & ~in_use]);
|
||||
std::swap(le, re);
|
||||
void swap(const size_type lhs, const size_type rhs) ENTT_NOEXCEPT {
|
||||
ENTT_ASSERT(lhs < direct.size());
|
||||
ENTT_ASSERT(rhs < direct.size());
|
||||
auto [src_page, src_offset] = index(direct[lhs]);
|
||||
auto [dst_page, dst_offset] = index(direct[rhs]);
|
||||
std::swap(reverse[src_page].first[src_offset], reverse[dst_page].first[dst_offset]);
|
||||
std::swap(direct[lhs], direct[rhs]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sort entities according to the given comparison function.
|
||||
*
|
||||
* Sort the elements so that iterating the sparse set with a couple of
|
||||
* iterators returns them in the expected order. See `begin` and `end` for
|
||||
* more details.
|
||||
*
|
||||
* @note
|
||||
* Attempting to iterate elements using the raw pointer returned by `data`
|
||||
* gives no guarantees on the order, even though `sort` has been invoked.
|
||||
*
|
||||
* @tparam Compare Type of the comparison function.
|
||||
* @param compare A comparison function whose signature shall be equivalent
|
||||
* to: `bool(Entity, Entity)`.
|
||||
*/
|
||||
template<typename Compare>
|
||||
void sort(Compare compare) {
|
||||
std::vector<pos_type> copy{direct.cbegin(), direct.cend()};
|
||||
std::sort(copy.begin(), copy.end(), [compare = std::move(compare)](auto... args) {
|
||||
return !compare(args...);
|
||||
});
|
||||
|
||||
for(pos_type i = 0; i < copy.size(); ++i) {
|
||||
if(direct[i] != copy[i]) {
|
||||
swap(direct[i], copy[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sort entities according to their order in a sparse set.
|
||||
* @brief Sort entities according to their order in another sparse set.
|
||||
*
|
||||
* Entities that are part of both the sparse sets are ordered internally
|
||||
* according to the order they have in `other`. All the other entities goes
|
||||
@@ -333,41 +487,32 @@ public:
|
||||
* sets by using one of them as a master and the other one as a slave.
|
||||
*
|
||||
* Iterating the sparse set with a couple of iterators returns elements in
|
||||
* the expected order after a call to `sort`. See `begin` and `end` for more
|
||||
* details.
|
||||
* the expected order after a call to `respect`. See `begin` and `end` for
|
||||
* more details.
|
||||
*
|
||||
* @note
|
||||
* Attempting to iterate elements using the raw pointer returned by `data`
|
||||
* gives no guarantees on the order, even though `sort` has been invoked.
|
||||
* gives no guarantees on the order, even though `respect` has been invoked.
|
||||
*
|
||||
* @param other The sparse sets that imposes the order of the entities.
|
||||
*/
|
||||
void respect(const SparseSet<Entity> &other) {
|
||||
struct Bool { bool value{false}; };
|
||||
std::vector<Bool> check(std::max(other.reverse.size(), reverse.size()));
|
||||
virtual void respect(const sparse_set &other) ENTT_NOEXCEPT {
|
||||
const auto to = other.end();
|
||||
auto from = other.begin();
|
||||
|
||||
for(auto entity: other.direct) {
|
||||
check[entity & traits_type::entity_mask].value = true;
|
||||
}
|
||||
size_type pos = direct.size() - 1;
|
||||
|
||||
sort([this, &other, &check](auto lhs, auto rhs) {
|
||||
const auto le = lhs & traits_type::entity_mask;
|
||||
const auto re = rhs & traits_type::entity_mask;
|
||||
while(pos && from != to) {
|
||||
if(has(*from)) {
|
||||
if(*from != direct[pos]) {
|
||||
swap(pos, get(*from));
|
||||
}
|
||||
|
||||
const bool bLhs = check[le].value;
|
||||
const bool bRhs = check[re].value;
|
||||
bool compare = false;
|
||||
|
||||
if(bLhs && bRhs) {
|
||||
compare = other.get(rhs) < other.get(lhs);
|
||||
} else if(!bLhs && !bRhs) {
|
||||
compare = re < le;
|
||||
} else {
|
||||
compare = bLhs;
|
||||
--pos;
|
||||
}
|
||||
|
||||
return compare;
|
||||
});
|
||||
++from;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -379,206 +524,11 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<pos_type> reverse;
|
||||
std::vector<std::pair<std::unique_ptr<entity_type[]>, size_type>> reverse;
|
||||
std::vector<entity_type> direct;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Extended sparse set implementation.
|
||||
*
|
||||
* This specialization of a sparse set associates an object to an entity. The
|
||||
* main purpose of this class is to use 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 input 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.
|
||||
*
|
||||
* @sa SparseSet<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>
|
||||
class SparseSet<Entity, Type>: public SparseSet<Entity> {
|
||||
using underlying_type = SparseSet<Entity>;
|
||||
|
||||
public:
|
||||
/*! @brief Type of the objects associated to the entities. */
|
||||
using object_type = Type;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = typename underlying_type::entity_type;
|
||||
/*! @brief Entity dependent position type. */
|
||||
using pos_type = typename underlying_type::pos_type;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename underlying_type::size_type;
|
||||
/*! @brief Input iterator type. */
|
||||
using iterator_type = typename underlying_type::iterator_type;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
SparseSet() noexcept = default;
|
||||
|
||||
/*! @brief Copying a sparse set isn't allowed. */
|
||||
SparseSet(const SparseSet &) = delete;
|
||||
/*! @brief Default move constructor. */
|
||||
SparseSet(SparseSet &&) = default;
|
||||
|
||||
/*! @brief Copying a sparse set isn't allowed. @return This sparse set. */
|
||||
SparseSet & operator=(const SparseSet &) = delete;
|
||||
/*! @brief Default move assignment operator. @return This sparse set. */
|
||||
SparseSet & operator=(SparseSet &&) = default;
|
||||
|
||||
/**
|
||||
* @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 `sort` 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 sparse set
|
||||
* in the expected order.
|
||||
*
|
||||
* @return A pointer to the array of objects.
|
||||
*/
|
||||
const object_type * raw() const noexcept {
|
||||
return instances.data();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 `sort` 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 sparse set
|
||||
* in the expected order.
|
||||
*
|
||||
* @return A pointer to the array of objects.
|
||||
*/
|
||||
object_type * raw() noexcept {
|
||||
return instances.data();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the object associated to an entity.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that doesn'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 entity.
|
||||
*
|
||||
* @param entity A valid entity identifier.
|
||||
* @return The object associated to the entity.
|
||||
*/
|
||||
const object_type & get(entity_type entity) const noexcept {
|
||||
return instances[underlying_type::get(entity)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the object associated to an entity.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that doesn'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 entity.
|
||||
*
|
||||
* @param entity A valid entity identifier.
|
||||
* @return The object associated to the entity.
|
||||
*/
|
||||
object_type & get(entity_type entity) noexcept {
|
||||
return const_cast<object_type &>(const_cast<const SparseSet *>(this)->get(entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns an entity to a sparse set and constructs its object.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that already belongs to the sparse set
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* sparse set already contains the given entity.
|
||||
*
|
||||
* @tparam Args Types of arguments to use to construct the object.
|
||||
* @param entity A valid entity identifier.
|
||||
* @param args Parameters to use to construct an object for the entity.
|
||||
* @return The object associated to the entity.
|
||||
*/
|
||||
template<typename... Args>
|
||||
object_type & construct(entity_type entity, Args&&... args) {
|
||||
underlying_type::construct(entity);
|
||||
// emplace_back doesn't work well with PODs because of its placement new
|
||||
instances.push_back({ std::forward<Args>(args)... });
|
||||
return instances.back();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes an entity from a sparse set and destroies its object.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that doesn'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 entity.
|
||||
*
|
||||
* @param entity A valid entity identifier.
|
||||
*/
|
||||
void destroy(entity_type entity) override {
|
||||
// swapping isn't required here, we are getting rid of the last element
|
||||
instances[underlying_type::get(entity)] = std::move(instances.back());
|
||||
instances.pop_back();
|
||||
underlying_type::destroy(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Swaps two entities and their objects.
|
||||
*
|
||||
* @note
|
||||
* This function doesn't swap objects between entities. It exchanges entity
|
||||
* and object positions in the sparse set. It's used mainly for sorting.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use 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 entity identifier.
|
||||
* @param rhs A valid entity identifier.
|
||||
*/
|
||||
void swap(entity_type lhs, entity_type rhs) override {
|
||||
std::swap(instances[underlying_type::get(lhs)], instances[underlying_type::get(rhs)]);
|
||||
underlying_type::swap(lhs, rhs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resets a sparse set.
|
||||
*/
|
||||
void reset() override {
|
||||
underlying_type::reset();
|
||||
instances.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<object_type> instances;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
698
src/entt/entity/storage.hpp
Normal file
698
src/entt/entity/storage.hpp
Normal file
@@ -0,0 +1,698 @@
|
||||
#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 input 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<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};
|
||||
}
|
||||
|
||||
inline iterator & operator-=(const difference_type value) ENTT_NOEXCEPT {
|
||||
return (*this += -value);
|
||||
}
|
||||
|
||||
inline 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;
|
||||
}
|
||||
|
||||
inline 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;
|
||||
}
|
||||
|
||||
inline bool operator<=(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this > other);
|
||||
}
|
||||
|
||||
inline 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];
|
||||
}
|
||||
|
||||
inline 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 = typename underlying_type::entity_type;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename underlying_type::size_type;
|
||||
/*! @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) override {
|
||||
underlying_type::reserve(cap);
|
||||
instances.reserve(cap);
|
||||
}
|
||||
|
||||
/*! @brief Requests the removal of unused capacity. */
|
||||
void shrink_to_fit() override {
|
||||
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
|
||||
* 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.
|
||||
*/
|
||||
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 */
|
||||
inline 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
|
||||
* 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.
|
||||
*/
|
||||
const_iterator_type cend() const ENTT_NOEXCEPT {
|
||||
return const_iterator_type{&instances, {}};
|
||||
}
|
||||
|
||||
/*! @copydoc cend */
|
||||
inline 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::get(entt)];
|
||||
}
|
||||
|
||||
/*! @copydoc get */
|
||||
inline 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::get(entt)) : nullptr;
|
||||
}
|
||||
|
||||
/*! @copydoc try_get */
|
||||
inline 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 constructs their
|
||||
* objects.
|
||||
*
|
||||
* 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 A pointer to the array of instances just created and sorted the
|
||||
* same of the entities.
|
||||
*/
|
||||
template<typename It>
|
||||
object_type * batch(It first, It last) {
|
||||
static_assert(std::is_default_constructible_v<object_type>);
|
||||
const auto skip = instances.size();
|
||||
instances.insert(instances.end(), last-first, {});
|
||||
// entity goes after component in case constructor throws
|
||||
underlying_type::batch(first, last);
|
||||
return instances.data() + skip;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) override {
|
||||
std::swap(instances[underlying_type::get(entt)], instances.back());
|
||||
instances.pop_back();
|
||||
underlying_type::destroy(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sort instances according to the given comparison function.
|
||||
*
|
||||
* Sort the elements so that iterating the storage 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 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(Compare compare, Sort algo = Sort{}, Args &&... args) {
|
||||
std::vector<size_type> copy(instances.size());
|
||||
std::iota(copy.begin(), copy.end(), 0);
|
||||
|
||||
if constexpr(std::is_invocable_v<Compare, const object_type &, const object_type &>) {
|
||||
static_assert(!std::is_empty_v<object_type>);
|
||||
|
||||
algo(copy.rbegin(), copy.rend(), [this, compare = std::move(compare)](const auto lhs, const auto rhs) {
|
||||
return compare(std::as_const(instances[lhs]), std::as_const(instances[rhs]));
|
||||
}, std::forward<Args>(args)...);
|
||||
} else {
|
||||
algo(copy.rbegin(), copy.rend(), [compare = std::move(compare), data = underlying_type::data()](const auto lhs, const auto rhs) {
|
||||
return compare(data[lhs], data[rhs]);
|
||||
}, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
for(size_type pos = 0, last = copy.size(); pos < last; ++pos) {
|
||||
auto curr = pos;
|
||||
auto next = copy[curr];
|
||||
|
||||
while(curr != next) {
|
||||
const auto lhs = copy[curr];
|
||||
const auto rhs = copy[next];
|
||||
std::swap(instances[lhs], instances[rhs]);
|
||||
underlying_type::swap(lhs, rhs);
|
||||
copy[curr] = curr;
|
||||
curr = next;
|
||||
next = copy[curr];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sort instances according to the order of the entities in another
|
||||
* sparse set.
|
||||
*
|
||||
* Entities that are part of both the storage are ordered internally
|
||||
* according to the order they have in `other`. All the other entities goes
|
||||
* to the end of the list and there are no guarantess on their order.
|
||||
* Instances are sorted according to the entities to which they belong.<br/>
|
||||
* In other terms, this function can be used to impose the same order on two
|
||||
* sets by using one of them as a master and the other one as a slave.
|
||||
*
|
||||
* Iterating the storage with a couple of iterators returns elements in the
|
||||
* expected order after a call to `respect`. See `begin` and `end` for more
|
||||
* details.
|
||||
*
|
||||
* @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
|
||||
* `respect` has been invoked.
|
||||
*
|
||||
* @param other The sparse sets that imposes the order of the entities.
|
||||
*/
|
||||
void respect(const sparse_set<Entity> &other) ENTT_NOEXCEPT override {
|
||||
const auto to = other.end();
|
||||
auto from = other.begin();
|
||||
|
||||
size_type pos = underlying_type::size() - 1;
|
||||
const auto *local = underlying_type::data();
|
||||
|
||||
while(pos && from != to) {
|
||||
const auto curr = *from;
|
||||
|
||||
if(underlying_type::has(curr)) {
|
||||
if(curr != *(local + pos)) {
|
||||
auto candidate = underlying_type::get(curr);
|
||||
std::swap(instances[pos], instances[candidate]);
|
||||
underlying_type::swap(pos, candidate);
|
||||
}
|
||||
|
||||
--pos;
|
||||
}
|
||||
|
||||
++from;
|
||||
}
|
||||
}
|
||||
|
||||
/*! @brief Resets a storage. */
|
||||
void reset() override {
|
||||
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 underlying_type = sparse_set<Entity>;
|
||||
using traits_type = entt_traits<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};
|
||||
}
|
||||
|
||||
inline iterator & operator-=(const difference_type value) ENTT_NOEXCEPT {
|
||||
return (*this += -value);
|
||||
}
|
||||
|
||||
inline 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;
|
||||
}
|
||||
|
||||
inline 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;
|
||||
}
|
||||
|
||||
inline bool operator<=(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this > other);
|
||||
}
|
||||
|
||||
inline bool operator>=(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this < other);
|
||||
}
|
||||
|
||||
pointer operator->() const ENTT_NOEXCEPT {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline 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 = typename underlying_type::entity_type;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename underlying_type::size_type;
|
||||
/*! @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 */
|
||||
inline 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 */
|
||||
inline 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 {};
|
||||
}
|
||||
};
|
||||
|
||||
/*! @copydoc basic_storage */
|
||||
template<typename Entity, typename Type>
|
||||
struct storage: basic_storage<Entity, Type> {};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_STORAGE_HPP
|
||||
@@ -1,96 +0,0 @@
|
||||
#ifndef ENTT_ENTITY_ENTT_HPP
|
||||
#define ENTT_ENTITY_ENTT_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 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:
|
||||
*
|
||||
* * 24 bits for the entity number (suitable for almost all the games).
|
||||
* * 8 bit for the version (resets in [0-255]).
|
||||
*/
|
||||
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 Mask to use to get the entity number out of an identifier. */
|
||||
static constexpr auto entity_mask = 0xFFFFFF;
|
||||
/*! @brief Mask to use to get the version out of an identifier. */
|
||||
static constexpr auto version_mask = 0xFF;
|
||||
/*! @brief Extent of the entity number within an identifier. */
|
||||
static constexpr auto entity_shift = 24;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Entity traits for a 64 bits entity identifier.
|
||||
*
|
||||
* A 64 bits entity identifier guarantees:
|
||||
*
|
||||
* * 40 bits for the entity number (an indecently large number).
|
||||
* * 24 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 Mask to use to get the entity number out of an identifier. */
|
||||
static constexpr auto entity_mask = 0xFFFFFFFFFF;
|
||||
/*! @brief Mask to use to get the version out of an identifier. */
|
||||
static constexpr auto version_mask = 0xFFFFFF;
|
||||
/*! @brief Extent of the entity number within an identifier. */
|
||||
static constexpr auto entity_shift = 40;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_ENTT_HPP
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,19 +1,30 @@
|
||||
#include "core/algorithm.hpp"
|
||||
#include "core/family.hpp"
|
||||
#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/group.hpp"
|
||||
#include "entity/helper.hpp"
|
||||
#include "entity/prototype.hpp"
|
||||
#include "entity/registry.hpp"
|
||||
#include "entity/runtime_view.hpp"
|
||||
#include "entity/snapshot.hpp"
|
||||
#include "entity/sparse_set.hpp"
|
||||
#include "entity/traits.hpp"
|
||||
#include "entity/storage.hpp"
|
||||
#include "entity/view.hpp"
|
||||
#include "locator/locator.hpp"
|
||||
#include "meta/factory.hpp"
|
||||
#include "meta/meta.hpp"
|
||||
#include "process/process.hpp"
|
||||
#include "process/scheduler.hpp"
|
||||
#include "resource/cache.hpp"
|
||||
#include "resource/handle.hpp"
|
||||
#include "resource/loader.hpp"
|
||||
#include "signal/bus.hpp"
|
||||
#include "signal/delegate.hpp"
|
||||
#include "signal/dispatcher.hpp"
|
||||
#include "signal/emitter.hpp"
|
||||
#include "signal/sigh.hpp"
|
||||
#include "signal/signal.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,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <cassert>
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
namespace entt {
|
||||
@@ -14,7 +14,7 @@ namespace entt {
|
||||
* @brief Service locator, nothing more.
|
||||
*
|
||||
* A service locator can be used to do what it promises: locate services.<br/>
|
||||
* Usually service locators are tighly bound to the services they expose and
|
||||
* Usually service locators are tightly bound to the services they expose and
|
||||
* thus it's hard to define a general purpose class to do that. 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.
|
||||
@@ -22,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() noexcept {
|
||||
inline static bool empty() ENTT_NOEXCEPT {
|
||||
return !static_cast<bool>(service);
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ struct ServiceLocator final {
|
||||
*
|
||||
* @return A reference to the service implementation currently set, if any.
|
||||
*/
|
||||
inline static std::weak_ptr<Service> get() noexcept {
|
||||
inline static std::weak_ptr<Service> get() ENTT_NOEXCEPT {
|
||||
return service;
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ struct ServiceLocator final {
|
||||
*
|
||||
* @return A reference to the service implementation currently set, if any.
|
||||
*/
|
||||
inline static Service & ref() noexcept {
|
||||
inline static Service & ref() ENTT_NOEXCEPT {
|
||||
return *service;
|
||||
}
|
||||
|
||||
@@ -78,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) {
|
||||
inline static void set(Args &&... args) {
|
||||
service = std::make_shared<Impl>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ struct ServiceLocator final {
|
||||
* @param ptr Service to use to replace the current one.
|
||||
*/
|
||||
inline static void set(std::shared_ptr<Service> ptr) {
|
||||
assert(static_cast<bool>(ptr));
|
||||
ENTT_ASSERT(static_cast<bool>(ptr));
|
||||
service = std::move(ptr);
|
||||
}
|
||||
|
||||
@@ -101,14 +101,10 @@ struct ServiceLocator final {
|
||||
}
|
||||
|
||||
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{};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
687
src/entt/meta/factory.hpp
Normal file
687
src/entt/meta/factory.hpp
Normal file
@@ -0,0 +1,687 @@
|
||||
#ifndef ENTT_META_FACTORY_HPP
|
||||
#define ENTT_META_FACTORY_HPP
|
||||
|
||||
|
||||
#include <utility>
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "../core/hashed_string.hpp"
|
||||
#include "meta.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
template<typename>
|
||||
class meta_factory;
|
||||
|
||||
|
||||
template<typename Type, typename... Property>
|
||||
meta_factory<Type> reflect(const char *str, Property &&... property) ENTT_NOEXCEPT;
|
||||
|
||||
|
||||
template<typename Type>
|
||||
bool unregister() ENTT_NOEXCEPT;
|
||||
|
||||
|
||||
/**
|
||||
* @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_object_v<Type> && !(std::is_const_v<Type> || std::is_volatile_v<Type>));
|
||||
|
||||
template<typename Node>
|
||||
inline bool duplicate(const hashed_string &name, const Node *node) ENTT_NOEXCEPT {
|
||||
return node ? node->name == name || duplicate(name, node->next) : false;
|
||||
}
|
||||
|
||||
inline 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::get<0>(prop);
|
||||
},
|
||||
[]() -> meta_any {
|
||||
return std::get<1>(prop);
|
||||
},
|
||||
[]() -> 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;
|
||||
}
|
||||
|
||||
template<typename... Property>
|
||||
meta_factory type(hashed_string name, Property &&... property) ENTT_NOEXCEPT {
|
||||
static internal::meta_type_node node{
|
||||
{},
|
||||
nullptr,
|
||||
nullptr,
|
||||
std::is_void_v<Type>,
|
||||
std::is_integral_v<Type>,
|
||||
std::is_floating_point_v<Type>,
|
||||
std::is_array_v<Type>,
|
||||
std::is_enum_v<Type>,
|
||||
std::is_union_v<Type>,
|
||||
std::is_class_v<Type>,
|
||||
std::is_pointer_v<Type>,
|
||||
std::is_function_v<Type>,
|
||||
std::is_member_object_pointer_v<Type>,
|
||||
std::is_member_function_pointer_v<Type>,
|
||||
std::extent_v<Type>,
|
||||
[]() -> meta_type {
|
||||
return internal::meta_info<std::remove_pointer_t<Type>>::resolve();
|
||||
},
|
||||
&internal::destroy<Type>,
|
||||
[]() -> meta_type {
|
||||
return &node;
|
||||
}
|
||||
};
|
||||
|
||||
node.name = name;
|
||||
node.next = internal::meta_info<>::type;
|
||||
node.prop = properties<Type>(std::forward<Property>(property)...);
|
||||
ENTT_ASSERT(!duplicate(name, node.next));
|
||||
ENTT_ASSERT(!internal::meta_info<Type>::type);
|
||||
internal::meta_info<Type>::type = &node;
|
||||
internal::meta_info<>::type = &node;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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->name = {};
|
||||
internal::meta_info<Type>::type->next = nullptr;
|
||||
internal::meta_info<Type>::type = nullptr;
|
||||
}
|
||||
|
||||
return registered;
|
||||
}
|
||||
|
||||
meta_factory() ENTT_NOEXCEPT = default;
|
||||
|
||||
public:
|
||||
template<typename Other, typename... Property>
|
||||
friend meta_factory<Other> reflect(const char *str, Property &&... property) ENTT_NOEXCEPT;
|
||||
|
||||
template<typename Other>
|
||||
friend bool unregister() ENTT_NOEXCEPT;
|
||||
|
||||
/**
|
||||
* @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) -> void * {
|
||||
return static_cast<Base *>(static_cast<Type *>(instance));
|
||||
},
|
||||
[]() -> 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,
|
||||
[](void *instance) -> meta_any {
|
||||
return static_cast<To>(*static_cast<Type *>(instance));
|
||||
},
|
||||
[]() -> 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 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 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... Property>
|
||||
meta_factory ctor(Property &&... property) ENTT_NOEXCEPT {
|
||||
using helper_type = internal::meta_function_helper<std::integral_constant<decltype(Func), 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>(nullptr, any, std::make_index_sequence<helper_type::size>{});
|
||||
},
|
||||
[]() -> 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<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, Args...>(any, std::make_index_sequence<helper_type::size>{});
|
||||
},
|
||||
[]() -> 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
|
||||
*
|
||||
* From a client's point of view, nothing changes if the destructor of a
|
||||
* meta type is the default one or a custom one.
|
||||
*
|
||||
* @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()
|
||||
? ((*Func)(static_cast<Type *>(handle.data())), true)
|
||||
: false;
|
||||
},
|
||||
[]() -> 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 Property Types of properties to assign to the meta data.
|
||||
* @param str The name 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 Data, typename... Property>
|
||||
meta_factory data(const char *str, 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 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; },
|
||||
[]() -> 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>,
|
||||
[]() -> 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<decltype(Data)>);
|
||||
using data_type = std::remove_pointer_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>,
|
||||
[]() -> meta_data {
|
||||
return &node;
|
||||
}
|
||||
};
|
||||
|
||||
node.prop = properties<std::integral_constant<decltype(Data), Data>>(std::forward<Property>(property)...);
|
||||
curr = &node;
|
||||
}
|
||||
|
||||
curr->name = hashed_string{str};
|
||||
curr->next = type->data;
|
||||
ENTT_ASSERT(!duplicate(hashed_string{str}, 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 pointer 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 Property Types of properties to assign to the meta data.
|
||||
* @param str The name 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 Setter, auto Getter, typename... Property>
|
||||
meta_factory data(const char *str, 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>,
|
||||
[]() -> meta_data {
|
||||
return &node;
|
||||
}
|
||||
};
|
||||
|
||||
node.name = hashed_string{str};
|
||||
node.next = type->data;
|
||||
node.prop = properties<owner_type>(std::forward<Property>(property)...);
|
||||
ENTT_ASSERT(!duplicate(hashed_string{str}, 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 Func The actual function to attach to the meta type.
|
||||
* @tparam Property Types of properties to assign to the meta function.
|
||||
* @param str The name to assign to the meta function.
|
||||
* @param property Properties to assign to the meta function.
|
||||
* @return A meta factory for the parent type.
|
||||
*/
|
||||
template<auto Func, typename... Property>
|
||||
meta_factory func(const char *str, Property &&... property) ENTT_NOEXCEPT {
|
||||
using owner_type = std::integral_constant<decltype(Func), Func>;
|
||||
using func_type = internal::meta_function_helper<std::integral_constant<decltype(Func), Func>>;
|
||||
auto * const type = internal::meta_info<Type>::resolve();
|
||||
|
||||
static internal::meta_func_node node{
|
||||
&internal::meta_info<Type>::template func<Func>,
|
||||
{},
|
||||
type,
|
||||
nullptr,
|
||||
nullptr,
|
||||
func_type::size,
|
||||
func_type::is_const,
|
||||
func_type::is_static,
|
||||
&internal::meta_info<typename func_type::return_type>::resolve,
|
||||
&func_type::arg,
|
||||
[](meta_handle handle, meta_any *any) {
|
||||
return internal::invoke<Type, Func>(handle, any, std::make_index_sequence<func_type::size>{});
|
||||
},
|
||||
[]() -> meta_func {
|
||||
return &node;
|
||||
}
|
||||
};
|
||||
|
||||
node.name = hashed_string{str};
|
||||
node.next = type->func;
|
||||
node.prop = properties<owner_type>(std::forward<Property>(property)...);
|
||||
ENTT_ASSERT(!duplicate(hashed_string{str}, node.next));
|
||||
ENTT_ASSERT((!internal::meta_info<Type>::template func<Func>));
|
||||
internal::meta_info<Type>::template func<Func> = &node;
|
||||
type->func = &node;
|
||||
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Basic 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 str The name to assign to the reflected type.
|
||||
* @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 char *str, Property &&... property) ENTT_NOEXCEPT {
|
||||
return meta_factory<Type>{}.type(hashed_string{str}, std::forward<Property>(property)...);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Basic 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 Basic 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 name.
|
||||
* @param str The name to use to search for a meta type.
|
||||
* @return The meta type associated with the given name, if any.
|
||||
*/
|
||||
inline meta_type resolve(const char *str) ENTT_NOEXCEPT {
|
||||
const auto *curr = internal::find_if([name = hashed_string{str}](auto *node) {
|
||||
return node->name == name;
|
||||
}, 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>
|
||||
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
|
||||
2275
src/entt/meta/meta.hpp
Normal file
2275
src/entt/meta/meta.hpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,36 +2,14 @@
|
||||
#define ENTT_PROCESS_PROCESS_HPP
|
||||
|
||||
|
||||
#include <type_traits>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
struct BaseProcess {
|
||||
enum class State: unsigned int {
|
||||
UNINITIALIZED = 0,
|
||||
RUNNING,
|
||||
PAUSED,
|
||||
SUCCEEDED,
|
||||
FAILED,
|
||||
ABORTED,
|
||||
FINISHED
|
||||
};
|
||||
|
||||
template<State state>
|
||||
using tag = std::integral_constant<State, state>;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Base class for processes.
|
||||
*
|
||||
@@ -41,33 +19,43 @@ struct BaseProcess {
|
||||
* required:
|
||||
*
|
||||
* * @code{.cpp}
|
||||
* void update(Delta);
|
||||
* void update(Delta, void *);
|
||||
* @endcode
|
||||
*
|
||||
* 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.
|
||||
* 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.
|
||||
*
|
||||
* * @code{.cpp}
|
||||
* void init();
|
||||
* @endcode
|
||||
* It's invoked at the first tick, immediately before 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();
|
||||
* @endcode
|
||||
*
|
||||
* It's invoked in case of success, immediately after an update and during the
|
||||
* same tick.
|
||||
*
|
||||
* * @code{.cpp}
|
||||
* void failed();
|
||||
* @endcode
|
||||
*
|
||||
* It's invoked in case of errors, immediately after an update and during the
|
||||
* same tick.
|
||||
*
|
||||
* * @code{.cpp}
|
||||
* void aborted();
|
||||
* @endcode
|
||||
*
|
||||
* 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.
|
||||
@@ -76,45 +64,58 @@ struct BaseProcess {
|
||||
* `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: private BaseProcess {
|
||||
class process {
|
||||
enum class state: unsigned int {
|
||||
UNINITIALIZED = 0,
|
||||
RUNNING,
|
||||
PAUSED,
|
||||
SUCCEEDED,
|
||||
FAILED,
|
||||
ABORTED,
|
||||
FINISHED
|
||||
};
|
||||
|
||||
template<state value>
|
||||
using state_value_t = std::integral_constant<state, value>;
|
||||
|
||||
template<typename Target = Derived>
|
||||
auto tick(int, tag<State::UNINITIALIZED>)
|
||||
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)
|
||||
-> decltype(std::declval<Target>().update(delta)) {
|
||||
static_cast<Target *>(this)->update(delta);
|
||||
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&&...) {}
|
||||
template<state value, typename... Args>
|
||||
void tick(char, state_value_t<value>, Args &&...) const ENTT_NOEXCEPT {}
|
||||
|
||||
protected:
|
||||
/**
|
||||
@@ -123,9 +124,9 @@ protected:
|
||||
* The function is idempotent and it does nothing if the process isn't
|
||||
* alive.
|
||||
*/
|
||||
void succeed() noexcept {
|
||||
void succeed() ENTT_NOEXCEPT {
|
||||
if(alive()) {
|
||||
current = State::SUCCEEDED;
|
||||
current = state::SUCCEEDED;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,9 +136,9 @@ protected:
|
||||
* The function is idempotent and it does nothing if the process isn't
|
||||
* alive.
|
||||
*/
|
||||
void fail() noexcept {
|
||||
void fail() ENTT_NOEXCEPT {
|
||||
if(alive()) {
|
||||
current = State::FAILED;
|
||||
current = state::FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,9 +148,9 @@ protected:
|
||||
* The function is idempotent and it does nothing if the process isn't
|
||||
* running.
|
||||
*/
|
||||
void pause() noexcept {
|
||||
if(current == State::RUNNING) {
|
||||
current = State::PAUSED;
|
||||
void pause() ENTT_NOEXCEPT {
|
||||
if(current == state::RUNNING) {
|
||||
current = state::PAUSED;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,9 +160,9 @@ protected:
|
||||
* The function is idempotent and it does nothing if the process isn't
|
||||
* paused.
|
||||
*/
|
||||
void unpause() noexcept {
|
||||
if(current == State::PAUSED) {
|
||||
current = State::RUNNING;
|
||||
void unpause() ENTT_NOEXCEPT {
|
||||
if(current == state::PAUSED) {
|
||||
current = state::RUNNING;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,8 +171,8 @@ public:
|
||||
using delta_type = Delta;
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
~Process() noexcept {
|
||||
static_assert(std::is_base_of<Process, Derived>::value, "!");
|
||||
virtual ~process() ENTT_NOEXCEPT {
|
||||
static_assert(std::is_base_of_v<process, Derived>);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,9 +183,9 @@ public:
|
||||
*
|
||||
* @param immediately Requests an immediate operation.
|
||||
*/
|
||||
void abort(bool immediately = false) noexcept {
|
||||
void abort(const bool immediately = false) ENTT_NOEXCEPT {
|
||||
if(alive()) {
|
||||
current = State::ABORTED;
|
||||
current = state::ABORTED;
|
||||
|
||||
if(immediately) {
|
||||
tick(0);
|
||||
@@ -196,46 +197,48 @@ public:
|
||||
* @brief Returns true if a process is either running or paused.
|
||||
* @return True if the process is still alive, false otherwise.
|
||||
*/
|
||||
bool alive() const noexcept {
|
||||
return current == State::RUNNING || current == State::PAUSED;
|
||||
bool alive() const ENTT_NOEXCEPT {
|
||||
return current == state::RUNNING || current == state::PAUSED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if a process is already terminated.
|
||||
* @return True if the process is terminated, false otherwise.
|
||||
*/
|
||||
bool dead() const noexcept {
|
||||
return current == State::FINISHED;
|
||||
bool dead() const ENTT_NOEXCEPT {
|
||||
return current == state::FINISHED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if a process is currently paused.
|
||||
* @return True if the process is paused, false otherwise.
|
||||
*/
|
||||
bool paused() const noexcept {
|
||||
return current == State::PAUSED;
|
||||
bool paused() const ENTT_NOEXCEPT {
|
||||
return current == state::PAUSED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if a process terminated with errors.
|
||||
* @return True if the process terminated with errors, false otherwise.
|
||||
*/
|
||||
bool rejected() const noexcept {
|
||||
bool rejected() const ENTT_NOEXCEPT {
|
||||
return stopped;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates a process and its internal state if required.
|
||||
* @param delta Elapsed time.
|
||||
* @param data Optional data.
|
||||
*/
|
||||
void tick(Delta delta) {
|
||||
void tick(const Delta delta, void *data = nullptr) {
|
||||
switch (current) {
|
||||
case State::UNINITIALIZED:
|
||||
tick(0, tag<State::UNINITIALIZED>{});
|
||||
current = State::RUNNING;
|
||||
// no break on purpose, tasks are executed immediately
|
||||
case State::RUNNING:
|
||||
tick(0, tag<State::RUNNING>{}, delta);
|
||||
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;
|
||||
@@ -243,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:
|
||||
@@ -264,7 +267,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
State current{State::UNINITIALIZED};
|
||||
state current{state::UNINITIALIZED};
|
||||
bool stopped{false};
|
||||
};
|
||||
|
||||
@@ -281,12 +284,13 @@ private:
|
||||
* 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.
|
||||
*
|
||||
@@ -301,30 +305,31 @@ 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)...}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Updates a process and its internal state if required.
|
||||
* @param delta Elapsed time.
|
||||
* @param data Optional data.
|
||||
*/
|
||||
void update(Delta delta) {
|
||||
Func::operator()(delta, [this](){ this->succeed(); }, [this](){ this->fail(); });
|
||||
void update(const Delta delta, void *data) {
|
||||
Func::operator()(delta, data, [this]() { this->succeed(); }, [this]() { this->fail(); });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "process.hpp"
|
||||
|
||||
|
||||
@@ -28,69 +28,69 @@ namespace entt {
|
||||
* Example of use (pseudocode):
|
||||
*
|
||||
* @code{.cpp}
|
||||
* scheduler.attach([](auto delta, auto succeed, auto fail) {
|
||||
* 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 {
|
||||
template<typename T>
|
||||
struct tag { using type = T; };
|
||||
|
||||
struct ProcessHandler final {
|
||||
class scheduler {
|
||||
struct process_handler {
|
||||
using instance_type = std::unique_ptr<void, void(*)(void *)>;
|
||||
using update_type = bool(*)(ProcessHandler &, Delta);
|
||||
using abort_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_type update;
|
||||
abort_type abort;
|
||||
update_fn_type *update;
|
||||
abort_fn_type *abort;
|
||||
next_type next;
|
||||
};
|
||||
|
||||
template<typename Lambda>
|
||||
struct Then final: Lambda {
|
||||
Then(Lambda &&lambda, ProcessHandler *handler)
|
||||
: Lambda{std::forward<Lambda>(lambda)}, 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 = Lambda::operator()(handler, tag<Proc>{}, 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, Delta delta) {
|
||||
static bool update(process_handler &handler, const Delta delta, void *data) {
|
||||
auto *process = static_cast<Proc *>(handler.instance.get());
|
||||
process->tick(delta);
|
||||
process->tick(delta, data);
|
||||
|
||||
auto dead = process->dead();
|
||||
|
||||
if(dead) {
|
||||
if(handler.next && !process->rejected()) {
|
||||
handler = std::move(*handler.next);
|
||||
dead = handler.update(handler, delta);
|
||||
// forces the process to exit the uninitialized state
|
||||
dead = handler.update(handler, {}, nullptr);
|
||||
} else {
|
||||
handler.instance.reset();
|
||||
}
|
||||
@@ -100,7 +100,7 @@ class Scheduler final {
|
||||
}
|
||||
|
||||
template<typename Proc>
|
||||
static void abort(ProcessHandler &handler, bool immediately) {
|
||||
static void abort(process_handler &handler, const bool immediately) {
|
||||
static_cast<Proc *>(handler.instance.get())->abort(immediately);
|
||||
}
|
||||
|
||||
@@ -109,44 +109,24 @@ class Scheduler final {
|
||||
delete static_cast<Proc *>(proc);
|
||||
}
|
||||
|
||||
auto then(ProcessHandler *handler) {
|
||||
auto lambda = [this](ProcessHandler *handler, auto next, auto... args) {
|
||||
using Proc = typename decltype(next)::type;
|
||||
|
||||
if(handler) {
|
||||
auto proc = typename ProcessHandler::instance_type{ new Proc{std::forward<decltype(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;
|
||||
};
|
||||
|
||||
return Then<decltype(lambda)>{std::move(lambda), 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() 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 assignament operator. @return This scheduler. */
|
||||
Scheduler & operator=(Scheduler &&) = default;
|
||||
/*! @brief Default move assignment operator. @return This scheduler. */
|
||||
scheduler & operator=(scheduler &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Number of processes currently scheduled.
|
||||
* @return Number of processes currently scheduled.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
size_type size() const ENTT_NOEXCEPT {
|
||||
return handlers.size();
|
||||
}
|
||||
|
||||
@@ -154,7 +134,7 @@ public:
|
||||
* @brief Returns true if at least a process is currently scheduled.
|
||||
* @return True if there are scheduled processes, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
return handlers.empty();
|
||||
}
|
||||
|
||||
@@ -179,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, auto succeed, auto fail) {
|
||||
* .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.
|
||||
@@ -194,14 +174,13 @@ public:
|
||||
* @return An opaque object to use to concatenate processes.
|
||||
*/
|
||||
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());
|
||||
auto attach(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>};
|
||||
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))};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -213,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.
|
||||
*
|
||||
@@ -237,18 +217,18 @@ public:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* // schedules a task in the form of a lambda function
|
||||
* scheduler.attach([](auto delta, auto succeed, auto fail) {
|
||||
* scheduler.attach([](auto delta, void *, auto succeed, auto fail) {
|
||||
* // code
|
||||
* })
|
||||
* // appends a child in the form of another lambda function
|
||||
* .then([](auto delta, auto succeed, auto fail) {
|
||||
* .then([](auto delta, void *, auto succeed, auto fail) {
|
||||
* // 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.
|
||||
@@ -256,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));
|
||||
}
|
||||
|
||||
@@ -269,18 +249,19 @@ public:
|
||||
* with its child.
|
||||
*
|
||||
* @param delta Elapsed time.
|
||||
* @param data Optional data.
|
||||
*/
|
||||
void update(Delta delta) {
|
||||
void update(const Delta delta, void *data = nullptr) {
|
||||
bool clean = false;
|
||||
|
||||
for(auto i = handlers.size(); i > 0; --i) {
|
||||
auto &handler = handlers[i-1];
|
||||
const bool dead = handler.update(handler, delta);
|
||||
for(auto pos = handlers.size(); pos; --pos) {
|
||||
auto &handler = handlers[pos-1];
|
||||
const bool dead = handler.update(handler, delta, data);
|
||||
clean = clean || dead;
|
||||
}
|
||||
|
||||
if(clean) {
|
||||
handlers.erase(std::remove_if(handlers.begin(), handlers.end(), [delta](auto &handler) {
|
||||
handlers.erase(std::remove_if(handlers.begin(), handlers.end(), [](auto &handler) {
|
||||
return !handler.instance;
|
||||
}), handlers.end());
|
||||
}
|
||||
@@ -296,7 +277,7 @@ public:
|
||||
*
|
||||
* @param immediately Requests an immediate operation.
|
||||
*/
|
||||
void abort(bool immediately = false) {
|
||||
void abort(const bool immediately = false) {
|
||||
decltype(handlers) exec;
|
||||
exec.swap(handlers);
|
||||
|
||||
@@ -309,7 +290,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<ProcessHandler> handlers{};
|
||||
std::vector<process_handler> handlers{};
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -6,9 +6,11 @@
|
||||
#include <utility>
|
||||
#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 {
|
||||
@@ -25,33 +27,29 @@ 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<hashed_string::hash_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 = typename hashed_string::hash_type;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
ResourceCache() = default;
|
||||
resource_cache() = default;
|
||||
|
||||
/*! @brief Copying a cache isn't allowed. */
|
||||
ResourceCache(const ResourceCache &) noexcept = delete;
|
||||
/*! @brief Default move constructor. */
|
||||
ResourceCache(ResourceCache &&) noexcept = default;
|
||||
resource_cache(resource_cache &&) = default;
|
||||
|
||||
/*! @brief Copying a cache isn't allowed. @return This cache. */
|
||||
ResourceCache & operator=(const ResourceCache &) noexcept = delete;
|
||||
/*! @brief Default move assignment operator. @return This cache. */
|
||||
ResourceCache & operator=(ResourceCache &&) noexcept = default;
|
||||
resource_cache & operator=(resource_cache &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Number of resources managed by a cache.
|
||||
* @return Number of resources currently stored.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
size_type size() const ENTT_NOEXCEPT {
|
||||
return resources.size();
|
||||
}
|
||||
|
||||
@@ -59,7 +57,7 @@ public:
|
||||
* @brief Returns true if a cache contains no resources, false otherwise.
|
||||
* @return True if the cache contains no resources, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
return resources.empty();
|
||||
}
|
||||
|
||||
@@ -69,12 +67,12 @@ public:
|
||||
* Handles are not invalidated and the memory used by a resource isn't
|
||||
* freed as long as at least a handle keeps the resource itself alive.
|
||||
*/
|
||||
void clear() noexcept {
|
||||
void clear() ENTT_NOEXCEPT {
|
||||
resources.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Loads the resource that corresponds to the given identifier.
|
||||
* @brief Loads the resource that corresponds to a given identifier.
|
||||
*
|
||||
* In case an identifier isn't already present in the cache, it loads its
|
||||
* resource and stores it aside for future uses. Arguments are forwarded
|
||||
@@ -85,24 +83,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(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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,56 +123,75 @@ 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>
|
||||
void reload(resource_type id, Args&&... args) {
|
||||
return (discard(id), load(id, std::forward<Args>(args)...));
|
||||
resource_handle<Resource> reload(const resource_type id, Args &&... args) {
|
||||
return (discard(id), load<Loader>(id, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a handle for the given resource identifier.
|
||||
* @brief Creates a temporary handle for a resource.
|
||||
*
|
||||
* Arguments are forwarded directly to the loader in order to construct
|
||||
* properly the requested resource. The handle isn't stored aside and the
|
||||
* cache isn't in charge of the lifetime of the resource itself.
|
||||
*
|
||||
* @tparam Loader Type of loader to use to load the resource.
|
||||
* @tparam Args Types of arguments to use to load the resource.
|
||||
* @param args Arguments to use to load the resource.
|
||||
* @return A handle for the given resource.
|
||||
*/
|
||||
template<typename Loader, typename... Args>
|
||||
resource_handle<Resource> temp(Args &&... args) const {
|
||||
return { Loader{}.get(std::forward<Args>(args)...) };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a handle for a given resource identifier.
|
||||
*
|
||||
* A resource handle can be in a either valid or invalid state. In other
|
||||
* terms, a resource handle is properly initialized with a resource if the
|
||||
* 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(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 };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a cache contains the given identifier.
|
||||
* @brief Checks if a cache contains a given identifier.
|
||||
* @param id Unique resource identifier.
|
||||
* @return True if the cache contains the resource, false otherwise.
|
||||
*/
|
||||
bool contains(resource_type id) const noexcept {
|
||||
return !(resources.find(id) == resources.cend());
|
||||
bool contains(const resource_type id) const ENTT_NOEXCEPT {
|
||||
return (resources.find(id) != resources.cend());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Discards the resource that corresponds to the given identifier.
|
||||
* @brief Discards the resource that corresponds to a given identifier.
|
||||
*
|
||||
* Handles are not invalidated and the memory used by the resource isn't
|
||||
* freed as long as at least a handle keeps the resource itself alive.
|
||||
*
|
||||
* @param id Unique resource identifier.
|
||||
*/
|
||||
void discard(resource_type id) noexcept {
|
||||
auto it = resources.find(id);
|
||||
|
||||
if(it != resources.end()) {
|
||||
void discard(const resource_type id) ENTT_NOEXCEPT {
|
||||
if(auto it = resources.find(id); it != resources.end()) {
|
||||
resources.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
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,16 +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.
|
||||
*
|
||||
@@ -27,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) noexcept
|
||||
resource_handle(std::shared_ptr<Resource> res) ENTT_NOEXCEPT
|
||||
: resource{std::move(res)}
|
||||
{}
|
||||
|
||||
public:
|
||||
/*! @brief Default copy constructor. */
|
||||
ResourceHandle(const ResourceHandle &) noexcept = default;
|
||||
/*! @brief Default move constructor. */
|
||||
ResourceHandle(ResourceHandle &&) noexcept = default;
|
||||
|
||||
/*! @brief Default copy assignment operator. @return This handle. */
|
||||
ResourceHandle & operator=(const ResourceHandle &) noexcept = default;
|
||||
/*! @brief Default move assignment operator. @return This handle. */
|
||||
ResourceHandle & operator=(ResourceHandle &&) noexcept = default;
|
||||
/*! @brief Default constructor. */
|
||||
resource_handle() ENTT_NOEXCEPT = default;
|
||||
|
||||
/**
|
||||
* @brief Gets a reference to the managed resource.
|
||||
@@ -56,8 +46,8 @@ public:
|
||||
*
|
||||
* @return A reference to the managed resource.
|
||||
*/
|
||||
const Resource & get() const noexcept {
|
||||
assert(static_cast<bool>(resource));
|
||||
const Resource & get() const ENTT_NOEXCEPT {
|
||||
ENTT_ASSERT(static_cast<bool>(resource));
|
||||
return *resource;
|
||||
}
|
||||
|
||||
@@ -69,7 +59,7 @@ public:
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* handle is empty.
|
||||
*/
|
||||
inline operator const Resource & () const noexcept { return get(); }
|
||||
inline operator const Resource &() const ENTT_NOEXCEPT { return get(); }
|
||||
|
||||
/**
|
||||
* @brief Dereferences a handle to obtain the managed resource.
|
||||
@@ -81,10 +71,10 @@ public:
|
||||
*
|
||||
* @return A reference to the managed resource.
|
||||
*/
|
||||
inline const Resource & operator *() const noexcept { return get(); }
|
||||
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 from a handle.
|
||||
*
|
||||
* @warning
|
||||
* The behavior is undefined if the handle doesn't contain a resource.<br/>
|
||||
@@ -94,13 +84,14 @@ public:
|
||||
* @return A pointer to the managed resource or `nullptr` if the handle
|
||||
* contains no resource at all.
|
||||
*/
|
||||
inline const Resource * operator ->() const noexcept {
|
||||
assert(static_cast<bool>(resource));
|
||||
inline const Resource * operator->() const ENTT_NOEXCEPT {
|
||||
ENTT_ASSERT(static_cast<bool>(resource));
|
||||
return resource.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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,31 +3,28 @@
|
||||
|
||||
|
||||
#include <memory>
|
||||
#include "fwd.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
template<typename Resource>
|
||||
class ResourceCache;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Base class for resource loaders.
|
||||
*
|
||||
* Resource loaders must inherit from this class and stay true to the CRTP
|
||||
* idiom. Moreover, a resource loader must expose a public, const member
|
||||
* function named `load` that accepts a variable number of arguments and return
|
||||
* function named `load` that accepts a variable number of arguments and returns
|
||||
* a shared pointer to the resource just created.<br/>
|
||||
* 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,12 +42,18 @@ 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 {
|
||||
std::shared_ptr<Resource> get(Args &&... args) const {
|
||||
return static_cast<const Loader *>(this)->load(std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,305 +0,0 @@
|
||||
#ifndef ENTT_SIGNAL_BUS_HPP
|
||||
#define ENTT_SIGNAL_BUS_HPP
|
||||
|
||||
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include "signal.hpp"
|
||||
#include "sigh.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Minimal event bus.
|
||||
*
|
||||
* Primary template isn't defined on purpose. The main reason for which it
|
||||
* exists is to work around the doxygen's parsing capabilities. In fact, there
|
||||
* is no need to declare it actually.
|
||||
*/
|
||||
template<template<typename...> class, typename...>
|
||||
class Bus;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Event bus specialization for multiple types.
|
||||
*
|
||||
* The event bus is designed to allow an easy registration of specific member
|
||||
* functions to a bunch of signal handlers (either manager or unmanaged).
|
||||
* Classes must publicly expose the required member functions to allow the bus
|
||||
* to detect them for the purpose of registering and unregistering
|
||||
* instances.<br/>
|
||||
* In particular, for each event type `E`, a matching member function has the
|
||||
* following signature: `void receive(const E &)`. Events will be properly
|
||||
* redirected to all the listeners by calling the right member functions, if
|
||||
* any.
|
||||
*
|
||||
* @tparam Sig Type of signal handler to use.
|
||||
* @tparam Event The list of events managed by the bus.
|
||||
*/
|
||||
template<template<typename...> class Sig, typename Event, typename... Other>
|
||||
class Bus<Sig, Event, Other...>
|
||||
: private Bus<Sig, Event>, private Bus<Sig, Other>...
|
||||
{
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
|
||||
/**
|
||||
* @brief Unregisters all the member functions of an instance.
|
||||
*
|
||||
* A bus is used to convey a certain set of events. This method detects
|
||||
* and unregisters from the bus all the matching member functions of an
|
||||
* instance.<br/>
|
||||
* For each event type `E`, a matching member function has the following
|
||||
* signature: `void receive(const E &)`.
|
||||
*
|
||||
* @tparam Instance Type of instance to unregister.
|
||||
* @param instance A valid instance of the right type.
|
||||
*/
|
||||
template<typename Instance>
|
||||
void unreg(Instance instance) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = {
|
||||
(Bus<Sig, Event>::unreg(instance), 0),
|
||||
(Bus<Sig, Other>::unreg(instance), 0)...
|
||||
};
|
||||
return void(accumulator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Registers all the member functions of an instance.
|
||||
*
|
||||
* A bus is used to convey a certain set of events. This method detects
|
||||
* and registers to the bus all the matching member functions of an
|
||||
* instance.<br/>
|
||||
* For each event type `E`, a matching member function has the following
|
||||
* signature: `void receive(const E &)`.
|
||||
*
|
||||
* @tparam Instance Type of instance to register.
|
||||
* @param instance A valid instance of the right type.
|
||||
*/
|
||||
template<typename Instance>
|
||||
void reg(Instance instance) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = {
|
||||
(Bus<Sig, Event>::reg(instance), 0),
|
||||
(Bus<Sig, Other>::reg(instance), 0)...
|
||||
};
|
||||
return void(accumulator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Number of listeners connected to the bus.
|
||||
* @return Number of listeners currently connected.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
using accumulator_type = std::size_t[];
|
||||
std::size_t sz = Bus<Sig, Event>::size();
|
||||
accumulator_type accumulator = { sz, (sz += Bus<Sig, Other>::size())... };
|
||||
return void(accumulator), sz;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns false if at least a listener is connected to the bus.
|
||||
* @return True if the bus has no listeners connected, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
using accumulator_type = bool[];
|
||||
bool ret = Bus<Sig, Event>::empty();
|
||||
accumulator_type accumulator = { ret, (ret = ret && Bus<Sig, Other>::empty())... };
|
||||
return void(accumulator), ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function to the bus.
|
||||
* @tparam Type Type of event to which to connect the function.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<typename Type, void(*Function)(const Type &)>
|
||||
void connect() {
|
||||
Bus<Sig, Type>::template connect<Function>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a free function from the bus.
|
||||
* @tparam Type Type of event from which to disconnect the function.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<typename Type, void(*Function)(const Type &)>
|
||||
void disconnect() {
|
||||
Bus<Sig, Type>::template disconnect<Function>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Publishes an event.
|
||||
*
|
||||
* All the listeners are notified. Order isn't guaranteed.
|
||||
*
|
||||
* @tparam Type Type of event to publish.
|
||||
* @tparam Args Types of arguments to use to construct the event.
|
||||
* @param args Arguments to use to construct the event.
|
||||
*/
|
||||
template<typename Type, typename... Args>
|
||||
void publish(Args&&... args) {
|
||||
Bus<Sig, Type>::publish(std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Event bus specialization for a single type.
|
||||
*
|
||||
* The event bus is designed to allow an easy registration of a specific member
|
||||
* function to a signal handler (either manager or unmanaged).
|
||||
* Classes must publicly expose the required member function to allow the bus to
|
||||
* detect it for the purpose of registering and unregistering instances.<br/>
|
||||
* In particular, a matching member function has the following signature:
|
||||
* `void receive(const Event &)`. Events of the given type will be properly
|
||||
* redirected to all the listeners by calling the right member function, if any.
|
||||
*
|
||||
* @tparam Sig Type of signal handler to use.
|
||||
* @tparam Event Type of event managed by the bus.
|
||||
*/
|
||||
template<template<typename...> class Sig, typename Event>
|
||||
class Bus<Sig, Event> {
|
||||
using signal_type = Sig<void(const Event &)>;
|
||||
|
||||
template<typename Class>
|
||||
using instance_type = typename signal_type::template instance_type<Class>;
|
||||
|
||||
template<typename Class>
|
||||
auto disconnect(int, instance_type<Class> instance)
|
||||
-> decltype(std::declval<Class>().receive(std::declval<Event>()), void()) {
|
||||
signal.template disconnect<Class, &Class::receive>(std::move(instance));
|
||||
}
|
||||
|
||||
template<typename Class>
|
||||
auto connect(int, instance_type<Class> instance)
|
||||
-> decltype(std::declval<Class>().receive(std::declval<Event>()), void()) {
|
||||
signal.template connect<Class, &Class::receive>(std::move(instance));
|
||||
}
|
||||
|
||||
template<typename Class> void disconnect(char, instance_type<Class>) {}
|
||||
template<typename Class> void connect(char, instance_type<Class>) {}
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename signal_type::size_type;
|
||||
|
||||
/**
|
||||
* @brief Unregisters member functions of instances.
|
||||
*
|
||||
* This method tries to detect and unregister from the bus matching member
|
||||
* functions of instances.<br/>
|
||||
* A matching member function has the following signature:
|
||||
* `void receive(const Event &)`.
|
||||
*
|
||||
* @tparam Class Type of instance to unregister.
|
||||
* @param instance A valid instance of the right type.
|
||||
*/
|
||||
template<typename Class>
|
||||
void unreg(instance_type<Class> instance) {
|
||||
disconnect(0, std::move(instance));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Tries to register an instance.
|
||||
*
|
||||
* This method tries to detect and register to the bus matching member
|
||||
* functions of instances.<br/>
|
||||
* A matching member function has the following signature:
|
||||
* `void receive(const Event &)`.
|
||||
*
|
||||
* @tparam Class Type of instance to register.
|
||||
* @param instance A valid instance of the right type.
|
||||
*/
|
||||
template<typename Class>
|
||||
void reg(instance_type<Class> instance) {
|
||||
connect(0, std::move(instance));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Number of listeners connected to the bus.
|
||||
* @return Number of listeners currently connected.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
return signal.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns false if at least a listener is connected to the bus.
|
||||
* @return True if the bus has no listeners connected, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
return signal.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function to the bus.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<void(*Function)(const Event &)>
|
||||
void connect() {
|
||||
signal.template connect<Function>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a free function from the bus.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<void(*Function)(const Event &)>
|
||||
void disconnect() {
|
||||
signal.template disconnect<Function>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Publishes an event.
|
||||
*
|
||||
* All the listeners are notified. Order isn't guaranteed.
|
||||
*
|
||||
* @tparam Args Types of arguments to use to construct the event.
|
||||
* @param args Arguments to use to construct the event.
|
||||
*/
|
||||
template<typename... Args>
|
||||
void publish(Args&&... args) {
|
||||
signal.publish({ std::forward<Args>(args)... });
|
||||
}
|
||||
|
||||
private:
|
||||
signal_type signal;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Managed event bus.
|
||||
*
|
||||
* A managed event bus uses the Signal class template as an underlying type. The
|
||||
* type of the instances is the one required by the signal handler:
|
||||
* `std::shared_ptr<Class>` (a shared pointer).
|
||||
*
|
||||
* @tparam Event The list of events managed by the bus.
|
||||
*/
|
||||
template<typename... Event>
|
||||
using ManagedBus = Bus<Signal, Event...>;
|
||||
|
||||
/**
|
||||
* @brief Unmanaged event bus.
|
||||
*
|
||||
* An unmanaged event bus uses the SigH class template as an underlying type.
|
||||
* The type of the instances is the one required by the signal handler:
|
||||
* `Class *` (a naked pointer).<br/>
|
||||
* When it comes to work with this kind of bus, users must guarantee that the
|
||||
* lifetimes of the instances overcome the one of the bus itself.
|
||||
*
|
||||
* @tparam Event The list of events managed by the bus.
|
||||
*/
|
||||
template<typename... Event>
|
||||
using UnmanagedBus = Bus<SigH, Event...>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_SIGNAL_BUS_HPP
|
||||
@@ -2,12 +2,60 @@
|
||||
#define ENTT_SIGNAL_DELEGATE_HPP
|
||||
|
||||
|
||||
#include <utility>
|
||||
#include <cstring>
|
||||
#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>
|
||||
auto to_function_pointer(Ret(*)(Type *, Args...), Type *) -> Ret(*)(Args...);
|
||||
|
||||
|
||||
template<typename Class, typename Ret, typename... Args>
|
||||
auto to_function_pointer(Ret(Class:: *)(Args...), Class *) -> Ret(*)(Args...);
|
||||
|
||||
|
||||
template<typename Class, typename Ret, typename... Args>
|
||||
auto to_function_pointer(Ret(Class:: *)(Args...) const, Class *) -> Ret(*)(Args...);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
@@ -15,122 +63,219 @@ 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_type = Ret(*)(void *, Args...);
|
||||
using stub_type = std::pair<void *, proto_type>;
|
||||
|
||||
static Ret fallback(void *, Args...) noexcept { return {}; }
|
||||
|
||||
template<Ret(*Function)(Args...)>
|
||||
static Ret proto(void *, Args... args) {
|
||||
return (Function)(args...);
|
||||
}
|
||||
|
||||
template<typename Class, Ret(Class::*Member)(Args...)>
|
||||
static Ret proto(void *instance, Args... args) {
|
||||
return (static_cast<Class *>(instance)->*Member)(args...);
|
||||
}
|
||||
class delegate<Ret(Args...)> {
|
||||
using proto_fn_type = Ret(const void *, Args...);
|
||||
|
||||
public:
|
||||
/*! @brief Function type of the delegate. */
|
||||
using function_type = Ret(Args...);
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
Delegate() noexcept
|
||||
: stub{std::make_pair(nullptr, &fallback)}
|
||||
delegate() ENTT_NOEXCEPT
|
||||
: fn{nullptr}, data{nullptr}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @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() 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 pointer that fits the purpose.
|
||||
*/
|
||||
template<typename Class, Ret(Class::*Member)(Args...)>
|
||||
void connect(Class *instance) 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 {
|
||||
static_assert(std::is_invocable_r_v<Ret, decltype(Function), Args...>);
|
||||
data = nullptr;
|
||||
|
||||
fn = [](const void *, Args... args) -> Ret {
|
||||
// this allows void(...) to eat return values and avoid errors
|
||||
return Ret(std::invoke(Function, args...));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 pointer that fits the purpose.
|
||||
*/
|
||||
template<auto Candidate, typename Type>
|
||||
void connect(Type *value_or_instance) ENTT_NOEXCEPT {
|
||||
static_assert(std::is_invocable_r_v<Ret, decltype(Candidate), Type *, Args...>);
|
||||
data = value_or_instance;
|
||||
|
||||
fn = [](const void *payload, 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));
|
||||
}
|
||||
|
||||
// this allows void(...) to eat return values and avoid errors
|
||||
return Ret(std::invoke(Candidate, curr, args...));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @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() noexcept {
|
||||
stub = std::make_pair(nullptr, &fallback);
|
||||
void reset() ENTT_NOEXCEPT {
|
||||
fn = nullptr;
|
||||
data = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the instance linked to a delegate, if any.
|
||||
* @return An opaque pointer to the instance linked to the delegate, if any.
|
||||
*/
|
||||
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) {
|
||||
return stub.second(stub.first, args...);
|
||||
Ret operator()(Args... args) const {
|
||||
ENTT_ASSERT(fn);
|
||||
return fn(data, args...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the contents of the two delegates are different.
|
||||
* @brief Checks whether a delegate actually stores a listener.
|
||||
* @return False if the delegate is empty, true otherwise.
|
||||
*/
|
||||
explicit operator bool() const ENTT_NOEXCEPT {
|
||||
// no need to test also data
|
||||
return fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the connected functions differ.
|
||||
*
|
||||
* Two delegates are identical if they contain the same listener.
|
||||
* Instances connected to delegates are ignored by this operator. Use the
|
||||
* `instance` member function instead.
|
||||
*
|
||||
* @param other Delegate with which to compare.
|
||||
* @return True if the two delegates are identical, false otherwise.
|
||||
* @return False if the connected functions differ, true otherwise.
|
||||
*/
|
||||
bool operator==(const Delegate<Ret(Args...)> &other) const noexcept {
|
||||
return stub.first == other.stub.first && stub.second == other.stub.second;
|
||||
bool operator==(const delegate<Ret(Args...)> &other) const ENTT_NOEXCEPT {
|
||||
return fn == other.fn;
|
||||
}
|
||||
|
||||
private:
|
||||
stub_type stub;
|
||||
proto_fn_type *fn;
|
||||
const void *data;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Checks if the contents of the two delegates are different.
|
||||
* @brief Checks if the connected functions differ.
|
||||
*
|
||||
* Two delegates are identical if they contain the same listener.
|
||||
* Instances connected to delegates are ignored by this operator. Use the
|
||||
* `instance` member function instead.
|
||||
*
|
||||
* @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 connected functions differ, false otherwise.
|
||||
*/
|
||||
template<typename Ret, typename... Args>
|
||||
bool operator!=(const Delegate<Ret(Args...)> &lhs, const Delegate<Ret(Args...)> &rhs) 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.
|
||||
*
|
||||
* @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 *) ENTT_NOEXCEPT
|
||||
-> delegate<std::remove_pointer_t<decltype(internal::to_function_pointer(Candidate, std::declval<Type *>()))>>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "../core/family.hpp"
|
||||
#include "signal.hpp"
|
||||
#include "../core/type_traits.hpp"
|
||||
#include "sigh.hpp"
|
||||
|
||||
|
||||
@@ -20,124 +21,130 @@ 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 signature:
|
||||
* `void(const Event &)`. Member functions named `receive` are automatically
|
||||
* detected and registered or unregistered by the dispatcher.
|
||||
* type `Event`, listeners are such that they can be invoked with an argument of
|
||||
* type `const Event &`, no matter what the return type is.
|
||||
*
|
||||
* @tparam Sig Type of the signal handler to use.
|
||||
* 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.
|
||||
*/
|
||||
template<template<typename...> class Sig>
|
||||
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 Sig<void(const Event &)>::template instance_type<Class>;
|
||||
using instance_type = typename sigh<void(const Event &)>::template instance_type<Class>;
|
||||
|
||||
struct BaseSignalWrapper {
|
||||
virtual ~BaseSignalWrapper() = default;
|
||||
virtual void publish(std::size_t) = 0;
|
||||
struct base_wrapper {
|
||||
virtual ~base_wrapper() = default;
|
||||
virtual void publish() = 0;
|
||||
};
|
||||
|
||||
template<typename Event>
|
||||
struct SignalWrapper final: BaseSignalWrapper {
|
||||
void publish(std::size_t current) final override {
|
||||
for(auto &&event: events[current]) {
|
||||
struct signal_wrapper: base_wrapper {
|
||||
using signal_type = sigh<void(const Event &)>;
|
||||
using sink_type = typename signal_type::sink_type;
|
||||
|
||||
void publish() override {
|
||||
for(const auto &event: events[current]) {
|
||||
signal.publish(event);
|
||||
}
|
||||
|
||||
events[current].clear();
|
||||
events[current++].clear();
|
||||
current %= std::extent<decltype(events)>::value;
|
||||
}
|
||||
|
||||
template<typename Class, void(Class::*Member)(const Event &)>
|
||||
inline void connect(instance_type<Class, Event> instance) noexcept {
|
||||
signal.template connect<Class, Member>(std::move(instance));
|
||||
}
|
||||
|
||||
template<typename Class, void(Class::*Member)(const Event &)>
|
||||
inline void disconnect(instance_type<Class, Event> instance) noexcept {
|
||||
signal.template disconnect<Class, Member>(std::move(instance));
|
||||
inline sink_type sink() ENTT_NOEXCEPT {
|
||||
return signal.sink();
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void trigger(Args&&... args) {
|
||||
inline void trigger(Args &&... args) {
|
||||
signal.publish({ std::forward<Args>(args)... });
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void enqueue(std::size_t current, Args&&... args) {
|
||||
events[current].push_back({ std::forward<Args>(args)... });
|
||||
inline void enqueue(Args &&... args) {
|
||||
events[current].emplace_back(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
private:
|
||||
Sig<void(const Event &)> signal{};
|
||||
signal_type signal{};
|
||||
std::vector<Event> events[2];
|
||||
int current{};
|
||||
};
|
||||
|
||||
inline static std::size_t buffer(bool mode) {
|
||||
return mode ? 0 : 1;
|
||||
struct wrapper_data {
|
||||
std::unique_ptr<base_wrapper> wrapper;
|
||||
ENTT_ID_TYPE runtime_type;
|
||||
};
|
||||
|
||||
template<typename 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>;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Event>
|
||||
SignalWrapper<Event> & wrapper() {
|
||||
auto type = event_family::type<Event>();
|
||||
signal_wrapper<Event> & assure() {
|
||||
const auto wtype = type<Event>();
|
||||
wrapper_data *wdata = nullptr;
|
||||
|
||||
if(!(type < wrappers.size())) {
|
||||
wrappers.resize(type + 1);
|
||||
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 Default constructor. */
|
||||
Dispatcher() noexcept
|
||||
: wrappers{}, mode{false}
|
||||
{}
|
||||
/*! @brief Type of sink for the given event. */
|
||||
template<typename Event>
|
||||
using sink_type = typename signal_wrapper<Event>::sink_type;
|
||||
|
||||
/**
|
||||
* @brief Registers a listener given in the form of a member function.
|
||||
* @brief Returns a sink object for the given event.
|
||||
*
|
||||
* A matching member function has the following signature:
|
||||
* `void receive(const Event &)`. Member functions named `receive` are
|
||||
* automatically detected and registered if available.
|
||||
* A sink is an opaque object used to connect listeners to events.
|
||||
*
|
||||
* @warning
|
||||
* Connecting a listener during an update may lead to unexpected behavior.
|
||||
* Register listeners before or after invoking the update if possible.
|
||||
* The function type for a listener is:
|
||||
* @code{.cpp}
|
||||
* void(const Event &);
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Event Type of event to which to connect the function.
|
||||
* @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 the right type.
|
||||
* The order of invocation of the listeners isn't guaranteed.
|
||||
*
|
||||
* @sa sink
|
||||
*
|
||||
* @tparam Event Type of event of which to get the sink.
|
||||
* @return A temporary sink object.
|
||||
*/
|
||||
template<typename Event, typename Class, void(Class::*Member)(const Event &) = &Class::receive>
|
||||
void connect(instance_type<Class, Event> instance) noexcept {
|
||||
wrapper<Event>().template connect<Class, Member>(std::move(instance));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Unregisters a listener given in the form of a member function.
|
||||
*
|
||||
* A matching member function has the following signature:
|
||||
* `void receive(const Event &)`. Member functions named `receive` are
|
||||
* automatically detected and unregistered if available.
|
||||
*
|
||||
* @warning
|
||||
* Disonnecting a listener during an update may lead to unexpected behavior.
|
||||
* Unregister listeners before or after invoking the update if possible.
|
||||
*
|
||||
* @tparam Event Type of event from which to disconnect the function.
|
||||
* @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 the right type.
|
||||
*/
|
||||
template<typename Event, typename Class, void(Class::*Member)(const Event &) = &Class::receive>
|
||||
void disconnect(instance_type<Class, Event> instance) noexcept {
|
||||
wrapper<Event>().template disconnect<Class, Member>(std::move(instance));
|
||||
template<typename Event>
|
||||
inline sink_type<Event> sink() ENTT_NOEXCEPT {
|
||||
return assure<Event>().sink();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -151,8 +158,22 @@ public:
|
||||
* @param args Arguments to use to construct the event.
|
||||
*/
|
||||
template<typename Event, typename... Args>
|
||||
void trigger(Args&&... args) {
|
||||
wrapper<Event>().trigger(std::forward<Args>(args)...);
|
||||
inline 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>
|
||||
inline void trigger(Event &&event) {
|
||||
assure<std::decay_t<Event>>().trigger(std::forward<Event>(event));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,61 +182,65 @@ 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>
|
||||
void enqueue(Args&&... args) {
|
||||
wrapper<Event>().enqueue(buffer(mode), std::forward<Args>(args)...);
|
||||
inline 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>
|
||||
inline void enqueue(Event &&event) {
|
||||
assure<std::decay_t<Event>>().enqueue(std::forward<Event>(event));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Delivers all the pending events of the given type.
|
||||
*
|
||||
* This method is blocking and it doesn't return until all the events are
|
||||
* 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.
|
||||
*
|
||||
* @tparam Event Type of events to send.
|
||||
*/
|
||||
template<typename Event>
|
||||
inline void update() {
|
||||
assure<Event>().publish();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Delivers all the pending events.
|
||||
*
|
||||
* This method is blocking and it doesn't return until all the events are
|
||||
* delivered to the registered listeners. It's responsability of the users
|
||||
* 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.
|
||||
*/
|
||||
void update() {
|
||||
auto buf = buffer(mode);
|
||||
mode = !mode;
|
||||
inline void update() const {
|
||||
for(auto pos = wrappers.size(); pos; --pos) {
|
||||
auto &wdata = wrappers[pos-1];
|
||||
|
||||
for(auto &&wrapper: wrappers) {
|
||||
if(wrapper) {
|
||||
wrapper->publish(buf);
|
||||
if(wdata.wrapper) {
|
||||
wdata.wrapper->publish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<BaseSignalWrapper>> wrappers;
|
||||
bool mode;
|
||||
std::vector<wrapper_data> wrappers;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Managed dispatcher.
|
||||
*
|
||||
* A managed dispatcher uses the Signal class template as an underlying type.
|
||||
* The type of the instances is the one required by the signal handler:
|
||||
* `std::shared_ptr<Class>` (a shared pointer).
|
||||
*/
|
||||
using ManagedDispatcher = Dispatcher<Signal>;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Unmanaged dispatcher.
|
||||
*
|
||||
* An unmanaged dispatcher uses the SigH class template as an underlying type.
|
||||
* The type of the instances is the one required by the signal handler:
|
||||
* `Class *` (a naked pointer).<br/>
|
||||
* When it comes to work with this kind of dispatcher, users must guarantee that
|
||||
* the lifetimes of the instances overcome the one of the dispatcher itself.
|
||||
*/
|
||||
using UnmanagedDispatcher = Dispatcher<SigH>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -6,10 +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 {
|
||||
@@ -21,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/>
|
||||
@@ -37,59 +39,61 @@ namespace entt {
|
||||
* @tparam Derived Actual type of emitter that extends the class template.
|
||||
*/
|
||||
template<typename Derived>
|
||||
class Emitter {
|
||||
struct BaseHandler {
|
||||
virtual ~BaseHandler() = default;
|
||||
virtual bool empty() const noexcept = 0;
|
||||
virtual void clear() noexcept = 0;
|
||||
class emitter {
|
||||
using handler_family = family<struct internal_emitter_handler_family>;
|
||||
|
||||
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>;
|
||||
using connection_type = typename container_type::iterator;
|
||||
|
||||
bool empty() const noexcept override {
|
||||
auto pred = [](auto &&element){ return element.first; };
|
||||
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() noexcept override {
|
||||
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);
|
||||
auto func = [](auto &&element) { element.first = true; };
|
||||
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));
|
||||
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));
|
||||
return on_list.emplace(on_list.cend(), false, std::move(listener));
|
||||
}
|
||||
|
||||
void erase(connection_type conn) noexcept {
|
||||
void erase(connection_type conn) ENTT_NOEXCEPT {
|
||||
conn->first = true;
|
||||
|
||||
if(!publishing) {
|
||||
auto pred = [](auto &&element){ return element.first; };
|
||||
onceL.remove_if(pred);
|
||||
onL.remove_if(pred);
|
||||
auto pred = [](auto &&element) { return element.first; };
|
||||
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);
|
||||
@@ -97,50 +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{};
|
||||
};
|
||||
|
||||
static std::size_t next() noexcept {
|
||||
static std::size_t counter = 0;
|
||||
return counter++;
|
||||
}
|
||||
struct handler_data {
|
||||
std::unique_ptr<base_handler> handler;
|
||||
ENTT_ID_TYPE runtime_type;
|
||||
};
|
||||
|
||||
template<typename>
|
||||
static std::size_t type() noexcept {
|
||||
static std::size_t value = next();
|
||||
return value;
|
||||
template<typename 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>;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Event>
|
||||
Handler<Event> & handler() noexcept {
|
||||
std::size_t family = type<Event>();
|
||||
event_handler<Event> * assure() const ENTT_NOEXCEPT {
|
||||
const auto htype = type<Event>();
|
||||
handler_data *hdata = nullptr;
|
||||
|
||||
if(!(family < handlers.size())) {
|
||||
handlers.resize(family+1);
|
||||
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 type of event. */
|
||||
/** @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.
|
||||
@@ -152,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() 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 assignament 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() noexcept = default;
|
||||
emitter() ENTT_NOEXCEPT = default;
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
virtual ~Emitter() 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 assignament operator. @return This emitter. */
|
||||
Emitter & operator=(Emitter &&) = default;
|
||||
/*! @brief Default move assignment operator. @return This emitter. */
|
||||
emitter & operator=(emitter &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Emits the given event.
|
||||
@@ -215,8 +219,8 @@ public:
|
||||
* @param args Parameters to use to initialize the event.
|
||||
*/
|
||||
template<typename Event, typename... Args>
|
||||
void publish(Args&&... args) {
|
||||
handler<Event>().publish({ std::forward<Args>(args)... }, *static_cast<Derived *>(this));
|
||||
void publish(Args &&... args) {
|
||||
assure<Event>()->publish({ std::forward<Args>(args)... }, *static_cast<Derived *>(this));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -236,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));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -261,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));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -279,32 +283,33 @@ public:
|
||||
* @param conn A valid connection.
|
||||
*/
|
||||
template<typename Event>
|
||||
void erase(Connection<Event> conn) noexcept {
|
||||
handler<Event>().erase(std::move(conn));
|
||||
void erase(connection<Event> conn) ENTT_NOEXCEPT {
|
||||
assure<Event>()->erase(std::move(conn));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects all the listeners for the given event type.
|
||||
*
|
||||
* All the connections previously returned for the given event are
|
||||
* invalidated. Using them results in undefined behaviour.
|
||||
* invalidated. Using them results in undefined behavior.
|
||||
*
|
||||
* @tparam Event Type of event to reset.
|
||||
*/
|
||||
template<typename Event>
|
||||
void clear() noexcept {
|
||||
handler<Event>().clear();
|
||||
void clear() ENTT_NOEXCEPT {
|
||||
assure<Event>()->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects all the listeners.
|
||||
*
|
||||
* All the connections previously returned are invalidated. Using them
|
||||
* results in undefined behaviour.
|
||||
* results in undefined behavior.
|
||||
*/
|
||||
void clear() noexcept {
|
||||
std::for_each(handlers.begin(), handlers.end(),
|
||||
[](auto &&handler){ if(handler) { handler->clear(); } });
|
||||
void clear() ENTT_NOEXCEPT {
|
||||
std::for_each(handlers.begin(), handlers.end(), [](auto &&hdata) {
|
||||
return hdata.handler ? hdata.handler->clear() : void();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -313,25 +318,22 @@ public:
|
||||
* @return True if there are no listeners registered, false otherwise.
|
||||
*/
|
||||
template<typename Event>
|
||||
bool empty() const noexcept {
|
||||
std::size_t family = type<Event>();
|
||||
|
||||
return (!(family < handlers.size()) ||
|
||||
!handlers[family] ||
|
||||
static_cast<Handler<Event> &>(*handlers[family]).empty());
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
return assure<Event>()->empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if there are listeners registered with the event emitter.
|
||||
* @return True if there are no listeners registered, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
return std::all_of(handlers.cbegin(), handlers.cend(),
|
||||
[](auto &&handler){ return !handler || handler->empty(); });
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
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, typename>
|
||||
struct sigh;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_SIGNAL_FWD_HPP
|
||||
@@ -5,71 +5,98 @@
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "delegate.hpp"
|
||||
#include "fwd.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
namespace {
|
||||
/**
|
||||
* @cond TURN_OFF_DOXYGEN
|
||||
* Internal details not to be documented.
|
||||
*/
|
||||
|
||||
|
||||
namespace internal {
|
||||
|
||||
|
||||
template<typename, typename>
|
||||
struct Invoker;
|
||||
struct invoker;
|
||||
|
||||
|
||||
template<typename Ret, typename... Args, typename Collector>
|
||||
struct Invoker<Ret(Args...), Collector> {
|
||||
using proto_type = Ret(*)(void *, Args...);
|
||||
using call_type = std::pair<void *, proto_type>;
|
||||
struct invoker<Ret(Args...), Collector> {
|
||||
virtual ~invoker() = default;
|
||||
|
||||
virtual ~Invoker() = default;
|
||||
|
||||
template<typename SFINAE = Ret>
|
||||
typename std::enable_if<std::is_void<SFINAE>::value, bool>::type
|
||||
invoke(Collector &, proto_type proto, void *instance, Args... args) {
|
||||
proto(instance, args...);
|
||||
return true;
|
||||
bool invoke(Collector &collector, const delegate<Ret(Args...)> &delegate, Args... args) const {
|
||||
return collector(delegate(args...));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename SFINAE = Ret>
|
||||
typename std::enable_if<!std::is_void<SFINAE>::value, bool>::type
|
||||
invoke(Collector &collector, proto_type proto, void *instance, Args... args) {
|
||||
return collector(proto(instance, args...));
|
||||
|
||||
template<typename... Args, typename Collector>
|
||||
struct invoker<void(Args...), Collector> {
|
||||
virtual ~invoker() = default;
|
||||
|
||||
bool invoke(Collector &, const delegate<void(Args...)> &delegate, Args... args) const {
|
||||
return (delegate(args...), true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename Ret>
|
||||
struct NullCollector final {
|
||||
struct null_collector {
|
||||
using result_type = Ret;
|
||||
bool operator()(result_type) const noexcept { return true; }
|
||||
bool operator()(result_type) const ENTT_NOEXCEPT { return true; }
|
||||
};
|
||||
|
||||
|
||||
template<>
|
||||
struct NullCollector<void> final {
|
||||
struct null_collector<void> {
|
||||
using result_type = void;
|
||||
bool operator()() const noexcept { return true; }
|
||||
bool operator()() const ENTT_NOEXCEPT { return true; }
|
||||
};
|
||||
|
||||
|
||||
template<typename>
|
||||
struct DefaultCollector;
|
||||
struct default_collector;
|
||||
|
||||
|
||||
template<typename Ret, typename... Args>
|
||||
struct DefaultCollector<Ret(Args...)> final {
|
||||
using collector_type = NullCollector<Ret>;
|
||||
struct default_collector<Ret(Args...)> {
|
||||
using collector_type = null_collector<Ret>;
|
||||
};
|
||||
|
||||
|
||||
template<typename Function>
|
||||
using DefaultCollectorType = typename DefaultCollector<Function>::collector_type;
|
||||
using default_collector_type = typename default_collector<Function>::collector_type;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Internal details not to be documented.
|
||||
* @endcond TURN_OFF_DOXYGEN
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @brief Sink implementation.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
template<typename Function>
|
||||
class sink;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Unmanaged signal handler declaration.
|
||||
*
|
||||
@@ -79,8 +106,135 @@ using DefaultCollectorType = typename DefaultCollector<Function>::collector_type
|
||||
* @tparam Function A valid function type.
|
||||
* @tparam Collector Type of collector to use, if any.
|
||||
*/
|
||||
template<typename Function, typename Collector = DefaultCollectorType<Function>>
|
||||
class SigH;
|
||||
template<typename Function, typename Collector = internal::default_collector_type<Function>>
|
||||
struct sigh;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Sink implementation.
|
||||
*
|
||||
* 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...)> {
|
||||
/*! @brief A signal is allowed to create sinks. */
|
||||
template<typename, typename>
|
||||
friend struct sigh;
|
||||
|
||||
template<typename Type>
|
||||
Type * payload_type(Ret(*)(Type *, Args...));
|
||||
|
||||
sink(std::vector<delegate<Ret(Args...)>> *ref) ENTT_NOEXCEPT
|
||||
: calls{ref}
|
||||
{}
|
||||
|
||||
public:
|
||||
/**
|
||||
* @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 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.
|
||||
*/
|
||||
template<auto Function>
|
||||
void connect() {
|
||||
disconnect<Function>();
|
||||
delegate<Ret(Args...)> delegate{};
|
||||
delegate.template connect<Function>();
|
||||
calls->emplace_back(std::move(delegate));
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 delegate.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
* @param value_or_instance A valid pointer that fits the purpose.
|
||||
*/
|
||||
template<auto Candidate, typename Type>
|
||||
void connect(Type *value_or_instance) {
|
||||
if constexpr(std::is_member_function_pointer_v<decltype(Candidate)>) {
|
||||
disconnect<Candidate>(value_or_instance);
|
||||
} else {
|
||||
disconnect<Candidate>();
|
||||
}
|
||||
|
||||
delegate<Ret(Args...)> delegate{};
|
||||
delegate.template connect<Candidate>(value_or_instance);
|
||||
calls->emplace_back(std::move(delegate));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a free function from a signal.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<auto Function>
|
||||
void disconnect() {
|
||||
delegate<Ret(Args...)> delegate{};
|
||||
|
||||
if constexpr(std::is_invocable_r_v<Ret, decltype(Function), Args...>) {
|
||||
delegate.template connect<Function>();
|
||||
} else {
|
||||
decltype(payload_type(Function)) payload = nullptr;
|
||||
delegate.template connect<Function>(payload);
|
||||
}
|
||||
|
||||
calls->erase(std::remove(calls->begin(), calls->end(), std::move(delegate)), calls->end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a given member function from a signal.
|
||||
* @tparam Member Member function to disconnect from the signal.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<auto Member, typename Class>
|
||||
void disconnect(Class *instance) {
|
||||
static_assert(std::is_member_function_pointer_v<decltype(Member)>);
|
||||
delegate<Ret(Args...)> delegate{};
|
||||
delegate.template connect<Member>(instance);
|
||||
calls->erase(std::remove_if(calls->begin(), calls->end(), [&delegate](const auto &other) {
|
||||
return other == delegate && other.instance() == delegate.instance();
|
||||
}), calls->end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects all the listeners from a signal.
|
||||
*/
|
||||
void disconnect() {
|
||||
calls->clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<delegate<Ret(Args...)>> *calls;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
@@ -100,31 +254,20 @@ class SigH;
|
||||
*
|
||||
* * `Param` is a type to which `Ret` can be converted.
|
||||
* * The return type is true if the handler must stop collecting data, false
|
||||
* otherwise.
|
||||
* 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 Invoker<Ret(Args...), Collector> {
|
||||
using typename Invoker<Ret(Args...), Collector>::call_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...);
|
||||
}
|
||||
|
||||
public:
|
||||
struct sigh<Ret(Args...), Collector>: private internal::invoker<Ret(Args...), Collector> {
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename std::vector<call_type>::size_type;
|
||||
using size_type = typename std::vector<delegate<Ret(Args...)>>::size_type;
|
||||
/*! @brief Collector type. */
|
||||
using collector_type = Collector;
|
||||
/*! @brief Sink type. */
|
||||
using sink_type = entt::sink<Ret(Args...)>;
|
||||
|
||||
/**
|
||||
* @brief Instance type when it comes to connecting member functions.
|
||||
@@ -137,7 +280,7 @@ public:
|
||||
* @brief Number of listeners connected to the signal.
|
||||
* @return Number of listeners currently connected.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
size_type size() const ENTT_NOEXCEPT {
|
||||
return calls.size();
|
||||
}
|
||||
|
||||
@@ -145,80 +288,21 @@ public:
|
||||
* @brief Returns false if at least a listener is connected to the signal.
|
||||
* @return True if the signal has no listeners connected, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
return calls.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects all the listeners from a signal.
|
||||
*/
|
||||
void clear() noexcept {
|
||||
calls.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function to a signal.
|
||||
* @brief Returns a sink object for the given signal.
|
||||
*
|
||||
* The signal handler performs checks to avoid multiple connections for free
|
||||
* functions.
|
||||
* 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.
|
||||
*
|
||||
* @tparam Function A valid free function pointer.
|
||||
* @return A temporary sink object.
|
||||
*/
|
||||
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...)>
|
||||
void connect(instance_type<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(instance_type<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(instance_type<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());
|
||||
sink_type sink() ENTT_NOEXCEPT {
|
||||
return { &calls };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,9 +312,10 @@ public:
|
||||
*
|
||||
* @param args Arguments to use to invoke listeners.
|
||||
*/
|
||||
void publish(Args... args) {
|
||||
for(auto &&call: calls) {
|
||||
call.second(call.first, args...);
|
||||
void publish(Args... args) const {
|
||||
for(auto pos = calls.size(); pos; --pos) {
|
||||
auto &call = calls[pos-1];
|
||||
call(args...);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,11 +324,11 @@ public:
|
||||
* @param args Arguments to use to invoke listeners.
|
||||
* @return An instance of the collector filled with collected data.
|
||||
*/
|
||||
collector_type collect(Args... args) {
|
||||
collector_type collect(Args... args) const {
|
||||
collector_type collector;
|
||||
|
||||
for(auto &&call: calls) {
|
||||
if(!this->invoke(collector, call.second, call.first, args...)) {
|
||||
if(!this->invoke(collector, call, args...)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -256,47 +341,16 @@ public:
|
||||
* @param lhs A valid signal object.
|
||||
* @param rhs A valid signal object.
|
||||
*/
|
||||
friend void swap(SigH &lhs, SigH &rhs) {
|
||||
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 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.
|
||||
*
|
||||
* Two signals are identical if they have the same size and the same
|
||||
* listeners registered exactly in the same order.
|
||||
*
|
||||
* @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) noexcept {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,227 +0,0 @@
|
||||
#ifndef ENTT_SIGNAL_SIGNAL_HPP
|
||||
#define ENTT_SIGNAL_SIGNAL_HPP
|
||||
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Managed signal handler declaration.
|
||||
*
|
||||
* Primary template isn't defined on purpose. All the specializations give a
|
||||
* compile-time error unless the template parameter is a function type.
|
||||
*/
|
||||
template<typename>
|
||||
class Signal;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Managed signal handler definition.
|
||||
*
|
||||
* Managed signal handler. It works with weak pointers to classes and pointers
|
||||
* to member functions as well as pointers to free functions. References are
|
||||
* automatically removed when the instances to which they point are freed.
|
||||
*
|
||||
* This class can be used to create signals used later to notify a bunch of
|
||||
* listeners.
|
||||
*
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
*/
|
||||
template<typename... Args>
|
||||
class Signal<void(Args...)> final {
|
||||
using proto_type = bool(*)(std::weak_ptr<void> &, Args...);
|
||||
using call_type = std::pair<std::weak_ptr<void>, proto_type>;
|
||||
|
||||
template<void(*Function)(Args...)>
|
||||
static bool proto(std::weak_ptr<void> &, Args... args) {
|
||||
Function(args...);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename Class, void(Class::*Member)(Args...)>
|
||||
static bool proto(std::weak_ptr<void> &wptr, Args... args) {
|
||||
bool ret = false;
|
||||
|
||||
if(!wptr.expired()) {
|
||||
auto ptr = std::static_pointer_cast<Class>(wptr.lock());
|
||||
(ptr.get()->*Member)(args...);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
|
||||
/**
|
||||
* @brief Instance type when it comes to connecting member functions.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
*/
|
||||
template<typename Class>
|
||||
using instance_type = std::shared_ptr<Class>;
|
||||
|
||||
/**
|
||||
* @brief Number of listeners connected to the signal.
|
||||
* @return Number of listeners currently connected.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
return calls.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns false if at least a listener is connected to the signal.
|
||||
* @return True if the signal has no listeners connected, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
return calls.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects all the listeners from a signal.
|
||||
*/
|
||||
void clear() noexcept {
|
||||
calls.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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<void(*Function)(Args...)>
|
||||
void connect() {
|
||||
disconnect<Function>();
|
||||
calls.emplace_back(std::weak_ptr<void>{}, &proto<Function>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a member function for a given instance to a signal.
|
||||
*
|
||||
* 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, void(Class::*Member)(Args...)>
|
||||
void connect(instance_type<Class> instance) {
|
||||
disconnect<Class, Member>(instance);
|
||||
calls.emplace_back(std::move(instance), &proto<Class, Member>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a free function from a signal.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<void(*Function)(Args...)>
|
||||
void disconnect() {
|
||||
calls.erase(std::remove_if(calls.begin(), calls.end(),
|
||||
[](const call_type &call) { return call.second == &proto<Function> && !call.first.lock(); }
|
||||
), 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, void(Class::*Member)(Args...)>
|
||||
void disconnect(instance_type<Class> instance) {
|
||||
calls.erase(std::remove_if(calls.begin(), calls.end(),
|
||||
[instance{std::move(instance)}](const call_type &call) { return call.second == &proto<Class, Member> && call.first.lock() == instance; }
|
||||
), 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(instance_type<Class> instance) {
|
||||
calls.erase(std::remove_if(calls.begin(), calls.end(),
|
||||
[instance{std::move(instance)}](const call_type &call) { return call.first.lock() == instance; }
|
||||
), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Triggers a signal.
|
||||
*
|
||||
* All the listeners are notified. Order isn't guaranteed.
|
||||
*
|
||||
* @param args Arguments to use to invoke listeners.
|
||||
*/
|
||||
void publish(Args... args) {
|
||||
for(auto it = calls.rbegin(), end = calls.rend(); it != end; it++) {
|
||||
if(!(it->second)(it->first, args...)) {
|
||||
calls.erase(std::next(it).base());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Swaps listeners between the two signals.
|
||||
* @param lhs A valid signal object.
|
||||
* @param rhs A valid signal object.
|
||||
*/
|
||||
friend void swap(Signal &lhs, Signal &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 Signal &other) const noexcept {
|
||||
return std::equal(calls.cbegin(), calls.cend(), other.calls.cbegin(), other.calls.cend(), [](const auto &lhs, const auto &rhs) {
|
||||
return (lhs.second == rhs.second) && (lhs.first.lock() == rhs.first.lock());
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<call_type> calls;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Checks if the contents of the two signals are different.
|
||||
*
|
||||
* Two signals are identical if they have the same size and the same
|
||||
* listeners registered exactly in the same order.
|
||||
*
|
||||
* @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... Args>
|
||||
bool operator!=(const Signal<void(Args...)> &lhs, const Signal<void(Args...)> &rhs) noexcept {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_SIGNAL_SIGNAL_HPP
|
||||
@@ -2,89 +2,128 @@
|
||||
# Tests configuration
|
||||
#
|
||||
|
||||
include_directories(${PROJECT_SRC_DIR})
|
||||
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)
|
||||
SETUP_LIBRARY_TARGET(odr)
|
||||
|
||||
macro(SETUP_AND_ADD_TEST TEST_NAME TEST_SOURCE)
|
||||
add_executable(${TEST_NAME} $<TARGET_OBJECTS:odr> ${TEST_SOURCE})
|
||||
set_target_properties(${TEST_NAME} PROPERTIES CXX_EXTENSIONS OFF)
|
||||
target_link_libraries(${TEST_NAME} PRIVATE EnTT GTest::Main Threads::Threads)
|
||||
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()
|
||||
|
||||
# Test benchmark
|
||||
|
||||
if(CMAKE_BUILD_TYPE MATCHES Release)
|
||||
add_executable(
|
||||
benchmark
|
||||
$<TARGET_OBJECTS:odr>
|
||||
entt/entity/benchmark.cpp
|
||||
)
|
||||
target_link_libraries(benchmark PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME benchmark COMMAND benchmark)
|
||||
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)
|
||||
set(DUKTAPE_DEPS_DIR ${EnTT_SOURCE_DIR}/deps/duktape)
|
||||
configure_file(${EnTT_SOURCE_DIR}/cmake/in/duktape.in ${DUKTAPE_DEPS_DIR}/CMakeLists.txt)
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . WORKING_DIRECTORY ${DUKTAPE_DEPS_DIR})
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} --build . WORKING_DIRECTORY ${DUKTAPE_DEPS_DIR})
|
||||
set(DUKTAPE_SRC_DIR ${DUKTAPE_DEPS_DIR}/src/src)
|
||||
|
||||
set(MOD_TEST_SOURCE ${DUKTAPE_SRC_DIR}/duktape.c mod/mod.cpp)
|
||||
SETUP_AND_ADD_TEST(mod "${MOD_TEST_SOURCE}")
|
||||
target_include_directories(mod PRIVATE ${DUKTAPE_SRC_DIR})
|
||||
endif()
|
||||
|
||||
# Test snapshot
|
||||
|
||||
if(BUILD_SNAPSHOT)
|
||||
set(CEREAL_DEPS_DIR ${EnTT_SOURCE_DIR}/deps/cereal)
|
||||
configure_file(${EnTT_SOURCE_DIR}/cmake/in/cereal.in ${CEREAL_DEPS_DIR}/CMakeLists.txt)
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . WORKING_DIRECTORY ${CEREAL_DEPS_DIR})
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} --build . WORKING_DIRECTORY ${CEREAL_DEPS_DIR})
|
||||
set(CEREAL_SRC_DIR ${CEREAL_DEPS_DIR}/src/include)
|
||||
|
||||
SETUP_AND_ADD_TEST(cereal snapshot/snapshot.cpp)
|
||||
target_include_directories(cereal PRIVATE ${CEREAL_SRC_DIR})
|
||||
endif()
|
||||
|
||||
# Test core
|
||||
|
||||
add_executable(
|
||||
core
|
||||
$<TARGET_OBJECTS:odr>
|
||||
entt/core/family.cpp
|
||||
entt/core/hashed_string.cpp
|
||||
entt/core/ident.cpp
|
||||
)
|
||||
target_link_libraries(core PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME core COMMAND core)
|
||||
SETUP_AND_ADD_TEST(algorithm entt/core/algorithm.cpp)
|
||||
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
|
||||
|
||||
add_executable(
|
||||
entity
|
||||
$<TARGET_OBJECTS:odr>
|
||||
entt/entity/actor.cpp
|
||||
entt/entity/registry.cpp
|
||||
entt/entity/sparse_set.cpp
|
||||
entt/entity/view.cpp
|
||||
)
|
||||
target_link_libraries(entity PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME entity COMMAND 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(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
|
||||
|
||||
add_executable(
|
||||
locator
|
||||
$<TARGET_OBJECTS:odr>
|
||||
entt/locator/locator.cpp
|
||||
)
|
||||
target_link_libraries(locator PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME locator COMMAND locator)
|
||||
SETUP_AND_ADD_TEST(locator entt/locator/locator.cpp)
|
||||
|
||||
# Test meta
|
||||
|
||||
SETUP_AND_ADD_TEST(meta entt/meta/meta.cpp)
|
||||
|
||||
# Test process
|
||||
|
||||
add_executable(
|
||||
process
|
||||
$<TARGET_OBJECTS:odr>
|
||||
entt/process/process.cpp
|
||||
entt/process/scheduler.cpp
|
||||
)
|
||||
target_link_libraries(process PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME process COMMAND process)
|
||||
SETUP_AND_ADD_TEST(process entt/process/process.cpp)
|
||||
SETUP_AND_ADD_TEST(scheduler entt/process/scheduler.cpp)
|
||||
|
||||
# Test resource
|
||||
|
||||
add_executable(
|
||||
resource
|
||||
$<TARGET_OBJECTS:odr>
|
||||
entt/resource/resource.cpp
|
||||
)
|
||||
target_link_libraries(resource PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME resource COMMAND resource)
|
||||
SETUP_AND_ADD_TEST(resource entt/resource/resource.cpp)
|
||||
|
||||
# Test signal
|
||||
|
||||
add_executable(
|
||||
signal
|
||||
$<TARGET_OBJECTS:odr>
|
||||
entt/signal/bus.cpp
|
||||
entt/signal/delegate.cpp
|
||||
entt/signal/dispatcher.cpp
|
||||
entt/signal/emitter.cpp
|
||||
entt/signal/sigh.cpp
|
||||
entt/signal/signal.cpp
|
||||
)
|
||||
target_link_libraries(signal PRIVATE gtest_main Threads::Threads)
|
||||
add_test(NAME signal COMMAND signal)
|
||||
SETUP_AND_ADD_TEST(delegate entt/signal/delegate.cpp)
|
||||
SETUP_AND_ADD_TEST(dispatcher entt/signal/dispatcher.cpp)
|
||||
SETUP_AND_ADD_TEST(emitter entt/signal/emitter.cpp)
|
||||
SETUP_AND_ADD_TEST(sigh entt/signal/sigh.cpp)
|
||||
|
||||
1095
test/benchmark/benchmark.cpp
Normal file
1095
test/benchmark/benchmark.cpp
Normal file
File diff suppressed because it is too large
Load Diff
33
test/entt/core/algorithm.cpp
Normal file
33
test/entt/core/algorithm.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#include <array>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/algorithm.hpp>
|
||||
|
||||
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::std_sort sort;
|
||||
|
||||
sort(arr.begin(), arr.end());
|
||||
|
||||
for(typename decltype(arr)::size_type i = 0; i < (arr.size() - 1); ++i) {
|
||||
ASSERT_LT(arr[i], arr[i+1]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Algorithm, InsertionSort) {
|
||||
std::array<int, 5> arr{{4, 1, 3, 2, 0}};
|
||||
entt::insertion_sort sort;
|
||||
|
||||
sort(arr.begin(), arr.end());
|
||||
|
||||
for(typename decltype(arr)::size_type i = 0; i < (arr.size() - 1); ++i) {
|
||||
ASSERT_LT(arr[i], arr[i+1]);
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
@@ -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,37 +1,64 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/hashed_string.hpp>
|
||||
|
||||
constexpr bool check(const char *str) {
|
||||
using hash_type = entt::HashedString::hash_type;
|
||||
|
||||
return (static_cast<hash_type>(entt::HashedString{str}) == entt::HashedString{str}
|
||||
&& static_cast<const char *>(entt::HashedString{str}) == str
|
||||
&& entt::HashedString{str} == entt::HashedString{str}
|
||||
&& !(entt::HashedString{str} != entt::HashedString{str}));
|
||||
}
|
||||
|
||||
TEST(HashedString, Constexprness) {
|
||||
// how would you test a constepxr otherwise?
|
||||
static_assert(check("foobar"), "!");
|
||||
ASSERT_TRUE(true);
|
||||
}
|
||||
|
||||
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_TRUE(fooHs == fooHs);
|
||||
ASSERT_FALSE(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(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::hashed_string::hash_type;
|
||||
// how would you test a constexpr otherwise?
|
||||
(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);
|
||||
}
|
||||
|
||||
@@ -2,32 +2,31 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/ident.hpp>
|
||||
|
||||
struct A {};
|
||||
struct B {};
|
||||
struct a_type {};
|
||||
struct another_type {};
|
||||
|
||||
TEST(Identifier, Uniqueness) {
|
||||
constexpr auto ID = entt::ident<A, B>;
|
||||
constexpr A a;
|
||||
constexpr B b;
|
||||
using id = entt::identifier<a_type, another_type>;
|
||||
constexpr a_type an_instance;
|
||||
constexpr another_type another_instance;
|
||||
|
||||
ASSERT_NE(ID.get<A>(), ID.get<B>());
|
||||
ASSERT_EQ(ID.get<A>(), ID.get<decltype(a)>());
|
||||
ASSERT_NE(ID.get<A>(), ID.get<decltype(b)>());
|
||||
ASSERT_EQ(ID.get<A>(), ID.get<A>());
|
||||
ASSERT_EQ(ID.get<B>(), ID.get<B>());
|
||||
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<B>()) {
|
||||
case ID.get<A>():
|
||||
switch(id::type<another_type>) {
|
||||
case id::type<a_type>:
|
||||
FAIL();
|
||||
break;
|
||||
case ID.get<B>():
|
||||
case id::type<another_type>:
|
||||
SUCCEED();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Identifier, SingleType) {
|
||||
constexpr auto ID = entt::ident<A>;
|
||||
std::integral_constant<decltype(ID)::identifier_type, ID.get()> ic;
|
||||
using id = entt::identifier<a_type>;
|
||||
std::integral_constant<id::identifier_type, id::type<a_type>> ic;
|
||||
(void)ic;
|
||||
}
|
||||
|
||||
20
test/entt/core/monostate.cpp
Normal file
20
test/entt/core/monostate.cpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/hashed_string.hpp>
|
||||
#include <entt/core/monostate.hpp>
|
||||
|
||||
TEST(Monostate, Functionalities) {
|
||||
const bool b_pre = entt::monostate<entt::hashed_string{"foobar"}>{};
|
||||
const int i_pre = entt::monostate<"foobar"_hs>{};
|
||||
|
||||
ASSERT_FALSE(b_pre);
|
||||
ASSERT_EQ(i_pre, int{});
|
||||
|
||||
entt::monostate<"foobar"_hs>{} = true;
|
||||
entt::monostate_v<"foobar"_hs> = 42;
|
||||
|
||||
const bool &b_post = entt::monostate<"foobar"_hs>{};
|
||||
const int &i_post = entt::monostate_v<entt::hashed_string{"foobar"}>;
|
||||
|
||||
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((type::size), (decltype(type::size){2}));
|
||||
ASSERT_EQ((other::size), (decltype(other::size){1}));
|
||||
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>>));
|
||||
}
|
||||
19
test/entt/core/utility.cpp
Normal file
19
test/entt/core/utility.cpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#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, 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));
|
||||
}
|
||||
@@ -3,55 +3,57 @@
|
||||
#include <entt/entity/actor.hpp>
|
||||
#include <entt/entity/registry.hpp>
|
||||
|
||||
struct TestActor: entt::DefaultActor<unsigned int> {
|
||||
using entt::DefaultActor<unsigned int>::DefaultActor;
|
||||
void update(unsigned int) {}
|
||||
};
|
||||
TEST(Actor, Component) {
|
||||
entt::registry registry;
|
||||
entt::actor actor{registry};
|
||||
|
||||
struct Position final {};
|
||||
struct Velocity final {};
|
||||
|
||||
TEST(Actor, Functionalities) {
|
||||
entt::DefaultRegistry registry;
|
||||
TestActor *actor = new TestActor{registry};
|
||||
const auto &cactor = *actor;
|
||||
|
||||
ASSERT_EQ(®istry, &actor->registry());
|
||||
ASSERT_EQ(®istry, &cactor.registry());
|
||||
ASSERT_TRUE(registry.empty<Position>());
|
||||
ASSERT_TRUE(registry.empty<Velocity>());
|
||||
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<Position>());
|
||||
ASSERT_FALSE(actor->has<Velocity>());
|
||||
ASSERT_FALSE(actor.has<int>());
|
||||
|
||||
const auto &position = actor->set<Position>();
|
||||
const auto &cint = actor.assign<int>();
|
||||
const auto &cchar = actor.assign<char>();
|
||||
|
||||
ASSERT_EQ(&position, &actor->get<Position>());
|
||||
ASSERT_EQ(&position, &cactor.get<Position>());
|
||||
ASSERT_FALSE(registry.empty<Position>());
|
||||
ASSERT_TRUE(registry.empty<Velocity>());
|
||||
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<Position>());
|
||||
ASSERT_FALSE(actor->has<Velocity>());
|
||||
ASSERT_TRUE(actor.has<int>());
|
||||
ASSERT_TRUE(actor.has<char>());
|
||||
ASSERT_FALSE(actor.has<double>());
|
||||
|
||||
actor->unset<Position>();
|
||||
actor.remove<int>();
|
||||
|
||||
ASSERT_TRUE(registry.empty<Position>());
|
||||
ASSERT_TRUE(registry.empty<Velocity>());
|
||||
ASSERT_TRUE(registry.empty<int>());
|
||||
ASSERT_FALSE(registry.empty());
|
||||
ASSERT_FALSE(actor->has<Position>());
|
||||
ASSERT_FALSE(actor->has<Velocity>());
|
||||
ASSERT_FALSE(actor.has<int>());
|
||||
}
|
||||
|
||||
actor->set<Position>();
|
||||
actor->set<Velocity>();
|
||||
TEST(Actor, EntityLifetime) {
|
||||
entt::registry registry;
|
||||
auto *actor = new entt::actor{registry};
|
||||
actor->assign<int>();
|
||||
|
||||
ASSERT_FALSE(registry.empty<int>());
|
||||
ASSERT_FALSE(registry.empty());
|
||||
ASSERT_FALSE(registry.empty<Position>());
|
||||
ASSERT_FALSE(registry.empty<Velocity>());
|
||||
|
||||
registry.each([actor](const auto entity) {
|
||||
ASSERT_EQ(actor->entity(), entity);
|
||||
});
|
||||
|
||||
delete actor;
|
||||
|
||||
ASSERT_TRUE(registry.empty<int>());
|
||||
ASSERT_TRUE(registry.empty());
|
||||
ASSERT_TRUE(registry.empty<Position>());
|
||||
ASSERT_TRUE(registry.empty<Velocity>());
|
||||
}
|
||||
|
||||
@@ -1,355 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <iostream>
|
||||
#include <cstddef>
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
#include <entt/entity/registry.hpp>
|
||||
|
||||
struct Position {
|
||||
uint64_t x;
|
||||
uint64_t y;
|
||||
};
|
||||
|
||||
struct Velocity {
|
||||
uint64_t x;
|
||||
uint64_t y;
|
||||
};
|
||||
|
||||
template<std::size_t>
|
||||
struct Comp {};
|
||||
|
||||
struct Timer final {
|
||||
Timer(): start{std::chrono::system_clock::now()} {}
|
||||
|
||||
void elapsed() {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
std::cout << std::chrono::duration<double>(now - start).count() << " seconds" << std::endl;
|
||||
}
|
||||
|
||||
private:
|
||||
std::chrono::time_point<std::chrono::system_clock> start;
|
||||
};
|
||||
|
||||
TEST(Benchmark, Construct) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Constructing 10000000 entities" << std::endl;
|
||||
|
||||
Timer timer;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create();
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, Destroy) {
|
||||
entt::DefaultRegistry registry;
|
||||
std::vector<entt::DefaultRegistry::entity_type> entities{};
|
||||
|
||||
std::cout << "Destroying 10000000 entities" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
entities.push_back(registry.create());
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
for(auto entity: entities) {
|
||||
registry.destroy(entity);
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateCreateDeleteSingleComponent) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Looping 10000 times creating and deleting a random number of entities" << std::endl;
|
||||
|
||||
Timer timer;
|
||||
|
||||
auto view = registry.view<Position>();
|
||||
|
||||
for(int i = 0; i < 10000; i++) {
|
||||
for(int j = 0; j < 10000; j++) {
|
||||
registry.create<Position>();
|
||||
}
|
||||
|
||||
for(auto entity: view) {
|
||||
if(rand() % 2 == 0) {
|
||||
registry.destroy(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateSingleComponent10M) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, one component" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position>().each([](auto, auto &) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTwoComponents10M) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position, Velocity>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTwoComponents10MHalf) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components, half of the entities have all the components" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity>();
|
||||
if(i % 2) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTwoComponents10MOne) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components, only one entity has all the components" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity>();
|
||||
if(i == 5000000L) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTwoComponentsPersistent10M) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components, persistent view" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position, Velocity>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.persistent<Position, Velocity>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTwoComponentsPersistent10MHalf) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components, persistent view, half of the entities have all the components" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity>();
|
||||
if(i % 2) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.persistent<Position, Velocity>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTwoComponentsPersistent10MOne) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, two components, persistent view, only one entity has all the components" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity>();
|
||||
if(i == 5000000L) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.persistent<Position, Velocity>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateFiveComponents10M) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, five components" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTenComponents10M) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTenComponents10MHalf) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components, half of the entities have all the components" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
if(i % 2) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTenComponents10MOne) {
|
||||
entt::DefaultRegistry registry;
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components, only one entity has all the components" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
if(i == 5000000L) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateFiveComponentsPersistent10M) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, five components, persistent view" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.persistent<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTenComponentsPersistent10M) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components, persistent view" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.persistent<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTenComponentsPersistent10MHalf) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components, persistent view, half of the entities have all the components" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
if(i % 2) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.persistent<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, IterateTenComponentsPersistent10MOne) {
|
||||
entt::DefaultRegistry registry;
|
||||
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
|
||||
std::cout << "Iterating over 10000000 entities, ten components, persistent view, only one entity has all the components" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
||||
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
||||
if(i == 5000000L) { registry.assign<Position>(entity); }
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
registry.persistent<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, SortSingle) {
|
||||
entt::DefaultRegistry registry;
|
||||
std::vector<entt::DefaultRegistry::entity_type> entities{};
|
||||
|
||||
std::cout << "Sort 150000 entities, one component" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 150000L; i++) {
|
||||
auto entity = registry.create<Position>({ i, i });
|
||||
entities.push_back(entity);
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
registry.sort<Position>([](const auto &lhs, const auto &rhs) {
|
||||
return lhs.x < rhs.x && lhs.y < rhs.y;
|
||||
});
|
||||
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, SortMulti) {
|
||||
entt::DefaultRegistry registry;
|
||||
std::vector<entt::DefaultRegistry::entity_type> entities{};
|
||||
|
||||
std::cout << "Sort 150000 entities, two components" << std::endl;
|
||||
|
||||
for(uint64_t i = 0; i < 150000L; i++) {
|
||||
auto entity = registry.create<Position, Velocity>({ i, i }, { i, i });
|
||||
entities.push_back(entity);
|
||||
}
|
||||
|
||||
registry.sort<Position>([](const auto &lhs, const auto &rhs) {
|
||||
return lhs.x < rhs.x && lhs.y < rhs.y;
|
||||
});
|
||||
|
||||
Timer timer;
|
||||
|
||||
registry.sort<Velocity, Position>();
|
||||
|
||||
timer.elapsed();
|
||||
}
|
||||
24
test/entt/entity/entity.cpp
Normal file
24
test/entt/entity/entity.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#include <functional>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/entity.hpp>
|
||||
#include <entt/entity/registry.hpp>
|
||||
|
||||
TEST(Traits, Null) {
|
||||
entt::registry registry{};
|
||||
|
||||
const auto entity = registry.create();
|
||||
registry.assign<int>(entity, 42);
|
||||
|
||||
ASSERT_TRUE(~entt::entity{} == entt::null);
|
||||
|
||||
ASSERT_TRUE(entt::null == entt::null);
|
||||
ASSERT_FALSE(entt::null != entt::null);
|
||||
|
||||
ASSERT_FALSE(entity == entt::null);
|
||||
ASSERT_FALSE(entt::null == entity);
|
||||
|
||||
ASSERT_TRUE(entity != entt::null);
|
||||
ASSERT_TRUE(entt::null != entity);
|
||||
|
||||
ASSERT_FALSE(registry.valid(entt::null));
|
||||
}
|
||||
892
test/entt/entity/group.cpp
Normal file
892
test/entt/entity/group.cpp
Normal file
@@ -0,0 +1,892 @@
|
||||
#include <utility>
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
#include <gtest/gtest.h>
|
||||
#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);
|
||||
|
||||
for(auto entity: registry.group<>(entt::get<char, int, float>)) {
|
||||
(void)entity;
|
||||
FAIL();
|
||||
}
|
||||
|
||||
for(auto entity: registry.group<>(entt::get<double, char, int, float>)) {
|
||||
(void)entity;
|
||||
FAIL();
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
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>);
|
||||
|
||||
ASSERT_EQ(group.size(), decltype(group.size()){0});
|
||||
|
||||
const auto entity = registry.create();
|
||||
registry.assign<int>(entity, 0);
|
||||
registry.assign<char>(entity, 'c');
|
||||
|
||||
ASSERT_EQ(group.size(), decltype(group.size()){1});
|
||||
|
||||
ASSERT_TRUE((std::is_same_v<decltype(group.get<int>(0)), int &>));
|
||||
ASSERT_TRUE((std::is_same_v<decltype(group.get<const char>(0)), const char &>));
|
||||
ASSERT_TRUE((std::is_same_v<decltype(group.get<int, const char>(0)), std::tuple<int &, const char &>>));
|
||||
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, auto &&i, auto &&c) {
|
||||
ASSERT_TRUE((std::is_same_v<decltype(i), int &>));
|
||||
ASSERT_TRUE((std::is_same_v<decltype(c), const char &>));
|
||||
});
|
||||
}
|
||||
|
||||
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(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);
|
||||
|
||||
for(auto entity: registry.group<char, int>(entt::get<float>)) {
|
||||
(void)entity;
|
||||
FAIL();
|
||||
}
|
||||
|
||||
for(auto entity: registry.group<double, float>(entt::get<char, int>)) {
|
||||
(void)entity;
|
||||
FAIL();
|
||||
}
|
||||
}
|
||||
|
||||
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 auto lhs, const auto 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>);
|
||||
|
||||
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);
|
||||
|
||||
ASSERT_EQ(group.size(), decltype(group.size()){1});
|
||||
|
||||
ASSERT_TRUE((std::is_same_v<decltype(group.get<int>(0)), int &>));
|
||||
ASSERT_TRUE((std::is_same_v<decltype(group.get<const char>(0)), const char &>));
|
||||
ASSERT_TRUE((std::is_same_v<decltype(group.get<double>(0)), double &>));
|
||||
ASSERT_TRUE((std::is_same_v<decltype(group.get<const float>(0)), const float &>));
|
||||
ASSERT_TRUE((std::is_same_v<decltype(group.get<int, const char, double, const float>(0)), std::tuple<int &, const char &, double &, const float &>>));
|
||||
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, auto &&i, auto &&c, auto &&d, auto &&f) {
|
||||
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 &>));
|
||||
});
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
106
test/entt/entity/helper.cpp
Normal file
106
test/entt/entity/helper.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/hashed_string.hpp>
|
||||
#include <entt/entity/helper.hpp>
|
||||
#include <entt/entity/registry.hpp>
|
||||
#include <entt/core/type_traits.hpp>
|
||||
|
||||
TEST(Helper, AsView) {
|
||||
entt::registry registry;
|
||||
const entt::registry cregistry;
|
||||
|
||||
([](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, AsGroup) {
|
||||
entt::registry registry;
|
||||
const entt::registry cregistry;
|
||||
|
||||
([](entt::group<entt::get_t<>, double, float>) {})(entt::as_group{registry});
|
||||
([](entt::group<entt::get_t<>, const double, float>) {})(entt::as_group{registry});
|
||||
([](entt::group<entt::get_t<>, const double, const float>) {})(entt::as_group{cregistry});
|
||||
}
|
||||
|
||||
TEST(Helper, Dependency) {
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
entt::connect<double, float>(registry.on_construct<int>());
|
||||
|
||||
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::disconnect<double, float>(registry.on_construct<int>());
|
||||
registry.assign<int>(entity);
|
||||
|
||||
ASSERT_FALSE(registry.has<double>(entity));
|
||||
ASSERT_FALSE(registry.has<float>(entity));
|
||||
}
|
||||
|
||||
TEST(Dependency, MultipleListenersOnTheSameType) {
|
||||
entt::registry registry;
|
||||
entt::connect<double>(registry.on_construct<int>());
|
||||
entt::connect<char>(registry.on_construct<int>());
|
||||
|
||||
const auto entity = registry.create();
|
||||
registry.assign<int>(entity);
|
||||
|
||||
ASSERT_TRUE(registry.has<double>(entity));
|
||||
ASSERT_TRUE(registry.has<char>(entity));
|
||||
}
|
||||
|
||||
TEST(Helper, Tag) {
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
registry.assign<entt::tag<"foobar"_hs>>(entity);
|
||||
registry.assign<int>(entity, 42);
|
||||
int counter{};
|
||||
|
||||
ASSERT_FALSE(registry.has<entt::tag<"barfoo"_hs>>(entity));
|
||||
ASSERT_TRUE(registry.has<entt::tag<"foobar"_hs>>(entity));
|
||||
|
||||
for(auto entt: registry.view<int, entt::tag<"foobar"_hs>>()) {
|
||||
(void)entt;
|
||||
++counter;
|
||||
}
|
||||
|
||||
ASSERT_NE(counter, 0);
|
||||
|
||||
for(auto entt: registry.view<entt::tag<"foobar"_hs>>()) {
|
||||
(void)entt;
|
||||
--counter;
|
||||
}
|
||||
|
||||
ASSERT_EQ(counter, 0);
|
||||
}
|
||||
161
test/entt/entity/prototype.cpp
Normal file
161
test/entt/entity/prototype.cpp
Normal file
@@ -0,0 +1,161 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/prototype.hpp>
|
||||
#include <entt/entity/registry.hpp>
|
||||
|
||||
TEST(Prototype, SameRegistry) {
|
||||
entt::registry registry;
|
||||
entt::prototype prototype{registry};
|
||||
|
||||
ASSERT_EQ(®istry, &prototype.backend());
|
||||
ASSERT_EQ(®istry, &std::as_const(prototype).backend());
|
||||
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(std::as_const(prototype).get<char>(), 'c');
|
||||
ASSERT_EQ(std::get<0>(prototype.get<int, char>()), 3);
|
||||
ASSERT_EQ(std::get<1>(std::as_const(prototype).get<int, char>()), 'c');
|
||||
|
||||
ASSERT_NE(prototype.try_get<int>(), nullptr);
|
||||
ASSERT_NE(prototype.try_get<char>(), nullptr);
|
||||
ASSERT_EQ(prototype.try_get<double>(), nullptr);
|
||||
ASSERT_EQ(*prototype.try_get<int>(), 3);
|
||||
ASSERT_EQ(*std::as_const(prototype).try_get<char>(), 'c');
|
||||
ASSERT_EQ(*std::get<0>(prototype.try_get<int, char, double>()), 3);
|
||||
ASSERT_EQ(*std::get<1>(std::as_const(prototype).try_get<int, char, double>()), '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.assign_or_replace(e1);
|
||||
|
||||
ASSERT_EQ(registry.get<char>(e1), 'c');
|
||||
}
|
||||
|
||||
TEST(Prototype, OtherRegistry) {
|
||||
entt::registry registry;
|
||||
entt::registry repository;
|
||||
entt::prototype prototype{repository};
|
||||
|
||||
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(std::as_const(prototype).get<char>(), 'c');
|
||||
ASSERT_EQ(std::get<0>(prototype.get<int, char>()), 3);
|
||||
ASSERT_EQ(std::get<1>(std::as_const(prototype).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.assign_or_replace(registry, e1);
|
||||
|
||||
ASSERT_EQ(registry.get<char>(e1), 'c');
|
||||
}
|
||||
|
||||
TEST(Prototype, RAII) {
|
||||
entt::registry registry;
|
||||
|
||||
{
|
||||
entt::prototype prototype{registry};
|
||||
prototype.set<int>(0);
|
||||
|
||||
ASSERT_FALSE(registry.empty());
|
||||
}
|
||||
|
||||
ASSERT_TRUE(registry.empty());
|
||||
}
|
||||
|
||||
TEST(Prototype, MoveConstructionAssignment) {
|
||||
entt::registry registry;
|
||||
|
||||
entt::prototype prototype{registry};
|
||||
prototype.set<int>(0);
|
||||
auto other{std::move(prototype)};
|
||||
const auto e0 = other();
|
||||
|
||||
ASSERT_EQ(registry.size(), entt::registry::size_type{2});
|
||||
ASSERT_TRUE(registry.has<int>(e0));
|
||||
|
||||
prototype = std::move(other);
|
||||
const auto e1 = prototype();
|
||||
|
||||
ASSERT_EQ(registry.size(), entt::registry::size_type{3});
|
||||
ASSERT_TRUE(registry.has<int>(e1));
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
208
test/entt/entity/runtime_view.cpp
Normal file
208
test/entt/entity/runtime_view.cpp
Normal file
@@ -0,0 +1,208 @@
|
||||
#include <iterator>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/registry.hpp>
|
||||
#include <entt/entity/runtime_view.hpp>
|
||||
|
||||
TEST(RuntimeView, Functionalities) {
|
||||
entt::registry registry;
|
||||
using component_type = typename decltype(registry)::component_type;
|
||||
|
||||
// forces the creation of the pools
|
||||
registry.reserve<int>(0);
|
||||
registry.reserve<char>(0);
|
||||
|
||||
component_type types[] = { registry.type<int>(), registry.type<char>() };
|
||||
auto view = registry.runtime_view(std::begin(types), std::end(types));
|
||||
|
||||
ASSERT_TRUE(view.empty());
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<char>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<int>(e1);
|
||||
|
||||
ASSERT_FALSE(view.empty());
|
||||
|
||||
registry.assign<char>(e1);
|
||||
|
||||
auto it = registry.runtime_view(std::begin(types), std::end(types)).begin();
|
||||
|
||||
ASSERT_EQ(*it, e1);
|
||||
ASSERT_EQ(++it, (registry.runtime_view(std::begin(types), std::end(types)).end()));
|
||||
|
||||
ASSERT_NO_THROW((registry.runtime_view(std::begin(types), std::end(types)).begin()++));
|
||||
ASSERT_NO_THROW((++registry.runtime_view(std::begin(types), std::end(types)).begin()));
|
||||
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
ASSERT_EQ(view.size(), decltype(view.size()){1});
|
||||
|
||||
registry.get<char>(e0) = '1';
|
||||
registry.get<char>(e1) = '2';
|
||||
registry.get<int>(e1) = 42;
|
||||
|
||||
for(auto entity: view) {
|
||||
ASSERT_EQ(registry.get<int>(entity), 42);
|
||||
ASSERT_EQ(registry.get<char>(entity), '2');
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RuntimeView, Iterator) {
|
||||
entt::registry registry;
|
||||
using component_type = typename decltype(registry)::component_type;
|
||||
|
||||
const auto entity = registry.create();
|
||||
registry.assign<int>(entity);
|
||||
registry.assign<char>(entity);
|
||||
|
||||
component_type types[] = { registry.type<int>(), registry.type<char>() };
|
||||
auto view = registry.runtime_view(std::begin(types), std::end(types));
|
||||
using iterator_type = typename decltype(view)::iterator_type;
|
||||
|
||||
iterator_type end{view.begin()};
|
||||
iterator_type begin{};
|
||||
begin = view.end();
|
||||
std::swap(begin, end);
|
||||
|
||||
ASSERT_EQ(begin, view.begin());
|
||||
ASSERT_EQ(end, view.end());
|
||||
ASSERT_NE(begin, end);
|
||||
|
||||
ASSERT_EQ(view.begin()++, view.begin());
|
||||
ASSERT_EQ(++view.begin(), view.end());
|
||||
}
|
||||
|
||||
TEST(RuntimeView, Contains) {
|
||||
entt::registry registry;
|
||||
using component_type = typename decltype(registry)::component_type;
|
||||
|
||||
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);
|
||||
|
||||
component_type types[] = { registry.type<int>(), registry.type<char>() };
|
||||
auto view = registry.runtime_view(std::begin(types), std::end(types));
|
||||
|
||||
ASSERT_FALSE(view.contains(e0));
|
||||
ASSERT_TRUE(view.contains(e1));
|
||||
}
|
||||
|
||||
TEST(RuntimeView, Empty) {
|
||||
entt::registry registry;
|
||||
using component_type = typename decltype(registry)::component_type;
|
||||
|
||||
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);
|
||||
|
||||
component_type types[] = { registry.type<char>(), registry.type<int>(), registry.type<float>() };
|
||||
auto view = registry.runtime_view(std::begin(types), std::end(types));
|
||||
|
||||
for(auto entity: view) {
|
||||
(void)entity;
|
||||
FAIL();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RuntimeView, Each) {
|
||||
entt::registry registry;
|
||||
using component_type = typename decltype(registry)::component_type;
|
||||
|
||||
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);
|
||||
|
||||
component_type types[] = { registry.type<int>(), registry.type<char>() };
|
||||
auto view = registry.runtime_view(std::begin(types), std::end(types));
|
||||
std::size_t cnt = 0;
|
||||
|
||||
view.each([&cnt](auto) { ++cnt; });
|
||||
|
||||
ASSERT_EQ(cnt, std::size_t{2});
|
||||
}
|
||||
|
||||
TEST(RuntimeView, EachWithHoles) {
|
||||
entt::registry registry;
|
||||
using component_type = typename decltype(registry)::component_type;
|
||||
|
||||
const auto e0 = registry.create();
|
||||
const auto e1 = registry.create();
|
||||
const auto e2 = registry.create();
|
||||
|
||||
registry.assign<char>(e0, '0');
|
||||
registry.assign<char>(e1, '1');
|
||||
|
||||
registry.assign<int>(e0, 0);
|
||||
registry.assign<int>(e2, 2);
|
||||
|
||||
component_type types[] = { registry.type<int>(), registry.type<char>() };
|
||||
auto view = registry.runtime_view(std::begin(types), std::end(types));
|
||||
|
||||
view.each([e0](auto entity) {
|
||||
ASSERT_EQ(e0, entity);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(RuntimeView, MissingPool) {
|
||||
entt::registry registry;
|
||||
using component_type = typename decltype(registry)::component_type;
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<int>(e0);
|
||||
|
||||
component_type types[] = { registry.type<int>(), registry.type<char>() };
|
||||
auto view = registry.runtime_view(std::begin(types), std::end(types));
|
||||
|
||||
ASSERT_TRUE(view.empty());
|
||||
ASSERT_EQ(view.size(), decltype(view.size()){0});
|
||||
|
||||
registry.assign<char>(e0);
|
||||
|
||||
ASSERT_TRUE(view.empty());
|
||||
ASSERT_EQ(view.size(), decltype(view.size()){0});
|
||||
ASSERT_FALSE(view.contains(e0));
|
||||
|
||||
view.each([](auto) { FAIL(); });
|
||||
|
||||
for(auto entity: view) {
|
||||
(void)entity;
|
||||
FAIL();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RuntimeView, EmptyRange) {
|
||||
entt::registry registry;
|
||||
using component_type = typename decltype(registry)::component_type;
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<int>(e0);
|
||||
|
||||
const component_type *ptr = nullptr;
|
||||
auto view = registry.runtime_view(ptr, ptr);
|
||||
|
||||
ASSERT_TRUE(view.empty());
|
||||
ASSERT_EQ(view.size(), decltype(view.size()){0});
|
||||
ASSERT_FALSE(view.contains(e0));
|
||||
|
||||
view.each([](auto) { FAIL(); });
|
||||
|
||||
for(auto entity: view) {
|
||||
(void)entity;
|
||||
FAIL();
|
||||
}
|
||||
}
|
||||
495
test/entt/entity/snapshot.cpp
Normal file
495
test/entt/entity/snapshot.cpp
Normal file
@@ -0,0 +1,495 @@
|
||||
#include <tuple>
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/registry.hpp>
|
||||
#include <entt/entity/entity.hpp>
|
||||
|
||||
template<typename Storage>
|
||||
struct output_archive {
|
||||
output_archive(Storage &instance)
|
||||
: storage{instance}
|
||||
{}
|
||||
|
||||
template<typename... Value>
|
||||
void operator()(const Value &... value) {
|
||||
(std::get<std::queue<Value>>(storage).push(value), ...);
|
||||
}
|
||||
|
||||
private:
|
||||
Storage &storage;
|
||||
};
|
||||
|
||||
template<typename Storage>
|
||||
struct input_archive {
|
||||
input_archive(Storage &instance)
|
||||
: storage{instance}
|
||||
{}
|
||||
|
||||
template<typename... Value>
|
||||
void operator()(Value &... value) {
|
||||
auto assign = [this](auto &val) {
|
||||
auto &queue = std::get<std::queue<std::decay_t<decltype(val)>>>(storage);
|
||||
val = queue.front();
|
||||
queue.pop();
|
||||
};
|
||||
|
||||
(assign(value), ...);
|
||||
}
|
||||
|
||||
private:
|
||||
Storage &storage;
|
||||
};
|
||||
|
||||
struct a_component {};
|
||||
|
||||
struct another_component {
|
||||
int key;
|
||||
int value;
|
||||
};
|
||||
|
||||
struct what_a_component {
|
||||
entt::entity bar;
|
||||
std::vector<entt::entity> quux;
|
||||
};
|
||||
|
||||
TEST(Snapshot, Dump) {
|
||||
entt::registry registry;
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<int>(e0, 42);
|
||||
registry.assign<char>(e0, 'c');
|
||||
registry.assign<double>(e0, .1);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
|
||||
const auto e2 = registry.create();
|
||||
registry.assign<int>(e2, 3);
|
||||
|
||||
const auto e3 = registry.create();
|
||||
registry.assign<a_component>(e3);
|
||||
registry.assign<char>(e3, '0');
|
||||
|
||||
registry.destroy(e1);
|
||||
auto v1 = registry.current(e1);
|
||||
|
||||
using storage_type = std::tuple<
|
||||
std::queue<entt::entity>,
|
||||
std::queue<int>,
|
||||
std::queue<char>,
|
||||
std::queue<double>,
|
||||
std::queue<a_component>,
|
||||
std::queue<another_component>
|
||||
>;
|
||||
|
||||
storage_type storage;
|
||||
output_archive<storage_type> output{storage};
|
||||
input_archive<storage_type> input{storage};
|
||||
|
||||
registry.snapshot()
|
||||
.entities(output)
|
||||
.destroyed(output)
|
||||
.component<int, char, double, a_component, another_component>(output);
|
||||
|
||||
registry.reset();
|
||||
|
||||
ASSERT_FALSE(registry.valid(e0));
|
||||
ASSERT_FALSE(registry.valid(e1));
|
||||
ASSERT_FALSE(registry.valid(e2));
|
||||
ASSERT_FALSE(registry.valid(e3));
|
||||
|
||||
registry.loader()
|
||||
.entities(input)
|
||||
.destroyed(input)
|
||||
.component<int, char, double, a_component, another_component>(input)
|
||||
.orphans();
|
||||
|
||||
ASSERT_TRUE(registry.valid(e0));
|
||||
ASSERT_FALSE(registry.valid(e1));
|
||||
ASSERT_TRUE(registry.valid(e2));
|
||||
ASSERT_TRUE(registry.valid(e3));
|
||||
|
||||
ASSERT_FALSE(registry.orphan(e0));
|
||||
ASSERT_FALSE(registry.orphan(e2));
|
||||
ASSERT_FALSE(registry.orphan(e3));
|
||||
|
||||
ASSERT_EQ(registry.get<int>(e0), 42);
|
||||
ASSERT_EQ(registry.get<char>(e0), 'c');
|
||||
ASSERT_EQ(registry.get<double>(e0), .1);
|
||||
ASSERT_EQ(registry.current(e1), v1);
|
||||
ASSERT_EQ(registry.get<int>(e2), 3);
|
||||
ASSERT_EQ(registry.get<char>(e3), '0');
|
||||
ASSERT_TRUE(registry.has<a_component>(e3));
|
||||
|
||||
ASSERT_TRUE(registry.empty<another_component>());
|
||||
}
|
||||
|
||||
TEST(Snapshot, Partial) {
|
||||
entt::registry registry;
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<int>(e0, 42);
|
||||
registry.assign<char>(e0, 'c');
|
||||
registry.assign<double>(e0, .1);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
|
||||
const auto e2 = registry.create();
|
||||
registry.assign<int>(e2, 3);
|
||||
|
||||
const auto e3 = registry.create();
|
||||
registry.assign<char>(e3, '0');
|
||||
|
||||
registry.destroy(e1);
|
||||
auto v1 = registry.current(e1);
|
||||
|
||||
using storage_type = std::tuple<
|
||||
std::queue<entt::entity>,
|
||||
std::queue<int>,
|
||||
std::queue<char>,
|
||||
std::queue<double>
|
||||
>;
|
||||
|
||||
storage_type storage;
|
||||
output_archive<storage_type> output{storage};
|
||||
input_archive<storage_type> input{storage};
|
||||
|
||||
registry.snapshot()
|
||||
.entities(output)
|
||||
.destroyed(output)
|
||||
.component<char, int>(output);
|
||||
|
||||
registry.reset();
|
||||
|
||||
ASSERT_FALSE(registry.valid(e0));
|
||||
ASSERT_FALSE(registry.valid(e1));
|
||||
ASSERT_FALSE(registry.valid(e2));
|
||||
ASSERT_FALSE(registry.valid(e3));
|
||||
|
||||
registry.loader()
|
||||
.entities(input)
|
||||
.destroyed(input)
|
||||
.component<char, int>(input);
|
||||
|
||||
ASSERT_TRUE(registry.valid(e0));
|
||||
ASSERT_FALSE(registry.valid(e1));
|
||||
ASSERT_TRUE(registry.valid(e2));
|
||||
ASSERT_TRUE(registry.valid(e3));
|
||||
|
||||
ASSERT_EQ(registry.get<int>(e0), 42);
|
||||
ASSERT_EQ(registry.get<char>(e0), 'c');
|
||||
ASSERT_FALSE(registry.has<double>(e0));
|
||||
ASSERT_EQ(registry.current(e1), v1);
|
||||
ASSERT_EQ(registry.get<int>(e2), 3);
|
||||
ASSERT_EQ(registry.get<char>(e3), '0');
|
||||
|
||||
registry.snapshot()
|
||||
.entities(output)
|
||||
.destroyed(output);
|
||||
|
||||
registry.reset();
|
||||
|
||||
ASSERT_FALSE(registry.valid(e0));
|
||||
ASSERT_FALSE(registry.valid(e1));
|
||||
ASSERT_FALSE(registry.valid(e2));
|
||||
ASSERT_FALSE(registry.valid(e3));
|
||||
|
||||
registry.loader()
|
||||
.entities(input)
|
||||
.destroyed(input)
|
||||
.orphans();
|
||||
|
||||
ASSERT_FALSE(registry.valid(e0));
|
||||
ASSERT_FALSE(registry.valid(e1));
|
||||
ASSERT_FALSE(registry.valid(e2));
|
||||
ASSERT_FALSE(registry.valid(e3));
|
||||
}
|
||||
|
||||
TEST(Snapshot, Iterator) {
|
||||
entt::registry registry;
|
||||
|
||||
for(auto i = 0; i < 50; ++i) {
|
||||
const auto entity = registry.create();
|
||||
registry.assign<another_component>(entity, i, i);
|
||||
|
||||
if(i % 2) {
|
||||
registry.assign<a_component>(entity);
|
||||
}
|
||||
}
|
||||
|
||||
using storage_type = std::tuple<
|
||||
std::queue<entt::entity>,
|
||||
std::queue<another_component>
|
||||
>;
|
||||
|
||||
storage_type storage;
|
||||
output_archive<storage_type> output{storage};
|
||||
input_archive<storage_type> input{storage};
|
||||
|
||||
const auto view = registry.view<a_component>();
|
||||
const auto size = view.size();
|
||||
|
||||
registry.snapshot().component<another_component>(output, view.begin(), view.end());
|
||||
registry.reset();
|
||||
registry.loader().component<another_component>(input);
|
||||
|
||||
ASSERT_EQ(registry.view<another_component>().size(), size);
|
||||
|
||||
registry.view<another_component>().each([](const auto entity, const auto &) {
|
||||
ASSERT_TRUE(entity % 2);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(Snapshot, Continuous) {
|
||||
entt::registry src;
|
||||
entt::registry dst;
|
||||
|
||||
entt::continuous_loader loader{dst};
|
||||
|
||||
std::vector<entt::entity> entities;
|
||||
entt::entity entity;
|
||||
|
||||
using storage_type = std::tuple<
|
||||
std::queue<entt::entity>,
|
||||
std::queue<another_component>,
|
||||
std::queue<what_a_component>,
|
||||
std::queue<double>
|
||||
>;
|
||||
|
||||
storage_type storage;
|
||||
output_archive<storage_type> output{storage};
|
||||
input_archive<storage_type> input{storage};
|
||||
|
||||
for(int i = 0; i < 10; ++i) {
|
||||
src.create();
|
||||
}
|
||||
|
||||
src.reset();
|
||||
|
||||
for(int i = 0; i < 5; ++i) {
|
||||
entity = src.create();
|
||||
entities.push_back(entity);
|
||||
|
||||
src.assign<a_component>(entity);
|
||||
src.assign<another_component>(entity, i, i);
|
||||
|
||||
if(i % 2) {
|
||||
src.assign<what_a_component>(entity, entity);
|
||||
}
|
||||
}
|
||||
|
||||
src.view<what_a_component>().each([&entities](auto, auto &what_a_component) {
|
||||
what_a_component.quux.insert(what_a_component.quux.begin(), entities.begin(), entities.end());
|
||||
});
|
||||
|
||||
entity = dst.create();
|
||||
dst.assign<a_component>(entity);
|
||||
dst.assign<another_component>(entity, -1, -1);
|
||||
|
||||
src.snapshot()
|
||||
.entities(output)
|
||||
.destroyed(output)
|
||||
.component<a_component, another_component, what_a_component>(output);
|
||||
|
||||
loader.entities(input)
|
||||
.destroyed(input)
|
||||
.component<a_component, another_component, what_a_component>(input, &what_a_component::bar, &what_a_component::quux)
|
||||
.orphans();
|
||||
|
||||
decltype(dst.size()) a_component_cnt{};
|
||||
decltype(dst.size()) another_component_cnt{};
|
||||
decltype(dst.size()) what_a_component_cnt{};
|
||||
|
||||
dst.each([&dst, &a_component_cnt](auto entt) {
|
||||
ASSERT_TRUE(dst.has<a_component>(entt));
|
||||
++a_component_cnt;
|
||||
});
|
||||
|
||||
dst.view<another_component>().each([&another_component_cnt](auto, const auto &component) {
|
||||
ASSERT_EQ(component.value, component.key < 0 ? -1 : component.key);
|
||||
++another_component_cnt;
|
||||
});
|
||||
|
||||
dst.view<what_a_component>().each([&dst, &what_a_component_cnt](auto entt, const auto &component) {
|
||||
ASSERT_EQ(entt, component.bar);
|
||||
|
||||
for(auto child: component.quux) {
|
||||
ASSERT_TRUE(dst.valid(child));
|
||||
}
|
||||
|
||||
++what_a_component_cnt;
|
||||
});
|
||||
|
||||
src.view<another_component>().each([](auto, auto &component) {
|
||||
component.value = 2 * component.key;
|
||||
});
|
||||
|
||||
auto size = dst.size();
|
||||
|
||||
src.snapshot()
|
||||
.entities(output)
|
||||
.destroyed(output)
|
||||
.component<a_component, what_a_component, another_component>(output);
|
||||
|
||||
loader.entities(input)
|
||||
.destroyed(input)
|
||||
.component<a_component, what_a_component, another_component>(input, &what_a_component::bar, &what_a_component::quux)
|
||||
.orphans();
|
||||
|
||||
ASSERT_EQ(size, dst.size());
|
||||
|
||||
ASSERT_EQ(dst.size<a_component>(), a_component_cnt);
|
||||
ASSERT_EQ(dst.size<another_component>(), another_component_cnt);
|
||||
ASSERT_EQ(dst.size<what_a_component>(), what_a_component_cnt);
|
||||
|
||||
dst.view<another_component>().each([](auto, auto &component) {
|
||||
ASSERT_EQ(component.value, component.key < 0 ? -1 : (2 * component.key));
|
||||
});
|
||||
|
||||
entity = src.create();
|
||||
|
||||
src.view<what_a_component>().each([entity](auto, auto &component) {
|
||||
component.bar = entity;
|
||||
});
|
||||
|
||||
src.snapshot()
|
||||
.entities(output)
|
||||
.destroyed(output)
|
||||
.component<what_a_component, a_component, another_component>(output);
|
||||
|
||||
loader.entities(input)
|
||||
.destroyed(input)
|
||||
.component<what_a_component, a_component, another_component>(input, &what_a_component::bar, &what_a_component::quux)
|
||||
.orphans();
|
||||
|
||||
dst.view<what_a_component>().each([&loader, entity](auto, auto &component) {
|
||||
ASSERT_EQ(component.bar, loader.map(entity));
|
||||
});
|
||||
|
||||
entities.clear();
|
||||
for(auto entt: src.view<a_component>()) {
|
||||
entities.push_back(entt);
|
||||
}
|
||||
|
||||
src.destroy(entity);
|
||||
loader.shrink();
|
||||
|
||||
src.snapshot()
|
||||
.entities(output)
|
||||
.destroyed(output)
|
||||
.component<a_component, another_component, what_a_component>(output);
|
||||
|
||||
loader.entities(input)
|
||||
.destroyed(input)
|
||||
.component<a_component, another_component, what_a_component>(input, &what_a_component::bar, &what_a_component::quux)
|
||||
.orphans()
|
||||
.shrink();
|
||||
|
||||
dst.view<what_a_component>().each([&dst](auto, auto &component) {
|
||||
ASSERT_FALSE(dst.valid(component.bar));
|
||||
});
|
||||
|
||||
ASSERT_FALSE(loader.has(entity));
|
||||
|
||||
entity = src.create();
|
||||
|
||||
src.view<what_a_component>().each([entity](auto, auto &component) {
|
||||
component.bar = entity;
|
||||
});
|
||||
|
||||
dst.reset<a_component>();
|
||||
a_component_cnt = src.size<a_component>();
|
||||
|
||||
src.snapshot()
|
||||
.entities(output)
|
||||
.destroyed(output)
|
||||
.component<a_component, what_a_component, another_component>(output);
|
||||
|
||||
loader.entities(input)
|
||||
.destroyed(input)
|
||||
.component<a_component, what_a_component, another_component>(input, &what_a_component::bar, &what_a_component::quux)
|
||||
.orphans();
|
||||
|
||||
ASSERT_EQ(dst.size<a_component>(), a_component_cnt);
|
||||
|
||||
src.reset<a_component>();
|
||||
a_component_cnt = {};
|
||||
|
||||
src.snapshot()
|
||||
.entities(output)
|
||||
.destroyed(output)
|
||||
.component<what_a_component, a_component, another_component>(output);
|
||||
|
||||
loader.entities(input)
|
||||
.destroyed(input)
|
||||
.component<what_a_component, a_component, another_component>(input, &what_a_component::bar, &what_a_component::quux)
|
||||
.orphans();
|
||||
|
||||
ASSERT_EQ(dst.size<a_component>(), a_component_cnt);
|
||||
}
|
||||
|
||||
TEST(Snapshot, MoreOnShrink) {
|
||||
entt::registry src;
|
||||
entt::registry dst;
|
||||
|
||||
entt::continuous_loader loader{dst};
|
||||
|
||||
using storage_type = std::tuple<std::queue<entt::entity>>;
|
||||
|
||||
storage_type storage;
|
||||
output_archive<storage_type> output{storage};
|
||||
input_archive<storage_type> input{storage};
|
||||
|
||||
auto entity = src.create();
|
||||
src.snapshot().entities(output);
|
||||
loader.entities(input).shrink();
|
||||
|
||||
ASSERT_TRUE(dst.valid(entity));
|
||||
|
||||
loader.shrink();
|
||||
|
||||
ASSERT_FALSE(dst.valid(entity));
|
||||
}
|
||||
|
||||
TEST(Snapshot, SyncDataMembers) {
|
||||
entt::registry src;
|
||||
entt::registry dst;
|
||||
|
||||
entt::continuous_loader loader{dst};
|
||||
|
||||
using storage_type = std::tuple<
|
||||
std::queue<entt::entity>,
|
||||
std::queue<what_a_component>
|
||||
>;
|
||||
|
||||
storage_type storage;
|
||||
output_archive<storage_type> output{storage};
|
||||
input_archive<storage_type> input{storage};
|
||||
|
||||
src.create();
|
||||
src.create();
|
||||
|
||||
src.reset();
|
||||
|
||||
auto parent = src.create();
|
||||
auto child = src.create();
|
||||
|
||||
src.assign<what_a_component>(parent, entt::null);
|
||||
src.assign<what_a_component>(child, parent).quux.push_back(child);
|
||||
|
||||
src.snapshot().entities(output).component<what_a_component>(output);
|
||||
loader.entities(input).component<what_a_component>(input, &what_a_component::bar, &what_a_component::quux);
|
||||
|
||||
ASSERT_FALSE(dst.valid(parent));
|
||||
ASSERT_FALSE(dst.valid(child));
|
||||
|
||||
ASSERT_TRUE(dst.has<what_a_component>(loader.map(parent)));
|
||||
ASSERT_TRUE(dst.has<what_a_component>(loader.map(child)));
|
||||
|
||||
ASSERT_EQ(dst.get<what_a_component>(loader.map(parent)).bar, static_cast<entt::entity>(entt::null));
|
||||
|
||||
const auto &component = dst.get<what_a_component>(loader.map(child));
|
||||
|
||||
ASSERT_EQ(component.bar, loader.map(parent));
|
||||
ASSERT_EQ(component.quux[0], loader.map(child));
|
||||
}
|
||||
@@ -1,11 +1,22 @@
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
#include <iterator>
|
||||
#include <type_traits>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/sparse_set.hpp>
|
||||
|
||||
TEST(SparseSetNoType, Functionalities) {
|
||||
entt::SparseSet<unsigned int> set;
|
||||
struct empty_type {};
|
||||
struct boxed_int { int value; };
|
||||
|
||||
TEST(SparseSet, Functionalities) {
|
||||
entt::sparse_set<std::uint64_t> set;
|
||||
|
||||
set.reserve(42);
|
||||
|
||||
ASSERT_EQ(set.capacity(), 42);
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end());
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
@@ -16,6 +27,7 @@ TEST(SparseSetNoType, Functionalities) {
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 1u);
|
||||
ASSERT_NE(std::as_const(set).begin(), std::as_const(set).end());
|
||||
ASSERT_NE(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_TRUE(set.has(42));
|
||||
@@ -25,29 +37,175 @@ TEST(SparseSetNoType, Functionalities) {
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end());
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
|
||||
set.construct(42);
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.get(42), 0u);
|
||||
|
||||
set.reset();
|
||||
ASSERT_TRUE(std::is_move_constructible_v<decltype(set)>);
|
||||
ASSERT_TRUE(std::is_move_assignable_v<decltype(set)>);
|
||||
|
||||
entt::sparse_set<std::uint64_t> cpy{set};
|
||||
set = cpy;
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_FALSE(cpy.empty());
|
||||
ASSERT_EQ(set.get(42), 0u);
|
||||
ASSERT_EQ(cpy.get(42), 0u);
|
||||
|
||||
entt::sparse_set<std::uint64_t> other{std::move(set)};
|
||||
|
||||
set = std::move(other);
|
||||
other = std::move(set);
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
ASSERT_FALSE(other.empty());
|
||||
ASSERT_EQ(other.get(42), 0u);
|
||||
|
||||
(void)entt::SparseSet<unsigned int>{std::move(set)};
|
||||
entt::SparseSet<unsigned int> other;
|
||||
other = std::move(set);
|
||||
other.reset();
|
||||
|
||||
ASSERT_TRUE(other.empty());
|
||||
ASSERT_EQ(other.size(), 0u);
|
||||
ASSERT_EQ(std::as_const(other).begin(), std::as_const(other).end());
|
||||
ASSERT_EQ(other.begin(), other.end());
|
||||
ASSERT_FALSE(other.has(0));
|
||||
ASSERT_FALSE(other.has(42));
|
||||
}
|
||||
|
||||
TEST(SparseSetNoType, DataBeginEnd) {
|
||||
entt::SparseSet<unsigned int> set;
|
||||
TEST(SparseSet, Pagination) {
|
||||
entt::sparse_set<std::uint64_t> set;
|
||||
constexpr auto entt_per_page = ENTT_PAGE_SIZE / sizeof(std::uint64_t);
|
||||
|
||||
ASSERT_EQ(set.extent(), 0);
|
||||
|
||||
set.construct(entt_per_page-1);
|
||||
|
||||
ASSERT_EQ(set.extent(), entt_per_page);
|
||||
ASSERT_TRUE(set.has(entt_per_page-1));
|
||||
|
||||
set.construct(entt_per_page);
|
||||
|
||||
ASSERT_EQ(set.extent(), 2 * entt_per_page);
|
||||
ASSERT_TRUE(set.has(entt_per_page-1));
|
||||
ASSERT_TRUE(set.has(entt_per_page));
|
||||
ASSERT_FALSE(set.has(entt_per_page+1));
|
||||
|
||||
set.destroy(entt_per_page-1);
|
||||
|
||||
ASSERT_EQ(set.extent(), 2 * entt_per_page);
|
||||
ASSERT_FALSE(set.has(entt_per_page-1));
|
||||
ASSERT_TRUE(set.has(entt_per_page));
|
||||
|
||||
set.shrink_to_fit();
|
||||
set.destroy(entt_per_page);
|
||||
|
||||
ASSERT_EQ(set.extent(), 2 * entt_per_page);
|
||||
ASSERT_FALSE(set.has(entt_per_page-1));
|
||||
ASSERT_FALSE(set.has(entt_per_page));
|
||||
|
||||
set.shrink_to_fit();
|
||||
|
||||
ASSERT_EQ(set.extent(), 0);
|
||||
}
|
||||
|
||||
TEST(SparseSet, BatchAdd) {
|
||||
entt::sparse_set<std::uint64_t> set;
|
||||
entt::sparse_set<std::uint64_t>::entity_type entities[2];
|
||||
|
||||
entities[0] = 3;
|
||||
entities[1] = 42;
|
||||
|
||||
set.construct(12);
|
||||
set.batch(std::begin(entities), std::end(entities));
|
||||
set.construct(24);
|
||||
|
||||
ASSERT_TRUE(set.has(entities[0]));
|
||||
ASSERT_TRUE(set.has(entities[1]));
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(9));
|
||||
ASSERT_TRUE(set.has(12));
|
||||
ASSERT_TRUE(set.has(24));
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 4u);
|
||||
ASSERT_EQ(set.get(12), 0u);
|
||||
ASSERT_EQ(set.get(entities[0]), 1u);
|
||||
ASSERT_EQ(set.get(entities[1]), 2u);
|
||||
ASSERT_EQ(set.get(24), 3u);
|
||||
}
|
||||
|
||||
TEST(SparseSet, Iterator) {
|
||||
using iterator_type = typename entt::sparse_set<std::uint64_t>::iterator_type;
|
||||
|
||||
entt::sparse_set<std::uint64_t> set;
|
||||
set.construct(3);
|
||||
|
||||
iterator_type end{set.begin()};
|
||||
iterator_type begin{};
|
||||
begin = set.end();
|
||||
std::swap(begin, end);
|
||||
|
||||
ASSERT_EQ(begin, set.begin());
|
||||
ASSERT_EQ(end, set.end());
|
||||
ASSERT_NE(begin, end);
|
||||
|
||||
ASSERT_EQ(begin++, set.begin());
|
||||
ASSERT_EQ(begin--, set.end());
|
||||
|
||||
ASSERT_EQ(begin+1, set.end());
|
||||
ASSERT_EQ(end-1, set.begin());
|
||||
|
||||
ASSERT_EQ(++begin, set.end());
|
||||
ASSERT_EQ(--begin, set.begin());
|
||||
|
||||
ASSERT_EQ(begin += 1, set.end());
|
||||
ASSERT_EQ(begin -= 1, set.begin());
|
||||
|
||||
ASSERT_EQ(begin + (end - begin), set.end());
|
||||
ASSERT_EQ(begin - (begin - end), set.end());
|
||||
|
||||
ASSERT_EQ(end - (end - begin), set.begin());
|
||||
ASSERT_EQ(end + (begin - end), set.begin());
|
||||
|
||||
ASSERT_EQ(begin[0], *set.begin());
|
||||
|
||||
ASSERT_LT(begin, end);
|
||||
ASSERT_LE(begin, set.begin());
|
||||
|
||||
ASSERT_GT(end, begin);
|
||||
ASSERT_GE(end, set.end());
|
||||
|
||||
ASSERT_EQ(*begin, 3);
|
||||
ASSERT_EQ(*begin.operator->(), 3);
|
||||
}
|
||||
|
||||
TEST(SparseSet, Find) {
|
||||
entt::sparse_set<std::uint64_t> set;
|
||||
set.construct(3);
|
||||
set.construct(42);
|
||||
set.construct(99);
|
||||
|
||||
ASSERT_NE(set.find(3), set.end());
|
||||
ASSERT_NE(set.find(42), set.end());
|
||||
ASSERT_NE(set.find(99), set.end());
|
||||
ASSERT_EQ(set.find(0), set.end());
|
||||
|
||||
auto it = set.find(99);
|
||||
|
||||
ASSERT_EQ(*it, 99);
|
||||
ASSERT_EQ(*(++it), 42);
|
||||
ASSERT_EQ(*(++it), 3);
|
||||
ASSERT_EQ(++it, set.end());
|
||||
ASSERT_EQ(++set.find(3), set.end());
|
||||
}
|
||||
|
||||
TEST(SparseSet, Data) {
|
||||
entt::sparse_set<std::uint64_t> set;
|
||||
|
||||
set.construct(3);
|
||||
set.construct(12);
|
||||
@@ -60,390 +218,180 @@ TEST(SparseSetNoType, DataBeginEnd) {
|
||||
ASSERT_EQ(*(set.data() + 0u), 3u);
|
||||
ASSERT_EQ(*(set.data() + 1u), 12u);
|
||||
ASSERT_EQ(*(set.data() + 2u), 42u);
|
||||
|
||||
auto begin = set.begin();
|
||||
auto end = set.end();
|
||||
|
||||
ASSERT_EQ(*(begin++), 42u);
|
||||
ASSERT_EQ(*(begin++), 12u);
|
||||
ASSERT_EQ(*(begin++), 3u);
|
||||
ASSERT_EQ(begin, end);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, AggregatesMustWork) {
|
||||
struct AggregateType { int value; };
|
||||
// the goal of this test is to enforce the requirements for aggregate types
|
||||
entt::SparseSet<unsigned int, AggregateType>{}.construct(0, 42);
|
||||
}
|
||||
TEST(SparseSet, RespectDisjoint) {
|
||||
entt::sparse_set<std::uint64_t> lhs;
|
||||
entt::sparse_set<std::uint64_t> rhs;
|
||||
|
||||
TEST(SparseSetWithType, Functionalities) {
|
||||
entt::SparseSet<unsigned int, int> set;
|
||||
lhs.construct(3);
|
||||
lhs.construct(12);
|
||||
lhs.construct(42);
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
|
||||
set.construct(42, 3);
|
||||
|
||||
ASSERT_EQ(set.get(42), 3);
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 1u);
|
||||
ASSERT_NE(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_TRUE(set.has(42));
|
||||
ASSERT_EQ(set.get(42), 3);
|
||||
|
||||
set.destroy(42);
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
|
||||
set.construct(42, 12);
|
||||
|
||||
ASSERT_EQ(set.get(42), 12);
|
||||
|
||||
set.reset();
|
||||
|
||||
ASSERT_TRUE(set.empty());
|
||||
ASSERT_EQ(set.size(), 0u);
|
||||
ASSERT_EQ(set.begin(), set.end());
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(42));
|
||||
|
||||
(void)entt::SparseSet<unsigned int>{std::move(set)};
|
||||
entt::SparseSet<unsigned int> other;
|
||||
other = std::move(set);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, RawBeginEnd) {
|
||||
entt::SparseSet<unsigned int, int> set;
|
||||
|
||||
set.construct(3, 3);
|
||||
set.construct(12, 6);
|
||||
set.construct(42, 9);
|
||||
|
||||
ASSERT_EQ(set.get(3), 3);
|
||||
ASSERT_EQ(set.get(12), 6);
|
||||
ASSERT_EQ(set.get(42), 9);
|
||||
|
||||
ASSERT_EQ(*(set.raw() + 0u), 3);
|
||||
ASSERT_EQ(*(set.raw() + 1u), 6);
|
||||
ASSERT_EQ(*(set.raw() + 2u), 9);
|
||||
|
||||
auto begin = set.begin();
|
||||
auto end = set.end();
|
||||
|
||||
ASSERT_EQ(set.get(*(begin++)), 9);
|
||||
ASSERT_EQ(set.get(*(begin++)), 6);
|
||||
ASSERT_EQ(set.get(*(begin++)), 3);
|
||||
ASSERT_EQ(begin, end);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, SortOrdered) {
|
||||
entt::SparseSet<unsigned int, int> set;
|
||||
|
||||
set.construct(12, 12);
|
||||
set.construct(42, 9);
|
||||
set.construct(7, 6);
|
||||
set.construct(3, 3);
|
||||
set.construct(9, 1);
|
||||
|
||||
ASSERT_EQ(set.get(12), 12);
|
||||
ASSERT_EQ(set.get(42), 9);
|
||||
ASSERT_EQ(set.get(7), 6);
|
||||
ASSERT_EQ(set.get(3), 3);
|
||||
ASSERT_EQ(set.get(9), 1);
|
||||
|
||||
set.sort([&set](auto lhs, auto rhs) {
|
||||
return set.get(lhs) < set.get(rhs);
|
||||
});
|
||||
|
||||
ASSERT_EQ(*(set.raw() + 0u), 12);
|
||||
ASSERT_EQ(*(set.raw() + 1u), 9);
|
||||
ASSERT_EQ(*(set.raw() + 2u), 6);
|
||||
ASSERT_EQ(*(set.raw() + 3u), 3);
|
||||
ASSERT_EQ(*(set.raw() + 4u), 1);
|
||||
|
||||
auto begin = set.begin();
|
||||
auto end = set.end();
|
||||
|
||||
ASSERT_EQ(set.get(*(begin++)), 1);
|
||||
ASSERT_EQ(set.get(*(begin++)), 3);
|
||||
ASSERT_EQ(set.get(*(begin++)), 6);
|
||||
ASSERT_EQ(set.get(*(begin++)), 9);
|
||||
ASSERT_EQ(set.get(*(begin++)), 12);
|
||||
ASSERT_EQ(begin, end);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, SortReverse) {
|
||||
entt::SparseSet<unsigned int, int> set;
|
||||
|
||||
set.construct(12, 1);
|
||||
set.construct(42, 3);
|
||||
set.construct(7, 6);
|
||||
set.construct(3, 9);
|
||||
set.construct(9, 12);
|
||||
|
||||
ASSERT_EQ(set.get(12), 1);
|
||||
ASSERT_EQ(set.get(42), 3);
|
||||
ASSERT_EQ(set.get(7), 6);
|
||||
ASSERT_EQ(set.get(3), 9);
|
||||
ASSERT_EQ(set.get(9), 12);
|
||||
|
||||
set.sort([&set](auto lhs, auto rhs) {
|
||||
return set.get(lhs) < set.get(rhs);
|
||||
});
|
||||
|
||||
ASSERT_EQ(*(set.raw() + 0u), 12);
|
||||
ASSERT_EQ(*(set.raw() + 1u), 9);
|
||||
ASSERT_EQ(*(set.raw() + 2u), 6);
|
||||
ASSERT_EQ(*(set.raw() + 3u), 3);
|
||||
ASSERT_EQ(*(set.raw() + 4u), 1);
|
||||
|
||||
auto begin = set.begin();
|
||||
auto end = set.end();
|
||||
|
||||
ASSERT_EQ(set.get(*(begin++)), 1);
|
||||
ASSERT_EQ(set.get(*(begin++)), 3);
|
||||
ASSERT_EQ(set.get(*(begin++)), 6);
|
||||
ASSERT_EQ(set.get(*(begin++)), 9);
|
||||
ASSERT_EQ(set.get(*(begin++)), 12);
|
||||
ASSERT_EQ(begin, end);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, SortUnordered) {
|
||||
entt::SparseSet<unsigned int, int> set;
|
||||
|
||||
set.construct(12, 6);
|
||||
set.construct(42, 3);
|
||||
set.construct(7, 1);
|
||||
set.construct(3, 9);
|
||||
set.construct(9, 12);
|
||||
|
||||
ASSERT_EQ(set.get(12), 6);
|
||||
ASSERT_EQ(set.get(42), 3);
|
||||
ASSERT_EQ(set.get(7), 1);
|
||||
ASSERT_EQ(set.get(3), 9);
|
||||
ASSERT_EQ(set.get(9), 12);
|
||||
|
||||
set.sort([&set](auto lhs, auto rhs) {
|
||||
return set.get(lhs) < set.get(rhs);
|
||||
});
|
||||
|
||||
ASSERT_EQ(*(set.raw() + 0u), 12);
|
||||
ASSERT_EQ(*(set.raw() + 1u), 9);
|
||||
ASSERT_EQ(*(set.raw() + 2u), 6);
|
||||
ASSERT_EQ(*(set.raw() + 3u), 3);
|
||||
ASSERT_EQ(*(set.raw() + 4u), 1);
|
||||
|
||||
auto begin = set.begin();
|
||||
auto end = set.end();
|
||||
|
||||
ASSERT_EQ(set.get(*(begin++)), 1);
|
||||
ASSERT_EQ(set.get(*(begin++)), 3);
|
||||
ASSERT_EQ(set.get(*(begin++)), 6);
|
||||
ASSERT_EQ(set.get(*(begin++)), 9);
|
||||
ASSERT_EQ(set.get(*(begin++)), 12);
|
||||
ASSERT_EQ(begin, end);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, RespectDisjoint) {
|
||||
entt::SparseSet<unsigned int, int> lhs;
|
||||
entt::SparseSet<unsigned int, int> rhs;
|
||||
const auto &clhs = lhs;
|
||||
|
||||
lhs.construct(3, 3);
|
||||
lhs.construct(12, 6);
|
||||
lhs.construct(42, 9);
|
||||
|
||||
ASSERT_EQ(lhs.get(3), 3);
|
||||
ASSERT_EQ(lhs.get(12), 6);
|
||||
ASSERT_EQ(lhs.get(42), 9);
|
||||
ASSERT_EQ(lhs.get(3), 0u);
|
||||
ASSERT_EQ(lhs.get(12), 1u);
|
||||
ASSERT_EQ(lhs.get(42), 2u);
|
||||
|
||||
lhs.respect(rhs);
|
||||
|
||||
ASSERT_EQ(*(clhs.raw() + 0u), 3);
|
||||
ASSERT_EQ(*(clhs.raw() + 1u), 6);
|
||||
ASSERT_EQ(*(clhs.raw() + 2u), 9);
|
||||
|
||||
auto begin = clhs.begin();
|
||||
auto end = clhs.end();
|
||||
|
||||
ASSERT_EQ(clhs.get(*(begin++)), 9);
|
||||
ASSERT_EQ(clhs.get(*(begin++)), 6);
|
||||
ASSERT_EQ(clhs.get(*(begin++)), 3);
|
||||
ASSERT_EQ(begin, end);
|
||||
ASSERT_EQ(std::as_const(lhs).get(3), 0u);
|
||||
ASSERT_EQ(std::as_const(lhs).get(12), 1u);
|
||||
ASSERT_EQ(std::as_const(lhs).get(42), 2u);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, RespectOverlap) {
|
||||
entt::SparseSet<unsigned int, int> lhs;
|
||||
entt::SparseSet<unsigned int, int> rhs;
|
||||
const auto &clhs = lhs;
|
||||
TEST(SparseSet, RespectOverlap) {
|
||||
entt::sparse_set<std::uint64_t> lhs;
|
||||
entt::sparse_set<std::uint64_t> rhs;
|
||||
|
||||
lhs.construct(3, 3);
|
||||
lhs.construct(12, 6);
|
||||
lhs.construct(42, 9);
|
||||
rhs.construct(12, 6);
|
||||
lhs.construct(3);
|
||||
lhs.construct(12);
|
||||
lhs.construct(42);
|
||||
|
||||
ASSERT_EQ(lhs.get(3), 3);
|
||||
ASSERT_EQ(lhs.get(12), 6);
|
||||
ASSERT_EQ(lhs.get(42), 9);
|
||||
ASSERT_EQ(rhs.get(12), 6);
|
||||
rhs.construct(12);
|
||||
|
||||
ASSERT_EQ(lhs.get(3), 0u);
|
||||
ASSERT_EQ(lhs.get(12), 1u);
|
||||
ASSERT_EQ(lhs.get(42), 2u);
|
||||
|
||||
lhs.respect(rhs);
|
||||
|
||||
ASSERT_EQ(*(clhs.raw() + 0u), 3);
|
||||
ASSERT_EQ(*(clhs.raw() + 1u), 9);
|
||||
ASSERT_EQ(*(clhs.raw() + 2u), 6);
|
||||
|
||||
auto begin = clhs.begin();
|
||||
auto end = clhs.end();
|
||||
|
||||
ASSERT_EQ(clhs.get(*(begin++)), 6);
|
||||
ASSERT_EQ(clhs.get(*(begin++)), 9);
|
||||
ASSERT_EQ(clhs.get(*(begin++)), 3);
|
||||
ASSERT_EQ(begin, end);
|
||||
ASSERT_EQ(std::as_const(lhs).get(3), 0u);
|
||||
ASSERT_EQ(std::as_const(lhs).get(12), 2u);
|
||||
ASSERT_EQ(std::as_const(lhs).get(42), 1u);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, RespectOrdered) {
|
||||
entt::SparseSet<unsigned int, int> lhs;
|
||||
entt::SparseSet<unsigned int, int> rhs;
|
||||
TEST(SparseSet, RespectOrdered) {
|
||||
entt::sparse_set<std::uint64_t> lhs;
|
||||
entt::sparse_set<std::uint64_t> rhs;
|
||||
|
||||
lhs.construct(1, 0);
|
||||
lhs.construct(2, 0);
|
||||
lhs.construct(3, 0);
|
||||
lhs.construct(4, 0);
|
||||
lhs.construct(5, 0);
|
||||
lhs.construct(1);
|
||||
lhs.construct(2);
|
||||
lhs.construct(3);
|
||||
lhs.construct(4);
|
||||
lhs.construct(5);
|
||||
|
||||
ASSERT_EQ(lhs.get(1), 0);
|
||||
ASSERT_EQ(lhs.get(2), 0);
|
||||
ASSERT_EQ(lhs.get(3), 0);
|
||||
ASSERT_EQ(lhs.get(4), 0);
|
||||
ASSERT_EQ(lhs.get(5), 0);
|
||||
ASSERT_EQ(lhs.get(1), 0u);
|
||||
ASSERT_EQ(lhs.get(2), 1u);
|
||||
ASSERT_EQ(lhs.get(3), 2u);
|
||||
ASSERT_EQ(lhs.get(4), 3u);
|
||||
ASSERT_EQ(lhs.get(5), 4u);
|
||||
|
||||
rhs.construct(6, 0);
|
||||
rhs.construct(1, 0);
|
||||
rhs.construct(2, 0);
|
||||
rhs.construct(3, 0);
|
||||
rhs.construct(4, 0);
|
||||
rhs.construct(5, 0);
|
||||
rhs.construct(6);
|
||||
rhs.construct(1);
|
||||
rhs.construct(2);
|
||||
rhs.construct(3);
|
||||
rhs.construct(4);
|
||||
rhs.construct(5);
|
||||
|
||||
ASSERT_EQ(rhs.get(6), 0);
|
||||
ASSERT_EQ(rhs.get(1), 0);
|
||||
ASSERT_EQ(rhs.get(2), 0);
|
||||
ASSERT_EQ(rhs.get(3), 0);
|
||||
ASSERT_EQ(rhs.get(4), 0);
|
||||
ASSERT_EQ(rhs.get(5), 0);
|
||||
ASSERT_EQ(rhs.get(6), 0u);
|
||||
ASSERT_EQ(rhs.get(1), 1u);
|
||||
ASSERT_EQ(rhs.get(2), 2u);
|
||||
ASSERT_EQ(rhs.get(3), 3u);
|
||||
ASSERT_EQ(rhs.get(4), 4u);
|
||||
ASSERT_EQ(rhs.get(5), 5u);
|
||||
|
||||
rhs.respect(lhs);
|
||||
|
||||
ASSERT_EQ(*(lhs.data() + 0u), 1u);
|
||||
ASSERT_EQ(*(lhs.data() + 1u), 2u);
|
||||
ASSERT_EQ(*(lhs.data() + 2u), 3u);
|
||||
ASSERT_EQ(*(lhs.data() + 3u), 4u);
|
||||
ASSERT_EQ(*(lhs.data() + 4u), 5u);
|
||||
|
||||
ASSERT_EQ(*(rhs.data() + 0u), 6u);
|
||||
ASSERT_EQ(*(rhs.data() + 1u), 1u);
|
||||
ASSERT_EQ(*(rhs.data() + 2u), 2u);
|
||||
ASSERT_EQ(*(rhs.data() + 3u), 3u);
|
||||
ASSERT_EQ(*(rhs.data() + 4u), 4u);
|
||||
ASSERT_EQ(*(rhs.data() + 5u), 5u);
|
||||
ASSERT_EQ(rhs.get(6), 0u);
|
||||
ASSERT_EQ(rhs.get(1), 1u);
|
||||
ASSERT_EQ(rhs.get(2), 2u);
|
||||
ASSERT_EQ(rhs.get(3), 3u);
|
||||
ASSERT_EQ(rhs.get(4), 4u);
|
||||
ASSERT_EQ(rhs.get(5), 5u);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, RespectReverse) {
|
||||
entt::SparseSet<unsigned int, int> lhs;
|
||||
entt::SparseSet<unsigned int, int> rhs;
|
||||
TEST(SparseSet, RespectReverse) {
|
||||
entt::sparse_set<std::uint64_t> lhs;
|
||||
entt::sparse_set<std::uint64_t> rhs;
|
||||
|
||||
lhs.construct(1, 0);
|
||||
lhs.construct(2, 0);
|
||||
lhs.construct(3, 0);
|
||||
lhs.construct(4, 0);
|
||||
lhs.construct(5, 0);
|
||||
lhs.construct(1);
|
||||
lhs.construct(2);
|
||||
lhs.construct(3);
|
||||
lhs.construct(4);
|
||||
lhs.construct(5);
|
||||
|
||||
ASSERT_EQ(lhs.get(1), 0);
|
||||
ASSERT_EQ(lhs.get(2), 0);
|
||||
ASSERT_EQ(lhs.get(3), 0);
|
||||
ASSERT_EQ(lhs.get(4), 0);
|
||||
ASSERT_EQ(lhs.get(5), 0);
|
||||
ASSERT_EQ(lhs.get(1), 0u);
|
||||
ASSERT_EQ(lhs.get(2), 1u);
|
||||
ASSERT_EQ(lhs.get(3), 2u);
|
||||
ASSERT_EQ(lhs.get(4), 3u);
|
||||
ASSERT_EQ(lhs.get(5), 4u);
|
||||
|
||||
rhs.construct(5, 0);
|
||||
rhs.construct(4, 0);
|
||||
rhs.construct(3, 0);
|
||||
rhs.construct(2, 0);
|
||||
rhs.construct(1, 0);
|
||||
rhs.construct(6, 0);
|
||||
rhs.construct(5);
|
||||
rhs.construct(4);
|
||||
rhs.construct(3);
|
||||
rhs.construct(2);
|
||||
rhs.construct(1);
|
||||
rhs.construct(6);
|
||||
|
||||
ASSERT_EQ(rhs.get(5), 0);
|
||||
ASSERT_EQ(rhs.get(4), 0);
|
||||
ASSERT_EQ(rhs.get(3), 0);
|
||||
ASSERT_EQ(rhs.get(2), 0);
|
||||
ASSERT_EQ(rhs.get(1), 0);
|
||||
ASSERT_EQ(rhs.get(6), 0);
|
||||
ASSERT_EQ(rhs.get(5), 0u);
|
||||
ASSERT_EQ(rhs.get(4), 1u);
|
||||
ASSERT_EQ(rhs.get(3), 2u);
|
||||
ASSERT_EQ(rhs.get(2), 3u);
|
||||
ASSERT_EQ(rhs.get(1), 4u);
|
||||
ASSERT_EQ(rhs.get(6), 5u);
|
||||
|
||||
rhs.respect(lhs);
|
||||
|
||||
ASSERT_EQ(*(lhs.data() + 0u), 1u);
|
||||
ASSERT_EQ(*(lhs.data() + 1u), 2u);
|
||||
ASSERT_EQ(*(lhs.data() + 2u), 3u);
|
||||
ASSERT_EQ(*(lhs.data() + 3u), 4u);
|
||||
ASSERT_EQ(*(lhs.data() + 4u), 5u);
|
||||
|
||||
ASSERT_EQ(*(rhs.data() + 0u), 6u);
|
||||
ASSERT_EQ(*(rhs.data() + 1u), 1u);
|
||||
ASSERT_EQ(*(rhs.data() + 2u), 2u);
|
||||
ASSERT_EQ(*(rhs.data() + 3u), 3u);
|
||||
ASSERT_EQ(*(rhs.data() + 4u), 4u);
|
||||
ASSERT_EQ(*(rhs.data() + 5u), 5u);
|
||||
ASSERT_EQ(rhs.get(6), 0u);
|
||||
ASSERT_EQ(rhs.get(1), 1u);
|
||||
ASSERT_EQ(rhs.get(2), 2u);
|
||||
ASSERT_EQ(rhs.get(3), 3u);
|
||||
ASSERT_EQ(rhs.get(4), 4u);
|
||||
ASSERT_EQ(rhs.get(5), 5u);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, RespectUnordered) {
|
||||
entt::SparseSet<unsigned int, int> lhs;
|
||||
entt::SparseSet<unsigned int, int> rhs;
|
||||
TEST(SparseSet, RespectUnordered) {
|
||||
entt::sparse_set<std::uint64_t> lhs;
|
||||
entt::sparse_set<std::uint64_t> rhs;
|
||||
|
||||
lhs.construct(1, 0);
|
||||
lhs.construct(2, 0);
|
||||
lhs.construct(3, 0);
|
||||
lhs.construct(4, 0);
|
||||
lhs.construct(5, 0);
|
||||
lhs.construct(1);
|
||||
lhs.construct(2);
|
||||
lhs.construct(3);
|
||||
lhs.construct(4);
|
||||
lhs.construct(5);
|
||||
|
||||
ASSERT_EQ(lhs.get(1), 0);
|
||||
ASSERT_EQ(lhs.get(2), 0);
|
||||
ASSERT_EQ(lhs.get(3), 0);
|
||||
ASSERT_EQ(lhs.get(4), 0);
|
||||
ASSERT_EQ(lhs.get(5), 0);
|
||||
ASSERT_EQ(lhs.get(1), 0u);
|
||||
ASSERT_EQ(lhs.get(2), 1u);
|
||||
ASSERT_EQ(lhs.get(3), 2u);
|
||||
ASSERT_EQ(lhs.get(4), 3u);
|
||||
ASSERT_EQ(lhs.get(5), 4u);
|
||||
|
||||
rhs.construct(3, 0);
|
||||
rhs.construct(2, 0);
|
||||
rhs.construct(6, 0);
|
||||
rhs.construct(1, 0);
|
||||
rhs.construct(4, 0);
|
||||
rhs.construct(5, 0);
|
||||
rhs.construct(3);
|
||||
rhs.construct(2);
|
||||
rhs.construct(6);
|
||||
rhs.construct(1);
|
||||
rhs.construct(4);
|
||||
rhs.construct(5);
|
||||
|
||||
ASSERT_EQ(rhs.get(3), 0);
|
||||
ASSERT_EQ(rhs.get(2), 0);
|
||||
ASSERT_EQ(rhs.get(6), 0);
|
||||
ASSERT_EQ(rhs.get(1), 0);
|
||||
ASSERT_EQ(rhs.get(4), 0);
|
||||
ASSERT_EQ(rhs.get(5), 0);
|
||||
ASSERT_EQ(rhs.get(3), 0u);
|
||||
ASSERT_EQ(rhs.get(2), 1u);
|
||||
ASSERT_EQ(rhs.get(6), 2u);
|
||||
ASSERT_EQ(rhs.get(1), 3u);
|
||||
ASSERT_EQ(rhs.get(4), 4u);
|
||||
ASSERT_EQ(rhs.get(5), 5u);
|
||||
|
||||
rhs.respect(lhs);
|
||||
|
||||
ASSERT_EQ(*(lhs.data() + 0u), 1u);
|
||||
ASSERT_EQ(*(lhs.data() + 1u), 2u);
|
||||
ASSERT_EQ(*(lhs.data() + 2u), 3u);
|
||||
ASSERT_EQ(*(lhs.data() + 3u), 4u);
|
||||
ASSERT_EQ(*(lhs.data() + 4u), 5u);
|
||||
|
||||
ASSERT_EQ(*(rhs.data() + 0u), 6u);
|
||||
ASSERT_EQ(*(rhs.data() + 1u), 1u);
|
||||
ASSERT_EQ(*(rhs.data() + 2u), 2u);
|
||||
ASSERT_EQ(*(rhs.data() + 3u), 3u);
|
||||
ASSERT_EQ(*(rhs.data() + 4u), 4u);
|
||||
ASSERT_EQ(*(rhs.data() + 5u), 5u);
|
||||
ASSERT_EQ(rhs.get(6), 0u);
|
||||
ASSERT_EQ(rhs.get(1), 1u);
|
||||
ASSERT_EQ(rhs.get(2), 2u);
|
||||
ASSERT_EQ(rhs.get(3), 3u);
|
||||
ASSERT_EQ(rhs.get(4), 4u);
|
||||
ASSERT_EQ(rhs.get(5), 5u);
|
||||
}
|
||||
|
||||
TEST(SparseSet, CanModifyDuringIteration) {
|
||||
entt::sparse_set<std::uint64_t> set;
|
||||
set.construct(0);
|
||||
|
||||
ASSERT_EQ(set.capacity(), entt::sparse_set<std::uint64_t>::size_type{1});
|
||||
|
||||
const auto it = set.begin();
|
||||
set.reserve(entt::sparse_set<std::uint64_t>::size_type{2});
|
||||
|
||||
ASSERT_EQ(set.capacity(), entt::sparse_set<std::uint64_t>::size_type{2});
|
||||
|
||||
// this should crash with asan enabled if we break the constraint
|
||||
const auto entity = *it;
|
||||
(void)entity;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user