Compare commits

..

85 Commits

Author SHA1 Message Date
Michele Caini
06426e4fd7 updated version 2018-09-02 22:48:24 +02:00
Michele Caini
c55a97c24d updated TODO 2018-09-01 16:26:58 +02:00
Michele Caini
0d61289bf3 fixed #135 2018-09-01 16:21:59 +02:00
Michele Caini
bf10cbc70b review: documentation 2018-09-01 14:57:06 +02:00
Michele Caini
2d945e426b fixed #133 2018-08-29 23:10:03 +02:00
Michele Caini
13250887fa review: dependency 2018-08-22 15:51:13 +02:00
Michele Caini
3507c22968 bug fixing (Snapshot::destroyed - #128) 2018-08-22 14:22:54 +02:00
Michele Caini
cc3f98ebcd fixed tests (#129) 2018-08-22 14:00:11 +02:00
Michele Caini
4116e2d6ac added some projects to the entt-in-action list 2018-08-19 14:23:31 +02:00
Michele Caini
48eab6b4a7 minor changes 2018-08-11 14:44:41 +02:00
Michele Caini
25866b5369 fixed typo 2018-08-11 01:09:44 +02:00
Michele Caini
c4dd06fa45 delegate/signal: support for const member functions 2018-08-11 00:54:43 +02:00
Michele Caini
4846d211e0 updated TODO list 2018-08-06 14:07:34 +02:00
Michele Caini
a586ad1237 updated build system 2018-08-05 15:17:48 +02:00
Michele Caini
b701c9c464 review 2018-08-05 15:10:56 +02:00
Michele Caini
d0f20ed2bf updated tests 2018-08-05 13:54:21 +02:00
Michele Caini
0f64a2f3b0 updated .travis.yml (see #110) 2018-08-03 08:17:03 +02:00
Michele Caini
bd83fba6cd updated version 2018-08-02 17:17:09 +02:00
Michele Caini
15b9255a25 fix #120 2018-08-02 16:55:03 +02:00
Michele Caini
6794d21487 one shot bubble sort alg 2018-07-31 09:11:18 +02:00
Michele Caini
118c4432ec typo 2018-07-24 09:25:01 +02:00
Michele Caini
ccda429bf1 Update README.md 2018-07-23 12:10:48 +02:00
Michele Caini
590937d2a0 added Registry::entity 2018-07-20 22:17:18 +02:00
Michele Caini
241827dd80 added gitter badge 2018-07-20 22:03:24 +02:00
Michele Caini
77b4e0b4bd EnTT in action: Face Smash 2018-07-18 15:01:36 +02:00
Michele Caini
6a53cb32d8 updated TODO 2018-07-18 14:08:30 +02:00
Michele Caini
f12ff3b15e fix #116 2018-07-18 14:07:48 +02:00
Michele Caini
69ad8676b7 added USE_ASAN option 2018-07-18 14:07:43 +02:00
Michele Caini
d0fd756f6b logo + review doc 2018-07-12 15:30:52 +02:00
Michele Caini
7f8ab67e9e performance improvements 2018-07-09 16:48:35 +02:00
Michele Caini
0e68bb3d2c added entt::label 2018-07-07 13:40:20 +02:00
Michele Caini
d5b3933752 updated README (added a note on EnTT and shared libraries - see #111) 2018-07-05 16:14:42 +02:00
Michele Caini
0b7206a92d review: README 2018-07-04 13:42:37 +02:00
Michele Caini
e875f306fd added FIND_GTEST_PACKAGE to force using system-wide gtest version (#106) 2018-07-03 22:09:28 +02:00
Michele Caini
8ed5e5ee28 updated cmake min version 2018-07-03 16:57:46 +02:00
Indi Kernick
948b0d40f6 Update packages with Travis-CI (#110) 2018-07-03 12:57:36 +02:00
David Kalnischkies
48f4feb7a7 Fix README: identifiers are accessed via type<>() 2018-07-02 13:29:03 +02:00
Michele Caini
ddc0a32bbc fix: runtime view (#108) 2018-07-02 08:40:30 +02:00
Michele Caini
10a7c54364 runtime view 2018-06-28 14:31:04 +02:00
Michele Caini
a66fa9d844 review: capacity 2018-06-28 08:36:24 +02:00
Michele Caini
a16f2ac15c fixed build system 2018-06-27 22:53:21 +02:00
Michele Caini
b699797a40 naming convention 2018-06-25 14:02:31 +02:00
Michele Caini
a6e9520d06 updated TODO 2018-06-25 13:16:11 +02:00
Michele Caini
f8310b1296 added monostate (built-in config system) 2018-06-23 19:43:22 +02:00
Michele Caini
353bf99cd5 now working with vs2017 (again) 2018-06-22 14:46:43 +02:00
Michele Caini
a478e4acc9 fixed compilation errors and suppressed warnings with clang 2018-06-22 14:16:02 +02:00
Michele Caini
d810e0ba7d review ident 2018-06-22 09:59:03 +02:00
Michele Caini
1e51ffdb72 user defined string literal for hashed strings 2018-06-20 17:08:14 +02:00
Michele Caini
7da1d1fc64 minor changes 2018-06-19 15:36:21 +02:00
Michele Caini
957697c383 review: delegate (see #101 and #102) 2018-06-18 17:10:31 +02:00
Michele Caini
107eb72225 updated TODO 2018-06-18 13:25:34 +02:00
Michele Caini
810b77f9da use null entity everywhere 2018-06-18 08:34:27 +02:00
Michele Caini
5e3bc2049b clean up 2018-06-14 14:48:19 +02:00
Michele Caini
229500347d review: process scheduler 2018-06-14 14:46:39 +02:00
Michele Caini
1f461db0a1 minor changes 2018-06-13 23:33:47 +02:00
Malte
6c55aafee3 Export CMake Config file and "modernize" CMake scripts (#87)
* CMake modifications and Config file generation

* CMake minor style changes

* Get rid of commented lines, fix indentation

* Respect Windows conventions for CMake config directory, Fix error with Config Version file, Export CMake package

* Add CMake option for use of ENTT_COMPILE_OPTIONS, Add CMake option for using libc++, Remove  from EnTT target and move it to test targets

* Fix indentation

* Fix indentation (again)

* Fix Windows problems with compile option -Wall in not using it on Windows

* Improved generator expression
The problem with -Wall is not due to the platform but due to the compiler MSVC

* Set compatibility for ConfigVersion file to AnyNewerVersion, Add PATH_VARS CMAKE_INSTALL_INCLUDE_DIR of configure_package_config_file, Remove redundant options, correct target_include_directory for INSTALL_INTERFACE, set the Version in EnTTConfig file and check CMake version

* Add missing closing brace, Add a special config file for the build tree
2018-06-13 14:04:05 +02:00
Michele Caini
d57e55b719 improved sort 2018-06-12 22:32:11 +02:00
Michele Caini
bf772e5fe5 docs 2018-06-12 13:00:33 +02:00
Michele Caini
413f3356ce updated README 2018-06-12 08:46:05 +02:00
Michele Caini
f147326fe0 typo 2018-06-11 14:50:43 +02:00
Michele Caini
30c59644b6 minor changes 2018-06-11 13:57:37 +02:00
Michele Caini
3fa5acf2e6 updated TOC 2018-06-11 08:26:53 +02:00
Michele Caini
3e6ded8823 more on destroy 2018-06-11 08:24:48 +02:00
Michele Caini
612017aaa2 null entity 2018-06-11 08:23:07 +02:00
Michele Caini
ef57d7e7b6 removed wrong noexcept 2018-06-10 00:10:12 +02:00
Michele Caini
a8d0db5036 updated README 2018-06-09 23:51:57 +02:00
Michele Caini
ad6b5f8fc1 fixed tests 2018-06-09 23:51:48 +02:00
Michele Caini
e3cb6a0aec minor changes 2018-06-09 21:58:49 +02:00
Sztergbaum Roman
cbf18a7dc4 fix warning on windows C4267 (#97) 2018-06-09 21:49:33 +02:00
Michele Caini
01559410a9 updated TODO 2018-06-09 12:26:59 +02:00
Michele Caini
f2ab94fa7f added operator[] to sparse set and views 2018-06-09 00:36:22 +02:00
Michele Caini
d7394a8369 more on prototype 2018-06-08 23:03:05 +02:00
Michele Caini
9feef11d6f cleanup + tests 2018-06-08 22:58:12 +02:00
Michele Caini
dc4e5ddc3c review: iterators (sparse set/view) 2018-06-08 22:30:50 +02:00
Michele Caini
8600781bb6 fixed doc + minor changes 2018-06-08 19:04:19 +02:00
Michele Caini
bdc7bbdc9d DefaultPrototype depends on DefaultRegistry now 2018-06-06 14:39:56 +02:00
Michele Caini
73badef594 updated gtest (#92) 2018-06-06 13:42:09 +02:00
Michele Caini
9474e6c08c updated prototype (#91) 2018-06-05 08:38:27 +02:00
Michele Caini
653ded0e6f updated version 2018-06-04 08:52:10 +02:00
Michele Caini
e34bec7dee cloning an entity is no longer allowed 2018-06-04 08:49:13 +02:00
Michele Caini
610b560fb5 typo 2018-06-03 22:24:45 +02:00
Michele Caini
0a03ddb8a7 typo 2018-06-03 22:17:06 +02:00
Michele Caini
f31790631a fixed include 2018-06-03 22:10:33 +02:00
Michele Caini
e07128760e review: prototype (#89) 2018-06-03 19:06:12 +02:00
Michele Caini
dd02ae313d minor changes 2018-06-02 17:06:44 +02:00
64 changed files with 6670 additions and 4055 deletions

View File

@@ -37,7 +37,7 @@ matrix:
compiler: clang
env: COMPILER=clang++
- os: osx
osx_image: xcode9.1
osx_image: xcode9.4
compiler: clang
env: COMPILER=clang++
- os: linux
@@ -71,3 +71,10 @@ script:
- mkdir -p build && cd build
- cmake .. && make -j4
- CTEST_OUTPUT_ON_FAILURE=1 make test
deploy:
provider: script
script: scripts/update_packages.sh $TRAVIS_TAG
on:
tags: true
condition: “$TRAVIS_BRANCH” = “$TRAVIS_TAG”

View File

@@ -7,3 +7,5 @@ Michele Caini aka skypjack
Paolo Monteverde aka morbo84
David Nerjes aka DavidHamburg
Indi Kernick aka Kerndog73
Malte Müller-Rowold aka m-waka
Richard Caseres aka richardbmx

View File

@@ -2,7 +2,7 @@
# EnTT
#
cmake_minimum_required(VERSION 3.2)
cmake_minimum_required(VERSION 3.7.2)
#
# Building in-tree is not allowed (we take care of your craziness).
@@ -16,7 +16,9 @@ endif()
# Project configuration
#
project(entt VERSION 2.6.0)
project(EnTT VERSION 2.7.3)
include(GNUInstallDirs)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
@@ -29,22 +31,24 @@ set(PROJECT_AUTHOR_EMAIL "michele.caini@gmail.com")
message("*")
message("* ${PROJECT_NAME} v${PROJECT_VERSION} (${CMAKE_BUILD_TYPE})")
message("* Copyright (c) 2018 ${PROJECT_AUTHOR} <${PROJECT_AUTHOR_EMAIL}>")
message("* Copyright (c) 2017-2018 ${PROJECT_AUTHOR} <${PROJECT_AUTHOR_EMAIL}>")
message("*")
option(USE_LIBCPP "Use libc++ by adding -stdlib=libc++ flag if availbale." ON)
option(USE_ASAN "Use address sanitizer by adding -fsanitize=address -fno-omit-frame-pointer flags" OFF)
option(USE_COMPILE_OPTIONS "Use compile options from EnTT." ON)
#
# Compiler stuff
#
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(NOT MSVC)
if(NOT MSVC AND USE_LIBCPP)
include(CheckCXXSourceCompiles)
include(CMakePushCheckState)
set(OLD_CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
cmake_push_check_state()
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -stdlib=libc++")
check_cxx_source_compiles("
#include<type_traits>
@@ -52,28 +56,107 @@ if(NOT MSVC)
" HAS_LIBCPP)
if(NOT HAS_LIBCPP)
set(CMAKE_CXX_FLAGS "${OLD_CMAKE_CXX_FLAGS}")
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()
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")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
# it seems that -O3 ruins the performance when using clang ...
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
else()
# ... on the other side, GCC is incredibly comfortable with it.
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
endif()
cmake_pop_check_state()
endif()
#
# Include EnTT
# Add EnTT target
#
include_directories(${entt_SOURCE_DIR}/src)
add_library(EnTT INTERFACE)
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
@@ -85,18 +168,25 @@ if(BUILD_TESTING)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
option(FIND_GTEST_PACKAGE "Enable finding gtest package." OFF)
if(FIND_GTEST_PACKAGE)
find_package(GTest REQUIRED)
else()
# gtest, gtest_main, gmock and gmock_main targets are available from now on
set(GOOGLETEST_DEPS_DIR ${EnTT_SOURCE_DIR}/deps/googletest)
configure_file(${EnTT_SOURCE_DIR}/cmake/in/googletest.in ${GOOGLETEST_DEPS_DIR}/CMakeLists.txt)
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . WORKING_DIRECTORY ${GOOGLETEST_DEPS_DIR})
execute_process(COMMAND ${CMAKE_COMMAND} --build . WORKING_DIRECTORY ${GOOGLETEST_DEPS_DIR})
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
add_subdirectory(${GOOGLETEST_DEPS_DIR}/src ${GOOGLETEST_DEPS_DIR}/build)
add_library(GTest::Main ALIAS gtest_main)
endif()
option(BUILD_BENCHMARK "Build benchmark." OFF)
option(BUILD_MOD "Build mod example." OFF)
option(BUILD_SNAPSHOT "Build snapshot example." OFF)
# gtest, gtest_main, gmock and gmock_main targets are available from now on
set(GOOGLETEST_DEPS_DIR ${entt_SOURCE_DIR}/deps/googletest)
configure_file(${entt_SOURCE_DIR}/cmake/in/googletest.in ${GOOGLETEST_DEPS_DIR}/CMakeLists.txt)
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . WORKING_DIRECTORY ${GOOGLETEST_DEPS_DIR})
execute_process(COMMAND ${CMAKE_COMMAND} --build . WORKING_DIRECTORY ${GOOGLETEST_DEPS_DIR})
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
add_subdirectory(${GOOGLETEST_DEPS_DIR}/src ${GOOGLETEST_DEPS_DIR}/build)
enable_testing()
add_subdirectory(test)
endif()
@@ -105,10 +195,14 @@ endif()
# Documentation
#
find_package(Doxygen 1.8)
option(BUILD_DOCS "Enable building with documentation." OFF)
if(DOXYGEN_FOUND)
add_subdirectory(docs)
if(BUILD_DOCS)
find_package(Doxygen 1.8)
if(DOXYGEN_FOUND)
add_subdirectory(docs)
endif()
endif()
#
@@ -120,7 +214,6 @@ add_custom_target(
SOURCES
appveyor.yml
AUTHORS
CONTRIBUTING
LICENSE
README.md
TODO

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2018 Michele Caini
Copyright (c) 2017-2018 Michele Caini
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

2448
README.md

File diff suppressed because it is too large Load Diff

18
TODO
View File

@@ -1,13 +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)
* review doc: separate it in multiple md/dox files, reduce the readme to a minimum and provide users with links to the online documentation on gh-pages
* debugging tools (#60): the issue online already contains interesting tips on this, look at it
* dynamic view, useful for runtime ecs, can be filled with the desired pool at runtime and are not constrained to a compile-time list of components
* define basic reactive systems (track entities to which component is attached, track entities from which component is removed, and so on)
* define systems as composable mixins (initializazion, reactive, update, whatever) with flexible auto-detected arguments (registry, views, etc)
* create dedicated flat map based on types implementation (sort of "type map") for types to use within the registry and so on...
* ease the assignment of tags as string (use a template class with a non-type template parameter behind the scene)
* improve CMake interface, see mail from Malte
* is registry/utility.hpp really required?
* "singleton mode" for tags (see #66)
* 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

View File

@@ -14,7 +14,7 @@ configuration:
before_build:
- cd %BUILD_DIR%
- cmake .. -DCMAKE_CXX_FLAGS=/D_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING -G"Visual Studio 15 2017"
- cmake .. -DCMAKE_CXX_FLAGS=/W1 -G"Visual Studio 15 2017"
build:
parallel: true

View 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()

View 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@")

View File

@@ -6,7 +6,7 @@ include(ExternalProject)
ExternalProject_Add(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.8.0
GIT_TAG master
DOWNLOAD_DIR ${GOOGLETEST_DEPS_DIR}
TMP_DIR ${GOOGLETEST_DEPS_DIR}/tmp
STAMP_DIR ${GOOGLETEST_DEPS_DIR}/stamp

View File

@@ -2,18 +2,16 @@
# Doxygen configuration (documentation)
#
set(TARGET_DOCS docs)
set(DOXY_SOURCE_DIRECTORY ${entt_SOURCE_DIR}/src)
set(DOXY_SOURCE_DIRECTORY ${EnTT_SOURCE_DIR}/src)
set(DOXY_DOCS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
set(DOXY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
configure_file(doxy.in doxy.cfg @ONLY)
add_custom_target(
${TARGET_DOCS}
docs ALL
COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxy.cfg
WORKING_DIRECTORY ${entt_SOURCE_DIR}
WORKING_DIRECTORY ${EnTT_SOURCE_DIR}
VERBATIM
SOURCES doxy.in
)
@@ -22,3 +20,17 @@ install(
DIRECTORY ${DOXY_OUTPUT_DIRECTORY}/html
DESTINATION share/${PROJECT_NAME}-${PROJECT_VERSION}/
)
add_custom_target(
docs_aob
SOURCES
CONTRIBUTING.md
core.md
entity.md
locator.md
process.md
resource.md
shared.md
signal.md
extra.dox
)

View File

@@ -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
View 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

File diff suppressed because it is too large Load Diff

75
docs/locator.md Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,3 @@
#!/bin/sh
scripts/update_homebrew.sh $1

View File

@@ -4,7 +4,13 @@
#ifndef ENTT_NOEXCEPT
#define ENTT_NOEXCEPT noexcept
#endif
#endif // ENTT_NOEXCEPT
#ifndef ENTT_HS_SUFFIX
#define ENTT_HS_SUFFIX _hs
#endif // ENTT_HS_SUFFIX
#endif // ENTT_CONFIG_CONFIG_H

View File

@@ -18,31 +18,33 @@ namespace entt {
* This class fills the gap by wrapping some flavors of `std::sort` in a
* function object.
*/
struct StdSort {
struct StdSort final {
/**
* @brief Sorts the element in a range.
* @brief Sorts the elements in a range.
*
* Sorts the element in a range using the given binary comparison function.
* 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<>>
void operator()(It first, It last, Compare compare = Compare{}) {
std::sort(std::move(first), std::move(last), std::move(compare));
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 {
struct InsertionSort final {
/**
* @brief Sorts the element in a range.
* @brief Sorts the elements in a range.
*
* Sorts the element in a range using the given binary comparison function.
* 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.
@@ -51,7 +53,7 @@ struct InsertionSort {
* @param compare A valid comparison function object.
*/
template<typename It, typename Compare = std::less<>>
void operator()(It first, It last, Compare compare = Compare{}) {
void operator()(It first, It last, Compare compare = Compare{}) const {
auto it = first + 1;
while(it != last) {
@@ -70,6 +72,39 @@ struct InsertionSort {
};
/*! @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++;
}
}
}
};
}

View File

@@ -2,9 +2,9 @@
#define ENTT_CORE_FAMILY_HPP
#include<type_traits>
#include<cstddef>
#include<atomic>
#include <type_traits>
#include <cstddef>
#include <atomic>
#include "../config/config.h"

View File

@@ -52,7 +52,7 @@ public:
* @tparam N Number of characters of the identifier.
* @param str Human-readable identifer.
*/
template <std::size_t N>
template<std::size_t N>
constexpr HashedString(const char (&str)[N]) ENTT_NOEXCEPT
: hash{helper(offset, str)}, str{str}
{}
@@ -108,4 +108,14 @@ constexpr bool operator!=(const HashedString &lhs, const HashedString &rhs) ENTT
}
/**
* @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

View File

@@ -2,55 +2,36 @@
#define ENTT_CORE_IDENT_HPP
#include<type_traits>
#include<cstddef>
#include<utility>
#include <type_traits>
#include <cstddef>
#include <utility>
#include <tuple>
#include "../config/config.h"
namespace entt {
namespace internal {
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
template<typename... Types>
struct Identifier final: Identifier<Types>... {
using identifier_type = std::size_t;
namespace internal {
template<std::size_t... Indexes>
constexpr Identifier(std::index_sequence<Indexes...>)
: Identifier<Types>{std::index_sequence<Indexes>{}}...
{}
template<typename Type>
constexpr std::size_t get() const {
return Identifier<std::decay_t<Type>>::get();
}
};
template<typename...>
struct IsPartOf;
template<typename Type, typename Current, typename... Other>
struct IsPartOf<Type, Current, Other...>: std::conditional_t<std::is_same<Type, Current>::value, std::true_type, IsPartOf<Type, Other...>> {};
template<typename Type>
struct Identifier<Type> {
using identifier_type = std::size_t;
struct IsPartOf<Type>: std::false_type {};
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;
};
}
/**
@@ -59,27 +40,24 @@ private:
*/
}
/**
* @brief Types identifers.
* @brief Types identifiers.
*
* Variable template used to generate identifiers at compile-time for the given
* types. Use the `constexpr` `get` member function to know what's the
* identifier associated to the specific type.
* types. Use the `get` member function to know what's the identifier associated
* to the specific type.
*
* @note
* Identifiers are constant expression and can be used in any context where such
* an expression is required. As an example:
* @code{.cpp}
* constexpr auto identifiers = entt::ident<AType, AnotherType>;
* using ID = entt::Identifier<AType, AnotherType>;
*
* switch(aTypeIdentifier) {
* case identifers.get<AType>():
* case ID::get<AType>():
* // ...
* break;
* case identifers.get<AnotherType>():
* case ID::get<AnotherType>():
* // ...
* break;
* default:
@@ -87,19 +65,37 @@ private:
* }
* @endcode
*
* @note
* In case of single type list, `get` isn't a member function template:
* @code{.cpp}
* func(std::integral_constant<
* entt::ident<AType>::identifier_type,
* entt::ident<AType>::get()
* >{});
* @endcode
*
* @tparam Types List of types for which to generate identifiers.
*/
template<typename... Types>
constexpr auto ident = internal::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)>{});
}
};
}

View 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

View File

@@ -6,6 +6,7 @@
#include <utility>
#include "../config/config.h"
#include "registry.hpp"
#include "entity.hpp"
namespace entt {
@@ -31,23 +32,54 @@ struct Actor {
* @param reg An entity-component system properly initialized.
*/
Actor(Registry<Entity> &reg)
: reg{reg}, entt{reg.create()}
: reg{&reg}, entt{reg.create()}
{}
/*! @brief Default destructor. */
virtual ~Actor() {
reg.destroy(entt);
reg->destroy(entt);
}
/*! @brief Default copy constructor. */
Actor(const Actor &) = default;
/*! @brief Default move constructor. */
Actor(Actor &&) = default;
/*! @brief Copying an actor isn't allowed. */
Actor(const Actor &) = delete;
/**
* @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. */
Actor & operator=(const Actor &) = default;
/*! @brief Default move assignment operator. @return This actor. */
Actor & operator=(Actor &&) = default;
Actor & operator=(const Actor &) = delete;
/**
* @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.
@@ -64,7 +96,7 @@ struct Actor {
*/
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)...));
return (reg->template remove<Tag>(), reg->template assign<Tag>(tag_t{}, entt, std::forward<Args>(args)...));
}
/**
@@ -83,7 +115,7 @@ struct Actor {
*/
template<typename Component, typename... Args>
Component & assign(Args &&... args) {
return reg.template accommodate<Component>(entt, std::forward<Args>(args)...);
return reg->template accommodate<Component>(entt, std::forward<Args>(args)...);
}
/**
@@ -93,7 +125,7 @@ struct Actor {
template<typename Tag>
void remove(tag_t) {
assert(has<Tag>(tag_t{}));
reg.template remove<Tag>();
reg->template remove<Tag>();
}
/**
@@ -102,7 +134,7 @@ struct Actor {
*/
template<typename Component>
void remove() {
reg.template remove<Component>(entt);
reg->template remove<Component>(entt);
}
/**
@@ -112,7 +144,7 @@ struct Actor {
*/
template<typename Tag>
bool has(tag_t) const ENTT_NOEXCEPT {
return (reg.template has<Tag>() && (reg.template attachee<Tag>() == entt));
return (reg->template has<Tag>() && (reg->template attachee<Tag>() == entt));
}
/**
@@ -122,7 +154,7 @@ struct Actor {
*/
template<typename Component>
bool has() const ENTT_NOEXCEPT {
return reg.template has<Component>(entt);
return reg->template has<Component>(entt);
}
/**
@@ -133,7 +165,7 @@ struct Actor {
template<typename Tag>
const Tag & get(tag_t) const ENTT_NOEXCEPT {
assert(has<Tag>(tag_t{}));
return reg.template get<Tag>();
return reg->template get<Tag>();
}
/**
@@ -153,7 +185,7 @@ struct Actor {
*/
template<typename Component>
const Component & get() const ENTT_NOEXCEPT {
return reg.template get<Component>(entt);
return reg->template get<Component>(entt);
}
/**
@@ -170,8 +202,8 @@ struct Actor {
* @brief Returns a reference to the underlying registry.
* @return A reference to the underlying registry.
*/
const registry_type & registry() const ENTT_NOEXCEPT {
return reg;
inline const registry_type & registry() const ENTT_NOEXCEPT {
return *reg;
}
/**
@@ -186,12 +218,12 @@ struct Actor {
* @brief Returns the entity associated with an actor.
* @return The entity associated with the actor.
*/
entity_type entity() const ENTT_NOEXCEPT {
inline entity_type entity() const ENTT_NOEXCEPT {
return entt;
}
private:
registry_type &reg;
registry_type * reg;
Entity entt;
};

View 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

View 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

View File

@@ -32,11 +32,13 @@ struct entt_traits<std::uint16_t> {
using entity_type = std::uint16_t;
/*! @brief Underlying version type. */
using version_type = std::uint8_t;
/*! @brief Difference type. */
using difference_type = std::int32_t;
/*! @brief Mask to use to get the entity number out of an identifier. */
static constexpr auto entity_mask = 0xFFF;
static constexpr std::uint16_t entity_mask = 0xFFF;
/*! @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. */
static constexpr auto entity_shift = 12;
};
@@ -47,8 +49,8 @@ struct entt_traits<std::uint16_t> {
*
* A 32 bits entity identifier guarantees:
*
* * 24 bits for the entity number (suitable for almost all the games).
* * 8 bit for the version (resets in [0-255]).
* * 20 bits for the entity number (suitable for almost all the games).
* * 12 bit for the version (resets in [0-4095]).
*/
template<>
struct entt_traits<std::uint32_t> {
@@ -56,11 +58,13 @@ struct entt_traits<std::uint32_t> {
using entity_type = std::uint32_t;
/*! @brief Underlying version type. */
using version_type = std::uint16_t;
/*! @brief Difference type. */
using difference_type = std::int64_t;
/*! @brief Mask to use to get the entity number out of an identifier. */
static constexpr auto entity_mask = 0xFFFFF;
static constexpr std::uint32_t entity_mask = 0xFFFFF;
/*! @brief Mask to use to get the version out of an identifier. */
static constexpr auto version_mask = 0xFFF;
static constexpr std::uint32_t version_mask = 0xFFF;
/*! @brief Extent of the entity number within an identifier. */
static constexpr auto entity_shift = 20;
};
@@ -71,8 +75,8 @@ struct entt_traits<std::uint32_t> {
*
* A 64 bits entity identifier guarantees:
*
* * 40 bits for the entity number (an indecently large number).
* * 24 bit for the version (an indecently large number).
* * 32 bits for the entity number (an indecently large number).
* * 32 bit for the version (an indecently large number).
*/
template<>
struct entt_traits<std::uint64_t> {
@@ -80,13 +84,15 @@ struct entt_traits<std::uint64_t> {
using entity_type = std::uint64_t;
/*! @brief Underlying version type. */
using version_type = std::uint32_t;
/*! @brief Difference type. */
using difference_type = std::int64_t;
/*! @brief Mask to use to get the entity number out of an identifier. */
static constexpr auto entity_mask = 0xFFFFFFFFFF;
static constexpr std::uint64_t entity_mask = 0xFFFFFFFF;
/*! @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. */
static constexpr auto entity_shift = 40;
static constexpr auto entity_shift = 32;
};

View File

@@ -2,6 +2,8 @@
#define ENTT_ENTITY_HELPER_HPP
#include <type_traits>
#include "../core/hashed_string.hpp"
#include "../signal/sigh.hpp"
#include "registry.hpp"
#include "utility.hpp"
@@ -42,7 +44,7 @@ void dependency(Registry<Entity> &registry, const Entity entity) {
* assigned to an entity:
* @code{.cpp}
* entt::DefaultRegistry registry;
* entt::dependency<AType, AnotherType>(registry.construction<MyType>());
* entt::connect<AType, AnotherType>(registry.construction<MyType>());
* @endcode
*
* @tparam Dependency Types of components to assign to an entity if triggered.
@@ -50,7 +52,7 @@ void dependency(Registry<Entity> &registry, const Entity entity) {
* @param sink A sink object properly initialized.
*/
template<typename... Dependency, typename Entity>
void dependency(Sink<void(Registry<Entity> &, const Entity)> sink) {
inline void connect(Sink<void(Registry<Entity> &, const Entity)> sink) {
sink.template connect<dependency<Entity, Dependency...>>();
}
@@ -65,7 +67,7 @@ void dependency(Sink<void(Registry<Entity> &, const Entity)> sink) {
* components `AType` and `AnotherType`:
* @code{.cpp}
* entt::DefaultRegistry registry;
* entt::dependency<AType, AnotherType>(entt::break_t{}, registry.construction<MyType>());
* entt::disconnect<AType, AnotherType>(registry.construction<MyType>());
* @endcode
*
* @tparam Dependency Types of components used to create the dependency.
@@ -73,11 +75,30 @@ void dependency(Sink<void(Registry<Entity> &, const Entity)> sink) {
* @param sink A sink object properly initialized.
*/
template<typename... Dependency, typename Entity>
void dependency(break_t, Sink<void(Registry<Entity> &, const Entity)> sink) {
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>;
}

View File

@@ -3,12 +3,13 @@
#include <tuple>
#include <memory>
#include <vector>
#include <utility>
#include <cstddef>
#include <algorithm>
#include <type_traits>
#include <unordered_map>
#include "../config/config.h"
#include "registry.hpp"
#include "entity.hpp"
namespace entt {
@@ -24,45 +25,36 @@ namespace entt {
* entities of a registry at once.
*
* @note
* Components used along with prototypes must be copy constructible.
* 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 {
class Prototype final {
using basic_fn_type = void(const Prototype &, Registry<Entity> &, const Entity);
using component_type = typename Registry<Entity>::component_type;
using fn_type = void(*)(Registry<Entity> &, const Entity, const void *);
using deleter_type = void(*)(void *);
using ptr_type = std::unique_ptr<void, deleter_type>;
template<typename Component>
static void accommodate(Registry<Entity> &registry, const Entity entity, const void *component) {
const auto &ref = *static_cast<const Component *>(component);
registry.template accommodate<Component>(entity, ref);
}
struct Wrapper { Component component; };
template<typename Component>
static void assign(Registry<Entity> &registry, const Entity entity, const void *component) {
if(!registry.template has<Component>(entity)) {
const auto &ref = *static_cast<const Component *>(component);
registry.template assign<Component>(entity, ref);
struct Handler {
basic_fn_type *accommodate;
basic_fn_type *assign;
};
void release() {
if(registry->valid(entity)) {
registry->destroy(entity);
}
}
struct Handler final {
Handler(ptr_type component, const fn_type accommodate, const fn_type assign, const component_type type)
: component{std::move(component)},
accommodate{accommodate},
assign{assign},
type{type}
{}
ptr_type component{nullptr, +[](void *) {}};
fn_type accommodate{nullptr};
fn_type assign{nullptr};
component_type type;
};
public:
/*! @brief Registry type. */
using registry_type = Registry<Entity>;
@@ -71,6 +63,66 @@ public:
/*! @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> &registry)
: registry{&registry},
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.
@@ -80,22 +132,21 @@ public:
*/
template<typename Component, typename... Args>
Component & set(Args &&... args) {
const auto ctype = registry_type::template type<Component>();
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);
};
auto it = std::find_if(handlers.begin(), handlers.end(), [ctype](const auto &handler) {
return handler.type == ctype;
});
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);
}
};
const auto deleter = +[](void *component) { delete static_cast<Component *>(component); };
ptr_type component{new Component{std::forward<Args>(args)...}, deleter};
if(it == handlers.cend()) {
handlers.emplace_back(std::move(component), &Prototype::accommodate<Component>, &Prototype::assign<Component>, ctype);
} else {
it->component = std::move(component);
}
return get<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;
}
/**
@@ -104,9 +155,8 @@ public:
*/
template<typename Component>
void unset() ENTT_NOEXCEPT {
handlers.erase(std::remove_if(handlers.begin(), handlers.end(), [](const auto &handler) {
return handler.type == registry_type::template type<Component>();
}), handlers.end());
registry->template reset<Wrapper<Component>>(entity);
handlers.erase(registry->template type<Component>());
}
/**
@@ -116,17 +166,7 @@ public:
*/
template<typename... Component>
bool has() const ENTT_NOEXCEPT {
auto found = [this](const auto ctype) {
return std::find_if(handlers.cbegin(), handlers.cend(), [ctype](const auto &handler) {
return handler.type == ctype;
}) != handlers.cend();
};
bool all = true;
using accumulator_type = bool[];
accumulator_type accumulator = { all, (all = all && found(registry_type::template type<Component>()))... };
(void)accumulator;
return all;
return registry->template has<Wrapper<Component>...>(entity);
}
/**
@@ -143,13 +183,7 @@ public:
*/
template<typename Component>
const Component & get() const ENTT_NOEXCEPT {
assert(has<Component>());
auto it = std::find_if(handlers.cbegin(), handlers.cend(), [](const auto &handler) {
return handler.type == registry_type::template type<Component>();
});
return *static_cast<Component *>(it->component.get());
return registry->template get<Wrapper<Component>>(entity).component;
}
/**
@@ -182,7 +216,7 @@ public:
* @return References to the components owned by the prototype.
*/
template<typename... Component>
std::enable_if_t<(sizeof...(Component) > 1), std::tuple<const Component &...>>
inline std::enable_if_t<(sizeof...(Component) > 1), std::tuple<const Component &...>>
get() const ENTT_NOEXCEPT {
return std::tuple<const Component &...>{get<Component>()...};
}
@@ -200,7 +234,7 @@ public:
* @return References to the components owned by the prototype.
*/
template<typename... Component>
std::enable_if_t<(sizeof...(Component) > 1), std::tuple<Component &...>>
inline std::enable_if_t<(sizeof...(Component) > 1), std::tuple<Component &...>>
get() ENTT_NOEXCEPT {
return std::tuple<Component &...>{get<Component>()...};
}
@@ -215,20 +249,41 @@ public:
* prototype(registry, entity);
* @endcode
*
* @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.
* @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 registry A valid reference to a registry.
* @param other A valid reference to a registry.
* @return A valid entity identifier.
*/
entity_type create(registry_type &registry) {
const auto entity = registry.create();
assign(registry, entity);
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.
*
@@ -237,18 +292,47 @@ public:
* 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 registry A valid reference to a registry.
* @param entity A valid entity identifier.
* @param other A valid reference to a registry.
* @param dst A valid entity identifier.
*/
void assign(registry_type &registry, const entity_type entity) {
std::for_each(handlers.begin(), handlers.end(), [&registry, entity](auto &&handler) {
handler.assign(registry, entity, handler.component.get());
});
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);
}
/**
@@ -257,18 +341,45 @@ public:
* 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 registry A valid reference to a registry.
* @param entity A valid entity identifier.
* @param other A valid reference to a registry.
* @param dst A valid entity identifier.
*/
void accommodate(registry_type &registry, const entity_type entity) {
std::for_each(handlers.begin(), handlers.end(), [&registry, entity](auto &&handler) {
handler.accommodate(registry, entity, handler.component.get());
});
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);
}
/**
@@ -279,16 +390,45 @@ public:
* 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 registry A valid reference to a registry.
* @param entity A valid entity identifier.
* @param other A valid reference to a registry.
* @param dst A valid entity identifier.
*/
inline void operator()(registry_type &registry, const entity_type entity) ENTT_NOEXCEPT {
assign(registry, entity);
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);
}
/**
@@ -301,27 +441,54 @@ public:
* prototype(registry, entity);
* @endcode
*
* @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.
* @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 registry A valid reference to a registry.
* @param other A valid reference to a registry.
* @return A valid entity identifier.
*/
inline entity_type operator()(registry_type &registry) ENTT_NOEXCEPT {
return create(registry);
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::vector<Handler> handlers;
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<uint32_t>;
using DefaultPrototype = Prototype<DefaultRegistry::entity_type>;
}

View File

@@ -9,12 +9,15 @@
#include <cstddef>
#include <cstdint>
#include <cassert>
#include <iterator>
#include <algorithm>
#include <type_traits>
#include "../config/config.h"
#include "../core/algorithm.hpp"
#include "../core/family.hpp"
#include "../signal/sigh.hpp"
#include "attachee.hpp"
#include "entity.hpp"
#include "entt_traits.hpp"
#include "snapshot.hpp"
#include "sparse_set.hpp"
@@ -43,10 +46,82 @@ class Registry {
using signal_type = SigH<void(Registry &, const Entity)>;
using traits_type = entt_traits<Entity>;
template<typename... Component>
template<typename Component>
struct Pool: SparseSet<Entity, Component> {
Pool(Registry *registry) ENTT_NOEXCEPT
: registry{registry}
{}
template<typename... Args>
Component & construct(const Entity entity, Args &&... args) {
auto &component = SparseSet<Entity, Component>::construct(entity, std::forward<Args>(args)...);
ctor.publish(*registry, entity);
return component;
}
void destroy(const Entity entity) override {
dtor.publish(*registry, entity);
SparseSet<Entity, Component>::destroy(entity);
}
typename signal_type::sink_type construction() ENTT_NOEXCEPT {
return ctor.sink();
}
typename signal_type::sink_type destruction() ENTT_NOEXCEPT {
return dtor.sink();
}
private:
Registry *registry;
signal_type ctor;
signal_type dtor;
};
template<typename Tag>
struct Attaching: Attachee<Entity, Tag> {
Attaching(Registry *registry)
: registry{registry}
{}
template<typename... Args>
Tag & construct(const Entity entity, Args &&... args) ENTT_NOEXCEPT {
auto &tag = Attachee<Entity, Tag>::construct(entity, std::forward<Args>(args)...);
ctor.publish(*registry, entity);
return tag;
}
void destroy() ENTT_NOEXCEPT override {
dtor.publish(*registry, Attachee<Entity>::get());
Attachee<Entity, Tag>::destroy();
}
Entity move(const Entity entity) ENTT_NOEXCEPT {
const auto owner = Attachee<Entity>::get();
dtor.publish(*registry, owner);
Attachee<Entity, Tag>::move(entity);
ctor.publish(*registry, entity);
return owner;
}
typename signal_type::sink_type construction() ENTT_NOEXCEPT {
return ctor.sink();
}
typename signal_type::sink_type destruction() ENTT_NOEXCEPT {
return dtor.sink();
}
private:
Registry *registry;
signal_type ctor;
signal_type dtor;
};
template<typename handler_family::family_type(*Type)(), typename... Component>
static void creating(Registry &registry, const Entity entity) {
if(registry.has<Component...>(entity)) {
registry.handlers[handler_family::type<Component...>()]->construct(entity);
registry.handlers[Type()]->construct(entity);
}
}
@@ -56,39 +131,65 @@ class Registry {
return handler.has(entity) ? handler.destroy(entity) : void();
}
struct Attachee {
Attachee(const Entity entity): entity{entity} {}
virtual ~Attachee() = default;
Entity entity;
};
template<typename Tag>
inline bool managed(tag_t) const ENTT_NOEXCEPT {
const auto ttype = tag_family::type<Tag>();
return ttype < tags.size() && tags[ttype];
}
template<typename Component>
inline bool managed() const ENTT_NOEXCEPT {
const auto ctype = component_family::type<Component>();
return ctype < pools.size() && pools[ctype];
}
template<typename Tag>
struct Attaching: Attachee {
// requirements for aggregates are relaxed only since C++17
template<typename... Args>
Attaching(const Entity entity, Args &&... args)
: Attachee{entity}, tag{std::forward<Args>(args)...}
{}
inline const Attaching<Tag> & pool(tag_t) const ENTT_NOEXCEPT {
assert(managed<Tag>(tag_t{}));
return static_cast<const Attaching<Tag> &>(*tags[tag_family::type<Tag>()]);
}
Tag tag;
};
template<typename Component>
bool managed() const ENTT_NOEXCEPT {
const auto ctype = component_family::type<Component>();
return ctype < pools.size() && std::get<0>(pools[ctype]);
template<typename Tag>
inline Attaching<Tag> & pool(tag_t) ENTT_NOEXCEPT {
return const_cast<Attaching<Tag> &>(const_cast<const Registry *>(this)->pool<Tag>(tag_t{}));
}
template<typename Component>
const SparseSet<Entity, Component> & pool() const ENTT_NOEXCEPT {
inline const Pool<Component> & pool() const ENTT_NOEXCEPT {
assert(managed<Component>());
const auto ctype = component_family::type<Component>();
return static_cast<SparseSet<Entity, Component> &>(*std::get<0>(pools[ctype]));
return static_cast<const Pool<Component> &>(*pools[component_family::type<Component>()]);
}
template<typename Component>
inline SparseSet<Entity, Component> & pool() ENTT_NOEXCEPT {
return const_cast<SparseSet<Entity, Component> &>(const_cast<const Registry *>(this)->pool<Component>());
inline Pool<Component> & pool() ENTT_NOEXCEPT {
return const_cast<Pool<Component> &>(const_cast<const Registry *>(this)->pool<Component>());
}
template<typename Comp, std::size_t Pivot, typename... Component, std::size_t... Indexes>
void connect(std::index_sequence<Indexes...>) {
pool<Comp>().construction().template connect<&Registry::creating<&handler_family::type<Component...>, std::tuple_element_t<(Indexes < Pivot ? Indexes : (Indexes+1)), std::tuple<Component...>>...>>();
pool<Comp>().destruction().template connect<&Registry::destroying<Component...>>();
}
template<typename... Component, std::size_t... Indexes>
void connect(std::index_sequence<Indexes...>) {
using accumulator_type = int[];
accumulator_type accumulator = { (assure<Component>(), connect<Component, Indexes, Component...>(std::make_index_sequence<sizeof...(Component)-1>{}), 0)... };
(void)accumulator;
}
template<typename Comp, std::size_t Pivot, typename... Component, std::size_t... Indexes>
void disconnect(std::index_sequence<Indexes...>) {
pool<Comp>().construction().template disconnect<&Registry::creating<&handler_family::type<Component...>, std::tuple_element_t<(Indexes < Pivot ? Indexes : (Indexes+1)), std::tuple<Component...>>...>>();
pool<Comp>().destruction().template disconnect<&Registry::destroying<Component...>>();
}
template<typename... Component, std::size_t... Indexes>
void disconnect(std::index_sequence<Indexes...>) {
using accumulator_type = int[];
// if a set exists, pools have already been created for it
accumulator_type accumulator = { (disconnect<Component, Indexes, Component...>(std::make_index_sequence<sizeof...(Component)-1>{}), 0)... };
(void)accumulator;
}
template<typename Component>
@@ -99,10 +200,8 @@ class Registry {
pools.resize(ctype + 1);
}
auto &cpool = std::get<0>(pools[ctype]);
if(!cpool) {
cpool = std::make_unique<SparseSet<Entity, Component>>();
if(!pools[ctype]) {
pools[ctype] = std::make_unique<Pool<Component>>(this);
}
}
@@ -113,6 +212,10 @@ class Registry {
if(!(ttype < tags.size())) {
tags.resize(ttype + 1);
}
if(!tags[ttype]) {
tags[ttype] = std::make_unique<Attaching<Tag>>(this);
}
}
public:
@@ -186,16 +289,24 @@ public:
return managed<Component>() ? pool<Component>().size() : size_type{};
}
/**
* @brief Returns the number of entities created so far.
* @return Number of entities created so far.
*/
size_type size() const ENTT_NOEXCEPT {
return entities.size();
}
/**
* @brief Returns the number of entities still in use.
* @return Number of entities still in use.
*/
size_type size() const ENTT_NOEXCEPT {
size_type alive() const ENTT_NOEXCEPT {
return entities.size() - available;
}
/**
* @brief Increases the capacity of the pool for a given component.
* @brief Increases the capacity of the pool for the given component.
*
* If the new capacity is greater than the current capacity, new storage is
* allocated, otherwise the method does nothing.
@@ -222,17 +333,28 @@ public:
}
/**
* @brief Returns the number of entities ever created.
* @return Number of entities ever created.
* @brief Returns the capacity of the pool for the given component.
* @tparam Component Type of component in which one is interested.
* @return Capacity of the pool of the given component.
*/
template<typename Component>
size_type capacity() const ENTT_NOEXCEPT {
return entities.size();
return managed<Component>() ? pool<Component>().capacity() : size_type{};
}
/**
* @brief Checks whether the pool for the given component is empty.
* @brief Returns the number of entities that a registry has currently
* allocated space for.
* @return Capacity of the registry.
*/
size_type capacity() const ENTT_NOEXCEPT {
return entities.capacity();
}
/**
* @brief Checks whether the pool of the given component is empty.
* @tparam Component Type of component in which one is interested.
* @return True if the pool for the given component is empty, false
* @return True if the pool of the given component is empty, false
* otherwise.
*/
template<typename Component>
@@ -338,13 +460,22 @@ public:
return (entities[pos] == entity);
}
/**
* @brief Returns the entity identifier without the version.
* @param entity An entity identifier, either valid or not.
* @return The entity identifier without the version.
*/
entity_type entity(const entity_type entity) const ENTT_NOEXCEPT {
return entity & traits_type::entity_mask;
}
/**
* @brief Returns the version stored along with an entity identifier.
* @param entity An entity identifier, either valid or not.
* @return Version stored along with the given entity identifier.
* @return The version stored along with the given entity identifier.
*/
version_type version(const entity_type entity) const ENTT_NOEXCEPT {
return version_type((entity >> traits_type::entity_shift) & traits_type::version_mask);
return version_type(entity >> traits_type::entity_shift);
}
/**
@@ -367,7 +498,7 @@ public:
version_type current(const entity_type entity) const ENTT_NOEXCEPT {
const auto pos = size_type(entity & traits_type::entity_mask);
assert(pos < entities.size());
return version_type((entities[pos] >> traits_type::entity_shift) & traits_type::version_mask);
return version_type(entities[pos] >> traits_type::entity_shift);
}
/**
@@ -383,19 +514,18 @@ public:
* function can be used to know if they are still valid or the entity has
* been destroyed and potentially recycled.
*
* The returned entity has no components assigned.
* The returned entity has no components nor tags assigned.
*
* @return A valid entity identifier.
*/
entity_type create() ENTT_NOEXCEPT {
entity_type create() {
entity_type entity;
if(available) {
const auto entt = next;
const auto version = entities[entt] & (~traits_type::entity_mask);
entity = entt | version;
const auto version = entities[entt] & (traits_type::version_mask << traits_type::entity_shift);
next = entities[entt] & traits_type::entity_mask;
entity = entt | version;
entities[entt] = entity;
--available;
} else {
@@ -409,48 +539,22 @@ public:
}
/**
* @brief Clones an entity and returns the newly created one.
* @brief Destroys the entity that owns the given tag, if any.
*
* There are two kinds of entity identifiers:
* Convenient shortcut to destroy an entity by means of a tag type.<br/>
* Syntactic sugar for the following snippet:
*
* * Newly created ones in case no entities have been previously destroyed.
* * Recycled ones with updated versions.
* @code{.cpp}
* if(registry.has<Tag>()) {
* registry.destroy(registry.attachee<Tag>());
* }
* @endcode
*
* Users should not care about the type of the returned entity identifier.
* In case entity identifers are stored around, the `valid` member
* function can be used to know if they are still valid or the entity has
* been destroyed and potentially recycled.
*
* @warning
* In case there are listeners that observe the construction of components
* and assign other components to the entity in their bodies, the result of
* invoking this function may not be as expected. In the worst case, it
* could lead to undefined behavior. An assertion will abort the execution
* at runtime in debug mode if a violation is detected.
*
* @warning
* Attempting to clone 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 entity A valid entity identifier
* @return A valid entity identifier.
* @tparam Tag Type of tag to use to search for the entity.
*/
entity_type clone(const entity_type entity) ENTT_NOEXCEPT {
assert(valid(entity));
const auto other = create();
for(auto pos = pools.size(); pos; --pos) {
auto &tup = pools[pos-1];
auto &cpool = std::get<0>(tup);
if(cpool && cpool->has(entity)) {
cpool->clone(other, entity);
std::get<1>(tup).publish(*this, other);
}
}
return other;
template<typename Tag>
void destroy(tag_t) {
return has<Tag>() ? destroy(attachee<Tag>()) : void();
}
/**
@@ -473,43 +577,61 @@ public:
* An assertion will abort the execution at runtime in debug mode in case of
* invalid entity.
*
* @param entity A valid entity identifier
* @param entity A valid entity identifier.
*/
void destroy(const entity_type entity) {
assert(valid(entity));
for(auto pos = pools.size(); pos; --pos) {
auto &tup = pools[pos-1];
auto &cpool = std::get<0>(tup);
auto &cpool = pools[pos-1];
if(cpool && cpool->has(entity)) {
std::get<2>(tup).publish(*this, entity);
cpool->destroy(entity);
}
};
for(auto pos = tags.size(); pos; --pos) {
auto &tup = tags[pos-1];
auto &tag = std::get<0>(tup);
auto &tag = tags[pos-1];
if(tag && tag->entity == entity) {
std::get<2>(tup).publish(*this, entity);
tag.reset();
if(tag && tag->get() == entity) {
tag->destroy();
}
};
// just a way to protect users from listeners that attach components
assert(orphan(entity));
// lengthens the implicit list of destroyed entities
const auto entt = entity & traits_type::entity_mask;
const auto version = (((entity >> traits_type::entity_shift) + 1) & traits_type::version_mask) << traits_type::entity_shift;
const auto version = ((entity >> traits_type::entity_shift) + 1) << traits_type::entity_shift;
const auto node = (available ? next : ((entt + 1) & traits_type::entity_mask)) | version;
entities[entt] = node;
next = entt;
++available;
}
/**
* @brief Destroys the entities that own the given components, if any.
*
* Convenient shortcut to destroy a set of entities at once.<br/>
* Syntactic sugar for the following snippet:
*
* @code{.cpp}
* for(const auto entity: registry.view<Component...>(Type{}...)) {
* registry.destroy(entity);
* }
* @endcode
*
* @tparam Component Types of components to use to search for the entities.
* @tparam Type Type of view to use or empty to use a standard view.
*/
template<typename... Component, typename... Type>
void destroy(Type...) {
for(const auto entity: view<Component...>(Type{}...)) {
destroy(entity);
}
}
/**
* @brief Attaches the given tag to an entity.
*
@@ -527,7 +649,7 @@ public:
*
* @tparam Tag Type of tag to create.
* @tparam Args Types of arguments to use to construct the tag.
* @param entity A valid entity identifier
* @param entity A valid entity identifier.
* @param args Parameters to use to initialize the tag.
* @return A reference to the newly created tag.
*/
@@ -536,10 +658,7 @@ public:
assert(valid(entity));
assert(!has<Tag>());
assure<Tag>(tag_t{});
auto &tup = tags[tag_family::type<Tag>()];
std::get<0>(tup).reset(new Attaching<Tag>{entity, std::forward<Args>(args)...});
std::get<1>(tup).publish(*this, entity);
return get<Tag>();
return pool<Tag>(tag_t{}).construct(entity, std::forward<Args>(args)...);
}
/**
@@ -566,9 +685,7 @@ public:
Component & assign(const entity_type entity, Args &&... args) {
assert(valid(entity));
assure<Component>();
pool<Component>().construct(entity, std::forward<Args>(args)...);
std::get<1>(pools[component_family::type<Component>()]).publish(*this, entity);
return pool<Component>().get(entity);
return pool<Component>().construct(entity, std::forward<Args>(args)...);
}
/**
@@ -577,12 +694,7 @@ public:
*/
template<typename Tag>
void remove() {
if(has<Tag>()) {
auto &tup = tags[tag_family::type<Tag>()];
auto &tag = std::get<0>(tup);
std::get<2>(tup).publish(*this, tag->entity);
tag.reset();
}
return has<Tag>() ? pool<Tag>(tag_t{}).destroy() : void();
}
/**
@@ -602,8 +714,6 @@ public:
void remove(const entity_type entity) {
assert(valid(entity));
assert(managed<Component>());
const auto ctype = component_family::type<Component>();
std::get<2>(pools[ctype]).publish(*this, entity);
pool<Component>().destroy(entity);
}
@@ -614,16 +724,7 @@ public:
*/
template<typename Tag>
bool has() const ENTT_NOEXCEPT {
const auto ttype = tag_family::type<Tag>();
bool found = false;
if(ttype < tags.size()) {
auto &tag = std::get<0>(tags[ttype]);
// it's a valid tag and the associated entity hasn't been destroyed in the meantime
found = tag && (tag->entity == (entities[tag->entity & traits_type::entity_mask]));
}
return found;
return managed<Tag>(tag_t{}) && tags[tag_family::type<Tag>()]->get() != null;
}
/**
@@ -681,7 +782,7 @@ public:
template<typename Tag>
const Tag & get() const ENTT_NOEXCEPT {
assert(has<Tag>());
return static_cast<Attaching<Tag> *>(std::get<0>(tags[tag_family::type<Tag>()]).get())->tag;
return pool<Tag>(tag_t{}).get();
}
/**
@@ -756,7 +857,7 @@ public:
* @return References to the components owned by the entity.
*/
template<typename... Component>
std::enable_if_t<(sizeof...(Component) > 1), std::tuple<const Component &...>>
inline std::enable_if_t<(sizeof...(Component) > 1), std::tuple<const Component &...>>
get(const entity_type entity) const ENTT_NOEXCEPT {
return std::tuple<const Component &...>{get<Component>(entity)...};
}
@@ -776,7 +877,7 @@ public:
* @return References to the components owned by the entity.
*/
template<typename... Component>
std::enable_if_t<(sizeof...(Component) > 1), std::tuple<Component &...>>
inline std::enable_if_t<(sizeof...(Component) > 1), std::tuple<Component &...>>
get(const entity_type entity) ENTT_NOEXCEPT {
return std::tuple<Component &...>{get<Component>(entity)...};
}
@@ -846,31 +947,21 @@ public:
* @return A valid entity identifier.
*/
template<typename Tag>
entity_type move(const entity_type entity) {
entity_type move(const entity_type entity) ENTT_NOEXCEPT {
assert(valid(entity));
assert(has<Tag>());
auto &tag = std::get<0>(tags[tag_family::type<Tag>()]);
const auto owner = tag->entity;
tag->entity = entity;
return owner;
return pool<Tag>(tag_t{}).move(entity);
}
/**
* @brief Gets the owner of the given tag, if any.
*
* @warning
* Attempting to get the owner of a tag that hasn't been previously attached
* to an entity results in undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode if the
* tag hasn't an owner.
*
* @tparam Tag Type of tag of which to get the owner.
* @return A valid entity identifier.
* @return A valid entity identifier if an owner exists, the null entity
* identifier otherwise.
*/
template<typename Tag>
entity_type attachee() const ENTT_NOEXCEPT {
assert(has<Tag>());
return std::get<0>(tags[tag_family::type<Tag>()])->entity;
return managed<Tag>(tag_t{}) ? tags[tag_family::type<Tag>()]->get() : null;
}
/**
@@ -904,9 +995,9 @@ public:
assure<Component>();
auto &cpool = pool<Component>();
return (cpool.has(entity)
return cpool.has(entity)
? cpool.get(entity) = Component{std::forward<Args>(args)...}
: cpool.construct(entity, std::forward<Args>(args)...));
: cpool.construct(entity, std::forward<Args>(args)...);
}
/**
@@ -935,7 +1026,7 @@ public:
template<typename Tag>
sink_type construction(tag_t) ENTT_NOEXCEPT {
assure<Tag>(tag_t{});
return std::get<1>(tags[tag_family::type<Tag>()]).sink();
return pool<Tag>(tag_t{}).construction();
}
/**
@@ -964,7 +1055,7 @@ public:
template<typename Component>
sink_type construction() ENTT_NOEXCEPT {
assure<Component>();
return std::get<1>(pools[component_family::type<Component>()]).sink();
return pool<Component>().construction();
}
/**
@@ -993,7 +1084,7 @@ public:
template<typename Tag>
sink_type destruction(tag_t) ENTT_NOEXCEPT {
assure<Tag>(tag_t{});
return std::get<2>(tags[tag_family::type<Tag>()]).sink();
return pool<Tag>(tag_t{}).destruction();
}
/**
@@ -1022,7 +1113,7 @@ public:
template<typename Component>
sink_type destruction() ENTT_NOEXCEPT {
assure<Component>();
return std::get<2>(pools[component_family::type<Component>()]).sink();
return pool<Component>().destruction();
}
/**
@@ -1033,7 +1124,7 @@ public:
* maximize the performance during iterations and users should not make any
* assumption on the order.<br/>
* This function can be used to impose an order to the elements in the pool
* for the given component. The order is kept valid until a component of the
* of the given component. The order is kept valid until a component of the
* given type is assigned or removed from an entity.
*
* The comparison function object must return `true` if the first element
@@ -1061,13 +1152,15 @@ public:
* @tparam Component Type of components to sort.
* @tparam Compare Type of comparison function object.
* @tparam Sort Type of sort function object.
* @tparam Args Types of arguments to forward to the sort function object.
* @param compare A valid comparison function object.
* @param sort A valid sort function object.
* @param args Arguments to forward to the sort function object, if any.
*/
template<typename Component, typename Compare, typename Sort = StdSort>
void sort(Compare compare, Sort sort = Sort{}) {
template<typename Component, typename Compare, typename Sort = StdSort, typename... Args>
void sort(Compare compare, Sort sort = Sort{}, Args &&... args) {
assure<Component>();
pool<Component>().sort(std::move(compare), std::move(sort));
pool<Component>().sort(std::move(compare), std::move(sort), std::forward<Args>(args)...);
}
/**
@@ -1125,11 +1218,9 @@ public:
void reset(const entity_type entity) {
assert(valid(entity));
assure<Component>();
const auto ctype = component_family::type<Component>();
auto &cpool = *std::get<0>(pools[ctype]);
auto &cpool = pool<Component>();
if(cpool.has(entity)) {
std::get<2>(pools[ctype]).publish(*this, entity);
cpool.destroy(entity);
}
}
@@ -1145,12 +1236,9 @@ public:
template<typename Component>
void reset() {
assure<Component>();
const auto ctype = component_family::type<Component>();
auto &cpool = *std::get<0>(pools[ctype]);
auto &sig = std::get<2>(pools[ctype]);
auto &cpool = pool<Component>();
for(const auto entity: cpool) {
sig.publish(*this, entity);
for(const auto entity: static_cast<SparseSet<Entity> &>(cpool)) {
cpool.destroy(entity);
}
}
@@ -1165,7 +1253,8 @@ public:
*/
void reset() {
each([this](const auto entity) {
destroy(entity);
// useless this-> used to suppress a warning with clang
this->destroy(entity);
});
}
@@ -1193,7 +1282,7 @@ public:
void each(Func func) const {
if(available) {
for(auto pos = entities.size(); pos; --pos) {
const entity_type curr = pos - 1;
const auto curr = entity_type(pos - 1);
const auto entity = entities[curr];
const auto entt = entity & traits_type::entity_mask;
@@ -1222,13 +1311,13 @@ public:
bool orphan = true;
for(std::size_t i = 0; i < pools.size() && orphan; ++i) {
const auto &pool = std::get<0>(pools[i]);
orphan = !(pool && pool->has(entity));
const auto &cpool = pools[i];
orphan = !(cpool && cpool->has(entity));
}
for(std::size_t i = 0; i < tags.size() && orphan; ++i) {
const auto &tag = std::get<0>(tags[i]);
orphan = !(tag && (tag->entity == entity));
const auto &tag = tags[i];
orphan = !(tag && (tag->get() == entity));
}
return orphan;
@@ -1288,6 +1377,7 @@ public:
* @see View<Entity, Component>
* @see PersistentView
* @see RawView
* @see RuntimeView
*
* @tparam Component Type of components used to construct the view.
* @return A newly created standard view.
@@ -1323,22 +1413,13 @@ public:
}
if(!handlers[htype]) {
connect<Component...>(std::make_index_sequence<sizeof...(Component)>{});
handlers[htype] = std::make_unique<SparseSet<entity_type>>();
auto &handler = handlers[htype];
auto &handler = *handlers[htype];
for(auto entity: view<Component...>()) {
handler->construct(entity);
handler.construct(entity);
}
auto connect = [this](const auto ctype) {
auto &cpool = pools[ctype];
std::get<1>(cpool).sink().template connect<&Registry::creating<Component...>>();
std::get<2>(cpool).sink().template connect<&Registry::destroying<Component...>>();
};
using accumulator_type = int[];
accumulator_type accumulator = { (assure<Component>(), connect(component_family::type<Component>()), 0)... };
(void)accumulator;
}
}
@@ -1360,19 +1441,8 @@ public:
template<typename... Component>
void discard() {
if(contains<Component...>()) {
const auto htype = handler_family::type<Component...>();
auto disconnect = [this](const auto ctype) {
auto &cpool = pools[ctype];
std::get<1>(cpool).sink().template disconnect<&Registry::creating<Component...>>();
std::get<2>(cpool).sink().template disconnect<&Registry::destroying<Component...>>();
};
// if a set exists, pools have already been created for it
using accumulator_type = int[];
accumulator_type accumulator = { (disconnect(component_family::type<Component>()), 0)... };
handlers[htype].reset();
(void)accumulator;
disconnect<Component...>(std::make_index_sequence<sizeof...(Component)>{});
handlers[handler_family::type<Component...>()].reset();
}
}
@@ -1422,6 +1492,7 @@ public:
* @see View<Entity, Component>
* @see PersistentView
* @see RawView
* @see RuntimeView
*
* @tparam Component Types of components used to construct the view.
* @return A newly created persistent view.
@@ -1451,6 +1522,7 @@ public:
* @see View<Entity, Component>
* @see PersistentView
* @see RawView
* @see RuntimeView
*
* @tparam Component Type of component used to construct the view.
* @return A newly created raw view.
@@ -1461,6 +1533,44 @@ public:
return RawView<Entity, Component>{pool<Component>()};
}
/**
* @brief Returns a runtime view for the given components.
*
* This kind of views are created on the fly and share with the registry its
* internal data structures.<br/>
* Users should throw away the view after use. Fortunately, creating and
* destroying a view is an incredibly cheap operation because they do not
* require any type of initialization.<br/>
* As a rule of thumb, storing a view should never be an option.
*
* Runtime views are well suited when users want to construct a view from
* some external inputs and don't know at compile-time what are the required
* components.<br/>
* This is particularly well suited to plugin systems and mods in general.
*
* @see View
* @see View<Entity, Component>
* @see PersistentView
* @see RawView
* @see RuntimeView
*
* @tparam It Type of forward iterator.
* @param first An iterator to the first element of the range of components.
* @param last An iterator past the last element of the range of components.
* @return A newly created runtime view.
*/
template<typename It>
RuntimeView<Entity> view(It first, It last) {
static_assert(std::is_convertible<typename std::iterator_traits<It>::value_type, component_type>::value, "!");
std::vector<const SparseSet<Entity> *> set(last - first);
std::transform(first, last, set.begin(), [this](const component_type ctype) {
return ctype < pools.size() ? pools[ctype].get() : nullptr;
});
return RuntimeView<Entity>{std::move(set)};
}
/**
* @brief Returns a temporary object to use to create snapshots.
*
@@ -1471,15 +1581,15 @@ public:
*
* @return A temporary object to use to take snasphosts.
*/
Snapshot<Entity> snapshot() const {
using follow_fn_type = entity_type(*)(const Registry &, const entity_type);
const entity_type seed = available ? (next | (entities[next] & ~traits_type::entity_mask)) : next;
Snapshot<Entity> snapshot() const ENTT_NOEXCEPT {
using follow_fn_type = entity_type(const Registry &, const entity_type);
const entity_type seed = available ? (next | (entities[next] & (traits_type::version_mask << traits_type::entity_shift))) : next;
follow_fn_type follow = [](const Registry &registry, const entity_type entity) -> entity_type {
follow_fn_type *follow = [](const Registry &registry, const entity_type entity) -> entity_type {
const auto &entities = registry.entities;
const auto entt = entity & traits_type::entity_mask;
const auto next = entities[entt] & traits_type::entity_mask;
return (next | (entities[next] & ~traits_type::entity_mask));
return (next | (entities[next] & (traits_type::version_mask << traits_type::entity_shift)));
};
return { *this, seed, follow };
@@ -1500,10 +1610,10 @@ public:
*
* @return A temporary object to use to load snasphosts.
*/
SnapshotLoader<Entity> restore() {
using assure_fn_type = void(*)(Registry &, const entity_type, const bool);
SnapshotLoader<Entity> restore() ENTT_NOEXCEPT {
using assure_fn_type = void(Registry &, const entity_type, const bool);
assure_fn_type assure = [](Registry &registry, const entity_type entity, const bool destroyed) {
assure_fn_type *assure = [](Registry &registry, const entity_type entity, const bool destroyed) {
using promotion_type = std::conditional_t<sizeof(size_type) >= sizeof(entity_type), size_type, entity_type>;
// explicit promotion to avoid warnings with std::uint16_t
const auto entt = promotion_type{entity} & traits_type::entity_mask;
@@ -1519,7 +1629,7 @@ public:
if(destroyed) {
registry.destroy(entity);
const auto version = (entity & (~traits_type::entity_mask));
const auto version = entity & (traits_type::version_mask << traits_type::entity_shift);
entities[entt] = ((entities[entt] & traits_type::entity_mask) | version);
}
};
@@ -1529,8 +1639,8 @@ public:
private:
std::vector<std::unique_ptr<SparseSet<Entity>>> handlers;
std::vector<std::tuple<std::unique_ptr<SparseSet<Entity>>, signal_type, signal_type>> pools;
std::vector<std::tuple<std::unique_ptr<Attachee>, signal_type, signal_type>> tags;
std::vector<std::unique_ptr<SparseSet<Entity>>> pools;
std::vector<std::unique_ptr<Attachee<Entity>>> tags;
std::vector<entity_type> entities;
size_type available{};
entity_type next{};

View File

@@ -39,16 +39,16 @@ 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);
using follow_fn_type = Entity(const Registry<Entity> &, const Entity);
Snapshot(const Registry<Entity> &registry, Entity seed, follow_fn_type follow) ENTT_NOEXCEPT
Snapshot(const Registry<Entity> &registry, 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) {
void get(Archive &archive, std::size_t sz, It first, It last) const {
archive(static_cast<Entity>(sz));
while(first != last) {
@@ -61,7 +61,7 @@ class Snapshot final {
}
template<typename... Component, typename Archive, typename It, std::size_t... Indexes>
void component(Archive &archive, It first, It last, std::index_sequence<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;
@@ -99,8 +99,8 @@ public:
* @return An object of this type to continue creating the snapshot.
*/
template<typename Archive>
Snapshot & entities(Archive &archive) {
archive(static_cast<Entity>(registry.size()));
const Snapshot & entities(Archive &archive) const {
archive(static_cast<Entity>(registry.alive()));
registry.each([&archive](const auto entity) { archive(entity); });
return *this;
}
@@ -116,14 +116,18 @@ public:
* @return An object of this type to continue creating the snapshot.
*/
template<typename Archive>
Snapshot & destroyed(Archive &archive) {
auto size = registry.capacity() - registry.size();
const Snapshot & destroyed(Archive &archive) const {
auto size = registry.size() - registry.alive();
archive(static_cast<Entity>(size));
auto curr = seed;
for(; size; --size) {
if(size) {
auto curr = seed;
archive(curr);
curr = follow(registry, curr);
for(--size; size; --size) {
curr = follow(registry, curr);
archive(curr);
}
}
return *this;
@@ -141,7 +145,7 @@ public:
* @return An object of this type to continue creating the snapshot.
*/
template<typename Component, typename Archive>
Snapshot & component(Archive &archive) {
const Snapshot & component(Archive &archive) const {
const auto sz = registry.template size<Component>();
const auto *entities = registry.template data<Component>();
@@ -167,8 +171,8 @@ public:
* @return An object of this type to continue creating the snapshot.
*/
template<typename... Component, typename Archive>
std::enable_if_t<(sizeof...(Component) > 1), Snapshot &>
component(Archive &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;
@@ -190,7 +194,7 @@ public:
* @return An object of this type to continue creating the snapshot.
*/
template<typename... Component, typename Archive, typename It>
Snapshot & component(Archive &archive, It first, It last) {
const Snapshot & component(Archive &archive, It first, It last) const {
component<Component...>(archive, first, last, std::make_index_sequence<sizeof...(Component)>{});
return *this;
}
@@ -207,7 +211,7 @@ public:
* @return An object of this type to continue creating the snapshot.
*/
template<typename Tag, typename Archive>
Snapshot & tag(Archive &archive) {
const Snapshot & tag(Archive &archive) const {
const bool has = registry.template has<Tag>();
// numerical length is forced for tags to facilitate loading
@@ -232,8 +236,8 @@ public:
* @return An object of this type to continue creating the snapshot.
*/
template<typename... Tag, typename Archive>
std::enable_if_t<(sizeof...(Tag) > 1), Snapshot &>
tag(Archive &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;
@@ -243,7 +247,7 @@ public:
private:
const Registry<Entity> &registry;
const Entity seed;
follow_fn_type follow;
follow_fn_type *follow;
};
@@ -262,9 +266,9 @@ 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);
using assure_fn_type = void(Registry<Entity> &, const Entity, const bool);
SnapshotLoader(Registry<Entity> &registry, assure_fn_type assure_fn) ENTT_NOEXCEPT
SnapshotLoader(Registry<Entity> &registry, assure_fn_type *assure_fn) ENTT_NOEXCEPT
: registry{registry},
assure_fn{assure_fn}
{
@@ -273,7 +277,7 @@ class SnapshotLoader final {
}
template<typename Archive>
void assure(Archive &archive, bool destroyed) {
void assure(Archive &archive, bool destroyed) const {
Entity length{};
archive(length);
@@ -285,7 +289,7 @@ class SnapshotLoader final {
}
template<typename Type, typename Archive, typename... Args>
void assign(Archive &archive, Args... args) {
void assign(Archive &archive, Args... args) const {
Entity length{};
archive(length);
@@ -321,7 +325,7 @@ public:
* @return A valid loader to continue restoring data.
*/
template<typename Archive>
SnapshotLoader & entities(Archive &archive) {
const SnapshotLoader & entities(Archive &archive) const {
static constexpr auto destroyed = false;
assure(archive, destroyed);
return *this;
@@ -338,7 +342,7 @@ public:
* @return A valid loader to continue restoring data.
*/
template<typename Archive>
SnapshotLoader & destroyed(Archive &archive) {
const SnapshotLoader & destroyed(Archive &archive) const {
static constexpr auto destroyed = true;
assure(archive, destroyed);
return *this;
@@ -358,7 +362,7 @@ public:
* @return A valid loader to continue restoring data.
*/
template<typename... Component, typename Archive>
SnapshotLoader & component(Archive &archive) {
const SnapshotLoader & component(Archive &archive) const {
using accumulator_type = int[];
accumulator_type accumulator = { 0, (assign<Component>(archive), 0)... };
(void)accumulator;
@@ -379,7 +383,7 @@ public:
* @return A valid loader to continue restoring data.
*/
template<typename... Tag, typename Archive>
SnapshotLoader & tag(Archive &archive) {
const SnapshotLoader & tag(Archive &archive) const {
using accumulator_type = int[];
accumulator_type accumulator = { 0, (assign<Tag>(archive, tag_t{}), 0)... };
(void)accumulator;
@@ -396,7 +400,7 @@ public:
*
* @return A valid loader to continue restoring data.
*/
SnapshotLoader & orphans() {
const SnapshotLoader & orphans() const {
registry.orphans([this](const auto entity) {
registry.destroy(entity);
});
@@ -406,7 +410,7 @@ public:
private:
Registry<Entity> &registry;
assure_fn_type assure_fn;
assure_fn_type *assure_fn;
};
@@ -687,7 +691,7 @@ public:
* @param entity An entity identifier.
* @return True if `entity` is managed by the loader, false otherwise.
*/
bool has(entity_type entity) {
bool has(entity_type entity) const ENTT_NOEXCEPT {
return (remloc.find(entity) != remloc.cend());
}
@@ -703,9 +707,9 @@ public:
* @param entity An entity identifier.
* @return The identifier to which `entity` refers in the target registry.
*/
entity_type map(entity_type entity) {
entity_type map(entity_type entity) const ENTT_NOEXCEPT {
assert(has(entity));
return remloc[entity].first;
return remloc.find(entity)->second.first;
}
private:

View File

@@ -13,6 +13,7 @@
#include "../config/config.h"
#include "../core/algorithm.hpp"
#include "entt_traits.hpp"
#include "entity.hpp"
namespace entt {
@@ -59,19 +60,30 @@ template<typename Entity>
class SparseSet<Entity> {
using traits_type = entt_traits<Entity>;
struct Iterator final {
using difference_type = std::size_t;
using value_type = Entity;
using pointer = const value_type *;
using reference = value_type;
using iterator_category = std::input_iterator_tag;
class Iterator final {
friend class SparseSet<Entity>;
Iterator(pointer direct, std::size_t pos)
: direct{direct}, pos{pos}
using direct_type = const std::vector<Entity>;
using index_type = typename traits_type::difference_type;
Iterator(direct_type *direct, index_type index) ENTT_NOEXCEPT
: direct{direct}, index{index}
{}
public:
using difference_type = index_type;
using value_type = const Entity;
using pointer = value_type *;
using reference = value_type &;
using iterator_category = std::random_access_iterator_tag;
Iterator() ENTT_NOEXCEPT = default;
Iterator(const Iterator &) ENTT_NOEXCEPT = default;
Iterator & operator=(const Iterator &) ENTT_NOEXCEPT = default;
Iterator & operator++() ENTT_NOEXCEPT {
return --pos, *this;
return --index, *this;
}
Iterator operator++(int) ENTT_NOEXCEPT {
@@ -79,39 +91,82 @@ class SparseSet<Entity> {
return ++(*this), orig;
}
Iterator & operator--() ENTT_NOEXCEPT {
return ++index, *this;
}
Iterator operator--(int) ENTT_NOEXCEPT {
Iterator orig = *this;
return --(*this), orig;
}
Iterator & operator+=(const difference_type value) ENTT_NOEXCEPT {
pos -= value;
index -= value;
return *this;
}
Iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
return Iterator{direct, pos-value};
return Iterator{direct, index-value};
}
inline Iterator & operator-=(const difference_type value) ENTT_NOEXCEPT {
return (*this += -value);
}
inline Iterator operator-(const difference_type value) const ENTT_NOEXCEPT {
return (*this + -value);
}
difference_type operator-(const Iterator &other) const ENTT_NOEXCEPT {
return other.index - index;
}
reference operator[](const difference_type value) const ENTT_NOEXCEPT {
const auto pos = size_type(index-value-1);
return (*direct)[pos];
}
bool operator==(const Iterator &other) const ENTT_NOEXCEPT {
return other.pos == pos;
return other.index == index;
}
inline bool operator!=(const Iterator &other) const ENTT_NOEXCEPT {
return !(*this == other);
}
reference operator*() const ENTT_NOEXCEPT {
return direct[pos-1];
bool operator<(const Iterator &other) const ENTT_NOEXCEPT {
return index > other.index;
}
bool operator>(const Iterator &other) const ENTT_NOEXCEPT {
return index < other.index;
}
inline bool operator<=(const Iterator &other) const ENTT_NOEXCEPT {
return !(*this > other);
}
inline bool operator>=(const Iterator &other) const ENTT_NOEXCEPT {
return !(*this < other);
}
pointer operator->() const ENTT_NOEXCEPT {
const auto pos = size_type(index-1);
return &(*direct)[pos];
}
inline reference operator*() const ENTT_NOEXCEPT {
return *operator->();
}
private:
pointer direct;
std::size_t pos;
direct_type *direct;
index_type index;
};
static constexpr auto pending = ~typename traits_type::entity_type{};
public:
/*! @brief Underlying entity identifier. */
using entity_type = Entity;
/*! @brief Entity dependent position type. */
using pos_type = entity_type;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Input iterator type. */
@@ -147,6 +202,15 @@ public:
direct.reserve(cap);
}
/**
* @brief Returns the number of elements that a sparse set has currently
* allocated space for.
* @return Capacity of the sparse set.
*/
size_type capacity() const ENTT_NOEXCEPT {
return direct.capacity();
}
/**
* @brief Returns the extent of a sparse set.
*
@@ -215,7 +279,8 @@ public:
* @return An iterator to the first entity of the internal packed array.
*/
const_iterator_type cbegin() const ENTT_NOEXCEPT {
return const_iterator_type{direct.data(), direct.size()};
const typename traits_type::difference_type pos = direct.size();
return const_iterator_type{&direct, pos};
}
/**
@@ -264,7 +329,7 @@ public:
* internal packed array.
*/
const_iterator_type cend() const ENTT_NOEXCEPT {
return const_iterator_type{direct.data(), {}};
return const_iterator_type{&direct, {}};
}
/**
@@ -301,6 +366,15 @@ public:
return cend();
}
/**
* @brief Returns a reference to the element at the given position.
* @param pos Position of the element to return.
* @return A reference to the requested element.
*/
inline const entity_type & operator[](const size_type pos) const ENTT_NOEXCEPT {
return cbegin()[pos];
}
/**
* @brief Checks if a sparse set contains an entity.
* @param entity A valid entity identifier.
@@ -308,8 +382,8 @@ public:
*/
bool has(const entity_type entity) const ENTT_NOEXCEPT {
const auto pos = size_type(entity & traits_type::entity_mask);
// testing against pending permits to avoid accessing the direct vector
return (pos < reverse.size()) && (reverse[pos] != pending);
// testing against null permits to avoid accessing the direct vector
return (pos < reverse.size()) && (reverse[pos] != null);
}
/**
@@ -332,8 +406,8 @@ public:
bool fast(const entity_type entity) const ENTT_NOEXCEPT {
const auto pos = size_type(entity & traits_type::entity_mask);
assert(pos < reverse.size());
// testing against pending permits to avoid accessing the direct vector
return (reverse[pos] != pending);
// testing against null permits to avoid accessing the direct vector
return (reverse[pos] != null);
}
/**
@@ -348,9 +422,10 @@ public:
* @param entity A valid entity identifier.
* @return The position of the entity in the sparse set.
*/
pos_type get(const entity_type entity) const ENTT_NOEXCEPT {
size_type get(const entity_type entity) const ENTT_NOEXCEPT {
assert(has(entity));
return reverse[entity & traits_type::entity_mask];
const auto pos = size_type(entity & traits_type::entity_mask);
return size_type(reverse[pos]);
}
/**
@@ -369,36 +444,14 @@ public:
const auto pos = size_type(entity & traits_type::entity_mask);
if(!(pos < reverse.size())) {
const auto value = pending;
reverse.resize(pos+1, value);
// null is safe in all cases for our purposes
reverse.resize(pos+1, null);
}
reverse[pos] = pos_type(direct.size());
reverse[pos] = entity_type(direct.size());
direct.push_back(entity);
}
/**
* @brief Assigns an entity to a sparse set by cloning another entity.
*
* @warning
* Attempting to clone an entity that doesn't belong to the sparse set or to
* assign an entity that already belongs to the sparse set results in
* undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode if the
* sparse set doesn't contain the entity to clone or if it already contains
* the given entity.
*
* @param entity A valid entity identifier.
* @param source A valid entity identifier from which to clone.
*/
inline virtual void clone(const entity_type entity, const entity_type source) {
assert(has(source));
assert(!has(entity));
construct(entity);
// useful to suppress warnings when asserts are disabled
(void)source;
}
/**
* @brief Removes an entity from a sparse set.
*
@@ -413,11 +466,11 @@ public:
virtual void destroy(const entity_type entity) {
assert(has(entity));
const auto back = direct.back();
auto &candidate = reverse[entity & traits_type::entity_mask];
auto &candidate = reverse[size_type(entity & traits_type::entity_mask)];
// swapping isn't required here, we are getting rid of the last element
reverse[back & traits_type::entity_mask] = candidate;
direct[candidate] = back;
candidate = pending;
direct[size_type(candidate)] = back;
candidate = null;
direct.pop_back();
}
@@ -436,7 +489,7 @@ public:
* @param lhs A valid position within the sparse set.
* @param rhs A valid position within the sparse set.
*/
void swap(const pos_type lhs, const pos_type rhs) ENTT_NOEXCEPT {
void swap(const size_type lhs, const size_type rhs) ENTT_NOEXCEPT {
assert(lhs < direct.size());
assert(rhs < direct.size());
auto &src = direct[lhs];
@@ -468,7 +521,7 @@ public:
auto from = other.cbegin();
auto to = other.cend();
pos_type pos = direct.size() - 1;
size_type pos = direct.size() - 1;
while(pos && from != to) {
if(has(*from)) {
@@ -492,7 +545,7 @@ public:
}
private:
std::vector<pos_type> reverse;
std::vector<entity_type> reverse;
std::vector<entity_type> direct;
};
@@ -522,21 +575,33 @@ private:
template<typename Entity, typename Type>
class SparseSet<Entity, Type>: public SparseSet<Entity> {
using underlying_type = SparseSet<Entity>;
using traits_type = entt_traits<Entity>;
template<bool Const>
struct Iterator final {
using difference_type = std::size_t;
class Iterator final {
friend class SparseSet<Entity, Type>;
using instance_type = std::conditional_t<Const, const std::vector<Type>, std::vector<Type>>;
using index_type = typename traits_type::difference_type;
Iterator(instance_type *instances, index_type index) ENTT_NOEXCEPT
: instances{instances}, index{index}
{}
public:
using difference_type = index_type;
using value_type = std::conditional_t<Const, const Type, Type>;
using pointer = value_type *;
using reference = value_type &;
using iterator_category = std::input_iterator_tag;
using iterator_category = std::random_access_iterator_tag;
Iterator(pointer instances, std::size_t pos)
: instances{instances}, pos{pos}
{}
Iterator() ENTT_NOEXCEPT = default;
Iterator(const Iterator &) ENTT_NOEXCEPT = default;
Iterator & operator=(const Iterator &) ENTT_NOEXCEPT = default;
Iterator & operator++() ENTT_NOEXCEPT {
return --pos, *this;
return --index, *this;
}
Iterator operator++(int) ENTT_NOEXCEPT {
@@ -544,34 +609,77 @@ class SparseSet<Entity, Type>: public SparseSet<Entity> {
return ++(*this), orig;
}
Iterator & operator--() ENTT_NOEXCEPT {
return ++index, *this;
}
Iterator operator--(int) ENTT_NOEXCEPT {
Iterator orig = *this;
return --(*this), orig;
}
Iterator & operator+=(const difference_type value) ENTT_NOEXCEPT {
pos -= value;
index -= value;
return *this;
}
Iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
return Iterator{instances, pos-value};
return Iterator{instances, index-value};
}
inline Iterator & operator-=(const difference_type value) ENTT_NOEXCEPT {
return (*this += -value);
}
inline Iterator operator-(const difference_type value) const ENTT_NOEXCEPT {
return (*this + -value);
}
difference_type operator-(const Iterator &other) const ENTT_NOEXCEPT {
return other.index - index;
}
reference operator[](const difference_type value) const ENTT_NOEXCEPT {
const auto pos = size_type(index-value-1);
return (*instances)[pos];
}
bool operator==(const Iterator &other) const ENTT_NOEXCEPT {
return other.pos == pos;
return other.index == index;
}
inline bool operator!=(const Iterator &other) const ENTT_NOEXCEPT {
return !(*this == other);
}
reference operator*() const ENTT_NOEXCEPT {
return instances[pos-1];
bool operator<(const Iterator &other) const ENTT_NOEXCEPT {
return index > other.index;
}
bool operator>(const Iterator &other) const ENTT_NOEXCEPT {
return index < other.index;
}
inline bool operator<=(const Iterator &other) const ENTT_NOEXCEPT {
return !(*this > other);
}
inline bool operator>=(const Iterator &other) const ENTT_NOEXCEPT {
return !(*this < other);
}
pointer operator->() const ENTT_NOEXCEPT {
return (instances+pos-1);
const auto pos = size_type(index-1);
return &(*instances)[pos];
}
inline reference operator*() const ENTT_NOEXCEPT {
return *operator->();
}
private:
pointer instances;
std::size_t pos;
instance_type *instances;
index_type index;
};
public:
@@ -579,8 +687,6 @@ public:
using object_type = Type;
/*! @brief Underlying entity identifier. */
using entity_type = typename underlying_type::entity_type;
/*! @brief Entity dependent position type. */
using pos_type = typename underlying_type::pos_type;
/*! @brief Unsigned integer type. */
using size_type = typename underlying_type::size_type;
/*! @brief Input iterator type. */
@@ -665,7 +771,8 @@ public:
* @return An iterator to the first instance of the given type.
*/
const_iterator_type cbegin() const ENTT_NOEXCEPT {
return const_iterator_type{instances.data(), instances.size()};
const typename traits_type::difference_type pos = instances.size();
return const_iterator_type{&instances, pos};
}
/**
@@ -697,7 +804,8 @@ public:
* @return An iterator to the first instance of the given type.
*/
iterator_type begin() ENTT_NOEXCEPT {
return iterator_type{instances.data(), instances.size()};
const typename traits_type::difference_type pos = instances.size();
return iterator_type{&instances, pos};
}
/**
@@ -715,7 +823,7 @@ public:
* given type.
*/
const_iterator_type cend() const ENTT_NOEXCEPT {
return const_iterator_type{instances.data(), {}};
return const_iterator_type{&instances, {}};
}
/**
@@ -751,7 +859,25 @@ public:
* given type.
*/
iterator_type end() ENTT_NOEXCEPT {
return iterator_type{instances.data(), {}};
return iterator_type{&instances, {}};
}
/**
* @brief Returns a reference to the element at the given position.
* @param pos Position of the element to return.
* @return A reference to the requested element.
*/
inline const object_type & operator[](const size_type pos) const ENTT_NOEXCEPT {
return cbegin()[pos];
}
/**
* @brief Returns a reference to the element at the given position.
* @param pos Position of the element to return.
* @return A reference to the requested element.
*/
inline object_type & operator[](const size_type pos) ENTT_NOEXCEPT {
return const_cast<object_type &>(const_cast<const SparseSet *>(this)->operator[](pos));
}
/**
@@ -842,24 +968,6 @@ public:
return instances.back();
}
/**
* @brief Assigns an entity to a sparse set by cloning another entity.
*
* @warning
* Attempting to clone an entity that doesn't belong to the sparse set or to
* assign an entity that already belongs to the sparse set results in
* undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode if the
* sparse set doesn't contain the entity to clone or if it already contains
* the given entity.
*
* @param entity A valid entity identifier.
* @param source A valid entity identifier from which to clone.
*/
inline void clone(const entity_type entity, const entity_type source) override {
construct(entity, get(source));
}
/**
* @brief Removes an entity from a sparse set and destroies its object.
*
@@ -916,19 +1024,21 @@ public:
*
* @tparam Compare Type of comparison function object.
* @tparam Sort Type of sort function object.
* @tparam Args Types of arguments to forward to the sort function object.
* @param compare A valid comparison function object.
* @param sort A valid sort function object.
* @param args Arguments to forward to the sort function object, if any.
*/
template<typename Compare, typename Sort = StdSort>
void sort(Compare compare, Sort sort = Sort{}) {
std::vector<pos_type> copy(instances.size());
template<typename Compare, typename Sort = StdSort, typename... Args>
void sort(Compare compare, Sort sort = Sort{}, Args &&... args) {
std::vector<size_type> copy(instances.size());
std::iota(copy.begin(), copy.end(), 0);
sort(copy.begin(), copy.end(), [this, compare = std::move(compare)](const auto lhs, const auto rhs) {
return compare(const_cast<const object_type &>(instances[rhs]), const_cast<const object_type &>(instances[lhs]));
});
}, std::forward<Args>(args)...);
for(pos_type pos = 0, last = copy.size(); pos < last; ++pos) {
for(size_type pos = 0, last = copy.size(); pos < last; ++pos) {
auto curr = pos;
auto next = copy[curr];
@@ -971,7 +1081,7 @@ public:
auto from = other.cbegin();
auto to = other.cend();
pos_type pos = underlying_type::size() - 1;
size_type pos = underlying_type::size() - 1;
const auto *local = underlying_type::data();
while(pos && from != to) {

View File

@@ -17,10 +17,6 @@ struct persistent_t final {};
struct raw_t final {};
/*! @brief Break type used to disambiguate overloads. */
struct break_t final {};
}

View File

@@ -2,9 +2,11 @@
#define ENTT_ENTITY_VIEW_HPP
#include <iterator>
#include <cassert>
#include <array>
#include <tuple>
#include <vector>
#include <utility>
#include <algorithm>
#include <type_traits>
@@ -60,6 +62,7 @@ class Registry;
* @sa View
* @sa View<Entity, Component>
* @sa RawView
* @sa RuntimeView
*
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Component Types of components iterated by the view.
@@ -82,14 +85,14 @@ class PersistentView final {
{}
public:
/*! @brief Input iterator type. */
using iterator_type = typename view_type::iterator_type;
/*! @brief Constant input iterator type. */
using const_iterator_type = typename view_type::const_iterator_type;
/*! @brief Underlying entity identifier. */
using entity_type = typename view_type::entity_type;
/*! @brief Unsigned integer type. */
using size_type = typename view_type::size_type;
/*! @brief Input iterator type. */
using iterator_type = typename view_type::iterator_type;
/*! @brief Constant input iterator type. */
using const_iterator_type = typename view_type::const_iterator_type;
/**
* @brief Returns the number of entities that have the given components.
@@ -234,6 +237,15 @@ public:
return view.end();
}
/**
* @brief Returns a reference to the element at the given position.
* @param pos Position of the element to return.
* @return A reference to the requested element.
*/
const entity_type & operator[](const size_type pos) const ENTT_NOEXCEPT {
return view[pos];
}
/**
* @brief Checks if a view contains an entity.
* @param entity A valid entity identifier.
@@ -306,7 +318,7 @@ public:
* @return The components assigned to the entity.
*/
template<typename... Comp>
std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<const Comp &...>>
inline std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<const Comp &...>>
get(const entity_type entity) const ENTT_NOEXCEPT {
assert(contains(entity));
return std::tuple<const Comp &...>{get<Comp>(entity)...};
@@ -330,7 +342,7 @@ public:
* @return The components assigned to the entity.
*/
template<typename... Comp>
std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<Comp &...>>
inline std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<Comp &...>>
get(const entity_type entity) ENTT_NOEXCEPT {
assert(contains(entity));
return std::tuple<Comp &...>{get<Comp>(entity)...};
@@ -445,6 +457,7 @@ private:
* @sa View<Entity, Component>
* @sa PersistentView
* @sa RawView
* @sa RuntimeView
*
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Component Types of components iterated by the view.
@@ -469,7 +482,25 @@ class View final {
using traits_type = entt_traits<Entity>;
class Iterator {
using size_type = typename view_type::size_type;
friend class View<Entity, Component...>;
using extent_type = typename view_type::size_type;
Iterator(unchecked_type unchecked, underlying_iterator_type begin, underlying_iterator_type end) ENTT_NOEXCEPT
: unchecked{unchecked},
begin{begin},
end{end},
extent{min(std::make_index_sequence<unchecked.size()>{})}
{
if(begin != end && !valid()) {
++(*this);
}
}
template<std::size_t... Indexes>
extent_type min(std::index_sequence<Indexes...>) const ENTT_NOEXCEPT {
return std::min({ std::get<Indexes>(unchecked)->extent()... });
}
bool valid() const ENTT_NOEXCEPT {
const auto entity = *begin;
@@ -485,18 +516,12 @@ class View final {
using value_type = typename underlying_iterator_type::value_type;
using pointer = typename underlying_iterator_type::pointer;
using reference = typename underlying_iterator_type::reference;
using iterator_category = typename underlying_iterator_type::iterator_category;
using iterator_category = std::forward_iterator_tag;
Iterator(unchecked_type unchecked, size_type extent, underlying_iterator_type begin, underlying_iterator_type end) ENTT_NOEXCEPT
: unchecked{unchecked},
extent{extent},
begin{begin},
end{end}
{
if(begin != end && !valid()) {
++(*this);
}
}
Iterator() ENTT_NOEXCEPT = default;
Iterator(const Iterator &) ENTT_NOEXCEPT = default;
Iterator & operator=(const Iterator &) ENTT_NOEXCEPT = default;
Iterator & operator++() ENTT_NOEXCEPT {
return (++begin != end && !valid()) ? ++(*this) : *this;
@@ -507,14 +532,6 @@ class View final {
return ++(*this), orig;
}
Iterator & operator+=(const difference_type value) ENTT_NOEXCEPT {
return ((begin += value) != end && !valid()) ? ++(*this) : *this;
}
Iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
return Iterator{unchecked, extent, begin+value, end};
}
bool operator==(const Iterator &other) const ENTT_NOEXCEPT {
return other.begin == begin;
}
@@ -523,15 +540,19 @@ class View final {
return !(*this == other);
}
value_type operator*() const ENTT_NOEXCEPT {
return *begin;
pointer operator->() const ENTT_NOEXCEPT {
return begin.operator->();
}
inline reference operator*() const ENTT_NOEXCEPT {
return *operator->();
}
private:
const unchecked_type unchecked;
const size_type extent;
unchecked_type unchecked;
underlying_iterator_type begin;
underlying_iterator_type end;
extent_type extent;
};
View(pool_type<Component> &... pools) ENTT_NOEXCEPT
@@ -543,11 +564,6 @@ class View final {
return std::get<pool_type<Comp> &>(pools);
}
template<typename Comp>
inline pool_type<Comp> & pool() ENTT_NOEXCEPT {
return const_cast<pool_type<Comp> &>(const_cast<const View *>(this)->pool<Comp>());
}
const view_type * candidate() const ENTT_NOEXCEPT {
return std::min({ static_cast<const view_type *>(&pool<Component>())... }, [](const auto *lhs, const auto *rhs) {
return lhs->size() < rhs->size();
@@ -563,10 +579,6 @@ class View final {
return other;
}
typename view_type::size_type extent() const ENTT_NOEXCEPT {
return std::min({ pool<Component>().extent()... });
}
template<typename Comp, typename Other>
inline std::enable_if_t<std::is_same<Comp, Other>::value, const Other &>
get(const component_iterator_type<Comp> &it, const Entity) const ENTT_NOEXCEPT { return *it; }
@@ -578,29 +590,24 @@ class View final {
template<typename Comp, typename Func, std::size_t... Indexes>
void each(const pool_type<Comp> &cpool, Func func, std::index_sequence<Indexes...>) const {
const auto other = unchecked(&cpool);
std::array<underlying_iterator_type, sizeof...(Component)> data{{cpool.view_type::cbegin(), std::get<Indexes>(other)->cbegin()...}};
std::array<underlying_iterator_type, sizeof...(Indexes)> data{{std::get<Indexes>(other)->cbegin()...}};
const auto extent = std::min({ pool<Component>().extent()... });
auto raw = std::make_tuple(pool<Component>().cbegin()...);
const auto end = cpool.view_type::cend();
std::size_t pos{};
auto begin = cpool.view_type::cbegin();
// we can directly use the raw iterators if pools are ordered
while(!pos && data[0] != end) {
for(pos = data.size() - 1; pos && *(data[pos]++) == *data[pos-1]; --pos);
if(!pos) {
func(*(data[0]++), *(std::get<component_iterator_type<Component>>(raw)++)...);
}
while(begin != end && std::min({ (*(std::get<Indexes>(data)++) == *begin)... })) {
func(*(begin++), *(std::get<component_iterator_type<Component>>(raw)++)...);
}
auto it = std::get<component_iterator_type<Comp>>(raw);
const auto ext = extent();
// fallback to visit what remains using indirections
for(; data[0] != end; ++data[0], ++it) {
const auto entity = *data[0];
while(begin != end) {
const auto entity = *(begin++);
const auto it = std::get<component_iterator_type<Comp>>(raw)++;
const auto sz = size_type(entity & traits_type::entity_mask);
if(sz < ext && std::all_of(other.cbegin(), other.cend(), [entity](const view_type *view) { return view->fast(entity); })) {
if(sz < extent && std::all_of(other.cbegin(), other.cend(), [entity](const view_type *view) { return view->fast(entity); })) {
// avoided at least the indirection due to the sparse set for the pivot type (see get for more details)
func(entity, get<Comp, Component>(it, entity)...);
}
@@ -608,14 +615,14 @@ class View final {
}
public:
/*! @brief Input iterator type. */
using iterator_type = Iterator;
/*! @brief Constant input iterator type. */
using const_iterator_type = Iterator;
/*! @brief Underlying entity identifier. */
using entity_type = typename view_type::entity_type;
/*! @brief Unsigned integer type. */
using size_type = typename view_type::size_type;
/*! @brief Input iterator type. */
using iterator_type = Iterator;
/*! @brief Constant input iterator type. */
using const_iterator_type = Iterator;
/**
* @brief Estimates the number of entities that have the given components.
@@ -649,7 +656,7 @@ public:
*/
const_iterator_type cbegin() const ENTT_NOEXCEPT {
const auto *view = candidate();
return iterator_type{ unchecked(view), extent(), view->cbegin(), view->cend() };
return const_iterator_type{unchecked(view), view->cbegin(), view->cend()};
}
/**
@@ -705,7 +712,7 @@ public:
*/
const_iterator_type cend() const ENTT_NOEXCEPT {
const auto *view = candidate();
return iterator_type{ unchecked(view), extent(), view->cend(), view->cend() };
return const_iterator_type{unchecked(view), view->cend(), view->cend()};
}
/**
@@ -753,7 +760,8 @@ public:
*/
bool contains(const entity_type entity) const ENTT_NOEXCEPT {
const auto sz = size_type(entity & traits_type::entity_mask);
return sz < extent() && std::min({ (pool<Component>().has(entity) && (pool<Component>().data()[pool<Component>().view_type::get(entity)] == entity))... });
const auto extent = std::min({ pool<Component>().extent()... });
return sz < extent && std::min({ (pool<Component>().has(entity) && (pool<Component>().data()[pool<Component>().view_type::get(entity)] == entity))... });
}
/**
@@ -819,7 +827,7 @@ public:
* @return The components assigned to the entity.
*/
template<typename... Comp>
std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<const Comp &...>>
inline std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<const Comp &...>>
get(const entity_type entity) const ENTT_NOEXCEPT {
assert(contains(entity));
return std::tuple<const Comp &...>{get<Comp>(entity)...};
@@ -843,7 +851,7 @@ public:
* @return The components assigned to the entity.
*/
template<typename... Comp>
std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<Comp &...>>
inline std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<Comp &...>>
get(const entity_type entity) ENTT_NOEXCEPT {
assert(contains(entity));
return std::tuple<Comp &...>{get<Comp>(entity)...};
@@ -934,6 +942,7 @@ private:
* @sa View
* @sa PersistentView
* @sa RawView
* @sa RuntimeView
*
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Component Type of component iterated by the view.
@@ -951,16 +960,16 @@ class View<Entity, Component> final {
{}
public:
/*! @brief Input iterator type. */
using iterator_type = typename view_type::iterator_type;
/*! @brief Constant input iterator type. */
using const_iterator_type = typename view_type::const_iterator_type;
/*! @brief Type of component iterated by the view. */
using raw_type = typename pool_type::object_type;
/*! @brief Underlying entity identifier. */
using entity_type = typename pool_type::entity_type;
/*! @brief Unsigned integer type. */
using size_type = typename pool_type::size_type;
/*! @brief Type of component iterated by the view. */
using raw_type = typename pool_type::object_type;
/*! @brief Input iterator type. */
using iterator_type = typename view_type::iterator_type;
/*! @brief Constant input iterator type. */
using const_iterator_type = typename view_type::const_iterator_type;
/**
* @brief Returns the number of entities that have the given component.
@@ -1137,6 +1146,15 @@ public:
return pool.view_type::end();
}
/**
* @brief Returns a reference to the element at the given position.
* @param pos Position of the element to return.
* @return A reference to the requested element.
*/
const entity_type & operator[](const size_type pos) const ENTT_NOEXCEPT {
return pool.view_type::operator[](pos);
}
/**
* @brief Checks if a view contains an entity.
* @param entity A valid entity identifier.
@@ -1268,6 +1286,7 @@ private:
* @sa View
* @sa View<Entity, Component>
* @sa PersistentView
* @sa RuntimeView
*
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Component Type of component iterated by the view.
@@ -1284,16 +1303,16 @@ class RawView final {
{}
public:
/*! @brief Input iterator type. */
using iterator_type = typename pool_type::iterator_type;
/*! @brief Constant input iterator type. */
using const_iterator_type = typename pool_type::const_iterator_type;
/*! @brief Type of component iterated by the view. */
using raw_type = typename pool_type::object_type;
/*! @brief Underlying entity identifier. */
using entity_type = typename pool_type::entity_type;
/*! @brief Unsigned integer type. */
using size_type = typename pool_type::size_type;
/*! @brief Type of component iterated by the view. */
using raw_type = typename pool_type::object_type;
/*! @brief Input iterator type. */
using iterator_type = typename pool_type::iterator_type;
/*! @brief Constant input iterator type. */
using const_iterator_type = typename pool_type::const_iterator_type;
/**
* @brief Returns the number of instances of the given type.
@@ -1464,6 +1483,24 @@ public:
return pool.end();
}
/**
* @brief Returns a reference to the element at the given position.
* @param pos Position of the element to return.
* @return A reference to the requested element.
*/
const raw_type & operator[](const size_type pos) const ENTT_NOEXCEPT {
return pool[pos];
}
/**
* @brief Returns a reference to the element at the given position.
* @param pos Position of the element to return.
* @return A reference to the requested element.
*/
inline raw_type & operator[](const size_type pos) ENTT_NOEXCEPT {
return const_cast<raw_type &>(const_cast<const RawView *>(this)->operator[](pos));
}
/**
* @brief Iterates components and applies the given function object to them.
*
@@ -1486,12 +1523,12 @@ public:
/**
* @brief Iterates components and applies the given function object to them.
*
* The function object is provided with a const reference to each component
* of the view.<br/>
* The function object is provided with a reference to each component of the
* view.<br/>
* The signature of the function should be equivalent to the following:
*
* @code{.cpp}
* void(const Component &);
* void(Component &);
* @endcode
*
* @tparam Func Type of the function object to invoke.
@@ -1507,6 +1544,345 @@ private:
};
/**
* @brief Runtime view.
*
* Runtime views iterate over those entities that have at least all the given
* components in their bags. During initialization, a runtime view looks at the
* number of entities available for each component and picks up a reference to
* the smallest set of candidate entities in order to get a performance boost
* when iterate.<br/>
* Order of elements during iterations are highly dependent on the order of the
* underlying data structures. See SparseSet and its specializations for more
* details.
*
* @b Important
*
* Iterators aren't invalidated if:
*
* * New instances of the given components are created and assigned to entities.
* * The entity currently pointed is modified (as an example, if one of the
* given components is removed from the entity to which the iterator points).
*
* In all the other cases, modifying the pools of the given components in any
* way invalidates all the iterators and using them results in undefined
* behavior.
*
* @note
* Views share references to the underlying data structures with the Registry
* that generated them. Therefore any change to the entities and to the
* components made by means of the registry are immediately reflected by views,
* unless a pool wasn't missing when the view was built (in this case, the view
* won't have a valid reference and won't be updated accordingly).
*
* @warning
* Lifetime of a view must overcome the one of the registry that generated it.
* In any other case, attempting to use a view results in undefined behavior.
*
* @sa View
* @sa View<Entity, Component>
* @sa PersistentView
* @sa RawView
*
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
class RuntimeView {
/*! @brief A registry is allowed to create views. */
friend class Registry<Entity>;
using view_type = SparseSet<Entity>;
using underlying_iterator_type = typename view_type::const_iterator_type;
using pattern_type = std::vector<const view_type *>;
using extent_type = typename view_type::size_type;
using traits_type = entt_traits<Entity>;
class Iterator {
friend class RuntimeView<Entity>;
Iterator(underlying_iterator_type begin, underlying_iterator_type end, const view_type * const *first, const view_type * const *last, extent_type extent) ENTT_NOEXCEPT
: begin{begin},
end{end},
first{first},
last{last},
extent{extent}
{
if(begin != end && !valid()) {
++(*this);
}
}
bool valid() const ENTT_NOEXCEPT {
const auto entity = *begin;
const auto sz = size_type(entity & traits_type::entity_mask);
return sz < extent && std::all_of(first, last, [entity](const auto *view) {
return view->fast(entity);
});
}
public:
using difference_type = typename underlying_iterator_type::difference_type;
using value_type = typename underlying_iterator_type::value_type;
using pointer = typename underlying_iterator_type::pointer;
using reference = typename underlying_iterator_type::reference;
using iterator_category = std::forward_iterator_tag;
Iterator() ENTT_NOEXCEPT = default;
Iterator(const Iterator &) ENTT_NOEXCEPT = default;
Iterator & operator=(const Iterator &) ENTT_NOEXCEPT = default;
Iterator & operator++() ENTT_NOEXCEPT {
return (++begin != end && !valid()) ? ++(*this) : *this;
}
Iterator operator++(int) ENTT_NOEXCEPT {
Iterator orig = *this;
return ++(*this), orig;
}
bool operator==(const Iterator &other) const ENTT_NOEXCEPT {
return other.begin == begin;
}
inline bool operator!=(const Iterator &other) const ENTT_NOEXCEPT {
return !(*this == other);
}
pointer operator->() const ENTT_NOEXCEPT {
return begin.operator->();
}
inline reference operator*() const ENTT_NOEXCEPT {
return *operator->();
}
private:
underlying_iterator_type begin;
underlying_iterator_type end;
const view_type * const *first;
const view_type * const *last;
extent_type extent;
};
RuntimeView(pattern_type others) ENTT_NOEXCEPT
: pools{std::move(others)}
{
const auto it = std::min_element(pools.begin(), pools.end(), [](const auto *lhs, const auto *rhs) {
return (!lhs && rhs) || (lhs && rhs && lhs->size() < rhs->size());
});
// brings the best candidate (if any) on front of the vector
std::rotate(pools.begin(), it, pools.end());
}
extent_type min() const ENTT_NOEXCEPT {
extent_type extent{};
if(valid()) {
const auto it = std::min_element(pools.cbegin(), pools.cend(), [](const auto *lhs, const auto *rhs) {
return lhs->extent() < rhs->extent();
});
extent = (*it)->extent();
}
return extent;
}
inline bool valid() const ENTT_NOEXCEPT {
return !pools.empty() && pools.front();
}
public:
/*! @brief Underlying entity identifier. */
using entity_type = typename view_type::entity_type;
/*! @brief Unsigned integer type. */
using size_type = typename view_type::size_type;
/*! @brief Input iterator type. */
using iterator_type = Iterator;
/*! @brief Constant input iterator type. */
using const_iterator_type = Iterator;
/**
* @brief Estimates the number of entities that have the given components.
* @return Estimated number of entities that have the given components.
*/
size_type size() const ENTT_NOEXCEPT {
return valid() ? pools.front()->size() : size_type{};
}
/**
* @brief Checks if the view is definitely empty.
* @return True if the view is definitely empty, false otherwise.
*/
bool empty() const ENTT_NOEXCEPT {
return !valid() || pools.front()->empty();
}
/**
* @brief Returns an iterator to the first entity that has the given
* components.
*
* The returned iterator points to the first entity that has the given
* components. If the view is empty, the returned iterator will be equal to
* `end()`.
*
* @note
* Input iterators stay true to the order imposed to the underlying data
* structures.
*
* @return An iterator to the first entity that has the given components.
*/
const_iterator_type cbegin() const ENTT_NOEXCEPT {
const_iterator_type it{};
if(valid()) {
const auto &pool = *pools.front();
const auto * const *data = pools.data();
it = { pool.cbegin(), pool.cend(), data + 1, data + pools.size(), min() };
}
return it;
}
/**
* @brief Returns an iterator to the first entity that has the given
* components.
*
* The returned iterator points to the first entity that has the given
* components. If the view is empty, the returned iterator will be equal to
* `end()`.
*
* @note
* Input iterators stay true to the order imposed to the underlying data
* structures.
*
* @return An iterator to the first entity that has the given components.
*/
inline const_iterator_type begin() const ENTT_NOEXCEPT {
return cbegin();
}
/**
* @brief Returns an iterator to the first entity that has the given
* components.
*
* The returned iterator points to the first entity that has the given
* components. If the view is empty, the returned iterator will be equal to
* `end()`.
*
* @note
* Input iterators stay true to the order imposed to the underlying data
* structures.
*
* @return An iterator to the first entity that has the given components.
*/
inline iterator_type begin() ENTT_NOEXCEPT {
return cbegin();
}
/**
* @brief Returns an iterator that is past the last entity that has the
* given components.
*
* The returned iterator points to the entity following the last entity that
* has the given components. Attempting to dereference the returned iterator
* results in undefined behavior.
*
* @note
* Input iterators stay true to the order imposed to the underlying data
* structures.
*
* @return An iterator to the entity following the last entity that has the
* given components.
*/
const_iterator_type cend() const ENTT_NOEXCEPT {
const_iterator_type it{};
if(valid()) {
const auto &pool = *pools.front();
it = { pool.cend(), pool.cend(), nullptr, nullptr, min() };
}
return it;
}
/**
* @brief Returns an iterator that is past the last entity that has the
* given components.
*
* The returned iterator points to the entity following the last entity that
* has the given components. Attempting to dereference the returned iterator
* results in undefined behavior.
*
* @note
* Input iterators stay true to the order imposed to the underlying data
* structures.
*
* @return An iterator to the entity following the last entity that has the
* given components.
*/
inline const_iterator_type end() const ENTT_NOEXCEPT {
return cend();
}
/**
* @brief Returns an iterator that is past the last entity that has the
* given components.
*
* The returned iterator points to the entity following the last entity that
* has the given components. Attempting to dereference the returned iterator
* results in undefined behavior.
*
* @note
* Input iterators stay true to the order imposed to the underlying data
* structures.
*
* @return An iterator to the entity following the last entity that has the
* given components.
*/
inline iterator_type end() ENTT_NOEXCEPT {
return cend();
}
/**
* @brief Checks if a view contains an entity.
* @param entity A valid entity identifier.
* @return True if the view contains the given entity, false otherwise.
*/
bool contains(const entity_type entity) const ENTT_NOEXCEPT {
return valid() && std::all_of(pools.cbegin(), pools.cend(), [entity](const auto *view) {
return view->has(entity) && view->data()[view->get(entity)] == entity;
});
}
/**
* @brief Iterates entities and applies the given function object to them.
*
* The function object is invoked for each entity. It is provided only with
* the entity itself. To get the components, users can use the registry with
* which the view was built.<br/>
* The signature of the function should be equivalent to the following:
*
* @code{.cpp}
* void(const entity_type);
* @endcode
*
* @tparam Func Type of the function object to invoke.
* @param func A valid function object.
*/
template<typename Func>
void each(Func func) const {
std::for_each(cbegin(), cend(), func);
}
private:
pattern_type pools;
};
}

View File

@@ -2,7 +2,10 @@
#include "core/family.hpp"
#include "core/hashed_string.hpp"
#include "core/ident.hpp"
#include "core/monostate.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"

View File

@@ -115,7 +115,7 @@ class Process {
}
template<State S, typename... Args>
void tick(char, tag<S>, Args &&...) {}
void tick(char, tag<S>, Args &&...) const ENTT_NOEXCEPT {}
protected:
/**

View File

@@ -5,7 +5,6 @@
#include <vector>
#include <memory>
#include <utility>
#include <iterator>
#include <algorithm>
#include <type_traits>
#include "../config/config.h"
@@ -43,31 +42,27 @@ namespace entt {
*/
template<typename Delta>
class Scheduler final {
template<typename T>
struct type_t { using type = T; };
struct ProcessHandler final {
using instance_type = std::unique_ptr<void, void(*)(void *)>;
using update_type = bool(*)(ProcessHandler &, Delta, void *);
using abort_type = void(*)(ProcessHandler &, bool);
using update_fn_type = bool(ProcessHandler &, Delta, void *);
using abort_fn_type = void(ProcessHandler &, bool);
using next_type = std::unique_ptr<ProcessHandler>;
instance_type instance;
update_type update;
abort_type abort;
update_fn_type *update;
abort_fn_type *abort;
next_type next;
};
template<typename Lambda>
struct Then final: Lambda {
Then(Lambda &&lambda, ProcessHandler *handler)
: Lambda{std::forward<Lambda>(lambda)}, handler{handler}
struct Then final {
Then(ProcessHandler *handler)
: handler{handler}
{}
template<typename Proc, typename... Args>
decltype(auto) then(Args &&... args) && {
static_assert(std::is_base_of<Process<Proc, Delta>, Proc>::value, "!");
handler = Lambda::operator()(handler, type_t<Proc>{}, std::forward<Args>(args)...);
handler = Scheduler::then<Proc>(handler, std::forward<Args>(args)...);
return std::move(*this);
}
@@ -110,20 +105,15 @@ class Scheduler final {
delete static_cast<Proc *>(proc);
}
auto then(ProcessHandler *handler) {
auto lambda = [](ProcessHandler *handler, auto next, auto... args) {
using Proc = typename decltype(next)::type;
template<typename Proc, typename... Args>
static auto then(ProcessHandler *handler, Args &&... args) {
if(handler) {
auto proc = typename ProcessHandler::instance_type{new Proc{std::forward<Args>(args)...}, &Scheduler::deleter<Proc>};
handler->next.reset(new ProcessHandler{std::move(proc), &Scheduler::update<Proc>, &Scheduler::abort<Proc>, nullptr});
handler = handler->next.get();
}
if(handler) {
auto proc = typename ProcessHandler::instance_type{new Proc{std::forward<decltype(args)>(args)...}, &Scheduler::deleter<Proc>};
handler->next.reset(new ProcessHandler{std::move(proc), &Scheduler::update<Proc>, &Scheduler::abort<Proc>, nullptr});
handler = handler->next.get();
}
return handler;
};
return Then<decltype(lambda)>{std::move(lambda), handler};
return handler;
}
public:
@@ -202,7 +192,7 @@ public:
ProcessHandler handler{std::move(proc), &Scheduler::update<Proc>, &Scheduler::abort<Proc>, nullptr};
handlers.push_back(std::move(handler));
return then(&handlers.back());
return Then{&handlers.back()};
}
/**

View File

@@ -85,7 +85,7 @@ public:
inline const Resource & operator *() const ENTT_NOEXCEPT { return get(); }
/**
* @brief Gets a pointer to the managed resource from a handle .
* @brief Gets a pointer to the managed resource from a handle.
*
* @warning
* The behavior is undefined if the handle doesn't contain a resource.<br/>

View File

@@ -34,16 +34,19 @@ class Delegate;
*/
template<typename Ret, typename... Args>
class Delegate<Ret(Args...)> final {
using proto_type = Ret(*)(void *, Args...);
using stub_type = std::pair<void *, proto_type>;
static Ret fallback(void *, Args...) ENTT_NOEXCEPT { return {}; }
using proto_fn_type = Ret(void *, Args...);
using stub_type = std::pair<void *, proto_fn_type *>;
template<Ret(*Function)(Args...)>
static Ret proto(void *, Args... args) {
return (Function)(args...);
}
template<typename Class, Ret(Class:: *Member)(Args...) 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) {
return (static_cast<Class *>(instance)->*Member)(args...);
@@ -52,9 +55,18 @@ class Delegate<Ret(Args...)> final {
public:
/*! @brief Default constructor. */
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.
* @tparam Function A valid free function pointer.
@@ -64,6 +76,22 @@ public:
stub = std::make_pair(nullptr, &proto<Function>);
}
/**
* @brief Connects a member function for a given instance to a delegate.
*
* The delegate isn't responsible for the connected object. Users must
* guarantee that the lifetime of the instance overcomes the one of the
* delegate.
*
* @tparam Class Type of class to which the member function belongs.
* @tparam Member Member function to connect to the delegate.
* @param instance A valid instance of type pointer to `Class`.
*/
template<typename Class, Ret(Class:: *Member)(Args...) const>
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.
*
@@ -86,7 +114,7 @@ public:
* After a reset, a delegate can be safely invoked with no effect.
*/
void reset() ENTT_NOEXCEPT {
stub = std::make_pair(nullptr, &fallback);
stub.second = nullptr;
}
/**
@@ -94,7 +122,7 @@ public:
* @param args Arguments to use to invoke 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...);
}

View File

@@ -167,7 +167,7 @@ public:
* delivered to the registered listeners. It's responsibility of the users
* to reduce at a minimum the time spent in the bodies of the listeners.
*/
inline void update() {
inline void update() const {
for(auto pos = wrappers.size(); pos; --pos) {
auto &wrapper = wrappers[pos-1];

View File

@@ -11,6 +11,7 @@
#include <vector>
#include <list>
#include "../config/config.h"
#include "../core/family.hpp"
namespace entt {
@@ -39,6 +40,8 @@ namespace entt {
*/
template<typename Derived>
class Emitter {
using handler_family = Family<struct InternalEmitterHandlerFamily>;
struct BaseHandler {
virtual ~BaseHandler() = default;
virtual bool empty() const ENTT_NOEXCEPT = 0;
@@ -112,20 +115,9 @@ class Emitter {
container_type onL{};
};
static std::size_t next() ENTT_NOEXCEPT {
static std::size_t counter = 0;
return counter++;
}
template<typename>
static std::size_t type() ENTT_NOEXCEPT {
static std::size_t value = next();
return value;
}
template<typename Event>
Handler<Event> & handler() ENTT_NOEXCEPT {
const std::size_t family = type<Event>();
const std::size_t family = handler_family::type<Event>();
if(!(family < handlers.size())) {
handlers.resize(family+1);
@@ -304,8 +296,9 @@ public:
* results in undefined behavior.
*/
void clear() ENTT_NOEXCEPT {
std::for_each(handlers.begin(), handlers.end(),
[](auto &&handler) { if(handler) { handler->clear(); } });
std::for_each(handlers.begin(), handlers.end(), [](auto &&handler) {
return handler ? handler->clear() : void();
});
}
/**
@@ -315,7 +308,7 @@ public:
*/
template<typename Event>
bool empty() const ENTT_NOEXCEPT {
const std::size_t family = type<Event>();
const std::size_t family = handler_family::type<Event>();
return (!(family < handlers.size()) ||
!handlers[family] ||
@@ -327,8 +320,9 @@ public:
* @return True if there are no listeners registered, false otherwise.
*/
bool empty() const ENTT_NOEXCEPT {
return std::all_of(handlers.cbegin(), handlers.cend(),
[](auto &&handler) { return !handler || handler->empty(); });
return std::all_of(handlers.cbegin(), handlers.cend(), [](auto &&handler) {
return !handler || handler->empty();
});
}
private:

View File

@@ -11,27 +11,37 @@
namespace entt {
namespace internal {
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
namespace internal {
template<typename>
struct sigh_traits;
template<typename Ret, typename... Args>
struct sigh_traits<Ret(Args...)> {
using proto_fn_type = Ret(void *, Args...);
using call_type = std::pair<void *, proto_fn_type *>;
};
template<typename, typename>
struct Invoker;
template<typename Ret, typename... Args, typename Collector>
struct Invoker<Ret(Args...), Collector> {
using proto_type = Ret(*)(void *, Args...);
using call_type = std::pair<void *, proto_type>;
using proto_fn_type = typename sigh_traits<Ret(Args...)>::proto_fn_type;
virtual ~Invoker() = default;
bool invoke(Collector &collector, proto_type proto, void *instance, Args... args) {
bool invoke(Collector &collector, proto_fn_type *proto, void *instance, Args... args) const {
return collector(proto(instance, args...));
}
};
@@ -39,12 +49,11 @@ struct Invoker<Ret(Args...), Collector> {
template<typename... Args, typename Collector>
struct Invoker<void(Args...), Collector> {
using proto_type = void(*)(void *, Args...);
using call_type = std::pair<void *, proto_type>;
using proto_fn_type = typename sigh_traits<void(Args...)>::proto_fn_type;
virtual ~Invoker() = default;
bool invoke(Collector &, proto_type proto, void *instance, Args... args) {
bool invoke(Collector &, proto_fn_type *proto, void *instance, Args... args) const {
return (proto(instance, args...), true);
}
};
@@ -78,15 +87,15 @@ template<typename Function>
using DefaultCollectorType = typename DefaultCollector<Function>::collector_type;
}
/**
* Internal details not to be documented.
* @endcond TURN_OFF_DOXYGEN
*/
}
/**
* @brief Sink implementation.
*
@@ -132,20 +141,24 @@ class Sink<Ret(Args...)> final {
template<typename, typename>
friend class SigH;
using proto_type = Ret(*)(void *, Args...);
using call_type = std::pair<void *, proto_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) 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)
Sink(std::vector<call_type> *calls) ENTT_NOEXCEPT
: calls{calls}
{}
@@ -161,7 +174,7 @@ public:
template<Ret(*Function)(Args...)>
void connect() {
disconnect<Function>();
calls.emplace_back(nullptr, &proto<Function>);
calls->emplace_back(nullptr, &proto<Function>);
}
/**
@@ -177,10 +190,29 @@ public:
* @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>
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>);
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>);
}
/**
@@ -190,7 +222,19 @@ public:
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());
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());
}
/**
@@ -202,7 +246,7 @@ public:
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());
calls->erase(std::remove(calls->begin(), calls->end(), std::move(target)), calls->end());
}
/**
@@ -213,18 +257,18 @@ public:
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());
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();
calls->clear();
}
private:
std::vector<call_type> &calls;
std::vector<call_type> *calls;
};
@@ -253,7 +297,7 @@ private:
*/
template<typename Ret, typename... Args, typename Collector>
class SigH<Ret(Args...), Collector> final: private internal::Invoker<Ret(Args...), Collector> {
using call_type = typename internal::Invoker<Ret(Args...), Collector>::call_type;
using call_type = typename internal::sigh_traits<Ret(Args...)>::call_type;
public:
/*! @brief Unsigned integer type. */
@@ -295,8 +339,8 @@ public:
*
* @return A temporary sink object.
*/
sink_type sink() {
return { calls };
sink_type sink() ENTT_NOEXCEPT {
return { &calls };
}
/**
@@ -306,7 +350,7 @@ public:
*
* @param args Arguments to use to invoke listeners.
*/
void publish(Args... args) {
void publish(Args... args) const {
for(auto pos = calls.size(); pos; --pos) {
auto &call = calls[pos-1];
call.second(call.first, args...);
@@ -318,7 +362,7 @@ public:
* @param args Arguments to use to invoke listeners.
* @return An instance of the collector filled with collected data.
*/
collector_type collect(Args... args) {
collector_type collect(Args... args) const {
collector_type collector;
for(auto &&call: calls) {

View File

@@ -2,80 +2,96 @@
# Tests configuration
#
add_library(odr OBJECT odr.cpp)
include_directories($<TARGET_PROPERTY:EnTT,INTERFACE_INCLUDE_DIRECTORIES>)
add_compile_options($<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_OPTIONS>)
macro(ADD_ENTT_TEST TEST_NAME TEST_SOURCE)
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})
target_link_libraries(${TEST_NAME} PRIVATE gtest_main Threads::Threads)
set_target_properties(${TEST_NAME} PROPERTIES CXX_EXTENSIONS OFF)
target_link_libraries(${TEST_NAME} PRIVATE EnTT GTest::Main Threads::Threads)
target_compile_definitions(${TEST_NAME} PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_DEFINITIONS>)
target_compile_features(${TEST_NAME} PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_FEATURES>)
target_compile_options(${TEST_NAME} PRIVATE $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-pedantic -Wall>)
target_compile_options(${TEST_NAME} PRIVATE $<$<CXX_COMPILER_ID:MSVC>:/EHsc>)
add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME})
endmacro()
# Test benchmark
if(BUILD_BENCHMARK)
ADD_ENTT_TEST(benchmark benchmark/benchmark.cpp)
SETUP_AND_ADD_TEST(benchmark benchmark/benchmark.cpp)
endif()
# Test mod
if(BUILD_MOD)
set(DUKTAPE_DEPS_DIR ${entt_SOURCE_DIR}/deps/duktape)
configure_file(${entt_SOURCE_DIR}/cmake/in/duktape.in ${DUKTAPE_DEPS_DIR}/CMakeLists.txt)
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)
ADD_ENTT_TEST(mod ${MOD_TEST_SOURCE})
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)
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)
ADD_ENTT_TEST(cereal snapshot/snapshot.cpp)
SETUP_AND_ADD_TEST(cereal snapshot/snapshot.cpp)
target_include_directories(cereal PRIVATE ${CEREAL_SRC_DIR})
endif()
# Test core
ADD_ENTT_TEST(algorithm entt/core/algorithm.cpp)
ADD_ENTT_TEST(family entt/core/family.cpp)
ADD_ENTT_TEST(hashed_string entt/core/hashed_string.cpp)
ADD_ENTT_TEST(ident entt/core/ident.cpp)
SETUP_AND_ADD_TEST(algorithm entt/core/algorithm.cpp)
SETUP_AND_ADD_TEST(family entt/core/family.cpp)
SETUP_AND_ADD_TEST(hashed_string entt/core/hashed_string.cpp)
SETUP_AND_ADD_TEST(ident entt/core/ident.cpp)
SETUP_AND_ADD_TEST(monostate entt/core/monostate.cpp)
# Test entity
ADD_ENTT_TEST(actor entt/entity/actor.cpp)
ADD_ENTT_TEST(helper entt/entity/helper.cpp)
ADD_ENTT_TEST(prototype entt/entity/prototype.cpp)
ADD_ENTT_TEST(registry entt/entity/registry.cpp)
ADD_ENTT_TEST(snapshot entt/entity/snapshot.cpp)
ADD_ENTT_TEST(sparse_set entt/entity/sparse_set.cpp)
ADD_ENTT_TEST(view entt/entity/view.cpp)
SETUP_AND_ADD_TEST(actor entt/entity/actor.cpp)
SETUP_AND_ADD_TEST(attachee entt/entity/attachee.cpp)
SETUP_AND_ADD_TEST(entity entt/entity/entity.cpp)
SETUP_AND_ADD_TEST(helper entt/entity/helper.cpp)
SETUP_AND_ADD_TEST(prototype entt/entity/prototype.cpp)
SETUP_AND_ADD_TEST(registry entt/entity/registry.cpp)
SETUP_AND_ADD_TEST(snapshot entt/entity/snapshot.cpp)
SETUP_AND_ADD_TEST(sparse_set entt/entity/sparse_set.cpp)
SETUP_AND_ADD_TEST(view entt/entity/view.cpp)
# Test locator
ADD_ENTT_TEST(locator entt/locator/locator.cpp)
SETUP_AND_ADD_TEST(locator entt/locator/locator.cpp)
# Test process
ADD_ENTT_TEST(process entt/process/process.cpp)
ADD_ENTT_TEST(scheduler entt/process/scheduler.cpp)
SETUP_AND_ADD_TEST(process entt/process/process.cpp)
SETUP_AND_ADD_TEST(scheduler entt/process/scheduler.cpp)
# Test resource
ADD_ENTT_TEST(resource entt/resource/resource.cpp)
SETUP_AND_ADD_TEST(resource entt/resource/resource.cpp)
# Test signal
ADD_ENTT_TEST(delegate entt/signal/delegate.cpp)
ADD_ENTT_TEST(dispatcher entt/signal/dispatcher.cpp)
ADD_ENTT_TEST(emitter entt/signal/emitter.cpp)
ADD_ENTT_TEST(sigh entt/signal/sigh.cpp)
SETUP_AND_ADD_TEST(delegate entt/signal/delegate.cpp)
SETUP_AND_ADD_TEST(dispatcher entt/signal/dispatcher.cpp)
SETUP_AND_ADD_TEST(emitter entt/signal/emitter.cpp)
SETUP_AND_ADD_TEST(sigh entt/signal/sigh.cpp)

View File

@@ -2,6 +2,7 @@
#include <cstddef>
#include <cstdint>
#include <chrono>
#include <iterator>
#include <gtest/gtest.h>
#include <entt/entity/registry.hpp>
@@ -135,6 +136,31 @@ TEST(Benchmark, IterateSingleComponentRaw1M) {
});
}
TEST(Benchmark, IterateSingleComponentRuntime1M) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 1000000 entities, one component, runtime view" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create();
registry.assign<Position>(entity);
}
auto test = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = { registry.type<Position>() };
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
});
}
TEST(Benchmark, IterateTwoComponents1M) {
entt::DefaultRegistry registry;
@@ -242,6 +268,94 @@ TEST(Benchmark, IterateTwoComponentsPersistent1M) {
});
}
TEST(Benchmark, IterateTwoComponentsRuntime1M) {
entt::DefaultRegistry registry;
registry.prepare<Position, Velocity>();
std::cout << "Iterating over 1000000 entities, two components, runtime view" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create();
registry.assign<Position>(entity);
registry.assign<Velocity>(entity);
}
auto test = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = { registry.type<Position>(), registry.type<Velocity>() };
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
registry.get<Velocity>(entity).x = {};
});
}
TEST(Benchmark, IterateTwoComponentsRuntime1MHalf) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 1000000 entities, two components, half of the entities have all the components, runtime view" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create();
registry.assign<Velocity>(entity);
if(i % 2) {
registry.assign<Position>(entity);
}
}
auto test = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = { registry.type<Position>(), registry.type<Velocity>() };
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
registry.get<Velocity>(entity).x = {};
});
}
TEST(Benchmark, IterateTwoComponentsRuntime1MOne) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 1000000 entities, two components, only one entity has all the components, runtime view" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create();
registry.assign<Velocity>(entity);
if(i == 5000000L) {
registry.assign<Position>(entity);
}
}
auto test = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = { registry.type<Position>(), registry.type<Velocity>() };
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
registry.get<Velocity>(entity).x = {};
});
}
TEST(Benchmark, IterateFiveComponents1M) {
entt::DefaultRegistry registry;
@@ -361,6 +475,130 @@ TEST(Benchmark, IterateFiveComponentsPersistent1M) {
});
}
TEST(Benchmark, IterateFiveComponentsRuntime1M) {
entt::DefaultRegistry registry;
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
std::cout << "Iterating over 1000000 entities, five components, runtime view" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create();
registry.assign<Position>(entity);
registry.assign<Velocity>(entity);
registry.assign<Comp<1>>(entity);
registry.assign<Comp<2>>(entity);
registry.assign<Comp<3>>(entity);
}
auto test = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = {
registry.type<Position>(),
registry.type<Velocity>(),
registry.type<Comp<1>>(),
registry.type<Comp<2>>(),
registry.type<Comp<3>>()
};
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
registry.get<Velocity>(entity).x = {};
registry.get<Comp<1>>(entity).x = {};
registry.get<Comp<2>>(entity).x = {};
registry.get<Comp<3>>(entity).x = {};
});
}
TEST(Benchmark, IterateFiveComponentsRuntime1MHalf) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 1000000 entities, five components, half of the entities have all the components, runtime view" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create();
registry.assign<Velocity>(entity);
registry.assign<Comp<1>>(entity);
registry.assign<Comp<2>>(entity);
registry.assign<Comp<3>>(entity);
if(i % 2) {
registry.assign<Position>(entity);
}
}
auto test = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = {
registry.type<Position>(),
registry.type<Velocity>(),
registry.type<Comp<1>>(),
registry.type<Comp<2>>(),
registry.type<Comp<3>>()
};
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
registry.get<Velocity>(entity).x = {};
registry.get<Comp<1>>(entity).x = {};
registry.get<Comp<2>>(entity).x = {};
registry.get<Comp<3>>(entity).x = {};
});
}
TEST(Benchmark, IterateFiveComponentsRuntime1MOne) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 1000000 entities, five components, only one entity has all the components, runtime view" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create();
registry.assign<Velocity>(entity);
registry.assign<Comp<1>>(entity);
registry.assign<Comp<2>>(entity);
registry.assign<Comp<3>>(entity);
if(i == 5000000L) {
registry.assign<Position>(entity);
}
}
auto test = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = {
registry.type<Position>(),
registry.type<Velocity>(),
registry.type<Comp<1>>(),
registry.type<Comp<2>>(),
registry.type<Comp<3>>()
};
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
registry.get<Velocity>(entity).x = {};
registry.get<Comp<1>>(entity).x = {};
registry.get<Comp<2>>(entity).x = {};
registry.get<Comp<3>>(entity).x = {};
});
}
TEST(Benchmark, IterateTenComponents1M) {
entt::DefaultRegistry registry;
@@ -500,6 +738,175 @@ TEST(Benchmark, IterateTenComponentsPersistent1M) {
});
}
TEST(Benchmark, IterateTenComponentsRuntime1M) {
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 1000000 entities, ten components, runtime view" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create();
registry.assign<Position>(entity);
registry.assign<Velocity>(entity);
registry.assign<Comp<1>>(entity);
registry.assign<Comp<2>>(entity);
registry.assign<Comp<3>>(entity);
registry.assign<Comp<4>>(entity);
registry.assign<Comp<5>>(entity);
registry.assign<Comp<6>>(entity);
registry.assign<Comp<7>>(entity);
registry.assign<Comp<8>>(entity);
}
auto test = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = {
registry.type<Position>(),
registry.type<Velocity>(),
registry.type<Comp<1>>(),
registry.type<Comp<2>>(),
registry.type<Comp<3>>(),
registry.type<Comp<4>>(),
registry.type<Comp<5>>(),
registry.type<Comp<6>>(),
registry.type<Comp<7>>(),
registry.type<Comp<8>>()
};
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
registry.get<Velocity>(entity).x = {};
registry.get<Comp<1>>(entity).x = {};
registry.get<Comp<2>>(entity).x = {};
registry.get<Comp<3>>(entity).x = {};
registry.get<Comp<4>>(entity).x = {};
registry.get<Comp<5>>(entity).x = {};
registry.get<Comp<6>>(entity).x = {};
registry.get<Comp<7>>(entity).x = {};
registry.get<Comp<8>>(entity).x = {};
});
}
TEST(Benchmark, IterateTenComponentsRuntime1MHalf) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 1000000 entities, ten components, half of the entities have all the components, runtime view" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create();
registry.assign<Velocity>(entity);
registry.assign<Comp<1>>(entity);
registry.assign<Comp<2>>(entity);
registry.assign<Comp<3>>(entity);
registry.assign<Comp<4>>(entity);
registry.assign<Comp<5>>(entity);
registry.assign<Comp<6>>(entity);
registry.assign<Comp<7>>(entity);
registry.assign<Comp<8>>(entity);
if(i % 2) {
registry.assign<Position>(entity);
}
}
auto test = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = {
registry.type<Position>(),
registry.type<Velocity>(),
registry.type<Comp<1>>(),
registry.type<Comp<2>>(),
registry.type<Comp<3>>(),
registry.type<Comp<4>>(),
registry.type<Comp<5>>(),
registry.type<Comp<6>>(),
registry.type<Comp<7>>(),
registry.type<Comp<8>>()
};
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
registry.get<Velocity>(entity).x = {};
registry.get<Comp<1>>(entity).x = {};
registry.get<Comp<2>>(entity).x = {};
registry.get<Comp<3>>(entity).x = {};
registry.get<Comp<4>>(entity).x = {};
registry.get<Comp<5>>(entity).x = {};
registry.get<Comp<6>>(entity).x = {};
registry.get<Comp<7>>(entity).x = {};
registry.get<Comp<8>>(entity).x = {};
});
}
TEST(Benchmark, IterateTenComponentsRuntime1MOne) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 1000000 entities, ten components, only one entity has all the components, runtime view" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create();
registry.assign<Velocity>(entity);
registry.assign<Comp<1>>(entity);
registry.assign<Comp<2>>(entity);
registry.assign<Comp<3>>(entity);
registry.assign<Comp<4>>(entity);
registry.assign<Comp<5>>(entity);
registry.assign<Comp<6>>(entity);
registry.assign<Comp<7>>(entity);
registry.assign<Comp<8>>(entity);
if(i == 5000000L) {
registry.assign<Position>(entity);
}
}
auto test = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = {
registry.type<Position>(),
registry.type<Velocity>(),
registry.type<Comp<1>>(),
registry.type<Comp<2>>(),
registry.type<Comp<3>>(),
registry.type<Comp<4>>(),
registry.type<Comp<5>>(),
registry.type<Comp<6>>(),
registry.type<Comp<7>>(),
registry.type<Comp<8>>()
};
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
registry.get<Velocity>(entity).x = {};
registry.get<Comp<1>>(entity).x = {};
registry.get<Comp<2>>(entity).x = {};
registry.get<Comp<3>>(entity).x = {};
registry.get<Comp<4>>(entity).x = {};
registry.get<Comp<5>>(entity).x = {};
registry.get<Comp<6>>(entity).x = {};
registry.get<Comp<7>>(entity).x = {};
registry.get<Comp<8>>(entity).x = {};
});
}
TEST(Benchmark, SortSingle) {
entt::DefaultRegistry registry;

View File

@@ -9,7 +9,7 @@ TEST(Algorithm, StdSort) {
sort(arr.begin(), arr.end());
for(auto i = 0; i < 4; ++i) {
for(typename decltype(arr)::size_type i = 0; i < (arr.size() - 1); ++i) {
ASSERT_LT(arr[i], arr[i+1]);
}
}
@@ -20,7 +20,21 @@ TEST(Algorithm, InsertionSort) {
sort(arr.begin(), arr.end());
for(auto i = 0; i < 4; ++i) {
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]);
}
}

View File

@@ -1,33 +1,7 @@
#include <cstddef>
#include <type_traits>
#include <gtest/gtest.h>
#include <entt/core/hashed_string.hpp>
static constexpr bool ptr(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}));
}
template<std::size_t N>
static constexpr bool ref(const char (&str)[N]) {
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 constexpr otherwise?
static_assert(ptr("foo"), "!");
static_assert(ref("bar"), "!");
ASSERT_TRUE(true);
}
TEST(HashedString, Functionalities) {
using hash_type = entt::HashedString::hash_type;
@@ -37,13 +11,24 @@ TEST(HashedString, Functionalities) {
auto barHs = entt::HashedString{bar};
ASSERT_NE(static_cast<hash_type>(fooHs), static_cast<hash_type>(barHs));
ASSERT_EQ(static_cast<const char *>(fooHs), "foo");
ASSERT_EQ(static_cast<const char *>(barHs), bar);
ASSERT_STREQ(static_cast<const char *>(fooHs), "foo");
ASSERT_STREQ(static_cast<const char *>(barHs), bar);
ASSERT_TRUE(fooHs == fooHs);
ASSERT_TRUE(fooHs != barHs);
ASSERT_EQ(fooHs, fooHs);
ASSERT_NE(fooHs, barHs);
entt::HashedString hs{"foobar"};
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);
}

View File

@@ -6,28 +6,27 @@ struct AType {};
struct AnotherType {};
TEST(Identifier, Uniqueness) {
constexpr auto ID = entt::ident<AType, AnotherType>;
using ID = entt::Identifier<AType, AnotherType>;
constexpr AType anInstance;
constexpr AnotherType anotherInstance;
ASSERT_NE(ID.get<AType>(), ID.get<AnotherType>());
ASSERT_EQ(ID.get<AType>(), ID.get<decltype(anInstance)>());
ASSERT_NE(ID.get<AType>(), ID.get<decltype(anotherInstance)>());
ASSERT_EQ(ID.get<AType>(), ID.get<AType>());
ASSERT_EQ(ID.get<AnotherType>(), ID.get<AnotherType>());
ASSERT_NE(ID::get<AType>(), ID::get<AnotherType>());
ASSERT_EQ(ID::get<AType>(), ID::get<decltype(anInstance)>());
ASSERT_NE(ID::get<AType>(), ID::get<decltype(anotherInstance)>());
ASSERT_EQ(ID::get<AType>(), ID::get<AType>());
ASSERT_EQ(ID::get<AnotherType>(), ID::get<AnotherType>());
// test uses in constant expressions
switch(ID.get<AnotherType>()) {
case ID.get<AType>():
switch(ID::get<AnotherType>()) {
case ID::get<AType>():
FAIL();
break;
case ID.get<AnotherType>():
case ID::get<AnotherType>():
SUCCEED();
}
}
TEST(Identifier, SingleType) {
constexpr auto ID = entt::ident<AType>;
std::integral_constant<decltype(ID)::identifier_type, ID.get()> ic;
using ID = entt::Identifier<AType>;
std::integral_constant<ID::identifier_type, ID::get<AType>()> ic;
(void)ic;
}

View 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);
}

View 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;
}

View 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));
}

View File

@@ -1,11 +1,12 @@
#include <gtest/gtest.h>
#include <entt/core/hashed_string.hpp>
#include <entt/entity/helper.hpp>
#include <entt/entity/registry.hpp>
TEST(Dependency, Functionalities) {
TEST(Helper, Dependency) {
entt::DefaultRegistry registry;
const auto entity = registry.create();
entt::dependency<double, float>(registry.construction<int>());
entt::connect<double, float>(registry.construction<int>());
ASSERT_FALSE(registry.has<double>(entity));
ASSERT_FALSE(registry.has<float>(entity));
@@ -41,9 +42,34 @@ TEST(Dependency, Functionalities) {
registry.remove<int>(entity);
registry.remove<double>(entity);
registry.remove<float>(entity);
entt::dependency<double, float>(entt::break_t{}, registry.construction<int>());
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);
}

View File

@@ -2,13 +2,13 @@
#include <entt/entity/prototype.hpp>
#include <entt/entity/registry.hpp>
TEST(Prototype, Functionalities) {
TEST(Prototype, SameRegistry) {
entt::DefaultRegistry registry;
entt::DefaultPrototype prototype;
entt::DefaultPrototype prototype{registry};
const auto &cprototype = prototype;
ASSERT_FALSE(registry.empty());
ASSERT_FALSE((prototype.has<int, char>()));
ASSERT_TRUE(registry.empty());
ASSERT_EQ(prototype.set<int>(2), 2);
ASSERT_EQ(prototype.set<int>(3), 3);
@@ -19,11 +19,70 @@ TEST(Prototype, Functionalities) {
ASSERT_EQ(std::get<0>(prototype.get<int, char>()), 3);
ASSERT_EQ(std::get<1>(cprototype.get<int, char>()), 'c');
const auto e0 = prototype(registry);
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));
ASSERT_FALSE(registry.empty());
const auto e1 = prototype(registry);
prototype(registry, e0);
@@ -61,3 +120,34 @@ TEST(Prototype, Functionalities) {
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));
}

View File

@@ -59,12 +59,15 @@ TEST(DefaultRegistry, Functionalities) {
entt::DefaultRegistry registry;
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_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<char>(), entt::DefaultRegistry::size_type{0});
ASSERT_TRUE(registry.empty<int>());
@@ -79,7 +82,6 @@ TEST(DefaultRegistry, Functionalities) {
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<char>(), entt::DefaultRegistry::size_type{1});
ASSERT_FALSE(registry.empty<int>());
@@ -133,13 +135,12 @@ TEST(DefaultRegistry, Functionalities) {
ASSERT_EQ(static_cast<const entt::DefaultRegistry &>(registry).get<int>(e1), 1);
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{3});
ASSERT_EQ(registry.alive(), entt::DefaultRegistry::size_type{3});
ASSERT_FALSE(registry.empty());
ASSERT_EQ(registry.version(e2), 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_EQ(registry.capacity(), entt::DefaultRegistry::size_type{3});
ASSERT_EQ(registry.version(e2), entt::DefaultRegistry::version_type{0});
ASSERT_EQ(registry.current(e2), entt::DefaultRegistry::version_type{1});
@@ -150,12 +151,14 @@ TEST(DefaultRegistry, Functionalities) {
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_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());
const auto e3 = registry.create();
@@ -195,6 +198,22 @@ TEST(DefaultRegistry, Functionalities) {
ASSERT_TRUE(registry.empty<int>());
}
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;
@@ -354,28 +373,13 @@ TEST(DefaultRegistry, CreateDestroyEntities) {
ASSERT_EQ(registry.current(pre), registry.current(post));
}
TEST(DefaultRegistry, CloneEntities) {
entt::DefaultRegistry registry;
const auto entity = registry.create();
registry.assign<int>(entity, 42);
registry.assign<char>(entity, 'c');
const auto other = registry.clone(entity);
ASSERT_TRUE(registry.has<int>(other));
ASSERT_TRUE(registry.has<char>(other));
ASSERT_EQ(registry.get<int>(entity), registry.get<int>(other));
ASSERT_EQ(registry.get<char>(entity), registry.get<char>(other));
ASSERT_EQ(registry.get<int>(other), 42);
ASSERT_EQ(registry.get<char>(other), 'c');
}
TEST(DefaultRegistry, AttachSetRemoveTags) {
entt::DefaultRegistry registry;
const auto &cregistry = registry;
const typename decltype(registry)::entity_type null = entt::null;
ASSERT_FALSE(registry.has<int>());
ASSERT_EQ(registry.attachee<int>(), null);
const auto entity = registry.create();
registry.assign<int>(entt::tag_t{}, entity, 42);
@@ -409,6 +413,7 @@ TEST(DefaultRegistry, AttachSetRemoveTags) {
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.assign<int>(entt::tag_t{}, entity, 42);
registry.destroy(entity);
@@ -719,11 +724,6 @@ TEST(DefaultRegistry, ComponentSignals) {
ASSERT_EQ(listener.counter, 1);
ASSERT_EQ(listener.last, e1);
e1 = registry.clone(e0);
ASSERT_EQ(listener.counter, 2);
ASSERT_EQ(listener.last, e1);
}
TEST(DefaultRegistry, TagSignals) {
@@ -763,3 +763,65 @@ TEST(DefaultRegistry, TagSignals) {
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()));
}

View File

@@ -3,10 +3,12 @@
#include <entt/entity/sparse_set.hpp>
TEST(SparseSetNoType, Functionalities) {
entt::SparseSet<unsigned int> set;
entt::SparseSet<std::uint64_t> set;
const auto &cset = set;
ASSERT_NO_THROW(set.reserve(42));
set.reserve(42);
ASSERT_EQ(set.capacity(), 42);
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u);
ASSERT_EQ(cset.begin(), cset.end());
@@ -49,30 +51,116 @@ TEST(SparseSetNoType, Functionalities) {
ASSERT_FALSE(set.has(0));
ASSERT_FALSE(set.has(42));
(void)entt::SparseSet<unsigned int>{std::move(set)};
entt::SparseSet<unsigned int> other;
(void)entt::SparseSet<std::uint64_t>{std::move(set)};
entt::SparseSet<std::uint64_t> other;
other = std::move(set);
}
TEST(SparseSetNoType, Clone) {
entt::SparseSet<unsigned int> set;
TEST(SparseSetNoType, ElementAccess) {
entt::SparseSet<std::uint64_t> set;
const auto &cset = set;
ASSERT_FALSE(set.has(0));
ASSERT_FALSE(set.has(42));
set.construct(42);
set.construct(3);
set.construct(0);
ASSERT_TRUE(set.has(0));
ASSERT_FALSE(set.has(42));
set.clone(42, 0);
ASSERT_TRUE(set.has(0));
ASSERT_TRUE(set.has(42));
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, DataBeginEnd) {
entt::SparseSet<unsigned int> set;
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(12);
@@ -85,36 +173,11 @@ TEST(SparseSetNoType, DataBeginEnd) {
ASSERT_EQ(*(set.data() + 0u), 3u);
ASSERT_EQ(*(set.data() + 1u), 12u);
ASSERT_EQ(*(set.data() + 2u), 42u);
auto it = set.begin();
ASSERT_EQ(*it, 42u);
ASSERT_EQ(*(it+1), 12u);
ASSERT_EQ(*(it+2), 3u);
ASSERT_EQ(it += 3, set.end());
auto begin = set.begin();
auto end = set.end();
ASSERT_EQ(*(begin++), 42u);
ASSERT_EQ(*(begin++), 12u);
ASSERT_EQ(*(begin++), 3u);
ASSERT_EQ(begin, end);
auto cbegin = set.cbegin();
auto cend = set.cend();
ASSERT_NE(cbegin, cend);
ASSERT_EQ(cbegin+3, cend);
ASSERT_NE(cbegin, cend);
ASSERT_EQ(cbegin += 3, cend);
ASSERT_EQ(cbegin, cend);
}
TEST(SparseSetNoType, RespectDisjoint) {
entt::SparseSet<unsigned int> lhs;
entt::SparseSet<unsigned int> rhs;
entt::SparseSet<std::uint64_t> lhs;
entt::SparseSet<std::uint64_t> rhs;
const auto &clhs = lhs;
lhs.construct(3);
@@ -133,8 +196,8 @@ TEST(SparseSetNoType, RespectDisjoint) {
}
TEST(SparseSetNoType, RespectOverlap) {
entt::SparseSet<unsigned int> lhs;
entt::SparseSet<unsigned int> rhs;
entt::SparseSet<std::uint64_t> lhs;
entt::SparseSet<std::uint64_t> rhs;
const auto &clhs = lhs;
lhs.construct(3);
@@ -155,8 +218,8 @@ TEST(SparseSetNoType, RespectOverlap) {
}
TEST(SparseSetNoType, RespectOrdered) {
entt::SparseSet<unsigned int> lhs;
entt::SparseSet<unsigned int> rhs;
entt::SparseSet<std::uint64_t> lhs;
entt::SparseSet<std::uint64_t> rhs;
lhs.construct(1);
lhs.construct(2);
@@ -195,8 +258,8 @@ TEST(SparseSetNoType, RespectOrdered) {
}
TEST(SparseSetNoType, RespectReverse) {
entt::SparseSet<unsigned int> lhs;
entt::SparseSet<unsigned int> rhs;
entt::SparseSet<std::uint64_t> lhs;
entt::SparseSet<std::uint64_t> rhs;
lhs.construct(1);
lhs.construct(2);
@@ -235,8 +298,8 @@ TEST(SparseSetNoType, RespectReverse) {
}
TEST(SparseSetNoType, RespectUnordered) {
entt::SparseSet<unsigned int> lhs;
entt::SparseSet<unsigned int> rhs;
entt::SparseSet<std::uint64_t> lhs;
entt::SparseSet<std::uint64_t> rhs;
lhs.construct(1);
lhs.construct(2);
@@ -274,11 +337,29 @@ TEST(SparseSetNoType, RespectUnordered) {
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) {
entt::SparseSet<unsigned int, int> set;
entt::SparseSet<std::uint64_t, int> set;
const auto &cset = set;
ASSERT_NO_THROW(set.reserve(42));
set.reserve(42);
ASSERT_EQ(set.capacity(), 42);
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u);
ASSERT_EQ(cset.begin(), cset.end());
@@ -319,46 +400,127 @@ TEST(SparseSetWithType, Functionalities) {
ASSERT_FALSE(set.has(0));
ASSERT_FALSE(set.has(42));
(void)entt::SparseSet<unsigned int>{std::move(set)};
entt::SparseSet<unsigned int> other;
(void)entt::SparseSet<std::uint64_t, int>{std::move(set)};
entt::SparseSet<std::uint64_t, int> other;
other = std::move(set);
}
TEST(SparseSetWithType, Clone) {
entt::SparseSet<unsigned int, int> set;
TEST(SparseSetWithType, ElementAccess) {
entt::SparseSet<std::uint64_t, int> set;
const auto &cset = set;
ASSERT_FALSE(set.has(0));
ASSERT_FALSE(set.has(42));
set.construct(42, 1);
set.construct(3, 0);
set.construct(0, 3);
ASSERT_TRUE(set.has(0));
ASSERT_FALSE(set.has(42));
ASSERT_EQ(set.get(0), 3);
set.clone(42, 0);
ASSERT_TRUE(set.has(0));
ASSERT_TRUE(set.has(42));
ASSERT_EQ(set.get(0), set.get(42));
ASSERT_EQ(set.get(42), 3);
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<unsigned int, AggregateType>{}.construct(0, 42);
entt::SparseSet<std::uint64_t, AggregateType>{}.construct(0, 42);
}
TEST(SparseSetWithType, TypesFromStandardTemplateLibraryMustWork) {
// see #37 - this test shouldn't crash, that's all
entt::SparseSet<unsigned int, std::unordered_set<int>> set;
entt::SparseSet<std::uint64_t, std::unordered_set<int>> set;
set.construct(0).insert(42);
set.destroy(0);
}
TEST(SparseSetWithType, RawBeginEnd) {
entt::SparseSet<unsigned int, int> set;
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(12, 6);
@@ -371,27 +533,10 @@ TEST(SparseSetWithType, RawBeginEnd) {
ASSERT_EQ(*(set.raw() + 0u), 3);
ASSERT_EQ(*(set.raw() + 1u), 6);
ASSERT_EQ(*(set.raw() + 2u), 9);
auto begin = set.begin();
auto end = set.end();
ASSERT_EQ(*(begin++), 9);
ASSERT_EQ(*(begin++), 6);
ASSERT_EQ(*(begin++), 3);
ASSERT_EQ(begin, end);
auto cbegin = set.cbegin();
auto cend = set.cend();
ASSERT_NE(cbegin, cend);
ASSERT_EQ(cbegin+3, cend);
ASSERT_NE(cbegin, cend);
ASSERT_EQ(cbegin += 3, cend);
ASSERT_EQ(cbegin, cend);
}
TEST(SparseSetWithType, SortOrdered) {
entt::SparseSet<unsigned int, int> set;
entt::SparseSet<std::uint64_t, int> set;
set.construct(12, 12);
set.construct(42, 9);
@@ -427,7 +572,7 @@ TEST(SparseSetWithType, SortOrdered) {
}
TEST(SparseSetWithType, SortReverse) {
entt::SparseSet<unsigned int, int> set;
entt::SparseSet<std::uint64_t, int> set;
set.construct(12, 1);
set.construct(42, 3);
@@ -463,7 +608,7 @@ TEST(SparseSetWithType, SortReverse) {
}
TEST(SparseSetWithType, SortUnordered) {
entt::SparseSet<unsigned int, int> set;
entt::SparseSet<std::uint64_t, int> set;
set.construct(12, 6);
set.construct(42, 3);
@@ -499,8 +644,8 @@ TEST(SparseSetWithType, SortUnordered) {
}
TEST(SparseSetWithType, RespectDisjoint) {
entt::SparseSet<unsigned int, int> lhs;
entt::SparseSet<unsigned int, int> rhs;
entt::SparseSet<std::uint64_t, int> lhs;
entt::SparseSet<std::uint64_t, int> rhs;
const auto &clhs = lhs;
lhs.construct(3, 3);
@@ -527,8 +672,8 @@ TEST(SparseSetWithType, RespectDisjoint) {
}
TEST(SparseSetWithType, RespectOverlap) {
entt::SparseSet<unsigned int, int> lhs;
entt::SparseSet<unsigned int, int> rhs;
entt::SparseSet<std::uint64_t, int> lhs;
entt::SparseSet<std::uint64_t, int> rhs;
const auto &clhs = lhs;
lhs.construct(3, 3);
@@ -557,8 +702,8 @@ TEST(SparseSetWithType, RespectOverlap) {
}
TEST(SparseSetWithType, RespectOrdered) {
entt::SparseSet<unsigned int, int> lhs;
entt::SparseSet<unsigned int, int> rhs;
entt::SparseSet<std::uint64_t, int> lhs;
entt::SparseSet<std::uint64_t, int> rhs;
lhs.construct(1, 0);
lhs.construct(2, 0);
@@ -603,8 +748,8 @@ TEST(SparseSetWithType, RespectOrdered) {
}
TEST(SparseSetWithType, RespectReverse) {
entt::SparseSet<unsigned int, int> lhs;
entt::SparseSet<unsigned int, int> rhs;
entt::SparseSet<std::uint64_t, int> lhs;
entt::SparseSet<std::uint64_t, int> rhs;
lhs.construct(1, 0);
lhs.construct(2, 0);
@@ -649,8 +794,8 @@ TEST(SparseSetWithType, RespectReverse) {
}
TEST(SparseSetWithType, RespectUnordered) {
entt::SparseSet<unsigned int, int> lhs;
entt::SparseSet<unsigned int, int> rhs;
entt::SparseSet<std::uint64_t, int> lhs;
entt::SparseSet<std::uint64_t, int> rhs;
lhs.construct(1, 0);
lhs.construct(2, 0);
@@ -694,10 +839,26 @@ TEST(SparseSetWithType, RespectUnordered) {
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<unsigned int, InternalType> set;
entt::SparseSet<std::uint64_t, InternalType> set;
set.construct(0, 0);
set.construct(1, 1);
@@ -723,3 +884,18 @@ TEST(SparseSetWithType, ReferencesGuaranteed) {
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;
}

View File

@@ -1,293 +1,14 @@
#include <utility>
#include <iterator>
#include <gtest/gtest.h>
#include <entt/entity/registry.hpp>
#include <entt/entity/view.hpp>
TEST(View, SingleComponent) {
entt::DefaultRegistry registry;
auto view = registry.view<char>();
const auto e0 = registry.create();
const auto e1 = registry.create();
ASSERT_TRUE(view.empty());
registry.assign<int>(e1);
registry.assign<char>(e1);
ASSERT_NO_THROW(registry.view<char>().begin()++);
ASSERT_NO_THROW(++registry.view<char>().begin());
ASSERT_NE(view.begin(), view.end());
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
ASSERT_FALSE(view.empty());
registry.assign<char>(e0);
ASSERT_EQ(view.size(), typename decltype(view)::size_type{2});
view.get(e0) = '1';
view.get(e1) = '2';
for(auto entity: view) {
const auto &cview = static_cast<const decltype(view) &>(view);
ASSERT_TRUE(cview.get(entity) == '1' || cview.get(entity) == '2');
}
ASSERT_EQ(*(view.data() + 0), e1);
ASSERT_EQ(*(view.data() + 1), e0);
ASSERT_EQ(*(view.raw() + 0), '2');
ASSERT_EQ(*(static_cast<const decltype(view) &>(view).raw() + 1), '1');
registry.remove<char>(e0);
registry.remove<char>(e1);
ASSERT_EQ(view.begin(), view.end());
ASSERT_TRUE(view.empty());
}
TEST(View, SingleComponentBeginEnd) {
entt::DefaultRegistry registry;
auto view = registry.view<int>();
const auto &cview = view;
for(auto i = 0; i < 3; ++i) {
registry.assign<int>(registry.create());
}
auto test = [](auto begin, auto end) {
ASSERT_NE(begin, end);
ASSERT_NE(++begin, end);
ASSERT_NE(begin++, end);
ASSERT_EQ(begin+1, end);
ASSERT_NE(begin, end);
ASSERT_EQ((begin += 1), end);
ASSERT_EQ(begin, end);
};
test(view.begin(), view.end());
test(cview.begin(), cview.end());
test(view.cbegin(), view.cend());
}
TEST(View, SingleComponentContains) {
entt::DefaultRegistry registry;
const auto e0 = registry.create();
registry.assign<int>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
registry.destroy(e0);
auto view = registry.view<int>();
ASSERT_FALSE(view.contains(e0));
ASSERT_TRUE(view.contains(e1));
}
TEST(View, SingleComponentEmpty) {
entt::DefaultRegistry registry;
const auto e0 = registry.create();
registry.assign<char>(e0);
registry.assign<double>(e0);
const auto e1 = registry.create();
registry.assign<char>(e1);
auto view = registry.view<int>();
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{0});
for(auto entity: view) {
(void)entity;
FAIL();
}
}
TEST(View, SingleComponentEach) {
entt::DefaultRegistry registry;
registry.assign<int>(registry.create());
registry.assign<int>(registry.create());
auto view = registry.view<int>();
const auto &cview = static_cast<const decltype(view) &>(view);
std::size_t cnt = 0;
view.each([&cnt](auto, int &) { ++cnt; });
ASSERT_EQ(cnt, std::size_t{2});
cview.each([&cnt](auto, const int &) { --cnt; });
ASSERT_EQ(cnt, std::size_t{0});
}
TEST(View, MultipleComponent) {
entt::DefaultRegistry registry;
auto view = registry.view<int, char>();
ASSERT_TRUE(view.empty());
const auto e0 = registry.create();
registry.assign<char>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
ASSERT_FALSE(view.empty());
registry.assign<char>(e1);
auto it = registry.view<char>().begin();
ASSERT_EQ(*it, e1);
ASSERT_EQ(*(it+1), e0);
ASSERT_EQ(it += 2, registry.view<char>().end());
ASSERT_NO_THROW((registry.view<int, char>().begin()++));
ASSERT_NO_THROW((++registry.view<int, char>().begin()));
ASSERT_NE(view.begin(), view.end());
ASSERT_EQ(view.begin()+1, view.end());
ASSERT_EQ(view.size(), decltype(view.size()){1});
registry.get<char>(e0) = '1';
registry.get<char>(e1) = '2';
registry.get<int>(e1) = 42;
for(auto entity: view) {
const auto &cview = static_cast<const decltype(view) &>(view);
ASSERT_EQ(std::get<0>(cview.get<int, char>(entity)), 42);
ASSERT_EQ(std::get<1>(view.get<int, char>(entity)), '2');
ASSERT_EQ(cview.get<char>(entity), '2');
}
registry.remove<char>(e0);
registry.remove<char>(e1);
}
TEST(View, MultipleComponentBeginEnd) {
entt::DefaultRegistry registry;
auto view = registry.view<int, char>();
const auto &cview = view;
for(auto i = 0; i < 3; ++i) {
const auto entity = registry.create();
registry.assign<int>(entity);
registry.assign<char>(entity);
}
auto test = [](auto begin, auto end) {
ASSERT_NE(begin, end);
ASSERT_NE(++begin, end);
ASSERT_NE(begin++, end);
ASSERT_EQ(begin+1, end);
ASSERT_NE(begin, end);
ASSERT_EQ((begin += 1), end);
ASSERT_EQ(begin, end);
};
test(cview.begin(), cview.end());
test(view.begin(), view.end());
test(view.cbegin(), view.cend());
}
TEST(View, MultipleComponentContains) {
entt::DefaultRegistry registry;
const auto e0 = registry.create();
registry.assign<int>(e0);
registry.assign<char>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
registry.assign<char>(e1);
registry.destroy(e0);
auto view = registry.view<int, char>();
ASSERT_FALSE(view.contains(e0));
ASSERT_TRUE(view.contains(e1));
}
TEST(View, MultipleComponentEmpty) {
entt::DefaultRegistry registry;
const auto e0 = registry.create();
registry.assign<double>(e0);
registry.assign<int>(e0);
registry.assign<float>(e0);
const auto e1 = registry.create();
registry.assign<char>(e1);
registry.assign<float>(e1);
auto view = registry.view<char, int, float>();
for(auto entity: view) {
(void)entity;
FAIL();
}
}
TEST(View, MultipleComponentEach) {
entt::DefaultRegistry registry;
const auto e0 = registry.create();
registry.assign<int>(e0);
registry.assign<char>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
registry.assign<char>(e1);
auto view = registry.view<int, char>();
const auto &cview = static_cast<const decltype(view) &>(view);
std::size_t cnt = 0;
view.each([&cnt](auto, int &, char &) { ++cnt; });
ASSERT_EQ(cnt, std::size_t{2});
cview.each([&cnt](auto, const int &, const char &) { --cnt; });
ASSERT_EQ(cnt, std::size_t{0});
}
TEST(View, MultipleComponentEachWithHoles) {
entt::DefaultRegistry registry;
const auto e0 = registry.create();
const auto e1 = registry.create();
const auto e2 = registry.create();
registry.assign<char>(e0, '0');
registry.assign<char>(e1, '1');
registry.assign<int>(e0, 0);
registry.assign<int>(e2, 2);
auto view = registry.view<char, int>();
view.each([e0](auto entity, const char &c, const int &i) {
if(e0 == entity) {
ASSERT_EQ(c, '0');
ASSERT_EQ(i, 0);
} else {
FAIL();
}
});
}
TEST(PersistentView, Prepare) {
entt::DefaultRegistry registry;
registry.prepare<int, char>();
auto view = registry.view<int, char>(entt::persistent_t{});
const auto &cview = view;
ASSERT_TRUE(view.empty());
@@ -303,6 +24,7 @@ TEST(PersistentView, Prepare) {
ASSERT_NO_THROW((++registry.view<int, char>(entt::persistent_t{}).begin()));
ASSERT_NE(view.begin(), view.end());
ASSERT_NE(cview.begin(), cview.end());
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
registry.assign<int>(e0);
@@ -330,6 +52,7 @@ TEST(PersistentView, Prepare) {
registry.remove<char>(e1);
ASSERT_EQ(view.begin(), view.end());
ASSERT_EQ(view.cbegin(), view.cend());
ASSERT_TRUE(view.empty());
}
@@ -378,33 +101,27 @@ TEST(PersistentView, NoPrepare) {
registry.remove<char>(e1);
ASSERT_EQ(view.begin(), view.end());
ASSERT_EQ(view.cbegin(), view.cend());
ASSERT_TRUE(view.empty());
}
TEST(PersistentView, BeginEnd) {
TEST(PersistentView, ElementAccess) {
entt::DefaultRegistry registry;
auto view = registry.view<int, char>(entt::persistent_t{});
const auto &cview = view;
for(auto i = 0; i < 3; ++i) {
const auto entity = registry.create();
registry.assign<int>(entity);
registry.assign<char>(entity);
const auto e0 = registry.create();
registry.assign<int>(e0);
registry.assign<char>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
registry.assign<char>(e1);
for(typename decltype(view)::size_type i{}; i < view.size(); ++i) {
ASSERT_EQ(view[i], i ? e0 : e1);
ASSERT_EQ(cview[i], i ? e0 : e1);
}
auto test = [](auto begin, auto end) {
ASSERT_NE(begin, end);
ASSERT_NE(++begin, end);
ASSERT_NE(begin++, end);
ASSERT_EQ(begin+1, end);
ASSERT_NE(begin, end);
ASSERT_EQ((begin += 1), end);
ASSERT_EQ(begin, end);
};
test(cview.begin(), cview.end());
test(view.begin(), view.end());
test(view.cbegin(), view.cend());
}
TEST(PersistentView, Contains) {
@@ -509,9 +226,303 @@ TEST(PersistentView, Sort) {
}
}
TEST(SingleComponentView, Functionalities) {
entt::DefaultRegistry registry;
auto view = registry.view<char>();
const auto &cview = view;
const auto e0 = registry.create();
const auto e1 = registry.create();
ASSERT_TRUE(view.empty());
registry.assign<int>(e1);
registry.assign<char>(e1);
ASSERT_NO_THROW(registry.view<char>().begin()++);
ASSERT_NO_THROW(++registry.view<char>().begin());
ASSERT_NE(view.begin(), view.end());
ASSERT_NE(cview.begin(), cview.end());
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
ASSERT_FALSE(view.empty());
registry.assign<char>(e0);
ASSERT_EQ(view.size(), typename decltype(view)::size_type{2});
view.get(e0) = '1';
view.get(e1) = '2';
for(auto entity: view) {
const auto &cview = static_cast<const decltype(view) &>(view);
ASSERT_TRUE(cview.get(entity) == '1' || cview.get(entity) == '2');
}
ASSERT_EQ(*(view.data() + 0), e1);
ASSERT_EQ(*(view.data() + 1), e0);
ASSERT_EQ(*(view.raw() + 0), '2');
ASSERT_EQ(*(static_cast<const decltype(view) &>(view).raw() + 1), '1');
registry.remove<char>(e0);
registry.remove<char>(e1);
ASSERT_EQ(view.begin(), view.end());
ASSERT_EQ(view.cbegin(), view.cend());
ASSERT_TRUE(view.empty());
}
TEST(SingleComponentView, ElementAccess) {
entt::DefaultRegistry registry;
auto view = registry.view<int>();
const auto &cview = view;
const auto e0 = registry.create();
registry.assign<int>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
for(typename decltype(view)::size_type i{}; i < view.size(); ++i) {
ASSERT_EQ(view[i], i ? e0 : e1);
ASSERT_EQ(cview[i], i ? e0 : e1);
}
}
TEST(SingleComponentView, Contains) {
entt::DefaultRegistry registry;
const auto e0 = registry.create();
registry.assign<int>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
registry.destroy(e0);
auto view = registry.view<int>();
ASSERT_FALSE(view.contains(e0));
ASSERT_TRUE(view.contains(e1));
}
TEST(SingleComponentView, Empty) {
entt::DefaultRegistry registry;
const auto e0 = registry.create();
registry.assign<char>(e0);
registry.assign<double>(e0);
const auto e1 = registry.create();
registry.assign<char>(e1);
auto view = registry.view<int>();
ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{0});
for(auto entity: view) {
(void)entity;
FAIL();
}
}
TEST(SingleComponentView, Each) {
entt::DefaultRegistry registry;
registry.assign<int>(registry.create());
registry.assign<int>(registry.create());
auto view = registry.view<int>();
const auto &cview = static_cast<const decltype(view) &>(view);
std::size_t cnt = 0;
view.each([&cnt](auto, int &) { ++cnt; });
ASSERT_EQ(cnt, std::size_t{2});
cview.each([&cnt](auto, const int &) { --cnt; });
ASSERT_EQ(cnt, std::size_t{0});
}
TEST(MultipleComponentView, Functionalities) {
entt::DefaultRegistry registry;
auto view = registry.view<int, char>();
const auto &cview = view;
ASSERT_TRUE(view.empty());
const auto e0 = registry.create();
registry.assign<char>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
ASSERT_FALSE(view.empty());
registry.assign<char>(e1);
auto it = registry.view<int, char>().begin();
ASSERT_EQ(*it, e1);
ASSERT_EQ(++it, (registry.view<int, char>().end()));
ASSERT_NO_THROW((registry.view<int, char>().begin()++));
ASSERT_NO_THROW((++registry.view<int, char>().begin()));
ASSERT_NE(view.begin(), view.end());
ASSERT_NE(cview.begin(), cview.end());
ASSERT_EQ(view.size(), decltype(view.size()){1});
registry.get<char>(e0) = '1';
registry.get<char>(e1) = '2';
registry.get<int>(e1) = 42;
for(auto entity: view) {
const auto &cview = static_cast<const decltype(view) &>(view);
ASSERT_EQ(std::get<0>(cview.get<int, char>(entity)), 42);
ASSERT_EQ(std::get<1>(view.get<int, char>(entity)), '2');
ASSERT_EQ(cview.get<char>(entity), '2');
}
}
TEST(MultipleComponentView, Iterator) {
entt::DefaultRegistry registry;
const auto entity = registry.create();
registry.assign<int>(entity);
registry.assign<char>(entity);
const auto view = registry.view<int, char>();
using iterator_type = typename decltype(view)::iterator_type;
iterator_type end{view.begin()};
iterator_type begin{};
begin = view.end();
std::swap(begin, end);
ASSERT_EQ(begin, view.begin());
ASSERT_EQ(end, view.end());
ASSERT_NE(begin, end);
ASSERT_EQ(view.begin()++, view.begin());
ASSERT_EQ(++view.begin(), view.end());
}
TEST(MultipleComponentView, ConstIterator) {
entt::DefaultRegistry registry;
const auto entity = registry.create();
registry.assign<int>(entity);
registry.assign<char>(entity);
const auto view = registry.view<int, char>();
using iterator_type = typename decltype(view)::iterator_type;
iterator_type cend{view.cbegin()};
iterator_type cbegin{};
cbegin = view.cend();
std::swap(cbegin, cend);
ASSERT_EQ(cbegin, view.cbegin());
ASSERT_EQ(cend, view.cend());
ASSERT_NE(cbegin, cend);
ASSERT_EQ(view.cbegin()++, view.cbegin());
ASSERT_EQ(++view.cbegin(), view.cend());
}
TEST(MultipleComponentView, Contains) {
entt::DefaultRegistry registry;
const auto e0 = registry.create();
registry.assign<int>(e0);
registry.assign<char>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
registry.assign<char>(e1);
registry.destroy(e0);
auto view = registry.view<int, char>();
ASSERT_FALSE(view.contains(e0));
ASSERT_TRUE(view.contains(e1));
}
TEST(MultipleComponentView, Empty) {
entt::DefaultRegistry registry;
const auto e0 = registry.create();
registry.assign<double>(e0);
registry.assign<int>(e0);
registry.assign<float>(e0);
const auto e1 = registry.create();
registry.assign<char>(e1);
registry.assign<float>(e1);
auto view = registry.view<char, int, float>();
for(auto entity: view) {
(void)entity;
FAIL();
}
}
TEST(MultipleComponentView, Each) {
entt::DefaultRegistry registry;
const auto e0 = registry.create();
registry.assign<int>(e0);
registry.assign<char>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
registry.assign<char>(e1);
auto view = registry.view<int, char>();
const auto &cview = static_cast<const decltype(view) &>(view);
std::size_t cnt = 0;
view.each([&cnt](auto, int &, char &) { ++cnt; });
ASSERT_EQ(cnt, std::size_t{2});
cview.each([&cnt](auto, const int &, const char &) { --cnt; });
ASSERT_EQ(cnt, std::size_t{0});
}
TEST(MultipleComponentView, EachWithHoles) {
entt::DefaultRegistry registry;
const auto e0 = registry.create();
const auto e1 = registry.create();
const auto e2 = registry.create();
registry.assign<char>(e0, '0');
registry.assign<char>(e1, '1');
registry.assign<int>(e0, 0);
registry.assign<int>(e2, 2);
auto view = registry.view<char, int>();
view.each([e0](auto entity, const char &c, const int &i) {
if(e0 == entity) {
ASSERT_EQ(c, '0');
ASSERT_EQ(i, 0);
} else {
FAIL();
}
});
}
TEST(RawView, Functionalities) {
entt::DefaultRegistry registry;
auto view = registry.view<char>(entt::raw_t{});
const auto &cview = view;
ASSERT_TRUE(view.empty());
@@ -526,6 +537,7 @@ TEST(RawView, Functionalities) {
ASSERT_NO_THROW(++registry.view<char>(entt::raw_t{}).begin());
ASSERT_NE(view.begin(), view.end());
ASSERT_NE(cview.begin(), cview.end());
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
registry.assign<char>(e0);
@@ -558,31 +570,25 @@ TEST(RawView, Functionalities) {
registry.remove<char>(e1);
ASSERT_EQ(view.begin(), view.end());
ASSERT_EQ(view.cbegin(), view.cend());
ASSERT_TRUE(view.empty());
}
TEST(RawView, BeginEnd) {
TEST(RawView, ElementAccess) {
entt::DefaultRegistry registry;
auto view = registry.view<int>(entt::raw_t{});
const auto &cview = view;
for(auto i = 0; i < 3; ++i) {
registry.assign<int>(registry.create());
const auto e0 = registry.create();
registry.assign<int>(e0, 42);
const auto e1 = registry.create();
registry.assign<int>(e1, 3);
for(typename decltype(view)::size_type i{}; i < view.size(); ++i) {
ASSERT_EQ(view[i], i ? 42 : 3);
ASSERT_EQ(cview[i], i ? 42 : 3);
}
auto test = [](auto begin, auto end) {
ASSERT_NE(begin, end);
ASSERT_NE(++begin, end);
ASSERT_NE(begin++, end);
ASSERT_EQ(begin+1, end);
ASSERT_NE(begin, end);
ASSERT_EQ((begin += 1), end);
ASSERT_EQ(begin, end);
};
test(cview.begin(), cview.end());
test(view.begin(), view.end());
test(view.cbegin(), view.cend());
}
TEST(RawView, Empty) {
@@ -623,3 +629,234 @@ TEST(RawView, Each) {
ASSERT_EQ(cnt, std::size_t{0});
}
TEST(RuntimeView, Functionalities) {
entt::DefaultRegistry registry;
using component_type = typename decltype(registry)::component_type;
// forces the creation of the pools
registry.reserve<int>(0);
registry.reserve<char>(0);
component_type types[] = { registry.type<int>(), registry.type<char>() };
auto view = registry.view(std::begin(types), std::end(types));
const auto &cview = view;
ASSERT_TRUE(view.empty());
const auto e0 = registry.create();
registry.assign<char>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
ASSERT_FALSE(view.empty());
registry.assign<char>(e1);
auto it = registry.view(std::begin(types), std::end(types)).begin();
ASSERT_EQ(*it, e1);
ASSERT_EQ(++it, (registry.view(std::begin(types), std::end(types)).end()));
ASSERT_NO_THROW((registry.view(std::begin(types), std::end(types)).begin()++));
ASSERT_NO_THROW((++registry.view(std::begin(types), std::end(types)).begin()));
ASSERT_NE(view.begin(), view.end());
ASSERT_NE(cview.begin(), cview.end());
ASSERT_EQ(view.size(), decltype(view.size()){1});
registry.get<char>(e0) = '1';
registry.get<char>(e1) = '2';
registry.get<int>(e1) = 42;
for(auto entity: view) {
ASSERT_EQ(registry.get<int>(entity), 42);
ASSERT_EQ(registry.get<char>(entity), '2');
}
}
TEST(RuntimeView, Iterator) {
entt::DefaultRegistry registry;
using component_type = typename decltype(registry)::component_type;
const auto entity = registry.create();
registry.assign<int>(entity);
registry.assign<char>(entity);
component_type types[] = { registry.type<int>(), registry.type<char>() };
auto view = registry.view(std::begin(types), std::end(types));
using iterator_type = typename decltype(view)::iterator_type;
iterator_type end{view.begin()};
iterator_type begin{};
begin = view.end();
std::swap(begin, end);
ASSERT_EQ(begin, view.begin());
ASSERT_EQ(end, view.end());
ASSERT_NE(begin, end);
ASSERT_EQ(view.begin()++, view.begin());
ASSERT_EQ(++view.begin(), view.end());
}
TEST(RuntimeView, ConstIterator) {
entt::DefaultRegistry registry;
using component_type = typename decltype(registry)::component_type;
const auto entity = registry.create();
registry.assign<int>(entity);
registry.assign<char>(entity);
component_type types[] = { registry.type<int>(), registry.type<char>() };
auto view = registry.view(std::begin(types), std::end(types));
using iterator_type = typename decltype(view)::iterator_type;
iterator_type cend{view.cbegin()};
iterator_type cbegin{};
cbegin = view.cend();
std::swap(cbegin, cend);
ASSERT_EQ(cbegin, view.cbegin());
ASSERT_EQ(cend, view.cend());
ASSERT_NE(cbegin, cend);
ASSERT_EQ(view.cbegin()++, view.cbegin());
ASSERT_EQ(++view.cbegin(), view.cend());
}
TEST(RuntimeView, Contains) {
entt::DefaultRegistry registry;
using component_type = typename decltype(registry)::component_type;
const auto e0 = registry.create();
registry.assign<int>(e0);
registry.assign<char>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
registry.assign<char>(e1);
registry.destroy(e0);
component_type types[] = { registry.type<int>(), registry.type<char>() };
auto view = registry.view(std::begin(types), std::end(types));
ASSERT_FALSE(view.contains(e0));
ASSERT_TRUE(view.contains(e1));
}
TEST(RuntimeView, Empty) {
entt::DefaultRegistry registry;
using component_type = typename decltype(registry)::component_type;
const auto e0 = registry.create();
registry.assign<double>(e0);
registry.assign<int>(e0);
registry.assign<float>(e0);
const auto e1 = registry.create();
registry.assign<char>(e1);
registry.assign<float>(e1);
component_type types[] = { registry.type<char>(), registry.type<int>(), registry.type<float>() };
auto view = registry.view(std::begin(types), std::end(types));
for(auto entity: view) {
(void)entity;
FAIL();
}
}
TEST(RuntimeView, Each) {
entt::DefaultRegistry registry;
using component_type = typename decltype(registry)::component_type;
const auto e0 = registry.create();
registry.assign<int>(e0);
registry.assign<char>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
registry.assign<char>(e1);
component_type types[] = { registry.type<int>(), registry.type<char>() };
auto view = registry.view(std::begin(types), std::end(types));
std::size_t cnt = 0;
view.each([&cnt](auto) { ++cnt; });
ASSERT_EQ(cnt, std::size_t{2});
}
TEST(RuntimeView, EachWithHoles) {
entt::DefaultRegistry registry;
using component_type = typename decltype(registry)::component_type;
const auto e0 = registry.create();
const auto e1 = registry.create();
const auto e2 = registry.create();
registry.assign<char>(e0, '0');
registry.assign<char>(e1, '1');
registry.assign<int>(e0, 0);
registry.assign<int>(e2, 2);
component_type types[] = { registry.type<int>(), registry.type<char>() };
auto view = registry.view(std::begin(types), std::end(types));
view.each([e0](auto entity) {
ASSERT_EQ(e0, entity);
});
}
TEST(RuntimeView, MissingPool) {
entt::DefaultRegistry registry;
using component_type = typename decltype(registry)::component_type;
const auto e0 = registry.create();
registry.assign<int>(e0);
component_type types[] = { registry.type<int>(), registry.type<char>() };
auto view = registry.view(std::begin(types), std::end(types));
ASSERT_TRUE(view.empty());
ASSERT_EQ(view.size(), decltype(view.size()){0});
registry.assign<char>(e0);
ASSERT_TRUE(view.empty());
ASSERT_EQ(view.size(), decltype(view.size()){0});
ASSERT_FALSE(view.contains(e0));
view.each([](auto) { FAIL(); });
for(auto entity: view) {
(void)entity;
FAIL();
}
}
TEST(RuntimeView, EmptyRange) {
entt::DefaultRegistry registry;
using component_type = typename decltype(registry)::component_type;
const auto e0 = registry.create();
registry.assign<int>(e0);
const component_type *ptr = nullptr;
auto view = registry.view(ptr, ptr);
ASSERT_TRUE(view.empty());
ASSERT_EQ(view.size(), decltype(view.size()){0});
ASSERT_FALSE(view.contains(e0));
view.each([](auto) { FAIL(); });
for(auto entity: view) {
(void)entity;
FAIL();
}
}

View File

@@ -4,6 +4,7 @@
struct AService {};
struct AnotherService {
virtual ~AnotherService() = default;
virtual void f(bool) = 0;
bool check{false};
};

View File

@@ -11,25 +11,36 @@ struct DelegateFunctor {
}
};
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) {
entt::Delegate<int(int)> ffdel;
entt::Delegate<int(int)> mfdel;
DelegateFunctor functor;
ASSERT_EQ(ffdel(42), int{});
ASSERT_EQ(mfdel(42), int{});
ASSERT_TRUE(ffdel.empty());
ASSERT_TRUE(mfdel.empty());
ffdel.connect<&delegateFunction>();
mfdel.connect<DelegateFunctor, &DelegateFunctor::operator()>(&functor);
ASSERT_FALSE(ffdel.empty());
ASSERT_FALSE(mfdel.empty());
ASSERT_EQ(ffdel(3), 9);
ASSERT_EQ(mfdel(3), 6);
ffdel.reset();
mfdel.reset();
ASSERT_EQ(ffdel(42), int{});
ASSERT_EQ(mfdel(42), int{});
ASSERT_TRUE(ffdel.empty());
ASSERT_TRUE(mfdel.empty());
}
TEST(Delegate, Comparison) {
@@ -43,3 +54,22 @@ TEST(Delegate, Comparison) {
ASSERT_TRUE(def == entt::Delegate<int(int)>{});
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);
}

View File

@@ -45,6 +45,14 @@ 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)>;
@@ -142,7 +150,7 @@ TEST(SigH, Functions) {
sigh.publish(v);
ASSERT_FALSE(sigh.empty());
ASSERT_EQ((entt::SigH<bool(int)>::size_type)1, sigh.size());
ASSERT_EQ(static_cast<entt::SigH<bool(int)>::size_type>(1), sigh.size());
ASSERT_EQ(42, v);
v = 0;
@@ -150,7 +158,7 @@ TEST(SigH, Functions) {
sigh.publish(v);
ASSERT_TRUE(sigh.empty());
ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
ASSERT_EQ(static_cast<entt::SigH<bool(int)>::size_type>(0), sigh.size());
ASSERT_EQ(0, v);
sigh.sink().connect<&SigHListener::f>();
@@ -166,25 +174,25 @@ TEST(SigH, Members) {
ASSERT_TRUE(s.k);
ASSERT_FALSE(sigh.empty());
ASSERT_EQ((entt::SigH<bool(int)>::size_type)1, sigh.size());
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((entt::SigH<bool(int)>::size_type)0, sigh.size());
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((entt::SigH<bool(int)>::size_type)2, sigh.size());
ASSERT_EQ(static_cast<entt::SigH<bool(int)>::size_type>(2), sigh.size());
sigh.sink().disconnect(ptr);
ASSERT_TRUE(sigh.empty());
ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
ASSERT_EQ(static_cast<entt::SigH<bool(int)>::size_type>(0), sigh.size());
}
TEST(SigH, Collector) {
@@ -205,7 +213,7 @@ TEST(SigH, Collector) {
ASSERT_FALSE(sigh_all.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[1]);
@@ -217,6 +225,27 @@ TEST(SigH, Collector) {
ASSERT_FALSE(sigh_first.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]);
}
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);
}

View File

@@ -235,34 +235,41 @@ public:
duk_push_array(ctx);
dreg.registry.each([ctx, nargs, &pos, &dreg](auto entity) {
auto &registry = dreg.registry;
auto &func = dreg.func;
bool match = true;
std::vector<typename entt::DefaultRegistry::component_type> components;
std::vector<typename entt::DefaultRegistry::component_type> runtime;
for (duk_idx_t arg = 0; match && arg < nargs; arg++) {
auto type = duk_require_uint(ctx, arg);
for(duk_idx_t arg = 0; arg < nargs; arg++) {
auto type = duk_require_uint(ctx, arg);
if(type < udef) {
assert(func.find(type) != func.cend());
match = (registry.*func[type].test)(entity);
} else {
const auto ctype = registry.type<DuktapeRuntime>();
assert(func.find(ctype) != func.cend());
match = (registry.*func[ctype].test)(entity);
if(match) {
auto &components = registry.get<DuktapeRuntime>(entity).components;
match = (components.find(type) != components.cend());
}
if(type < udef) {
components.push_back(type);
} else {
if(runtime.empty()) {
components.push_back(dreg.registry.type<DuktapeRuntime>());
}
}
if(match) {
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;
}