Compare commits
250 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06426e4fd7 | ||
|
|
c55a97c24d | ||
|
|
0d61289bf3 | ||
|
|
bf10cbc70b | ||
|
|
2d945e426b | ||
|
|
13250887fa | ||
|
|
3507c22968 | ||
|
|
cc3f98ebcd | ||
|
|
4116e2d6ac | ||
|
|
48eab6b4a7 | ||
|
|
25866b5369 | ||
|
|
c4dd06fa45 | ||
|
|
4846d211e0 | ||
|
|
a586ad1237 | ||
|
|
b701c9c464 | ||
|
|
d0f20ed2bf | ||
|
|
0f64a2f3b0 | ||
|
|
bd83fba6cd | ||
|
|
15b9255a25 | ||
|
|
6794d21487 | ||
|
|
118c4432ec | ||
|
|
ccda429bf1 | ||
|
|
590937d2a0 | ||
|
|
241827dd80 | ||
|
|
77b4e0b4bd | ||
|
|
6a53cb32d8 | ||
|
|
f12ff3b15e | ||
|
|
69ad8676b7 | ||
|
|
d0fd756f6b | ||
|
|
7f8ab67e9e | ||
|
|
0e68bb3d2c | ||
|
|
d5b3933752 | ||
|
|
0b7206a92d | ||
|
|
e875f306fd | ||
|
|
8ed5e5ee28 | ||
|
|
948b0d40f6 | ||
|
|
48f4feb7a7 | ||
|
|
ddc0a32bbc | ||
|
|
10a7c54364 | ||
|
|
a66fa9d844 | ||
|
|
a16f2ac15c | ||
|
|
b699797a40 | ||
|
|
a6e9520d06 | ||
|
|
f8310b1296 | ||
|
|
353bf99cd5 | ||
|
|
a478e4acc9 | ||
|
|
d810e0ba7d | ||
|
|
1e51ffdb72 | ||
|
|
7da1d1fc64 | ||
|
|
957697c383 | ||
|
|
107eb72225 | ||
|
|
810b77f9da | ||
|
|
5e3bc2049b | ||
|
|
229500347d | ||
|
|
1f461db0a1 | ||
|
|
6c55aafee3 | ||
|
|
d57e55b719 | ||
|
|
bf772e5fe5 | ||
|
|
413f3356ce | ||
|
|
f147326fe0 | ||
|
|
30c59644b6 | ||
|
|
3fa5acf2e6 | ||
|
|
3e6ded8823 | ||
|
|
612017aaa2 | ||
|
|
ef57d7e7b6 | ||
|
|
a8d0db5036 | ||
|
|
ad6b5f8fc1 | ||
|
|
e3cb6a0aec | ||
|
|
cbf18a7dc4 | ||
|
|
01559410a9 | ||
|
|
f2ab94fa7f | ||
|
|
d7394a8369 | ||
|
|
9feef11d6f | ||
|
|
dc4e5ddc3c | ||
|
|
8600781bb6 | ||
|
|
bdc7bbdc9d | ||
|
|
73badef594 | ||
|
|
9474e6c08c | ||
|
|
653ded0e6f | ||
|
|
e34bec7dee | ||
|
|
610b560fb5 | ||
|
|
0a03ddb8a7 | ||
|
|
f31790631a | ||
|
|
e07128760e | ||
|
|
dd02ae313d | ||
|
|
d14b0b6843 | ||
|
|
f763c8a777 | ||
|
|
4231b040d8 | ||
|
|
67c587e673 | ||
|
|
3978e24fd8 | ||
|
|
c45de0c032 | ||
|
|
ff935efea1 | ||
|
|
808464f47d | ||
|
|
c986a6c4dd | ||
|
|
17c0479343 | ||
|
|
d93238912a | ||
|
|
d6ef0956e6 | ||
|
|
05db0aad29 | ||
|
|
89bbdfa1fe | ||
|
|
871bda6198 | ||
|
|
20732c9206 | ||
|
|
2a34a3ebb6 | ||
|
|
3f04247a53 | ||
|
|
ca0a1f8f8b | ||
|
|
d8a9f0ca12 | ||
|
|
c2116b841e | ||
|
|
700cf69f18 | ||
|
|
ed5dee5218 | ||
|
|
b224dfdfac | ||
|
|
e7da68547f | ||
|
|
9a785ceb2e | ||
|
|
fca1cd5a1c | ||
|
|
24b862e32e | ||
|
|
2ec9043cf2 | ||
|
|
1102d63469 | ||
|
|
b89f39d78c | ||
|
|
7ba479c9c9 | ||
|
|
8ad6a2980c | ||
|
|
d3b6ed78d9 | ||
|
|
8bd5605c2a | ||
|
|
08dc2fcf33 | ||
|
|
caa8d16371 | ||
|
|
bce92b3d85 | ||
|
|
67858bf300 | ||
|
|
7157e7e77d | ||
|
|
cf5074bdc5 | ||
|
|
fda44063ce | ||
|
|
f9becda02c | ||
|
|
fb9fc952c6 | ||
|
|
18451edfe9 | ||
|
|
8c73cac72f | ||
|
|
c54cedf14b | ||
|
|
8ef4cdc9c3 | ||
|
|
c0213e84f6 | ||
|
|
29de6d89d4 | ||
|
|
c9bf38ce36 | ||
|
|
338eb75bab | ||
|
|
31b1b453b0 | ||
|
|
aaf0e145eb | ||
|
|
089b3e13fd | ||
|
|
e9da2ce12a | ||
|
|
92048ac17b | ||
|
|
5e8561a578 | ||
|
|
d2f5e13074 | ||
|
|
820178f006 | ||
|
|
0a36a91e6d | ||
|
|
5013a92795 | ||
|
|
d81ecfec32 | ||
|
|
e99d7e2c3c | ||
|
|
d417984ff3 | ||
|
|
d38b3e641b | ||
|
|
28ce491dd5 | ||
|
|
c260d72125 | ||
|
|
d1d1b3156d | ||
|
|
472064b751 | ||
|
|
95ab9a0b70 | ||
|
|
4b03f6a039 | ||
|
|
c3460727fa | ||
|
|
2cc1850212 | ||
|
|
2d7443acaf | ||
|
|
13d0b0940c | ||
|
|
c101797924 | ||
|
|
83b55f8e3f | ||
|
|
b3b6362cd9 | ||
|
|
fc9af32d5f | ||
|
|
4cd1025011 | ||
|
|
5233fe8abc | ||
|
|
041e31ea78 | ||
|
|
7a3e881099 | ||
|
|
631bf42f84 | ||
|
|
1f704a7019 | ||
|
|
d295c88474 | ||
|
|
1dd9da4dff | ||
|
|
f2eb0c8427 | ||
|
|
c8ba11faf8 | ||
|
|
a2e243d992 | ||
|
|
c588fff5ca | ||
|
|
87f9599fea | ||
|
|
0459599b1d | ||
|
|
9447b1a696 | ||
|
|
0ccb7443c2 | ||
|
|
02cf27091f | ||
|
|
fdfbd04503 | ||
|
|
866c18200a | ||
|
|
c1cada49d4 | ||
|
|
7bf550a75f | ||
|
|
9c540c03aa | ||
|
|
b3df46db19 | ||
|
|
7ca615a1c1 | ||
|
|
c83db557a6 | ||
|
|
d54594f11d | ||
|
|
434e38608f | ||
|
|
871f090ca0 | ||
|
|
d1d235e025 | ||
|
|
e822a5fd53 | ||
|
|
7b82a4ae50 | ||
|
|
c532e9f2eb | ||
|
|
3fd034816e | ||
|
|
bb4b868c79 | ||
|
|
3b3da11a36 | ||
|
|
f2cbb5306b | ||
|
|
94ede1b324 | ||
|
|
0367248338 | ||
|
|
936db30e58 | ||
|
|
4822f0dd11 | ||
|
|
456d220829 | ||
|
|
b459ba6ea7 | ||
|
|
a19ef9bd16 | ||
|
|
59cec88a28 | ||
|
|
3ebc75af80 | ||
|
|
4dce474e03 | ||
|
|
31a18da578 | ||
|
|
8c499850fc | ||
|
|
6b6998a247 | ||
|
|
a6cb0fc856 | ||
|
|
e36b93e87b | ||
|
|
1e3723b8bb | ||
|
|
412372289e | ||
|
|
96f7e66073 | ||
|
|
6040f8f263 | ||
|
|
9761b6e14a | ||
|
|
cb49910ed2 | ||
|
|
62bd742673 | ||
|
|
42d0a3d734 | ||
|
|
f0f8681455 | ||
|
|
c801afddcb | ||
|
|
20e0e1333e | ||
|
|
a6b373fec4 | ||
|
|
41c77720bb | ||
|
|
92e6340120 | ||
|
|
1221f63cbd | ||
|
|
0f24418891 | ||
|
|
f477c0ab87 | ||
|
|
9358691901 | ||
|
|
cd343ba598 | ||
|
|
50069d3743 | ||
|
|
1e03f27f23 | ||
|
|
36bb55a9ce | ||
|
|
451e4050db | ||
|
|
367fd3e87f | ||
|
|
a67a2e12fd | ||
|
|
292978daf0 | ||
|
|
85a4a76a14 | ||
|
|
9d0ab7ed70 | ||
|
|
3d5b6a5e0b | ||
|
|
ab20372093 | ||
|
|
ab887f30e4 | ||
|
|
6cb6a8c25f | ||
|
|
9d1d2aca0a | ||
|
|
75cb2cd1f7 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1 @@
|
|||||||
# QtCreator
|
|
||||||
*.user
|
*.user
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ matrix:
|
|||||||
compiler: clang
|
compiler: clang
|
||||||
env: COMPILER=clang++
|
env: COMPILER=clang++
|
||||||
- os: osx
|
- os: osx
|
||||||
osx_image: xcode9.1
|
osx_image: xcode9.4
|
||||||
compiler: clang
|
compiler: clang
|
||||||
env: COMPILER=clang++
|
env: COMPILER=clang++
|
||||||
- os: linux
|
- os: linux
|
||||||
@@ -71,3 +71,10 @@ script:
|
|||||||
- mkdir -p build && cd build
|
- mkdir -p build && cd build
|
||||||
- cmake .. && make -j4
|
- cmake .. && make -j4
|
||||||
- CTEST_OUTPUT_ON_FAILURE=1 make test
|
- 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”
|
||||||
|
|||||||
4
AUTHORS
4
AUTHORS
@@ -5,3 +5,7 @@ Michele Caini aka skypjack
|
|||||||
# Contributors
|
# Contributors
|
||||||
|
|
||||||
Paolo Monteverde aka morbo84
|
Paolo Monteverde aka morbo84
|
||||||
|
David Nerjes aka DavidHamburg
|
||||||
|
Indi Kernick aka Kerndog73
|
||||||
|
Malte Müller-Rowold aka m-waka
|
||||||
|
Richard Caseres aka richardbmx
|
||||||
|
|||||||
186
CMakeLists.txt
186
CMakeLists.txt
@@ -2,7 +2,7 @@
|
|||||||
# EnTT
|
# EnTT
|
||||||
#
|
#
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.2)
|
cmake_minimum_required(VERSION 3.7.2)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Building in-tree is not allowed (we take care of your craziness).
|
# Building in-tree is not allowed (we take care of your craziness).
|
||||||
@@ -16,7 +16,9 @@ endif()
|
|||||||
# Project configuration
|
# Project configuration
|
||||||
#
|
#
|
||||||
|
|
||||||
project(entt VERSION 2.2.0)
|
project(EnTT VERSION 2.7.3)
|
||||||
|
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
if(NOT CMAKE_BUILD_TYPE)
|
if(NOT CMAKE_BUILD_TYPE)
|
||||||
set(CMAKE_BUILD_TYPE Debug)
|
set(CMAKE_BUILD_TYPE Debug)
|
||||||
@@ -29,40 +31,132 @@ set(PROJECT_AUTHOR_EMAIL "michele.caini@gmail.com")
|
|||||||
|
|
||||||
message("*")
|
message("*")
|
||||||
message("* ${PROJECT_NAME} v${PROJECT_VERSION} (${CMAKE_BUILD_TYPE})")
|
message("* ${PROJECT_NAME} v${PROJECT_VERSION} (${CMAKE_BUILD_TYPE})")
|
||||||
message("* Copyright (c) 2017 ${PROJECT_AUTHOR} <${PROJECT_AUTHOR_EMAIL}>")
|
message("* Copyright (c) 2017-2018 ${PROJECT_AUTHOR} <${PROJECT_AUTHOR_EMAIL}>")
|
||||||
message("*")
|
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
|
# Compiler stuff
|
||||||
#
|
#
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 14)
|
if(NOT MSVC AND USE_LIBCPP)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
include(CheckCXXSourceCompiles)
|
||||||
|
include(CMakePushCheckState)
|
||||||
|
|
||||||
if(NOT MSVC)
|
cmake_push_check_state()
|
||||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
|
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic -Wall")
|
|
||||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DRELEASE")
|
|
||||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g -DDEBUG")
|
|
||||||
|
|
||||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -stdlib=libc++")
|
||||||
# it seems that -O3 ruins the performance when using clang ...
|
|
||||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
|
check_cxx_source_compiles("
|
||||||
else()
|
#include<type_traits>
|
||||||
# ... on the other side, GCC is incredibly comfortable with it.
|
int main() { return std::is_same<int, int>::value ? 0 : 1; }
|
||||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
|
" HAS_LIBCPP)
|
||||||
|
|
||||||
|
if(NOT HAS_LIBCPP)
|
||||||
|
message(WARNING "The option USE_LIBCPP is set (by default) but libc++ is not available. The flag will not be added to the target.")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
cmake_pop_check_state()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
#
|
#
|
||||||
# CMake configuration
|
# Add EnTT target
|
||||||
#
|
#
|
||||||
|
|
||||||
set(PROJECT_CMAKE_IN ${entt_SOURCE_DIR}/cmake/in)
|
add_library(EnTT INTERFACE)
|
||||||
set(PROJECT_DEPS_DIR ${entt_SOURCE_DIR}/deps)
|
|
||||||
set(PROJECT_SRC_DIR ${entt_SOURCE_DIR}/src)
|
|
||||||
|
|
||||||
set(PROJECT_RUNTIME_OUTPUT_DIRECTORY bin)
|
target_include_directories(
|
||||||
|
EnTT INTERFACE
|
||||||
|
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src>
|
||||||
|
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(
|
||||||
|
EnTT
|
||||||
|
INTERFACE $<$<AND:$<CONFIG:Debug>,$<NOT:$<CXX_COMPILER_ID:MSVC>>>:DEBUG>
|
||||||
|
INTERFACE $<$<AND:$<CONFIG:Release>,$<NOT:$<CXX_COMPILER_ID:MSVC>>>:RELEASE>
|
||||||
|
)
|
||||||
|
|
||||||
|
if(USE_ASAN)
|
||||||
|
target_compile_options(EnTT INTERFACE $<$<AND:$<CONFIG:Debug>,$<NOT:$<CXX_COMPILER_ID:MSVC>>>:-fsanitize=address -fno-omit-frame-pointer>)
|
||||||
|
target_link_libraries(EnTT INTERFACE $<$<AND:$<CONFIG:Debug>,$<NOT:$<CXX_COMPILER_ID:MSVC>>>:-fsanitize=address -fno-omit-frame-pointer>)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(USE_COMPILE_OPTIONS)
|
||||||
|
target_compile_options(
|
||||||
|
EnTT
|
||||||
|
INTERFACE $<$<AND:$<CONFIG:Debug>,$<NOT:$<CXX_COMPILER_ID:MSVC>>>:-O0 -g>
|
||||||
|
# it seems that -O3 ruins a bit the performance when using clang ...
|
||||||
|
INTERFACE $<$<AND:$<CONFIG:Release>,$<CXX_COMPILER_ID:Clang>>:-O2>
|
||||||
|
# ... on the other side, GCC is incredibly comfortable with it.
|
||||||
|
INTERFACE $<$<AND:$<CONFIG:Release>,$<CXX_COMPILER_ID:GNU>>:-O3>
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(HAS_LIBCPP)
|
||||||
|
target_compile_options(EnTT BEFORE INTERFACE -stdlib=libc++)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_compile_features(EnTT INTERFACE cxx_std_14)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Install EnTT
|
||||||
|
#
|
||||||
|
|
||||||
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
|
||||||
|
set(CUSTOM_INSTALL_CONFIGDIR cmake)
|
||||||
|
else()
|
||||||
|
set(CUSTOM_INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/entt)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
install(DIRECTORY src/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||||
|
install(TARGETS EnTT EXPORT EnTTTargets)
|
||||||
|
|
||||||
|
export(EXPORT EnTTTargets FILE ${EnTT_BINARY_DIR}/EnTTTargets.cmake)
|
||||||
|
|
||||||
|
install(
|
||||||
|
EXPORT EnTTTargets
|
||||||
|
FILE EnTTTargets.cmake
|
||||||
|
DESTINATION ${CUSTOM_INSTALL_CONFIGDIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Build tree package config file
|
||||||
|
#
|
||||||
|
|
||||||
|
configure_file(cmake/in/EnTTBuildConfig.cmake.in EnTTConfig.cmake @ONLY)
|
||||||
|
|
||||||
|
include(CMakePackageConfigHelpers)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Install tree package config file
|
||||||
|
#
|
||||||
|
|
||||||
|
configure_package_config_file(
|
||||||
|
cmake/in/EnTTConfig.cmake.in
|
||||||
|
${CUSTOM_INSTALL_CONFIGDIR}/EnTTConfig.cmake
|
||||||
|
INSTALL_DESTINATION ${CUSTOM_INSTALL_CONFIGDIR}
|
||||||
|
PATH_VARS CMAKE_INSTALL_INCLUDEDIR
|
||||||
|
NO_CHECK_REQUIRED_COMPONENTS_MACRO
|
||||||
|
)
|
||||||
|
|
||||||
|
write_basic_package_version_file(
|
||||||
|
${EnTT_BINARY_DIR}/EnTTConfigVersion.cmake
|
||||||
|
VERSION ${PROJECT_VERSION}
|
||||||
|
COMPATIBILITY AnyNewerVersion
|
||||||
|
)
|
||||||
|
|
||||||
|
install(
|
||||||
|
FILES
|
||||||
|
${EnTT_BINARY_DIR}/${CUSTOM_INSTALL_CONFIGDIR}/EnTTConfig.cmake
|
||||||
|
${EnTT_BINARY_DIR}/EnTTConfigVersion.cmake
|
||||||
|
DESTINATION ${CUSTOM_INSTALL_CONFIGDIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
export(PACKAGE EnTT)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Tests
|
# Tests
|
||||||
@@ -74,13 +168,24 @@ if(BUILD_TESTING)
|
|||||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
|
|
||||||
# gtest, gtest_main, gmock and gmock_main targets are available from now on
|
option(FIND_GTEST_PACKAGE "Enable finding gtest package." OFF)
|
||||||
set(GOOGLETEST_DEPS_DIR ${PROJECT_DEPS_DIR}/googletest)
|
|
||||||
configure_file(${PROJECT_CMAKE_IN}/googletest.in ${GOOGLETEST_DEPS_DIR}/CMakeLists.txt)
|
if(FIND_GTEST_PACKAGE)
|
||||||
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . WORKING_DIRECTORY ${GOOGLETEST_DEPS_DIR})
|
find_package(GTest REQUIRED)
|
||||||
execute_process(COMMAND ${CMAKE_COMMAND} --build . WORKING_DIRECTORY ${GOOGLETEST_DEPS_DIR})
|
else()
|
||||||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
# gtest, gtest_main, gmock and gmock_main targets are available from now on
|
||||||
add_subdirectory(${GOOGLETEST_DEPS_DIR}/src ${GOOGLETEST_DEPS_DIR}/build)
|
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_SNAPSHOT "Build snapshot example." OFF)
|
||||||
|
|
||||||
enable_testing()
|
enable_testing()
|
||||||
add_subdirectory(test)
|
add_subdirectory(test)
|
||||||
@@ -90,8 +195,27 @@ endif()
|
|||||||
# Documentation
|
# Documentation
|
||||||
#
|
#
|
||||||
|
|
||||||
find_package(Doxygen 1.8)
|
option(BUILD_DOCS "Enable building with documentation." OFF)
|
||||||
|
|
||||||
if(DOXYGEN_FOUND)
|
if(BUILD_DOCS)
|
||||||
add_subdirectory(docs)
|
find_package(Doxygen 1.8)
|
||||||
|
|
||||||
|
if(DOXYGEN_FOUND)
|
||||||
|
add_subdirectory(docs)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
#
|
||||||
|
# AOB
|
||||||
|
#
|
||||||
|
|
||||||
|
add_custom_target(
|
||||||
|
entt_aob
|
||||||
|
SOURCES
|
||||||
|
appveyor.yml
|
||||||
|
AUTHORS
|
||||||
|
LICENSE
|
||||||
|
README.md
|
||||||
|
TODO
|
||||||
|
.travis.yml
|
||||||
|
)
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2017 Michele Caini
|
Copyright (c) 2017-2018 Michele Caini
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
895
README.md
895
README.md
@@ -1,77 +1,80 @@
|
|||||||
# EnTT Framework
|

|
||||||
|
|
||||||
|
<!--
|
||||||
|
@cond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
[](https://travis-ci.org/skypjack/entt)
|
[](https://travis-ci.org/skypjack/entt)
|
||||||
[](https://ci.appveyor.com/project/skypjack/entt)
|
[](https://ci.appveyor.com/project/skypjack/entt)
|
||||||
[](https://coveralls.io/github/skypjack/entt?branch=master)
|
[](https://coveralls.io/github/skypjack/entt?branch=master)
|
||||||
|
[](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.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)
|
||||||
|
|
||||||
|
# Table of Contents
|
||||||
|
|
||||||
|
* [Introduction](#introduction)
|
||||||
|
* [Code Example](#code-example)
|
||||||
|
* [Motivation](#motivation)
|
||||||
|
* [Performance](#performance)
|
||||||
|
* [Build Instructions](#build-instructions)
|
||||||
|
* [Requirements](#requirements)
|
||||||
|
* [Library](#library)
|
||||||
|
* [Documentation](#documentation)
|
||||||
|
* [Tests](#tests)
|
||||||
|
* [Packaging Tools](#packaging-tools)
|
||||||
|
* [EnTT in Action](#entt-in-action)
|
||||||
|
* [Contributors](#contributors)
|
||||||
|
* [License](#license)
|
||||||
|
* [Support](#support)
|
||||||
|
* [Donation](#donation)
|
||||||
|
* [Hire me](#hire-me)
|
||||||
|
<!--
|
||||||
|
@endcond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
|
||||||
# Introduction
|
# Introduction
|
||||||
|
|
||||||
`EnTT` is a header-only, tiny and easy to use framework written in modern
|
`EnTT` is a header-only, tiny and easy to use entity-component system (and much
|
||||||
C++.<br/>
|
more) written in modern C++ and even
|
||||||
It was originally designed entirely around an architectural pattern called _ECS_
|
[used by Mojang in Minecraft](https://minecraft.net/en-us/attribution/).<br/>
|
||||||
that is used mostly in game development. For further details:
|
The entity-component-system (also known as _ECS_) is an architectural pattern
|
||||||
|
used mostly in game development. For further details:
|
||||||
|
|
||||||
* [Entity Systems Wiki](http://entity-systems.wikidot.com/)
|
* [Entity Systems Wiki](http://entity-systems.wikidot.com/)
|
||||||
* [Evolve Your Hierarchy](http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/)
|
* [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)
|
* [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 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
|
a while the codebase has grown and more and more classes have become part of the
|
||||||
of the repository.<br/>
|
repository.<br/>
|
||||||
That's why today it's called _the EnTT Framework_.
|
Here is a brief, yet incomplete list of what it offers today:
|
||||||
|
|
||||||
Currently, `EnTT` is tested on Linux, Microsoft Windows and OS X. 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.
|
|
||||||
|
|
||||||
## The framework
|
|
||||||
|
|
||||||
`EnTT` was written initially as a faster alternative to other well known and
|
|
||||||
open source entity-component systems. Nowadays the `EnTT` framework is moving
|
|
||||||
its first steps. Much more will come in the future and hopefully I'm going to
|
|
||||||
work on it for a long time.<br/>
|
|
||||||
Requests for feature, PR, suggestions ad feedback are highly appreciated.
|
|
||||||
|
|
||||||
If you find you can help me and want to contribute to the `EnTT` framework with
|
|
||||||
your experience or you do want to get part of the project for some other
|
|
||||||
reason, feel free to contact me directly (you can find the mail in the
|
|
||||||
[profile](https://github.com/skypjack)).<br/>
|
|
||||||
I can't promise that each and every contribution will be accepted, but I can
|
|
||||||
assure that I'll do my best to take them all seriously.
|
|
||||||
|
|
||||||
### State of the art
|
|
||||||
|
|
||||||
Here is a brief 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).
|
compile-time or at runtime).
|
||||||
* A constexpr utility for human readable resource identifiers.
|
* 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
|
* 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
|
views and a _pay for what you use_ policy to adjust performance and memory
|
||||||
pressure according to the users' requirements.
|
usage according to users' requirements.
|
||||||
* Actor class for those who aren't confident with entity-component systems.
|
* 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.
|
* The smallest and most basic implementation of a service locator ever seen.
|
||||||
* A cooperative scheduler for processes of any type.
|
* A cooperative scheduler for processes of any type.
|
||||||
* All what is needed for resource management (cache, loaders, handles).
|
* All what is needed for resource management (cache, loaders, handles).
|
||||||
* Signal handlers of any type, delegates and an event bus.
|
* 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.
|
* 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.
|
* An event dispatcher for immediate and delayed events to integrate in loops.
|
||||||
* ...
|
* ...
|
||||||
* Any other business.
|
* Any other business.
|
||||||
|
|
||||||
Consider it a work in progress. For more details and an updated list, please
|
Consider it a work in progress. The whole API is also fully documented in-code
|
||||||
refer to the [online documentation](https://skypjack.github.io/entt/).
|
for those who are brave enough to read it.
|
||||||
|
|
||||||
### A note about the README
|
Currently, `EnTT` is tested on Linux, Microsoft Windows and OS X. It has proven
|
||||||
|
to work also on both Android and iOS.<br/>
|
||||||
The README file stays true to the original project and it describes only the
|
Most likely it will not be problematic on other systems as well, but has not
|
||||||
entity-component system. However, the whole API is fully documented in-code and
|
been sufficiently tested so far.
|
||||||
the [online documentation](https://skypjack.github.io/entt/) contains much
|
|
||||||
more.<br/>
|
|
||||||
Continue reading to know how the core part of the project works or follow the
|
|
||||||
link above to take a look at the API reference for all other available classes.
|
|
||||||
|
|
||||||
## Code Example
|
## Code Example
|
||||||
|
|
||||||
@@ -120,7 +123,8 @@ int main() {
|
|||||||
std::uint64_t dt = 16;
|
std::uint64_t dt = 16;
|
||||||
|
|
||||||
for(auto i = 0; i < 10; ++i) {
|
for(auto i = 0; i < 10; ++i) {
|
||||||
auto entity = registry.create(Position{i * 1.f, i * 1.f});
|
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); }
|
if(i % 2 == 0) { registry.assign<Velocity>(entity, i * .1f, i * .1f); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,51 +139,64 @@ int main() {
|
|||||||
|
|
||||||
I started working on `EnTT` because of the wrong reason: my goal was to design
|
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
|
an entity-component system that beated another well known open source solution
|
||||||
in terms of performance.<br/>
|
in terms of performance and used (possibly) less memory in the average
|
||||||
I did it, of course, but it wasn't much satisfying. Actually it wasn't
|
case.<br/>
|
||||||
|
In the end, I did it, but it wasn't much satisfying. Actually it wasn't
|
||||||
satisfying at all. The fastest and nothing more, fairly little indeed. When I
|
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
|
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* entity-component system at the same
|
add all the features I wanted to see in *my own library* at the same time.
|
||||||
time.
|
|
||||||
|
|
||||||
Today `EnTT` is finally what I was looking for: still faster than its
|
Nowadays, `EnTT` is finally what I was looking for: still faster than its
|
||||||
_competitors_, a really good API and an amazing set of features. And even more,
|
_competitors_, lower memory usage in the average case, a really good API and an
|
||||||
of course.
|
amazing set of features. And even more, of course.
|
||||||
|
|
||||||
## Performance
|
## Performance
|
||||||
|
|
||||||
As it stands right now, `EnTT` is just fast enough for my requirements if
|
As it stands right now, `EnTT` is just fast enough for my requirements if
|
||||||
compared to my first choice (that was already amazingly fast indeed).<br/>
|
compared to my first choice (it was already amazingly fast actually).<br/>
|
||||||
Here is a comparision between the two (both of them compiled with GCC 7.2.0 on a
|
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):
|
Dell XPS 13 out of the mid 2014):
|
||||||
|
|
||||||
| Benchmark | EntityX (compile-time) | EnTT |
|
| Benchmark | EntityX (compile-time) | EnTT |
|
||||||
|-----------|-------------|-------------|
|
|-----------|-------------|-------------|
|
||||||
| Create 10M entities | 0.1289s | **0.0409s** |
|
| Create 1M entities | 0.0147s | **0.0046s** |
|
||||||
| Destroy 10M entities | **0.0531s** | 0.0546s |
|
| Destroy 1M entities | 0.0053s | **0.0045s** |
|
||||||
| Standard view, 10M entities, one component | 0.0107s | **1.6e-07s** |
|
| 1M entities, one component | 0.0012s | **1.9e-07s** |
|
||||||
| Standard view, 10M entities, two components | **0.0113s** | 0.0295s |
|
| 1M entities, two components | 0.0012s | **3.8e-07s** |
|
||||||
| Standard view, 10M entities, two components<br/>Half of the entities have all the components | **0.0078s** | 0.0150s |
|
| 1M entities, two components<br/>Half of the entities have all the components | 0.0009s | **3.8e-07s** |
|
||||||
| Standard view, 10M entities, two components<br/>One of the entities has all the components | 0.0071s | **8.8e-07s** |
|
| 1M entities, two components<br/>One of the entities has all the components | 0.0008s | **1.0e-06s** |
|
||||||
| Persistent view, 10M entities, two components | 0.0113s | **5.7e-07s** |
|
| 1M entities, five components | 0.0010s | **7.0e-07s** |
|
||||||
| Standard view, 10M entities, five components | **0.0091s** | 0.0688s |
|
| 1M entities, ten components | 0.0011s | **1.2e-06s** |
|
||||||
| Persistent view, 10M entities, five components | 0.0091s | **2.9e-07s** |
|
| 1M entities, ten components<br/>Half of the entities have all the components | 0.0010s | **1.2e-06s** |
|
||||||
| Standard view, 10M entities, ten components | **0.0105s** | 0.1403s |
|
| 1M entities, ten components<br/>One of the entities has all the components | 0.0008s | **1.2e-06s** |
|
||||||
| Standard view, 10M entities, ten components<br/>Half of the entities have all the components | **0.0090s** | 0.0620s |
|
| Sort 150k entities, one component<br/>Arrays are in reverse order | - | **0.0036s** |
|
||||||
| Standard view, 10M entities, ten components<br/>One of the entities has all the components | 0.0070s | **1.3e-06s** |
|
| Sort 150k entities, enforce permutation<br/>Arrays are in reverse order | - | **0.0005s** |
|
||||||
| Persistent view, 10M entities, ten components | 0.0105s | **6.2e-07s** |
|
| Sort 150k entities, one component<br/>Arrays are almost sorted, std::sort | - | **0.0035s** |
|
||||||
| Sort 150k entities, one component | - | **0.0084s** |
|
| Sort 150k entities, one component<br/>Arrays are almost sorted, insertion sort | - | **0.0007s** |
|
||||||
| Sort 150k entities, enforce permutation | - | **0.0067s** |
|
|
||||||
|
|
||||||
`EnTT` includes its own tests and benchmarks. See
|
Note: The default version of `EntityX` (`master` branch) wasn't added to the
|
||||||
[benchmark.cpp](https://github.com/skypjack/entt/blob/master/test/benchmark.cpp)
|
comparison because it's already much slower than its compile-time counterpart.
|
||||||
for further details.<br/>
|
|
||||||
On Github users can find also a
|
|
||||||
[benchmark suite](https://github.com/abeimler/ecs_benchmark) that compares a
|
|
||||||
bunch of different projects, one of which is `EnTT`.
|
|
||||||
|
|
||||||
Of course, probably I'll try to get out of `EnTT` more features and better
|
Pretty interesting, aren't them? In fact, these benchmarks are the same used by
|
||||||
performance in the future, mainly for fun.<br/>
|
`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.
|
||||||
|
|
||||||
|
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
|
If you want to contribute and/or have any suggestion, feel free to make a PR or
|
||||||
open an issue to discuss your idea.
|
open an issue to discuss your idea.
|
||||||
|
|
||||||
@@ -197,8 +214,8 @@ documentation:
|
|||||||
|
|
||||||
## Library
|
## Library
|
||||||
|
|
||||||
`EnTT` is a header-only library. This means that including the `entt.hpp`
|
`EnTT` is a header-only library. This means that including the `entt.hpp` header
|
||||||
header is enough to include the whole framework and use it. For those who are
|
is enough to include the library as a whole and use it. For those who are
|
||||||
interested only in the entity-component system, consider to include the sole
|
interested only in the entity-component system, consider to include the sole
|
||||||
`entity/registry.hpp` header instead.<br/>
|
`entity/registry.hpp` header instead.<br/>
|
||||||
It's a matter of adding the following line to the top of a file:
|
It's a matter of adding the following line to the top of a file:
|
||||||
@@ -222,8 +239,8 @@ The documentation is based on [doxygen](http://www.stack.nl/~dimitri/doxygen/).
|
|||||||
To build it:
|
To build it:
|
||||||
|
|
||||||
$ cd build
|
$ cd build
|
||||||
$ cmake ..
|
$ cmake .. -DBUILD_DOCS=ON
|
||||||
$ make docs
|
$ make
|
||||||
|
|
||||||
The API reference will be created in HTML format within the directory
|
The API reference will be created in HTML format within the directory
|
||||||
`build/docs/html`. To navigate it with your favorite browser:
|
`build/docs/html`. To navigate it with your favorite browser:
|
||||||
@@ -231,634 +248,148 @@ The API reference will be created in HTML format within the directory
|
|||||||
$ cd build
|
$ cd build
|
||||||
$ your_favorite_browser docs/html/index.html
|
$ your_favorite_browser docs/html/index.html
|
||||||
|
|
||||||
|
<!--
|
||||||
|
@cond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
The API reference is also available [online](https://skypjack.github.io/entt/)
|
The API reference is also available [online](https://skypjack.github.io/entt/)
|
||||||
for the latest version.
|
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.
|
||||||
|
<!--
|
||||||
|
@endcond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
To compile and run the tests, `EnTT` requires *googletest*.<br/>
|
To compile and run the tests, `EnTT` requires *googletest*.<br/>
|
||||||
`cmake` will download and compile the library before to compile anything else.
|
`cmake` will download and compile the library before compiling anything else.
|
||||||
|
In order to build without tests set CMake option `BUILD_TESTING=OFF`.
|
||||||
|
|
||||||
To build the tests:
|
To build the most basic set of tests:
|
||||||
|
|
||||||
* `$ cd build`
|
* `$ cd build`
|
||||||
* `$ cmake ..`
|
* `$ cmake ..`
|
||||||
* `$ make`
|
* `$ make`
|
||||||
* `$ make test`
|
* `$ make test`
|
||||||
|
|
||||||
To build the benchmarks, use the following line instead:
|
Note that benchmarks are not part of this set.
|
||||||
|
|
||||||
* `$ cmake -DCMAKE_BUILD_TYPE=Release ..`
|
# Packaging Tools
|
||||||
|
|
||||||
Benchmarks are compiled only in release mode currently.
|
`EnTT` is available for some of the most known packaging tools. In particular:
|
||||||
|
|
||||||
# Crash Course
|
* [`vcpkg`](https://github.com/Microsoft/vcpkg/tree/master/ports/entt),
|
||||||
|
Microsoft VC++ Packaging Tool.
|
||||||
## Design choices
|
* [`Homebrew`](https://github.com/skypjack/homebrew-entt), the missing package
|
||||||
|
manager for macOS.<br/>
|
||||||
### A bitset-free entity-component system
|
Available as a homebrew formula. Just type the following to install it:
|
||||||
|
```
|
||||||
`EnTT` is a _bitset-free_ entity-component system that doesn't require users to
|
brew install skypjack/entt/entt
|
||||||
specify the component set at compile-time.<br/>
|
|
||||||
That's the reason for which users can instantiate the core class simply as:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
entt::DefaultRegistry registry;
|
|
||||||
```
|
|
||||||
|
|
||||||
In place of its more annoying and error-prone counterpart:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
entt::DefaultRegistry<Comp0, Comp1, ..., CompN> registry;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pay per use
|
|
||||||
|
|
||||||
`EnTT` is entirely designed around the principle that users have to pay only for
|
|
||||||
what they want.
|
|
||||||
|
|
||||||
When it comes to use an entity-componet system, the tradeoff is usually between
|
|
||||||
performance and memory usage. The faster it is, the more memory it uses.
|
|
||||||
However, slightly worse performance along non-critical paths are the right price
|
|
||||||
to pay to reduce memory usage and I've always wondered why this kind of tools do
|
|
||||||
not leave me the choice.<br/>
|
|
||||||
`EnTT` follows a completely different approach. It squezees the best from the
|
|
||||||
basic data structures and gives users the possibility to pay more for higher
|
|
||||||
performance where needed.<br/>
|
|
||||||
The disadvantage of this approach is that users need to know the systems they
|
|
||||||
are working on and the tools they are using. Otherwise, the risk to ruin the
|
|
||||||
performance along critical paths is high.
|
|
||||||
|
|
||||||
So far, this choice has proven to be a good one and I really hope it can be for
|
|
||||||
many others besides me.
|
|
||||||
|
|
||||||
## Vademecum
|
|
||||||
|
|
||||||
The `Registry` to store, the `View`s to iterate. That's all.
|
|
||||||
|
|
||||||
An entity (the _E_ of an _ECS_) is an opaque identifier that users should just
|
|
||||||
use as-is and store around if needed. Do not try to inspect an entity
|
|
||||||
identifier, its type can change in future and a registry offers all the
|
|
||||||
functionalities to query them out-of-the-box. The underlying type of an entity
|
|
||||||
(either `std::uint16_t`, `std::uint32_t` or `std::uint64_t`) can be specified
|
|
||||||
when defining a registry (actually the DefaultRegistry is nothing more than a
|
|
||||||
Registry where the type of the entities is `std::uint32_t`).<br/>
|
|
||||||
Components (the _C_ of an _ECS_) should be plain old data structures or more
|
|
||||||
complex and moveable data structures with a proper constructor. Actually, the
|
|
||||||
sole requirement of a component type is that it must be both move constructible
|
|
||||||
and move assignable. They are list initialized by using the parameters provided
|
|
||||||
to construct the component itself. No need to register components or their types
|
|
||||||
neither with the registry nor with the entity-component system at all.<br/>
|
|
||||||
Systems (the _S_ of an _ECS_) are just plain functions, functors, lambdas or
|
|
||||||
whatever the users want. They can accept a Registry, a View or a PersistentView
|
|
||||||
and use them the way they prefer. No need to register systems or their types
|
|
||||||
neither with the registry nor with the entity-component system at all.
|
|
||||||
|
|
||||||
The following sections will explain in short how to use the entity-component
|
|
||||||
system, the core part of the whole framework.<br/>
|
|
||||||
In fact, the framework is composed of many other classes in addition to those
|
|
||||||
describe below. For more details, please refer to the
|
|
||||||
[online documentation](https://skypjack.github.io/entt/).
|
|
||||||
|
|
||||||
## The Registry, the Entity and the Component
|
|
||||||
|
|
||||||
A registry is used to store and manage entities as well as to create views to
|
|
||||||
iterate the underlying data structures.<br/>
|
|
||||||
Registry is a class template that lets the users decide what's the preferred
|
|
||||||
type to represent an entity. Because `std::uint32_t` is large enough for almost
|
|
||||||
all the cases, there exists also an alias named DefaultRegistry for
|
|
||||||
`Registry<std::uint32_t>`.
|
|
||||||
|
|
||||||
Entities are represented by _entitiy identifiers_. An entity identifier is an
|
|
||||||
opaque type that users should not inspect or modify in any way. It carries
|
|
||||||
information about the entity itself and its version.
|
|
||||||
|
|
||||||
A registry can be used both to construct and to destroy entities:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// constructs a naked entity with no components ad returns its identifier
|
|
||||||
auto entity = registry.create();
|
|
||||||
|
|
||||||
// constructs an entity and assigns it default-initialized components
|
|
||||||
auto another = registry.create<Position, Velocity>();
|
|
||||||
|
|
||||||
// destroys an entity and all its components
|
|
||||||
registry.destroy(entity);
|
|
||||||
```
|
|
||||||
|
|
||||||
Once an entity is deleted, the registry can freely reuse it internally with a
|
|
||||||
slightly different identifier. In particular, the version of an entity is
|
|
||||||
increased each and every time it's destroyed.<br/>
|
|
||||||
In case entity identifiers are stored around, the registry offers all the
|
|
||||||
functionalities required to test them and get out of the them all the
|
|
||||||
information they carry:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// returns true if the entity is still valid, false otherwise
|
|
||||||
bool b = registry.valid(entity);
|
|
||||||
|
|
||||||
// gets the version contained in the entity identifier
|
|
||||||
auto version = registry.version(entity);
|
|
||||||
|
|
||||||
// gets the actual version for the given entity
|
|
||||||
auto curr = registry.current(entity);
|
|
||||||
```
|
|
||||||
|
|
||||||
Components can be assigned to or removed from entities at any time with a few
|
|
||||||
calls to member functions of the registry. As for the entities, the registry
|
|
||||||
offers also a set of functionalities users can use to work with the components.
|
|
||||||
|
|
||||||
The `assign` member function template creates, initializes and assigns to an
|
|
||||||
entity the given component. It accepts a variable number of arguments that are
|
|
||||||
used to construct the component itself if present:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
registry.assign<Position>(entity, 0., 0.);
|
|
||||||
|
|
||||||
// ...
|
|
||||||
|
|
||||||
auto &velocity = registry.assign<Velocity>(entity);
|
|
||||||
velocity.dx = 0.;
|
|
||||||
velocity.dy = 0.;
|
|
||||||
```
|
|
||||||
|
|
||||||
If the entity already has the given component, the `replace` member function
|
|
||||||
template can be used to replace it:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
registry.replace<Position>(entity, 0., 0.);
|
|
||||||
|
|
||||||
// ...
|
|
||||||
|
|
||||||
auto &velocity = registry.replace<Velocity>(entity);
|
|
||||||
velocity.dx = 0.;
|
|
||||||
velocity.dy = 0.;
|
|
||||||
```
|
|
||||||
|
|
||||||
In case users want to assign a component to an entity, but it's unknown whether
|
|
||||||
the entity already has it or not, `accomodate` does the work in a single call
|
|
||||||
(there is a performance penalty to pay for that mainly due to the fact that it
|
|
||||||
must check if `entity` already has the given component or not):
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
registry.accomodate<Position>(entity, 0., 0.);
|
|
||||||
|
|
||||||
// ...
|
|
||||||
|
|
||||||
auto &velocity = registry.accomodate<Velocity>(entity);
|
|
||||||
velocity.dx = 0.;
|
|
||||||
velocity.dy = 0.;
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that `accomodate` is a sliglhty faster alternative for the following
|
|
||||||
`if`/`else` statement and nothing more:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
if(registry.has<Comp>(entity)) {
|
|
||||||
registry.replace<Comp>(entity, arg1, argN);
|
|
||||||
} else {
|
|
||||||
registry.assign<Comp>(entity, arg1, argN);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
As already shown, if in doubt about whether or not an entity has one or more
|
|
||||||
components, the `has` member function template may be useful:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
bool b = registry.has<Position, Velocity>(entity);
|
|
||||||
```
|
|
||||||
|
|
||||||
On the other side, if the goal is to delete a single component, the `remove`
|
|
||||||
member function template is the way to go when it's certain that the entity owns
|
|
||||||
a copy of the component:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
registry.remove<Position>(entity);
|
|
||||||
```
|
|
||||||
|
|
||||||
Otherwise consider to use the `reset` member function. It behaves similarly to
|
|
||||||
`remove` but with a strictly defined behaviour (and a performance penalty is the
|
|
||||||
price to pay for that). In particular it removes the component if and only if it
|
|
||||||
exists, otherwise it returns safely to the caller:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
registry.reset<Position>(entity);
|
|
||||||
```
|
|
||||||
|
|
||||||
There exist also two other _versions_ of the `reset` member function:
|
|
||||||
|
|
||||||
* If no entity is passed to it, `reset` will remove the given component from
|
|
||||||
each entity that has it:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
registry.reset<Position>();
|
|
||||||
```
|
```
|
||||||
|
|
||||||
* If neither the entity nor the component are specified, all the entities and
|
Consider this list a work in progress and help me to make it longer.
|
||||||
their components are destroyed:
|
|
||||||
|
# EnTT in Action
|
||||||
```cpp
|
|
||||||
registry.reset();
|
`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/>
|
||||||
Finally, references to components can be retrieved simply by doing this:
|
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.
|
||||||
```cpp
|
|
||||||
// either a non-const reference ...
|
Below an incomplete list of projects and articles:
|
||||||
entt::DefaultRegistry registry;
|
|
||||||
auto &position = registry.get<Position>(entity);
|
* [Minecraft](https://minecraft.net/en-us/attribution/): of course, **that**
|
||||||
|
Minecraft, by Mojang (see the open source attributions page).
|
||||||
// ... or a const one
|
* [Face Smash](https://play.google.com/store/apps/details?id=com.gamee.facesmash):
|
||||||
const auto &cregistry = registry;
|
the emojis dominate the world, destroy them all with your facial expressions.
|
||||||
const auto &position = cregistry.get<Position>(entity);
|
* [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.
|
||||||
The `get` member function template gives direct access to the component of an
|
[Check it out](https://indi-kernick.itch.io/classic-tower-defence).
|
||||||
entity stored in the underlying data structures of the registry.
|
* [The Machine](https://github.com/Kerndog73/The-Machine): a box pushing puzzler
|
||||||
|
with logic gates and other cool stuff.
|
||||||
### Single instance components
|
[Check it out](https://indi-kernick.itch.io/the-machine-web-version).
|
||||||
|
* [EnttPong](https://github.com/reworks/EnttPong): example game with `EnTT`.
|
||||||
In those cases where all what is needed is a single instance component, tags are
|
* [Space Battle: Huge edition](http://victor.madtriangles.com/code%20experiment/2018/06/11/post-ecs-battle-huge.html):
|
||||||
the right tool to achieve the purpose.<br/>
|
huge space battle built entirely from scratch.
|
||||||
Tags undergo the same requirements of components. They can be either plain old
|
* [Space Battle](https://github.com/vblanco20-1/ECS_SpaceBattle): huge space
|
||||||
data structures or more complex and moveable data structures with a proper
|
battle built on `UE4`.
|
||||||
constructor.<br/>
|
* [Experimenting with ECS in UE4](http://victor.madtriangles.com/code%20experiment/2018/03/25/post-ue4-ecs-battle.html):
|
||||||
Actually, the same type can be used both as a tag and as a component and the
|
interesting article about `UE4` and `EnTT`.
|
||||||
registry will not complain about it. It is up to the users to properly manage
|
* [Implementing ECS architecture in UE4](https://forums.unrealengine.com/development-discussion/c-gameplay-programming/1449913-implementing-ecs-architecture-in-ue4-giant-space-battle):
|
||||||
their own types.
|
giant space battle.
|
||||||
|
* [MatchOneEntt](https://github.com/mhaemmerle/MatchOneEntt): port of
|
||||||
Attaching tags to entities and removing them is trivial:
|
[Match One](https://github.com/sschmid/Match-One) for `Entitas-CSharp`.
|
||||||
|
* [Randballs](https://github.com/gale93/randballs): simple `SFML` and `EnTT`
|
||||||
```cpp
|
playground.
|
||||||
auto player = registry.create();
|
* ...
|
||||||
auto camera = registry.create();
|
|
||||||
|
If you know of other resources out there that are about `EnTT`, feel free to
|
||||||
// attaches a default-initialized tag to an entity
|
open an issue or a PR and I'll be glad to add them to the list.
|
||||||
registry.attach<PlayingCharacter>(player);
|
|
||||||
|
<!--
|
||||||
// attaches a tag to an entity and initializes it
|
@cond TURN_OFF_DOXYGEN
|
||||||
registry.attach<Camera>(camera, player);
|
-->
|
||||||
|
|
||||||
// removes tags from their owners
|
|
||||||
registry.remove<PlayingCharacter>();
|
|
||||||
registry.remove<Camera>();
|
|
||||||
```
|
|
||||||
|
|
||||||
If in doubt about whether or not a tag has already an owner, the `has` member
|
|
||||||
function template may be useful:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
bool b = registry.has<PlayingCharacter>();
|
|
||||||
```
|
|
||||||
|
|
||||||
References to tags can be retrieved simply by doing this:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// either a non-const reference ...
|
|
||||||
entt::DefaultRegistry registry;
|
|
||||||
auto &player = registry.get<PlayingCharacter>();
|
|
||||||
|
|
||||||
// ... or a const one
|
|
||||||
const auto &cregistry = registry;
|
|
||||||
const auto &camera = cregistry.get<Camera>();
|
|
||||||
```
|
|
||||||
|
|
||||||
The `get` member function template gives direct access to the tag as stored in
|
|
||||||
the underlying data structures of the registry.
|
|
||||||
|
|
||||||
As shown above, in almost all the cases the entity identifier isn't required,
|
|
||||||
since a single instance component can have only one associated entity and
|
|
||||||
therefore it doesn't make much sense to mention it explicitly.<br/>
|
|
||||||
To find out who the owner is, just do the following:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
auto player = registry.attachee<PlayingCharacter>();
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that iterating tags isn't possible for obvious reasons. Tags give direct
|
|
||||||
access to single entities and nothing more.
|
|
||||||
|
|
||||||
### Sorting: is it possible?
|
|
||||||
|
|
||||||
It goes without saying that sorting entities and components is possible with
|
|
||||||
`EnTT`.<br/>
|
|
||||||
In fact, there are two functions that respond to slightly different needs:
|
|
||||||
|
|
||||||
* Components can be sorted directly:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
registry.sort<Renderable>([](const auto &lhs, const auto &rhs) {
|
|
||||||
return lhs.z < rhs.z;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
* Components can be sorted according to the order imposed by another component:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
registry.sort<Movement, Physics>();
|
|
||||||
```
|
|
||||||
|
|
||||||
In this case, instances of `Movement` are arranged in memory so that cache
|
|
||||||
misses are minimized when the two components are iterated together.
|
|
||||||
|
|
||||||
## View: to persist or not to persist?
|
|
||||||
|
|
||||||
There are mainly two kinds of views: standard (also known as View) and
|
|
||||||
persistent (alsa known as PersistentView).<br/>
|
|
||||||
Both of them have pros and cons to take in consideration. In particular:
|
|
||||||
|
|
||||||
* Standard views:
|
|
||||||
|
|
||||||
Pros:
|
|
||||||
* They work out-of-the-box and don't require any dedicated data
|
|
||||||
structure.
|
|
||||||
* Creating and destroying them isn't expensive at all because they don't
|
|
||||||
have any type of initialization.
|
|
||||||
* They are the best tool to iterate single components.
|
|
||||||
* They are the best tool to iterate multiple components at once when
|
|
||||||
tags are involved or one of the component is assigned to a
|
|
||||||
significantly low number of entities.
|
|
||||||
* They don't affect any other operations of the registry.
|
|
||||||
|
|
||||||
Cons:
|
|
||||||
* Their performance tend to degenerate when the number of components
|
|
||||||
to iterate grows up and the most of the entities have all of them.
|
|
||||||
|
|
||||||
* Persistent views:
|
|
||||||
|
|
||||||
Pros:
|
|
||||||
* Once prepared, creating and destroying them isn't expensive at all
|
|
||||||
because they don't have any type of initialization.
|
|
||||||
* They are the best tool to iterate multiple components at once when
|
|
||||||
the most of the entities have all of them.
|
|
||||||
|
|
||||||
Cons:
|
|
||||||
* They have dedicated data structures and thus affect the memory
|
|
||||||
pressure to a minimal extent.
|
|
||||||
* If not previously prepared, the first time they are used they go
|
|
||||||
through an initialization step that could take a while.
|
|
||||||
* They affect to a minimum the creation and destruction of entities and
|
|
||||||
components. In other terms: the more persistent views there will be,
|
|
||||||
the less performing will be creating and destroying entities and
|
|
||||||
components.
|
|
||||||
|
|
||||||
To sum up and as a rule of thumb, use a standard view:
|
|
||||||
* To iterate entities for a single component.
|
|
||||||
* To iterate entities for multiple components when a significantly low
|
|
||||||
number of entities have one of the components.
|
|
||||||
* In all those cases where a persistent view would give a boost to
|
|
||||||
performance but the iteration isn't performed frequently.
|
|
||||||
|
|
||||||
Use a persistent view in all the other cases.
|
|
||||||
|
|
||||||
To easily iterate entities, all the views offer the common `begin` and `end`
|
|
||||||
member functions that allow users to use a view in a typical range-for
|
|
||||||
loop.<br/>
|
|
||||||
Continue reading for more details or refer to the
|
|
||||||
[official documentation](https://skypjack.github.io/entt/).
|
|
||||||
|
|
||||||
### Standard View
|
|
||||||
|
|
||||||
A standard view behaves differently if it's constructed for a single component
|
|
||||||
or if it has been requested to iterate multiple components. Even the API is
|
|
||||||
different in the two cases.<br/>
|
|
||||||
All that they share is the way they are created by means of a registry:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// single component standard view
|
|
||||||
auto single = registry.view<Position>();
|
|
||||||
|
|
||||||
// multi component standard view
|
|
||||||
auto multi = registry.view<Position, Velocity>();
|
|
||||||
```
|
|
||||||
|
|
||||||
For all that remains, it's worth discussing them separately.<br/>
|
|
||||||
|
|
||||||
#### Single component standard view
|
|
||||||
|
|
||||||
Single component standard views are specialized in order to give a boost in
|
|
||||||
terms of performance in all the situation. This kind of views can access the
|
|
||||||
underlying data structures directly and avoid superflous checks.<br/>
|
|
||||||
They offer a bunch of functionalities to get the number of entities they are
|
|
||||||
going to return and a raw access to the entity list as well as to the component
|
|
||||||
list.<br/>
|
|
||||||
Refer to the [official documentation](https://skypjack.github.io/entt/) for all
|
|
||||||
the details.
|
|
||||||
|
|
||||||
There is no need to store views around for they are extremely cheap to
|
|
||||||
construct, even though they can be copied without problems and reused
|
|
||||||
freely. In fact, they return newly created and correctly initialized iterators
|
|
||||||
whenever `begin` or `end` are invoked.<br/>
|
|
||||||
To iterate a single component standard view, either use it in range-for loop:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
auto view = registry.view<Renderable>();
|
|
||||||
|
|
||||||
for(auto entity: view) {
|
|
||||||
auto &renderable = view.get(entity);
|
|
||||||
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Or rely on the `each` member function to iterate entities and get all their
|
|
||||||
components at once:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
registry.view<Renderable>().each([](auto entity, auto &renderable) {
|
|
||||||
// ...
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Performance are more or less the same. The best approach depends mainly on
|
|
||||||
whether all the components have to be accessed or not.
|
|
||||||
|
|
||||||
**Note**: prefer the `get` member function of a view instead of the `get` member
|
|
||||||
function template of a registry during iterations, if possible. However, keep in
|
|
||||||
mind that it works only with the components of the view itself.
|
|
||||||
|
|
||||||
#### Multi component standard view
|
|
||||||
|
|
||||||
Multi component standard views iterate entities that have at least all the given
|
|
||||||
components in their bags. During construction, these views look at the number
|
|
||||||
of entities available for each component and pick up a reference to the smallest
|
|
||||||
set of candidates in order to speed up iterations.<br/>
|
|
||||||
They offer fewer functionalities than their companion views for single
|
|
||||||
component, the most important of which can be used to reset the view and refresh
|
|
||||||
the reference to the set of candidate entities to iterate.<br/>
|
|
||||||
Refer to the [official documentation](https://skypjack.github.io/entt/) for all
|
|
||||||
the details.
|
|
||||||
|
|
||||||
There is no need to store views around for they are extremely cheap to
|
|
||||||
construct, even though they can be copied without problems and reused
|
|
||||||
freely. In fact, they return newly created and correctly initialized iterators
|
|
||||||
whenever `begin` or `end` are invoked.<br/>
|
|
||||||
To iterate a multi component standard view, either use it in range-for loop:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
auto view = registry.view<Position, Velocity>();
|
|
||||||
|
|
||||||
for(auto entity: view) {
|
|
||||||
auto &position = view.get<Position>(entity);
|
|
||||||
auto &velocity = view.get<Velocity>(entity);
|
|
||||||
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Or rely on the `each` member function to iterate entities and get all their
|
|
||||||
components at once:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
registry.view<Position, Velocity>().each([](auto entity, auto &position, auto &velocity) {
|
|
||||||
// ...
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Performance are more or less the same. The best approach depends mainly on
|
|
||||||
whether all the components have to be accessed or not.
|
|
||||||
|
|
||||||
**Note**: prefer the `get` member function of a view instead of the `get` member
|
|
||||||
function template of a registry during iterations, if possible. However, keep in
|
|
||||||
mind that it works only with the components of the view itself.
|
|
||||||
|
|
||||||
### Persistent View
|
|
||||||
|
|
||||||
A persistent view returns all the entities and only the entities that have at
|
|
||||||
least the given components. Moreover, it's guaranteed that the entity list is
|
|
||||||
thightly packed in memory for fast iterations.<br/>
|
|
||||||
In general, persistent views don't stay true to the order of any set of
|
|
||||||
components unless users explicitly sort them.
|
|
||||||
|
|
||||||
Persistent views can be used only to iterate multiple components. Create them
|
|
||||||
as it follows:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
auto view = registry.persistent<Position, Velocity>();
|
|
||||||
```
|
|
||||||
|
|
||||||
There is no need to store views around for they are extremely cheap to
|
|
||||||
construct, even though they can be copied without problems and reused
|
|
||||||
freely. In fact, they return newly created and correctly initialized iterators
|
|
||||||
whenever `begin` or `end` are invoked.<br/>
|
|
||||||
That being said, persistent views perform an initialization step the very first
|
|
||||||
time they are constructed and this could be quite costly. To avoid it, consider
|
|
||||||
asking to the registry to _prepare_ them when no entities have been created yet:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
registry.prepare<Position, Velocity>();
|
|
||||||
```
|
|
||||||
|
|
||||||
If the registry is empty, preparation is extremely fast. Moreover the `prepare`
|
|
||||||
member function template is idempotent. Feel free to invoke it even more than
|
|
||||||
once: if the view has been alreadt prepared before, the function returns
|
|
||||||
immediately and does nothing.
|
|
||||||
|
|
||||||
A persistent view offers a bunch of functionalities to get the number of
|
|
||||||
entities it's going to return, a raw access to the entity list and the
|
|
||||||
possibility to sort the underlying data structures according to the order of one
|
|
||||||
of the components for which it has been constructed.<br/>
|
|
||||||
Refer to the [official documentation](https://skypjack.github.io/entt/) for all
|
|
||||||
the details.
|
|
||||||
|
|
||||||
To iterate a persistent view, either use it in range-for loop:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
auto view = registry.persistent<Position, Velocity>();
|
|
||||||
|
|
||||||
for(auto entity: view) {
|
|
||||||
auto &position = view.get<Position>(entity);
|
|
||||||
auto &velocity = view.get<Velocity>(entity);
|
|
||||||
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Or rely on the `each` member function to iterate entities and get all their
|
|
||||||
components at once:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
registry.persistent<Position, Velocity>().each([](auto entity, auto &position, auto &velocity) {
|
|
||||||
// ...
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Performance are more or less the same. The best approach depends mainly on
|
|
||||||
whether all the components have to be accessed or not.
|
|
||||||
|
|
||||||
**Note**: prefer the `get` member function of a view instead of the `get` member
|
|
||||||
function template of a registry during iterations, if possible. However, keep in
|
|
||||||
mind that it works only with the components of the view itself.
|
|
||||||
|
|
||||||
## Side notes
|
|
||||||
|
|
||||||
* Entity identifiers are numbers and nothing more. They are not classes and they
|
|
||||||
have no member functions at all. As already mentioned, do no try to inspect or
|
|
||||||
modify an entity descriptor in any way.
|
|
||||||
|
|
||||||
* As shown in the examples above, the preferred way to get references to the
|
|
||||||
components while iterating a view is by using the view itself. It's a faster
|
|
||||||
alternative to the `get` member function template that is part of the API of
|
|
||||||
the Registry. That's because the registry must ensure that a pool for the
|
|
||||||
given component exists before to use it; on the other side, views force the
|
|
||||||
construction of the pools for all their components and access them directly,
|
|
||||||
thus avoiding all the checks.
|
|
||||||
|
|
||||||
* Most of the _ECS_ available out there have an annoying limitation (at least
|
|
||||||
from my point of view): entities and components cannot be created and/or
|
|
||||||
deleted during iterations.<br/>
|
|
||||||
`EnTT` partially solves the problem with a few limitations:
|
|
||||||
|
|
||||||
* Creating entities and components is allowed during iterations.
|
|
||||||
* Deleting an entity or removing its components is allowed during
|
|
||||||
iterations if it's the one currently returned by a view. For all the
|
|
||||||
other entities, destroying them or removing their components isn't
|
|
||||||
allowed and it can result in undefined behavior.
|
|
||||||
|
|
||||||
Iterators are invalidated and the behaviour is undefined if an entity is
|
|
||||||
modified or destroyed and it's not the one currently returned by the
|
|
||||||
view.<br/>
|
|
||||||
To work around it, possible approaches are:
|
|
||||||
|
|
||||||
* Store aside the entities and the components to be removed and perform the
|
|
||||||
operations at the end of the iteration.
|
|
||||||
* Mark entities and components with a proper tag component that indicates
|
|
||||||
they must be purged, then perform a second iteration to clean them up one
|
|
||||||
by one.
|
|
||||||
|
|
||||||
* Views and thus their iterators aren't thread safe. Do no try to iterate a set
|
|
||||||
of components and modify the same set concurrently.<br/>
|
|
||||||
That being said, as long as a thread iterates the entities that have the
|
|
||||||
component `X` or assign and removes that component from a set of entities,
|
|
||||||
another thread can safely do the same with components `Y` and `Z` and
|
|
||||||
everything will work like a charm.<br/>
|
|
||||||
As an example, users can freely execute the rendering system and iterate the
|
|
||||||
renderable entities while updating a physic component concurrently on a
|
|
||||||
separate thread if needed.
|
|
||||||
|
|
||||||
# Contributors
|
# Contributors
|
||||||
|
|
||||||
If you want to contribute, please send patches as pull requests against the
|
`EnTT` was written initially as a faster alternative to other well known and
|
||||||
branch `master`.<br/>
|
open source entity-component systems. Nowadays this library is moving its first
|
||||||
Check the
|
steps. Much more will come in the future and hopefully I'm going to work on it
|
||||||
[contributors list](https://github.com/skypjack/entt/blob/master/AUTHORS) to see
|
for a long time.<br/>
|
||||||
who has partecipated so far.
|
Requests for features, PR, suggestions ad feedback are highly appreciated.
|
||||||
|
|
||||||
|
If you find you can help me and want to contribute to the project with your
|
||||||
|
experience or you do want to get part of the project for some other reasons,
|
||||||
|
feel free to contact me directly (you can find the mail in the
|
||||||
|
[profile](https://github.com/skypjack)).<br/>
|
||||||
|
I can't promise that each and every contribution will be accepted, but I can
|
||||||
|
assure that I'll do my best to take them all seriously.
|
||||||
|
|
||||||
|
If you decide to participate, please see the guidelines for
|
||||||
|
[contributing](docs/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.
|
||||||
|
<!--
|
||||||
|
@endcond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
|
||||||
# License
|
# License
|
||||||
|
|
||||||
Code and documentation Copyright (c) 2017 Michele Caini.<br/>
|
Code and documentation Copyright (c) 2017-2018 Michele Caini.<br/>
|
||||||
|
Logo Copyright (c) 2018 Richard Caseres.
|
||||||
|
|
||||||
Code released under
|
Code released under
|
||||||
[the MIT license](https://github.com/skypjack/entt/blob/master/LICENSE).
|
[the MIT license](https://github.com/skypjack/entt/blob/master/LICENSE).
|
||||||
Docs released under
|
Documentation released under
|
||||||
[Creative Commons](https://github.com/skypjack/entt/blob/master/docs/LICENSE).
|
[CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).<br/>
|
||||||
|
Logo released under
|
||||||
|
[CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/).
|
||||||
|
|
||||||
# Donation
|
<!--
|
||||||
|
@cond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
# Support
|
||||||
|
|
||||||
|
## Donation
|
||||||
|
|
||||||
Developing and maintaining `EnTT` takes some time and lots of coffee. I'd like
|
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
|
to add more and more functionalities in future and turn it in a full-featured
|
||||||
framework.<br/>
|
solution.<br/>
|
||||||
If you want to support this project, you can offer me an espresso. I'm from
|
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
|
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/>
|
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
|
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).
|
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.
|
||||||
|
<!--
|
||||||
|
@endcond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
|||||||
17
TODO
Normal file
17
TODO
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
* 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
|
||||||
@@ -14,7 +14,7 @@ configuration:
|
|||||||
|
|
||||||
before_build:
|
before_build:
|
||||||
- cd %BUILD_DIR%
|
- cd %BUILD_DIR%
|
||||||
- cmake .. -G"Visual Studio 15 2017"
|
- cmake .. -DCMAKE_CXX_FLAGS=/W1 -G"Visual Studio 15 2017"
|
||||||
|
|
||||||
build:
|
build:
|
||||||
parallel: true
|
parallel: true
|
||||||
|
|||||||
6
cmake/in/EnTTBuildConfig.cmake.in
Normal file
6
cmake/in/EnTTBuildConfig.cmake.in
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
set(ENTT_VERSION "@PROJECT_VERSION@")
|
||||||
|
set(ENTT_INCLUDE_DIRS "@CMAKE_CURRENT_SOURCE_DIR@/src")
|
||||||
|
|
||||||
|
if(NOT CMAKE_VERSION VERSION_LESS "3.0")
|
||||||
|
include("${CMAKE_CURRENT_LIST_DIR}/EnTTTargets.cmake")
|
||||||
|
endif()
|
||||||
11
cmake/in/EnTTConfig.cmake.in
Normal file
11
cmake/in/EnTTConfig.cmake.in
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
set(ENTT_VERSION "@PROJECT_VERSION@")
|
||||||
|
|
||||||
|
@PACKAGE_INIT@
|
||||||
|
|
||||||
|
set_and_check(ENTT_INCLUDE_DIRS "@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@")
|
||||||
|
|
||||||
|
if(NOT CMAKE_VERSION VERSION_LESS "3.0")
|
||||||
|
include("${CMAKE_CURRENT_LIST_DIR}/EnTTTargets.cmake")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
check_required_components("@PROJECT_NAME@")
|
||||||
19
cmake/in/cereal.in
Normal file
19
cmake/in/cereal.in
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
project(cereal-download NONE)
|
||||||
|
cmake_minimum_required(VERSION 3.2)
|
||||||
|
|
||||||
|
include(ExternalProject)
|
||||||
|
|
||||||
|
ExternalProject_Add(
|
||||||
|
cereal
|
||||||
|
GIT_REPOSITORY https://github.com/USCiLab/cereal.git
|
||||||
|
GIT_TAG v1.2.2
|
||||||
|
DOWNLOAD_DIR ${CEREAL_DEPS_DIR}
|
||||||
|
TMP_DIR ${CEREAL_DEPS_DIR}/tmp
|
||||||
|
STAMP_DIR ${CEREAL_DEPS_DIR}/stamp
|
||||||
|
SOURCE_DIR ${CEREAL_DEPS_DIR}/src
|
||||||
|
BINARY_DIR ${CEREAL_DEPS_DIR}/build
|
||||||
|
CONFIGURE_COMMAND ""
|
||||||
|
BUILD_COMMAND ""
|
||||||
|
INSTALL_COMMAND ""
|
||||||
|
TEST_COMMAND ""
|
||||||
|
)
|
||||||
19
cmake/in/duktape.in
Normal file
19
cmake/in/duktape.in
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
project(duktape-download NONE)
|
||||||
|
cmake_minimum_required(VERSION 3.2)
|
||||||
|
|
||||||
|
include(ExternalProject)
|
||||||
|
|
||||||
|
ExternalProject_Add(
|
||||||
|
duktape
|
||||||
|
GIT_REPOSITORY https://github.com/svaarala/duktape-releases.git
|
||||||
|
GIT_TAG v2.2.0
|
||||||
|
DOWNLOAD_DIR ${DUKTAPE_DEPS_DIR}
|
||||||
|
TMP_DIR ${DUKTAPE_DEPS_DIR}/tmp
|
||||||
|
STAMP_DIR ${DUKTAPE_DEPS_DIR}/stamp
|
||||||
|
SOURCE_DIR ${DUKTAPE_DEPS_DIR}/src
|
||||||
|
BINARY_DIR ${DUKTAPE_DEPS_DIR}/build
|
||||||
|
CONFIGURE_COMMAND ""
|
||||||
|
BUILD_COMMAND ""
|
||||||
|
INSTALL_COMMAND ""
|
||||||
|
TEST_COMMAND ""
|
||||||
|
)
|
||||||
@@ -6,7 +6,7 @@ include(ExternalProject)
|
|||||||
ExternalProject_Add(
|
ExternalProject_Add(
|
||||||
googletest
|
googletest
|
||||||
GIT_REPOSITORY https://github.com/google/googletest.git
|
GIT_REPOSITORY https://github.com/google/googletest.git
|
||||||
GIT_TAG release-1.8.0
|
GIT_TAG master
|
||||||
DOWNLOAD_DIR ${GOOGLETEST_DEPS_DIR}
|
DOWNLOAD_DIR ${GOOGLETEST_DEPS_DIR}
|
||||||
TMP_DIR ${GOOGLETEST_DEPS_DIR}/tmp
|
TMP_DIR ${GOOGLETEST_DEPS_DIR}/tmp
|
||||||
STAMP_DIR ${GOOGLETEST_DEPS_DIR}/stamp
|
STAMP_DIR ${GOOGLETEST_DEPS_DIR}/stamp
|
||||||
|
|||||||
@@ -2,26 +2,35 @@
|
|||||||
# Doxygen configuration (documentation)
|
# Doxygen configuration (documentation)
|
||||||
#
|
#
|
||||||
|
|
||||||
set(TARGET_DOCS docs)
|
set(DOXY_SOURCE_DIRECTORY ${EnTT_SOURCE_DIR}/src)
|
||||||
|
|
||||||
set(DOXY_IN_FILE doxy.in)
|
|
||||||
|
|
||||||
set(DOXY_SOURCE_DIRECTORY ${PROJECT_SRC_DIR})
|
|
||||||
set(DOXY_DOCS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
set(DOXY_DOCS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
set(DOXY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
set(DOXY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
set(DOXY_CFG_FILE doxy.cfg)
|
|
||||||
|
|
||||||
configure_file(${DOXY_IN_FILE} ${DOXY_CFG_FILE} @ONLY)
|
configure_file(doxy.in doxy.cfg @ONLY)
|
||||||
|
|
||||||
add_custom_target(
|
add_custom_target(
|
||||||
${TARGET_DOCS}
|
docs ALL
|
||||||
COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/${DOXY_CFG_FILE}
|
COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxy.cfg
|
||||||
WORKING_DIRECTORY ${entt_SOURCE_DIR}
|
WORKING_DIRECTORY ${EnTT_SOURCE_DIR}
|
||||||
VERBATIM
|
VERBATIM
|
||||||
SOURCES ${DOXY_IN_FILE}
|
SOURCES doxy.in
|
||||||
)
|
)
|
||||||
|
|
||||||
install(
|
install(
|
||||||
DIRECTORY ${DOXY_OUTPUT_DIRECTORY}/html
|
DIRECTORY ${DOXY_OUTPUT_DIRECTORY}/html
|
||||||
DESTINATION share/${PROJECT_NAME}-${PROJECT_VERSION}/
|
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
|
||||||
|
)
|
||||||
|
|||||||
43
docs/CONTRIBUTING.md
Normal file
43
docs/CONTRIBUTING.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
First of all, thank you very much for taking the time to contribute to the
|
||||||
|
`EnTT` framework.<br/>
|
||||||
|
How to do it mostly depends on the type of contribution:
|
||||||
|
|
||||||
|
* If you have a question, **please** ensure there isn't already an answer for
|
||||||
|
you by searching on GitHub under
|
||||||
|
[issues](https://github.com/skypjack/entt/issues). Do not forget to search
|
||||||
|
also through the closed ones. If you are unable to find a proper answer, feel
|
||||||
|
free to [open a new issue](https://github.com/skypjack/entt/issues/new).
|
||||||
|
Usually, questions are marked as such and closed in a few days.
|
||||||
|
|
||||||
|
* If you want to fix a typo in the inline documentation or in the README file,
|
||||||
|
if you want to add some new sections or if you want to help me with the
|
||||||
|
language by reviewing what I wrote so far (I'm not a native speaker after
|
||||||
|
all), **please** open a new
|
||||||
|
[pull request](https://github.com/skypjack/entt/pulls) with your changes.
|
||||||
|
|
||||||
|
* If you found a bug, **please** ensure there isn't already an answer for you by
|
||||||
|
searching on GitHub under [issues](https://github.com/skypjack/entt/issues).
|
||||||
|
If you are unable to find an open issue addressing the problem, feel free to
|
||||||
|
[open a new one](https://github.com/skypjack/entt/issues/new). **Please**, do
|
||||||
|
not forget to carefully describe how to reproduce the problem, then add all
|
||||||
|
the informaion about the system on which you are experiencing it and point out
|
||||||
|
the version of `EnTT` you are using (tag or commit).
|
||||||
|
|
||||||
|
* If you found a bug and you wrote a patch to fix it, open a new
|
||||||
|
[pull request](https://github.com/skypjack/entt/pulls) with your code.
|
||||||
|
**Please**, add some tests to avoid regressions in future if possible, it
|
||||||
|
would be really appreciated. Note that the `EnTT` framework has a
|
||||||
|
[coverage at 100%](https://coveralls.io/github/skypjack/entt?branch=master)
|
||||||
|
(at least it was at 100% at the time I wrote this file) and this is the reason
|
||||||
|
for which you can be confident with using it in a production environment.
|
||||||
|
|
||||||
|
* If you want to propose a new feature and you know how to code it, **please**
|
||||||
|
do not issue directly a pull request. Before to do it,
|
||||||
|
[create a new issue](https://github.com/skypjack/entt/issues/new) to discuss
|
||||||
|
your proposal. Other users could be interested in your idea and the discussion
|
||||||
|
that will follow can refine it and therefore give us a better solution.
|
||||||
|
|
||||||
|
* If you want to request a new feature, I'm available for hiring. Take a look at
|
||||||
|
[my profile](https://github.com/skypjack) and feel free to write me.
|
||||||
395
docs/LICENSE
395
docs/LICENSE
@@ -1,395 +0,0 @@
|
|||||||
Attribution 4.0 International
|
|
||||||
|
|
||||||
=======================================================================
|
|
||||||
|
|
||||||
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
|
||||||
does not provide legal services or legal advice. Distribution of
|
|
||||||
Creative Commons public licenses does not create a lawyer-client or
|
|
||||||
other relationship. Creative Commons makes its licenses and related
|
|
||||||
information available on an "as-is" basis. Creative Commons gives no
|
|
||||||
warranties regarding its licenses, any material licensed under their
|
|
||||||
terms and conditions, or any related information. Creative Commons
|
|
||||||
disclaims all liability for damages resulting from their use to the
|
|
||||||
fullest extent possible.
|
|
||||||
|
|
||||||
Using Creative Commons Public Licenses
|
|
||||||
|
|
||||||
Creative Commons public licenses provide a standard set of terms and
|
|
||||||
conditions that creators and other rights holders may use to share
|
|
||||||
original works of authorship and other material subject to copyright
|
|
||||||
and certain other rights specified in the public license below. The
|
|
||||||
following considerations are for informational purposes only, are not
|
|
||||||
exhaustive, and do not form part of our licenses.
|
|
||||||
|
|
||||||
Considerations for licensors: Our public licenses are
|
|
||||||
intended for use by those authorized to give the public
|
|
||||||
permission to use material in ways otherwise restricted by
|
|
||||||
copyright and certain other rights. Our licenses are
|
|
||||||
irrevocable. Licensors should read and understand the terms
|
|
||||||
and conditions of the license they choose before applying it.
|
|
||||||
Licensors should also secure all rights necessary before
|
|
||||||
applying our licenses so that the public can reuse the
|
|
||||||
material as expected. Licensors should clearly mark any
|
|
||||||
material not subject to the license. This includes other CC-
|
|
||||||
licensed material, or material used under an exception or
|
|
||||||
limitation to copyright. More considerations for licensors:
|
|
||||||
wiki.creativecommons.org/Considerations_for_licensors
|
|
||||||
|
|
||||||
Considerations for the public: By using one of our public
|
|
||||||
licenses, a licensor grants the public permission to use the
|
|
||||||
licensed material under specified terms and conditions. If
|
|
||||||
the licensor's permission is not necessary for any reason--for
|
|
||||||
example, because of any applicable exception or limitation to
|
|
||||||
copyright--then that use is not regulated by the license. Our
|
|
||||||
licenses grant only permissions under copyright and certain
|
|
||||||
other rights that a licensor has authority to grant. Use of
|
|
||||||
the licensed material may still be restricted for other
|
|
||||||
reasons, including because others have copyright or other
|
|
||||||
rights in the material. A licensor may make special requests,
|
|
||||||
such as asking that all changes be marked or described.
|
|
||||||
Although not required by our licenses, you are encouraged to
|
|
||||||
respect those requests where reasonable. More_considerations
|
|
||||||
for the public:
|
|
||||||
wiki.creativecommons.org/Considerations_for_licensees
|
|
||||||
|
|
||||||
=======================================================================
|
|
||||||
|
|
||||||
Creative Commons Attribution 4.0 International Public License
|
|
||||||
|
|
||||||
By exercising the Licensed Rights (defined below), You accept and agree
|
|
||||||
to be bound by the terms and conditions of this Creative Commons
|
|
||||||
Attribution 4.0 International Public License ("Public License"). To the
|
|
||||||
extent this Public License may be interpreted as a contract, You are
|
|
||||||
granted the Licensed Rights in consideration of Your acceptance of
|
|
||||||
these terms and conditions, and the Licensor grants You such rights in
|
|
||||||
consideration of benefits the Licensor receives from making the
|
|
||||||
Licensed Material available under these terms and conditions.
|
|
||||||
|
|
||||||
|
|
||||||
Section 1 -- Definitions.
|
|
||||||
|
|
||||||
a. Adapted Material means material subject to Copyright and Similar
|
|
||||||
Rights that is derived from or based upon the Licensed Material
|
|
||||||
and in which the Licensed Material is translated, altered,
|
|
||||||
arranged, transformed, or otherwise modified in a manner requiring
|
|
||||||
permission under the Copyright and Similar Rights held by the
|
|
||||||
Licensor. For purposes of this Public License, where the Licensed
|
|
||||||
Material is a musical work, performance, or sound recording,
|
|
||||||
Adapted Material is always produced where the Licensed Material is
|
|
||||||
synched in timed relation with a moving image.
|
|
||||||
|
|
||||||
b. Adapter's License means the license You apply to Your Copyright
|
|
||||||
and Similar Rights in Your contributions to Adapted Material in
|
|
||||||
accordance with the terms and conditions of this Public License.
|
|
||||||
|
|
||||||
c. Copyright and Similar Rights means copyright and/or similar rights
|
|
||||||
closely related to copyright including, without limitation,
|
|
||||||
performance, broadcast, sound recording, and Sui Generis Database
|
|
||||||
Rights, without regard to how the rights are labeled or
|
|
||||||
categorized. For purposes of this Public License, the rights
|
|
||||||
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
|
||||||
Rights.
|
|
||||||
|
|
||||||
d. Effective Technological Measures means those measures that, in the
|
|
||||||
absence of proper authority, may not be circumvented under laws
|
|
||||||
fulfilling obligations under Article 11 of the WIPO Copyright
|
|
||||||
Treaty adopted on December 20, 1996, and/or similar international
|
|
||||||
agreements.
|
|
||||||
|
|
||||||
e. Exceptions and Limitations means fair use, fair dealing, and/or
|
|
||||||
any other exception or limitation to Copyright and Similar Rights
|
|
||||||
that applies to Your use of the Licensed Material.
|
|
||||||
|
|
||||||
f. Licensed Material means the artistic or literary work, database,
|
|
||||||
or other material to which the Licensor applied this Public
|
|
||||||
License.
|
|
||||||
|
|
||||||
g. Licensed Rights means the rights granted to You subject to the
|
|
||||||
terms and conditions of this Public License, which are limited to
|
|
||||||
all Copyright and Similar Rights that apply to Your use of the
|
|
||||||
Licensed Material and that the Licensor has authority to license.
|
|
||||||
|
|
||||||
h. Licensor means the individual(s) or entity(ies) granting rights
|
|
||||||
under this Public License.
|
|
||||||
|
|
||||||
i. Share means to provide material to the public by any means or
|
|
||||||
process that requires permission under the Licensed Rights, such
|
|
||||||
as reproduction, public display, public performance, distribution,
|
|
||||||
dissemination, communication, or importation, and to make material
|
|
||||||
available to the public including in ways that members of the
|
|
||||||
public may access the material from a place and at a time
|
|
||||||
individually chosen by them.
|
|
||||||
|
|
||||||
j. Sui Generis Database Rights means rights other than copyright
|
|
||||||
resulting from Directive 96/9/EC of the European Parliament and of
|
|
||||||
the Council of 11 March 1996 on the legal protection of databases,
|
|
||||||
as amended and/or succeeded, as well as other essentially
|
|
||||||
equivalent rights anywhere in the world.
|
|
||||||
|
|
||||||
k. You means the individual or entity exercising the Licensed Rights
|
|
||||||
under this Public License. Your has a corresponding meaning.
|
|
||||||
|
|
||||||
|
|
||||||
Section 2 -- Scope.
|
|
||||||
|
|
||||||
a. License grant.
|
|
||||||
|
|
||||||
1. Subject to the terms and conditions of this Public License,
|
|
||||||
the Licensor hereby grants You a worldwide, royalty-free,
|
|
||||||
non-sublicensable, non-exclusive, irrevocable license to
|
|
||||||
exercise the Licensed Rights in the Licensed Material to:
|
|
||||||
|
|
||||||
a. reproduce and Share the Licensed Material, in whole or
|
|
||||||
in part; and
|
|
||||||
|
|
||||||
b. produce, reproduce, and Share Adapted Material.
|
|
||||||
|
|
||||||
2. Exceptions and Limitations. For the avoidance of doubt, where
|
|
||||||
Exceptions and Limitations apply to Your use, this Public
|
|
||||||
License does not apply, and You do not need to comply with
|
|
||||||
its terms and conditions.
|
|
||||||
|
|
||||||
3. Term. The term of this Public License is specified in Section
|
|
||||||
6(a).
|
|
||||||
|
|
||||||
4. Media and formats; technical modifications allowed. The
|
|
||||||
Licensor authorizes You to exercise the Licensed Rights in
|
|
||||||
all media and formats whether now known or hereafter created,
|
|
||||||
and to make technical modifications necessary to do so. The
|
|
||||||
Licensor waives and/or agrees not to assert any right or
|
|
||||||
authority to forbid You from making technical modifications
|
|
||||||
necessary to exercise the Licensed Rights, including
|
|
||||||
technical modifications necessary to circumvent Effective
|
|
||||||
Technological Measures. For purposes of this Public License,
|
|
||||||
simply making modifications authorized by this Section 2(a)
|
|
||||||
(4) never produces Adapted Material.
|
|
||||||
|
|
||||||
5. Downstream recipients.
|
|
||||||
|
|
||||||
a. Offer from the Licensor -- Licensed Material. Every
|
|
||||||
recipient of the Licensed Material automatically
|
|
||||||
receives an offer from the Licensor to exercise the
|
|
||||||
Licensed Rights under the terms and conditions of this
|
|
||||||
Public License.
|
|
||||||
|
|
||||||
b. No downstream restrictions. You may not offer or impose
|
|
||||||
any additional or different terms or conditions on, or
|
|
||||||
apply any Effective Technological Measures to, the
|
|
||||||
Licensed Material if doing so restricts exercise of the
|
|
||||||
Licensed Rights by any recipient of the Licensed
|
|
||||||
Material.
|
|
||||||
|
|
||||||
6. No endorsement. Nothing in this Public License constitutes or
|
|
||||||
may be construed as permission to assert or imply that You
|
|
||||||
are, or that Your use of the Licensed Material is, connected
|
|
||||||
with, or sponsored, endorsed, or granted official status by,
|
|
||||||
the Licensor or others designated to receive attribution as
|
|
||||||
provided in Section 3(a)(1)(A)(i).
|
|
||||||
|
|
||||||
b. Other rights.
|
|
||||||
|
|
||||||
1. Moral rights, such as the right of integrity, are not
|
|
||||||
licensed under this Public License, nor are publicity,
|
|
||||||
privacy, and/or other similar personality rights; however, to
|
|
||||||
the extent possible, the Licensor waives and/or agrees not to
|
|
||||||
assert any such rights held by the Licensor to the limited
|
|
||||||
extent necessary to allow You to exercise the Licensed
|
|
||||||
Rights, but not otherwise.
|
|
||||||
|
|
||||||
2. Patent and trademark rights are not licensed under this
|
|
||||||
Public License.
|
|
||||||
|
|
||||||
3. To the extent possible, the Licensor waives any right to
|
|
||||||
collect royalties from You for the exercise of the Licensed
|
|
||||||
Rights, whether directly or through a collecting society
|
|
||||||
under any voluntary or waivable statutory or compulsory
|
|
||||||
licensing scheme. In all other cases the Licensor expressly
|
|
||||||
reserves any right to collect such royalties.
|
|
||||||
|
|
||||||
|
|
||||||
Section 3 -- License Conditions.
|
|
||||||
|
|
||||||
Your exercise of the Licensed Rights is expressly made subject to the
|
|
||||||
following conditions.
|
|
||||||
|
|
||||||
a. Attribution.
|
|
||||||
|
|
||||||
1. If You Share the Licensed Material (including in modified
|
|
||||||
form), You must:
|
|
||||||
|
|
||||||
a. retain the following if it is supplied by the Licensor
|
|
||||||
with the Licensed Material:
|
|
||||||
|
|
||||||
i. identification of the creator(s) of the Licensed
|
|
||||||
Material and any others designated to receive
|
|
||||||
attribution, in any reasonable manner requested by
|
|
||||||
the Licensor (including by pseudonym if
|
|
||||||
designated);
|
|
||||||
|
|
||||||
ii. a copyright notice;
|
|
||||||
|
|
||||||
iii. a notice that refers to this Public License;
|
|
||||||
|
|
||||||
iv. a notice that refers to the disclaimer of
|
|
||||||
warranties;
|
|
||||||
|
|
||||||
v. a URI or hyperlink to the Licensed Material to the
|
|
||||||
extent reasonably practicable;
|
|
||||||
|
|
||||||
b. indicate if You modified the Licensed Material and
|
|
||||||
retain an indication of any previous modifications; and
|
|
||||||
|
|
||||||
c. indicate the Licensed Material is licensed under this
|
|
||||||
Public License, and include the text of, or the URI or
|
|
||||||
hyperlink to, this Public License.
|
|
||||||
|
|
||||||
2. You may satisfy the conditions in Section 3(a)(1) in any
|
|
||||||
reasonable manner based on the medium, means, and context in
|
|
||||||
which You Share the Licensed Material. For example, it may be
|
|
||||||
reasonable to satisfy the conditions by providing a URI or
|
|
||||||
hyperlink to a resource that includes the required
|
|
||||||
information.
|
|
||||||
|
|
||||||
3. If requested by the Licensor, You must remove any of the
|
|
||||||
information required by Section 3(a)(1)(A) to the extent
|
|
||||||
reasonably practicable.
|
|
||||||
|
|
||||||
4. If You Share Adapted Material You produce, the Adapter's
|
|
||||||
License You apply must not prevent recipients of the Adapted
|
|
||||||
Material from complying with this Public License.
|
|
||||||
|
|
||||||
|
|
||||||
Section 4 -- Sui Generis Database Rights.
|
|
||||||
|
|
||||||
Where the Licensed Rights include Sui Generis Database Rights that
|
|
||||||
apply to Your use of the Licensed Material:
|
|
||||||
|
|
||||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
|
||||||
to extract, reuse, reproduce, and Share all or a substantial
|
|
||||||
portion of the contents of the database;
|
|
||||||
|
|
||||||
b. if You include all or a substantial portion of the database
|
|
||||||
contents in a database in which You have Sui Generis Database
|
|
||||||
Rights, then the database in which You have Sui Generis Database
|
|
||||||
Rights (but not its individual contents) is Adapted Material; and
|
|
||||||
|
|
||||||
c. You must comply with the conditions in Section 3(a) if You Share
|
|
||||||
all or a substantial portion of the contents of the database.
|
|
||||||
|
|
||||||
For the avoidance of doubt, this Section 4 supplements and does not
|
|
||||||
replace Your obligations under this Public License where the Licensed
|
|
||||||
Rights include other Copyright and Similar Rights.
|
|
||||||
|
|
||||||
|
|
||||||
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
|
||||||
|
|
||||||
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
|
||||||
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
|
||||||
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
|
||||||
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
|
||||||
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
|
||||||
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
||||||
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
|
||||||
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
|
||||||
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
|
||||||
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
|
||||||
|
|
||||||
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
|
||||||
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
|
||||||
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
|
||||||
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
|
||||||
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
|
||||||
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
|
||||||
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
|
||||||
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
|
||||||
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
|
||||||
|
|
||||||
c. The disclaimer of warranties and limitation of liability provided
|
|
||||||
above shall be interpreted in a manner that, to the extent
|
|
||||||
possible, most closely approximates an absolute disclaimer and
|
|
||||||
waiver of all liability.
|
|
||||||
|
|
||||||
|
|
||||||
Section 6 -- Term and Termination.
|
|
||||||
|
|
||||||
a. This Public License applies for the term of the Copyright and
|
|
||||||
Similar Rights licensed here. However, if You fail to comply with
|
|
||||||
this Public License, then Your rights under this Public License
|
|
||||||
terminate automatically.
|
|
||||||
|
|
||||||
b. Where Your right to use the Licensed Material has terminated under
|
|
||||||
Section 6(a), it reinstates:
|
|
||||||
|
|
||||||
1. automatically as of the date the violation is cured, provided
|
|
||||||
it is cured within 30 days of Your discovery of the
|
|
||||||
violation; or
|
|
||||||
|
|
||||||
2. upon express reinstatement by the Licensor.
|
|
||||||
|
|
||||||
For the avoidance of doubt, this Section 6(b) does not affect any
|
|
||||||
right the Licensor may have to seek remedies for Your violations
|
|
||||||
of this Public License.
|
|
||||||
|
|
||||||
c. For the avoidance of doubt, the Licensor may also offer the
|
|
||||||
Licensed Material under separate terms or conditions or stop
|
|
||||||
distributing the Licensed Material at any time; however, doing so
|
|
||||||
will not terminate this Public License.
|
|
||||||
|
|
||||||
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
|
||||||
License.
|
|
||||||
|
|
||||||
|
|
||||||
Section 7 -- Other Terms and Conditions.
|
|
||||||
|
|
||||||
a. The Licensor shall not be bound by any additional or different
|
|
||||||
terms or conditions communicated by You unless expressly agreed.
|
|
||||||
|
|
||||||
b. Any arrangements, understandings, or agreements regarding the
|
|
||||||
Licensed Material not stated herein are separate from and
|
|
||||||
independent of the terms and conditions of this Public License.
|
|
||||||
|
|
||||||
|
|
||||||
Section 8 -- Interpretation.
|
|
||||||
|
|
||||||
a. For the avoidance of doubt, this Public License does not, and
|
|
||||||
shall not be interpreted to, reduce, limit, restrict, or impose
|
|
||||||
conditions on any use of the Licensed Material that could lawfully
|
|
||||||
be made without permission under this Public License.
|
|
||||||
|
|
||||||
b. To the extent possible, if any provision of this Public License is
|
|
||||||
deemed unenforceable, it shall be automatically reformed to the
|
|
||||||
minimum extent necessary to make it enforceable. If the provision
|
|
||||||
cannot be reformed, it shall be severed from this Public License
|
|
||||||
without affecting the enforceability of the remaining terms and
|
|
||||||
conditions.
|
|
||||||
|
|
||||||
c. No term or condition of this Public License will be waived and no
|
|
||||||
failure to comply consented to unless expressly agreed to by the
|
|
||||||
Licensor.
|
|
||||||
|
|
||||||
d. Nothing in this Public License constitutes or may be interpreted
|
|
||||||
as a limitation upon, or waiver of, any privileges and immunities
|
|
||||||
that apply to the Licensor or You, including from the legal
|
|
||||||
processes of any jurisdiction or authority.
|
|
||||||
|
|
||||||
|
|
||||||
=======================================================================
|
|
||||||
|
|
||||||
Creative Commons is not a party to its public
|
|
||||||
licenses. Notwithstanding, Creative Commons may elect to apply one of
|
|
||||||
its public licenses to material it publishes and in those instances
|
|
||||||
will be considered the “Licensor.” The text of the Creative Commons
|
|
||||||
public licenses is dedicated to the public domain under the CC0 Public
|
|
||||||
Domain Dedication. Except for the limited purpose of indicating that
|
|
||||||
material is shared under a Creative Commons public license or as
|
|
||||||
otherwise permitted by the Creative Commons policies published at
|
|
||||||
creativecommons.org/policies, Creative Commons does not authorize the
|
|
||||||
use of the trademark "Creative Commons" or any other trademark or logo
|
|
||||||
of Creative Commons without its prior written consent including,
|
|
||||||
without limitation, in connection with any unauthorized modifications
|
|
||||||
to any of its public licenses or any other arrangements,
|
|
||||||
understandings, or agreements concerning use of licensed material. For
|
|
||||||
the avoidance of doubt, this paragraph does not form part of the
|
|
||||||
public licenses.
|
|
||||||
|
|
||||||
Creative Commons may be contacted at creativecommons.org.
|
|
||||||
167
docs/core.md
Normal file
167
docs/core.md
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
# Crash Course: core functionalities
|
||||||
|
|
||||||
|
<!--
|
||||||
|
@cond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
# Table of Contents
|
||||||
|
|
||||||
|
* [Introduction](#introduction)
|
||||||
|
* [Compile-time identifiers](#compile-time-identifiers)
|
||||||
|
* [Runtime identifiers](#runtime-identifiers)
|
||||||
|
* [Hashed strings](#hashed-strings)
|
||||||
|
* [Conflicts](#conflicts)
|
||||||
|
* [Monostate](#monostate)
|
||||||
|
<!--
|
||||||
|
@endcond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Introduction
|
||||||
|
|
||||||
|
`EnTT` comes with a bunch of core functionalities mostly used by the other parts
|
||||||
|
of the library itself.<br/>
|
||||||
|
Hardly users will include these features in their code, but it's worth
|
||||||
|
describing what `EnTT` offers so as not to reinvent the wheel in case of need.
|
||||||
|
|
||||||
|
# Compile-time identifiers
|
||||||
|
|
||||||
|
Sometimes it's useful to be able to give unique identifiers to types at
|
||||||
|
compile-time.<br/>
|
||||||
|
There are plenty of different solutions out there and I could have used one of
|
||||||
|
them. However, I decided to spend my time to define a compact and versatile tool
|
||||||
|
that fully embraces what the modern C++ has to offer.
|
||||||
|
|
||||||
|
The _result of my efforts_ is the `Identifier` class template:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <ident.hpp>
|
||||||
|
|
||||||
|
// defines the identifiers for the given types
|
||||||
|
using ID = entt::Identifier<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"}>{};
|
||||||
|
```
|
||||||
1400
docs/entity.md
Normal file
1400
docs/entity.md
Normal file
File diff suppressed because it is too large
Load Diff
75
docs/locator.md
Normal file
75
docs/locator.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# Crash Course: service locator
|
||||||
|
|
||||||
|
<!--
|
||||||
|
@cond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
# Table of Contents
|
||||||
|
|
||||||
|
* [Introduction](#introduction)
|
||||||
|
* [Service locator](#service-locator)
|
||||||
|
<!--
|
||||||
|
@endcond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Introduction
|
||||||
|
|
||||||
|
Usually service locators are tightly bound to the services they expose and it's
|
||||||
|
hard to define a general purpose solution. This template based implementation
|
||||||
|
tries to fill the gap and to get rid of the burden of defining a different
|
||||||
|
specific locator for each application.<br/>
|
||||||
|
This class is tiny, partially unsafe and thus risky to use. Moreover it doesn't
|
||||||
|
fit probably most of the scenarios in which a service locator is required. Look
|
||||||
|
at it as a small tool that can sometimes be useful if users know how to handle
|
||||||
|
it.
|
||||||
|
|
||||||
|
# Service locator
|
||||||
|
|
||||||
|
The API is straightforward. The basic idea is that services are implemented by
|
||||||
|
means of interfaces and rely on polymorphism.<br/>
|
||||||
|
The locator is instantiated with the base type of the service if any and a
|
||||||
|
concrete implementation is provided along with all the parameters required to
|
||||||
|
initialize it. As an example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// the service has no base type, a locator is used to treat it as a kind of singleton
|
||||||
|
entt::ServiceLocator<MyService>::set(params...);
|
||||||
|
|
||||||
|
// sets up an opaque service
|
||||||
|
entt::ServiceLocator<AudioInterface>::set<AudioImplementation>(params...);
|
||||||
|
|
||||||
|
// resets (destroys) the service
|
||||||
|
entt::ServiceLocator<AudioInterface>::reset();
|
||||||
|
```
|
||||||
|
|
||||||
|
The locator can also be queried to know if an active service is currently set
|
||||||
|
and to retrieve it if necessary (either as a pointer or as a reference):
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// no service currently set
|
||||||
|
auto empty = entt::ServiceLocator<AudioInterface>::empty();
|
||||||
|
|
||||||
|
// gets a (possibly empty) shared pointer to the service ...
|
||||||
|
std::shared_ptr<AudioInterface> ptr = entt::ServiceLocator<AudioInterface>::get();
|
||||||
|
|
||||||
|
// ... or a reference, but it's undefined behaviour if the service isn't set yet
|
||||||
|
AudioInterface &ref = entt::ServiceLocator<AudioInterface>::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>;
|
||||||
|
// ...
|
||||||
|
};
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
void init() {
|
||||||
|
Locator::Camera::set<CameraNull>();
|
||||||
|
Locator::Audio::set<AudioImplementation>(params...);
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
211
docs/process.md
Normal file
211
docs/process.md
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
# Crash Course: cooperative scheduler
|
||||||
|
|
||||||
|
<!--
|
||||||
|
@cond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
# Table of Contents
|
||||||
|
|
||||||
|
* [Introduction](#introduction)
|
||||||
|
* [The process](#the-process)
|
||||||
|
* [Adaptor](#adaptor)
|
||||||
|
* [The scheduler](#the-scheduler)
|
||||||
|
<!--
|
||||||
|
@endcond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Introduction
|
||||||
|
|
||||||
|
Sometimes processes are a useful tool to work around the strict definition of a
|
||||||
|
system and introduce logic in a different way, usually without resorting to the
|
||||||
|
introduction of other components.
|
||||||
|
|
||||||
|
`EnTT` offers a minimal support to this paradigm by introducing a few classes
|
||||||
|
that users can use to define and execute cooperative processes.
|
||||||
|
|
||||||
|
# The process
|
||||||
|
|
||||||
|
A typical process must inherit from the `Process` class template that stays true
|
||||||
|
to the CRTP idiom. Moreover, derived classes must specify what's the intended
|
||||||
|
type for elapsed times.
|
||||||
|
|
||||||
|
A process should expose publicly the following member functions whether
|
||||||
|
required (note that it isn't required to define a function unless the derived
|
||||||
|
class wants to _override_ the default behavior):
|
||||||
|
|
||||||
|
* `void update(Delta, void *);`
|
||||||
|
|
||||||
|
It's invoked once per tick until a process is explicitly aborted or it
|
||||||
|
terminates either with or without errors. Even though it's not mandatory to
|
||||||
|
declare this member function, as a rule of thumb each process should at
|
||||||
|
least define it to work properly. The `void *` parameter is an opaque pointer
|
||||||
|
to user data (if any) forwarded directly to the process during an update.
|
||||||
|
|
||||||
|
* `void init(void *);`
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
* `void succeeded();`
|
||||||
|
|
||||||
|
It's invoked in case of success, immediately after an update and during the
|
||||||
|
same tick.
|
||||||
|
|
||||||
|
* `void failed();`
|
||||||
|
|
||||||
|
It's invoked in case of errors, immediately after an update and during the
|
||||||
|
same tick.
|
||||||
|
|
||||||
|
* `void aborted();`
|
||||||
|
|
||||||
|
It's invoked only if a process is explicitly aborted. There is no guarantee
|
||||||
|
that it executes in the same tick, this depends solely on whether the
|
||||||
|
process is aborted immediately or not.
|
||||||
|
|
||||||
|
Derived classes can also change the internal state of a process by invoking
|
||||||
|
`succeed` and `fail`, as well as `pause` and `unpause` the process itself. All
|
||||||
|
these are protected member functions made available to be able to manage the
|
||||||
|
life cycle of a process from a derived class.
|
||||||
|
|
||||||
|
Here is a minimal example for the sake of curiosity:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct MyProcess: entt::Process<MyProcess, std::uint32_t> {
|
||||||
|
using delta_type = std::uint32_t;
|
||||||
|
|
||||||
|
void update(delta_type delta, void *) {
|
||||||
|
remaining = delta > remaining ? delta_type{] : (remaining - delta);
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
if(!remaining) {
|
||||||
|
succeed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(void *data) {
|
||||||
|
remaining = *static_cast<delta_type *>(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
delta_type remaining;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adaptor
|
||||||
|
|
||||||
|
Lambdas and functors can't be used directly with a scheduler for they are not
|
||||||
|
properly defined processes with managed life cycles.<br/>
|
||||||
|
This class helps in filling the gap and turning lambdas and functors into
|
||||||
|
full featured processes usable by a scheduler.
|
||||||
|
|
||||||
|
The function call operator has a signature similar to the one of the `update`
|
||||||
|
function of a process but for the fact that it receives two extra arguments to
|
||||||
|
call whenever a process is terminated with success or with an error:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void(Delta delta, void *data, auto succeed, auto fail);
|
||||||
|
```
|
||||||
|
|
||||||
|
Parameters have the following meaning:
|
||||||
|
|
||||||
|
* `delta` is the elapsed time.
|
||||||
|
* `data` is an opaque pointer to user data if any, `nullptr` otherwise.
|
||||||
|
* `succeed` is a function to call when a process terminates with success.
|
||||||
|
* `fail` is a function to call when a process terminates with errors.
|
||||||
|
|
||||||
|
Both `succeed` and `fail` accept no parameters at all.
|
||||||
|
|
||||||
|
Note that usually users shouldn't worry about creating adaptors at all. A
|
||||||
|
scheduler creates them internally each and every time a lambda or a functor is
|
||||||
|
used as a process.
|
||||||
|
|
||||||
|
# The scheduler
|
||||||
|
|
||||||
|
A cooperative scheduler runs different processes and helps managing their life
|
||||||
|
cycles.
|
||||||
|
|
||||||
|
Each process is invoked once per tick. If it terminates, it's removed
|
||||||
|
automatically from the scheduler and it's never invoked again. Otherwise it's
|
||||||
|
a good candidate to run 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.
|
||||||
|
|
||||||
|
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;
|
||||||
|
```
|
||||||
|
|
||||||
|
It has member functions to query its internal data structures, like `empty` or
|
||||||
|
`size`, as well as a `clear` utility to reset it to a clean state:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// checks if there are processes still running
|
||||||
|
const auto empty = scheduler.empty();
|
||||||
|
|
||||||
|
// gets the number of processes still running
|
||||||
|
Scheduler<std::uint32_t>::size_type size = scheduler.size();
|
||||||
|
|
||||||
|
// resets the scheduler to its initial state and discards all the processes
|
||||||
|
scheduler.clear();
|
||||||
|
```
|
||||||
|
|
||||||
|
To attach a process to a scheduler there are mainly two ways:
|
||||||
|
|
||||||
|
* If the process inherits from the `Process` class template, it's enough to
|
||||||
|
indicate its type and submit all the parameters required to construct it to
|
||||||
|
the `attach` member function:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
scheduler.attach<MyProcess>("foobar");
|
||||||
|
```
|
||||||
|
|
||||||
|
* Otherwise, in case of a lambda or a functor, it's enough to provide an
|
||||||
|
instance of the class to the `attach` member function:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
scheduler.attach([](auto...){ /* ... */ });
|
||||||
|
```
|
||||||
|
|
||||||
|
In both cases, the return value is an opaque object that offers a `then` member
|
||||||
|
function to use to create chains of processes to run sequentially.<br/>
|
||||||
|
As a minimal example of use:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// schedules a task in the form of a lambda function
|
||||||
|
scheduler.attach([](auto delta, void *, auto succeed, auto fail) {
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
// appends a child in the form of another lambda function
|
||||||
|
.then([](auto delta, void *, auto succeed, auto fail) {
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
// appends a child in the form of a process class
|
||||||
|
.then<MyProcess>();
|
||||||
|
```
|
||||||
|
|
||||||
|
To update a scheduler and thus all its processes, the `update` member function
|
||||||
|
is the way to go:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// updates all the processes, no user data are provided
|
||||||
|
scheduler.update(delta);
|
||||||
|
|
||||||
|
// updates all the processes and provides them with custom data
|
||||||
|
scheduler.update(delta, &data);
|
||||||
|
```
|
||||||
|
|
||||||
|
In addition to these functions, the scheduler offers an `abort` member function
|
||||||
|
that can be used to discard all the running processes at once:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// aborts all the processes abruptly ...
|
||||||
|
scheduler.abort(true);
|
||||||
|
|
||||||
|
// ... or gracefully during the next tick
|
||||||
|
scheduler.abort();
|
||||||
|
```
|
||||||
240
docs/resource.md
Normal file
240
docs/resource.md
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
# Crash Course: resource management
|
||||||
|
|
||||||
|
<!--
|
||||||
|
@cond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
# Table of Contents
|
||||||
|
|
||||||
|
* [Introduction](#introduction)
|
||||||
|
* [The resource, the loader and the cache](#the-resource-the-loader-and-the-cache)
|
||||||
|
<!--
|
||||||
|
@endcond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Introduction
|
||||||
|
|
||||||
|
Resource management is usually one of the most critical part of a software like
|
||||||
|
a game. Solutions are often tuned to the particular application. There exist
|
||||||
|
several approaches and all of them are perfectly fine as long as they fit the
|
||||||
|
requirements of the piece of software in which they are used.<br/>
|
||||||
|
Examples are loading everything on start, loading on request, predictive
|
||||||
|
loading, and so on.
|
||||||
|
|
||||||
|
`EnTT` doesn't pretend to offer a _one-fits-all_ solution for the different
|
||||||
|
cases. Instead, it offers a minimal and perhaps trivial cache that can be useful
|
||||||
|
most of the time during prototyping and sometimes even in a production
|
||||||
|
environment.<br/>
|
||||||
|
For those interested in the subject, the plan is to improve it considerably over
|
||||||
|
time in terms of performance, memory usage and functionalities. Hoping to make
|
||||||
|
it, of course, one step at a time.
|
||||||
|
|
||||||
|
# The resource, the loader and the cache
|
||||||
|
|
||||||
|
There are three main actors in the model: the resource, the loader and the
|
||||||
|
cache.
|
||||||
|
|
||||||
|
The _resource_ is whatever users want it to be. An image, a video, an audio,
|
||||||
|
whatever. There are no limits.<br/>
|
||||||
|
As a minimal example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct MyResource { 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> {
|
||||||
|
// ...
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Where `MyResource` 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 {
|
||||||
|
// ...
|
||||||
|
return std::shared_ptr<MyResource>(new MyResource{ value });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
In general, resource loaders should not have a state or retain data of any type.
|
||||||
|
They should let the cache manage their resources instead.<br/>
|
||||||
|
As a side note, base class and CRTP idiom aren't strictly required with the
|
||||||
|
current implementation. One could argue that a cache can easily work with
|
||||||
|
loaders of any type. However, future changes won't be breaking ones by forcing
|
||||||
|
the use of a base class today and that's why the model is already in its place.
|
||||||
|
|
||||||
|
Finally, a cache is a specialization of a class template tailored to a specific
|
||||||
|
resource:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using MyResourceCache = entt::ResourceCache<MyResource>;
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
MyResourceCache 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/>
|
||||||
|
As a (very) trivial example, audio tracks can survive in most of the scenes of
|
||||||
|
an application while meshes can be associated with a single scene and then
|
||||||
|
discarded when users leave it.
|
||||||
|
|
||||||
|
A cache offers a set of basic functionalities to query its internal state and to
|
||||||
|
_organize_ it:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// gets the number of resources managed by a cache
|
||||||
|
const auto size = cache.size();
|
||||||
|
|
||||||
|
// checks if a cache contains at least a valid resource
|
||||||
|
const auto empty = cache.empty();
|
||||||
|
|
||||||
|
// clears a cache and discards its content
|
||||||
|
cache.clear();
|
||||||
|
```
|
||||||
|
|
||||||
|
Besides these member functions, it 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
|
||||||
|
```
|
||||||
|
|
||||||
|
Where `resource_type` is an alias for `entt::HashedString`. Therefore, resource
|
||||||
|
identifiers are created explicitly as in the following example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
constexpr auto identifier = entt::ResourceCache<Resource>::resource_type{"my/resource/identifier"};
|
||||||
|
// this is equivalent to the following
|
||||||
|
constexpr auto hs = entt::HashedString{"my/resource/identifier"};
|
||||||
|
```
|
||||||
|
|
||||||
|
The class `HashedString` is described in a dedicated section, so I won't do in
|
||||||
|
details here.
|
||||||
|
|
||||||
|
Resources are loaded and thus stored in a cache through the `load` member
|
||||||
|
function. It accepts the loader to use as a template parameter, the resource
|
||||||
|
identifier and the parameters used to construct the resource as arguments:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// uses the identifier declared above
|
||||||
|
cache.load<MyLoader>(identifier, 0);
|
||||||
|
|
||||||
|
// uses a const char * directly as an identifier
|
||||||
|
cache.load<MyLoader>("another/identifier", 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:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
if(!cache.load<MyLoader>("another/identifier", 42)) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto exists = cache.contains("my/identifier");
|
||||||
|
```
|
||||||
|
|
||||||
|
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);
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
snippet:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
cache.discard(identifier);
|
||||||
|
cache.load<MyLoader>(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.
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto handle = cache.handle("my/identifier");
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
resource as a const reference on request:
|
||||||
|
|
||||||
|
* By means of the `get` member function:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
const auto &resource = handle.get();
|
||||||
|
```
|
||||||
|
|
||||||
|
* Using the proper cast operator:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
const auto &resource = handle;
|
||||||
|
```
|
||||||
|
|
||||||
|
* Through the dereference operator:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
const auto &resource = *handle;
|
||||||
|
```
|
||||||
|
|
||||||
|
The resource can also be accessed directly using the arrow operator if required:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto value = handle->value;
|
||||||
|
```
|
||||||
|
|
||||||
|
To test if a handle is still valid, the cast operator to `bool` allows users to
|
||||||
|
use it in a guard:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
if(handle) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, in case there is the need to load a resource and thus to get a handle
|
||||||
|
without storing the resource itself in the cache, users can rely on the `temp`
|
||||||
|
member function template.<br/>
|
||||||
|
The declaration is similar to 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:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto handle = cache.temp<MyLoader>("another/identifier", 42);
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not forget to test the handle for validity. Otherwise, getting the reference
|
||||||
|
to the resource it points may result in undefined behavior.
|
||||||
39
docs/shared.md
Normal file
39
docs/shared.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
### 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
Normal file
412
docs/signal.md
Normal file
@@ -0,0 +1,412 @@
|
|||||||
|
# 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.
|
||||||
60
scripts/update_homebrew.sh
Executable file
60
scripts/update_homebrew.sh
Executable file
@@ -0,0 +1,60 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# only argument should be the version to upgrade to
|
||||||
|
if [ $# != 1 ]
|
||||||
|
then
|
||||||
|
echo "Expected a version tag like v2.7.1"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
VERSION="$1"
|
||||||
|
URL="https://github.com/skypjack/entt/archive/$VERSION.tar.gz"
|
||||||
|
FORMULA="entt.rb"
|
||||||
|
|
||||||
|
echo "Updating homebrew package to $VERSION"
|
||||||
|
|
||||||
|
echo "Cloning..."
|
||||||
|
git clone https://github.com/skypjack/homebrew-entt.git
|
||||||
|
if [ $? != 0 ]
|
||||||
|
then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
cd homebrew-entt
|
||||||
|
|
||||||
|
# download the repo at the version
|
||||||
|
# exit with error messages if curl fails
|
||||||
|
echo "Curling..."
|
||||||
|
curl "$URL" --location --fail --silent --show-error --output archive.tar.gz
|
||||||
|
if [ $? != 0 ]
|
||||||
|
then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# compute sha256 hash
|
||||||
|
echo "Hashing..."
|
||||||
|
HASH="$(openssl sha256 archive.tar.gz | cut -d " " -f 2)"
|
||||||
|
|
||||||
|
# delete the archive
|
||||||
|
rm archive.tar.gz
|
||||||
|
|
||||||
|
echo "Sedding..."
|
||||||
|
|
||||||
|
# change the url in the formula file
|
||||||
|
# the slashes in the URL must be escaped
|
||||||
|
ESCAPED_URL="$(sed -e 's/[\/&]/\\&/g' <<< "$URL")"
|
||||||
|
sed -i -e '/url/s/".*"/"'$ESCAPED_URL'"/' $FORMULA
|
||||||
|
|
||||||
|
# change the hash in the formula file
|
||||||
|
sed -i -e '/sha256/s/".*"/"'$HASH'"/' $FORMULA
|
||||||
|
|
||||||
|
# delete temporary file created by sed
|
||||||
|
rm "$FORMULA-e"
|
||||||
|
|
||||||
|
# update remote repo
|
||||||
|
echo "Gitting..."
|
||||||
|
git add entt.rb
|
||||||
|
git commit -m "Update to $VERSION"
|
||||||
|
git push origin master
|
||||||
|
|
||||||
|
# out of homebrew-entt dir
|
||||||
|
cd ..
|
||||||
3
scripts/update_packages.sh
Executable file
3
scripts/update_packages.sh
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
scripts/update_homebrew.sh $1
|
||||||
16
src/entt/config/config.h
Normal file
16
src/entt/config/config.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#ifndef ENTT_CONFIG_CONFIG_H
|
||||||
|
#define ENTT_CONFIG_CONFIG_H
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef ENTT_NOEXCEPT
|
||||||
|
#define ENTT_NOEXCEPT noexcept
|
||||||
|
#endif // ENTT_NOEXCEPT
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef ENTT_HS_SUFFIX
|
||||||
|
#define ENTT_HS_SUFFIX _hs
|
||||||
|
#endif // ENTT_HS_SUFFIX
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif // ENTT_CONFIG_CONFIG_H
|
||||||
111
src/entt/core/algorithm.hpp
Normal file
111
src/entt/core/algorithm.hpp
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
#ifndef ENTT_CORE_ALGORITHM_HPP
|
||||||
|
#define ENTT_CORE_ALGORITHM_HPP
|
||||||
|
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
|
||||||
|
namespace entt {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Function object to wrap `std::sort` in a class type.
|
||||||
|
*
|
||||||
|
* Unfortunately, `std::sort` cannot be passed as template argument to a class
|
||||||
|
* template or a function template.<br/>
|
||||||
|
* This class fills the gap by wrapping some flavors of `std::sort` in a
|
||||||
|
* function object.
|
||||||
|
*/
|
||||||
|
struct StdSort final {
|
||||||
|
/**
|
||||||
|
* @brief Sorts the elements in a range.
|
||||||
|
*
|
||||||
|
* Sorts the elements in a range using the given binary comparison function.
|
||||||
|
*
|
||||||
|
* @tparam It Type of random access iterator.
|
||||||
|
* @tparam Compare Type of comparison function object.
|
||||||
|
* @tparam Args Types of arguments to forward to the sort function.
|
||||||
|
* @param first An iterator to the first element of the range to sort.
|
||||||
|
* @param last An iterator past the last element of the range to sort.
|
||||||
|
* @param compare A valid comparison function object.
|
||||||
|
* @param args Arguments to forward to the sort function, if any.
|
||||||
|
*/
|
||||||
|
template<typename It, typename Compare = std::less<>, typename... Args>
|
||||||
|
void operator()(It first, It last, Compare compare = Compare{}, Args &&... args) const {
|
||||||
|
std::sort(std::forward<Args>(args)..., std::move(first), std::move(last), std::move(compare));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*! @brief Function object for performing insertion sort. */
|
||||||
|
struct InsertionSort final {
|
||||||
|
/**
|
||||||
|
* @brief Sorts the elements in a range.
|
||||||
|
*
|
||||||
|
* Sorts the elements in a range using the given binary comparison function.
|
||||||
|
*
|
||||||
|
* @tparam It Type of random access iterator.
|
||||||
|
* @tparam Compare Type of comparison function object.
|
||||||
|
* @param first An iterator to the first element of the range to sort.
|
||||||
|
* @param last An iterator past the last element of the range to sort.
|
||||||
|
* @param compare A valid comparison function object.
|
||||||
|
*/
|
||||||
|
template<typename It, typename Compare = std::less<>>
|
||||||
|
void operator()(It first, It last, Compare compare = Compare{}) const {
|
||||||
|
auto it = first + 1;
|
||||||
|
|
||||||
|
while(it != last) {
|
||||||
|
auto value = *it;
|
||||||
|
auto pre = it;
|
||||||
|
|
||||||
|
while(pre != first && compare(value, *(pre-1))) {
|
||||||
|
*pre = *(pre-1);
|
||||||
|
--pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
*pre = value;
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*! @brief Function object for performing bubble sort (single iteration). */
|
||||||
|
struct OneShotBubbleSort final {
|
||||||
|
/**
|
||||||
|
* @brief Tries to sort 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.
|
||||||
|
*
|
||||||
|
* @tparam It Type of random access iterator.
|
||||||
|
* @tparam Compare Type of comparison function object.
|
||||||
|
* @param first An iterator to the first element of the range to sort.
|
||||||
|
* @param last An iterator past the last element of the range to sort.
|
||||||
|
* @param compare A valid comparison function object.
|
||||||
|
*/
|
||||||
|
template<typename It, typename Compare = std::less<>>
|
||||||
|
void operator()(It first, It last, Compare compare = Compare{}) const {
|
||||||
|
if(first != last) {
|
||||||
|
auto it = first++;
|
||||||
|
|
||||||
|
while(first != last) {
|
||||||
|
if(compare(*first, *it)) {
|
||||||
|
using std::swap;
|
||||||
|
std::swap(*first, *it);
|
||||||
|
}
|
||||||
|
|
||||||
|
it = first++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif // ENTT_CORE_ALGORITHM_HPP
|
||||||
@@ -2,8 +2,10 @@
|
|||||||
#define ENTT_CORE_FAMILY_HPP
|
#define ENTT_CORE_FAMILY_HPP
|
||||||
|
|
||||||
|
|
||||||
#include<type_traits>
|
#include <type_traits>
|
||||||
#include<cstddef>
|
#include <cstddef>
|
||||||
|
#include <atomic>
|
||||||
|
#include "../config/config.h"
|
||||||
|
|
||||||
|
|
||||||
namespace entt {
|
namespace entt {
|
||||||
@@ -18,14 +20,11 @@ namespace entt {
|
|||||||
*/
|
*/
|
||||||
template<typename...>
|
template<typename...>
|
||||||
class Family {
|
class Family {
|
||||||
static std::size_t identifier() noexcept {
|
static std::atomic<std::size_t> identifier;
|
||||||
static std::size_t value = 0;
|
|
||||||
return value++;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename...>
|
template<typename...>
|
||||||
static std::size_t family() noexcept {
|
static std::size_t family() ENTT_NOEXCEPT {
|
||||||
static const std::size_t value = identifier();
|
static const std::size_t value = identifier.fetch_add(1);
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,12 +37,16 @@ public:
|
|||||||
* @return Statically generated unique identifier for the given type.
|
* @return Statically generated unique identifier for the given type.
|
||||||
*/
|
*/
|
||||||
template<typename... Type>
|
template<typename... Type>
|
||||||
static family_type type() noexcept {
|
inline static family_type type() ENTT_NOEXCEPT {
|
||||||
return family<std::decay_t<Type>...>();
|
return family<std::decay_t<Type>...>();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template<typename... Types>
|
||||||
|
std::atomic<std::size_t> Family<Types...>::identifier{};
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
#define ENTT_CORE_HASHED_STRING_HPP
|
#define ENTT_CORE_HASHED_STRING_HPP
|
||||||
|
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include "../config/config.h"
|
||||||
|
|
||||||
|
|
||||||
namespace entt {
|
namespace entt {
|
||||||
@@ -20,15 +22,15 @@ namespace entt {
|
|||||||
class HashedString final {
|
class HashedString final {
|
||||||
struct ConstCharWrapper final {
|
struct ConstCharWrapper final {
|
||||||
// non-explicit constructor on purpose
|
// non-explicit constructor on purpose
|
||||||
constexpr ConstCharWrapper(const char *str) noexcept: str{str} {}
|
constexpr ConstCharWrapper(const char *str) ENTT_NOEXCEPT: str{str} {}
|
||||||
const char *str;
|
const char *str;
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr std::uint64_t offset = 14695981039346656037u;
|
static constexpr std::uint64_t offset = 14695981039346656037ull;
|
||||||
static constexpr std::uint64_t prime = 1099511628211u;
|
static constexpr std::uint64_t prime = 1099511628211ull;
|
||||||
|
|
||||||
// Fowler–Noll–Vo hash function v. 1a - the good
|
// Fowler–Noll–Vo hash function v. 1a - the good
|
||||||
static constexpr std::uint64_t helper(std::uint64_t partial, const char *str) noexcept {
|
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);
|
return str[0] == 0 ? partial : helper((partial^str[0])*prime, str+1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,8 +52,8 @@ public:
|
|||||||
* @tparam N Number of characters of the identifier.
|
* @tparam N Number of characters of the identifier.
|
||||||
* @param str Human-readable identifer.
|
* @param str Human-readable identifer.
|
||||||
*/
|
*/
|
||||||
template <std::size_t N>
|
template<std::size_t N>
|
||||||
constexpr HashedString(const char (&str)[N]) noexcept
|
constexpr HashedString(const char (&str)[N]) ENTT_NOEXCEPT
|
||||||
: hash{helper(offset, str)}, str{str}
|
: hash{helper(offset, str)}, str{str}
|
||||||
{}
|
{}
|
||||||
|
|
||||||
@@ -61,7 +63,7 @@ public:
|
|||||||
*
|
*
|
||||||
* @param wrapper Helps achieving the purpose by relying on overloading.
|
* @param wrapper Helps achieving the purpose by relying on overloading.
|
||||||
*/
|
*/
|
||||||
explicit constexpr HashedString(ConstCharWrapper wrapper) noexcept
|
explicit constexpr HashedString(ConstCharWrapper wrapper) ENTT_NOEXCEPT
|
||||||
: hash{helper(offset, wrapper.str)}, str{wrapper.str}
|
: hash{helper(offset, wrapper.str)}, str{wrapper.str}
|
||||||
{}
|
{}
|
||||||
|
|
||||||
@@ -69,20 +71,20 @@ public:
|
|||||||
* @brief Returns the human-readable representation of a hashed string.
|
* @brief Returns the human-readable representation of a hashed string.
|
||||||
* @return The string used to initialize the instance.
|
* @return The string used to initialize the instance.
|
||||||
*/
|
*/
|
||||||
constexpr operator const char *() const noexcept { return str; }
|
constexpr operator const char *() const ENTT_NOEXCEPT { return str; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the numeric representation of a hashed string.
|
* @brief Returns the numeric representation of a hashed string.
|
||||||
* @return The numeric representation of the instance.
|
* @return The numeric representation of the instance.
|
||||||
*/
|
*/
|
||||||
constexpr operator hash_type() const noexcept { return hash; }
|
constexpr operator hash_type() const ENTT_NOEXCEPT { return hash; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Compares two hashed strings.
|
* @brief Compares two hashed strings.
|
||||||
* @param other Hashed string with which to compare.
|
* @param other Hashed string with which to compare.
|
||||||
* @return True if the two hashed strings are identical, false otherwise.
|
* @return True if the two hashed strings are identical, false otherwise.
|
||||||
*/
|
*/
|
||||||
constexpr bool operator==(const HashedString &other) const noexcept {
|
constexpr bool operator==(const HashedString &other) const ENTT_NOEXCEPT {
|
||||||
return hash == other.hash;
|
return hash == other.hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +100,7 @@ private:
|
|||||||
* @param rhs A valid hashed string.
|
* @param rhs A valid hashed string.
|
||||||
* @return True if the two hashed strings are identical, false otherwise.
|
* @return True if the two hashed strings are identical, false otherwise.
|
||||||
*/
|
*/
|
||||||
constexpr bool operator!=(const HashedString &lhs, const HashedString &rhs) noexcept {
|
constexpr bool operator!=(const HashedString &lhs, const HashedString &rhs) ENTT_NOEXCEPT {
|
||||||
return !(lhs == rhs);
|
return !(lhs == rhs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,4 +108,14 @@ constexpr bool operator!=(const HashedString &lhs, const HashedString &rhs) noex
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief User defined literal for hashed strings.
|
||||||
|
* @param str The literal without its suffix.
|
||||||
|
* @return A properly initialized hashed string.
|
||||||
|
*/
|
||||||
|
constexpr entt::HashedString operator"" ENTT_HS_SUFFIX(const char *str, std::size_t) ENTT_NOEXCEPT {
|
||||||
|
return entt::HashedString{str};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#endif // ENTT_CORE_HASHED_STRING_HPP
|
#endif // ENTT_CORE_HASHED_STRING_HPP
|
||||||
|
|||||||
@@ -2,72 +2,62 @@
|
|||||||
#define ENTT_CORE_IDENT_HPP
|
#define ENTT_CORE_IDENT_HPP
|
||||||
|
|
||||||
|
|
||||||
#include<type_traits>
|
#include <type_traits>
|
||||||
#include<cstddef>
|
#include <cstddef>
|
||||||
#include<utility>
|
#include <utility>
|
||||||
|
#include <tuple>
|
||||||
|
#include "../config/config.h"
|
||||||
|
|
||||||
|
|
||||||
namespace entt {
|
namespace entt {
|
||||||
|
|
||||||
|
|
||||||
namespace {
|
/**
|
||||||
|
* @cond TURN_OFF_DOXYGEN
|
||||||
|
* Internal details not to be documented.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
template<typename... Types>
|
namespace internal {
|
||||||
struct Identifier final: Identifier<Types>... {
|
|
||||||
using identifier_type = std::size_t;
|
|
||||||
|
|
||||||
template<std::size_t... Indexes>
|
|
||||||
constexpr Identifier(std::index_sequence<Indexes...>)
|
|
||||||
: Identifier<Types>{std::index_sequence<Indexes>{}}...
|
|
||||||
{}
|
|
||||||
|
|
||||||
template<typename Type>
|
template<typename...>
|
||||||
constexpr std::size_t get() const {
|
struct IsPartOf;
|
||||||
return Identifier<std::decay_t<Type>>::get();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
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>
|
template<typename Type>
|
||||||
struct Identifier<Type> {
|
struct IsPartOf<Type>: std::false_type {};
|
||||||
using identifier_type = std::size_t;
|
|
||||||
|
|
||||||
template<std::size_t Index>
|
|
||||||
constexpr Identifier(std::index_sequence<Index>)
|
|
||||||
: index{Index}
|
|
||||||
{}
|
|
||||||
|
|
||||||
constexpr std::size_t get() const {
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
const std::size_t index;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Types identifers.
|
* Internal details not to be documented.
|
||||||
|
* @endcond TURN_OFF_DOXYGEN
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Types identifiers.
|
||||||
*
|
*
|
||||||
* Variable template used to generate identifiers at compile-time for the given
|
* Variable template used to generate identifiers at compile-time for the given
|
||||||
* types. Use the `constexpr` `get` member function to know what's the
|
* types. Use the `get` member function to know what's the identifier associated
|
||||||
* identifier associated to the specific type.
|
* to the specific type.
|
||||||
*
|
*
|
||||||
* @note
|
* @note
|
||||||
* Identifiers are constant expression and can be used in any context where such
|
* Identifiers are constant expression and can be used in any context where such
|
||||||
* an expression is required. As an example:
|
* an expression is required. As an example:
|
||||||
* @code{.cpp}
|
* @code{.cpp}
|
||||||
* constexpr auto identifiers = entt::ident<AType, AnotherType>;
|
* using ID = entt::Identifier<AType, AnotherType>;
|
||||||
*
|
*
|
||||||
* switch(aTypeIdentifier) {
|
* switch(aTypeIdentifier) {
|
||||||
* case identifers.get<AType>():
|
* case ID::get<AType>():
|
||||||
* // ...
|
* // ...
|
||||||
* break;
|
* break;
|
||||||
* case identifers.get<AnotherType>():
|
* case ID::get<AnotherType>():
|
||||||
* // ...
|
* // ...
|
||||||
* break;
|
* break;
|
||||||
* default:
|
* default:
|
||||||
@@ -75,19 +65,37 @@ private:
|
|||||||
* }
|
* }
|
||||||
* @endcode
|
* @endcode
|
||||||
*
|
*
|
||||||
* @note
|
|
||||||
* In case of single type list, `get` isn't a member function template:
|
|
||||||
* @code{.cpp}
|
|
||||||
* func(std::integral_constant<
|
|
||||||
* entt::ident<AType>::identifier_type,
|
|
||||||
* entt::ident<AType>::get()
|
|
||||||
* >{});
|
|
||||||
* @endcode
|
|
||||||
*
|
|
||||||
* @tparam Types List of types for which to generate identifiers.
|
* @tparam Types List of types for which to generate identifiers.
|
||||||
*/
|
*/
|
||||||
template<typename... Types>
|
template<typename... Types>
|
||||||
constexpr auto ident = Identifier<std::decay_t<Types>...>{std::make_index_sequence<sizeof...(Types)>{}};
|
class Identifier final {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! @brief Unsigned integer type. */
|
||||||
|
using identifier_type = std::size_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
template<typename Type>
|
||||||
|
static constexpr identifier_type get() ENTT_NOEXCEPT {
|
||||||
|
return get<std::decay_t<Type>>(std::make_index_sequence<sizeof...(Types)>{});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
61
src/entt/core/monostate.hpp
Normal file
61
src/entt/core/monostate.hpp
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#ifndef ENTT_CORE_MONOSTATE_HPP
|
||||||
|
#define ENTT_CORE_MONOSTATE_HPP
|
||||||
|
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cassert>
|
||||||
|
#include "family.hpp"
|
||||||
|
#include "hashed_string.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
namespace entt {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Minimal implementation of the monostate pattern.
|
||||||
|
*
|
||||||
|
* A minimal, yet complete configuration system built on top of the monostate
|
||||||
|
* pattern. Thread safe by design, it works only with basic types like `int`s or
|
||||||
|
* `bool`s.<br/>
|
||||||
|
* Multiple types and therefore more than one value can be associated with a
|
||||||
|
* single key. Because of this, users must pay attention to use the same type
|
||||||
|
* both during an assignment and when they try to read back their data.
|
||||||
|
* Otherwise, they can incur in unexpected results.
|
||||||
|
*/
|
||||||
|
template<HashedString::hash_type>
|
||||||
|
struct Monostate {
|
||||||
|
/**
|
||||||
|
* @brief Assigns a value of a specific type to a given key.
|
||||||
|
* @tparam Type Type of the value to assign.
|
||||||
|
* @param val User data to assign to the given key.
|
||||||
|
*/
|
||||||
|
template<typename Type>
|
||||||
|
void operator=(Type val) const ENTT_NOEXCEPT {
|
||||||
|
Monostate::value<Type> = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets a value of a specific type for a given key.
|
||||||
|
* @tparam Type Type of the value to get.
|
||||||
|
* @return Stored value, if any.
|
||||||
|
*/
|
||||||
|
template<typename Type>
|
||||||
|
operator Type() const ENTT_NOEXCEPT {
|
||||||
|
return Monostate::value<Type>;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template<typename Type>
|
||||||
|
static std::atomic<Type> value;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template<HashedString::hash_type ID>
|
||||||
|
template<typename Type>
|
||||||
|
std::atomic<Type> Monostate<ID>::value{};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif // ENTT_CORE_MONOSTATE_HPP
|
||||||
@@ -2,8 +2,11 @@
|
|||||||
#define ENTT_ENTITY_ACTOR_HPP
|
#define ENTT_ENTITY_ACTOR_HPP
|
||||||
|
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include "../config/config.h"
|
||||||
#include "registry.hpp"
|
#include "registry.hpp"
|
||||||
|
#include "entity.hpp"
|
||||||
|
|
||||||
|
|
||||||
namespace entt {
|
namespace entt {
|
||||||
@@ -16,37 +19,85 @@ namespace entt {
|
|||||||
* with entity-component systems and prefer to iterate objects directly.
|
* with entity-component systems and prefer to iterate objects directly.
|
||||||
*
|
*
|
||||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||||
* @tparam Delta Type to use to provide elapsed time.
|
|
||||||
*/
|
*/
|
||||||
template<typename Entity, typename Delta>
|
template<typename Entity>
|
||||||
struct Actor {
|
struct Actor {
|
||||||
/*! @brief Type of registry used internally. */
|
/*! @brief Type of registry used internally. */
|
||||||
using registry_type = Registry<Entity>;
|
using registry_type = Registry<Entity>;
|
||||||
/*! @brief Type used to provide elapsed time. */
|
/*! @brief Underlying entity identifier. */
|
||||||
using delta_type = Delta;
|
using entity_type = Entity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Constructs an actor by using the given registry.
|
* @brief Constructs an actor by using the given registry.
|
||||||
* @param reg An entity-component system properly initialized.
|
* @param reg An entity-component system properly initialized.
|
||||||
*/
|
*/
|
||||||
Actor(Registry<Entity> ®)
|
Actor(Registry<Entity> ®)
|
||||||
: reg{reg}, entity{reg.create()}
|
: reg{®}, entt{reg.create()}
|
||||||
{}
|
{}
|
||||||
|
|
||||||
/*! @brief Default destructor. */
|
/*! @brief Default destructor. */
|
||||||
virtual ~Actor() {
|
virtual ~Actor() {
|
||||||
reg.destroy(entity);
|
reg->destroy(entt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! @brief Default copy constructor. */
|
/*! @brief Copying an actor isn't allowed. */
|
||||||
Actor(const Actor &) = default;
|
Actor(const Actor &) = delete;
|
||||||
/*! @brief Default move constructor. */
|
|
||||||
Actor(Actor &&) = default;
|
/**
|
||||||
|
* @brief Move constructor.
|
||||||
|
*
|
||||||
|
* After actor move construction, instances that have been moved from are
|
||||||
|
* placed in a valid but unspecified state. It's highly discouraged to
|
||||||
|
* continue using them.
|
||||||
|
*
|
||||||
|
* @param other The instance to move from.
|
||||||
|
*/
|
||||||
|
Actor(Actor &&other)
|
||||||
|
: reg{other.reg}, entt{other.entt}
|
||||||
|
{
|
||||||
|
other.entt = entt::null;
|
||||||
|
}
|
||||||
|
|
||||||
/*! @brief Default copy assignment operator. @return This actor. */
|
/*! @brief Default copy assignment operator. @return This actor. */
|
||||||
Actor & operator=(const Actor &) = default;
|
Actor & operator=(const Actor &) = delete;
|
||||||
/*! @brief Default move assignment operator. @return This actor. */
|
|
||||||
Actor & operator=(Actor &&) = default;
|
/**
|
||||||
|
* @brief Move assignment operator.
|
||||||
|
*
|
||||||
|
* After actor move assignment, instances that have been moved from are
|
||||||
|
* placed in a valid but unspecified state. It's highly discouraged to
|
||||||
|
* continue using them.
|
||||||
|
*
|
||||||
|
* @param other The instance to move from.
|
||||||
|
* @return This actor.
|
||||||
|
*/
|
||||||
|
Actor & operator=(Actor &&other) {
|
||||||
|
if(this != &other) {
|
||||||
|
auto tmp{std::move(other)};
|
||||||
|
std::swap(reg, tmp.reg);
|
||||||
|
std::swap(entt, tmp.entt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Assigns the given 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.
|
* @brief Assigns the given component to an actor.
|
||||||
@@ -63,8 +114,18 @@ struct Actor {
|
|||||||
* @return A reference to the newly created component.
|
* @return A reference to the newly created component.
|
||||||
*/
|
*/
|
||||||
template<typename Component, typename... Args>
|
template<typename Component, typename... Args>
|
||||||
Component & set(Args&&... args) {
|
Component & assign(Args &&... args) {
|
||||||
return reg.template accomodate<Component>(entity, std::forward<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>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -72,8 +133,18 @@ struct Actor {
|
|||||||
* @tparam Component Type of the component to remove.
|
* @tparam Component Type of the component to remove.
|
||||||
*/
|
*/
|
||||||
template<typename Component>
|
template<typename Component>
|
||||||
void unset() {
|
void remove() {
|
||||||
reg.template remove<Component>(entity);
|
reg->template remove<Component>(entt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks if an actor owns the given tag.
|
||||||
|
* @tparam Tag Type of the tag for which to perform the check.
|
||||||
|
* @return True if the actor owns the tag, false otherwise.
|
||||||
|
*/
|
||||||
|
template<typename Tag>
|
||||||
|
bool has(tag_t) const ENTT_NOEXCEPT {
|
||||||
|
return (reg->template has<Tag>() && (reg->template attachee<Tag>() == entt));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -82,55 +153,78 @@ struct Actor {
|
|||||||
* @return True if the actor has the component, false otherwise.
|
* @return True if the actor has the component, false otherwise.
|
||||||
*/
|
*/
|
||||||
template<typename Component>
|
template<typename Component>
|
||||||
bool has() const noexcept {
|
bool has() const ENTT_NOEXCEPT {
|
||||||
return reg.template has<Component>(entity);
|
return reg->template has<Component>(entt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a reference to the given tag for an actor.
|
||||||
|
* @tparam Tag Type of the tag to get.
|
||||||
|
* @return A reference to the instance of the tag owned by the actor.
|
||||||
|
*/
|
||||||
|
template<typename Tag>
|
||||||
|
const Tag & get(tag_t) const ENTT_NOEXCEPT {
|
||||||
|
assert(has<Tag>(tag_t{}));
|
||||||
|
return reg->template get<Tag>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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.
|
* @brief Returns a reference to the given component for an actor.
|
||||||
* @tparam Component Type of the component to get.
|
* @tparam Component Type of the component to get.
|
||||||
* @return A reference to the instance of the component owned by the entity.
|
* @return A reference to the instance of the component owned by the actor.
|
||||||
*/
|
*/
|
||||||
template<typename Component>
|
template<typename Component>
|
||||||
const Component & get() const noexcept {
|
const Component & get() const ENTT_NOEXCEPT {
|
||||||
return reg.template get<Component>(entity);
|
return reg->template get<Component>(entt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns a reference to the given component for an actor.
|
* @brief Returns a reference to the given component for an actor.
|
||||||
* @tparam Component Type of the component to get.
|
* @tparam Component Type of the component to get.
|
||||||
* @return A reference to the instance of the component owned by the entity.
|
* @return A reference to the instance of the component owned by the actor.
|
||||||
*/
|
*/
|
||||||
template<typename Component>
|
template<typename Component>
|
||||||
Component & get() noexcept {
|
inline Component & get() ENTT_NOEXCEPT {
|
||||||
return const_cast<Component &>(const_cast<const Actor *>(this)->get<Component>());
|
return const_cast<Component &>(const_cast<const Actor *>(this)->get<Component>());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns a reference to the underlying registry.
|
* @brief Returns a reference to the underlying registry.
|
||||||
* @return A reference to the underlying registry
|
* @return A reference to the underlying registry.
|
||||||
*/
|
*/
|
||||||
const registry_type & registry() const noexcept {
|
inline const registry_type & registry() const ENTT_NOEXCEPT {
|
||||||
return reg;
|
return *reg;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns a reference to the underlying registry.
|
* @brief Returns a reference to the underlying registry.
|
||||||
* @return A reference to the underlying registry
|
* @return A reference to the underlying registry.
|
||||||
*/
|
*/
|
||||||
registry_type & registry() noexcept {
|
inline registry_type & registry() ENTT_NOEXCEPT {
|
||||||
return const_cast<registry_type &>(const_cast<const Actor *>(this)->registry());
|
return const_cast<registry_type &>(const_cast<const Actor *>(this)->registry());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Updates an actor, whatever it means to update it.
|
* @brief Returns the entity associated with an actor.
|
||||||
* @param delta Elapsed time.
|
* @return The entity associated with the actor.
|
||||||
*/
|
*/
|
||||||
virtual void update(delta_type delta) = 0;
|
inline entity_type entity() const ENTT_NOEXCEPT {
|
||||||
|
return entt;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
registry_type ®
|
registry_type * reg;
|
||||||
Entity entity;
|
Entity entt;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -139,11 +233,8 @@ private:
|
|||||||
*
|
*
|
||||||
* The default actor is the best choice for almost all the applications.<br/>
|
* The default actor is the best choice for almost all the applications.<br/>
|
||||||
* Users should have a really good reason to choose something different.
|
* Users should have a really good reason to choose something different.
|
||||||
*
|
|
||||||
* @tparam Delta Type to use to provide elapsed time.
|
|
||||||
*/
|
*/
|
||||||
template<typename Delta>
|
using DefaultActor = Actor<DefaultRegistry::entity_type>;
|
||||||
using DefaultActor = Actor<std::uint32_t, Delta>;
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
230
src/entt/entity/attachee.hpp
Normal file
230
src/entt/entity/attachee.hpp
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
#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
|
||||||
84
src/entt/entity/entity.hpp
Normal file
84
src/entt/entity/entity.hpp
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
#ifndef ENTT_ENTITY_ENTITY_HPP
|
||||||
|
#define ENTT_ENTITY_ENTITY_HPP
|
||||||
|
|
||||||
|
|
||||||
|
#include "../config/config.h"
|
||||||
|
#include "entt_traits.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
namespace entt {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cond TURN_OFF_DOXYGEN
|
||||||
|
* Internal details not to be documented.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
|
||||||
|
struct Null {
|
||||||
|
explicit constexpr Null() = default;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool operator==(Null) const ENTT_NOEXCEPT {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool operator!=(Null) const ENTT_NOEXCEPT {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Entity>
|
||||||
|
constexpr bool operator==(const Entity entity) const ENTT_NOEXCEPT {
|
||||||
|
return entity == static_cast<Entity>(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Entity>
|
||||||
|
constexpr bool operator!=(const Entity entity) const ENTT_NOEXCEPT {
|
||||||
|
return entity != static_cast<Entity>(*this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template<typename Entity>
|
||||||
|
constexpr bool operator==(const Entity entity, Null null) ENTT_NOEXCEPT {
|
||||||
|
return null == entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template<typename Entity>
|
||||||
|
constexpr bool operator!=(const Entity entity, Null null) ENTT_NOEXCEPT {
|
||||||
|
return null != entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal details not to be documented.
|
||||||
|
* @endcond TURN_OFF_DOXYGEN
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Null entity.
|
||||||
|
*
|
||||||
|
* There exist implicit conversions from this variable to entity identifiers of
|
||||||
|
* any allowed type. Similarly, there exist comparision operators between the
|
||||||
|
* null entity and any other entity identifier.
|
||||||
|
*/
|
||||||
|
constexpr auto null = internal::Null{};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif // ENTT_ENTITY_ENTITY_HPP
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#ifndef ENTT_ENTITY_ENTT_HPP
|
#ifndef ENTT_ENTITY_ENTT_TRAITS_HPP
|
||||||
#define ENTT_ENTITY_ENTT_HPP
|
#define ENTT_ENTITY_ENTT_TRAITS_HPP
|
||||||
|
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -32,11 +32,13 @@ struct entt_traits<std::uint16_t> {
|
|||||||
using entity_type = std::uint16_t;
|
using entity_type = std::uint16_t;
|
||||||
/*! @brief Underlying version type. */
|
/*! @brief Underlying version type. */
|
||||||
using version_type = std::uint8_t;
|
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. */
|
/*! @brief Mask to use to get the entity number out of an identifier. */
|
||||||
static constexpr auto entity_mask = 0xFFF;
|
static constexpr std::uint16_t entity_mask = 0xFFF;
|
||||||
/*! @brief Mask to use to get the version out of an identifier. */
|
/*! @brief Mask to use to get the version out of an identifier. */
|
||||||
static constexpr auto version_mask = 0xF;
|
static constexpr std::uint16_t version_mask = 0xF;
|
||||||
/*! @brief Extent of the entity number within an identifier. */
|
/*! @brief Extent of the entity number within an identifier. */
|
||||||
static constexpr auto entity_shift = 12;
|
static constexpr auto entity_shift = 12;
|
||||||
};
|
};
|
||||||
@@ -47,8 +49,8 @@ struct entt_traits<std::uint16_t> {
|
|||||||
*
|
*
|
||||||
* A 32 bits entity identifier guarantees:
|
* A 32 bits entity identifier guarantees:
|
||||||
*
|
*
|
||||||
* * 24 bits for the entity number (suitable for almost all the games).
|
* * 20 bits for the entity number (suitable for almost all the games).
|
||||||
* * 8 bit for the version (resets in [0-255]).
|
* * 12 bit for the version (resets in [0-4095]).
|
||||||
*/
|
*/
|
||||||
template<>
|
template<>
|
||||||
struct entt_traits<std::uint32_t> {
|
struct entt_traits<std::uint32_t> {
|
||||||
@@ -56,13 +58,15 @@ struct entt_traits<std::uint32_t> {
|
|||||||
using entity_type = std::uint32_t;
|
using entity_type = std::uint32_t;
|
||||||
/*! @brief Underlying version type. */
|
/*! @brief Underlying version type. */
|
||||||
using version_type = std::uint16_t;
|
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. */
|
/*! @brief Mask to use to get the entity number out of an identifier. */
|
||||||
static constexpr auto entity_mask = 0xFFFFFF;
|
static constexpr std::uint32_t entity_mask = 0xFFFFF;
|
||||||
/*! @brief Mask to use to get the version out of an identifier. */
|
/*! @brief Mask to use to get the version out of an identifier. */
|
||||||
static constexpr auto version_mask = 0xFF;
|
static constexpr std::uint32_t version_mask = 0xFFF;
|
||||||
/*! @brief Extent of the entity number within an identifier. */
|
/*! @brief Extent of the entity number within an identifier. */
|
||||||
static constexpr auto entity_shift = 24;
|
static constexpr auto entity_shift = 20;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -71,8 +75,8 @@ struct entt_traits<std::uint32_t> {
|
|||||||
*
|
*
|
||||||
* A 64 bits entity identifier guarantees:
|
* A 64 bits entity identifier guarantees:
|
||||||
*
|
*
|
||||||
* * 40 bits for the entity number (an indecently large number).
|
* * 32 bits for the entity number (an indecently large number).
|
||||||
* * 24 bit for the version (an indecently large number).
|
* * 32 bit for the version (an indecently large number).
|
||||||
*/
|
*/
|
||||||
template<>
|
template<>
|
||||||
struct entt_traits<std::uint64_t> {
|
struct entt_traits<std::uint64_t> {
|
||||||
@@ -80,17 +84,19 @@ struct entt_traits<std::uint64_t> {
|
|||||||
using entity_type = std::uint64_t;
|
using entity_type = std::uint64_t;
|
||||||
/*! @brief Underlying version type. */
|
/*! @brief Underlying version type. */
|
||||||
using version_type = std::uint32_t;
|
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. */
|
/*! @brief Mask to use to get the entity number out of an identifier. */
|
||||||
static constexpr auto entity_mask = 0xFFFFFFFFFF;
|
static constexpr std::uint64_t entity_mask = 0xFFFFFFFF;
|
||||||
/*! @brief Mask to use to get the version out of an identifier. */
|
/*! @brief Mask to use to get the version out of an identifier. */
|
||||||
static constexpr auto version_mask = 0xFFFFFF;
|
static constexpr std::uint64_t version_mask = 0xFFFFFFFF;
|
||||||
/*! @brief Extent of the entity number within an identifier. */
|
/*! @brief Extent of the entity number within an identifier. */
|
||||||
static constexpr auto entity_shift = 40;
|
static constexpr auto entity_shift = 32;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#endif // ENTT_ENTITY_ENTT_HPP
|
#endif // ENTT_ENTITY_ENTT_TRAITS_HPP
|
||||||
105
src/entt/entity/helper.hpp
Normal file
105
src/entt/entity/helper.hpp
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
#ifndef ENTT_ENTITY_HELPER_HPP
|
||||||
|
#define ENTT_ENTITY_HELPER_HPP
|
||||||
|
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
#include "../core/hashed_string.hpp"
|
||||||
|
#include "../signal/sigh.hpp"
|
||||||
|
#include "registry.hpp"
|
||||||
|
#include "utility.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
namespace entt {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Dependency function prototype.
|
||||||
|
*
|
||||||
|
* A _dependency function_ is a built-in listener to use to automatically assign
|
||||||
|
* components to an entity when a type has a dependency on some other types.
|
||||||
|
*
|
||||||
|
* This is a prototype function to use to create dependencies.<br/>
|
||||||
|
* It isn't intended for direct use, although nothing forbids using it freely.
|
||||||
|
*
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Connects a dependency function to the given sink.
|
||||||
|
*
|
||||||
|
* A _dependency function_ is a built-in listener to use to automatically assign
|
||||||
|
* components to an entity when a type has a dependency on some other types.
|
||||||
|
*
|
||||||
|
* The following adds components `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...>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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.
|
||||||
|
* @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...>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Alias template to ease the assignment of labels to entities.
|
||||||
|
*
|
||||||
|
* If used in combination with hashed strings, it simplifies the assignment of
|
||||||
|
* labels to entities and the use of labels in general where a type would be
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* @tparam Value The numeric representation of an instance of hashed string.
|
||||||
|
*/
|
||||||
|
template<typename HashedString::hash_type Value>
|
||||||
|
using label = std::integral_constant<typename HashedString::hash_type, Value>;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif // ENTT_ENTITY_HELPER_HPP
|
||||||
497
src/entt/entity/prototype.hpp
Normal file
497
src/entt/entity/prototype.hpp
Normal file
@@ -0,0 +1,497 @@
|
|||||||
|
#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
724
src/entt/entity/snapshot.hpp
Normal file
724
src/entt/entity/snapshot.hpp
Normal file
@@ -0,0 +1,724 @@
|
|||||||
|
#ifndef ENTT_ENTITY_SNAPSHOT_HPP
|
||||||
|
#define ENTT_ENTITY_SNAPSHOT_HPP
|
||||||
|
|
||||||
|
|
||||||
|
#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"
|
||||||
|
|
||||||
|
|
||||||
|
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/>
|
||||||
|
* 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 {
|
||||||
|
/*! @brief A registry is allowed to create snapshots. */
|
||||||
|
friend class Registry<Entity>;
|
||||||
|
|
||||||
|
using follow_fn_type = Entity(const Registry<Entity> &, const Entity);
|
||||||
|
|
||||||
|
Snapshot(const Registry<Entity> ®istry, Entity seed, follow_fn_type *follow) ENTT_NOEXCEPT
|
||||||
|
: registry{registry},
|
||||||
|
seed{seed},
|
||||||
|
follow{follow}
|
||||||
|
{}
|
||||||
|
|
||||||
|
template<typename Component, typename Archive, typename It>
|
||||||
|
void get(Archive &archive, std::size_t sz, It first, It last) const {
|
||||||
|
archive(static_cast<Entity>(sz));
|
||||||
|
|
||||||
|
while(first != last) {
|
||||||
|
const auto entity = *(first++);
|
||||||
|
|
||||||
|
if(registry.template has<Component>(entity)) {
|
||||||
|
archive(entity, registry.template get<Component>(entity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... Component, typename Archive, typename It, std::size_t... Indexes>
|
||||||
|
void component(Archive &archive, It first, It last, std::index_sequence<Indexes...>) const {
|
||||||
|
std::array<std::size_t, sizeof...(Indexes)> size{};
|
||||||
|
auto begin = first;
|
||||||
|
|
||||||
|
while(begin != last) {
|
||||||
|
const auto entity = *(begin++);
|
||||||
|
using accumulator_type = std::size_t[];
|
||||||
|
accumulator_type accumulator = { (registry.template has<Component>(entity) ? ++size[Indexes] : size[Indexes])... };
|
||||||
|
(void)accumulator;
|
||||||
|
}
|
||||||
|
|
||||||
|
using accumulator_type = int[];
|
||||||
|
accumulator_type accumulator = { (get<Component>(archive, size[Indexes], first, last), 0)... };
|
||||||
|
(void)accumulator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! @brief Copying a snapshot isn't allowed. */
|
||||||
|
Snapshot(const Snapshot &) = delete;
|
||||||
|
/*! @brief Default move constructor. */
|
||||||
|
Snapshot(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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Puts aside all the entities that are still in use.
|
||||||
|
*
|
||||||
|
* Entities are serialized along with their versions. Destroyed entities are
|
||||||
|
* not taken in consideration by this function.
|
||||||
|
*
|
||||||
|
* @tparam Archive Type of output archive.
|
||||||
|
* @param archive A valid reference to an output archive.
|
||||||
|
* @return An object of this type to continue creating the snapshot.
|
||||||
|
*/
|
||||||
|
template<typename Archive>
|
||||||
|
const Snapshot & entities(Archive &archive) const {
|
||||||
|
archive(static_cast<Entity>(registry.alive()));
|
||||||
|
registry.each([&archive](const auto entity) { archive(entity); });
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Puts aside destroyed entities.
|
||||||
|
*
|
||||||
|
* Entities are serialized along with their versions. Entities that are
|
||||||
|
* still in use are not taken in consideration by this function.
|
||||||
|
*
|
||||||
|
* @tparam Archive Type of output archive.
|
||||||
|
* @param archive A valid reference to an output archive.
|
||||||
|
* @return An object of this type to continue creating the snapshot.
|
||||||
|
*/
|
||||||
|
template<typename Archive>
|
||||||
|
const Snapshot & destroyed(Archive &archive) const {
|
||||||
|
auto size = registry.size() - registry.alive();
|
||||||
|
archive(static_cast<Entity>(size));
|
||||||
|
|
||||||
|
if(size) {
|
||||||
|
auto curr = seed;
|
||||||
|
archive(curr);
|
||||||
|
|
||||||
|
for(--size; size; --size) {
|
||||||
|
curr = follow(registry, curr);
|
||||||
|
archive(curr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Puts aside the given component.
|
||||||
|
*
|
||||||
|
* Each instance is serialized together with the entity to which it belongs.
|
||||||
|
* Entities are serialized along with their versions.
|
||||||
|
*
|
||||||
|
* @tparam Component Type of component to serialize.
|
||||||
|
* @tparam Archive Type of output archive.
|
||||||
|
* @param archive A valid reference to an output archive.
|
||||||
|
* @return An object of this type to continue creating the snapshot.
|
||||||
|
*/
|
||||||
|
template<typename Component, typename Archive>
|
||||||
|
const Snapshot & component(Archive &archive) const {
|
||||||
|
const auto sz = registry.template size<Component>();
|
||||||
|
const auto *entities = registry.template data<Component>();
|
||||||
|
|
||||||
|
archive(static_cast<Entity>(sz));
|
||||||
|
|
||||||
|
for(std::remove_const_t<decltype(sz)> i{}; i < sz; ++i) {
|
||||||
|
const auto entity = entities[i];
|
||||||
|
archive(entity, registry.template get<Component>(entity));
|
||||||
|
};
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Puts aside the given components.
|
||||||
|
*
|
||||||
|
* Each instance is serialized together with the entity to which it belongs.
|
||||||
|
* Entities are serialized along with their versions.
|
||||||
|
*
|
||||||
|
* @tparam Component Types of components to serialize.
|
||||||
|
* @tparam Archive Type of output archive.
|
||||||
|
* @param archive A valid reference to an output archive.
|
||||||
|
* @return An object of this type to continue creating the snapshot.
|
||||||
|
*/
|
||||||
|
template<typename... Component, typename Archive>
|
||||||
|
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;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Puts aside the given components for the entities in a range.
|
||||||
|
*
|
||||||
|
* Each instance is serialized together with the entity to which it belongs.
|
||||||
|
* Entities are serialized along with their versions.
|
||||||
|
*
|
||||||
|
* @tparam Component Types of components to serialize.
|
||||||
|
* @tparam Archive Type of output archive.
|
||||||
|
* @tparam It Type of input iterator.
|
||||||
|
* @param archive A valid reference to an output archive.
|
||||||
|
* @param first An iterator to the first element of the range to serialize.
|
||||||
|
* @param last An iterator past the last element of the range to serialize.
|
||||||
|
* @return An object of this type to continue creating the snapshot.
|
||||||
|
*/
|
||||||
|
template<typename... Component, typename Archive, typename It>
|
||||||
|
const Snapshot & component(Archive &archive, It first, It last) const {
|
||||||
|
component<Component...>(archive, first, last, std::make_index_sequence<sizeof...(Component)>{});
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Puts aside the given tag.
|
||||||
|
*
|
||||||
|
* Each instance is serialized together with the entity to which it belongs.
|
||||||
|
* Entities are serialized along with their versions.
|
||||||
|
*
|
||||||
|
* @tparam Tag Type of tag to serialize.
|
||||||
|
* @tparam Archive Type of output archive.
|
||||||
|
* @param archive A valid reference to an output archive.
|
||||||
|
* @return An object of this type to continue creating the snapshot.
|
||||||
|
*/
|
||||||
|
template<typename Tag, typename Archive>
|
||||||
|
const Snapshot & tag(Archive &archive) const {
|
||||||
|
const bool has = registry.template has<Tag>();
|
||||||
|
|
||||||
|
// numerical length is forced for tags to facilitate loading
|
||||||
|
archive(has ? Entity(1): Entity{});
|
||||||
|
|
||||||
|
if(has) {
|
||||||
|
archive(registry.template attachee<Tag>(), registry.template get<Tag>());
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Puts aside the given tags.
|
||||||
|
*
|
||||||
|
* Each instance is serialized together with the entity to which it belongs.
|
||||||
|
* Entities are serialized along with their versions.
|
||||||
|
*
|
||||||
|
* @tparam Tag Types of tags to serialize.
|
||||||
|
* @tparam Archive Type of output archive.
|
||||||
|
* @param archive A valid reference to an output archive.
|
||||||
|
* @return An object of this type to continue creating the snapshot.
|
||||||
|
*/
|
||||||
|
template<typename... Tag, typename Archive>
|
||||||
|
std::enable_if_t<(sizeof...(Tag) > 1), const Snapshot &>
|
||||||
|
tag(Archive &archive) const {
|
||||||
|
using accumulator_type = int[];
|
||||||
|
accumulator_type accumulator = { 0, (tag<Tag>(archive), 0)... };
|
||||||
|
(void)accumulator;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const Registry<Entity> ®istry;
|
||||||
|
const Entity seed;
|
||||||
|
follow_fn_type *follow;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Utility class to restore a snapshot as a whole.
|
||||||
|
*
|
||||||
|
* A snapshot loader requires that the destination registry be empty and loads
|
||||||
|
* all the data at once while keeping intact the identifiers that the entities
|
||||||
|
* originally had.<br/>
|
||||||
|
* An example of use is the implementation of a save/restore utility.
|
||||||
|
*
|
||||||
|
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||||
|
*/
|
||||||
|
template<typename Entity>
|
||||||
|
class SnapshotLoader final {
|
||||||
|
/*! @brief A registry is allowed to create snapshot loaders. */
|
||||||
|
friend class Registry<Entity>;
|
||||||
|
|
||||||
|
using assure_fn_type = void(Registry<Entity> &, const Entity, const bool);
|
||||||
|
|
||||||
|
SnapshotLoader(Registry<Entity> ®istry, assure_fn_type *assure_fn) ENTT_NOEXCEPT
|
||||||
|
: registry{registry},
|
||||||
|
assure_fn{assure_fn}
|
||||||
|
{
|
||||||
|
// restore a snapshot as a whole requires a clean registry
|
||||||
|
assert(!registry.capacity());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Archive>
|
||||||
|
void assure(Archive &archive, bool destroyed) const {
|
||||||
|
Entity length{};
|
||||||
|
archive(length);
|
||||||
|
|
||||||
|
while(length--) {
|
||||||
|
Entity entity{};
|
||||||
|
archive(entity);
|
||||||
|
assure_fn(registry, entity, destroyed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Type, typename Archive, typename... Args>
|
||||||
|
void assign(Archive &archive, Args... args) const {
|
||||||
|
Entity 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! @brief Copying a snapshot loader isn't allowed. */
|
||||||
|
SnapshotLoader(const SnapshotLoader &) = delete;
|
||||||
|
/*! @brief Default move constructor. */
|
||||||
|
SnapshotLoader(SnapshotLoader &&) = 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Restores entities that were in use during serialization.
|
||||||
|
*
|
||||||
|
* This function restores the entities that were in use during serialization
|
||||||
|
* and gives them the versions they originally had.
|
||||||
|
*
|
||||||
|
* @tparam Archive Type of input archive.
|
||||||
|
* @param archive A valid reference to an input archive.
|
||||||
|
* @return A valid loader to continue restoring data.
|
||||||
|
*/
|
||||||
|
template<typename Archive>
|
||||||
|
const SnapshotLoader & entities(Archive &archive) const {
|
||||||
|
static constexpr auto destroyed = false;
|
||||||
|
assure(archive, destroyed);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Restores entities that were destroyed during serialization.
|
||||||
|
*
|
||||||
|
* This function restores the entities that were destroyed during
|
||||||
|
* serialization and gives them the versions they originally had.
|
||||||
|
*
|
||||||
|
* @tparam Archive Type of input archive.
|
||||||
|
* @param archive A valid reference to an input archive.
|
||||||
|
* @return A valid loader to continue restoring data.
|
||||||
|
*/
|
||||||
|
template<typename Archive>
|
||||||
|
const SnapshotLoader & destroyed(Archive &archive) const {
|
||||||
|
static constexpr auto destroyed = true;
|
||||||
|
assure(archive, destroyed);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Restores components and assigns them to the right entities.
|
||||||
|
*
|
||||||
|
* The template parameter list must be exactly the same used during
|
||||||
|
* serialization. In the event that the entity to which the component is
|
||||||
|
* assigned doesn't exist yet, the loader will take care to create it with
|
||||||
|
* the version it originally had.
|
||||||
|
*
|
||||||
|
* @tparam Component Types of components to restore.
|
||||||
|
* @tparam Archive Type of input archive.
|
||||||
|
* @param archive A valid reference to an input archive.
|
||||||
|
* @return A valid loader to continue restoring data.
|
||||||
|
*/
|
||||||
|
template<typename... Component, typename Archive>
|
||||||
|
const SnapshotLoader & component(Archive &archive) const {
|
||||||
|
using accumulator_type = int[];
|
||||||
|
accumulator_type accumulator = { 0, (assign<Component>(archive), 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 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.
|
||||||
|
*
|
||||||
|
* 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/>
|
||||||
|
* 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);
|
||||||
|
});
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Registry<Entity> ®istry;
|
||||||
|
assure_fn_type *assure_fn;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Utility class for _continuous loading_.
|
||||||
|
*
|
||||||
|
* A _continuous loader_ is designed to load data from a source registry to a
|
||||||
|
* (possibly) non-empty destination. The loader can accomodate in a registry
|
||||||
|
* more than one snapshot in a sort of _continuous loading_ that updates the
|
||||||
|
* destination one step at a time.<br/>
|
||||||
|
* Identifiers that entities originally had are not transferred to the target.
|
||||||
|
* Instead, the loader maps remote identifiers to local ones while restoring a
|
||||||
|
* snapshot.<br/>
|
||||||
|
* An example of use is the implementation of a client-server applications with
|
||||||
|
* the requirement of transferring somehow parts of the representation side to
|
||||||
|
* side.
|
||||||
|
*
|
||||||
|
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||||
|
*/
|
||||||
|
template<typename Entity>
|
||||||
|
class ContinuousLoader final {
|
||||||
|
using traits_type = entt_traits<Entity>;
|
||||||
|
|
||||||
|
void destroy(Entity entity) {
|
||||||
|
const auto it = remloc.find(entity);
|
||||||
|
|
||||||
|
if(it == remloc.cend()) {
|
||||||
|
const auto local = registry.create();
|
||||||
|
remloc.emplace(entity, std::make_pair(local, true));
|
||||||
|
registry.destroy(local);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void restore(Entity entity) {
|
||||||
|
const auto it = remloc.find(entity);
|
||||||
|
|
||||||
|
if(it == remloc.cend()) {
|
||||||
|
const auto local = registry.create();
|
||||||
|
remloc.emplace(entity, std::make_pair(local, true));
|
||||||
|
} else {
|
||||||
|
remloc[entity].first =
|
||||||
|
registry.valid(remloc[entity].first)
|
||||||
|
? remloc[entity].first
|
||||||
|
: registry.create();
|
||||||
|
|
||||||
|
// set the dirty flag
|
||||||
|
remloc[entity].second = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Type, typename Member>
|
||||||
|
std::enable_if_t<std::is_same<Member, Entity>::value>
|
||||||
|
update(Type &instance, Member Type:: *member) {
|
||||||
|
instance.*member = map(instance.*member);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Type, typename Member>
|
||||||
|
std::enable_if_t<std::is_same<typename std::iterator_traits<typename Member::iterator>::value_type, Entity>::value>
|
||||||
|
update(Type &instance, Member Type:: *member) {
|
||||||
|
for(auto &entity: instance.*member) {
|
||||||
|
entity = map(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Other, typename Type, typename Member>
|
||||||
|
std::enable_if_t<!std::is_same<Other, Type>::value>
|
||||||
|
update(Other &, Member Type:: *) {}
|
||||||
|
|
||||||
|
template<typename Archive>
|
||||||
|
void assure(Archive &archive, void(ContinuousLoader:: *member)(Entity)) {
|
||||||
|
Entity length{};
|
||||||
|
archive(length);
|
||||||
|
|
||||||
|
while(length--) {
|
||||||
|
Entity entity{};
|
||||||
|
archive(entity);
|
||||||
|
(this->*member)(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Component>
|
||||||
|
void reset() {
|
||||||
|
for(auto &&ref: remloc) {
|
||||||
|
const auto local = ref.second.first;
|
||||||
|
|
||||||
|
if(registry.valid(local)) {
|
||||||
|
registry.template reset<Component>(local);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Other, typename Archive, typename Func, typename... Type, typename... Member>
|
||||||
|
void assign(Archive &archive, Func func, Member Type:: *... member) {
|
||||||
|
Entity length{};
|
||||||
|
archive(length);
|
||||||
|
|
||||||
|
while(length--) {
|
||||||
|
Entity entity{};
|
||||||
|
Other instance{};
|
||||||
|
|
||||||
|
archive(entity, instance);
|
||||||
|
restore(entity);
|
||||||
|
|
||||||
|
using accumulator_type = int[];
|
||||||
|
accumulator_type accumulator = { 0, (update(instance, member), 0)... };
|
||||||
|
(void)accumulator;
|
||||||
|
|
||||||
|
func(map(entity), instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! @brief Underlying entity identifier. */
|
||||||
|
using entity_type = Entity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs a loader that is bound to a given registry.
|
||||||
|
* @param registry A valid reference to a registry.
|
||||||
|
*/
|
||||||
|
ContinuousLoader(Registry<entity_type> ®istry) ENTT_NOEXCEPT
|
||||||
|
: registry{registry}
|
||||||
|
{}
|
||||||
|
|
||||||
|
/*! @brief Copying a snapshot loader isn't allowed. */
|
||||||
|
ContinuousLoader(const ContinuousLoader &) = delete;
|
||||||
|
/*! @brief Default move constructor. */
|
||||||
|
ContinuousLoader(ContinuousLoader &&) = 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Restores entities that were in use during serialization.
|
||||||
|
*
|
||||||
|
* This function restores the entities that were in use during serialization
|
||||||
|
* and creates local counterparts for them if required.
|
||||||
|
*
|
||||||
|
* @tparam Archive Type of input archive.
|
||||||
|
* @param archive A valid reference to an input archive.
|
||||||
|
* @return A non-const reference to this loader.
|
||||||
|
*/
|
||||||
|
template<typename Archive>
|
||||||
|
ContinuousLoader & entities(Archive &archive) {
|
||||||
|
assure(archive, &ContinuousLoader::restore);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Restores entities that were destroyed during serialization.
|
||||||
|
*
|
||||||
|
* This function restores the entities that were destroyed during
|
||||||
|
* serialization and creates local counterparts for them if required.
|
||||||
|
*
|
||||||
|
* @tparam Archive Type of input archive.
|
||||||
|
* @param archive A valid reference to an input archive.
|
||||||
|
* @return A non-const reference to this loader.
|
||||||
|
*/
|
||||||
|
template<typename Archive>
|
||||||
|
ContinuousLoader & destroyed(Archive &archive) {
|
||||||
|
assure(archive, &ContinuousLoader::destroy);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Restores components and assigns them to the right entities.
|
||||||
|
*
|
||||||
|
* The template parameter list must be exactly the same used during
|
||||||
|
* serialization. In the event that the entity to which the component is
|
||||||
|
* assigned doesn't exist yet, the loader will take care to create a local
|
||||||
|
* counterpart for it.<br/>
|
||||||
|
* Members can be either data members of type entity_type or containers of
|
||||||
|
* entities. In both cases, the loader will visit them and update the
|
||||||
|
* entities by replacing each one with its local counterpart.
|
||||||
|
*
|
||||||
|
* @tparam Component Type of component to restore.
|
||||||
|
* @tparam Archive Type of input archive.
|
||||||
|
* @tparam Type Types of components to update with local counterparts.
|
||||||
|
* @tparam Member Types of members to update with their local counterparts.
|
||||||
|
* @param archive A valid reference to an input archive.
|
||||||
|
* @param member Members to update with their local counterparts.
|
||||||
|
* @return A non-const reference to this loader.
|
||||||
|
*/
|
||||||
|
template<typename... Component, typename Archive, typename... Type, typename... Member>
|
||||||
|
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;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helps to purge entities that no longer have a conterpart.
|
||||||
|
*
|
||||||
|
* Users should invoke this member function after restoring each snapshot,
|
||||||
|
* unless they know exactly what they are doing.
|
||||||
|
*
|
||||||
|
* @return A non-const reference to this loader.
|
||||||
|
*/
|
||||||
|
ContinuousLoader & shrink() {
|
||||||
|
auto it = remloc.begin();
|
||||||
|
|
||||||
|
while(it != remloc.cend()) {
|
||||||
|
const auto local = it->second.first;
|
||||||
|
bool &dirty = it->second.second;
|
||||||
|
|
||||||
|
if(dirty) {
|
||||||
|
dirty = false;
|
||||||
|
++it;
|
||||||
|
} else {
|
||||||
|
if(registry.valid(local)) {
|
||||||
|
registry.destroy(local);
|
||||||
|
}
|
||||||
|
|
||||||
|
it = remloc.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroys those entities that have neither components nor tags.
|
||||||
|
*
|
||||||
|
* 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/>
|
||||||
|
* 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);
|
||||||
|
});
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Tests if a loader knows about a given entity.
|
||||||
|
* @param entity 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
entity_type map(entity_type entity) const ENTT_NOEXCEPT {
|
||||||
|
assert(has(entity));
|
||||||
|
return remloc.find(entity)->second.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_map<Entity, std::pair<Entity, bool>> remloc;
|
||||||
|
Registry<Entity> ®istry;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif // ENTT_ENTITY_SNAPSHOT_HPP
|
||||||
File diff suppressed because it is too large
Load Diff
23
src/entt/entity/utility.hpp
Normal file
23
src/entt/entity/utility.hpp
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#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,10 +1,18 @@
|
|||||||
|
#include "core/algorithm.hpp"
|
||||||
#include "core/family.hpp"
|
#include "core/family.hpp"
|
||||||
#include "core/hashed_string.hpp"
|
#include "core/hashed_string.hpp"
|
||||||
#include "core/ident.hpp"
|
#include "core/ident.hpp"
|
||||||
|
#include "core/monostate.hpp"
|
||||||
#include "entity/actor.hpp"
|
#include "entity/actor.hpp"
|
||||||
|
#include "entity/attachee.hpp"
|
||||||
|
#include "entity/entity.hpp"
|
||||||
|
#include "entity/entt_traits.hpp"
|
||||||
|
#include "entity/helper.hpp"
|
||||||
|
#include "entity/prototype.hpp"
|
||||||
#include "entity/registry.hpp"
|
#include "entity/registry.hpp"
|
||||||
|
#include "entity/snapshot.hpp"
|
||||||
#include "entity/sparse_set.hpp"
|
#include "entity/sparse_set.hpp"
|
||||||
#include "entity/traits.hpp"
|
#include "entity/utility.hpp"
|
||||||
#include "entity/view.hpp"
|
#include "entity/view.hpp"
|
||||||
#include "locator/locator.hpp"
|
#include "locator/locator.hpp"
|
||||||
#include "process/process.hpp"
|
#include "process/process.hpp"
|
||||||
@@ -12,8 +20,7 @@
|
|||||||
#include "resource/cache.hpp"
|
#include "resource/cache.hpp"
|
||||||
#include "resource/handle.hpp"
|
#include "resource/handle.hpp"
|
||||||
#include "resource/loader.hpp"
|
#include "resource/loader.hpp"
|
||||||
#include "signal/bus.hpp"
|
|
||||||
#include "signal/delegate.hpp"
|
#include "signal/delegate.hpp"
|
||||||
|
#include "signal/dispatcher.hpp"
|
||||||
#include "signal/emitter.hpp"
|
#include "signal/emitter.hpp"
|
||||||
#include "signal/sigh.hpp"
|
#include "signal/sigh.hpp"
|
||||||
#include "signal/signal.hpp"
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include "../config/config.h"
|
||||||
|
|
||||||
|
|
||||||
namespace entt {
|
namespace entt {
|
||||||
@@ -14,7 +15,7 @@ namespace entt {
|
|||||||
* @brief Service locator, nothing more.
|
* @brief Service locator, nothing more.
|
||||||
*
|
*
|
||||||
* A service locator can be used to do what it promises: locate services.<br/>
|
* A service locator can be used to do what it promises: locate services.<br/>
|
||||||
* Usually service locators are tighly bound to the services they expose and
|
* Usually service locators are tightly bound to the services they expose and
|
||||||
* thus it's hard to define a general purpose class to do that. This template
|
* thus it's hard to define a general purpose class to do that. This template
|
||||||
* based implementation tries to fill the gap and to get rid of the burden of
|
* based implementation tries to fill the gap and to get rid of the burden of
|
||||||
* defining a different specific locator for each application.
|
* defining a different specific locator for each application.
|
||||||
@@ -35,7 +36,7 @@ struct ServiceLocator final {
|
|||||||
* @brief Tests if a valid service implementation is set.
|
* @brief Tests if a valid service implementation is set.
|
||||||
* @return True if the service is set, false otherwise.
|
* @return True if the service is set, false otherwise.
|
||||||
*/
|
*/
|
||||||
inline static bool empty() noexcept {
|
inline static bool empty() ENTT_NOEXCEPT {
|
||||||
return !static_cast<bool>(service);
|
return !static_cast<bool>(service);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +50,7 @@ struct ServiceLocator final {
|
|||||||
*
|
*
|
||||||
* @return A reference to the service implementation currently set, if any.
|
* @return A reference to the service implementation currently set, if any.
|
||||||
*/
|
*/
|
||||||
inline static std::weak_ptr<Service> get() noexcept {
|
inline static std::weak_ptr<Service> get() ENTT_NOEXCEPT {
|
||||||
return service;
|
return service;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +68,7 @@ struct ServiceLocator final {
|
|||||||
*
|
*
|
||||||
* @return A reference to the service implementation currently set, if any.
|
* @return A reference to the service implementation currently set, if any.
|
||||||
*/
|
*/
|
||||||
inline static Service & ref() noexcept {
|
inline static Service & ref() ENTT_NOEXCEPT {
|
||||||
return *service;
|
return *service;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +79,7 @@ struct ServiceLocator final {
|
|||||||
* @param args Parameters to use to construct the service.
|
* @param args Parameters to use to construct the service.
|
||||||
*/
|
*/
|
||||||
template<typename Impl = Service, typename... Args>
|
template<typename Impl = Service, typename... Args>
|
||||||
inline static void set(Args&&... args) {
|
inline static void set(Args &&... args) {
|
||||||
service = std::make_shared<Impl>(std::forward<Args>(args)...);
|
service = std::make_shared<Impl>(std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,33 +5,12 @@
|
|||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include "../config/config.h"
|
||||||
|
|
||||||
|
|
||||||
namespace entt {
|
namespace entt {
|
||||||
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
|
|
||||||
struct BaseProcess {
|
|
||||||
enum class State: unsigned int {
|
|
||||||
UNINITIALIZED = 0,
|
|
||||||
RUNNING,
|
|
||||||
PAUSED,
|
|
||||||
SUCCEEDED,
|
|
||||||
FAILED,
|
|
||||||
ABORTED,
|
|
||||||
FINISHED
|
|
||||||
};
|
|
||||||
|
|
||||||
template<State state>
|
|
||||||
using tag = std::integral_constant<State, state>;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Base class for processes.
|
* @brief Base class for processes.
|
||||||
*
|
*
|
||||||
@@ -41,33 +20,42 @@ struct BaseProcess {
|
|||||||
* required:
|
* required:
|
||||||
*
|
*
|
||||||
* * @code{.cpp}
|
* * @code{.cpp}
|
||||||
* void update(Delta);
|
* void update(Delta, void *);
|
||||||
* @endcode
|
* @endcode
|
||||||
|
*
|
||||||
* It's invoked once per tick until a process is explicitly aborted or it
|
* It's invoked once per tick until a process is explicitly aborted or it
|
||||||
* terminates either with or without errors. Even though it's not mandatory to
|
* terminates either with or without errors. Even though it's not mandatory to
|
||||||
* declare this member function, as a rule of thumb each process should at
|
* declare this member function, as a rule of thumb each process should at
|
||||||
* least define it to work properly.
|
* least define it to work properly. The `void *` parameter is an opaque
|
||||||
|
* pointer to user data (if any) forwarded directly to the process during an
|
||||||
|
* update.
|
||||||
*
|
*
|
||||||
* * @code{.cpp}
|
* * @code{.cpp}
|
||||||
* void init();
|
* void init(void *);
|
||||||
* @endcode
|
* @endcode
|
||||||
* It's invoked at the first tick, immediately before an update.
|
*
|
||||||
|
* 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.
|
||||||
*
|
*
|
||||||
* * @code{.cpp}
|
* * @code{.cpp}
|
||||||
* void succeeded();
|
* void succeeded();
|
||||||
* @endcode
|
* @endcode
|
||||||
|
*
|
||||||
* It's invoked in case of success, immediately after an update and during the
|
* It's invoked in case of success, immediately after an update and during the
|
||||||
* same tick.
|
* same tick.
|
||||||
*
|
*
|
||||||
* * @code{.cpp}
|
* * @code{.cpp}
|
||||||
* void failed();
|
* void failed();
|
||||||
* @endcode
|
* @endcode
|
||||||
|
*
|
||||||
* It's invoked in case of errors, immediately after an update and during the
|
* It's invoked in case of errors, immediately after an update and during the
|
||||||
* same tick.
|
* same tick.
|
||||||
*
|
*
|
||||||
* * @code{.cpp}
|
* * @code{.cpp}
|
||||||
* void aborted();
|
* void aborted();
|
||||||
* @endcode
|
* @endcode
|
||||||
|
*
|
||||||
* It's invoked only if a process is explicitly aborted. There is no guarantee
|
* It's invoked only if a process is explicitly aborted. There is no guarantee
|
||||||
* that it executes in the same tick, this depends solely on whether the
|
* that it executes in the same tick, this depends solely on whether the
|
||||||
* process is aborted immediately or not.
|
* process is aborted immediately or not.
|
||||||
@@ -82,17 +70,30 @@ struct BaseProcess {
|
|||||||
* @tparam Delta Type to use to provide elapsed time.
|
* @tparam Delta Type to use to provide elapsed time.
|
||||||
*/
|
*/
|
||||||
template<typename Derived, typename Delta>
|
template<typename Derived, typename Delta>
|
||||||
class Process: private BaseProcess {
|
class Process {
|
||||||
|
enum class State: unsigned int {
|
||||||
|
UNINITIALIZED = 0,
|
||||||
|
RUNNING,
|
||||||
|
PAUSED,
|
||||||
|
SUCCEEDED,
|
||||||
|
FAILED,
|
||||||
|
ABORTED,
|
||||||
|
FINISHED
|
||||||
|
};
|
||||||
|
|
||||||
|
template<State state>
|
||||||
|
using tag = std::integral_constant<State, state>;
|
||||||
|
|
||||||
template<typename Target = Derived>
|
template<typename Target = Derived>
|
||||||
auto tick(int, tag<State::UNINITIALIZED>)
|
auto tick(int, tag<State::UNINITIALIZED>, void *data)
|
||||||
-> decltype(std::declval<Target>().init()) {
|
-> decltype(std::declval<Target>().init(data)) {
|
||||||
static_cast<Target *>(this)->init();
|
static_cast<Target *>(this)->init(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Target = Derived>
|
template<typename Target = Derived>
|
||||||
auto tick(int, tag<State::RUNNING>, Delta delta)
|
auto tick(int, tag<State::RUNNING>, Delta delta, void *data)
|
||||||
-> decltype(std::declval<Target>().update(delta)) {
|
-> decltype(std::declval<Target>().update(delta, data)) {
|
||||||
static_cast<Target *>(this)->update(delta);
|
static_cast<Target *>(this)->update(delta, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Target = Derived>
|
template<typename Target = Derived>
|
||||||
@@ -114,7 +115,7 @@ class Process: private BaseProcess {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<State S, typename... Args>
|
template<State S, typename... Args>
|
||||||
void tick(char, tag<S>, Args&&...) {}
|
void tick(char, tag<S>, Args &&...) const ENTT_NOEXCEPT {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/**
|
/**
|
||||||
@@ -123,7 +124,7 @@ protected:
|
|||||||
* The function is idempotent and it does nothing if the process isn't
|
* The function is idempotent and it does nothing if the process isn't
|
||||||
* alive.
|
* alive.
|
||||||
*/
|
*/
|
||||||
void succeed() noexcept {
|
void succeed() ENTT_NOEXCEPT {
|
||||||
if(alive()) {
|
if(alive()) {
|
||||||
current = State::SUCCEEDED;
|
current = State::SUCCEEDED;
|
||||||
}
|
}
|
||||||
@@ -135,7 +136,7 @@ protected:
|
|||||||
* The function is idempotent and it does nothing if the process isn't
|
* The function is idempotent and it does nothing if the process isn't
|
||||||
* alive.
|
* alive.
|
||||||
*/
|
*/
|
||||||
void fail() noexcept {
|
void fail() ENTT_NOEXCEPT {
|
||||||
if(alive()) {
|
if(alive()) {
|
||||||
current = State::FAILED;
|
current = State::FAILED;
|
||||||
}
|
}
|
||||||
@@ -147,7 +148,7 @@ protected:
|
|||||||
* The function is idempotent and it does nothing if the process isn't
|
* The function is idempotent and it does nothing if the process isn't
|
||||||
* running.
|
* running.
|
||||||
*/
|
*/
|
||||||
void pause() noexcept {
|
void pause() ENTT_NOEXCEPT {
|
||||||
if(current == State::RUNNING) {
|
if(current == State::RUNNING) {
|
||||||
current = State::PAUSED;
|
current = State::PAUSED;
|
||||||
}
|
}
|
||||||
@@ -159,7 +160,7 @@ protected:
|
|||||||
* The function is idempotent and it does nothing if the process isn't
|
* The function is idempotent and it does nothing if the process isn't
|
||||||
* paused.
|
* paused.
|
||||||
*/
|
*/
|
||||||
void unpause() noexcept {
|
void unpause() ENTT_NOEXCEPT {
|
||||||
if(current == State::PAUSED) {
|
if(current == State::PAUSED) {
|
||||||
current = State::RUNNING;
|
current = State::RUNNING;
|
||||||
}
|
}
|
||||||
@@ -170,7 +171,7 @@ public:
|
|||||||
using delta_type = Delta;
|
using delta_type = Delta;
|
||||||
|
|
||||||
/*! @brief Default destructor. */
|
/*! @brief Default destructor. */
|
||||||
~Process() noexcept {
|
virtual ~Process() ENTT_NOEXCEPT {
|
||||||
static_assert(std::is_base_of<Process, Derived>::value, "!");
|
static_assert(std::is_base_of<Process, Derived>::value, "!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +183,7 @@ public:
|
|||||||
*
|
*
|
||||||
* @param immediately Requests an immediate operation.
|
* @param immediately Requests an immediate operation.
|
||||||
*/
|
*/
|
||||||
void abort(bool immediately = false) noexcept {
|
void abort(const bool immediately = false) ENTT_NOEXCEPT {
|
||||||
if(alive()) {
|
if(alive()) {
|
||||||
current = State::ABORTED;
|
current = State::ABORTED;
|
||||||
|
|
||||||
@@ -196,7 +197,7 @@ public:
|
|||||||
* @brief Returns true if a process is either running or paused.
|
* @brief Returns true if a process is either running or paused.
|
||||||
* @return True if the process is still alive, false otherwise.
|
* @return True if the process is still alive, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool alive() const noexcept {
|
bool alive() const ENTT_NOEXCEPT {
|
||||||
return current == State::RUNNING || current == State::PAUSED;
|
return current == State::RUNNING || current == State::PAUSED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,7 +205,7 @@ public:
|
|||||||
* @brief Returns true if a process is already terminated.
|
* @brief Returns true if a process is already terminated.
|
||||||
* @return True if the process is terminated, false otherwise.
|
* @return True if the process is terminated, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool dead() const noexcept {
|
bool dead() const ENTT_NOEXCEPT {
|
||||||
return current == State::FINISHED;
|
return current == State::FINISHED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,7 +213,7 @@ public:
|
|||||||
* @brief Returns true if a process is currently paused.
|
* @brief Returns true if a process is currently paused.
|
||||||
* @return True if the process is paused, false otherwise.
|
* @return True if the process is paused, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool paused() const noexcept {
|
bool paused() const ENTT_NOEXCEPT {
|
||||||
return current == State::PAUSED;
|
return current == State::PAUSED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,22 +221,23 @@ public:
|
|||||||
* @brief Returns true if a process terminated with errors.
|
* @brief Returns true if a process terminated with errors.
|
||||||
* @return True if the process terminated with errors, false otherwise.
|
* @return True if the process terminated with errors, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool rejected() const noexcept {
|
bool rejected() const ENTT_NOEXCEPT {
|
||||||
return stopped;
|
return stopped;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Updates a process and its internal state if required.
|
* @brief Updates a process and its internal state if required.
|
||||||
* @param delta Elapsed time.
|
* @param delta Elapsed time.
|
||||||
|
* @param data Optional data.
|
||||||
*/
|
*/
|
||||||
void tick(Delta delta) {
|
void tick(const Delta delta, void *data = nullptr) {
|
||||||
switch (current) {
|
switch (current) {
|
||||||
case State::UNINITIALIZED:
|
case State::UNINITIALIZED:
|
||||||
tick(0, tag<State::UNINITIALIZED>{});
|
tick(0, tag<State::UNINITIALIZED>{}, data);
|
||||||
current = State::RUNNING;
|
current = State::RUNNING;
|
||||||
// no break on purpose, tasks are executed immediately
|
// no break on purpose, tasks are executed immediately
|
||||||
case State::RUNNING:
|
case State::RUNNING:
|
||||||
tick(0, tag<State::RUNNING>{}, delta);
|
tick(0, tag<State::RUNNING>{}, delta, data);
|
||||||
default:
|
default:
|
||||||
// suppress warnings
|
// suppress warnings
|
||||||
break;
|
break;
|
||||||
@@ -281,12 +283,13 @@ private:
|
|||||||
* following:
|
* following:
|
||||||
*
|
*
|
||||||
* @code{.cpp}
|
* @code{.cpp}
|
||||||
* void(Delta delta, auto succeed, auto fail);
|
* void(Delta delta, void *data, auto succeed, auto fail);
|
||||||
* @endcode
|
* @endcode
|
||||||
*
|
*
|
||||||
* Where:
|
* Where:
|
||||||
*
|
*
|
||||||
* * `delta` is the elapsed time.
|
* * `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.
|
* * `succeed` is a function to call when a process terminates with success.
|
||||||
* * `fail` is a function to call when a process terminates with errors.
|
* * `fail` is a function to call when a process terminates with errors.
|
||||||
*
|
*
|
||||||
@@ -315,16 +318,17 @@ struct ProcessAdaptor: Process<ProcessAdaptor<Func, Delta>, Delta>, private Func
|
|||||||
* @param args Parameters to use to initialize the actual process.
|
* @param args Parameters to use to initialize the actual process.
|
||||||
*/
|
*/
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
ProcessAdaptor(Args&&... args)
|
ProcessAdaptor(Args &&... args)
|
||||||
: Func{std::forward<Args>(args)...}
|
: Func{std::forward<Args>(args)...}
|
||||||
{}
|
{}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Updates a process and its internal state if required.
|
* @brief Updates a process and its internal state if required.
|
||||||
* @param delta Elapsed time.
|
* @param delta Elapsed time.
|
||||||
|
* @param data Optional data.
|
||||||
*/
|
*/
|
||||||
void update(Delta delta) {
|
void update(const Delta delta, void *data) {
|
||||||
Func::operator()(delta, [this](){ this->succeed(); }, [this](){ this->fail(); });
|
Func::operator()(delta, data, [this]() { this->succeed(); }, [this]() { this->fail(); });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <iterator>
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include "../config/config.h"
|
||||||
#include "process.hpp"
|
#include "process.hpp"
|
||||||
|
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ namespace entt {
|
|||||||
* Example of use (pseudocode):
|
* Example of use (pseudocode):
|
||||||
*
|
*
|
||||||
* @code{.cpp}
|
* @code{.cpp}
|
||||||
* scheduler.attach([](auto delta, auto succeed, auto fail) {
|
* scheduler.attach([](auto delta, void *, auto succeed, auto fail) {
|
||||||
* // code
|
* // code
|
||||||
* }).then<MyProcess>(arguments...);
|
* }).then<MyProcess>(arguments...);
|
||||||
* @endcode
|
* @endcode
|
||||||
@@ -42,31 +42,27 @@ namespace entt {
|
|||||||
*/
|
*/
|
||||||
template<typename Delta>
|
template<typename Delta>
|
||||||
class Scheduler final {
|
class Scheduler final {
|
||||||
template<typename T>
|
|
||||||
struct tag { using type = T; };
|
|
||||||
|
|
||||||
struct ProcessHandler final {
|
struct ProcessHandler final {
|
||||||
using instance_type = std::unique_ptr<void, void(*)(void *)>;
|
using instance_type = std::unique_ptr<void, void(*)(void *)>;
|
||||||
using update_type = bool(*)(ProcessHandler &, Delta);
|
using update_fn_type = bool(ProcessHandler &, Delta, void *);
|
||||||
using abort_type = void(*)(ProcessHandler &, bool);
|
using abort_fn_type = void(ProcessHandler &, bool);
|
||||||
using next_type = std::unique_ptr<ProcessHandler>;
|
using next_type = std::unique_ptr<ProcessHandler>;
|
||||||
|
|
||||||
instance_type instance;
|
instance_type instance;
|
||||||
update_type update;
|
update_fn_type *update;
|
||||||
abort_type abort;
|
abort_fn_type *abort;
|
||||||
next_type next;
|
next_type next;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename Lambda>
|
struct Then final {
|
||||||
struct Then final: Lambda {
|
Then(ProcessHandler *handler)
|
||||||
Then(Lambda &&lambda, ProcessHandler *handler)
|
: handler{handler}
|
||||||
: Lambda{std::forward<Lambda>(lambda)}, handler{handler}
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
template<typename Proc, typename... Args>
|
template<typename Proc, typename... Args>
|
||||||
decltype(auto) then(Args&&... args) && {
|
decltype(auto) then(Args &&... args) && {
|
||||||
static_assert(std::is_base_of<Process<Proc, Delta>, Proc>::value, "!");
|
static_assert(std::is_base_of<Process<Proc, Delta>, Proc>::value, "!");
|
||||||
handler = Lambda::operator()(handler, tag<Proc>{}, std::forward<Args>(args)...);
|
handler = Scheduler::then<Proc>(handler, std::forward<Args>(args)...);
|
||||||
return std::move(*this);
|
return std::move(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,16 +77,16 @@ class Scheduler final {
|
|||||||
};
|
};
|
||||||
|
|
||||||
template<typename Proc>
|
template<typename Proc>
|
||||||
static bool update(ProcessHandler &handler, Delta delta) {
|
static bool update(ProcessHandler &handler, const Delta delta, void *data) {
|
||||||
auto *process = static_cast<Proc *>(handler.instance.get());
|
auto *process = static_cast<Proc *>(handler.instance.get());
|
||||||
process->tick(delta);
|
process->tick(delta, data);
|
||||||
|
|
||||||
auto dead = process->dead();
|
auto dead = process->dead();
|
||||||
|
|
||||||
if(dead) {
|
if(dead) {
|
||||||
if(handler.next && !process->rejected()) {
|
if(handler.next && !process->rejected()) {
|
||||||
handler = std::move(*handler.next);
|
handler = std::move(*handler.next);
|
||||||
dead = handler.update(handler, delta);
|
dead = handler.update(handler, delta, data);
|
||||||
} else {
|
} else {
|
||||||
handler.instance.reset();
|
handler.instance.reset();
|
||||||
}
|
}
|
||||||
@@ -100,7 +96,7 @@ class Scheduler final {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<typename Proc>
|
template<typename Proc>
|
||||||
static void abort(ProcessHandler &handler, bool immediately) {
|
static void abort(ProcessHandler &handler, const bool immediately) {
|
||||||
static_cast<Proc *>(handler.instance.get())->abort(immediately);
|
static_cast<Proc *>(handler.instance.get())->abort(immediately);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,20 +105,15 @@ class Scheduler final {
|
|||||||
delete static_cast<Proc *>(proc);
|
delete static_cast<Proc *>(proc);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto then(ProcessHandler *handler) {
|
template<typename Proc, typename... Args>
|
||||||
auto lambda = [this](ProcessHandler *handler, auto next, auto... args) {
|
static auto then(ProcessHandler *handler, Args &&... args) {
|
||||||
using Proc = typename decltype(next)::type;
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
if(handler) {
|
return handler;
|
||||||
auto proc = typename ProcessHandler::instance_type{ new Proc{std::forward<decltype(args)>(args)...}, &Scheduler::deleter<Proc> };
|
|
||||||
handler->next.reset(new ProcessHandler{std::move(proc), &Scheduler::update<Proc>, &Scheduler::abort<Proc>, nullptr});
|
|
||||||
handler = handler->next.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
return handler;
|
|
||||||
};
|
|
||||||
|
|
||||||
return Then<decltype(lambda)>{std::move(lambda), handler};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -130,7 +121,7 @@ public:
|
|||||||
using size_type = typename std::vector<ProcessHandler>::size_type;
|
using size_type = typename std::vector<ProcessHandler>::size_type;
|
||||||
|
|
||||||
/*! @brief Default constructor. */
|
/*! @brief Default constructor. */
|
||||||
Scheduler() noexcept= default;
|
Scheduler() ENTT_NOEXCEPT = default;
|
||||||
|
|
||||||
/*! @brief Copying a scheduler isn't allowed. */
|
/*! @brief Copying a scheduler isn't allowed. */
|
||||||
Scheduler(const Scheduler &) = delete;
|
Scheduler(const Scheduler &) = delete;
|
||||||
@@ -139,14 +130,14 @@ public:
|
|||||||
|
|
||||||
/*! @brief Copying a scheduler isn't allowed. @return This scheduler. */
|
/*! @brief Copying a scheduler isn't allowed. @return This scheduler. */
|
||||||
Scheduler & operator=(const Scheduler &) = delete;
|
Scheduler & operator=(const Scheduler &) = delete;
|
||||||
/*! @brief Default move assignament operator. @return This scheduler. */
|
/*! @brief Default move assignment operator. @return This scheduler. */
|
||||||
Scheduler & operator=(Scheduler &&) = default;
|
Scheduler & operator=(Scheduler &&) = default;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Number of processes currently scheduled.
|
* @brief Number of processes currently scheduled.
|
||||||
* @return Number of processes currently scheduled.
|
* @return Number of processes currently scheduled.
|
||||||
*/
|
*/
|
||||||
size_type size() const noexcept {
|
size_type size() const ENTT_NOEXCEPT {
|
||||||
return handlers.size();
|
return handlers.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +145,7 @@ public:
|
|||||||
* @brief Returns true if at least a process is currently scheduled.
|
* @brief Returns true if at least a process is currently scheduled.
|
||||||
* @return True if there are scheduled processes, false otherwise.
|
* @return True if there are scheduled processes, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool empty() const noexcept {
|
bool empty() const ENTT_NOEXCEPT {
|
||||||
return handlers.empty();
|
return handlers.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,7 +172,7 @@ public:
|
|||||||
* // schedules a task in the form of a process class
|
* // schedules a task in the form of a process class
|
||||||
* scheduler.attach<MyProcess>(arguments...)
|
* scheduler.attach<MyProcess>(arguments...)
|
||||||
* // appends a child in the form of a lambda function
|
* // appends a child in the form of a lambda function
|
||||||
* .then([](auto delta, auto succeed, auto fail) {
|
* .then([](auto delta, void *, auto succeed, auto fail) {
|
||||||
* // code
|
* // code
|
||||||
* })
|
* })
|
||||||
* // appends a child in the form of another process class
|
* // appends a child in the form of another process class
|
||||||
@@ -194,14 +185,14 @@ public:
|
|||||||
* @return An opaque object to use to concatenate processes.
|
* @return An opaque object to use to concatenate processes.
|
||||||
*/
|
*/
|
||||||
template<typename Proc, typename... Args>
|
template<typename Proc, typename... Args>
|
||||||
auto attach(Args&&... args) {
|
auto attach(Args &&... args) {
|
||||||
static_assert(std::is_base_of<Process<Proc, Delta>, Proc>::value, "!");
|
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> };
|
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};
|
ProcessHandler handler{std::move(proc), &Scheduler::update<Proc>, &Scheduler::abort<Proc>, nullptr};
|
||||||
handlers.push_back(std::move(handler));
|
handlers.push_back(std::move(handler));
|
||||||
|
|
||||||
return then(&handlers.back());
|
return Then{&handlers.back()};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -237,11 +228,11 @@ public:
|
|||||||
*
|
*
|
||||||
* @code{.cpp}
|
* @code{.cpp}
|
||||||
* // schedules a task in the form of a lambda function
|
* // schedules a task in the form of a lambda function
|
||||||
* scheduler.attach([](auto delta, auto succeed, auto fail) {
|
* scheduler.attach([](auto delta, void *, auto succeed, auto fail) {
|
||||||
* // code
|
* // code
|
||||||
* })
|
* })
|
||||||
* // appends a child in the form of another lambda function
|
* // appends a child in the form of another lambda function
|
||||||
* .then([](auto delta, auto succeed, auto fail) {
|
* .then([](auto delta, void *, auto succeed, auto fail) {
|
||||||
* // code
|
* // code
|
||||||
* })
|
* })
|
||||||
* // appends a child in the form of a process class
|
* // appends a child in the form of a process class
|
||||||
@@ -269,18 +260,19 @@ public:
|
|||||||
* with its child.
|
* with its child.
|
||||||
*
|
*
|
||||||
* @param delta Elapsed time.
|
* @param delta Elapsed time.
|
||||||
|
* @param data Optional data.
|
||||||
*/
|
*/
|
||||||
void update(Delta delta) {
|
void update(const Delta delta, void *data = nullptr) {
|
||||||
bool clean = false;
|
bool clean = false;
|
||||||
|
|
||||||
for(auto i = handlers.size(); i > 0; --i) {
|
for(auto pos = handlers.size(); pos; --pos) {
|
||||||
auto &handler = handlers[i-1];
|
auto &handler = handlers[pos-1];
|
||||||
const bool dead = handler.update(handler, delta);
|
const bool dead = handler.update(handler, delta, data);
|
||||||
clean = clean || dead;
|
clean = clean || dead;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(clean) {
|
if(clean) {
|
||||||
handlers.erase(std::remove_if(handlers.begin(), handlers.end(), [delta](auto &handler) {
|
handlers.erase(std::remove_if(handlers.begin(), handlers.end(), [](auto &handler) {
|
||||||
return !handler.instance;
|
return !handler.instance;
|
||||||
}), handlers.end());
|
}), handlers.end());
|
||||||
}
|
}
|
||||||
@@ -296,7 +288,7 @@ public:
|
|||||||
*
|
*
|
||||||
* @param immediately Requests an immediate operation.
|
* @param immediately Requests an immediate operation.
|
||||||
*/
|
*/
|
||||||
void abort(bool immediately = false) {
|
void abort(const bool immediately = false) {
|
||||||
decltype(handlers) exec;
|
decltype(handlers) exec;
|
||||||
exec.swap(handlers);
|
exec.swap(handlers);
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include "../config/config.h"
|
||||||
#include "../core/hashed_string.hpp"
|
#include "../core/hashed_string.hpp"
|
||||||
#include "handle.hpp"
|
#include "handle.hpp"
|
||||||
#include "loader.hpp"
|
#include "loader.hpp"
|
||||||
@@ -38,20 +39,20 @@ public:
|
|||||||
ResourceCache() = default;
|
ResourceCache() = default;
|
||||||
|
|
||||||
/*! @brief Copying a cache isn't allowed. */
|
/*! @brief Copying a cache isn't allowed. */
|
||||||
ResourceCache(const ResourceCache &) noexcept = delete;
|
ResourceCache(const ResourceCache &) ENTT_NOEXCEPT = delete;
|
||||||
/*! @brief Default move constructor. */
|
/*! @brief Default move constructor. */
|
||||||
ResourceCache(ResourceCache &&) noexcept = default;
|
ResourceCache(ResourceCache &&) ENTT_NOEXCEPT = default;
|
||||||
|
|
||||||
/*! @brief Copying a cache isn't allowed. @return This cache. */
|
/*! @brief Copying a cache isn't allowed. @return This cache. */
|
||||||
ResourceCache & operator=(const ResourceCache &) noexcept = delete;
|
ResourceCache & operator=(const ResourceCache &) ENTT_NOEXCEPT = delete;
|
||||||
/*! @brief Default move assignment operator. @return This cache. */
|
/*! @brief Default move assignment operator. @return This cache. */
|
||||||
ResourceCache & operator=(ResourceCache &&) noexcept = default;
|
ResourceCache & operator=(ResourceCache &&) ENTT_NOEXCEPT = default;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Number of resources managed by a cache.
|
* @brief Number of resources managed by a cache.
|
||||||
* @return Number of resources currently stored.
|
* @return Number of resources currently stored.
|
||||||
*/
|
*/
|
||||||
size_type size() const noexcept {
|
size_type size() const ENTT_NOEXCEPT {
|
||||||
return resources.size();
|
return resources.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +60,7 @@ public:
|
|||||||
* @brief Returns true if a cache contains no resources, false otherwise.
|
* @brief Returns true if a cache contains no resources, false otherwise.
|
||||||
* @return True if the cache contains no resources, false otherwise.
|
* @return True if the cache contains no resources, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool empty() const noexcept {
|
bool empty() const ENTT_NOEXCEPT {
|
||||||
return resources.empty();
|
return resources.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,12 +70,12 @@ public:
|
|||||||
* Handles are not invalidated and the memory used by a resource isn't
|
* Handles are not invalidated and the memory used by a resource isn't
|
||||||
* freed as long as at least a handle keeps the resource itself alive.
|
* freed as long as at least a handle keeps the resource itself alive.
|
||||||
*/
|
*/
|
||||||
void clear() noexcept {
|
void clear() ENTT_NOEXCEPT {
|
||||||
resources.clear();
|
resources.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Loads the resource that corresponds to the given identifier.
|
* @brief Loads the resource that corresponds to a given identifier.
|
||||||
*
|
*
|
||||||
* In case an identifier isn't already present in the cache, it loads its
|
* In case an identifier isn't already present in the cache, it loads its
|
||||||
* resource and stores it aside for future uses. Arguments are forwarded
|
* resource and stores it aside for future uses. Arguments are forwarded
|
||||||
@@ -92,7 +93,7 @@ public:
|
|||||||
* @return True if the resource is ready to use, false otherwise.
|
* @return True if the resource is ready to use, false otherwise.
|
||||||
*/
|
*/
|
||||||
template<typename Loader, typename... Args>
|
template<typename Loader, typename... Args>
|
||||||
bool load(resource_type id, Args&&... args) {
|
bool load(const resource_type id, Args &&... args) {
|
||||||
static_assert(std::is_base_of<ResourceLoader<Loader, Resource>, Loader>::value, "!");
|
static_assert(std::is_base_of<ResourceLoader<Loader, Resource>, Loader>::value, "!");
|
||||||
|
|
||||||
bool loaded = true;
|
bool loaded = true;
|
||||||
@@ -125,12 +126,29 @@ public:
|
|||||||
* @return True if the resource is ready to use, false otherwise.
|
* @return True if the resource is ready to use, false otherwise.
|
||||||
*/
|
*/
|
||||||
template<typename Loader, typename... Args>
|
template<typename Loader, typename... Args>
|
||||||
void reload(resource_type id, Args&&... args) {
|
bool reload(const resource_type id, Args &&... args) {
|
||||||
return (discard(id), load(id, std::forward<Args>(args)...));
|
return (discard(id), load<Loader>(id, std::forward<Args>(args)...));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Creates a handle for the given resource identifier.
|
* @brief Creates a temporary handle for a resource.
|
||||||
|
*
|
||||||
|
* Arguments are forwarded directly to the loader in order to construct
|
||||||
|
* properly the requested resource. The handle isn't stored aside and the
|
||||||
|
* cache isn't in charge of the lifetime of the resource itself.
|
||||||
|
*
|
||||||
|
* @tparam Loader Type of loader to use to load the resource.
|
||||||
|
* @tparam Args Types of arguments to use to load the resource.
|
||||||
|
* @param args Arguments to use to load the resource.
|
||||||
|
* @return A handle for the given resource.
|
||||||
|
*/
|
||||||
|
template<typename Loader, typename... Args>
|
||||||
|
ResourceHandle<Resource> temp(Args &&... args) const {
|
||||||
|
return { Loader{}.get(std::forward<Args>(args)...) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Creates a handle for a given resource identifier.
|
||||||
*
|
*
|
||||||
* A resource handle can be in a either valid or invalid state. In other
|
* A resource handle can be in a either valid or invalid state. In other
|
||||||
* terms, a resource handle is properly initialized with a resource if the
|
* terms, a resource handle is properly initialized with a resource if the
|
||||||
@@ -142,29 +160,29 @@ public:
|
|||||||
* @param id Unique resource identifier.
|
* @param id Unique resource identifier.
|
||||||
* @return A handle for the given resource.
|
* @return A handle for the given resource.
|
||||||
*/
|
*/
|
||||||
ResourceHandle<Resource> handle(resource_type id) const {
|
ResourceHandle<Resource> handle(const resource_type id) const {
|
||||||
auto it = resources.find(id);
|
auto it = resources.find(id);
|
||||||
return { it == resources.end() ? nullptr : it->second };
|
return { it == resources.end() ? nullptr : it->second };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Checks if a cache contains the given identifier.
|
* @brief Checks if a cache contains a given identifier.
|
||||||
* @param id Unique resource identifier.
|
* @param id Unique resource identifier.
|
||||||
* @return True if the cache contains the resource, false otherwise.
|
* @return True if the cache contains the resource, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool contains(resource_type id) const noexcept {
|
bool contains(const resource_type id) const ENTT_NOEXCEPT {
|
||||||
return !(resources.find(id) == resources.cend());
|
return (resources.find(id) != resources.cend());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Discards the resource that corresponds to the given identifier.
|
* @brief Discards the resource that corresponds to a given identifier.
|
||||||
*
|
*
|
||||||
* Handles are not invalidated and the memory used by the resource isn't
|
* Handles are not invalidated and the memory used by the resource isn't
|
||||||
* freed as long as at least a handle keeps the resource itself alive.
|
* freed as long as at least a handle keeps the resource itself alive.
|
||||||
*
|
*
|
||||||
* @param id Unique resource identifier.
|
* @param id Unique resource identifier.
|
||||||
*/
|
*/
|
||||||
void discard(resource_type id) noexcept {
|
void discard(const resource_type id) ENTT_NOEXCEPT {
|
||||||
auto it = resources.find(id);
|
auto it = resources.find(id);
|
||||||
|
|
||||||
if(it != resources.end()) {
|
if(it != resources.end()) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include "../config/config.h"
|
||||||
|
|
||||||
|
|
||||||
namespace entt {
|
namespace entt {
|
||||||
@@ -31,20 +32,20 @@ class ResourceHandle final {
|
|||||||
/*! @brief Resource handles are friends of their caches. */
|
/*! @brief Resource handles are friends of their caches. */
|
||||||
friend class ResourceCache<Resource>;
|
friend class ResourceCache<Resource>;
|
||||||
|
|
||||||
ResourceHandle(std::shared_ptr<Resource> res) noexcept
|
ResourceHandle(std::shared_ptr<Resource> res) ENTT_NOEXCEPT
|
||||||
: resource{std::move(res)}
|
: resource{std::move(res)}
|
||||||
{}
|
{}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/*! @brief Default copy constructor. */
|
/*! @brief Default copy constructor. */
|
||||||
ResourceHandle(const ResourceHandle &) noexcept = default;
|
ResourceHandle(const ResourceHandle &) ENTT_NOEXCEPT = default;
|
||||||
/*! @brief Default move constructor. */
|
/*! @brief Default move constructor. */
|
||||||
ResourceHandle(ResourceHandle &&) noexcept = default;
|
ResourceHandle(ResourceHandle &&) ENTT_NOEXCEPT = default;
|
||||||
|
|
||||||
/*! @brief Default copy assignment operator. @return This handle. */
|
/*! @brief Default copy assignment operator. @return This handle. */
|
||||||
ResourceHandle & operator=(const ResourceHandle &) noexcept = default;
|
ResourceHandle & operator=(const ResourceHandle &) ENTT_NOEXCEPT = default;
|
||||||
/*! @brief Default move assignment operator. @return This handle. */
|
/*! @brief Default move assignment operator. @return This handle. */
|
||||||
ResourceHandle & operator=(ResourceHandle &&) noexcept = default;
|
ResourceHandle & operator=(ResourceHandle &&) ENTT_NOEXCEPT = default;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Gets a reference to the managed resource.
|
* @brief Gets a reference to the managed resource.
|
||||||
@@ -56,7 +57,7 @@ public:
|
|||||||
*
|
*
|
||||||
* @return A reference to the managed resource.
|
* @return A reference to the managed resource.
|
||||||
*/
|
*/
|
||||||
const Resource & get() const noexcept {
|
const Resource & get() const ENTT_NOEXCEPT {
|
||||||
assert(static_cast<bool>(resource));
|
assert(static_cast<bool>(resource));
|
||||||
return *resource;
|
return *resource;
|
||||||
}
|
}
|
||||||
@@ -69,7 +70,7 @@ public:
|
|||||||
* An assertion will abort the execution at runtime in debug mode if the
|
* An assertion will abort the execution at runtime in debug mode if the
|
||||||
* handle is empty.
|
* handle is empty.
|
||||||
*/
|
*/
|
||||||
inline operator const Resource & () const noexcept { return get(); }
|
inline operator const Resource &() const ENTT_NOEXCEPT { return get(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Dereferences a handle to obtain the managed resource.
|
* @brief Dereferences a handle to obtain the managed resource.
|
||||||
@@ -81,10 +82,10 @@ public:
|
|||||||
*
|
*
|
||||||
* @return A reference to the managed resource.
|
* @return A reference to the managed resource.
|
||||||
*/
|
*/
|
||||||
inline const Resource & operator *() const noexcept { return get(); }
|
inline const Resource & operator *() const ENTT_NOEXCEPT { return get(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Gets a pointer to the managed resource from a handle .
|
* @brief Gets a pointer to the managed resource from a handle.
|
||||||
*
|
*
|
||||||
* @warning
|
* @warning
|
||||||
* The behavior is undefined if the handle doesn't contain a resource.<br/>
|
* The behavior is undefined if the handle doesn't contain a resource.<br/>
|
||||||
@@ -94,7 +95,7 @@ public:
|
|||||||
* @return A pointer to the managed resource or `nullptr` if the handle
|
* @return A pointer to the managed resource or `nullptr` if the handle
|
||||||
* contains no resource at all.
|
* contains no resource at all.
|
||||||
*/
|
*/
|
||||||
inline const Resource * operator ->() const noexcept {
|
inline const Resource * operator ->() const ENTT_NOEXCEPT {
|
||||||
assert(static_cast<bool>(resource));
|
assert(static_cast<bool>(resource));
|
||||||
return resource.get();
|
return resource.get();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class ResourceCache;
|
|||||||
*
|
*
|
||||||
* Resource loaders must inherit from this class and stay true to the CRTP
|
* Resource loaders must inherit from this class and stay true to the CRTP
|
||||||
* idiom. Moreover, a resource loader must expose a public, const member
|
* idiom. Moreover, a resource loader must expose a public, const member
|
||||||
* function named `load` that accepts a variable number of arguments and return
|
* function named `load` that accepts a variable number of arguments and returns
|
||||||
* a shared pointer to the resource just created.<br/>
|
* a shared pointer to the resource just created.<br/>
|
||||||
* As an example:
|
* As an example:
|
||||||
*
|
*
|
||||||
@@ -50,7 +50,7 @@ class ResourceLoader {
|
|||||||
friend class ResourceCache<Resource>;
|
friend class ResourceCache<Resource>;
|
||||||
|
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
std::shared_ptr<Resource> get(Args&&... args) const {
|
std::shared_ptr<Resource> get(Args &&... args) const {
|
||||||
return static_cast<const Loader *>(this)->load(std::forward<Args>(args)...);
|
return static_cast<const Loader *>(this)->load(std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,305 +0,0 @@
|
|||||||
#ifndef ENTT_SIGNAL_BUS_HPP
|
|
||||||
#define ENTT_SIGNAL_BUS_HPP
|
|
||||||
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <utility>
|
|
||||||
#include "signal.hpp"
|
|
||||||
#include "sigh.hpp"
|
|
||||||
|
|
||||||
|
|
||||||
namespace entt {
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Minimal event bus.
|
|
||||||
*
|
|
||||||
* Primary template isn't defined on purpose. The main reason for which it
|
|
||||||
* exists is to work around the doxygen's parsing capabilities. In fact, there
|
|
||||||
* is no need to declare it actually.
|
|
||||||
*/
|
|
||||||
template<template<typename...> class, typename...>
|
|
||||||
class Bus;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Event bus specialization for multiple types.
|
|
||||||
*
|
|
||||||
* The event bus is designed to allow an easy registration of specific member
|
|
||||||
* functions to a bunch of signal handlers (either manager or unmanaged).
|
|
||||||
* Classes must publicly expose the required member functions to allow the bus
|
|
||||||
* to detect them for the purpose of registering and unregistering
|
|
||||||
* instances.<br/>
|
|
||||||
* In particular, for each event type `E`, a matching member function has the
|
|
||||||
* following signature: `void receive(const E &)`. Events will be properly
|
|
||||||
* redirected to all the listeners by calling the right member functions, if
|
|
||||||
* any.
|
|
||||||
*
|
|
||||||
* @tparam Sig Type of signal handler to use.
|
|
||||||
* @tparam Event The list of events managed by the bus.
|
|
||||||
*/
|
|
||||||
template<template<typename...> class Sig, typename Event, typename... Other>
|
|
||||||
class Bus<Sig, Event, Other...>
|
|
||||||
: private Bus<Sig, Event>, private Bus<Sig, Other>...
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
/*! @brief Unsigned integer type. */
|
|
||||||
using size_type = std::size_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Unregisters all the member functions of an instance.
|
|
||||||
*
|
|
||||||
* A bus is used to convey a certain set of events. This method detects
|
|
||||||
* and unregisters from the bus all the matching member functions of an
|
|
||||||
* instance.<br/>
|
|
||||||
* For each event type `E`, a matching member function has the following
|
|
||||||
* signature: `void receive(const E &)`.
|
|
||||||
*
|
|
||||||
* @tparam Instance Type of instance to unregister.
|
|
||||||
* @param instance A valid instance of the right type.
|
|
||||||
*/
|
|
||||||
template<typename Instance>
|
|
||||||
void unreg(Instance instance) {
|
|
||||||
using accumulator_type = int[];
|
|
||||||
accumulator_type accumulator = {
|
|
||||||
(Bus<Sig, Event>::unreg(instance), 0),
|
|
||||||
(Bus<Sig, Other>::unreg(instance), 0)...
|
|
||||||
};
|
|
||||||
return void(accumulator);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Registers all the member functions of an instance.
|
|
||||||
*
|
|
||||||
* A bus is used to convey a certain set of events. This method detects
|
|
||||||
* and registers to the bus all the matching member functions of an
|
|
||||||
* instance.<br/>
|
|
||||||
* For each event type `E`, a matching member function has the following
|
|
||||||
* signature: `void receive(const E &)`.
|
|
||||||
*
|
|
||||||
* @tparam Instance Type of instance to register.
|
|
||||||
* @param instance A valid instance of the right type.
|
|
||||||
*/
|
|
||||||
template<typename Instance>
|
|
||||||
void reg(Instance instance) {
|
|
||||||
using accumulator_type = int[];
|
|
||||||
accumulator_type accumulator = {
|
|
||||||
(Bus<Sig, Event>::reg(instance), 0),
|
|
||||||
(Bus<Sig, Other>::reg(instance), 0)...
|
|
||||||
};
|
|
||||||
return void(accumulator);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Number of listeners connected to the bus.
|
|
||||||
* @return Number of listeners currently connected.
|
|
||||||
*/
|
|
||||||
size_type size() const noexcept {
|
|
||||||
using accumulator_type = std::size_t[];
|
|
||||||
std::size_t sz = Bus<Sig, Event>::size();
|
|
||||||
accumulator_type accumulator = { sz, (sz += Bus<Sig, Other>::size())... };
|
|
||||||
return void(accumulator), sz;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns false if at least a listener is connected to the bus.
|
|
||||||
* @return True if the bus has no listeners connected, false otherwise.
|
|
||||||
*/
|
|
||||||
bool empty() const noexcept {
|
|
||||||
using accumulator_type = bool[];
|
|
||||||
bool ret = Bus<Sig, Event>::empty();
|
|
||||||
accumulator_type accumulator = { ret, (ret = ret && Bus<Sig, Other>::empty())... };
|
|
||||||
return void(accumulator), ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Connects a free function to the bus.
|
|
||||||
* @tparam Type Type of event to which to connect the function.
|
|
||||||
* @tparam Function A valid free function pointer.
|
|
||||||
*/
|
|
||||||
template<typename Type, void(*Function)(const Type &)>
|
|
||||||
void connect() {
|
|
||||||
Bus<Sig, Type>::template connect<Function>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Disconnects a free function from the bus.
|
|
||||||
* @tparam Type Type of event from which to disconnect the function.
|
|
||||||
* @tparam Function A valid free function pointer.
|
|
||||||
*/
|
|
||||||
template<typename Type, void(*Function)(const Type &)>
|
|
||||||
void disconnect() {
|
|
||||||
Bus<Sig, Type>::template disconnect<Function>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Publishes an event.
|
|
||||||
*
|
|
||||||
* All the listeners are notified. Order isn't guaranteed.
|
|
||||||
*
|
|
||||||
* @tparam Type Type of event to publish.
|
|
||||||
* @tparam Args Types of arguments to use to construct the event.
|
|
||||||
* @param args Arguments to use to construct the event.
|
|
||||||
*/
|
|
||||||
template<typename Type, typename... Args>
|
|
||||||
void publish(Args&&... args) {
|
|
||||||
Bus<Sig, Type>::publish(std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Event bus specialization for a single type.
|
|
||||||
*
|
|
||||||
* The event bus is designed to allow an easy registration of a specific member
|
|
||||||
* function to a signal handler (either manager or unmanaged).
|
|
||||||
* Classes must publicly expose the required member function to allow the bus to
|
|
||||||
* detect it for the purpose of registering and unregistering instances.<br/>
|
|
||||||
* In particular, a matching member function has the following signature:
|
|
||||||
* `void receive(const Event &)`. Events of the given type will be properly
|
|
||||||
* redirected to all the listeners by calling the right member function, if any.
|
|
||||||
*
|
|
||||||
* @tparam Sig Type of signal handler to use.
|
|
||||||
* @tparam Event Type of event managed by the bus.
|
|
||||||
*/
|
|
||||||
template<template<typename...> class Sig, typename Event>
|
|
||||||
class Bus<Sig, Event> {
|
|
||||||
using signal_type = Sig<void(const Event &)>;
|
|
||||||
|
|
||||||
template<typename Class>
|
|
||||||
using instance_type = typename signal_type::template instance_type<Class>;
|
|
||||||
|
|
||||||
template<typename Class>
|
|
||||||
auto disconnect(int, instance_type<Class> instance)
|
|
||||||
-> decltype(std::declval<Class>().receive(std::declval<Event>()), void()) {
|
|
||||||
signal.template disconnect<Class, &Class::receive>(std::move(instance));
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Class>
|
|
||||||
auto connect(int, instance_type<Class> instance)
|
|
||||||
-> decltype(std::declval<Class>().receive(std::declval<Event>()), void()) {
|
|
||||||
signal.template connect<Class, &Class::receive>(std::move(instance));
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Class> void disconnect(char, instance_type<Class>) {}
|
|
||||||
template<typename Class> void connect(char, instance_type<Class>) {}
|
|
||||||
|
|
||||||
public:
|
|
||||||
/*! @brief Unsigned integer type. */
|
|
||||||
using size_type = typename signal_type::size_type;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Unregisters member functions of instances.
|
|
||||||
*
|
|
||||||
* This method tries to detect and unregister from the bus matching member
|
|
||||||
* functions of instances.<br/>
|
|
||||||
* A matching member function has the following signature:
|
|
||||||
* `void receive(const Event &)`.
|
|
||||||
*
|
|
||||||
* @tparam Class Type of instance to unregister.
|
|
||||||
* @param instance A valid instance of the right type.
|
|
||||||
*/
|
|
||||||
template<typename Class>
|
|
||||||
void unreg(instance_type<Class> instance) {
|
|
||||||
disconnect(0, std::move(instance));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Tries to register an instance.
|
|
||||||
*
|
|
||||||
* This method tries to detect and register to the bus matching member
|
|
||||||
* functions of instances.<br/>
|
|
||||||
* A matching member function has the following signature:
|
|
||||||
* `void receive(const Event &)`.
|
|
||||||
*
|
|
||||||
* @tparam Class Type of instance to register.
|
|
||||||
* @param instance A valid instance of the right type.
|
|
||||||
*/
|
|
||||||
template<typename Class>
|
|
||||||
void reg(instance_type<Class> instance) {
|
|
||||||
connect(0, std::move(instance));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Number of listeners connected to the bus.
|
|
||||||
* @return Number of listeners currently connected.
|
|
||||||
*/
|
|
||||||
size_type size() const noexcept {
|
|
||||||
return signal.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns false if at least a listener is connected to the bus.
|
|
||||||
* @return True if the bus has no listeners connected, false otherwise.
|
|
||||||
*/
|
|
||||||
bool empty() const noexcept {
|
|
||||||
return signal.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Connects a free function to the bus.
|
|
||||||
* @tparam Function A valid free function pointer.
|
|
||||||
*/
|
|
||||||
template<void(*Function)(const Event &)>
|
|
||||||
void connect() {
|
|
||||||
signal.template connect<Function>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Disconnects a free function from the bus.
|
|
||||||
* @tparam Function A valid free function pointer.
|
|
||||||
*/
|
|
||||||
template<void(*Function)(const Event &)>
|
|
||||||
void disconnect() {
|
|
||||||
signal.template disconnect<Function>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Publishes an event.
|
|
||||||
*
|
|
||||||
* All the listeners are notified. Order isn't guaranteed.
|
|
||||||
*
|
|
||||||
* @tparam Args Types of arguments to use to construct the event.
|
|
||||||
* @param args Arguments to use to construct the event.
|
|
||||||
*/
|
|
||||||
template<typename... Args>
|
|
||||||
void publish(Args&&... args) {
|
|
||||||
signal.publish({ std::forward<Args>(args)... });
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
signal_type signal;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Managed event bus.
|
|
||||||
*
|
|
||||||
* A managed event bus uses the Signal class template as an underlying type. The
|
|
||||||
* type of the instances is the one required by the signal handler:
|
|
||||||
* `std::shared_ptr<Class>` (a shared pointer).
|
|
||||||
*
|
|
||||||
* @tparam Event The list of events managed by the bus.
|
|
||||||
*/
|
|
||||||
template<typename... Event>
|
|
||||||
using ManagedBus = Bus<Signal, Event...>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Unmanaged event bus.
|
|
||||||
*
|
|
||||||
* An unmanaged event bus uses the SigH class template as an underlying type.
|
|
||||||
* The type of the instances is the one required by the signal handler:
|
|
||||||
* `Class *` (a naked pointer).<br/>
|
|
||||||
* When it comes to work with this kind of bus, users must guarantee that the
|
|
||||||
* lifetimes of the instances overcome the one of the bus itself.
|
|
||||||
*
|
|
||||||
* @tparam Event The list of events managed by the bus.
|
|
||||||
*/
|
|
||||||
template<typename... Event>
|
|
||||||
using UnmanagedBus = Bus<SigH, Event...>;
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#endif // ENTT_SIGNAL_BUS_HPP
|
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include "../config/config.h"
|
||||||
|
|
||||||
|
|
||||||
namespace entt {
|
namespace entt {
|
||||||
@@ -33,33 +34,45 @@ class Delegate;
|
|||||||
*/
|
*/
|
||||||
template<typename Ret, typename... Args>
|
template<typename Ret, typename... Args>
|
||||||
class Delegate<Ret(Args...)> final {
|
class Delegate<Ret(Args...)> final {
|
||||||
using proto_type = Ret(*)(void *, Args...);
|
using proto_fn_type = Ret(void *, Args...);
|
||||||
using stub_type = std::pair<void *, proto_type>;
|
using stub_type = std::pair<void *, proto_fn_type *>;
|
||||||
|
|
||||||
static Ret fallback(void *, Args...) noexcept { return {}; }
|
|
||||||
|
|
||||||
template<Ret(*Function)(Args...)>
|
template<Ret(*Function)(Args...)>
|
||||||
static Ret proto(void *, Args... args) {
|
static Ret proto(void *, Args... args) {
|
||||||
return (Function)(args...);
|
return (Function)(args...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Class, Ret(Class::*Member)(Args...)>
|
template<typename Class, Ret(Class:: *Member)(Args...) const>
|
||||||
|
static Ret proto(void *instance, Args... args) {
|
||||||
|
return (static_cast<const Class *>(instance)->*Member)(args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Class, Ret(Class:: *Member)(Args...)>
|
||||||
static Ret proto(void *instance, Args... args) {
|
static Ret proto(void *instance, Args... args) {
|
||||||
return (static_cast<Class *>(instance)->*Member)(args...);
|
return (static_cast<Class *>(instance)->*Member)(args...);
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/*! @brief Default constructor. */
|
/*! @brief Default constructor. */
|
||||||
Delegate() noexcept
|
Delegate() ENTT_NOEXCEPT
|
||||||
: stub{std::make_pair(nullptr, &fallback)}
|
: stub{}
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks whether a delegate actually stores a listener.
|
||||||
|
* @return True if the delegate is empty, false otherwise.
|
||||||
|
*/
|
||||||
|
bool empty() const ENTT_NOEXCEPT {
|
||||||
|
// no need to test also stub.first
|
||||||
|
return !stub.second;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Binds a free function to a delegate.
|
* @brief Binds a free function to a delegate.
|
||||||
* @tparam Function A valid free function pointer.
|
* @tparam Function A valid free function pointer.
|
||||||
*/
|
*/
|
||||||
template<Ret(*Function)(Args...)>
|
template<Ret(*Function)(Args...)>
|
||||||
void connect() noexcept {
|
void connect() ENTT_NOEXCEPT {
|
||||||
stub = std::make_pair(nullptr, &proto<Function>);
|
stub = std::make_pair(nullptr, &proto<Function>);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,8 +87,24 @@ public:
|
|||||||
* @tparam Member Member function to connect to the delegate.
|
* @tparam Member Member function to connect to the delegate.
|
||||||
* @param instance A valid instance of type pointer to `Class`.
|
* @param instance A valid instance of type pointer to `Class`.
|
||||||
*/
|
*/
|
||||||
template<typename Class, Ret(Class::*Member)(Args...)>
|
template<typename Class, Ret(Class:: *Member)(Args...) const>
|
||||||
void connect(Class *instance) noexcept {
|
void connect(Class *instance) ENTT_NOEXCEPT {
|
||||||
|
stub = std::make_pair(instance, &proto<Class, Member>);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Connects a member function for a given instance to a delegate.
|
||||||
|
*
|
||||||
|
* The delegate isn't responsible for the connected object. Users must
|
||||||
|
* guarantee that the lifetime of the instance overcomes the one of the
|
||||||
|
* delegate.
|
||||||
|
*
|
||||||
|
* @tparam Class Type of class to which the member function belongs.
|
||||||
|
* @tparam Member Member function to connect to the delegate.
|
||||||
|
* @param instance A valid instance of type pointer to `Class`.
|
||||||
|
*/
|
||||||
|
template<typename Class, Ret(Class:: *Member)(Args...)>
|
||||||
|
void connect(Class *instance) ENTT_NOEXCEPT {
|
||||||
stub = std::make_pair(instance, &proto<Class, Member>);
|
stub = std::make_pair(instance, &proto<Class, Member>);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,8 +113,8 @@ public:
|
|||||||
*
|
*
|
||||||
* After a reset, a delegate can be safely invoked with no effect.
|
* After a reset, a delegate can be safely invoked with no effect.
|
||||||
*/
|
*/
|
||||||
void reset() noexcept {
|
void reset() ENTT_NOEXCEPT {
|
||||||
stub = std::make_pair(nullptr, &fallback);
|
stub.second = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -93,7 +122,7 @@ public:
|
|||||||
* @param args Arguments to use to invoke the underlying function.
|
* @param args Arguments to use to invoke the underlying function.
|
||||||
* @return The value returned by the underlying function.
|
* @return The value returned by the underlying function.
|
||||||
*/
|
*/
|
||||||
Ret operator()(Args... args) {
|
Ret operator()(Args... args) const {
|
||||||
return stub.second(stub.first, args...);
|
return stub.second(stub.first, args...);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +134,7 @@ public:
|
|||||||
* @param other Delegate with which to compare.
|
* @param other Delegate with which to compare.
|
||||||
* @return True if the two delegates are identical, false otherwise.
|
* @return True if the two delegates are identical, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool operator==(const Delegate<Ret(Args...)> &other) const noexcept {
|
bool operator==(const Delegate<Ret(Args...)> &other) const ENTT_NOEXCEPT {
|
||||||
return stub.first == other.stub.first && stub.second == other.stub.second;
|
return stub.first == other.stub.first && stub.second == other.stub.second;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +155,7 @@ private:
|
|||||||
* @return True if the two delegates are different, false otherwise.
|
* @return True if the two delegates are different, false otherwise.
|
||||||
*/
|
*/
|
||||||
template<typename Ret, typename... Args>
|
template<typename Ret, typename... Args>
|
||||||
bool operator!=(const Delegate<Ret(Args...)> &lhs, const Delegate<Ret(Args...)> &rhs) noexcept {
|
bool operator!=(const Delegate<Ret(Args...)> &lhs, const Delegate<Ret(Args...)> &rhs) ENTT_NOEXCEPT {
|
||||||
return !(lhs == rhs);
|
return !(lhs == rhs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,10 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <type_traits>
|
||||||
|
#include "../config/config.h"
|
||||||
#include "../core/family.hpp"
|
#include "../core/family.hpp"
|
||||||
#include "signal.hpp"
|
|
||||||
#include "sigh.hpp"
|
#include "sigh.hpp"
|
||||||
|
|
||||||
|
|
||||||
@@ -20,66 +22,61 @@ namespace entt {
|
|||||||
* A dispatcher can be used either to trigger an immediate event or to enqueue
|
* A dispatcher can be used either to trigger an immediate event or to enqueue
|
||||||
* events to be published all together once per tick.<br/>
|
* events to be published all together once per tick.<br/>
|
||||||
* Listeners are provided in the form of member functions. For each event of
|
* Listeners are provided in the form of member functions. For each event of
|
||||||
* type `Event`, listeners must have the following signature:
|
* type `Event`, listeners must have the following function type:
|
||||||
* `void(const Event &)`. Member functions named `receive` are automatically
|
* @code{.cpp}
|
||||||
* detected and registered or unregistered by the dispatcher.
|
* void(const Event &)
|
||||||
|
* @endcode
|
||||||
*
|
*
|
||||||
* @tparam Sig Type of the signal handler to use.
|
* 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.
|
||||||
*/
|
*/
|
||||||
template<template<typename...> class Sig>
|
|
||||||
class Dispatcher final {
|
class Dispatcher final {
|
||||||
using event_family = Family<struct InternalDispatcherEventFamily>;
|
using event_family = Family<struct InternalDispatcherEventFamily>;
|
||||||
|
|
||||||
template<typename Class, typename Event>
|
template<typename Class, typename Event>
|
||||||
using instance_type = typename Sig<void(const Event &)>::template instance_type<Class>;
|
using instance_type = typename SigH<void(const Event &)>::template instance_type<Class>;
|
||||||
|
|
||||||
struct BaseSignalWrapper {
|
struct BaseSignalWrapper {
|
||||||
virtual ~BaseSignalWrapper() = default;
|
virtual ~BaseSignalWrapper() = default;
|
||||||
virtual void publish(std::size_t) = 0;
|
virtual void publish() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename Event>
|
template<typename Event>
|
||||||
struct SignalWrapper final: BaseSignalWrapper {
|
struct SignalWrapper final: BaseSignalWrapper {
|
||||||
void publish(std::size_t current) final override {
|
using sink_type = typename SigH<void(const Event &)>::sink_type;
|
||||||
for(auto &&event: events[current]) {
|
|
||||||
signal.publish(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
events[current].clear();
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Class, void(Class::*Member)(const Event &)>
|
inline sink_type sink() ENTT_NOEXCEPT {
|
||||||
inline void connect(instance_type<Class, Event> instance) noexcept {
|
return signal.sink();
|
||||||
signal.template connect<Class, Member>(std::move(instance));
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Class, void(Class::*Member)(const Event &)>
|
|
||||||
inline void disconnect(instance_type<Class, Event> instance) noexcept {
|
|
||||||
signal.template disconnect<Class, Member>(std::move(instance));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
inline void trigger(Args&&... args) {
|
inline void trigger(Args &&... args) {
|
||||||
signal.publish({ std::forward<Args>(args)... });
|
signal.publish({ std::forward<Args>(args)... });
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
inline void enqueue(std::size_t current, Args&&... args) {
|
inline void enqueue(Args &&... args) {
|
||||||
events[current].push_back({ std::forward<Args>(args)... });
|
events[current].push_back({ std::forward<Args>(args)... });
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Sig<void(const Event &)> signal{};
|
SigH<void(const Event &)> signal{};
|
||||||
std::vector<Event> events[2];
|
std::vector<Event> events[2];
|
||||||
|
int current{};
|
||||||
};
|
};
|
||||||
|
|
||||||
inline static std::size_t buffer(bool mode) {
|
|
||||||
return mode ? 0 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Event>
|
template<typename Event>
|
||||||
SignalWrapper<Event> & wrapper() {
|
SignalWrapper<Event> & wrapper() {
|
||||||
auto type = event_family::type<Event>();
|
const auto type = event_family::type<Event>();
|
||||||
|
|
||||||
if(!(type < wrappers.size())) {
|
if(!(type < wrappers.size())) {
|
||||||
wrappers.resize(type + 1);
|
wrappers.resize(type + 1);
|
||||||
@@ -93,51 +90,30 @@ class Dispatcher final {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/*! @brief Default constructor. */
|
/*! @brief Type of sink for the given event. */
|
||||||
Dispatcher() noexcept
|
template<typename Event>
|
||||||
: wrappers{}, mode{false}
|
using sink_type = typename SignalWrapper<Event>::sink_type;
|
||||||
{}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Registers a listener given in the form of a member function.
|
* @brief Returns a sink object for the given event.
|
||||||
*
|
*
|
||||||
* A matching member function has the following signature:
|
* A sink is an opaque object used to connect listeners to events.
|
||||||
* `void receive(const Event &)`. Member functions named `receive` are
|
|
||||||
* automatically detected and registered if available.
|
|
||||||
*
|
*
|
||||||
* @warning
|
* The function type for a listener is:
|
||||||
* Connecting a listener during an update may lead to unexpected behavior.
|
* @code{.cpp}
|
||||||
* Register listeners before or after invoking the update if possible.
|
* void(const Event &)
|
||||||
|
* @endcode
|
||||||
*
|
*
|
||||||
* @tparam Event Type of event to which to connect the function.
|
* The order of invocation of the listeners isn't guaranteed.
|
||||||
* @tparam Class Type of class to which the member function belongs.
|
*
|
||||||
* @tparam Member Member function to connect to the signal.
|
* @sa SigH::Sink
|
||||||
* @param instance A valid instance of the right type.
|
*
|
||||||
|
* @tparam Event Type of event of which to get the sink.
|
||||||
|
* @return A temporary sink object.
|
||||||
*/
|
*/
|
||||||
template<typename Event, typename Class, void(Class::*Member)(const Event &) = &Class::receive>
|
template<typename Event>
|
||||||
void connect(instance_type<Class, Event> instance) noexcept {
|
inline sink_type<Event> sink() ENTT_NOEXCEPT {
|
||||||
wrapper<Event>().template connect<Class, Member>(std::move(instance));
|
return wrapper<Event>().sink();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Unregisters a listener given in the form of a member function.
|
|
||||||
*
|
|
||||||
* A matching member function has the following signature:
|
|
||||||
* `void receive(const Event &)`. Member functions named `receive` are
|
|
||||||
* automatically detected and unregistered if available.
|
|
||||||
*
|
|
||||||
* @warning
|
|
||||||
* Disonnecting a listener during an update may lead to unexpected behavior.
|
|
||||||
* Unregister listeners before or after invoking the update if possible.
|
|
||||||
*
|
|
||||||
* @tparam Event Type of event from which to disconnect the function.
|
|
||||||
* @tparam Class Type of class to which the member function belongs.
|
|
||||||
* @tparam Member Member function to connect to the signal.
|
|
||||||
* @param instance A valid instance of the right type.
|
|
||||||
*/
|
|
||||||
template<typename Event, typename Class, void(Class::*Member)(const Event &) = &Class::receive>
|
|
||||||
void disconnect(instance_type<Class, Event> instance) noexcept {
|
|
||||||
wrapper<Event>().template disconnect<Class, Member>(std::move(instance));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -151,7 +127,7 @@ public:
|
|||||||
* @param args Arguments to use to construct the event.
|
* @param args Arguments to use to construct the event.
|
||||||
*/
|
*/
|
||||||
template<typename Event, typename... Args>
|
template<typename Event, typename... Args>
|
||||||
void trigger(Args&&... args) {
|
inline void trigger(Args &&... args) {
|
||||||
wrapper<Event>().trigger(std::forward<Args>(args)...);
|
wrapper<Event>().trigger(std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,56 +142,46 @@ public:
|
|||||||
* @param args Arguments to use to construct the event.
|
* @param args Arguments to use to construct the event.
|
||||||
*/
|
*/
|
||||||
template<typename Event, typename... Args>
|
template<typename Event, typename... Args>
|
||||||
void enqueue(Args&&... args) {
|
inline void enqueue(Args &&... args) {
|
||||||
wrapper<Event>().enqueue(buffer(mode), std::forward<Args>(args)...);
|
wrapper<Event>().enqueue(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Delivers all the pending events of the given type.
|
||||||
|
*
|
||||||
|
* This method is blocking and it doesn't return until all the events are
|
||||||
|
* delivered to the registered listeners. It's responsibility of the users
|
||||||
|
* to reduce at a minimum the time spent in the bodies of the listeners.
|
||||||
|
*
|
||||||
|
* @tparam Event Type of events to send.
|
||||||
|
*/
|
||||||
|
template<typename Event>
|
||||||
|
inline void update() {
|
||||||
|
wrapper<Event>().publish();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Delivers all the pending events.
|
* @brief Delivers all the pending events.
|
||||||
*
|
*
|
||||||
* This method is blocking and it doesn't return until all the events are
|
* This method is blocking and it doesn't return until all the events are
|
||||||
* delivered to the registered listeners. It's responsability of the users
|
* delivered to the registered listeners. It's responsibility of the users
|
||||||
* to reduce at a minimum the time spent in the bodies of the listeners.
|
* to reduce at a minimum the time spent in the bodies of the listeners.
|
||||||
*/
|
*/
|
||||||
void update() {
|
inline void update() const {
|
||||||
auto buf = buffer(mode);
|
for(auto pos = wrappers.size(); pos; --pos) {
|
||||||
mode = !mode;
|
auto &wrapper = wrappers[pos-1];
|
||||||
|
|
||||||
for(auto &&wrapper: wrappers) {
|
|
||||||
if(wrapper) {
|
if(wrapper) {
|
||||||
wrapper->publish(buf);
|
wrapper->publish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<std::unique_ptr<BaseSignalWrapper>> wrappers;
|
std::vector<std::unique_ptr<BaseSignalWrapper>> wrappers;
|
||||||
bool mode;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Managed dispatcher.
|
|
||||||
*
|
|
||||||
* A managed dispatcher uses the Signal class template as an underlying type.
|
|
||||||
* The type of the instances is the one required by the signal handler:
|
|
||||||
* `std::shared_ptr<Class>` (a shared pointer).
|
|
||||||
*/
|
|
||||||
using ManagedDispatcher = Dispatcher<Signal>;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Unmanaged dispatcher.
|
|
||||||
*
|
|
||||||
* An unmanaged dispatcher uses the SigH class template as an underlying type.
|
|
||||||
* The type of the instances is the one required by the signal handler:
|
|
||||||
* `Class *` (a naked pointer).<br/>
|
|
||||||
* When it comes to work with this kind of dispatcher, users must guarantee that
|
|
||||||
* the lifetimes of the instances overcome the one of the dispatcher itself.
|
|
||||||
*/
|
|
||||||
using UnmanagedDispatcher = Dispatcher<SigH>;
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
#include "../config/config.h"
|
||||||
|
#include "../core/family.hpp"
|
||||||
|
|
||||||
|
|
||||||
namespace entt {
|
namespace entt {
|
||||||
@@ -38,10 +40,12 @@ namespace entt {
|
|||||||
*/
|
*/
|
||||||
template<typename Derived>
|
template<typename Derived>
|
||||||
class Emitter {
|
class Emitter {
|
||||||
|
using handler_family = Family<struct InternalEmitterHandlerFamily>;
|
||||||
|
|
||||||
struct BaseHandler {
|
struct BaseHandler {
|
||||||
virtual ~BaseHandler() = default;
|
virtual ~BaseHandler() = default;
|
||||||
virtual bool empty() const noexcept = 0;
|
virtual bool empty() const ENTT_NOEXCEPT = 0;
|
||||||
virtual void clear() noexcept = 0;
|
virtual void clear() ENTT_NOEXCEPT = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename Event>
|
template<typename Event>
|
||||||
@@ -51,16 +55,16 @@ class Emitter {
|
|||||||
using container_type = std::list<element_type>;
|
using container_type = std::list<element_type>;
|
||||||
using connection_type = typename container_type::iterator;
|
using connection_type = typename container_type::iterator;
|
||||||
|
|
||||||
bool empty() const noexcept override {
|
bool empty() const ENTT_NOEXCEPT override {
|
||||||
auto pred = [](auto &&element){ return element.first; };
|
auto pred = [](auto &&element) { return element.first; };
|
||||||
|
|
||||||
return std::all_of(onceL.cbegin(), onceL.cend(), pred) &&
|
return std::all_of(onceL.cbegin(), onceL.cend(), pred) &&
|
||||||
std::all_of(onL.cbegin(), onL.cend(), pred);
|
std::all_of(onL.cbegin(), onL.cend(), pred);
|
||||||
}
|
}
|
||||||
|
|
||||||
void clear() noexcept override {
|
void clear() ENTT_NOEXCEPT override {
|
||||||
if(publishing) {
|
if(publishing) {
|
||||||
auto func = [](auto &&element){ element.first = true; };
|
auto func = [](auto &&element) { element.first = true; };
|
||||||
std::for_each(onceL.begin(), onceL.end(), func);
|
std::for_each(onceL.begin(), onceL.end(), func);
|
||||||
std::for_each(onL.begin(), onL.end(), func);
|
std::for_each(onL.begin(), onL.end(), func);
|
||||||
} else {
|
} else {
|
||||||
@@ -77,11 +81,11 @@ class Emitter {
|
|||||||
return onL.emplace(onL.cend(), false, std::move(listener));
|
return onL.emplace(onL.cend(), false, std::move(listener));
|
||||||
}
|
}
|
||||||
|
|
||||||
void erase(connection_type conn) noexcept {
|
void erase(connection_type conn) ENTT_NOEXCEPT {
|
||||||
conn->first = true;
|
conn->first = true;
|
||||||
|
|
||||||
if(!publishing) {
|
if(!publishing) {
|
||||||
auto pred = [](auto &&element){ return element.first; };
|
auto pred = [](auto &&element) { return element.first; };
|
||||||
onceL.remove_if(pred);
|
onceL.remove_if(pred);
|
||||||
onL.remove_if(pred);
|
onL.remove_if(pred);
|
||||||
}
|
}
|
||||||
@@ -102,7 +106,7 @@ class Emitter {
|
|||||||
|
|
||||||
publishing = false;
|
publishing = false;
|
||||||
|
|
||||||
onL.remove_if([](auto &&element){ return element.first; });
|
onL.remove_if([](auto &&element) { return element.first; });
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -111,20 +115,9 @@ class Emitter {
|
|||||||
container_type onL{};
|
container_type onL{};
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::size_t next() noexcept {
|
|
||||||
static std::size_t counter = 0;
|
|
||||||
return counter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename>
|
|
||||||
static std::size_t type() noexcept {
|
|
||||||
static std::size_t value = next();
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Event>
|
template<typename Event>
|
||||||
Handler<Event> & handler() noexcept {
|
Handler<Event> & handler() ENTT_NOEXCEPT {
|
||||||
std::size_t family = type<Event>();
|
const std::size_t family = handler_family::type<Event>();
|
||||||
|
|
||||||
if(!(family < handlers.size())) {
|
if(!(family < handlers.size())) {
|
||||||
handlers.resize(family+1);
|
handlers.resize(family+1);
|
||||||
@@ -138,7 +131,7 @@ class Emitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/** @brief Type of listeners accepted for the given type of event. */
|
/** @brief Type of listeners accepted for the given event. */
|
||||||
template<typename Event>
|
template<typename Event>
|
||||||
using Listener = typename Handler<Event>::listener_type;
|
using Listener = typename Handler<Event>::listener_type;
|
||||||
|
|
||||||
@@ -157,7 +150,7 @@ public:
|
|||||||
friend class Emitter;
|
friend class Emitter;
|
||||||
|
|
||||||
/*! @brief Default constructor. */
|
/*! @brief Default constructor. */
|
||||||
Connection() noexcept = default;
|
Connection() ENTT_NOEXCEPT = default;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Creates a connection that wraps its underlying instance.
|
* @brief Creates a connection that wraps its underlying instance.
|
||||||
@@ -173,7 +166,7 @@ public:
|
|||||||
Connection(Connection &&) = default;
|
Connection(Connection &&) = default;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Default copy assignament operator.
|
* @brief Default copy assignment operator.
|
||||||
* @return This connection.
|
* @return This connection.
|
||||||
*/
|
*/
|
||||||
Connection & operator=(const Connection &) = default;
|
Connection & operator=(const Connection &) = default;
|
||||||
@@ -186,10 +179,10 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*! @brief Default constructor. */
|
/*! @brief Default constructor. */
|
||||||
Emitter() noexcept = default;
|
Emitter() ENTT_NOEXCEPT = default;
|
||||||
|
|
||||||
/*! @brief Default destructor. */
|
/*! @brief Default destructor. */
|
||||||
virtual ~Emitter() noexcept {
|
virtual ~Emitter() ENTT_NOEXCEPT {
|
||||||
static_assert(std::is_base_of<Emitter<Derived>, Derived>::value, "!");
|
static_assert(std::is_base_of<Emitter<Derived>, Derived>::value, "!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +193,7 @@ public:
|
|||||||
|
|
||||||
/*! @brief Copying an emitter isn't allowed. @return This emitter. */
|
/*! @brief Copying an emitter isn't allowed. @return This emitter. */
|
||||||
Emitter & operator=(const Emitter &) = delete;
|
Emitter & operator=(const Emitter &) = delete;
|
||||||
/*! @brief Default move assignament operator. @return This emitter. */
|
/*! @brief Default move assignment operator. @return This emitter. */
|
||||||
Emitter & operator=(Emitter &&) = default;
|
Emitter & operator=(Emitter &&) = default;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -215,7 +208,7 @@ public:
|
|||||||
* @param args Parameters to use to initialize the event.
|
* @param args Parameters to use to initialize the event.
|
||||||
*/
|
*/
|
||||||
template<typename Event, typename... Args>
|
template<typename Event, typename... Args>
|
||||||
void publish(Args&&... args) {
|
void publish(Args &&... args) {
|
||||||
handler<Event>().publish({ std::forward<Args>(args)... }, *static_cast<Derived *>(this));
|
handler<Event>().publish({ std::forward<Args>(args)... }, *static_cast<Derived *>(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,7 +272,7 @@ public:
|
|||||||
* @param conn A valid connection.
|
* @param conn A valid connection.
|
||||||
*/
|
*/
|
||||||
template<typename Event>
|
template<typename Event>
|
||||||
void erase(Connection<Event> conn) noexcept {
|
void erase(Connection<Event> conn) ENTT_NOEXCEPT {
|
||||||
handler<Event>().erase(std::move(conn));
|
handler<Event>().erase(std::move(conn));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,12 +280,12 @@ public:
|
|||||||
* @brief Disconnects all the listeners for the given event type.
|
* @brief Disconnects all the listeners for the given event type.
|
||||||
*
|
*
|
||||||
* All the connections previously returned for the given event are
|
* All the connections previously returned for the given event are
|
||||||
* invalidated. Using them results in undefined behaviour.
|
* invalidated. Using them results in undefined behavior.
|
||||||
*
|
*
|
||||||
* @tparam Event Type of event to reset.
|
* @tparam Event Type of event to reset.
|
||||||
*/
|
*/
|
||||||
template<typename Event>
|
template<typename Event>
|
||||||
void clear() noexcept {
|
void clear() ENTT_NOEXCEPT {
|
||||||
handler<Event>().clear();
|
handler<Event>().clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,11 +293,12 @@ public:
|
|||||||
* @brief Disconnects all the listeners.
|
* @brief Disconnects all the listeners.
|
||||||
*
|
*
|
||||||
* All the connections previously returned are invalidated. Using them
|
* All the connections previously returned are invalidated. Using them
|
||||||
* results in undefined behaviour.
|
* results in undefined behavior.
|
||||||
*/
|
*/
|
||||||
void clear() noexcept {
|
void clear() ENTT_NOEXCEPT {
|
||||||
std::for_each(handlers.begin(), handlers.end(),
|
std::for_each(handlers.begin(), handlers.end(), [](auto &&handler) {
|
||||||
[](auto &&handler){ if(handler) { handler->clear(); } });
|
return handler ? handler->clear() : void();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -313,8 +307,8 @@ public:
|
|||||||
* @return True if there are no listeners registered, false otherwise.
|
* @return True if there are no listeners registered, false otherwise.
|
||||||
*/
|
*/
|
||||||
template<typename Event>
|
template<typename Event>
|
||||||
bool empty() const noexcept {
|
bool empty() const ENTT_NOEXCEPT {
|
||||||
std::size_t family = type<Event>();
|
const std::size_t family = handler_family::type<Event>();
|
||||||
|
|
||||||
return (!(family < handlers.size()) ||
|
return (!(family < handlers.size()) ||
|
||||||
!handlers[family] ||
|
!handlers[family] ||
|
||||||
@@ -325,9 +319,10 @@ public:
|
|||||||
* @brief Checks if there are listeners registered with the event emitter.
|
* @brief Checks if there are listeners registered with the event emitter.
|
||||||
* @return True if there are no listeners registered, false otherwise.
|
* @return True if there are no listeners registered, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool empty() const noexcept {
|
bool empty() const ENTT_NOEXCEPT {
|
||||||
return std::all_of(handlers.cbegin(), handlers.cend(),
|
return std::all_of(handlers.cbegin(), handlers.cend(), [](auto &&handler) {
|
||||||
[](auto &&handler){ return !handler || handler->empty(); });
|
return !handler || handler->empty();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -5,12 +5,30 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include "../config/config.h"
|
||||||
|
|
||||||
|
|
||||||
namespace entt {
|
namespace entt {
|
||||||
|
|
||||||
|
|
||||||
namespace {
|
/**
|
||||||
|
* @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>
|
template<typename, typename>
|
||||||
@@ -19,37 +37,39 @@ struct Invoker;
|
|||||||
|
|
||||||
template<typename Ret, typename... Args, typename Collector>
|
template<typename Ret, typename... Args, typename Collector>
|
||||||
struct Invoker<Ret(Args...), Collector> {
|
struct Invoker<Ret(Args...), Collector> {
|
||||||
using proto_type = Ret(*)(void *, Args...);
|
using proto_fn_type = typename sigh_traits<Ret(Args...)>::proto_fn_type;
|
||||||
using call_type = std::pair<void *, proto_type>;
|
|
||||||
|
|
||||||
virtual ~Invoker() = default;
|
virtual ~Invoker() = default;
|
||||||
|
|
||||||
template<typename SFINAE = Ret>
|
bool invoke(Collector &collector, proto_fn_type *proto, void *instance, Args... args) const {
|
||||||
typename std::enable_if<std::is_void<SFINAE>::value, bool>::type
|
|
||||||
invoke(Collector &, proto_type proto, void *instance, Args... args) {
|
|
||||||
proto(instance, args...);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename SFINAE = Ret>
|
|
||||||
typename std::enable_if<!std::is_void<SFINAE>::value, bool>::type
|
|
||||||
invoke(Collector &collector, proto_type proto, void *instance, Args... args) {
|
|
||||||
return collector(proto(instance, args...));
|
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>
|
template<typename Ret>
|
||||||
struct NullCollector final {
|
struct NullCollector final {
|
||||||
using result_type = Ret;
|
using result_type = Ret;
|
||||||
bool operator()(result_type) const noexcept { return true; }
|
bool operator()(result_type) const ENTT_NOEXCEPT { return true; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
struct NullCollector<void> final {
|
struct NullCollector<void> final {
|
||||||
using result_type = void;
|
using result_type = void;
|
||||||
bool operator()() const noexcept { return true; }
|
bool operator()() const ENTT_NOEXCEPT { return true; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -70,6 +90,24 @@ using DefaultCollectorType = typename DefaultCollector<Function>::collector_type
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal details not to be documented.
|
||||||
|
* @endcond TURN_OFF_DOXYGEN
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sink implementation.
|
||||||
|
*
|
||||||
|
* Primary template isn't defined on purpose. All the specializations give a
|
||||||
|
* compile-time error unless the template parameter is a function type.
|
||||||
|
*
|
||||||
|
* @tparam Function A valid function type.
|
||||||
|
*/
|
||||||
|
template<typename Function>
|
||||||
|
class Sink;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Unmanaged signal handler declaration.
|
* @brief Unmanaged signal handler declaration.
|
||||||
*
|
*
|
||||||
@@ -79,10 +117,161 @@ using DefaultCollectorType = typename DefaultCollector<Function>::collector_type
|
|||||||
* @tparam Function A valid function type.
|
* @tparam Function A valid function type.
|
||||||
* @tparam Collector Type of collector to use, if any.
|
* @tparam Collector Type of collector to use, if any.
|
||||||
*/
|
*/
|
||||||
template<typename Function, typename Collector = DefaultCollectorType<Function>>
|
template<typename Function, typename Collector = internal::DefaultCollectorType<Function>>
|
||||||
class SigH;
|
class SigH;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sink implementation.
|
||||||
|
*
|
||||||
|
* A sink is an opaque object used to connect listeners to signals.<br/>
|
||||||
|
* The function type for a listener is the one of the signal to which it
|
||||||
|
* belongs.
|
||||||
|
*
|
||||||
|
* The clear separation between a signal and a sink permits to store the
|
||||||
|
* former as private data member without exposing the publish functionality
|
||||||
|
* to the users of a class.
|
||||||
|
*
|
||||||
|
* @tparam Ret Return type of a function type.
|
||||||
|
* @tparam Args Types of arguments of a function type.
|
||||||
|
*/
|
||||||
|
template<typename Ret, typename... Args>
|
||||||
|
class Sink<Ret(Args...)> 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.
|
* @brief Unmanaged signal handler definition.
|
||||||
*
|
*
|
||||||
@@ -100,31 +289,23 @@ class SigH;
|
|||||||
*
|
*
|
||||||
* * `Param` is a type to which `Ret` can be converted.
|
* * `Param` is a type to which `Ret` can be converted.
|
||||||
* * The return type is true if the handler must stop collecting data, false
|
* * The return type is true if the handler must stop collecting data, false
|
||||||
* otherwise.
|
* otherwise.
|
||||||
*
|
*
|
||||||
* @tparam Ret Return type of a function type.
|
* @tparam Ret Return type of a function type.
|
||||||
* @tparam Args Types of arguments of a function type.
|
* @tparam Args Types of arguments of a function type.
|
||||||
* @tparam Collector Type of collector to use, if any.
|
* @tparam Collector Type of collector to use, if any.
|
||||||
*/
|
*/
|
||||||
template<typename Ret, typename... Args, typename Collector>
|
template<typename Ret, typename... Args, typename Collector>
|
||||||
class SigH<Ret(Args...), Collector> final: private Invoker<Ret(Args...), Collector> {
|
class SigH<Ret(Args...), Collector> final: private internal::Invoker<Ret(Args...), Collector> {
|
||||||
using typename Invoker<Ret(Args...), Collector>::call_type;
|
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)>
|
|
||||||
static Ret proto(void *instance, Args... args) {
|
|
||||||
return (static_cast<Class *>(instance)->*Member)(args...);
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/*! @brief Unsigned integer type. */
|
/*! @brief Unsigned integer type. */
|
||||||
using size_type = typename std::vector<call_type>::size_type;
|
using size_type = typename std::vector<call_type>::size_type;
|
||||||
/*! @brief Collector type. */
|
/*! @brief Collector type. */
|
||||||
using collector_type = Collector;
|
using collector_type = Collector;
|
||||||
|
/*! @brief Sink type. */
|
||||||
|
using sink_type = Sink<Ret(Args...)>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Instance type when it comes to connecting member functions.
|
* @brief Instance type when it comes to connecting member functions.
|
||||||
@@ -137,7 +318,7 @@ public:
|
|||||||
* @brief Number of listeners connected to the signal.
|
* @brief Number of listeners connected to the signal.
|
||||||
* @return Number of listeners currently connected.
|
* @return Number of listeners currently connected.
|
||||||
*/
|
*/
|
||||||
size_type size() const noexcept {
|
size_type size() const ENTT_NOEXCEPT {
|
||||||
return calls.size();
|
return calls.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,80 +326,21 @@ public:
|
|||||||
* @brief Returns false if at least a listener is connected to the signal.
|
* @brief Returns false if at least a listener is connected to the signal.
|
||||||
* @return True if the signal has no listeners connected, false otherwise.
|
* @return True if the signal has no listeners connected, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool empty() const noexcept {
|
bool empty() const ENTT_NOEXCEPT {
|
||||||
return calls.empty();
|
return calls.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Disconnects all the listeners from a signal.
|
* @brief Returns a sink object for the given signal.
|
||||||
*/
|
|
||||||
void clear() noexcept {
|
|
||||||
calls.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Connects a free function to a signal.
|
|
||||||
*
|
*
|
||||||
* The signal handler performs checks to avoid multiple connections for free
|
* A sink is an opaque object used to connect listeners to signals.<br/>
|
||||||
* functions.
|
* The function type for a listener is the one of the signal to which it
|
||||||
|
* belongs. The order of invocation of the listeners isn't guaranteed.
|
||||||
*
|
*
|
||||||
* @tparam Function A valid free function pointer.
|
* @return A temporary sink object.
|
||||||
*/
|
*/
|
||||||
template<Ret(*Function)(Args...)>
|
sink_type sink() ENTT_NOEXCEPT {
|
||||||
void connect() {
|
return { &calls };
|
||||||
disconnect<Function>();
|
|
||||||
calls.emplace_back(nullptr, &proto<Function>);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Connects a member function for a given instance to a signal.
|
|
||||||
*
|
|
||||||
* The signal isn't responsible for the connected object. Users must
|
|
||||||
* guarantee that the lifetime of the instance overcomes the one of the
|
|
||||||
* signal. On the other side, the signal handler performs checks to avoid
|
|
||||||
* multiple connections for the same member function of a given instance.
|
|
||||||
*
|
|
||||||
* @tparam Class Type of class to which the member function belongs.
|
|
||||||
* @tparam Member Member function to connect to the signal.
|
|
||||||
* @param instance A valid instance of type pointer to `Class`.
|
|
||||||
*/
|
|
||||||
template <typename Class, Ret(Class::*Member)(Args...)>
|
|
||||||
void connect(instance_type<Class> instance) {
|
|
||||||
disconnect<Class, Member>(instance);
|
|
||||||
calls.emplace_back(instance, &proto<Class, Member>);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Disconnects a free function from a signal.
|
|
||||||
* @tparam Function A valid free function pointer.
|
|
||||||
*/
|
|
||||||
template<Ret(*Function)(Args...)>
|
|
||||||
void disconnect() {
|
|
||||||
call_type target{nullptr, &proto<Function>};
|
|
||||||
calls.erase(std::remove(calls.begin(), calls.end(), std::move(target)), calls.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Disconnects the given member function from a signal.
|
|
||||||
* @tparam Class Type of class to which the member function belongs.
|
|
||||||
* @tparam Member Member function to connect to the signal.
|
|
||||||
* @param instance A valid instance of type pointer to `Class`.
|
|
||||||
*/
|
|
||||||
template<typename Class, Ret(Class::*Member)(Args...)>
|
|
||||||
void disconnect(instance_type<Class> instance) {
|
|
||||||
call_type target{instance, &proto<Class, Member>};
|
|
||||||
calls.erase(std::remove(calls.begin(), calls.end(), std::move(target)), calls.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Removes all existing connections for the given instance.
|
|
||||||
* @tparam Class Type of class to which the member function belongs.
|
|
||||||
* @param instance A valid instance of type pointer to `Class`.
|
|
||||||
*/
|
|
||||||
template<typename Class>
|
|
||||||
void disconnect(instance_type<Class> instance) {
|
|
||||||
auto func = [instance](const call_type &call) { return call.first == instance; };
|
|
||||||
calls.erase(std::remove_if(calls.begin(), calls.end(), std::move(func)), calls.end());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -228,8 +350,9 @@ public:
|
|||||||
*
|
*
|
||||||
* @param args Arguments to use to invoke listeners.
|
* @param args Arguments to use to invoke listeners.
|
||||||
*/
|
*/
|
||||||
void publish(Args... args) {
|
void publish(Args... args) const {
|
||||||
for(auto &&call: calls) {
|
for(auto pos = calls.size(); pos; --pos) {
|
||||||
|
auto &call = calls[pos-1];
|
||||||
call.second(call.first, args...);
|
call.second(call.first, args...);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,7 +362,7 @@ public:
|
|||||||
* @param args Arguments to use to invoke listeners.
|
* @param args Arguments to use to invoke listeners.
|
||||||
* @return An instance of the collector filled with collected data.
|
* @return An instance of the collector filled with collected data.
|
||||||
*/
|
*/
|
||||||
collector_type collect(Args... args) {
|
collector_type collect(Args... args) const {
|
||||||
collector_type collector;
|
collector_type collector;
|
||||||
|
|
||||||
for(auto &&call: calls) {
|
for(auto &&call: calls) {
|
||||||
@@ -270,7 +393,7 @@ public:
|
|||||||
* @param other Signal with which to compare.
|
* @param other Signal with which to compare.
|
||||||
* @return True if the two signals are identical, false otherwise.
|
* @return True if the two signals are identical, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool operator==(const SigH &other) const noexcept {
|
bool operator==(const SigH &other) const ENTT_NOEXCEPT {
|
||||||
return std::equal(calls.cbegin(), calls.cend(), other.calls.cbegin(), other.calls.cend());
|
return std::equal(calls.cbegin(), calls.cend(), other.calls.cbegin(), other.calls.cend());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,7 +415,7 @@ private:
|
|||||||
* @return True if the two signals are different, false otherwise.
|
* @return True if the two signals are different, false otherwise.
|
||||||
*/
|
*/
|
||||||
template<typename Ret, typename... Args>
|
template<typename Ret, typename... Args>
|
||||||
bool operator!=(const SigH<Ret(Args...)> &lhs, const SigH<Ret(Args...)> &rhs) noexcept {
|
bool operator!=(const SigH<Ret(Args...)> &lhs, const SigH<Ret(Args...)> &rhs) ENTT_NOEXCEPT {
|
||||||
return !(lhs == rhs);
|
return !(lhs == rhs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,227 +0,0 @@
|
|||||||
#ifndef ENTT_SIGNAL_SIGNAL_HPP
|
|
||||||
#define ENTT_SIGNAL_SIGNAL_HPP
|
|
||||||
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
|
||||||
#include <utility>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <iterator>
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
|
|
||||||
namespace entt {
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Managed signal handler declaration.
|
|
||||||
*
|
|
||||||
* Primary template isn't defined on purpose. All the specializations give a
|
|
||||||
* compile-time error unless the template parameter is a function type.
|
|
||||||
*/
|
|
||||||
template<typename>
|
|
||||||
class Signal;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Managed signal handler definition.
|
|
||||||
*
|
|
||||||
* Managed signal handler. It works with weak pointers to classes and pointers
|
|
||||||
* to member functions as well as pointers to free functions. References are
|
|
||||||
* automatically removed when the instances to which they point are freed.
|
|
||||||
*
|
|
||||||
* This class can be used to create signals used later to notify a bunch of
|
|
||||||
* listeners.
|
|
||||||
*
|
|
||||||
* @tparam Args Types of arguments of a function type.
|
|
||||||
*/
|
|
||||||
template<typename... Args>
|
|
||||||
class Signal<void(Args...)> final {
|
|
||||||
using proto_type = bool(*)(std::weak_ptr<void> &, Args...);
|
|
||||||
using call_type = std::pair<std::weak_ptr<void>, proto_type>;
|
|
||||||
|
|
||||||
template<void(*Function)(Args...)>
|
|
||||||
static bool proto(std::weak_ptr<void> &, Args... args) {
|
|
||||||
Function(args...);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Class, void(Class::*Member)(Args...)>
|
|
||||||
static bool proto(std::weak_ptr<void> &wptr, Args... args) {
|
|
||||||
bool ret = false;
|
|
||||||
|
|
||||||
if(!wptr.expired()) {
|
|
||||||
auto ptr = std::static_pointer_cast<Class>(wptr.lock());
|
|
||||||
(ptr.get()->*Member)(args...);
|
|
||||||
ret = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
/*! @brief Unsigned integer type. */
|
|
||||||
using size_type = std::size_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Instance type when it comes to connecting member functions.
|
|
||||||
* @tparam Class Type of class to which the member function belongs.
|
|
||||||
*/
|
|
||||||
template<typename Class>
|
|
||||||
using instance_type = std::shared_ptr<Class>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Number of listeners connected to the signal.
|
|
||||||
* @return Number of listeners currently connected.
|
|
||||||
*/
|
|
||||||
size_type size() const noexcept {
|
|
||||||
return calls.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns false if at least a listener is connected to the signal.
|
|
||||||
* @return True if the signal has no listeners connected, false otherwise.
|
|
||||||
*/
|
|
||||||
bool empty() const noexcept {
|
|
||||||
return calls.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Disconnects all the listeners from a signal.
|
|
||||||
*/
|
|
||||||
void clear() noexcept {
|
|
||||||
calls.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Connects a free function to a signal.
|
|
||||||
*
|
|
||||||
* The signal handler performs checks to avoid multiple connections for free
|
|
||||||
* functions.
|
|
||||||
*
|
|
||||||
* @tparam Function A valid free function pointer.
|
|
||||||
*/
|
|
||||||
template<void(*Function)(Args...)>
|
|
||||||
void connect() {
|
|
||||||
disconnect<Function>();
|
|
||||||
calls.emplace_back(std::weak_ptr<void>{}, &proto<Function>);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Connects a member function for a given instance to a signal.
|
|
||||||
*
|
|
||||||
* The signal handler performs checks to avoid multiple connections for the
|
|
||||||
* same member function of a given instance.
|
|
||||||
*
|
|
||||||
* @tparam Class Type of class to which the member function belongs.
|
|
||||||
* @tparam Member Member function to connect to the signal.
|
|
||||||
* @param instance A valid instance of type pointer to `Class`.
|
|
||||||
*/
|
|
||||||
template<typename Class, void(Class::*Member)(Args...)>
|
|
||||||
void connect(instance_type<Class> instance) {
|
|
||||||
disconnect<Class, Member>(instance);
|
|
||||||
calls.emplace_back(std::move(instance), &proto<Class, Member>);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Disconnects a free function from a signal.
|
|
||||||
* @tparam Function A valid free function pointer.
|
|
||||||
*/
|
|
||||||
template<void(*Function)(Args...)>
|
|
||||||
void disconnect() {
|
|
||||||
calls.erase(std::remove_if(calls.begin(), calls.end(),
|
|
||||||
[](const call_type &call) { return call.second == &proto<Function> && !call.first.lock(); }
|
|
||||||
), calls.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Disconnects the given member function from a signal.
|
|
||||||
* @tparam Class Type of class to which the member function belongs.
|
|
||||||
* @tparam Member Member function to connect to the signal.
|
|
||||||
* @param instance A valid instance of type pointer to `Class`.
|
|
||||||
*/
|
|
||||||
template<typename Class, void(Class::*Member)(Args...)>
|
|
||||||
void disconnect(instance_type<Class> instance) {
|
|
||||||
calls.erase(std::remove_if(calls.begin(), calls.end(),
|
|
||||||
[instance{std::move(instance)}](const call_type &call) { return call.second == &proto<Class, Member> && call.first.lock() == instance; }
|
|
||||||
), calls.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Removes all existing connections for the given instance.
|
|
||||||
* @tparam Class Type of class to which the member function belongs.
|
|
||||||
* @param instance A valid instance of type pointer to `Class`.
|
|
||||||
*/
|
|
||||||
template<typename Class>
|
|
||||||
void disconnect(instance_type<Class> instance) {
|
|
||||||
calls.erase(std::remove_if(calls.begin(), calls.end(),
|
|
||||||
[instance{std::move(instance)}](const call_type &call) { return call.first.lock() == instance; }
|
|
||||||
), calls.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Triggers a signal.
|
|
||||||
*
|
|
||||||
* All the listeners are notified. Order isn't guaranteed.
|
|
||||||
*
|
|
||||||
* @param args Arguments to use to invoke listeners.
|
|
||||||
*/
|
|
||||||
void publish(Args... args) {
|
|
||||||
for(auto it = calls.rbegin(), end = calls.rend(); it != end; it++) {
|
|
||||||
if(!(it->second)(it->first, args...)) {
|
|
||||||
calls.erase(std::next(it).base());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Swaps listeners between the two signals.
|
|
||||||
* @param lhs A valid signal object.
|
|
||||||
* @param rhs A valid signal object.
|
|
||||||
*/
|
|
||||||
friend void swap(Signal &lhs, Signal &rhs) {
|
|
||||||
using std::swap;
|
|
||||||
swap(lhs.calls, rhs.calls);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Checks if the contents of the two signals are identical.
|
|
||||||
*
|
|
||||||
* Two signals are identical if they have the same size and the same
|
|
||||||
* listeners registered exactly in the same order.
|
|
||||||
*
|
|
||||||
* @param other Signal with which to compare.
|
|
||||||
* @return True if the two signals are identical, false otherwise.
|
|
||||||
*/
|
|
||||||
bool operator==(const Signal &other) const noexcept {
|
|
||||||
return std::equal(calls.cbegin(), calls.cend(), other.calls.cbegin(), other.calls.cend(), [](const auto &lhs, const auto &rhs) {
|
|
||||||
return (lhs.second == rhs.second) && (lhs.first.lock() == rhs.first.lock());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<call_type> calls;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Checks if the contents of the two signals are different.
|
|
||||||
*
|
|
||||||
* Two signals are identical if they have the same size and the same
|
|
||||||
* listeners registered exactly in the same order.
|
|
||||||
*
|
|
||||||
* @tparam Args Types of arguments of a function type.
|
|
||||||
* @param lhs A valid signal object.
|
|
||||||
* @param rhs A valid signal object.
|
|
||||||
* @return True if the two signals are different, false otherwise.
|
|
||||||
*/
|
|
||||||
template<typename... Args>
|
|
||||||
bool operator!=(const Signal<void(Args...)> &lhs, const Signal<void(Args...)> &rhs) noexcept {
|
|
||||||
return !(lhs == rhs);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#endif // ENTT_SIGNAL_SIGNAL_HPP
|
|
||||||
@@ -2,89 +2,96 @@
|
|||||||
# Tests configuration
|
# Tests configuration
|
||||||
#
|
#
|
||||||
|
|
||||||
include_directories(${PROJECT_SRC_DIR})
|
include_directories($<TARGET_PROPERTY:EnTT,INTERFACE_INCLUDE_DIRECTORIES>)
|
||||||
|
add_compile_options($<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_OPTIONS>)
|
||||||
|
|
||||||
add_library(odr OBJECT odr.cpp)
|
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>)
|
||||||
|
|
||||||
|
macro(SETUP_AND_ADD_TEST TEST_NAME TEST_SOURCE)
|
||||||
|
add_executable(${TEST_NAME} $<TARGET_OBJECTS:odr> ${TEST_SOURCE})
|
||||||
|
set_target_properties(${TEST_NAME} PROPERTIES CXX_EXTENSIONS OFF)
|
||||||
|
target_link_libraries(${TEST_NAME} PRIVATE EnTT GTest::Main Threads::Threads)
|
||||||
|
target_compile_definitions(${TEST_NAME} PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_DEFINITIONS>)
|
||||||
|
target_compile_features(${TEST_NAME} PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_FEATURES>)
|
||||||
|
target_compile_options(${TEST_NAME} PRIVATE $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-pedantic -Wall>)
|
||||||
|
target_compile_options(${TEST_NAME} PRIVATE $<$<CXX_COMPILER_ID:MSVC>:/EHsc>)
|
||||||
|
add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME})
|
||||||
|
endmacro()
|
||||||
|
|
||||||
# Test benchmark
|
# Test benchmark
|
||||||
|
|
||||||
if(CMAKE_BUILD_TYPE MATCHES Release)
|
if(BUILD_BENCHMARK)
|
||||||
add_executable(
|
SETUP_AND_ADD_TEST(benchmark benchmark/benchmark.cpp)
|
||||||
benchmark
|
endif()
|
||||||
$<TARGET_OBJECTS:odr>
|
|
||||||
entt/entity/benchmark.cpp
|
# Test mod
|
||||||
)
|
|
||||||
target_link_libraries(benchmark PRIVATE gtest_main Threads::Threads)
|
if(BUILD_MOD)
|
||||||
add_test(NAME benchmark COMMAND benchmark)
|
set(DUKTAPE_DEPS_DIR ${EnTT_SOURCE_DIR}/deps/duktape)
|
||||||
|
configure_file(${EnTT_SOURCE_DIR}/cmake/in/duktape.in ${DUKTAPE_DEPS_DIR}/CMakeLists.txt)
|
||||||
|
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . WORKING_DIRECTORY ${DUKTAPE_DEPS_DIR})
|
||||||
|
execute_process(COMMAND ${CMAKE_COMMAND} --build . WORKING_DIRECTORY ${DUKTAPE_DEPS_DIR})
|
||||||
|
set(DUKTAPE_SRC_DIR ${DUKTAPE_DEPS_DIR}/src/src)
|
||||||
|
|
||||||
|
set(MOD_TEST_SOURCE ${DUKTAPE_SRC_DIR}/duktape.c mod/mod.cpp)
|
||||||
|
SETUP_AND_ADD_TEST(mod "${MOD_TEST_SOURCE}")
|
||||||
|
target_include_directories(mod PRIVATE ${DUKTAPE_SRC_DIR})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Test snapshot
|
||||||
|
|
||||||
|
if(BUILD_SNAPSHOT)
|
||||||
|
set(CEREAL_DEPS_DIR ${EnTT_SOURCE_DIR}/deps/cereal)
|
||||||
|
configure_file(${EnTT_SOURCE_DIR}/cmake/in/cereal.in ${CEREAL_DEPS_DIR}/CMakeLists.txt)
|
||||||
|
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . WORKING_DIRECTORY ${CEREAL_DEPS_DIR})
|
||||||
|
execute_process(COMMAND ${CMAKE_COMMAND} --build . WORKING_DIRECTORY ${CEREAL_DEPS_DIR})
|
||||||
|
set(CEREAL_SRC_DIR ${CEREAL_DEPS_DIR}/src/include)
|
||||||
|
|
||||||
|
SETUP_AND_ADD_TEST(cereal snapshot/snapshot.cpp)
|
||||||
|
target_include_directories(cereal PRIVATE ${CEREAL_SRC_DIR})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Test core
|
# Test core
|
||||||
|
|
||||||
add_executable(
|
SETUP_AND_ADD_TEST(algorithm entt/core/algorithm.cpp)
|
||||||
core
|
SETUP_AND_ADD_TEST(family entt/core/family.cpp)
|
||||||
$<TARGET_OBJECTS:odr>
|
SETUP_AND_ADD_TEST(hashed_string entt/core/hashed_string.cpp)
|
||||||
entt/core/family.cpp
|
SETUP_AND_ADD_TEST(ident entt/core/ident.cpp)
|
||||||
entt/core/hashed_string.cpp
|
SETUP_AND_ADD_TEST(monostate entt/core/monostate.cpp)
|
||||||
entt/core/ident.cpp
|
|
||||||
)
|
|
||||||
target_link_libraries(core PRIVATE gtest_main Threads::Threads)
|
|
||||||
add_test(NAME core COMMAND core)
|
|
||||||
|
|
||||||
# Test entity
|
# Test entity
|
||||||
|
|
||||||
add_executable(
|
SETUP_AND_ADD_TEST(actor entt/entity/actor.cpp)
|
||||||
entity
|
SETUP_AND_ADD_TEST(attachee entt/entity/attachee.cpp)
|
||||||
$<TARGET_OBJECTS:odr>
|
SETUP_AND_ADD_TEST(entity entt/entity/entity.cpp)
|
||||||
entt/entity/actor.cpp
|
SETUP_AND_ADD_TEST(helper entt/entity/helper.cpp)
|
||||||
entt/entity/registry.cpp
|
SETUP_AND_ADD_TEST(prototype entt/entity/prototype.cpp)
|
||||||
entt/entity/sparse_set.cpp
|
SETUP_AND_ADD_TEST(registry entt/entity/registry.cpp)
|
||||||
entt/entity/view.cpp
|
SETUP_AND_ADD_TEST(snapshot entt/entity/snapshot.cpp)
|
||||||
)
|
SETUP_AND_ADD_TEST(sparse_set entt/entity/sparse_set.cpp)
|
||||||
target_link_libraries(entity PRIVATE gtest_main Threads::Threads)
|
SETUP_AND_ADD_TEST(view entt/entity/view.cpp)
|
||||||
add_test(NAME entity COMMAND entity)
|
|
||||||
|
|
||||||
# Test locator
|
# Test locator
|
||||||
|
|
||||||
add_executable(
|
SETUP_AND_ADD_TEST(locator entt/locator/locator.cpp)
|
||||||
locator
|
|
||||||
$<TARGET_OBJECTS:odr>
|
|
||||||
entt/locator/locator.cpp
|
|
||||||
)
|
|
||||||
target_link_libraries(locator PRIVATE gtest_main Threads::Threads)
|
|
||||||
add_test(NAME locator COMMAND locator)
|
|
||||||
|
|
||||||
# Test process
|
# Test process
|
||||||
|
|
||||||
add_executable(
|
SETUP_AND_ADD_TEST(process entt/process/process.cpp)
|
||||||
process
|
SETUP_AND_ADD_TEST(scheduler entt/process/scheduler.cpp)
|
||||||
$<TARGET_OBJECTS:odr>
|
|
||||||
entt/process/process.cpp
|
|
||||||
entt/process/scheduler.cpp
|
|
||||||
)
|
|
||||||
target_link_libraries(process PRIVATE gtest_main Threads::Threads)
|
|
||||||
add_test(NAME process COMMAND process)
|
|
||||||
|
|
||||||
# Test resource
|
# Test resource
|
||||||
|
|
||||||
add_executable(
|
SETUP_AND_ADD_TEST(resource entt/resource/resource.cpp)
|
||||||
resource
|
|
||||||
$<TARGET_OBJECTS:odr>
|
|
||||||
entt/resource/resource.cpp
|
|
||||||
)
|
|
||||||
target_link_libraries(resource PRIVATE gtest_main Threads::Threads)
|
|
||||||
add_test(NAME resource COMMAND resource)
|
|
||||||
|
|
||||||
# Test signal
|
# Test signal
|
||||||
|
|
||||||
add_executable(
|
SETUP_AND_ADD_TEST(delegate entt/signal/delegate.cpp)
|
||||||
signal
|
SETUP_AND_ADD_TEST(dispatcher entt/signal/dispatcher.cpp)
|
||||||
$<TARGET_OBJECTS:odr>
|
SETUP_AND_ADD_TEST(emitter entt/signal/emitter.cpp)
|
||||||
entt/signal/bus.cpp
|
SETUP_AND_ADD_TEST(sigh entt/signal/sigh.cpp)
|
||||||
entt/signal/delegate.cpp
|
|
||||||
entt/signal/dispatcher.cpp
|
|
||||||
entt/signal/emitter.cpp
|
|
||||||
entt/signal/sigh.cpp
|
|
||||||
entt/signal/signal.cpp
|
|
||||||
)
|
|
||||||
target_link_libraries(signal PRIVATE gtest_main Threads::Threads)
|
|
||||||
add_test(NAME signal COMMAND signal)
|
|
||||||
|
|||||||
1009
test/benchmark/benchmark.cpp
Normal file
1009
test/benchmark/benchmark.cpp
Normal file
File diff suppressed because it is too large
Load Diff
40
test/entt/core/algorithm.cpp
Normal file
40
test/entt/core/algorithm.cpp
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#include <array>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <entt/core/algorithm.hpp>
|
||||||
|
|
||||||
|
TEST(Algorithm, StdSort) {
|
||||||
|
// well, I'm pretty sure it works, it's std::sort!!
|
||||||
|
std::array<int, 5> arr{{4, 1, 3, 2, 0}};
|
||||||
|
entt::StdSort sort;
|
||||||
|
|
||||||
|
sort(arr.begin(), arr.end());
|
||||||
|
|
||||||
|
for(typename decltype(arr)::size_type i = 0; i < (arr.size() - 1); ++i) {
|
||||||
|
ASSERT_LT(arr[i], arr[i+1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Algorithm, InsertionSort) {
|
||||||
|
std::array<int, 5> arr{{4, 1, 3, 2, 0}};
|
||||||
|
entt::InsertionSort sort;
|
||||||
|
|
||||||
|
sort(arr.begin(), arr.end());
|
||||||
|
|
||||||
|
for(typename decltype(arr)::size_type i = 0; i < (arr.size() - 1); ++i) {
|
||||||
|
ASSERT_LT(arr[i], arr[i+1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Algorithm, OneShotBubbleSort) {
|
||||||
|
std::array<int, 5> arr{{4, 1, 3, 2, 0}};
|
||||||
|
entt::OneShotBubbleSort sort;
|
||||||
|
|
||||||
|
sort(arr.begin(), arr.end());
|
||||||
|
sort(arr.begin(), arr.end());
|
||||||
|
sort(arr.begin(), arr.end());
|
||||||
|
sort(arr.begin(), arr.end());
|
||||||
|
|
||||||
|
for(typename decltype(arr)::size_type i = 0; i < (arr.size() - 1); ++i) {
|
||||||
|
ASSERT_LT(arr[i], arr[i+1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,37 +1,34 @@
|
|||||||
|
#include <type_traits>
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <entt/core/hashed_string.hpp>
|
#include <entt/core/hashed_string.hpp>
|
||||||
|
|
||||||
constexpr bool check(const char *str) {
|
|
||||||
using hash_type = entt::HashedString::hash_type;
|
|
||||||
|
|
||||||
return (static_cast<hash_type>(entt::HashedString{str}) == entt::HashedString{str}
|
|
||||||
&& static_cast<const char *>(entt::HashedString{str}) == str
|
|
||||||
&& entt::HashedString{str} == entt::HashedString{str}
|
|
||||||
&& !(entt::HashedString{str} != entt::HashedString{str}));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(HashedString, Constexprness) {
|
|
||||||
// how would you test a constepxr otherwise?
|
|
||||||
static_assert(check("foobar"), "!");
|
|
||||||
ASSERT_TRUE(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(HashedString, Functionalities) {
|
TEST(HashedString, Functionalities) {
|
||||||
using hash_type = entt::HashedString::hash_type;
|
using hash_type = entt::HashedString::hash_type;
|
||||||
|
|
||||||
const char *bar = "bar";
|
const char *bar = "bar";
|
||||||
|
|
||||||
auto fooHs = entt::HashedString("foo");
|
auto fooHs = entt::HashedString{"foo"};
|
||||||
auto barHs = entt::HashedString(bar);
|
auto barHs = entt::HashedString{bar};
|
||||||
|
|
||||||
ASSERT_NE(static_cast<hash_type>(fooHs), static_cast<hash_type>(barHs));
|
ASSERT_NE(static_cast<hash_type>(fooHs), static_cast<hash_type>(barHs));
|
||||||
ASSERT_EQ(static_cast<const char *>(fooHs), "foo");
|
ASSERT_STREQ(static_cast<const char *>(fooHs), "foo");
|
||||||
ASSERT_EQ(static_cast<const char *>(barHs), bar);
|
ASSERT_STREQ(static_cast<const char *>(barHs), bar);
|
||||||
|
|
||||||
ASSERT_TRUE(fooHs == fooHs);
|
ASSERT_EQ(fooHs, fooHs);
|
||||||
ASSERT_FALSE(fooHs == barHs);
|
ASSERT_NE(fooHs, barHs);
|
||||||
|
|
||||||
entt::HashedString hs{"foobar"};
|
entt::HashedString hs{"foobar"};
|
||||||
|
|
||||||
ASSERT_EQ(static_cast<hash_type>(hs), 0x85944171f73967e8);
|
ASSERT_EQ(static_cast<hash_type>(hs), 0x85944171f73967e8);
|
||||||
|
|
||||||
|
ASSERT_EQ(fooHs, "foo"_hs);
|
||||||
|
ASSERT_NE(barHs, "foo"_hs);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(HashedString, Constexprness) {
|
||||||
|
using hash_type = entt::HashedString::hash_type;
|
||||||
|
// how would you test a constexpr otherwise?
|
||||||
|
(void)std::integral_constant<hash_type, entt::HashedString{"quux"}>{};
|
||||||
|
(void)std::integral_constant<hash_type, "quux"_hs>{};
|
||||||
|
ASSERT_TRUE(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,32 +2,31 @@
|
|||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <entt/core/ident.hpp>
|
#include <entt/core/ident.hpp>
|
||||||
|
|
||||||
struct A {};
|
struct AType {};
|
||||||
struct B {};
|
struct AnotherType {};
|
||||||
|
|
||||||
TEST(Identifier, Uniqueness) {
|
TEST(Identifier, Uniqueness) {
|
||||||
constexpr auto ID = entt::ident<A, B>;
|
using ID = entt::Identifier<AType, AnotherType>;
|
||||||
constexpr A a;
|
constexpr AType anInstance;
|
||||||
constexpr B b;
|
constexpr AnotherType anotherInstance;
|
||||||
|
|
||||||
ASSERT_NE(ID.get<A>(), ID.get<B>());
|
ASSERT_NE(ID::get<AType>(), ID::get<AnotherType>());
|
||||||
ASSERT_EQ(ID.get<A>(), ID.get<decltype(a)>());
|
ASSERT_EQ(ID::get<AType>(), ID::get<decltype(anInstance)>());
|
||||||
ASSERT_NE(ID.get<A>(), ID.get<decltype(b)>());
|
ASSERT_NE(ID::get<AType>(), ID::get<decltype(anotherInstance)>());
|
||||||
ASSERT_EQ(ID.get<A>(), ID.get<A>());
|
ASSERT_EQ(ID::get<AType>(), ID::get<AType>());
|
||||||
ASSERT_EQ(ID.get<B>(), ID.get<B>());
|
ASSERT_EQ(ID::get<AnotherType>(), ID::get<AnotherType>());
|
||||||
|
|
||||||
// test uses in constant expressions
|
// test uses in constant expressions
|
||||||
switch(ID.get<B>()) {
|
switch(ID::get<AnotherType>()) {
|
||||||
case ID.get<A>():
|
case ID::get<AType>():
|
||||||
FAIL();
|
FAIL();
|
||||||
break;
|
case ID::get<AnotherType>():
|
||||||
case ID.get<B>():
|
|
||||||
SUCCEED();
|
SUCCEED();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(Identifier, SingleType) {
|
TEST(Identifier, SingleType) {
|
||||||
constexpr auto ID = entt::ident<A>;
|
using ID = entt::Identifier<AType>;
|
||||||
std::integral_constant<decltype(ID)::identifier_type, ID.get()> ic;
|
std::integral_constant<ID::identifier_type, ID::get<AType>()> ic;
|
||||||
(void)ic;
|
(void)ic;
|
||||||
}
|
}
|
||||||
|
|||||||
20
test/entt/core/monostate.cpp
Normal file
20
test/entt/core/monostate.cpp
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <entt/core/hashed_string.hpp>
|
||||||
|
#include <entt/core/monostate.hpp>
|
||||||
|
|
||||||
|
TEST(Monostate, Functionalities) {
|
||||||
|
const bool bPre = entt::Monostate<entt::HashedString{"foobar"}>{};
|
||||||
|
const int iPre = entt::Monostate<"foobar"_hs>{};
|
||||||
|
|
||||||
|
ASSERT_FALSE(bPre);
|
||||||
|
ASSERT_EQ(iPre, int{});
|
||||||
|
|
||||||
|
entt::Monostate<"foobar"_hs>{} = true;
|
||||||
|
entt::Monostate<"foobar"_hs>{} = 42;
|
||||||
|
|
||||||
|
const bool &bPost = entt::Monostate<"foobar"_hs>{};
|
||||||
|
const int &iPost = entt::Monostate<entt::HashedString{"foobar"}>{};
|
||||||
|
|
||||||
|
ASSERT_TRUE(bPost);
|
||||||
|
ASSERT_EQ(iPost, 42);
|
||||||
|
}
|
||||||
@@ -3,55 +3,74 @@
|
|||||||
#include <entt/entity/actor.hpp>
|
#include <entt/entity/actor.hpp>
|
||||||
#include <entt/entity/registry.hpp>
|
#include <entt/entity/registry.hpp>
|
||||||
|
|
||||||
struct TestActor: entt::DefaultActor<unsigned int> {
|
struct ActorComponent final {};
|
||||||
using entt::DefaultActor<unsigned int>::DefaultActor;
|
struct ActorTag final {};
|
||||||
void update(unsigned int) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Position final {};
|
TEST(Actor, Component) {
|
||||||
struct Velocity final {};
|
|
||||||
|
|
||||||
TEST(Actor, Functionalities) {
|
|
||||||
entt::DefaultRegistry registry;
|
entt::DefaultRegistry registry;
|
||||||
TestActor *actor = new TestActor{registry};
|
entt::DefaultActor actor{registry};
|
||||||
const auto &cactor = *actor;
|
const auto &cactor = actor;
|
||||||
|
|
||||||
ASSERT_EQ(®istry, &actor->registry());
|
ASSERT_EQ(®istry, &actor.registry());
|
||||||
ASSERT_EQ(®istry, &cactor.registry());
|
ASSERT_EQ(®istry, &cactor.registry());
|
||||||
ASSERT_TRUE(registry.empty<Position>());
|
ASSERT_TRUE(registry.empty<ActorComponent>());
|
||||||
ASSERT_TRUE(registry.empty<Velocity>());
|
|
||||||
ASSERT_FALSE(registry.empty());
|
ASSERT_FALSE(registry.empty());
|
||||||
ASSERT_FALSE(actor->has<Position>());
|
ASSERT_FALSE(actor.has<ActorComponent>());
|
||||||
ASSERT_FALSE(actor->has<Velocity>());
|
|
||||||
|
|
||||||
const auto &position = actor->set<Position>();
|
const auto &component = actor.assign<ActorComponent>();
|
||||||
|
|
||||||
ASSERT_EQ(&position, &actor->get<Position>());
|
ASSERT_EQ(&component, &actor.get<ActorComponent>());
|
||||||
ASSERT_EQ(&position, &cactor.get<Position>());
|
ASSERT_EQ(&component, &cactor.get<ActorComponent>());
|
||||||
ASSERT_FALSE(registry.empty<Position>());
|
ASSERT_FALSE(registry.empty<ActorComponent>());
|
||||||
ASSERT_TRUE(registry.empty<Velocity>());
|
|
||||||
ASSERT_FALSE(registry.empty());
|
ASSERT_FALSE(registry.empty());
|
||||||
ASSERT_TRUE(actor->has<Position>());
|
ASSERT_TRUE(actor.has<ActorComponent>());
|
||||||
ASSERT_FALSE(actor->has<Velocity>());
|
|
||||||
|
|
||||||
actor->unset<Position>();
|
actor.remove<ActorComponent>();
|
||||||
|
|
||||||
ASSERT_TRUE(registry.empty<Position>());
|
ASSERT_TRUE(registry.empty<ActorComponent>());
|
||||||
ASSERT_TRUE(registry.empty<Velocity>());
|
|
||||||
ASSERT_FALSE(registry.empty());
|
ASSERT_FALSE(registry.empty());
|
||||||
ASSERT_FALSE(actor->has<Position>());
|
ASSERT_FALSE(actor.has<ActorComponent>());
|
||||||
ASSERT_FALSE(actor->has<Velocity>());
|
}
|
||||||
|
|
||||||
actor->set<Position>();
|
TEST(Actor, Tag) {
|
||||||
actor->set<Velocity>();
|
entt::DefaultRegistry registry;
|
||||||
|
entt::DefaultActor actor{registry};
|
||||||
|
const auto &cactor = actor;
|
||||||
|
|
||||||
|
ASSERT_EQ(®istry, &actor.registry());
|
||||||
|
ASSERT_EQ(®istry, &cactor.registry());
|
||||||
|
ASSERT_FALSE(registry.has<ActorTag>());
|
||||||
|
ASSERT_FALSE(actor.has<ActorTag>(entt::tag_t{}));
|
||||||
|
|
||||||
|
const auto &tag = actor.assign<ActorTag>(entt::tag_t{});
|
||||||
|
|
||||||
|
ASSERT_EQ(&tag, &actor.get<ActorTag>(entt::tag_t{}));
|
||||||
|
ASSERT_EQ(&tag, &cactor.get<ActorTag>(entt::tag_t{}));
|
||||||
|
ASSERT_TRUE(registry.has<ActorTag>());
|
||||||
ASSERT_FALSE(registry.empty());
|
ASSERT_FALSE(registry.empty());
|
||||||
ASSERT_FALSE(registry.empty<Position>());
|
ASSERT_TRUE(actor.has<ActorTag>(entt::tag_t{}));
|
||||||
ASSERT_FALSE(registry.empty<Velocity>());
|
|
||||||
|
actor.remove<ActorTag>(entt::tag_t{});
|
||||||
|
|
||||||
|
ASSERT_FALSE(registry.has<ActorTag>());
|
||||||
|
ASSERT_FALSE(registry.empty());
|
||||||
|
ASSERT_FALSE(actor.has<ActorTag>(entt::tag_t{}));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Actor, EntityLifetime) {
|
||||||
|
entt::DefaultRegistry registry;
|
||||||
|
auto *actor = new entt::DefaultActor{registry};
|
||||||
|
actor->assign<ActorComponent>();
|
||||||
|
|
||||||
|
ASSERT_FALSE(registry.empty<ActorComponent>());
|
||||||
|
ASSERT_FALSE(registry.empty());
|
||||||
|
|
||||||
|
registry.each([actor](const auto entity) {
|
||||||
|
ASSERT_EQ(actor->entity(), entity);
|
||||||
|
});
|
||||||
|
|
||||||
delete actor;
|
delete actor;
|
||||||
|
|
||||||
|
ASSERT_TRUE(registry.empty<ActorComponent>());
|
||||||
ASSERT_TRUE(registry.empty());
|
ASSERT_TRUE(registry.empty());
|
||||||
ASSERT_TRUE(registry.empty<Position>());
|
|
||||||
ASSERT_TRUE(registry.empty<Velocity>());
|
|
||||||
}
|
}
|
||||||
|
|||||||
69
test/entt/entity/attachee.cpp
Normal file
69
test/entt/entity/attachee.cpp
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
#include <unordered_set>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <entt/entity/attachee.hpp>
|
||||||
|
|
||||||
|
TEST(AttacheeNoType, Functionalities) {
|
||||||
|
entt::Attachee<std::uint64_t> attachee;
|
||||||
|
|
||||||
|
attachee.construct(42u);
|
||||||
|
|
||||||
|
ASSERT_EQ(attachee.get(), 42u);
|
||||||
|
|
||||||
|
attachee.destroy();
|
||||||
|
|
||||||
|
ASSERT_NE(attachee.get(), 42u);
|
||||||
|
|
||||||
|
(void)entt::Attachee<std::uint64_t>{std::move(attachee)};
|
||||||
|
entt::Attachee<std::uint64_t> other;
|
||||||
|
other = std::move(attachee);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AttacheeWithType, Functionalities) {
|
||||||
|
entt::Attachee<std::uint64_t, int> attachee;
|
||||||
|
const auto &cattachee = attachee;
|
||||||
|
|
||||||
|
attachee.construct(42u, 3);
|
||||||
|
|
||||||
|
ASSERT_EQ(attachee.get(), 3);
|
||||||
|
ASSERT_EQ(cattachee.get(), 3);
|
||||||
|
ASSERT_EQ(attachee.Attachee<std::uint64_t>::get(), 42u);
|
||||||
|
|
||||||
|
attachee.move(0u);
|
||||||
|
|
||||||
|
ASSERT_EQ(attachee.get(), 3);
|
||||||
|
ASSERT_EQ(cattachee.get(), 3);
|
||||||
|
ASSERT_EQ(attachee.Attachee<std::uint64_t>::get(), 0u);
|
||||||
|
|
||||||
|
attachee.destroy();
|
||||||
|
|
||||||
|
ASSERT_NE(attachee.Attachee<std::uint64_t>::get(), 0u);
|
||||||
|
ASSERT_NE(attachee.Attachee<std::uint64_t>::get(), 42u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AttacheeWithType, AggregatesMustWork) {
|
||||||
|
struct AggregateType { int value; };
|
||||||
|
// the goal of this test is to enforce the requirements for aggregate types
|
||||||
|
entt::Attachee<std::uint64_t, AggregateType>{}.construct(0, 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AttacheeWithType, TypesFromStandardTemplateLibraryMustWork) {
|
||||||
|
// see #37 - this test shouldn't crash, that's all
|
||||||
|
entt::Attachee<std::uint64_t, std::unordered_set<int>> attachee;
|
||||||
|
attachee.construct(0).insert(42);
|
||||||
|
attachee.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AttacheeWithType, MoveOnlyComponent) {
|
||||||
|
struct MoveOnlyComponent {
|
||||||
|
MoveOnlyComponent() = default;
|
||||||
|
~MoveOnlyComponent() = default;
|
||||||
|
MoveOnlyComponent(const MoveOnlyComponent &) = delete;
|
||||||
|
MoveOnlyComponent(MoveOnlyComponent &&) = default;
|
||||||
|
MoveOnlyComponent & operator=(const MoveOnlyComponent &) = delete;
|
||||||
|
MoveOnlyComponent & operator=(MoveOnlyComponent &&) = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
// it's purpose is to ensure that move only components are always accepted
|
||||||
|
entt::Attachee<std::uint64_t, MoveOnlyComponent> attachee;
|
||||||
|
(void)attachee;
|
||||||
|
}
|
||||||
@@ -1,355 +0,0 @@
|
|||||||
#include <gtest/gtest.h>
|
|
||||||
#include <iostream>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <chrono>
|
|
||||||
#include <vector>
|
|
||||||
#include <entt/entity/registry.hpp>
|
|
||||||
|
|
||||||
struct Position {
|
|
||||||
uint64_t x;
|
|
||||||
uint64_t y;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Velocity {
|
|
||||||
uint64_t x;
|
|
||||||
uint64_t y;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<std::size_t>
|
|
||||||
struct Comp {};
|
|
||||||
|
|
||||||
struct Timer final {
|
|
||||||
Timer(): start{std::chrono::system_clock::now()} {}
|
|
||||||
|
|
||||||
void elapsed() {
|
|
||||||
auto now = std::chrono::system_clock::now();
|
|
||||||
std::cout << std::chrono::duration<double>(now - start).count() << " seconds" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::chrono::time_point<std::chrono::system_clock> start;
|
|
||||||
};
|
|
||||||
|
|
||||||
TEST(Benchmark, Construct) {
|
|
||||||
entt::DefaultRegistry registry;
|
|
||||||
|
|
||||||
std::cout << "Constructing 10000000 entities" << std::endl;
|
|
||||||
|
|
||||||
Timer timer;
|
|
||||||
|
|
||||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
|
||||||
registry.create();
|
|
||||||
}
|
|
||||||
|
|
||||||
timer.elapsed();
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Benchmark, Destroy) {
|
|
||||||
entt::DefaultRegistry registry;
|
|
||||||
std::vector<entt::DefaultRegistry::entity_type> entities{};
|
|
||||||
|
|
||||||
std::cout << "Destroying 10000000 entities" << std::endl;
|
|
||||||
|
|
||||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
|
||||||
entities.push_back(registry.create());
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer timer;
|
|
||||||
|
|
||||||
for(auto entity: entities) {
|
|
||||||
registry.destroy(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
timer.elapsed();
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Benchmark, IterateCreateDeleteSingleComponent) {
|
|
||||||
entt::DefaultRegistry registry;
|
|
||||||
|
|
||||||
std::cout << "Looping 10000 times creating and deleting a random number of entities" << std::endl;
|
|
||||||
|
|
||||||
Timer timer;
|
|
||||||
|
|
||||||
auto view = registry.view<Position>();
|
|
||||||
|
|
||||||
for(int i = 0; i < 10000; i++) {
|
|
||||||
for(int j = 0; j < 10000; j++) {
|
|
||||||
registry.create<Position>();
|
|
||||||
}
|
|
||||||
|
|
||||||
for(auto entity: view) {
|
|
||||||
if(rand() % 2 == 0) {
|
|
||||||
registry.destroy(entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
timer.elapsed();
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Benchmark, IterateSingleComponent10M) {
|
|
||||||
entt::DefaultRegistry registry;
|
|
||||||
|
|
||||||
std::cout << "Iterating over 10000000 entities, one component" << std::endl;
|
|
||||||
|
|
||||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
|
||||||
registry.create<Position>();
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer timer;
|
|
||||||
registry.view<Position>().each([](auto, auto &) {});
|
|
||||||
timer.elapsed();
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Benchmark, IterateTwoComponents10M) {
|
|
||||||
entt::DefaultRegistry registry;
|
|
||||||
|
|
||||||
std::cout << "Iterating over 10000000 entities, two components" << std::endl;
|
|
||||||
|
|
||||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
|
||||||
registry.create<Position, Velocity>();
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer timer;
|
|
||||||
registry.view<Position, Velocity>().each([](auto, auto &...) {});
|
|
||||||
timer.elapsed();
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Benchmark, IterateTwoComponents10MHalf) {
|
|
||||||
entt::DefaultRegistry registry;
|
|
||||||
|
|
||||||
std::cout << "Iterating over 10000000 entities, two components, half of the entities have all the components" << std::endl;
|
|
||||||
|
|
||||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
|
||||||
auto entity = registry.create<Velocity>();
|
|
||||||
if(i % 2) { registry.assign<Position>(entity); }
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer timer;
|
|
||||||
registry.view<Position, Velocity>().each([](auto, auto &...) {});
|
|
||||||
timer.elapsed();
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Benchmark, IterateTwoComponents10MOne) {
|
|
||||||
entt::DefaultRegistry registry;
|
|
||||||
|
|
||||||
std::cout << "Iterating over 10000000 entities, two components, only one entity has all the components" << std::endl;
|
|
||||||
|
|
||||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
|
||||||
auto entity = registry.create<Velocity>();
|
|
||||||
if(i == 5000000L) { registry.assign<Position>(entity); }
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer timer;
|
|
||||||
registry.view<Position, Velocity>().each([](auto, auto &...) {});
|
|
||||||
timer.elapsed();
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Benchmark, IterateTwoComponentsPersistent10M) {
|
|
||||||
entt::DefaultRegistry registry;
|
|
||||||
registry.prepare<Position, Velocity>();
|
|
||||||
|
|
||||||
std::cout << "Iterating over 10000000 entities, two components, persistent view" << std::endl;
|
|
||||||
|
|
||||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
|
||||||
registry.create<Position, Velocity>();
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer timer;
|
|
||||||
registry.persistent<Position, Velocity>().each([](auto, auto &...) {});
|
|
||||||
timer.elapsed();
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Benchmark, IterateTwoComponentsPersistent10MHalf) {
|
|
||||||
entt::DefaultRegistry registry;
|
|
||||||
registry.prepare<Position, Velocity>();
|
|
||||||
|
|
||||||
std::cout << "Iterating over 10000000 entities, two components, persistent view, half of the entities have all the components" << std::endl;
|
|
||||||
|
|
||||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
|
||||||
auto entity = registry.create<Velocity>();
|
|
||||||
if(i % 2) { registry.assign<Position>(entity); }
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer timer;
|
|
||||||
registry.persistent<Position, Velocity>().each([](auto, auto &...) {});
|
|
||||||
timer.elapsed();
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Benchmark, IterateTwoComponentsPersistent10MOne) {
|
|
||||||
entt::DefaultRegistry registry;
|
|
||||||
registry.prepare<Position, Velocity>();
|
|
||||||
|
|
||||||
std::cout << "Iterating over 10000000 entities, two components, persistent view, only one entity has all the components" << std::endl;
|
|
||||||
|
|
||||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
|
||||||
auto entity = registry.create<Velocity>();
|
|
||||||
if(i == 5000000L) { registry.assign<Position>(entity); }
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer timer;
|
|
||||||
registry.persistent<Position, Velocity>().each([](auto, auto &...) {});
|
|
||||||
timer.elapsed();
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Benchmark, IterateFiveComponents10M) {
|
|
||||||
entt::DefaultRegistry registry;
|
|
||||||
|
|
||||||
std::cout << "Iterating over 10000000 entities, five components" << std::endl;
|
|
||||||
|
|
||||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
|
||||||
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer timer;
|
|
||||||
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>().each([](auto, auto &...) {});
|
|
||||||
timer.elapsed();
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Benchmark, IterateTenComponents10M) {
|
|
||||||
entt::DefaultRegistry registry;
|
|
||||||
|
|
||||||
std::cout << "Iterating over 10000000 entities, ten components" << std::endl;
|
|
||||||
|
|
||||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
|
||||||
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer timer;
|
|
||||||
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
|
|
||||||
timer.elapsed();
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Benchmark, IterateTenComponents10MHalf) {
|
|
||||||
entt::DefaultRegistry registry;
|
|
||||||
|
|
||||||
std::cout << "Iterating over 10000000 entities, ten components, half of the entities have all the components" << std::endl;
|
|
||||||
|
|
||||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
|
||||||
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
|
||||||
if(i % 2) { registry.assign<Position>(entity); }
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer timer;
|
|
||||||
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
|
|
||||||
timer.elapsed();
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Benchmark, IterateTenComponents10MOne) {
|
|
||||||
entt::DefaultRegistry registry;
|
|
||||||
|
|
||||||
std::cout << "Iterating over 10000000 entities, ten components, only one entity has all the components" << std::endl;
|
|
||||||
|
|
||||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
|
||||||
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
|
||||||
if(i == 5000000L) { registry.assign<Position>(entity); }
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer timer;
|
|
||||||
registry.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
|
|
||||||
timer.elapsed();
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Benchmark, IterateFiveComponentsPersistent10M) {
|
|
||||||
entt::DefaultRegistry registry;
|
|
||||||
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
|
|
||||||
|
|
||||||
std::cout << "Iterating over 10000000 entities, five components, persistent view" << std::endl;
|
|
||||||
|
|
||||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
|
||||||
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer timer;
|
|
||||||
registry.persistent<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>().each([](auto, auto &...) {});
|
|
||||||
timer.elapsed();
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Benchmark, IterateTenComponentsPersistent10M) {
|
|
||||||
entt::DefaultRegistry registry;
|
|
||||||
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
|
||||||
|
|
||||||
std::cout << "Iterating over 10000000 entities, ten components, persistent view" << std::endl;
|
|
||||||
|
|
||||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
|
||||||
registry.create<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer timer;
|
|
||||||
registry.persistent<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
|
|
||||||
timer.elapsed();
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Benchmark, IterateTenComponentsPersistent10MHalf) {
|
|
||||||
entt::DefaultRegistry registry;
|
|
||||||
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
|
||||||
|
|
||||||
std::cout << "Iterating over 10000000 entities, ten components, persistent view, half of the entities have all the components" << std::endl;
|
|
||||||
|
|
||||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
|
||||||
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
|
||||||
if(i % 2) { registry.assign<Position>(entity); }
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer timer;
|
|
||||||
registry.persistent<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
|
|
||||||
timer.elapsed();
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Benchmark, IterateTenComponentsPersistent10MOne) {
|
|
||||||
entt::DefaultRegistry registry;
|
|
||||||
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
|
||||||
|
|
||||||
std::cout << "Iterating over 10000000 entities, ten components, persistent view, only one entity has all the components" << std::endl;
|
|
||||||
|
|
||||||
for(uint64_t i = 0; i < 10000000L; i++) {
|
|
||||||
auto entity = registry.create<Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
|
|
||||||
if(i == 5000000L) { registry.assign<Position>(entity); }
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer timer;
|
|
||||||
registry.persistent<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>().each([](auto, auto &...) {});
|
|
||||||
timer.elapsed();
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Benchmark, SortSingle) {
|
|
||||||
entt::DefaultRegistry registry;
|
|
||||||
std::vector<entt::DefaultRegistry::entity_type> entities{};
|
|
||||||
|
|
||||||
std::cout << "Sort 150000 entities, one component" << std::endl;
|
|
||||||
|
|
||||||
for(uint64_t i = 0; i < 150000L; i++) {
|
|
||||||
auto entity = registry.create<Position>({ i, i });
|
|
||||||
entities.push_back(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer timer;
|
|
||||||
|
|
||||||
registry.sort<Position>([](const auto &lhs, const auto &rhs) {
|
|
||||||
return lhs.x < rhs.x && lhs.y < rhs.y;
|
|
||||||
});
|
|
||||||
|
|
||||||
timer.elapsed();
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Benchmark, SortMulti) {
|
|
||||||
entt::DefaultRegistry registry;
|
|
||||||
std::vector<entt::DefaultRegistry::entity_type> entities{};
|
|
||||||
|
|
||||||
std::cout << "Sort 150000 entities, two components" << std::endl;
|
|
||||||
|
|
||||||
for(uint64_t i = 0; i < 150000L; i++) {
|
|
||||||
auto entity = registry.create<Position, Velocity>({ i, i }, { i, i });
|
|
||||||
entities.push_back(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
registry.sort<Position>([](const auto &lhs, const auto &rhs) {
|
|
||||||
return lhs.x < rhs.x && lhs.y < rhs.y;
|
|
||||||
});
|
|
||||||
|
|
||||||
Timer timer;
|
|
||||||
|
|
||||||
registry.sort<Velocity, Position>();
|
|
||||||
|
|
||||||
timer.elapsed();
|
|
||||||
}
|
|
||||||
27
test/entt/entity/entity.cpp
Normal file
27
test/entt/entity/entity.cpp
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#include <functional>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <entt/entity/entity.hpp>
|
||||||
|
#include <entt/entity/registry.hpp>
|
||||||
|
|
||||||
|
template<bool>
|
||||||
|
struct S {};
|
||||||
|
|
||||||
|
TEST(Traits, Null) {
|
||||||
|
entt::DefaultRegistry registry{};
|
||||||
|
|
||||||
|
const auto entity = registry.create();
|
||||||
|
registry.assign<int>(entity, 42);
|
||||||
|
|
||||||
|
ASSERT_TRUE(~typename entt::DefaultRegistry::entity_type{} == entt::null);
|
||||||
|
|
||||||
|
ASSERT_TRUE(entt::null == entt::null);
|
||||||
|
ASSERT_FALSE(entt::null != entt::null);
|
||||||
|
|
||||||
|
ASSERT_FALSE(entity == entt::null);
|
||||||
|
ASSERT_FALSE(entt::null == entity);
|
||||||
|
|
||||||
|
ASSERT_TRUE(entity != entt::null);
|
||||||
|
ASSERT_TRUE(entt::null != entity);
|
||||||
|
|
||||||
|
ASSERT_FALSE(registry.valid(entt::null));
|
||||||
|
}
|
||||||
75
test/entt/entity/helper.cpp
Normal file
75
test/entt/entity/helper.cpp
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <entt/core/hashed_string.hpp>
|
||||||
|
#include <entt/entity/helper.hpp>
|
||||||
|
#include <entt/entity/registry.hpp>
|
||||||
|
|
||||||
|
TEST(Helper, Dependency) {
|
||||||
|
entt::DefaultRegistry registry;
|
||||||
|
const auto entity = registry.create();
|
||||||
|
entt::connect<double, float>(registry.construction<int>());
|
||||||
|
|
||||||
|
ASSERT_FALSE(registry.has<double>(entity));
|
||||||
|
ASSERT_FALSE(registry.has<float>(entity));
|
||||||
|
|
||||||
|
registry.assign<char>(entity);
|
||||||
|
|
||||||
|
ASSERT_FALSE(registry.has<double>(entity));
|
||||||
|
ASSERT_FALSE(registry.has<float>(entity));
|
||||||
|
|
||||||
|
registry.assign<int>(entity);
|
||||||
|
|
||||||
|
ASSERT_TRUE(registry.has<double>(entity));
|
||||||
|
ASSERT_TRUE(registry.has<float>(entity));
|
||||||
|
ASSERT_EQ(registry.get<double>(entity), .0);
|
||||||
|
ASSERT_EQ(registry.get<float>(entity), .0f);
|
||||||
|
|
||||||
|
registry.get<double>(entity) = .3;
|
||||||
|
registry.get<float>(entity) = .1f;
|
||||||
|
registry.remove<int>(entity);
|
||||||
|
registry.assign<int>(entity);
|
||||||
|
|
||||||
|
ASSERT_EQ(registry.get<double>(entity), .3);
|
||||||
|
ASSERT_EQ(registry.get<float>(entity), .1f);
|
||||||
|
|
||||||
|
registry.remove<int>(entity);
|
||||||
|
registry.remove<float>(entity);
|
||||||
|
registry.assign<int>(entity);
|
||||||
|
|
||||||
|
ASSERT_TRUE(registry.has<float>(entity));
|
||||||
|
ASSERT_EQ(registry.get<double>(entity), .3);
|
||||||
|
ASSERT_EQ(registry.get<float>(entity), .0f);
|
||||||
|
|
||||||
|
registry.remove<int>(entity);
|
||||||
|
registry.remove<double>(entity);
|
||||||
|
registry.remove<float>(entity);
|
||||||
|
entt::disconnect<double, float>(registry.construction<int>());
|
||||||
|
registry.assign<int>(entity);
|
||||||
|
|
||||||
|
ASSERT_FALSE(registry.has<double>(entity));
|
||||||
|
ASSERT_FALSE(registry.has<float>(entity));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Helper, Label) {
|
||||||
|
entt::DefaultRegistry registry;
|
||||||
|
const auto entity = registry.create();
|
||||||
|
registry.assign<entt::label<"foobar"_hs>>(entity);
|
||||||
|
registry.assign<int>(entity, 42);
|
||||||
|
int counter{};
|
||||||
|
|
||||||
|
ASSERT_FALSE(registry.has<entt::label<"barfoo"_hs>>(entity));
|
||||||
|
ASSERT_TRUE(registry.has<entt::label<"foobar"_hs>>(entity));
|
||||||
|
|
||||||
|
for(auto entity: registry.view<int, entt::label<"foobar"_hs>>()) {
|
||||||
|
(void)entity;
|
||||||
|
++counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_NE(counter, 0);
|
||||||
|
|
||||||
|
for(auto entity: registry.view<entt::label<"foobar"_hs>>()) {
|
||||||
|
(void)entity;
|
||||||
|
--counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_EQ(counter, 0);
|
||||||
|
}
|
||||||
153
test/entt/entity/prototype.cpp
Normal file
153
test/entt/entity/prototype.cpp
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <entt/entity/prototype.hpp>
|
||||||
|
#include <entt/entity/registry.hpp>
|
||||||
|
|
||||||
|
TEST(Prototype, SameRegistry) {
|
||||||
|
entt::DefaultRegistry registry;
|
||||||
|
entt::DefaultPrototype prototype{registry};
|
||||||
|
const auto &cprototype = prototype;
|
||||||
|
|
||||||
|
ASSERT_FALSE(registry.empty());
|
||||||
|
ASSERT_FALSE((prototype.has<int, char>()));
|
||||||
|
|
||||||
|
ASSERT_EQ(prototype.set<int>(2), 2);
|
||||||
|
ASSERT_EQ(prototype.set<int>(3), 3);
|
||||||
|
ASSERT_EQ(prototype.set<char>('c'), 'c');
|
||||||
|
|
||||||
|
ASSERT_EQ(prototype.get<int>(), 3);
|
||||||
|
ASSERT_EQ(cprototype.get<char>(), 'c');
|
||||||
|
ASSERT_EQ(std::get<0>(prototype.get<int, char>()), 3);
|
||||||
|
ASSERT_EQ(std::get<1>(cprototype.get<int, char>()), 'c');
|
||||||
|
|
||||||
|
const auto e0 = prototype.create();
|
||||||
|
|
||||||
|
ASSERT_TRUE((prototype.has<int, char>()));
|
||||||
|
ASSERT_FALSE(registry.orphan(e0));
|
||||||
|
|
||||||
|
const auto e1 = prototype();
|
||||||
|
prototype(e0);
|
||||||
|
|
||||||
|
ASSERT_FALSE(registry.orphan(e0));
|
||||||
|
ASSERT_FALSE(registry.orphan(e1));
|
||||||
|
|
||||||
|
ASSERT_TRUE((registry.has<int, char>(e0)));
|
||||||
|
ASSERT_TRUE((registry.has<int, char>(e1)));
|
||||||
|
|
||||||
|
registry.remove<int>(e0);
|
||||||
|
registry.remove<int>(e1);
|
||||||
|
prototype.unset<int>();
|
||||||
|
|
||||||
|
ASSERT_FALSE((prototype.has<int, char>()));
|
||||||
|
ASSERT_FALSE((prototype.has<int>()));
|
||||||
|
ASSERT_TRUE((prototype.has<char>()));
|
||||||
|
|
||||||
|
prototype(e0);
|
||||||
|
prototype(e1);
|
||||||
|
|
||||||
|
ASSERT_FALSE(registry.has<int>(e0));
|
||||||
|
ASSERT_FALSE(registry.has<int>(e1));
|
||||||
|
|
||||||
|
ASSERT_EQ(registry.get<char>(e0), 'c');
|
||||||
|
ASSERT_EQ(registry.get<char>(e1), 'c');
|
||||||
|
|
||||||
|
registry.get<char>(e0) = '*';
|
||||||
|
prototype.assign(e0);
|
||||||
|
|
||||||
|
ASSERT_EQ(registry.get<char>(e0), '*');
|
||||||
|
|
||||||
|
registry.get<char>(e1) = '*';
|
||||||
|
prototype.accommodate(e1);
|
||||||
|
|
||||||
|
ASSERT_EQ(registry.get<char>(e1), 'c');
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Prototype, OtherRegistry) {
|
||||||
|
entt::DefaultRegistry registry;
|
||||||
|
entt::DefaultRegistry repository;
|
||||||
|
entt::DefaultPrototype prototype{repository};
|
||||||
|
const auto &cprototype = prototype;
|
||||||
|
|
||||||
|
ASSERT_TRUE(registry.empty());
|
||||||
|
ASSERT_FALSE((prototype.has<int, char>()));
|
||||||
|
|
||||||
|
ASSERT_EQ(prototype.set<int>(2), 2);
|
||||||
|
ASSERT_EQ(prototype.set<int>(3), 3);
|
||||||
|
ASSERT_EQ(prototype.set<char>('c'), 'c');
|
||||||
|
|
||||||
|
ASSERT_EQ(prototype.get<int>(), 3);
|
||||||
|
ASSERT_EQ(cprototype.get<char>(), 'c');
|
||||||
|
ASSERT_EQ(std::get<0>(prototype.get<int, char>()), 3);
|
||||||
|
ASSERT_EQ(std::get<1>(cprototype.get<int, char>()), 'c');
|
||||||
|
|
||||||
|
const auto e0 = prototype.create(registry);
|
||||||
|
|
||||||
|
ASSERT_TRUE((prototype.has<int, char>()));
|
||||||
|
ASSERT_FALSE(registry.orphan(e0));
|
||||||
|
|
||||||
|
const auto e1 = prototype(registry);
|
||||||
|
prototype(registry, e0);
|
||||||
|
|
||||||
|
ASSERT_FALSE(registry.orphan(e0));
|
||||||
|
ASSERT_FALSE(registry.orphan(e1));
|
||||||
|
|
||||||
|
ASSERT_TRUE((registry.has<int, char>(e0)));
|
||||||
|
ASSERT_TRUE((registry.has<int, char>(e1)));
|
||||||
|
|
||||||
|
registry.remove<int>(e0);
|
||||||
|
registry.remove<int>(e1);
|
||||||
|
prototype.unset<int>();
|
||||||
|
|
||||||
|
ASSERT_FALSE((prototype.has<int, char>()));
|
||||||
|
ASSERT_FALSE((prototype.has<int>()));
|
||||||
|
ASSERT_TRUE((prototype.has<char>()));
|
||||||
|
|
||||||
|
prototype(registry, e0);
|
||||||
|
prototype(registry, e1);
|
||||||
|
|
||||||
|
ASSERT_FALSE(registry.has<int>(e0));
|
||||||
|
ASSERT_FALSE(registry.has<int>(e1));
|
||||||
|
|
||||||
|
ASSERT_EQ(registry.get<char>(e0), 'c');
|
||||||
|
ASSERT_EQ(registry.get<char>(e1), 'c');
|
||||||
|
|
||||||
|
registry.get<char>(e0) = '*';
|
||||||
|
prototype.assign(registry, e0);
|
||||||
|
|
||||||
|
ASSERT_EQ(registry.get<char>(e0), '*');
|
||||||
|
|
||||||
|
registry.get<char>(e1) = '*';
|
||||||
|
prototype.accommodate(registry, e1);
|
||||||
|
|
||||||
|
ASSERT_EQ(registry.get<char>(e1), 'c');
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Prototype, RAII) {
|
||||||
|
entt::DefaultRegistry registry;
|
||||||
|
|
||||||
|
{
|
||||||
|
entt::DefaultPrototype prototype{registry};
|
||||||
|
prototype.set<int>(0);
|
||||||
|
|
||||||
|
ASSERT_FALSE(registry.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_TRUE(registry.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Prototype, MoveConstructionAssignment) {
|
||||||
|
entt::DefaultRegistry registry;
|
||||||
|
|
||||||
|
entt::DefaultPrototype prototype{registry};
|
||||||
|
prototype.set<int>(0);
|
||||||
|
auto other{std::move(prototype)};
|
||||||
|
const auto e0 = other();
|
||||||
|
|
||||||
|
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{2});
|
||||||
|
ASSERT_TRUE(registry.has<int>(e0));
|
||||||
|
|
||||||
|
prototype = std::move(other);
|
||||||
|
const auto e1 = prototype();
|
||||||
|
|
||||||
|
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{3});
|
||||||
|
ASSERT_TRUE(registry.has<int>(e1));
|
||||||
|
}
|
||||||
@@ -1,95 +1,170 @@
|
|||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <type_traits>
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
#include <entt/entity/entt_traits.hpp>
|
||||||
#include <entt/entity/registry.hpp>
|
#include <entt/entity/registry.hpp>
|
||||||
|
|
||||||
|
struct Listener {
|
||||||
|
template<typename Component>
|
||||||
|
void incrComponent(entt::DefaultRegistry ®istry, entt::DefaultRegistry::entity_type entity) {
|
||||||
|
ASSERT_TRUE(registry.valid(entity));
|
||||||
|
ASSERT_TRUE(registry.has<Component>(entity));
|
||||||
|
last = entity;
|
||||||
|
++counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Tag>
|
||||||
|
void incrTag(entt::DefaultRegistry ®istry, entt::DefaultRegistry::entity_type entity) {
|
||||||
|
ASSERT_TRUE(registry.valid(entity));
|
||||||
|
ASSERT_TRUE(registry.has<Tag>());
|
||||||
|
ASSERT_EQ(registry.attachee<Tag>(), entity);
|
||||||
|
last = entity;
|
||||||
|
++counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Component>
|
||||||
|
void decrComponent(entt::DefaultRegistry ®istry, entt::DefaultRegistry::entity_type entity) {
|
||||||
|
ASSERT_TRUE(registry.valid(entity));
|
||||||
|
ASSERT_TRUE(registry.has<Component>(entity));
|
||||||
|
last = entity;
|
||||||
|
--counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Tag>
|
||||||
|
void decrTag(entt::DefaultRegistry ®istry, entt::DefaultRegistry::entity_type entity) {
|
||||||
|
ASSERT_TRUE(registry.valid(entity));
|
||||||
|
ASSERT_TRUE(registry.has<Tag>());
|
||||||
|
ASSERT_EQ(registry.attachee<Tag>(), entity);
|
||||||
|
last = entity;
|
||||||
|
--counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
entt::DefaultRegistry::entity_type last;
|
||||||
|
int counter{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(DefaultRegistry, Types) {
|
||||||
|
entt::DefaultRegistry registry;
|
||||||
|
|
||||||
|
ASSERT_EQ(registry.type<int>(entt::tag_t{}), registry.type<int>(entt::tag_t{}));
|
||||||
|
ASSERT_EQ(registry.type<int>(), registry.type<int>());
|
||||||
|
|
||||||
|
ASSERT_NE(registry.type<int>(entt::tag_t{}), registry.type<double>(entt::tag_t{}));
|
||||||
|
ASSERT_NE(registry.type<int>(), registry.type<double>(entt::tag_t{}));
|
||||||
|
}
|
||||||
|
|
||||||
TEST(DefaultRegistry, Functionalities) {
|
TEST(DefaultRegistry, Functionalities) {
|
||||||
entt::DefaultRegistry registry;
|
entt::DefaultRegistry registry;
|
||||||
|
|
||||||
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{0});
|
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{0});
|
||||||
|
ASSERT_EQ(registry.alive(), entt::DefaultRegistry::size_type{0});
|
||||||
|
ASSERT_NO_THROW(registry.reserve(42));
|
||||||
|
ASSERT_NO_THROW(registry.reserve<int>(8));
|
||||||
|
ASSERT_NO_THROW(registry.reserve<char>(8));
|
||||||
ASSERT_TRUE(registry.empty());
|
ASSERT_TRUE(registry.empty());
|
||||||
|
|
||||||
ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{0});
|
ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{42});
|
||||||
|
ASSERT_EQ(registry.capacity<int>(), entt::DefaultRegistry::size_type{8});
|
||||||
|
ASSERT_EQ(registry.capacity<char>(), entt::DefaultRegistry::size_type{8});
|
||||||
ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{0});
|
ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{0});
|
||||||
ASSERT_EQ(registry.size<char>(), entt::DefaultRegistry::size_type{0});
|
ASSERT_EQ(registry.size<char>(), entt::DefaultRegistry::size_type{0});
|
||||||
ASSERT_TRUE(registry.empty<int>());
|
ASSERT_TRUE(registry.empty<int>());
|
||||||
ASSERT_TRUE(registry.empty<char>());
|
ASSERT_TRUE(registry.empty<char>());
|
||||||
|
|
||||||
auto e1 = registry.create();
|
const auto e0 = registry.create();
|
||||||
auto e2 = registry.create<int, char>();
|
const auto e1 = registry.create();
|
||||||
|
|
||||||
|
registry.assign<int>(e1);
|
||||||
|
registry.assign<char>(e1);
|
||||||
|
|
||||||
|
ASSERT_TRUE(registry.has<>(e0));
|
||||||
|
ASSERT_TRUE(registry.has<>(e1));
|
||||||
|
|
||||||
ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{2});
|
|
||||||
ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{1});
|
ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{1});
|
||||||
ASSERT_EQ(registry.size<char>(), entt::DefaultRegistry::size_type{1});
|
ASSERT_EQ(registry.size<char>(), entt::DefaultRegistry::size_type{1});
|
||||||
ASSERT_FALSE(registry.empty<int>());
|
ASSERT_FALSE(registry.empty<int>());
|
||||||
ASSERT_FALSE(registry.empty<char>());
|
ASSERT_FALSE(registry.empty<char>());
|
||||||
|
|
||||||
ASSERT_NE(e1, e2);
|
ASSERT_NE(e0, e1);
|
||||||
|
|
||||||
ASSERT_FALSE(registry.has<int>(e1));
|
|
||||||
ASSERT_TRUE(registry.has<int>(e2));
|
|
||||||
ASSERT_FALSE(registry.has<char>(e1));
|
|
||||||
ASSERT_TRUE(registry.has<char>(e2));
|
|
||||||
ASSERT_FALSE((registry.has<int, char>(e1)));
|
|
||||||
ASSERT_TRUE((registry.has<int, char>(e2)));
|
|
||||||
|
|
||||||
ASSERT_EQ(registry.assign<int>(e1, 42), 42);
|
|
||||||
ASSERT_EQ(registry.assign<char>(e1, 'c'), 'c');
|
|
||||||
ASSERT_NO_THROW(registry.remove<int>(e2));
|
|
||||||
ASSERT_NO_THROW(registry.remove<char>(e2));
|
|
||||||
|
|
||||||
|
ASSERT_FALSE(registry.has<int>(e0));
|
||||||
ASSERT_TRUE(registry.has<int>(e1));
|
ASSERT_TRUE(registry.has<int>(e1));
|
||||||
ASSERT_FALSE(registry.has<int>(e2));
|
ASSERT_FALSE(registry.has<char>(e0));
|
||||||
ASSERT_TRUE(registry.has<char>(e1));
|
ASSERT_TRUE(registry.has<char>(e1));
|
||||||
ASSERT_FALSE(registry.has<char>(e2));
|
ASSERT_FALSE((registry.has<int, char>(e0)));
|
||||||
ASSERT_TRUE((registry.has<int, char>(e1)));
|
ASSERT_TRUE((registry.has<int, char>(e1)));
|
||||||
ASSERT_FALSE((registry.has<int, char>(e2)));
|
|
||||||
|
|
||||||
auto e3 = registry.create();
|
ASSERT_EQ(registry.assign<int>(e0, 42), 42);
|
||||||
|
ASSERT_EQ(registry.assign<char>(e0, 'c'), 'c');
|
||||||
|
ASSERT_NO_THROW(registry.remove<int>(e1));
|
||||||
|
ASSERT_NO_THROW(registry.remove<char>(e1));
|
||||||
|
|
||||||
registry.accomodate<int>(e3, registry.get<int>(e1));
|
ASSERT_TRUE(registry.has<int>(e0));
|
||||||
registry.accomodate<char>(e3, registry.get<char>(e1));
|
ASSERT_FALSE(registry.has<int>(e1));
|
||||||
|
ASSERT_TRUE(registry.has<char>(e0));
|
||||||
|
ASSERT_FALSE(registry.has<char>(e1));
|
||||||
|
ASSERT_TRUE((registry.has<int, char>(e0)));
|
||||||
|
ASSERT_FALSE((registry.has<int, char>(e1)));
|
||||||
|
|
||||||
ASSERT_TRUE(registry.has<int>(e3));
|
const auto e2 = registry.create();
|
||||||
ASSERT_TRUE(registry.has<char>(e3));
|
|
||||||
ASSERT_EQ(registry.get<int>(e1), 42);
|
|
||||||
ASSERT_EQ(registry.get<char>(e1), 'c');
|
|
||||||
ASSERT_EQ(registry.get<int>(e1), registry.get<int>(e3));
|
|
||||||
ASSERT_EQ(registry.get<char>(e1), registry.get<char>(e3));
|
|
||||||
ASSERT_NE(®istry.get<int>(e1), ®istry.get<int>(e3));
|
|
||||||
ASSERT_NE(®istry.get<char>(e1), ®istry.get<char>(e3));
|
|
||||||
|
|
||||||
ASSERT_NO_THROW(registry.replace<int>(e1, 0));
|
registry.accommodate<int>(e2, registry.get<int>(e0));
|
||||||
ASSERT_EQ(registry.get<int>(e1), 0);
|
registry.accommodate<char>(e2, registry.get<char>(e0));
|
||||||
|
|
||||||
ASSERT_NO_THROW(registry.accomodate<int>(e1, 1));
|
ASSERT_TRUE(registry.has<int>(e2));
|
||||||
ASSERT_NO_THROW(registry.accomodate<int>(e2, 1));
|
ASSERT_TRUE(registry.has<char>(e2));
|
||||||
|
ASSERT_EQ(registry.get<int>(e0), 42);
|
||||||
|
ASSERT_EQ(registry.get<char>(e0), 'c');
|
||||||
|
|
||||||
|
ASSERT_EQ(std::get<0>(registry.get<int, char>(e0)), 42);
|
||||||
|
ASSERT_EQ(std::get<1>(static_cast<const entt::DefaultRegistry &>(registry).get<int, char>(e0)), 'c');
|
||||||
|
|
||||||
|
ASSERT_EQ(registry.get<int>(e0), registry.get<int>(e2));
|
||||||
|
ASSERT_EQ(registry.get<char>(e0), registry.get<char>(e2));
|
||||||
|
ASSERT_NE(®istry.get<int>(e0), ®istry.get<int>(e2));
|
||||||
|
ASSERT_NE(®istry.get<char>(e0), ®istry.get<char>(e2));
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(registry.replace<int>(e0, 0));
|
||||||
|
ASSERT_EQ(registry.get<int>(e0), 0);
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(registry.accommodate<int>(e0, 1));
|
||||||
|
ASSERT_NO_THROW(registry.accommodate<int>(e1, 1));
|
||||||
|
ASSERT_EQ(static_cast<const entt::DefaultRegistry &>(registry).get<int>(e0), 1);
|
||||||
ASSERT_EQ(static_cast<const entt::DefaultRegistry &>(registry).get<int>(e1), 1);
|
ASSERT_EQ(static_cast<const entt::DefaultRegistry &>(registry).get<int>(e1), 1);
|
||||||
ASSERT_EQ(static_cast<const entt::DefaultRegistry &>(registry).get<int>(e2), 1);
|
|
||||||
|
|
||||||
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{3});
|
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{3});
|
||||||
|
ASSERT_EQ(registry.alive(), entt::DefaultRegistry::size_type{3});
|
||||||
ASSERT_FALSE(registry.empty());
|
ASSERT_FALSE(registry.empty());
|
||||||
|
|
||||||
ASSERT_EQ(registry.version(e3), entt::DefaultRegistry::version_type{0});
|
ASSERT_EQ(registry.version(e2), entt::DefaultRegistry::version_type{0});
|
||||||
ASSERT_EQ(registry.current(e3), entt::DefaultRegistry::version_type{0});
|
ASSERT_EQ(registry.current(e2), entt::DefaultRegistry::version_type{0});
|
||||||
ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{3});
|
ASSERT_NO_THROW(registry.destroy(e2));
|
||||||
ASSERT_NO_THROW(registry.destroy(e3));
|
ASSERT_EQ(registry.version(e2), entt::DefaultRegistry::version_type{0});
|
||||||
ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{3});
|
ASSERT_EQ(registry.current(e2), entt::DefaultRegistry::version_type{1});
|
||||||
ASSERT_EQ(registry.version(e3), entt::DefaultRegistry::version_type{0});
|
|
||||||
ASSERT_EQ(registry.current(e3), entt::DefaultRegistry::version_type{1});
|
|
||||||
|
|
||||||
|
ASSERT_TRUE(registry.valid(e0));
|
||||||
|
ASSERT_TRUE(registry.fast(e0));
|
||||||
ASSERT_TRUE(registry.valid(e1));
|
ASSERT_TRUE(registry.valid(e1));
|
||||||
ASSERT_TRUE(registry.valid(e2));
|
ASSERT_TRUE(registry.fast(e1));
|
||||||
ASSERT_FALSE(registry.valid(e3));
|
ASSERT_FALSE(registry.valid(e2));
|
||||||
|
ASSERT_FALSE(registry.fast(e2));
|
||||||
|
|
||||||
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{2});
|
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{3});
|
||||||
|
ASSERT_EQ(registry.alive(), entt::DefaultRegistry::size_type{2});
|
||||||
ASSERT_FALSE(registry.empty());
|
ASSERT_FALSE(registry.empty());
|
||||||
|
|
||||||
ASSERT_NO_THROW(registry.reset());
|
ASSERT_NO_THROW(registry.reset());
|
||||||
|
|
||||||
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{0});
|
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{3});
|
||||||
|
ASSERT_EQ(registry.alive(), entt::DefaultRegistry::size_type{0});
|
||||||
ASSERT_TRUE(registry.empty());
|
ASSERT_TRUE(registry.empty());
|
||||||
|
|
||||||
registry.create<int, char>();
|
const auto e3 = registry.create();
|
||||||
|
|
||||||
|
registry.assign<int>(e3);
|
||||||
|
registry.assign<char>(e3);
|
||||||
|
|
||||||
ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{1});
|
ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{1});
|
||||||
ASSERT_EQ(registry.size<char>(), entt::DefaultRegistry::size_type{1});
|
ASSERT_EQ(registry.size<char>(), entt::DefaultRegistry::size_type{1});
|
||||||
@@ -110,63 +185,260 @@ TEST(DefaultRegistry, Functionalities) {
|
|||||||
ASSERT_TRUE(registry.empty<int>());
|
ASSERT_TRUE(registry.empty<int>());
|
||||||
ASSERT_TRUE(registry.empty<char>());
|
ASSERT_TRUE(registry.empty<char>());
|
||||||
|
|
||||||
e1 = registry.create<int>();
|
const auto e4 = registry.create();
|
||||||
e2 = registry.create();
|
const auto e5 = registry.create();
|
||||||
|
|
||||||
ASSERT_NO_THROW(registry.reset<int>(e1));
|
registry.assign<int>(e4);
|
||||||
ASSERT_NO_THROW(registry.reset<int>(e2));
|
|
||||||
|
ASSERT_NO_THROW(registry.reset<int>(e4));
|
||||||
|
ASSERT_NO_THROW(registry.reset<int>(e5));
|
||||||
|
|
||||||
ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{0});
|
ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{0});
|
||||||
ASSERT_EQ(registry.size<char>(), entt::DefaultRegistry::size_type{0});
|
ASSERT_EQ(registry.size<char>(), entt::DefaultRegistry::size_type{0});
|
||||||
ASSERT_TRUE(registry.empty<int>());
|
ASSERT_TRUE(registry.empty<int>());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(DefaultRegistry, CreateDestroyEntities) {
|
TEST(DefaultRegistry, Identifiers) {
|
||||||
|
entt::DefaultRegistry registry;
|
||||||
|
const auto pre = registry.create();
|
||||||
|
|
||||||
|
ASSERT_EQ(pre, registry.entity(pre));
|
||||||
|
|
||||||
|
registry.destroy(pre);
|
||||||
|
const auto post = registry.create();
|
||||||
|
|
||||||
|
ASSERT_NE(pre, post);
|
||||||
|
ASSERT_EQ(registry.entity(pre), registry.entity(post));
|
||||||
|
ASSERT_NE(registry.version(pre), registry.version(post));
|
||||||
|
ASSERT_NE(registry.version(pre), registry.current(pre));
|
||||||
|
ASSERT_EQ(registry.version(post), registry.current(post));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DefaultRegistry, RawData) {
|
||||||
|
entt::DefaultRegistry registry;
|
||||||
|
const entt::DefaultRegistry &cregistry = registry;
|
||||||
|
const auto entity = registry.create();
|
||||||
|
|
||||||
|
ASSERT_EQ(registry.raw<int>(), nullptr);
|
||||||
|
ASSERT_EQ(cregistry.raw<int>(), nullptr);
|
||||||
|
ASSERT_EQ(cregistry.data<int>(), nullptr);
|
||||||
|
|
||||||
|
registry.assign<int>(entity, 42);
|
||||||
|
|
||||||
|
ASSERT_NE(registry.raw<int>(), nullptr);
|
||||||
|
ASSERT_NE(cregistry.raw<int>(), nullptr);
|
||||||
|
ASSERT_NE(cregistry.data<int>(), nullptr);
|
||||||
|
|
||||||
|
ASSERT_EQ(*registry.raw<int>(), 42);
|
||||||
|
ASSERT_EQ(*cregistry.raw<int>(), 42);
|
||||||
|
ASSERT_EQ(*cregistry.data<int>(), entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DefaultRegistry, CreateDestroyCornerCase) {
|
||||||
entt::DefaultRegistry registry;
|
entt::DefaultRegistry registry;
|
||||||
|
|
||||||
auto pre = registry.create<double>();
|
const auto e0 = registry.create();
|
||||||
registry.destroy(pre);
|
const auto e1 = registry.create();
|
||||||
auto post = registry.create<double>();
|
|
||||||
|
registry.destroy(e0);
|
||||||
|
registry.destroy(e1);
|
||||||
|
|
||||||
|
registry.each([](auto) { FAIL(); });
|
||||||
|
|
||||||
|
ASSERT_EQ(registry.current(e0), entt::DefaultRegistry::version_type{1});
|
||||||
|
ASSERT_EQ(registry.current(e1), entt::DefaultRegistry::version_type{1});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DefaultRegistry, VersionOverflow) {
|
||||||
|
entt::DefaultRegistry registry;
|
||||||
|
|
||||||
|
const auto entity = registry.create();
|
||||||
|
registry.destroy(entity);
|
||||||
|
|
||||||
|
ASSERT_EQ(registry.version(entity), entt::DefaultRegistry::version_type{});
|
||||||
|
|
||||||
|
for(auto i = entt::entt_traits<entt::DefaultRegistry::entity_type>::version_mask; i; --i) {
|
||||||
|
ASSERT_NE(registry.current(entity), registry.version(entity));
|
||||||
|
registry.destroy(registry.create());
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_EQ(registry.current(entity), registry.version(entity));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DefaultRegistry, Each) {
|
||||||
|
entt::DefaultRegistry registry;
|
||||||
|
entt::DefaultRegistry::size_type tot;
|
||||||
|
entt::DefaultRegistry::size_type match;
|
||||||
|
|
||||||
|
registry.create();
|
||||||
|
registry.assign<int>(registry.create());
|
||||||
|
registry.create();
|
||||||
|
registry.assign<int>(registry.create());
|
||||||
|
registry.create();
|
||||||
|
|
||||||
|
tot = 0u;
|
||||||
|
match = 0u;
|
||||||
|
|
||||||
|
registry.each([&](auto entity) {
|
||||||
|
if(registry.has<int>(entity)) { ++match; }
|
||||||
|
registry.create();
|
||||||
|
++tot;
|
||||||
|
});
|
||||||
|
|
||||||
|
ASSERT_EQ(tot, 5u);
|
||||||
|
ASSERT_EQ(match, 2u);
|
||||||
|
|
||||||
|
tot = 0u;
|
||||||
|
match = 0u;
|
||||||
|
|
||||||
|
registry.each([&](auto entity) {
|
||||||
|
if(registry.has<int>(entity)) {
|
||||||
|
registry.destroy(entity);
|
||||||
|
++match;
|
||||||
|
}
|
||||||
|
|
||||||
|
++tot;
|
||||||
|
});
|
||||||
|
|
||||||
|
ASSERT_EQ(tot, 10u);
|
||||||
|
ASSERT_EQ(match, 2u);
|
||||||
|
|
||||||
|
tot = 0u;
|
||||||
|
match = 0u;
|
||||||
|
|
||||||
|
registry.each([&](auto entity) {
|
||||||
|
if(registry.has<int>(entity)) { ++match; }
|
||||||
|
registry.destroy(entity);
|
||||||
|
++tot;
|
||||||
|
});
|
||||||
|
|
||||||
|
ASSERT_EQ(tot, 8u);
|
||||||
|
ASSERT_EQ(match, 0u);
|
||||||
|
|
||||||
|
registry.each([&](auto) { FAIL(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DefaultRegistry, Orphans) {
|
||||||
|
entt::DefaultRegistry registry;
|
||||||
|
entt::DefaultRegistry::size_type tot{};
|
||||||
|
|
||||||
|
registry.assign<int>(registry.create());
|
||||||
|
registry.create();
|
||||||
|
registry.assign<int>(registry.create());
|
||||||
|
registry.create();
|
||||||
|
registry.assign<double>(entt::tag_t{}, registry.create());
|
||||||
|
|
||||||
|
registry.orphans([&](auto) { ++tot; });
|
||||||
|
ASSERT_EQ(tot, 2u);
|
||||||
|
tot = 0u;
|
||||||
|
|
||||||
|
registry.each([&](auto entity) { registry.reset<int>(entity); });
|
||||||
|
registry.orphans([&](auto) { ++tot; });
|
||||||
|
ASSERT_EQ(tot, 4u);
|
||||||
|
registry.reset();
|
||||||
|
tot = 0u;
|
||||||
|
|
||||||
|
registry.orphans([&](auto) { ++tot; });
|
||||||
|
ASSERT_EQ(tot, 0u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DefaultRegistry, CreateDestroyEntities) {
|
||||||
|
entt::DefaultRegistry registry;
|
||||||
|
entt::DefaultRegistry::entity_type pre{}, post{};
|
||||||
|
|
||||||
|
for(int i = 0; i < 10; ++i) {
|
||||||
|
const auto entity = registry.create();
|
||||||
|
registry.assign<double>(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
registry.reset();
|
||||||
|
|
||||||
|
for(int i = 0; i < 7; ++i) {
|
||||||
|
const auto entity = registry.create();
|
||||||
|
registry.assign<int>(entity);
|
||||||
|
if(i == 3) { pre = entity; }
|
||||||
|
}
|
||||||
|
|
||||||
|
registry.reset();
|
||||||
|
|
||||||
|
for(int i = 0; i < 5; ++i) {
|
||||||
|
const auto entity = registry.create();
|
||||||
|
if(i == 3) { post = entity; }
|
||||||
|
}
|
||||||
|
|
||||||
ASSERT_FALSE(registry.valid(pre));
|
ASSERT_FALSE(registry.valid(pre));
|
||||||
ASSERT_TRUE(registry.valid(post));
|
ASSERT_TRUE(registry.valid(post));
|
||||||
ASSERT_NE(registry.version(pre), registry.version(post));
|
ASSERT_NE(registry.version(pre), registry.version(post));
|
||||||
|
ASSERT_EQ(registry.version(pre) + 1, registry.version(post));
|
||||||
ASSERT_EQ(registry.current(pre), registry.current(post));
|
ASSERT_EQ(registry.current(pre), registry.current(post));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(DefaultRegistry, AttachRemoveTags) {
|
TEST(DefaultRegistry, AttachSetRemoveTags) {
|
||||||
entt::DefaultRegistry registry;
|
entt::DefaultRegistry registry;
|
||||||
const auto &cregistry = registry;
|
const auto &cregistry = registry;
|
||||||
|
const typename decltype(registry)::entity_type null = entt::null;
|
||||||
|
|
||||||
ASSERT_FALSE(registry.has<int>());
|
ASSERT_FALSE(registry.has<int>());
|
||||||
|
ASSERT_EQ(registry.attachee<int>(), null);
|
||||||
|
|
||||||
auto entity = registry.create();
|
const auto entity = registry.create();
|
||||||
registry.attach<int>(entity, 42);
|
registry.assign<int>(entt::tag_t{}, entity, 42);
|
||||||
|
|
||||||
ASSERT_TRUE(registry.has<int>());
|
ASSERT_TRUE(registry.has<int>());
|
||||||
|
ASSERT_TRUE(registry.has<int>(entt::tag_t{}, entity));
|
||||||
ASSERT_EQ(registry.get<int>(), 42);
|
ASSERT_EQ(registry.get<int>(), 42);
|
||||||
ASSERT_EQ(cregistry.get<int>(), 42);
|
ASSERT_EQ(cregistry.get<int>(), 42);
|
||||||
ASSERT_EQ(registry.attachee<int>(), entity);
|
ASSERT_EQ(registry.attachee<int>(), entity);
|
||||||
|
|
||||||
|
registry.replace<int>(entt::tag_t{}, 3);
|
||||||
|
|
||||||
|
ASSERT_TRUE(registry.has<int>());
|
||||||
|
ASSERT_TRUE(registry.has<int>(entt::tag_t{}, entity));
|
||||||
|
ASSERT_EQ(registry.get<int>(), 3);
|
||||||
|
ASSERT_EQ(cregistry.get<int>(), 3);
|
||||||
|
ASSERT_EQ(registry.attachee<int>(), entity);
|
||||||
|
|
||||||
|
const auto other = registry.create();
|
||||||
|
registry.move<int>(other);
|
||||||
|
|
||||||
|
ASSERT_TRUE(registry.has<int>());
|
||||||
|
ASSERT_FALSE(registry.has<int>(entt::tag_t{}, entity));
|
||||||
|
ASSERT_TRUE(registry.has<int>(entt::tag_t{}, other));
|
||||||
|
ASSERT_EQ(registry.get<int>(), 3);
|
||||||
|
ASSERT_EQ(cregistry.get<int>(), 3);
|
||||||
|
ASSERT_EQ(registry.attachee<int>(), other);
|
||||||
|
|
||||||
registry.remove<int>();
|
registry.remove<int>();
|
||||||
|
|
||||||
ASSERT_FALSE(registry.has<int>());
|
ASSERT_FALSE(registry.has<int>());
|
||||||
|
ASSERT_FALSE(registry.has<int>(entt::tag_t{}, entity));
|
||||||
|
ASSERT_FALSE(registry.has<int>(entt::tag_t{}, other));
|
||||||
|
ASSERT_EQ(registry.attachee<int>(), null);
|
||||||
|
|
||||||
registry.attach<int>(entity, 42);
|
registry.assign<int>(entt::tag_t{}, entity, 42);
|
||||||
registry.destroy(entity);
|
registry.destroy(entity);
|
||||||
|
|
||||||
ASSERT_FALSE(registry.has<int>());
|
ASSERT_FALSE(registry.has<int>());
|
||||||
|
ASSERT_FALSE(registry.has<int>(entt::tag_t{}, entity));
|
||||||
|
ASSERT_FALSE(registry.has<int>(entt::tag_t{}, other));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(DefaultRegistry, StandardViews) {
|
TEST(DefaultRegistry, StandardView) {
|
||||||
entt::DefaultRegistry registry;
|
entt::DefaultRegistry registry;
|
||||||
auto mview = registry.view<int, char>();
|
auto mview = registry.view<int, char>();
|
||||||
auto iview = registry.view<int>();
|
auto iview = registry.view<int>();
|
||||||
auto cview = registry.view<char>();
|
auto cview = registry.view<char>();
|
||||||
|
|
||||||
registry.create(0, 'c');
|
const auto e0 = registry.create();
|
||||||
registry.create(0);
|
registry.assign<int>(e0, 0);
|
||||||
registry.create(0, 'c');
|
registry.assign<char>(e0, 'c');
|
||||||
|
|
||||||
|
const auto e1 = registry.create();
|
||||||
|
registry.assign<int>(e1, 0);
|
||||||
|
|
||||||
|
const auto e2 = registry.create();
|
||||||
|
registry.assign<int>(e2, 0);
|
||||||
|
registry.assign<char>(e2, 'c');
|
||||||
|
|
||||||
ASSERT_EQ(iview.size(), decltype(iview)::size_type{3});
|
ASSERT_EQ(iview.size(), decltype(iview)::size_type{3});
|
||||||
ASSERT_EQ(cview.size(), decltype(cview)::size_type{2});
|
ASSERT_EQ(cview.size(), decltype(cview)::size_type{2});
|
||||||
@@ -177,9 +449,9 @@ TEST(DefaultRegistry, StandardViews) {
|
|||||||
ASSERT_EQ(cnt, decltype(mview)::size_type{2});
|
ASSERT_EQ(cnt, decltype(mview)::size_type{2});
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(DefaultRegistry, PersistentViews) {
|
TEST(DefaultRegistry, PersistentView) {
|
||||||
entt::DefaultRegistry registry;
|
entt::DefaultRegistry registry;
|
||||||
auto view = registry.persistent<int, char>();
|
auto view = registry.view<int, char>(entt::persistent_t{});
|
||||||
|
|
||||||
ASSERT_TRUE((registry.contains<int, char>()));
|
ASSERT_TRUE((registry.contains<int, char>()));
|
||||||
ASSERT_FALSE((registry.contains<int, double>()));
|
ASSERT_FALSE((registry.contains<int, double>()));
|
||||||
@@ -192,9 +464,16 @@ TEST(DefaultRegistry, PersistentViews) {
|
|||||||
|
|
||||||
ASSERT_FALSE((registry.contains<int, double>()));
|
ASSERT_FALSE((registry.contains<int, double>()));
|
||||||
|
|
||||||
registry.create(0, 'c');
|
const auto e0 = registry.create();
|
||||||
registry.create(0);
|
registry.assign<int>(e0, 0);
|
||||||
registry.create(0, 'c');
|
registry.assign<char>(e0, 'c');
|
||||||
|
|
||||||
|
const auto e1 = registry.create();
|
||||||
|
registry.assign<int>(e1, 0);
|
||||||
|
|
||||||
|
const auto e2 = registry.create();
|
||||||
|
registry.assign<int>(e2, 0);
|
||||||
|
registry.assign<char>(e2, 'c');
|
||||||
|
|
||||||
decltype(view)::size_type cnt{0};
|
decltype(view)::size_type cnt{0};
|
||||||
view.each([&cnt](auto...) { ++cnt; });
|
view.each([&cnt](auto...) { ++cnt; });
|
||||||
@@ -202,10 +481,28 @@ TEST(DefaultRegistry, PersistentViews) {
|
|||||||
ASSERT_EQ(cnt, decltype(view)::size_type{2});
|
ASSERT_EQ(cnt, decltype(view)::size_type{2});
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(DefaultRegistry, CleanStandardViewsAfterReset) {
|
TEST(DefaultRegistry, RawView) {
|
||||||
|
entt::DefaultRegistry registry;
|
||||||
|
auto view = registry.view<int>(entt::raw_t{});
|
||||||
|
|
||||||
|
const auto e0 = registry.create();
|
||||||
|
registry.assign<int>(e0, 0);
|
||||||
|
registry.assign<char>(e0, 'c');
|
||||||
|
|
||||||
|
const auto e1 = registry.create();
|
||||||
|
registry.assign<int>(e1, 0);
|
||||||
|
registry.assign<char>(e1, 'c');
|
||||||
|
|
||||||
|
decltype(view)::size_type cnt{0};
|
||||||
|
view.each([&cnt](auto &...) { ++cnt; });
|
||||||
|
|
||||||
|
ASSERT_EQ(cnt, decltype(view)::size_type{2});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DefaultRegistry, CleanStandardViewAfterReset) {
|
||||||
entt::DefaultRegistry registry;
|
entt::DefaultRegistry registry;
|
||||||
auto view = registry.view<int>();
|
auto view = registry.view<int>();
|
||||||
registry.create(0);
|
registry.assign<int>(registry.create(), 0);
|
||||||
|
|
||||||
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{1});
|
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{1});
|
||||||
|
|
||||||
@@ -214,10 +511,25 @@ TEST(DefaultRegistry, CleanStandardViewsAfterReset) {
|
|||||||
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{0});
|
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{0});
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(DefaultRegistry, CleanPersistentViewsAfterReset) {
|
TEST(DefaultRegistry, CleanPersistentViewAfterReset) {
|
||||||
entt::DefaultRegistry registry;
|
entt::DefaultRegistry registry;
|
||||||
auto view = registry.persistent<int, char>();
|
auto view = registry.view<int, char>(entt::persistent_t{});
|
||||||
registry.create(0, 'c');
|
|
||||||
|
const auto entity = registry.create();
|
||||||
|
registry.assign<int>(entity, 0);
|
||||||
|
registry.assign<char>(entity, 'c');
|
||||||
|
|
||||||
|
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{1});
|
||||||
|
|
||||||
|
registry.reset();
|
||||||
|
|
||||||
|
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{0});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DefaultRegistry, CleanRawViewAfterReset) {
|
||||||
|
entt::DefaultRegistry registry;
|
||||||
|
auto view = registry.view<int>(entt::raw_t{});
|
||||||
|
registry.assign<int>(registry.create(), 0);
|
||||||
|
|
||||||
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{1});
|
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{1});
|
||||||
|
|
||||||
@@ -228,8 +540,8 @@ TEST(DefaultRegistry, CleanPersistentViewsAfterReset) {
|
|||||||
|
|
||||||
TEST(DefaultRegistry, CleanTagsAfterReset) {
|
TEST(DefaultRegistry, CleanTagsAfterReset) {
|
||||||
entt::DefaultRegistry registry;
|
entt::DefaultRegistry registry;
|
||||||
auto entity = registry.create();
|
const auto entity = registry.create();
|
||||||
registry.attach<int>(entity);
|
registry.assign<int>(entt::tag_t{}, entity);
|
||||||
|
|
||||||
ASSERT_TRUE(registry.has<int>());
|
ASSERT_TRUE(registry.has<int>());
|
||||||
|
|
||||||
@@ -243,9 +555,9 @@ TEST(DefaultRegistry, SortSingle) {
|
|||||||
|
|
||||||
int val = 0;
|
int val = 0;
|
||||||
|
|
||||||
registry.create(val++);
|
registry.assign<int>(registry.create(), val++);
|
||||||
registry.create(val++);
|
registry.assign<int>(registry.create(), val++);
|
||||||
registry.create(val++);
|
registry.assign<int>(registry.create(), val++);
|
||||||
|
|
||||||
for(auto entity: registry.view<int>()) {
|
for(auto entity: registry.view<int>()) {
|
||||||
ASSERT_EQ(registry.get<int>(entity), --val);
|
ASSERT_EQ(registry.get<int>(entity), --val);
|
||||||
@@ -264,9 +576,11 @@ TEST(DefaultRegistry, SortMulti) {
|
|||||||
unsigned int uval = 0u;
|
unsigned int uval = 0u;
|
||||||
int ival = 0;
|
int ival = 0;
|
||||||
|
|
||||||
registry.create(uval++, ival++);
|
for(auto i = 0; i < 3; ++i) {
|
||||||
registry.create(uval++, ival++);
|
const auto entity = registry.create();
|
||||||
registry.create(uval++, ival++);
|
registry.assign<unsigned int>(entity, uval++);
|
||||||
|
registry.assign<int>(entity, ival++);
|
||||||
|
}
|
||||||
|
|
||||||
for(auto entity: registry.view<unsigned int>()) {
|
for(auto entity: registry.view<unsigned int>()) {
|
||||||
ASSERT_EQ(registry.get<unsigned int>(entity), --uval);
|
ASSERT_EQ(registry.get<unsigned int>(entity), --uval);
|
||||||
@@ -287,3 +601,227 @@ TEST(DefaultRegistry, SortMulti) {
|
|||||||
ASSERT_EQ(registry.get<int>(entity), ival++);
|
ASSERT_EQ(registry.get<int>(entity), ival++);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(DefaultRegistry, ComponentsWithTypesFromStandardTemplateLibrary) {
|
||||||
|
// see #37 - the test shouldn't crash, that's all
|
||||||
|
entt::DefaultRegistry registry;
|
||||||
|
const auto entity = registry.create();
|
||||||
|
registry.assign<std::unordered_set<int>>(entity).insert(42);
|
||||||
|
registry.destroy(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DefaultRegistry, ConstructWithComponents) {
|
||||||
|
// it should compile, that's all
|
||||||
|
entt::DefaultRegistry registry;
|
||||||
|
const auto value = 0;
|
||||||
|
registry.assign<int>(registry.create(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DefaultRegistry, MergeTwoRegistries) {
|
||||||
|
using entity_type = entt::DefaultRegistry::entity_type;
|
||||||
|
|
||||||
|
entt::DefaultRegistry src;
|
||||||
|
entt::DefaultRegistry dst;
|
||||||
|
|
||||||
|
std::unordered_map<entity_type, entity_type> ref;
|
||||||
|
|
||||||
|
auto merge = [&ref](const auto &view, auto &dst) {
|
||||||
|
view.each([&](auto entity, const auto &component) {
|
||||||
|
if(ref.find(entity) == ref.cend()) {
|
||||||
|
const auto other = dst.create();
|
||||||
|
dst.template assign<std::decay_t<decltype(component)>>(other, component);
|
||||||
|
ref.emplace(entity, other);
|
||||||
|
} else {
|
||||||
|
using component_type = std::decay_t<decltype(component)>;
|
||||||
|
dst.template assign<component_type>(ref[entity], component);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
auto e0 = src.create();
|
||||||
|
src.assign<int>(e0);
|
||||||
|
src.assign<float>(e0);
|
||||||
|
src.assign<double>(e0);
|
||||||
|
|
||||||
|
auto e1 = src.create();
|
||||||
|
src.assign<char>(e1);
|
||||||
|
src.assign<float>(e1);
|
||||||
|
src.assign<int>(e1);
|
||||||
|
|
||||||
|
auto e2 = dst.create();
|
||||||
|
dst.assign<int>(e2);
|
||||||
|
dst.assign<char>(e2);
|
||||||
|
dst.assign<double>(e2);
|
||||||
|
|
||||||
|
auto e3 = dst.create();
|
||||||
|
dst.assign<float>(e3);
|
||||||
|
dst.assign<int>(e3);
|
||||||
|
|
||||||
|
auto eq = [](auto begin, auto end) { ASSERT_EQ(begin, end); };
|
||||||
|
auto ne = [](auto begin, auto end) { ASSERT_NE(begin, end); };
|
||||||
|
|
||||||
|
eq(dst.view<int, float, double>().begin(), dst.view<int, float, double>().end());
|
||||||
|
eq(dst.view<char, float, int>().begin(), dst.view<char, float, int>().end());
|
||||||
|
|
||||||
|
merge(src.view<int>(), dst);
|
||||||
|
merge(src.view<char>(), dst);
|
||||||
|
merge(src.view<double>(), dst);
|
||||||
|
merge(src.view<float>(), dst);
|
||||||
|
|
||||||
|
ne(dst.view<int, float, double>().begin(), dst.view<int, float, double>().end());
|
||||||
|
ne(dst.view<char, float, int>().begin(), dst.view<char, float, int>().end());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DefaultRegistry, ComponentSignals) {
|
||||||
|
entt::DefaultRegistry registry;
|
||||||
|
Listener listener;
|
||||||
|
|
||||||
|
registry.construction<int>().connect<Listener, &Listener::incrComponent<int>>(&listener);
|
||||||
|
registry.destruction<int>().connect<Listener, &Listener::decrComponent<int>>(&listener);
|
||||||
|
|
||||||
|
auto e0 = registry.create();
|
||||||
|
auto e1 = registry.create();
|
||||||
|
|
||||||
|
registry.assign<int>(e0);
|
||||||
|
registry.assign<int>(e1);
|
||||||
|
|
||||||
|
ASSERT_EQ(listener.counter, 2);
|
||||||
|
ASSERT_EQ(listener.last, e1);
|
||||||
|
|
||||||
|
registry.remove<int>(e0);
|
||||||
|
|
||||||
|
ASSERT_EQ(listener.counter, 1);
|
||||||
|
ASSERT_EQ(listener.last, e0);
|
||||||
|
|
||||||
|
registry.destruction<int>().disconnect<Listener, &Listener::decrComponent<int>>(&listener);
|
||||||
|
registry.remove<int>(e1);
|
||||||
|
|
||||||
|
ASSERT_EQ(listener.counter, 1);
|
||||||
|
ASSERT_EQ(listener.last, e0);
|
||||||
|
|
||||||
|
registry.construction<int>().disconnect<Listener, &Listener::incrComponent<int>>(&listener);
|
||||||
|
registry.assign<int>(e1);
|
||||||
|
|
||||||
|
ASSERT_EQ(listener.counter, 1);
|
||||||
|
ASSERT_EQ(listener.last, e0);
|
||||||
|
|
||||||
|
registry.construction<int>().connect<Listener, &Listener::incrComponent<int>>(&listener);
|
||||||
|
registry.destruction<int>().connect<Listener, &Listener::decrComponent<int>>(&listener);
|
||||||
|
registry.assign<int>(e0);
|
||||||
|
registry.reset<int>(e1);
|
||||||
|
|
||||||
|
ASSERT_EQ(listener.counter, 1);
|
||||||
|
ASSERT_EQ(listener.last, e1);
|
||||||
|
|
||||||
|
registry.reset<int>();
|
||||||
|
|
||||||
|
ASSERT_EQ(listener.counter, 0);
|
||||||
|
ASSERT_EQ(listener.last, e0);
|
||||||
|
|
||||||
|
registry.assign<int>(e0);
|
||||||
|
registry.assign<int>(e1);
|
||||||
|
registry.destroy(e1);
|
||||||
|
|
||||||
|
ASSERT_EQ(listener.counter, 1);
|
||||||
|
ASSERT_EQ(listener.last, e1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DefaultRegistry, TagSignals) {
|
||||||
|
entt::DefaultRegistry registry;
|
||||||
|
Listener listener;
|
||||||
|
|
||||||
|
registry.construction<int>(entt::tag_t{}).connect<Listener, &Listener::incrTag<int>>(&listener);
|
||||||
|
registry.destruction<int>(entt::tag_t{}).connect<Listener, &Listener::decrTag<int>>(&listener);
|
||||||
|
|
||||||
|
auto e0 = registry.create();
|
||||||
|
registry.assign<int>(entt::tag_t{}, e0);
|
||||||
|
|
||||||
|
ASSERT_EQ(listener.counter, 1);
|
||||||
|
ASSERT_EQ(listener.last, e0);
|
||||||
|
|
||||||
|
auto e1 = registry.create();
|
||||||
|
registry.move<int>(e1);
|
||||||
|
registry.remove<int>();
|
||||||
|
|
||||||
|
ASSERT_EQ(listener.counter, 0);
|
||||||
|
ASSERT_EQ(listener.last, e1);
|
||||||
|
|
||||||
|
registry.construction<int>(entt::tag_t{}).disconnect<Listener, &Listener::incrTag<int>>(&listener);
|
||||||
|
registry.destruction<int>(entt::tag_t{}).disconnect<Listener, &Listener::decrTag<int>>(&listener);
|
||||||
|
registry.assign<int>(entt::tag_t{}, e0);
|
||||||
|
registry.remove<int>();
|
||||||
|
|
||||||
|
ASSERT_EQ(listener.counter, 0);
|
||||||
|
ASSERT_EQ(listener.last, e1);
|
||||||
|
|
||||||
|
registry.construction<int>(entt::tag_t{}).connect<Listener, &Listener::incrTag<int>>(&listener);
|
||||||
|
registry.destruction<int>(entt::tag_t{}).connect<Listener, &Listener::decrTag<int>>(&listener);
|
||||||
|
|
||||||
|
registry.assign<int>(entt::tag_t{}, e0);
|
||||||
|
registry.destroy(e0);
|
||||||
|
|
||||||
|
ASSERT_EQ(listener.counter, 0);
|
||||||
|
ASSERT_EQ(listener.last, e0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DefaultRegistry, DestroyByTagAndComponents) {
|
||||||
|
entt::DefaultRegistry registry;
|
||||||
|
|
||||||
|
const auto e0 = registry.create();
|
||||||
|
const auto e1 = registry.create();
|
||||||
|
const auto e2 = registry.create();
|
||||||
|
const auto e3 = registry.create();
|
||||||
|
|
||||||
|
registry.assign<int>(e0);
|
||||||
|
registry.assign<char>(e0);
|
||||||
|
registry.assign<double>(e0);
|
||||||
|
|
||||||
|
registry.assign<int>(e1);
|
||||||
|
registry.assign<char>(e1);
|
||||||
|
|
||||||
|
registry.assign<int>(e2);
|
||||||
|
|
||||||
|
registry.assign<float>(entt::tag_t{}, e3);
|
||||||
|
|
||||||
|
ASSERT_TRUE(registry.valid(e0));
|
||||||
|
ASSERT_TRUE(registry.valid(e1));
|
||||||
|
ASSERT_TRUE(registry.valid(e2));
|
||||||
|
ASSERT_TRUE(registry.valid(e3));
|
||||||
|
|
||||||
|
registry.destroy<int, char, double>(entt::persistent_t{});
|
||||||
|
|
||||||
|
ASSERT_FALSE(registry.valid(e0));
|
||||||
|
ASSERT_TRUE(registry.valid(e1));
|
||||||
|
ASSERT_TRUE(registry.valid(e2));
|
||||||
|
ASSERT_TRUE(registry.valid(e3));
|
||||||
|
|
||||||
|
registry.destroy<int, char>();
|
||||||
|
|
||||||
|
ASSERT_FALSE(registry.valid(e0));
|
||||||
|
ASSERT_FALSE(registry.valid(e1));
|
||||||
|
ASSERT_TRUE(registry.valid(e2));
|
||||||
|
ASSERT_TRUE(registry.valid(e3));
|
||||||
|
|
||||||
|
registry.destroy<int>();
|
||||||
|
|
||||||
|
ASSERT_FALSE(registry.valid(e0));
|
||||||
|
ASSERT_FALSE(registry.valid(e1));
|
||||||
|
ASSERT_FALSE(registry.valid(e2));
|
||||||
|
ASSERT_TRUE(registry.valid(e3));
|
||||||
|
|
||||||
|
registry.destroy<int>(entt::tag_t{});
|
||||||
|
registry.destroy<char>(entt::tag_t{});
|
||||||
|
registry.destroy<double>(entt::tag_t{});
|
||||||
|
registry.destroy<float>(entt::tag_t{});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DefaultRegistry, SignalsOnAccommodate) {
|
||||||
|
entt::DefaultRegistry registry;
|
||||||
|
const auto entity = registry.create();
|
||||||
|
|
||||||
|
registry.prepare<int, char>();
|
||||||
|
registry.assign<int>(entity);
|
||||||
|
registry.accommodate<char>(entity);
|
||||||
|
|
||||||
|
ASSERT_FALSE((registry.view<int, char>(entt::persistent_t{}).empty()));
|
||||||
|
}
|
||||||
|
|||||||
575
test/entt/entity/snapshot.cpp
Normal file
575
test/entt/entity/snapshot.cpp
Normal file
@@ -0,0 +1,575 @@
|
|||||||
|
#include <tuple>
|
||||||
|
#include <queue>
|
||||||
|
#include <vector>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <entt/entity/registry.hpp>
|
||||||
|
|
||||||
|
template<typename Storage>
|
||||||
|
struct OutputArchive {
|
||||||
|
OutputArchive(Storage &storage)
|
||||||
|
: storage{storage}
|
||||||
|
{}
|
||||||
|
|
||||||
|
template<typename... Value>
|
||||||
|
void operator()(const Value &... value) {
|
||||||
|
using accumulator_type = int[];
|
||||||
|
accumulator_type accumulator = { (std::get<std::queue<Value>>(storage).push(value), 0)... };
|
||||||
|
(void)accumulator;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Storage &storage;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Storage>
|
||||||
|
struct InputArchive {
|
||||||
|
InputArchive(Storage &storage)
|
||||||
|
: storage{storage}
|
||||||
|
{}
|
||||||
|
|
||||||
|
template<typename... Value>
|
||||||
|
void operator()(Value &... value) {
|
||||||
|
auto assign = [this](auto &value) {
|
||||||
|
auto &queue = std::get<std::queue<std::decay_t<decltype(value)>>>(storage);
|
||||||
|
value = queue.front();
|
||||||
|
queue.pop();
|
||||||
|
};
|
||||||
|
|
||||||
|
using accumulator_type = int[];
|
||||||
|
accumulator_type accumulator = { (assign(value), 0)... };
|
||||||
|
(void)accumulator;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Storage &storage;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AComponent {};
|
||||||
|
|
||||||
|
struct AnotherComponent {
|
||||||
|
int key;
|
||||||
|
int value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WhatAComponent {
|
||||||
|
entt::DefaultRegistry::entity_type bar;
|
||||||
|
std::vector<entt::DefaultRegistry::entity_type> quux;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(Snapshot, Dump) {
|
||||||
|
entt::DefaultRegistry registry;
|
||||||
|
|
||||||
|
const auto e0 = registry.create();
|
||||||
|
registry.assign<int>(e0, 42);
|
||||||
|
registry.assign<char>(e0, 'c');
|
||||||
|
registry.assign<double>(e0, .1);
|
||||||
|
|
||||||
|
const auto e1 = registry.create();
|
||||||
|
|
||||||
|
const auto e2 = registry.create();
|
||||||
|
registry.assign<int>(e2, 3);
|
||||||
|
|
||||||
|
const auto e3 = registry.create();
|
||||||
|
registry.assign<char>(e3, '0');
|
||||||
|
registry.assign<float>(entt::tag_t{}, e3, .3f);
|
||||||
|
|
||||||
|
const auto e4 = registry.create();
|
||||||
|
registry.assign<AComponent>(entt::tag_t{}, e4);
|
||||||
|
|
||||||
|
registry.destroy(e1);
|
||||||
|
auto v1 = registry.current(e1);
|
||||||
|
|
||||||
|
using storage_type = std::tuple<
|
||||||
|
std::queue<entt::DefaultRegistry::entity_type>,
|
||||||
|
std::queue<int>,
|
||||||
|
std::queue<char>,
|
||||||
|
std::queue<double>,
|
||||||
|
std::queue<float>,
|
||||||
|
std::queue<bool>,
|
||||||
|
std::queue<AComponent>,
|
||||||
|
std::queue<AnotherComponent>,
|
||||||
|
std::queue<WhatAComponent>
|
||||||
|
>;
|
||||||
|
|
||||||
|
storage_type storage;
|
||||||
|
OutputArchive<storage_type> output{storage};
|
||||||
|
InputArchive<storage_type> input{storage};
|
||||||
|
|
||||||
|
registry.snapshot()
|
||||||
|
.entities(output)
|
||||||
|
.destroyed(output)
|
||||||
|
.component<int, char, AnotherComponent, double>(output)
|
||||||
|
.tag<float, bool, AComponent>(output);
|
||||||
|
|
||||||
|
registry.reset();
|
||||||
|
|
||||||
|
ASSERT_FALSE(registry.valid(e0));
|
||||||
|
ASSERT_FALSE(registry.valid(e1));
|
||||||
|
ASSERT_FALSE(registry.valid(e2));
|
||||||
|
ASSERT_FALSE(registry.valid(e3));
|
||||||
|
ASSERT_FALSE(registry.valid(e4));
|
||||||
|
|
||||||
|
registry.restore()
|
||||||
|
.entities(input)
|
||||||
|
.destroyed(input)
|
||||||
|
.component<int, char, AnotherComponent, double>(input)
|
||||||
|
.tag<float, bool, AComponent>(input)
|
||||||
|
.orphans();
|
||||||
|
|
||||||
|
ASSERT_TRUE(registry.valid(e0));
|
||||||
|
ASSERT_FALSE(registry.valid(e1));
|
||||||
|
ASSERT_TRUE(registry.valid(e2));
|
||||||
|
ASSERT_TRUE(registry.valid(e3));
|
||||||
|
ASSERT_TRUE(registry.valid(e4));
|
||||||
|
|
||||||
|
ASSERT_FALSE(registry.orphan(e0));
|
||||||
|
ASSERT_FALSE(registry.orphan(e2));
|
||||||
|
ASSERT_FALSE(registry.orphan(e3));
|
||||||
|
ASSERT_FALSE(registry.orphan(e4));
|
||||||
|
|
||||||
|
ASSERT_EQ(registry.get<int>(e0), 42);
|
||||||
|
ASSERT_EQ(registry.get<char>(e0), 'c');
|
||||||
|
ASSERT_EQ(registry.get<double>(e0), .1);
|
||||||
|
ASSERT_EQ(registry.current(e1), v1);
|
||||||
|
ASSERT_EQ(registry.get<int>(e2), 3);
|
||||||
|
ASSERT_EQ(registry.get<char>(e3), '0');
|
||||||
|
|
||||||
|
ASSERT_TRUE(registry.has<float>());
|
||||||
|
ASSERT_EQ(registry.attachee<float>(), e3);
|
||||||
|
ASSERT_EQ(registry.get<float>(), .3f);
|
||||||
|
|
||||||
|
ASSERT_TRUE(registry.has<AComponent>());
|
||||||
|
ASSERT_EQ(registry.attachee<AComponent>(), e4);
|
||||||
|
|
||||||
|
ASSERT_TRUE(registry.empty<AnotherComponent>());
|
||||||
|
ASSERT_FALSE(registry.has<long int>());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Snapshot, Partial) {
|
||||||
|
entt::DefaultRegistry registry;
|
||||||
|
|
||||||
|
const auto e0 = registry.create();
|
||||||
|
registry.assign<int>(e0, 42);
|
||||||
|
registry.assign<char>(e0, 'c');
|
||||||
|
registry.assign<double>(e0, .1);
|
||||||
|
|
||||||
|
const auto e1 = registry.create();
|
||||||
|
|
||||||
|
const auto e2 = registry.create();
|
||||||
|
registry.assign<int>(e2, 3);
|
||||||
|
|
||||||
|
const auto e3 = registry.create();
|
||||||
|
registry.assign<char>(e3, '0');
|
||||||
|
registry.assign<float>(entt::tag_t{}, e3, .3f);
|
||||||
|
|
||||||
|
const auto e4 = registry.create();
|
||||||
|
registry.assign<AComponent>(entt::tag_t{}, e4);
|
||||||
|
|
||||||
|
registry.destroy(e1);
|
||||||
|
auto v1 = registry.current(e1);
|
||||||
|
|
||||||
|
using storage_type = std::tuple<
|
||||||
|
std::queue<entt::DefaultRegistry::entity_type>,
|
||||||
|
std::queue<int>,
|
||||||
|
std::queue<char>,
|
||||||
|
std::queue<double>,
|
||||||
|
std::queue<float>,
|
||||||
|
std::queue<bool>,
|
||||||
|
std::queue<AComponent>,
|
||||||
|
std::queue<WhatAComponent>
|
||||||
|
>;
|
||||||
|
|
||||||
|
storage_type storage;
|
||||||
|
OutputArchive<storage_type> output{storage};
|
||||||
|
InputArchive<storage_type> input{storage};
|
||||||
|
|
||||||
|
registry.snapshot()
|
||||||
|
.entities(output)
|
||||||
|
.destroyed(output)
|
||||||
|
.component<char, int>(output)
|
||||||
|
.tag<bool, float>(output);
|
||||||
|
|
||||||
|
registry.reset();
|
||||||
|
|
||||||
|
ASSERT_FALSE(registry.valid(e0));
|
||||||
|
ASSERT_FALSE(registry.valid(e1));
|
||||||
|
ASSERT_FALSE(registry.valid(e2));
|
||||||
|
ASSERT_FALSE(registry.valid(e3));
|
||||||
|
ASSERT_FALSE(registry.valid(e4));
|
||||||
|
|
||||||
|
registry.restore()
|
||||||
|
.entities(input)
|
||||||
|
.destroyed(input)
|
||||||
|
.component<char, int>(input)
|
||||||
|
.tag<bool, float>(input);
|
||||||
|
|
||||||
|
ASSERT_TRUE(registry.valid(e0));
|
||||||
|
ASSERT_FALSE(registry.valid(e1));
|
||||||
|
ASSERT_TRUE(registry.valid(e2));
|
||||||
|
ASSERT_TRUE(registry.valid(e3));
|
||||||
|
ASSERT_TRUE(registry.valid(e4));
|
||||||
|
|
||||||
|
ASSERT_EQ(registry.get<int>(e0), 42);
|
||||||
|
ASSERT_EQ(registry.get<char>(e0), 'c');
|
||||||
|
ASSERT_FALSE(registry.has<double>(e0));
|
||||||
|
ASSERT_EQ(registry.current(e1), v1);
|
||||||
|
ASSERT_EQ(registry.get<int>(e2), 3);
|
||||||
|
ASSERT_EQ(registry.get<char>(e3), '0');
|
||||||
|
ASSERT_TRUE(registry.orphan(e4));
|
||||||
|
|
||||||
|
ASSERT_TRUE(registry.has<float>());
|
||||||
|
ASSERT_EQ(registry.attachee<float>(), e3);
|
||||||
|
ASSERT_EQ(registry.get<float>(), .3f);
|
||||||
|
ASSERT_FALSE(registry.has<long int>());
|
||||||
|
|
||||||
|
registry.snapshot()
|
||||||
|
.tag<float>(output)
|
||||||
|
.destroyed(output)
|
||||||
|
.entities(output);
|
||||||
|
|
||||||
|
registry.reset();
|
||||||
|
|
||||||
|
ASSERT_FALSE(registry.valid(e0));
|
||||||
|
ASSERT_FALSE(registry.valid(e1));
|
||||||
|
ASSERT_FALSE(registry.valid(e2));
|
||||||
|
ASSERT_FALSE(registry.valid(e3));
|
||||||
|
ASSERT_FALSE(registry.valid(e4));
|
||||||
|
|
||||||
|
registry.restore()
|
||||||
|
.tag<float>(input)
|
||||||
|
.destroyed(input)
|
||||||
|
.entities(input)
|
||||||
|
.orphans();
|
||||||
|
|
||||||
|
ASSERT_FALSE(registry.valid(e0));
|
||||||
|
ASSERT_FALSE(registry.valid(e1));
|
||||||
|
ASSERT_FALSE(registry.valid(e2));
|
||||||
|
ASSERT_TRUE(registry.valid(e3));
|
||||||
|
ASSERT_FALSE(registry.valid(e4));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Snapshot, Iterator) {
|
||||||
|
entt::DefaultRegistry registry;
|
||||||
|
|
||||||
|
for(auto i = 0; i < 50; ++i) {
|
||||||
|
const auto entity = registry.create();
|
||||||
|
registry.assign<AnotherComponent>(entity, i, i);
|
||||||
|
|
||||||
|
if(i % 2) {
|
||||||
|
registry.assign<AComponent>(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using storage_type = std::tuple<
|
||||||
|
std::queue<entt::DefaultRegistry::entity_type>,
|
||||||
|
std::queue<AnotherComponent>
|
||||||
|
>;
|
||||||
|
|
||||||
|
storage_type storage;
|
||||||
|
OutputArchive<storage_type> output{storage};
|
||||||
|
InputArchive<storage_type> input{storage};
|
||||||
|
|
||||||
|
const auto view = registry.view<AComponent>();
|
||||||
|
const auto size = view.size();
|
||||||
|
|
||||||
|
registry.snapshot().component<AnotherComponent>(output, view.cbegin(), view.cend());
|
||||||
|
registry.reset();
|
||||||
|
registry.restore().component<AnotherComponent>(input);
|
||||||
|
|
||||||
|
ASSERT_EQ(registry.view<AnotherComponent>().size(), size);
|
||||||
|
|
||||||
|
registry.view<AnotherComponent>().each([](const auto entity, auto &&...) {
|
||||||
|
ASSERT_TRUE(entity % 2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Snapshot, Continuous) {
|
||||||
|
using entity_type = entt::DefaultRegistry::entity_type;
|
||||||
|
|
||||||
|
entt::DefaultRegistry src;
|
||||||
|
entt::DefaultRegistry dst;
|
||||||
|
|
||||||
|
entt::ContinuousLoader<entity_type> loader{dst};
|
||||||
|
|
||||||
|
std::vector<entity_type> entities;
|
||||||
|
entity_type entity;
|
||||||
|
|
||||||
|
using storage_type = std::tuple<
|
||||||
|
std::queue<entity_type>,
|
||||||
|
std::queue<AComponent>,
|
||||||
|
std::queue<AnotherComponent>,
|
||||||
|
std::queue<WhatAComponent>,
|
||||||
|
std::queue<double>
|
||||||
|
>;
|
||||||
|
|
||||||
|
storage_type storage;
|
||||||
|
OutputArchive<storage_type> output{storage};
|
||||||
|
InputArchive<storage_type> input{storage};
|
||||||
|
|
||||||
|
for(int i = 0; i < 10; ++i) {
|
||||||
|
src.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
src.reset();
|
||||||
|
|
||||||
|
for(int i = 0; i < 5; ++i) {
|
||||||
|
entity = src.create();
|
||||||
|
entities.push_back(entity);
|
||||||
|
|
||||||
|
src.assign<AComponent>(entity);
|
||||||
|
src.assign<AnotherComponent>(entity, i, i);
|
||||||
|
|
||||||
|
if(i % 2) {
|
||||||
|
src.assign<WhatAComponent>(entity, entity);
|
||||||
|
} else if(i == 2) {
|
||||||
|
src.assign<double>(entt::tag_t{}, entity, .3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
src.view<WhatAComponent>().each([&entities](auto, auto &whatAComponent) {
|
||||||
|
whatAComponent.quux.insert(whatAComponent.quux.begin(), entities.begin(), entities.end());
|
||||||
|
});
|
||||||
|
|
||||||
|
entity = dst.create();
|
||||||
|
dst.assign<AComponent>(entity);
|
||||||
|
dst.assign<AnotherComponent>(entity, -1, -1);
|
||||||
|
|
||||||
|
src.snapshot()
|
||||||
|
.entities(output)
|
||||||
|
.destroyed(output)
|
||||||
|
.component<AComponent, AnotherComponent, WhatAComponent>(output)
|
||||||
|
.tag<double>(output);
|
||||||
|
|
||||||
|
loader.entities(input)
|
||||||
|
.destroyed(input)
|
||||||
|
.component<AComponent, AnotherComponent, WhatAComponent>(input, &WhatAComponent::bar, &WhatAComponent::quux)
|
||||||
|
.tag<double>(input)
|
||||||
|
.orphans();
|
||||||
|
|
||||||
|
decltype(dst.size()) aComponentCnt{};
|
||||||
|
decltype(dst.size()) anotherComponentCnt{};
|
||||||
|
decltype(dst.size()) whatAComponentCnt{};
|
||||||
|
|
||||||
|
dst.each([&dst, &aComponentCnt](auto entity) {
|
||||||
|
ASSERT_TRUE(dst.has<AComponent>(entity));
|
||||||
|
++aComponentCnt;
|
||||||
|
});
|
||||||
|
|
||||||
|
dst.view<AnotherComponent>().each([&anotherComponentCnt](auto, const auto &component) {
|
||||||
|
ASSERT_EQ(component.value, component.key < 0 ? -1 : component.key);
|
||||||
|
++anotherComponentCnt;
|
||||||
|
});
|
||||||
|
|
||||||
|
dst.view<WhatAComponent>().each([&dst, &whatAComponentCnt](auto entity, const auto &component) {
|
||||||
|
ASSERT_EQ(entity, component.bar);
|
||||||
|
|
||||||
|
for(auto entity: component.quux) {
|
||||||
|
ASSERT_TRUE(dst.valid(entity));
|
||||||
|
}
|
||||||
|
|
||||||
|
++whatAComponentCnt;
|
||||||
|
});
|
||||||
|
|
||||||
|
ASSERT_TRUE(dst.has<double>());
|
||||||
|
ASSERT_EQ(dst.get<double>(), .3);
|
||||||
|
|
||||||
|
src.view<AnotherComponent>().each([](auto, auto &component) {
|
||||||
|
component.value = 2 * component.key;
|
||||||
|
});
|
||||||
|
|
||||||
|
auto size = dst.size();
|
||||||
|
|
||||||
|
src.snapshot()
|
||||||
|
.entities(output)
|
||||||
|
.destroyed(output)
|
||||||
|
.component<AComponent, WhatAComponent, AnotherComponent>(output)
|
||||||
|
.tag<double>(output);
|
||||||
|
|
||||||
|
loader.entities(input)
|
||||||
|
.destroyed(input)
|
||||||
|
.component<AComponent, WhatAComponent, AnotherComponent>(input, &WhatAComponent::bar, &WhatAComponent::quux)
|
||||||
|
.tag<double>(input)
|
||||||
|
.orphans();
|
||||||
|
|
||||||
|
ASSERT_EQ(size, dst.size());
|
||||||
|
|
||||||
|
ASSERT_EQ(dst.size<AComponent>(), aComponentCnt);
|
||||||
|
ASSERT_EQ(dst.size<AnotherComponent>(), anotherComponentCnt);
|
||||||
|
ASSERT_EQ(dst.size<WhatAComponent>(), whatAComponentCnt);
|
||||||
|
ASSERT_TRUE(dst.has<double>());
|
||||||
|
|
||||||
|
dst.view<AnotherComponent>().each([](auto, auto &component) {
|
||||||
|
ASSERT_EQ(component.value, component.key < 0 ? -1 : (2 * component.key));
|
||||||
|
});
|
||||||
|
|
||||||
|
entity = src.create();
|
||||||
|
|
||||||
|
src.view<WhatAComponent>().each([entity](auto, auto &component) {
|
||||||
|
component.bar = entity;
|
||||||
|
});
|
||||||
|
|
||||||
|
src.snapshot()
|
||||||
|
.entities(output)
|
||||||
|
.destroyed(output)
|
||||||
|
.component<WhatAComponent, AComponent, AnotherComponent>(output)
|
||||||
|
.tag<double>(output);
|
||||||
|
|
||||||
|
loader.entities(input)
|
||||||
|
.destroyed(input)
|
||||||
|
.component<WhatAComponent, AComponent, AnotherComponent>(input, &WhatAComponent::bar, &WhatAComponent::quux)
|
||||||
|
.tag<double>(input)
|
||||||
|
.orphans();
|
||||||
|
|
||||||
|
dst.view<WhatAComponent>().each([&loader, entity](auto, auto &component) {
|
||||||
|
ASSERT_EQ(component.bar, loader.map(entity));
|
||||||
|
});
|
||||||
|
|
||||||
|
entities.clear();
|
||||||
|
for(auto entity: src.view<AComponent>()) {
|
||||||
|
entities.push_back(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
src.destroy(entity);
|
||||||
|
loader.shrink();
|
||||||
|
|
||||||
|
src.snapshot()
|
||||||
|
.entities(output)
|
||||||
|
.destroyed(output)
|
||||||
|
.component<AComponent, AnotherComponent, WhatAComponent>(output)
|
||||||
|
.tag<double>(output);
|
||||||
|
|
||||||
|
loader.entities(input)
|
||||||
|
.destroyed(input)
|
||||||
|
.component<AComponent, AnotherComponent, WhatAComponent>(input, &WhatAComponent::bar, &WhatAComponent::quux)
|
||||||
|
.tag<double>(input)
|
||||||
|
.orphans()
|
||||||
|
.shrink();
|
||||||
|
|
||||||
|
dst.view<WhatAComponent>().each([&dst](auto, auto &component) {
|
||||||
|
ASSERT_FALSE(dst.valid(component.bar));
|
||||||
|
});
|
||||||
|
|
||||||
|
ASSERT_FALSE(loader.has(entity));
|
||||||
|
|
||||||
|
entity = src.create();
|
||||||
|
|
||||||
|
src.view<WhatAComponent>().each([entity](auto, auto &component) {
|
||||||
|
component.bar = entity;
|
||||||
|
});
|
||||||
|
|
||||||
|
dst.reset<AComponent>();
|
||||||
|
aComponentCnt = src.size<AComponent>();
|
||||||
|
|
||||||
|
src.snapshot()
|
||||||
|
.entities(output)
|
||||||
|
.destroyed(output)
|
||||||
|
.component<AComponent, WhatAComponent, AnotherComponent>(output)
|
||||||
|
.tag<double>(output);
|
||||||
|
|
||||||
|
loader.entities(input)
|
||||||
|
.destroyed(input)
|
||||||
|
.component<AComponent, WhatAComponent, AnotherComponent>(input, &WhatAComponent::bar, &WhatAComponent::quux)
|
||||||
|
.tag<double>(input)
|
||||||
|
.orphans();
|
||||||
|
|
||||||
|
ASSERT_EQ(dst.size<AComponent>(), aComponentCnt);
|
||||||
|
ASSERT_TRUE(dst.has<double>());
|
||||||
|
|
||||||
|
src.reset<AComponent>();
|
||||||
|
src.remove<double>();
|
||||||
|
aComponentCnt = {};
|
||||||
|
|
||||||
|
src.snapshot()
|
||||||
|
.entities(output)
|
||||||
|
.destroyed(output)
|
||||||
|
.component<WhatAComponent, AComponent, AnotherComponent>(output)
|
||||||
|
.tag<double>(output);
|
||||||
|
|
||||||
|
loader.entities(input)
|
||||||
|
.destroyed(input)
|
||||||
|
.component<WhatAComponent, AComponent, AnotherComponent>(input, &WhatAComponent::bar, &WhatAComponent::quux)
|
||||||
|
.tag<double>(input)
|
||||||
|
.orphans();
|
||||||
|
|
||||||
|
ASSERT_EQ(dst.size<AComponent>(), aComponentCnt);
|
||||||
|
ASSERT_FALSE(dst.has<double>());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Snapshot, ContinuousMoreOnShrink) {
|
||||||
|
using entity_type = entt::DefaultRegistry::entity_type;
|
||||||
|
|
||||||
|
entt::DefaultRegistry src;
|
||||||
|
entt::DefaultRegistry dst;
|
||||||
|
|
||||||
|
entt::ContinuousLoader<entity_type> loader{dst};
|
||||||
|
|
||||||
|
using storage_type = std::tuple<
|
||||||
|
std::queue<entity_type>,
|
||||||
|
std::queue<AComponent>
|
||||||
|
>;
|
||||||
|
|
||||||
|
storage_type storage;
|
||||||
|
OutputArchive<storage_type> output{storage};
|
||||||
|
InputArchive<storage_type> input{storage};
|
||||||
|
|
||||||
|
auto entity = src.create();
|
||||||
|
src.snapshot().entities(output);
|
||||||
|
loader.entities(input).shrink();
|
||||||
|
|
||||||
|
ASSERT_TRUE(dst.valid(entity));
|
||||||
|
|
||||||
|
loader.shrink();
|
||||||
|
|
||||||
|
ASSERT_FALSE(dst.valid(entity));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Snapshot, SyncDataMembers) {
|
||||||
|
using entity_type = entt::DefaultRegistry::entity_type;
|
||||||
|
|
||||||
|
entt::DefaultRegistry src;
|
||||||
|
entt::DefaultRegistry dst;
|
||||||
|
|
||||||
|
entt::ContinuousLoader<entity_type> loader{dst};
|
||||||
|
|
||||||
|
using storage_type = std::tuple<
|
||||||
|
std::queue<entity_type>,
|
||||||
|
std::queue<WhatAComponent>
|
||||||
|
>;
|
||||||
|
|
||||||
|
storage_type storage;
|
||||||
|
OutputArchive<storage_type> output{storage};
|
||||||
|
InputArchive<storage_type> input{storage};
|
||||||
|
|
||||||
|
src.create();
|
||||||
|
src.create();
|
||||||
|
|
||||||
|
src.reset();
|
||||||
|
|
||||||
|
auto parent = src.create();
|
||||||
|
auto child = src.create();
|
||||||
|
|
||||||
|
src.assign<WhatAComponent>(entt::tag_t{}, child, parent).quux.push_back(parent);
|
||||||
|
src.assign<WhatAComponent>(child, child).quux.push_back(child);
|
||||||
|
|
||||||
|
src.snapshot().entities(output)
|
||||||
|
.component<WhatAComponent>(output)
|
||||||
|
.tag<WhatAComponent>(output);
|
||||||
|
|
||||||
|
loader.entities(input)
|
||||||
|
.component<WhatAComponent>(input, &WhatAComponent::bar, &WhatAComponent::quux)
|
||||||
|
.tag<WhatAComponent>(input, &WhatAComponent::bar, &WhatAComponent::quux);
|
||||||
|
|
||||||
|
ASSERT_FALSE(dst.valid(parent));
|
||||||
|
ASSERT_FALSE(dst.valid(child));
|
||||||
|
|
||||||
|
ASSERT_TRUE(dst.has<WhatAComponent>());
|
||||||
|
ASSERT_EQ(dst.attachee<WhatAComponent>(), loader.map(child));
|
||||||
|
ASSERT_EQ(dst.get<WhatAComponent>().bar, loader.map(parent));
|
||||||
|
ASSERT_EQ(dst.get<WhatAComponent>().quux[0], loader.map(parent));
|
||||||
|
ASSERT_TRUE(dst.has<WhatAComponent>(loader.map(child)));
|
||||||
|
|
||||||
|
const auto &component = dst.get<WhatAComponent>(loader.map(child));
|
||||||
|
|
||||||
|
ASSERT_EQ(component.bar, loader.map(child));
|
||||||
|
ASSERT_EQ(component.quux[0], loader.map(child));
|
||||||
|
}
|
||||||
@@ -1,11 +1,17 @@
|
|||||||
|
#include <unordered_set>
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <entt/entity/sparse_set.hpp>
|
#include <entt/entity/sparse_set.hpp>
|
||||||
|
|
||||||
TEST(SparseSetNoType, Functionalities) {
|
TEST(SparseSetNoType, Functionalities) {
|
||||||
entt::SparseSet<unsigned int> set;
|
entt::SparseSet<std::uint64_t> set;
|
||||||
|
const auto &cset = set;
|
||||||
|
|
||||||
|
set.reserve(42);
|
||||||
|
|
||||||
|
ASSERT_EQ(set.capacity(), 42);
|
||||||
ASSERT_TRUE(set.empty());
|
ASSERT_TRUE(set.empty());
|
||||||
ASSERT_EQ(set.size(), 0u);
|
ASSERT_EQ(set.size(), 0u);
|
||||||
|
ASSERT_EQ(cset.begin(), cset.end());
|
||||||
ASSERT_EQ(set.begin(), set.end());
|
ASSERT_EQ(set.begin(), set.end());
|
||||||
ASSERT_FALSE(set.has(0));
|
ASSERT_FALSE(set.has(0));
|
||||||
ASSERT_FALSE(set.has(42));
|
ASSERT_FALSE(set.has(42));
|
||||||
@@ -16,15 +22,18 @@ TEST(SparseSetNoType, Functionalities) {
|
|||||||
|
|
||||||
ASSERT_FALSE(set.empty());
|
ASSERT_FALSE(set.empty());
|
||||||
ASSERT_EQ(set.size(), 1u);
|
ASSERT_EQ(set.size(), 1u);
|
||||||
|
ASSERT_NE(cset.begin(), cset.end());
|
||||||
ASSERT_NE(set.begin(), set.end());
|
ASSERT_NE(set.begin(), set.end());
|
||||||
ASSERT_FALSE(set.has(0));
|
ASSERT_FALSE(set.has(0));
|
||||||
ASSERT_TRUE(set.has(42));
|
ASSERT_TRUE(set.has(42));
|
||||||
|
ASSERT_TRUE(set.fast(42));
|
||||||
ASSERT_EQ(set.get(42), 0u);
|
ASSERT_EQ(set.get(42), 0u);
|
||||||
|
|
||||||
set.destroy(42);
|
set.destroy(42);
|
||||||
|
|
||||||
ASSERT_TRUE(set.empty());
|
ASSERT_TRUE(set.empty());
|
||||||
ASSERT_EQ(set.size(), 0u);
|
ASSERT_EQ(set.size(), 0u);
|
||||||
|
ASSERT_EQ(cset.begin(), cset.end());
|
||||||
ASSERT_EQ(set.begin(), set.end());
|
ASSERT_EQ(set.begin(), set.end());
|
||||||
ASSERT_FALSE(set.has(0));
|
ASSERT_FALSE(set.has(0));
|
||||||
ASSERT_FALSE(set.has(42));
|
ASSERT_FALSE(set.has(42));
|
||||||
@@ -37,17 +46,121 @@ TEST(SparseSetNoType, Functionalities) {
|
|||||||
|
|
||||||
ASSERT_TRUE(set.empty());
|
ASSERT_TRUE(set.empty());
|
||||||
ASSERT_EQ(set.size(), 0u);
|
ASSERT_EQ(set.size(), 0u);
|
||||||
|
ASSERT_EQ(cset.begin(), cset.end());
|
||||||
ASSERT_EQ(set.begin(), set.end());
|
ASSERT_EQ(set.begin(), set.end());
|
||||||
ASSERT_FALSE(set.has(0));
|
ASSERT_FALSE(set.has(0));
|
||||||
ASSERT_FALSE(set.has(42));
|
ASSERT_FALSE(set.has(42));
|
||||||
|
|
||||||
(void)entt::SparseSet<unsigned int>{std::move(set)};
|
(void)entt::SparseSet<std::uint64_t>{std::move(set)};
|
||||||
entt::SparseSet<unsigned int> other;
|
entt::SparseSet<std::uint64_t> other;
|
||||||
other = std::move(set);
|
other = std::move(set);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SparseSetNoType, DataBeginEnd) {
|
TEST(SparseSetNoType, ElementAccess) {
|
||||||
entt::SparseSet<unsigned int> set;
|
entt::SparseSet<std::uint64_t> set;
|
||||||
|
const auto &cset = set;
|
||||||
|
|
||||||
|
set.construct(42);
|
||||||
|
set.construct(3);
|
||||||
|
|
||||||
|
for(typename entt::SparseSet<std::uint64_t>::size_type i{}; i < set.size(); ++i) {
|
||||||
|
ASSERT_EQ(set[i], i ? 42 : 3);
|
||||||
|
ASSERT_EQ(cset[i], i ? 42 : 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SparseSetNoType, Iterator) {
|
||||||
|
using iterator_type = typename entt::SparseSet<std::uint64_t>::iterator_type;
|
||||||
|
|
||||||
|
entt::SparseSet<std::uint64_t> set;
|
||||||
|
set.construct(3);
|
||||||
|
|
||||||
|
iterator_type end{set.begin()};
|
||||||
|
iterator_type begin{};
|
||||||
|
begin = set.end();
|
||||||
|
std::swap(begin, end);
|
||||||
|
|
||||||
|
ASSERT_EQ(begin, set.begin());
|
||||||
|
ASSERT_EQ(end, set.end());
|
||||||
|
ASSERT_NE(begin, end);
|
||||||
|
|
||||||
|
ASSERT_EQ(begin++, set.begin());
|
||||||
|
ASSERT_EQ(begin--, set.end());
|
||||||
|
|
||||||
|
ASSERT_EQ(begin+1, set.end());
|
||||||
|
ASSERT_EQ(end-1, set.begin());
|
||||||
|
|
||||||
|
ASSERT_EQ(++begin, set.end());
|
||||||
|
ASSERT_EQ(--begin, set.begin());
|
||||||
|
|
||||||
|
ASSERT_EQ(begin += 1, set.end());
|
||||||
|
ASSERT_EQ(begin -= 1, set.begin());
|
||||||
|
|
||||||
|
ASSERT_EQ(begin + (end - begin), set.end());
|
||||||
|
ASSERT_EQ(begin - (begin - end), set.end());
|
||||||
|
|
||||||
|
ASSERT_EQ(end - (end - begin), set.begin());
|
||||||
|
ASSERT_EQ(end + (begin - end), set.begin());
|
||||||
|
|
||||||
|
ASSERT_EQ(begin[0], *set.begin());
|
||||||
|
|
||||||
|
ASSERT_LT(begin, end);
|
||||||
|
ASSERT_LE(begin, set.begin());
|
||||||
|
|
||||||
|
ASSERT_GT(end, begin);
|
||||||
|
ASSERT_GE(end, set.end());
|
||||||
|
|
||||||
|
ASSERT_EQ(*begin, 3);
|
||||||
|
ASSERT_EQ(*begin.operator->(), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SparseSetNoType, ConstIterator) {
|
||||||
|
using iterator_type = typename entt::SparseSet<std::uint64_t>::const_iterator_type;
|
||||||
|
|
||||||
|
entt::SparseSet<std::uint64_t> set;
|
||||||
|
set.construct(3);
|
||||||
|
|
||||||
|
iterator_type cend{set.cbegin()};
|
||||||
|
iterator_type cbegin{};
|
||||||
|
cbegin = set.cend();
|
||||||
|
std::swap(cbegin, cend);
|
||||||
|
|
||||||
|
ASSERT_EQ(cbegin, set.cbegin());
|
||||||
|
ASSERT_EQ(cend, set.cend());
|
||||||
|
ASSERT_NE(cbegin, cend);
|
||||||
|
|
||||||
|
ASSERT_EQ(cbegin++, set.cbegin());
|
||||||
|
ASSERT_EQ(cbegin--, set.cend());
|
||||||
|
|
||||||
|
ASSERT_EQ(cbegin+1, set.cend());
|
||||||
|
ASSERT_EQ(cend-1, set.cbegin());
|
||||||
|
|
||||||
|
ASSERT_EQ(++cbegin, set.cend());
|
||||||
|
ASSERT_EQ(--cbegin, set.cbegin());
|
||||||
|
|
||||||
|
ASSERT_EQ(cbegin += 1, set.cend());
|
||||||
|
ASSERT_EQ(cbegin -= 1, set.cbegin());
|
||||||
|
|
||||||
|
ASSERT_EQ(cbegin + (cend - cbegin), set.cend());
|
||||||
|
ASSERT_EQ(cbegin - (cbegin - cend), set.cend());
|
||||||
|
|
||||||
|
ASSERT_EQ(cend - (cend - cbegin), set.cbegin());
|
||||||
|
ASSERT_EQ(cend + (cbegin - cend), set.cbegin());
|
||||||
|
|
||||||
|
ASSERT_EQ(cbegin[0], *set.cbegin());
|
||||||
|
|
||||||
|
ASSERT_LT(cbegin, cend);
|
||||||
|
ASSERT_LE(cbegin, set.cbegin());
|
||||||
|
|
||||||
|
ASSERT_GT(cend, cbegin);
|
||||||
|
ASSERT_GE(cend, set.cend());
|
||||||
|
|
||||||
|
ASSERT_EQ(*cbegin, 3);
|
||||||
|
ASSERT_EQ(*cbegin.operator->(), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SparseSetNoType, Data) {
|
||||||
|
entt::SparseSet<std::uint64_t> set;
|
||||||
|
|
||||||
set.construct(3);
|
set.construct(3);
|
||||||
set.construct(12);
|
set.construct(12);
|
||||||
@@ -60,45 +173,216 @@ TEST(SparseSetNoType, DataBeginEnd) {
|
|||||||
ASSERT_EQ(*(set.data() + 0u), 3u);
|
ASSERT_EQ(*(set.data() + 0u), 3u);
|
||||||
ASSERT_EQ(*(set.data() + 1u), 12u);
|
ASSERT_EQ(*(set.data() + 1u), 12u);
|
||||||
ASSERT_EQ(*(set.data() + 2u), 42u);
|
ASSERT_EQ(*(set.data() + 2u), 42u);
|
||||||
|
|
||||||
auto begin = set.begin();
|
|
||||||
auto end = set.end();
|
|
||||||
|
|
||||||
ASSERT_EQ(*(begin++), 42u);
|
|
||||||
ASSERT_EQ(*(begin++), 12u);
|
|
||||||
ASSERT_EQ(*(begin++), 3u);
|
|
||||||
ASSERT_EQ(begin, end);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SparseSetWithType, AggregatesMustWork) {
|
TEST(SparseSetNoType, RespectDisjoint) {
|
||||||
struct AggregateType { int value; };
|
entt::SparseSet<std::uint64_t> lhs;
|
||||||
// the goal of this test is to enforce the requirements for aggregate types
|
entt::SparseSet<std::uint64_t> rhs;
|
||||||
entt::SparseSet<unsigned int, AggregateType>{}.construct(0, 42);
|
const auto &clhs = lhs;
|
||||||
|
|
||||||
|
lhs.construct(3);
|
||||||
|
lhs.construct(12);
|
||||||
|
lhs.construct(42);
|
||||||
|
|
||||||
|
ASSERT_EQ(lhs.get(3), 0u);
|
||||||
|
ASSERT_EQ(lhs.get(12), 1u);
|
||||||
|
ASSERT_EQ(lhs.get(42), 2u);
|
||||||
|
|
||||||
|
lhs.respect(rhs);
|
||||||
|
|
||||||
|
ASSERT_EQ(clhs.get(3), 0u);
|
||||||
|
ASSERT_EQ(clhs.get(12), 1u);
|
||||||
|
ASSERT_EQ(clhs.get(42), 2u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SparseSetNoType, RespectOverlap) {
|
||||||
|
entt::SparseSet<std::uint64_t> lhs;
|
||||||
|
entt::SparseSet<std::uint64_t> rhs;
|
||||||
|
const auto &clhs = lhs;
|
||||||
|
|
||||||
|
lhs.construct(3);
|
||||||
|
lhs.construct(12);
|
||||||
|
lhs.construct(42);
|
||||||
|
|
||||||
|
rhs.construct(12);
|
||||||
|
|
||||||
|
ASSERT_EQ(lhs.get(3), 0u);
|
||||||
|
ASSERT_EQ(lhs.get(12), 1u);
|
||||||
|
ASSERT_EQ(lhs.get(42), 2u);
|
||||||
|
|
||||||
|
lhs.respect(rhs);
|
||||||
|
|
||||||
|
ASSERT_EQ(clhs.get(3), 0u);
|
||||||
|
ASSERT_EQ(clhs.get(12), 2u);
|
||||||
|
ASSERT_EQ(clhs.get(42), 1u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SparseSetNoType, RespectOrdered) {
|
||||||
|
entt::SparseSet<std::uint64_t> lhs;
|
||||||
|
entt::SparseSet<std::uint64_t> rhs;
|
||||||
|
|
||||||
|
lhs.construct(1);
|
||||||
|
lhs.construct(2);
|
||||||
|
lhs.construct(3);
|
||||||
|
lhs.construct(4);
|
||||||
|
lhs.construct(5);
|
||||||
|
|
||||||
|
ASSERT_EQ(lhs.get(1), 0u);
|
||||||
|
ASSERT_EQ(lhs.get(2), 1u);
|
||||||
|
ASSERT_EQ(lhs.get(3), 2u);
|
||||||
|
ASSERT_EQ(lhs.get(4), 3u);
|
||||||
|
ASSERT_EQ(lhs.get(5), 4u);
|
||||||
|
|
||||||
|
rhs.construct(6);
|
||||||
|
rhs.construct(1);
|
||||||
|
rhs.construct(2);
|
||||||
|
rhs.construct(3);
|
||||||
|
rhs.construct(4);
|
||||||
|
rhs.construct(5);
|
||||||
|
|
||||||
|
ASSERT_EQ(rhs.get(6), 0u);
|
||||||
|
ASSERT_EQ(rhs.get(1), 1u);
|
||||||
|
ASSERT_EQ(rhs.get(2), 2u);
|
||||||
|
ASSERT_EQ(rhs.get(3), 3u);
|
||||||
|
ASSERT_EQ(rhs.get(4), 4u);
|
||||||
|
ASSERT_EQ(rhs.get(5), 5u);
|
||||||
|
|
||||||
|
rhs.respect(lhs);
|
||||||
|
|
||||||
|
ASSERT_EQ(rhs.get(6), 0u);
|
||||||
|
ASSERT_EQ(rhs.get(1), 1u);
|
||||||
|
ASSERT_EQ(rhs.get(2), 2u);
|
||||||
|
ASSERT_EQ(rhs.get(3), 3u);
|
||||||
|
ASSERT_EQ(rhs.get(4), 4u);
|
||||||
|
ASSERT_EQ(rhs.get(5), 5u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SparseSetNoType, RespectReverse) {
|
||||||
|
entt::SparseSet<std::uint64_t> lhs;
|
||||||
|
entt::SparseSet<std::uint64_t> rhs;
|
||||||
|
|
||||||
|
lhs.construct(1);
|
||||||
|
lhs.construct(2);
|
||||||
|
lhs.construct(3);
|
||||||
|
lhs.construct(4);
|
||||||
|
lhs.construct(5);
|
||||||
|
|
||||||
|
ASSERT_EQ(lhs.get(1), 0u);
|
||||||
|
ASSERT_EQ(lhs.get(2), 1u);
|
||||||
|
ASSERT_EQ(lhs.get(3), 2u);
|
||||||
|
ASSERT_EQ(lhs.get(4), 3u);
|
||||||
|
ASSERT_EQ(lhs.get(5), 4u);
|
||||||
|
|
||||||
|
rhs.construct(5);
|
||||||
|
rhs.construct(4);
|
||||||
|
rhs.construct(3);
|
||||||
|
rhs.construct(2);
|
||||||
|
rhs.construct(1);
|
||||||
|
rhs.construct(6);
|
||||||
|
|
||||||
|
ASSERT_EQ(rhs.get(5), 0u);
|
||||||
|
ASSERT_EQ(rhs.get(4), 1u);
|
||||||
|
ASSERT_EQ(rhs.get(3), 2u);
|
||||||
|
ASSERT_EQ(rhs.get(2), 3u);
|
||||||
|
ASSERT_EQ(rhs.get(1), 4u);
|
||||||
|
ASSERT_EQ(rhs.get(6), 5u);
|
||||||
|
|
||||||
|
rhs.respect(lhs);
|
||||||
|
|
||||||
|
ASSERT_EQ(rhs.get(6), 0u);
|
||||||
|
ASSERT_EQ(rhs.get(1), 1u);
|
||||||
|
ASSERT_EQ(rhs.get(2), 2u);
|
||||||
|
ASSERT_EQ(rhs.get(3), 3u);
|
||||||
|
ASSERT_EQ(rhs.get(4), 4u);
|
||||||
|
ASSERT_EQ(rhs.get(5), 5u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SparseSetNoType, RespectUnordered) {
|
||||||
|
entt::SparseSet<std::uint64_t> lhs;
|
||||||
|
entt::SparseSet<std::uint64_t> rhs;
|
||||||
|
|
||||||
|
lhs.construct(1);
|
||||||
|
lhs.construct(2);
|
||||||
|
lhs.construct(3);
|
||||||
|
lhs.construct(4);
|
||||||
|
lhs.construct(5);
|
||||||
|
|
||||||
|
ASSERT_EQ(lhs.get(1), 0u);
|
||||||
|
ASSERT_EQ(lhs.get(2), 1u);
|
||||||
|
ASSERT_EQ(lhs.get(3), 2u);
|
||||||
|
ASSERT_EQ(lhs.get(4), 3u);
|
||||||
|
ASSERT_EQ(lhs.get(5), 4u);
|
||||||
|
|
||||||
|
rhs.construct(3);
|
||||||
|
rhs.construct(2);
|
||||||
|
rhs.construct(6);
|
||||||
|
rhs.construct(1);
|
||||||
|
rhs.construct(4);
|
||||||
|
rhs.construct(5);
|
||||||
|
|
||||||
|
ASSERT_EQ(rhs.get(3), 0u);
|
||||||
|
ASSERT_EQ(rhs.get(2), 1u);
|
||||||
|
ASSERT_EQ(rhs.get(6), 2u);
|
||||||
|
ASSERT_EQ(rhs.get(1), 3u);
|
||||||
|
ASSERT_EQ(rhs.get(4), 4u);
|
||||||
|
ASSERT_EQ(rhs.get(5), 5u);
|
||||||
|
|
||||||
|
rhs.respect(lhs);
|
||||||
|
|
||||||
|
ASSERT_EQ(rhs.get(6), 0u);
|
||||||
|
ASSERT_EQ(rhs.get(1), 1u);
|
||||||
|
ASSERT_EQ(rhs.get(2), 2u);
|
||||||
|
ASSERT_EQ(rhs.get(3), 3u);
|
||||||
|
ASSERT_EQ(rhs.get(4), 4u);
|
||||||
|
ASSERT_EQ(rhs.get(5), 5u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SparseSetNoType, CanModifyDuringIteration) {
|
||||||
|
entt::SparseSet<std::uint64_t> set;
|
||||||
|
set.construct(0);
|
||||||
|
|
||||||
|
ASSERT_EQ(set.capacity(), entt::SparseSet<std::uint64_t>::size_type{1});
|
||||||
|
|
||||||
|
const auto it = set.cbegin();
|
||||||
|
set.reserve(entt::SparseSet<std::uint64_t>::size_type{2});
|
||||||
|
|
||||||
|
ASSERT_EQ(set.capacity(), entt::SparseSet<std::uint64_t>::size_type{2});
|
||||||
|
|
||||||
|
// this should crash with asan enabled if we break the constraint
|
||||||
|
const auto entity = *it;
|
||||||
|
(void)entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SparseSetWithType, Functionalities) {
|
TEST(SparseSetWithType, Functionalities) {
|
||||||
entt::SparseSet<unsigned int, int> set;
|
entt::SparseSet<std::uint64_t, int> set;
|
||||||
|
const auto &cset = set;
|
||||||
|
|
||||||
|
set.reserve(42);
|
||||||
|
|
||||||
|
ASSERT_EQ(set.capacity(), 42);
|
||||||
ASSERT_TRUE(set.empty());
|
ASSERT_TRUE(set.empty());
|
||||||
ASSERT_EQ(set.size(), 0u);
|
ASSERT_EQ(set.size(), 0u);
|
||||||
|
ASSERT_EQ(cset.begin(), cset.end());
|
||||||
ASSERT_EQ(set.begin(), set.end());
|
ASSERT_EQ(set.begin(), set.end());
|
||||||
ASSERT_FALSE(set.has(0));
|
ASSERT_FALSE(set.has(0));
|
||||||
ASSERT_FALSE(set.has(42));
|
ASSERT_FALSE(set.has(42));
|
||||||
|
|
||||||
set.construct(42, 3);
|
set.construct(42, 3);
|
||||||
|
|
||||||
ASSERT_EQ(set.get(42), 3);
|
|
||||||
ASSERT_FALSE(set.empty());
|
ASSERT_FALSE(set.empty());
|
||||||
ASSERT_EQ(set.size(), 1u);
|
ASSERT_EQ(set.size(), 1u);
|
||||||
|
ASSERT_NE(cset.begin(), cset.end());
|
||||||
ASSERT_NE(set.begin(), set.end());
|
ASSERT_NE(set.begin(), set.end());
|
||||||
ASSERT_FALSE(set.has(0));
|
ASSERT_FALSE(set.has(0));
|
||||||
ASSERT_TRUE(set.has(42));
|
ASSERT_TRUE(set.has(42));
|
||||||
|
ASSERT_TRUE(set.fast(42));
|
||||||
ASSERT_EQ(set.get(42), 3);
|
ASSERT_EQ(set.get(42), 3);
|
||||||
|
|
||||||
set.destroy(42);
|
set.destroy(42);
|
||||||
|
|
||||||
ASSERT_TRUE(set.empty());
|
ASSERT_TRUE(set.empty());
|
||||||
ASSERT_EQ(set.size(), 0u);
|
ASSERT_EQ(set.size(), 0u);
|
||||||
|
ASSERT_EQ(cset.begin(), cset.end());
|
||||||
ASSERT_EQ(set.begin(), set.end());
|
ASSERT_EQ(set.begin(), set.end());
|
||||||
ASSERT_FALSE(set.has(0));
|
ASSERT_FALSE(set.has(0));
|
||||||
ASSERT_FALSE(set.has(42));
|
ASSERT_FALSE(set.has(42));
|
||||||
@@ -111,17 +395,132 @@ TEST(SparseSetWithType, Functionalities) {
|
|||||||
|
|
||||||
ASSERT_TRUE(set.empty());
|
ASSERT_TRUE(set.empty());
|
||||||
ASSERT_EQ(set.size(), 0u);
|
ASSERT_EQ(set.size(), 0u);
|
||||||
|
ASSERT_EQ(cset.begin(), cset.end());
|
||||||
ASSERT_EQ(set.begin(), set.end());
|
ASSERT_EQ(set.begin(), set.end());
|
||||||
ASSERT_FALSE(set.has(0));
|
ASSERT_FALSE(set.has(0));
|
||||||
ASSERT_FALSE(set.has(42));
|
ASSERT_FALSE(set.has(42));
|
||||||
|
|
||||||
(void)entt::SparseSet<unsigned int>{std::move(set)};
|
(void)entt::SparseSet<std::uint64_t, int>{std::move(set)};
|
||||||
entt::SparseSet<unsigned int> other;
|
entt::SparseSet<std::uint64_t, int> other;
|
||||||
other = std::move(set);
|
other = std::move(set);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SparseSetWithType, RawBeginEnd) {
|
TEST(SparseSetWithType, ElementAccess) {
|
||||||
entt::SparseSet<unsigned int, int> set;
|
entt::SparseSet<std::uint64_t, int> set;
|
||||||
|
const auto &cset = set;
|
||||||
|
|
||||||
|
set.construct(42, 1);
|
||||||
|
set.construct(3, 0);
|
||||||
|
|
||||||
|
for(typename entt::SparseSet<std::uint64_t, int>::size_type i{}; i < set.size(); ++i) {
|
||||||
|
ASSERT_EQ(set[i], i);
|
||||||
|
ASSERT_EQ(cset[i], i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SparseSetWithType, AggregatesMustWork) {
|
||||||
|
struct AggregateType { int value; };
|
||||||
|
// the goal of this test is to enforce the requirements for aggregate types
|
||||||
|
entt::SparseSet<std::uint64_t, AggregateType>{}.construct(0, 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SparseSetWithType, TypesFromStandardTemplateLibraryMustWork) {
|
||||||
|
// see #37 - this test shouldn't crash, that's all
|
||||||
|
entt::SparseSet<std::uint64_t, std::unordered_set<int>> set;
|
||||||
|
set.construct(0).insert(42);
|
||||||
|
set.destroy(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SparseSetWithType, Iterator) {
|
||||||
|
struct InternalType { int value; };
|
||||||
|
|
||||||
|
using iterator_type = typename entt::SparseSet<std::uint64_t, InternalType>::iterator_type;
|
||||||
|
|
||||||
|
entt::SparseSet<std::uint64_t, InternalType> set;
|
||||||
|
set.construct(3, 42);
|
||||||
|
|
||||||
|
iterator_type end{set.begin()};
|
||||||
|
iterator_type begin{};
|
||||||
|
begin = set.end();
|
||||||
|
std::swap(begin, end);
|
||||||
|
|
||||||
|
ASSERT_EQ(begin, set.begin());
|
||||||
|
ASSERT_EQ(end, set.end());
|
||||||
|
ASSERT_NE(begin, end);
|
||||||
|
|
||||||
|
ASSERT_EQ(begin++, set.begin());
|
||||||
|
ASSERT_EQ(begin--, set.end());
|
||||||
|
|
||||||
|
ASSERT_EQ(begin+1, set.end());
|
||||||
|
ASSERT_EQ(end-1, set.begin());
|
||||||
|
|
||||||
|
ASSERT_EQ(++begin, set.end());
|
||||||
|
ASSERT_EQ(--begin, set.begin());
|
||||||
|
|
||||||
|
ASSERT_EQ(begin += 1, set.end());
|
||||||
|
ASSERT_EQ(begin -= 1, set.begin());
|
||||||
|
|
||||||
|
ASSERT_EQ(begin + (end - begin), set.end());
|
||||||
|
ASSERT_EQ(begin - (begin - end), set.end());
|
||||||
|
|
||||||
|
ASSERT_EQ(end - (end - begin), set.begin());
|
||||||
|
ASSERT_EQ(end + (begin - end), set.begin());
|
||||||
|
|
||||||
|
ASSERT_EQ(begin[0].value, set.begin()->value);
|
||||||
|
|
||||||
|
ASSERT_LT(begin, end);
|
||||||
|
ASSERT_LE(begin, set.begin());
|
||||||
|
|
||||||
|
ASSERT_GT(end, begin);
|
||||||
|
ASSERT_GE(end, set.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SparseSetWithType, ConstIterator) {
|
||||||
|
struct InternalType { int value; };
|
||||||
|
|
||||||
|
using iterator_type = typename entt::SparseSet<std::uint64_t, InternalType>::const_iterator_type;
|
||||||
|
|
||||||
|
entt::SparseSet<std::uint64_t, InternalType> set;
|
||||||
|
set.construct(3, 42);
|
||||||
|
|
||||||
|
iterator_type cend{set.cbegin()};
|
||||||
|
iterator_type cbegin{};
|
||||||
|
cbegin = set.cend();
|
||||||
|
std::swap(cbegin, cend);
|
||||||
|
|
||||||
|
ASSERT_EQ(cbegin, set.cbegin());
|
||||||
|
ASSERT_EQ(cend, set.cend());
|
||||||
|
ASSERT_NE(cbegin, cend);
|
||||||
|
|
||||||
|
ASSERT_EQ(cbegin++, set.cbegin());
|
||||||
|
ASSERT_EQ(cbegin--, set.cend());
|
||||||
|
|
||||||
|
ASSERT_EQ(cbegin+1, set.cend());
|
||||||
|
ASSERT_EQ(cend-1, set.cbegin());
|
||||||
|
|
||||||
|
ASSERT_EQ(++cbegin, set.cend());
|
||||||
|
ASSERT_EQ(--cbegin, set.cbegin());
|
||||||
|
|
||||||
|
ASSERT_EQ(cbegin += 1, set.cend());
|
||||||
|
ASSERT_EQ(cbegin -= 1, set.cbegin());
|
||||||
|
|
||||||
|
ASSERT_EQ(cbegin + (cend - cbegin), set.cend());
|
||||||
|
ASSERT_EQ(cbegin - (cbegin - cend), set.cend());
|
||||||
|
|
||||||
|
ASSERT_EQ(cend - (cend - cbegin), set.cbegin());
|
||||||
|
ASSERT_EQ(cend + (cbegin - cend), set.cbegin());
|
||||||
|
|
||||||
|
ASSERT_EQ(cbegin[0].value, set.cbegin()->value);
|
||||||
|
|
||||||
|
ASSERT_LT(cbegin, cend);
|
||||||
|
ASSERT_LE(cbegin, set.cbegin());
|
||||||
|
|
||||||
|
ASSERT_GT(cend, cbegin);
|
||||||
|
ASSERT_GE(cend, set.cend());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SparseSetWithType, Raw) {
|
||||||
|
entt::SparseSet<std::uint64_t, int> set;
|
||||||
|
|
||||||
set.construct(3, 3);
|
set.construct(3, 3);
|
||||||
set.construct(12, 6);
|
set.construct(12, 6);
|
||||||
@@ -134,18 +533,10 @@ TEST(SparseSetWithType, RawBeginEnd) {
|
|||||||
ASSERT_EQ(*(set.raw() + 0u), 3);
|
ASSERT_EQ(*(set.raw() + 0u), 3);
|
||||||
ASSERT_EQ(*(set.raw() + 1u), 6);
|
ASSERT_EQ(*(set.raw() + 1u), 6);
|
||||||
ASSERT_EQ(*(set.raw() + 2u), 9);
|
ASSERT_EQ(*(set.raw() + 2u), 9);
|
||||||
|
|
||||||
auto begin = set.begin();
|
|
||||||
auto end = set.end();
|
|
||||||
|
|
||||||
ASSERT_EQ(set.get(*(begin++)), 9);
|
|
||||||
ASSERT_EQ(set.get(*(begin++)), 6);
|
|
||||||
ASSERT_EQ(set.get(*(begin++)), 3);
|
|
||||||
ASSERT_EQ(begin, end);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SparseSetWithType, SortOrdered) {
|
TEST(SparseSetWithType, SortOrdered) {
|
||||||
entt::SparseSet<unsigned int, int> set;
|
entt::SparseSet<std::uint64_t, int> set;
|
||||||
|
|
||||||
set.construct(12, 12);
|
set.construct(12, 12);
|
||||||
set.construct(42, 9);
|
set.construct(42, 9);
|
||||||
@@ -159,8 +550,8 @@ TEST(SparseSetWithType, SortOrdered) {
|
|||||||
ASSERT_EQ(set.get(3), 3);
|
ASSERT_EQ(set.get(3), 3);
|
||||||
ASSERT_EQ(set.get(9), 1);
|
ASSERT_EQ(set.get(9), 1);
|
||||||
|
|
||||||
set.sort([&set](auto lhs, auto rhs) {
|
set.sort([](auto lhs, auto rhs) {
|
||||||
return set.get(lhs) < set.get(rhs);
|
return lhs < rhs;
|
||||||
});
|
});
|
||||||
|
|
||||||
ASSERT_EQ(*(set.raw() + 0u), 12);
|
ASSERT_EQ(*(set.raw() + 0u), 12);
|
||||||
@@ -172,16 +563,16 @@ TEST(SparseSetWithType, SortOrdered) {
|
|||||||
auto begin = set.begin();
|
auto begin = set.begin();
|
||||||
auto end = set.end();
|
auto end = set.end();
|
||||||
|
|
||||||
ASSERT_EQ(set.get(*(begin++)), 1);
|
ASSERT_EQ(*(begin++), 1);
|
||||||
ASSERT_EQ(set.get(*(begin++)), 3);
|
ASSERT_EQ(*(begin++), 3);
|
||||||
ASSERT_EQ(set.get(*(begin++)), 6);
|
ASSERT_EQ(*(begin++), 6);
|
||||||
ASSERT_EQ(set.get(*(begin++)), 9);
|
ASSERT_EQ(*(begin++), 9);
|
||||||
ASSERT_EQ(set.get(*(begin++)), 12);
|
ASSERT_EQ(*(begin++), 12);
|
||||||
ASSERT_EQ(begin, end);
|
ASSERT_EQ(begin, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SparseSetWithType, SortReverse) {
|
TEST(SparseSetWithType, SortReverse) {
|
||||||
entt::SparseSet<unsigned int, int> set;
|
entt::SparseSet<std::uint64_t, int> set;
|
||||||
|
|
||||||
set.construct(12, 1);
|
set.construct(12, 1);
|
||||||
set.construct(42, 3);
|
set.construct(42, 3);
|
||||||
@@ -195,8 +586,8 @@ TEST(SparseSetWithType, SortReverse) {
|
|||||||
ASSERT_EQ(set.get(3), 9);
|
ASSERT_EQ(set.get(3), 9);
|
||||||
ASSERT_EQ(set.get(9), 12);
|
ASSERT_EQ(set.get(9), 12);
|
||||||
|
|
||||||
set.sort([&set](auto lhs, auto rhs) {
|
set.sort([](auto lhs, auto rhs) {
|
||||||
return set.get(lhs) < set.get(rhs);
|
return lhs < rhs;
|
||||||
});
|
});
|
||||||
|
|
||||||
ASSERT_EQ(*(set.raw() + 0u), 12);
|
ASSERT_EQ(*(set.raw() + 0u), 12);
|
||||||
@@ -208,16 +599,16 @@ TEST(SparseSetWithType, SortReverse) {
|
|||||||
auto begin = set.begin();
|
auto begin = set.begin();
|
||||||
auto end = set.end();
|
auto end = set.end();
|
||||||
|
|
||||||
ASSERT_EQ(set.get(*(begin++)), 1);
|
ASSERT_EQ(*(begin++), 1);
|
||||||
ASSERT_EQ(set.get(*(begin++)), 3);
|
ASSERT_EQ(*(begin++), 3);
|
||||||
ASSERT_EQ(set.get(*(begin++)), 6);
|
ASSERT_EQ(*(begin++), 6);
|
||||||
ASSERT_EQ(set.get(*(begin++)), 9);
|
ASSERT_EQ(*(begin++), 9);
|
||||||
ASSERT_EQ(set.get(*(begin++)), 12);
|
ASSERT_EQ(*(begin++), 12);
|
||||||
ASSERT_EQ(begin, end);
|
ASSERT_EQ(begin, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SparseSetWithType, SortUnordered) {
|
TEST(SparseSetWithType, SortUnordered) {
|
||||||
entt::SparseSet<unsigned int, int> set;
|
entt::SparseSet<std::uint64_t, int> set;
|
||||||
|
|
||||||
set.construct(12, 6);
|
set.construct(12, 6);
|
||||||
set.construct(42, 3);
|
set.construct(42, 3);
|
||||||
@@ -231,8 +622,8 @@ TEST(SparseSetWithType, SortUnordered) {
|
|||||||
ASSERT_EQ(set.get(3), 9);
|
ASSERT_EQ(set.get(3), 9);
|
||||||
ASSERT_EQ(set.get(9), 12);
|
ASSERT_EQ(set.get(9), 12);
|
||||||
|
|
||||||
set.sort([&set](auto lhs, auto rhs) {
|
set.sort([](auto lhs, auto rhs) {
|
||||||
return set.get(lhs) < set.get(rhs);
|
return lhs < rhs;
|
||||||
});
|
});
|
||||||
|
|
||||||
ASSERT_EQ(*(set.raw() + 0u), 12);
|
ASSERT_EQ(*(set.raw() + 0u), 12);
|
||||||
@@ -244,26 +635,26 @@ TEST(SparseSetWithType, SortUnordered) {
|
|||||||
auto begin = set.begin();
|
auto begin = set.begin();
|
||||||
auto end = set.end();
|
auto end = set.end();
|
||||||
|
|
||||||
ASSERT_EQ(set.get(*(begin++)), 1);
|
ASSERT_EQ(*(begin++), 1);
|
||||||
ASSERT_EQ(set.get(*(begin++)), 3);
|
ASSERT_EQ(*(begin++), 3);
|
||||||
ASSERT_EQ(set.get(*(begin++)), 6);
|
ASSERT_EQ(*(begin++), 6);
|
||||||
ASSERT_EQ(set.get(*(begin++)), 9);
|
ASSERT_EQ(*(begin++), 9);
|
||||||
ASSERT_EQ(set.get(*(begin++)), 12);
|
ASSERT_EQ(*(begin++), 12);
|
||||||
ASSERT_EQ(begin, end);
|
ASSERT_EQ(begin, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SparseSetWithType, RespectDisjoint) {
|
TEST(SparseSetWithType, RespectDisjoint) {
|
||||||
entt::SparseSet<unsigned int, int> lhs;
|
entt::SparseSet<std::uint64_t, int> lhs;
|
||||||
entt::SparseSet<unsigned int, int> rhs;
|
entt::SparseSet<std::uint64_t, int> rhs;
|
||||||
const auto &clhs = lhs;
|
const auto &clhs = lhs;
|
||||||
|
|
||||||
lhs.construct(3, 3);
|
lhs.construct(3, 3);
|
||||||
lhs.construct(12, 6);
|
lhs.construct(12, 6);
|
||||||
lhs.construct(42, 9);
|
lhs.construct(42, 9);
|
||||||
|
|
||||||
ASSERT_EQ(lhs.get(3), 3);
|
ASSERT_EQ(clhs.get(3), 3);
|
||||||
ASSERT_EQ(lhs.get(12), 6);
|
ASSERT_EQ(clhs.get(12), 6);
|
||||||
ASSERT_EQ(lhs.get(42), 9);
|
ASSERT_EQ(clhs.get(42), 9);
|
||||||
|
|
||||||
lhs.respect(rhs);
|
lhs.respect(rhs);
|
||||||
|
|
||||||
@@ -271,18 +662,18 @@ TEST(SparseSetWithType, RespectDisjoint) {
|
|||||||
ASSERT_EQ(*(clhs.raw() + 1u), 6);
|
ASSERT_EQ(*(clhs.raw() + 1u), 6);
|
||||||
ASSERT_EQ(*(clhs.raw() + 2u), 9);
|
ASSERT_EQ(*(clhs.raw() + 2u), 9);
|
||||||
|
|
||||||
auto begin = clhs.begin();
|
auto begin = lhs.begin();
|
||||||
auto end = clhs.end();
|
auto end = lhs.end();
|
||||||
|
|
||||||
ASSERT_EQ(clhs.get(*(begin++)), 9);
|
ASSERT_EQ(*(begin++), 9);
|
||||||
ASSERT_EQ(clhs.get(*(begin++)), 6);
|
ASSERT_EQ(*(begin++), 6);
|
||||||
ASSERT_EQ(clhs.get(*(begin++)), 3);
|
ASSERT_EQ(*(begin++), 3);
|
||||||
ASSERT_EQ(begin, end);
|
ASSERT_EQ(begin, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SparseSetWithType, RespectOverlap) {
|
TEST(SparseSetWithType, RespectOverlap) {
|
||||||
entt::SparseSet<unsigned int, int> lhs;
|
entt::SparseSet<std::uint64_t, int> lhs;
|
||||||
entt::SparseSet<unsigned int, int> rhs;
|
entt::SparseSet<std::uint64_t, int> rhs;
|
||||||
const auto &clhs = lhs;
|
const auto &clhs = lhs;
|
||||||
|
|
||||||
lhs.construct(3, 3);
|
lhs.construct(3, 3);
|
||||||
@@ -290,9 +681,9 @@ TEST(SparseSetWithType, RespectOverlap) {
|
|||||||
lhs.construct(42, 9);
|
lhs.construct(42, 9);
|
||||||
rhs.construct(12, 6);
|
rhs.construct(12, 6);
|
||||||
|
|
||||||
ASSERT_EQ(lhs.get(3), 3);
|
ASSERT_EQ(clhs.get(3), 3);
|
||||||
ASSERT_EQ(lhs.get(12), 6);
|
ASSERT_EQ(clhs.get(12), 6);
|
||||||
ASSERT_EQ(lhs.get(42), 9);
|
ASSERT_EQ(clhs.get(42), 9);
|
||||||
ASSERT_EQ(rhs.get(12), 6);
|
ASSERT_EQ(rhs.get(12), 6);
|
||||||
|
|
||||||
lhs.respect(rhs);
|
lhs.respect(rhs);
|
||||||
@@ -301,18 +692,18 @@ TEST(SparseSetWithType, RespectOverlap) {
|
|||||||
ASSERT_EQ(*(clhs.raw() + 1u), 9);
|
ASSERT_EQ(*(clhs.raw() + 1u), 9);
|
||||||
ASSERT_EQ(*(clhs.raw() + 2u), 6);
|
ASSERT_EQ(*(clhs.raw() + 2u), 6);
|
||||||
|
|
||||||
auto begin = clhs.begin();
|
auto begin = lhs.begin();
|
||||||
auto end = clhs.end();
|
auto end = lhs.end();
|
||||||
|
|
||||||
ASSERT_EQ(clhs.get(*(begin++)), 6);
|
ASSERT_EQ(*(begin++), 6);
|
||||||
ASSERT_EQ(clhs.get(*(begin++)), 9);
|
ASSERT_EQ(*(begin++), 9);
|
||||||
ASSERT_EQ(clhs.get(*(begin++)), 3);
|
ASSERT_EQ(*(begin++), 3);
|
||||||
ASSERT_EQ(begin, end);
|
ASSERT_EQ(begin, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SparseSetWithType, RespectOrdered) {
|
TEST(SparseSetWithType, RespectOrdered) {
|
||||||
entt::SparseSet<unsigned int, int> lhs;
|
entt::SparseSet<std::uint64_t, int> lhs;
|
||||||
entt::SparseSet<unsigned int, int> rhs;
|
entt::SparseSet<std::uint64_t, int> rhs;
|
||||||
|
|
||||||
lhs.construct(1, 0);
|
lhs.construct(1, 0);
|
||||||
lhs.construct(2, 0);
|
lhs.construct(2, 0);
|
||||||
@@ -357,8 +748,8 @@ TEST(SparseSetWithType, RespectOrdered) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(SparseSetWithType, RespectReverse) {
|
TEST(SparseSetWithType, RespectReverse) {
|
||||||
entt::SparseSet<unsigned int, int> lhs;
|
entt::SparseSet<std::uint64_t, int> lhs;
|
||||||
entt::SparseSet<unsigned int, int> rhs;
|
entt::SparseSet<std::uint64_t, int> rhs;
|
||||||
|
|
||||||
lhs.construct(1, 0);
|
lhs.construct(1, 0);
|
||||||
lhs.construct(2, 0);
|
lhs.construct(2, 0);
|
||||||
@@ -403,8 +794,8 @@ TEST(SparseSetWithType, RespectReverse) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(SparseSetWithType, RespectUnordered) {
|
TEST(SparseSetWithType, RespectUnordered) {
|
||||||
entt::SparseSet<unsigned int, int> lhs;
|
entt::SparseSet<std::uint64_t, int> lhs;
|
||||||
entt::SparseSet<unsigned int, int> rhs;
|
entt::SparseSet<std::uint64_t, int> rhs;
|
||||||
|
|
||||||
lhs.construct(1, 0);
|
lhs.construct(1, 0);
|
||||||
lhs.construct(2, 0);
|
lhs.construct(2, 0);
|
||||||
@@ -447,3 +838,64 @@ TEST(SparseSetWithType, RespectUnordered) {
|
|||||||
ASSERT_EQ(*(rhs.data() + 4u), 4u);
|
ASSERT_EQ(*(rhs.data() + 4u), 4u);
|
||||||
ASSERT_EQ(*(rhs.data() + 5u), 5u);
|
ASSERT_EQ(*(rhs.data() + 5u), 5u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(SparseSetWithType, CanModifyDuringIteration) {
|
||||||
|
entt::SparseSet<std::uint64_t, int> set;
|
||||||
|
set.construct(0, 42);
|
||||||
|
|
||||||
|
ASSERT_EQ(set.capacity(), entt::SparseSet<std::uint64_t>::size_type{1});
|
||||||
|
|
||||||
|
const auto it = set.cbegin();
|
||||||
|
set.reserve(entt::SparseSet<std::uint64_t>::size_type{2});
|
||||||
|
|
||||||
|
ASSERT_EQ(set.capacity(), entt::SparseSet<std::uint64_t>::size_type{2});
|
||||||
|
|
||||||
|
// this should crash with asan enabled if we break the constraint
|
||||||
|
const auto entity = *it;
|
||||||
|
(void)entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SparseSetWithType, ReferencesGuaranteed) {
|
||||||
|
struct InternalType { int value; };
|
||||||
|
|
||||||
|
entt::SparseSet<std::uint64_t, InternalType> set;
|
||||||
|
|
||||||
|
set.construct(0, 0);
|
||||||
|
set.construct(1, 1);
|
||||||
|
|
||||||
|
ASSERT_EQ(set.get(0).value, 0);
|
||||||
|
ASSERT_EQ(set.get(1).value, 1);
|
||||||
|
|
||||||
|
for(auto &&type: set) {
|
||||||
|
if(type.value) {
|
||||||
|
type.value = 42;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_EQ(set.get(0).value, 0);
|
||||||
|
ASSERT_EQ(set.get(1).value, 42);
|
||||||
|
|
||||||
|
auto begin = set.begin();
|
||||||
|
|
||||||
|
while(begin != set.end()) {
|
||||||
|
(begin++)->value = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_EQ(set.get(0).value, 3);
|
||||||
|
ASSERT_EQ(set.get(1).value, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SparseSetWithType, MoveOnlyComponent) {
|
||||||
|
struct MoveOnlyComponent {
|
||||||
|
MoveOnlyComponent() = default;
|
||||||
|
~MoveOnlyComponent() = default;
|
||||||
|
MoveOnlyComponent(const MoveOnlyComponent &) = delete;
|
||||||
|
MoveOnlyComponent(MoveOnlyComponent &&) = default;
|
||||||
|
MoveOnlyComponent & operator=(const MoveOnlyComponent &) = delete;
|
||||||
|
MoveOnlyComponent & operator=(MoveOnlyComponent &&) = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
// it's purpose is to ensure that move only components are always accepted
|
||||||
|
entt::SparseSet<std::uint64_t, MoveOnlyComponent> set;
|
||||||
|
(void)set;
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,49 +1,50 @@
|
|||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <entt/locator/locator.hpp>
|
#include <entt/locator/locator.hpp>
|
||||||
|
|
||||||
struct A {};
|
struct AService {};
|
||||||
|
|
||||||
struct B {
|
struct AnotherService {
|
||||||
|
virtual ~AnotherService() = default;
|
||||||
virtual void f(bool) = 0;
|
virtual void f(bool) = 0;
|
||||||
bool check{false};
|
bool check{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct D: B {
|
struct DerivedService: AnotherService {
|
||||||
D(int): B{} {}
|
DerivedService(int): AnotherService{} {}
|
||||||
void f(bool b) override { check = b; }
|
void f(bool b) override { check = b; }
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST(ServiceLocator, Functionalities) {
|
TEST(ServiceLocator, Functionalities) {
|
||||||
using entt::ServiceLocator;
|
using entt::ServiceLocator;
|
||||||
|
|
||||||
ASSERT_TRUE(ServiceLocator<A>::empty());
|
ASSERT_TRUE(ServiceLocator<AService>::empty());
|
||||||
ASSERT_TRUE(ServiceLocator<B>::empty());
|
ASSERT_TRUE(ServiceLocator<AnotherService>::empty());
|
||||||
|
|
||||||
ServiceLocator<A>::set();
|
ServiceLocator<AService>::set();
|
||||||
|
|
||||||
ASSERT_FALSE(ServiceLocator<A>::empty());
|
ASSERT_FALSE(ServiceLocator<AService>::empty());
|
||||||
ASSERT_TRUE(ServiceLocator<B>::empty());
|
ASSERT_TRUE(ServiceLocator<AnotherService>::empty());
|
||||||
|
|
||||||
ServiceLocator<A>::reset();
|
ServiceLocator<AService>::reset();
|
||||||
|
|
||||||
ASSERT_TRUE(ServiceLocator<A>::empty());
|
ASSERT_TRUE(ServiceLocator<AService>::empty());
|
||||||
ASSERT_TRUE(ServiceLocator<B>::empty());
|
ASSERT_TRUE(ServiceLocator<AnotherService>::empty());
|
||||||
|
|
||||||
ServiceLocator<A>::set(std::make_shared<A>());
|
ServiceLocator<AService>::set(std::make_shared<AService>());
|
||||||
|
|
||||||
ASSERT_FALSE(ServiceLocator<A>::empty());
|
ASSERT_FALSE(ServiceLocator<AService>::empty());
|
||||||
ASSERT_TRUE(ServiceLocator<B>::empty());
|
ASSERT_TRUE(ServiceLocator<AnotherService>::empty());
|
||||||
|
|
||||||
ServiceLocator<B>::set<D>(42);
|
ServiceLocator<AnotherService>::set<DerivedService>(42);
|
||||||
|
|
||||||
ASSERT_FALSE(ServiceLocator<A>::empty());
|
ASSERT_FALSE(ServiceLocator<AService>::empty());
|
||||||
ASSERT_FALSE(ServiceLocator<B>::empty());
|
ASSERT_FALSE(ServiceLocator<AnotherService>::empty());
|
||||||
|
|
||||||
ServiceLocator<B>::get().lock()->f(!ServiceLocator<B>::get().lock()->check);
|
ServiceLocator<AnotherService>::get().lock()->f(!ServiceLocator<AnotherService>::get().lock()->check);
|
||||||
|
|
||||||
ASSERT_TRUE(ServiceLocator<B>::get().lock()->check);
|
ASSERT_TRUE(ServiceLocator<AnotherService>::get().lock()->check);
|
||||||
|
|
||||||
ServiceLocator<B>::ref().f(!ServiceLocator<B>::get().lock()->check);
|
ServiceLocator<AnotherService>::ref().f(!ServiceLocator<AnotherService>::get().lock()->check);
|
||||||
|
|
||||||
ASSERT_FALSE(ServiceLocator<B>::get().lock()->check);
|
ASSERT_FALSE(ServiceLocator<AnotherService>::get().lock()->check);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
#include <cstdint>
|
||||||
#include <entt/process/process.hpp>
|
#include <entt/process/process.hpp>
|
||||||
|
|
||||||
struct FakeProcess: entt::Process<FakeProcess, int> {
|
struct FakeProcess: entt::Process<FakeProcess, int> {
|
||||||
@@ -9,12 +10,19 @@ struct FakeProcess: entt::Process<FakeProcess, int> {
|
|||||||
void pause() noexcept { process_type::pause(); }
|
void pause() noexcept { process_type::pause(); }
|
||||||
void unpause() noexcept { process_type::unpause(); }
|
void unpause() noexcept { process_type::unpause(); }
|
||||||
|
|
||||||
void init() { initInvoked = true; }
|
void init(void *) { initInvoked = true; }
|
||||||
void update(delta_type) { updateInvoked = true; }
|
|
||||||
void succeeded() { succeededInvoked = true; }
|
void succeeded() { succeededInvoked = true; }
|
||||||
void failed() { failedInvoked = true; }
|
void failed() { failedInvoked = true; }
|
||||||
void aborted() { abortedInvoked = true; }
|
void aborted() { abortedInvoked = true; }
|
||||||
|
|
||||||
|
void update(delta_type, void *data) {
|
||||||
|
if(data) {
|
||||||
|
(*static_cast<int *>(data))++;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateInvoked = true;
|
||||||
|
}
|
||||||
|
|
||||||
bool initInvoked{false};
|
bool initInvoked{false};
|
||||||
bool updateInvoked{false};
|
bool updateInvoked{false};
|
||||||
bool succeededInvoked{false};
|
bool succeededInvoked{false};
|
||||||
@@ -94,6 +102,26 @@ TEST(Process, Fail) {
|
|||||||
ASSERT_FALSE(process.abortedInvoked);
|
ASSERT_FALSE(process.abortedInvoked);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(Process, Data) {
|
||||||
|
FakeProcess process;
|
||||||
|
int value = 0;
|
||||||
|
|
||||||
|
process.tick(0, &value);
|
||||||
|
process.succeed();
|
||||||
|
process.tick(0, &value);
|
||||||
|
|
||||||
|
ASSERT_FALSE(process.alive());
|
||||||
|
ASSERT_TRUE(process.dead());
|
||||||
|
ASSERT_FALSE(process.paused());
|
||||||
|
|
||||||
|
ASSERT_EQ(value, 1);
|
||||||
|
ASSERT_TRUE(process.initInvoked);
|
||||||
|
ASSERT_TRUE(process.updateInvoked);
|
||||||
|
ASSERT_TRUE(process.succeededInvoked);
|
||||||
|
ASSERT_FALSE(process.failedInvoked);
|
||||||
|
ASSERT_FALSE(process.abortedInvoked);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(Process, AbortNextTick) {
|
TEST(Process, AbortNextTick) {
|
||||||
FakeProcess process;
|
FakeProcess process;
|
||||||
|
|
||||||
@@ -131,13 +159,13 @@ TEST(Process, AbortImmediately) {
|
|||||||
|
|
||||||
TEST(ProcessAdaptor, Resolved) {
|
TEST(ProcessAdaptor, Resolved) {
|
||||||
bool updated = false;
|
bool updated = false;
|
||||||
auto lambda = [&updated](uint64_t, auto resolve, auto) {
|
auto lambda = [&updated](std::uint64_t, void *, auto resolve, auto) {
|
||||||
ASSERT_FALSE(updated);
|
ASSERT_FALSE(updated);
|
||||||
updated = true;
|
updated = true;
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
auto process = entt::ProcessAdaptor<decltype(lambda), uint64_t>{lambda};
|
auto process = entt::ProcessAdaptor<decltype(lambda), std::uint64_t>{lambda};
|
||||||
|
|
||||||
process.tick(0);
|
process.tick(0);
|
||||||
|
|
||||||
@@ -147,16 +175,32 @@ TEST(ProcessAdaptor, Resolved) {
|
|||||||
|
|
||||||
TEST(ProcessAdaptor, Rejected) {
|
TEST(ProcessAdaptor, Rejected) {
|
||||||
bool updated = false;
|
bool updated = false;
|
||||||
auto lambda = [&updated](uint64_t, auto, auto rejected) {
|
auto lambda = [&updated](std::uint64_t, void *, auto, auto rejected) {
|
||||||
ASSERT_FALSE(updated);
|
ASSERT_FALSE(updated);
|
||||||
updated = true;
|
updated = true;
|
||||||
rejected();
|
rejected();
|
||||||
};
|
};
|
||||||
|
|
||||||
auto process = entt::ProcessAdaptor<decltype(lambda), uint64_t>{lambda};
|
auto process = entt::ProcessAdaptor<decltype(lambda), std::uint64_t>{lambda};
|
||||||
|
|
||||||
process.tick(0);
|
process.tick(0);
|
||||||
|
|
||||||
ASSERT_TRUE(process.rejected());
|
ASSERT_TRUE(process.rejected());
|
||||||
ASSERT_TRUE(updated);
|
ASSERT_TRUE(updated);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(ProcessAdaptor, Data) {
|
||||||
|
int value = 0;
|
||||||
|
|
||||||
|
auto lambda = [](std::uint64_t, void *data, auto resolve, auto) {
|
||||||
|
*static_cast<int *>(data) = 42;
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
auto process = entt::ProcessAdaptor<decltype(lambda), std::uint64_t>{lambda};
|
||||||
|
|
||||||
|
process.tick(0, &value);
|
||||||
|
|
||||||
|
ASSERT_TRUE(process.dead());
|
||||||
|
ASSERT_EQ(value, 42);
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ struct FooProcess: entt::Process<FooProcess, int> {
|
|||||||
: onUpdate{onUpdate}, onAborted{onAborted}
|
: onUpdate{onUpdate}, onAborted{onAborted}
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void update(delta_type) { onUpdate(); }
|
void update(delta_type, void *) { onUpdate(); }
|
||||||
void aborted() { onAborted(); }
|
void aborted() { onAborted(); }
|
||||||
|
|
||||||
std::function<void()> onUpdate;
|
std::function<void()> onUpdate;
|
||||||
@@ -16,7 +16,7 @@ struct FooProcess: entt::Process<FooProcess, int> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct SucceededProcess: entt::Process<SucceededProcess, int> {
|
struct SucceededProcess: entt::Process<SucceededProcess, int> {
|
||||||
void update(delta_type) {
|
void update(delta_type, void *) {
|
||||||
ASSERT_FALSE(updated);
|
ASSERT_FALSE(updated);
|
||||||
updated = true;
|
updated = true;
|
||||||
++invoked;
|
++invoked;
|
||||||
@@ -30,7 +30,7 @@ struct SucceededProcess: entt::Process<SucceededProcess, int> {
|
|||||||
unsigned int SucceededProcess::invoked = 0;
|
unsigned int SucceededProcess::invoked = 0;
|
||||||
|
|
||||||
struct FailedProcess: entt::Process<FailedProcess, int> {
|
struct FailedProcess: entt::Process<FailedProcess, int> {
|
||||||
void update(delta_type) {
|
void update(delta_type, void *) {
|
||||||
ASSERT_FALSE(updated);
|
ASSERT_FALSE(updated);
|
||||||
updated = true;
|
updated = true;
|
||||||
fail();
|
fail();
|
||||||
@@ -92,11 +92,11 @@ TEST(Scheduler, Functor) {
|
|||||||
bool firstFunctor = false;
|
bool firstFunctor = false;
|
||||||
bool secondFunctor = false;
|
bool secondFunctor = false;
|
||||||
|
|
||||||
scheduler.attach([&firstFunctor](auto, auto resolve, auto){
|
scheduler.attach([&firstFunctor](auto, void *, auto resolve, auto){
|
||||||
ASSERT_FALSE(firstFunctor);
|
ASSERT_FALSE(firstFunctor);
|
||||||
firstFunctor = true;
|
firstFunctor = true;
|
||||||
resolve();
|
resolve();
|
||||||
}).then([&secondFunctor](auto, auto, auto reject){
|
}).then([&secondFunctor](auto, void *, auto, auto reject){
|
||||||
ASSERT_FALSE(secondFunctor);
|
ASSERT_FALSE(secondFunctor);
|
||||||
secondFunctor = true;
|
secondFunctor = true;
|
||||||
reject();
|
reject();
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ TEST(ResourceCache, Functionalities) {
|
|||||||
ASSERT_FALSE(cache.contains(hs2));
|
ASSERT_FALSE(cache.contains(hs2));
|
||||||
|
|
||||||
ASSERT_FALSE(cache.load<BrokenLoader>(hs1, 42));
|
ASSERT_FALSE(cache.load<BrokenLoader>(hs1, 42));
|
||||||
|
ASSERT_FALSE(cache.reload<BrokenLoader>(hs1, 42));
|
||||||
|
|
||||||
ASSERT_EQ(cache.size(), entt::ResourceCache<Resource>::size_type{});
|
ASSERT_EQ(cache.size(), entt::ResourceCache<Resource>::size_type{});
|
||||||
ASSERT_TRUE(cache.empty());
|
ASSERT_TRUE(cache.empty());
|
||||||
@@ -34,6 +35,7 @@ TEST(ResourceCache, Functionalities) {
|
|||||||
ASSERT_FALSE(cache.contains(hs2));
|
ASSERT_FALSE(cache.contains(hs2));
|
||||||
|
|
||||||
ASSERT_TRUE(cache.load<Loader>(hs1, 42));
|
ASSERT_TRUE(cache.load<Loader>(hs1, 42));
|
||||||
|
ASSERT_TRUE(cache.reload<Loader>(hs1, 42));
|
||||||
|
|
||||||
ASSERT_NE(cache.size(), entt::ResourceCache<Resource>::size_type{});
|
ASSERT_NE(cache.size(), entt::ResourceCache<Resource>::size_type{});
|
||||||
ASSERT_FALSE(cache.empty());
|
ASSERT_FALSE(cache.empty());
|
||||||
@@ -77,4 +79,7 @@ TEST(ResourceCache, Functionalities) {
|
|||||||
|
|
||||||
ASSERT_EQ(cache.size(), entt::ResourceCache<Resource>::size_type{});
|
ASSERT_EQ(cache.size(), entt::ResourceCache<Resource>::size_type{});
|
||||||
ASSERT_TRUE(cache.empty());
|
ASSERT_TRUE(cache.empty());
|
||||||
|
|
||||||
|
ASSERT_TRUE(cache.temp<Loader>(42));
|
||||||
|
ASSERT_TRUE(cache.empty());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,141 +0,0 @@
|
|||||||
#include <memory>
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
#include <entt/signal/bus.hpp>
|
|
||||||
|
|
||||||
struct EventA
|
|
||||||
{
|
|
||||||
EventA(int x, int y): value{x+y} {}
|
|
||||||
int value;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct EventB {};
|
|
||||||
struct EventC {};
|
|
||||||
|
|
||||||
struct MyListener
|
|
||||||
{
|
|
||||||
void receive(const EventA &) { A++; }
|
|
||||||
static void listen(const EventB &) { B++; }
|
|
||||||
void receive(const EventC &) { C++; }
|
|
||||||
void reset() { A = 0; B = 0; C = 0; }
|
|
||||||
int A{0};
|
|
||||||
static int B;
|
|
||||||
int C{0};
|
|
||||||
};
|
|
||||||
|
|
||||||
int MyListener::B = 0;
|
|
||||||
|
|
||||||
template<typename Bus, typename Listener>
|
|
||||||
void testRegUnregEmit(Listener listener) {
|
|
||||||
Bus bus;
|
|
||||||
|
|
||||||
listener->reset();
|
|
||||||
bus.template publish<EventA>(40, 2);
|
|
||||||
bus.template publish<EventB>();
|
|
||||||
bus.template publish<EventC>();
|
|
||||||
|
|
||||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))0);
|
|
||||||
ASSERT_TRUE(bus.empty());
|
|
||||||
ASSERT_EQ(listener->A, 0);
|
|
||||||
ASSERT_EQ(listener->B, 0);
|
|
||||||
ASSERT_EQ(listener->C, 0);
|
|
||||||
|
|
||||||
bus.reg(listener);
|
|
||||||
bus.template connect<EventB, &MyListener::listen>();
|
|
||||||
|
|
||||||
listener->reset();
|
|
||||||
bus.template publish<EventA>(40, 2);
|
|
||||||
bus.template publish<EventB>();
|
|
||||||
bus.template publish<EventC>();
|
|
||||||
|
|
||||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))3);
|
|
||||||
ASSERT_FALSE(bus.empty());
|
|
||||||
ASSERT_EQ(listener->A, 1);
|
|
||||||
ASSERT_EQ(listener->B, 1);
|
|
||||||
ASSERT_EQ(listener->C, 1);
|
|
||||||
|
|
||||||
bus.unreg(listener);
|
|
||||||
|
|
||||||
listener->reset();
|
|
||||||
bus.template publish<EventA>(40, 2);
|
|
||||||
bus.template publish<EventB>();
|
|
||||||
bus.template publish<EventC>();
|
|
||||||
|
|
||||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))1);
|
|
||||||
ASSERT_FALSE(bus.empty());
|
|
||||||
ASSERT_EQ(listener->A, 0);
|
|
||||||
ASSERT_EQ(listener->B, 1);
|
|
||||||
ASSERT_EQ(listener->C, 0);
|
|
||||||
|
|
||||||
bus.template disconnect<EventB, MyListener::listen>();
|
|
||||||
|
|
||||||
listener->reset();
|
|
||||||
bus.template publish<EventA>(40, 2);
|
|
||||||
bus.template publish<EventB>();
|
|
||||||
bus.template publish<EventC>();
|
|
||||||
|
|
||||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))0);
|
|
||||||
ASSERT_TRUE(bus.empty());
|
|
||||||
ASSERT_EQ(listener->A, 0);
|
|
||||||
ASSERT_EQ(listener->B, 0);
|
|
||||||
ASSERT_EQ(listener->C, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(ManagedBus, RegUnregEmit) {
|
|
||||||
using MyManagedBus = entt::ManagedBus<EventA, EventB, EventC>;
|
|
||||||
testRegUnregEmit<MyManagedBus>(std::make_shared<MyListener>());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(ManagedBus, ExpiredListeners) {
|
|
||||||
entt::ManagedBus<EventA, EventB, EventC> bus;
|
|
||||||
auto listener = std::make_shared<MyListener>();
|
|
||||||
|
|
||||||
listener->reset();
|
|
||||||
bus.reg(listener);
|
|
||||||
bus.template publish<EventA>(40, 2);
|
|
||||||
bus.template publish<EventB>();
|
|
||||||
|
|
||||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))2);
|
|
||||||
ASSERT_FALSE(bus.empty());
|
|
||||||
ASSERT_EQ(listener->A, 1);
|
|
||||||
ASSERT_EQ(listener->B, 0);
|
|
||||||
|
|
||||||
listener->reset();
|
|
||||||
listener = nullptr;
|
|
||||||
|
|
||||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))2);
|
|
||||||
ASSERT_FALSE(bus.empty());
|
|
||||||
|
|
||||||
EXPECT_NO_THROW(bus.template publish<EventA>(40, 2));
|
|
||||||
EXPECT_NO_THROW(bus.template publish<EventC>());
|
|
||||||
|
|
||||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))0);
|
|
||||||
ASSERT_TRUE(bus.empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(UnmanagedBus, RegUnregEmit) {
|
|
||||||
using MyUnmanagedBus = entt::UnmanagedBus<EventA, EventB, EventC>;
|
|
||||||
auto ptr = std::make_unique<MyListener>();
|
|
||||||
testRegUnregEmit<MyUnmanagedBus>(ptr.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(UnmanagedBus, ExpiredListeners) {
|
|
||||||
entt::UnmanagedBus<EventA, EventB, EventC> bus;
|
|
||||||
auto listener = std::make_unique<MyListener>();
|
|
||||||
|
|
||||||
listener->reset();
|
|
||||||
bus.reg(listener.get());
|
|
||||||
bus.template publish<EventA>(40, 2);
|
|
||||||
bus.template publish<EventB>();
|
|
||||||
|
|
||||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))2);
|
|
||||||
ASSERT_FALSE(bus.empty());
|
|
||||||
ASSERT_EQ(listener->A, 1);
|
|
||||||
ASSERT_EQ(listener->B, 0);
|
|
||||||
|
|
||||||
listener->reset();
|
|
||||||
listener = nullptr;
|
|
||||||
|
|
||||||
// dangling pointer inside ... well, unmanaged means unmanaged!! :-)
|
|
||||||
ASSERT_EQ(bus.size(), (decltype(bus.size()))2);
|
|
||||||
ASSERT_FALSE(bus.empty());
|
|
||||||
}
|
|
||||||
@@ -1,26 +1,37 @@
|
|||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <entt/signal/delegate.hpp>
|
#include <entt/signal/delegate.hpp>
|
||||||
|
|
||||||
int f(int i) {
|
int delegateFunction(int i) {
|
||||||
return i*i;
|
return i*i;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct S {
|
struct DelegateFunctor {
|
||||||
int f(int i) {
|
int operator()(int i) {
|
||||||
return i+i;
|
return i+i;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ConstNonConstNoExcept {
|
||||||
|
void f() { ++cnt; }
|
||||||
|
void g() noexcept { ++cnt; }
|
||||||
|
void h() const { ++cnt; }
|
||||||
|
void i() const noexcept { ++cnt; }
|
||||||
|
mutable int cnt{0};
|
||||||
|
};
|
||||||
|
|
||||||
TEST(Delegate, Functionalities) {
|
TEST(Delegate, Functionalities) {
|
||||||
entt::Delegate<int(int)> ffdel;
|
entt::Delegate<int(int)> ffdel;
|
||||||
entt::Delegate<int(int)> mfdel;
|
entt::Delegate<int(int)> mfdel;
|
||||||
S test;
|
DelegateFunctor functor;
|
||||||
|
|
||||||
ASSERT_EQ(ffdel(42), int{});
|
ASSERT_TRUE(ffdel.empty());
|
||||||
ASSERT_EQ(mfdel(42), int{});
|
ASSERT_TRUE(mfdel.empty());
|
||||||
|
|
||||||
ffdel.connect<&f>();
|
ffdel.connect<&delegateFunction>();
|
||||||
mfdel.connect<S, &S::f>(&test);
|
mfdel.connect<DelegateFunctor, &DelegateFunctor::operator()>(&functor);
|
||||||
|
|
||||||
|
ASSERT_FALSE(ffdel.empty());
|
||||||
|
ASSERT_FALSE(mfdel.empty());
|
||||||
|
|
||||||
ASSERT_EQ(ffdel(3), 9);
|
ASSERT_EQ(ffdel(3), 9);
|
||||||
ASSERT_EQ(mfdel(3), 6);
|
ASSERT_EQ(mfdel(3), 6);
|
||||||
@@ -28,14 +39,14 @@ TEST(Delegate, Functionalities) {
|
|||||||
ffdel.reset();
|
ffdel.reset();
|
||||||
mfdel.reset();
|
mfdel.reset();
|
||||||
|
|
||||||
ASSERT_EQ(ffdel(42), int{});
|
ASSERT_TRUE(ffdel.empty());
|
||||||
ASSERT_EQ(mfdel(42), int{});
|
ASSERT_TRUE(mfdel.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(Delegate, Comparison) {
|
TEST(Delegate, Comparison) {
|
||||||
entt::Delegate<int(int)> delegate;
|
entt::Delegate<int(int)> delegate;
|
||||||
entt::Delegate<int(int)> def;
|
entt::Delegate<int(int)> def;
|
||||||
delegate.connect<&f>();
|
delegate.connect<&delegateFunction>();
|
||||||
|
|
||||||
ASSERT_EQ(def, entt::Delegate<int(int)>{});
|
ASSERT_EQ(def, entt::Delegate<int(int)>{});
|
||||||
ASSERT_NE(def, delegate);
|
ASSERT_NE(def, delegate);
|
||||||
@@ -43,3 +54,22 @@ TEST(Delegate, Comparison) {
|
|||||||
ASSERT_TRUE(def == entt::Delegate<int(int)>{});
|
ASSERT_TRUE(def == entt::Delegate<int(int)>{});
|
||||||
ASSERT_TRUE (def != delegate);
|
ASSERT_TRUE (def != delegate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(Delegate, ConstNonConstNoExcept) {
|
||||||
|
entt::Delegate<void()> delegate;
|
||||||
|
ConstNonConstNoExcept functor;
|
||||||
|
|
||||||
|
delegate.connect<ConstNonConstNoExcept, &ConstNonConstNoExcept::f>(&functor);
|
||||||
|
delegate();
|
||||||
|
|
||||||
|
delegate.connect<ConstNonConstNoExcept, &ConstNonConstNoExcept::g>(&functor);
|
||||||
|
delegate();
|
||||||
|
|
||||||
|
delegate.connect<ConstNonConstNoExcept, &ConstNonConstNoExcept::h>(&functor);
|
||||||
|
delegate();
|
||||||
|
|
||||||
|
delegate.connect<ConstNonConstNoExcept, &ConstNonConstNoExcept::i>(&functor);
|
||||||
|
delegate();
|
||||||
|
|
||||||
|
ASSERT_EQ(functor.cnt, 4);
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,46 +2,39 @@
|
|||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <entt/signal/dispatcher.hpp>
|
#include <entt/signal/dispatcher.hpp>
|
||||||
|
|
||||||
struct Event {};
|
struct AnEvent {};
|
||||||
|
struct AnotherEvent {};
|
||||||
|
|
||||||
struct Receiver {
|
struct Receiver {
|
||||||
void receive(const Event &) { ++cnt; }
|
void receive(const AnEvent &) { ++cnt; }
|
||||||
void reset() { cnt = 0; }
|
void reset() { cnt = 0; }
|
||||||
std::size_t cnt{0};
|
int cnt{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename Dispatcher, typename Rec>
|
TEST(Dispatcher, Functionalities) {
|
||||||
void testDispatcher(Rec receiver) {
|
entt::Dispatcher dispatcher;
|
||||||
Dispatcher dispatcher;
|
Receiver receiver;
|
||||||
|
|
||||||
dispatcher.template connect<Event>(receiver);
|
dispatcher.template sink<AnEvent>().connect(&receiver);
|
||||||
dispatcher.template trigger<Event>();
|
dispatcher.template trigger<AnEvent>();
|
||||||
dispatcher.template enqueue<Event>();
|
dispatcher.template enqueue<AnEvent>();
|
||||||
|
dispatcher.template enqueue<AnotherEvent>();
|
||||||
|
dispatcher.update<AnotherEvent>();
|
||||||
|
|
||||||
ASSERT_EQ(receiver->cnt, static_cast<decltype(receiver->cnt)>(1));
|
ASSERT_EQ(receiver.cnt, 1);
|
||||||
|
|
||||||
|
dispatcher.update<AnEvent>();
|
||||||
|
dispatcher.template trigger<AnEvent>();
|
||||||
|
|
||||||
|
ASSERT_EQ(receiver.cnt, 3);
|
||||||
|
|
||||||
|
receiver.reset();
|
||||||
|
|
||||||
|
dispatcher.template sink<AnEvent>().disconnect(&receiver);
|
||||||
|
dispatcher.template trigger<AnEvent>();
|
||||||
|
dispatcher.template enqueue<AnEvent>();
|
||||||
dispatcher.update();
|
dispatcher.update();
|
||||||
dispatcher.update();
|
dispatcher.template trigger<AnEvent>();
|
||||||
dispatcher.template trigger<Event>();
|
|
||||||
|
|
||||||
ASSERT_EQ(receiver->cnt, static_cast<decltype(receiver->cnt)>(3));
|
ASSERT_EQ(receiver.cnt, 0);
|
||||||
|
|
||||||
receiver->reset();
|
|
||||||
|
|
||||||
dispatcher.template disconnect<Event>(receiver);
|
|
||||||
dispatcher.template trigger<Event>();
|
|
||||||
dispatcher.template enqueue<Event>();
|
|
||||||
dispatcher.update();
|
|
||||||
dispatcher.template trigger<Event>();
|
|
||||||
|
|
||||||
ASSERT_EQ(receiver->cnt, static_cast<decltype(receiver->cnt)>(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(ManagedDispatcher, Basics) {
|
|
||||||
testDispatcher<entt::ManagedDispatcher>(std::make_shared<Receiver>());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(UnmanagedDispatcher, Basics) {
|
|
||||||
auto ptr = std::make_unique<Receiver>();
|
|
||||||
testDispatcher<entt::UnmanagedDispatcher>(ptr.get());
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,148 +3,18 @@
|
|||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <entt/signal/sigh.hpp>
|
#include <entt/signal/sigh.hpp>
|
||||||
|
|
||||||
TEST(SigH, Lifetime) {
|
struct SigHListener {
|
||||||
using signal = entt::SigH<void(void)>;
|
|
||||||
|
|
||||||
ASSERT_NO_THROW(signal{});
|
|
||||||
|
|
||||||
signal src{}, other{};
|
|
||||||
|
|
||||||
ASSERT_NO_THROW(signal{src});
|
|
||||||
ASSERT_NO_THROW(signal{std::move(other)});
|
|
||||||
ASSERT_NO_THROW(src = other);
|
|
||||||
ASSERT_NO_THROW(src = std::move(other));
|
|
||||||
|
|
||||||
ASSERT_NO_THROW(delete new signal{});
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(SigH, Comparison) {
|
|
||||||
struct S {
|
|
||||||
void f() {}
|
|
||||||
void g() {}
|
|
||||||
};
|
|
||||||
|
|
||||||
entt::SigH<void()> sig1;
|
|
||||||
entt::SigH<void()> sig2;
|
|
||||||
|
|
||||||
S s1;
|
|
||||||
S s2;
|
|
||||||
|
|
||||||
sig1.connect<S, &S::f>(&s1);
|
|
||||||
sig2.connect<S, &S::f>(&s2);
|
|
||||||
|
|
||||||
ASSERT_FALSE(sig1 == sig2);
|
|
||||||
ASSERT_TRUE(sig1 != sig2);
|
|
||||||
|
|
||||||
sig1.disconnect<S, &S::f>(&s1);
|
|
||||||
sig2.disconnect<S, &S::f>(&s2);
|
|
||||||
|
|
||||||
sig1.connect<S, &S::f>(&s1);
|
|
||||||
sig2.connect<S, &S::g>(&s1);
|
|
||||||
|
|
||||||
ASSERT_FALSE(sig1 == sig2);
|
|
||||||
ASSERT_TRUE(sig1 != sig2);
|
|
||||||
|
|
||||||
sig1.disconnect<S, &S::f>(&s1);
|
|
||||||
sig2.disconnect<S, &S::g>(&s1);
|
|
||||||
|
|
||||||
ASSERT_TRUE(sig1 == sig2);
|
|
||||||
ASSERT_FALSE(sig1 != sig2);
|
|
||||||
|
|
||||||
sig1.connect<S, &S::f>(&s1);
|
|
||||||
sig1.connect<S, &S::g>(&s1);
|
|
||||||
sig2.connect<S, &S::f>(&s1);
|
|
||||||
sig2.connect<S, &S::g>(&s1);
|
|
||||||
|
|
||||||
ASSERT_TRUE(sig1 == sig2);
|
|
||||||
|
|
||||||
sig1.disconnect<S, &S::f>(&s1);
|
|
||||||
sig1.disconnect<S, &S::g>(&s1);
|
|
||||||
sig2.disconnect<S, &S::f>(&s1);
|
|
||||||
sig2.disconnect<S, &S::g>(&s1);
|
|
||||||
|
|
||||||
sig1.connect<S, &S::f>(&s1);
|
|
||||||
sig1.connect<S, &S::g>(&s1);
|
|
||||||
sig2.connect<S, &S::g>(&s1);
|
|
||||||
sig2.connect<S, &S::f>(&s1);
|
|
||||||
|
|
||||||
ASSERT_FALSE(sig1 == sig2);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct S {
|
|
||||||
static void f(int &v) { v = 42; }
|
static void f(int &v) { v = 42; }
|
||||||
|
|
||||||
|
bool g(int) { k = !k; return true; }
|
||||||
|
bool h(int) { return k; }
|
||||||
|
|
||||||
|
void i() {}
|
||||||
|
void l() {}
|
||||||
|
|
||||||
|
bool k{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST(SigH, Clear) {
|
|
||||||
entt::SigH<void(int &)> sigh;
|
|
||||||
sigh.connect<&S::f>();
|
|
||||||
|
|
||||||
ASSERT_FALSE(sigh.empty());
|
|
||||||
|
|
||||||
sigh.clear();
|
|
||||||
|
|
||||||
ASSERT_TRUE(sigh.empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(SigH, Functions) {
|
|
||||||
entt::SigH<void(int &)> sigh;
|
|
||||||
int v = 0;
|
|
||||||
|
|
||||||
sigh.connect<&S::f>();
|
|
||||||
sigh.publish(v);
|
|
||||||
|
|
||||||
ASSERT_FALSE(sigh.empty());
|
|
||||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)1, sigh.size());
|
|
||||||
ASSERT_EQ(42, v);
|
|
||||||
|
|
||||||
v = 0;
|
|
||||||
sigh.disconnect<&S::f>();
|
|
||||||
sigh.publish(v);
|
|
||||||
|
|
||||||
ASSERT_TRUE(sigh.empty());
|
|
||||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
|
|
||||||
ASSERT_EQ(0, v);
|
|
||||||
|
|
||||||
sigh.connect<&S::f>();
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(SigH, Members) {
|
|
||||||
struct S {
|
|
||||||
bool f(int) { b = !b; return true; }
|
|
||||||
bool g(int) { return b; }
|
|
||||||
bool b{false};
|
|
||||||
};
|
|
||||||
|
|
||||||
S s;
|
|
||||||
S *ptr = &s;
|
|
||||||
entt::SigH<bool(int)> sigh;
|
|
||||||
|
|
||||||
sigh.connect<S, &S::f>(ptr);
|
|
||||||
sigh.publish(42);
|
|
||||||
|
|
||||||
ASSERT_TRUE(s.b);
|
|
||||||
ASSERT_FALSE(sigh.empty());
|
|
||||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)1, sigh.size());
|
|
||||||
|
|
||||||
sigh.disconnect<S, &S::f>(ptr);
|
|
||||||
sigh.publish(42);
|
|
||||||
|
|
||||||
ASSERT_TRUE(s.b);
|
|
||||||
ASSERT_TRUE(sigh.empty());
|
|
||||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
|
|
||||||
|
|
||||||
sigh.connect<S, &S::f>(ptr);
|
|
||||||
sigh.connect<S, &S::g>(ptr);
|
|
||||||
|
|
||||||
ASSERT_FALSE(sigh.empty());
|
|
||||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)2, sigh.size());
|
|
||||||
|
|
||||||
sigh.disconnect(ptr);
|
|
||||||
|
|
||||||
ASSERT_TRUE(sigh.empty());
|
|
||||||
ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Ret>
|
template<typename Ret>
|
||||||
struct TestCollectAll {
|
struct TestCollectAll {
|
||||||
std::vector<Ret> vec{};
|
std::vector<Ret> vec{};
|
||||||
@@ -175,10 +45,160 @@ struct TestCollectFirst {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ConstNonConstNoExcept {
|
||||||
|
void f() { ++cnt; }
|
||||||
|
void g() noexcept { ++cnt; }
|
||||||
|
void h() const { ++cnt; }
|
||||||
|
void i() const noexcept { ++cnt; }
|
||||||
|
mutable int cnt{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(SigH, Lifetime) {
|
||||||
|
using signal = entt::SigH<void(void)>;
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(signal{});
|
||||||
|
|
||||||
|
signal src{}, other{};
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(signal{src});
|
||||||
|
ASSERT_NO_THROW(signal{std::move(other)});
|
||||||
|
ASSERT_NO_THROW(src = other);
|
||||||
|
ASSERT_NO_THROW(src = std::move(other));
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(delete new signal{});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SigH, Comparison) {
|
||||||
|
entt::SigH<void()> sig1;
|
||||||
|
entt::SigH<void()> sig2;
|
||||||
|
|
||||||
|
SigHListener s1;
|
||||||
|
SigHListener s2;
|
||||||
|
|
||||||
|
sig1.sink().connect<SigHListener, &SigHListener::i>(&s1);
|
||||||
|
sig2.sink().connect<SigHListener, &SigHListener::i>(&s2);
|
||||||
|
|
||||||
|
ASSERT_FALSE(sig1 == sig2);
|
||||||
|
ASSERT_TRUE(sig1 != sig2);
|
||||||
|
|
||||||
|
sig1.sink().disconnect<SigHListener, &SigHListener::i>(&s1);
|
||||||
|
sig2.sink().disconnect<SigHListener, &SigHListener::i>(&s2);
|
||||||
|
|
||||||
|
sig1.sink().connect<SigHListener, &SigHListener::i>(&s1);
|
||||||
|
sig2.sink().connect<SigHListener, &SigHListener::l>(&s1);
|
||||||
|
|
||||||
|
ASSERT_FALSE(sig1 == sig2);
|
||||||
|
ASSERT_TRUE(sig1 != sig2);
|
||||||
|
|
||||||
|
sig1.sink().disconnect<SigHListener, &SigHListener::i>(&s1);
|
||||||
|
sig2.sink().disconnect<SigHListener, &SigHListener::l>(&s1);
|
||||||
|
|
||||||
|
ASSERT_TRUE(sig1 == sig2);
|
||||||
|
ASSERT_FALSE(sig1 != sig2);
|
||||||
|
|
||||||
|
sig1.sink().connect<SigHListener, &SigHListener::i>(&s1);
|
||||||
|
sig1.sink().connect<SigHListener, &SigHListener::l>(&s1);
|
||||||
|
sig2.sink().connect<SigHListener, &SigHListener::i>(&s1);
|
||||||
|
sig2.sink().connect<SigHListener, &SigHListener::l>(&s1);
|
||||||
|
|
||||||
|
ASSERT_TRUE(sig1 == sig2);
|
||||||
|
|
||||||
|
sig1.sink().disconnect<SigHListener, &SigHListener::i>(&s1);
|
||||||
|
sig1.sink().disconnect<SigHListener, &SigHListener::l>(&s1);
|
||||||
|
sig2.sink().disconnect<SigHListener, &SigHListener::i>(&s1);
|
||||||
|
sig2.sink().disconnect<SigHListener, &SigHListener::l>(&s1);
|
||||||
|
|
||||||
|
sig1.sink().connect<SigHListener, &SigHListener::i>(&s1);
|
||||||
|
sig1.sink().connect<SigHListener, &SigHListener::l>(&s1);
|
||||||
|
sig2.sink().connect<SigHListener, &SigHListener::l>(&s1);
|
||||||
|
sig2.sink().connect<SigHListener, &SigHListener::i>(&s1);
|
||||||
|
|
||||||
|
ASSERT_FALSE(sig1 == sig2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SigH, Clear) {
|
||||||
|
entt::SigH<void(int &)> sigh;
|
||||||
|
sigh.sink().connect<&SigHListener::f>();
|
||||||
|
|
||||||
|
ASSERT_FALSE(sigh.empty());
|
||||||
|
|
||||||
|
sigh.sink().disconnect();
|
||||||
|
|
||||||
|
ASSERT_TRUE(sigh.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SigH, Swap) {
|
||||||
|
entt::SigH<void(int &)> sigh1;
|
||||||
|
entt::SigH<void(int &)> sigh2;
|
||||||
|
|
||||||
|
sigh1.sink().connect<&SigHListener::f>();
|
||||||
|
|
||||||
|
ASSERT_FALSE(sigh1.empty());
|
||||||
|
ASSERT_TRUE(sigh2.empty());
|
||||||
|
|
||||||
|
std::swap(sigh1, sigh2);
|
||||||
|
|
||||||
|
ASSERT_TRUE(sigh1.empty());
|
||||||
|
ASSERT_FALSE(sigh2.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SigH, Functions) {
|
||||||
|
entt::SigH<void(int &)> sigh;
|
||||||
|
int v = 0;
|
||||||
|
|
||||||
|
sigh.sink().connect<&SigHListener::f>();
|
||||||
|
sigh.publish(v);
|
||||||
|
|
||||||
|
ASSERT_FALSE(sigh.empty());
|
||||||
|
ASSERT_EQ(static_cast<entt::SigH<bool(int)>::size_type>(1), sigh.size());
|
||||||
|
ASSERT_EQ(42, v);
|
||||||
|
|
||||||
|
v = 0;
|
||||||
|
sigh.sink().disconnect<&SigHListener::f>();
|
||||||
|
sigh.publish(v);
|
||||||
|
|
||||||
|
ASSERT_TRUE(sigh.empty());
|
||||||
|
ASSERT_EQ(static_cast<entt::SigH<bool(int)>::size_type>(0), sigh.size());
|
||||||
|
ASSERT_EQ(0, v);
|
||||||
|
|
||||||
|
sigh.sink().connect<&SigHListener::f>();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SigH, Members) {
|
||||||
|
SigHListener s;
|
||||||
|
SigHListener *ptr = &s;
|
||||||
|
entt::SigH<bool(int)> sigh;
|
||||||
|
|
||||||
|
sigh.sink().connect<SigHListener, &SigHListener::g>(ptr);
|
||||||
|
sigh.publish(42);
|
||||||
|
|
||||||
|
ASSERT_TRUE(s.k);
|
||||||
|
ASSERT_FALSE(sigh.empty());
|
||||||
|
ASSERT_EQ(static_cast<entt::SigH<bool(int)>::size_type>(1), sigh.size());
|
||||||
|
|
||||||
|
sigh.sink().disconnect<SigHListener, &SigHListener::g>(ptr);
|
||||||
|
sigh.publish(42);
|
||||||
|
|
||||||
|
ASSERT_TRUE(s.k);
|
||||||
|
ASSERT_TRUE(sigh.empty());
|
||||||
|
ASSERT_EQ(static_cast<entt::SigH<bool(int)>::size_type>(0), sigh.size());
|
||||||
|
|
||||||
|
sigh.sink().connect<SigHListener, &SigHListener::g>(ptr);
|
||||||
|
sigh.sink().connect<SigHListener, &SigHListener::h>(ptr);
|
||||||
|
|
||||||
|
ASSERT_FALSE(sigh.empty());
|
||||||
|
ASSERT_EQ(static_cast<entt::SigH<bool(int)>::size_type>(2), sigh.size());
|
||||||
|
|
||||||
|
sigh.sink().disconnect(ptr);
|
||||||
|
|
||||||
|
ASSERT_TRUE(sigh.empty());
|
||||||
|
ASSERT_EQ(static_cast<entt::SigH<bool(int)>::size_type>(0), sigh.size());
|
||||||
|
}
|
||||||
|
|
||||||
TEST(SigH, Collector) {
|
TEST(SigH, Collector) {
|
||||||
entt::SigH<void(), TestCollectAll<void>> sigh_void;
|
entt::SigH<void(), TestCollectAll<void>> sigh_void;
|
||||||
|
|
||||||
sigh_void.connect<&TestCollectAll<void>::h>();
|
sigh_void.sink().connect<&TestCollectAll<void>::h>();
|
||||||
auto collector_void = sigh_void.collect();
|
auto collector_void = sigh_void.collect();
|
||||||
|
|
||||||
ASSERT_FALSE(sigh_void.empty());
|
ASSERT_FALSE(sigh_void.empty());
|
||||||
@@ -186,25 +206,46 @@ TEST(SigH, Collector) {
|
|||||||
|
|
||||||
entt::SigH<int(), TestCollectAll<int>> sigh_all;
|
entt::SigH<int(), TestCollectAll<int>> sigh_all;
|
||||||
|
|
||||||
sigh_all.connect<&TestCollectAll<int>::f>();
|
sigh_all.sink().connect<&TestCollectAll<int>::f>();
|
||||||
sigh_all.connect<&TestCollectAll<int>::f>();
|
sigh_all.sink().connect<&TestCollectAll<int>::f>();
|
||||||
sigh_all.connect<&TestCollectAll<int>::g>();
|
sigh_all.sink().connect<&TestCollectAll<int>::g>();
|
||||||
auto collector_all = sigh_all.collect();
|
auto collector_all = sigh_all.collect();
|
||||||
|
|
||||||
ASSERT_FALSE(sigh_all.empty());
|
ASSERT_FALSE(sigh_all.empty());
|
||||||
ASSERT_FALSE(collector_all.vec.empty());
|
ASSERT_FALSE(collector_all.vec.empty());
|
||||||
ASSERT_EQ((std::vector<int>::size_type)2, collector_all.vec.size());
|
ASSERT_EQ(static_cast<std::vector<int>::size_type>(2), collector_all.vec.size());
|
||||||
ASSERT_EQ(42, collector_all.vec[0]);
|
ASSERT_EQ(42, collector_all.vec[0]);
|
||||||
ASSERT_EQ(42, collector_all.vec[1]);
|
ASSERT_EQ(42, collector_all.vec[1]);
|
||||||
|
|
||||||
entt::SigH<int(), TestCollectFirst<int>> sigh_first;
|
entt::SigH<int(), TestCollectFirst<int>> sigh_first;
|
||||||
|
|
||||||
sigh_first.connect<&TestCollectFirst<int>::f>();
|
sigh_first.sink().connect<&TestCollectFirst<int>::f>();
|
||||||
sigh_first.connect<&TestCollectFirst<int>::f>();
|
sigh_first.sink().connect<&TestCollectFirst<int>::f>();
|
||||||
auto collector_first = sigh_first.collect();
|
auto collector_first = sigh_first.collect();
|
||||||
|
|
||||||
ASSERT_FALSE(sigh_first.empty());
|
ASSERT_FALSE(sigh_first.empty());
|
||||||
ASSERT_FALSE(collector_first.vec.empty());
|
ASSERT_FALSE(collector_first.vec.empty());
|
||||||
ASSERT_EQ((std::vector<int>::size_type)1, collector_first.vec.size());
|
ASSERT_EQ(static_cast<std::vector<int>::size_type>(1), collector_first.vec.size());
|
||||||
ASSERT_EQ(42, collector_first.vec[0]);
|
ASSERT_EQ(42, collector_first.vec[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(SigH, ConstNonConstNoExcept) {
|
||||||
|
entt::SigH<void()> sigh;
|
||||||
|
ConstNonConstNoExcept functor;
|
||||||
|
|
||||||
|
sigh.sink().connect<ConstNonConstNoExcept, &ConstNonConstNoExcept::f>(&functor);
|
||||||
|
sigh.sink().connect<ConstNonConstNoExcept, &ConstNonConstNoExcept::g>(&functor);
|
||||||
|
sigh.sink().connect<ConstNonConstNoExcept, &ConstNonConstNoExcept::h>(&functor);
|
||||||
|
sigh.sink().connect<ConstNonConstNoExcept, &ConstNonConstNoExcept::i>(&functor);
|
||||||
|
sigh.publish();
|
||||||
|
|
||||||
|
ASSERT_EQ(functor.cnt, 4);
|
||||||
|
|
||||||
|
sigh.sink().disconnect<ConstNonConstNoExcept, &ConstNonConstNoExcept::f>(&functor);
|
||||||
|
sigh.sink().disconnect<ConstNonConstNoExcept, &ConstNonConstNoExcept::g>(&functor);
|
||||||
|
sigh.sink().disconnect<ConstNonConstNoExcept, &ConstNonConstNoExcept::h>(&functor);
|
||||||
|
sigh.sink().disconnect<ConstNonConstNoExcept, &ConstNonConstNoExcept::i>(&functor);
|
||||||
|
sigh.publish();
|
||||||
|
|
||||||
|
ASSERT_EQ(functor.cnt, 4);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,164 +0,0 @@
|
|||||||
#include <memory>
|
|
||||||
#include <utility>
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
#include <entt/signal/signal.hpp>
|
|
||||||
|
|
||||||
struct S {
|
|
||||||
static void f(const int &j) { i = j; }
|
|
||||||
void g(const int &j) { i = j; }
|
|
||||||
void h(const int &) {}
|
|
||||||
static int i;
|
|
||||||
};
|
|
||||||
|
|
||||||
int S::i = 0;
|
|
||||||
|
|
||||||
TEST(Signal, Lifetime) {
|
|
||||||
using signal = entt::Signal<void(void)>;
|
|
||||||
|
|
||||||
ASSERT_NO_THROW(signal{});
|
|
||||||
|
|
||||||
signal src{}, other{};
|
|
||||||
|
|
||||||
ASSERT_NO_THROW(signal{src});
|
|
||||||
ASSERT_NO_THROW(signal{std::move(other)});
|
|
||||||
ASSERT_NO_THROW(src = other);
|
|
||||||
ASSERT_NO_THROW(src = std::move(other));
|
|
||||||
|
|
||||||
ASSERT_NO_THROW(delete new signal{});
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Signal, Comparison) {
|
|
||||||
struct S {
|
|
||||||
void f() {}
|
|
||||||
void g() {}
|
|
||||||
};
|
|
||||||
|
|
||||||
entt::Signal<void()> sig1;
|
|
||||||
entt::Signal<void()> sig2;
|
|
||||||
|
|
||||||
auto s1 = std::make_shared<S>();
|
|
||||||
auto s2 = std::make_shared<S>();
|
|
||||||
|
|
||||||
sig1.connect<S, &S::f>(s1);
|
|
||||||
sig2.connect<S, &S::f>(s2);
|
|
||||||
|
|
||||||
ASSERT_FALSE(sig1 == sig2);
|
|
||||||
ASSERT_TRUE(sig1 != sig2);
|
|
||||||
|
|
||||||
sig1.disconnect<S, &S::f>(s1);
|
|
||||||
sig2.disconnect<S, &S::f>(s2);
|
|
||||||
|
|
||||||
sig1.connect<S, &S::f>(s1);
|
|
||||||
sig2.connect<S, &S::g>(s1);
|
|
||||||
|
|
||||||
ASSERT_FALSE(sig1 == sig2);
|
|
||||||
ASSERT_TRUE(sig1 != sig2);
|
|
||||||
|
|
||||||
sig1.disconnect<S, &S::f>(s1);
|
|
||||||
sig2.disconnect<S, &S::g>(s1);
|
|
||||||
|
|
||||||
ASSERT_TRUE(sig1 == sig2);
|
|
||||||
ASSERT_FALSE(sig1 != sig2);
|
|
||||||
|
|
||||||
sig1.connect<S, &S::f>(s1);
|
|
||||||
sig1.connect<S, &S::g>(s1);
|
|
||||||
sig2.connect<S, &S::f>(s1);
|
|
||||||
sig2.connect<S, &S::g>(s1);
|
|
||||||
|
|
||||||
ASSERT_TRUE(sig1 == sig2);
|
|
||||||
|
|
||||||
sig1.disconnect<S, &S::f>(s1);
|
|
||||||
sig1.disconnect<S, &S::g>(s1);
|
|
||||||
sig2.disconnect<S, &S::f>(s1);
|
|
||||||
sig2.disconnect<S, &S::g>(s1);
|
|
||||||
|
|
||||||
sig1.connect<S, &S::f>(s1);
|
|
||||||
sig1.connect<S, &S::g>(s1);
|
|
||||||
sig2.connect<S, &S::g>(s1);
|
|
||||||
sig2.connect<S, &S::f>(s1);
|
|
||||||
|
|
||||||
ASSERT_FALSE(sig1 == sig2);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Signal, Clear) {
|
|
||||||
entt::Signal<void(const int &)> signal;
|
|
||||||
signal.connect<&S::f>();
|
|
||||||
|
|
||||||
ASSERT_FALSE(signal.empty());
|
|
||||||
|
|
||||||
signal.clear();
|
|
||||||
|
|
||||||
ASSERT_TRUE(signal.empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Signal, Functions) {
|
|
||||||
entt::Signal<void(const int &)> signal;
|
|
||||||
auto val = S::i + 1;
|
|
||||||
|
|
||||||
signal.connect<&S::f>();
|
|
||||||
signal.publish(val);
|
|
||||||
|
|
||||||
ASSERT_FALSE(signal.empty());
|
|
||||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{1}, signal.size());
|
|
||||||
ASSERT_EQ(S::i, val);
|
|
||||||
|
|
||||||
signal.disconnect<&S::f>();
|
|
||||||
signal.publish(val+1);
|
|
||||||
|
|
||||||
ASSERT_TRUE(signal.empty());
|
|
||||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{0}, signal.size());
|
|
||||||
ASSERT_EQ(S::i, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Signal, Members) {
|
|
||||||
entt::Signal<void(const int &)> signal;
|
|
||||||
auto ptr = std::make_shared<S>();
|
|
||||||
auto val = S::i + 1;
|
|
||||||
|
|
||||||
signal.connect<S, &S::g>(ptr);
|
|
||||||
signal.publish(val);
|
|
||||||
|
|
||||||
ASSERT_FALSE(signal.empty());
|
|
||||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{1}, signal.size());
|
|
||||||
ASSERT_EQ(S::i, val);
|
|
||||||
|
|
||||||
signal.disconnect<S, &S::g>(ptr);
|
|
||||||
signal.publish(val+1);
|
|
||||||
|
|
||||||
ASSERT_TRUE(signal.empty());
|
|
||||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{0}, signal.size());
|
|
||||||
ASSERT_EQ(S::i, val);
|
|
||||||
|
|
||||||
++val;
|
|
||||||
|
|
||||||
signal.connect<S, &S::g>(ptr);
|
|
||||||
signal.connect<S, &S::h>(ptr);
|
|
||||||
signal.publish(val);
|
|
||||||
|
|
||||||
ASSERT_FALSE(signal.empty());
|
|
||||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{2}, signal.size());
|
|
||||||
ASSERT_EQ(S::i, val);
|
|
||||||
|
|
||||||
signal.disconnect(ptr);
|
|
||||||
signal.publish(val+1);
|
|
||||||
|
|
||||||
ASSERT_TRUE(signal.empty());
|
|
||||||
ASSERT_EQ(entt::Signal<void(const int &)>::size_type{0}, signal.size());
|
|
||||||
ASSERT_EQ(S::i, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Signal, Cleanup) {
|
|
||||||
entt::Signal<void(const int &)> signal;
|
|
||||||
auto ptr = std::make_shared<S>();
|
|
||||||
signal.connect<S, &S::g>(ptr);
|
|
||||||
auto val = S::i;
|
|
||||||
ptr = nullptr;
|
|
||||||
|
|
||||||
ASSERT_FALSE(signal.empty());
|
|
||||||
ASSERT_EQ(S::i, val);
|
|
||||||
|
|
||||||
signal.publish(val);
|
|
||||||
|
|
||||||
ASSERT_TRUE(signal.empty());
|
|
||||||
ASSERT_EQ(S::i, val);
|
|
||||||
}
|
|
||||||
436
test/mod/mod.cpp
Normal file
436
test/mod/mod.cpp
Normal file
@@ -0,0 +1,436 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <cassert>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <duktape.h>
|
||||||
|
#include <entt/entity/registry.hpp>
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
struct tag { using type = Type; };
|
||||||
|
|
||||||
|
struct Position {
|
||||||
|
double x;
|
||||||
|
double y;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Renderable {};
|
||||||
|
|
||||||
|
struct DuktapeRuntime {
|
||||||
|
std::map<duk_uint_t, std::string> components;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Comp>
|
||||||
|
duk_ret_t set(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||||
|
const auto entity = duk_require_uint(ctx, 0);
|
||||||
|
registry.accommodate<Comp>(entity);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
duk_ret_t set<Position>(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||||
|
const auto entity = duk_require_uint(ctx, 0);
|
||||||
|
const auto x = duk_require_number(ctx, 2);
|
||||||
|
const auto y = duk_require_number(ctx, 3);
|
||||||
|
registry.accommodate<Position>(entity, x, y);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
duk_ret_t set<DuktapeRuntime>(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||||
|
const auto entity = duk_require_uint(ctx, 0);
|
||||||
|
const auto type = duk_require_uint(ctx, 1);
|
||||||
|
|
||||||
|
duk_dup(ctx, 2);
|
||||||
|
|
||||||
|
if(!registry.has<DuktapeRuntime>(entity)) {
|
||||||
|
registry.assign<DuktapeRuntime>(entity).components[type] = duk_json_encode(ctx, -1);
|
||||||
|
} else {
|
||||||
|
registry.get<DuktapeRuntime>(entity).components[type] = duk_json_encode(ctx, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
duk_pop(ctx);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Comp>
|
||||||
|
duk_ret_t unset(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||||
|
const auto entity = duk_require_uint(ctx, 0);
|
||||||
|
registry.remove<Comp>(entity);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
duk_ret_t unset<DuktapeRuntime>(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||||
|
const auto entity = duk_require_uint(ctx, 0);
|
||||||
|
const auto type = duk_require_uint(ctx, 1);
|
||||||
|
|
||||||
|
auto &components = registry.get<DuktapeRuntime>(entity).components;
|
||||||
|
assert(components.find(type) != components.cend());
|
||||||
|
components.erase(type);
|
||||||
|
|
||||||
|
if(components.empty()) {
|
||||||
|
registry.remove<DuktapeRuntime>(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Comp>
|
||||||
|
duk_ret_t has(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||||
|
const auto entity = duk_require_uint(ctx, 0);
|
||||||
|
duk_push_boolean(ctx, registry.has<Comp>(entity));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
duk_ret_t has<DuktapeRuntime>(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||||
|
const auto entity = duk_require_uint(ctx, 0);
|
||||||
|
duk_push_boolean(ctx, registry.has<DuktapeRuntime>(entity));
|
||||||
|
|
||||||
|
if(registry.has<DuktapeRuntime>(entity)) {
|
||||||
|
const auto type = duk_require_uint(ctx, 1);
|
||||||
|
const auto &components = registry.get<DuktapeRuntime>(entity).components;
|
||||||
|
duk_push_boolean(ctx, components.find(type) != components.cend());
|
||||||
|
} else {
|
||||||
|
duk_push_false(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Comp>
|
||||||
|
duk_ret_t get(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||||
|
assert(registry.has<Comp>(duk_require_uint(ctx, 0)));
|
||||||
|
duk_push_object(ctx);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
duk_ret_t get<Position>(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||||
|
const auto entity = duk_require_uint(ctx, 0);
|
||||||
|
const auto &position = registry.get<Position>(entity);
|
||||||
|
|
||||||
|
const auto idx = duk_push_object(ctx);
|
||||||
|
|
||||||
|
duk_push_string(ctx, "x");
|
||||||
|
duk_push_number(ctx, position.x);
|
||||||
|
duk_def_prop(ctx, idx, DUK_DEFPROP_HAVE_VALUE);
|
||||||
|
|
||||||
|
duk_push_string(ctx, "y");
|
||||||
|
duk_push_number(ctx, position.y);
|
||||||
|
duk_def_prop(ctx, idx, DUK_DEFPROP_HAVE_VALUE);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
duk_ret_t get<DuktapeRuntime>(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||||
|
const auto entity = duk_require_uint(ctx, 0);
|
||||||
|
const auto type = duk_require_uint(ctx, 1);
|
||||||
|
|
||||||
|
auto &runtime = registry.get<DuktapeRuntime>(entity);
|
||||||
|
assert(runtime.components.find(type) != runtime.components.cend());
|
||||||
|
|
||||||
|
duk_push_string(ctx, runtime.components[type].c_str());
|
||||||
|
duk_json_decode(ctx, -1);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
class DuktapeRegistry {
|
||||||
|
// I'm pretty sure I won't have more than 99 components in the example
|
||||||
|
static constexpr entt::DefaultRegistry::component_type udef = 100;
|
||||||
|
|
||||||
|
struct Func {
|
||||||
|
using func_type = duk_ret_t(*)(duk_context *, entt::DefaultRegistry &);
|
||||||
|
using test_type = bool(entt::DefaultRegistry:: *)(entt::DefaultRegistry::entity_type) const;
|
||||||
|
|
||||||
|
func_type set;
|
||||||
|
func_type unset;
|
||||||
|
func_type has;
|
||||||
|
func_type get;
|
||||||
|
test_type test;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Comp>
|
||||||
|
void reg() {
|
||||||
|
using accumulator_type = int[];
|
||||||
|
accumulator_type acc = { (func[registry.type<Comp>()] = {
|
||||||
|
&::set<Comp>,
|
||||||
|
&::unset<Comp>,
|
||||||
|
&::has<Comp>,
|
||||||
|
&::get<Comp>,
|
||||||
|
&entt::DefaultRegistry::has<Comp>
|
||||||
|
}, 0)... };
|
||||||
|
(void)acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DuktapeRegistry & instance(duk_context *ctx) {
|
||||||
|
duk_push_this(ctx);
|
||||||
|
|
||||||
|
duk_push_string(ctx, DUK_HIDDEN_SYMBOL("dreg"));
|
||||||
|
duk_get_prop(ctx, -2);
|
||||||
|
auto &dreg = *static_cast<DuktapeRegistry *>(duk_require_pointer(ctx, -1));
|
||||||
|
duk_pop_2(ctx);
|
||||||
|
|
||||||
|
return dreg;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<Func::func_type Func::*Op>
|
||||||
|
static duk_ret_t invoke(duk_context *ctx) {
|
||||||
|
auto &dreg = instance(ctx);
|
||||||
|
auto &func = dreg.func;
|
||||||
|
auto ®istry = dreg.registry;
|
||||||
|
auto type = duk_require_uint(ctx, 1);
|
||||||
|
|
||||||
|
if(type >= udef) {
|
||||||
|
type = registry.type<DuktapeRuntime>();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(func.find(type) != func.cend());
|
||||||
|
|
||||||
|
return (func[type].*Op)(ctx, registry);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
DuktapeRegistry(entt::DefaultRegistry ®istry)
|
||||||
|
: registry{registry}
|
||||||
|
{
|
||||||
|
reg<Position, Renderable, DuktapeRuntime>();
|
||||||
|
}
|
||||||
|
|
||||||
|
static duk_ret_t identifier(duk_context *ctx) {
|
||||||
|
static auto next = udef;
|
||||||
|
duk_push_uint(ctx, next++);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static duk_ret_t create(duk_context *ctx) {
|
||||||
|
auto &dreg = instance(ctx);
|
||||||
|
duk_push_uint(ctx, dreg.registry.create());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static duk_ret_t set(duk_context *ctx) {
|
||||||
|
return invoke<&Func::set>(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static duk_ret_t unset(duk_context *ctx) {
|
||||||
|
return invoke<&Func::unset>(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static duk_ret_t has(duk_context *ctx) {
|
||||||
|
return invoke<&Func::has>(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static duk_ret_t get(duk_context *ctx) {
|
||||||
|
return invoke<&Func::get>(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static duk_ret_t entities(duk_context *ctx) {
|
||||||
|
const duk_idx_t nargs = duk_get_top(ctx);
|
||||||
|
auto &dreg = instance(ctx);
|
||||||
|
duk_uarridx_t pos = 0;
|
||||||
|
|
||||||
|
duk_push_array(ctx);
|
||||||
|
|
||||||
|
std::vector<typename entt::DefaultRegistry::component_type> components;
|
||||||
|
std::vector<typename entt::DefaultRegistry::component_type> runtime;
|
||||||
|
|
||||||
|
for(duk_idx_t arg = 0; arg < nargs; arg++) {
|
||||||
|
auto type = duk_require_uint(ctx, arg);
|
||||||
|
|
||||||
|
if(type < udef) {
|
||||||
|
components.push_back(type);
|
||||||
|
} else {
|
||||||
|
if(runtime.empty()) {
|
||||||
|
components.push_back(dreg.registry.type<DuktapeRuntime>());
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.push_back(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto view = dreg.registry.view(components.cbegin(), components.cend());
|
||||||
|
|
||||||
|
for(const auto entity: view) {
|
||||||
|
if(runtime.empty()) {
|
||||||
|
duk_push_uint(ctx, entity);
|
||||||
|
duk_put_prop_index(ctx, -2, pos++);
|
||||||
|
} else {
|
||||||
|
const auto &components = dreg.registry.get<DuktapeRuntime>(entity).components;
|
||||||
|
const auto match = std::all_of(runtime.cbegin(), runtime.cend(), [&components](const auto type) {
|
||||||
|
return components.find(type) != components.cend();
|
||||||
|
});
|
||||||
|
|
||||||
|
if(match) {
|
||||||
|
duk_push_uint(ctx, entity);
|
||||||
|
duk_put_prop_index(ctx, -2, pos++);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::map<duk_uint_t, Func> func;
|
||||||
|
entt::DefaultRegistry ®istry;
|
||||||
|
};
|
||||||
|
|
||||||
|
const duk_function_list_entry js_DuktapeRegistry_methods[] = {
|
||||||
|
{ "identifier", &DuktapeRegistry::identifier, 0 },
|
||||||
|
{ "create", &DuktapeRegistry::create, 0 },
|
||||||
|
{ "set", &DuktapeRegistry::set, DUK_VARARGS },
|
||||||
|
{ "unset", &DuktapeRegistry::unset, 2 },
|
||||||
|
{ "has", &DuktapeRegistry::has, 2 },
|
||||||
|
{ "get", &DuktapeRegistry::get, 2 },
|
||||||
|
{ "entities", &DuktapeRegistry::entities, DUK_VARARGS },
|
||||||
|
{ nullptr, nullptr, 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
void exportTypes(duk_context *ctx, entt::DefaultRegistry ®istry) {
|
||||||
|
auto exportType = [](auto *ctx, auto ®istry, auto idx, auto type, const auto *name) {
|
||||||
|
duk_push_string(ctx, name);
|
||||||
|
duk_push_uint(ctx, registry.template type<typename decltype(type)::type>());
|
||||||
|
duk_def_prop(ctx, idx, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_CLEAR_WRITABLE);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto idx = duk_push_object(ctx);
|
||||||
|
|
||||||
|
exportType(ctx, registry, idx, tag<Position>{}, "POSITION");
|
||||||
|
exportType(ctx, registry, idx, tag<Renderable>{}, "RENDERABLE");
|
||||||
|
|
||||||
|
duk_put_global_string(ctx, "Types");
|
||||||
|
}
|
||||||
|
|
||||||
|
void exportDuktapeRegistry(duk_context *ctx, DuktapeRegistry &dreg) {
|
||||||
|
auto idx = duk_push_object(ctx);
|
||||||
|
|
||||||
|
duk_push_string(ctx, DUK_HIDDEN_SYMBOL("dreg"));
|
||||||
|
duk_push_pointer(ctx, &dreg);
|
||||||
|
duk_put_prop(ctx, idx);
|
||||||
|
|
||||||
|
duk_put_function_list(ctx, idx, js_DuktapeRegistry_methods);
|
||||||
|
duk_put_global_string(ctx, "Registry");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Mod, Duktape) {
|
||||||
|
entt::DefaultRegistry registry;
|
||||||
|
DuktapeRegistry dreg{registry};
|
||||||
|
duk_context *ctx = duk_create_heap_default();
|
||||||
|
|
||||||
|
if(!ctx) {
|
||||||
|
FAIL();
|
||||||
|
}
|
||||||
|
|
||||||
|
exportTypes(ctx, registry);
|
||||||
|
exportDuktapeRegistry(ctx, dreg);
|
||||||
|
|
||||||
|
const char *s0 = ""
|
||||||
|
"Types[\"PLAYING_CHARACTER\"] = Registry.identifier();"
|
||||||
|
"Types[\"VELOCITY\"] = Registry.identifier();"
|
||||||
|
"";
|
||||||
|
|
||||||
|
if(duk_peval_string(ctx, s0)) {
|
||||||
|
FAIL();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto e0 = registry.create();
|
||||||
|
registry.assign<Position>(e0, 0., 0.);
|
||||||
|
registry.assign<Renderable>(e0);
|
||||||
|
|
||||||
|
const auto e1 = registry.create();
|
||||||
|
registry.assign<Position>(e1, 0., 0.);
|
||||||
|
|
||||||
|
const char *s1 = ""
|
||||||
|
"Registry.entities(Types.POSITION, Types.RENDERABLE).forEach(function(entity) {"
|
||||||
|
"Registry.set(entity, Types.POSITION, 100., 100.);"
|
||||||
|
"});"
|
||||||
|
"var entity = Registry.create();"
|
||||||
|
"Registry.set(entity, Types.POSITION, 100., 100.);"
|
||||||
|
"Registry.set(entity, Types.RENDERABLE);"
|
||||||
|
"";
|
||||||
|
|
||||||
|
if(duk_peval_string(ctx, s1)) {
|
||||||
|
FAIL();
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_EQ(registry.view<DuktapeRuntime>().size(), 0u);
|
||||||
|
ASSERT_EQ(registry.view<Position>().size(), 3u);
|
||||||
|
ASSERT_EQ(registry.view<Renderable>().size(), 2u);
|
||||||
|
|
||||||
|
registry.view<Position>().each([®istry](auto entity, const auto &position) {
|
||||||
|
ASSERT_FALSE(registry.has<DuktapeRuntime>(entity));
|
||||||
|
|
||||||
|
if(registry.has<Renderable>(entity)) {
|
||||||
|
ASSERT_EQ(position.x, 100.);
|
||||||
|
ASSERT_EQ(position.y, 100.);
|
||||||
|
} else {
|
||||||
|
ASSERT_EQ(position.x, 0.);
|
||||||
|
ASSERT_EQ(position.y, 0.);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const char *s2 = ""
|
||||||
|
"Registry.entities(Types.POSITION).forEach(function(entity) {"
|
||||||
|
"if(!Registry.has(entity, Types.RENDERABLE)) {"
|
||||||
|
"Registry.set(entity, Types.VELOCITY, { \"dx\": -100., \"dy\": -100. });"
|
||||||
|
"Registry.set(entity, Types.PLAYING_CHARACTER, {});"
|
||||||
|
"}"
|
||||||
|
"});"
|
||||||
|
"";
|
||||||
|
|
||||||
|
if(duk_peval_string(ctx, s2)) {
|
||||||
|
FAIL();
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_EQ(registry.view<DuktapeRuntime>().size(), 1u);
|
||||||
|
ASSERT_EQ(registry.view<Position>().size(), 3u);
|
||||||
|
ASSERT_EQ(registry.view<Renderable>().size(), 2u);
|
||||||
|
|
||||||
|
registry.view<DuktapeRuntime>().each([](auto, const DuktapeRuntime &runtime) {
|
||||||
|
ASSERT_EQ(runtime.components.size(), 2u);
|
||||||
|
});
|
||||||
|
|
||||||
|
const char *s3 = ""
|
||||||
|
"Registry.entities(Types.POSITION, Types.RENDERABLE, Types.VELOCITY, Types.PLAYING_CHARACTER).forEach(function(entity) {"
|
||||||
|
"var velocity = Registry.get(entity, Types.VELOCITY);"
|
||||||
|
"Registry.set(entity, Types.POSITION, velocity.dx, velocity.dy)"
|
||||||
|
"});"
|
||||||
|
"";
|
||||||
|
|
||||||
|
if(duk_peval_string(ctx, s3)) {
|
||||||
|
FAIL();
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_EQ(registry.view<DuktapeRuntime>().size(), 1u);
|
||||||
|
ASSERT_EQ(registry.view<Position>().size(), 3u);
|
||||||
|
ASSERT_EQ(registry.view<Renderable>().size(), 2u);
|
||||||
|
|
||||||
|
registry.view<Position, Renderable, DuktapeRuntime>().each([](auto, const Position &position, const auto &...) {
|
||||||
|
ASSERT_EQ(position.x, -100.);
|
||||||
|
ASSERT_EQ(position.y, -100.);
|
||||||
|
});
|
||||||
|
|
||||||
|
const char *s4 = ""
|
||||||
|
"Registry.entities(Types.VELOCITY, Types.PLAYING_CHARACTER).forEach(function(entity) {"
|
||||||
|
"Registry.unset(entity, Types.VELOCITY);"
|
||||||
|
"Registry.unset(entity, Types.PLAYING_CHARACTER);"
|
||||||
|
"});"
|
||||||
|
"Registry.entities(Types.POSITION).forEach(function(entity) {"
|
||||||
|
"Registry.unset(entity, Types.POSITION);"
|
||||||
|
"});"
|
||||||
|
"";
|
||||||
|
|
||||||
|
if(duk_peval_string(ctx, s4)) {
|
||||||
|
FAIL();
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_EQ(registry.view<DuktapeRuntime>().size(), 0u);
|
||||||
|
ASSERT_EQ(registry.view<Position>().size(), 0u);
|
||||||
|
ASSERT_EQ(registry.view<Renderable>().size(), 2u);
|
||||||
|
|
||||||
|
duk_destroy_heap(ctx);
|
||||||
|
}
|
||||||
181
test/snapshot/snapshot.cpp
Normal file
181
test/snapshot/snapshot.cpp
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <sstream>
|
||||||
|
#include <vector>
|
||||||
|
#include <cereal/archives/json.hpp>
|
||||||
|
#include <entt/entity/registry.hpp>
|
||||||
|
|
||||||
|
struct Position {
|
||||||
|
float x;
|
||||||
|
float y;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Timer {
|
||||||
|
int duration;
|
||||||
|
int elapsed{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Relationship {
|
||||||
|
entt::DefaultRegistry::entity_type parent;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Archive>
|
||||||
|
void serialize(Archive &archive, Position &position) {
|
||||||
|
archive(position.x, position.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Archive>
|
||||||
|
void serialize(Archive &archive, Timer &timer) {
|
||||||
|
archive(timer.duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Archive>
|
||||||
|
void serialize(Archive &archive, Relationship &relationship) {
|
||||||
|
archive(relationship.parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Snapshot, Full) {
|
||||||
|
std::stringstream storage;
|
||||||
|
|
||||||
|
entt::DefaultRegistry source;
|
||||||
|
entt::DefaultRegistry destination;
|
||||||
|
|
||||||
|
auto e0 = source.create();
|
||||||
|
source.assign<Position>(e0, 16.f, 16.f);
|
||||||
|
|
||||||
|
source.destroy(source.create());
|
||||||
|
|
||||||
|
auto e1 = source.create();
|
||||||
|
source.assign<Position>(e1, .8f, .0f);
|
||||||
|
source.assign<Relationship>(e1, e0);
|
||||||
|
|
||||||
|
auto e2 = source.create();
|
||||||
|
|
||||||
|
auto e3 = source.create();
|
||||||
|
source.assign<Timer>(e3, 1000, 100);
|
||||||
|
|
||||||
|
source.destroy(e2);
|
||||||
|
auto v2 = source.current(e2);
|
||||||
|
|
||||||
|
{
|
||||||
|
// output finishes flushing its contents when it goes out of scope
|
||||||
|
cereal::JSONOutputArchive output{storage};
|
||||||
|
source.snapshot().entities(output).destroyed(output)
|
||||||
|
.component<Position, Timer, Relationship>(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
cereal::JSONInputArchive input{storage};
|
||||||
|
destination.restore().entities(input).destroyed(input)
|
||||||
|
.component<Position, Timer, Relationship>(input);
|
||||||
|
|
||||||
|
ASSERT_TRUE(destination.valid(e0));
|
||||||
|
ASSERT_TRUE(destination.has<Position>(e0));
|
||||||
|
ASSERT_EQ(destination.get<Position>(e0).x, 16.f);
|
||||||
|
ASSERT_EQ(destination.get<Position>(e0).y, 16.f);
|
||||||
|
|
||||||
|
ASSERT_TRUE(destination.valid(e1));
|
||||||
|
ASSERT_TRUE(destination.has<Position>(e1));
|
||||||
|
ASSERT_EQ(destination.get<Position>(e1).x, .8f);
|
||||||
|
ASSERT_EQ(destination.get<Position>(e1).y, .0f);
|
||||||
|
ASSERT_TRUE(destination.has<Relationship>(e1));
|
||||||
|
ASSERT_EQ(destination.get<Relationship>(e1).parent, e0);
|
||||||
|
|
||||||
|
ASSERT_FALSE(destination.valid(e2));
|
||||||
|
ASSERT_EQ(destination.current(e2), v2);
|
||||||
|
|
||||||
|
ASSERT_TRUE(destination.valid(e3));
|
||||||
|
ASSERT_TRUE(destination.has<Timer>(e3));
|
||||||
|
ASSERT_EQ(destination.get<Timer>(e3).duration, 1000);
|
||||||
|
ASSERT_EQ(destination.get<Timer>(e3).elapsed, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Snapshot, Continuous) {
|
||||||
|
std::stringstream storage;
|
||||||
|
|
||||||
|
entt::DefaultRegistry source;
|
||||||
|
entt::DefaultRegistry destination;
|
||||||
|
|
||||||
|
std::vector<entt::DefaultRegistry::entity_type> entities;
|
||||||
|
for(auto i = 0; i < 10; ++i) {
|
||||||
|
entities.push_back(source.create());
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto entity: entities) {
|
||||||
|
source.destroy(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto e0 = source.create();
|
||||||
|
source.assign<Position>(e0, 0.f, 0.f);
|
||||||
|
source.assign<Relationship>(e0, e0);
|
||||||
|
|
||||||
|
auto e1 = source.create();
|
||||||
|
source.assign<Position>(e1, 1.f, 1.f);
|
||||||
|
source.assign<Relationship>(e1, e0);
|
||||||
|
|
||||||
|
auto e2 = source.create();
|
||||||
|
source.assign<Position>(e2, .2f, .2f);
|
||||||
|
source.assign<Relationship>(e2, e0);
|
||||||
|
|
||||||
|
auto e3 = source.create();
|
||||||
|
source.assign<Timer>(e3, 1000, 1000);
|
||||||
|
source.assign<Relationship>(e3, e2);
|
||||||
|
|
||||||
|
{
|
||||||
|
// output finishes flushing its contents when it goes out of scope
|
||||||
|
cereal::JSONOutputArchive output{storage};
|
||||||
|
source.snapshot().entities(output).component<Position, Relationship, Timer>(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
cereal::JSONInputArchive input{storage};
|
||||||
|
entt::ContinuousLoader<entt::DefaultRegistry::entity_type> loader{destination};
|
||||||
|
loader.entities(input)
|
||||||
|
.component<Position, Relationship>(input, &Relationship::parent)
|
||||||
|
.component<Timer>(input);
|
||||||
|
|
||||||
|
ASSERT_FALSE(destination.valid(e0));
|
||||||
|
ASSERT_TRUE(loader.has(e0));
|
||||||
|
|
||||||
|
auto l0 = loader.map(e0);
|
||||||
|
|
||||||
|
ASSERT_TRUE(destination.valid(l0));
|
||||||
|
ASSERT_TRUE(destination.has<Position>(l0));
|
||||||
|
ASSERT_EQ(destination.get<Position>(l0).x, 0.f);
|
||||||
|
ASSERT_EQ(destination.get<Position>(l0).y, 0.f);
|
||||||
|
ASSERT_TRUE(destination.has<Relationship>(l0));
|
||||||
|
ASSERT_EQ(destination.get<Relationship>(l0).parent, l0);
|
||||||
|
|
||||||
|
ASSERT_FALSE(destination.valid(e1));
|
||||||
|
ASSERT_TRUE(loader.has(e1));
|
||||||
|
|
||||||
|
auto l1 = loader.map(e1);
|
||||||
|
|
||||||
|
ASSERT_TRUE(destination.valid(l1));
|
||||||
|
ASSERT_TRUE(destination.has<Position>(l1));
|
||||||
|
ASSERT_EQ(destination.get<Position>(l1).x, 1.f);
|
||||||
|
ASSERT_EQ(destination.get<Position>(l1).y, 1.f);
|
||||||
|
ASSERT_TRUE(destination.has<Relationship>(l1));
|
||||||
|
ASSERT_EQ(destination.get<Relationship>(l1).parent, l0);
|
||||||
|
|
||||||
|
ASSERT_FALSE(destination.valid(e2));
|
||||||
|
ASSERT_TRUE(loader.has(e2));
|
||||||
|
|
||||||
|
auto l2 = loader.map(e2);
|
||||||
|
|
||||||
|
ASSERT_TRUE(destination.valid(l2));
|
||||||
|
ASSERT_TRUE(destination.has<Position>(l2));
|
||||||
|
ASSERT_EQ(destination.get<Position>(l2).x, .2f);
|
||||||
|
ASSERT_EQ(destination.get<Position>(l2).y, .2f);
|
||||||
|
ASSERT_TRUE(destination.has<Relationship>(l2));
|
||||||
|
ASSERT_EQ(destination.get<Relationship>(l2).parent, l0);
|
||||||
|
|
||||||
|
ASSERT_FALSE(destination.valid(e3));
|
||||||
|
ASSERT_TRUE(loader.has(e3));
|
||||||
|
|
||||||
|
auto l3 = loader.map(e3);
|
||||||
|
|
||||||
|
ASSERT_TRUE(destination.valid(l3));
|
||||||
|
ASSERT_TRUE(destination.has<Timer>(l3));
|
||||||
|
ASSERT_EQ(destination.get<Timer>(l3).duration, 1000);
|
||||||
|
ASSERT_EQ(destination.get<Timer>(l3).elapsed, 0);
|
||||||
|
ASSERT_TRUE(destination.has<Relationship>(l3));
|
||||||
|
ASSERT_EQ(destination.get<Relationship>(l3).parent, l2);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user