Compare commits
962 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50ba8c6c39 | ||
|
|
94d15ebbef | ||
|
|
e150882231 | ||
|
|
80a659c90c | ||
|
|
17d96427ea | ||
|
|
7fdda788af | ||
|
|
182adbd9d9 | ||
|
|
4931c9cd9b | ||
|
|
a112409735 | ||
|
|
7aaa6dd986 | ||
|
|
3c39cfe645 | ||
|
|
6a46325e7e | ||
|
|
15c9688a5a | ||
|
|
d9f93ccc11 | ||
|
|
652e569afc | ||
|
|
c55372459f | ||
|
|
09f36e32f7 | ||
|
|
688d435d8e | ||
|
|
e7403d8551 | ||
|
|
60039441a1 | ||
|
|
97f4414cc4 | ||
|
|
df9595bc0d | ||
|
|
93533b7bc6 | ||
|
|
a99afa2ddf | ||
|
|
1e651a5145 | ||
|
|
79ecfa2573 | ||
|
|
e2b676d54c | ||
|
|
2b73bf35b6 | ||
|
|
2137b3a879 | ||
|
|
e52b3fd5bc | ||
|
|
0dfd2aa714 | ||
|
|
5d63c4c981 | ||
|
|
84d3f9ab9a | ||
|
|
1133dba9d8 | ||
|
|
cd2fdc2ffe | ||
|
|
57d017ed8e | ||
|
|
3ad2c559f1 | ||
|
|
c3facfa925 | ||
|
|
db8d9ebc72 | ||
|
|
3aef00af18 | ||
|
|
a4d576bae8 | ||
|
|
9320365ef6 | ||
|
|
0be7494042 | ||
|
|
df55f338ca | ||
|
|
8210efb548 | ||
|
|
43503e2fc9 | ||
|
|
448c3ae425 | ||
|
|
f7684a8a3e | ||
|
|
4f200ac71e | ||
|
|
4772559e8b | ||
|
|
a984ce5bba | ||
|
|
eee8a338e2 | ||
|
|
8894c0ef48 | ||
|
|
c5b8577d94 | ||
|
|
ce9def4a56 | ||
|
|
894601aad2 | ||
|
|
285c91e81b | ||
|
|
ba8ca93afc | ||
|
|
ad0dab2493 | ||
|
|
8b66316180 | ||
|
|
da4ee38914 | ||
|
|
62fde229e2 | ||
|
|
676115d214 | ||
|
|
f36d838df1 | ||
|
|
f32f3ab895 | ||
|
|
fc0432df8c | ||
|
|
797b5bcb53 | ||
|
|
b816f203cd | ||
|
|
714e2f86a1 | ||
|
|
ff75085f15 | ||
|
|
dbc8b18b80 | ||
|
|
efb2bbe8b2 | ||
|
|
8278c8eedd | ||
|
|
6f696a026b | ||
|
|
f558797246 | ||
|
|
c8671d1c2b | ||
|
|
7fd41e48b9 | ||
|
|
409d5f5966 | ||
|
|
5d1802faf7 | ||
|
|
a580bac385 | ||
|
|
c43214543f | ||
|
|
b3c568fb6f | ||
|
|
b34fe3200c | ||
|
|
dada8dbdeb | ||
|
|
e7d4077065 | ||
|
|
b1e54bfd86 | ||
|
|
85ad4c4766 | ||
|
|
3dce560759 | ||
|
|
969b8f0a7a | ||
|
|
ba8e738f40 | ||
|
|
4bc80fae8d | ||
|
|
ee5156704d | ||
|
|
986cd03732 | ||
|
|
937fdabaa9 | ||
|
|
c0fa97510e | ||
|
|
2fc6fe442d | ||
|
|
7ba14f5a57 | ||
|
|
352e4576fc | ||
|
|
210eebc0dc | ||
|
|
5904941361 | ||
|
|
b352815cf8 | ||
|
|
38a2751cd9 | ||
|
|
6388607d11 | ||
|
|
469276b8be | ||
|
|
2a863ee851 | ||
|
|
b6dae2fe57 | ||
|
|
d202bd3e50 | ||
|
|
dc2e44b2c0 | ||
|
|
32c481c28c | ||
|
|
8519c99706 | ||
|
|
5b1bac4c19 | ||
|
|
0a95a0674b | ||
|
|
2f11f6dc37 | ||
|
|
af66274c72 | ||
|
|
c5679b208f | ||
|
|
020fc179a5 | ||
|
|
dc9c1fc762 | ||
|
|
f0938d70b2 | ||
|
|
072761c5ba | ||
|
|
8aacd4d022 | ||
|
|
b6f9ca0021 | ||
|
|
a43f354511 | ||
|
|
43766406ae | ||
|
|
37dd1ae363 | ||
|
|
5a3effaef5 | ||
|
|
cd4bcce70f | ||
|
|
c1e7549b61 | ||
|
|
036ef25da9 | ||
|
|
9884c37ef4 | ||
|
|
23069d76eb | ||
|
|
b2d0e7fae2 | ||
|
|
30a039a031 | ||
|
|
8d67bb726c | ||
|
|
c4dec4cd4d | ||
|
|
f17b975fb9 | ||
|
|
7a3b7593d0 | ||
|
|
bad342b840 | ||
|
|
e3968a8f9d | ||
|
|
f577183c24 | ||
|
|
be58f2e68a | ||
|
|
99f81e82d5 | ||
|
|
7cfd3957de | ||
|
|
c51c88a17c | ||
|
|
e09af98dc5 | ||
|
|
89635f6583 | ||
|
|
14034642f1 | ||
|
|
4af7d975c1 | ||
|
|
d9884917fb | ||
|
|
95fd4e4c99 | ||
|
|
9457a55910 | ||
|
|
56e7dd0de0 | ||
|
|
f0dc882ae9 | ||
|
|
acb70d4440 | ||
|
|
167721bf8c | ||
|
|
4f438e5228 | ||
|
|
0f0aeb6d47 | ||
|
|
d1a6edad8a | ||
|
|
388feb7edb | ||
|
|
962b068c92 | ||
|
|
a32373bc3a | ||
|
|
94a5a4ac1e | ||
|
|
ed8eea12bd | ||
|
|
83bea8b654 | ||
|
|
d4d2db228e | ||
|
|
f8b8c91fe8 | ||
|
|
8451301a5a | ||
|
|
449b03f6bd | ||
|
|
3853ff725f | ||
|
|
ee66e3ef9e | ||
|
|
2643500957 | ||
|
|
88467a87a4 | ||
|
|
d1cdeb4a2d | ||
|
|
f5ced7fe39 | ||
|
|
023267ecab | ||
|
|
3d515a760c | ||
|
|
026c5987dd | ||
|
|
84fb3694f2 | ||
|
|
169dcbcd74 | ||
|
|
d28b6fbf1e | ||
|
|
9f7a2ef84e | ||
|
|
cc5ea60d2b | ||
|
|
8bc63494bc | ||
|
|
9acdeeed04 | ||
|
|
d2cdb2a209 | ||
|
|
36bad31355 | ||
|
|
427587e591 | ||
|
|
4f028d8201 | ||
|
|
d59c052554 | ||
|
|
93ebe91bc5 | ||
|
|
fadb8f695e | ||
|
|
8bb1518d09 | ||
|
|
e8d9d663a7 | ||
|
|
2d2648cf57 | ||
|
|
231036784d | ||
|
|
882b91b221 | ||
|
|
df346ab380 | ||
|
|
c7b8e82ada | ||
|
|
b8784863f2 | ||
|
|
3f67054f03 | ||
|
|
36d1b0a4bc | ||
|
|
748777b8eb | ||
|
|
acd8e0bce5 | ||
|
|
d2d068edff | ||
|
|
e2a7a7ce20 | ||
|
|
b25b1c45fb | ||
|
|
c3b0fa6c93 | ||
|
|
63a4e67174 | ||
|
|
88e37438fa | ||
|
|
5be1bf7d82 | ||
|
|
eb4689d2f1 | ||
|
|
e81549e5fd | ||
|
|
becaef36ff | ||
|
|
6aefa40ca2 | ||
|
|
250ef50361 | ||
|
|
5a1af60357 | ||
|
|
46db75308c | ||
|
|
33f5e13b29 | ||
|
|
54bb4797db | ||
|
|
7a14fbb221 | ||
|
|
18349f5ee4 | ||
|
|
22b93cdd0a | ||
|
|
63bc0b2ba1 | ||
|
|
c57a7c745d | ||
|
|
f2831b5f5c | ||
|
|
96f793f91b | ||
|
|
4e2a0d6e58 | ||
|
|
442c7f1f09 | ||
|
|
dbff4af7c6 | ||
|
|
93b09836da | ||
|
|
1782dc1565 | ||
|
|
cc16874d58 | ||
|
|
c8925b2ae3 | ||
|
|
fc043b9fcd | ||
|
|
ea86d33bc1 | ||
|
|
82f33b82e3 | ||
|
|
c645cb83a2 | ||
|
|
d6d79a2aa5 | ||
|
|
786568fd2f | ||
|
|
434f77a058 | ||
|
|
02f777a143 | ||
|
|
8025a84aeb | ||
|
|
c37a50d3b8 | ||
|
|
6dbbb265d1 | ||
|
|
02ce2ff74c | ||
|
|
28e5267132 | ||
|
|
f498b8a049 | ||
|
|
b31afe5c8c | ||
|
|
eac944dbea | ||
|
|
965b20c37a | ||
|
|
fba85754d7 | ||
|
|
3c69f98451 | ||
|
|
cad8a90124 | ||
|
|
b8d888b17f | ||
|
|
942d783e4e | ||
|
|
0e352cb466 | ||
|
|
50af38b952 | ||
|
|
1459c59cd0 | ||
|
|
e74f5b2991 | ||
|
|
0dec05fd70 | ||
|
|
3cff21b4d0 | ||
|
|
c380da7214 | ||
|
|
e2bf903c49 | ||
|
|
9cf64ba881 | ||
|
|
74d901b7ae | ||
|
|
b15cb46a6d | ||
|
|
89dc76e3b2 | ||
|
|
000b7d2651 | ||
|
|
9ffa372ba2 | ||
|
|
510cfcf1fc | ||
|
|
e7f6d93a1e | ||
|
|
a17c449075 | ||
|
|
c4da7a87f9 | ||
|
|
4a8b2f826d | ||
|
|
4901e4fb80 | ||
|
|
ce3a0157cb | ||
|
|
a146b06e4b | ||
|
|
dc78a3e56d | ||
|
|
86c524dade | ||
|
|
6889edcfc4 | ||
|
|
3cd5934bad | ||
|
|
064104f23a | ||
|
|
e80b1c264c | ||
|
|
03c3ee514b | ||
|
|
c93d0f0d59 | ||
|
|
85793d38bc | ||
|
|
49b4a85d55 | ||
|
|
5e8a9340f1 | ||
|
|
2b97c01bd6 | ||
|
|
39e3a5a708 | ||
|
|
df13d993f7 | ||
|
|
36c62763e5 | ||
|
|
ad5651ffb2 | ||
|
|
717b091b49 | ||
|
|
4376bbe40b | ||
|
|
2382d2e21c | ||
|
|
520ccfcd06 | ||
|
|
0bdc5dfe95 | ||
|
|
6a656a1b0a | ||
|
|
5ad4079dbf | ||
|
|
bfa46b795f | ||
|
|
ecaa9c275c | ||
|
|
93fd1757f8 | ||
|
|
71d0958398 | ||
|
|
ed89d94d7a | ||
|
|
135132e5f0 | ||
|
|
1ad75f5c1b | ||
|
|
21c6f9e394 | ||
|
|
5a7e954aee | ||
|
|
c1117e260c | ||
|
|
b0ea150e94 | ||
|
|
0e3bdc02ea | ||
|
|
e5a075a329 | ||
|
|
30c7a64619 | ||
|
|
ab12f29ebf | ||
|
|
a0c3a82c76 | ||
|
|
085a281f8a | ||
|
|
1f871c24b9 | ||
|
|
9f46b51985 | ||
|
|
fc572b8717 | ||
|
|
4404601fa3 | ||
|
|
faf7e28119 | ||
|
|
0303facfd9 | ||
|
|
86e056a736 | ||
|
|
75105dc1fc | ||
|
|
14b4979c98 | ||
|
|
f956c64765 | ||
|
|
8b3f954ad8 | ||
|
|
a013cfcd1e | ||
|
|
65553e6c43 | ||
|
|
401c881c00 | ||
|
|
cf0e4d24a1 | ||
|
|
21810f3b39 | ||
|
|
0b211ab0ba | ||
|
|
30c7cf3512 | ||
|
|
bf8839a560 | ||
|
|
818998c532 | ||
|
|
98f54029d0 | ||
|
|
d41f2c138b | ||
|
|
50f07a6a0d | ||
|
|
b10116febb | ||
|
|
17b5d732e4 | ||
|
|
5b16b1d827 | ||
|
|
e5fe962130 | ||
|
|
00f03d9bb7 | ||
|
|
f7c285af20 | ||
|
|
f6e3121c76 | ||
|
|
a95aa81850 | ||
|
|
ffa5825658 | ||
|
|
df5b90cc7c | ||
|
|
9dde644fd0 | ||
|
|
d525a1536c | ||
|
|
54f18f3f0d | ||
|
|
ce4f26a5d5 | ||
|
|
177a7429f1 | ||
|
|
f672d734f9 | ||
|
|
faf5a38f1c | ||
|
|
03c4267b84 | ||
|
|
ab08621808 | ||
|
|
d06c0e5c5d | ||
|
|
32e76298e4 | ||
|
|
752163a5c0 | ||
|
|
6568fa23f8 | ||
|
|
9c69288304 | ||
|
|
3a3346e750 | ||
|
|
47ab73d9a4 | ||
|
|
9555385325 | ||
|
|
f80a03afc7 | ||
|
|
c1b64770d1 | ||
|
|
400f106d62 | ||
|
|
1cc20716ae | ||
|
|
fc7d123871 | ||
|
|
2a8a014ad0 | ||
|
|
861f452d26 | ||
|
|
64a7d5e4fd | ||
|
|
7c45423969 | ||
|
|
480f70d7e2 | ||
|
|
7f182896ca | ||
|
|
1319fe2eb5 | ||
|
|
f5c231aa80 | ||
|
|
84d468333a | ||
|
|
dfdbb0a061 | ||
|
|
27365abbe3 | ||
|
|
f3aed46c94 | ||
|
|
7e0ea176cf | ||
|
|
791e13e7bf | ||
|
|
9dff3ac749 | ||
|
|
45f207164d | ||
|
|
bea6f3ec14 | ||
|
|
1edc883ca2 | ||
|
|
116825d604 | ||
|
|
26bc5ef091 | ||
|
|
8c228287aa | ||
|
|
5c7c682d41 | ||
|
|
bfc2eca041 | ||
|
|
b043b48e6e | ||
|
|
d7c03d7356 | ||
|
|
c41f6aebf4 | ||
|
|
5e6cda7c5e | ||
|
|
9c71b53abc | ||
|
|
3f96e04a7b | ||
|
|
90eeeedf52 | ||
|
|
6649362fec | ||
|
|
39c8889585 | ||
|
|
b5b1a64f0c | ||
|
|
cd108b5f57 | ||
|
|
780f3f8552 | ||
|
|
afea5eb69e | ||
|
|
b72cf5d364 | ||
|
|
c133686dde | ||
|
|
e216fa98ef | ||
|
|
8f9d8e188f | ||
|
|
800751cbe0 | ||
|
|
14bc73cde9 | ||
|
|
41307046be | ||
|
|
c259c79384 | ||
|
|
49f69facaf | ||
|
|
da2b6acf91 | ||
|
|
fbdbe848c5 | ||
|
|
cc41caede9 | ||
|
|
ee373eb6fb | ||
|
|
d4b18b1e27 | ||
|
|
94e2fe2cbb | ||
|
|
c93658ee04 | ||
|
|
ad7ee0b716 | ||
|
|
b2fad7a567 | ||
|
|
e80adc2799 | ||
|
|
ac111224ae | ||
|
|
0813bb1a28 | ||
|
|
4d1ad8f749 | ||
|
|
80b444e531 | ||
|
|
3a6468cd0a | ||
|
|
c9fdb215f3 | ||
|
|
85ff5f2d10 | ||
|
|
be3597524f | ||
|
|
5717dbda4f | ||
|
|
588eec4672 | ||
|
|
cc3d0d8211 | ||
|
|
1550efaf7e | ||
|
|
007300b881 | ||
|
|
454f072f13 | ||
|
|
b3be6c75c3 | ||
|
|
f8774f314b | ||
|
|
3db35ad343 | ||
|
|
0e42a779e2 | ||
|
|
d34e829811 | ||
|
|
ebd988702e | ||
|
|
1232398742 | ||
|
|
6dbcd47143 | ||
|
|
0e07482e26 | ||
|
|
6bb4800ecd | ||
|
|
37e1ac71b0 | ||
|
|
869f96816c | ||
|
|
35a7008444 | ||
|
|
67f80ee111 | ||
|
|
083a58753b | ||
|
|
b652357a5c | ||
|
|
49a52140b0 | ||
|
|
e37f84a227 | ||
|
|
50fc83d478 | ||
|
|
a3da1c54eb | ||
|
|
b8471e3cf1 | ||
|
|
4bd645d5c6 | ||
|
|
83446c0694 | ||
|
|
b4c30fc07b | ||
|
|
8325f5bbae | ||
|
|
9eb1196526 | ||
|
|
5fe5d4b6fc | ||
|
|
34af44b8d3 | ||
|
|
643a20fec3 | ||
|
|
576b931090 | ||
|
|
2cc1f044df | ||
|
|
c651392643 | ||
|
|
9a669176ac | ||
|
|
b902831db8 | ||
|
|
90a1de383a | ||
|
|
83f9d42d31 | ||
|
|
94136531a5 | ||
|
|
b8b1e6ba62 | ||
|
|
d6911337f3 | ||
|
|
d2cbb72490 | ||
|
|
c835b4d363 | ||
|
|
ece4480200 | ||
|
|
57f8d90e38 | ||
|
|
da2b2701fe | ||
|
|
6d7fba3abf | ||
|
|
90798c161b | ||
|
|
65e270ff57 | ||
|
|
4912e19281 | ||
|
|
6402564f44 | ||
|
|
a06d5dd7bb | ||
|
|
15e7486ca3 | ||
|
|
ac462cfce9 | ||
|
|
d921306dd8 | ||
|
|
05417705ed | ||
|
|
1c7d9e3d75 | ||
|
|
97dd928ca4 | ||
|
|
03949f7d60 | ||
|
|
effe477351 | ||
|
|
12ac858242 | ||
|
|
eec1937f52 | ||
|
|
906cd48fb7 | ||
|
|
78beecf6f1 | ||
|
|
06a0646541 | ||
|
|
0499a6c6f9 | ||
|
|
0e62f2ee85 | ||
|
|
2ba4c36af0 | ||
|
|
688e6ad79f | ||
|
|
f8ee0ce185 | ||
|
|
a226e06ae8 | ||
|
|
e0e51b51b7 | ||
|
|
9e9e3d43a8 | ||
|
|
7691a9bdad | ||
|
|
22020cae49 | ||
|
|
6cd910b0f6 | ||
|
|
b5e9811063 | ||
|
|
7668c7dd00 | ||
|
|
d71f743e85 | ||
|
|
73ef0222ac | ||
|
|
6152387096 | ||
|
|
b93ffe3b44 | ||
|
|
478eb7c5b5 | ||
|
|
6c40d8bd8b | ||
|
|
7026fe4d5a | ||
|
|
9a4491dc50 | ||
|
|
0d835e5014 | ||
|
|
29b5ffb58d | ||
|
|
a179ec485b | ||
|
|
f220a577e0 | ||
|
|
b22b1d4794 | ||
|
|
d2d19e3fbc | ||
|
|
19ef290499 | ||
|
|
f6584b45ed | ||
|
|
bad97a8b5b | ||
|
|
7116df15d6 | ||
|
|
ffbb018d12 | ||
|
|
a4dcdfc403 | ||
|
|
87d901b1ae | ||
|
|
9126651cb9 | ||
|
|
3996ce8ce5 | ||
|
|
6340423b32 | ||
|
|
0d921dc1fc | ||
|
|
25afea4f9c | ||
|
|
ba46bf3af5 | ||
|
|
416b4885f9 | ||
|
|
9a10ee0efa | ||
|
|
d63b78f0e7 | ||
|
|
99411241f6 | ||
|
|
941fb1349b | ||
|
|
a470925305 | ||
|
|
e3ac292af1 | ||
|
|
260b9a8d0d | ||
|
|
530407f9b2 | ||
|
|
52e7b50e9b | ||
|
|
23aa097d0a | ||
|
|
0bea153aa0 | ||
|
|
00be58e65d | ||
|
|
a5cc985bc2 | ||
|
|
afba54fb5e | ||
|
|
46150a2da4 | ||
|
|
842988c2ad | ||
|
|
ebad34ae51 | ||
|
|
bceb359438 | ||
|
|
19a32134ab | ||
|
|
e44a71daf2 | ||
|
|
573750a43d | ||
|
|
dd4c73b41f | ||
|
|
094326220f | ||
|
|
729a071284 | ||
|
|
ee7857a560 | ||
|
|
4cba604712 | ||
|
|
18a62797d9 | ||
|
|
5d4ac62c9e | ||
|
|
2ef8c41b48 | ||
|
|
891831a96a | ||
|
|
11d18fa1af | ||
|
|
ad69cbc681 | ||
|
|
10995de7bd | ||
|
|
c4d78c06eb | ||
|
|
eb252321a7 | ||
|
|
04dd1447ee | ||
|
|
b14edbe1f4 | ||
|
|
c697e52c0c | ||
|
|
b6911da8f0 | ||
|
|
038448865b | ||
|
|
9e44fdbc55 | ||
|
|
e088f0f31e | ||
|
|
6101feda10 | ||
|
|
7a3fa68468 | ||
|
|
16a11638a7 | ||
|
|
43932492a7 | ||
|
|
8d08ffc4ae | ||
|
|
a5a4ee4bfe | ||
|
|
e66f3a80e8 | ||
|
|
e69efacb47 | ||
|
|
528b361e34 | ||
|
|
a77bb3d341 | ||
|
|
6b17385ad7 | ||
|
|
33fc01ef4b | ||
|
|
482324c0bd | ||
|
|
0ab13a3a70 | ||
|
|
699fb73726 | ||
|
|
ddba29cc09 | ||
|
|
1cdb9ef47c | ||
|
|
ade3305b4c | ||
|
|
bd2bd37bc4 | ||
|
|
ee6f55193e | ||
|
|
1868df61ea | ||
|
|
dfa1fc7b09 | ||
|
|
a52b0495f1 | ||
|
|
ce00eee29f | ||
|
|
858bfade6a | ||
|
|
4535cf9274 | ||
|
|
84d1fa54ec | ||
|
|
7cf4721efa | ||
|
|
6578838587 | ||
|
|
46b7c4717f | ||
|
|
52bcf63554 | ||
|
|
37ec97550e | ||
|
|
55e20abea8 | ||
|
|
ec7d81dc7a | ||
|
|
e93387b719 | ||
|
|
050b476fc3 | ||
|
|
5b5ffd7eaf | ||
|
|
39bf5fdc05 | ||
|
|
09ff43ef0a | ||
|
|
5a02aeaa89 | ||
|
|
2a3a91af42 | ||
|
|
d4a64e93e0 | ||
|
|
c126b64892 | ||
|
|
526814b9ed | ||
|
|
bc3b790f13 | ||
|
|
1fb0765443 | ||
|
|
c4b85f9bda | ||
|
|
161b5530b9 | ||
|
|
e03979ebdc | ||
|
|
356c0ab594 | ||
|
|
85152bac34 | ||
|
|
c832888d56 | ||
|
|
cac1b87b44 | ||
|
|
30f71b2ed6 | ||
|
|
9f8e9a1916 | ||
|
|
8519fe57ae | ||
|
|
1442a3853c | ||
|
|
2658ddf868 | ||
|
|
f95eecfcc6 | ||
|
|
1e598cca31 | ||
|
|
f3eaeb96f0 | ||
|
|
91fac8049c | ||
|
|
b1325a7514 | ||
|
|
150b83b4f3 | ||
|
|
3a3a5dc071 | ||
|
|
9b76a5bd66 | ||
|
|
850a1f9155 | ||
|
|
ef6c8a38d0 | ||
|
|
e228cb6648 | ||
|
|
3a53cac607 | ||
|
|
df4bd57c82 | ||
|
|
de737fc72d | ||
|
|
ccea7a5783 | ||
|
|
5b84e1528a | ||
|
|
7382addfe4 | ||
|
|
b8dd6822ac | ||
|
|
8c47b85b9b | ||
|
|
4d2666a0e3 | ||
|
|
fc18eaa292 | ||
|
|
22199cdd87 | ||
|
|
76820f25c6 | ||
|
|
bac4984496 | ||
|
|
1414c5feac | ||
|
|
c344f63154 | ||
|
|
77ea28bef2 | ||
|
|
bf63b15e07 | ||
|
|
ce05504278 | ||
|
|
95b90974a2 | ||
|
|
1adbef7352 | ||
|
|
3109928dcd | ||
|
|
24dd63789c | ||
|
|
27225e6b5e | ||
|
|
2994978fdb | ||
|
|
ffd864dc72 | ||
|
|
53a2c88d4b | ||
|
|
444ae73ffb | ||
|
|
9f5f4b9c91 | ||
|
|
ffd2af0257 | ||
|
|
b983845745 | ||
|
|
cfcc6e1029 | ||
|
|
6fea4377b2 | ||
|
|
e01230e425 | ||
|
|
f66db81edd | ||
|
|
a47471d0ae | ||
|
|
06b5a91cd3 | ||
|
|
8aacf3497e | ||
|
|
7fc5e304e3 | ||
|
|
65536febd5 | ||
|
|
60f7910840 | ||
|
|
829c3c612e | ||
|
|
355c7b59aa | ||
|
|
6508cdc823 | ||
|
|
9ef9f603a3 | ||
|
|
e76f881c67 | ||
|
|
4292d2ce86 | ||
|
|
ae927c5600 | ||
|
|
57e2eed771 | ||
|
|
ca807b892f | ||
|
|
9bc015d10e | ||
|
|
59b204c3bc | ||
|
|
4ac2472a62 | ||
|
|
054b9c2402 | ||
|
|
96f267cbfd | ||
|
|
2018163107 | ||
|
|
253b5b1fb7 | ||
|
|
0be2f1b295 | ||
|
|
a8838fc611 | ||
|
|
d02636e370 | ||
|
|
f3c1b09188 | ||
|
|
7bac69f360 | ||
|
|
5a3fdd225e | ||
|
|
4660f58653 | ||
|
|
5fabca3616 | ||
|
|
edb74dae87 | ||
|
|
31b833b46a | ||
|
|
21bc1c51ba | ||
|
|
4f6cab9a45 | ||
|
|
33626fa47c | ||
|
|
9fcc1e17f2 | ||
|
|
a5f48f8af8 | ||
|
|
3dfa526ec6 | ||
|
|
dc28066017 | ||
|
|
2f2edfbde8 | ||
|
|
3beac98499 | ||
|
|
f70ce9dd26 | ||
|
|
e301b9d399 | ||
|
|
42f92ecd7a | ||
|
|
27eb0d04b9 | ||
|
|
112d13c127 | ||
|
|
16afc9d0f8 | ||
|
|
5b4889b46b | ||
|
|
f7eed0e2af | ||
|
|
cce287e8a6 | ||
|
|
9a001ebacc | ||
|
|
79ab4c02bb | ||
|
|
b6de70a3a6 | ||
|
|
15cafe2d78 | ||
|
|
90197abc5c | ||
|
|
2147436a2a | ||
|
|
8ef0c66966 | ||
|
|
2779ad6c6f | ||
|
|
f30fe3c746 | ||
|
|
f71496693f | ||
|
|
e4cd5dbf1f | ||
|
|
ffd5defcb6 | ||
|
|
0b81421124 | ||
|
|
0f73b9e07f | ||
|
|
d3078699d6 | ||
|
|
cc39f9f891 | ||
|
|
70608df843 | ||
|
|
e7f2c6b5ba | ||
|
|
420fc9aa24 | ||
|
|
200012fd41 | ||
|
|
e3c858278b | ||
|
|
1ec7c3afa4 | ||
|
|
6ab0b60f12 | ||
|
|
5ec38b44b0 | ||
|
|
39c2294d43 | ||
|
|
3b92481133 | ||
|
|
6b8d24d2f5 | ||
|
|
62d6ff61ab | ||
|
|
f7c056145a | ||
|
|
451f54b036 | ||
|
|
ef378de883 | ||
|
|
2f2b63da4a | ||
|
|
01d8f2c7e6 | ||
|
|
330d553ffb | ||
|
|
f1d0895eb1 | ||
|
|
4eaf6415b7 | ||
|
|
f0650bf64f | ||
|
|
44856c6207 | ||
|
|
dcbf6e43c7 | ||
|
|
0d22da672d | ||
|
|
a62471b9b8 | ||
|
|
f673f2c5bc | ||
|
|
a7caae5c17 | ||
|
|
d0deefd0d7 | ||
|
|
9810da6982 | ||
|
|
cb93a3bee3 | ||
|
|
aeacc3e39d | ||
|
|
e1c861cc8a | ||
|
|
37ee05a576 | ||
|
|
07a2bd817d | ||
|
|
78c171e0c2 | ||
|
|
1ae436cb08 | ||
|
|
b0d3a84d1e | ||
|
|
1acd9ca981 | ||
|
|
72467b6b9c | ||
|
|
862c85387c | ||
|
|
eeeca3e21c | ||
|
|
4ee4af7fd4 | ||
|
|
d131cc1871 | ||
|
|
bd21ee318d | ||
|
|
02e7fc0bca | ||
|
|
51ba46970e | ||
|
|
9d65b44ab7 | ||
|
|
350d51a92d | ||
|
|
2a8202caa7 | ||
|
|
9e4591554f | ||
|
|
e98ecfd1f9 | ||
|
|
1cbf5c4359 | ||
|
|
0446faeb6f | ||
|
|
0417ea23c3 | ||
|
|
de9886e011 | ||
|
|
770e57c361 | ||
|
|
d80a00701d | ||
|
|
62d3cd8795 | ||
|
|
ad9ec22fd1 | ||
|
|
293985ec10 | ||
|
|
105598ac8e | ||
|
|
ff93f2220a | ||
|
|
33fddcb289 | ||
|
|
6a09bf2c54 | ||
|
|
2913f5bbbe | ||
|
|
671bdb09b8 | ||
|
|
353a0d14a4 | ||
|
|
e0fd83211b | ||
|
|
2c94d9cc37 | ||
|
|
f7dec412ec | ||
|
|
f7c756d215 | ||
|
|
617635a989 | ||
|
|
ea3b2c4713 | ||
|
|
15455a7e68 | ||
|
|
ad5cedc08c | ||
|
|
328f0c6da8 | ||
|
|
8ef818f581 | ||
|
|
48dace64e4 | ||
|
|
ab5837c7c4 | ||
|
|
9fbdbc1844 | ||
|
|
54129cecc2 | ||
|
|
69c514d1a4 | ||
|
|
7047d12eba | ||
|
|
8ab61637f0 | ||
|
|
7e0bd92593 | ||
|
|
99d9a9f44e | ||
|
|
9c55111e14 | ||
|
|
b1d6ba57ad | ||
|
|
47e9330646 | ||
|
|
e16a3d503c | ||
|
|
dc9c93b347 | ||
|
|
faceff620a | ||
|
|
a5a6a58556 | ||
|
|
45fdab27c9 | ||
|
|
8b360479f4 | ||
|
|
b170f2f69a | ||
|
|
7f1abab9ae | ||
|
|
2ec7bc878e | ||
|
|
e97d9bb095 | ||
|
|
2f3b02e870 | ||
|
|
11b7a45c32 | ||
|
|
fb10d2f9c2 | ||
|
|
60bb7f66cf | ||
|
|
ad5143169a | ||
|
|
d68c90a429 | ||
|
|
8a51549395 | ||
|
|
9f997fe1fa | ||
|
|
00f1f6d86b | ||
|
|
be72728000 | ||
|
|
f558126854 | ||
|
|
08ed1500ac | ||
|
|
f0926f1cc7 | ||
|
|
fe3f6aa22b | ||
|
|
1325f75e81 | ||
|
|
4b1d3a7b6f | ||
|
|
404afcdfd6 | ||
|
|
99793180f1 | ||
|
|
c91f9beddb | ||
|
|
c9c5ccaa6d | ||
|
|
da42de7ac8 | ||
|
|
3c9a6ad218 | ||
|
|
f0389d6b47 | ||
|
|
76eab21738 | ||
|
|
3b32c57d94 | ||
|
|
d119032cb9 | ||
|
|
397a04b49c | ||
|
|
d32c8e9457 | ||
|
|
ebb2974ca7 | ||
|
|
cbb1131a5d | ||
|
|
d9de7fe3b0 | ||
|
|
24ba692884 | ||
|
|
960bbbde29 | ||
|
|
eb79b79c35 | ||
|
|
8212ed6d87 | ||
|
|
c639cb5285 | ||
|
|
a88fd1c669 | ||
|
|
b053f23d15 | ||
|
|
9d32a89491 | ||
|
|
cb9a147fff | ||
|
|
c9fddedbf1 | ||
|
|
530bbbe4c5 | ||
|
|
73e5a9f45b | ||
|
|
72d4e947b6 | ||
|
|
41750c3639 | ||
|
|
52b36f38a0 | ||
|
|
58ceb454dd | ||
|
|
9e5a2db4c5 | ||
|
|
781f283c89 | ||
|
|
f1cd085727 | ||
|
|
3aba855bd6 | ||
|
|
7e3abb27a4 | ||
|
|
f0c11daa37 | ||
|
|
167c174f4d | ||
|
|
0173060eaf | ||
|
|
4edd25ff5d | ||
|
|
2a4e098645 | ||
|
|
7c5019c30f | ||
|
|
c13fe3feb6 | ||
|
|
0ced60e712 | ||
|
|
d0764d5854 | ||
|
|
f7905e3bc2 | ||
|
|
3e770792a4 | ||
|
|
8f9934a7f4 | ||
|
|
b921e7d595 | ||
|
|
a9cb2d2efd | ||
|
|
fe519b3acf | ||
|
|
30cc4a1668 | ||
|
|
a71e4d8970 | ||
|
|
66e7141415 | ||
|
|
6f0cdc864f | ||
|
|
4f9deaaf09 | ||
|
|
380745a814 | ||
|
|
ce4e335412 | ||
|
|
274b8181d1 | ||
|
|
767bdcc2ce | ||
|
|
c00cde8bcc | ||
|
|
5859a18b3a | ||
|
|
375124642d | ||
|
|
af9e6dea68 | ||
|
|
78f2267b06 | ||
|
|
0a34f22240 | ||
|
|
b0651fcaed | ||
|
|
d59faa2a08 | ||
|
|
6a3bc37dc1 | ||
|
|
2ea7796b26 | ||
|
|
cf0057bac4 | ||
|
|
fafb325a88 | ||
|
|
6b69b4671f | ||
|
|
eaa95ee840 | ||
|
|
a86ba1fdf6 | ||
|
|
09e0d2d15b | ||
|
|
6d9fa6418d | ||
|
|
6010f5b7a6 | ||
|
|
9c164e1cea | ||
|
|
880fba8dce | ||
|
|
17d57ee49a | ||
|
|
234949a627 | ||
|
|
ff34671526 | ||
|
|
6e22983d98 | ||
|
|
18254c30ec | ||
|
|
922c955239 | ||
|
|
618a325057 | ||
|
|
d81cb7f866 | ||
|
|
f71a4d5381 | ||
|
|
282cb60a66 | ||
|
|
58dbac4422 | ||
|
|
53e228465b |
12
.github/FUNDING.yml
vendored
Normal file
12
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: skypjack
|
||||
patreon: skypjack
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: https://www.paypal.me/skypjack
|
||||
75
.github/workflows/build.yml
vendored
Normal file
75
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
name: build
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
|
||||
linux:
|
||||
timeout-minutes: 10
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
compiler: [g++, clang++]
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Compile tests
|
||||
working-directory: build
|
||||
env:
|
||||
CXX: ${{ matrix.compiler }}
|
||||
run: |
|
||||
cmake -DBUILD_TESTING=ON -DBUILD_LIB=ON ..
|
||||
make -j4
|
||||
- name: Run tests
|
||||
working-directory: build
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
run: ctest --timeout 5 -C Debug -j4
|
||||
|
||||
windows:
|
||||
timeout-minutes: 10
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-latest, windows-2016]
|
||||
toolset: [clang-cl, default]
|
||||
include:
|
||||
- toolset: clang-cl
|
||||
toolset_option: -T"ClangCl"
|
||||
exclude:
|
||||
- os: windows-2016
|
||||
toolset: clang-cl
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Compile tests
|
||||
working-directory: build
|
||||
run: |
|
||||
cmake -DBUILD_TESTING=ON -DBUILD_LIB=ON ${{ matrix.toolset_option }} ..
|
||||
cmake --build . -j 4
|
||||
- name: Run tests
|
||||
working-directory: build
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
run: ctest --timeout 5 -C Debug -j4
|
||||
|
||||
macos:
|
||||
timeout-minutes: 10
|
||||
runs-on: macOS-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Compile tests
|
||||
working-directory: build
|
||||
run: |
|
||||
cmake -DBUILD_TESTING=ON -DBUILD_LIB=ON ..
|
||||
make -j4
|
||||
- name: Run tests
|
||||
working-directory: build
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
run: ctest --timeout 5 -C Debug -j4
|
||||
33
.github/workflows/coverage.yml
vendored
Normal file
33
.github/workflows/coverage.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: coverage
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
|
||||
codecov:
|
||||
timeout-minutes: 10
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Compile tests
|
||||
working-directory: build
|
||||
env:
|
||||
CXXFLAGS: "-O0 --coverage -fno-inline -fno-inline-small-functions -fno-default-inline"
|
||||
CXX: g++
|
||||
run: |
|
||||
cmake -DBUILD_TESTING=ON -DBUILD_LIB=ON ..
|
||||
make -j4
|
||||
- name: Run tests
|
||||
working-directory: build
|
||||
env:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
run: ctest --timeout 5 -C Debug -j4
|
||||
- name: Upload coverage to Codecov
|
||||
working-directory: build
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
run: |
|
||||
wget https://codecov.io/bash -O codecov
|
||||
chmod +x codecov
|
||||
./codecov -t $CODECOV_TOKEN -B $GITHUB_REF -s test/
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -1 +1,10 @@
|
||||
# Conan
|
||||
conan/test_package/build
|
||||
|
||||
# IDEs
|
||||
*.user
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
# Bazel
|
||||
/bazel-*
|
||||
|
||||
80
.travis.yml
80
.travis.yml
@@ -1,80 +0,0 @@
|
||||
language: cpp
|
||||
dist: trusty
|
||||
sudo: false
|
||||
|
||||
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:
|
||||
apt:
|
||||
sources: ['ubuntu-toolchain-r-test']
|
||||
packages: ['g++-7']
|
||||
env: COMPILER=g++-7
|
||||
- os: linux
|
||||
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
|
||||
- os: osx
|
||||
osx_image: xcode8.3
|
||||
compiler: clang
|
||||
env: COMPILER=clang++
|
||||
- os: osx
|
||||
osx_image: xcode9.4
|
||||
compiler: clang
|
||||
env: COMPILER=clang++
|
||||
- os: linux
|
||||
compiler: gcc
|
||||
addons:
|
||||
apt:
|
||||
sources: ['ubuntu-toolchain-r-test']
|
||||
packages: ['g++-7']
|
||||
env:
|
||||
- COMPILER=g++-7
|
||||
- CXXFLAGS="-O0 --coverage -fno-inline -fno-inline-small-functions -fno-default-inline"
|
||||
before_script:
|
||||
- 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
|
||||
|
||||
notifications:
|
||||
email:
|
||||
on_success: never
|
||||
on_failure: always
|
||||
|
||||
install:
|
||||
- echo ${PATH}
|
||||
- cmake --version
|
||||
- export CXX=${COMPILER}
|
||||
- echo ${CXX}
|
||||
- ${CXX} --version
|
||||
- ${CXX} -v
|
||||
|
||||
script:
|
||||
- mkdir -p build && cd build
|
||||
- cmake .. && make -j4
|
||||
- CTEST_OUTPUT_ON_FAILURE=1 make test
|
||||
|
||||
deploy:
|
||||
provider: script
|
||||
script: scripts/update_packages.sh $TRAVIS_TAG
|
||||
on:
|
||||
tags: true
|
||||
condition: “$TRAVIS_BRANCH” = “$TRAVIS_TAG”
|
||||
45
AUTHORS
45
AUTHORS
@@ -1,11 +1,44 @@
|
||||
# Author
|
||||
|
||||
Michele Caini aka skypjack
|
||||
skypjack
|
||||
|
||||
# Contributors
|
||||
|
||||
Paolo Monteverde aka morbo84
|
||||
David Nerjes aka DavidHamburg
|
||||
Indi Kernick aka Kerndog73
|
||||
Malte Müller-Rowold aka m-waka
|
||||
Richard Caseres aka richardbmx
|
||||
BenediktConze
|
||||
bjadamson
|
||||
ceeac
|
||||
ColinH
|
||||
corystegel
|
||||
Croydon
|
||||
dbacchet
|
||||
dBagrat
|
||||
djarek
|
||||
DonKult
|
||||
drglove
|
||||
eugeneko
|
||||
gale83
|
||||
ghost
|
||||
grdowns
|
||||
Green-Sky
|
||||
Innokentiy-Alaytsev
|
||||
Kerndog73
|
||||
Lawrencemm
|
||||
markand
|
||||
mhammerc
|
||||
Milerius
|
||||
morbo84
|
||||
m-waka
|
||||
NixAJ
|
||||
Paolo-Oliverio
|
||||
pgruenbacher
|
||||
prowolf
|
||||
stefanofiorentino
|
||||
suVrik
|
||||
szunhammer
|
||||
The5-1
|
||||
vblanco20-1
|
||||
willtunnels
|
||||
WizardIke
|
||||
w1th0utnam3
|
||||
xissburg
|
||||
zaucy
|
||||
|
||||
15
BUILD.bazel
Normal file
15
BUILD.bazel
Normal file
@@ -0,0 +1,15 @@
|
||||
_msvc_copts = ["/std:c++17"]
|
||||
_gcc_copts = ["-std=c++17"]
|
||||
|
||||
cc_library(
|
||||
name = "entt",
|
||||
visibility = ["//visibility:public"],
|
||||
strip_include_prefix = "src",
|
||||
hdrs = glob(["src/**/*.h", "src/**/*.hpp"]),
|
||||
copts = select({
|
||||
"@bazel_tools//src/conditions:windows": _msvc_copts,
|
||||
"@bazel_tools//src/conditions:windows_msvc": _msvc_copts,
|
||||
"@bazel_tools//src/conditions:windows_msys": _msvc_copts,
|
||||
"//conditions:default": _gcc_copts,
|
||||
}),
|
||||
)
|
||||
109
CMakeLists.txt
109
CMakeLists.txt
@@ -2,7 +2,7 @@
|
||||
# EnTT
|
||||
#
|
||||
|
||||
cmake_minimum_required(VERSION 3.7.2)
|
||||
cmake_minimum_required(VERSION 3.12.4)
|
||||
|
||||
#
|
||||
# Building in-tree is not allowed (we take care of your craziness).
|
||||
@@ -12,37 +12,44 @@ if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR})
|
||||
message(FATAL_ERROR "Prevented in-tree built. Please create a build directory outside of the source code and call cmake from there. Thank you.")
|
||||
endif()
|
||||
|
||||
#
|
||||
# Read project version
|
||||
#
|
||||
|
||||
set(ENTT_VERSION_REGEX "#define ENTT_VERSION_.*[ \t]+(.+)")
|
||||
file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/src/entt/config/version.h" ENTT_VERSION REGEX ${ENTT_VERSION_REGEX})
|
||||
list(TRANSFORM ENTT_VERSION REPLACE ${ENTT_VERSION_REGEX} "\\1")
|
||||
string(JOIN "." ENTT_VERSION ${ENTT_VERSION})
|
||||
|
||||
#
|
||||
# Project configuration
|
||||
#
|
||||
|
||||
project(EnTT VERSION 2.7.3)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
project(
|
||||
EnTT
|
||||
VERSION ${ENTT_VERSION}
|
||||
DESCRIPTION "Gaming meets modern C++ - a fast and reliable entity-component system (ECS) and much more"
|
||||
HOMEPAGE_URL "https://github.com/skypjack/entt"
|
||||
LANGUAGES CXX
|
||||
)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Debug)
|
||||
endif()
|
||||
|
||||
set(SETTINGS_ORGANIZATION "Michele Caini")
|
||||
set(SETTINGS_APPLICATION ${PROJECT_NAME})
|
||||
set(PROJECT_AUTHOR "Michele Caini")
|
||||
set(PROJECT_AUTHOR_EMAIL "michele.caini@gmail.com")
|
||||
|
||||
message("*")
|
||||
message("* ${PROJECT_NAME} v${PROJECT_VERSION} (${CMAKE_BUILD_TYPE})")
|
||||
message("* Copyright (c) 2017-2018 ${PROJECT_AUTHOR} <${PROJECT_AUTHOR_EMAIL}>")
|
||||
message("* Copyright (c) 2017-2020 Michele Caini <michele.caini@gmail.com>")
|
||||
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
|
||||
#
|
||||
|
||||
if(NOT MSVC AND USE_LIBCPP)
|
||||
if(NOT WIN32 AND USE_LIBCPP)
|
||||
include(CheckCXXSourceCompiles)
|
||||
include(CMakePushCheckState)
|
||||
|
||||
@@ -52,7 +59,7 @@ if(NOT MSVC AND USE_LIBCPP)
|
||||
|
||||
check_cxx_source_compiles("
|
||||
#include<type_traits>
|
||||
int main() { return std::is_same<int, int>::value ? 0 : 1; }
|
||||
int main() { return std::is_same_v<int, char>; }
|
||||
" HAS_LIBCPP)
|
||||
|
||||
if(NOT HAS_LIBCPP)
|
||||
@@ -66,41 +73,35 @@ endif()
|
||||
# Add EnTT target
|
||||
#
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
add_library(EnTT INTERFACE)
|
||||
add_library(EnTT::EnTT ALIAS EnTT)
|
||||
|
||||
target_include_directories(
|
||||
EnTT INTERFACE
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
||||
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>
|
||||
INTERFACE
|
||||
$<$<AND:$<CONFIG:Debug>,$<NOT:$<CXX_COMPILER_ID:MSVC>>>:DEBUG>
|
||||
$<$<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>
|
||||
)
|
||||
target_compile_options(EnTT INTERFACE $<$<CONFIG:Debug>:-fsanitize=address -fno-omit-frame-pointer>)
|
||||
target_link_libraries(EnTT INTERFACE $<$<CONFIG:Debug>:-fsanitize=address -fno-omit-frame-pointer>)
|
||||
endif()
|
||||
|
||||
if(HAS_LIBCPP)
|
||||
target_compile_options(EnTT BEFORE INTERFACE -stdlib=libc++)
|
||||
endif()
|
||||
|
||||
target_compile_features(EnTT INTERFACE cxx_std_14)
|
||||
target_compile_features(EnTT INTERFACE cxx_std_17)
|
||||
|
||||
#
|
||||
# Install EnTT
|
||||
@@ -121,6 +122,7 @@ install(
|
||||
EXPORT EnTTTargets
|
||||
FILE EnTTTargets.cmake
|
||||
DESTINATION ${CUSTOM_INSTALL_CONFIGDIR}
|
||||
NAMESPACE EnTT::
|
||||
)
|
||||
|
||||
#
|
||||
@@ -140,7 +142,6 @@ configure_package_config_file(
|
||||
${CUSTOM_INSTALL_CONFIGDIR}/EnTTConfig.cmake
|
||||
INSTALL_DESTINATION ${CUSTOM_INSTALL_CONFIGDIR}
|
||||
PATH_VARS CMAKE_INSTALL_INCLUDEDIR
|
||||
NO_CHECK_REQUIRED_COMPONENTS_MACRO
|
||||
)
|
||||
|
||||
write_basic_package_version_file(
|
||||
@@ -162,29 +163,12 @@ 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)
|
||||
|
||||
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)
|
||||
add_library(GTest::Main ALIAS gtest_main)
|
||||
endif()
|
||||
|
||||
option(BUILD_BENCHMARK "Build benchmark." OFF)
|
||||
option(BUILD_MOD "Build mod example." OFF)
|
||||
option(BUILD_LIB "Build lib example." OFF)
|
||||
option(BUILD_SNAPSHOT "Build snapshot example." OFF)
|
||||
|
||||
enable_testing()
|
||||
@@ -209,13 +193,16 @@ endif()
|
||||
# AOB
|
||||
#
|
||||
|
||||
add_custom_target(
|
||||
entt_aob
|
||||
SOURCES
|
||||
appveyor.yml
|
||||
AUTHORS
|
||||
LICENSE
|
||||
README.md
|
||||
TODO
|
||||
.travis.yml
|
||||
set(
|
||||
AOB_SOURCES
|
||||
.github/workflows/build.yml
|
||||
.github/workflows/coverage.yml
|
||||
.github/FUNDING.yml
|
||||
AUTHORS
|
||||
CONTRIBUTING.md
|
||||
LICENSE
|
||||
README.md
|
||||
TODO
|
||||
)
|
||||
|
||||
add_custom_target(aob SOURCES ${AOB_SOURCES})
|
||||
|
||||
@@ -22,8 +22,8 @@ How to do it mostly depends on the type of contribution:
|
||||
If you are unable to find an open issue addressing the problem, feel free to
|
||||
[open a new one](https://github.com/skypjack/entt/issues/new). **Please**, do
|
||||
not forget to carefully describe how to reproduce the problem, then add all
|
||||
the informaion about the system on which you are experiencing it and point out
|
||||
the version of `EnTT` you are using (tag or commit).
|
||||
the information about the system on which you are experiencing it and point
|
||||
out the version of `EnTT` you are using (tag or commit).
|
||||
|
||||
* If you found a bug and you wrote a patch to fix it, open a new
|
||||
[pull request](https://github.com/skypjack/entt/pulls) with your code.
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2018 Michele Caini
|
||||
Copyright (c) 2017-2020 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
|
||||
|
||||
324
README.md
324
README.md
@@ -3,11 +3,44 @@
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
[](https://travis-ci.org/skypjack/entt)
|
||||
[](https://ci.appveyor.com/project/skypjack/entt)
|
||||
[](https://coveralls.io/github/skypjack/entt?branch=master)
|
||||
[](https://github.com/skypjack/entt/releases)
|
||||
[](https://github.com/skypjack/entt/actions)
|
||||
[](https://codecov.io/gh/skypjack/entt)
|
||||
[](https://godbolt.org/z/v8txVr)
|
||||
[](https://gitter.im/skypjack/entt)
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=W2HF9FESD5LJY&lc=IT&item_name=Michele%20Caini¤cy_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted)
|
||||
[](https://www.paypal.me/skypjack)
|
||||
[](https://www.patreon.com/bePatron?c=1772573)
|
||||
|
||||
`EnTT` is a header-only, tiny and easy to use library for game programming and
|
||||
much more written in **modern C++**, mainly known for its innovative
|
||||
**entity-component-system (ECS)** model.<br/>
|
||||
[Among others](https://github.com/skypjack/entt/wiki/EnTT-in-Action), it's used
|
||||
in [**Minecraft**](https://minecraft.net/en-us/attribution/) by Mojang and the
|
||||
[**ArcGIS Runtime SDKs**](https://developers.arcgis.com/arcgis-runtime/) by
|
||||
Esri.<br/>
|
||||
If you don't see your project in the list, please open an issue, submit a PR or
|
||||
add the [#entt](https://github.com/topics/entt) tag to your _topics_! :+1:
|
||||
|
||||
---
|
||||
|
||||
Do you want to **keep up with changes** or do you have a **question** that
|
||||
doesn't require you to open an issue?<br/>
|
||||
Join the [gitter channel](https://gitter.im/skypjack/entt) and meet other users
|
||||
like you. The more we are, the better for everyone.
|
||||
|
||||
Wondering why your **debug build** is so slow on Windows or how to represent a
|
||||
**hierarchy** with components?<br/>
|
||||
Check out the
|
||||
[FAQ](https://github.com/skypjack/entt/wiki/Frequently-Asked-Questions) and the
|
||||
[wiki](https://github.com/skypjack/entt/wiki) if you have these or other doubts,
|
||||
your answers may already be there.
|
||||
|
||||
If you use `EnTT` and you want to say thanks or support the project, please
|
||||
**consider becoming a
|
||||
[sponsor](https://github.com/users/skypjack/sponsorship)**.<br/>
|
||||
You can help me make the difference.
|
||||
[Many thanks](https://skypjack.github.io/sponsorship/) to those who supported me
|
||||
and still support me today.
|
||||
|
||||
# Table of Contents
|
||||
|
||||
@@ -25,17 +58,12 @@
|
||||
* [Contributors](#contributors)
|
||||
* [License](#license)
|
||||
* [Support](#support)
|
||||
* [Donation](#donation)
|
||||
* [Hire me](#hire-me)
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
|
||||
# Introduction
|
||||
|
||||
`EnTT` is a header-only, tiny and easy to use entity-component system (and much
|
||||
more) written in modern C++ and even
|
||||
[used by Mojang in Minecraft](https://minecraft.net/en-us/attribution/).<br/>
|
||||
The entity-component-system (also known as _ECS_) is an architectural pattern
|
||||
used mostly in game development. For further details:
|
||||
|
||||
@@ -43,38 +71,39 @@ used mostly in game development. For further details:
|
||||
* [Evolve Your Hierarchy](http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/)
|
||||
* [ECS on Wikipedia](https://en.wikipedia.org/wiki/Entity%E2%80%93component%E2%80%93system)
|
||||
|
||||
A long time ago, the sole entity-component system was part of the project. After
|
||||
a while the codebase has grown and more and more classes have become part of the
|
||||
repository.<br/>
|
||||
This project started off as a pure entity-component system. Over time the
|
||||
codebase has grown as more and more classes and functionalities were added.<br/>
|
||||
Here is a brief, yet incomplete list of what it offers today:
|
||||
|
||||
* Statically generated integer identifiers for types (assigned either at
|
||||
* Statically generated integer **identifiers** for types (assigned either at
|
||||
compile-time or at runtime).
|
||||
* A constexpr utility for human readable resource identifiers.
|
||||
* A minimal configuration system built on top of the monostate pattern.
|
||||
* An incredibly fast entity-component system based on sparse sets, with its own
|
||||
views and a _pay for what you use_ policy to adjust performance and memory
|
||||
usage according to users' requirements.
|
||||
* A lot of facilities built on top of the entity-component system to help
|
||||
developers and avoid reinventing the wheel (ie dependencies, snapshot, actor
|
||||
class for those who aren't confident with the architecture and so on).
|
||||
* The smallest and most basic implementation of a service locator ever seen.
|
||||
* A cooperative scheduler for processes of any type.
|
||||
* All what is needed for resource management (cache, loaders, handles).
|
||||
* Delegates, signal handlers (with built-in support for collectors) and a tiny
|
||||
event dispatcher.
|
||||
* A general purpose event emitter, that is a CRTP idiom based class template.
|
||||
* An event dispatcher for immediate and delayed events to integrate in loops.
|
||||
* ...
|
||||
* Any other business.
|
||||
* A `constexpr` utility for human readable **resource names**.
|
||||
* A minimal **configuration system** built using the monostate pattern.
|
||||
* An incredibly fast **entity-component system** based on sparse sets, with its
|
||||
own _pay for what you use_ policy to adjust performance and memory usage
|
||||
according to the users' requirements.
|
||||
* Views and groups to iterate entities and components and allow different access
|
||||
patterns, from **perfect SoA** to fully random.
|
||||
* A lot of **facilities** built on top of the entity-component system to help
|
||||
the users and avoid reinventing the wheel (dependencies, snapshot, actor
|
||||
class, support for **reactive systems** and so on).
|
||||
* The smallest and most basic implementation of a **service locator** ever seen.
|
||||
* A built-in, non-intrusive and macro-free runtime **reflection system**.
|
||||
* A **cooperative scheduler** for processes of any type.
|
||||
* All that is needed for **resource management** (cache, loaders, handles).
|
||||
* Delegates, **signal handlers** (with built-in support for collectors) and a
|
||||
tiny event dispatcher for immediate and delayed events to integrate in loops.
|
||||
* A general purpose **event emitter** as a CRTP idiom based class template.
|
||||
* And **much more**! Check out the
|
||||
[**wiki**](https://github.com/skypjack/entt/wiki).
|
||||
|
||||
Consider it a work in progress. The whole API is also fully documented in-code
|
||||
for those who are brave enough to read it.
|
||||
Consider this list a work in progress as well as the project. The whole API is
|
||||
fully documented in-code for those who are brave enough to read it.
|
||||
|
||||
Currently, `EnTT` is tested on Linux, Microsoft Windows and OS X. It has proven
|
||||
Currently, `EnTT` is tested on Linux, Microsoft Windows and OSX. It has proven
|
||||
to work also on both Android and iOS.<br/>
|
||||
Most likely it will not be problematic on other systems as well, but has not
|
||||
been sufficiently tested so far.
|
||||
Most likely it won't be problematic on other systems as well, but it hasn't been
|
||||
sufficiently tested so far.
|
||||
|
||||
## Code Example
|
||||
|
||||
@@ -82,50 +111,50 @@ been sufficiently tested so far.
|
||||
#include <entt/entt.hpp>
|
||||
#include <cstdint>
|
||||
|
||||
struct Position {
|
||||
struct position {
|
||||
float x;
|
||||
float y;
|
||||
};
|
||||
|
||||
struct Velocity {
|
||||
struct velocity {
|
||||
float dx;
|
||||
float dy;
|
||||
};
|
||||
|
||||
void update(entt::DefaultRegistry ®istry) {
|
||||
auto view = registry.view<Position, Velocity>();
|
||||
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 &velocity = view.get<Velocity>(entity);
|
||||
auto &vel = view.get<velocity>(entity);
|
||||
|
||||
velocity.dx = 0.;
|
||||
velocity.dy = 0.;
|
||||
vel.dx = 0.;
|
||||
vel.dy = 0.;
|
||||
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
void update(std::uint64_t dt, entt::DefaultRegistry ®istry) {
|
||||
registry.view<Position, Velocity>().each([dt](auto entity, auto &position, auto &velocity) {
|
||||
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 ...
|
||||
|
||||
position.x += velocity.dx * dt;
|
||||
position.y += velocity.dy * dt;
|
||||
pos.x += vel.dx * dt;
|
||||
pos.y += vel.dy * dt;
|
||||
|
||||
// ...
|
||||
});
|
||||
}
|
||||
|
||||
int main() {
|
||||
entt::DefaultRegistry registry;
|
||||
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); }
|
||||
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);
|
||||
@@ -137,11 +166,10 @@ int main() {
|
||||
|
||||
## Motivation
|
||||
|
||||
I started working on `EnTT` because of the wrong reason: my goal was to design
|
||||
an entity-component system that beated another well known open source solution
|
||||
in terms of performance and used (possibly) less memory in the average
|
||||
case.<br/>
|
||||
In the end, I did it, but it wasn't much satisfying. Actually it wasn't
|
||||
I started developing `EnTT` for the _wrong_ reason: my goal was to design an
|
||||
entity-component system to beat another well known open source solution both in
|
||||
terms of performance and possibly memory usage.<br/>
|
||||
In the end, I did it, but it wasn't very satisfying. Actually it wasn't
|
||||
satisfying at all. The fastest and nothing more, fairly little indeed. When I
|
||||
realized it, I tried hard to keep intact the great performance of `EnTT` and to
|
||||
add all the features I wanted to see in *my own library* at the same time.
|
||||
@@ -152,52 +180,34 @@ amazing set of features. And even more, of course.
|
||||
|
||||
## Performance
|
||||
|
||||
As it stands right now, `EnTT` is just fast enough for my requirements if
|
||||
compared to my first choice (it was already amazingly fast actually).<br/>
|
||||
Below is a comparison between the two (both of them compiled with GCC 7.3.0 on a
|
||||
Dell XPS 13 out of the mid 2014):
|
||||
The proposed entity-component system is incredibly fast to iterate entities and
|
||||
components, this is a fact. Some compilers make a lot of optimizations because
|
||||
of how `EnTT` works, some others aren't that good. In general, if we consider
|
||||
real world cases, `EnTT` is somewhere between a bit and much faster than many of
|
||||
the other solutions around, although I couldn't check them all for obvious
|
||||
reasons.
|
||||
|
||||
| Benchmark | EntityX (compile-time) | EnTT |
|
||||
|-----------|-------------|-------------|
|
||||
| Create 1M entities | 0.0147s | **0.0046s** |
|
||||
| Destroy 1M entities | 0.0053s | **0.0045s** |
|
||||
| 1M entities, one component | 0.0012s | **1.9e-07s** |
|
||||
| 1M entities, two components | 0.0012s | **3.8e-07s** |
|
||||
| 1M entities, two components<br/>Half of the entities have all the components | 0.0009s | **3.8e-07s** |
|
||||
| 1M entities, two components<br/>One of the entities has all the components | 0.0008s | **1.0e-06s** |
|
||||
| 1M entities, five components | 0.0010s | **7.0e-07s** |
|
||||
| 1M entities, ten components | 0.0011s | **1.2e-06s** |
|
||||
| 1M entities, ten components<br/>Half of the entities have all the components | 0.0010s | **1.2e-06s** |
|
||||
| 1M entities, ten components<br/>One of the entities has all the components | 0.0008s | **1.2e-06s** |
|
||||
| Sort 150k entities, one component<br/>Arrays are in reverse order | - | **0.0036s** |
|
||||
| Sort 150k entities, enforce permutation<br/>Arrays are in reverse order | - | **0.0005s** |
|
||||
| Sort 150k entities, one component<br/>Arrays are almost sorted, std::sort | - | **0.0035s** |
|
||||
| Sort 150k entities, one component<br/>Arrays are almost sorted, insertion sort | - | **0.0007s** |
|
||||
If you are interested, you can compile the `benchmark` test in release mode (to
|
||||
enable compiler optimizations, otherwise it would make little sense) by setting
|
||||
the `BUILD_BENCHMARK` option of `CMake` to `ON`, then evaluate yourself whether
|
||||
you're satisfied with the results or not.
|
||||
|
||||
Note: The default version of `EntityX` (`master` branch) wasn't added to the
|
||||
comparison because it's already much slower than its compile-time counterpart.
|
||||
Honestly I got tired of updating the README file whenever there is an
|
||||
improvement.<br/>
|
||||
There are already a lot of projects out there that use `EnTT` as a basis for
|
||||
comparison (this should already tell you a lot). Many of these benchmarks are
|
||||
completely wrong, many others are simply incomplete, good at omitting some
|
||||
information and using the wrong function to compare a given feature. Certainly
|
||||
there are also good ones but they age quickly if nobody updates them, especially
|
||||
when the library they are dealing with is actively developed.
|
||||
|
||||
Pretty interesting, aren't them? In fact, these benchmarks are the same used by
|
||||
`EntityX` to show _how fast it is_. To be honest, they aren't so good and these
|
||||
results shouldn't be taken much seriously (they are completely unrealistic
|
||||
indeed).<br/>
|
||||
The proposed entity-component system is incredibly fast to iterate entities,
|
||||
this is a fact. The compiler can make a lot of optimizations because of how
|
||||
`EnTT` works, even more when components aren't used at all. This is exactly the
|
||||
case for these benchmarks. On the other hand and if we consider real world
|
||||
cases, `EnTT` is in the middle between a bit and much faster than the other
|
||||
solutions around when users also access the components and not just the
|
||||
entities, although it is not as fast as reported by these benchmarks.<br/>
|
||||
This is why they are completely wrong and cannot be used to evaluate any of the
|
||||
entity-component systems.
|
||||
The choice to use `EnTT` should be based on its carefully designed API, its
|
||||
set of features and the general performance, **not** because some single
|
||||
benchmark shows it to be the fastest tool available.
|
||||
|
||||
If you decide to use `EnTT`, choose it because of its API, features and
|
||||
performance, not because there is a benchmark somewhere that makes it seem the
|
||||
fastest.
|
||||
|
||||
Probably I'll try to get out of `EnTT` more features and even better performance
|
||||
in the future, mainly for fun.<br/>
|
||||
If you want to contribute and/or have any suggestion, feel free to make a PR or
|
||||
In the future I'll likely try to get even better performance while still adding
|
||||
new features, mainly for fun.<br/>
|
||||
If you want to contribute and/or have suggestions, feel free to make a PR or
|
||||
open an issue to discuss your idea.
|
||||
|
||||
# Build Instructions
|
||||
@@ -205,12 +215,19 @@ open an issue to discuss your idea.
|
||||
## Requirements
|
||||
|
||||
To be able to use `EnTT`, users must provide a full-featured compiler that
|
||||
supports at least C++14.<br/>
|
||||
supports at least C++17.<br/>
|
||||
The requirements below are mandatory to compile the tests and to extract the
|
||||
documentation:
|
||||
|
||||
* CMake version 3.2 or later.
|
||||
* Doxygen version 1.8 or later.
|
||||
* `CMake` version 3.7 or later.
|
||||
* `Doxygen` version 1.8 or later.
|
||||
|
||||
Alternatively, [Bazel](https://bazel.build) is also supported as a build system
|
||||
(credits to [zaucy](https://github.com/zaucy) who offered to maintain it).<br/>
|
||||
In the documentation below I'll still refer to `CMake`, this being the official
|
||||
build system of the library.
|
||||
|
||||
If you are looking for a C++14 version of `EnTT`, check out the git tag `cpp14`.
|
||||
|
||||
## Library
|
||||
|
||||
@@ -235,7 +252,7 @@ the include paths.
|
||||
|
||||
## Documentation
|
||||
|
||||
The documentation is based on [doxygen](http://www.stack.nl/~dimitri/doxygen/).
|
||||
The documentation is based on [doxygen](http://www.doxygen.nl/).
|
||||
To build it:
|
||||
|
||||
$ cd build
|
||||
@@ -251,10 +268,10 @@ The API reference will be created in HTML format within the directory
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
The API reference is also available [online](https://skypjack.github.io/entt/)
|
||||
for the latest version.<br/>
|
||||
There exists also a [wiki](https://github.com/skypjack/entt/wiki) dedicated to
|
||||
the project where users can find all related documentation pages.
|
||||
It's also available [online](https://skypjack.github.io/entt/) for the latest
|
||||
version, that is the last stable tag.<br/>
|
||||
Moreover, there exists a [wiki](https://github.com/skypjack/entt/wiki) dedicated
|
||||
to the project where users can find all related documentation pages.
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
@@ -263,12 +280,12 @@ the project where users can find all related documentation pages.
|
||||
|
||||
To compile and run the tests, `EnTT` requires *googletest*.<br/>
|
||||
`cmake` will download and compile the library before compiling anything else.
|
||||
In order to build without tests set CMake option `BUILD_TESTING=OFF`.
|
||||
In order to build the tests, set the CMake option `BUILD_TESTING` to `ON`.
|
||||
|
||||
To build the most basic set of tests:
|
||||
|
||||
* `$ cd build`
|
||||
* `$ cmake ..`
|
||||
* `$ cmake -DBUILD_TESTING=ON ..`
|
||||
* `$ make`
|
||||
* `$ make test`
|
||||
|
||||
@@ -278,59 +295,55 @@ Note that benchmarks are not part of this set.
|
||||
|
||||
`EnTT` is available for some of the most known packaging tools. In particular:
|
||||
|
||||
* [`vcpkg`](https://github.com/Microsoft/vcpkg/tree/master/ports/entt),
|
||||
Microsoft VC++ Packaging Tool.
|
||||
* [`Conan`](https://github.com/conan-io/conan-center-index), the C/C++ Package
|
||||
Manager for Developers.
|
||||
|
||||
* [`vcpkg`](https://github.com/Microsoft/vcpkg), Microsoft VC++ Packaging
|
||||
Tool.<br/>
|
||||
You can download and install `EnTT` in just a few simple steps:
|
||||
|
||||
```
|
||||
$ git clone https://github.com/Microsoft/vcpkg.git
|
||||
$ cd vcpkg
|
||||
$ ./bootstrap-vcpkg.sh
|
||||
$ ./vcpkg integrate install
|
||||
$ vcpkg install entt
|
||||
```
|
||||
|
||||
The `EnTT` port in `vcpkg` is kept up to date by Microsoft team members and
|
||||
community contributors.<br/>
|
||||
If the version is out of date, please
|
||||
[create an issue or pull request](https://github.com/Microsoft/vcpkg) on the
|
||||
`vcpkg` repository.
|
||||
|
||||
* [`Homebrew`](https://github.com/skypjack/homebrew-entt), the missing package
|
||||
manager for macOS.<br/>
|
||||
Available as a homebrew formula. Just type the following to install it:
|
||||
|
||||
```
|
||||
brew install skypjack/entt/entt
|
||||
```
|
||||
|
||||
Consider this list a work in progress and help me to make it longer.
|
||||
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
# 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.<br/>
|
||||
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.
|
||||
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 projects and articles:
|
||||
|
||||
* [Minecraft](https://minecraft.net/en-us/attribution/): of course, **that**
|
||||
Minecraft, by Mojang (see the open source attributions page).
|
||||
* [Face Smash](https://play.google.com/store/apps/details?id=com.gamee.facesmash):
|
||||
the emojis dominate the world, destroy them all with your facial expressions.
|
||||
* [shiva](https://github.com/Milerius/shiva): modern C++ Engine with modularity.
|
||||
* [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): example game with `EnTT`.
|
||||
* [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.
|
||||
* [MatchOneEntt](https://github.com/mhaemmerle/MatchOneEntt): port of
|
||||
[Match One](https://github.com/sschmid/Match-One) for `Entitas-CSharp`.
|
||||
* [Randballs](https://github.com/gale93/randballs): simple `SFML` and `EnTT`
|
||||
playground.
|
||||
* ...
|
||||
[Here](https://github.com/skypjack/entt/wiki/EnTT-in-Action) you can find an
|
||||
incomplete list of games, applications and articles that can be used as a
|
||||
reference.
|
||||
|
||||
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 the list.
|
||||
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
# Contributors
|
||||
|
||||
`EnTT` was written initially as a faster alternative to other well known and
|
||||
@@ -347,7 +360,8 @@ I can't promise that each and every contribution will be accepted, but I can
|
||||
assure that I'll do my best to take them all seriously.
|
||||
|
||||
If you decide to participate, please see the guidelines for
|
||||
[contributing](docs/CONTRIBUTING.md) before to create issues or pull requests.<br/>
|
||||
[contributing](CONTRIBUTING.md) before to create issues or pull
|
||||
requests.<br/>
|
||||
Take also a look at the
|
||||
[contributors list](https://github.com/skypjack/entt/blob/master/AUTHORS) to
|
||||
know who has participated so far.
|
||||
@@ -357,8 +371,8 @@ know who has participated so far.
|
||||
|
||||
# License
|
||||
|
||||
Code and documentation Copyright (c) 2017-2018 Michele Caini.<br/>
|
||||
Logo Copyright (c) 2018 Richard Caseres.
|
||||
Code and documentation Copyright (c) 2017-2020 Michele Caini.<br/>
|
||||
Logo Copyright (c) 2018-2020 Richard Caseres.
|
||||
|
||||
Code released under
|
||||
[the MIT license](https://github.com/skypjack/entt/blob/master/LICENSE).
|
||||
@@ -372,24 +386,10 @@ Logo released under
|
||||
-->
|
||||
# Support
|
||||
|
||||
## Donation
|
||||
|
||||
Developing and maintaining `EnTT` takes some time and lots of coffee. I'd like
|
||||
to add more and more functionalities in future and turn it in a full-featured
|
||||
solution.<br/>
|
||||
If you want to support this project, you can offer me an espresso. I'm from
|
||||
Italy, we're used to turning the best coffee ever in code. If you find that
|
||||
it's not enough, feel free to support me the way you prefer.<br/>
|
||||
Take a look at the donation button at the top of the page for more details or
|
||||
just click [here](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=W2HF9FESD5LJY&lc=IT&item_name=Michele%20Caini¤cy_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted).
|
||||
|
||||
## Hire me
|
||||
|
||||
If you start using `EnTT` and need help, if you want a new feature and want me
|
||||
to give it the highest priority, if you have any other reason to contact me:
|
||||
do not hesitate. I'm available for hiring.<br/>
|
||||
Feel free to take a look at my [profile](https://github.com/skypjack) and
|
||||
contact me by mail.
|
||||
If you want to support this project, you can
|
||||
[offer me](https://github.com/users/skypjack/sponsorship) an espresso.<br/>
|
||||
If you find that it's not enough, feel free to
|
||||
[help me](https://www.paypal.me/skypjack) the way you prefer.
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
|
||||
41
TODO
41
TODO
@@ -1,17 +1,28 @@
|
||||
* long term feature: shared_ptr less locator and resource cache
|
||||
* custom allocators and EnTT allocator-aware in general (long term feature, I don't actually need it at the moment) - see #22
|
||||
* scene management (I prefer the concept of spaces, that is a kind of scene anyway)
|
||||
* debugging tools (#60): the issue online already contains interesting tips on this, look at it
|
||||
* define basic reactive systems (track entities to which component is attached, track entities from which component is removed, and so on)
|
||||
* define systems as composable mixins (initializazion, reactive, update, whatever) with flexible auto-detected arguments (registry, views, etc)
|
||||
* registry::create with a "hint" on the entity identifier to use, it should ease combining multiple registries
|
||||
* deep copy of a registry (or use the snapshot stuff to copy components and keep intact ids at least)
|
||||
* is it possible to iterate all the components assigned to an entity through a common base class?
|
||||
* optimize for empty components, it would be a mid improvement in terms of memory usage
|
||||
* can we do more for shared libraries? who knows... see #144
|
||||
* work stealing job system (see #100)
|
||||
* make view copyable/moveable
|
||||
* reflection system (maybe)
|
||||
* C++17. That's all.
|
||||
* AOB
|
||||
* lower case names (?)
|
||||
* tag_t and the others, create constexpr var
|
||||
* work stealing job system (see #100) + mt scheduler based on const awareness for types
|
||||
* meta: sort of meta view based on meta stuff to iterate entities, void * and meta info objects (remove runtime views, welcome reflection)
|
||||
* add opaque input iterators to views and groups that return tuples <entity, T &...> (proxy), multi-pass guaranteed
|
||||
* allow to replace std:: with custom implementations
|
||||
* custom (decoupled) pools ==> N-buffering, shared components, multi-model, hibitsets, and so on
|
||||
* add examples (and credits) from @alanjfs :)
|
||||
* static reflection, hint: template<> meta_type_t<Type>: meta_descriptor<name, func..., props..., etc...> (see #342)
|
||||
* observer: user defined filters (eg .replace<T, &function> or .group<T, U, &func>)
|
||||
* use underlying_type as entity type within pools and registry? it would make different registries work together flawlessy
|
||||
* can we write a bool conv func for entt::entity that silently compares it to null?
|
||||
* reset... reset everywhere...
|
||||
* is it possible to make 0 the entity null?
|
||||
* document undocumented parts (entt::overload and a few others)
|
||||
* any-of rule for views/groups (eg entity has A and any of B/C/D)
|
||||
- get -> all, exclude -> none
|
||||
* review prepare after clone and the others have been removed
|
||||
* remove copy-ctor/op from sparse set and storage (it doesn't make much sense)
|
||||
|
||||
* WIP:
|
||||
- deprecate snapshot, loader, ...
|
||||
- provide documentation to describe alternatives
|
||||
|
||||
* WIP: snapshot rework/deprecation
|
||||
- remove snapshot/loader from registry, make them external (faster) tools
|
||||
- deprecate snapshot classes, update documentation to describe alternatives
|
||||
|
||||
22
appveyor.yml
22
appveyor.yml
@@ -1,22 +0,0 @@
|
||||
# can use variables like {build} and {branch}
|
||||
version: 1.0.{build}
|
||||
|
||||
image: Visual Studio 2017
|
||||
|
||||
environment:
|
||||
BUILD_DIR: "%APPVEYOR_BUILD_FOLDER%\\build"
|
||||
|
||||
platform:
|
||||
- Win32
|
||||
|
||||
configuration:
|
||||
- Release
|
||||
|
||||
before_build:
|
||||
- cd %BUILD_DIR%
|
||||
- cmake .. -DCMAKE_CXX_FLAGS=/W1 -G"Visual Studio 15 2017"
|
||||
|
||||
build:
|
||||
parallel: true
|
||||
project: build/entt.sln
|
||||
verbosity: minimal
|
||||
@@ -1,19 +0,0 @@
|
||||
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 ""
|
||||
)
|
||||
@@ -1,19 +0,0 @@
|
||||
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 ""
|
||||
)
|
||||
@@ -1,19 +0,0 @@
|
||||
project(googletest-download NONE)
|
||||
cmake_minimum_required(VERSION 3.2)
|
||||
|
||||
include(ExternalProject)
|
||||
|
||||
ExternalProject_Add(
|
||||
googletest
|
||||
GIT_REPOSITORY https://github.com/google/googletest.git
|
||||
GIT_TAG master
|
||||
DOWNLOAD_DIR ${GOOGLETEST_DEPS_DIR}
|
||||
TMP_DIR ${GOOGLETEST_DEPS_DIR}/tmp
|
||||
STAMP_DIR ${GOOGLETEST_DEPS_DIR}/stamp
|
||||
SOURCE_DIR ${GOOGLETEST_DEPS_DIR}/src
|
||||
BINARY_DIR ${GOOGLETEST_DEPS_DIR}/build
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
TEST_COMMAND ""
|
||||
)
|
||||
37
conan/build.py
Normal file
37
conan/build.py
Normal file
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
from cpt.packager import ConanMultiPackager
|
||||
import os
|
||||
|
||||
if __name__ == "__main__":
|
||||
username = os.getenv("GITHUB_ACTOR")
|
||||
tag_version = os.getenv("GITHUB_REF")
|
||||
tag_package = os.getenv("GITHUB_REPOSITORY")
|
||||
login_username = os.getenv("CONAN_LOGIN_USERNAME")
|
||||
package_version = tag_version.replace("refs/tags/v", "")
|
||||
package_name = tag_package.replace("skypjack/", "")
|
||||
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)
|
||||
disable_shared = os.getenv("CONAN_DISABLE_SHARED_BUILD", "False")
|
||||
|
||||
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)
|
||||
builder.add()
|
||||
|
||||
filtered_builds = []
|
||||
for settings, options, env_vars, build_requires, reference in builder.items:
|
||||
if disable_shared == "False" or not options["{}:shared".format(package_name)]:
|
||||
filtered_builds.append([settings, options, env_vars, build_requires])
|
||||
builder.builds = filtered_builds
|
||||
|
||||
builder.run()
|
||||
7
conan/ci/build.sh
Normal file
7
conan/ci/build.sh
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
set -x
|
||||
|
||||
conan user
|
||||
python conan/build.py
|
||||
6
conan/ci/install.sh
Normal file
6
conan/ci/install.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
set -x
|
||||
|
||||
pip install -U conan_package_tools conan
|
||||
13
conan/test_package/CMakeLists.txt
Normal file
13
conan/test_package/CMakeLists.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
cmake_minimum_required(VERSION 3.7.2)
|
||||
project(test_package)
|
||||
|
||||
set(CMAKE_VERBOSE_MAKEFILE TRUE)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
|
||||
conan_basic_setup()
|
||||
|
||||
add_executable(${PROJECT_NAME} test_package.cpp)
|
||||
target_link_libraries(${PROJECT_NAME} ${CONAN_LIBS})
|
||||
19
conan/test_package/conanfile.py
Normal file
19
conan/test_package/conanfile.py
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from conans import ConanFile, CMake
|
||||
import os
|
||||
|
||||
|
||||
class TestPackageConan(ConanFile):
|
||||
settings = "os", "compiler", "build_type", "arch"
|
||||
generators = "cmake"
|
||||
|
||||
def build(self):
|
||||
cmake = CMake(self)
|
||||
cmake.configure()
|
||||
cmake.build()
|
||||
|
||||
def test(self):
|
||||
bin_path = os.path.join("bin", "test_package")
|
||||
self.run(bin_path, run_environment=True)
|
||||
56
conan/test_package/test_package.cpp
Normal file
56
conan/test_package/test_package.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#include <entt/entt.hpp>
|
||||
#include <cstdint>
|
||||
|
||||
struct position {
|
||||
float x;
|
||||
float y;
|
||||
};
|
||||
|
||||
struct velocity {
|
||||
float dx;
|
||||
float dy;
|
||||
};
|
||||
|
||||
void update(entt::registry ®istry) {
|
||||
auto view = registry.view<position, velocity>();
|
||||
|
||||
for(auto entity: view) {
|
||||
// gets only the components that are going to be used ...
|
||||
|
||||
auto &vel = view.get<velocity>(entity);
|
||||
|
||||
vel.dx = 0.;
|
||||
vel.dy = 0.;
|
||||
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
void update(std::uint64_t dt, entt::registry ®istry) {
|
||||
registry.view<position, velocity>().each([dt](auto &pos, auto &vel) {
|
||||
// gets all the components of the view at once ...
|
||||
|
||||
pos.x += vel.dx * dt;
|
||||
pos.y += vel.dy * dt;
|
||||
|
||||
// ...
|
||||
});
|
||||
}
|
||||
|
||||
int main() {
|
||||
entt::registry registry;
|
||||
std::uint64_t dt = 16;
|
||||
|
||||
for(auto i = 0; i < 10; ++i) {
|
||||
auto entity = registry.create();
|
||||
registry.assign<position>(entity, i * 1.f, i * 1.f);
|
||||
if(i % 2 == 0) { registry.assign<velocity>(entity, i * .1f, i * .1f); }
|
||||
}
|
||||
|
||||
update(dt, registry);
|
||||
update(registry);
|
||||
|
||||
// ...
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
27
conanfile.py
Normal file
27
conanfile.py
Normal file
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
from conans import ConanFile
|
||||
|
||||
|
||||
class EnttConan(ConanFile):
|
||||
name = "entt"
|
||||
description = "Gaming meets modern C++ - a fast and reliable entity-component system (ECS) and much more "
|
||||
topics = ("conan," "entt", "gaming", "entity", "ecs")
|
||||
url = "https://github.com/skypjack/entt"
|
||||
homepage = url
|
||||
author = "Michele Caini <michele.caini@gmail.com>"
|
||||
license = "MIT"
|
||||
exports = ["LICENSE"]
|
||||
exports_sources = ["src/*"]
|
||||
no_copy_source = True
|
||||
|
||||
def package(self):
|
||||
self.copy(pattern="LICENSE", dst="licenses")
|
||||
self.copy(pattern="*", dst="include", src="src", keep_path=True)
|
||||
|
||||
def package_info(self):
|
||||
if not self.in_local_cache:
|
||||
self.cpp_info.includedirs = ["src"]
|
||||
|
||||
def package_id(self):
|
||||
self.info.header_only()
|
||||
2
deps/.gitignore
vendored
2
deps/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
*
|
||||
!.gitignore
|
||||
@@ -2,35 +2,38 @@
|
||||
# Doxygen configuration (documentation)
|
||||
#
|
||||
|
||||
set(DOXY_DEPS_DIRECTORY ${EnTT_SOURCE_DIR}/deps)
|
||||
set(DOXY_SOURCE_DIRECTORY ${EnTT_SOURCE_DIR}/src)
|
||||
set(DOXY_DOCS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
set(DOXY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
configure_file(doxy.in doxy.cfg @ONLY)
|
||||
|
||||
set(
|
||||
DOC_SOURCES
|
||||
dox/extra.dox
|
||||
md/core.md
|
||||
md/entity.md
|
||||
md/faq.md
|
||||
md/lib.md
|
||||
md/links.md
|
||||
md/locator.md
|
||||
md/meta.md
|
||||
md/process.md
|
||||
md/resource.md
|
||||
md/signal.md
|
||||
doxy.in
|
||||
)
|
||||
|
||||
add_custom_target(
|
||||
docs ALL
|
||||
COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxy.cfg
|
||||
WORKING_DIRECTORY ${EnTT_SOURCE_DIR}
|
||||
VERBATIM
|
||||
SOURCES doxy.in
|
||||
SOURCES ${DOC_SOURCES}
|
||||
)
|
||||
|
||||
install(
|
||||
DIRECTORY ${DOXY_OUTPUT_DIRECTORY}/html
|
||||
DESTINATION share/${PROJECT_NAME}-${PROJECT_VERSION}/
|
||||
)
|
||||
|
||||
add_custom_target(
|
||||
docs_aob
|
||||
SOURCES
|
||||
CONTRIBUTING.md
|
||||
core.md
|
||||
entity.md
|
||||
locator.md
|
||||
process.md
|
||||
resource.md
|
||||
shared.md
|
||||
signal.md
|
||||
extra.dox
|
||||
)
|
||||
|
||||
167
docs/core.md
167
docs/core.md
@@ -1,167 +0,0 @@
|
||||
# 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<AType, AnotherType>;
|
||||
|
||||
// ...
|
||||
|
||||
switch(aTypeIdentifier) {
|
||||
case ID::get<AType>():
|
||||
// ...
|
||||
break;
|
||||
case ID::get<AnotherType>():
|
||||
// ...
|
||||
break;
|
||||
default:
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
This is all what the class template has to offer: a static `get` member function
|
||||
that returns 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 IgnoreType {};
|
||||
|
||||
using ID = entt::Identifier<
|
||||
ATypeStillValid,
|
||||
IgnoreType<ATypeNoLongerValid>,
|
||||
AnotherTypeStillValid
|
||||
>;
|
||||
```
|
||||
|
||||
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 InternalRegistryComponentFamily>;
|
||||
|
||||
// ...
|
||||
|
||||
template<typename Component>
|
||||
component_type component() const noexcept {
|
||||
return component_family::type<Component>();
|
||||
}
|
||||
```
|
||||
|
||||
This is all what a _family_ has to offer: a `type` member function that returns
|
||||
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 resource 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::HashedString::hash_type resource) {
|
||||
// uses the numeric representation of the resource to load and return it
|
||||
}
|
||||
|
||||
auto resource = load(entt::HashedString{"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 resource 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::HashedString{"mykey"}>{} = true;
|
||||
entt::Monostate<"mykey"_hs>{} = 42;
|
||||
|
||||
// ...
|
||||
|
||||
const bool b = entt::Monostate<"mykey"_hs>{};
|
||||
const int i = entt::Monostate<entt::HashedString{"mykey"}>{};
|
||||
```
|
||||
230
docs/doxy.in
230
docs/doxy.in
@@ -1,4 +1,4 @@
|
||||
# Doxyfile 1.8.13
|
||||
# Doxyfile 1.8.16
|
||||
|
||||
# This file describes the settings to be used by the documentation system
|
||||
# doxygen (www.doxygen.org) for a project.
|
||||
@@ -17,11 +17,11 @@
|
||||
# Project related configuration options
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
# This tag specifies the encoding used for all characters in the config file
|
||||
# that follow. The default is UTF-8 which is also the encoding used for all text
|
||||
# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
|
||||
# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv
|
||||
# for the list of possible encodings.
|
||||
# This tag specifies the encoding used for all characters in the configuration
|
||||
# file that follow. The default is UTF-8 which is also the encoding used for all
|
||||
# text before the first occurrence of this tag. Doxygen uses libiconv (or the
|
||||
# iconv built into libc) for the transcoding. See
|
||||
# https://www.gnu.org/software/libiconv/ for the list of possible encodings.
|
||||
# The default value is: UTF-8.
|
||||
|
||||
DOXYFILE_ENCODING = UTF-8
|
||||
@@ -93,6 +93,14 @@ ALLOW_UNICODE_NAMES = NO
|
||||
|
||||
OUTPUT_LANGUAGE = English
|
||||
|
||||
# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all
|
||||
# documentation generated by doxygen is written. Doxygen will use this
|
||||
# information to generate all generated output in the proper direction.
|
||||
# Possible values are: None, LTR, RTL and Context.
|
||||
# The default value is: None.
|
||||
|
||||
OUTPUT_TEXT_DIRECTION = None
|
||||
|
||||
# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
|
||||
# descriptions after the members that are listed in the file and class
|
||||
# documentation (similar to Javadoc). Set to NO to disable this.
|
||||
@@ -179,6 +187,16 @@ SHORT_NAMES = NO
|
||||
|
||||
JAVADOC_AUTOBRIEF = NO
|
||||
|
||||
# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line
|
||||
# such as
|
||||
# /***************
|
||||
# as being the beginning of a Javadoc-style comment "banner". If set to NO, the
|
||||
# Javadoc-style will behave just like regular comments and it will not be
|
||||
# interpreted by doxygen.
|
||||
# The default value is: NO.
|
||||
|
||||
JAVADOC_BANNER = NO
|
||||
|
||||
# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
|
||||
# line (until the first dot) of a Qt-style comment as the brief description. If
|
||||
# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
|
||||
@@ -226,7 +244,12 @@ TAB_SIZE = 4
|
||||
# will allow you to put the command \sideeffect (or @sideeffect) in the
|
||||
# documentation, which will result in a user-defined paragraph with heading
|
||||
# "Side Effects:". You can put \n's in the value part of an alias to insert
|
||||
# newlines.
|
||||
# newlines (in the resulting output). You can put ^^ in the value part of an
|
||||
# alias to insert a newline as if a physical newline was in the original file.
|
||||
# When you need a literal { or } or , in the value part of an alias you have to
|
||||
# escape them by means of a backslash (\), this can lead to conflicts with the
|
||||
# commands \{ and \} for these it is advised to use the version @{ and @} or use
|
||||
# a double escape (\\{ and \\})
|
||||
|
||||
ALIASES =
|
||||
|
||||
@@ -264,17 +287,26 @@ OPTIMIZE_FOR_FORTRAN = NO
|
||||
|
||||
OPTIMIZE_OUTPUT_VHDL = NO
|
||||
|
||||
# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice
|
||||
# sources only. Doxygen will then generate output that is more tailored for that
|
||||
# language. For instance, namespaces will be presented as modules, types will be
|
||||
# separated into more groups, etc.
|
||||
# The default value is: NO.
|
||||
|
||||
OPTIMIZE_OUTPUT_SLICE = NO
|
||||
|
||||
# Doxygen selects the parser to use depending on the extension of the files it
|
||||
# parses. With this tag you can assign which parser to use for a given
|
||||
# extension. Doxygen has a built-in mapping, but you can override or extend it
|
||||
# using this tag. The format is ext=language, where ext is a file extension, and
|
||||
# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
|
||||
# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:
|
||||
# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:
|
||||
# Fortran. In the later case the parser tries to guess whether the code is fixed
|
||||
# or free formatted code, this is the default for Fortran type files), VHDL. For
|
||||
# instance to make doxygen treat .inc files as Fortran files (default is PHP),
|
||||
# and .f files as C (default is Fortran), use: inc=Fortran f=C.
|
||||
# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice,
|
||||
# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
|
||||
# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
|
||||
# tries to guess whether the code is fixed or free formatted code, this is the
|
||||
# default for Fortran type files), VHDL, tcl. For instance to make doxygen treat
|
||||
# .inc files as Fortran files (default is PHP), and .f files as C (default is
|
||||
# Fortran), use: inc=Fortran f=C.
|
||||
#
|
||||
# Note: For files without extension you can use no_extension as a placeholder.
|
||||
#
|
||||
@@ -285,7 +317,7 @@ EXTENSION_MAPPING =
|
||||
|
||||
# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
|
||||
# according to the Markdown format, which allows for more readable
|
||||
# documentation. See http://daringfireball.net/projects/markdown/ for details.
|
||||
# documentation. See https://daringfireball.net/projects/markdown/ for details.
|
||||
# The output of markdown processing is further processed by doxygen, so you can
|
||||
# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
|
||||
# case of backward compatibilities issues.
|
||||
@@ -297,10 +329,10 @@ MARKDOWN_SUPPORT = YES
|
||||
# to that level are automatically included in the table of contents, even if
|
||||
# they do not have an id attribute.
|
||||
# Note: This feature currently applies only to Markdown headings.
|
||||
# Minimum value: 0, maximum value: 99, default value: 0.
|
||||
# Minimum value: 0, maximum value: 99, default value: 5.
|
||||
# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
|
||||
|
||||
TOC_INCLUDE_HEADINGS = 4
|
||||
TOC_INCLUDE_HEADINGS = 5
|
||||
|
||||
# When enabled doxygen tries to link words that correspond to documented
|
||||
# classes, or namespaces to their corresponding documentation. Such a link can
|
||||
@@ -327,7 +359,7 @@ BUILTIN_STL_SUPPORT = NO
|
||||
CPP_CLI_SUPPORT = NO
|
||||
|
||||
# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
|
||||
# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen
|
||||
# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen
|
||||
# will parse them like normal C++ but will assume all classes use public instead
|
||||
# of private inheritance when no explicit protection keyword is present.
|
||||
# The default value is: NO.
|
||||
@@ -433,6 +465,12 @@ EXTRACT_ALL = NO
|
||||
|
||||
EXTRACT_PRIVATE = NO
|
||||
|
||||
# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual
|
||||
# methods of a class will be included in the documentation.
|
||||
# The default value is: NO.
|
||||
|
||||
EXTRACT_PRIV_VIRTUAL = NO
|
||||
|
||||
# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
|
||||
# scope will be included in the documentation.
|
||||
# The default value is: NO.
|
||||
@@ -511,7 +549,7 @@ INTERNAL_DOCS = NO
|
||||
# names in lower-case letters. If set to YES, upper-case letters are also
|
||||
# allowed. This is useful if you have classes or files whose names only differ
|
||||
# in case and if your file system supports case sensitive file names. Windows
|
||||
# and Mac users are advised to set this option to NO.
|
||||
# (including Cygwin) ands Mac users are advised to set this option to NO.
|
||||
# The default value is: system dependent.
|
||||
|
||||
CASE_SENSE_NAMES = YES
|
||||
@@ -698,7 +736,7 @@ LAYOUT_FILE =
|
||||
# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
|
||||
# the reference definitions. This must be a list of .bib files. The .bib
|
||||
# extension is automatically appended if omitted. This requires the bibtex tool
|
||||
# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.
|
||||
# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.
|
||||
# For LaTeX the style of the bibliography can be controlled using
|
||||
# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
|
||||
# search path. See also \cite for info how to create references.
|
||||
@@ -743,7 +781,8 @@ WARN_IF_DOC_ERROR = YES
|
||||
# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
|
||||
# are documented, but have no documentation for their parameters or return
|
||||
# value. If set to NO, doxygen will only warn about wrong or incomplete
|
||||
# parameter documentation, but not about the absence of documentation.
|
||||
# parameter documentation, but not about the absence of documentation. If
|
||||
# EXTRACT_ALL is set to YES then this flag will automatically be disabled.
|
||||
# The default value is: NO.
|
||||
|
||||
WARN_NO_PARAMDOC = YES
|
||||
@@ -780,12 +819,14 @@ 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
|
||||
# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
|
||||
# documentation (see: http://www.gnu.org/software/libiconv) for the list of
|
||||
# documentation (see: https://www.gnu.org/software/libiconv/) for the list of
|
||||
# possible encodings.
|
||||
# The default value is: UTF-8.
|
||||
|
||||
@@ -803,11 +844,9 @@ INPUT_ENCODING = UTF-8
|
||||
# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
|
||||
# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
|
||||
# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
|
||||
# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf.
|
||||
# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice.
|
||||
|
||||
FILE_PATTERNS = *.cpp \
|
||||
*.c++ \
|
||||
*.h \
|
||||
FILE_PATTERNS = *.h \
|
||||
*.hpp \
|
||||
*.md \
|
||||
*.dox
|
||||
@@ -825,7 +864,7 @@ RECURSIVE = YES
|
||||
# Note that relative paths are relative to the directory from which doxygen is
|
||||
# run.
|
||||
|
||||
EXCLUDE =
|
||||
EXCLUDE = @DOXY_DEPS_DIRECTORY@
|
||||
|
||||
# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
|
||||
# directories that are symbolic links (a Unix file system feature) are excluded
|
||||
@@ -963,7 +1002,7 @@ INLINE_SOURCES = NO
|
||||
STRIP_CODE_COMMENTS = YES
|
||||
|
||||
# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
|
||||
# function all documented functions referencing it will be listed.
|
||||
# entity all documented functions referencing it will be listed.
|
||||
# The default value is: NO.
|
||||
|
||||
REFERENCED_BY_RELATION = NO
|
||||
@@ -995,12 +1034,12 @@ SOURCE_TOOLTIPS = YES
|
||||
# If the USE_HTAGS tag is set to YES then the references to source code will
|
||||
# point to the HTML generated by the htags(1) tool instead of doxygen built-in
|
||||
# source browser. The htags tool is part of GNU's global source tagging system
|
||||
# (see http://www.gnu.org/software/global/global.html). You will need version
|
||||
# (see https://www.gnu.org/software/global/global.html). You will need version
|
||||
# 4.8.6 or higher.
|
||||
#
|
||||
# To use it do the following:
|
||||
# - Install the latest version of global
|
||||
# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
|
||||
# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file
|
||||
# - Make sure the INPUT points to the root of the source tree
|
||||
# - Run doxygen as normal
|
||||
#
|
||||
@@ -1028,7 +1067,7 @@ VERBATIM_HEADERS = YES
|
||||
# rich C++ code for which doxygen's built-in parser lacks the necessary type
|
||||
# information.
|
||||
# Note: The availability of this option depends on whether or not doxygen was
|
||||
# generated with the -Duse-libclang=ON option for CMake.
|
||||
# generated with the -Duse_libclang=ON option for CMake.
|
||||
# The default value is: NO.
|
||||
|
||||
CLANG_ASSISTED_PARSING = NO
|
||||
@@ -1041,6 +1080,16 @@ CLANG_ASSISTED_PARSING = NO
|
||||
|
||||
CLANG_OPTIONS =
|
||||
|
||||
# If clang assisted parsing is enabled you can provide the clang parser with the
|
||||
# path to the compilation database (see:
|
||||
# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) used when the files
|
||||
# were built. This is equivalent to specifying the "-p" option to a clang tool,
|
||||
# such as clang-check. These options will then be passed to the parser.
|
||||
# Note: The availability of this option depends on whether or not doxygen was
|
||||
# generated with the -Duse_libclang=ON option for CMake.
|
||||
|
||||
CLANG_DATABASE_PATH =
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the alphabetical class index
|
||||
#---------------------------------------------------------------------------
|
||||
@@ -1159,7 +1208,7 @@ HTML_EXTRA_FILES =
|
||||
# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
|
||||
# will adjust the colors in the style sheet and background images according to
|
||||
# this color. Hue is specified as an angle on a colorwheel, see
|
||||
# http://en.wikipedia.org/wiki/Hue for more information. For instance the value
|
||||
# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
|
||||
# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
|
||||
# purple, and 360 is red again.
|
||||
# Minimum value: 0, maximum value: 359, default value: 220.
|
||||
@@ -1195,6 +1244,17 @@ HTML_COLORSTYLE_GAMMA = 80
|
||||
|
||||
HTML_TIMESTAMP = NO
|
||||
|
||||
# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
|
||||
# documentation will contain a main index with vertical navigation menus that
|
||||
# are dynamically created via Javascript. If disabled, the navigation index will
|
||||
# consists of multiple levels of tabs that are statically embedded in every HTML
|
||||
# page. Disable this option to support browsers that do not have Javascript,
|
||||
# like the Qt help browser.
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
HTML_DYNAMIC_MENUS = YES
|
||||
|
||||
# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
|
||||
# documentation will contain sections that can be hidden and shown after the
|
||||
# page has loaded.
|
||||
@@ -1218,13 +1278,13 @@ HTML_INDEX_NUM_ENTRIES = 100
|
||||
|
||||
# If the GENERATE_DOCSET tag is set to YES, additional index files will be
|
||||
# generated that can be used as input for Apple's Xcode 3 integrated development
|
||||
# environment (see: http://developer.apple.com/tools/xcode/), introduced with
|
||||
# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
|
||||
# environment (see: https://developer.apple.com/xcode/), introduced with OSX
|
||||
# 10.5 (Leopard). To create a documentation set, doxygen will generate a
|
||||
# Makefile in the HTML output directory. Running make will produce the docset in
|
||||
# that directory and running make install will install the docset in
|
||||
# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
|
||||
# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
|
||||
# for more information.
|
||||
# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy
|
||||
# genXcode/_index.html for more information.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
@@ -1263,7 +1323,7 @@ DOCSET_PUBLISHER_NAME = Publisher
|
||||
# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
|
||||
# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
|
||||
# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
|
||||
# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
|
||||
# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on
|
||||
# Windows.
|
||||
#
|
||||
# The HTML Help Workshop contains a compiler that can convert all HTML output
|
||||
@@ -1339,7 +1399,7 @@ QCH_FILE =
|
||||
|
||||
# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
|
||||
# Project output. For more information please see Qt Help Project / Namespace
|
||||
# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).
|
||||
# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
|
||||
# The default value is: org.doxygen.Project.
|
||||
# This tag requires that the tag GENERATE_QHP is set to YES.
|
||||
|
||||
@@ -1347,7 +1407,7 @@ QHP_NAMESPACE = org.doxygen.Project
|
||||
|
||||
# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
|
||||
# Help Project output. For more information please see Qt Help Project / Virtual
|
||||
# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-
|
||||
# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-
|
||||
# folders).
|
||||
# The default value is: doc.
|
||||
# This tag requires that the tag GENERATE_QHP is set to YES.
|
||||
@@ -1356,7 +1416,7 @@ QHP_VIRTUAL_FOLDER = doc
|
||||
|
||||
# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
|
||||
# filter to add. For more information please see Qt Help Project / Custom
|
||||
# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
|
||||
# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
|
||||
# filters).
|
||||
# This tag requires that the tag GENERATE_QHP is set to YES.
|
||||
|
||||
@@ -1364,7 +1424,7 @@ QHP_CUST_FILTER_NAME =
|
||||
|
||||
# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
|
||||
# custom filter to add. For more information please see Qt Help Project / Custom
|
||||
# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
|
||||
# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
|
||||
# filters).
|
||||
# This tag requires that the tag GENERATE_QHP is set to YES.
|
||||
|
||||
@@ -1372,7 +1432,7 @@ QHP_CUST_FILTER_ATTRS =
|
||||
|
||||
# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
|
||||
# project's filter section matches. Qt Help Project / Filter Attributes (see:
|
||||
# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).
|
||||
# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).
|
||||
# This tag requires that the tag GENERATE_QHP is set to YES.
|
||||
|
||||
QHP_SECT_FILTER_ATTRS =
|
||||
@@ -1465,7 +1525,7 @@ EXT_LINKS_IN_WINDOW = NO
|
||||
|
||||
FORMULA_FONTSIZE = 10
|
||||
|
||||
# Use the FORMULA_TRANPARENT tag to determine whether or not the images
|
||||
# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
|
||||
# generated for formulas are transparent PNGs. Transparent PNGs are not
|
||||
# supported properly for IE 6.0, but are supported on all modern browsers.
|
||||
#
|
||||
@@ -1477,7 +1537,7 @@ FORMULA_FONTSIZE = 10
|
||||
FORMULA_TRANSPARENT = YES
|
||||
|
||||
# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
|
||||
# http://www.mathjax.org) which uses client side Javascript for the rendering
|
||||
# https://www.mathjax.org) which uses client side Javascript for the rendering
|
||||
# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
|
||||
# installed or if you want to formulas look prettier in the HTML output. When
|
||||
# enabled you may also need to install MathJax separately and configure the path
|
||||
@@ -1504,11 +1564,11 @@ MATHJAX_FORMAT = HTML-CSS
|
||||
# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
|
||||
# Content Delivery Network so you can quickly see the result without installing
|
||||
# MathJax. However, it is strongly recommended to install a local copy of
|
||||
# MathJax from http://www.mathjax.org before deployment.
|
||||
# The default value is: http://cdn.mathjax.org/mathjax/latest.
|
||||
# MathJax from https://www.mathjax.org before deployment.
|
||||
# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/.
|
||||
# This tag requires that the tag USE_MATHJAX is set to YES.
|
||||
|
||||
MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
|
||||
MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/
|
||||
|
||||
# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
|
||||
# extension names that should be enabled during MathJax rendering. For example
|
||||
@@ -1566,7 +1626,7 @@ SERVER_BASED_SEARCH = NO
|
||||
#
|
||||
# Doxygen ships with an example indexer (doxyindexer) and search engine
|
||||
# (doxysearch.cgi) which are based on the open source search engine library
|
||||
# Xapian (see: http://xapian.org/).
|
||||
# Xapian (see: https://xapian.org/).
|
||||
#
|
||||
# See the section "External Indexing and Searching" for details.
|
||||
# The default value is: NO.
|
||||
@@ -1579,7 +1639,7 @@ EXTERNAL_SEARCH = NO
|
||||
#
|
||||
# Doxygen ships with an example indexer (doxyindexer) and search engine
|
||||
# (doxysearch.cgi) which are based on the open source search engine library
|
||||
# Xapian (see: http://xapian.org/). See the section "External Indexing and
|
||||
# Xapian (see: https://xapian.org/). See the section "External Indexing and
|
||||
# Searching" for details.
|
||||
# This tag requires that the tag SEARCHENGINE is set to YES.
|
||||
|
||||
@@ -1631,21 +1691,35 @@ LATEX_OUTPUT = latex
|
||||
# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
|
||||
# invoked.
|
||||
#
|
||||
# Note that when enabling USE_PDFLATEX this option is only used for generating
|
||||
# bitmaps for formulas in the HTML output, but not in the Makefile that is
|
||||
# written to the output directory.
|
||||
# The default file is: latex.
|
||||
# Note that when not enabling USE_PDFLATEX the default is latex when enabling
|
||||
# USE_PDFLATEX the default is pdflatex and when in the later case latex is
|
||||
# chosen this is overwritten by pdflatex. For specific output languages the
|
||||
# default can have been set differently, this depends on the implementation of
|
||||
# the output language.
|
||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||
|
||||
LATEX_CMD_NAME = latex
|
||||
LATEX_CMD_NAME =
|
||||
|
||||
# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
|
||||
# index for LaTeX.
|
||||
# Note: This tag is used in the Makefile / make.bat.
|
||||
# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file
|
||||
# (.tex).
|
||||
# The default file is: makeindex.
|
||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||
|
||||
MAKEINDEX_CMD_NAME = makeindex
|
||||
|
||||
# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to
|
||||
# generate index for LaTeX. In case there is no backslash (\) as first character
|
||||
# it will be automatically added in the LaTeX code.
|
||||
# Note: This tag is used in the generated output file (.tex).
|
||||
# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat.
|
||||
# The default value is: makeindex.
|
||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||
|
||||
LATEX_MAKEINDEX_CMD = makeindex
|
||||
|
||||
# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
|
||||
# documents. This may be useful for small projects and may help to save some
|
||||
# trees in general.
|
||||
@@ -1766,7 +1840,7 @@ LATEX_SOURCE_CODE = NO
|
||||
|
||||
# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
|
||||
# bibliography, e.g. plainnat, or ieeetr. See
|
||||
# http://en.wikipedia.org/wiki/BibTeX and \cite for more info.
|
||||
# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
|
||||
# The default value is: plain.
|
||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||
|
||||
@@ -1780,6 +1854,14 @@ LATEX_BIB_STYLE = plain
|
||||
|
||||
LATEX_TIMESTAMP = NO
|
||||
|
||||
# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
|
||||
# path from which the emoji images will be read. If a relative path is entered,
|
||||
# it will be relative to the LATEX_OUTPUT directory. If left blank the
|
||||
# LATEX_OUTPUT directory will be used.
|
||||
# This tag requires that the tag GENERATE_LATEX is set to YES.
|
||||
|
||||
LATEX_EMOJI_DIRECTORY =
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the RTF output
|
||||
#---------------------------------------------------------------------------
|
||||
@@ -1819,9 +1901,9 @@ COMPACT_RTF = NO
|
||||
|
||||
RTF_HYPERLINKS = NO
|
||||
|
||||
# Load stylesheet definitions from file. Syntax is similar to doxygen's config
|
||||
# file, i.e. a series of assignments. You only have to provide replacements,
|
||||
# missing definitions are set to their default value.
|
||||
# Load stylesheet definitions from file. Syntax is similar to doxygen's
|
||||
# configuration file, i.e. a series of assignments. You only have to provide
|
||||
# replacements, missing definitions are set to their default value.
|
||||
#
|
||||
# See also section "Doxygen usage" for information on how to generate the
|
||||
# default style sheet that doxygen normally uses.
|
||||
@@ -1830,8 +1912,8 @@ RTF_HYPERLINKS = NO
|
||||
RTF_STYLESHEET_FILE =
|
||||
|
||||
# Set optional variables used in the generation of an RTF document. Syntax is
|
||||
# similar to doxygen's config file. A template extensions file can be generated
|
||||
# using doxygen -e rtf extensionFile.
|
||||
# similar to doxygen's configuration file. A template extensions file can be
|
||||
# generated using doxygen -e rtf extensionFile.
|
||||
# This tag requires that the tag GENERATE_RTF is set to YES.
|
||||
|
||||
RTF_EXTENSIONS_FILE =
|
||||
@@ -1917,6 +1999,13 @@ XML_OUTPUT = xml
|
||||
|
||||
XML_PROGRAMLISTING = YES
|
||||
|
||||
# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include
|
||||
# namespace members in file scope as well, matching the HTML output.
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_XML is set to YES.
|
||||
|
||||
XML_NS_MEMB_FILE_SCOPE = NO
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the DOCBOOK output
|
||||
#---------------------------------------------------------------------------
|
||||
@@ -1949,9 +2038,9 @@ DOCBOOK_PROGRAMLISTING = NO
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
|
||||
# AutoGen Definitions (see http://autogen.sf.net) file that captures the
|
||||
# structure of the code including all documentation. Note that this feature is
|
||||
# still experimental and incomplete at the moment.
|
||||
# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures
|
||||
# the structure of the code including all documentation. Note that this feature
|
||||
# is still experimental and incomplete at the moment.
|
||||
# The default value is: NO.
|
||||
|
||||
GENERATE_AUTOGEN_DEF = NO
|
||||
@@ -2118,12 +2207,6 @@ EXTERNAL_GROUPS = YES
|
||||
|
||||
EXTERNAL_PAGES = YES
|
||||
|
||||
# The PERL_PATH should be the absolute path and name of the perl script
|
||||
# interpreter (i.e. the result of 'which perl').
|
||||
# The default file (with absolute path) is: /usr/bin/perl.
|
||||
|
||||
PERL_PATH = /usr/bin/perl
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the dot tool
|
||||
#---------------------------------------------------------------------------
|
||||
@@ -2137,15 +2220,6 @@ PERL_PATH = /usr/bin/perl
|
||||
|
||||
CLASS_DIAGRAMS = YES
|
||||
|
||||
# You can define message sequence charts within doxygen comments using the \msc
|
||||
# command. Doxygen will then run the mscgen tool (see:
|
||||
# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
|
||||
# documentation. The MSCGEN_PATH tag allows you to specify the directory where
|
||||
# the mscgen tool resides. If left empty the tool is assumed to be found in the
|
||||
# default search path.
|
||||
|
||||
MSCGEN_PATH =
|
||||
|
||||
# You can include diagrams made with dia in doxygen documentation. Doxygen will
|
||||
# then run dia to produce the diagram and insert it in the documentation. The
|
||||
# DIA_PATH tag allows you to specify the directory where the dia binary resides.
|
||||
|
||||
1400
docs/entity.md
1400
docs/entity.md
File diff suppressed because it is too large
Load Diff
296
docs/md/core.md
Normal file
296
docs/md/core.md
Normal file
@@ -0,0 +1,296 @@
|
||||
# Crash Course: core functionalities
|
||||
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
# Table of Contents
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [Compile-time identifiers](#compile-time-identifiers)
|
||||
* [Runtime identifiers](#runtime-identifiers)
|
||||
* [Hashed strings](#hashed-strings)
|
||||
* [Wide characters](wide-characters)
|
||||
* [Conflicts](#conflicts)
|
||||
* [Monostate](#monostate)
|
||||
* [Type info](#type-info)
|
||||
* [Almost unique identifiers](#almost-unique-identifiers)
|
||||
* [Traits](#traits)
|
||||
* [Member class type](#member-class-type)
|
||||
* [Tags](#tags)
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
|
||||
# Introduction
|
||||
|
||||
`EnTT` comes with a bunch of core functionalities mostly used by the other parts
|
||||
of the library itself.<br/>
|
||||
Hardly users will include these features in their code, but it's worth
|
||||
describing what `EnTT` offers so as not to reinvent the wheel in case of need.
|
||||
|
||||
# Compile-time identifiers
|
||||
|
||||
Sometimes it's useful to be able to give unique identifiers to types at
|
||||
compile-time.<br/>
|
||||
There are plenty of different solutions out there and I could have used one of
|
||||
them. However, I decided to spend my time to define a compact and versatile tool
|
||||
that fully embraces what the modern C++ has to offer.
|
||||
|
||||
The _result of my efforts_ is the `identifier` class template:
|
||||
|
||||
```cpp
|
||||
#include <ident.hpp>
|
||||
|
||||
// defines the identifiers for the given types
|
||||
using id = entt::identifier<a_type, another_type>;
|
||||
|
||||
// ...
|
||||
|
||||
switch(a_type_identifier) {
|
||||
case id::type<a_type>:
|
||||
// ...
|
||||
break;
|
||||
case id::type<another_type>:
|
||||
// ...
|
||||
break;
|
||||
default:
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
This is all what the class template has to offer: a `type` inline variable that
|
||||
contains a numerical identifier for the given type. It can be used in any
|
||||
context where constant expressions are required.
|
||||
|
||||
As long as the list remains unchanged, identifiers are also guaranteed to be the
|
||||
same for every run. In case they have been used in a production environment and
|
||||
a type has to be removed, one can just use a placeholder to left the other
|
||||
identifiers unchanged:
|
||||
|
||||
```cpp
|
||||
template<typename> struct ignore_type {};
|
||||
|
||||
using id = entt::identifier<
|
||||
a_type_still_valid,
|
||||
ignore_type<a_type_no_longer_valid>,
|
||||
another_type_still_valid
|
||||
>;
|
||||
```
|
||||
|
||||
A bit ugly to see, but it works at least.
|
||||
|
||||
# Runtime identifiers
|
||||
|
||||
Sometimes it's useful to be able to give unique identifiers to types at
|
||||
runtime.<br/>
|
||||
There are plenty of different solutions out there and I could have used one of
|
||||
them. In fact, I adapted the most common one to my requirements and used it
|
||||
extensively within the entire library.
|
||||
|
||||
It's the `family` class. Here is an example of use directly from the
|
||||
entity-component system:
|
||||
|
||||
```cpp
|
||||
using component_family = entt::family<struct internal_registry_component_family>;
|
||||
|
||||
// ...
|
||||
|
||||
template<typename Component>
|
||||
component_type component() const noexcept {
|
||||
return component_family::type<Component>;
|
||||
}
|
||||
```
|
||||
|
||||
This is all what a _family_ has to offer: a `type` inline variable that contains
|
||||
a numerical identifier for the given type.
|
||||
|
||||
Please, note that identifiers aren't guaranteed to be the same for every run.
|
||||
Indeed it mostly depends on the flow of execution.
|
||||
|
||||
# Hashed strings
|
||||
|
||||
A hashed string is a zero overhead unique identifier. Users can use
|
||||
human-readable identifiers in the codebase while using their numeric
|
||||
counterparts at runtime, thus without affecting performance.<br/>
|
||||
The class has an implicit `constexpr` constructor that chews a bunch of
|
||||
characters. Once created, all what one can do with it is getting back the
|
||||
original string or converting it into a number.<br/>
|
||||
The good part is that a hashed string can be used wherever a constant expression
|
||||
is required and no _string-to-number_ conversion will take place at runtime if
|
||||
used carefully.
|
||||
|
||||
Example of use:
|
||||
|
||||
```cpp
|
||||
auto load(entt::hashed_string::hash_type resource) {
|
||||
// uses the numeric representation of the resource to load and return it
|
||||
}
|
||||
|
||||
auto resource = load(entt::hashed_string{"gui/background"});
|
||||
```
|
||||
|
||||
There is also a _user defined literal_ dedicated to hashed strings to make them
|
||||
more user-friendly:
|
||||
|
||||
```cpp
|
||||
constexpr auto str = "text"_hs;
|
||||
```
|
||||
|
||||
## Wide characters
|
||||
|
||||
The hashed string has a design that is close to that of an `std::basic_string`.
|
||||
It means that `hashed_string` is nothing more than an alias for
|
||||
`basic_hashed_string<char>`. For those who want to use the C++ type for wide
|
||||
character representation, there exists also the alias `hashed_wstring` for
|
||||
`basic_hashed_string<wchar_t>`.<br/>
|
||||
In this case, the user defined literal to use to create hashed strings on the
|
||||
fly is `_hws`:
|
||||
|
||||
```cpp
|
||||
constexpr auto str = "text"_hws;
|
||||
```
|
||||
|
||||
Note that the hash type of the `hashed_wstring` is the same of its counterpart.
|
||||
|
||||
## Conflicts
|
||||
|
||||
The hashed string class uses internally FNV-1a to compute the numeric
|
||||
counterpart of a string. Because of the _pigeonhole principle_, conflicts are
|
||||
possible. This is a fact.<br/>
|
||||
There is no silver bullet to solve the problem of conflicts when dealing with
|
||||
hashing functions. In this case, the best solution seemed to be to give up.
|
||||
That's all.<br/>
|
||||
After all, human-readable unique identifiers aren't something strictly defined
|
||||
and over which users have not the control. Choosing a slightly different
|
||||
identifier is probably the best solution to make the conflict disappear in this
|
||||
case.
|
||||
|
||||
# Monostate
|
||||
|
||||
The monostate pattern is often presented as an alternative to a singleton based
|
||||
configuration system. This is exactly its purpose in `EnTT`. Moreover, this
|
||||
implementation is thread safe by design (hopefully).<br/>
|
||||
Keys are represented by hashed strings, values are basic types like `int`s or
|
||||
`bool`s. Values of different types can be associated to each key, even more than
|
||||
one at a time. Because of this, users must pay attention to use the same type
|
||||
both during an assignment and when they try to read back their data. Otherwise,
|
||||
they will probably incur in unexpected results.
|
||||
|
||||
Example of use:
|
||||
|
||||
```cpp
|
||||
entt::monostate<entt::hashed_string{"mykey"}>{} = true;
|
||||
entt::monostate<"mykey"_hs>{} = 42;
|
||||
|
||||
// ...
|
||||
|
||||
const bool b = entt::monostate<"mykey"_hs>{};
|
||||
const int i = entt::monostate<entt::hashed_string{"mykey"}>{};
|
||||
```
|
||||
|
||||
# Type info
|
||||
|
||||
The `type_info` class template is meant to provide some basic information about
|
||||
types of all kinds.<br/>
|
||||
Currently, the only information available is the numeric identifier associated
|
||||
with a given type:
|
||||
|
||||
```cpp
|
||||
auto id = entt::type_info<my_type>::id();
|
||||
```
|
||||
|
||||
In general, the `id` function is also `constexpr` but this isn't guaranteed for
|
||||
all compilers and platforms (although it's valid with the most well-known and
|
||||
popular compilers).<br/>
|
||||
This function **can** use non-standard features of the language for its own
|
||||
purposes. This allows it to provide compile-time identifiers that remain stable
|
||||
between different runs. However, it's possible to force the use of standard
|
||||
features only by defining the macro `ENTT_STANDARD_CPP`. In this case, there is
|
||||
no guarantee that the identifiers are stable across executions though. Moreover,
|
||||
identifiers are generated at runtime and are no longer a compile-time thing.
|
||||
|
||||
An external type system can also be used if needed. In fact, `type_info` can be
|
||||
specialized by type and is also _sfinae-friendly_ in order to allow more refined
|
||||
specializations such as:
|
||||
|
||||
```cpp
|
||||
template<typename Type>
|
||||
struct entt::type_info<Type, std::void_d<decltype(Type::custom_id())>> {
|
||||
static constexpr ENTT_ID_TYPE id() ENTT_NOEXCEPT {
|
||||
return Type::custom_id();
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Note that this class template and its specializations are widely used within
|
||||
`EnTT`. It also plays a very important role in making `EnTT` work transparently
|
||||
across boundaries.<br/>
|
||||
Please refer to the dedicated section for more details.
|
||||
|
||||
## Almost unique identifiers
|
||||
|
||||
Since the default non-standard, compile-time implementation makes use of hashed
|
||||
strings, it may happen that two types are assigned the same numeric
|
||||
identifier.<br/>
|
||||
In fact, although this is quite rare, it's not entirely excluded.
|
||||
|
||||
Another case where two types are assigned the same identifier is when classes
|
||||
from different contexts (for example two or more libraries loaded at runtime)
|
||||
have the same fully qualified name.<br/>
|
||||
Since the default model is based on the name of the classes, if the types belong
|
||||
to the same namespace then their identifiers _could_ be identical (they won't
|
||||
necessarily be the same though).
|
||||
|
||||
Fortunately, there are several easy ways to deal with this:
|
||||
|
||||
* The most trivial one is to define the `ENTT_STANDARD_CPP` macro. Note that
|
||||
runtime identifiers don't suffer from the sam problem. However, this solution
|
||||
doesn't work well with a plugin system, where the libraries aren't linked.
|
||||
|
||||
* Another possibility is to specialize the `type_info` class for one of the
|
||||
conflicting types, in order to assign it a custom identifier. This is probably
|
||||
the easiest solution that also preserves the feature of the tool.
|
||||
|
||||
* A fully customized identifier generation policy (based for example on enum
|
||||
classes or preprocessing steps) may represent yet another option.
|
||||
|
||||
These are just some examples of possible approaches to the problem but there are
|
||||
many others. As already mentioned above, since users have full control over
|
||||
their types, this problem is in any case easy to solve and should not worry too
|
||||
much.<br/>
|
||||
In all likelihood, it will never happen to run into a conflict anyway.
|
||||
|
||||
# Traits
|
||||
|
||||
This section contains a handful of utilities and traits not present in the
|
||||
standard template library but which can be useful in everyday life.
|
||||
|
||||
## Member class type
|
||||
|
||||
The `auto` template parameter introduced with C++17 made it possible to simplify
|
||||
many class templates and template functions but also made the class type opaque
|
||||
when members are passed as template arguments.<br/>
|
||||
The purpose of this utility is to extract the class type in a few lines of code:
|
||||
|
||||
```cpp
|
||||
template<typename Member>
|
||||
using clazz = entt::member_class_t<Member>;
|
||||
```
|
||||
|
||||
## Tags
|
||||
|
||||
Since in `EnTT` the type identified by `ENTT_ID_TYPE` is very important and
|
||||
widely used, there is a more user-friendly shortcut for the creation of integral
|
||||
constants based on it.<br/>
|
||||
This shortcut is the alias template `entt::tag`.
|
||||
|
||||
If used in combination with hashed strings, it helps to use human-readable names
|
||||
where types would be required otherwise. As an example:
|
||||
|
||||
```cpp
|
||||
registry.assign<entt::tag<"enemy"_hs>>(entity);
|
||||
```
|
||||
|
||||
However, this isn't the only permitted use. Literally any value convertible to
|
||||
`ENTT_ID_TYPE` is a good candidate, such as the named constants of an unscoped
|
||||
enum.
|
||||
1749
docs/md/entity.md
Normal file
1749
docs/md/entity.md
Normal file
File diff suppressed because it is too large
Load Diff
200
docs/md/faq.md
Normal file
200
docs/md/faq.md
Normal file
@@ -0,0 +1,200 @@
|
||||
# Frequently Asked Questions
|
||||
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
# Table of Contents
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [FAQ](#faq)
|
||||
* [Why is my debug build on Windows so slow?](#why-is-my-debug-build-on-windows-so-slow)
|
||||
* [How can I represent hierarchies with my components?](#how-can-i-represent-hierarchies-with-my-components)
|
||||
* [Custom entity identifiers: yay or nay?](#custom-entity-identifiers-yay-or-nay)
|
||||
* [Warning C4307: integral constant overflow](#warning-C4307-integral-constant-overflow)
|
||||
* [Warning C4003: the min, the max and the macro](#warning-C4003-the-min-the-max-and-the-macro)
|
||||
* [The standard and the non-copyable types](#the-standard-and-the-non-copyable-types)
|
||||
<!--
|
||||
@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_ASSERT` should be redefined to disable internal checks
|
||||
made by `EnTT` in debug:
|
||||
|
||||
```cpp
|
||||
#define ENTT_ASSERT(...) ((void)0)
|
||||
```
|
||||
|
||||
These asserts are introduced to help the users but they require to access to the
|
||||
underlying containers and therefore risk ruining the performance in some cases.
|
||||
|
||||
With these changes, debug performance should increase enough for most cases. If
|
||||
you want something more, you can can also switch to an optimization level `O0`
|
||||
or preferably `O1`.
|
||||
|
||||
## How can I represent hierarchies with my components?
|
||||
|
||||
This is one of the first questions that anyone makes when starting to work with
|
||||
the entity-component-system architectural pattern.<br/>
|
||||
There are several approaches to the problem and what’s the best one depends
|
||||
mainly on the real problem one is facing. In all cases, how to do it doesn't
|
||||
strictly depend on the library in use, but the latter can certainly allow or
|
||||
not different techniques depending on how the data are laid out.
|
||||
|
||||
I tried to describe some of the techniques that fit well with the model of
|
||||
`EnTT`. [Here](https://skypjack.github.io/2019-06-25-ecs-baf-part-4/) is the
|
||||
first post of a series that tries to explore the problem. More will probably
|
||||
come in future.
|
||||
|
||||
Long story short, you can always define a tree where the nodes expose implicit
|
||||
lists of children by means of the following type:
|
||||
|
||||
```cpp
|
||||
struct relationship {
|
||||
std::size_t children{};
|
||||
entt::entity first{entt::null};
|
||||
entt::entity prev{entt::null};
|
||||
entt::entity next{entt::null};
|
||||
entt::entity parent{entt::null};
|
||||
// ... other data members ...
|
||||
};
|
||||
```
|
||||
|
||||
The sort functionalities of `EnTT`, the groups and all the other features of the
|
||||
library can help then to get the best in terms of data locality and therefore
|
||||
performance from this component.
|
||||
|
||||
## Custom entity identifiers: yay or nay?
|
||||
|
||||
Custom entity identifiers are definitely a good idea in two cases at least:
|
||||
|
||||
* If `std::uint32_t` isn't large enough as an underlying type.
|
||||
* If you want to avoid conflicts when using multiple registries.
|
||||
|
||||
These identifiers are nothing more than enum classes with some salt.<br/>
|
||||
To simplify the creation of new identifiers, `EnTT` provides the macro
|
||||
`ENTT_OPAQUE_TYPE` that accepts two arguments:
|
||||
|
||||
* The name you want to give to the new identifier (watch out for namespaces).
|
||||
* The underlying type to use (either `std::uint16_t`, `std::uint32_t`
|
||||
or `std::uint64_t`).
|
||||
|
||||
In fact, this is the definition of `entt::entity`:
|
||||
|
||||
```cpp
|
||||
ENTT_OPAQUE_TYPE(entity, std::uint32_t)
|
||||
```
|
||||
|
||||
The use of this macro is highly recommended, so as not to run into problems if
|
||||
the requirements for the identifiers should change in the future.
|
||||
|
||||
## Warning C4307: integral constant overflow
|
||||
|
||||
According to [this](https://github.com/skypjack/entt/issues/121) issue, using a
|
||||
hashed string under VS could generate a warning.<br/>
|
||||
First of all, I want to reassure you: it's expected and harmless. However, it
|
||||
can be annoying.
|
||||
|
||||
To suppress it and if you don't want to suppress all the other warnings as well,
|
||||
here is a workaround in the form of a macro:
|
||||
|
||||
```cpp
|
||||
#if defined(_MSC_VER)
|
||||
#define HS(str) __pragma(warning(suppress:4307)) entt::hashed_string{str}\
|
||||
#else
|
||||
#define HS(str) entt::hashed_string{str}
|
||||
#endif
|
||||
```
|
||||
|
||||
With an example of use included:
|
||||
|
||||
```cpp
|
||||
constexpr auto identifier = HS("my/resource/identifier");
|
||||
```
|
||||
|
||||
Thanks to [huwpascoe](https://github.com/huwpascoe) for the courtesy.
|
||||
|
||||
## Warning C4003: the min, the max and the macro
|
||||
|
||||
On Windows, a header file defines two macros `min` and `max` which may result in
|
||||
conflicts with their counterparts in the standard library and therefore in
|
||||
errors during compilation.
|
||||
|
||||
It's a pretty big problem but fortunately it's not a problem of `EnTT` and there
|
||||
is a fairly simple solution to it.<br/>
|
||||
It consists in defining the `NOMINMAX` macro before to include any other header
|
||||
so as to get rid of the extra definitions:
|
||||
|
||||
```cpp
|
||||
#define NOMINMAX
|
||||
```
|
||||
|
||||
Please refer to [this](https://github.com/skypjack/entt/issues/96) issue for
|
||||
more details.
|
||||
|
||||
## The standard and the non-copyable types
|
||||
|
||||
`EnTT` uses internally the trait `std::is_copy_constructible_v` to check if a
|
||||
component is actually copyable. This trait doesn't check if an object can
|
||||
actually be copied but only verifies if there is a copy constructor
|
||||
available.<br/>
|
||||
This can lead to surprising results due to some idiosyncrasies of the standard
|
||||
mainly related to the need to guarantee backward compatibility.
|
||||
|
||||
For example, `std::vector` defines a copy constructor no matter if its value
|
||||
type is copyable or not. As a result, `std::is_copy_constructible_v` is true
|
||||
for the following specialization:
|
||||
|
||||
```cpp
|
||||
struct type {
|
||||
std::vector<std::unique_ptr<action>> vec;
|
||||
};
|
||||
```
|
||||
|
||||
When trying to assign an instance of this type to an entity in the ECS part,
|
||||
this may trigger a compilation error because we cannot really make a copy of
|
||||
it.<br/>
|
||||
As a workaround, users can mark the type explicitly as non-copyable:
|
||||
|
||||
```cpp
|
||||
struct type {
|
||||
type(const type &) = delete;
|
||||
type & operator=(const type &) = delete;
|
||||
|
||||
std::vector<std::unique_ptr<action>> vec;
|
||||
};
|
||||
```
|
||||
|
||||
Unfortunately, this will also disable aggregate initialization.
|
||||
103
docs/md/lib.md
Normal file
103
docs/md/lib.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# Push EnTT across boundaries
|
||||
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
# Table of Contents
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [The EnTT way](#the-entt-way)
|
||||
* [Meta context](#meta-context)
|
||||
* [Memory management](#memory-management)
|
||||
<!--
|
||||
@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 was mainly due to a custom utility used to assign unique, sequential
|
||||
identifiers to different types.<br/>
|
||||
Fortunately, nowadays using `EnTT` across boundaries is straightforward. In
|
||||
fact, everything just works transparently in almost all cases. There are only a
|
||||
few exceptions, easy to deal with anyway.
|
||||
|
||||
# The EnTT way
|
||||
|
||||
Many classes in `EnTT` make extensive use of type erasure for their purposes.
|
||||
This isn't a problem in itself (in fact, it's the basis of an API so convenient
|
||||
to use). However, a way is needed to recognize the objects whose type has been
|
||||
erased on the other side of a boundary.<br/>
|
||||
The `type_info` class template is how identifiers are generated and thus made
|
||||
available to the rest of the library.
|
||||
|
||||
In general, this class doesn't arouse much interest. The only exception is in
|
||||
case of conflicts between identifiers (definitely uncommon though) or where the
|
||||
default solution proposed by `EnTT` isn't suitable for the user's purposes.<br/>
|
||||
The section dedicated to this core class contains all the details to get around
|
||||
the problem in a concise and elegant way. Please refer to the specific
|
||||
documentation.
|
||||
|
||||
# Meta context
|
||||
|
||||
The runtime reflection system deserves a special mention when it comes to using
|
||||
it across boundaries.<br/>
|
||||
Since it's linked to a static context to which the visible components are
|
||||
attached and different contexts don't relate to each other, they must be
|
||||
_shared_ to allow the use of meta types across boundaries.
|
||||
|
||||
Sharing a context is trivial though. First of all, the local one must be
|
||||
acquired in the main space:
|
||||
|
||||
```cpp
|
||||
entt::meta_ctx ctx{};
|
||||
```
|
||||
|
||||
Then, it must passed to the receiving space that will set it as its global
|
||||
context, thus releasing the local one that remains available but is no longer
|
||||
referred to by the runtime reflection system:
|
||||
|
||||
```cpp
|
||||
entt::meta_ctx::bind(ctx);
|
||||
```
|
||||
|
||||
From now on, both spaces will refer to the same context and on it will be
|
||||
attached the new visible meta types, no matter where they are created.<br/>
|
||||
A context can also be reset and then associated again locally as:
|
||||
|
||||
```cpp
|
||||
entt::meta_ctx::bind{entt::meta_ctx{});
|
||||
```
|
||||
|
||||
This is allowed because local and global contexts are separated. Therefore, it's
|
||||
always possible to make the local context the current one again.
|
||||
|
||||
Before to release a context, all locally registered types should be reset to
|
||||
avoid dangling references. Otherwise, if a type is accessed from another space
|
||||
by name, there could be an attempt to address its parts that are no longer
|
||||
available.
|
||||
|
||||
# Memory Management
|
||||
|
||||
There is another subtle problem due to memory management that can lead to
|
||||
headaches.<br/>
|
||||
It can occur where there are pools of objects (such as components or events)
|
||||
dynamically created on demand. This is usually not a problem when working with
|
||||
linked libraries that rely on the same dynamic runtime. However, it can occur in
|
||||
the case of plugins or statically linked runtimes.
|
||||
|
||||
As an example, imagine creating an instance of `registry` in the main executable
|
||||
and sharing it with a plugin. If the latter starts working with a component that
|
||||
is unknown to the former, a dedicated pool is created within the registry on
|
||||
first use.<br/>
|
||||
As one can guess, this pool is instantiated on a different side of the boundary
|
||||
from the `registry`. Therefore, the instance is now managing memory from
|
||||
different spaces and this can quickly lead to crashes if not properly addressed.
|
||||
|
||||
To overcome the risk, it's recommended to use well-defined interfaces that make
|
||||
fundamental types pass through the boundaries, isolating the instances of the
|
||||
`EnTT` classes from time to time and as appropriate.<br/>
|
||||
Refer to the test suite for some examples, read the documentation available
|
||||
online about this type of issues or consult someone who has already had such
|
||||
experiences to avoid problems.
|
||||
127
docs/md/links.md
Normal file
127
docs/md/links.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# 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.
|
||||
* [Land of the Rair](https://github.com/LandOfTheRair/core2): the new backend
|
||||
of [a retro-style MUD](https://rair.land/) for the new age.
|
||||
* [Openblack](https://github.com/openblack/openblack): open source
|
||||
reimplementation of the game _Black & White_ (2001).
|
||||
* [Face Smash](https://play.google.com/store/apps/details?id=com.gamee.facesmash):
|
||||
a game to play with your face.
|
||||
* [EnTT Pacman](https://github.com/Kerndog73/EnTT-Pacman): an example of how
|
||||
to make Pacman with `EnTT`.
|
||||
* [Wacman](https://github.com/carlfindahl/wacman): a pacman clone with OpenGL.
|
||||
* [Classic Tower Defence](https://github.com/kerndog73/Classic-Tower-Defence):
|
||||
a tiny little tower defence game featuring a homemade font.
|
||||
[Check it out](https://indi-kernick.itch.io/classic-tower-defence).
|
||||
* [The Machine](https://github.com/Kerndog73/The-Machine): a box pushing
|
||||
puzzler with logic gates and other cool stuff.
|
||||
[Check it out](https://indi-kernick.itch.io/the-machine-web-version).
|
||||
* [EnttPong](https://github.com/reworks/EnttPong): an example of how to make
|
||||
Pong with `EnTT`.
|
||||
* [Randballs](https://github.com/gale93/randballs): simple `SFML` and `EnTT`
|
||||
playground.
|
||||
* [EnTT Tower Defense](https://github.com/Daivuk/tddod): a data oriented tower
|
||||
defense example.
|
||||
* [EnTT Breakout](https://github.com/vblanco20-1/entt-breakout): simple
|
||||
example of a breakout game, using `SDL` and `EnTT`.
|
||||
* [Arcade puzzle game with EnTT](https://github.com/MasonRG/ArcadePuzzleGame):
|
||||
arcade puzzle game made in C++ using the `SDL2` and `EnTT` libraries.
|
||||
* [Snake with EnTT](https://github.com/MasonRG/SnakeGame): simple snake game
|
||||
made in C++ with the `SDL2` and `EnTT` libraries.
|
||||
* [Mirrors lasers and robots](https://github.com/guillaume-haerinck/imac-tower-defense):
|
||||
a small tower defense game based on mirror orientation.
|
||||
* [PopHead](https://github.com/SPC-Some-Polish-Coders/PopHead/): 2D, Zombie,
|
||||
RPG game made from scratch in C++.
|
||||
* [Robotligan](https://github.com/Trisslotten/robotligan): multiplayer
|
||||
football game.
|
||||
* [DungeonSlayer](https://github.com/alohaeee/DungeonSlayer): 2D game made
|
||||
from scratch in C++.
|
||||
|
||||
* Engines and the like:
|
||||
* [Fling Engine](https://github.com/flingengine/FlingEngine): a Vulkan game
|
||||
engine with a focus on data oriented design.
|
||||
* [Apparently](https://teamwisp.github.io/credits/)
|
||||
[Wisp](https://teamwisp.github.io/product/) by
|
||||
[Team Wisp](https://teamwisp.github.io/): an advanced real-time ray tracing
|
||||
renderer built for the demands of video game artists.
|
||||
* [starlight](https://github.com/DomRe/starlight): game programming framework
|
||||
using `Allegro`, `Lua` and modern C++.
|
||||
* [Apparently](https://github.com/JosiahWI/qub3d-libdeps)
|
||||
[Qub3d](https://qub3d.org/): because blocks should be open source.
|
||||
* [shiva](https://github.com/Milerius/shiva): modern C++ engine with
|
||||
modularity.
|
||||
* [NovusCore](https://github.com/novuscore/NovusCore): a modern take on World
|
||||
of Warcraft emulation.
|
||||
* [ImGui/EnTT editor](https://github.com/Green-Sky/imgui_entt_entity_editor):
|
||||
a drop-in, single-file entity editor for `EnTT` that uses `ImGui` as
|
||||
graphical backend (with
|
||||
[demo code](https://github.com/Green-Sky/imgui_entt_entity_editor_demo)).
|
||||
* [SgOgl](https://github.com/stwe/SgOgl): a game engine library for OpenGL
|
||||
developed for educational purposes.
|
||||
* [Lumos](https://github.com/jmorton06/Lumos): game engine written in C++
|
||||
using OpenGL and Vulkan.
|
||||
|
||||
* 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).
|
||||
* [Sequentity](https://github.com/alanjfs/sequentity): A MIDI-like
|
||||
sequencer/tracker for C++ and `ImGui` (with `Magnum` and `EnTT`).
|
||||
* [Apparently](https://www.linkedin.com/in/skypjack/)
|
||||
[NIO](https://www.nio.io/): there was a collaboration to make some changes
|
||||
to `EnTT`, at the time used for internal projects.
|
||||
* [Apparently](https://www.linkedin.com/jobs/view/architekt-c%2B%2B-at-tieto-1219512333/)
|
||||
[Tieto](https://www.tieto.com/): they published a job post where `EnTT` was
|
||||
listed on their software stack.
|
||||
* [Godot meets EnTT](https://github.com/portaloffreedom/godot_entt_example/):
|
||||
a simple example on how to use `EnTT` within
|
||||
[`Godot`](https://godotengine.org/).
|
||||
* [Godot and GameNetworkingSockets meet EnTT](https://github.com/portaloffreedom/godot_entt_net_example):
|
||||
a simple example on how to use `EnTT` and
|
||||
[`GameNetworkingSockets`](https://github.com/ValveSoftware/GameNetworkingSockets)
|
||||
within [`Godot`](https://godotengine.org/).
|
||||
* [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.
|
||||
@@ -32,13 +32,13 @@ 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::ServiceLocator<MyService>::set(params...);
|
||||
entt::service_locator<my_service>::set(params...);
|
||||
|
||||
// sets up an opaque service
|
||||
entt::ServiceLocator<AudioInterface>::set<AudioImplementation>(params...);
|
||||
entt::service_locator<audio_interface>::set<audio_implementation>(params...);
|
||||
|
||||
// resets (destroys) the service
|
||||
entt::ServiceLocator<AudioInterface>::reset();
|
||||
entt::service_locator<audio_interface>::reset();
|
||||
```
|
||||
|
||||
The locator can also be queried to know if an active service is currently set
|
||||
@@ -46,30 +46,30 @@ and to retrieve it if necessary (either as a pointer or as a reference):
|
||||
|
||||
```cpp
|
||||
// no service currently set
|
||||
auto empty = entt::ServiceLocator<AudioInterface>::empty();
|
||||
auto empty = entt::service_locator<audio_interface>::empty();
|
||||
|
||||
// gets a (possibly empty) shared pointer to the service ...
|
||||
std::shared_ptr<AudioInterface> ptr = entt::ServiceLocator<AudioInterface>::get();
|
||||
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
|
||||
AudioInterface &ref = entt::ServiceLocator<AudioInterface>::ref();
|
||||
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::ServiceLocator<CameraInterface>;
|
||||
using Audio = entt::ServiceLocator<AudioInterface>;
|
||||
struct locator {
|
||||
using camera = entt::service_locator<camera_interface>;
|
||||
using audio = entt::service_locator<audio_interface>;
|
||||
// ...
|
||||
};
|
||||
|
||||
// ...
|
||||
|
||||
void init() {
|
||||
Locator::Camera::set<CameraNull>();
|
||||
Locator::Audio::set<AudioImplementation>(params...);
|
||||
locator::camera::set<camera_null>();
|
||||
locator::audio::set<audio_implementation>(params...);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
591
docs/md/meta.md
Normal file
591
docs/md/meta.md
Normal file
@@ -0,0 +1,591 @@
|
||||
# Crash Course: runtime reflection system
|
||||
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
# Table of Contents
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [Names and identifiers](#names-and-identifiers)
|
||||
* [Reflection in a nutshell](#reflection-in-a-nutshell)
|
||||
* [Any as in any type](#any-as-in-any-type)
|
||||
* [Enjoy the runtime](#enjoy-the-runtime)
|
||||
* [Policies: the more, the less](#policies-the-more-the-less)
|
||||
* [Named constants and enums](#named-constants-and-enums)
|
||||
* [Properties and meta objects](#properties-and-meta-objects)
|
||||
* [Unregister types](#unregister-types)
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
|
||||
# Introduction
|
||||
|
||||
Reflection (or rather, its lack) is a trending topic in the C++ world and, in
|
||||
the specific case of `EnTT`, a tool that can unlock a lot of other features. I
|
||||
looked for a third-party library that met my needs on the subject, but I always
|
||||
came across some details that I didn't like: macros, being intrusive, too many
|
||||
allocations. In one word: unsatisfactory.<br/>
|
||||
I finally decided to write a built-in, non-intrusive and macro-free runtime
|
||||
reflection system for `EnTT`. Maybe I didn't do better than others or maybe yes,
|
||||
time will tell me, but at least I can model this tool around the library to
|
||||
which it belongs and not vice versa.
|
||||
|
||||
# Names and identifiers
|
||||
|
||||
The meta system doesn't force users to rely on the tools provided by the library
|
||||
when it comes to working with names and identifiers. It does this by offering an
|
||||
API that works with opaque identifiers that may or may not be generated by means
|
||||
of a hashed string.<br/>
|
||||
This means that users can assign any type of identifier to the meta objects, as
|
||||
long as they are numeric. It doesn't matter if they are generated at runtime, at
|
||||
compile-time or with custom functions.
|
||||
|
||||
However, the examples in the following sections are all based on the
|
||||
`hashed_string` class as provided by this library. Therefore, where an
|
||||
identifier is required, it's likely that a user defined literal is used as
|
||||
follows:
|
||||
|
||||
```cpp
|
||||
auto factory = entt::meta<my_type>().type("reflected_type"_hs);
|
||||
```
|
||||
|
||||
For what it's worth, this is likely completely equivalent to:
|
||||
|
||||
```cpp
|
||||
auto factory = entt::meta<my_type>().type(42);
|
||||
```
|
||||
|
||||
Obviously, human-readable identifiers are more convenient to use and highly
|
||||
recommended.
|
||||
|
||||
# Reflection in a nutshell
|
||||
|
||||
Reflection always starts from real types (users cannot reflect imaginary types
|
||||
and it would not make much sense, we wouldn't be talking about reflection
|
||||
anymore).<br/>
|
||||
To create a meta node, the library provides the `meta` function that accepts a
|
||||
type to reflect as a template parameter:
|
||||
|
||||
```cpp
|
||||
auto factory = entt::meta<my_type>();
|
||||
```
|
||||
|
||||
This isn't enough to _export_ the given type and make it visible though.<br/>
|
||||
The returned value is a factory object to use to continue building the meta
|
||||
type. In order to make the type _visible_, users can assign it an identifier:
|
||||
|
||||
```cpp
|
||||
auto factory = entt::meta<my_type>().type("reflected_type"_hs);
|
||||
```
|
||||
|
||||
When working with named types, it isn't even necessary to specify the
|
||||
identifier. In fact, it isn't allowed and it will trigger a compilation
|
||||
error.<br/>
|
||||
Identifiers are important because users can retrieve meta types at runtime by
|
||||
searching for them by _name_ other than by type. On the other hand, 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_. In this case, it's sufficient not
|
||||
to invoke `type` and the type will not be searchable _by name_.
|
||||
|
||||
A factory is such that all its member functions returns the factory itself or
|
||||
a decorated version of it. This object can be used to add the following:
|
||||
|
||||
* _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::meta<my_type>().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::meta<my_type>().dtor<&destroy>();
|
||||
```
|
||||
|
||||
A function should neither delete nor explicitly invoke the destructor of a
|
||||
given instance.
|
||||
|
||||
* _Data members_. Both real data members of the underlying type and static and
|
||||
global variables, as well as constants of any kind, can be attached to a meta
|
||||
type. From a client's point of view, all the variables associated with the
|
||||
reflected type will appear as if they were part of the type itself.<br/>
|
||||
Use the `data` member function for this purpose:
|
||||
|
||||
```cpp
|
||||
entt::meta<my_type>()
|
||||
.data<&my_type::static_variable>("static"_hs)
|
||||
.data<&my_type::data_member>("member"_hs)
|
||||
.data<&global_variable>("global"_hs);
|
||||
```
|
||||
|
||||
This function requires as an argument the identifier to give to the meta data
|
||||
once created. Users can then access meta data at runtime by searching for them
|
||||
by _name_.<br/>
|
||||
Data members can be set also by means of a couple of functions, namely a
|
||||
setter and a getter. Setters and getters can be either free functions, member
|
||||
functions or mixed ones, as long as they respect the required signatures.<br/>
|
||||
Refer to the inline documentation for all the details.
|
||||
|
||||
* _Member functions_. Both real member functions of the underlying type and free
|
||||
functions can be attached to a meta type. From a client's point of view, all
|
||||
the functions associated with the reflected type will appear as if they were
|
||||
part of the type itself.<br/>
|
||||
Use the `func` member function for this purpose:
|
||||
|
||||
```cpp
|
||||
entt::meta<my_type>()
|
||||
.func<&my_type::static_function>("static"_hs)
|
||||
.func<&my_type::member_function>("member"_hs)
|
||||
.func<&free_function>("free"_hs);
|
||||
```
|
||||
|
||||
This function requires as an argument the identifier to give to the meta
|
||||
function once created. Users can then access meta functions at runtime by
|
||||
searching for them by _name_.
|
||||
|
||||
* _Base classes_. A base class is such that the underlying type is actually
|
||||
derived from it. In this case, the reflection system tracks the relationship
|
||||
and allows for implicit casts at runtime when required.<br/>
|
||||
Use the `base` member function for this purpose:
|
||||
|
||||
```cpp
|
||||
entt::meta<derived_type>().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::meta<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.
|
||||
|
||||
The class `meta_any` offers 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 through the reflection system, allocations are at zero.
|
||||
|
||||
Creating instances of `meta_any`, whether empty or from existing objects, is
|
||||
trivial:
|
||||
|
||||
```cpp
|
||||
// a container for an int
|
||||
entt::meta_any any{0};
|
||||
|
||||
// an empty container
|
||||
entt::meta_any empty{};
|
||||
```
|
||||
|
||||
The `meta_any` class takes also the burden of destroying the contained object
|
||||
when required.<br/>
|
||||
Furthermore, an instance of `meta_any` is not tied to a specific type.
|
||||
Therefore, the wrapper will be reconfigured by assigning it an object of a
|
||||
different type than the one contained, so as to be able to handle the new
|
||||
instance.
|
||||
|
||||
A particularly interesting feature of this class is that it can also be used as
|
||||
an opaque container for unmanaged objects:
|
||||
|
||||
```cpp
|
||||
int value;
|
||||
entt::meta_any any{std::ref(value)};
|
||||
```
|
||||
|
||||
In other words, whenever `meta_any` intercepts a `reference_wrapper`, it acts as
|
||||
a reference to the original instance rather than making a copy of it. The
|
||||
contained object is never destroyed and users must ensure that its lifetime
|
||||
exceeds that of the container.<br/>
|
||||
Similarly, to create a copy that works as a light reference for the managed
|
||||
object, it's possible to dereference a given `meta_any`:
|
||||
|
||||
```cpp
|
||||
entt::meta_any ref = *any;
|
||||
```
|
||||
|
||||
It doesn't matter if the starting container actually holds an object or acts as
|
||||
a reference for unmanaged elements, the new instance thus created won't create
|
||||
copies and will only serve as a reference for the original item.<br/>
|
||||
It means that, starting from the example above, both `ref` and` any` will point
|
||||
to the same object, whether it's initially contained in `any` or already an
|
||||
unmanaged one. This is particularly useful for passing instances of `meta_any`
|
||||
belonging to the external context by reference to a function or a constructor
|
||||
rather than making copies of them.
|
||||
|
||||
The `meta_any` class has also a `type` member function that returns the meta
|
||||
type of the contained value, if any. The member functions `try_cast`, `cast` and
|
||||
`convert` are then used to know if the underlying object has a given type as a
|
||||
base or if it can be converted implicitly to it.
|
||||
|
||||
## Enjoy the runtime
|
||||
|
||||
Once the web of reflected types has been constructed, it's a matter of using it
|
||||
at runtime where required.<br/>
|
||||
All this has the great merit that, unlike the vast majority of the things
|
||||
present in this library and closely linked to the compile-time, the reflection
|
||||
system stands in fact as a non-intrusive tool for the runtime.
|
||||
|
||||
To search for a reflected type there are two options: by type or by _name_. In
|
||||
both cases, the search can be done by means of the `resolve` function:
|
||||
|
||||
```cpp
|
||||
// search for a reflected type by type
|
||||
auto by_type = entt::resolve<my_type>();
|
||||
|
||||
// search for a reflected type by name
|
||||
auto by_name = entt::resolve("reflected_type"_hs);
|
||||
```
|
||||
|
||||
There exits also a third overload of the `resolve` function to use to iterate
|
||||
all the reflected types at once:
|
||||
|
||||
```cpp
|
||||
resolve([](auto type) {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
In all cases, the returned value is an instance of `meta_type`. This kind of
|
||||
objects offer an API to know their _runtime identifiers_, to iterate all the
|
||||
meta objects associated with them and even to build 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 its arguments and their
|
||||
expected meta types. Furthermor, it's possible to invoke it and therefore to
|
||||
construct new instances of the underlying type.
|
||||
|
||||
* _Meta data_. They are accessed by _name_:
|
||||
|
||||
```cpp
|
||||
auto data = entt::resolve<my_type>().data("member"_hs);
|
||||
```
|
||||
|
||||
The returned type is `meta_data` and may be invalid if there is no meta data
|
||||
object associated with the given identifier.<br/>
|
||||
A meta data object offers an API to query the underlying type (for example, to
|
||||
know if it's a const or a static one), to get the meta type of the variable
|
||||
and to set or get the contained value.
|
||||
|
||||
* _Meta functions_. They are accessed by _name_:
|
||||
|
||||
```cpp
|
||||
auto func = entt::resolve<my_type>().func("member"_hs);
|
||||
```
|
||||
|
||||
The returned type is `meta_func` and may be invalid if there is no meta
|
||||
function object associated with the given identifier.<br/>
|
||||
A meta function object offers an API to query the underlying type (for
|
||||
example, 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 a `meta_any` object.
|
||||
|
||||
* _Meta bases_. They are accessed through the _name_ of the base types:
|
||||
|
||||
```cpp
|
||||
auto base = entt::resolve<derived_type>().base("base"_hs);
|
||||
```
|
||||
|
||||
The returned type is `meta_base` and may be invalid if there is no meta base
|
||||
object associated with the given identifier.<br/>
|
||||
Meta bases aren't meant to be used directly, even though they are freely
|
||||
accessible. They expose only a few methods to use to know the meta type of the
|
||||
base class and to convert a raw pointer between types.
|
||||
|
||||
* _Meta conversion functions_. They are accessed by type:
|
||||
|
||||
```cpp
|
||||
auto conv = entt::resolve<double>().conv<int>();
|
||||
```
|
||||
|
||||
The returned type is `meta_conv` and may be invalid if there is no meta
|
||||
conversion function associated with the given type.<br/>
|
||||
The meta conversion functions are as thin as the meta bases and with a very
|
||||
similar interface. The sole difference is that they return a newly created
|
||||
instance wrapped in a `meta_any` object when they convert between different
|
||||
types.
|
||||
|
||||
All the objects thus obtained as well as the meta types can be explicitly
|
||||
converted to a boolean value to check if they are valid:
|
||||
|
||||
```cpp
|
||||
if(auto func = entt::resolve<my_type>().func("member"_hs); func) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Furthermore, all meta objects 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 be used to `construct` 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 then returns a `meta_any` object that may
|
||||
or may not be initialized, depending on whether a suitable constructor has been
|
||||
found or not.
|
||||
|
||||
There is no object that wraps the destructor of a meta type nor a `destroy`
|
||||
member function in its API. The reason is quickly explained: destructors are
|
||||
invoked implicitly by `meta_any` behind the scenes and users have not to deal
|
||||
with them explicitly. Furthermore, they have no name, cannot be searched and
|
||||
wouldn't have member functions to expose anyway.<br/>
|
||||
Therefore, exposing destructors would be pointless and would add nothing to the
|
||||
library itself.
|
||||
|
||||
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 inline documentation to get the best out of this powerful tool.
|
||||
|
||||
## Policies: the more, the less
|
||||
|
||||
Policies are a kind of compile-time directives that can be used when recording
|
||||
reflection information.<br/>
|
||||
Their purpose is to require slightly different behavior than the default in some
|
||||
specific cases. For example, when reading a given data member, its value is
|
||||
returned wrapped in a `meta_any` object which, by default, makes a copy of it.
|
||||
For large objects or if the caller wants to access the original instance, this
|
||||
behavior isn't desirable. Policies are there to offer a solution to this and
|
||||
other problems.
|
||||
|
||||
There are a few alternatives available at the moment:
|
||||
|
||||
* The _as-is_ policy, associated with the type `entt::as_is_t`.<br/>
|
||||
This is the default policy. In general, it should never be used explicitly,
|
||||
since it's implicitly selected if no other policy is specified.<br/>
|
||||
In this case, the return values of the functions as well as the properties
|
||||
exposed as data members are always returned by copy in a dedicated wrapper and
|
||||
therefore associated with their original meta types.
|
||||
|
||||
* The _as-void_ policy, associated with the type `entt::as_void_t`.<br/>
|
||||
Its purpose is to discard the return value of a meta object, whatever it is,
|
||||
thus making it appear as if its type were `void`.<br/>
|
||||
If the use with functions is obvious, it must be said that it's also possible
|
||||
to use this policy with constructors and data members. In the first case, the
|
||||
constructor will be invoked but the returned wrapper will actually be empty.
|
||||
In the second case, instead, the property will not be accessible for reading.
|
||||
|
||||
As an example of use:
|
||||
|
||||
```cpp
|
||||
entt::meta<my_type>().func<&my_type::member_function, entt::as_void_t>("member"_hs);
|
||||
```
|
||||
|
||||
* The _as-alias_ policy, associated with the type `entt::as_alias_t`.<br/>
|
||||
It allows to build wrappers that act as aliases for the objects that generated
|
||||
them. Modifying the object contained in the wrapper for which the _aliasing_
|
||||
was requested will make it possible to directly modify the instance used to
|
||||
initialize the wrapper itself.<br/>
|
||||
This policy works with constructors (for example, when objects are taken from
|
||||
an external container rather than created on demand), data members and
|
||||
functions in general (as long as their return types are lvalue references).
|
||||
|
||||
As an example of use:
|
||||
|
||||
```cpp
|
||||
entt::meta<my_type>().data<&my_type::data_member, entt::as_alias_t>("member"_hs);
|
||||
```
|
||||
|
||||
Some uses are rather trivial, but it's useful to note that there are some less
|
||||
obvious corner cases that can in turn be solved with the use of policies.
|
||||
|
||||
## Named constants and enums
|
||||
|
||||
A special mention should be made for constant values and enums. It wouldn't be
|
||||
necessary, but it will help distracted readers.
|
||||
|
||||
As mentioned, the `data` member function can be used to reflect constants of any
|
||||
type among the other things.<br/>
|
||||
This allows users to create meta types for enums that will work exactly like any
|
||||
other meta type built from a class. Similarly, arithmetic types can be enriched
|
||||
with constants of special meaning where required.<br/>
|
||||
Personally, I find it very useful not to export what is the difference between
|
||||
enums and classes in C++ directly in the space of the reflected types.
|
||||
|
||||
All the values thus exported will appear to users as if they were constant data
|
||||
members of the reflected types.
|
||||
|
||||
Exporting constant values or elements from an enum is as simple as ever:
|
||||
|
||||
```cpp
|
||||
entt::meta<my_enum>()
|
||||
.data<my_enum::a_value>("a_value"_hs)
|
||||
.data<my_enum::another_value>("another_value"_hs);
|
||||
|
||||
entt::meta<int>().data<2048>("max_int"_hs);
|
||||
```
|
||||
|
||||
It goes without saying that accessing them is trivial as well. It's a matter of
|
||||
doing the following, as with any other data member of a meta type:
|
||||
|
||||
```cpp
|
||||
auto value = entt::resolve<my_enum>().data("a_value"_hs).get({}).cast<my_enum>();
|
||||
auto max = entt::resolve<int>().data("max_int"_hs).get({}).cast<int>();
|
||||
```
|
||||
|
||||
As a side note, remember that all this happens behind the scenes without any
|
||||
allocation because of the small object optimization performed by the `meta_any`
|
||||
class.
|
||||
|
||||
## Properties and meta objects
|
||||
|
||||
Sometimes (for example, when it comes to creating an editor) it might be useful
|
||||
to attach properties to the meta objects created. Fortunately, this is possible
|
||||
for most of them.<br/>
|
||||
For the meta objects that support properties, the member functions of the
|
||||
factory used for registering them will return a decorated version of the factory
|
||||
itself. The latter can be used to attach properties to the last created meta
|
||||
object.<br/>
|
||||
Apparently, it's more difficult to say than to do:
|
||||
|
||||
```cpp
|
||||
entt::meta<my_type>().type("reflected_type"_hs).prop("tooltip"_hs, "message");
|
||||
```
|
||||
|
||||
Properties are always in the key/value form. There are no restrictions on the
|
||||
type of the key or value, as long as they are copy constructible objects.<br/>
|
||||
Multiple formats are supported when it comes to defining a property:
|
||||
|
||||
* Properties as key/value pairs:
|
||||
|
||||
```cpp
|
||||
entt::meta<my_type>().type("reflected_type"_hs).prop("tooltip"_hs, "message");
|
||||
```
|
||||
|
||||
* Properties as `std::pair`s:
|
||||
|
||||
```cpp
|
||||
entt::meta<my_type>().type("reflected_type"_hs).prop(std::make_pair("tooltip"_hs, "message"));
|
||||
```
|
||||
|
||||
* Key only properties:
|
||||
|
||||
```cpp
|
||||
entt::meta<my_type>().type("reflected_type"_hs).prop(my_enum::key_only);
|
||||
```
|
||||
|
||||
* Properties as `std::tuple`s:
|
||||
|
||||
```cpp
|
||||
entt::meta<my_type>().type("reflected_type"_hs)
|
||||
.prop(std::make_tuple(std::make_pair("tooltip"_hs, "message"), my_enum::key_only));
|
||||
```
|
||||
|
||||
A tuple contains one or more properties. All of them are treated individually.
|
||||
|
||||
* Annotations:
|
||||
|
||||
```cpp
|
||||
entt::meta<my_type>().type("reflected_type"_hs).prop(&property_generator);
|
||||
```
|
||||
|
||||
An annotation is an invocable object that returns one or more properties. All
|
||||
of them are treated individually.
|
||||
|
||||
It's possible to invoke the `prop` function several times if needed, one for
|
||||
each property to associate with the last meta object created:
|
||||
|
||||
```cpp
|
||||
entt::meta<my_type>()
|
||||
.type("reflected_type"_hs)
|
||||
.prop(entt::hashed_string{"Name"}, "Reflected Type")
|
||||
.data<&my_type::data_member>("member"_hs)
|
||||
.prop(std::make_pair("tooltip"_hs, "Member"))
|
||||
.prop(my_enum::a_value, 42);
|
||||
```
|
||||
|
||||
Alternatively, the `props` function is available to associate several properties
|
||||
at a time. However, in this case properties in the key/value form aren't
|
||||
allowed, since they would be interpreted as two different properties rather than
|
||||
a single one.
|
||||
|
||||
The meta objects for which properties are supported are currently the meta
|
||||
types, meta constructors, meta data and meta functions. It's not possible to
|
||||
attach properties to other types of meta objects and the factory returned as a
|
||||
result of their construction won't allow such an operation.
|
||||
|
||||
These types offer a couple of member functions named `prop` to iterate all
|
||||
properties at once or to search a specific property by key:
|
||||
|
||||
```cpp
|
||||
// iterate all 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.<br/>
|
||||
Roughly speaking, unregistering a type means disconnecting all associated meta
|
||||
objects from it and making its identifier no longer visible. The underlying node
|
||||
will remain available though, as if it were implicitly generated:
|
||||
|
||||
```cpp
|
||||
entt::meta<my_type>().reset();
|
||||
```
|
||||
|
||||
The type can be re-registered later with a completely different name and form.
|
||||
@@ -24,7 +24,7 @@ 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
|
||||
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.
|
||||
|
||||
@@ -40,11 +40,12 @@ class wants to _override_ the default behavior):
|
||||
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(void *);`
|
||||
* `void init();`
|
||||
|
||||
It's invoked at the first tick, immediately before an update. The `void *`
|
||||
parameter is an opaque pointer to user data (if any) forwarded directly to the
|
||||
process during an update.
|
||||
It's invoked when the process joins the running queue of a scheduler. This
|
||||
happens as soon as it's attached to the scheduler if the process is a top
|
||||
level one, otherwise when it replaces its parent if the process is a
|
||||
continuation.
|
||||
|
||||
* `void succeeded();`
|
||||
|
||||
@@ -70,11 +71,15 @@ life cycle of a process from a derived class.
|
||||
Here is a minimal example for the sake of curiosity:
|
||||
|
||||
```cpp
|
||||
struct MyProcess: entt::Process<MyProcess, std::uint32_t> {
|
||||
struct my_process: entt::process<my_process, std::uint32_t> {
|
||||
using delta_type = std::uint32_t;
|
||||
|
||||
my_process(delta_type delay)
|
||||
: remaining{delay}
|
||||
{}
|
||||
|
||||
void update(delta_type delta, void *) {
|
||||
remaining = delta > remaining ? delta_type{] : (remaining - delta);
|
||||
remaining -= std::min(remaining, delta);
|
||||
|
||||
// ...
|
||||
|
||||
@@ -83,10 +88,6 @@ struct MyProcess: entt::Process<MyProcess, std::uint32_t> {
|
||||
}
|
||||
}
|
||||
|
||||
void init(void *data) {
|
||||
remaining = *static_cast<delta_type *>(data);
|
||||
}
|
||||
|
||||
private:
|
||||
delta_type remaining;
|
||||
};
|
||||
@@ -127,17 +128,17 @@ 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 once more the next tick.<br/>
|
||||
A process can also have a child. In this case, the process is replaced with
|
||||
its child when it terminates if it returns with success. In case of errors,
|
||||
both the process and its child are discarded. This way, it's easy to create
|
||||
chain of processes to run sequentially.
|
||||
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
|
||||
Scheduler<std::uint32_t> scheduler;
|
||||
entt::scheduler<std::uint32_t> scheduler;
|
||||
```
|
||||
|
||||
It has member functions to query its internal data structures, like `empty` or
|
||||
@@ -148,7 +149,7 @@ It has member functions to query its internal data structures, like `empty` or
|
||||
const auto empty = scheduler.empty();
|
||||
|
||||
// gets the number of processes still running
|
||||
Scheduler<std::uint32_t>::size_type size = scheduler.size();
|
||||
entt::scheduler<std::uint32_t>::size_type size = scheduler.size();
|
||||
|
||||
// resets the scheduler to its initial state and discards all the processes
|
||||
scheduler.clear();
|
||||
@@ -156,12 +157,12 @@ 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
|
||||
* 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<MyProcess>("foobar");
|
||||
scheduler.attach<my_process>(1000u);
|
||||
```
|
||||
|
||||
* Otherwise, in case of a lambda or a functor, it's enough to provide an
|
||||
@@ -185,11 +186,11 @@ scheduler.attach([](auto delta, void *, auto succeed, auto fail) {
|
||||
// ...
|
||||
})
|
||||
// appends a child in the form of a process class
|
||||
.then<MyProcess>();
|
||||
.then<my_process>(1000u);
|
||||
```
|
||||
|
||||
To update a scheduler and thus all its processes, the `update` member function
|
||||
is the way to go:
|
||||
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
|
||||
@@ -38,29 +38,29 @@ whatever. There are no limits.<br/>
|
||||
As a minimal example:
|
||||
|
||||
```cpp
|
||||
struct MyResource { const int value; };
|
||||
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 MyLoader final: entt::ResourceLoader<MyLoader, MyResource> {
|
||||
struct my_loader final: entt::loader<my_loader, my_resource> {
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
Where `MyResource` is the type of resources it creates.<br/>
|
||||
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 MyLoader: entt::ResourceLoader<MyLoader, MyResource> {
|
||||
std::shared_ptr<MyResource> load(int value) const {
|
||||
struct my_loader: entt::loader<my_loader, my_resource> {
|
||||
std::shared_ptr<my_resource> load(int value) const {
|
||||
// ...
|
||||
return std::shared_ptr<MyResource>(new MyResource{ value });
|
||||
return std::shared_ptr<my_resource>(new my_resource{ value });
|
||||
}
|
||||
};
|
||||
```
|
||||
@@ -76,15 +76,15 @@ Finally, a cache is a specialization of a class template tailored to a specific
|
||||
resource:
|
||||
|
||||
```cpp
|
||||
using MyResourceCache = entt::ResourceCache<MyResource>;
|
||||
using my_cache = entt::cache<my_resource>;
|
||||
|
||||
// ...
|
||||
|
||||
MyResourceCache cache{};
|
||||
my_cache cache{};
|
||||
```
|
||||
|
||||
The idea is to create different caches for different types of resources and to
|
||||
manage each one independently and in the most appropriate way.<br/>
|
||||
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.
|
||||
@@ -103,25 +103,25 @@ const auto empty = cache.empty();
|
||||
cache.clear();
|
||||
```
|
||||
|
||||
Besides these member functions, it contains what is needed to load, use and
|
||||
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::ResourceCache<Resource>::resource_type
|
||||
entt::cache<resource>::id_type
|
||||
```
|
||||
|
||||
Where `resource_type` is an alias for `entt::HashedString`. Therefore, resource
|
||||
identifiers are created explicitly as in the following example:
|
||||
Where `id_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::ResourceCache<Resource>::resource_type{"my/resource/identifier"};
|
||||
constexpr auto identifier = entt::cache<resource>::id_type{"my/resource/identifier"_hs};
|
||||
// this is equivalent to the following
|
||||
constexpr auto hs = entt::HashedString{"my/resource/identifier"};
|
||||
constexpr auto hs = entt::hashed_string{"my/resource/identifier"};
|
||||
```
|
||||
|
||||
The class `HashedString` is described in a dedicated section, so I won't do in
|
||||
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
|
||||
@@ -130,66 +130,62 @@ identifier and the parameters used to construct the resource as arguments:
|
||||
|
||||
```cpp
|
||||
// uses the identifier declared above
|
||||
cache.load<MyLoader>(identifier, 0);
|
||||
cache.load<my_loader>(identifier, 0);
|
||||
|
||||
// uses a const char * directly as an identifier
|
||||
cache.load<MyLoader>("another/identifier", 42);
|
||||
cache.load<my_loader>("another/identifier"_hs, 42);
|
||||
```
|
||||
|
||||
The return value can be used to know if the resource has been loaded correctly.
|
||||
In case the loader returns an invalid pointer or the resource already exists in
|
||||
the cache, a false value is returned:
|
||||
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(!cache.load<MyLoader>("another/identifier", 42)) {
|
||||
if(auto handle = cache.load<my_loader>("another/identifier"_hs, 42); handle) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Unfortunately, in this case there is no way to know what was the problem
|
||||
exactly. However, before trying to load a resource or after an error, one can
|
||||
use the `contains` member function to know if a cache already contains a
|
||||
specific resource:
|
||||
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");
|
||||
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 result = cache.reload<MyLoader>("another/identifier", 42);
|
||||
auto handle = cache.reload<my_loader>("another/identifier"_hs, 42);
|
||||
```
|
||||
|
||||
As above, the function returns true in case of success, false otherwise. The
|
||||
sole difference in this case is that an error necessarily means that the loader
|
||||
has failed for some reasons to load the resource.<br/>
|
||||
Note that the `reload` member function is a kind of alias of the following
|
||||
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<MyLoader>(identifier, 42);
|
||||
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, the
|
||||
function does nothing and returns immediately.
|
||||
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:
|
||||
They are returned to users in the form of handles. To get one of them later on:
|
||||
|
||||
```cpp
|
||||
auto handle = cache.handle("my/identifier");
|
||||
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 owns a handle and it 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 returns the contained
|
||||
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:
|
||||
@@ -228,13 +224,14 @@ 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 the one of `load` but for the fact that it doesn't
|
||||
return a boolean value. Instead, it returns a (possibly invalid) handle for the
|
||||
resource:
|
||||
The declaration is similar to that of `load`, a (possibly invalid) handle for
|
||||
the resource is returned also in this case:
|
||||
|
||||
```cpp
|
||||
auto handle = cache.temp<MyLoader>("another/identifier", 42);
|
||||
if(auto handle = cache.temp<my_loader>(42); handle) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Do not forget to test the handle for validity. Otherwise, getting the reference
|
||||
to the resource it points may result in undefined behavior.
|
||||
Do not forget to test the handle for validity. Otherwise, getting a reference to
|
||||
the resource it points may result in undefined behavior.
|
||||
506
docs/md/signal.md
Normal file
506
docs/md/signal.md
Normal file
@@ -0,0 +1,506 @@
|
||||
# Crash Course: events, signals and everything in between
|
||||
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
# Table of Contents
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [Delegate](#delegate)
|
||||
* [Signals](#signals)
|
||||
* [Event dispatcher](#event-dispatcher)
|
||||
* [Event emitter](#event-emitter)
|
||||
<!--
|
||||
@endcond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
|
||||
# Introduction
|
||||
|
||||
Signals are usually a core part of games and software architectures in
|
||||
general.<br/>
|
||||
Roughly speaking, they help to decouple the various parts of a system while
|
||||
allowing them to communicate with each other somehow.
|
||||
|
||||
The so called _modern C++_ comes with a tool that can be useful in these terms,
|
||||
the `std::function`. As an example, it can be used to create delegates.<br/>
|
||||
However, there is no guarantee that an `std::function` does not perform
|
||||
allocations under the hood and this could be problematic sometimes. Furthermore,
|
||||
it solves a problem but may not adapt well to other requirements that may arise
|
||||
from time to time.
|
||||
|
||||
In case that the flexibility and power of an `std::function` isn't required or
|
||||
if the price to pay for them is too high,` EnTT` offers a complete set of
|
||||
lightweight classes to solve the same and many other problems.
|
||||
|
||||
# Delegate
|
||||
|
||||
A delegate can be used as a general purpose invoker with no memory overhead for
|
||||
free functions and members provided along with an instance on which to invoke
|
||||
them.<br/>
|
||||
It doesn't claim to be a drop-in replacement for an `std::function`, so don't
|
||||
expect to use it whenever an `std::function` fits well. That said, it's most
|
||||
likely even a better fit than an `std::function` in a lot of cases, so expect to
|
||||
use it quite a lot anyway.
|
||||
|
||||
The interface is trivial. It offers a default constructor to create empty
|
||||
delegates:
|
||||
|
||||
```cpp
|
||||
entt::delegate<int(int)> delegate{};
|
||||
```
|
||||
|
||||
All what is needed to create an instance is to specify the type of the function
|
||||
the delegate will _contain_, that is the signature of the free function or the
|
||||
member one wants to assign to it.
|
||||
|
||||
Attempting to use an empty delegate by invoking its function call operator
|
||||
results in undefined behavior or most likely a crash. Before to use a delegate,
|
||||
it must be initialized.<br/>
|
||||
There exists a bunch of overloads of the `connect` member function to do that.
|
||||
As an example of use:
|
||||
|
||||
```cpp
|
||||
int f(int i) { return i; }
|
||||
|
||||
struct my_struct {
|
||||
int f(const int &i) { return i }
|
||||
};
|
||||
|
||||
// bind a free function to the delegate
|
||||
delegate.connect<&f>();
|
||||
|
||||
// bind a member function to the delegate
|
||||
my_struct instance;
|
||||
delegate.connect<&my_struct::f>(instance);
|
||||
```
|
||||
|
||||
The delegate class accepts also data members, if needed. In this case, the
|
||||
function type of the delegate is such that the parameter list is empty and the
|
||||
value of the data member is at least convertible to the return type.
|
||||
|
||||
Free functions having type equivalent to `void(T &, args...)` are accepted as
|
||||
well. The first argument `T &` is considered a payload and the function will
|
||||
receive it back every time it's invoked. In other terms, this works just fine
|
||||
with the above definition:
|
||||
|
||||
```cpp
|
||||
void g(const char &c, int i) { /* ... */ }
|
||||
const char c = 'c';
|
||||
|
||||
delegate.connect<&g>(c);
|
||||
delegate(42);
|
||||
```
|
||||
|
||||
The function `g` will be invoked with a reference to `c` and `42`. However, the
|
||||
function type of the delegate is still `void(int)`. This is also the signature
|
||||
of its function call operator.
|
||||
|
||||
Another interesting aspect of the delegate class is that it accepts also
|
||||
functions with a list of parameters that is shorter than that of the function
|
||||
type used to specialize the delegate itself.<br/>
|
||||
The following code is therefore perfectly valid:
|
||||
|
||||
```cpp
|
||||
void g() { /* ... */ }
|
||||
delegate.connect<&g>();
|
||||
delegate(42);
|
||||
```
|
||||
|
||||
Where the function type of the delegate is `void(int)` as above. It goes without
|
||||
saying that the extra arguments are silently discarded internally.<br/>
|
||||
This is a nice-to-have feature in a lot of cases, as an example when the
|
||||
`delegate` class is used as a building block of a signal-slot system.
|
||||
|
||||
To create and initialize a delegate at once, there are a few specialized
|
||||
constructors. Because of the rules of the language, the listener is provided by
|
||||
means of the `entt::connect_arg` variable template:
|
||||
|
||||
```cpp
|
||||
entt::delegate<int(int)> func{entt::connect_arg<&f>};
|
||||
```
|
||||
|
||||
Aside `connect`, a `disconnect` counterpart isn't provided. Instead, there
|
||||
exists a `reset` member function to use to clear a delegate.<br/>
|
||||
To know if a delegate is empty, it can be used explicitly in every conditional
|
||||
statement:
|
||||
|
||||
```cpp
|
||||
if(delegate) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Finally, to invoke a delegate, the function call operator is the way to go as
|
||||
already shown in the examples above:
|
||||
|
||||
```cpp
|
||||
auto ret = delegate(42);
|
||||
```
|
||||
|
||||
In all cases, the listeners don't have to strictly follow the signature of the
|
||||
delegate. As long as a listener can be invoked with the given arguments to yield
|
||||
a result that is convertible to the given result type, everything works just
|
||||
fine.
|
||||
|
||||
As a side note, note that members of a class may or may not be associated with
|
||||
instances. If they are not, the first argument of the function type must be that
|
||||
of the class on which the members operate and an instance of this class must
|
||||
obviously be passed when invoking the delegate:
|
||||
|
||||
```
|
||||
entt::delegate<void(my_struct &, int)> delegate;
|
||||
delegate.connect<&my_struct::f>();
|
||||
|
||||
my_struct instance;
|
||||
delegate(instance, 42);
|
||||
```
|
||||
|
||||
In this case, it's not possible to deduce the function type since the first
|
||||
argument doesn't necessarily have to be a reference (for example, it can be a
|
||||
pointer, as well as a const reference).<br/>
|
||||
Therefore, the function type must be declared explicitly for unbound members.
|
||||
|
||||
# Signals
|
||||
|
||||
Signal handlers work with references to classes, function pointers and pointers
|
||||
to members. Listeners can be any kind of objects and users are in charge of
|
||||
connecting and disconnecting them from a signal to avoid crashes due to
|
||||
different lifetimes. On the other side, performance shouldn't be affected that
|
||||
much by the presence of such a signal handler.<br/>
|
||||
Signals make use of delegates internally and therefore they undergo the same
|
||||
rules and offer similar functionalities. It may be a good idea to consult the
|
||||
documentation of the `delegate` class for further information.
|
||||
|
||||
A signal handler can be used as a private data member without exposing any
|
||||
_publish_ functionality to the clients of a class. The basic idea is to impose a
|
||||
clear separation between the signal itself and the `sink` class, that is a tool
|
||||
to be used to connect and disconnect listeners on the fly.
|
||||
|
||||
The API of a signal handler is straightforward. If a collector is supplied to
|
||||
the signal when something is published, all the values returned by the listeners
|
||||
can be literally _collected_ and used later by the caller. Otherwise, the class
|
||||
works just like a plain signal that emits events from time to time.<br/>
|
||||
To create instances of signal handlers it is sufficient to provide the type of
|
||||
function to which they refer:
|
||||
|
||||
```cpp
|
||||
entt::sigh<void(int, char)> signal;
|
||||
```
|
||||
|
||||
Signals offer all the basic functionalities required to know how many listeners
|
||||
they contain (`size`) or if they contain at least a listener (`empty`), as well
|
||||
as a function to use to swap handlers (`swap`).
|
||||
|
||||
Besides them, there are member functions to use both to connect and disconnect
|
||||
listeners in all their forms by means of a sink:
|
||||
|
||||
```cpp
|
||||
void foo(int, char) { /* ... */ }
|
||||
|
||||
struct listener {
|
||||
void bar(const int &, char) { /* ... */ }
|
||||
};
|
||||
|
||||
// ...
|
||||
|
||||
entt::sink sink{signal};
|
||||
listener instance;
|
||||
|
||||
sink.connect<&foo>();
|
||||
sink.connect<&listener::bar>(instance);
|
||||
|
||||
// ...
|
||||
|
||||
// disconnects a free function
|
||||
sink.disconnect<&foo>();
|
||||
|
||||
// disconnect a member function of an instance
|
||||
sink.disconnect<&listener::bar>(instance);
|
||||
|
||||
// disconnect all member functions of an instance, if any
|
||||
sink.disconnect(instance);
|
||||
|
||||
// discards all listeners at once
|
||||
sink.disconnect();
|
||||
```
|
||||
|
||||
As shown above, the listeners don't have to strictly follow the signature of the
|
||||
signal. As long as a listener can be invoked with the given arguments to yield a
|
||||
result that is convertible to the given return type, everything works just
|
||||
fine.<br/>
|
||||
It's also possible to connect a listener before other listeners already
|
||||
contained by the signal. The `before` function returns a `sink` object correctly
|
||||
initialized for the purpose that can be used to connect one or more listeners in
|
||||
order and in the desired position:
|
||||
|
||||
```cpp
|
||||
sink.before<&foo>().connect<&listener::bar>(instance);
|
||||
```
|
||||
|
||||
In all cases, the `connect` member function returns by default a `connection`
|
||||
object to be used as an alternative to break a connection by means of its
|
||||
`release` member function. A `scoped_connection` can also be created from a
|
||||
connection. In this case, the link is broken automatically as soon as the object
|
||||
goes out of scope.
|
||||
|
||||
Once listeners are attached (or even if there are no listeners at all), events
|
||||
and data in general can be published through a signal by means of the `publish`
|
||||
member function:
|
||||
|
||||
```cpp
|
||||
signal.publish(42, 'c');
|
||||
```
|
||||
|
||||
To collect data, the `collect` member function should be used instead. Below is
|
||||
a minimal example to show how to use it:
|
||||
|
||||
```cpp
|
||||
int f() { return 0; }
|
||||
int g() { return 1; }
|
||||
|
||||
// ...
|
||||
|
||||
entt::sigh<int()> signal;
|
||||
entt::sink sink{signal};
|
||||
|
||||
sink.connect<&f>();
|
||||
sink.connect<&g>();
|
||||
|
||||
std::vector<int> vec{};
|
||||
signal.collect([&vec](int value) { vec.push_back(value); });
|
||||
|
||||
assert(vec[0] == 0);
|
||||
assert(vec[1] == 1);
|
||||
```
|
||||
|
||||
A collector must expose a function operator that accepts as an argument a type
|
||||
to which the return type of the listeners can be converted. Moreover, it can
|
||||
optionally return a boolean value that is true to stop collecting data, false
|
||||
otherwise. This way one can avoid calling all the listeners in case it isn't
|
||||
necessary.<br/>
|
||||
Functors can also be used in place of a lambda. Since the collector is copied
|
||||
when invoking the `collect` member function, `std::ref` is the way to go in this
|
||||
case:
|
||||
|
||||
```cpp
|
||||
struct my_collector {
|
||||
std::vector<int> vec{};
|
||||
|
||||
bool operator()(int v) noexcept {
|
||||
vec.push_back(v);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// ...
|
||||
|
||||
my_collector collector;
|
||||
signal.collect(std::ref(collector));
|
||||
```
|
||||
|
||||
# Event dispatcher
|
||||
|
||||
The event dispatcher class is designed so as to be used in a loop. It allows
|
||||
users both to trigger immediate events or to queue events to be published all
|
||||
together once per tick.<br/>
|
||||
This class shares part of its API with the one of the signal handler, but it
|
||||
doesn't require that all the types of events are specified when declared:
|
||||
|
||||
```cpp
|
||||
// define a general purpose dispatcher
|
||||
entt::dispatcher dispatcher{};
|
||||
```
|
||||
|
||||
In order to register an instance of a class to a dispatcher, its type must
|
||||
expose one or more member functions the arguments of which are such that
|
||||
`const E &` can be converted to them for each type of event `E`, no matter what
|
||||
the return value is.<br/>
|
||||
The name of the member function aimed to receive the event must be provided to
|
||||
the `connect` member function of the sink in charge for the specific event:
|
||||
|
||||
```cpp
|
||||
struct an_event { int value; };
|
||||
struct another_event {};
|
||||
|
||||
struct listener {
|
||||
void receive(const an_event &) { /* ... */ }
|
||||
void method(const another_event &) { /* ... */ }
|
||||
};
|
||||
|
||||
// ...
|
||||
|
||||
listener listener;
|
||||
dispatcher.sink<an_event>().connect<&listener::receive>(listener);
|
||||
dispatcher.sink<another_event>().connect<&listener::method>(listener);
|
||||
```
|
||||
|
||||
The `disconnect` member function follows the same pattern and can be used to
|
||||
remove one listener at a time or all of them at once:
|
||||
|
||||
```cpp
|
||||
dispatcher.sink<an_event>().disconnect<&listener::receive>(listener);
|
||||
dispatcher.sink<another_event>().disconnect(listener);
|
||||
```
|
||||
|
||||
The `trigger` member function serves the purpose of sending an immediate event
|
||||
to all the listeners registered so far. It offers a convenient approach that
|
||||
relieves users from having to create the event itself. Instead, it's enough to
|
||||
specify the type of event and provide all the parameters required to construct
|
||||
it.<br/>
|
||||
As an example:
|
||||
|
||||
```cpp
|
||||
dispatcher.trigger<an_event>(42);
|
||||
dispatcher.trigger<another_event>();
|
||||
```
|
||||
|
||||
Listeners are invoked immediately, order of execution isn't guaranteed. This
|
||||
method can be used to push around urgent messages like an _is terminating_
|
||||
notification on a mobile app.
|
||||
|
||||
On the other hand, the `enqueue` member function queues messages together and
|
||||
allows to maintain control over the moment they are sent to listeners. The
|
||||
signature of this method is more or less the same of `trigger`:
|
||||
|
||||
```cpp
|
||||
dispatcher.enqueue<an_event>(42);
|
||||
dispatcher.enqueue<another_event>();
|
||||
```
|
||||
|
||||
Events are stored aside until the `update` member function is invoked, then all
|
||||
the messages that are still pending are sent to the listeners at once:
|
||||
|
||||
```cpp
|
||||
// emits all the events of the given type at once
|
||||
dispatcher.update<my_event>();
|
||||
|
||||
// emits all the events queued so far at once
|
||||
dispatcher.update();
|
||||
```
|
||||
|
||||
This way users can embed the dispatcher in a loop and literally dispatch events
|
||||
once per tick to their systems.
|
||||
|
||||
# Event emitter
|
||||
|
||||
A general purpose event emitter thought mainly for those cases where it comes to
|
||||
working with asynchronous stuff.<br/>
|
||||
Originally designed to fit the requirements of
|
||||
[`uvw`](https://github.com/skypjack/uvw) (a wrapper for `libuv` written in
|
||||
modern C++), it was adapted later to be included in this library.
|
||||
|
||||
To create a custom emitter type, derived classes must inherit directly from the
|
||||
base class as:
|
||||
|
||||
```cpp
|
||||
struct my_emitter: emitter<my_emitter> {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
The full list of accepted types of events isn't required. Handlers are created
|
||||
internally on the fly and thus each type of event is accepted by default.
|
||||
|
||||
Whenever an event is published, an emitter provides the listeners with a
|
||||
reference to itself along with a const reference to the event. Therefore
|
||||
listeners have an handy way to work with it without incurring in the need of
|
||||
capturing a reference to the emitter itself.<br/>
|
||||
In addition, an opaque object is returned each time a connection is established
|
||||
between an emitter and a listener, allowing the caller to disconnect them at a
|
||||
later time.<br/>
|
||||
The opaque object used to handle connections is both movable and copyable. On
|
||||
the other side, an event emitter is movable but not copyable by default.
|
||||
|
||||
To create new instances of an emitter, no arguments are required:
|
||||
|
||||
```cpp
|
||||
my_emitter emitter{};
|
||||
```
|
||||
|
||||
Listeners must be movable and callable objects (free functions, lambdas,
|
||||
functors, `std::function`s, whatever) whose function type is:
|
||||
|
||||
```cpp
|
||||
void(const Event &, my_emitter &)
|
||||
```
|
||||
|
||||
Where `Event` is the type of event they want to listen.<br/>
|
||||
There are two ways to attach a listener to an event emitter that differ
|
||||
slightly from each other:
|
||||
|
||||
* To register a long-lived listener, use the `on` member function. It is meant
|
||||
to register a listener designed to be invoked more than once for the given
|
||||
event type.<br/>
|
||||
As an example:
|
||||
|
||||
```cpp
|
||||
auto conn = emitter.on<my_event>([](const my_event &event, my_emitter &emitter) {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
The connection object can be freely discarded. Otherwise, it can be used later
|
||||
to disconnect the listener if required.
|
||||
|
||||
* To register a short-lived listener, use the `once` member function. It is
|
||||
meant to register a listener designed to be invoked only once for the given
|
||||
event type. The listener is automatically disconnected after the first
|
||||
invocation.<br/>
|
||||
As an example:
|
||||
|
||||
```cpp
|
||||
auto conn = emitter.once<my_event>([](const my_event &event, my_emitter &emitter) {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
The connection object can be freely discarded. Otherwise, it can be used later
|
||||
to disconnect the listener if required.
|
||||
|
||||
In both cases, the connection object can be used with the `erase` member
|
||||
function:
|
||||
|
||||
```cpp
|
||||
emitter.erase(conn);
|
||||
```
|
||||
|
||||
There are also two member functions to use either to disconnect all the
|
||||
listeners for a given type of event or to clear the emitter:
|
||||
|
||||
```cpp
|
||||
// removes all the listener for the specific event
|
||||
emitter.clear<my_event>();
|
||||
|
||||
// removes all the listeners registered so far
|
||||
emitter.clear();
|
||||
```
|
||||
|
||||
To send an event to all the listeners that are interested in it, the `publish`
|
||||
member function offers a convenient approach that relieves users from having to
|
||||
create the event:
|
||||
|
||||
```cpp
|
||||
struct my_event { int i; };
|
||||
|
||||
// ...
|
||||
|
||||
emitter.publish<my_event>(42);
|
||||
```
|
||||
|
||||
Finally, the `empty` member function tests if there exists at least either a
|
||||
listener registered with the event emitter or to a given type of event:
|
||||
|
||||
```cpp
|
||||
bool empty;
|
||||
|
||||
// checks if there is any listener registered for the specific event
|
||||
empty = emitter.empty<my_event>();
|
||||
|
||||
// checks it there are listeners registered with the event emitter
|
||||
empty = emitter.empty();
|
||||
```
|
||||
|
||||
In general, the event emitter is a handy tool when the derived classes _wrap_
|
||||
asynchronous operations, because it introduces a _nice-to-have_ model based on
|
||||
events and listeners that kindly hides the complexity behind the scenes. However
|
||||
it is not limited to such uses.
|
||||
@@ -1,39 +0,0 @@
|
||||
### EnTT and shared libraries
|
||||
|
||||
To make sure that an application and a shared library that use both `EnTT` can
|
||||
interact correctly when symbols are hidden by default, there are some tricks to
|
||||
follow.<br/>
|
||||
In particular and in order to avoid undefined behaviors, all the instantiation
|
||||
of the `Family` class template shall be made explicit along with the system-wide
|
||||
specifier to use to export them.
|
||||
|
||||
At the time I'm writing this document, the classes that use internally the above
|
||||
mentioned class template are `Dispatcher`, `Emitter` and `Registry`. Therefore
|
||||
and as an example, if you use the `Registry` class template in your shared
|
||||
library and want to set symbols visibility to _hidden_ by default, the following
|
||||
lines are required to allow it to function properly with a client that also uses
|
||||
the `Registry` somehow:
|
||||
|
||||
* On GNU/Linux:
|
||||
|
||||
```cpp
|
||||
namespace entt {
|
||||
template class __attribute__((visibility("default"))) Family<struct InternalRegistryTagFamily>;
|
||||
template class __attribute__((visibility("default"))) Family<struct InternalRegistryComponentFamily>;
|
||||
template class __attribute__((visibility("default"))) Family<struct InternalRegistryHandlerFamily>;
|
||||
}
|
||||
```
|
||||
|
||||
* On Windows:
|
||||
|
||||
```cpp
|
||||
namespace entt {
|
||||
template class __declspec(dllexport) Family<struct InternalRegistryTagFamily>;
|
||||
template class __declspec(dllexport) Family<struct InternalRegistryComponentFamily>;
|
||||
template class __declspec(dllexport) Family<struct InternalRegistryHandlerFamily>;
|
||||
}
|
||||
```
|
||||
|
||||
Otherwise, the risk is that type identifiers are different between the shared
|
||||
library and the application and this will prevent the whole thing from
|
||||
functioning correctly for obvious reasons.
|
||||
412
docs/signal.md
412
docs/signal.md
@@ -1,412 +0,0 @@
|
||||
# Crash Course: events, signals and everything in between
|
||||
|
||||
<!--
|
||||
@cond TURN_OFF_DOXYGEN
|
||||
-->
|
||||
# Table of Contents
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [Signals](#signals)
|
||||
* [Delegate](#delegate)
|
||||
* [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.
|
||||
|
||||
# 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), MyCollector<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 S {
|
||||
void bar(int, char) { /* ... */ }
|
||||
};
|
||||
|
||||
// ...
|
||||
|
||||
S instance;
|
||||
|
||||
signal.sink().connect<&foo>();
|
||||
signal.sink().connect<S, &S::bar>(&instance);
|
||||
|
||||
// ...
|
||||
|
||||
// disconnects a free function
|
||||
signal.sink().disconnect<&foo>();
|
||||
|
||||
// disconnect a specific member function of an instance ...
|
||||
signal.sink().disconnect<S, &S::bar>(&instance);
|
||||
|
||||
// ... or an instance as a whole
|
||||
signal.sink().disconnect(&instance);
|
||||
|
||||
// discards all the listeners at once
|
||||
signal.sink().disconnect();
|
||||
```
|
||||
|
||||
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 MyCollector {
|
||||
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(), MyCollector<int>> signal;
|
||||
|
||||
signal.sink().connect<&f>();
|
||||
signal.sink().connect<&g>();
|
||||
|
||||
MyCollector collector = signal.collect();
|
||||
|
||||
assert(collector.vec[0] == 0);
|
||||
assert(collector.vec[1] == 1);
|
||||
```
|
||||
|
||||
As shown above, 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.
|
||||
|
||||
# Delegate
|
||||
|
||||
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.<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, most likely a crash actually. Before to use a
|
||||
delegate, it must be initialized.<br/>
|
||||
There exist two functions to do that, both named `connect`:
|
||||
|
||||
```cpp
|
||||
int f(int i) { return i; }
|
||||
|
||||
struct MyStruct {
|
||||
int f(int i) { return i }
|
||||
};
|
||||
|
||||
// bind a free function to the delegate
|
||||
delegate.connect<&f>();
|
||||
|
||||
// bind a member function to the delegate
|
||||
MyStruct instance;
|
||||
delegate.connect<MyStruct, &MyStruct::f>(&instance);
|
||||
```
|
||||
|
||||
It hasn't a `disconnect` counterpart. Instead, there exists a `reset` member
|
||||
function to clear it.<br/>
|
||||
The `empty` member function can be used to know if a delegate is empty:
|
||||
|
||||
```cpp
|
||||
const auto empty = delegate.empty();
|
||||
```
|
||||
|
||||
Finally, to invoke a delegate, the function call operator is the way to go as
|
||||
usual:
|
||||
|
||||
```cpp
|
||||
auto ret = delegate(42);
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
# 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 of which the return types are `void` and the
|
||||
argument lists are `const E &`, for each type of event `E`.<br/>
|
||||
To ease the development, member functions that are named `receive` are
|
||||
automatically detected and have not to be explicitly specified when registered.
|
||||
In all the other cases, the name of the member function aimed to receive the
|
||||
event must be provided to the `connect` member function of the sink bound to the
|
||||
specific event:
|
||||
|
||||
```cpp
|
||||
struct AnEvent { int value; };
|
||||
struct AnotherEvent {};
|
||||
|
||||
struct Listener
|
||||
{
|
||||
void receive(const AnEvent &) { /* ... */ }
|
||||
void method(const AnotherEvent &) { /* ... */ }
|
||||
};
|
||||
|
||||
// ...
|
||||
|
||||
Listener listener;
|
||||
dispatcher.sink<AnEvent>().connect(&listener);
|
||||
dispatcher.sink<AnotherEvent>().connect<Listener, &Listener::method>(&listener);
|
||||
```
|
||||
|
||||
The `disconnect` member function follows the same pattern and can be used to
|
||||
selectively remove listeners:
|
||||
|
||||
```cpp
|
||||
dispatcher.sink<AnEvent>().disconnect(&listener);
|
||||
dispatcher.sink<AnotherEvent>().disconnect<Listener, &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<AnEvent>(42);
|
||||
dispatcher.trigger<AnotherEvent>();
|
||||
```
|
||||
|
||||
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<AnEvent>(42);
|
||||
dispatcher.enqueue<AnotherEvent>();
|
||||
```
|
||||
|
||||
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<MyEvent>();
|
||||
|
||||
// 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 MyEmitter: Emitter<MyEmitter> {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
MyEmitter emitter{};
|
||||
```
|
||||
|
||||
Listeners must be movable and callable objects (free functions, lambdas,
|
||||
functors, `std::function`s, whatever) whose function type is:
|
||||
|
||||
```cpp
|
||||
void(const Event &, MyEmitter &)
|
||||
```
|
||||
|
||||
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<MyEvent>([](const MyEvent &event, MyEmitter &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<MyEvent>([](const MyEvent &event, MyEmitter &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<MyEvent>();
|
||||
|
||||
// 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 MyEvent { int i; };
|
||||
|
||||
// ...
|
||||
|
||||
emitter.publish<MyEvent>(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<MyEvent>();
|
||||
|
||||
// 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"]
|
||||
}
|
||||
@@ -41,14 +41,14 @@ echo "Sedding..."
|
||||
|
||||
# change the url in the formula file
|
||||
# the slashes in the URL must be escaped
|
||||
ESCAPED_URL="$(sed -e 's/[\/&]/\\&/g' <<< "$URL")"
|
||||
ESCAPED_URL="$(echo "$URL" | sed -e 's/[\/&]/\\&/g')"
|
||||
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"
|
||||
rm -rf "$FORMULA-e"
|
||||
|
||||
# update remote repo
|
||||
echo "Gitting..."
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
scripts/update_homebrew.sh $1
|
||||
17743
single_include/entt/entt.hpp
Normal file
17743
single_include/entt/entt.hpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,14 +3,65 @@
|
||||
|
||||
|
||||
#ifndef ENTT_NOEXCEPT
|
||||
#define ENTT_NOEXCEPT noexcept
|
||||
#endif // ENTT_NOEXCEPT
|
||||
# define ENTT_NOEXCEPT noexcept
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef ENTT_HS_SUFFIX
|
||||
#define ENTT_HS_SUFFIX _hs
|
||||
#endif // ENTT_HS_SUFFIX
|
||||
# define ENTT_HS_SUFFIX _hs
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef ENTT_HWS_SUFFIX
|
||||
# define ENTT_HWS_SUFFIX _hws
|
||||
#endif
|
||||
|
||||
#endif // ENTT_CONFIG_CONFIG_H
|
||||
|
||||
#ifndef ENTT_USE_ATOMIC
|
||||
# define ENTT_MAYBE_ATOMIC(Type) Type
|
||||
#else
|
||||
# include <atomic>
|
||||
# define ENTT_MAYBE_ATOMIC(Type) std::atomic<Type>
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef ENTT_ID_TYPE
|
||||
# include <cstdint>
|
||||
# define ENTT_ID_TYPE std::uint32_t
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef ENTT_PAGE_SIZE
|
||||
# define ENTT_PAGE_SIZE 32768
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef ENTT_ASSERT
|
||||
# include <cassert>
|
||||
# define ENTT_ASSERT(condition) assert(condition)
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef ENTT_DISABLE_ETO
|
||||
# include <type_traits>
|
||||
# define ENTT_ENABLE_ETO(Type) (std::is_default_constructible_v<Type> && std::is_empty_v<Type>)
|
||||
#else
|
||||
# // sfinae-friendly definition
|
||||
# define ENTT_ENABLE_ETO(Type) (false && std::is_void_v<Type>)
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef ENTT_STANDARD_CPP
|
||||
# if defined _MSC_VER
|
||||
# define ENTT_PRETTY_FUNCTION __FUNCSIG__
|
||||
# define ENTT_PRETTY_FUNCTION_CONSTEXPR ENTT_PRETTY_FUNCTION
|
||||
# elif defined __clang__ || (defined __GNUC__ && __GNUC__ > 8)
|
||||
# define ENTT_PRETTY_FUNCTION __PRETTY_FUNCTION__
|
||||
# define ENTT_PRETTY_FUNCTION_CONSTEXPR ENTT_PRETTY_FUNCTION
|
||||
# elif defined __GNUC__
|
||||
# define ENTT_PRETTY_FUNCTION __PRETTY_FUNCTION__
|
||||
# endif
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
10
src/entt/config/version.h
Normal file
10
src/entt/config/version.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#ifndef ENTT_CONFIG_VERSION_H
|
||||
#define ENTT_CONFIG_VERSION_H
|
||||
|
||||
|
||||
#define ENTT_VERSION_MAJOR 3
|
||||
#define ENTT_VERSION_MINOR 3
|
||||
#define ENTT_VERSION_PATCH 1
|
||||
|
||||
|
||||
#endif
|
||||
@@ -2,9 +2,12 @@
|
||||
#define ENTT_CORE_ALGORITHM_HPP
|
||||
|
||||
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include "utility.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
@@ -18,7 +21,7 @@ namespace entt {
|
||||
* This class fills the gap by wrapping some flavors of `std::sort` in a
|
||||
* function object.
|
||||
*/
|
||||
struct StdSort final {
|
||||
struct std_sort {
|
||||
/**
|
||||
* @brief Sorts the elements in a range.
|
||||
*
|
||||
@@ -40,7 +43,7 @@ struct StdSort final {
|
||||
|
||||
|
||||
/*! @brief Function object for performing insertion sort. */
|
||||
struct InsertionSort final {
|
||||
struct insertion_sort {
|
||||
/**
|
||||
* @brief Sorts the elements in a range.
|
||||
*
|
||||
@@ -54,51 +57,81 @@ struct InsertionSort final {
|
||||
*/
|
||||
template<typename It, typename Compare = std::less<>>
|
||||
void operator()(It first, It last, Compare compare = Compare{}) const {
|
||||
auto it = first + 1;
|
||||
if(first < last) {
|
||||
for(auto it = first+1; it < last; ++it) {
|
||||
auto value = std::move(*it);
|
||||
auto pre = it;
|
||||
|
||||
while(it != last) {
|
||||
auto value = *it;
|
||||
auto pre = it;
|
||||
for(; pre > first && compare(value, *(pre-1)); --pre) {
|
||||
*pre = std::move(*(pre-1));
|
||||
}
|
||||
|
||||
while(pre != first && compare(value, *(pre-1))) {
|
||||
*pre = *(pre-1);
|
||||
--pre;
|
||||
*pre = std::move(value);
|
||||
}
|
||||
|
||||
*pre = value;
|
||||
++it;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*! @brief Function object for performing bubble sort (single iteration). */
|
||||
struct OneShotBubbleSort final {
|
||||
/**
|
||||
* @brief Function object for performing LSD radix sort.
|
||||
* @tparam Bit Number of bits processed per pass.
|
||||
* @tparam N Maximum number of bits to sort.
|
||||
*/
|
||||
template<std::size_t Bit, std::size_t N>
|
||||
struct radix_sort {
|
||||
static_assert((N % Bit) == 0);
|
||||
|
||||
/**
|
||||
* @brief Tries to sort the elements in a range.
|
||||
* @brief Sorts the elements in a range.
|
||||
*
|
||||
* Performs a single iteration to sort the elements in a range using the
|
||||
* given binary comparison function. The range may not be completely sorted
|
||||
* after running this function.
|
||||
* Sorts the elements in a range using the given _getter_ to access the
|
||||
* actual data to be sorted.
|
||||
*
|
||||
* This implementation is inspired by the online book
|
||||
* [Physically Based Rendering](http://www.pbr-book.org/3ed-2018/Primitives_and_Intersection_Acceleration/Bounding_Volume_Hierarchies.html#RadixSort).
|
||||
*
|
||||
* @tparam It Type of random access iterator.
|
||||
* @tparam Compare Type of comparison function object.
|
||||
* @tparam Getter Type of _getter_ function object.
|
||||
* @param first An iterator to the first element of the range to sort.
|
||||
* @param last An iterator past the last element of the range to sort.
|
||||
* @param compare A valid comparison function object.
|
||||
* @param getter A valid _getter_ function object.
|
||||
*/
|
||||
template<typename It, typename Compare = std::less<>>
|
||||
void operator()(It first, It last, Compare compare = Compare{}) const {
|
||||
if(first != last) {
|
||||
auto it = first++;
|
||||
template<typename It, typename Getter = identity>
|
||||
void operator()(It first, It last, Getter getter = Getter{}) const {
|
||||
if(first < last) {
|
||||
static constexpr auto mask = (1 << Bit) - 1;
|
||||
static constexpr auto buckets = 1 << Bit;
|
||||
static constexpr auto passes = N / Bit;
|
||||
|
||||
while(first != last) {
|
||||
if(compare(*first, *it)) {
|
||||
using std::swap;
|
||||
std::swap(*first, *it);
|
||||
}
|
||||
using value_type = typename std::iterator_traits<It>::value_type;
|
||||
std::vector<value_type> aux(std::distance(first, last));
|
||||
|
||||
it = first++;
|
||||
auto part = [getter = std::move(getter)](auto from, auto to, auto out, auto start) {
|
||||
std::size_t index[buckets]{};
|
||||
std::size_t count[buckets]{};
|
||||
|
||||
std::for_each(from, to, [&getter, &count, start](const value_type &item) {
|
||||
++count[(getter(item) >> start) & mask];
|
||||
});
|
||||
|
||||
std::for_each(std::next(std::begin(index)), std::end(index), [index = std::begin(index), count = std::begin(count)](auto &item) mutable {
|
||||
item = *(index++) + *(count++);
|
||||
});
|
||||
|
||||
std::for_each(from, to, [&getter, &out, &index, start](value_type &item) {
|
||||
out[index[(getter(item) >> start) & mask]++] = std::move(item);
|
||||
});
|
||||
};
|
||||
|
||||
for(std::size_t pass = 0; pass < (passes & ~1); pass += 2) {
|
||||
part(first, last, aux.begin(), pass * Bit);
|
||||
part(aux.begin(), aux.end(), first, (pass + 1) * Bit);
|
||||
}
|
||||
|
||||
if constexpr(passes & 1) {
|
||||
part(first, last, aux.begin(), (passes - 1) * Bit);
|
||||
std::move(aux.begin(), aux.end(), first);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,4 +141,4 @@ struct OneShotBubbleSort final {
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_CORE_ALGORITHM_HPP
|
||||
#endif
|
||||
|
||||
33
src/entt/core/attribute.h
Normal file
33
src/entt/core/attribute.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef ENTT_CORE_ATTRIBUTE_H
|
||||
#define ENTT_CORE_ATTRIBUTE_H
|
||||
|
||||
|
||||
#ifndef ENTT_EXPORT
|
||||
# if defined _WIN32 || defined __CYGWIN__ || defined _MSC_VER
|
||||
# define ENTT_EXPORT __declspec(dllexport)
|
||||
# define ENTT_IMPORT __declspec(dllimport)
|
||||
# define ENTT_HIDDEN
|
||||
# elif defined __GNUC__ && __GNUC__ >= 4
|
||||
# define ENTT_EXPORT __attribute__((visibility("default")))
|
||||
# define ENTT_IMPORT __attribute__((visibility("default")))
|
||||
# define ENTT_HIDDEN __attribute__((visibility("hidden")))
|
||||
# else /* Unsupported compiler */
|
||||
# define ENTT_EXPORT
|
||||
# define ENTT_IMPORT
|
||||
# define ENTT_HIDDEN
|
||||
# endif
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef ENTT_API
|
||||
# if defined ENTT_API_EXPORT
|
||||
# define ENTT_API ENTT_EXPORT
|
||||
# elif defined ENTT_API_IMPORT
|
||||
# define ENTT_API ENTT_IMPORT
|
||||
# else /* No API */
|
||||
# define ENTT_API
|
||||
# endif
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
||||
@@ -2,9 +2,6 @@
|
||||
#define ENTT_CORE_FAMILY_HPP
|
||||
|
||||
|
||||
#include <type_traits>
|
||||
#include <cstddef>
|
||||
#include <atomic>
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
@@ -19,35 +16,21 @@ namespace entt {
|
||||
* identifiers.
|
||||
*/
|
||||
template<typename...>
|
||||
class Family {
|
||||
static std::atomic<std::size_t> identifier;
|
||||
|
||||
template<typename...>
|
||||
static std::size_t family() ENTT_NOEXCEPT {
|
||||
static const std::size_t value = identifier.fetch_add(1);
|
||||
return value;
|
||||
}
|
||||
class family {
|
||||
inline static ENTT_MAYBE_ATOMIC(ENTT_ID_TYPE) identifier{};
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using family_type = std::size_t;
|
||||
using family_type = ENTT_ID_TYPE;
|
||||
|
||||
/**
|
||||
* @brief Returns an unique identifier for the given type.
|
||||
* @return Statically generated unique identifier for the given type.
|
||||
*/
|
||||
/*! @brief Statically generated unique identifier for the given type. */
|
||||
template<typename... Type>
|
||||
inline static family_type type() ENTT_NOEXCEPT {
|
||||
return family<std::decay_t<Type>...>();
|
||||
}
|
||||
// at the time I'm writing, clang crashes during compilation if auto is used instead of family_type
|
||||
inline static const family_type type = identifier++;
|
||||
};
|
||||
|
||||
|
||||
template<typename... Types>
|
||||
std::atomic<std::size_t> Family<Types...>::identifier{};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_CORE_FAMILY_HPP
|
||||
#endif
|
||||
|
||||
@@ -11,100 +11,231 @@ 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> {
|
||||
using type = std::uint32_t;
|
||||
static constexpr std::uint32_t offset = 2166136261;
|
||||
static constexpr std::uint32_t prime = 16777619;
|
||||
};
|
||||
|
||||
|
||||
template<>
|
||||
struct fnv1a_traits<std::uint64_t> {
|
||||
using type = std::uint64_t;
|
||||
static constexpr std::uint64_t offset = 14695981039346656037ull;
|
||||
static constexpr std::uint64_t prime = 1099511628211ull;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Internal details not to be documented.
|
||||
* @endcond TURN_OFF_DOXYGEN
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @brief Zero overhead unique identifier.
|
||||
*
|
||||
* A hashed string is a compile-time tool that allows users to use
|
||||
* human-readable identifers in the codebase while using their numeric
|
||||
* counterparts at runtime.<br/>
|
||||
* Because of that, a hashed string can also be used in constant expressions if
|
||||
* required.
|
||||
*
|
||||
* @tparam Char Character type.
|
||||
*/
|
||||
class HashedString final {
|
||||
struct ConstCharWrapper final {
|
||||
template<typename Char>
|
||||
class basic_hashed_string {
|
||||
using traits_type = internal::fnv1a_traits<ENTT_ID_TYPE>;
|
||||
|
||||
struct const_wrapper {
|
||||
// non-explicit constructor on purpose
|
||||
constexpr ConstCharWrapper(const char *str) ENTT_NOEXCEPT: str{str} {}
|
||||
const char *str;
|
||||
constexpr const_wrapper(const Char *curr) ENTT_NOEXCEPT: str{curr} {}
|
||||
const Char *str;
|
||||
};
|
||||
|
||||
static constexpr std::uint64_t offset = 14695981039346656037ull;
|
||||
static constexpr std::uint64_t prime = 1099511628211ull;
|
||||
|
||||
// Fowler–Noll–Vo hash function v. 1a - the good
|
||||
static constexpr std::uint64_t helper(std::uint64_t partial, const char *str) ENTT_NOEXCEPT {
|
||||
return str[0] == 0 ? partial : helper((partial^str[0])*prime, str+1);
|
||||
static constexpr ENTT_ID_TYPE helper(const Char *curr) ENTT_NOEXCEPT {
|
||||
auto value = traits_type::offset;
|
||||
|
||||
while(*curr != 0) {
|
||||
value = (value ^ static_cast<traits_type::type>(*(curr++))) * traits_type::prime;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Character type. */
|
||||
using value_type = Char;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using hash_type = std::uint64_t;
|
||||
using hash_type = ENTT_ID_TYPE;
|
||||
|
||||
/**
|
||||
* @brief Constructs a hashed string from an array of const chars.
|
||||
* @brief Returns directly the numeric representation of a string.
|
||||
*
|
||||
* Forcing template resolution avoids implicit conversions. An
|
||||
* human-readable identifier can be anything but a plain, old bunch of
|
||||
* characters.<br/>
|
||||
* Example of use:
|
||||
* @code{.cpp}
|
||||
* HashedString sh{"my.png"};
|
||||
* const auto value = basic_hashed_string<char>::to_value("my.png");
|
||||
* @endcode
|
||||
*
|
||||
* @tparam N Number of characters of the identifier.
|
||||
* @param str Human-readable identifer.
|
||||
* @return The numeric representation of the string.
|
||||
*/
|
||||
template<std::size_t N>
|
||||
constexpr HashedString(const char (&str)[N]) ENTT_NOEXCEPT
|
||||
: hash{helper(offset, str)}, str{str}
|
||||
static constexpr hash_type value(const value_type (&str)[N]) ENTT_NOEXCEPT {
|
||||
return helper(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns directly the numeric representation of a string.
|
||||
* @param wrapper Helps achieving the purpose by relying on overloading.
|
||||
* @return The numeric representation of the string.
|
||||
*/
|
||||
static hash_type value(const_wrapper wrapper) ENTT_NOEXCEPT {
|
||||
return helper(wrapper.str);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns directly the numeric representation of a string view.
|
||||
* @param str Human-readable identifer.
|
||||
* @param size Length of the string to hash.
|
||||
* @return The numeric representation of the string.
|
||||
*/
|
||||
static hash_type value(const value_type *str, std::size_t size) ENTT_NOEXCEPT {
|
||||
ENTT_ID_TYPE partial{traits_type::offset};
|
||||
while(size--) { partial = (partial^(str++)[0])*traits_type::prime; }
|
||||
return partial;
|
||||
}
|
||||
|
||||
/*! @brief Constructs an empty hashed string. */
|
||||
constexpr basic_hashed_string() ENTT_NOEXCEPT
|
||||
: str{nullptr}, hash{}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Constructs a hashed string from an array of const characters.
|
||||
*
|
||||
* Forcing template resolution avoids implicit conversions. An
|
||||
* human-readable identifier can be anything but a plain, old bunch of
|
||||
* characters.<br/>
|
||||
* Example of use:
|
||||
* @code{.cpp}
|
||||
* basic_hashed_string<char> hs{"my.png"};
|
||||
* @endcode
|
||||
*
|
||||
* @tparam N Number of characters of the identifier.
|
||||
* @param curr Human-readable identifer.
|
||||
*/
|
||||
template<std::size_t N>
|
||||
constexpr basic_hashed_string(const value_type (&curr)[N]) ENTT_NOEXCEPT
|
||||
: str{curr}, hash{helper(curr)}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Explicit constructor on purpose to avoid constructing a hashed
|
||||
* string directly from a `const char *`.
|
||||
*
|
||||
* string directly from a `const value_type *`.
|
||||
* @param wrapper Helps achieving the purpose by relying on overloading.
|
||||
*/
|
||||
explicit constexpr HashedString(ConstCharWrapper wrapper) ENTT_NOEXCEPT
|
||||
: hash{helper(offset, wrapper.str)}, str{wrapper.str}
|
||||
explicit constexpr basic_hashed_string(const_wrapper wrapper) ENTT_NOEXCEPT
|
||||
: str{wrapper.str}, hash{helper(wrapper.str)}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Returns the human-readable representation of a hashed string.
|
||||
* @return The string used to initialize the instance.
|
||||
*/
|
||||
constexpr operator const char *() const ENTT_NOEXCEPT { return str; }
|
||||
constexpr const value_type * data() const ENTT_NOEXCEPT {
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the numeric representation of a hashed string.
|
||||
* @return The numeric representation of the instance.
|
||||
*/
|
||||
constexpr operator hash_type() const ENTT_NOEXCEPT { return hash; }
|
||||
constexpr hash_type value() const ENTT_NOEXCEPT {
|
||||
return hash;
|
||||
}
|
||||
|
||||
/*! @copydoc data */
|
||||
constexpr operator const value_type *() const ENTT_NOEXCEPT { return data(); }
|
||||
|
||||
/**
|
||||
* @brief Returns the numeric representation of a hashed string.
|
||||
* @return The numeric representation of the instance.
|
||||
*/
|
||||
constexpr operator hash_type() const ENTT_NOEXCEPT { return value(); }
|
||||
|
||||
/**
|
||||
* @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 ENTT_NOEXCEPT {
|
||||
constexpr bool operator==(const basic_hashed_string &other) const ENTT_NOEXCEPT {
|
||||
return hash == other.hash;
|
||||
}
|
||||
|
||||
private:
|
||||
const hash_type hash;
|
||||
const char *str;
|
||||
const value_type *str;
|
||||
hash_type hash;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Deduction guide.
|
||||
*
|
||||
* It allows to deduce the character type of the hashed string directly from a
|
||||
* human-readable identifer provided to the constructor.
|
||||
*
|
||||
* @tparam Char Character type.
|
||||
* @tparam N Number of characters of the identifier.
|
||||
* @param str Human-readable identifer.
|
||||
*/
|
||||
template<typename Char, std::size_t N>
|
||||
basic_hashed_string(const Char (&str)[N]) ENTT_NOEXCEPT
|
||||
-> basic_hashed_string<Char>;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Compares two hashed strings.
|
||||
* @tparam Char Character type.
|
||||
* @param lhs A valid hashed string.
|
||||
* @param rhs A valid hashed string.
|
||||
* @return True if the two hashed strings are identical, false otherwise.
|
||||
*/
|
||||
constexpr bool operator!=(const HashedString &lhs, const HashedString &rhs) ENTT_NOEXCEPT {
|
||||
template<typename Char>
|
||||
constexpr bool operator!=(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) ENTT_NOEXCEPT {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
|
||||
/*! @brief Aliases for common character types. */
|
||||
using hashed_string = basic_hashed_string<char>;
|
||||
|
||||
|
||||
/*! @brief Aliases for common character types. */
|
||||
using hashed_wstring = basic_hashed_string<wchar_t>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -113,9 +244,19 @@ constexpr bool operator!=(const HashedString &lhs, const HashedString &rhs) ENTT
|
||||
* @param str The literal without its suffix.
|
||||
* @return A properly initialized hashed string.
|
||||
*/
|
||||
constexpr entt::HashedString operator"" ENTT_HS_SUFFIX(const char *str, std::size_t) ENTT_NOEXCEPT {
|
||||
return entt::HashedString{str};
|
||||
constexpr entt::hashed_string operator"" ENTT_HS_SUFFIX(const char *str, std::size_t) ENTT_NOEXCEPT {
|
||||
return entt::hashed_string{str};
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_CORE_HASHED_STRING_HPP
|
||||
/**
|
||||
* @brief User defined literal for hashed wstrings.
|
||||
* @param str The literal without its suffix.
|
||||
* @return A properly initialized hashed wstring.
|
||||
*/
|
||||
constexpr entt::hashed_wstring operator"" ENTT_HWS_SUFFIX(const wchar_t *str, std::size_t) ENTT_NOEXCEPT {
|
||||
return entt::hashed_wstring{str};
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -2,44 +2,16 @@
|
||||
#define ENTT_CORE_IDENT_HPP
|
||||
|
||||
|
||||
#include <type_traits>
|
||||
#include <tuple>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @cond TURN_OFF_DOXYGEN
|
||||
* Internal details not to be documented.
|
||||
*/
|
||||
|
||||
|
||||
namespace internal {
|
||||
|
||||
|
||||
template<typename...>
|
||||
struct IsPartOf;
|
||||
|
||||
template<typename Type, typename Current, typename... Other>
|
||||
struct IsPartOf<Type, Current, Other...>: std::conditional_t<std::is_same<Type, Current>::value, std::true_type, IsPartOf<Type, Other...>> {};
|
||||
|
||||
template<typename Type>
|
||||
struct IsPartOf<Type>: std::false_type {};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Internal details not to be documented.
|
||||
* @endcond TURN_OFF_DOXYGEN
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @brief Types identifiers.
|
||||
*
|
||||
@@ -51,13 +23,13 @@ struct IsPartOf<Type>: std::false_type {};
|
||||
* Identifiers are constant expression and can be used in any context where such
|
||||
* an expression is required. As an example:
|
||||
* @code{.cpp}
|
||||
* using ID = entt::Identifier<AType, AnotherType>;
|
||||
* using id = entt::identifier<a_type, another_type>;
|
||||
*
|
||||
* switch(aTypeIdentifier) {
|
||||
* case ID::get<AType>():
|
||||
* switch(a_type_identifier) {
|
||||
* case id::type<a_type>:
|
||||
* // ...
|
||||
* break;
|
||||
* case ID::get<AnotherType>():
|
||||
* case id::type<another_type>:
|
||||
* // ...
|
||||
* break;
|
||||
* default:
|
||||
@@ -68,37 +40,26 @@ struct IsPartOf<Type>: std::false_type {};
|
||||
* @tparam Types List of types for which to generate identifiers.
|
||||
*/
|
||||
template<typename... Types>
|
||||
class Identifier final {
|
||||
class identifier {
|
||||
using tuple_type = std::tuple<std::decay_t<Types>...>;
|
||||
|
||||
template<typename Type, std::size_t... Indexes>
|
||||
static constexpr std::size_t get(std::index_sequence<Indexes...>) ENTT_NOEXCEPT {
|
||||
static_assert(internal::IsPartOf<Type, Types...>::value, "!");
|
||||
|
||||
std::size_t max{};
|
||||
using accumulator_type = std::size_t[];
|
||||
accumulator_type accumulator = { (max = std::is_same<Type, std::tuple_element_t<Indexes, tuple_type>>::value ? Indexes : max)... };
|
||||
(void)accumulator;
|
||||
return max;
|
||||
static constexpr ENTT_ID_TYPE get(std::index_sequence<Indexes...>) {
|
||||
static_assert(std::disjunction_v<std::is_same<Type, Types>...>);
|
||||
return (0 + ... + (std::is_same_v<Type, std::tuple_element_t<Indexes, tuple_type>> ? ENTT_ID_TYPE(Indexes) : ENTT_ID_TYPE{}));
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using identifier_type = std::size_t;
|
||||
using identifier_type = ENTT_ID_TYPE;
|
||||
|
||||
/**
|
||||
* @brief Returns the identifier associated with a given type.
|
||||
* @tparam Type of which to return the identifier.
|
||||
* @return The identifier associated with the given type.
|
||||
*/
|
||||
/*! @brief Statically generated unique identifier for the given type. */
|
||||
template<typename Type>
|
||||
static constexpr identifier_type get() ENTT_NOEXCEPT {
|
||||
return get<std::decay_t<Type>>(std::make_index_sequence<sizeof...(Types)>{});
|
||||
}
|
||||
static constexpr identifier_type type = get<std::decay_t<Type>>(std::index_sequence_for<Types...>{});
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_CORE_IDENT_HPP
|
||||
#endif
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
#define ENTT_CORE_MONOSTATE_HPP
|
||||
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include "family.hpp"
|
||||
#include "hashed_string.hpp"
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
namespace entt {
|
||||
@@ -22,8 +19,8 @@ namespace entt {
|
||||
* both during an assignment and when they try to read back their data.
|
||||
* Otherwise, they can incur in unexpected results.
|
||||
*/
|
||||
template<HashedString::hash_type>
|
||||
struct Monostate {
|
||||
template<ENTT_ID_TYPE>
|
||||
struct monostate {
|
||||
/**
|
||||
* @brief Assigns a value of a specific type to a given key.
|
||||
* @tparam Type Type of the value to assign.
|
||||
@@ -31,7 +28,7 @@ struct Monostate {
|
||||
*/
|
||||
template<typename Type>
|
||||
void operator=(Type val) const ENTT_NOEXCEPT {
|
||||
Monostate::value<Type> = val;
|
||||
value<Type> = val;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,21 +38,24 @@ struct Monostate {
|
||||
*/
|
||||
template<typename Type>
|
||||
operator Type() const ENTT_NOEXCEPT {
|
||||
return Monostate::value<Type>;
|
||||
return value<Type>;
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename Type>
|
||||
static std::atomic<Type> value;
|
||||
inline static ENTT_MAYBE_ATOMIC(Type) value{};
|
||||
};
|
||||
|
||||
|
||||
template<HashedString::hash_type ID>
|
||||
template<typename Type>
|
||||
std::atomic<Type> Monostate<ID>::value{};
|
||||
/**
|
||||
* @brief Helper variable template.
|
||||
* @tparam Value Value used to differentiate between different variables.
|
||||
*/
|
||||
template<ENTT_ID_TYPE Value>
|
||||
inline monostate<Value> monostate_v = {};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_CORE_MONOSTATE_HPP
|
||||
#endif
|
||||
|
||||
80
src/entt/core/type_info.hpp
Normal file
80
src/entt/core/type_info.hpp
Normal file
@@ -0,0 +1,80 @@
|
||||
#ifndef ENTT_CORE_TYPE_INFO_HPP
|
||||
#define ENTT_CORE_TYPE_INFO_HPP
|
||||
|
||||
|
||||
#include "../config/config.h"
|
||||
#include "../core/attribute.h"
|
||||
#include "hashed_string.hpp"
|
||||
|
||||
|
||||
#ifndef ENTT_PRETTY_FUNCTION
|
||||
# define ENTT_TYPE_ID_API ENTT_API
|
||||
#else
|
||||
# define ENTT_TYPE_ID_API
|
||||
#endif
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
#ifndef ENTT_PRETTY_FUNCTION
|
||||
/**
|
||||
* @cond TURN_OFF_DOXYGEN
|
||||
* Internal details not to be documented.
|
||||
*/
|
||||
|
||||
|
||||
namespace internal {
|
||||
|
||||
|
||||
struct ENTT_API type_id_generator {
|
||||
static ENTT_ID_TYPE next() ENTT_NOEXCEPT {
|
||||
static ENTT_ID_TYPE value{};
|
||||
return value++;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Internal details not to be documented.
|
||||
* @endcond TURN_OFF_DOXYGEN
|
||||
*/
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* @brief Types identifiers.
|
||||
* @tparam Type Type for which to generate an identifier.
|
||||
*/
|
||||
template<typename Type, typename = void>
|
||||
struct ENTT_TYPE_ID_API type_info {
|
||||
/**
|
||||
* @brief Returns the numeric representation of a given type.
|
||||
* @return The numeric representation of the given type.
|
||||
*/
|
||||
#if defined ENTT_PRETTY_FUNCTION_CONSTEXPR
|
||||
static constexpr ENTT_ID_TYPE id() ENTT_NOEXCEPT {
|
||||
constexpr auto value = entt::hashed_string::value(ENTT_PRETTY_FUNCTION_CONSTEXPR);
|
||||
return value;
|
||||
}
|
||||
#elif defined ENTT_PRETTY_FUNCTION
|
||||
static ENTT_ID_TYPE id() ENTT_NOEXCEPT {
|
||||
static const auto value = entt::hashed_string::value(ENTT_PRETTY_FUNCTION);
|
||||
return value;
|
||||
}
|
||||
#else
|
||||
static ENTT_ID_TYPE id() ENTT_NOEXCEPT {
|
||||
static const ENTT_ID_TYPE value = internal::type_id_generator::next();
|
||||
return value;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
229
src/entt/core/type_traits.hpp
Normal file
229
src/entt/core/type_traits.hpp
Normal file
@@ -0,0 +1,229 @@
|
||||
#ifndef ENTT_CORE_TYPE_TRAITS_HPP
|
||||
#define ENTT_CORE_TYPE_TRAITS_HPP
|
||||
|
||||
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "../core/hashed_string.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Utility class to disambiguate overloaded functions.
|
||||
* @tparam N Number of choices available.
|
||||
*/
|
||||
template<std::size_t N>
|
||||
struct choice_t
|
||||
// Unfortunately, doxygen cannot parse such a construct.
|
||||
/*! @cond TURN_OFF_DOXYGEN */
|
||||
: choice_t<N-1>
|
||||
/*! @endcond TURN_OFF_DOXYGEN */
|
||||
{};
|
||||
|
||||
|
||||
/*! @copybrief choice_t */
|
||||
template<>
|
||||
struct choice_t<0> {};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Variable template for the choice trick.
|
||||
* @tparam N Number of choices available.
|
||||
*/
|
||||
template<std::size_t N>
|
||||
constexpr choice_t<N> choice{};
|
||||
|
||||
|
||||
/*! @brief A class to use to push around lists of types, nothing more. */
|
||||
template<typename...>
|
||||
struct type_list {};
|
||||
|
||||
|
||||
/*! @brief Primary template isn't defined on purpose. */
|
||||
template<typename>
|
||||
struct type_list_size;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Compile-time number of elements in a type list.
|
||||
* @tparam Type Types provided by the type list.
|
||||
*/
|
||||
template<typename... Type>
|
||||
struct type_list_size<type_list<Type...>>
|
||||
: std::integral_constant<std::size_t, sizeof...(Type)>
|
||||
{};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Helper variable template.
|
||||
* @tparam List Type list.
|
||||
*/
|
||||
template<class List>
|
||||
constexpr auto type_list_size_v = type_list_size<List>::value;
|
||||
|
||||
|
||||
/*! @brief Primary template isn't defined on purpose. */
|
||||
template<typename...>
|
||||
struct type_list_cat;
|
||||
|
||||
|
||||
/*! @brief Concatenates multiple type lists. */
|
||||
template<>
|
||||
struct type_list_cat<> {
|
||||
/*! @brief A type list composed by the types of all the type lists. */
|
||||
using type = type_list<>;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Concatenates multiple type lists.
|
||||
* @tparam Type Types provided by the first type list.
|
||||
* @tparam Other Types provided by the second type list.
|
||||
* @tparam List Other type lists, if any.
|
||||
*/
|
||||
template<typename... Type, typename... Other, typename... List>
|
||||
struct type_list_cat<type_list<Type...>, type_list<Other...>, List...> {
|
||||
/*! @brief A type list composed by the types of all the type lists. */
|
||||
using type = typename type_list_cat<type_list<Type..., Other...>, List...>::type;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Concatenates multiple type lists.
|
||||
* @tparam Type Types provided by the type list.
|
||||
*/
|
||||
template<typename... Type>
|
||||
struct type_list_cat<type_list<Type...>> {
|
||||
/*! @brief A type list composed by the types of all the type lists. */
|
||||
using type = type_list<Type...>;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Helper type.
|
||||
* @tparam List Type lists to concatenate.
|
||||
*/
|
||||
template<typename... List>
|
||||
using type_list_cat_t = typename type_list_cat<List...>::type;
|
||||
|
||||
|
||||
/*! @brief Primary template isn't defined on purpose. */
|
||||
template<typename>
|
||||
struct type_list_unique;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Removes duplicates types from a type list.
|
||||
* @tparam Type One of the types provided by the given type list.
|
||||
* @tparam Other The other types provided by the given type list.
|
||||
*/
|
||||
template<typename Type, typename... Other>
|
||||
struct type_list_unique<type_list<Type, Other...>> {
|
||||
/*! @brief A type list without duplicate types. */
|
||||
using type = std::conditional_t<
|
||||
std::disjunction_v<std::is_same<Type, Other>...>,
|
||||
typename type_list_unique<type_list<Other...>>::type,
|
||||
type_list_cat_t<type_list<Type>, typename type_list_unique<type_list<Other...>>::type>
|
||||
>;
|
||||
};
|
||||
|
||||
|
||||
/*! @brief Removes duplicates types from a type list. */
|
||||
template<>
|
||||
struct type_list_unique<type_list<>> {
|
||||
/*! @brief A type list without duplicate types. */
|
||||
using type = type_list<>;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Helper type.
|
||||
* @tparam Type A type list.
|
||||
*/
|
||||
template<typename Type>
|
||||
using type_list_unique_t = typename type_list_unique<Type>::type;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Provides the member constant `value` to true if a given type is
|
||||
* equality comparable, false otherwise.
|
||||
* @tparam Type Potentially equality comparable type.
|
||||
*/
|
||||
template<typename Type, typename = std::void_t<>>
|
||||
struct is_equality_comparable: std::false_type {};
|
||||
|
||||
|
||||
/*! @copydoc is_equality_comparable */
|
||||
template<typename Type>
|
||||
struct is_equality_comparable<Type, std::void_t<decltype(std::declval<Type>() == std::declval<Type>())>>: std::true_type {};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Helper variable template.
|
||||
* @tparam Type Potentially equality comparable type.
|
||||
*/
|
||||
template<class Type>
|
||||
constexpr auto is_equality_comparable_v = is_equality_comparable<Type>::value;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Extracts the class of a non-static member object or function.
|
||||
* @tparam Member A pointer to a non-static member object or function.
|
||||
*/
|
||||
template<typename Member>
|
||||
class member_class {
|
||||
static_assert(std::is_member_pointer_v<Member>);
|
||||
|
||||
template<typename Class, typename Ret, typename... Args>
|
||||
static Class * clazz(Ret(Class:: *)(Args...));
|
||||
|
||||
template<typename Class, typename Ret, typename... Args>
|
||||
static Class * clazz(Ret(Class:: *)(Args...) const);
|
||||
|
||||
template<typename Class, typename Type>
|
||||
static Class * clazz(Type Class:: *);
|
||||
|
||||
public:
|
||||
/*! @brief The class of the given non-static member object or function. */
|
||||
using type = std::remove_pointer_t<decltype(clazz(std::declval<Member>()))>;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Helper type.
|
||||
* @tparam Member A pointer to a non-static member object or function.
|
||||
*/
|
||||
template<typename Member>
|
||||
using member_class_t = typename member_class<Member>::type;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Alias template to ease the creation of named values.
|
||||
* @tparam Value A constant value at least convertible to `ENTT_ID_TYPE`.
|
||||
*/
|
||||
template<ENTT_ID_TYPE Value>
|
||||
using tag = std::integral_constant<ENTT_ID_TYPE, Value>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Defines an enum class to use for opaque identifiers and a dedicate
|
||||
* `to_integer` function to convert the identifiers to their underlying type.
|
||||
* @param clazz The name to use for the enum class.
|
||||
* @param type The underlying type for the enum class.
|
||||
*/
|
||||
#define ENTT_OPAQUE_TYPE(clazz, type)\
|
||||
enum class clazz: type {};\
|
||||
constexpr auto to_integral(const clazz id) ENTT_NOEXCEPT {\
|
||||
return static_cast<std::underlying_type_t<clazz>>(id);\
|
||||
}\
|
||||
static_assert(true)
|
||||
|
||||
|
||||
#endif
|
||||
105
src/entt/core/utility.hpp
Normal file
105
src/entt/core/utility.hpp
Normal file
@@ -0,0 +1,105 @@
|
||||
#ifndef ENTT_CORE_UTILITY_HPP
|
||||
#define ENTT_CORE_UTILITY_HPP
|
||||
|
||||
|
||||
#include <utility>
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/*! @brief Identity function object (waiting for C++20). */
|
||||
struct identity {
|
||||
/**
|
||||
* @brief Returns its argument unchanged.
|
||||
* @tparam Type Type of the argument.
|
||||
* @param value The actual argument.
|
||||
* @return The submitted value as-is.
|
||||
*/
|
||||
template<class Type>
|
||||
constexpr Type && operator()(Type &&value) const ENTT_NOEXCEPT {
|
||||
return std::forward<Type>(value);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Constant utility to disambiguate overloaded members of a class.
|
||||
* @tparam Type Type of the desired overload.
|
||||
* @tparam Class Type of class to which the member belongs.
|
||||
* @param member A valid pointer to a member.
|
||||
* @return Pointer to the member.
|
||||
*/
|
||||
template<typename Type, typename Class>
|
||||
constexpr auto overload(Type Class:: *member) ENTT_NOEXCEPT { return member; }
|
||||
|
||||
|
||||
/**
|
||||
* @brief Constant utility to disambiguate overloaded functions.
|
||||
* @tparam Func Function type of the desired overload.
|
||||
* @param func A valid pointer to a function.
|
||||
* @return Pointer to the function.
|
||||
*/
|
||||
template<typename Func>
|
||||
constexpr auto overload(Func *func) ENTT_NOEXCEPT { return func; }
|
||||
|
||||
|
||||
/**
|
||||
* @brief Helper type for visitors.
|
||||
* @tparam Func Types of function objects.
|
||||
*/
|
||||
template<class... Func>
|
||||
struct overloaded: Func... {
|
||||
using Func::operator()...;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Deduction guide.
|
||||
* @tparam Func Types of function objects.
|
||||
*/
|
||||
template<class... Func>
|
||||
overloaded(Func...) -> overloaded<Func...>;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Basic implementation of a y-combinator.
|
||||
* @tparam Func Type of a potentially recursive function.
|
||||
*/
|
||||
template<class Func>
|
||||
struct y_combinator {
|
||||
/**
|
||||
* @brief Constructs a y-combinator from a given function.
|
||||
* @param recursive A potentially recursive function.
|
||||
*/
|
||||
y_combinator(Func recursive):
|
||||
func{std::move(recursive)}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Invokes a y-combinator and therefore its underlying function.
|
||||
* @tparam Args Types of arguments to use to invoke the underlying function.
|
||||
* @param args Parameters to use to invoke the underlying function.
|
||||
* @return Return value of the underlying function, if any.
|
||||
*/
|
||||
template <class... Args>
|
||||
decltype(auto) operator()(Args &&... args) const {
|
||||
return func(*this, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/*! @copydoc operator()() */
|
||||
template <class... Args>
|
||||
decltype(auto) operator()(Args &&... args) {
|
||||
return func(*this, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
private:
|
||||
Func func;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -2,47 +2,38 @@
|
||||
#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 {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Dedicated to those who aren't confident with entity-component systems.
|
||||
* @brief Dedicated to those who aren't confident with the
|
||||
* entity-component-system architecture.
|
||||
*
|
||||
* Tiny wrapper around a registry, for all those users that aren't confident
|
||||
* with entity-component systems and prefer to iterate objects directly.
|
||||
* with entity-component-system architecture and prefer to iterate objects
|
||||
* directly.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
struct Actor {
|
||||
struct basic_actor {
|
||||
/*! @brief Type of registry used internally. */
|
||||
using registry_type = Registry<Entity>;
|
||||
using registry_type = basic_registry<Entity>;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
|
||||
/**
|
||||
* @brief Constructs an actor by using the given registry.
|
||||
* @param reg An entity-component system properly initialized.
|
||||
*/
|
||||
Actor(Registry<Entity> ®)
|
||||
: reg{®}, entt{reg.create()}
|
||||
basic_actor() ENTT_NOEXCEPT
|
||||
: entt{entt::null}, reg{nullptr}
|
||||
{}
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
virtual ~Actor() {
|
||||
reg->destroy(entt);
|
||||
}
|
||||
|
||||
/*! @brief Copying an actor isn't allowed. */
|
||||
Actor(const Actor &) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*
|
||||
@@ -52,14 +43,37 @@ struct Actor {
|
||||
*
|
||||
* @param other The instance to move from.
|
||||
*/
|
||||
Actor(Actor &&other)
|
||||
: reg{other.reg}, entt{other.entt}
|
||||
basic_actor(basic_actor &&other) ENTT_NOEXCEPT
|
||||
: entt{other.entt}, reg{other.reg}
|
||||
{
|
||||
other.entt = entt::null;
|
||||
other.entt = null;
|
||||
}
|
||||
|
||||
/*! @brief Default copy assignment operator. @return This actor. */
|
||||
Actor & operator=(const Actor &) = delete;
|
||||
/**
|
||||
* @brief Constructs an actor from a given registry.
|
||||
* @param ref An instance of the registry class.
|
||||
*/
|
||||
explicit basic_actor(registry_type &ref)
|
||||
: entt{ref.create()}, reg{&ref}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Constructs an actor from a given entity.
|
||||
* @param entity A valid entity identifier.
|
||||
* @param ref An instance of the registry class.
|
||||
*/
|
||||
explicit basic_actor(entity_type entity, registry_type &ref) ENTT_NOEXCEPT
|
||||
: entt{entity}, reg{&ref}
|
||||
{
|
||||
ENTT_ASSERT(ref.valid(entity));
|
||||
}
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
virtual ~basic_actor() {
|
||||
if(*this) {
|
||||
reg->destroy(entt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move assignment operator.
|
||||
@@ -71,7 +85,7 @@ struct Actor {
|
||||
* @param other The instance to move from.
|
||||
* @return This actor.
|
||||
*/
|
||||
Actor & operator=(Actor &&other) {
|
||||
basic_actor & operator=(basic_actor &&other) ENTT_NOEXCEPT {
|
||||
if(this != &other) {
|
||||
auto tmp{std::move(other)};
|
||||
std::swap(reg, tmp.reg);
|
||||
@@ -81,24 +95,6 @@ struct Actor {
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns the given tag to an actor.
|
||||
*
|
||||
* A new instance of the given tag is created and initialized with the
|
||||
* arguments provided (the tag must have a proper constructor or be of
|
||||
* aggregate type). Then the tag is removed from its previous owner (if any)
|
||||
* and assigned to the actor.
|
||||
*
|
||||
* @tparam Tag Type of the tag to create.
|
||||
* @tparam Args Types of arguments to use to construct the tag.
|
||||
* @param args Parameters to use to initialize the tag.
|
||||
* @return A reference to the newly created tag.
|
||||
*/
|
||||
template<typename Tag, typename... Args>
|
||||
Tag & assign(tag_t, Args &&... args) {
|
||||
return (reg->template remove<Tag>(), reg->template assign<Tag>(tag_t{}, entt, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns the given component to an actor.
|
||||
*
|
||||
@@ -114,18 +110,8 @@ struct Actor {
|
||||
* @return A reference to the newly created component.
|
||||
*/
|
||||
template<typename Component, typename... Args>
|
||||
Component & assign(Args &&... args) {
|
||||
return reg->template accommodate<Component>(entt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes the given tag from an actor.
|
||||
* @tparam Tag Type of the tag to remove.
|
||||
*/
|
||||
template<typename Tag>
|
||||
void remove(tag_t) {
|
||||
assert(has<Tag>(tag_t{}));
|
||||
reg->template remove<Tag>();
|
||||
decltype(auto) assign(Args &&... args) {
|
||||
return reg->template assign_or_replace<Component>(entt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,106 +124,83 @@ struct Actor {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if an actor owns the given tag.
|
||||
* @tparam Tag Type of the tag for which to perform the check.
|
||||
* @return True if the actor owns the tag, false otherwise.
|
||||
* @brief Checks if an actor has the given components.
|
||||
* @tparam Component Components for which to perform the check.
|
||||
* @return True if the actor has all the components, false otherwise.
|
||||
*/
|
||||
template<typename Tag>
|
||||
bool has(tag_t) const ENTT_NOEXCEPT {
|
||||
return (reg->template has<Tag>() && (reg->template attachee<Tag>() == entt));
|
||||
template<typename... Component>
|
||||
bool has() const {
|
||||
return (reg->template has<Component>(entt) && ...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if an actor has the given component.
|
||||
* @tparam Component Type of the component for which to perform the check.
|
||||
* @return True if the actor has the component, false otherwise.
|
||||
* @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>
|
||||
bool has() const ENTT_NOEXCEPT {
|
||||
return reg->template has<Component>(entt);
|
||||
template<typename... Component>
|
||||
decltype(auto) get() const {
|
||||
return std::as_const(*reg).template get<Component...>(entt);
|
||||
}
|
||||
|
||||
/*! @copydoc get */
|
||||
template<typename... Component>
|
||||
decltype(auto) get() {
|
||||
return reg->template get<Component...>(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the given tag for an actor.
|
||||
* @tparam Tag Type of the tag to get.
|
||||
* @return A reference to the instance of the tag owned by the actor.
|
||||
* @brief Returns pointers to the given components for an actor.
|
||||
* @tparam Component Types of components to get.
|
||||
* @return Pointers to the components owned by the actor.
|
||||
*/
|
||||
template<typename Tag>
|
||||
const Tag & get(tag_t) const ENTT_NOEXCEPT {
|
||||
assert(has<Tag>(tag_t{}));
|
||||
return reg->template get<Tag>();
|
||||
template<typename... Component>
|
||||
auto try_get() const {
|
||||
return std::as_const(*reg).template try_get<Component...>(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the given tag for an actor.
|
||||
* @tparam Tag Type of the tag to get.
|
||||
* @return A reference to the instance of the tag owned by the actor.
|
||||
*/
|
||||
template<typename Tag>
|
||||
inline Tag & get(tag_t) ENTT_NOEXCEPT {
|
||||
return const_cast<Tag &>(const_cast<const Actor *>(this)->get<Tag>(tag_t{}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the given component for an actor.
|
||||
* @tparam Component Type of the component to get.
|
||||
* @return A reference to the instance of the component owned by the actor.
|
||||
*/
|
||||
template<typename Component>
|
||||
const Component & get() const ENTT_NOEXCEPT {
|
||||
return reg->template get<Component>(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the given component for an actor.
|
||||
* @tparam Component Type of the component to get.
|
||||
* @return A reference to the instance of the component owned by the actor.
|
||||
*/
|
||||
template<typename Component>
|
||||
inline Component & get() ENTT_NOEXCEPT {
|
||||
return const_cast<Component &>(const_cast<const Actor *>(this)->get<Component>());
|
||||
/*! @copydoc try_get */
|
||||
template<typename... Component>
|
||||
auto try_get() {
|
||||
return reg->template try_get<Component...>(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the underlying registry.
|
||||
* @return A reference to the underlying registry.
|
||||
*/
|
||||
inline const registry_type & registry() const ENTT_NOEXCEPT {
|
||||
const registry_type & backend() const ENTT_NOEXCEPT {
|
||||
return *reg;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the underlying registry.
|
||||
* @return A reference to the underlying registry.
|
||||
*/
|
||||
inline registry_type & registry() ENTT_NOEXCEPT {
|
||||
return const_cast<registry_type &>(const_cast<const Actor *>(this)->registry());
|
||||
/*! @copydoc backend */
|
||||
registry_type & backend() ENTT_NOEXCEPT {
|
||||
return const_cast<registry_type &>(std::as_const(*this).backend());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the entity associated with an actor.
|
||||
* @return The entity associated with the actor.
|
||||
*/
|
||||
inline entity_type entity() const ENTT_NOEXCEPT {
|
||||
entity_type entity() const ENTT_NOEXCEPT {
|
||||
return entt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if an actor refers to a valid entity or not.
|
||||
* @return True if the actor refers to a valid entity, false otherwise.
|
||||
*/
|
||||
explicit operator bool() const {
|
||||
return reg && reg->valid(entt);
|
||||
}
|
||||
|
||||
private:
|
||||
registry_type * reg;
|
||||
Entity entt;
|
||||
entity_type entt;
|
||||
registry_type *reg;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Default actor class.
|
||||
*
|
||||
* The default actor is the best choice for almost all the applications.<br/>
|
||||
* Users should have a really good reason to choose something different.
|
||||
*/
|
||||
using DefaultActor = Actor<DefaultRegistry::entity_type>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_ACTOR_HPP
|
||||
#endif
|
||||
|
||||
@@ -1,230 +0,0 @@
|
||||
#ifndef ENTT_ENTITY_ATTACHEE_HPP
|
||||
#define ENTT_ENTITY_ATTACHEE_HPP
|
||||
|
||||
|
||||
#include <cassert>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "entity.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Attachee.
|
||||
*
|
||||
* Primary template isn't defined on purpose. All the specializations give a
|
||||
* compile-time error, but for a few reasonable cases.
|
||||
*/
|
||||
template<typename...>
|
||||
class Attachee;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Basic attachee implementation.
|
||||
*
|
||||
* Convenience data structure used to store single instance components.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
class Attachee<Entity> {
|
||||
public:
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
Attachee() ENTT_NOEXCEPT
|
||||
: owner{null}
|
||||
{}
|
||||
|
||||
/*! @brief Default copy constructor. */
|
||||
Attachee(const Attachee &) = default;
|
||||
/*! @brief Default move constructor. */
|
||||
Attachee(Attachee &&) = default;
|
||||
|
||||
/*! @brief Default copy assignment operator. @return This attachee. */
|
||||
Attachee & operator=(const Attachee &) = default;
|
||||
/*! @brief Default move assignment operator. @return This attachee. */
|
||||
Attachee & operator=(Attachee &&) = default;
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
virtual ~Attachee() ENTT_NOEXCEPT = default;
|
||||
|
||||
/**
|
||||
* @brief Returns the owner of an attachee.
|
||||
* @return A valid entity identifier if an owner exists, the null entity
|
||||
* identifier otherwise.
|
||||
*/
|
||||
inline entity_type get() const ENTT_NOEXCEPT {
|
||||
return owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns an entity to an attachee.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to assigns an entity to an attachee that already has an owner
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case
|
||||
* the attachee already has an owner.
|
||||
*
|
||||
* @param entity A valid entity identifier.
|
||||
*/
|
||||
inline void construct(const entity_type entity) ENTT_NOEXCEPT {
|
||||
assert(owner == null);
|
||||
owner = entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes an entity from an attachee.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to free an empty attachee results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* attachee is already empty.
|
||||
*/
|
||||
virtual void destroy() ENTT_NOEXCEPT {
|
||||
assert(owner != null);
|
||||
owner = null;
|
||||
}
|
||||
|
||||
private:
|
||||
entity_type owner;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Extended attachee implementation.
|
||||
*
|
||||
* This specialization of an attachee associates an object to an entity. The
|
||||
* main purpose of this class is to use attachees to store tags in a Registry.
|
||||
* It guarantees fast access both to the element and to the entity.
|
||||
*
|
||||
* @sa Attachee<Entity>
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Type Type of object assigned to the entity.
|
||||
*/
|
||||
template<typename Entity, typename Type>
|
||||
class Attachee<Entity, Type>: public Attachee<Entity> {
|
||||
using underlying_type = Attachee<Entity>;
|
||||
|
||||
public:
|
||||
/*! @brief Type of the object associated to the attachee. */
|
||||
using object_type = Type;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = typename underlying_type::entity_type;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
Attachee() ENTT_NOEXCEPT = default;
|
||||
|
||||
/*! @brief Copying an attachee isn't allowed. */
|
||||
Attachee(const Attachee &) = delete;
|
||||
/*! @brief Moving an attachee isn't allowed. */
|
||||
Attachee(Attachee &&) = delete;
|
||||
|
||||
/*! @brief Copying an attachee isn't allowed. @return This attachee. */
|
||||
Attachee & operator=(const Attachee &) = delete;
|
||||
/*! @brief Moving an attachee isn't allowed. @return This attachee. */
|
||||
Attachee & operator=(Attachee &&) = delete;
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
~Attachee() {
|
||||
if(underlying_type::get() != null) {
|
||||
reinterpret_cast<Type *>(&storage)->~Type();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the object associated to an attachee.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to query an empty attachee results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* attachee is empty.
|
||||
*
|
||||
* @return The object associated to the attachee.
|
||||
*/
|
||||
const Type & get() const ENTT_NOEXCEPT {
|
||||
assert(underlying_type::get() != null);
|
||||
return *reinterpret_cast<const Type *>(&storage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the object associated to an attachee.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to query an empty attachee results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* attachee is empty.
|
||||
*
|
||||
* @return The object associated to the attachee.
|
||||
*/
|
||||
Type & get() ENTT_NOEXCEPT {
|
||||
return const_cast<Type &>(const_cast<const Attachee *>(this)->get());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns an entity to an attachee and constructs its object.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to assigns an entity to an attachee that already has an owner
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case
|
||||
* the attachee already has an owner.
|
||||
*
|
||||
* @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 attachee.
|
||||
*/
|
||||
template<typename... Args>
|
||||
Type & construct(entity_type entity, Args &&... args) ENTT_NOEXCEPT {
|
||||
underlying_type::construct(entity);
|
||||
new (&storage) Type{std::forward<Args>(args)...};
|
||||
return *reinterpret_cast<Type *>(&storage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes an entity from an attachee and destroies its object.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to free an empty attachee results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* attachee is already empty.
|
||||
*/
|
||||
void destroy() ENTT_NOEXCEPT override {
|
||||
reinterpret_cast<Type *>(&storage)->~Type();
|
||||
underlying_type::destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Changes the owner of an attachee.
|
||||
*
|
||||
* The ownership of the attachee is transferred from one entity to another.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to transfer the ownership of an attachee that hasn't an owner
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case
|
||||
* the attachee hasn't an owner yet.
|
||||
*
|
||||
* @param entity A valid entity identifier.
|
||||
*/
|
||||
void move(const entity_type entity) ENTT_NOEXCEPT {
|
||||
underlying_type::destroy();
|
||||
underlying_type::construct(entity);
|
||||
}
|
||||
|
||||
private:
|
||||
std::aligned_storage_t<sizeof(Type), alignof(Type)> storage;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_ATTACHEE_HPP
|
||||
@@ -2,13 +2,103 @@
|
||||
#define ENTT_ENTITY_ENTITY_HPP
|
||||
|
||||
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "entt_traits.hpp"
|
||||
#include "../core/type_traits.hpp"
|
||||
|
||||
|
||||
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.
|
||||
@@ -18,44 +108,45 @@ namespace entt {
|
||||
namespace internal {
|
||||
|
||||
|
||||
struct Null {
|
||||
explicit constexpr Null() = default;
|
||||
class null {
|
||||
template<typename Entity>
|
||||
using traits_type = entt_traits<std::underlying_type_t<Entity>>;
|
||||
|
||||
public:
|
||||
template<typename Entity>
|
||||
constexpr operator Entity() const ENTT_NOEXCEPT {
|
||||
using traits_type = entt::entt_traits<Entity>;
|
||||
return traits_type::entity_mask | (traits_type::version_mask << traits_type::entity_shift);
|
||||
return Entity{traits_type<Entity>::entity_mask};
|
||||
}
|
||||
|
||||
constexpr bool operator==(Null) const ENTT_NOEXCEPT {
|
||||
constexpr bool operator==(null) const ENTT_NOEXCEPT {
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr bool operator!=(Null) const ENTT_NOEXCEPT {
|
||||
constexpr bool operator!=(null) const ENTT_NOEXCEPT {
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename Entity>
|
||||
constexpr bool operator==(const Entity entity) const ENTT_NOEXCEPT {
|
||||
return entity == static_cast<Entity>(*this);
|
||||
return (to_integral(entity) & traits_type<Entity>::entity_mask) == to_integral(static_cast<Entity>(*this));
|
||||
}
|
||||
|
||||
template<typename Entity>
|
||||
constexpr bool operator!=(const Entity entity) const ENTT_NOEXCEPT {
|
||||
return entity != static_cast<Entity>(*this);
|
||||
return !(entity == *this);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename Entity>
|
||||
constexpr bool operator==(const Entity entity, Null null) ENTT_NOEXCEPT {
|
||||
return null == entity;
|
||||
constexpr bool operator==(const Entity entity, null other) ENTT_NOEXCEPT {
|
||||
return other == entity;
|
||||
}
|
||||
|
||||
|
||||
template<typename Entity>
|
||||
constexpr bool operator!=(const Entity entity, Null null) ENTT_NOEXCEPT {
|
||||
return null != entity;
|
||||
constexpr bool operator!=(const Entity entity, null other) ENTT_NOEXCEPT {
|
||||
return other != entity;
|
||||
}
|
||||
|
||||
|
||||
@@ -69,16 +160,16 @@ constexpr bool operator!=(const Entity entity, Null null) ENTT_NOEXCEPT {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Null entity.
|
||||
* @brief Compile-time constant for null entities.
|
||||
*
|
||||
* There exist implicit conversions from this variable to entity identifiers of
|
||||
* any allowed type. Similarly, there exist comparision operators between the
|
||||
* null entity and any other entity identifier.
|
||||
*/
|
||||
constexpr auto null = internal::Null{};
|
||||
constexpr auto null = internal::null{};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_ENTITY_HPP
|
||||
#endif
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
#ifndef ENTT_ENTITY_ENTT_TRAITS_HPP
|
||||
#define ENTT_ENTITY_ENTT_TRAITS_HPP
|
||||
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Entity traits.
|
||||
*
|
||||
* Primary template isn't defined on purpose. All the specializations give a
|
||||
* compile-time error unless the template parameter is an accepted entity type.
|
||||
*/
|
||||
template<typename>
|
||||
struct entt_traits;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Entity traits for a 16 bits entity identifier.
|
||||
*
|
||||
* A 16 bits entity identifier guarantees:
|
||||
*
|
||||
* * 12 bits for the entity number (up to 4k entities).
|
||||
* * 4 bit for the version (resets in [0-15]).
|
||||
*/
|
||||
template<>
|
||||
struct entt_traits<std::uint16_t> {
|
||||
/*! @brief Underlying entity type. */
|
||||
using entity_type = std::uint16_t;
|
||||
/*! @brief Underlying version type. */
|
||||
using version_type = std::uint8_t;
|
||||
/*! @brief Difference type. */
|
||||
using difference_type = std::int32_t;
|
||||
|
||||
/*! @brief Mask to use to get the entity number out of an identifier. */
|
||||
static constexpr 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;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_ENTT_TRAITS_HPP
|
||||
122
src/entt/entity/fwd.hpp
Normal file
122
src/entt/entity/fwd.hpp
Normal file
@@ -0,0 +1,122 @@
|
||||
#ifndef ENTT_ENTITY_FWD_HPP
|
||||
#define ENTT_ENTITY_FWD_HPP
|
||||
|
||||
|
||||
#include "../config/config.h"
|
||||
#include "../core/type_traits.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Alias for exclusion lists.
|
||||
* @tparam Type List of types.
|
||||
*/
|
||||
template<typename... Type>
|
||||
struct exclude_t: type_list<Type...> {};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Variable template for exclusion lists.
|
||||
* @tparam Type List of types.
|
||||
*/
|
||||
template<typename... Type>
|
||||
constexpr exclude_t<Type...> exclude{};
|
||||
|
||||
|
||||
/**
|
||||
* @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{};
|
||||
|
||||
|
||||
/*! @class basic_registry */
|
||||
template <typename>
|
||||
class basic_registry;
|
||||
|
||||
/*! @class basic_view */
|
||||
template<typename...>
|
||||
class basic_view;
|
||||
|
||||
/*! @class basic_runtime_view */
|
||||
template<typename>
|
||||
class basic_runtime_view;
|
||||
|
||||
/*! @class basic_group */
|
||||
template<typename...>
|
||||
class basic_group;
|
||||
|
||||
/*! @class basic_observer */
|
||||
template<typename>
|
||||
class basic_observer;
|
||||
|
||||
/*! @struct basic_actor */
|
||||
template <typename>
|
||||
struct basic_actor;
|
||||
|
||||
/*! @class basic_snapshot */
|
||||
template<typename>
|
||||
class basic_snapshot;
|
||||
|
||||
/*! @class basic_snapshot_loader */
|
||||
template<typename>
|
||||
class basic_snapshot_loader;
|
||||
|
||||
/*! @class basic_continuous_loader */
|
||||
template<typename>
|
||||
class basic_continuous_loader;
|
||||
|
||||
/*! @brief Alias declaration for the most common use case. */
|
||||
ENTT_OPAQUE_TYPE(entity, ENTT_ID_TYPE);
|
||||
|
||||
/*! @brief Alias declaration for the most common use case. */
|
||||
using registry = basic_registry<entity>;
|
||||
|
||||
/*! @brief Alias declaration for the most common use case. */
|
||||
using observer = basic_observer<entity>;
|
||||
|
||||
/*! @brief Alias declaration for the most common use case. */
|
||||
using actor = basic_actor<entity>;
|
||||
|
||||
/*! @brief Alias declaration for the most common use case. */
|
||||
using 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 Types 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
|
||||
890
src/entt/entity/group.hpp
Normal file
890
src/entt/entity/group.hpp
Normal file
@@ -0,0 +1,890 @@
|
||||
#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 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 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.
|
||||
*
|
||||
* @b Important
|
||||
*
|
||||
* Iterators aren't invalidated if:
|
||||
*
|
||||
* * New instances of the given components are created and assigned to entities.
|
||||
* * The entity currently pointed is modified (as an example, if one of the
|
||||
* given components is removed from the entity to which the iterator points).
|
||||
* * The entity currently pointed is destroyed.
|
||||
*
|
||||
* In all other cases, modifying the pools iterated by the group 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 instances 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_ entities and components).
|
||||
*
|
||||
* @warning
|
||||
* Lifetime of a group must not overcome that of the registry that generated it.
|
||||
* In any other case, attempting to use a group results in undefined behavior.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Exclude Types of components used to filter the group.
|
||||
* @tparam Get Type of components observed by the group.
|
||||
*/
|
||||
template<typename Entity, typename... Exclude, typename... Get>
|
||||
class basic_group<Entity, exclude_t<Exclude...>, get_t<Get...>> {
|
||||
/*! @brief A registry is allowed to create groups. */
|
||||
friend class basic_registry<Entity>;
|
||||
|
||||
template<typename Component>
|
||||
using pool_type = std::conditional_t<std::is_const_v<Component>, const storage<Entity, std::remove_const_t<Component>>, storage<Entity, Component>>;
|
||||
|
||||
// we could use pool_type<Type> &..., but vs complains about it and refuses to compile for unknown reasons (most likely a bug)
|
||||
basic_group(sparse_set<Entity> &ref, storage<Entity, std::remove_const_t<Get>> &... gpool) ENTT_NOEXCEPT
|
||||
: handler{&ref},
|
||||
pools{&gpool...}
|
||||
{}
|
||||
|
||||
template<typename Func, typename... Weak>
|
||||
void traverse(Func func, type_list<Weak...>) const {
|
||||
for(const auto entt: *handler) {
|
||||
if constexpr(std::is_invocable_v<Func, decltype(get<Weak>({}))...>) {
|
||||
func(std::get<pool_type<Weak> *>(pools)->get(entt)...);
|
||||
} else {
|
||||
func(entt, std::get<pool_type<Weak> *>(pools)->get(entt)...);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Input iterator type. */
|
||||
using iterator_type = typename sparse_set<Entity>::iterator_type;
|
||||
|
||||
/**
|
||||
* @brief 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 a group or some pools are empty.
|
||||
* @tparam Component Types of components in which one is interested.
|
||||
* @return True if the group or the pools are empty, false otherwise.
|
||||
*/
|
||||
template<typename... Component>
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
if constexpr(sizeof...(Component) == 0) {
|
||||
return handler->empty();
|
||||
} else {
|
||||
return (std::get<pool_type<Component> *>(pools)->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 Returns the first entity that has the given components, if any.
|
||||
* @return The first entity that has the given components if one exists, the
|
||||
* null entity otherwise.
|
||||
*/
|
||||
entity_type front() const {
|
||||
const auto it = begin();
|
||||
return it != end() ? *it : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the last entity that has the given components, if any.
|
||||
* @return The last entity that has the given components if one exists, the
|
||||
* null entity otherwise.
|
||||
*/
|
||||
entity_type back() const {
|
||||
const auto it = std::make_reverse_iterator(end());
|
||||
return it != std::make_reverse_iterator(begin()) ? *it : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Finds an entity.
|
||||
* @param entt A valid entity identifier.
|
||||
* @return An iterator to the given entity if it's found, past the end
|
||||
* iterator otherwise.
|
||||
*/
|
||||
iterator_type find(const entity_type entt) const {
|
||||
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 {
|
||||
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 {
|
||||
return handler->has(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the components assigned to the given entity.
|
||||
*
|
||||
* Prefer this function instead of `registry::get` during iterations. It has
|
||||
* far better performance than its counterpart.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid component type results in a compilation
|
||||
* error. Attempting to use an entity that doesn't belong to the 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_ASSERT(contains(entt));
|
||||
|
||||
if constexpr(sizeof...(Component) == 1) {
|
||||
return (std::get<pool_type<Component> *>(pools)->get(entt), ...);
|
||||
} else {
|
||||
return std::tuple<decltype(get<Component>({}))...>{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>
|
||||
void each(Func func) const {
|
||||
traverse(std::move(func), type_list<Get...>{});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterates entities and components and applies the given function
|
||||
* object to them.
|
||||
*
|
||||
* The function object is invoked for each entity. It is provided with the
|
||||
* entity itself and a set of references to non-empty components. The
|
||||
* _constness_ of the components is as requested.<br/>
|
||||
* The signature of the function must be equivalent to one of the following
|
||||
* forms:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(const entity_type, Type &...);
|
||||
* void(Type &...);
|
||||
* @endcode
|
||||
*
|
||||
* @sa each
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void less(Func func) const {
|
||||
using get_type_list = type_list_cat_t<std::conditional_t<ENTT_ENABLE_ETO(Get), type_list<>, type_list<Get>>...>;
|
||||
traverse(std::move(func), get_type_list{});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sort a group according to the given comparison function.
|
||||
*
|
||||
* Sort the group so that iterating it with a couple of iterators returns
|
||||
* entities and components in the expected order. See `begin` and `end` for
|
||||
* more details.
|
||||
*
|
||||
* The comparison function object must return `true` if the first element
|
||||
* is _less_ than the second one, `false` otherwise. The signature of the
|
||||
* comparison function should be equivalent to one of the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* bool(std::tuple<Component &...>, std::tuple<Component &...>);
|
||||
* bool(const Component &..., const Component &...);
|
||||
* bool(const Entity, const Entity);
|
||||
* @endcode
|
||||
*
|
||||
* Where `Component` are 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.
|
||||
*
|
||||
* @note
|
||||
* Attempting to iterate elements using a raw pointer returned by a call to
|
||||
* either `data` or `raw` gives no guarantees on the order, even though
|
||||
* `sort` has been invoked.
|
||||
*
|
||||
* @tparam Component Optional types of components to compare.
|
||||
* @tparam Compare Type of comparison function object.
|
||||
* @tparam Sort Type of sort function object.
|
||||
* @tparam Args Types of arguments to forward to the sort function object.
|
||||
* @param compare A valid comparison function object.
|
||||
* @param algo A valid sort function object.
|
||||
* @param args Arguments to forward to the sort function object, if any.
|
||||
*/
|
||||
template<typename... Component, typename Compare, typename Sort = std_sort, typename... Args>
|
||||
void sort(Compare compare, Sort algo = Sort{}, Args &&... args) {
|
||||
if constexpr(sizeof...(Component) == 0) {
|
||||
static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>);
|
||||
handler->sort(handler->begin(), handler->end(), std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||
} else if constexpr(sizeof...(Component) == 1) {
|
||||
handler->sort(handler->begin(), handler->end(), [this, compare = std::move(compare)](const entity_type lhs, const entity_type rhs) {
|
||||
return compare((std::get<pool_type<Component> *>(pools)->get(lhs), ...), (std::get<pool_type<Component> *>(pools)->get(rhs), ...));
|
||||
}, std::move(algo), std::forward<Args>(args)...);
|
||||
} else {
|
||||
handler->sort(handler->begin(), handler->end(), [this, compare = std::move(compare)](const entity_type lhs, const entity_type rhs) {
|
||||
return compare(std::tuple<decltype(get<Component>({}))...>{std::get<pool_type<Component> *>(pools)->get(lhs)...}, std::tuple<decltype(get<Component>({}))...>{std::get<pool_type<Component> *>(pools)->get(rhs)...});
|
||||
}, std::move(algo), std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sort the shared pool of entities according to the given component.
|
||||
*
|
||||
* Non-owning groups of the same type share with the registry a pool of
|
||||
* entities with its own order that doesn't depend on the order of any pool
|
||||
* of components. Users can order the underlying data structure so that it
|
||||
* respects the order of the pool of the given component.
|
||||
*
|
||||
* @note
|
||||
* The shared pool of entities and thus its order is affected by the changes
|
||||
* to each and every pool that it tracks. Therefore changes to those pools
|
||||
* can quickly ruin the order imposed to the pool of entities shared between
|
||||
* the non-owning groups.
|
||||
*
|
||||
* @tparam Component Type of component to use to impose the order.
|
||||
*/
|
||||
template<typename Component>
|
||||
void sort() const {
|
||||
handler->respect(*std::get<pool_type<Component> *>(pools));
|
||||
}
|
||||
|
||||
private:
|
||||
sparse_set<entity_type> *handler;
|
||||
const std::tuple<pool_type<Get> *...> pools;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Owning group.
|
||||
*
|
||||
* Owning groups return all 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 instances have
|
||||
* the same order in memory.
|
||||
*
|
||||
* The more types of components are owned by a group, the faster it is to
|
||||
* iterate them.
|
||||
*
|
||||
* @b Important
|
||||
*
|
||||
* Iterators aren't invalidated if:
|
||||
*
|
||||
* * New instances of the given components are created and assigned to entities.
|
||||
* * The entity currently pointed is modified (as an example, if one of the
|
||||
* given components is removed from the entity to which the iterator points).
|
||||
* * The entity currently pointed is destroyed.
|
||||
*
|
||||
* In all other cases, modifying the pools iterated by the group 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 not overcome that of the registry that generated it.
|
||||
* In any other case, attempting to use a group results in undefined behavior.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Exclude Types of components used to filter the group.
|
||||
* @tparam Get Types of components observed by the group.
|
||||
* @tparam Owned Types of components owned by the group.
|
||||
*/
|
||||
template<typename Entity, typename... Exclude, typename... Get, typename... Owned>
|
||||
class basic_group<Entity, exclude_t<Exclude...>, get_t<Get...>, Owned...> {
|
||||
/*! @brief A registry is allowed to create groups. */
|
||||
friend class basic_registry<Entity>;
|
||||
|
||||
template<typename Component>
|
||||
using pool_type = std::conditional_t<std::is_const_v<Component>, const storage<Entity, std::remove_const_t<Component>>, storage<Entity, Component>>;
|
||||
|
||||
template<typename Component>
|
||||
using component_iterator_type = decltype(std::declval<pool_type<Component>>().begin());
|
||||
|
||||
// we could use pool_type<Type> &..., but vs complains about it and refuses to compile for unknown reasons (most likely a bug)
|
||||
basic_group(const std::size_t &ref, const std::size_t &extent, storage<Entity, std::remove_const_t<Owned>> &... opool, storage<Entity, std::remove_const_t<Get>> &... gpool) ENTT_NOEXCEPT
|
||||
: pools{&opool..., &gpool...},
|
||||
length{&extent},
|
||||
super{&ref}
|
||||
{}
|
||||
|
||||
template<typename Func, typename... Strong, typename... Weak>
|
||||
void traverse(Func func, type_list<Strong...>, type_list<Weak...>) const {
|
||||
[[maybe_unused]] auto it = std::make_tuple((std::get<pool_type<Strong> *>(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<Strong>({}))..., decltype(get<Weak>({}))...>) {
|
||||
if constexpr(sizeof...(Weak) == 0) {
|
||||
func(*(std::get<component_iterator_type<Strong>>(it)++)...);
|
||||
} else {
|
||||
const auto entt = *(data++);
|
||||
func(*(std::get<component_iterator_type<Strong>>(it)++)..., std::get<pool_type<Weak> *>(pools)->get(entt)...);
|
||||
}
|
||||
} else {
|
||||
const auto entt = *(data++);
|
||||
func(entt, *(std::get<component_iterator_type<Strong>>(it)++)..., std::get<pool_type<Weak> *>(pools)->get(entt)...);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Input iterator type. */
|
||||
using iterator_type = typename sparse_set<Entity>::iterator_type;
|
||||
|
||||
/**
|
||||
* @brief 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 a group or some pools are empty.
|
||||
* @tparam Component Types of components in which one is interested.
|
||||
* @return True if the group or the pools are empty, false otherwise.
|
||||
*/
|
||||
template<typename... Component>
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
if constexpr(sizeof...(Component) == 0) {
|
||||
return !*length;
|
||||
} else {
|
||||
return (std::get<pool_type<Component> *>(pools)->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.<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 Returns the first entity that has the given components, if any.
|
||||
* @return The first entity that has the given components if one exists, the
|
||||
* null entity otherwise.
|
||||
*/
|
||||
entity_type front() const {
|
||||
const auto it = begin();
|
||||
return it != end() ? *it : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the last entity that has the given components, if any.
|
||||
* @return The last entity that has the given components if one exists, the
|
||||
* null entity otherwise.
|
||||
*/
|
||||
entity_type back() const {
|
||||
const auto it = std::make_reverse_iterator(end());
|
||||
return it != std::make_reverse_iterator(begin()) ? *it : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Finds an entity.
|
||||
* @param entt A valid entity identifier.
|
||||
* @return An iterator to the given entity if it's found, past the end
|
||||
* iterator otherwise.
|
||||
*/
|
||||
iterator_type find(const entity_type entt) const {
|
||||
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 {
|
||||
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 {
|
||||
return std::get<0>(pools)->has(entt) && (std::get<0>(pools)->index(entt) < (*length));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the components assigned to the given entity.
|
||||
*
|
||||
* Prefer this function instead of `registry::get` during iterations. It has
|
||||
* far better performance than its counterpart.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid component type results in a compilation
|
||||
* error. Attempting to use an entity that doesn't belong to the 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_ASSERT(contains(entt));
|
||||
|
||||
if constexpr(sizeof...(Component) == 1) {
|
||||
return (std::get<pool_type<Component> *>(pools)->get(entt), ...);
|
||||
} else {
|
||||
return std::tuple<decltype(get<Component>({}))...>{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>
|
||||
void each(Func func) const {
|
||||
traverse(std::move(func), type_list<Owned...>{}, type_list<Get...>{});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterates entities and components and applies the given function
|
||||
* object to them.
|
||||
*
|
||||
* The function object is invoked for each entity. It is provided with the
|
||||
* entity itself and a set of references to non-empty components. The
|
||||
* _constness_ of the components is as requested.<br/>
|
||||
* The signature of the function must be equivalent to one of the following
|
||||
* forms:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(const entity_type, Type &...);
|
||||
* void(Type &...);
|
||||
* @endcode
|
||||
*
|
||||
* @sa each
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void less(Func func) const {
|
||||
using owned_type_list = type_list_cat_t<std::conditional_t<ENTT_ENABLE_ETO(Owned), type_list<>, type_list<Owned>>...>;
|
||||
using get_type_list = type_list_cat_t<std::conditional_t<ENTT_ENABLE_ETO(Get), type_list<>, type_list<Get>>...>;
|
||||
traverse(std::move(func), owned_type_list{}, get_type_list{});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks whether the group can be sorted.
|
||||
* @return True if the group can be sorted, false otherwise.
|
||||
*/
|
||||
bool sortable() const ENTT_NOEXCEPT {
|
||||
constexpr auto size = sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude);
|
||||
return *super == size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sort a group according to the given comparison function.
|
||||
*
|
||||
* Sort the group so that iterating it with a couple of iterators returns
|
||||
* entities and components in the expected order. See `begin` and `end` for
|
||||
* more details.
|
||||
*
|
||||
* The comparison function object must return `true` if the first element
|
||||
* is _less_ than the second one, `false` otherwise. The signature of the
|
||||
* comparison function should be equivalent to one of the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* bool(std::tuple<Component &...>, std::tuple<Component &...>);
|
||||
* bool(const Component &, const Component &);
|
||||
* bool(const Entity, const Entity);
|
||||
* @endcode
|
||||
*
|
||||
* Where `Component` are either owned types or not but still such that they
|
||||
* are iterated by the group.<br/>
|
||||
* Moreover, the comparison function object shall induce a
|
||||
* _strict weak ordering_ on the values.
|
||||
*
|
||||
* The sort function oject must offer a member function template
|
||||
* `operator()` that accepts three arguments:
|
||||
*
|
||||
* * An iterator to the first element of the range to sort.
|
||||
* * An iterator past the last element of the range to sort.
|
||||
* * A comparison function to use to compare the elements.
|
||||
*
|
||||
* @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) {
|
||||
ENTT_ASSERT(sortable());
|
||||
auto *cpool = std::get<0>(pools);
|
||||
|
||||
if constexpr(sizeof...(Component) == 0) {
|
||||
static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>);
|
||||
cpool->sort(cpool->end()-*length, cpool->end(), std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||
} else if constexpr(sizeof...(Component) == 1) {
|
||||
cpool->sort(cpool->end()-*length, cpool->end(), [this, compare = std::move(compare)](const entity_type lhs, const entity_type rhs) {
|
||||
return compare((std::get<pool_type<Component> *>(pools)->get(lhs), ...), (std::get<pool_type<Component> *>(pools)->get(rhs), ...));
|
||||
}, std::move(algo), std::forward<Args>(args)...);
|
||||
} else {
|
||||
cpool->sort(cpool->end()-*length, cpool->end(), [this, compare = std::move(compare)](const entity_type lhs, const entity_type rhs) {
|
||||
return compare(std::tuple<decltype(get<Component>({}))...>{std::get<pool_type<Component> *>(pools)->get(lhs)...}, std::tuple<decltype(get<Component>({}))...>{std::get<pool_type<Component> *>(pools)->get(rhs)...});
|
||||
}, std::move(algo), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
[this](auto *head, auto *... other) {
|
||||
for(auto next = *length; next; --next) {
|
||||
const auto pos = next - 1;
|
||||
[[maybe_unused]] const auto entt = head->data()[pos];
|
||||
(other->swap(other->data()[pos], entt), ...);
|
||||
}
|
||||
}(std::get<pool_type<Owned> *>(pools)...);
|
||||
}
|
||||
|
||||
private:
|
||||
const std::tuple<pool_type<Owned> *..., pool_type<Get> *...> pools;
|
||||
const size_type *length;
|
||||
const size_type *super;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -3,103 +3,115 @@
|
||||
|
||||
|
||||
#include <type_traits>
|
||||
#include "../core/hashed_string.hpp"
|
||||
#include "../signal/sigh.hpp"
|
||||
#include "../core/type_traits.hpp"
|
||||
#include "../config/config.h"
|
||||
#include "registry.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "fwd.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Dependency function prototype.
|
||||
*
|
||||
* A _dependency function_ is a built-in listener to use to automatically assign
|
||||
* components to an entity when a type has a dependency on some other types.
|
||||
*
|
||||
* This is a prototype function to use to create dependencies.<br/>
|
||||
* It isn't intended for direct use, although nothing forbids using it freely.
|
||||
*
|
||||
* @brief Converts a registry to a view.
|
||||
* @tparam Const Constness of the accepted registry.
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Component Types of components to assign to an entity if triggered.
|
||||
* @param registry A valid reference to a registry.
|
||||
* @param entity A valid entity identifier.
|
||||
*/
|
||||
template<typename Entity, typename... Component>
|
||||
void dependency(Registry<Entity> ®istry, const Entity entity) {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { ((registry.template has<Component>(entity) ? void() : (registry.template assign<Component>(entity), void())), 0)... };
|
||||
(void)accumulator;
|
||||
}
|
||||
template<bool Const, typename Entity>
|
||||
struct as_view {
|
||||
/*! @brief Type of registry to convert. */
|
||||
using registry_type = std::conditional_t<Const, const entt::basic_registry<Entity>, entt::basic_registry<Entity>>;
|
||||
|
||||
/**
|
||||
* @brief Constructs a converter for a given registry.
|
||||
* @param source A valid reference to a registry.
|
||||
*/
|
||||
as_view(registry_type &source) ENTT_NOEXCEPT: reg{source} {}
|
||||
|
||||
/**
|
||||
* @brief Conversion function from a registry to a view.
|
||||
* @tparam Exclude Types of components used to filter the view.
|
||||
* @tparam Component Type of components used to construct the view.
|
||||
* @return A newly created view.
|
||||
*/
|
||||
template<typename Exclude, typename... Component>
|
||||
operator entt::basic_view<Entity, Exclude, Component...>() const {
|
||||
return reg.template view<Component...>(Exclude{});
|
||||
}
|
||||
|
||||
private:
|
||||
registry_type ®
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Connects a dependency function to the given sink.
|
||||
* @brief Deduction guide.
|
||||
*
|
||||
* A _dependency function_ is a built-in listener to use to automatically assign
|
||||
* components to an entity when a type has a dependency on some other types.
|
||||
* It allows to deduce the constness of a registry directly from the instance
|
||||
* provided to the constructor.
|
||||
*
|
||||
* The following adds components `AType` and `AnotherType` whenever `MyType` is
|
||||
* assigned to an entity:
|
||||
* @code{.cpp}
|
||||
* entt::DefaultRegistry registry;
|
||||
* entt::connect<AType, AnotherType>(registry.construction<MyType>());
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Dependency Types of components to assign to an entity if triggered.
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @param sink A sink object properly initialized.
|
||||
*/
|
||||
template<typename... Dependency, typename Entity>
|
||||
inline void connect(Sink<void(Registry<Entity> &, const Entity)> sink) {
|
||||
sink.template connect<dependency<Entity, Dependency...>>();
|
||||
}
|
||||
template<typename Entity>
|
||||
as_view(basic_registry<Entity> &) ENTT_NOEXCEPT -> as_view<false, Entity>;
|
||||
|
||||
|
||||
/*! @copydoc as_view */
|
||||
template<typename Entity>
|
||||
as_view(const basic_registry<Entity> &) ENTT_NOEXCEPT -> as_view<true, Entity>;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Disconnects a dependency function from the given sink.
|
||||
*
|
||||
* A _dependency function_ is a built-in listener to use to automatically assign
|
||||
* components to an entity when a type has a dependency on some other types.
|
||||
*
|
||||
* The following breaks the dependency between the component `MyType` and the
|
||||
* components `AType` and `AnotherType`:
|
||||
* @code{.cpp}
|
||||
* entt::DefaultRegistry registry;
|
||||
* entt::disconnect<AType, AnotherType>(registry.construction<MyType>());
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Dependency Types of components used to create the dependency.
|
||||
* @brief Converts a registry to a group.
|
||||
* @tparam Const Constness of the accepted registry.
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @param sink A sink object properly initialized.
|
||||
*/
|
||||
template<typename... Dependency, typename Entity>
|
||||
inline void disconnect(Sink<void(Registry<Entity> &, const Entity)> sink) {
|
||||
sink.template disconnect<dependency<Entity, Dependency...>>();
|
||||
}
|
||||
template<bool Const, typename Entity>
|
||||
struct as_group {
|
||||
/*! @brief Type of registry to convert. */
|
||||
using registry_type = std::conditional_t<Const, const entt::basic_registry<Entity>, entt::basic_registry<Entity>>;
|
||||
|
||||
/**
|
||||
* @brief Constructs a converter for a given registry.
|
||||
* @param source A valid reference to a registry.
|
||||
*/
|
||||
as_group(registry_type &source) ENTT_NOEXCEPT: reg{source} {}
|
||||
|
||||
/**
|
||||
* @brief Conversion function from a registry to a group.
|
||||
* @tparam Exclude Types of components used to filter the group.
|
||||
* @tparam Get Types of components observed by the group.
|
||||
* @tparam Owned Types of components owned by the group.
|
||||
* @return A newly created group.
|
||||
*/
|
||||
template<typename Exclude, typename Get, typename... Owned>
|
||||
operator entt::basic_group<Entity, Exclude, Get, Owned...>() const {
|
||||
return reg.template group<Owned...>(Get{}, Exclude{});
|
||||
}
|
||||
|
||||
private:
|
||||
registry_type ®
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Alias template to ease the assignment of labels to entities.
|
||||
* @brief Deduction guide.
|
||||
*
|
||||
* If used in combination with hashed strings, it simplifies the assignment of
|
||||
* labels to entities and the use of labels in general where a type would be
|
||||
* required otherwise.<br/>
|
||||
* As an example and where the user defined literal for hashed strings hasn't
|
||||
* been changed:
|
||||
* @code{.cpp}
|
||||
* entt::DefaultRegistry registry;
|
||||
* registry.assign<entt::label<"enemy"_hs>>(entity);
|
||||
* @endcode
|
||||
* It allows to deduce the constness of a registry directly from the instance
|
||||
* provided to the constructor.
|
||||
*
|
||||
* @tparam Value The numeric representation of an instance of hashed string.
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename HashedString::hash_type Value>
|
||||
using label = std::integral_constant<typename HashedString::hash_type, Value>;
|
||||
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>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_HELPER_HPP
|
||||
#endif
|
||||
|
||||
442
src/entt/entity/observer.hpp
Normal file
442
src/entt/entity/observer.hpp
Normal file
@@ -0,0 +1,442 @@
|
||||
#ifndef ENTT_ENTITY_OBSERVER_HPP
|
||||
#define ENTT_ENTITY_OBSERVER_HPP
|
||||
|
||||
|
||||
#include <limits>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "../core/type_traits.hpp"
|
||||
#include "registry.hpp"
|
||||
#include "storage.hpp"
|
||||
#include "entity.hpp"
|
||||
#include "fwd.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/*! @brief Grouping matcher. */
|
||||
template<typename...>
|
||||
struct matcher {};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Collector.
|
||||
*
|
||||
* Primary template isn't defined on purpose. All the specializations give a
|
||||
* compile-time error, but for a few reasonable cases.
|
||||
*/
|
||||
template<typename...>
|
||||
struct basic_collector;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Collector.
|
||||
*
|
||||
* A collector contains a set of rules (literally, matchers) to use to track
|
||||
* entities.<br/>
|
||||
* Its main purpose is to generate a descriptor that allows an observer to know
|
||||
* how to connect to a registry.
|
||||
*/
|
||||
template<>
|
||||
struct basic_collector<> {
|
||||
/**
|
||||
* @brief Adds a grouping matcher to the collector.
|
||||
* @tparam AllOf Types of components tracked by the matcher.
|
||||
* @tparam NoneOf Types of components used to filter out entities.
|
||||
* @return The updated collector.
|
||||
*/
|
||||
template<typename... AllOf, typename... NoneOf>
|
||||
static constexpr auto group(exclude_t<NoneOf...> = {}) ENTT_NOEXCEPT {
|
||||
return basic_collector<matcher<type_list<>, type_list<>, type_list<NoneOf...>, AllOf...>>{};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds an observing matcher to the collector.
|
||||
* @tparam AnyOf Type of component for which changes should be detected.
|
||||
* @return The updated collector.
|
||||
*/
|
||||
template<typename AnyOf>
|
||||
static constexpr auto replace() ENTT_NOEXCEPT {
|
||||
return basic_collector<matcher<type_list<>, type_list<>, AnyOf>>{};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Collector.
|
||||
* @copydetails basic_collector<>
|
||||
* @tparam Reject Untracked types used to filter out entities.
|
||||
* @tparam Require Untracked types required by the matcher.
|
||||
* @tparam Rule Specific details of the current matcher.
|
||||
* @tparam Other Other matchers.
|
||||
*/
|
||||
template<typename... Reject, typename... Require, typename... Rule, typename... Other>
|
||||
struct basic_collector<matcher<type_list<Reject...>, type_list<Require...>, Rule...>, Other...> {
|
||||
/*! @brief Current matcher. */
|
||||
using current_type = matcher<type_list<Reject...>, type_list<Require...>, Rule...>;
|
||||
|
||||
/**
|
||||
* @brief Adds a grouping matcher to the collector.
|
||||
* @tparam AllOf Types of components tracked by the matcher.
|
||||
* @tparam NoneOf Types of components used to filter out entities.
|
||||
* @return The updated collector.
|
||||
*/
|
||||
template<typename... AllOf, typename... NoneOf>
|
||||
static constexpr auto group(exclude_t<NoneOf...> = {}) ENTT_NOEXCEPT {
|
||||
return basic_collector<matcher<type_list<>, type_list<>, type_list<NoneOf...>, AllOf...>, current_type, Other...>{};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds an observing matcher to the collector.
|
||||
* @tparam AnyOf Type of component for which changes should be detected.
|
||||
* @return The updated collector.
|
||||
*/
|
||||
template<typename AnyOf>
|
||||
static constexpr auto replace() ENTT_NOEXCEPT {
|
||||
return basic_collector<matcher<type_list<>, type_list<>, AnyOf>, current_type, Other...>{};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates the filter of the last added matcher.
|
||||
* @tparam AllOf Types of components required by the matcher.
|
||||
* @tparam NoneOf Types of components used to filter out entities.
|
||||
* @return The updated collector.
|
||||
*/
|
||||
template<typename... AllOf, typename... NoneOf>
|
||||
static constexpr auto where(exclude_t<NoneOf...> = {}) ENTT_NOEXCEPT {
|
||||
using extended_type = matcher<type_list<Reject..., NoneOf...>, type_list<Require..., AllOf...>, Rule...>;
|
||||
return basic_collector<extended_type, Other...>{};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*! @brief Variable template used to ease the definition of collectors. */
|
||||
constexpr basic_collector<> collector{};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Observer.
|
||||
*
|
||||
* An observer returns all the entities and only the entities that fit the
|
||||
* requirements of at least one matcher. Moreover, it's guaranteed that the
|
||||
* entity list is tightly packed in memory for fast iterations.<br/>
|
||||
* In general, observers don't stay true to the order of any set of components.
|
||||
*
|
||||
* Observers work mainly with two types of matchers, provided through a
|
||||
* collector:
|
||||
*
|
||||
* * Observing matcher: an observer will return at least all the living entities
|
||||
* for which one or more of the given components have been explicitly
|
||||
* replaced and not yet destroyed.
|
||||
* * Grouping matcher: an observer will return at least all the living entities
|
||||
* that would have entered the given group if it existed and that would have
|
||||
* not yet left it.
|
||||
*
|
||||
* If an entity respects the requirements of multiple matchers, it will be
|
||||
* returned once and only once by the observer in any case.
|
||||
*
|
||||
* Matchers support also filtering by means of a _where_ clause that accepts
|
||||
* both a list of types and an exclusion list.<br/>
|
||||
* Whenever a matcher finds that an entity matches its requirements, the
|
||||
* condition of the filter is verified before to register the entity itself.
|
||||
* Moreover, a registered entity isn't returned by the observer if the condition
|
||||
* set by the filter is broken in the meantime.
|
||||
*
|
||||
* @b Important
|
||||
*
|
||||
* Iterators aren't invalidated if:
|
||||
*
|
||||
* * New instances of the given components are created and assigned to entities.
|
||||
* * The entity currently pointed is modified (as an example, if one of the
|
||||
* given components is removed from the entity to which the iterator points).
|
||||
* * The entity currently pointed is destroyed.
|
||||
*
|
||||
* In all the other cases, modifying the pools of the given components in any
|
||||
* way invalidates all the iterators and using them results in undefined
|
||||
* behavior.
|
||||
*
|
||||
* @warning
|
||||
* Lifetime of an observer doesn't necessarily have to overcome that of the
|
||||
* registry to which it is connected. However, the observer must be disconnected
|
||||
* from the registry before being destroyed to avoid crashes due to dangling
|
||||
* pointers.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
class basic_observer {
|
||||
using payload_type = std::uint32_t;
|
||||
|
||||
template<typename>
|
||||
struct matcher_handler;
|
||||
|
||||
template<typename... Reject, typename... Require, typename AnyOf>
|
||||
struct matcher_handler<matcher<type_list<Reject...>, type_list<Require...>, AnyOf>> {
|
||||
template<std::size_t Index>
|
||||
static void maybe_valid_if(basic_observer &obs, const basic_registry<Entity> ®, const Entity entt) {
|
||||
if(reg.template has<Require...>(entt) && !(reg.template has<Reject>(entt) || ...)) {
|
||||
if(auto *comp = obs.view.try_get(entt); !comp) {
|
||||
obs.view.construct(entt);
|
||||
}
|
||||
|
||||
obs.view.get(entt) |= (1 << Index);
|
||||
}
|
||||
}
|
||||
|
||||
template<std::size_t Index>
|
||||
static void discard_if(basic_observer &obs, const basic_registry<Entity> &, const Entity entt) {
|
||||
if(auto *value = obs.view.try_get(entt); value && !(*value &= (~(1 << Index)))) {
|
||||
obs.view.destroy(entt);
|
||||
}
|
||||
}
|
||||
|
||||
template<std::size_t Index>
|
||||
static void connect(basic_observer &obs, basic_registry<Entity> ®) {
|
||||
(reg.template on_destroy<Require>().template connect<&discard_if<Index>>(obs), ...);
|
||||
(reg.template on_construct<Reject>().template connect<&discard_if<Index>>(obs), ...);
|
||||
reg.template on_replace<AnyOf>().template connect<&maybe_valid_if<Index>>(obs);
|
||||
reg.template on_destroy<AnyOf>().template connect<&discard_if<Index>>(obs);
|
||||
}
|
||||
|
||||
static void disconnect(basic_observer &obs, basic_registry<Entity> ®) {
|
||||
(reg.template on_destroy<Require>().disconnect(obs), ...);
|
||||
(reg.template on_construct<Reject>().disconnect(obs), ...);
|
||||
reg.template on_replace<AnyOf>().disconnect(obs);
|
||||
reg.template on_destroy<AnyOf>().disconnect(obs);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Reject, typename... Require, typename... NoneOf, typename... AllOf>
|
||||
struct matcher_handler<matcher<type_list<Reject...>, type_list<Require...>, type_list<NoneOf...>, AllOf...>> {
|
||||
template<std::size_t Index>
|
||||
static void maybe_valid_if(basic_observer &obs, const basic_registry<Entity> ®, const Entity entt) {
|
||||
if(reg.template has<AllOf..., Require...>(entt) && !(reg.template has<NoneOf>(entt) || ...) && !(reg.template has<Reject>(entt) || ...)) {
|
||||
if(auto *comp = obs.view.try_get(entt); !comp) {
|
||||
obs.view.construct(entt);
|
||||
}
|
||||
|
||||
obs.view.get(entt) |= (1 << Index);
|
||||
}
|
||||
}
|
||||
|
||||
template<std::size_t Index>
|
||||
static void discard_if(basic_observer &obs, const basic_registry<Entity> &, const Entity entt) {
|
||||
if(auto *value = obs.view.try_get(entt); value && !(*value &= (~(1 << Index)))) {
|
||||
obs.view.destroy(entt);
|
||||
}
|
||||
}
|
||||
|
||||
template<std::size_t Index>
|
||||
static void connect(basic_observer &obs, basic_registry<Entity> ®) {
|
||||
(reg.template on_destroy<Require>().template connect<&discard_if<Index>>(obs), ...);
|
||||
(reg.template on_construct<Reject>().template connect<&discard_if<Index>>(obs), ...);
|
||||
(reg.template on_construct<AllOf>().template connect<&maybe_valid_if<Index>>(obs), ...);
|
||||
(reg.template on_destroy<NoneOf>().template connect<&maybe_valid_if<Index>>(obs), ...);
|
||||
(reg.template on_destroy<AllOf>().template connect<&discard_if<Index>>(obs), ...);
|
||||
(reg.template on_construct<NoneOf>().template connect<&discard_if<Index>>(obs), ...);
|
||||
}
|
||||
|
||||
static void disconnect(basic_observer &obs, basic_registry<Entity> ®) {
|
||||
(reg.template on_destroy<Require>().disconnect(obs), ...);
|
||||
(reg.template on_construct<Reject>().disconnect(obs), ...);
|
||||
(reg.template on_construct<AllOf>().disconnect(obs), ...);
|
||||
(reg.template on_destroy<NoneOf>().disconnect(obs), ...);
|
||||
(reg.template on_destroy<AllOf>().disconnect(obs), ...);
|
||||
(reg.template on_construct<NoneOf>().disconnect(obs), ...);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Matcher>
|
||||
static void disconnect(basic_observer &obs, basic_registry<Entity> ®) {
|
||||
(matcher_handler<Matcher>::disconnect(obs, reg), ...);
|
||||
}
|
||||
|
||||
template<typename... Matcher, std::size_t... Index>
|
||||
void connect(basic_registry<Entity> ®, std::index_sequence<Index...>) {
|
||||
static_assert(sizeof...(Matcher) < std::numeric_limits<payload_type>::digits);
|
||||
(matcher_handler<Matcher>::template connect<Index>(*this, reg), ...);
|
||||
release = &basic_observer::disconnect<Matcher...>;
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Input iterator type. */
|
||||
using iterator_type = typename sparse_set<Entity>::iterator_type;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
basic_observer()
|
||||
: target{}, release{}, view{}
|
||||
{}
|
||||
|
||||
/*! @brief Default copy constructor, deleted on purpose. */
|
||||
basic_observer(const basic_observer &) = delete;
|
||||
/*! @brief Default move constructor, deleted on purpose. */
|
||||
basic_observer(basic_observer &&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Creates an observer and connects it to a given registry.
|
||||
* @tparam Matcher Types of matchers to use to initialize the observer.
|
||||
* @param reg A valid reference to a registry.
|
||||
*/
|
||||
template<typename... Matcher>
|
||||
basic_observer(basic_registry<entity_type> ®, basic_collector<Matcher...>)
|
||||
: target{®},
|
||||
release{},
|
||||
view{}
|
||||
{
|
||||
connect<Matcher...>(reg, std::index_sequence_for<Matcher...>{});
|
||||
}
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
~basic_observer() = default;
|
||||
|
||||
/**
|
||||
* @brief Default copy assignment operator, deleted on purpose.
|
||||
* @return This observer.
|
||||
*/
|
||||
basic_observer & operator=(const basic_observer &) = delete;
|
||||
|
||||
/**
|
||||
* @brief Default move assignment operator, deleted on purpose.
|
||||
* @return This observer.
|
||||
*/
|
||||
basic_observer & operator=(basic_observer &&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Connects an observer to a given registry.
|
||||
* @tparam Matcher Types of matchers to use to initialize the observer.
|
||||
* @param reg A valid reference to a registry.
|
||||
*/
|
||||
template<typename... Matcher>
|
||||
void connect(basic_registry<entity_type> ®, basic_collector<Matcher...>) {
|
||||
disconnect();
|
||||
connect<Matcher...>(reg, std::index_sequence_for<Matcher...>{});
|
||||
target = ®
|
||||
view.clear();
|
||||
}
|
||||
|
||||
/*! @brief Disconnects an observer from the registry it keeps track of. */
|
||||
void disconnect() {
|
||||
if(release) {
|
||||
release(*this, *target);
|
||||
release = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the number of elements in an observer.
|
||||
* @return Number of elements.
|
||||
*/
|
||||
size_type size() const ENTT_NOEXCEPT {
|
||||
return view.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks whether an observer is empty.
|
||||
* @return True if the observer is empty, false otherwise.
|
||||
*/
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
return view.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Direct access to the list of entities of the observer.
|
||||
*
|
||||
* The returned pointer is such that range `[data(), data() + size()]` is
|
||||
* always a valid range, even if the container is empty.
|
||||
*
|
||||
* @note
|
||||
* There are no guarantees on the order of the entities. Use `begin` and
|
||||
* `end` if you want to iterate the observer in the expected order.
|
||||
*
|
||||
* @return A pointer to the array of entities.
|
||||
*/
|
||||
const entity_type * data() const ENTT_NOEXCEPT {
|
||||
return view.data();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the first entity of the observer.
|
||||
*
|
||||
* The returned iterator points to the first entity of the observer. If the
|
||||
* container is empty, the returned iterator will be equal to `end()`.
|
||||
*
|
||||
* @return An iterator to the first entity of the observer.
|
||||
*/
|
||||
iterator_type begin() const ENTT_NOEXCEPT {
|
||||
return view.sparse_set<entity_type>::begin();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator that is past the last entity of the observer.
|
||||
*
|
||||
* The returned iterator points to the entity following the last entity of
|
||||
* the observer. Attempting to dereference the returned iterator results in
|
||||
* undefined behavior.
|
||||
*
|
||||
* @return An iterator to the entity following the last entity of the
|
||||
* observer.
|
||||
*/
|
||||
iterator_type end() const ENTT_NOEXCEPT {
|
||||
return view.sparse_set<entity_type>::end();
|
||||
}
|
||||
|
||||
/*! @brief Clears the underlying container. */
|
||||
void clear() ENTT_NOEXCEPT {
|
||||
view.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterates entities and applies the given function object to them.
|
||||
*
|
||||
* The function object is invoked for each entity.<br/>
|
||||
* The signature of the function must be equivalent to the following form:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(const entity_type);
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void each(Func func) const {
|
||||
static_assert(std::is_invocable_v<Func, entity_type>);
|
||||
|
||||
for(const auto entity: *this) {
|
||||
func(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterates entities and applies the given function object to them,
|
||||
* then clears the observer.
|
||||
*
|
||||
* @sa each
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void each(Func func) {
|
||||
std::as_const(*this).each(std::move(func));
|
||||
clear();
|
||||
}
|
||||
|
||||
private:
|
||||
basic_registry<entity_type> *target;
|
||||
void(* release)(basic_observer &, basic_registry<entity_type> &);
|
||||
storage<entity_type, payload_type> view;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -1,497 +0,0 @@
|
||||
#ifndef ENTT_ENTITY_PROTOTYPE_HPP
|
||||
#define ENTT_ENTITY_PROTOTYPE_HPP
|
||||
|
||||
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include "../config/config.h"
|
||||
#include "registry.hpp"
|
||||
#include "entity.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Prototype container for _concepts_.
|
||||
*
|
||||
* A prototype is used to define a _concept_ in terms of components.<br/>
|
||||
* Prototypes act as templates for those specific types of an application which
|
||||
* users would otherwise define through a series of component assignments to
|
||||
* entities. In other words, prototypes can be used to assign components to
|
||||
* entities of a registry at once.
|
||||
*
|
||||
* @note
|
||||
* Components used along with prototypes must be copy constructible. Prototypes
|
||||
* wrap component types with custom types, so they do not interfere with other
|
||||
* users of the registry they were built with.
|
||||
*
|
||||
* @warning
|
||||
* Prototypes directly use their underlying registries to store entities and
|
||||
* components for their purposes. Users must ensure that the lifetime of a
|
||||
* registry and its contents exceed that of the prototypes that use it.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
class Prototype final {
|
||||
using basic_fn_type = void(const Prototype &, Registry<Entity> &, const Entity);
|
||||
using component_type = typename Registry<Entity>::component_type;
|
||||
|
||||
template<typename Component>
|
||||
struct Wrapper { Component component; };
|
||||
|
||||
struct Handler {
|
||||
basic_fn_type *accommodate;
|
||||
basic_fn_type *assign;
|
||||
};
|
||||
|
||||
void release() {
|
||||
if(registry->valid(entity)) {
|
||||
registry->destroy(entity);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Registry type. */
|
||||
using registry_type = Registry<Entity>;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
|
||||
/**
|
||||
* @brief Constructs a prototype that is bound to a given registry.
|
||||
* @param registry A valid reference to a registry.
|
||||
*/
|
||||
Prototype(Registry<Entity> ®istry)
|
||||
: registry{®istry},
|
||||
entity{registry.create()}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Releases all its resources.
|
||||
*/
|
||||
~Prototype() {
|
||||
release();
|
||||
}
|
||||
|
||||
/*! @brief Copying a prototype isn't allowed. */
|
||||
Prototype(const Prototype &) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
*
|
||||
* After prototype move construction, instances that have been moved from
|
||||
* are placed in a valid but unspecified state. It's highly discouraged to
|
||||
* continue using them.
|
||||
*
|
||||
* @param other The instance to move from.
|
||||
*/
|
||||
Prototype(Prototype &&other)
|
||||
: handlers{std::move(other.handlers)},
|
||||
registry{other.registry},
|
||||
entity{other.entity}
|
||||
{
|
||||
other.entity = entt::null;
|
||||
}
|
||||
|
||||
/*! @brief Copying a prototype isn't allowed. @return This Prototype. */
|
||||
Prototype & operator=(const Prototype &) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move assignment operator.
|
||||
*
|
||||
* After prototype move assignment, instances that have been moved from are
|
||||
* placed in a valid but unspecified state. It's highly discouraged to
|
||||
* continue using them.
|
||||
*
|
||||
* @param other The instance to move from.
|
||||
* @return This Prototype.
|
||||
*/
|
||||
Prototype & operator=(Prototype &&other) {
|
||||
if(this != &other) {
|
||||
auto tmp{std::move(other)};
|
||||
handlers.swap(tmp.handlers);
|
||||
std::swap(registry, tmp.registry);
|
||||
std::swap(entity, tmp.entity);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns to or replaces the given component of a prototype.
|
||||
* @tparam Component Type of component to assign or replace.
|
||||
* @tparam Args Types of arguments to use to construct the component.
|
||||
* @param args Parameters to use to initialize the component.
|
||||
* @return A reference to the newly created component.
|
||||
*/
|
||||
template<typename Component, typename... Args>
|
||||
Component & set(Args &&... args) {
|
||||
basic_fn_type *accommodate = [](const Prototype &prototype, Registry<Entity> &other, const Entity dst) {
|
||||
const auto &wrapper = prototype.registry->template get<Wrapper<Component>>(prototype.entity);
|
||||
other.template accommodate<Component>(dst, wrapper.component);
|
||||
};
|
||||
|
||||
basic_fn_type *assign = [](const Prototype &prototype, Registry<Entity> &other, const Entity dst) {
|
||||
if(!other.template has<Component>(dst)) {
|
||||
const auto &wrapper = prototype.registry->template get<Wrapper<Component>>(prototype.entity);
|
||||
other.template assign<Component>(dst, wrapper.component);
|
||||
}
|
||||
};
|
||||
|
||||
handlers[registry->template type<Component>()] = Handler{accommodate, assign};
|
||||
auto &wrapper = registry->template accommodate<Wrapper<Component>>(entity, Component{std::forward<Args>(args)...});
|
||||
return wrapper.component;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes the given component from a prototype.
|
||||
* @tparam Component Type of component to remove.
|
||||
*/
|
||||
template<typename Component>
|
||||
void unset() ENTT_NOEXCEPT {
|
||||
registry->template reset<Wrapper<Component>>(entity);
|
||||
handlers.erase(registry->template type<Component>());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a prototype owns all the given components.
|
||||
* @tparam Component Components for which to perform the check.
|
||||
* @return True if the prototype owns all the components, false otherwise.
|
||||
*/
|
||||
template<typename... Component>
|
||||
bool has() const ENTT_NOEXCEPT {
|
||||
return registry->template has<Wrapper<Component>...>(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the given component.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to get a component from a prototype that doesn't own it
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* prototype doesn't own an instance of the given component.
|
||||
*
|
||||
* @tparam Component Type of component to get.
|
||||
* @return A reference to the component owned by the prototype.
|
||||
*/
|
||||
template<typename Component>
|
||||
const Component & get() const ENTT_NOEXCEPT {
|
||||
return registry->template get<Wrapper<Component>>(entity).component;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the given component.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to get a component from a prototype that doesn't own it
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* prototype doesn't own an instance of the given component.
|
||||
*
|
||||
* @tparam Component Type of component to get.
|
||||
* @return A reference to the component owned by the prototype.
|
||||
*/
|
||||
template<typename Component>
|
||||
inline Component & get() ENTT_NOEXCEPT {
|
||||
return const_cast<Component &>(const_cast<const Prototype *>(this)->get<Component>());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the given components.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to get components from a prototype that doesn't own them
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* prototype doesn't own instances of the given components.
|
||||
*
|
||||
* @tparam Component Type of components to get.
|
||||
* @return References to the components owned by the prototype.
|
||||
*/
|
||||
template<typename... Component>
|
||||
inline std::enable_if_t<(sizeof...(Component) > 1), std::tuple<const Component &...>>
|
||||
get() const ENTT_NOEXCEPT {
|
||||
return std::tuple<const Component &...>{get<Component>()...};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the given components.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to get components from a prototype that doesn't own them
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* prototype doesn't own instances of the given components.
|
||||
*
|
||||
* @tparam Component Type of components to get.
|
||||
* @return References to the components owned by the prototype.
|
||||
*/
|
||||
template<typename... Component>
|
||||
inline std::enable_if_t<(sizeof...(Component) > 1), std::tuple<Component &...>>
|
||||
get() ENTT_NOEXCEPT {
|
||||
return std::tuple<Component &...>{get<Component>()...};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a new entity using a given prototype.
|
||||
*
|
||||
* Utility shortcut, equivalent to the following snippet:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* const auto entity = registry.create();
|
||||
* prototype(registry, entity);
|
||||
* @endcode
|
||||
*
|
||||
* @note
|
||||
* The registry may or may not be different from the one already used by
|
||||
* the prototype. There is also an overload that directly uses the
|
||||
* underlying registry.
|
||||
*
|
||||
* @param other A valid reference to a registry.
|
||||
* @return A valid entity identifier.
|
||||
*/
|
||||
entity_type create(registry_type &other) const {
|
||||
const auto entity = other.create();
|
||||
assign(other, entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a new entity using a given prototype.
|
||||
*
|
||||
* Utility shortcut, equivalent to the following snippet:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* const auto entity = registry.create();
|
||||
* prototype(entity);
|
||||
* @endcode
|
||||
*
|
||||
* @note
|
||||
* This overload directly uses the underlying registry as a working space.
|
||||
* Therefore, the components of the prototype and of the entity will share
|
||||
* the same registry.
|
||||
*
|
||||
* @return A valid entity identifier.
|
||||
*/
|
||||
inline entity_type create() const {
|
||||
return create(*registry);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns the components of a prototype to a given entity.
|
||||
*
|
||||
* Assigning a prototype to an entity won't overwrite existing components
|
||||
* under any circumstances.<br/>
|
||||
* In other words, only those components that the entity doesn't own yet are
|
||||
* copied over. All the other components remain unchanged.
|
||||
*
|
||||
* @note
|
||||
* The registry may or may not be different from the one already used by
|
||||
* the prototype. There is also an overload that directly uses the
|
||||
* underlying registry.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity.
|
||||
*
|
||||
* @param other A valid reference to a registry.
|
||||
* @param dst A valid entity identifier.
|
||||
*/
|
||||
void assign(registry_type &other, const entity_type dst) const {
|
||||
for(auto &handler: handlers) {
|
||||
handler.second.assign(*this, other, dst);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns the components of a prototype to a given entity.
|
||||
*
|
||||
* Assigning a prototype to an entity won't overwrite existing components
|
||||
* under any circumstances.<br/>
|
||||
* In other words, only those components that the entity doesn't own yet are
|
||||
* copied over. All the other components remain unchanged.
|
||||
*
|
||||
* @note
|
||||
* This overload directly uses the underlying registry as a working space.
|
||||
* Therefore, the components of the prototype and of the entity will share
|
||||
* the same registry.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity.
|
||||
*
|
||||
* @param dst A valid entity identifier.
|
||||
*/
|
||||
inline void assign(const entity_type dst) const {
|
||||
assign(*registry, dst);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns or replaces the components of a prototype for an entity.
|
||||
*
|
||||
* Existing components are overwritten, if any. All the other components
|
||||
* will be copied over to the target entity.
|
||||
*
|
||||
* @note
|
||||
* The registry may or may not be different from the one already used by
|
||||
* the prototype. There is also an overload that directly uses the
|
||||
* underlying registry.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity.
|
||||
*
|
||||
* @param other A valid reference to a registry.
|
||||
* @param dst A valid entity identifier.
|
||||
*/
|
||||
void accommodate(registry_type &other, const entity_type dst) const {
|
||||
for(auto &handler: handlers) {
|
||||
handler.second.accommodate(*this, other, dst);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns or replaces the components of a prototype for an entity.
|
||||
*
|
||||
* Existing components are overwritten, if any. All the other components
|
||||
* will be copied over to the target entity.
|
||||
*
|
||||
* @note
|
||||
* This overload directly uses the underlying registry as a working space.
|
||||
* Therefore, the components of the prototype and of the entity will share
|
||||
* the same registry.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity.
|
||||
*
|
||||
* @param dst A valid entity identifier.
|
||||
*/
|
||||
inline void accommodate(const entity_type dst) const {
|
||||
accommodate(*registry, dst);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns the components of a prototype to an entity.
|
||||
*
|
||||
* Assigning a prototype to an entity won't overwrite existing components
|
||||
* under any circumstances.<br/>
|
||||
* In other words, only the components that the entity doesn't own yet are
|
||||
* copied over. All the other components remain unchanged.
|
||||
*
|
||||
* @note
|
||||
* The registry may or may not be different from the one already used by
|
||||
* the prototype. There is also an overload that directly uses the
|
||||
* underlying registry.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity.
|
||||
*
|
||||
* @param other A valid reference to a registry.
|
||||
* @param dst A valid entity identifier.
|
||||
*/
|
||||
inline void operator()(registry_type &other, const entity_type dst) const ENTT_NOEXCEPT {
|
||||
assign(other, dst);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns the components of a prototype to an entity.
|
||||
*
|
||||
* Assigning a prototype to an entity won't overwrite existing components
|
||||
* under any circumstances.<br/>
|
||||
* In other words, only the components that the entity doesn't own yet are
|
||||
* copied over. All the other components remain unchanged.
|
||||
*
|
||||
* @note
|
||||
* This overload directly uses the underlying registry as a working space.
|
||||
* Therefore, the components of the prototype and of the entity will share
|
||||
* the same registry.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an invalid entity results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode in case of
|
||||
* invalid entity.
|
||||
*
|
||||
* @param dst A valid entity identifier.
|
||||
*/
|
||||
inline void operator()(const entity_type dst) const ENTT_NOEXCEPT {
|
||||
assign(*registry, dst);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a new entity using a given prototype.
|
||||
*
|
||||
* Utility shortcut, equivalent to the following snippet:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* const auto entity = registry.create();
|
||||
* prototype(registry, entity);
|
||||
* @endcode
|
||||
*
|
||||
* @note
|
||||
* The registry may or may not be different from the one already used by
|
||||
* the prototype. There is also an overload that directly uses the
|
||||
* underlying registry.
|
||||
*
|
||||
* @param other A valid reference to a registry.
|
||||
* @return A valid entity identifier.
|
||||
*/
|
||||
inline entity_type operator()(registry_type &other) const ENTT_NOEXCEPT {
|
||||
return create(other);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a new entity using a given prototype.
|
||||
*
|
||||
* Utility shortcut, equivalent to the following snippet:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* const auto entity = registry.create();
|
||||
* prototype(entity);
|
||||
* @endcode
|
||||
*
|
||||
* @note
|
||||
* This overload directly uses the underlying registry as a working space.
|
||||
* Therefore, the components of the prototype and of the entity will share
|
||||
* the same registry.
|
||||
*
|
||||
* @return A valid entity identifier.
|
||||
*/
|
||||
inline entity_type operator()() const ENTT_NOEXCEPT {
|
||||
return create(*registry);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<component_type, Handler> handlers;
|
||||
Registry<Entity> *registry;
|
||||
entity_type entity;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Default prototype
|
||||
*
|
||||
* The default prototype is the best choice for almost all the
|
||||
* applications.<br/>
|
||||
* Users should have a really good reason to choose something different.
|
||||
*/
|
||||
using DefaultPrototype = Prototype<DefaultRegistry::entity_type>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_PROTOTYPE_HPP
|
||||
File diff suppressed because it is too large
Load Diff
263
src/entt/entity/runtime_view.hpp
Normal file
263
src/entt/entity/runtime_view.hpp
Normal file
@@ -0,0 +1,263 @@
|
||||
#ifndef ENTT_ENTITY_RUNTIME_VIEW_HPP
|
||||
#define ENTT_ENTITY_RUNTIME_VIEW_HPP
|
||||
|
||||
|
||||
#include <iterator>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "sparse_set.hpp"
|
||||
#include "entity.hpp"
|
||||
#include "fwd.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Runtime view.
|
||||
*
|
||||
* Runtime views iterate over those entities that have at least all the given
|
||||
* components in their bags. During initialization, a runtime view looks at the
|
||||
* number of entities available for each component and picks up a reference to
|
||||
* the smallest set of candidate entities in order to get a performance boost
|
||||
* when iterate.<br/>
|
||||
* Order of elements during iterations are highly dependent on the order of the
|
||||
* underlying data structures. See sparse_set and its specializations for more
|
||||
* details.
|
||||
*
|
||||
* @b Important
|
||||
*
|
||||
* Iterators aren't invalidated if:
|
||||
*
|
||||
* * New instances of the given components are created and assigned to entities.
|
||||
* * The entity currently pointed is modified (as an example, if one of the
|
||||
* given components is removed from the entity to which the iterator points).
|
||||
* * The entity currently pointed is destroyed.
|
||||
*
|
||||
* In all the other cases, modifying the pools of the given components in any
|
||||
* way invalidates all the iterators and using them results in undefined
|
||||
* behavior.
|
||||
*
|
||||
* @note
|
||||
* Views share references to the underlying data structures of the registry that
|
||||
* generated them. Therefore any change to the entities and to the components
|
||||
* made by means of the registry are immediately reflected by the views, unless
|
||||
* a pool was missing when the view was built (in this case, the view won't
|
||||
* have a valid reference and won't be updated accordingly).
|
||||
*
|
||||
* @warning
|
||||
* Lifetime of a view must not overcome that 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;
|
||||
|
||||
class iterator {
|
||||
friend class basic_runtime_view<Entity>;
|
||||
|
||||
using direct_type = std::vector<const sparse_set<Entity> *>;
|
||||
|
||||
iterator(const direct_type *all, underlying_iterator_type curr) ENTT_NOEXCEPT
|
||||
: pools{all},
|
||||
it{curr}
|
||||
{
|
||||
if(it != (*pools)[0]->end() && !valid()) {
|
||||
++(*this);
|
||||
}
|
||||
}
|
||||
|
||||
bool valid() const {
|
||||
return std::all_of(pools->begin()++, pools->end(), [entt = *it](const auto *curr) {
|
||||
return curr->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::bidirectional_iterator_tag;
|
||||
|
||||
iterator() ENTT_NOEXCEPT = default;
|
||||
|
||||
iterator & operator++() {
|
||||
while(++it != (*pools)[0]->end() && !valid());
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator operator++(int) {
|
||||
iterator orig = *this;
|
||||
return operator++(), orig;
|
||||
}
|
||||
|
||||
iterator & operator--() ENTT_NOEXCEPT {
|
||||
while(--it != (*pools)[0]->begin() && !valid());
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator operator--(int) ENTT_NOEXCEPT {
|
||||
iterator orig = *this;
|
||||
return operator--(), orig;
|
||||
}
|
||||
|
||||
bool operator==(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return other.it == it;
|
||||
}
|
||||
|
||||
bool operator!=(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
pointer operator->() const {
|
||||
return it.operator->();
|
||||
}
|
||||
|
||||
reference operator*() const {
|
||||
return *operator->();
|
||||
}
|
||||
|
||||
private:
|
||||
const direct_type *pools;
|
||||
underlying_iterator_type it;
|
||||
};
|
||||
|
||||
basic_runtime_view(std::vector<const sparse_set<Entity> *> others) ENTT_NOEXCEPT
|
||||
: pools{std::move(others)}
|
||||
{
|
||||
const auto it = std::min_element(pools.begin(), pools.end(), [](const auto *lhs, const auto *rhs) {
|
||||
return (!lhs && rhs) || (lhs && rhs && lhs->size() < rhs->size());
|
||||
});
|
||||
|
||||
// brings the best candidate (if any) on front of the vector
|
||||
std::rotate(pools.begin(), it, pools.end());
|
||||
}
|
||||
|
||||
bool valid() const {
|
||||
return !pools.empty() && pools.front();
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Input iterator type. */
|
||||
using iterator_type = iterator;
|
||||
|
||||
/**
|
||||
* @brief Estimates the number of entities that have the given components.
|
||||
* @return Estimated number of entities that have the given components.
|
||||
*/
|
||||
size_type size() const {
|
||||
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 {
|
||||
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 {
|
||||
iterator_type it{};
|
||||
|
||||
if(valid()) {
|
||||
it = { &pools, pools[0]->begin() };
|
||||
}
|
||||
|
||||
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 {
|
||||
iterator_type it{};
|
||||
|
||||
if(valid()) {
|
||||
it = { &pools, pools[0]->end() };
|
||||
}
|
||||
|
||||
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 {
|
||||
return valid() && std::all_of(pools.cbegin(), pools.cend(), [entt](const auto *view) {
|
||||
return view->find(entt) != view->end();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterates entities and applies the given function object to them.
|
||||
*
|
||||
* The function object is invoked for each entity. It is provided only with
|
||||
* the entity itself. To get the components, users can use the registry with
|
||||
* which the view was built.<br/>
|
||||
* The signature of the function should be equivalent to the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(const entity_type);
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void each(Func func) const {
|
||||
for(const auto entity: *this) {
|
||||
func(entity);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<const sparse_set<Entity> *> pools;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -5,57 +5,54 @@
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include <cassert>
|
||||
#include <iterator>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include "../config/config.h"
|
||||
#include "entt_traits.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "entity.hpp"
|
||||
#include "fwd.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Forward declaration of the registry class.
|
||||
*/
|
||||
template<typename>
|
||||
class Registry;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Utility class to create snapshots from a registry.
|
||||
*
|
||||
* A _snapshot_ can be either a dump of the entire registry or a narrower
|
||||
* selection of components and tags of interest.<br/>
|
||||
* selection of components of interest.<br/>
|
||||
* This type can be used in both cases if provided with a correctly configured
|
||||
* output archive.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
class Snapshot final {
|
||||
class basic_snapshot {
|
||||
/*! @brief A registry is allowed to create snapshots. */
|
||||
friend class Registry<Entity>;
|
||||
friend class basic_registry<Entity>;
|
||||
|
||||
using follow_fn_type = Entity(const Registry<Entity> &, const Entity);
|
||||
using follow_fn_type = Entity(const basic_registry<Entity> &, const Entity);
|
||||
using traits_type = entt_traits<std::underlying_type_t<Entity>>;
|
||||
|
||||
Snapshot(const Registry<Entity> ®istry, Entity seed, follow_fn_type *follow) ENTT_NOEXCEPT
|
||||
: registry{registry},
|
||||
seed{seed},
|
||||
follow{follow}
|
||||
basic_snapshot(const basic_registry<Entity> *source, Entity init, follow_fn_type *fn) ENTT_NOEXCEPT
|
||||
: reg{source},
|
||||
seed{init},
|
||||
follow{fn}
|
||||
{}
|
||||
|
||||
template<typename Component, typename Archive, typename It>
|
||||
void get(Archive &archive, std::size_t sz, It first, It last) const {
|
||||
archive(static_cast<Entity>(sz));
|
||||
archive(typename traits_type::entity_type(sz));
|
||||
|
||||
while(first != last) {
|
||||
const auto entity = *(first++);
|
||||
const auto entt = *(first++);
|
||||
|
||||
if(registry.template has<Component>(entity)) {
|
||||
archive(entity, registry.template get<Component>(entity));
|
||||
if(reg->template has<Component>(entt)) {
|
||||
if constexpr(std::is_empty_v<Component>) {
|
||||
archive(entt);
|
||||
} else {
|
||||
archive(entt, reg->template get<Component>(entt));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,27 +63,19 @@ class Snapshot final {
|
||||
auto begin = first;
|
||||
|
||||
while(begin != last) {
|
||||
const auto entity = *(begin++);
|
||||
using accumulator_type = std::size_t[];
|
||||
accumulator_type accumulator = { (registry.template has<Component>(entity) ? ++size[Indexes] : size[Indexes])... };
|
||||
(void)accumulator;
|
||||
const auto entt = *(begin++);
|
||||
((reg->template has<Component>(entt) ? ++size[Indexes] : size[Indexes]), ...);
|
||||
}
|
||||
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { (get<Component>(archive, size[Indexes], first, last), 0)... };
|
||||
(void)accumulator;
|
||||
(get<Component>(archive, size[Indexes], first, last), ...);
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Copying a snapshot isn't allowed. */
|
||||
Snapshot(const Snapshot &) = delete;
|
||||
/*! @brief Default move constructor. */
|
||||
Snapshot(Snapshot &&) = default;
|
||||
basic_snapshot(basic_snapshot &&) = default;
|
||||
|
||||
/*! @brief Copying a snapshot isn't allowed. @return This snapshot. */
|
||||
Snapshot & operator=(const Snapshot &) = delete;
|
||||
/*! @brief Default move assignment operator. @return This snapshot. */
|
||||
Snapshot & operator=(Snapshot &&) = default;
|
||||
basic_snapshot & operator=(basic_snapshot &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Puts aside all the entities that are still in use.
|
||||
@@ -99,9 +88,9 @@ public:
|
||||
* @return An object of this type to continue creating the snapshot.
|
||||
*/
|
||||
template<typename Archive>
|
||||
const Snapshot & entities(Archive &archive) const {
|
||||
archive(static_cast<Entity>(registry.alive()));
|
||||
registry.each([&archive](const auto entity) { archive(entity); });
|
||||
const basic_snapshot & entities(Archive &archive) const {
|
||||
archive(typename traits_type::entity_type(reg->alive()));
|
||||
reg->each([&archive](const auto entt) { archive(entt); });
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -116,16 +105,16 @@ public:
|
||||
* @return An object of this type to continue creating the snapshot.
|
||||
*/
|
||||
template<typename Archive>
|
||||
const Snapshot & destroyed(Archive &archive) const {
|
||||
auto size = registry.size() - registry.alive();
|
||||
archive(static_cast<Entity>(size));
|
||||
const basic_snapshot & destroyed(Archive &archive) const {
|
||||
auto size = reg->size() - reg->alive();
|
||||
archive(typename traits_type::entity_type(size));
|
||||
|
||||
if(size) {
|
||||
auto curr = seed;
|
||||
archive(curr);
|
||||
|
||||
for(--size; size; --size) {
|
||||
curr = follow(registry, curr);
|
||||
curr = follow(*reg, curr);
|
||||
archive(curr);
|
||||
}
|
||||
}
|
||||
@@ -133,32 +122,6 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Puts aside the given component.
|
||||
*
|
||||
* Each instance is serialized together with the entity to which it belongs.
|
||||
* Entities are serialized along with their versions.
|
||||
*
|
||||
* @tparam Component Type of component to serialize.
|
||||
* @tparam Archive Type of output archive.
|
||||
* @param archive A valid reference to an output archive.
|
||||
* @return An object of this type to continue creating the snapshot.
|
||||
*/
|
||||
template<typename Component, typename Archive>
|
||||
const Snapshot & component(Archive &archive) const {
|
||||
const auto sz = registry.template size<Component>();
|
||||
const auto *entities = registry.template data<Component>();
|
||||
|
||||
archive(static_cast<Entity>(sz));
|
||||
|
||||
for(std::remove_const_t<decltype(sz)> i{}; i < sz; ++i) {
|
||||
const auto entity = entities[i];
|
||||
archive(entity, registry.template get<Component>(entity));
|
||||
};
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Puts aside the given components.
|
||||
*
|
||||
@@ -171,11 +134,8 @@ public:
|
||||
* @return An object of this type to continue creating the snapshot.
|
||||
*/
|
||||
template<typename... Component, typename Archive>
|
||||
std::enable_if_t<(sizeof...(Component) > 1), const Snapshot &>
|
||||
component(Archive &archive) const {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (component<Component>(archive), 0)... };
|
||||
(void)accumulator;
|
||||
const basic_snapshot & component(Archive &archive) const {
|
||||
(component<Component>(archive, reg->template data<Component>(), reg->template data<Component>() + reg->template size<Component>()), ...);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -194,58 +154,13 @@ public:
|
||||
* @return An object of this type to continue creating the snapshot.
|
||||
*/
|
||||
template<typename... Component, typename Archive, typename It>
|
||||
const Snapshot & component(Archive &archive, It first, It last) const {
|
||||
component<Component...>(archive, first, last, std::make_index_sequence<sizeof...(Component)>{});
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Puts aside the given tag.
|
||||
*
|
||||
* Each instance is serialized together with the entity to which it belongs.
|
||||
* Entities are serialized along with their versions.
|
||||
*
|
||||
* @tparam Tag Type of tag to serialize.
|
||||
* @tparam Archive Type of output archive.
|
||||
* @param archive A valid reference to an output archive.
|
||||
* @return An object of this type to continue creating the snapshot.
|
||||
*/
|
||||
template<typename Tag, typename Archive>
|
||||
const Snapshot & tag(Archive &archive) const {
|
||||
const bool has = registry.template has<Tag>();
|
||||
|
||||
// numerical length is forced for tags to facilitate loading
|
||||
archive(has ? Entity(1): Entity{});
|
||||
|
||||
if(has) {
|
||||
archive(registry.template attachee<Tag>(), registry.template get<Tag>());
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Puts aside the given tags.
|
||||
*
|
||||
* Each instance is serialized together with the entity to which it belongs.
|
||||
* Entities are serialized along with their versions.
|
||||
*
|
||||
* @tparam Tag Types of tags to serialize.
|
||||
* @tparam Archive Type of output archive.
|
||||
* @param archive A valid reference to an output archive.
|
||||
* @return An object of this type to continue creating the snapshot.
|
||||
*/
|
||||
template<typename... Tag, typename Archive>
|
||||
std::enable_if_t<(sizeof...(Tag) > 1), const Snapshot &>
|
||||
tag(Archive &archive) const {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (tag<Tag>(archive), 0)... };
|
||||
(void)accumulator;
|
||||
const basic_snapshot & component(Archive &archive, It first, It last) const {
|
||||
component<Component...>(archive, first, last, std::index_sequence_for<Component...>{});
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
const Registry<Entity> ®istry;
|
||||
const basic_registry<Entity> *reg;
|
||||
const Entity seed;
|
||||
follow_fn_type *follow;
|
||||
};
|
||||
@@ -262,57 +177,61 @@ private:
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
class SnapshotLoader final {
|
||||
class basic_snapshot_loader {
|
||||
/*! @brief A registry is allowed to create snapshot loaders. */
|
||||
friend class Registry<Entity>;
|
||||
friend class basic_registry<Entity>;
|
||||
|
||||
using assure_fn_type = void(Registry<Entity> &, const Entity, const bool);
|
||||
using force_fn_type = void(basic_registry<Entity> &, const Entity, const bool);
|
||||
using traits_type = entt_traits<std::underlying_type_t<Entity>>;
|
||||
|
||||
SnapshotLoader(Registry<Entity> ®istry, assure_fn_type *assure_fn) ENTT_NOEXCEPT
|
||||
: registry{registry},
|
||||
assure_fn{assure_fn}
|
||||
basic_snapshot_loader(basic_registry<Entity> *source, force_fn_type *fn) ENTT_NOEXCEPT
|
||||
: reg{source},
|
||||
force{fn}
|
||||
{
|
||||
// restore a snapshot as a whole requires a clean registry
|
||||
assert(!registry.capacity());
|
||||
// to restore a snapshot as a whole requires a clean registry
|
||||
ENTT_ASSERT(reg->empty());
|
||||
}
|
||||
|
||||
template<typename Archive>
|
||||
void assure(Archive &archive, bool destroyed) const {
|
||||
Entity length{};
|
||||
void assure(Archive &archive, bool discard) const {
|
||||
typename traits_type::entity_type length{};
|
||||
archive(length);
|
||||
|
||||
while(length--) {
|
||||
Entity entity{};
|
||||
archive(entity);
|
||||
assure_fn(registry, entity, destroyed);
|
||||
Entity entt{};
|
||||
archive(entt);
|
||||
force(*reg, entt, discard);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Type, typename Archive, typename... Args>
|
||||
void assign(Archive &archive, Args... args) const {
|
||||
Entity length{};
|
||||
typename traits_type::entity_type length{};
|
||||
archive(length);
|
||||
|
||||
while(length--) {
|
||||
Entity entity{};
|
||||
Type instance{};
|
||||
archive(entity, instance);
|
||||
static constexpr auto destroyed = false;
|
||||
assure_fn(registry, entity, destroyed);
|
||||
registry.template assign<Type>(args..., entity, static_cast<const Type &>(instance));
|
||||
static constexpr auto discard = false;
|
||||
Entity entt{};
|
||||
|
||||
if constexpr(std::is_empty_v<Type>) {
|
||||
archive(entt);
|
||||
force(*reg, entt, discard);
|
||||
reg->template assign<Type>(args..., entt);
|
||||
} else {
|
||||
Type instance{};
|
||||
archive(entt, instance);
|
||||
force(*reg, entt, discard);
|
||||
reg->template assign<Type>(args..., entt, std::as_const(instance));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Copying a snapshot loader isn't allowed. */
|
||||
SnapshotLoader(const SnapshotLoader &) = delete;
|
||||
/*! @brief Default move constructor. */
|
||||
SnapshotLoader(SnapshotLoader &&) = default;
|
||||
basic_snapshot_loader(basic_snapshot_loader &&) = default;
|
||||
|
||||
/*! @brief Copying a snapshot loader isn't allowed. @return This loader. */
|
||||
SnapshotLoader & operator=(const SnapshotLoader &) = delete;
|
||||
/*! @brief Default move assignment operator. @return This loader. */
|
||||
SnapshotLoader & operator=(SnapshotLoader &&) = default;
|
||||
basic_snapshot_loader & operator=(basic_snapshot_loader &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Restores entities that were in use during serialization.
|
||||
@@ -325,9 +244,9 @@ public:
|
||||
* @return A valid loader to continue restoring data.
|
||||
*/
|
||||
template<typename Archive>
|
||||
const SnapshotLoader & entities(Archive &archive) const {
|
||||
static constexpr auto destroyed = false;
|
||||
assure(archive, destroyed);
|
||||
const basic_snapshot_loader & entities(Archive &archive) const {
|
||||
static constexpr auto discard = false;
|
||||
assure(archive, discard);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -342,9 +261,9 @@ public:
|
||||
* @return A valid loader to continue restoring data.
|
||||
*/
|
||||
template<typename Archive>
|
||||
const SnapshotLoader & destroyed(Archive &archive) const {
|
||||
static constexpr auto destroyed = true;
|
||||
assure(archive, destroyed);
|
||||
const basic_snapshot_loader & destroyed(Archive &archive) const {
|
||||
static constexpr auto discard = true;
|
||||
assure(archive, discard);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -362,55 +281,32 @@ public:
|
||||
* @return A valid loader to continue restoring data.
|
||||
*/
|
||||
template<typename... Component, typename Archive>
|
||||
const SnapshotLoader & component(Archive &archive) const {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (assign<Component>(archive), 0)... };
|
||||
(void)accumulator;
|
||||
const basic_snapshot_loader & component(Archive &archive) const {
|
||||
(assign<Component>(archive), ...);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Restores tags and assigns them to the right entities.
|
||||
*
|
||||
* The template parameter list must be exactly the same used during
|
||||
* serialization. In the event that the entity to which the tag is assigned
|
||||
* doesn't exist yet, the loader will take care to create it with the
|
||||
* version it originally had.
|
||||
*
|
||||
* @tparam Tag Types of tags to restore.
|
||||
* @tparam Archive Type of input archive.
|
||||
* @param archive A valid reference to an input archive.
|
||||
* @return A valid loader to continue restoring data.
|
||||
*/
|
||||
template<typename... Tag, typename Archive>
|
||||
const SnapshotLoader & tag(Archive &archive) const {
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (assign<Tag>(archive, tag_t{}), 0)... };
|
||||
(void)accumulator;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destroys those entities that have neither components nor tags.
|
||||
* @brief Destroys those entities that have no components.
|
||||
*
|
||||
* In case all the entities were serialized but only part of the components
|
||||
* and tags was saved, it could happen that some of the entities have
|
||||
* neither components nor tags once restored.<br/>
|
||||
* was saved, it could happen that some of the entities have no components
|
||||
* once restored.<br/>
|
||||
* This functions helps to identify and destroy those entities.
|
||||
*
|
||||
* @return A valid loader to continue restoring data.
|
||||
*/
|
||||
const SnapshotLoader & orphans() const {
|
||||
registry.orphans([this](const auto entity) {
|
||||
registry.destroy(entity);
|
||||
const basic_snapshot_loader & orphans() const {
|
||||
reg->orphans([this](const auto entt) {
|
||||
reg->destroy(entt);
|
||||
});
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
Registry<Entity> ®istry;
|
||||
assure_fn_type *assure_fn;
|
||||
basic_registry<Entity> *reg;
|
||||
force_fn_type *force;
|
||||
};
|
||||
|
||||
|
||||
@@ -431,63 +327,87 @@ private:
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
*/
|
||||
template<typename Entity>
|
||||
class ContinuousLoader final {
|
||||
using traits_type = entt_traits<Entity>;
|
||||
class basic_continuous_loader {
|
||||
using traits_type = entt_traits<std::underlying_type_t<Entity>>;
|
||||
|
||||
void destroy(Entity entity) {
|
||||
const auto it = remloc.find(entity);
|
||||
void destroy(Entity entt) {
|
||||
const auto it = remloc.find(entt);
|
||||
|
||||
if(it == remloc.cend()) {
|
||||
const auto local = registry.create();
|
||||
remloc.emplace(entity, std::make_pair(local, true));
|
||||
registry.destroy(local);
|
||||
const auto local = reg->create();
|
||||
remloc.emplace(entt, std::make_pair(local, true));
|
||||
reg->destroy(local);
|
||||
}
|
||||
}
|
||||
|
||||
void restore(Entity entity) {
|
||||
const auto it = remloc.find(entity);
|
||||
void restore(Entity entt) {
|
||||
const auto it = remloc.find(entt);
|
||||
|
||||
if(it == remloc.cend()) {
|
||||
const auto local = registry.create();
|
||||
remloc.emplace(entity, std::make_pair(local, true));
|
||||
const auto local = reg->create();
|
||||
remloc.emplace(entt, std::make_pair(local, true));
|
||||
} else {
|
||||
remloc[entity].first =
|
||||
registry.valid(remloc[entity].first)
|
||||
? remloc[entity].first
|
||||
: registry.create();
|
||||
|
||||
remloc[entt].first = reg->valid(remloc[entt].first) ? remloc[entt].first : reg->create();
|
||||
// set the dirty flag
|
||||
remloc[entity].second = true;
|
||||
remloc[entt].second = true;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Type, typename Member>
|
||||
std::enable_if_t<std::is_same<Member, Entity>::value>
|
||||
update(Type &instance, Member Type:: *member) {
|
||||
instance.*member = map(instance.*member);
|
||||
template<typename Container>
|
||||
auto update(int, Container &container)
|
||||
-> decltype(typename Container::mapped_type{}, void()) {
|
||||
// map like container
|
||||
Container other;
|
||||
|
||||
for(auto &&pair: container) {
|
||||
using first_type = std::remove_const_t<typename std::decay_t<decltype(pair)>::first_type>;
|
||||
using second_type = typename std::decay_t<decltype(pair)>::second_type;
|
||||
|
||||
if constexpr(std::is_same_v<first_type, Entity> && std::is_same_v<second_type, Entity>) {
|
||||
other.emplace(map(pair.first), map(pair.second));
|
||||
} else if constexpr(std::is_same_v<first_type, Entity>) {
|
||||
other.emplace(map(pair.first), std::move(pair.second));
|
||||
} else {
|
||||
static_assert(std::is_same_v<second_type, Entity>);
|
||||
other.emplace(std::move(pair.first), map(pair.second));
|
||||
}
|
||||
}
|
||||
|
||||
std::swap(container, other);
|
||||
}
|
||||
|
||||
template<typename Type, typename Member>
|
||||
std::enable_if_t<std::is_same<typename std::iterator_traits<typename Member::iterator>::value_type, Entity>::value>
|
||||
update(Type &instance, Member Type:: *member) {
|
||||
for(auto &entity: instance.*member) {
|
||||
entity = map(entity);
|
||||
template<typename Container>
|
||||
auto update(char, Container &container)
|
||||
-> decltype(typename Container::value_type{}, void()) {
|
||||
// vector like container
|
||||
static_assert(std::is_same_v<typename Container::value_type, Entity>);
|
||||
|
||||
for(auto &&entt: container) {
|
||||
entt = map(entt);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Other, typename Type, typename Member>
|
||||
std::enable_if_t<!std::is_same<Other, Type>::value>
|
||||
update(Other &, Member Type:: *) {}
|
||||
void update([[maybe_unused]] Other &instance, [[maybe_unused]] 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...
|
||||
update(0, instance.*member);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Archive>
|
||||
void assure(Archive &archive, void(ContinuousLoader:: *member)(Entity)) {
|
||||
Entity length{};
|
||||
void assure(Archive &archive, void(basic_continuous_loader:: *member)(Entity)) {
|
||||
typename traits_type::entity_type length{};
|
||||
archive(length);
|
||||
|
||||
while(length--) {
|
||||
Entity entity{};
|
||||
archive(entity);
|
||||
(this->*member)(entity);
|
||||
Entity entt{};
|
||||
archive(entt);
|
||||
(this->*member)(entt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -496,29 +416,31 @@ class ContinuousLoader final {
|
||||
for(auto &&ref: remloc) {
|
||||
const auto local = ref.second.first;
|
||||
|
||||
if(registry.valid(local)) {
|
||||
registry.template reset<Component>(local);
|
||||
if(reg->valid(local)) {
|
||||
reg->template remove_if_exists<Component>(local);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Other, typename Archive, typename Func, typename... Type, typename... Member>
|
||||
void assign(Archive &archive, Func func, Member Type:: *... member) {
|
||||
Entity length{};
|
||||
template<typename Other, typename Archive, typename... Type, typename... Member>
|
||||
void assign(Archive &archive, [[maybe_unused]] Member Type:: *... member) {
|
||||
typename traits_type::entity_type length{};
|
||||
archive(length);
|
||||
|
||||
while(length--) {
|
||||
Entity entity{};
|
||||
Other instance{};
|
||||
Entity entt{};
|
||||
|
||||
archive(entity, instance);
|
||||
restore(entity);
|
||||
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (update(instance, member), 0)... };
|
||||
(void)accumulator;
|
||||
|
||||
func(map(entity), instance);
|
||||
if constexpr(std::is_empty_v<Other>) {
|
||||
archive(entt);
|
||||
restore(entt);
|
||||
reg->template assign_or_replace<Other>(map(entt));
|
||||
} else {
|
||||
Other instance{};
|
||||
archive(entt, instance);
|
||||
(update(instance, member), ...);
|
||||
restore(entt);
|
||||
reg->template assign_or_replace<Other>(map(entt), std::as_const(instance));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -528,21 +450,17 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Constructs a loader that is bound to a given registry.
|
||||
* @param registry A valid reference to a registry.
|
||||
* @param source A valid reference to a registry.
|
||||
*/
|
||||
ContinuousLoader(Registry<entity_type> ®istry) ENTT_NOEXCEPT
|
||||
: registry{registry}
|
||||
basic_continuous_loader(basic_registry<entity_type> &source) ENTT_NOEXCEPT
|
||||
: reg{&source}
|
||||
{}
|
||||
|
||||
/*! @brief Copying a snapshot loader isn't allowed. */
|
||||
ContinuousLoader(const ContinuousLoader &) = delete;
|
||||
/*! @brief Default move constructor. */
|
||||
ContinuousLoader(ContinuousLoader &&) = default;
|
||||
basic_continuous_loader(basic_continuous_loader &&) = default;
|
||||
|
||||
/*! @brief Copying a snapshot loader isn't allowed. @return This loader. */
|
||||
ContinuousLoader & operator=(const ContinuousLoader &) = delete;
|
||||
/*! @brief Default move assignment operator. @return This loader. */
|
||||
ContinuousLoader & operator=(ContinuousLoader &&) = default;
|
||||
basic_continuous_loader & operator=(basic_continuous_loader &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Restores entities that were in use during serialization.
|
||||
@@ -555,8 +473,8 @@ public:
|
||||
* @return A non-const reference to this loader.
|
||||
*/
|
||||
template<typename Archive>
|
||||
ContinuousLoader & entities(Archive &archive) {
|
||||
assure(archive, &ContinuousLoader::restore);
|
||||
basic_continuous_loader & entities(Archive &archive) {
|
||||
assure(archive, &basic_continuous_loader::restore);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -571,8 +489,8 @@ public:
|
||||
* @return A non-const reference to this loader.
|
||||
*/
|
||||
template<typename Archive>
|
||||
ContinuousLoader & destroyed(Archive &archive) {
|
||||
assure(archive, &ContinuousLoader::destroy);
|
||||
basic_continuous_loader & destroyed(Archive &archive) {
|
||||
assure(archive, &basic_continuous_loader::destroy);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -596,45 +514,9 @@ public:
|
||||
* @return A non-const reference to this loader.
|
||||
*/
|
||||
template<typename... Component, typename Archive, typename... Type, typename... Member>
|
||||
ContinuousLoader & component(Archive &archive, Member Type:: *... member) {
|
||||
auto apply = [this](const auto entity, const auto &component) {
|
||||
registry.template accommodate<std::decay_t<decltype(component)>>(entity, component);
|
||||
};
|
||||
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (reset<Component>(), assign<Component>(archive, apply, member...), 0)... };
|
||||
(void)accumulator;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Restores tags and assigns them to the right entities.
|
||||
*
|
||||
* The template parameter list must be exactly the same used during
|
||||
* serialization. In the event that the entity to which the tag is assigned
|
||||
* doesn't exist yet, the loader will take care to create a local
|
||||
* counterpart for it.<br/>
|
||||
* Members can be either data members of type entity_type or containers of
|
||||
* entities. In both cases, the loader will visit them and update the
|
||||
* entities by replacing each one with its local counterpart.
|
||||
*
|
||||
* @tparam Tag Type of tag to restore.
|
||||
* @tparam Archive Type of input archive.
|
||||
* @tparam Type Types of components to update with local counterparts.
|
||||
* @tparam Member Types of members to update with their local counterparts.
|
||||
* @param archive A valid reference to an input archive.
|
||||
* @param member Members to update with their local counterparts.
|
||||
* @return A non-const reference to this loader.
|
||||
*/
|
||||
template<typename... Tag, typename Archive, typename... Type, typename... Member>
|
||||
ContinuousLoader & tag(Archive &archive, Member Type:: *... member) {
|
||||
auto apply = [this](const auto entity, const auto &tag) {
|
||||
registry.template assign<std::decay_t<decltype(tag)>>(tag_t{}, entity, tag);
|
||||
};
|
||||
|
||||
using accumulator_type = int[];
|
||||
accumulator_type accumulator = { 0, (registry.template remove<Tag>(), assign<Tag>(archive, apply, member...), 0)... };
|
||||
(void)accumulator;
|
||||
basic_continuous_loader & component(Archive &archive, Member Type:: *... member) {
|
||||
(reset<Component>(), ...);
|
||||
(assign<Component>(archive, member...), ...);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -646,7 +528,7 @@ public:
|
||||
*
|
||||
* @return A non-const reference to this loader.
|
||||
*/
|
||||
ContinuousLoader & shrink() {
|
||||
basic_continuous_loader & shrink() {
|
||||
auto it = remloc.begin();
|
||||
|
||||
while(it != remloc.cend()) {
|
||||
@@ -657,8 +539,8 @@ public:
|
||||
dirty = false;
|
||||
++it;
|
||||
} else {
|
||||
if(registry.valid(local)) {
|
||||
registry.destroy(local);
|
||||
if(reg->valid(local)) {
|
||||
reg->destroy(local);
|
||||
}
|
||||
|
||||
it = remloc.erase(it);
|
||||
@@ -669,18 +551,18 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destroys those entities that have neither components nor tags.
|
||||
* @brief Destroys those entities that have no components.
|
||||
*
|
||||
* In case all the entities were serialized but only part of the components
|
||||
* and tags was saved, it could happen that some of the entities have
|
||||
* neither components nor tags once restored.<br/>
|
||||
* was saved, it could happen that some of the entities have no components
|
||||
* once restored.<br/>
|
||||
* This functions helps to identify and destroy those entities.
|
||||
*
|
||||
* @return A non-const reference to this loader.
|
||||
*/
|
||||
ContinuousLoader & orphans() {
|
||||
registry.orphans([this](const auto entity) {
|
||||
registry.destroy(entity);
|
||||
basic_continuous_loader & orphans() {
|
||||
reg->orphans([this](const auto entt) {
|
||||
reg->destroy(entt);
|
||||
});
|
||||
|
||||
return *this;
|
||||
@@ -688,37 +570,36 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Tests if a loader knows about a given entity.
|
||||
* @param entity An entity identifier.
|
||||
* @param entt An entity identifier.
|
||||
* @return True if `entity` is managed by the loader, false otherwise.
|
||||
*/
|
||||
bool has(entity_type entity) const ENTT_NOEXCEPT {
|
||||
return (remloc.find(entity) != remloc.cend());
|
||||
bool has(entity_type entt) const ENTT_NOEXCEPT {
|
||||
return (remloc.find(entt) != remloc.cend());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the identifier to which an entity refers.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that isn't managed by the loader results in
|
||||
* undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* loader doesn't knows about the entity.
|
||||
*
|
||||
* @param entity An entity identifier.
|
||||
* @return The identifier to which `entity` refers in the target registry.
|
||||
* @param entt An entity identifier.
|
||||
* @return The local identifier if any, the null entity otherwise.
|
||||
*/
|
||||
entity_type map(entity_type entity) const ENTT_NOEXCEPT {
|
||||
assert(has(entity));
|
||||
return remloc.find(entity)->second.first;
|
||||
entity_type map(entity_type entt) const ENTT_NOEXCEPT {
|
||||
const auto it = remloc.find(entt);
|
||||
entity_type other = null;
|
||||
|
||||
if(it != remloc.cend()) {
|
||||
other = it->second.first;
|
||||
}
|
||||
|
||||
return other;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<Entity, std::pair<Entity, bool>> remloc;
|
||||
Registry<Entity> ®istry;
|
||||
std::unordered_map<entity_type, std::pair<entity_type, bool>> remloc;
|
||||
basic_registry<entity_type> *reg;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_SNAPSHOT_HPP
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
718
src/entt/entity/storage.hpp
Normal file
718
src/entt/entity/storage.hpp
Normal file
@@ -0,0 +1,718 @@
|
||||
#ifndef ENTT_ENTITY_STORAGE_HPP
|
||||
#define ENTT_ENTITY_STORAGE_HPP
|
||||
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "../core/algorithm.hpp"
|
||||
#include "sparse_set.hpp"
|
||||
#include "entity.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Basic storage implementation.
|
||||
*
|
||||
* This class is a refinement of a sparse set that associates an object to an
|
||||
* entity. The main purpose of this class is to extend sparse sets to store
|
||||
* components in a registry. It guarantees fast access both to the elements and
|
||||
* to the entities.
|
||||
*
|
||||
* @note
|
||||
* Entities and objects have the same order. It's guaranteed both in case of raw
|
||||
* access (either to entities or objects) and when using random or input access
|
||||
* iterators.
|
||||
*
|
||||
* @note
|
||||
* Internal data structures arrange elements to maximize performance. Because of
|
||||
* that, there are no guarantees that elements have the expected order when
|
||||
* iterate directly the internal packed array (see `raw` and `size` member
|
||||
* functions for that). Use `begin` and `end` instead.
|
||||
*
|
||||
* @warning
|
||||
* Empty types aren't explicitly instantiated. Temporary objects are returned in
|
||||
* place of the instances of the components and raw access isn't available for
|
||||
* them.
|
||||
*
|
||||
* @sa sparse_set<Entity>
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Type Type of objects assigned to the entities.
|
||||
*/
|
||||
template<typename Entity, typename Type, typename = std::void_t<>>
|
||||
class storage: public sparse_set<Entity> {
|
||||
using underlying_type = sparse_set<Entity>;
|
||||
using traits_type = entt_traits<std::underlying_type_t<Entity>>;
|
||||
|
||||
template<bool Const>
|
||||
class iterator {
|
||||
friend class 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 operator++(), orig;
|
||||
}
|
||||
|
||||
iterator & operator--() ENTT_NOEXCEPT {
|
||||
return ++index, *this;
|
||||
}
|
||||
|
||||
iterator operator--(int) ENTT_NOEXCEPT {
|
||||
iterator orig = *this;
|
||||
return operator--(), orig;
|
||||
}
|
||||
|
||||
iterator & operator+=(const difference_type value) ENTT_NOEXCEPT {
|
||||
index -= value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
|
||||
return iterator{instances, index-value};
|
||||
}
|
||||
|
||||
iterator & operator-=(const difference_type value) ENTT_NOEXCEPT {
|
||||
return (*this += -value);
|
||||
}
|
||||
|
||||
iterator operator-(const difference_type value) const ENTT_NOEXCEPT {
|
||||
return (*this + -value);
|
||||
}
|
||||
|
||||
difference_type operator-(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return other.index - index;
|
||||
}
|
||||
|
||||
reference operator[](const difference_type value) const ENTT_NOEXCEPT {
|
||||
const auto pos = size_type(index-value-1);
|
||||
return (*instances)[pos];
|
||||
}
|
||||
|
||||
bool operator==(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return other.index == index;
|
||||
}
|
||||
|
||||
bool operator!=(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
bool operator<(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return index > other.index;
|
||||
}
|
||||
|
||||
bool operator>(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return index < other.index;
|
||||
}
|
||||
|
||||
bool operator<=(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this > other);
|
||||
}
|
||||
|
||||
bool operator>=(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this < other);
|
||||
}
|
||||
|
||||
pointer operator->() const ENTT_NOEXCEPT {
|
||||
const auto pos = size_type(index-1);
|
||||
return &(*instances)[pos];
|
||||
}
|
||||
|
||||
reference operator*() const ENTT_NOEXCEPT {
|
||||
return *operator->();
|
||||
}
|
||||
|
||||
private:
|
||||
instance_type *instances;
|
||||
index_type index;
|
||||
};
|
||||
|
||||
public:
|
||||
/*! @brief Type of the objects associated with the entities. */
|
||||
using object_type = Type;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Random access iterator type. */
|
||||
using iterator_type = iterator<false>;
|
||||
/*! @brief Constant random access iterator type. */
|
||||
using const_iterator_type = iterator<true>;
|
||||
|
||||
/**
|
||||
* @brief Increases the capacity of a storage.
|
||||
*
|
||||
* If the new capacity is greater than the current capacity, new storage is
|
||||
* allocated, otherwise the method does nothing.
|
||||
*
|
||||
* @param cap Desired capacity.
|
||||
*/
|
||||
void reserve(const size_type cap) {
|
||||
underlying_type::reserve(cap);
|
||||
instances.reserve(cap);
|
||||
}
|
||||
|
||||
/*! @brief Requests the removal of unused capacity. */
|
||||
void shrink_to_fit() {
|
||||
underlying_type::shrink_to_fit();
|
||||
instances.shrink_to_fit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Direct access to the array of objects.
|
||||
*
|
||||
* The returned pointer is such that range `[raw(), raw() + size()]` is
|
||||
* always a valid range, even if the container is empty.
|
||||
*
|
||||
* @note
|
||||
* There are no guarantees on the order, even though either `sort` or
|
||||
* `respect` has been previously invoked. Internal data structures arrange
|
||||
* elements to maximize performance. Accessing them directly gives a
|
||||
* performance boost but less guarantees. Use `begin` and `end` if you want
|
||||
* to iterate the storage in the expected order.
|
||||
*
|
||||
* @return A pointer to the array of objects.
|
||||
*/
|
||||
const object_type * raw() const ENTT_NOEXCEPT {
|
||||
return instances.data();
|
||||
}
|
||||
|
||||
/*! @copydoc raw */
|
||||
object_type * raw() ENTT_NOEXCEPT {
|
||||
return const_cast<object_type *>(std::as_const(*this).raw());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the beginning.
|
||||
*
|
||||
* The returned iterator points to the first instance of the given type. If
|
||||
* the storage is empty, the returned iterator will be equal to `end()`.
|
||||
*
|
||||
* @note
|
||||
* Random access iterators stay true to the order imposed by a call to
|
||||
* either `sort` or `respect`.
|
||||
*
|
||||
* @return An iterator to the first instance of the given type.
|
||||
*/
|
||||
const_iterator_type cbegin() const ENTT_NOEXCEPT {
|
||||
const typename traits_type::difference_type pos = underlying_type::size();
|
||||
return const_iterator_type{&instances, pos};
|
||||
}
|
||||
|
||||
/*! @copydoc cbegin */
|
||||
const_iterator_type begin() const ENTT_NOEXCEPT {
|
||||
return cbegin();
|
||||
}
|
||||
|
||||
/*! @copydoc begin */
|
||||
iterator_type begin() ENTT_NOEXCEPT {
|
||||
const typename traits_type::difference_type pos = underlying_type::size();
|
||||
return iterator_type{&instances, pos};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the end.
|
||||
*
|
||||
* The returned iterator points to the element following the last instance
|
||||
* of the given type. Attempting to dereference the returned iterator
|
||||
* results in undefined behavior.
|
||||
*
|
||||
* @note
|
||||
* Random access iterators stay true to the order imposed by a call to
|
||||
* either `sort` or `respect`.
|
||||
*
|
||||
* @return An iterator to the element following the last instance of the
|
||||
* given type.
|
||||
*/
|
||||
const_iterator_type cend() const ENTT_NOEXCEPT {
|
||||
return const_iterator_type{&instances, {}};
|
||||
}
|
||||
|
||||
/*! @copydoc cend */
|
||||
const_iterator_type end() const ENTT_NOEXCEPT {
|
||||
return cend();
|
||||
}
|
||||
|
||||
/*! @copydoc end */
|
||||
iterator_type end() ENTT_NOEXCEPT {
|
||||
return iterator_type{&instances, {}};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the object associated with an entity.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that doesn't belong to the storage results in
|
||||
* undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* storage doesn't contain the given entity.
|
||||
*
|
||||
* @param entt A valid entity identifier.
|
||||
* @return The object associated with the entity.
|
||||
*/
|
||||
const object_type & get(const entity_type entt) const {
|
||||
return instances[underlying_type::index(entt)];
|
||||
}
|
||||
|
||||
/*! @copydoc get */
|
||||
object_type & get(const entity_type entt) {
|
||||
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 {
|
||||
return underlying_type::has(entt) ? (instances.data() + underlying_type::index(entt)) : nullptr;
|
||||
}
|
||||
|
||||
/*! @copydoc try_get */
|
||||
object_type * try_get(const entity_type entt) {
|
||||
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.
|
||||
*/
|
||||
template<typename... Args>
|
||||
void construct(const entity_type entt, Args &&... args) {
|
||||
if constexpr(std::is_aggregate_v<object_type>) {
|
||||
instances.push_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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns one or more entities to a storage and constructs their
|
||||
* objects from a given instance.
|
||||
*
|
||||
* @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 input iterator.
|
||||
* @param first An iterator to the first element of the range of entities.
|
||||
* @param last An iterator past the last element of the range of entities.
|
||||
* @param value An instance of the object to construct.
|
||||
*/
|
||||
template<typename It>
|
||||
std::enable_if_t<std::is_same_v<typename std::iterator_traits<It>::value_type, entity_type>, void>
|
||||
construct(It first, It last, const object_type &value = {}) {
|
||||
instances.insert(instances.end(), std::distance(first, last), value);
|
||||
// entities go after components in case constructors throw
|
||||
underlying_type::construct(first, last);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns one or more entities to a storage and constructs their
|
||||
* objects from a given range.
|
||||
*
|
||||
* @sa construct
|
||||
*
|
||||
* @tparam EIt Type of input iterator.
|
||||
* @tparam CIt Type of input iterator.
|
||||
* @param first An iterator to the first element of the range of entities.
|
||||
* @param last An iterator past the last element of the range of entities.
|
||||
* @param value An iterator to the first element of the range of objects.
|
||||
*/
|
||||
template<typename EIt, typename CIt>
|
||||
std::enable_if_t<std::is_same_v<typename std::iterator_traits<EIt>::value_type, entity_type>, void>
|
||||
construct(EIt first, EIt last, CIt value) {
|
||||
instances.insert(instances.end(), value, value + std::distance(first, last));
|
||||
// entities go after components in case constructors throw
|
||||
underlying_type::construct(first, last);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes an entity from a storage and destroys its object.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that doesn't belong to the storage results in
|
||||
* undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* storage doesn't contain the given entity.
|
||||
*
|
||||
* @param entt A valid entity identifier.
|
||||
*/
|
||||
void destroy(const entity_type entt) {
|
||||
auto other = std::move(instances.back());
|
||||
instances[underlying_type::index(entt)] = std::move(other);
|
||||
instances.pop_back();
|
||||
underlying_type::destroy(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Swaps entities and objects in the internal packed arrays.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to swap entities that don't belong to the sparse set results
|
||||
* in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* sparse set doesn't contain the given entities.
|
||||
*
|
||||
* @param lhs A valid entity identifier.
|
||||
* @param rhs A valid entity identifier.
|
||||
*/
|
||||
void swap(const entity_type lhs, const entity_type rhs) override {
|
||||
std::swap(instances[underlying_type::index(lhs)], instances[underlying_type::index(rhs)]);
|
||||
underlying_type::swap(lhs, rhs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sort elements according to the given comparison function.
|
||||
*
|
||||
* Sort the elements so that iterating the range with a couple of iterators
|
||||
* returns them in the expected order. See `begin` and `end` for more
|
||||
* details.
|
||||
*
|
||||
* The comparison function object must return `true` if the first element
|
||||
* is _less_ than the second one, `false` otherwise. The signature of the
|
||||
* comparison function should be equivalent to one of the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* bool(const Entity, const Entity);
|
||||
* bool(const Type &, const Type &);
|
||||
* @endcode
|
||||
*
|
||||
* Moreover, the comparison function object shall induce a
|
||||
* _strict weak ordering_ on the values.
|
||||
*
|
||||
* The sort function oject must offer a member function template
|
||||
* `operator()` that accepts three arguments:
|
||||
*
|
||||
* * An iterator to the first element of the range to sort.
|
||||
* * An iterator past the last element of the range to sort.
|
||||
* * A comparison function to use to compare the elements.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @warning
|
||||
* Empty types are never instantiated. Therefore, only comparison function
|
||||
* objects that require to return entities rather than components are
|
||||
* accepted.
|
||||
*
|
||||
* @tparam Compare Type of comparison function object.
|
||||
* @tparam Sort Type of sort function object.
|
||||
* @tparam Args Types of arguments to forward to the sort function object.
|
||||
* @param first An iterator to the first element of the range to sort.
|
||||
* @param last An iterator past the last element of the range to sort.
|
||||
* @param compare A valid comparison function object.
|
||||
* @param algo A valid sort function object.
|
||||
* @param args Arguments to forward to the sort function object, if any.
|
||||
*/
|
||||
template<typename Compare, typename Sort = std_sort, typename... Args>
|
||||
void sort(iterator_type first, iterator_type last, Compare compare, Sort algo = Sort{}, Args &&... args) {
|
||||
ENTT_ASSERT(!(last < first));
|
||||
ENTT_ASSERT(!(last > end()));
|
||||
|
||||
const auto from = underlying_type::begin() + std::distance(begin(), first);
|
||||
const auto to = from + std::distance(first, last);
|
||||
|
||||
const auto apply = [this](const auto lhs, const auto rhs) {
|
||||
std::swap(instances[underlying_type::index(lhs)], instances[underlying_type::index(rhs)]);
|
||||
};
|
||||
|
||||
if constexpr(std::is_invocable_v<Compare, const object_type &, const object_type &>) {
|
||||
underlying_type::arrange(from, to, std::move(apply), [this, compare = std::move(compare)](const auto lhs, const auto rhs) {
|
||||
return compare(std::as_const(instances[underlying_type::index(lhs)]), std::as_const(instances[underlying_type::index(rhs)]));
|
||||
}, std::move(algo), std::forward<Args>(args)...);
|
||||
} else {
|
||||
underlying_type::arrange(from, to, std::move(apply), std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
/*! @brief Clears a storage. */
|
||||
void clear() {
|
||||
underlying_type::clear();
|
||||
instances.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<object_type> instances;
|
||||
};
|
||||
|
||||
|
||||
/*! @copydoc storage */
|
||||
template<typename Entity, typename Type>
|
||||
class storage<Entity, Type, std::enable_if_t<ENTT_ENABLE_ETO(Type)>>: public sparse_set<Entity> {
|
||||
using traits_type = entt_traits<std::underlying_type_t<Entity>>;
|
||||
using underlying_type = sparse_set<Entity>;
|
||||
|
||||
class iterator {
|
||||
friend class 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 operator++(), orig;
|
||||
}
|
||||
|
||||
iterator & operator--() ENTT_NOEXCEPT {
|
||||
return ++index, *this;
|
||||
}
|
||||
|
||||
iterator operator--(int) ENTT_NOEXCEPT {
|
||||
iterator orig = *this;
|
||||
return operator--(), orig;
|
||||
}
|
||||
|
||||
iterator & operator+=(const difference_type value) ENTT_NOEXCEPT {
|
||||
index -= value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
|
||||
return iterator{index-value};
|
||||
}
|
||||
|
||||
iterator & operator-=(const difference_type value) ENTT_NOEXCEPT {
|
||||
return (*this += -value);
|
||||
}
|
||||
|
||||
iterator operator-(const difference_type value) const ENTT_NOEXCEPT {
|
||||
return (*this + -value);
|
||||
}
|
||||
|
||||
difference_type operator-(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return other.index - index;
|
||||
}
|
||||
|
||||
reference operator[](const difference_type) const ENTT_NOEXCEPT {
|
||||
return {};
|
||||
}
|
||||
|
||||
bool operator==(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return other.index == index;
|
||||
}
|
||||
|
||||
bool operator!=(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
bool operator<(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return index > other.index;
|
||||
}
|
||||
|
||||
bool operator>(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return index < other.index;
|
||||
}
|
||||
|
||||
bool operator<=(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this > other);
|
||||
}
|
||||
|
||||
bool operator>=(const iterator &other) const ENTT_NOEXCEPT {
|
||||
return !(*this < other);
|
||||
}
|
||||
|
||||
pointer operator->() const ENTT_NOEXCEPT {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
reference operator*() const ENTT_NOEXCEPT {
|
||||
return {};
|
||||
}
|
||||
|
||||
private:
|
||||
index_type index;
|
||||
};
|
||||
|
||||
public:
|
||||
/*! @brief Type of the objects associated with the entities. */
|
||||
using object_type = Type;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Random access iterator type. */
|
||||
using iterator_type = iterator;
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the beginning.
|
||||
*
|
||||
* The returned iterator points to the first instance of the given type. If
|
||||
* the storage is empty, the returned iterator will be equal to `end()`.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed by a call to either `sort`
|
||||
* or `respect`.
|
||||
*
|
||||
* @return An iterator to the first instance of the given type.
|
||||
*/
|
||||
iterator_type cbegin() const ENTT_NOEXCEPT {
|
||||
const typename traits_type::difference_type pos = underlying_type::size();
|
||||
return iterator_type{pos};
|
||||
}
|
||||
|
||||
/*! @copydoc cbegin */
|
||||
iterator_type begin() const ENTT_NOEXCEPT {
|
||||
return cbegin();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns an iterator to the end.
|
||||
*
|
||||
* The returned iterator points to the element following the last instance
|
||||
* of the given type. Attempting to dereference the returned iterator
|
||||
* results in undefined behavior.
|
||||
*
|
||||
* @note
|
||||
* Input iterators stay true to the order imposed by a call to either `sort`
|
||||
* or `respect`.
|
||||
*
|
||||
* @return An iterator to the element following the last instance of the
|
||||
* given type.
|
||||
*/
|
||||
iterator_type cend() const ENTT_NOEXCEPT {
|
||||
return iterator_type{};
|
||||
}
|
||||
|
||||
/*! @copydoc cend */
|
||||
iterator_type end() const ENTT_NOEXCEPT {
|
||||
return cend();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the object associated with an entity.
|
||||
*
|
||||
* @note
|
||||
* Empty types aren't explicitly instantiated. Therefore, this function
|
||||
* always returns a temporary object.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an entity that doesn't belong to the storage results in
|
||||
* undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* storage doesn't contain the given entity.
|
||||
*
|
||||
* @param entt A valid entity identifier.
|
||||
* @return The object associated with the entity.
|
||||
*/
|
||||
object_type get([[maybe_unused]] const entity_type entt) const {
|
||||
ENTT_ASSERT(underlying_type::has(entt));
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns an entity to a storage and constructs its object.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
template<typename... Args>
|
||||
void construct(const entity_type entt, Args &&...) {
|
||||
underlying_type::construct(entt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns one or more entities to a storage.
|
||||
*
|
||||
* @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 input iterator.
|
||||
* @param first An iterator to the first element of the range of entities.
|
||||
* @param last An iterator past the last element of the range of entities.
|
||||
*/
|
||||
template<typename It>
|
||||
std::enable_if_t<std::is_same_v<typename std::iterator_traits<It>::value_type, entity_type>, void>
|
||||
construct(It first, It last, const object_type & = {}) {
|
||||
underlying_type::construct(first, last);
|
||||
}
|
||||
|
||||
/*! @copydoc storage::sort */
|
||||
template<typename Compare, typename Sort = std_sort, typename... Args>
|
||||
void sort(iterator_type first, iterator_type last, Compare compare, Sort algo = Sort{}, Args &&... args) {
|
||||
ENTT_ASSERT(!(last < first));
|
||||
ENTT_ASSERT(!(last > end()));
|
||||
|
||||
const auto from = underlying_type::begin() + std::distance(begin(), first);
|
||||
const auto to = from + std::distance(first, last);
|
||||
|
||||
underlying_type::sort(from, to, std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -1,23 +0,0 @@
|
||||
#ifndef ENTT_ENTITY_UTILITY_HPP
|
||||
#define ENTT_ENTITY_UTILITY_HPP
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/*! @brief Tag class type used to disambiguate overloads. */
|
||||
struct tag_t final {};
|
||||
|
||||
|
||||
/*! @brief Persistent view type used to disambiguate overloads. */
|
||||
struct persistent_t final {};
|
||||
|
||||
|
||||
/*! @brief Raw view type used to disambiguate overloads. */
|
||||
struct raw_t final {};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_ENTITY_UTILITY_HPP
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,20 +1,27 @@
|
||||
#include "core/algorithm.hpp"
|
||||
#include "core/attribute.h"
|
||||
#include "core/family.hpp"
|
||||
#include "core/hashed_string.hpp"
|
||||
#include "core/ident.hpp"
|
||||
#include "core/monostate.hpp"
|
||||
#include "core/type_info.hpp"
|
||||
#include "core/type_traits.hpp"
|
||||
#include "core/utility.hpp"
|
||||
#include "entity/actor.hpp"
|
||||
#include "entity/attachee.hpp"
|
||||
#include "entity/entity.hpp"
|
||||
#include "entity/entt_traits.hpp"
|
||||
#include "entity/group.hpp"
|
||||
#include "entity/helper.hpp"
|
||||
#include "entity/prototype.hpp"
|
||||
#include "entity/observer.hpp"
|
||||
#include "entity/registry.hpp"
|
||||
#include "entity/runtime_view.hpp"
|
||||
#include "entity/snapshot.hpp"
|
||||
#include "entity/sparse_set.hpp"
|
||||
#include "entity/utility.hpp"
|
||||
#include "entity/storage.hpp"
|
||||
#include "entity/view.hpp"
|
||||
#include "locator/locator.hpp"
|
||||
#include "meta/factory.hpp"
|
||||
#include "meta/meta.hpp"
|
||||
#include "meta/policy.hpp"
|
||||
#include "process/process.hpp"
|
||||
#include "process/scheduler.hpp"
|
||||
#include "resource/cache.hpp"
|
||||
|
||||
3
src/entt/fwd.hpp
Normal file
3
src/entt/fwd.hpp
Normal file
@@ -0,0 +1,3 @@
|
||||
#include "entity/fwd.hpp"
|
||||
#include "resource/fwd.hpp"
|
||||
#include "signal/fwd.hpp"
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <cassert>
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
@@ -23,20 +22,20 @@ namespace entt {
|
||||
* @tparam Service Type of service managed by the locator.
|
||||
*/
|
||||
template<typename Service>
|
||||
struct ServiceLocator final {
|
||||
struct service_locator {
|
||||
/*! @brief Type of service offered. */
|
||||
using service_type = Service;
|
||||
|
||||
/*! @brief Default constructor, deleted on purpose. */
|
||||
ServiceLocator() = delete;
|
||||
service_locator() = delete;
|
||||
/*! @brief Default destructor, deleted on purpose. */
|
||||
~ServiceLocator() = delete;
|
||||
~service_locator() = delete;
|
||||
|
||||
/**
|
||||
* @brief Tests if a valid service implementation is set.
|
||||
* @return True if the service is set, false otherwise.
|
||||
*/
|
||||
inline static bool empty() ENTT_NOEXCEPT {
|
||||
static bool empty() ENTT_NOEXCEPT {
|
||||
return !static_cast<bool>(service);
|
||||
}
|
||||
|
||||
@@ -50,7 +49,7 @@ struct ServiceLocator final {
|
||||
*
|
||||
* @return A reference to the service implementation currently set, if any.
|
||||
*/
|
||||
inline static std::weak_ptr<Service> get() ENTT_NOEXCEPT {
|
||||
static std::weak_ptr<Service> get() ENTT_NOEXCEPT {
|
||||
return service;
|
||||
}
|
||||
|
||||
@@ -68,7 +67,7 @@ struct ServiceLocator final {
|
||||
*
|
||||
* @return A reference to the service implementation currently set, if any.
|
||||
*/
|
||||
inline static Service & ref() ENTT_NOEXCEPT {
|
||||
static Service & ref() ENTT_NOEXCEPT {
|
||||
return *service;
|
||||
}
|
||||
|
||||
@@ -79,7 +78,7 @@ struct ServiceLocator final {
|
||||
* @param args Parameters to use to construct the service.
|
||||
*/
|
||||
template<typename Impl = Service, typename... Args>
|
||||
inline static void set(Args &&... args) {
|
||||
static void set(Args &&... args) {
|
||||
service = std::make_shared<Impl>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
@@ -87,8 +86,8 @@ struct ServiceLocator final {
|
||||
* @brief Sets or replaces a service.
|
||||
* @param ptr Service to use to replace the current one.
|
||||
*/
|
||||
inline static void set(std::shared_ptr<Service> ptr) {
|
||||
assert(static_cast<bool>(ptr));
|
||||
static void set(std::shared_ptr<Service> ptr) {
|
||||
ENTT_ASSERT(static_cast<bool>(ptr));
|
||||
service = std::move(ptr);
|
||||
}
|
||||
|
||||
@@ -97,20 +96,16 @@ struct ServiceLocator final {
|
||||
*
|
||||
* The service is no longer valid after a reset.
|
||||
*/
|
||||
inline static void reset() {
|
||||
static void reset() {
|
||||
service.reset();
|
||||
}
|
||||
|
||||
private:
|
||||
static std::shared_ptr<Service> service;
|
||||
inline static std::shared_ptr<Service> service = nullptr;
|
||||
};
|
||||
|
||||
|
||||
template<typename Service>
|
||||
std::shared_ptr<Service> ServiceLocator<Service>::service{};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_LOCATOR_LOCATOR_HPP
|
||||
#endif
|
||||
|
||||
880
src/entt/meta/factory.hpp
Normal file
880
src/entt/meta/factory.hpp
Normal file
@@ -0,0 +1,880 @@
|
||||
#ifndef ENTT_META_FACTORY_HPP
|
||||
#define ENTT_META_FACTORY_HPP
|
||||
|
||||
|
||||
#include <tuple>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "../core/type_traits.hpp"
|
||||
#include "../core/utility.hpp"
|
||||
#include "policy.hpp"
|
||||
#include "meta.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @cond TURN_OFF_DOXYGEN
|
||||
* Internal details not to be documented.
|
||||
*/
|
||||
|
||||
|
||||
namespace internal {
|
||||
|
||||
|
||||
template<typename>
|
||||
struct meta_function_helper;
|
||||
|
||||
|
||||
template<typename Ret, typename... Args>
|
||||
struct meta_function_helper<Ret(Args...)> {
|
||||
using return_type = std::remove_cv_t<std::remove_reference_t<Ret>>;
|
||||
using args_type = std::tuple<std::remove_cv_t<std::remove_reference_t<Args>>...>;
|
||||
|
||||
static constexpr std::index_sequence_for<Args...> index_sequence{};
|
||||
static constexpr auto is_const = false;
|
||||
|
||||
static auto arg(typename internal::meta_func_node::size_type index) ENTT_NOEXCEPT {
|
||||
return std::array<meta_type_node *, sizeof...(Args)>{{meta_info<Args>::resolve()...}}[index];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename Ret, typename... Args>
|
||||
struct meta_function_helper<Ret(Args...) const>: meta_function_helper<Ret(Args...)> {
|
||||
static constexpr auto is_const = true;
|
||||
};
|
||||
|
||||
|
||||
template<typename Ret, typename... Args, typename Class>
|
||||
constexpr meta_function_helper<Ret(Args...)>
|
||||
to_meta_function_helper(Ret(Class:: *)(Args...));
|
||||
|
||||
|
||||
template<typename Ret, typename... Args, typename Class>
|
||||
constexpr meta_function_helper<Ret(Args...) const>
|
||||
to_meta_function_helper(Ret(Class:: *)(Args...) const);
|
||||
|
||||
|
||||
template<typename Ret, typename... Args>
|
||||
constexpr meta_function_helper<Ret(Args...)>
|
||||
to_meta_function_helper(Ret(*)(Args...));
|
||||
|
||||
|
||||
constexpr void to_meta_function_helper(...);
|
||||
|
||||
|
||||
template<typename Candidate>
|
||||
using meta_function_helper_t = decltype(to_meta_function_helper(std::declval<Candidate>()));
|
||||
|
||||
|
||||
template<typename Type, typename... Args, std::size_t... Indexes>
|
||||
meta_any construct(meta_any * const args, std::index_sequence<Indexes...>) {
|
||||
[[maybe_unused]] auto direct = std::make_tuple((args+Indexes)->try_cast<Args>()...);
|
||||
meta_any any{};
|
||||
|
||||
if(((std::get<Indexes>(direct) || (args+Indexes)->convert<Args>()) && ...)) {
|
||||
any = Type{(std::get<Indexes>(direct) ? *std::get<Indexes>(direct) : (args+Indexes)->cast<Args>())...};
|
||||
}
|
||||
|
||||
return any;
|
||||
}
|
||||
|
||||
|
||||
template<bool Const, typename Type, auto Data>
|
||||
bool setter([[maybe_unused]] meta_any instance, [[maybe_unused]] meta_any index, [[maybe_unused]] meta_any value) {
|
||||
bool accepted = false;
|
||||
|
||||
if constexpr(!Const) {
|
||||
if constexpr(std::is_function_v<std::remove_reference_t<std::remove_pointer_t<decltype(Data)>>> || std::is_member_function_pointer_v<decltype(Data)>) {
|
||||
using helper_type = meta_function_helper_t<decltype(Data)>;
|
||||
using data_type = std::tuple_element_t<!std::is_member_function_pointer_v<decltype(Data)>, typename helper_type::args_type>;
|
||||
static_assert(std::is_invocable_v<decltype(Data), Type &, data_type>);
|
||||
auto * const clazz = instance.try_cast<Type>();
|
||||
auto * const direct = value.try_cast<data_type>();
|
||||
|
||||
if(clazz && (direct || value.convert<data_type>())) {
|
||||
std::invoke(Data, *clazz, direct ? *direct : value.cast<data_type>());
|
||||
accepted = true;
|
||||
}
|
||||
} else if constexpr(std::is_member_object_pointer_v<decltype(Data)>) {
|
||||
using data_type = std::remove_cv_t<std::remove_reference_t<decltype(std::declval<Type>().*Data)>>;
|
||||
static_assert(std::is_invocable_v<decltype(Data), Type *>);
|
||||
auto * const clazz = instance.try_cast<Type>();
|
||||
|
||||
if constexpr(std::is_array_v<data_type>) {
|
||||
using underlying_type = std::remove_extent_t<data_type>;
|
||||
auto * const direct = value.try_cast<underlying_type>();
|
||||
auto * const idx = index.try_cast<std::size_t>();
|
||||
|
||||
if(clazz && idx && (direct || value.convert<underlying_type>())) {
|
||||
std::invoke(Data, clazz)[*idx] = direct ? *direct : value.cast<underlying_type>();
|
||||
accepted = true;
|
||||
}
|
||||
} else {
|
||||
auto * const direct = value.try_cast<data_type>();
|
||||
|
||||
if(clazz && (direct || value.convert<data_type>())) {
|
||||
std::invoke(Data, clazz) = (direct ? *direct : value.cast<data_type>());
|
||||
accepted = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
static_assert(std::is_pointer_v<decltype(Data)>);
|
||||
using data_type = std::remove_cv_t<std::remove_reference_t<decltype(*Data)>>;
|
||||
|
||||
if constexpr(std::is_array_v<data_type>) {
|
||||
using underlying_type = std::remove_extent_t<data_type>;
|
||||
auto * const direct = value.try_cast<underlying_type>();
|
||||
auto * const idx = index.try_cast<std::size_t>();
|
||||
|
||||
if(idx && (direct || value.convert<underlying_type>())) {
|
||||
(*Data)[*idx] = (direct ? *direct : value.cast<underlying_type>());
|
||||
accepted = true;
|
||||
}
|
||||
} else {
|
||||
auto * const direct = value.try_cast<data_type>();
|
||||
|
||||
if(direct || value.convert<data_type>()) {
|
||||
*Data = (direct ? *direct : value.cast<data_type>());
|
||||
accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return accepted;
|
||||
}
|
||||
|
||||
|
||||
template<typename Type, auto Data, typename Policy>
|
||||
meta_any getter([[maybe_unused]] meta_any instance, [[maybe_unused]] meta_any index) {
|
||||
auto dispatch = [](auto &&value) {
|
||||
if constexpr(std::is_same_v<Policy, as_void_t>) {
|
||||
return meta_any{std::in_place_type<void>, std::forward<decltype(value)>(value)};
|
||||
} else if constexpr(std::is_same_v<Policy, as_alias_t>) {
|
||||
return meta_any{std::ref(std::forward<decltype(value)>(value))};
|
||||
} else {
|
||||
static_assert(std::is_same_v<Policy, as_is_t>);
|
||||
return meta_any{std::forward<decltype(value)>(value)};
|
||||
}
|
||||
};
|
||||
|
||||
if constexpr(std::is_function_v<std::remove_reference_t<std::remove_pointer_t<decltype(Data)>>> || std::is_member_function_pointer_v<decltype(Data)>) {
|
||||
static_assert(std::is_invocable_v<decltype(Data), Type &>);
|
||||
auto * const clazz = instance.try_cast<Type>();
|
||||
return clazz ? dispatch(std::invoke(Data, *clazz)) : meta_any{};
|
||||
} else if constexpr(std::is_member_object_pointer_v<decltype(Data)>) {
|
||||
using data_type = std::remove_cv_t<std::remove_reference_t<decltype(std::declval<Type>().*Data)>>;
|
||||
static_assert(std::is_invocable_v<decltype(Data), Type *>);
|
||||
auto * const clazz = instance.try_cast<Type>();
|
||||
|
||||
if constexpr(std::is_array_v<data_type>) {
|
||||
auto * const idx = index.try_cast<std::size_t>();
|
||||
return (clazz && idx) ? dispatch(std::invoke(Data, clazz)[*idx]) : meta_any{};
|
||||
} else {
|
||||
return clazz ? dispatch(std::invoke(Data, clazz)) : meta_any{};
|
||||
}
|
||||
} else {
|
||||
static_assert(std::is_pointer_v<std::decay_t<decltype(Data)>>);
|
||||
|
||||
if constexpr(std::is_array_v<std::remove_pointer_t<decltype(Data)>>) {
|
||||
auto * const idx = index.try_cast<std::size_t>();
|
||||
return idx ? dispatch((*Data)[*idx]) : meta_any{};
|
||||
} else {
|
||||
return dispatch(*Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<typename Type, auto Candidate, typename Policy, std::size_t... Indexes>
|
||||
meta_any invoke([[maybe_unused]] meta_any instance, meta_any *args, std::index_sequence<Indexes...>) {
|
||||
using helper_type = meta_function_helper_t<decltype(Candidate)>;
|
||||
|
||||
auto dispatch = [](auto *... params) {
|
||||
if constexpr(std::is_void_v<typename helper_type::return_type> || std::is_same_v<Policy, as_void_t>) {
|
||||
std::invoke(Candidate, *params...);
|
||||
return meta_any{std::in_place_type<void>};
|
||||
} else if constexpr(std::is_same_v<Policy, as_alias_t>) {
|
||||
return meta_any{std::ref(std::invoke(Candidate, *params...))};
|
||||
} else {
|
||||
static_assert(std::is_same_v<Policy, as_is_t>);
|
||||
return meta_any{std::invoke(Candidate, *params...)};
|
||||
}
|
||||
};
|
||||
|
||||
[[maybe_unused]] const auto direct = std::make_tuple([](meta_any *any, auto *value) {
|
||||
using arg_type = std::remove_reference_t<decltype(*value)>;
|
||||
|
||||
if(!value && any->convert<arg_type>()) {
|
||||
value = any->try_cast<arg_type>();
|
||||
}
|
||||
|
||||
return value;
|
||||
}(args+Indexes, (args+Indexes)->try_cast<std::tuple_element_t<Indexes, typename helper_type::args_type>>())...);
|
||||
|
||||
if constexpr(std::is_function_v<std::remove_reference_t<std::remove_pointer_t<decltype(Candidate)>>>) {
|
||||
return (std::get<Indexes>(direct) && ...) ? dispatch(std::get<Indexes>(direct)...) : meta_any{};
|
||||
} else {
|
||||
auto * const clazz = instance.try_cast<Type>();
|
||||
return (clazz && (std::get<Indexes>(direct) && ...)) ? dispatch(clazz, std::get<Indexes>(direct)...) : meta_any{};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Internal details not to be documented.
|
||||
* @endcond TURN_OFF_DOXYGEN
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @brief Meta factory to be used for reflection purposes.
|
||||
*
|
||||
* The meta factory is an utility class used to reflect types, data members 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.
|
||||
*/
|
||||
template<typename...>
|
||||
class meta_factory;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Extended meta factory to be used for reflection purposes.
|
||||
* @tparam Type Reflected type for which the factory was created.
|
||||
* @tparam Spec Property specialization pack used to disambiguate overloads.
|
||||
*/
|
||||
template<typename Type, typename... Spec>
|
||||
class meta_factory<Type, Spec...>: public meta_factory<Type> {
|
||||
bool exists(const meta_any &key, const internal::meta_prop_node *node) ENTT_NOEXCEPT {
|
||||
return node && (node->key() == key || exists(key, node->next));
|
||||
}
|
||||
|
||||
template<std::size_t Step = 0, std::size_t... Index, typename... Property, typename... Other>
|
||||
void unpack(std::index_sequence<Index...>, std::tuple<Property...> property, Other &&... other) {
|
||||
unroll<Step>(choice<3>, std::move(std::get<Index>(property))..., std::forward<Other>(other)...);
|
||||
}
|
||||
|
||||
template<std::size_t Step = 0, typename... Property, typename... Other>
|
||||
void unroll(choice_t<3>, std::tuple<Property...> property, Other &&... other) {
|
||||
unpack<Step>(std::index_sequence_for<Property...>{}, std::move(property), std::forward<Other>(other)...);
|
||||
}
|
||||
|
||||
template<std::size_t Step = 0, typename... Property, typename... Other>
|
||||
void unroll(choice_t<2>, std::pair<Property...> property, Other &&... other) {
|
||||
assign<Step>(std::move(property.first), std::move(property.second));
|
||||
unroll<Step+1>(choice<3>, std::forward<Other>(other)...);
|
||||
}
|
||||
|
||||
template<std::size_t Step = 0, typename Property, typename... Other>
|
||||
std::enable_if_t<!std::is_invocable_v<Property>>
|
||||
unroll(choice_t<1>, Property &&property, Other &&... other) {
|
||||
assign<Step>(std::forward<Property>(property));
|
||||
unroll<Step+1>(choice<3>, std::forward<Other>(other)...);
|
||||
}
|
||||
|
||||
template<std::size_t Step = 0, typename Func, typename... Other>
|
||||
void unroll(choice_t<0>, Func &&invocable, Other &&... other) {
|
||||
unroll<Step>(choice<3>, std::forward<Func>(invocable)(), std::forward<Other>(other)...);
|
||||
}
|
||||
|
||||
template<std::size_t>
|
||||
void unroll(choice_t<0>) {}
|
||||
|
||||
template<std::size_t = 0, typename Key, typename... Value>
|
||||
void assign(Key &&key, Value &&... value) {
|
||||
static const auto property{std::make_tuple(std::forward<Key>(key), std::forward<Value>(value)...)};
|
||||
|
||||
static internal::meta_prop_node node{
|
||||
nullptr,
|
||||
[]() -> meta_any {
|
||||
return std::get<0>(property);
|
||||
},
|
||||
[]() -> meta_any {
|
||||
if constexpr(sizeof...(Value) == 0) {
|
||||
return {};
|
||||
} else {
|
||||
return std::get<1>(property);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ENTT_ASSERT(!exists(node.key(), *curr));
|
||||
node.next = *curr;
|
||||
*curr = &node;
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs an extended factory from a given node.
|
||||
* @param target The underlying node to which to assign the properties.
|
||||
*/
|
||||
meta_factory(entt::internal::meta_prop_node **target) ENTT_NOEXCEPT
|
||||
: curr{target}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Assigns a property to the last meta object created.
|
||||
*
|
||||
* Both the key and the value (if any) must be at least copy constructible.
|
||||
*
|
||||
* @tparam PropertyOrKey Type of the property or property key.
|
||||
* @tparam Value Optional type of the property value.
|
||||
* @param property_or_key Property or property key.
|
||||
* @param value Optional property value.
|
||||
* @return A meta factory for the parent type.
|
||||
*/
|
||||
template<typename PropertyOrKey, typename... Value>
|
||||
auto prop(PropertyOrKey &&property_or_key, Value &&... value) && {
|
||||
if constexpr(sizeof...(Value) == 0) {
|
||||
unroll(choice<3>, std::forward<PropertyOrKey>(property_or_key));
|
||||
} else {
|
||||
assign(std::forward<PropertyOrKey>(property_or_key), std::forward<Value>(value)...);
|
||||
}
|
||||
|
||||
return meta_factory<Type, Spec..., PropertyOrKey, Value...>{curr};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns properties to the last meta object created.
|
||||
*
|
||||
* Both the keys and the values (if any) must be at least copy
|
||||
* constructible.
|
||||
*
|
||||
* @tparam Property Types of the properties.
|
||||
* @param property Properties to assign to the last meta object created.
|
||||
* @return A meta factory for the parent type.
|
||||
*/
|
||||
template <typename... Property>
|
||||
auto props(Property... property) && {
|
||||
unroll(choice<3>, std::forward<Property>(property)...);
|
||||
return meta_factory<Type, Spec..., Property...>{curr};
|
||||
}
|
||||
|
||||
private:
|
||||
entt::internal::meta_prop_node **curr;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Basic meta factory to be used for reflection purposes.
|
||||
* @tparam Type Reflected type for which the factory was created.
|
||||
*/
|
||||
template<typename Type>
|
||||
class meta_factory<Type> {
|
||||
template<typename Node>
|
||||
bool exists(const Node *candidate, const Node *node) ENTT_NOEXCEPT {
|
||||
return node && (node == candidate || exists(candidate, node->next));
|
||||
}
|
||||
|
||||
template<typename Node>
|
||||
bool exists(const ENTT_ID_TYPE alias, const Node *node) ENTT_NOEXCEPT {
|
||||
return node && (node->alias == alias || exists(alias, node->next));
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Extends a meta type by assigning it an alias.
|
||||
* @param value Unique identifier.
|
||||
* @return An extended meta factory for the given type.
|
||||
*/
|
||||
auto alias(const ENTT_ID_TYPE value) ENTT_NOEXCEPT {
|
||||
auto * const node = internal::meta_info<Type>::resolve();
|
||||
|
||||
ENTT_ASSERT(!exists(value, *internal::meta_info<>::global));
|
||||
ENTT_ASSERT(!exists(node, *internal::meta_info<>::global));
|
||||
node->alias = value;
|
||||
node->next = *internal::meta_info<>::global;
|
||||
*internal::meta_info<>::global = node;
|
||||
|
||||
return meta_factory<Type, Type>{&node->prop};
|
||||
}
|
||||
|
||||
/*! @copydoc alias */
|
||||
[[deprecated("Use ::alias instead")]]
|
||||
auto type(const ENTT_ID_TYPE value) ENTT_NOEXCEPT {
|
||||
return alias(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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>
|
||||
auto 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{
|
||||
type,
|
||||
nullptr,
|
||||
&internal::meta_info<Base>::resolve,
|
||||
[](void *instance) ENTT_NOEXCEPT -> void * {
|
||||
return static_cast<Base *>(static_cast<Type *>(instance));
|
||||
}
|
||||
};
|
||||
|
||||
ENTT_ASSERT(!exists(&node, type->base));
|
||||
node.next = type->base;
|
||||
type->base = &node;
|
||||
|
||||
return meta_factory<Type>{};
|
||||
}
|
||||
|
||||
/**
|
||||
* @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>
|
||||
auto 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{
|
||||
type,
|
||||
nullptr,
|
||||
&internal::meta_info<To>::resolve,
|
||||
[](const void *instance) -> meta_any {
|
||||
return static_cast<To>(*static_cast<const Type *>(instance));
|
||||
}
|
||||
};
|
||||
|
||||
ENTT_ASSERT(!exists(&node, type->conv));
|
||||
node.next = type->conv;
|
||||
type->conv = &node;
|
||||
|
||||
return meta_factory<Type>{};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns a meta conversion function to a meta type.
|
||||
*
|
||||
* Conversion functions can be either free functions or member
|
||||
* functions.<br/>
|
||||
* In case of free functions, they must accept a const reference to an
|
||||
* instance of the parent type as an argument. In case of member functions,
|
||||
* they should have no arguments at all.
|
||||
*
|
||||
* @tparam Candidate The actual function to use for the conversion.
|
||||
* @return A meta factory for the parent type.
|
||||
*/
|
||||
template<auto Candidate>
|
||||
auto conv() ENTT_NOEXCEPT {
|
||||
using conv_type = std::invoke_result_t<decltype(Candidate), Type &>;
|
||||
auto * const type = internal::meta_info<Type>::resolve();
|
||||
|
||||
static internal::meta_conv_node node{
|
||||
type,
|
||||
nullptr,
|
||||
&internal::meta_info<conv_type>::resolve,
|
||||
[](const void *instance) -> meta_any {
|
||||
return std::invoke(Candidate, *static_cast<const Type *>(instance));
|
||||
}
|
||||
};
|
||||
|
||||
ENTT_ASSERT(!exists(&node, type->conv));
|
||||
node.next = type->conv;
|
||||
type->conv = &node;
|
||||
|
||||
return meta_factory<Type>{};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns a meta constructor to a meta type.
|
||||
*
|
||||
* Free functions can be assigned to meta types in the role of constructors.
|
||||
* All that is required is that they return an instance of the underlying
|
||||
* type.<br/>
|
||||
* From a client's point of view, nothing changes if a constructor of a meta
|
||||
* type is a built-in one or a free function.
|
||||
*
|
||||
* @tparam Func The actual function to use as a constructor.
|
||||
* @tparam Policy Optional policy (no policy set by default).
|
||||
* @return An extended meta factory for the parent type.
|
||||
*/
|
||||
template<auto Func, typename Policy = as_is_t>
|
||||
auto ctor() ENTT_NOEXCEPT {
|
||||
using helper_type = internal::meta_function_helper_t<decltype(Func)>;
|
||||
static_assert(std::is_same_v<typename helper_type::return_type, Type>);
|
||||
auto * const type = internal::meta_info<Type>::resolve();
|
||||
|
||||
static internal::meta_ctor_node node{
|
||||
type,
|
||||
nullptr,
|
||||
nullptr,
|
||||
helper_type::index_sequence.size(),
|
||||
&helper_type::arg,
|
||||
[](meta_any * const any) {
|
||||
return internal::invoke<Type, Func, Policy>({}, any, helper_type::index_sequence);
|
||||
}
|
||||
};
|
||||
|
||||
ENTT_ASSERT(!exists(&node, type->ctor));
|
||||
node.next = type->ctor;
|
||||
type->ctor = &node;
|
||||
|
||||
return meta_factory<Type, std::integral_constant<decltype(Func), Func>>{&node.prop};
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
* @return An extended meta factory for the parent type.
|
||||
*/
|
||||
template<typename... Args>
|
||||
auto ctor() ENTT_NOEXCEPT {
|
||||
using helper_type = internal::meta_function_helper_t<Type(*)(Args...)>;
|
||||
auto * const type = internal::meta_info<Type>::resolve();
|
||||
|
||||
static internal::meta_ctor_node node{
|
||||
type,
|
||||
nullptr,
|
||||
nullptr,
|
||||
helper_type::index_sequence.size(),
|
||||
&helper_type::arg,
|
||||
[](meta_any * const any) {
|
||||
return internal::construct<Type, std::remove_cv_t<std::remove_reference_t<Args>>...>(any, helper_type::index_sequence);
|
||||
}
|
||||
};
|
||||
|
||||
ENTT_ASSERT(!exists(&node, type->ctor));
|
||||
node.next = type->ctor;
|
||||
type->ctor = &node;
|
||||
|
||||
return meta_factory<Type, Type(Args...)>{&node.prop};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns a meta destructor to a meta type.
|
||||
*
|
||||
* Free functions can be assigned to meta types in the role of destructors.
|
||||
* The signature of the function should identical to the following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(Type &);
|
||||
* @endcode
|
||||
*
|
||||
* The purpose is to give users the ability to free up resources that
|
||||
* require special treatment before an object is actually destroyed.
|
||||
*
|
||||
* @tparam Func The actual function to use as a destructor.
|
||||
* @return A meta factory for the parent type.
|
||||
*/
|
||||
template<auto Func>
|
||||
auto 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{
|
||||
type,
|
||||
[](void *instance) {
|
||||
if(instance) {
|
||||
std::invoke(Func, *static_cast<Type *>(instance));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ENTT_ASSERT(!type->dtor);
|
||||
type->dtor = &node;
|
||||
|
||||
return meta_factory<Type>{};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns a meta data to a meta type.
|
||||
*
|
||||
* Both data members and static and global variables, as well as constants
|
||||
* of any kind, can be assigned to a meta type.<br/>
|
||||
* From a client's point of view, all the variables associated with the
|
||||
* reflected object will appear as if they were part of the type itself.
|
||||
*
|
||||
* @tparam Data The actual variable to attach to the meta type.
|
||||
* @tparam Policy Optional policy (no policy set by default).
|
||||
* @param alias Unique identifier.
|
||||
* @return An extended meta factory for the parent type.
|
||||
*/
|
||||
template<auto Data, typename Policy = as_is_t>
|
||||
auto data(const ENTT_ID_TYPE alias) ENTT_NOEXCEPT {
|
||||
auto * const type = internal::meta_info<Type>::resolve();
|
||||
internal::meta_data_node *curr = nullptr;
|
||||
|
||||
if constexpr(std::is_same_v<Type, decltype(Data)>) {
|
||||
static_assert(std::is_same_v<Policy, as_is_t>);
|
||||
|
||||
static internal::meta_data_node node{
|
||||
{},
|
||||
type,
|
||||
nullptr,
|
||||
nullptr,
|
||||
true,
|
||||
true,
|
||||
&internal::meta_info<Type>::resolve,
|
||||
[](meta_any, meta_any, meta_any) { return false; },
|
||||
[](meta_any, meta_any) -> meta_any { return Data; }
|
||||
};
|
||||
|
||||
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{
|
||||
{},
|
||||
type,
|
||||
nullptr,
|
||||
nullptr,
|
||||
std::is_const_v<data_type>,
|
||||
!std::is_member_object_pointer_v<decltype(Data)>,
|
||||
&internal::meta_info<data_type>::resolve,
|
||||
&internal::setter<std::is_const_v<data_type>, Type, Data>,
|
||||
&internal::getter<Type, Data, Policy>
|
||||
};
|
||||
|
||||
curr = &node;
|
||||
} else {
|
||||
static_assert(std::is_pointer_v<std::decay_t<decltype(Data)>>);
|
||||
using data_type = std::remove_pointer_t<std::decay_t<decltype(Data)>>;
|
||||
|
||||
static internal::meta_data_node node{
|
||||
{},
|
||||
type,
|
||||
nullptr,
|
||||
nullptr,
|
||||
std::is_const_v<data_type>,
|
||||
!std::is_member_object_pointer_v<decltype(Data)>,
|
||||
&internal::meta_info<data_type>::resolve,
|
||||
&internal::setter<std::is_const_v<data_type>, Type, Data>,
|
||||
&internal::getter<Type, Data, Policy>
|
||||
};
|
||||
|
||||
curr = &node;
|
||||
}
|
||||
|
||||
ENTT_ASSERT(!exists(alias, type->data));
|
||||
ENTT_ASSERT(!exists(curr, type->data));
|
||||
curr->alias = alias;
|
||||
curr->next = type->data;
|
||||
type->data = curr;
|
||||
|
||||
return meta_factory<Type, std::integral_constant<decltype(Data), Data>>{&curr->prop};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns a meta data to a meta type by means of its setter and
|
||||
* getter.
|
||||
*
|
||||
* Setters and getters can be either free functions, member functions or a
|
||||
* mix of them.<br/>
|
||||
* In case of free functions, setters and getters must accept a reference to
|
||||
* an instance of the parent type as their first argument. A setter has then
|
||||
* an extra argument of a type convertible to that of the parameter to
|
||||
* set.<br/>
|
||||
* In case of member functions, getters have no arguments at all, while
|
||||
* setters has an argument of a type convertible to that of the parameter to
|
||||
* set.
|
||||
*
|
||||
* @tparam Setter The actual function to use as a setter.
|
||||
* @tparam Getter The actual function to use as a getter.
|
||||
* @tparam Policy Optional policy (no policy set by default).
|
||||
* @param alias Unique identifier.
|
||||
* @return An extended meta factory for the parent type.
|
||||
*/
|
||||
template<auto Setter, auto Getter, typename Policy = as_is_t>
|
||||
auto data(const ENTT_ID_TYPE alias) ENTT_NOEXCEPT {
|
||||
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{
|
||||
{},
|
||||
type,
|
||||
nullptr,
|
||||
nullptr,
|
||||
false,
|
||||
false,
|
||||
&internal::meta_info<underlying_type>::resolve,
|
||||
&internal::setter<false, Type, Setter>,
|
||||
&internal::getter<Type, Getter, Policy>
|
||||
};
|
||||
|
||||
ENTT_ASSERT(!exists(alias, type->data));
|
||||
ENTT_ASSERT(!exists(&node, type->data));
|
||||
node.alias = alias;
|
||||
node.next = type->data;
|
||||
type->data = &node;
|
||||
|
||||
return meta_factory<Type, std::integral_constant<decltype(Setter), Setter>, std::integral_constant<decltype(Getter), Getter>>{&node.prop};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns a meta funcion to a meta type.
|
||||
*
|
||||
* Both member functions and free functions can be assigned to a meta
|
||||
* type.<br/>
|
||||
* From a client's point of view, all the functions associated with the
|
||||
* reflected object will appear as if they were part of the type itself.
|
||||
*
|
||||
* @tparam Candidate The actual function to attach to the meta type.
|
||||
* @tparam Policy Optional policy (no policy set by default).
|
||||
* @param alias Unique identifier.
|
||||
* @return An extended meta factory for the parent type.
|
||||
*/
|
||||
template<auto Candidate, typename Policy = as_is_t>
|
||||
auto func(const ENTT_ID_TYPE alias) ENTT_NOEXCEPT {
|
||||
using helper_type = internal::meta_function_helper_t<decltype(Candidate)>;
|
||||
auto * const type = internal::meta_info<Type>::resolve();
|
||||
|
||||
static internal::meta_func_node node{
|
||||
{},
|
||||
type,
|
||||
nullptr,
|
||||
nullptr,
|
||||
helper_type::index_sequence.size(),
|
||||
helper_type::is_const,
|
||||
!std::is_member_function_pointer_v<decltype(Candidate)>,
|
||||
&internal::meta_info<std::conditional_t<std::is_same_v<Policy, as_void_t>, void, typename helper_type::return_type>>::resolve,
|
||||
&helper_type::arg,
|
||||
[](meta_any instance, meta_any *args) {
|
||||
return internal::invoke<Type, Candidate, Policy>(std::move(instance), args, helper_type::index_sequence);
|
||||
}
|
||||
};
|
||||
|
||||
ENTT_ASSERT(!exists(alias, type->func));
|
||||
ENTT_ASSERT(!exists(&node, type->func));
|
||||
node.alias = alias;
|
||||
node.next = type->func;
|
||||
type->func = &node;
|
||||
|
||||
return meta_factory<Type, std::integral_constant<decltype(Candidate), Candidate>>{&node.prop};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resets a meta type and all its parts.
|
||||
*
|
||||
* This function resets a meta type and all its data members, member
|
||||
* functions and properties, as well as its constructors, destructors and
|
||||
* conversion functions if any.<br/>
|
||||
* Base classes aren't reset but the link between the two types is removed.
|
||||
*
|
||||
* @return An extended meta factory for the given type.
|
||||
*/
|
||||
auto reset() ENTT_NOEXCEPT {
|
||||
auto * const node = internal::meta_info<Type>::resolve();
|
||||
auto **it = internal::meta_info<>::global;
|
||||
|
||||
while(*it && *it != node) {
|
||||
it = &(*it)->next;
|
||||
}
|
||||
|
||||
if(*it) {
|
||||
*it = (*it)->next;
|
||||
}
|
||||
|
||||
const auto unregister_all = y_combinator{
|
||||
[](auto &&self, auto **curr, auto... member) {
|
||||
while(*curr) {
|
||||
auto *prev = *curr;
|
||||
(self(&(prev->*member)), ...);
|
||||
*curr = prev->next;
|
||||
prev->next = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
unregister_all(&node->prop);
|
||||
unregister_all(&node->base);
|
||||
unregister_all(&node->conv);
|
||||
unregister_all(&node->ctor, &internal::meta_ctor_node::prop);
|
||||
unregister_all(&node->data, &internal::meta_data_node::prop);
|
||||
unregister_all(&node->func, &internal::meta_func_node::prop);
|
||||
|
||||
node->alias = {};
|
||||
node->next = nullptr;
|
||||
node->dtor = nullptr;
|
||||
|
||||
return meta_factory<Type, Type>{&node->prop};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Utility function to use for reflection.
|
||||
*
|
||||
* This is the point from which everything starts.<br/>
|
||||
* By invoking this function with a type that is not yet reflected, a meta type
|
||||
* is created to which it will be possible to attach meta objects through a
|
||||
* dedicated factory.
|
||||
*
|
||||
* @tparam Type Type to reflect.
|
||||
* @return An meta factory for the given type.
|
||||
*/
|
||||
template<typename Type>
|
||||
inline meta_factory<Type> meta() ENTT_NOEXCEPT {
|
||||
auto * const node = internal::meta_info<Type>::resolve();
|
||||
// extended meta factory to allow assigning properties to opaque meta types
|
||||
return meta_factory<Type, Type>{&node->prop};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @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();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Returns the meta type associated with a given alias.
|
||||
* @param alias Unique identifier.
|
||||
* @return The meta type associated with the given alias, if any.
|
||||
*/
|
||||
inline meta_type resolve(const ENTT_ID_TYPE alias) ENTT_NOEXCEPT {
|
||||
return internal::find_if([alias](const auto *curr) {
|
||||
return curr->alias == alias;
|
||||
}, *internal::meta_info<>::global);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Iterates all the reflected types.
|
||||
* @tparam Op Type of the function object to invoke.
|
||||
* @param op A valid function object.
|
||||
*/
|
||||
template<typename Op>
|
||||
inline std::enable_if_t<std::is_invocable_v<Op, meta_type>, void>
|
||||
resolve(Op op) {
|
||||
internal::visit<meta_type>(std::move(op), *internal::meta_info<>::global);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
1572
src/entt/meta/meta.hpp
Normal file
1572
src/entt/meta/meta.hpp
Normal file
File diff suppressed because it is too large
Load Diff
27
src/entt/meta/policy.hpp
Normal file
27
src/entt/meta/policy.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef ENTT_META_POLICY_HPP
|
||||
#define ENTT_META_POLICY_HPP
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/*! @brief Empty class type used to request the _as alias_ policy. */
|
||||
struct as_alias_t {};
|
||||
|
||||
|
||||
/*! @brief Disambiguation tag. */
|
||||
constexpr as_alias_t as_alias;
|
||||
|
||||
|
||||
/*! @brief Empty class type used to request the _as-is_ policy. */
|
||||
struct as_is_t {};
|
||||
|
||||
|
||||
/*! @brief Empty class type used to request the _as void_ policy. */
|
||||
struct as_void_t {};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -2,9 +2,8 @@
|
||||
#define ENTT_PROCESS_PROCESS_HPP
|
||||
|
||||
|
||||
#include <type_traits>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
|
||||
|
||||
@@ -31,12 +30,13 @@ namespace entt {
|
||||
* update.
|
||||
*
|
||||
* * @code{.cpp}
|
||||
* void init(void *);
|
||||
* void init();
|
||||
* @endcode
|
||||
*
|
||||
* It's invoked at the first tick, immediately before an update. The `void *`
|
||||
* parameter is an opaque pointer to user data (if any) forwarded directly to
|
||||
* the process during an update.
|
||||
* It's invoked when the process joins the running queue of a scheduler. This
|
||||
* happens as soon as it's attached to the scheduler if the process is a top
|
||||
* level one, otherwise when it replaces its parent if the process is a
|
||||
* continuation.
|
||||
*
|
||||
* * @code{.cpp}
|
||||
* void succeeded();
|
||||
@@ -64,14 +64,14 @@ namespace entt {
|
||||
* `succeed` and `fail` protected member functions and even pause or unpause the
|
||||
* process itself.
|
||||
*
|
||||
* @sa Scheduler
|
||||
* @sa scheduler
|
||||
*
|
||||
* @tparam Derived Actual type of process that extends the class template.
|
||||
* @tparam Delta Type to use to provide elapsed time.
|
||||
*/
|
||||
template<typename Derived, typename Delta>
|
||||
class Process {
|
||||
enum class State: unsigned int {
|
||||
class process {
|
||||
enum class state: unsigned int {
|
||||
UNINITIALIZED = 0,
|
||||
RUNNING,
|
||||
PAUSED,
|
||||
@@ -81,41 +81,41 @@ class Process {
|
||||
FINISHED
|
||||
};
|
||||
|
||||
template<State state>
|
||||
using tag = std::integral_constant<State, state>;
|
||||
template<state value>
|
||||
using state_value_t = std::integral_constant<state, value>;
|
||||
|
||||
template<typename Target = Derived>
|
||||
auto tick(int, tag<State::UNINITIALIZED>, void *data)
|
||||
-> decltype(std::declval<Target>().init(data)) {
|
||||
static_cast<Target *>(this)->init(data);
|
||||
auto tick(int, state_value_t<state::UNINITIALIZED>)
|
||||
-> decltype(std::declval<Target>().init()) {
|
||||
static_cast<Target *>(this)->init();
|
||||
}
|
||||
|
||||
template<typename Target = Derived>
|
||||
auto tick(int, tag<State::RUNNING>, Delta delta, void *data)
|
||||
auto tick(int, state_value_t<state::RUNNING>, Delta delta, void *data)
|
||||
-> decltype(std::declval<Target>().update(delta, data)) {
|
||||
static_cast<Target *>(this)->update(delta, data);
|
||||
}
|
||||
|
||||
template<typename Target = Derived>
|
||||
auto tick(int, tag<State::SUCCEEDED>)
|
||||
auto tick(int, state_value_t<state::SUCCEEDED>)
|
||||
-> decltype(std::declval<Target>().succeeded()) {
|
||||
static_cast<Target *>(this)->succeeded();
|
||||
}
|
||||
|
||||
template<typename Target = Derived>
|
||||
auto tick(int, tag<State::FAILED>)
|
||||
auto tick(int, state_value_t<state::FAILED>)
|
||||
-> decltype(std::declval<Target>().failed()) {
|
||||
static_cast<Target *>(this)->failed();
|
||||
}
|
||||
|
||||
template<typename Target = Derived>
|
||||
auto tick(int, tag<State::ABORTED>)
|
||||
auto tick(int, state_value_t<state::ABORTED>)
|
||||
-> decltype(std::declval<Target>().aborted()) {
|
||||
static_cast<Target *>(this)->aborted();
|
||||
}
|
||||
|
||||
template<State S, typename... Args>
|
||||
void tick(char, tag<S>, Args &&...) const ENTT_NOEXCEPT {}
|
||||
template<state value, typename... Args>
|
||||
void tick(char, state_value_t<value>, Args &&...) const ENTT_NOEXCEPT {}
|
||||
|
||||
protected:
|
||||
/**
|
||||
@@ -126,7 +126,7 @@ protected:
|
||||
*/
|
||||
void succeed() ENTT_NOEXCEPT {
|
||||
if(alive()) {
|
||||
current = State::SUCCEEDED;
|
||||
current = state::SUCCEEDED;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ protected:
|
||||
*/
|
||||
void fail() ENTT_NOEXCEPT {
|
||||
if(alive()) {
|
||||
current = State::FAILED;
|
||||
current = state::FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,8 +149,8 @@ protected:
|
||||
* running.
|
||||
*/
|
||||
void pause() ENTT_NOEXCEPT {
|
||||
if(current == State::RUNNING) {
|
||||
current = State::PAUSED;
|
||||
if(current == state::RUNNING) {
|
||||
current = state::PAUSED;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,8 +161,8 @@ protected:
|
||||
* paused.
|
||||
*/
|
||||
void unpause() ENTT_NOEXCEPT {
|
||||
if(current == State::PAUSED) {
|
||||
current = State::RUNNING;
|
||||
if(current == state::PAUSED) {
|
||||
current = state::RUNNING;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,8 +171,8 @@ public:
|
||||
using delta_type = Delta;
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
virtual ~Process() ENTT_NOEXCEPT {
|
||||
static_assert(std::is_base_of<Process, Derived>::value, "!");
|
||||
virtual ~process() {
|
||||
static_assert(std::is_base_of_v<process, Derived>);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,12 +183,12 @@ public:
|
||||
*
|
||||
* @param immediately Requests an immediate operation.
|
||||
*/
|
||||
void abort(const bool immediately = false) ENTT_NOEXCEPT {
|
||||
void abort(const bool immediately = false) {
|
||||
if(alive()) {
|
||||
current = State::ABORTED;
|
||||
current = state::ABORTED;
|
||||
|
||||
if(immediately) {
|
||||
tick(0);
|
||||
tick({});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,7 +198,7 @@ public:
|
||||
* @return True if the process is still alive, false otherwise.
|
||||
*/
|
||||
bool alive() const ENTT_NOEXCEPT {
|
||||
return current == State::RUNNING || current == State::PAUSED;
|
||||
return current == state::RUNNING || current == state::PAUSED;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -206,7 +206,7 @@ public:
|
||||
* @return True if the process is terminated, false otherwise.
|
||||
*/
|
||||
bool dead() const ENTT_NOEXCEPT {
|
||||
return current == State::FINISHED;
|
||||
return current == state::FINISHED;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -214,7 +214,7 @@ public:
|
||||
* @return True if the process is paused, false otherwise.
|
||||
*/
|
||||
bool paused() const ENTT_NOEXCEPT {
|
||||
return current == State::PAUSED;
|
||||
return current == state::PAUSED;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -232,12 +232,13 @@ public:
|
||||
*/
|
||||
void tick(const Delta delta, void *data = nullptr) {
|
||||
switch (current) {
|
||||
case State::UNINITIALIZED:
|
||||
tick(0, tag<State::UNINITIALIZED>{}, data);
|
||||
current = State::RUNNING;
|
||||
// no break on purpose, tasks are executed immediately
|
||||
case State::RUNNING:
|
||||
tick(0, tag<State::RUNNING>{}, delta, data);
|
||||
case state::UNINITIALIZED:
|
||||
tick(0, state_value_t<state::UNINITIALIZED>{});
|
||||
current = state::RUNNING;
|
||||
break;
|
||||
case state::RUNNING:
|
||||
tick(0, state_value_t<state::RUNNING>{}, delta, data);
|
||||
break;
|
||||
default:
|
||||
// suppress warnings
|
||||
break;
|
||||
@@ -245,18 +246,18 @@ public:
|
||||
|
||||
// if it's dead, it must be notified and removed immediately
|
||||
switch(current) {
|
||||
case State::SUCCEEDED:
|
||||
tick(0, tag<State::SUCCEEDED>{});
|
||||
current = State::FINISHED;
|
||||
case state::SUCCEEDED:
|
||||
tick(0, state_value_t<state::SUCCEEDED>{});
|
||||
current = state::FINISHED;
|
||||
break;
|
||||
case State::FAILED:
|
||||
tick(0, tag<State::FAILED>{});
|
||||
current = State::FINISHED;
|
||||
case state::FAILED:
|
||||
tick(0, state_value_t<state::FAILED>{});
|
||||
current = state::FINISHED;
|
||||
stopped = true;
|
||||
break;
|
||||
case State::ABORTED:
|
||||
tick(0, tag<State::ABORTED>{});
|
||||
current = State::FINISHED;
|
||||
case state::ABORTED:
|
||||
tick(0, state_value_t<state::ABORTED>{});
|
||||
current = state::FINISHED;
|
||||
stopped = true;
|
||||
break;
|
||||
default:
|
||||
@@ -266,7 +267,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
State current{State::UNINITIALIZED};
|
||||
state current{state::UNINITIALIZED};
|
||||
bool stopped{false};
|
||||
};
|
||||
|
||||
@@ -304,21 +305,21 @@ private:
|
||||
* create them internally each and avery time a lambda or a functor is used as
|
||||
* a process.
|
||||
*
|
||||
* @sa Process
|
||||
* @sa Scheduler
|
||||
* @sa process
|
||||
* @sa scheduler
|
||||
*
|
||||
* @tparam Func Actual type of process.
|
||||
* @tparam Delta Type to use to provide elapsed time.
|
||||
*/
|
||||
template<typename Func, typename Delta>
|
||||
struct ProcessAdaptor: Process<ProcessAdaptor<Func, Delta>, Delta>, private Func {
|
||||
struct process_adaptor: process<process_adaptor<Func, Delta>, Delta>, private Func {
|
||||
/**
|
||||
* @brief Constructs a process adaptor from a lambda or a functor.
|
||||
* @tparam Args Types of arguments to use to initialize the actual process.
|
||||
* @param args Parameters to use to initialize the actual process.
|
||||
*/
|
||||
template<typename... Args>
|
||||
ProcessAdaptor(Args &&... args)
|
||||
process_adaptor(Args &&... args)
|
||||
: Func{std::forward<Args>(args)...}
|
||||
{}
|
||||
|
||||
@@ -336,4 +337,4 @@ struct ProcessAdaptor: Process<ProcessAdaptor<Func, Delta>, Delta>, private Func
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_PROCESS_PROCESS_HPP
|
||||
#endif
|
||||
|
||||
@@ -30,23 +30,23 @@ namespace entt {
|
||||
* @code{.cpp}
|
||||
* scheduler.attach([](auto delta, void *, auto succeed, auto fail) {
|
||||
* // code
|
||||
* }).then<MyProcess>(arguments...);
|
||||
* }).then<my_process>(arguments...);
|
||||
* @endcode
|
||||
*
|
||||
* In order to invoke all scheduled processes, call the `update` member function
|
||||
* passing it the elapsed time to forward to the tasks.
|
||||
*
|
||||
* @sa Process
|
||||
* @sa process
|
||||
*
|
||||
* @tparam Delta Type to use to provide elapsed time.
|
||||
*/
|
||||
template<typename Delta>
|
||||
class Scheduler final {
|
||||
struct ProcessHandler final {
|
||||
class scheduler {
|
||||
struct process_handler {
|
||||
using instance_type = std::unique_ptr<void, void(*)(void *)>;
|
||||
using update_fn_type = bool(ProcessHandler &, Delta, void *);
|
||||
using abort_fn_type = void(ProcessHandler &, bool);
|
||||
using next_type = std::unique_ptr<ProcessHandler>;
|
||||
using update_fn_type = bool(process_handler &, Delta, void *);
|
||||
using abort_fn_type = void(process_handler &, bool);
|
||||
using next_type = std::unique_ptr<process_handler>;
|
||||
|
||||
instance_type instance;
|
||||
update_fn_type *update;
|
||||
@@ -54,30 +54,33 @@ class Scheduler final {
|
||||
next_type next;
|
||||
};
|
||||
|
||||
struct Then final {
|
||||
Then(ProcessHandler *handler)
|
||||
: handler{handler}
|
||||
{}
|
||||
struct continuation {
|
||||
continuation(process_handler *ref)
|
||||
: handler{ref}
|
||||
{
|
||||
ENTT_ASSERT(handler);
|
||||
}
|
||||
|
||||
template<typename Proc, typename... Args>
|
||||
decltype(auto) then(Args &&... args) && {
|
||||
static_assert(std::is_base_of<Process<Proc, Delta>, Proc>::value, "!");
|
||||
handler = Scheduler::then<Proc>(handler, std::forward<Args>(args)...);
|
||||
return std::move(*this);
|
||||
continuation then(Args &&... args) {
|
||||
static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>);
|
||||
auto proc = typename process_handler::instance_type{new Proc{std::forward<Args>(args)...}, &scheduler::deleter<Proc>};
|
||||
handler->next.reset(new process_handler{std::move(proc), &scheduler::update<Proc>, &scheduler::abort<Proc>, nullptr});
|
||||
handler = handler->next.get();
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename Func>
|
||||
decltype(auto) then(Func &&func) && {
|
||||
using Proc = ProcessAdaptor<std::decay_t<Func>, Delta>;
|
||||
return std::move(*this).template then<Proc>(std::forward<Func>(func));
|
||||
continuation then(Func &&func) {
|
||||
return then<process_adaptor<std::decay_t<Func>, Delta>>(std::forward<Func>(func));
|
||||
}
|
||||
|
||||
private:
|
||||
ProcessHandler *handler;
|
||||
process_handler *handler;
|
||||
};
|
||||
|
||||
template<typename Proc>
|
||||
static bool update(ProcessHandler &handler, const Delta delta, void *data) {
|
||||
static bool update(process_handler &handler, const Delta delta, void *data) {
|
||||
auto *process = static_cast<Proc *>(handler.instance.get());
|
||||
process->tick(delta, data);
|
||||
|
||||
@@ -86,7 +89,8 @@ class Scheduler final {
|
||||
if(dead) {
|
||||
if(handler.next && !process->rejected()) {
|
||||
handler = std::move(*handler.next);
|
||||
dead = handler.update(handler, delta, data);
|
||||
// forces the process to exit the uninitialized state
|
||||
dead = handler.update(handler, {}, nullptr);
|
||||
} else {
|
||||
handler.instance.reset();
|
||||
}
|
||||
@@ -96,7 +100,7 @@ class Scheduler final {
|
||||
}
|
||||
|
||||
template<typename Proc>
|
||||
static void abort(ProcessHandler &handler, const bool immediately) {
|
||||
static void abort(process_handler &handler, const bool immediately) {
|
||||
static_cast<Proc *>(handler.instance.get())->abort(immediately);
|
||||
}
|
||||
|
||||
@@ -105,33 +109,18 @@ class Scheduler final {
|
||||
delete static_cast<Proc *>(proc);
|
||||
}
|
||||
|
||||
template<typename Proc, typename... Args>
|
||||
static auto then(ProcessHandler *handler, Args &&... args) {
|
||||
if(handler) {
|
||||
auto proc = typename ProcessHandler::instance_type{new Proc{std::forward<Args>(args)...}, &Scheduler::deleter<Proc>};
|
||||
handler->next.reset(new ProcessHandler{std::move(proc), &Scheduler::update<Proc>, &Scheduler::abort<Proc>, nullptr});
|
||||
handler = handler->next.get();
|
||||
}
|
||||
|
||||
return handler;
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename std::vector<ProcessHandler>::size_type;
|
||||
using size_type = std::size_t;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
Scheduler() ENTT_NOEXCEPT = default;
|
||||
scheduler() = default;
|
||||
|
||||
/*! @brief Copying a scheduler isn't allowed. */
|
||||
Scheduler(const Scheduler &) = delete;
|
||||
/*! @brief Default move constructor. */
|
||||
Scheduler(Scheduler &&) = default;
|
||||
scheduler(scheduler &&) = default;
|
||||
|
||||
/*! @brief Copying a scheduler isn't allowed. @return This scheduler. */
|
||||
Scheduler & operator=(const Scheduler &) = delete;
|
||||
/*! @brief Default move assignment operator. @return This scheduler. */
|
||||
Scheduler & operator=(Scheduler &&) = default;
|
||||
scheduler & operator=(scheduler &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Number of processes currently scheduled.
|
||||
@@ -170,13 +159,13 @@ public:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* // schedules a task in the form of a process class
|
||||
* scheduler.attach<MyProcess>(arguments...)
|
||||
* scheduler.attach<my_process>(arguments...)
|
||||
* // appends a child in the form of a lambda function
|
||||
* .then([](auto delta, void *, auto succeed, auto fail) {
|
||||
* // code
|
||||
* })
|
||||
* // appends a child in the form of another process class
|
||||
* .then<MyOtherProcess>();
|
||||
* .then<my_other_process>();
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Proc Type of process to schedule.
|
||||
@@ -186,13 +175,12 @@ public:
|
||||
*/
|
||||
template<typename Proc, typename... Args>
|
||||
auto attach(Args &&... args) {
|
||||
static_assert(std::is_base_of<Process<Proc, Delta>, Proc>::value, "!");
|
||||
|
||||
auto proc = typename ProcessHandler::instance_type{new Proc{std::forward<Args>(args)...}, &Scheduler::deleter<Proc>};
|
||||
ProcessHandler handler{std::move(proc), &Scheduler::update<Proc>, &Scheduler::abort<Proc>, nullptr};
|
||||
handlers.push_back(std::move(handler));
|
||||
|
||||
return Then{&handlers.back()};
|
||||
static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>);
|
||||
auto proc = typename process_handler::instance_type{new Proc{std::forward<Args>(args)...}, &scheduler::deleter<Proc>};
|
||||
process_handler handler{std::move(proc), &scheduler::update<Proc>, &scheduler::abort<Proc>, nullptr};
|
||||
// forces the process to exit the uninitialized state
|
||||
handler.update(handler, {}, nullptr);
|
||||
return continuation{&handlers.emplace_back(std::move(handler))};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -204,12 +192,13 @@ public:
|
||||
* following:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(Delta delta, auto succeed, auto fail);
|
||||
* void(Delta delta, void *data, auto succeed, auto fail);
|
||||
* @endcode
|
||||
*
|
||||
* Where:
|
||||
*
|
||||
* * `delta` is the elapsed time.
|
||||
* * `data` is an opaque pointer to user data if any, `nullptr` otherwise.
|
||||
* * `succeed` is a function to call when a process terminates with success.
|
||||
* * `fail` is a function to call when a process terminates with errors.
|
||||
*
|
||||
@@ -236,10 +225,10 @@ public:
|
||||
* // code
|
||||
* })
|
||||
* // appends a child in the form of a process class
|
||||
* .then<MyProcess>(arguments...);
|
||||
* .then<my_process>(arguments...);
|
||||
* @endcode
|
||||
*
|
||||
* @sa ProcessAdaptor
|
||||
* @sa process_adaptor
|
||||
*
|
||||
* @tparam Func Type of process to schedule.
|
||||
* @param func Either a lambda or a functor to use as a process.
|
||||
@@ -247,7 +236,7 @@ public:
|
||||
*/
|
||||
template<typename Func>
|
||||
auto attach(Func &&func) {
|
||||
using Proc = ProcessAdaptor<std::decay_t<Func>, Delta>;
|
||||
using Proc = process_adaptor<std::decay_t<Func>, Delta>;
|
||||
return attach<Proc>(std::forward<Func>(func));
|
||||
}
|
||||
|
||||
@@ -292,20 +281,20 @@ public:
|
||||
decltype(handlers) exec;
|
||||
exec.swap(handlers);
|
||||
|
||||
std::for_each(exec.begin(), exec.end(), [immediately](auto &handler) {
|
||||
for(auto &&handler: exec) {
|
||||
handler.abort(handler, immediately);
|
||||
});
|
||||
}
|
||||
|
||||
std::move(handlers.begin(), handlers.end(), std::back_inserter(exec));
|
||||
handlers.swap(exec);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<ProcessHandler> handlers{};
|
||||
std::vector<process_handler> handlers{};
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_PROCESS_SCHEDULER_HPP
|
||||
#endif
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include "../config/config.h"
|
||||
#include "../core/hashed_string.hpp"
|
||||
#include "handle.hpp"
|
||||
#include "loader.hpp"
|
||||
#include "fwd.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
@@ -26,27 +26,22 @@ 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>>;
|
||||
|
||||
public:
|
||||
struct cache {
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename container_type::size_type;
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Type of resources managed by a cache. */
|
||||
using resource_type = HashedString;
|
||||
using resource_type = Resource;
|
||||
/*! @brief Unique identifier type for resources. */
|
||||
using id_type = ENTT_ID_TYPE;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
ResourceCache() = default;
|
||||
cache() = default;
|
||||
|
||||
/*! @brief Copying a cache isn't allowed. */
|
||||
ResourceCache(const ResourceCache &) ENTT_NOEXCEPT = delete;
|
||||
/*! @brief Default move constructor. */
|
||||
ResourceCache(ResourceCache &&) ENTT_NOEXCEPT = default;
|
||||
cache(cache &&) = default;
|
||||
|
||||
/*! @brief Copying a cache isn't allowed. @return This cache. */
|
||||
ResourceCache & operator=(const ResourceCache &) ENTT_NOEXCEPT = delete;
|
||||
/*! @brief Default move assignment operator. @return This cache. */
|
||||
ResourceCache & operator=(ResourceCache &&) ENTT_NOEXCEPT = default;
|
||||
cache & operator=(cache &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Number of resources managed by a cache.
|
||||
@@ -86,24 +81,31 @@ public:
|
||||
* If the identifier is already present in the cache, this function does
|
||||
* nothing and the arguments are simply discarded.
|
||||
*
|
||||
* @warning
|
||||
* If the resource cannot be loaded correctly, the returned handle will be
|
||||
* invalid and any use of it will result in undefined behavior.
|
||||
*
|
||||
* @tparam Loader Type of loader to use to load the resource if required.
|
||||
* @tparam Args Types of arguments to use to load the resource if required.
|
||||
* @param id Unique resource identifier.
|
||||
* @param args Arguments to use to load the resource if required.
|
||||
* @return True if the resource is ready to use, false otherwise.
|
||||
* @return A handle for the given resource.
|
||||
*/
|
||||
template<typename Loader, typename... Args>
|
||||
bool load(const resource_type id, Args &&... args) {
|
||||
static_assert(std::is_base_of<ResourceLoader<Loader, Resource>, Loader>::value, "!");
|
||||
entt::handle<Resource> load(const id_type id, Args &&... args) {
|
||||
static_assert(std::is_base_of_v<loader<Loader, Resource>, Loader>);
|
||||
entt::handle<Resource> resource{};
|
||||
|
||||
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 instance = Loader{}.get(std::forward<Args>(args)...); instance) {
|
||||
resources[id] = instance;
|
||||
resource = std::move(instance);
|
||||
}
|
||||
} else {
|
||||
resource = it->second;
|
||||
}
|
||||
|
||||
return loaded;
|
||||
return resource;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,14 +121,18 @@ public:
|
||||
* Arguments are forwarded directly to the loader in order to construct
|
||||
* properly the requested resource.
|
||||
*
|
||||
* @warning
|
||||
* If the resource cannot be loaded correctly, the returned handle will be
|
||||
* invalid and any use of it will result in undefined behavior.
|
||||
*
|
||||
* @tparam Loader Type of loader to use to load the resource.
|
||||
* @tparam Args Types of arguments to use to load the resource.
|
||||
* @param id Unique resource identifier.
|
||||
* @param args Arguments to use to load the resource.
|
||||
* @return True if the resource is ready to use, false otherwise.
|
||||
* @return A handle for the given resource.
|
||||
*/
|
||||
template<typename Loader, typename... Args>
|
||||
bool reload(const resource_type id, Args &&... args) {
|
||||
entt::handle<Resource> reload(const id_type id, Args &&... args) {
|
||||
return (discard(id), load<Loader>(id, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
@@ -143,7 +149,7 @@ public:
|
||||
* @return A handle for the given resource.
|
||||
*/
|
||||
template<typename Loader, typename... Args>
|
||||
ResourceHandle<Resource> temp(Args &&... args) const {
|
||||
entt::handle<Resource> temp(Args &&... args) const {
|
||||
return { Loader{}.get(std::forward<Args>(args)...) };
|
||||
}
|
||||
|
||||
@@ -155,12 +161,12 @@ public:
|
||||
* cache contains the resource itself. Otherwise the returned handle is
|
||||
* uninitialized and accessing it results in undefined behavior.
|
||||
*
|
||||
* @sa ResourceHandle
|
||||
* @sa handle
|
||||
*
|
||||
* @param id Unique resource identifier.
|
||||
* @return A handle for the given resource.
|
||||
*/
|
||||
ResourceHandle<Resource> handle(const resource_type id) const {
|
||||
entt::handle<Resource> handle(const id_type id) const {
|
||||
auto it = resources.find(id);
|
||||
return { it == resources.end() ? nullptr : it->second };
|
||||
}
|
||||
@@ -170,7 +176,7 @@ public:
|
||||
* @param id Unique resource identifier.
|
||||
* @return True if the cache contains the resource, false otherwise.
|
||||
*/
|
||||
bool contains(const resource_type id) const ENTT_NOEXCEPT {
|
||||
bool contains(const id_type id) const {
|
||||
return (resources.find(id) != resources.cend());
|
||||
}
|
||||
|
||||
@@ -182,20 +188,53 @@ public:
|
||||
*
|
||||
* @param id Unique resource identifier.
|
||||
*/
|
||||
void discard(const resource_type id) ENTT_NOEXCEPT {
|
||||
auto it = resources.find(id);
|
||||
|
||||
if(it != resources.end()) {
|
||||
void discard(const id_type id) {
|
||||
if(auto it = resources.find(id); it != resources.end()) {
|
||||
resources.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterates all resources.
|
||||
*
|
||||
* The function object is invoked for each element. It is provided with
|
||||
* either the resource identifier, the resource handle or both of them.<br/>
|
||||
* The signature of the function must be equivalent to one of the following
|
||||
* forms:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* void(const id_type);
|
||||
* void(handle<Resource>);
|
||||
* void(const id_type, handle<Resource>);
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Func Type of the function object to invoke.
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template <typename Func>
|
||||
void each(Func func) const {
|
||||
auto begin = resources.begin();
|
||||
auto end = resources.end();
|
||||
|
||||
while(begin != end) {
|
||||
auto curr = begin++;
|
||||
|
||||
if constexpr(std::is_invocable_v<Func, id_type>) {
|
||||
func(curr->first);
|
||||
} else if constexpr(std::is_invocable_v<Func, entt::handle<Resource>>) {
|
||||
func(entt::handle{ curr->second });
|
||||
} else {
|
||||
func(curr->first, entt::handle{ curr->second });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
container_type resources;
|
||||
std::unordered_map<id_type, std::shared_ptr<Resource>> resources;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_RESOURCE_CACHE_HPP
|
||||
#endif
|
||||
|
||||
24
src/entt/resource/fwd.hpp
Normal file
24
src/entt/resource/fwd.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#ifndef ENTT_RESOURCE_FWD_HPP
|
||||
#define ENTT_RESOURCE_FWD_HPP
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/*! @struct cache */
|
||||
template<typename>
|
||||
struct cache;
|
||||
|
||||
/*! @class handle */
|
||||
template<typename>
|
||||
class handle;
|
||||
|
||||
/*! @class loader */
|
||||
template<typename, typename>
|
||||
class loader;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -4,17 +4,13 @@
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <cassert>
|
||||
#include "../config/config.h"
|
||||
#include "fwd.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
template<typename Resource>
|
||||
class ResourceCache;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Shared resource handle.
|
||||
*
|
||||
@@ -28,24 +24,17 @@ class ResourceCache;
|
||||
* @tparam Resource Type of resource managed by a handle.
|
||||
*/
|
||||
template<typename Resource>
|
||||
class ResourceHandle final {
|
||||
class handle {
|
||||
/*! @brief Resource handles are friends of their caches. */
|
||||
friend class ResourceCache<Resource>;
|
||||
friend struct cache<Resource>;
|
||||
|
||||
ResourceHandle(std::shared_ptr<Resource> res) ENTT_NOEXCEPT
|
||||
handle(std::shared_ptr<Resource> res) ENTT_NOEXCEPT
|
||||
: resource{std::move(res)}
|
||||
{}
|
||||
|
||||
public:
|
||||
/*! @brief Default copy constructor. */
|
||||
ResourceHandle(const ResourceHandle &) ENTT_NOEXCEPT = default;
|
||||
/*! @brief Default move constructor. */
|
||||
ResourceHandle(ResourceHandle &&) ENTT_NOEXCEPT = default;
|
||||
|
||||
/*! @brief Default copy assignment operator. @return This handle. */
|
||||
ResourceHandle & operator=(const ResourceHandle &) ENTT_NOEXCEPT = default;
|
||||
/*! @brief Default move assignment operator. @return This handle. */
|
||||
ResourceHandle & operator=(ResourceHandle &&) ENTT_NOEXCEPT = default;
|
||||
/*! @brief Default constructor. */
|
||||
handle() ENTT_NOEXCEPT = default;
|
||||
|
||||
/**
|
||||
* @brief Gets a reference to the managed resource.
|
||||
@@ -58,34 +47,29 @@ public:
|
||||
* @return A reference to the managed resource.
|
||||
*/
|
||||
const Resource & get() const ENTT_NOEXCEPT {
|
||||
assert(static_cast<bool>(resource));
|
||||
ENTT_ASSERT(static_cast<bool>(resource));
|
||||
return *resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Casts a handle and gets a reference to the managed resource.
|
||||
*
|
||||
* @warning
|
||||
* The behavior is undefined if the handle doesn't contain a resource.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* handle is empty.
|
||||
*/
|
||||
inline operator const Resource &() const ENTT_NOEXCEPT { return get(); }
|
||||
/*! @copydoc get */
|
||||
Resource & get() ENTT_NOEXCEPT {
|
||||
return const_cast<Resource &>(std::as_const(*this).get());
|
||||
}
|
||||
|
||||
/*! @copydoc get */
|
||||
operator const Resource & () const ENTT_NOEXCEPT { return get(); }
|
||||
|
||||
/*! @copydoc get */
|
||||
operator Resource & () ENTT_NOEXCEPT { return get(); }
|
||||
|
||||
/*! @copydoc get */
|
||||
const Resource & operator *() const ENTT_NOEXCEPT { return get(); }
|
||||
|
||||
/*! @copydoc get */
|
||||
Resource & operator *() ENTT_NOEXCEPT { return get(); }
|
||||
|
||||
/**
|
||||
* @brief Dereferences a handle to obtain the managed resource.
|
||||
*
|
||||
* @warning
|
||||
* The behavior is undefined if the handle doesn't contain a resource.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* handle is empty.
|
||||
*
|
||||
* @return A reference to the managed resource.
|
||||
*/
|
||||
inline const Resource & operator *() const ENTT_NOEXCEPT { return get(); }
|
||||
|
||||
/**
|
||||
* @brief Gets a pointer to the managed resource from a handle.
|
||||
* @brief Gets a pointer to the managed resource.
|
||||
*
|
||||
* @warning
|
||||
* The behavior is undefined if the handle doesn't contain a resource.<br/>
|
||||
@@ -95,15 +79,23 @@ public:
|
||||
* @return A pointer to the managed resource or `nullptr` if the handle
|
||||
* contains no resource at all.
|
||||
*/
|
||||
inline const Resource * operator ->() const ENTT_NOEXCEPT {
|
||||
assert(static_cast<bool>(resource));
|
||||
const Resource * operator->() const ENTT_NOEXCEPT {
|
||||
ENTT_ASSERT(static_cast<bool>(resource));
|
||||
return resource.get();
|
||||
}
|
||||
|
||||
/*! @copydoc operator-> */
|
||||
Resource * operator->() ENTT_NOEXCEPT {
|
||||
return const_cast<Resource *>(std::as_const(*this).operator->());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if the handle contains a resource, false otherwise.
|
||||
* @brief Returns true if a handle contains a resource, false otherwise.
|
||||
* @return True if the handle contains a resource, false otherwise.
|
||||
*/
|
||||
explicit operator bool() const { return static_cast<bool>(resource); }
|
||||
explicit operator bool() const ENTT_NOEXCEPT {
|
||||
return static_cast<bool>(resource);
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Resource> resource;
|
||||
@@ -113,4 +105,4 @@ private:
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_RESOURCE_HANDLE_HPP
|
||||
#endif
|
||||
|
||||
@@ -3,15 +3,12 @@
|
||||
|
||||
|
||||
#include <memory>
|
||||
#include "fwd.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
template<typename Resource>
|
||||
class ResourceCache;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Base class for resource loaders.
|
||||
*
|
||||
@@ -22,12 +19,12 @@ class ResourceCache;
|
||||
* As an example:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* struct MyResource {};
|
||||
* struct my_resource {};
|
||||
*
|
||||
* struct MyLoader: entt::ResourceLoader<MyLoader, MyResource> {
|
||||
* std::shared_ptr<MyResource> load(int) const {
|
||||
* struct my_loader: entt::loader<my_loader, my_resource> {
|
||||
* std::shared_ptr<my_resource> load(int) const {
|
||||
* // use the integer value somehow
|
||||
* return std::make_shared<MyResource>();
|
||||
* return std::make_shared<my_resource>();
|
||||
* }
|
||||
* };
|
||||
* @endcode
|
||||
@@ -45,10 +42,16 @@ class ResourceCache;
|
||||
* @tparam Resource Type of resource for which to use the loader.
|
||||
*/
|
||||
template<typename Loader, typename Resource>
|
||||
class ResourceLoader {
|
||||
class loader {
|
||||
/*! @brief Resource loaders are friends of their caches. */
|
||||
friend class ResourceCache<Resource>;
|
||||
friend struct cache<Resource>;
|
||||
|
||||
/**
|
||||
* @brief Loads the resource and returns it.
|
||||
* @tparam Args Types of arguments for the loader.
|
||||
* @param args Arguments for the loader.
|
||||
* @return The resource just loaded or an empty pointer in case of errors.
|
||||
*/
|
||||
template<typename... Args>
|
||||
std::shared_ptr<Resource> get(Args &&... args) const {
|
||||
return static_cast<const Loader *>(this)->load(std::forward<Args>(args)...);
|
||||
@@ -59,4 +62,4 @@ class ResourceLoader {
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_RESOURCE_LOADER_HPP
|
||||
#endif
|
||||
|
||||
@@ -2,13 +2,75 @@
|
||||
#define ENTT_SIGNAL_DELEGATE_HPP
|
||||
|
||||
|
||||
#include <tuple>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#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 function_pointer(Ret(*)(Args...)) -> Ret(*)(Args...);
|
||||
|
||||
|
||||
template<typename Ret, typename Type, typename... Args, typename Other>
|
||||
auto function_pointer(Ret(*)(Type, Args...), Other &&) -> Ret(*)(Args...);
|
||||
|
||||
|
||||
template<typename Class, typename Ret, typename... Args, typename... Other>
|
||||
auto function_pointer(Ret(Class:: *)(Args...), Other &&...) -> Ret(*)(Args...);
|
||||
|
||||
|
||||
template<typename Class, typename Ret, typename... Args, typename... Other>
|
||||
auto function_pointer(Ret(Class:: *)(Args...) const, Other &&...) -> Ret(*)(Args...);
|
||||
|
||||
|
||||
template<typename Class, typename Type, typename... Other>
|
||||
auto function_pointer(Type Class:: *, Other &&...) -> Type(*)();
|
||||
|
||||
|
||||
template<typename... Type>
|
||||
using function_pointer_t = decltype(internal::function_pointer(std::declval<Type>()...));
|
||||
|
||||
|
||||
template<typename... Class, typename Ret, typename... Args>
|
||||
constexpr auto index_sequence_for(Ret(*)(Args...)) {
|
||||
return std::index_sequence_for<Class..., 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.
|
||||
*
|
||||
@@ -16,151 +78,253 @@ 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.
|
||||
* A delegate can be used as a general purpose invoker without memory overhead
|
||||
* for free functions possibly with payloads and bound or unbound members.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
*/
|
||||
template<typename Ret, typename... Args>
|
||||
class Delegate<Ret(Args...)> final {
|
||||
using proto_fn_type = Ret(void *, Args...);
|
||||
using stub_type = std::pair<void *, proto_fn_type *>;
|
||||
class delegate<Ret(Args...)> {
|
||||
using proto_fn_type = Ret(const void *, Args...);
|
||||
|
||||
template<Ret(*Function)(Args...)>
|
||||
static Ret proto(void *, Args... args) {
|
||||
return (Function)(args...);
|
||||
template<auto Candidate, std::size_t... Index>
|
||||
auto wrap(std::index_sequence<Index...>) ENTT_NOEXCEPT {
|
||||
return [](const void *, Args... args) -> Ret {
|
||||
const auto arguments = std::forward_as_tuple(std::forward<Args>(args)...);
|
||||
return Ret(std::invoke(Candidate, std::forward<std::tuple_element_t<Index, std::tuple<Args...>>>(std::get<Index>(arguments))...));
|
||||
};
|
||||
}
|
||||
|
||||
template<typename Class, Ret(Class:: *Member)(Args...) const>
|
||||
static Ret proto(void *instance, Args... args) {
|
||||
return (static_cast<const Class *>(instance)->*Member)(args...);
|
||||
template<auto Candidate, typename Type, std::size_t... Index>
|
||||
auto wrap(Type &, std::index_sequence<Index...>) ENTT_NOEXCEPT {
|
||||
return [](const void *payload, Args... args) -> Ret {
|
||||
const auto arguments = std::forward_as_tuple(std::forward<Args>(args)...);
|
||||
Type *curr = static_cast<Type *>(const_cast<std::conditional_t<std::is_const_v<Type>, const void *, void *>>(payload));
|
||||
return Ret(std::invoke(Candidate, *curr, std::forward<std::tuple_element_t<Index, std::tuple<Args...>>>(std::get<Index>(arguments))...));
|
||||
};
|
||||
}
|
||||
|
||||
template<typename Class, Ret(Class:: *Member)(Args...)>
|
||||
static Ret proto(void *instance, Args... args) {
|
||||
return (static_cast<Class *>(instance)->*Member)(args...);
|
||||
template<auto Candidate, typename Type, std::size_t... Index>
|
||||
auto wrap(Type *, std::index_sequence<Index...>) ENTT_NOEXCEPT {
|
||||
return [](const void *payload, Args... args) -> Ret {
|
||||
const auto arguments = std::forward_as_tuple(std::forward<Args>(args)...);
|
||||
Type *curr = static_cast<Type *>(const_cast<std::conditional_t<std::is_const_v<Type>, const void *, void *>>(payload));
|
||||
return Ret(std::invoke(Candidate, curr, std::forward<std::tuple_element_t<Index, std::tuple<Args...>>>(std::get<Index>(arguments))...));
|
||||
};
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Function type of the delegate. */
|
||||
using function_type = Ret(Args...);
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
Delegate() ENTT_NOEXCEPT
|
||||
: stub{}
|
||||
delegate() ENTT_NOEXCEPT
|
||||
: fn{nullptr}, data{nullptr}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Checks whether a delegate actually stores a listener.
|
||||
* @return True if the delegate is empty, false otherwise.
|
||||
* @brief Constructs a delegate and connects a free function or an unbound
|
||||
* member.
|
||||
* @tparam Candidate Function or member to connect to the delegate.
|
||||
*/
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
// no need to test also stub.first
|
||||
return !stub.second;
|
||||
template<auto Candidate>
|
||||
delegate(connect_arg_t<Candidate>) ENTT_NOEXCEPT
|
||||
: delegate{}
|
||||
{
|
||||
connect<Candidate>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Binds a free function to a delegate.
|
||||
* @tparam Function A valid free function pointer.
|
||||
* @brief Constructs a delegate and connects a free function with payload or
|
||||
* a bound member.
|
||||
* @tparam Candidate Function or member to connect to the delegate.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
* @param value_or_instance A valid object that fits the purpose.
|
||||
*/
|
||||
template<Ret(*Function)(Args...)>
|
||||
template<auto Candidate, typename Type>
|
||||
delegate(connect_arg_t<Candidate>, Type &&value_or_instance) ENTT_NOEXCEPT
|
||||
: delegate{}
|
||||
{
|
||||
connect<Candidate>(std::forward<Type>(value_or_instance));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function or an unbound member to a delegate.
|
||||
* @tparam Candidate Function or member to connect to the delegate.
|
||||
*/
|
||||
template<auto Candidate>
|
||||
void connect() ENTT_NOEXCEPT {
|
||||
stub = std::make_pair(nullptr, &proto<Function>);
|
||||
data = nullptr;
|
||||
|
||||
if constexpr(std::is_invocable_r_v<Ret, decltype(Candidate), Args...>) {
|
||||
fn = [](const void *, Args... args) -> Ret {
|
||||
return Ret(std::invoke(Candidate, std::forward<Args>(args)...));
|
||||
};
|
||||
} else if constexpr(std::is_member_pointer_v<decltype(Candidate)>) {
|
||||
fn = wrap<Candidate>(internal::index_sequence_for<std::tuple_element_t<0, std::tuple<Args...>>>(internal::function_pointer_t<decltype(Candidate)>{}));
|
||||
} else {
|
||||
fn = wrap<Candidate>(internal::index_sequence_for(internal::function_pointer_t<decltype(Candidate)>{}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
* @brief Connects a free function with payload or a bound member to a
|
||||
* 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`.
|
||||
* 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 Function or member to connect to the delegate.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
* @param value_or_instance A valid reference that fits the purpose.
|
||||
*/
|
||||
template<typename Class, Ret(Class:: *Member)(Args...) const>
|
||||
void connect(Class *instance) ENTT_NOEXCEPT {
|
||||
stub = std::make_pair(instance, &proto<Class, Member>);
|
||||
template<auto Candidate, typename Type>
|
||||
void connect(Type &value_or_instance) ENTT_NOEXCEPT {
|
||||
data = &value_or_instance;
|
||||
|
||||
if constexpr(std::is_invocable_r_v<Ret, decltype(Candidate), Type &, Args...>) {
|
||||
fn = [](const void *payload, Args... args) -> Ret {
|
||||
Type *curr = static_cast<Type *>(const_cast<std::conditional_t<std::is_const_v<Type>, const void *, void *>>(payload));
|
||||
return Ret(std::invoke(Candidate, *curr, std::forward<Args>(args)...));
|
||||
};
|
||||
} else {
|
||||
fn = wrap<Candidate>(value_or_instance, internal::index_sequence_for(internal::function_pointer_t<decltype(Candidate), Type>{}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
* @brief Connects a free function with payload or a bound member to a
|
||||
* 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`.
|
||||
* @sa connect(Type &)
|
||||
*
|
||||
* @tparam Candidate Function or member 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) ENTT_NOEXCEPT {
|
||||
stub = std::make_pair(instance, &proto<Class, Member>);
|
||||
template<auto Candidate, typename Type>
|
||||
void connect(Type *value_or_instance) ENTT_NOEXCEPT {
|
||||
data = value_or_instance;
|
||||
|
||||
if constexpr(std::is_invocable_r_v<Ret, decltype(Candidate), Type *, Args...>) {
|
||||
fn = [](const void *payload, Args... args) -> Ret {
|
||||
Type *curr = static_cast<Type *>(const_cast<std::conditional_t<std::is_const_v<Type>, const void *, void *>>(payload));
|
||||
return Ret(std::invoke(Candidate, curr, std::forward<Args>(args)...));
|
||||
};
|
||||
} else {
|
||||
fn = wrap<Candidate>(value_or_instance, internal::index_sequence_for(internal::function_pointer_t<decltype(Candidate), Type>{}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resets a delegate.
|
||||
*
|
||||
* After a reset, a delegate can be safely invoked with no effect.
|
||||
* After a reset, a delegate cannot be invoked anymore.
|
||||
*/
|
||||
void reset() ENTT_NOEXCEPT {
|
||||
stub.second = nullptr;
|
||||
fn = nullptr;
|
||||
data = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the instance or the payload linked to a delegate, if any.
|
||||
* @return An opaque pointer to the underlying data.
|
||||
*/
|
||||
const void * instance() const ENTT_NOEXCEPT {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Triggers a delegate.
|
||||
*
|
||||
* The delegate invokes the underlying function and returns the result.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to trigger an invalid delegate results in undefined
|
||||
* behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* delegate has not yet been set.
|
||||
*
|
||||
* @param args Arguments to use to invoke the underlying function.
|
||||
* @return The value returned by the underlying function.
|
||||
*/
|
||||
Ret operator()(Args... args) const {
|
||||
return stub.second(stub.first, args...);
|
||||
ENTT_ASSERT(fn);
|
||||
return fn(data, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the contents of the two delegates are different.
|
||||
*
|
||||
* Two delegates are identical if they contain the same listener.
|
||||
*
|
||||
* @param other Delegate with which to compare.
|
||||
* @return True if the two delegates are identical, false otherwise.
|
||||
* @brief Checks whether a delegate actually stores a listener.
|
||||
* @return False if the delegate is empty, true otherwise.
|
||||
*/
|
||||
bool operator==(const Delegate<Ret(Args...)> &other) const ENTT_NOEXCEPT {
|
||||
return stub.first == other.stub.first && stub.second == other.stub.second;
|
||||
explicit operator bool() const ENTT_NOEXCEPT {
|
||||
// no need to test also data
|
||||
return !(fn == nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compares the contents of two delegates.
|
||||
* @param other Delegate with which to compare.
|
||||
* @return False if the two contents differ, true otherwise.
|
||||
*/
|
||||
bool operator==(const delegate<Ret(Args...)> &other) const ENTT_NOEXCEPT {
|
||||
return fn == other.fn && data == other.data;
|
||||
}
|
||||
|
||||
private:
|
||||
stub_type stub;
|
||||
proto_fn_type *fn;
|
||||
const void *data;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Checks if the contents of the two delegates are different.
|
||||
*
|
||||
* Two delegates are identical if they contain the same listener.
|
||||
*
|
||||
* @brief Compares the contents of two delegates.
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
* @param lhs A valid delegate object.
|
||||
* @param rhs A valid delegate object.
|
||||
* @return True if the two delegates are different, false otherwise.
|
||||
* @return True if the two contents differ, false otherwise.
|
||||
*/
|
||||
template<typename Ret, typename... Args>
|
||||
bool operator!=(const Delegate<Ret(Args...)> &lhs, const Delegate<Ret(Args...)> &rhs) ENTT_NOEXCEPT {
|
||||
bool operator!=(const delegate<Ret(Args...)> &lhs, const delegate<Ret(Args...)> &rhs) ENTT_NOEXCEPT {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Deduction guide.
|
||||
* @tparam Candidate Function or member to connect to the delegate.
|
||||
*/
|
||||
template<auto Candidate>
|
||||
delegate(connect_arg_t<Candidate>) ENTT_NOEXCEPT
|
||||
-> delegate<std::remove_pointer_t<internal::function_pointer_t<decltype(Candidate)>>>;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Deduction guide.
|
||||
* @tparam Candidate Function or member 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<internal::function_pointer_t<decltype(Candidate), Type>>>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_SIGNAL_DELEGATE_HPP
|
||||
#endif
|
||||
|
||||
@@ -4,12 +4,11 @@
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include <cstdint>
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "../core/family.hpp"
|
||||
#include "../core/type_info.hpp"
|
||||
#include "sigh.hpp"
|
||||
|
||||
|
||||
@@ -22,78 +21,79 @@ namespace entt {
|
||||
* A dispatcher can be used either to trigger an immediate event or to enqueue
|
||||
* events to be published all together once per tick.<br/>
|
||||
* Listeners are provided in the form of member functions. For each event of
|
||||
* type `Event`, listeners must have the following function type:
|
||||
* @code{.cpp}
|
||||
* void(const Event &)
|
||||
* @endcode
|
||||
* type `Event`, listeners are such that they can be invoked with an argument of
|
||||
* type `const Event &`, no matter what the return type is.
|
||||
*
|
||||
* Member functions named `receive` are automatically detected and registered or
|
||||
* unregistered by the dispatcher. The type of the instances is `Class *` (a
|
||||
* naked pointer). It means that users must guarantee that the lifetimes of the
|
||||
* instances overcome the one of the dispatcher itself to avoid crashes.
|
||||
* The dispatcher creates instances of the `sigh` class internally. Refer to the
|
||||
* documentation of the latter for more details.
|
||||
*/
|
||||
class Dispatcher final {
|
||||
using event_family = Family<struct InternalDispatcherEventFamily>;
|
||||
|
||||
template<typename Class, typename Event>
|
||||
using instance_type = typename SigH<void(const Event &)>::template instance_type<Class>;
|
||||
|
||||
struct BaseSignalWrapper {
|
||||
virtual ~BaseSignalWrapper() = default;
|
||||
class dispatcher {
|
||||
struct basic_pool {
|
||||
virtual ~basic_pool() = default;
|
||||
virtual void publish() = 0;
|
||||
virtual void clear() ENTT_NOEXCEPT = 0;
|
||||
virtual ENTT_ID_TYPE type_id() const ENTT_NOEXCEPT = 0;
|
||||
};
|
||||
|
||||
template<typename Event>
|
||||
struct SignalWrapper final: BaseSignalWrapper {
|
||||
using sink_type = typename SigH<void(const Event &)>::sink_type;
|
||||
struct pool_handler: basic_pool {
|
||||
using signal_type = sigh<void(const Event &)>;
|
||||
using sink_type = typename signal_type::sink_type;
|
||||
|
||||
void publish() override {
|
||||
const auto &curr = current++;
|
||||
current %= std::extent<decltype(events)>::value;
|
||||
std::for_each(events[curr].cbegin(), events[curr].cend(), [this](const auto &event) { signal.publish(event); });
|
||||
events[curr].clear();
|
||||
const auto length = events.size();
|
||||
|
||||
for(std::size_t pos{}; pos < length; ++pos) {
|
||||
signal.publish(events[pos]);
|
||||
}
|
||||
|
||||
events.erase(events.cbegin(), events.cbegin()+length);
|
||||
}
|
||||
|
||||
inline sink_type sink() ENTT_NOEXCEPT {
|
||||
return signal.sink();
|
||||
void clear() ENTT_NOEXCEPT override {
|
||||
events.clear();
|
||||
}
|
||||
|
||||
sink_type sink() ENTT_NOEXCEPT {
|
||||
return entt::sink{signal};
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void trigger(Args &&... args) {
|
||||
signal.publish({ std::forward<Args>(args)... });
|
||||
void trigger(Args &&... args) {
|
||||
signal.publish(Event{std::forward<Args>(args)...});
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void enqueue(Args &&... args) {
|
||||
events[current].push_back({ std::forward<Args>(args)... });
|
||||
void enqueue(Args &&... args) {
|
||||
events.emplace_back(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
ENTT_ID_TYPE type_id() const ENTT_NOEXCEPT override {
|
||||
return type_info<Event>::id();
|
||||
}
|
||||
|
||||
private:
|
||||
SigH<void(const Event &)> signal{};
|
||||
std::vector<Event> events[2];
|
||||
int current{};
|
||||
signal_type signal{};
|
||||
std::vector<Event> events;
|
||||
};
|
||||
|
||||
template<typename Event>
|
||||
SignalWrapper<Event> & wrapper() {
|
||||
const auto type = event_family::type<Event>();
|
||||
pool_handler<Event> & assure() {
|
||||
static_assert(std::is_same_v<Event, std::decay_t<Event>>);
|
||||
static std::size_t index{pools.size()};
|
||||
|
||||
if(!(type < wrappers.size())) {
|
||||
wrappers.resize(type + 1);
|
||||
if(const auto length = pools.size(); !(index < length) || pools[index]->type_id() != type_info<Event>::id()) {
|
||||
for(index = {}; index < length && pools[index]->type_id() != type_info<Event>::id(); ++index);
|
||||
|
||||
if(index == pools.size()) {
|
||||
pools.emplace_back(new pool_handler<Event>{});
|
||||
}
|
||||
}
|
||||
|
||||
if(!wrappers[type]) {
|
||||
wrappers[type] = std::make_unique<SignalWrapper<Event>>();
|
||||
}
|
||||
|
||||
return static_cast<SignalWrapper<Event> &>(*wrappers[type]);
|
||||
return static_cast<pool_handler<Event> &>(*pools[index]);
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Type of sink for the given event. */
|
||||
template<typename Event>
|
||||
using sink_type = typename SignalWrapper<Event>::sink_type;
|
||||
|
||||
/**
|
||||
* @brief Returns a sink object for the given event.
|
||||
*
|
||||
@@ -101,19 +101,19 @@ public:
|
||||
*
|
||||
* The function type for a listener is:
|
||||
* @code{.cpp}
|
||||
* void(const Event &)
|
||||
* void(const Event &);
|
||||
* @endcode
|
||||
*
|
||||
* The order of invocation of the listeners isn't guaranteed.
|
||||
*
|
||||
* @sa SigH::Sink
|
||||
* @sa sink
|
||||
*
|
||||
* @tparam Event Type of event of which to get the sink.
|
||||
* @return A temporary sink object.
|
||||
*/
|
||||
template<typename Event>
|
||||
inline sink_type<Event> sink() ENTT_NOEXCEPT {
|
||||
return wrapper<Event>().sink();
|
||||
auto sink() {
|
||||
return assure<Event>().sink();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,8 +127,22 @@ public:
|
||||
* @param args Arguments to use to construct the event.
|
||||
*/
|
||||
template<typename Event, typename... Args>
|
||||
inline void trigger(Args &&... args) {
|
||||
wrapper<Event>().trigger(std::forward<Args>(args)...);
|
||||
void trigger(Args &&... args) {
|
||||
assure<Event>().trigger(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Triggers an immediate event of the given type.
|
||||
*
|
||||
* All the listeners registered for the given type are immediately notified.
|
||||
* The event is discarded after the execution.
|
||||
*
|
||||
* @tparam Event Type of event to trigger.
|
||||
* @param event An instance of the given type of event.
|
||||
*/
|
||||
template<typename Event>
|
||||
void trigger(Event &&event) {
|
||||
assure<std::decay_t<Event>>().trigger(std::forward<Event>(event));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,13 +151,46 @@ public:
|
||||
* An event of the given type is queued. No listener is invoked. Use the
|
||||
* `update` member function to notify listeners when ready.
|
||||
*
|
||||
* @tparam Event Type of event to trigger.
|
||||
* @tparam Event Type of event to enqueue.
|
||||
* @tparam Args Types of arguments to use to construct the event.
|
||||
* @param args Arguments to use to construct the event.
|
||||
*/
|
||||
template<typename Event, typename... Args>
|
||||
inline void enqueue(Args &&... args) {
|
||||
wrapper<Event>().enqueue(std::forward<Args>(args)...);
|
||||
void enqueue(Args &&... args) {
|
||||
assure<Event>().enqueue(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enqueues an event of the given type.
|
||||
*
|
||||
* An event of the given type is queued. No listener is invoked. Use the
|
||||
* `update` member function to notify listeners when ready.
|
||||
*
|
||||
* @tparam Event Type of event to enqueue.
|
||||
* @param event An instance of the given type of event.
|
||||
*/
|
||||
template<typename Event>
|
||||
void enqueue(Event &&event) {
|
||||
assure<std::decay_t<Event>>().enqueue(std::forward<Event>(event));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Discards all the events queued so far.
|
||||
*
|
||||
* If no types are provided, the dispatcher will clear all the existing
|
||||
* pools.
|
||||
*
|
||||
* @tparam Event Type of events to discard.
|
||||
*/
|
||||
template<typename... Event>
|
||||
void clear() {
|
||||
if constexpr(sizeof...(Event) == 0) {
|
||||
for(auto &&cpool: pools) {
|
||||
cpool->clear();
|
||||
}
|
||||
} else {
|
||||
(assure<Event>().clear(), ...);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,8 +203,8 @@ public:
|
||||
* @tparam Event Type of events to send.
|
||||
*/
|
||||
template<typename Event>
|
||||
inline void update() {
|
||||
wrapper<Event>().publish();
|
||||
void update() {
|
||||
assure<Event>().publish();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,22 +214,18 @@ public:
|
||||
* delivered to the registered listeners. It's responsibility of the users
|
||||
* to reduce at a minimum the time spent in the bodies of the listeners.
|
||||
*/
|
||||
inline void update() const {
|
||||
for(auto pos = wrappers.size(); pos; --pos) {
|
||||
auto &wrapper = wrappers[pos-1];
|
||||
|
||||
if(wrapper) {
|
||||
wrapper->publish();
|
||||
}
|
||||
void update() const {
|
||||
for(auto pos = pools.size(); pos; --pos) {
|
||||
pools[pos-1]->publish();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<BaseSignalWrapper>> wrappers;
|
||||
std::vector<std::unique_ptr<basic_pool>> pools;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_SIGNAL_DISPATCHER_HPP
|
||||
#endif
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <iterator>
|
||||
#include "../config/config.h"
|
||||
#include "../core/family.hpp"
|
||||
#include "../core/type_info.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
@@ -23,13 +23,13 @@ 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
|
||||
* Pools 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/>
|
||||
* Moreover, whenever an event is published, an emitter provides the listeners
|
||||
* with a reference to itself along with a const reference to the event.
|
||||
@@ -39,17 +39,16 @@ namespace entt {
|
||||
* @tparam Derived Actual type of emitter that extends the class template.
|
||||
*/
|
||||
template<typename Derived>
|
||||
class Emitter {
|
||||
using handler_family = Family<struct InternalEmitterHandlerFamily>;
|
||||
|
||||
struct BaseHandler {
|
||||
virtual ~BaseHandler() = default;
|
||||
class emitter {
|
||||
struct basic_pool {
|
||||
virtual ~basic_pool() = default;
|
||||
virtual bool empty() const ENTT_NOEXCEPT = 0;
|
||||
virtual void clear() ENTT_NOEXCEPT = 0;
|
||||
virtual ENTT_ID_TYPE type_id() const ENTT_NOEXCEPT = 0;
|
||||
};
|
||||
|
||||
template<typename Event>
|
||||
struct Handler final: BaseHandler {
|
||||
struct pool_handler: basic_pool {
|
||||
using listener_type = std::function<void(const Event &, Derived &)>;
|
||||
using element_type = std::pair<bool, listener_type>;
|
||||
using container_type = std::list<element_type>;
|
||||
@@ -58,82 +57,97 @@ class Emitter {
|
||||
bool empty() const ENTT_NOEXCEPT override {
|
||||
auto pred = [](auto &&element) { return element.first; };
|
||||
|
||||
return std::all_of(onceL.cbegin(), onceL.cend(), pred) &&
|
||||
std::all_of(onL.cbegin(), onL.cend(), pred);
|
||||
return std::all_of(once_list.cbegin(), once_list.cend(), pred) &&
|
||||
std::all_of(on_list.cbegin(), on_list.cend(), pred);
|
||||
}
|
||||
|
||||
void clear() ENTT_NOEXCEPT override {
|
||||
if(publishing) {
|
||||
auto func = [](auto &&element) { element.first = true; };
|
||||
std::for_each(onceL.begin(), onceL.end(), func);
|
||||
std::for_each(onL.begin(), onL.end(), func);
|
||||
for(auto &&element: once_list) {
|
||||
element.first = true;
|
||||
}
|
||||
|
||||
for(auto &&element: on_list) {
|
||||
element.first = true;
|
||||
}
|
||||
} else {
|
||||
onceL.clear();
|
||||
onL.clear();
|
||||
once_list.clear();
|
||||
on_list.clear();
|
||||
}
|
||||
}
|
||||
|
||||
inline connection_type once(listener_type listener) {
|
||||
return onceL.emplace(onceL.cend(), false, std::move(listener));
|
||||
connection_type once(listener_type listener) {
|
||||
return once_list.emplace(once_list.cend(), false, std::move(listener));
|
||||
}
|
||||
|
||||
inline connection_type on(listener_type listener) {
|
||||
return onL.emplace(onL.cend(), false, std::move(listener));
|
||||
connection_type on(listener_type listener) {
|
||||
return on_list.emplace(on_list.cend(), false, std::move(listener));
|
||||
}
|
||||
|
||||
void erase(connection_type conn) ENTT_NOEXCEPT {
|
||||
void erase(connection_type conn) {
|
||||
conn->first = true;
|
||||
|
||||
if(!publishing) {
|
||||
auto pred = [](auto &&element) { return element.first; };
|
||||
onceL.remove_if(pred);
|
||||
onL.remove_if(pred);
|
||||
once_list.remove_if(pred);
|
||||
on_list.remove_if(pred);
|
||||
}
|
||||
}
|
||||
|
||||
void publish(const Event &event, Derived &ref) {
|
||||
container_type currentL;
|
||||
onceL.swap(currentL);
|
||||
|
||||
auto func = [&event, &ref](auto &&element) {
|
||||
return element.first ? void() : element.second(event, ref);
|
||||
};
|
||||
container_type swap_list;
|
||||
once_list.swap(swap_list);
|
||||
|
||||
publishing = true;
|
||||
|
||||
std::for_each(onL.rbegin(), onL.rend(), func);
|
||||
std::for_each(currentL.rbegin(), currentL.rend(), func);
|
||||
for(auto &&element: on_list) {
|
||||
element.first ? void() : element.second(event, ref);
|
||||
}
|
||||
|
||||
for(auto &&element: swap_list) {
|
||||
element.first ? void() : element.second(event, ref);
|
||||
}
|
||||
|
||||
publishing = false;
|
||||
|
||||
onL.remove_if([](auto &&element) { return element.first; });
|
||||
on_list.remove_if([](auto &&element) { return element.first; });
|
||||
}
|
||||
|
||||
ENTT_ID_TYPE type_id() const ENTT_NOEXCEPT override {
|
||||
return type_info<Event>::id();
|
||||
}
|
||||
|
||||
private:
|
||||
bool publishing{false};
|
||||
container_type onceL{};
|
||||
container_type onL{};
|
||||
container_type once_list{};
|
||||
container_type on_list{};
|
||||
};
|
||||
|
||||
template<typename Event>
|
||||
Handler<Event> & handler() ENTT_NOEXCEPT {
|
||||
const std::size_t family = handler_family::type<Event>();
|
||||
const pool_handler<Event> & assure() const {
|
||||
static_assert(std::is_same_v<Event, std::decay_t<Event>>);
|
||||
static std::size_t index{pools.size()};
|
||||
|
||||
if(!(family < handlers.size())) {
|
||||
handlers.resize(family+1);
|
||||
if(const auto length = pools.size(); !(index < length) || pools[index]->type_id() != type_info<Event>::id()) {
|
||||
for(index = {}; index < length && pools[index]->type_id() != type_info<Event>::id(); ++index);
|
||||
|
||||
if(index == pools.size()) {
|
||||
pools.emplace_back(new pool_handler<Event>{});
|
||||
}
|
||||
}
|
||||
|
||||
if(!handlers[family]) {
|
||||
handlers[family] = std::make_unique<Handler<Event>>();
|
||||
}
|
||||
return static_cast<pool_handler<Event> &>(*pools[index]);
|
||||
}
|
||||
|
||||
return static_cast<Handler<Event> &>(*handlers[family]);
|
||||
template<typename Event>
|
||||
pool_handler<Event> & assure() {
|
||||
return const_cast<pool_handler<Event> &>(std::as_const(*this).template assure<Event>());
|
||||
}
|
||||
|
||||
public:
|
||||
/** @brief Type of listeners accepted for the given event. */
|
||||
template<typename Event>
|
||||
using Listener = typename Handler<Event>::listener_type;
|
||||
using listener = typename pool_handler<Event>::listener_type;
|
||||
|
||||
/**
|
||||
* @brief Generic connection type for events.
|
||||
@@ -145,56 +159,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 pool_handler<Event>::connection_type {
|
||||
/** @brief Event emitters are friend classes of connections. */
|
||||
friend class Emitter;
|
||||
friend class emitter;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
Connection() ENTT_NOEXCEPT = default;
|
||||
connection() = 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 pool_handler<Event>::connection_type conn)
|
||||
: pool_handler<Event>::connection_type{std::move(conn)}
|
||||
{}
|
||||
|
||||
/*! @brief Default copy constructor. */
|
||||
Connection(const Connection &) = default;
|
||||
/*! @brief Default move constructor. */
|
||||
Connection(Connection &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Default copy assignment operator.
|
||||
* @return This connection.
|
||||
*/
|
||||
Connection & operator=(const Connection &) = default;
|
||||
|
||||
/**
|
||||
* @brief Default move assignment operator.
|
||||
* @return This connection.
|
||||
*/
|
||||
Connection & operator=(Connection &&) = default;
|
||||
};
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
Emitter() ENTT_NOEXCEPT = default;
|
||||
emitter() = default;
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
virtual ~Emitter() ENTT_NOEXCEPT {
|
||||
static_assert(std::is_base_of<Emitter<Derived>, Derived>::value, "!");
|
||||
virtual ~emitter() {
|
||||
static_assert(std::is_base_of_v<emitter<Derived>, Derived>);
|
||||
}
|
||||
|
||||
/*! @brief Copying an emitter isn't allowed. */
|
||||
Emitter(const Emitter &) = delete;
|
||||
/*! @brief Default move constructor. */
|
||||
Emitter(Emitter &&) = default;
|
||||
emitter(emitter &&) = default;
|
||||
|
||||
/*! @brief Copying an emitter isn't allowed. @return This emitter. */
|
||||
Emitter & operator=(const Emitter &) = delete;
|
||||
/*! @brief Default move assignment operator. @return This emitter. */
|
||||
Emitter & operator=(Emitter &&) = default;
|
||||
emitter & operator=(emitter &&) = default;
|
||||
|
||||
/**
|
||||
* @brief Emits the given event.
|
||||
@@ -209,7 +202,7 @@ public:
|
||||
*/
|
||||
template<typename Event, typename... Args>
|
||||
void publish(Args &&... args) {
|
||||
handler<Event>().publish({ std::forward<Args>(args)... }, *static_cast<Derived *>(this));
|
||||
assure<Event>().publish(Event{std::forward<Args>(args)...}, *static_cast<Derived *>(this));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,12 +222,12 @@ public:
|
||||
* instances for later uses.
|
||||
*
|
||||
* @tparam Event Type of event to which to connect the listener.
|
||||
* @param listener The listener to register.
|
||||
* @param instance The listener to register.
|
||||
* @return Connection object that can be used to disconnect the listener.
|
||||
*/
|
||||
template<typename Event>
|
||||
Connection<Event> on(Listener<Event> listener) {
|
||||
return handler<Event>().on(std::move(listener));
|
||||
connection<Event> on(listener<Event> instance) {
|
||||
return assure<Event>().on(std::move(instance));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -254,12 +247,12 @@ public:
|
||||
* instances for later uses.
|
||||
*
|
||||
* @tparam Event Type of event to which to connect the listener.
|
||||
* @param listener The listener to register.
|
||||
* @param instance The listener to register.
|
||||
* @return Connection object that can be used to disconnect the listener.
|
||||
*/
|
||||
template<typename Event>
|
||||
Connection<Event> once(Listener<Event> listener) {
|
||||
return handler<Event>().once(std::move(listener));
|
||||
connection<Event> once(listener<Event> instance) {
|
||||
return assure<Event>().once(std::move(instance));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,8 +265,8 @@ public:
|
||||
* @param conn A valid connection.
|
||||
*/
|
||||
template<typename Event>
|
||||
void erase(Connection<Event> conn) ENTT_NOEXCEPT {
|
||||
handler<Event>().erase(std::move(conn));
|
||||
void erase(connection<Event> conn) {
|
||||
assure<Event>().erase(std::move(conn));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -285,8 +278,8 @@ public:
|
||||
* @tparam Event Type of event to reset.
|
||||
*/
|
||||
template<typename Event>
|
||||
void clear() ENTT_NOEXCEPT {
|
||||
handler<Event>().clear();
|
||||
void clear() {
|
||||
assure<Event>().clear();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -296,9 +289,9 @@ public:
|
||||
* results in undefined behavior.
|
||||
*/
|
||||
void clear() ENTT_NOEXCEPT {
|
||||
std::for_each(handlers.begin(), handlers.end(), [](auto &&handler) {
|
||||
return handler ? handler->clear() : void();
|
||||
});
|
||||
for(auto &&cpool: pools) {
|
||||
cpool->clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -307,12 +300,8 @@ public:
|
||||
* @return True if there are no listeners registered, false otherwise.
|
||||
*/
|
||||
template<typename Event>
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
const std::size_t family = handler_family::type<Event>();
|
||||
|
||||
return (!(family < handlers.size()) ||
|
||||
!handlers[family] ||
|
||||
static_cast<Handler<Event> &>(*handlers[family]).empty());
|
||||
bool empty() const {
|
||||
return assure<Event>().empty();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -320,17 +309,17 @@ public:
|
||||
* @return True if there are no listeners registered, false otherwise.
|
||||
*/
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
return std::all_of(handlers.cbegin(), handlers.cend(), [](auto &&handler) {
|
||||
return !handler || handler->empty();
|
||||
return std::all_of(pools.cbegin(), pools.cend(), [](auto &&cpool) {
|
||||
return cpool->empty();
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<BaseHandler>> handlers{};
|
||||
mutable std::vector<std::unique_ptr<basic_pool>> pools{};
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_SIGNAL_EMITTER_HPP
|
||||
#endif
|
||||
|
||||
37
src/entt/signal/fwd.hpp
Normal file
37
src/entt/signal/fwd.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#ifndef ENTT_SIGNAL_FWD_HPP
|
||||
#define ENTT_SIGNAL_FWD_HPP
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/*! @class delegate */
|
||||
template<typename>
|
||||
class delegate;
|
||||
|
||||
/*! @class dispatcher */
|
||||
class dispatcher;
|
||||
|
||||
/*! @class emitter */
|
||||
template<typename>
|
||||
class emitter;
|
||||
|
||||
/*! @class connection */
|
||||
class connection;
|
||||
|
||||
/*! @class scoped_connection */
|
||||
struct scoped_connection;
|
||||
|
||||
/*! @class sink */
|
||||
template<typename>
|
||||
class sink;
|
||||
|
||||
/*! @class sigh */
|
||||
template<typename>
|
||||
class sigh;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -2,102 +2,22 @@
|
||||
#define ENTT_SIGNAL_SIGH_HPP
|
||||
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
#include "delegate.hpp"
|
||||
#include "fwd.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @cond TURN_OFF_DOXYGEN
|
||||
* Internal details not to be documented.
|
||||
*/
|
||||
|
||||
|
||||
namespace internal {
|
||||
|
||||
|
||||
template<typename>
|
||||
struct sigh_traits;
|
||||
|
||||
|
||||
template<typename Ret, typename... Args>
|
||||
struct sigh_traits<Ret(Args...)> {
|
||||
using proto_fn_type = Ret(void *, Args...);
|
||||
using call_type = std::pair<void *, proto_fn_type *>;
|
||||
};
|
||||
|
||||
|
||||
template<typename, typename>
|
||||
struct Invoker;
|
||||
|
||||
|
||||
template<typename Ret, typename... Args, typename Collector>
|
||||
struct Invoker<Ret(Args...), Collector> {
|
||||
using proto_fn_type = typename sigh_traits<Ret(Args...)>::proto_fn_type;
|
||||
|
||||
virtual ~Invoker() = default;
|
||||
|
||||
bool invoke(Collector &collector, proto_fn_type *proto, void *instance, Args... args) const {
|
||||
return collector(proto(instance, args...));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename... Args, typename Collector>
|
||||
struct Invoker<void(Args...), Collector> {
|
||||
using proto_fn_type = typename sigh_traits<void(Args...)>::proto_fn_type;
|
||||
|
||||
virtual ~Invoker() = default;
|
||||
|
||||
bool invoke(Collector &, proto_fn_type *proto, void *instance, Args... args) const {
|
||||
return (proto(instance, args...), true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename Ret>
|
||||
struct NullCollector final {
|
||||
using result_type = Ret;
|
||||
bool operator()(result_type) const ENTT_NOEXCEPT { return true; }
|
||||
};
|
||||
|
||||
|
||||
template<>
|
||||
struct NullCollector<void> final {
|
||||
using result_type = void;
|
||||
bool operator()() const ENTT_NOEXCEPT { return true; }
|
||||
};
|
||||
|
||||
|
||||
template<typename>
|
||||
struct DefaultCollector;
|
||||
|
||||
|
||||
template<typename Ret, typename... Args>
|
||||
struct DefaultCollector<Ret(Args...)> final {
|
||||
using collector_type = NullCollector<Ret>;
|
||||
};
|
||||
|
||||
|
||||
template<typename Function>
|
||||
using DefaultCollectorType = typename DefaultCollector<Function>::collector_type;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Internal details not to be documented.
|
||||
* @endcond TURN_OFF_DOXYGEN
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @brief Sink implementation.
|
||||
* @brief Sink class.
|
||||
*
|
||||
* Primary template isn't defined on purpose. All the specializations give a
|
||||
* compile-time error unless the template parameter is a function type.
|
||||
@@ -105,207 +25,46 @@ using DefaultCollectorType = typename DefaultCollector<Function>::collector_type
|
||||
* @tparam Function A valid function type.
|
||||
*/
|
||||
template<typename Function>
|
||||
class Sink;
|
||||
class sink;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Unmanaged signal handler declaration.
|
||||
* @brief Unmanaged signal handler.
|
||||
*
|
||||
* Primary template isn't defined on purpose. All the specializations give a
|
||||
* compile-time error unless the template parameter is a function type.
|
||||
*
|
||||
* @tparam Function A valid function type.
|
||||
* @tparam Collector Type of collector to use, if any.
|
||||
*/
|
||||
template<typename Function, typename Collector = internal::DefaultCollectorType<Function>>
|
||||
class SigH;
|
||||
template<typename Function>
|
||||
class sigh;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Sink implementation.
|
||||
* @brief Unmanaged signal handler.
|
||||
*
|
||||
* A sink is an opaque object used to connect listeners to signals.<br/>
|
||||
* The function type for a listener is the one of the signal to which it
|
||||
* belongs.
|
||||
*
|
||||
* The clear separation between a signal and a sink permits to store the
|
||||
* former as private data member without exposing the publish functionality
|
||||
* to the users of a class.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
*/
|
||||
template<typename Ret, typename... Args>
|
||||
class Sink<Ret(Args...)> final {
|
||||
/*! @brief A signal is allowed to create sinks. */
|
||||
template<typename, typename>
|
||||
friend class SigH;
|
||||
|
||||
using call_type = typename internal::sigh_traits<Ret(Args...)>::call_type;
|
||||
|
||||
template<Ret(*Function)(Args...)>
|
||||
static Ret proto(void *, Args... args) {
|
||||
return (Function)(args...);
|
||||
}
|
||||
|
||||
template<typename Class, Ret(Class:: *Member)(Args... args) const>
|
||||
static Ret proto(void *instance, Args... args) {
|
||||
return (static_cast<const Class *>(instance)->*Member)(args...);
|
||||
}
|
||||
|
||||
template<typename Class, Ret(Class:: *Member)(Args... args)>
|
||||
static Ret proto(void *instance, Args... args) {
|
||||
return (static_cast<Class *>(instance)->*Member)(args...);
|
||||
}
|
||||
|
||||
Sink(std::vector<call_type> *calls) ENTT_NOEXCEPT
|
||||
: calls{calls}
|
||||
{}
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Connects a free function to a signal.
|
||||
*
|
||||
* The signal handler performs checks to avoid multiple connections for
|
||||
* free functions.
|
||||
*
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<Ret(*Function)(Args...)>
|
||||
void connect() {
|
||||
disconnect<Function>();
|
||||
calls->emplace_back(nullptr, &proto<Function>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a member function for a given instance to a signal.
|
||||
*
|
||||
* The signal isn't responsible for the connected object. Users must
|
||||
* guarantee that the lifetime of the instance overcomes the one of the
|
||||
* signal. On the other side, the signal handler performs checks to
|
||||
* avoid multiple connections for the same member function of a given
|
||||
* instance.
|
||||
*
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class, Ret(Class:: *Member)(Args...) const = &Class::receive>
|
||||
void connect(Class *instance) {
|
||||
disconnect<Class, Member>(instance);
|
||||
calls->emplace_back(instance, &proto<Class, Member>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a member function for a given instance to a signal.
|
||||
*
|
||||
* The signal isn't responsible for the connected object. Users must
|
||||
* guarantee that the lifetime of the instance overcomes the one of the
|
||||
* signal. On the other side, the signal handler performs checks to
|
||||
* avoid multiple connections for the same member function of a given
|
||||
* instance.
|
||||
*
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class, Ret(Class:: *Member)(Args...) = &Class::receive>
|
||||
void connect(Class *instance) {
|
||||
disconnect<Class, Member>(instance);
|
||||
calls->emplace_back(instance, &proto<Class, Member>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a free function from a signal.
|
||||
* @tparam Function A valid free function pointer.
|
||||
*/
|
||||
template<Ret(*Function)(Args...)>
|
||||
void disconnect() {
|
||||
call_type target{nullptr, &proto<Function>};
|
||||
calls->erase(std::remove(calls->begin(), calls->end(), std::move(target)), calls->end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects the given member function from a signal.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class, Ret(Class:: *Member)(Args...) const>
|
||||
void disconnect(Class *instance) {
|
||||
call_type target{instance, &proto<Class, Member>};
|
||||
calls->erase(std::remove(calls->begin(), calls->end(), std::move(target)), calls->end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects the given member function from a signal.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class, Ret(Class:: *Member)(Args...)>
|
||||
void disconnect(Class *instance) {
|
||||
call_type target{instance, &proto<Class, Member>};
|
||||
calls->erase(std::remove(calls->begin(), calls->end(), std::move(target)), calls->end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes all existing connections for the given instance.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
*/
|
||||
template<typename Class>
|
||||
void disconnect(Class *instance) {
|
||||
auto func = [instance](const call_type &call) { return call.first == instance; };
|
||||
calls->erase(std::remove_if(calls->begin(), calls->end(), std::move(func)), calls->end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects all the listeners from a signal.
|
||||
*/
|
||||
void disconnect() {
|
||||
calls->clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<call_type> *calls;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Unmanaged signal handler definition.
|
||||
*
|
||||
* Unmanaged signal handler. It works directly with naked pointers to classes
|
||||
* and pointers to member functions as well as pointers to free functions. Users
|
||||
* of this class are in charge of disconnecting instances before deleting them.
|
||||
* It works directly with references to classes and pointers to member functions
|
||||
* as well as pointers to free functions. Users of this class are in charge of
|
||||
* disconnecting instances before deleting them.
|
||||
*
|
||||
* This class serves mainly two purposes:
|
||||
*
|
||||
* * Creating signals used later to notify a bunch of listeners.
|
||||
* * Creating signals to use later to notify a bunch of listeners.
|
||||
* * Collecting results from a set of functions like in a voting system.
|
||||
*
|
||||
* The default collector does nothing. To properly collect data, define and use
|
||||
* a class that has a call operator the signature of which is `bool(Param)` and:
|
||||
*
|
||||
* * `Param` is a type to which `Ret` can be converted.
|
||||
* * The return type is true if the handler must stop collecting data, false
|
||||
* otherwise.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
* @tparam Collector Type of collector to use, if any.
|
||||
*/
|
||||
template<typename Ret, typename... Args, typename Collector>
|
||||
class SigH<Ret(Args...), Collector> final: private internal::Invoker<Ret(Args...), Collector> {
|
||||
using call_type = typename internal::sigh_traits<Ret(Args...)>::call_type;
|
||||
template<typename Ret, typename... Args>
|
||||
class sigh<Ret(Args...)> {
|
||||
/*! @brief A sink is allowed to modify a signal. */
|
||||
friend class sink<Ret(Args...)>;
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename std::vector<call_type>::size_type;
|
||||
/*! @brief Collector type. */
|
||||
using collector_type = Collector;
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Sink type. */
|
||||
using sink_type = Sink<Ret(Args...)>;
|
||||
using sink_type = entt::sink<Ret(Args...)>;
|
||||
|
||||
/**
|
||||
* @brief Instance type when it comes to connecting member functions.
|
||||
@@ -330,19 +89,6 @@ public:
|
||||
return calls.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a sink object for the given signal.
|
||||
*
|
||||
* A sink is an opaque object used to connect listeners to signals.<br/>
|
||||
* The function type for a listener is the one of the signal to which it
|
||||
* belongs. The order of invocation of the listeners isn't guaranteed.
|
||||
*
|
||||
* @return A temporary sink object.
|
||||
*/
|
||||
sink_type sink() ENTT_NOEXCEPT {
|
||||
return { &calls };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Triggers a signal.
|
||||
*
|
||||
@@ -351,76 +97,419 @@ public:
|
||||
* @param args Arguments to use to invoke listeners.
|
||||
*/
|
||||
void publish(Args... args) const {
|
||||
for(auto pos = calls.size(); pos; --pos) {
|
||||
auto &call = calls[pos-1];
|
||||
call.second(call.first, args...);
|
||||
for(auto &&call: std::as_const(calls)) {
|
||||
call(args...);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Collects return values from the listeners.
|
||||
*
|
||||
* The collector must expose a call operator with the following properties:
|
||||
*
|
||||
* * The return type is either `void` or such that it's convertible to
|
||||
* `bool`. In the second case, a true value will stop the iteration.
|
||||
* * The list of parameters is empty if `Ret` is `void`, otherwise it
|
||||
* contains a single element such that `Ret` is convertible to it.
|
||||
*
|
||||
* @tparam Func Type of collector to use, if any.
|
||||
* @param func A valid function object.
|
||||
* @param args Arguments to use to invoke listeners.
|
||||
* @return An instance of the collector filled with collected data.
|
||||
*/
|
||||
collector_type collect(Args... args) const {
|
||||
collector_type collector;
|
||||
|
||||
template<typename Func>
|
||||
void collect(Func func, Args... args) const {
|
||||
for(auto &&call: calls) {
|
||||
if(!this->invoke(collector, call.second, call.first, args...)) {
|
||||
break;
|
||||
if constexpr(std::is_void_v<Ret>) {
|
||||
if constexpr(std::is_invocable_r_v<bool, Func>) {
|
||||
call(args...);
|
||||
if(func()) { break; }
|
||||
} else {
|
||||
call(args...);
|
||||
func();
|
||||
}
|
||||
} else {
|
||||
if constexpr(std::is_invocable_r_v<bool, Func, Ret>) {
|
||||
if(func(call(args...))) { break; }
|
||||
} else {
|
||||
func(call(args...));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return collector;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Swaps listeners between the two signals.
|
||||
* @param lhs A valid signal object.
|
||||
* @param rhs A valid signal object.
|
||||
*/
|
||||
friend void swap(SigH &lhs, SigH &rhs) {
|
||||
using std::swap;
|
||||
swap(lhs.calls, rhs.calls);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the contents of the two signals are identical.
|
||||
*
|
||||
* Two signals are identical if they have the same size and the same
|
||||
* listeners registered exactly in the same order.
|
||||
*
|
||||
* @param other Signal with which to compare.
|
||||
* @return True if the two signals are identical, false otherwise.
|
||||
*/
|
||||
bool operator==(const SigH &other) const ENTT_NOEXCEPT {
|
||||
return std::equal(calls.cbegin(), calls.cend(), other.calls.cbegin(), other.calls.cend());
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<call_type> calls;
|
||||
std::vector<delegate<Ret(Args...)>> calls;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Checks if the contents of the two signals are different.
|
||||
* @brief Connection class.
|
||||
*
|
||||
* Two signals are identical if they have the same size and the same
|
||||
* listeners registered exactly in the same order.
|
||||
* Opaque object the aim of which is to allow users to release an already
|
||||
* estabilished connection without having to keep a reference to the signal or
|
||||
* the sink that generated it.
|
||||
*/
|
||||
class connection {
|
||||
/*! @brief A sink is allowed to create connection objects. */
|
||||
template<typename>
|
||||
friend class sink;
|
||||
|
||||
connection(delegate<void(void *)> fn, void *ref)
|
||||
: disconnect{fn}, signal{ref}
|
||||
{}
|
||||
|
||||
public:
|
||||
/*! @brief Default constructor. */
|
||||
connection() = default;
|
||||
|
||||
/**
|
||||
* @brief Checks whether a connection is properly initialized.
|
||||
* @return True if the connection is properly initialized, false otherwise.
|
||||
*/
|
||||
explicit operator bool() const ENTT_NOEXCEPT {
|
||||
return static_cast<bool>(disconnect);
|
||||
}
|
||||
|
||||
/*! @brief Breaks the connection. */
|
||||
void release() {
|
||||
if(disconnect) {
|
||||
disconnect(signal);
|
||||
disconnect.reset();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
delegate<void(void *)> disconnect;
|
||||
void *signal{};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Scoped connection class.
|
||||
*
|
||||
* Opaque object the aim of which is to allow users to release an already
|
||||
* estabilished connection without having to keep a reference to the signal or
|
||||
* the sink that generated it.<br/>
|
||||
* A scoped connection automatically breaks the link between the two objects
|
||||
* when it goes out of scope.
|
||||
*/
|
||||
struct scoped_connection {
|
||||
/*! @brief Default constructor. */
|
||||
scoped_connection() = default;
|
||||
|
||||
/**
|
||||
* @brief Constructs a scoped connection from a basic connection.
|
||||
* @param other A valid connection object.
|
||||
*/
|
||||
scoped_connection(const connection &other)
|
||||
: conn{other}
|
||||
{}
|
||||
|
||||
/*! @brief Default copy constructor, deleted on purpose. */
|
||||
scoped_connection(const scoped_connection &) = delete;
|
||||
|
||||
/*! @brief Automatically breaks the link on destruction. */
|
||||
~scoped_connection() {
|
||||
conn.release();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Default copy assignment operator, deleted on purpose.
|
||||
* @return This scoped connection.
|
||||
*/
|
||||
scoped_connection & operator=(const scoped_connection &) = delete;
|
||||
|
||||
/**
|
||||
* @brief Acquires a connection.
|
||||
* @param other The connection object to acquire.
|
||||
* @return This scoped connection.
|
||||
*/
|
||||
scoped_connection & operator=(connection other) {
|
||||
conn = std::move(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks whether a scoped connection is properly initialized.
|
||||
* @return True if the connection is properly initialized, false otherwise.
|
||||
*/
|
||||
explicit operator bool() const ENTT_NOEXCEPT {
|
||||
return static_cast<bool>(conn);
|
||||
}
|
||||
|
||||
/*! @brief Breaks the connection. */
|
||||
void release() {
|
||||
conn.release();
|
||||
}
|
||||
|
||||
private:
|
||||
connection conn;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Sink class.
|
||||
*
|
||||
* A sink is used to connect listeners to signals and to disconnect them.<br/>
|
||||
* The function type for a listener is the one of the signal to which it
|
||||
* belongs.
|
||||
*
|
||||
* The clear separation between a signal and a sink permits to store the former
|
||||
* as private data member without exposing the publish functionality to the
|
||||
* users of the class.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
* @param lhs A valid signal object.
|
||||
* @param rhs A valid signal object.
|
||||
* @return True if the two signals are different, false otherwise.
|
||||
*/
|
||||
template<typename Ret, typename... Args>
|
||||
bool operator!=(const SigH<Ret(Args...)> &lhs, const SigH<Ret(Args...)> &rhs) ENTT_NOEXCEPT {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
class sink<Ret(Args...)> {
|
||||
using signal_type = sigh<Ret(Args...)>;
|
||||
using difference_type = typename std::iterator_traits<typename decltype(signal_type::calls)::iterator>::difference_type;
|
||||
|
||||
template<auto Candidate, typename Type>
|
||||
static void release(Type value_or_instance, void *signal) {
|
||||
sink{*static_cast<signal_type *>(signal)}.disconnect<Candidate>(value_or_instance);
|
||||
}
|
||||
|
||||
template<auto Candidate>
|
||||
static void release(void *signal) {
|
||||
sink{*static_cast<signal_type *>(signal)}.disconnect<Candidate>();
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a sink that is allowed to modify a given signal.
|
||||
* @param ref A valid reference to a signal object.
|
||||
*/
|
||||
sink(sigh<Ret(Args...)> &ref) ENTT_NOEXCEPT
|
||||
: offset{},
|
||||
signal{&ref}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Returns false if at least a listener is connected to the sink.
|
||||
* @return True if the sink has no listeners connected, false otherwise.
|
||||
*/
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
return signal->calls.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a sink that connects before a given free function or an
|
||||
* unbound member.
|
||||
* @tparam Function A valid free function pointer.
|
||||
* @return A properly initialized sink object.
|
||||
*/
|
||||
template<auto Function>
|
||||
sink before() {
|
||||
delegate<Ret(Args...)> call{};
|
||||
call.template connect<Function>();
|
||||
|
||||
const auto &calls = signal->calls;
|
||||
const auto it = std::find(calls.cbegin(), calls.cend(), std::move(call));
|
||||
|
||||
sink other{*this};
|
||||
other.offset = std::distance(it, calls.cend());
|
||||
return other;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a sink that connects before a free function with payload
|
||||
* or a bound member.
|
||||
* @tparam Candidate Member or free function to look for.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
* @param value_or_instance A valid object that fits the purpose.
|
||||
* @return A properly initialized sink object.
|
||||
*/
|
||||
template<auto Candidate, typename Type>
|
||||
sink before(Type &&value_or_instance) {
|
||||
delegate<Ret(Args...)> call{};
|
||||
call.template connect<Candidate>(std::forward<Type>(value_or_instance));
|
||||
|
||||
const auto &calls = signal->calls;
|
||||
const auto it = std::find(calls.cbegin(), calls.cend(), std::move(call));
|
||||
|
||||
sink other{*this};
|
||||
other.offset = std::distance(it, calls.cend());
|
||||
return other;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a sink that connects before a given instance or specific
|
||||
* payload.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
* @param value_or_instance A valid object that fits the purpose.
|
||||
* @return A properly initialized sink object.
|
||||
*/
|
||||
template<typename Type>
|
||||
sink before(Type &value_or_instance) {
|
||||
return before(&value_or_instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a sink that connects before a given instance or specific
|
||||
* payload.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
* @param value_or_instance A valid pointer that fits the purpose.
|
||||
* @return A properly initialized sink object.
|
||||
*/
|
||||
template<typename Type>
|
||||
sink before(Type *value_or_instance) {
|
||||
sink other{*this};
|
||||
|
||||
if(value_or_instance) {
|
||||
const auto &calls = signal->calls;
|
||||
const auto it = std::find_if(calls.cbegin(), calls.cend(), [value_or_instance](const auto &delegate) {
|
||||
return delegate.instance() == value_or_instance;
|
||||
});
|
||||
|
||||
other.offset = std::distance(it, calls.cend());
|
||||
}
|
||||
|
||||
return other;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a sink that connects before anything else.
|
||||
* @return A properly initialized sink object.
|
||||
*/
|
||||
sink before() {
|
||||
sink other{*this};
|
||||
other.offset = signal->calls.size();
|
||||
return other;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function or an unbound member to a signal.
|
||||
*
|
||||
* The signal handler performs checks to avoid multiple connections for the
|
||||
* same function.
|
||||
*
|
||||
* @tparam Candidate Function or member to connect to the signal.
|
||||
* @return A properly initialized connection object.
|
||||
*/
|
||||
template<auto Candidate>
|
||||
connection connect() {
|
||||
disconnect<Candidate>();
|
||||
|
||||
delegate<Ret(Args...)> call{};
|
||||
call.template connect<Candidate>();
|
||||
signal->calls.insert(signal->calls.end() - offset, std::move(call));
|
||||
|
||||
delegate<void(void *)> conn{};
|
||||
conn.template connect<&release<Candidate>>();
|
||||
return { std::move(conn), signal };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function with payload or a bound member 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 signal. 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 signal itself.
|
||||
*
|
||||
* @tparam Candidate Function or member to connect to the signal.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
* @param value_or_instance A valid object that fits the purpose.
|
||||
* @return A properly initialized connection object.
|
||||
*/
|
||||
template<auto Candidate, typename Type>
|
||||
connection connect(Type &&value_or_instance) {
|
||||
disconnect<Candidate>(value_or_instance);
|
||||
|
||||
delegate<Ret(Args...)> call{};
|
||||
call.template connect<Candidate>(value_or_instance);
|
||||
signal->calls.insert(signal->calls.end() - offset, std::move(call));
|
||||
|
||||
delegate<void(void *)> conn{};
|
||||
conn.template connect<&release<Candidate, Type>>(value_or_instance);
|
||||
return { std::move(conn), signal };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a free function or an unbound member from a signal.
|
||||
* @tparam Candidate Function or member to disconnect from the signal.
|
||||
*/
|
||||
template<auto Candidate>
|
||||
void disconnect() {
|
||||
auto &calls = signal->calls;
|
||||
delegate<Ret(Args...)> call{};
|
||||
call.template connect<Candidate>();
|
||||
calls.erase(std::remove(calls.begin(), calls.end(), std::move(call)), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects a free function with payload or a bound member from a
|
||||
* signal.
|
||||
* @tparam Candidate Function or member to disconnect from the signal.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
* @param value_or_instance A valid object that fits the purpose.
|
||||
*/
|
||||
template<auto Candidate, typename Type>
|
||||
void disconnect(Type &&value_or_instance) {
|
||||
auto &calls = signal->calls;
|
||||
delegate<Ret(Args...)> call{};
|
||||
call.template connect<Candidate>(std::forward<Type>(value_or_instance));
|
||||
calls.erase(std::remove(calls.begin(), calls.end(), std::move(call)), calls.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects free functions with payload or bound members from a
|
||||
* signal.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
* @param value_or_instance A valid object that fits the purpose.
|
||||
*/
|
||||
template<typename Type>
|
||||
void disconnect(Type &value_or_instance) {
|
||||
disconnect(&value_or_instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects free functions with payload or bound members from a
|
||||
* signal.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
* @param value_or_instance A valid object that fits the purpose.
|
||||
*/
|
||||
template<typename Type>
|
||||
void disconnect(Type *value_or_instance) {
|
||||
if(value_or_instance) {
|
||||
auto &calls = signal->calls;
|
||||
calls.erase(std::remove_if(calls.begin(), calls.end(), [value_or_instance](const auto &delegate) {
|
||||
return delegate.instance() == value_or_instance;
|
||||
}), calls.end());
|
||||
}
|
||||
}
|
||||
|
||||
/*! @brief Disconnects all the listeners from a signal. */
|
||||
void disconnect() {
|
||||
signal->calls.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
difference_type offset;
|
||||
signal_type *signal;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Deduction guide.
|
||||
*
|
||||
* It allows to deduce the function type of a sink directly from the signal it
|
||||
* refers to.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
*/
|
||||
template<typename Ret, typename... Args>
|
||||
sink(sigh<Ret(Args...)> &) ENTT_NOEXCEPT -> sink<Ret(Args...)>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_SIGNAL_SIGH_HPP
|
||||
#endif
|
||||
|
||||
@@ -2,96 +2,201 @@
|
||||
# Tests configuration
|
||||
#
|
||||
|
||||
include(FetchContent)
|
||||
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
if(FIND_GTEST_PACKAGE)
|
||||
find_package(GTest REQUIRED)
|
||||
else()
|
||||
FetchContent_Declare(
|
||||
googletest
|
||||
GIT_REPOSITORY https://github.com/google/googletest.git
|
||||
GIT_TAG master
|
||||
GIT_SHALLOW 1
|
||||
)
|
||||
|
||||
FetchContent_GetProperties(googletest)
|
||||
|
||||
if(NOT googletest_POPULATED)
|
||||
FetchContent_Populate(googletest)
|
||||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||
add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR})
|
||||
endif()
|
||||
|
||||
add_library(GTest::Main ALIAS gtest_main)
|
||||
|
||||
target_compile_features(gtest PUBLIC cxx_std_17)
|
||||
target_compile_features(gtest_main PUBLIC cxx_std_17)
|
||||
target_compile_features(gmock PUBLIC cxx_std_17)
|
||||
target_compile_features(gmock_main PUBLIC cxx_std_17)
|
||||
endif()
|
||||
|
||||
include_directories($<TARGET_PROPERTY:EnTT,INTERFACE_INCLUDE_DIRECTORIES>)
|
||||
add_compile_options($<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_OPTIONS>)
|
||||
|
||||
add_library(odr OBJECT odr.cpp)
|
||||
set_target_properties(odr PROPERTIES CXX_EXTENSIONS OFF)
|
||||
target_compile_definitions(odr PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_DEFINITIONS>)
|
||||
target_compile_features(odr PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_FEATURES>)
|
||||
target_compile_options(odr PRIVATE $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-pedantic -Wall>)
|
||||
target_compile_options(odr PRIVATE $<$<CXX_COMPILER_ID:MSVC>:/EHsc>)
|
||||
function(SETUP_TARGET TARGET_NAME)
|
||||
set_target_properties(${TARGET_NAME} PROPERTIES CXX_EXTENSIONS OFF)
|
||||
target_link_libraries(${TARGET_NAME} PRIVATE EnTT)
|
||||
|
||||
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>)
|
||||
target_compile_options(
|
||||
${TARGET_NAME}
|
||||
PRIVATE
|
||||
$<$<NOT:$<PLATFORM_ID:Windows>>:-pedantic -fvisibility=hidden -Wall -Wshadow -Wno-deprecated-declarations>
|
||||
$<$<PLATFORM_ID:Windows>:/EHsc /W1 /wd4996 /w14800>
|
||||
)
|
||||
|
||||
target_compile_options(
|
||||
${TARGET_NAME}
|
||||
PRIVATE
|
||||
$<$<AND:$<CONFIG:Debug>,$<NOT:$<PLATFORM_ID:Windows>>>:-O0 -g>
|
||||
$<$<AND:$<CONFIG:Release>,$<NOT:$<PLATFORM_ID:Windows>>>:-O2>
|
||||
$<$<AND:$<CONFIG:Debug>,$<PLATFORM_ID:Windows>>:/Od>
|
||||
$<$<AND:$<CONFIG:Release>,$<PLATFORM_ID:Windows>>:/O2>
|
||||
)
|
||||
endfunction()
|
||||
|
||||
add_library(odr OBJECT odr.cpp)
|
||||
SETUP_TARGET(odr)
|
||||
|
||||
function(SETUP_BASIC_TEST TEST_NAME TEST_SOURCES)
|
||||
add_executable(${TEST_NAME} $<TARGET_OBJECTS:odr> ${TEST_SOURCES})
|
||||
target_link_libraries(${TEST_NAME} PRIVATE GTest::Main Threads::Threads)
|
||||
SETUP_TARGET(${TEST_NAME})
|
||||
add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME})
|
||||
endmacro()
|
||||
endfunction()
|
||||
|
||||
function(SETUP_LIB_TEST TEST_NAME)
|
||||
add_library(_${TEST_NAME} SHARED lib/${TEST_NAME}/lib.cpp)
|
||||
SETUP_TARGET(_${TEST_NAME})
|
||||
SETUP_BASIC_TEST(lib_${TEST_NAME} lib/${TEST_NAME}/main.cpp)
|
||||
target_compile_definitions(_${TEST_NAME} PRIVATE ENTT_API_EXPORT ${ARGV1})
|
||||
target_compile_definitions(lib_${TEST_NAME} PRIVATE ENTT_API_IMPORT ${ARGV1})
|
||||
target_link_libraries(lib_${TEST_NAME} PRIVATE _${TEST_NAME})
|
||||
endfunction()
|
||||
|
||||
function(SETUP_PLUGIN_TEST TEST_NAME)
|
||||
add_library(_${TEST_NAME} MODULE lib/${TEST_NAME}/plugin.cpp)
|
||||
SETUP_TARGET(_${TEST_NAME})
|
||||
SETUP_BASIC_TEST(lib_${TEST_NAME} lib/${TEST_NAME}/main.cpp)
|
||||
target_include_directories(_${TEST_NAME} PRIVATE ${cr_INCLUDE_DIR})
|
||||
target_include_directories(lib_${TEST_NAME} PRIVATE ${cr_INCLUDE_DIR})
|
||||
target_compile_definitions(lib_${TEST_NAME} PRIVATE NOMINMAX PLUGIN="$<TARGET_FILE:_${TEST_NAME}>" ${ARGV1})
|
||||
target_compile_definitions(_${TEST_NAME} PRIVATE NOMINMAX ${ARGV1})
|
||||
target_link_libraries(lib_${TEST_NAME} PRIVATE ${CMAKE_DL_LIBS})
|
||||
endfunction()
|
||||
|
||||
# Test benchmark
|
||||
|
||||
if(BUILD_BENCHMARK)
|
||||
SETUP_AND_ADD_TEST(benchmark benchmark/benchmark.cpp)
|
||||
SETUP_BASIC_TEST(benchmark benchmark/benchmark.cpp)
|
||||
endif()
|
||||
|
||||
# Test mod
|
||||
# Test lib
|
||||
|
||||
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)
|
||||
if(BUILD_LIB)
|
||||
FetchContent_Declare(
|
||||
cr
|
||||
GIT_REPOSITORY https://github.com/fungos/cr.git
|
||||
GIT_TAG master
|
||||
GIT_SHALLOW 1
|
||||
)
|
||||
|
||||
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})
|
||||
FetchContent_GetProperties(cr)
|
||||
|
||||
if(NOT cr_POPULATED)
|
||||
FetchContent_Populate(cr)
|
||||
set(cr_INCLUDE_DIR ${cr_SOURCE_DIR})
|
||||
endif()
|
||||
|
||||
SETUP_LIB_TEST(dispatcher)
|
||||
SETUP_LIB_TEST(emitter)
|
||||
SETUP_LIB_TEST(meta)
|
||||
SETUP_LIB_TEST(registry)
|
||||
|
||||
SETUP_LIB_TEST(dispatcher_std ENTT_STANDARD_CPP)
|
||||
SETUP_LIB_TEST(emitter_std ENTT_STANDARD_CPP)
|
||||
SETUP_LIB_TEST(meta_std ENTT_STANDARD_CPP)
|
||||
SETUP_LIB_TEST(registry_std ENTT_STANDARD_CPP)
|
||||
|
||||
SETUP_PLUGIN_TEST(dispatcher_plugin)
|
||||
SETUP_PLUGIN_TEST(emitter_plugin)
|
||||
SETUP_PLUGIN_TEST(meta_plugin)
|
||||
SETUP_PLUGIN_TEST(registry_plugin)
|
||||
|
||||
SETUP_PLUGIN_TEST(dispatcher_plugin_std ENTT_STANDARD_CPP)
|
||||
SETUP_PLUGIN_TEST(emitter_plugin_std ENTT_STANDARD_CPP)
|
||||
SETUP_PLUGIN_TEST(meta_plugin_std ENTT_STANDARD_CPP)
|
||||
SETUP_PLUGIN_TEST(registry_plugin_std ENTT_STANDARD_CPP)
|
||||
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)
|
||||
FetchContent_Declare(
|
||||
cereal
|
||||
GIT_REPOSITORY https://github.com/USCiLab/cereal.git
|
||||
GIT_TAG v1.2.2
|
||||
GIT_SHALLOW 1
|
||||
)
|
||||
|
||||
SETUP_AND_ADD_TEST(cereal snapshot/snapshot.cpp)
|
||||
target_include_directories(cereal PRIVATE ${CEREAL_SRC_DIR})
|
||||
FetchContent_GetProperties(cereal)
|
||||
|
||||
if(NOT cereal_POPULATED)
|
||||
FetchContent_Populate(cereal)
|
||||
set(cereal_INCLUDE_DIR ${cereal_SOURCE_DIR}/include)
|
||||
endif()
|
||||
|
||||
SETUP_BASIC_TEST(cereal snapshot/snapshot.cpp)
|
||||
target_include_directories(cereal PRIVATE ${cereal_INCLUDE_DIR})
|
||||
endif()
|
||||
|
||||
# Test 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_BASIC_TEST(algorithm entt/core/algorithm.cpp)
|
||||
SETUP_BASIC_TEST(family entt/core/family.cpp)
|
||||
SETUP_BASIC_TEST(hashed_string entt/core/hashed_string.cpp)
|
||||
SETUP_BASIC_TEST(ident entt/core/ident.cpp)
|
||||
SETUP_BASIC_TEST(monostate entt/core/monostate.cpp)
|
||||
SETUP_BASIC_TEST(type_info entt/core/type_info.cpp)
|
||||
SETUP_BASIC_TEST(type_traits entt/core/type_traits.cpp)
|
||||
SETUP_BASIC_TEST(utility entt/core/utility.cpp)
|
||||
|
||||
# Test entity
|
||||
|
||||
SETUP_AND_ADD_TEST(actor entt/entity/actor.cpp)
|
||||
SETUP_AND_ADD_TEST(attachee entt/entity/attachee.cpp)
|
||||
SETUP_AND_ADD_TEST(entity entt/entity/entity.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(snapshot entt/entity/snapshot.cpp)
|
||||
SETUP_AND_ADD_TEST(sparse_set entt/entity/sparse_set.cpp)
|
||||
SETUP_AND_ADD_TEST(view entt/entity/view.cpp)
|
||||
SETUP_BASIC_TEST(actor entt/entity/actor.cpp)
|
||||
SETUP_BASIC_TEST(entity entt/entity/entity.cpp)
|
||||
SETUP_BASIC_TEST(group entt/entity/group.cpp)
|
||||
SETUP_BASIC_TEST(helper entt/entity/helper.cpp)
|
||||
SETUP_BASIC_TEST(observer entt/entity/observer.cpp)
|
||||
SETUP_BASIC_TEST(registry entt/entity/registry.cpp)
|
||||
SETUP_BASIC_TEST(runtime_view entt/entity/runtime_view.cpp)
|
||||
SETUP_BASIC_TEST(snapshot entt/entity/snapshot.cpp)
|
||||
SETUP_BASIC_TEST(sparse_set entt/entity/sparse_set.cpp)
|
||||
SETUP_BASIC_TEST(storage entt/entity/storage.cpp)
|
||||
SETUP_BASIC_TEST(view entt/entity/view.cpp)
|
||||
|
||||
# Test locator
|
||||
|
||||
SETUP_AND_ADD_TEST(locator entt/locator/locator.cpp)
|
||||
SETUP_BASIC_TEST(locator entt/locator/locator.cpp)
|
||||
|
||||
# Test meta
|
||||
|
||||
SETUP_BASIC_TEST(meta entt/meta/meta.cpp)
|
||||
|
||||
# Test process
|
||||
|
||||
SETUP_AND_ADD_TEST(process entt/process/process.cpp)
|
||||
SETUP_AND_ADD_TEST(scheduler entt/process/scheduler.cpp)
|
||||
SETUP_BASIC_TEST(process entt/process/process.cpp)
|
||||
SETUP_BASIC_TEST(scheduler entt/process/scheduler.cpp)
|
||||
|
||||
# Test resource
|
||||
|
||||
SETUP_AND_ADD_TEST(resource entt/resource/resource.cpp)
|
||||
SETUP_BASIC_TEST(resource entt/resource/resource.cpp)
|
||||
|
||||
# Test 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)
|
||||
SETUP_BASIC_TEST(delegate entt/signal/delegate.cpp)
|
||||
SETUP_BASIC_TEST(dispatcher entt/signal/dispatcher.cpp)
|
||||
SETUP_BASIC_TEST(emitter entt/signal/emitter.cpp)
|
||||
SETUP_BASIC_TEST(sigh entt/signal/sigh.cpp)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,10 +2,14 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/algorithm.hpp>
|
||||
|
||||
struct boxed_int {
|
||||
int value;
|
||||
};
|
||||
|
||||
TEST(Algorithm, StdSort) {
|
||||
// well, I'm pretty sure it works, it's std::sort!!
|
||||
std::array<int, 5> arr{{4, 1, 3, 2, 0}};
|
||||
entt::StdSort sort;
|
||||
entt::std_sort sort;
|
||||
|
||||
sort(arr.begin(), arr.end());
|
||||
|
||||
@@ -14,9 +18,23 @@ TEST(Algorithm, StdSort) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Algorithm, StdSortBoxedInt) {
|
||||
// well, I'm pretty sure it works, it's std::sort!!
|
||||
std::array<boxed_int, 6> arr{{{4}, {1}, {3}, {2}, {0}, {6}}};
|
||||
entt::std_sort sort;
|
||||
|
||||
sort(arr.begin(), arr.end(), [](const auto &lhs, const auto &rhs) {
|
||||
return lhs.value > rhs.value;
|
||||
});
|
||||
|
||||
for(typename decltype(arr)::size_type i = 0; i < (arr.size() - 1); ++i) {
|
||||
ASSERT_GT(arr[i].value, arr[i+1].value);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Algorithm, InsertionSort) {
|
||||
std::array<int, 5> arr{{4, 1, 3, 2, 0}};
|
||||
entt::InsertionSort sort;
|
||||
entt::insertion_sort sort;
|
||||
|
||||
sort(arr.begin(), arr.end());
|
||||
|
||||
@@ -25,16 +43,55 @@ TEST(Algorithm, InsertionSort) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Algorithm, OneShotBubbleSort) {
|
||||
std::array<int, 5> arr{{4, 1, 3, 2, 0}};
|
||||
entt::OneShotBubbleSort sort;
|
||||
TEST(Algorithm, InsertionSortBoxedInt) {
|
||||
std::array<boxed_int, 6> arr{{{4}, {1}, {3}, {2}, {0}, {6}}};
|
||||
entt::insertion_sort sort;
|
||||
|
||||
sort(arr.begin(), arr.end());
|
||||
sort(arr.begin(), arr.end());
|
||||
sort(arr.begin(), arr.end());
|
||||
sort(arr.begin(), arr.end());
|
||||
sort(arr.begin(), arr.end(), [](const auto &lhs, const auto &rhs) {
|
||||
return lhs.value > rhs.value;
|
||||
});
|
||||
|
||||
for(typename decltype(arr)::size_type i = 0; i < (arr.size() - 1); ++i) {
|
||||
ASSERT_GT(arr[i].value, arr[i+1].value);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Algorithm, InsertionSortEmptyContainer) {
|
||||
std::vector<int> vec{};
|
||||
entt::insertion_sort sort;
|
||||
// this should crash with asan enabled if we break the constraint
|
||||
sort(vec.begin(), vec.end());
|
||||
}
|
||||
|
||||
TEST(Algorithm, RadixSort) {
|
||||
std::array<uint32_t, 5> arr{{4, 1, 3, 2, 0}};
|
||||
entt::radix_sort<8, 32> sort;
|
||||
|
||||
sort(arr.begin(), arr.end(), [](const auto &value) {
|
||||
return value;
|
||||
});
|
||||
|
||||
for(typename decltype(arr)::size_type i = 0; i < (arr.size() - 1); ++i) {
|
||||
ASSERT_LT(arr[i], arr[i+1]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Algorithm, RadixSortBoxedInt) {
|
||||
std::array<boxed_int, 6> arr{{{4}, {1}, {3}, {2}, {0}, {6}}};
|
||||
entt::radix_sort<2, 6> sort;
|
||||
|
||||
sort(arr.rbegin(), arr.rend(), [](const auto &instance) {
|
||||
return instance.value;
|
||||
});
|
||||
|
||||
for(typename decltype(arr)::size_type i = 0; i < (arr.size() - 1); ++i) {
|
||||
ASSERT_GT(arr[i].value, arr[i+1].value);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Algorithm, RadixSortEmptyContainer) {
|
||||
std::vector<int> vec{};
|
||||
entt::radix_sort<8, 32> sort;
|
||||
// this should crash with asan enabled if we break the constraint
|
||||
sort(vec.begin(), vec.end());
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/family.hpp>
|
||||
|
||||
using my_family = entt::Family<struct MyFamily>;
|
||||
using your_family = entt::Family<struct YourFamily>;
|
||||
using a_family = entt::family<struct a_family_type>;
|
||||
using another_family = entt::family<struct another_family_type>;
|
||||
|
||||
TEST(Family, Functionalities) {
|
||||
auto myFamilyType = my_family::type<struct MyFamilyType>();
|
||||
auto mySameFamilyType = my_family::type<struct MyFamilyType>();
|
||||
auto myOtherFamilyType = my_family::type<struct MyOtherFamilyType>();
|
||||
auto yourFamilyType = your_family::type<struct YourFamilyType>();
|
||||
auto t1 = a_family::type<int>;
|
||||
auto t2 = a_family::type<int>;
|
||||
auto t3 = a_family::type<char>;
|
||||
auto t4 = another_family::type<double>;
|
||||
|
||||
ASSERT_EQ(myFamilyType, mySameFamilyType);
|
||||
ASSERT_NE(myFamilyType, myOtherFamilyType);
|
||||
ASSERT_EQ(myFamilyType, yourFamilyType);
|
||||
ASSERT_EQ(t1, t2);
|
||||
ASSERT_NE(t1, t3);
|
||||
ASSERT_EQ(t1, t4);
|
||||
}
|
||||
|
||||
TEST(Family, Uniqueness) {
|
||||
ASSERT_EQ(my_family::type<int>(), my_family::type<int &>());
|
||||
ASSERT_EQ(my_family::type<int>(), my_family::type<int &&>());
|
||||
ASSERT_EQ(my_family::type<int>(), my_family::type<const int &>());
|
||||
ASSERT_NE(a_family::type<int>, a_family::type<int &>);
|
||||
ASSERT_NE(a_family::type<int>, a_family::type<int &&>);
|
||||
ASSERT_NE(a_family::type<int>, a_family::type<const int &>);
|
||||
}
|
||||
|
||||
@@ -1,34 +1,128 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/hashed_string.hpp>
|
||||
|
||||
TEST(HashedString, Functionalities) {
|
||||
using hash_type = entt::HashedString::hash_type;
|
||||
using hash_type = entt::hashed_string::hash_type;
|
||||
|
||||
const char *bar = "bar";
|
||||
|
||||
auto fooHs = entt::HashedString{"foo"};
|
||||
auto barHs = entt::HashedString{bar};
|
||||
auto foo_hs = entt::hashed_string{"foo"};
|
||||
auto bar_hs = entt::hashed_string{bar};
|
||||
|
||||
ASSERT_NE(static_cast<hash_type>(fooHs), static_cast<hash_type>(barHs));
|
||||
ASSERT_STREQ(static_cast<const char *>(fooHs), "foo");
|
||||
ASSERT_STREQ(static_cast<const char *>(barHs), bar);
|
||||
ASSERT_NE(static_cast<hash_type>(foo_hs), static_cast<hash_type>(bar_hs));
|
||||
ASSERT_STREQ(static_cast<const char *>(foo_hs), "foo");
|
||||
ASSERT_STREQ(static_cast<const char *>(bar_hs), bar);
|
||||
ASSERT_STREQ(foo_hs.data(), "foo");
|
||||
ASSERT_STREQ(bar_hs.data(), bar);
|
||||
|
||||
ASSERT_EQ(fooHs, fooHs);
|
||||
ASSERT_NE(fooHs, barHs);
|
||||
ASSERT_EQ(foo_hs, foo_hs);
|
||||
ASSERT_NE(foo_hs, bar_hs);
|
||||
|
||||
entt::HashedString hs{"foobar"};
|
||||
entt::hashed_string hs{"foobar"};
|
||||
|
||||
ASSERT_EQ(static_cast<hash_type>(hs), 0x85944171f73967e8);
|
||||
ASSERT_EQ(static_cast<hash_type>(hs), 0xbf9cf968);
|
||||
ASSERT_EQ(hs.value(), 0xbf9cf968);
|
||||
|
||||
ASSERT_EQ(fooHs, "foo"_hs);
|
||||
ASSERT_NE(barHs, "foo"_hs);
|
||||
ASSERT_EQ(foo_hs, "foo"_hs);
|
||||
ASSERT_NE(bar_hs, "foo"_hs);
|
||||
}
|
||||
|
||||
TEST(HashedString, Empty) {
|
||||
using hash_type = entt::hashed_string::hash_type;
|
||||
|
||||
entt::hashed_string hs{};
|
||||
|
||||
ASSERT_EQ(static_cast<hash_type>(hs), hash_type{});
|
||||
ASSERT_EQ(static_cast<const char *>(hs), nullptr);
|
||||
}
|
||||
|
||||
TEST(HashedString, Constexprness) {
|
||||
using hash_type = entt::HashedString::hash_type;
|
||||
using hash_type = entt::hashed_string::hash_type;
|
||||
// how would you test a constexpr otherwise?
|
||||
(void)std::integral_constant<hash_type, entt::HashedString{"quux"}>{};
|
||||
(void)std::integral_constant<hash_type, entt::hashed_string{"quux"}>{};
|
||||
(void)std::integral_constant<hash_type, "quux"_hs>{};
|
||||
ASSERT_TRUE(true);
|
||||
}
|
||||
|
||||
TEST(HashedString, ToValue) {
|
||||
using hash_type = entt::hashed_string::hash_type;
|
||||
|
||||
const char *foobar = "foobar";
|
||||
|
||||
ASSERT_EQ(entt::hashed_string::value(foobar), 0xbf9cf968);
|
||||
// how would you test a constexpr otherwise?
|
||||
(void)std::integral_constant<hash_type, entt::hashed_string::value("quux")>{};
|
||||
}
|
||||
|
||||
TEST(HashedString, StringView) {
|
||||
std::string str{"__foobar__"};
|
||||
std::string_view view{str.data()+2, 6};
|
||||
ASSERT_EQ(entt::hashed_string::value(view.data(), view.size()), 0xbf9cf968);
|
||||
}
|
||||
|
||||
TEST(HashedWString, Functionalities) {
|
||||
using hash_type = entt::hashed_wstring::hash_type;
|
||||
|
||||
const wchar_t *bar = L"bar";
|
||||
|
||||
auto foo_hws = entt::hashed_wstring{L"foo"};
|
||||
auto bar_hws = entt::hashed_wstring{bar};
|
||||
|
||||
ASSERT_NE(static_cast<hash_type>(foo_hws), static_cast<hash_type>(bar_hws));
|
||||
ASSERT_STREQ(static_cast<const wchar_t *>(foo_hws), L"foo");
|
||||
ASSERT_STREQ(static_cast<const wchar_t *>(bar_hws), bar);
|
||||
ASSERT_STREQ(foo_hws.data(), L"foo");
|
||||
ASSERT_STREQ(bar_hws.data(), bar);
|
||||
|
||||
ASSERT_EQ(foo_hws, foo_hws);
|
||||
ASSERT_NE(foo_hws, bar_hws);
|
||||
|
||||
entt::hashed_wstring hws{L"foobar"};
|
||||
|
||||
ASSERT_EQ(static_cast<hash_type>(hws), 0xbf9cf968);
|
||||
ASSERT_EQ(hws.value(), 0xbf9cf968);
|
||||
|
||||
ASSERT_EQ(foo_hws, L"foo"_hws);
|
||||
ASSERT_NE(bar_hws, L"foo"_hws);
|
||||
}
|
||||
|
||||
TEST(HashedWString, Empty) {
|
||||
using hash_type = entt::hashed_wstring::hash_type;
|
||||
|
||||
entt::hashed_wstring hws{};
|
||||
|
||||
ASSERT_EQ(static_cast<hash_type>(hws), hash_type{});
|
||||
ASSERT_EQ(static_cast<const wchar_t *>(hws), nullptr);
|
||||
}
|
||||
|
||||
TEST(HashedWString, Constexprness) {
|
||||
using hash_type = entt::hashed_wstring::hash_type;
|
||||
// how would you test a constexpr otherwise?
|
||||
(void)std::integral_constant<hash_type, entt::hashed_wstring{L"quux"}>{};
|
||||
(void)std::integral_constant<hash_type, L"quux"_hws>{};
|
||||
ASSERT_TRUE(true);
|
||||
}
|
||||
|
||||
TEST(HashedWString, ToValue) {
|
||||
using hash_type = entt::hashed_wstring::hash_type;
|
||||
|
||||
const wchar_t *foobar = L"foobar";
|
||||
|
||||
ASSERT_EQ(entt::hashed_wstring::value(foobar), 0xbf9cf968);
|
||||
// how would you test a constexpr otherwise?
|
||||
(void)std::integral_constant<hash_type, entt::hashed_wstring::value(L"quux")>{};
|
||||
}
|
||||
|
||||
TEST(HashedWString, StringView) {
|
||||
std::wstring str{L"__foobar__"};
|
||||
std::wstring_view view{str.data()+2, 6};
|
||||
ASSERT_EQ(entt::hashed_wstring::value(view.data(), view.size()), 0xbf9cf968);
|
||||
}
|
||||
|
||||
TEST(BasicHashedString, DeductionGuide) {
|
||||
static_assert(std::is_same_v<decltype(entt::basic_hashed_string{"foo"}), entt::hashed_string>);
|
||||
static_assert(std::is_same_v<decltype(entt::basic_hashed_string{L"foo"}), entt::hashed_wstring>);
|
||||
}
|
||||
|
||||
@@ -2,31 +2,31 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/ident.hpp>
|
||||
|
||||
struct AType {};
|
||||
struct AnotherType {};
|
||||
struct a_type {};
|
||||
struct another_type {};
|
||||
|
||||
TEST(Identifier, Uniqueness) {
|
||||
using ID = entt::Identifier<AType, AnotherType>;
|
||||
constexpr AType anInstance;
|
||||
constexpr AnotherType anotherInstance;
|
||||
using id = entt::identifier<a_type, another_type>;
|
||||
constexpr a_type an_instance;
|
||||
constexpr another_type another_instance;
|
||||
|
||||
ASSERT_NE(ID::get<AType>(), ID::get<AnotherType>());
|
||||
ASSERT_EQ(ID::get<AType>(), ID::get<decltype(anInstance)>());
|
||||
ASSERT_NE(ID::get<AType>(), ID::get<decltype(anotherInstance)>());
|
||||
ASSERT_EQ(ID::get<AType>(), ID::get<AType>());
|
||||
ASSERT_EQ(ID::get<AnotherType>(), ID::get<AnotherType>());
|
||||
ASSERT_NE(id::type<a_type>, id::type<another_type>);
|
||||
ASSERT_EQ(id::type<a_type>, id::type<decltype(an_instance)>);
|
||||
ASSERT_NE(id::type<a_type>, id::type<decltype(another_instance)>);
|
||||
ASSERT_EQ(id::type<a_type>, id::type<a_type>);
|
||||
ASSERT_EQ(id::type<another_type>, id::type<another_type>);
|
||||
|
||||
// test uses in constant expressions
|
||||
switch(ID::get<AnotherType>()) {
|
||||
case ID::get<AType>():
|
||||
switch(id::type<another_type>) {
|
||||
case id::type<a_type>:
|
||||
FAIL();
|
||||
case ID::get<AnotherType>():
|
||||
case id::type<another_type>:
|
||||
SUCCEED();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Identifier, SingleType) {
|
||||
using ID = entt::Identifier<AType>;
|
||||
std::integral_constant<ID::identifier_type, ID::get<AType>()> ic;
|
||||
using id = entt::identifier<a_type>;
|
||||
std::integral_constant<id::identifier_type, id::type<a_type>> ic;
|
||||
(void)ic;
|
||||
}
|
||||
|
||||
@@ -3,18 +3,18 @@
|
||||
#include <entt/core/monostate.hpp>
|
||||
|
||||
TEST(Monostate, Functionalities) {
|
||||
const bool bPre = entt::Monostate<entt::HashedString{"foobar"}>{};
|
||||
const int iPre = entt::Monostate<"foobar"_hs>{};
|
||||
const bool b_pre = entt::monostate<entt::hashed_string{"foobar"}>{};
|
||||
const int i_pre = entt::monostate<"foobar"_hs>{};
|
||||
|
||||
ASSERT_FALSE(bPre);
|
||||
ASSERT_EQ(iPre, int{});
|
||||
ASSERT_FALSE(b_pre);
|
||||
ASSERT_EQ(i_pre, int{});
|
||||
|
||||
entt::Monostate<"foobar"_hs>{} = true;
|
||||
entt::Monostate<"foobar"_hs>{} = 42;
|
||||
entt::monostate<"foobar"_hs>{} = true;
|
||||
entt::monostate_v<"foobar"_hs> = 42;
|
||||
|
||||
const bool &bPost = entt::Monostate<"foobar"_hs>{};
|
||||
const int &iPost = entt::Monostate<entt::HashedString{"foobar"}>{};
|
||||
const bool &b_post = entt::monostate<"foobar"_hs>{};
|
||||
const int &i_post = entt::monostate_v<entt::hashed_string{"foobar"}>;
|
||||
|
||||
ASSERT_TRUE(bPost);
|
||||
ASSERT_EQ(iPost, 42);
|
||||
ASSERT_TRUE(b_post);
|
||||
ASSERT_EQ(i_post, 42);
|
||||
}
|
||||
|
||||
9
test/entt/core/type_info.cpp
Normal file
9
test/entt/core/type_info.cpp
Normal file
@@ -0,0 +1,9 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/core/hashed_string.hpp>
|
||||
#include <entt/core/type_info.hpp>
|
||||
|
||||
TEST(TypeId, Functionalities) {
|
||||
ASSERT_NE(entt::type_info<int>::id(), entt::type_info<const int>::id());
|
||||
ASSERT_NE(entt::type_info<int>::id(), entt::type_info<char>::id());
|
||||
ASSERT_EQ(entt::type_info<int>::id(), entt::type_info<int>::id());
|
||||
}
|
||||
43
test/entt/core/type_traits.cpp
Normal file
43
test/entt/core/type_traits.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/config/config.h>
|
||||
#include <entt/core/hashed_string.hpp>
|
||||
#include <entt/core/type_traits.hpp>
|
||||
|
||||
TEST(Choice, Functionalities) {
|
||||
ASSERT_TRUE((std::is_base_of_v<entt::choice_t<0>, entt::choice_t<1>>));
|
||||
ASSERT_FALSE((std::is_base_of_v<entt::choice_t<1>, entt::choice_t<0>>));
|
||||
}
|
||||
|
||||
TEST(TypeList, Functionalities) {
|
||||
using type = entt::type_list<int, char>;
|
||||
using other = entt::type_list<double>;
|
||||
|
||||
ASSERT_EQ(entt::type_list_size_v<type>, 2u);
|
||||
ASSERT_EQ(entt::type_list_size_v<other>, 1u);
|
||||
ASSERT_TRUE((std::is_same_v<entt::type_list_cat_t<type, other, type, other>, entt::type_list<int, char, double, int, char, double>>));
|
||||
ASSERT_TRUE((std::is_same_v<entt::type_list_cat_t<type, other>, entt::type_list<int, char, double>>));
|
||||
ASSERT_TRUE((std::is_same_v<entt::type_list_cat_t<type, type>, entt::type_list<int, char, int, char>>));
|
||||
ASSERT_TRUE((std::is_same_v<entt::type_list_unique_t<entt::type_list_cat_t<type, type>>, entt::type_list<int, char>>));
|
||||
}
|
||||
|
||||
TEST(IsEqualityComparable, Functionalities) {
|
||||
ASSERT_TRUE(entt::is_equality_comparable_v<int>);
|
||||
ASSERT_FALSE(entt::is_equality_comparable_v<void>);
|
||||
}
|
||||
|
||||
TEST(MemberClass, Functionalities) {
|
||||
struct clazz {
|
||||
char foo(int) { return {}; }
|
||||
int bar(double, float) const { return {}; }
|
||||
bool quux;
|
||||
};
|
||||
|
||||
ASSERT_TRUE((std::is_same_v<clazz, entt::member_class_t<decltype(&clazz::foo)>>));
|
||||
ASSERT_TRUE((std::is_same_v<clazz, entt::member_class_t<decltype(&clazz::bar)>>));
|
||||
ASSERT_TRUE((std::is_same_v<clazz, entt::member_class_t<decltype(&clazz::quux)>>));
|
||||
}
|
||||
|
||||
TEST(Tag, Functionalities) {
|
||||
ASSERT_EQ(entt::tag<"foobar"_hs>::value, entt::hashed_string::value("foobar"));
|
||||
ASSERT_TRUE((std::is_same_v<typename entt::tag<"foobar"_hs>::value_type, ENTT_ID_TYPE>));
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user