@ -1,18 +1,6 @@ | |||||
[submodule "modules/entt"] | |||||
path = modules/entt | |||||
url = git@cjhoward.org:entt.git | |||||
[submodule "modules/vmq"] | |||||
path = modules/vmq | |||||
url = git@cjhoward.org:vmq.git | |||||
[submodule "modules/antkeeper-source"] | [submodule "modules/antkeeper-source"] | ||||
path = modules/antkeeper-source | path = modules/antkeeper-source | ||||
url = git@cjhoward.org:antkeeper-source.git | |||||
url = https://github.com/antkeeper/antkeeper-source.git | |||||
[submodule "modules/antkeeper-data"] | [submodule "modules/antkeeper-data"] | ||||
path = modules/antkeeper-data | path = modules/antkeeper-data | ||||
url = git@cjhoward.org:antkeeper-data.git | |||||
[submodule "modules/SDL2"] | |||||
path = modules/SDL2 | |||||
url = git@cjhoward.org:SDL2.git | |||||
[submodule "modules/openal-soft"] | |||||
path = modules/openal-soft | |||||
url = git@cjhoward.org:openal-soft.git | |||||
url = https://github.com/antkeeper/antkeeper-data.gits |
@ -0,0 +1 @@ | |||||
vmq |
@ -1 +1 @@ | |||||
Subproject commit c9779bf7663b0732163f18fb4afd2d9fb143ad1e | |||||
Subproject commit 5a2d8861130f27d4a5cc87ed1c702d8bc170566a |
@ -0,0 +1,32 @@ | |||||
cmake_minimum_required(VERSION 3.7) | |||||
project(dr_wav) | |||||
add_library(dr_wav ${PROJECT_SOURCE_DIR}/dr_wav.cpp) | |||||
# Install library | |||||
install(TARGETS ${PROJECT_NAME} | |||||
EXPORT ${PROJECT_NAME}-targets | |||||
ARCHIVE DESTINATION lib | |||||
LIBRARY DESTINATION lib | |||||
RUNTIME DESTINATION bin) | |||||
# Install header | |||||
install( | |||||
FILES ${PROJECT_SOURCE_DIR}/dr_wav.h | |||||
DESTINATION include/dr_libs) | |||||
# Install CMake config file | |||||
install(EXPORT ${PROJECT_NAME}-targets | |||||
FILE ${PROJECT_NAME}-targets.cmake | |||||
DESTINATION lib/cmake/${PROJECT_NAME}) | |||||
include(CMakePackageConfigHelpers) | |||||
configure_package_config_file( | |||||
${PROJECT_SOURCE_DIR}/${PROJECT_NAME}-config.cmake.in | |||||
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake | |||||
INSTALL_DESTINATION lib/cmake/${PROJECT_NAME}) | |||||
install( | |||||
FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake | |||||
DESTINATION lib/cmake/${PROJECT_NAME}) |
@ -0,0 +1,5 @@ | |||||
@PACKAGE_INIT@ | |||||
include(CMakeFindDependencyMacro) | |||||
include("${CMAKE_CURRENT_LIST_DIR}/dr_wav-targets.cmake") |
@ -0,0 +1,2 @@ | |||||
#define DR_WAV_IMPLEMENTATION | |||||
#include "dr_wav.h" |
@ -0,0 +1,5 @@ | |||||
*.user | |||||
conan/test_package/build | |||||
# IDEs | |||||
.idea |
@ -0,0 +1,97 @@ | |||||
language: cpp | |||||
dist: trusty | |||||
sudo: false | |||||
env: | |||||
global: | |||||
- CONAN_USERNAME="skypjack" | |||||
- CONAN_PACKAGE_NAME="entt" | |||||
- CONAN_HEADER_ONLY="True" | |||||
- NON_CONAN_DEPLOYMENT="True" | |||||
conan-buildsteps: &conan-buildsteps | |||||
before_install: | |||||
# use this step if you desire to manipulate CONAN variables programmatically | |||||
- NON_CONAN_DEPLOYMENT="False" | |||||
install: | |||||
- chmod +x ./conan/ci/install.sh | |||||
- ./conan/ci/install.sh | |||||
script: | |||||
- chmod +x ./conan/ci/build.sh | |||||
- ./conan/ci/build.sh | |||||
# the following are dummies to overwrite default build steps | |||||
before_script: | |||||
- true | |||||
after_success: | |||||
- true | |||||
if: tag IS present | |||||
conan-linux: &conan-linux | |||||
os: linux | |||||
sudo: required | |||||
language: python | |||||
python: "3.6" | |||||
services: | |||||
- docker | |||||
<<: *conan-buildsteps | |||||
matrix: | |||||
include: | |||||
- os: linux | |||||
compiler: gcc | |||||
addons: | |||||
apt: | |||||
sources: ['ubuntu-toolchain-r-test'] | |||||
packages: ['g++-7'] | |||||
env: COMPILER=g++-7 | |||||
- os: linux | |||||
compiler: clang | |||||
addons: | |||||
apt: | |||||
sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-6.0'] | |||||
packages: ['clang-6.0', 'g++-7'] | |||||
env: COMPILER=clang++-6.0 | |||||
- os: osx | |||||
osx_image: xcode10 | |||||
compiler: clang | |||||
env: COMPILER=clang++ | |||||
- os: linux | |||||
compiler: gcc | |||||
addons: | |||||
apt: | |||||
sources: ['ubuntu-toolchain-r-test'] | |||||
packages: ['g++-7'] | |||||
env: | |||||
- COMPILER=g++-7 | |||||
- CXXFLAGS="-O0 --coverage -fno-inline -fno-inline-small-functions -fno-default-inline" | |||||
before_script: | |||||
- pip install --user cpp-coveralls | |||||
after_success: | |||||
- coveralls --gcov gcov-7 --gcov-options '\-lp' --root ${TRAVIS_BUILD_DIR} --build-root ${TRAVIS_BUILD_DIR}/build --extension cpp --extension hpp --exclude deps --include src | |||||
# Conan testing and uploading | |||||
- <<: *conan-linux | |||||
env: CONAN_GCC_VERSIONS=8 CONAN_DOCKER_IMAGE=conanio/gcc8 | |||||
notifications: | |||||
email: | |||||
on_success: never | |||||
on_failure: always | |||||
install: | |||||
- echo ${PATH} | |||||
- cmake --version | |||||
- export CXX=${COMPILER} | |||||
- echo ${CXX} | |||||
- ${CXX} --version | |||||
- ${CXX} -v | |||||
script: | |||||
- mkdir -p build && cd build | |||||
- cmake -DBUILD_TESTING=ON -DCMAKE_BUILD_TYPE=Release .. && make -j4 | |||||
- CTEST_OUTPUT_ON_FAILURE=1 ctest -j4 | |||||
deploy: | |||||
provider: script | |||||
script: scripts/update_packages.sh $TRAVIS_TAG | |||||
on: | |||||
tags: true | |||||
condition: “$NON_CONAN_DEPLOYMENT = “True” |
@ -0,0 +1,34 @@ | |||||
# Author | |||||
skypjack | |||||
# Contributors | |||||
BenediktConze | |||||
bjadamson | |||||
ceeac | |||||
ColinH | |||||
corystegel | |||||
Croydon | |||||
dbacchet | |||||
dBagrat | |||||
djarek | |||||
DonKult | |||||
drglove | |||||
eugeneko | |||||
gale83 | |||||
ghost | |||||
Green-Sky | |||||
mhammerc | |||||
Milerius | |||||
morbo84 | |||||
m-waka | |||||
Kerndog73 | |||||
Paolo-Oliverio | |||||
pgruenbacher | |||||
prowolf | |||||
The5-1 | |||||
vblanco20-1 | |||||
willtunnels | |||||
WizardIke | |||||
w1th0utnam3 |
@ -0,0 +1,226 @@ | |||||
# | |||||
# EnTT | |||||
# | |||||
cmake_minimum_required(VERSION 3.7.2) | |||||
# | |||||
# Building in-tree is not allowed (we take care of your craziness). | |||||
# | |||||
if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR}) | |||||
message(FATAL_ERROR "Prevented in-tree built. Please create a build directory outside of the source code and call cmake from there. Thank you.") | |||||
endif() | |||||
# | |||||
# Project configuration | |||||
# | |||||
project(EnTT VERSION 3.1.0) | |||||
include(GNUInstallDirs) | |||||
if(NOT CMAKE_BUILD_TYPE) | |||||
set(CMAKE_BUILD_TYPE Debug) | |||||
endif() | |||||
set(SETTINGS_ORGANIZATION "Michele Caini") | |||||
set(SETTINGS_APPLICATION ${PROJECT_NAME}) | |||||
set(PROJECT_AUTHOR "Michele Caini") | |||||
set(PROJECT_AUTHOR_EMAIL "michele.caini@gmail.com") | |||||
message("*") | |||||
message("* ${PROJECT_NAME} v${PROJECT_VERSION} (${CMAKE_BUILD_TYPE})") | |||||
message("* Copyright (c) 2017-2019 ${PROJECT_AUTHOR} <${PROJECT_AUTHOR_EMAIL}>") | |||||
message("*") | |||||
option(USE_LIBCPP "Use libc++ by adding -stdlib=libc++ flag if availbale." ON) | |||||
option(USE_ASAN "Use address sanitizer by adding -fsanitize=address -fno-omit-frame-pointer flags" OFF) | |||||
option(USE_COMPILE_OPTIONS "Use compile options from EnTT." ON) | |||||
# | |||||
# Compiler stuff | |||||
# | |||||
if(NOT MSVC AND USE_LIBCPP) | |||||
include(CheckCXXSourceCompiles) | |||||
include(CMakePushCheckState) | |||||
cmake_push_check_state() | |||||
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -stdlib=libc++") | |||||
check_cxx_source_compiles(" | |||||
#include<type_traits> | |||||
int main() { return std::is_same_v<int, int> ? 0 : 1; } | |||||
" HAS_LIBCPP) | |||||
if(NOT HAS_LIBCPP) | |||||
message(WARNING "The option USE_LIBCPP is set (by default) but libc++ is not available. The flag will not be added to the target.") | |||||
endif() | |||||
cmake_pop_check_state() | |||||
endif() | |||||
# | |||||
# Add EnTT target | |||||
# | |||||
add_library(EnTT INTERFACE) | |||||
configure_file(${EnTT_SOURCE_DIR}/cmake/in/version.h.in ${EnTT_SOURCE_DIR}/src/entt/config/version.h @ONLY) | |||||
target_include_directories( | |||||
EnTT INTERFACE | |||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src> | |||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> | |||||
) | |||||
target_compile_definitions( | |||||
EnTT | |||||
INTERFACE $<$<AND:$<CONFIG:Debug>,$<NOT:$<CXX_COMPILER_ID:MSVC>>>:DEBUG> | |||||
INTERFACE $<$<AND:$<CONFIG:Release>,$<NOT:$<CXX_COMPILER_ID:MSVC>>>:RELEASE> | |||||
) | |||||
if(USE_ASAN) | |||||
target_compile_options(EnTT INTERFACE $<$<AND:$<CONFIG:Debug>,$<NOT:$<CXX_COMPILER_ID:MSVC>>>:-fsanitize=address -fno-omit-frame-pointer>) | |||||
target_link_libraries(EnTT INTERFACE $<$<AND:$<CONFIG:Debug>,$<NOT:$<CXX_COMPILER_ID:MSVC>>>:-fsanitize=address -fno-omit-frame-pointer>) | |||||
endif() | |||||
if(USE_COMPILE_OPTIONS) | |||||
target_compile_options( | |||||
EnTT | |||||
INTERFACE $<$<AND:$<CONFIG:Debug>,$<NOT:$<CXX_COMPILER_ID:MSVC>>>:-O0 -g> | |||||
# it seems that -O3 ruins a bit the performance when using clang ... | |||||
INTERFACE $<$<AND:$<CONFIG:Release>,$<CXX_COMPILER_ID:Clang>>:-O2> | |||||
# ... on the other side, GCC is incredibly comfortable with it. | |||||
INTERFACE $<$<AND:$<CONFIG:Release>,$<CXX_COMPILER_ID:GNU>>:-O3> | |||||
) | |||||
endif() | |||||
if(HAS_LIBCPP) | |||||
target_compile_options(EnTT BEFORE INTERFACE -stdlib=libc++) | |||||
endif() | |||||
target_compile_features(EnTT INTERFACE cxx_std_17) | |||||
# | |||||
# Install EnTT | |||||
# | |||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") | |||||
set(CUSTOM_INSTALL_CONFIGDIR cmake) | |||||
else() | |||||
set(CUSTOM_INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/entt) | |||||
endif() | |||||
install(DIRECTORY src/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) | |||||
install(TARGETS EnTT EXPORT EnTTTargets) | |||||
export(EXPORT EnTTTargets FILE ${EnTT_BINARY_DIR}/EnTTTargets.cmake) | |||||
install( | |||||
EXPORT EnTTTargets | |||||
FILE EnTTTargets.cmake | |||||
DESTINATION ${CUSTOM_INSTALL_CONFIGDIR} | |||||
) | |||||
# | |||||
# Build tree package config file | |||||
# | |||||
configure_file(cmake/in/EnTTBuildConfig.cmake.in EnTTConfig.cmake @ONLY) | |||||
include(CMakePackageConfigHelpers) | |||||
# | |||||
# Install tree package config file | |||||
# | |||||
configure_package_config_file( | |||||
cmake/in/EnTTConfig.cmake.in | |||||
${CUSTOM_INSTALL_CONFIGDIR}/EnTTConfig.cmake | |||||
INSTALL_DESTINATION ${CUSTOM_INSTALL_CONFIGDIR} | |||||
PATH_VARS CMAKE_INSTALL_INCLUDEDIR | |||||
) | |||||
write_basic_package_version_file( | |||||
${EnTT_BINARY_DIR}/EnTTConfigVersion.cmake | |||||
VERSION ${PROJECT_VERSION} | |||||
COMPATIBILITY AnyNewerVersion | |||||
) | |||||
install( | |||||
FILES | |||||
${EnTT_BINARY_DIR}/${CUSTOM_INSTALL_CONFIGDIR}/EnTTConfig.cmake | |||||
${EnTT_BINARY_DIR}/EnTTConfigVersion.cmake | |||||
DESTINATION ${CUSTOM_INSTALL_CONFIGDIR} | |||||
) | |||||
export(PACKAGE EnTT) | |||||
# | |||||
# Tests | |||||
# | |||||
option(BUILD_TESTING "Enable testing with ctest." OFF) | |||||
if(BUILD_TESTING) | |||||
set(THREADS_PREFER_PTHREAD_FLAG ON) | |||||
find_package(Threads REQUIRED) | |||||
option(FIND_GTEST_PACKAGE "Enable finding gtest package." OFF) | |||||
if(FIND_GTEST_PACKAGE) | |||||
find_package(GTest REQUIRED) | |||||
else() | |||||
# gtest, gtest_main, gmock and gmock_main targets are available from now on | |||||
set(GOOGLETEST_DEPS_DIR ${EnTT_SOURCE_DIR}/deps/googletest) | |||||
configure_file(${EnTT_SOURCE_DIR}/cmake/in/googletest.in ${GOOGLETEST_DEPS_DIR}/CMakeLists.txt) | |||||
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . WORKING_DIRECTORY ${GOOGLETEST_DEPS_DIR}) | |||||
execute_process(COMMAND ${CMAKE_COMMAND} --build . WORKING_DIRECTORY ${GOOGLETEST_DEPS_DIR}) | |||||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) | |||||
add_subdirectory(${GOOGLETEST_DEPS_DIR}/src ${GOOGLETEST_DEPS_DIR}/build) | |||||
target_compile_features(gmock_main PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_FEATURES>) | |||||
target_compile_features(gmock PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_FEATURES>) | |||||
add_library(GTest::Main ALIAS gtest_main) | |||||
endif() | |||||
option(BUILD_BENCHMARK "Build benchmark." OFF) | |||||
option(BUILD_LIB "Build lib example." OFF) | |||||
option(BUILD_MOD "Build mod example." OFF) | |||||
option(BUILD_SNAPSHOT "Build snapshot example." OFF) | |||||
enable_testing() | |||||
add_subdirectory(test) | |||||
endif() | |||||
# | |||||
# Documentation | |||||
# | |||||
option(BUILD_DOCS "Enable building with documentation." OFF) | |||||
if(BUILD_DOCS) | |||||
find_package(Doxygen 1.8) | |||||
if(DOXYGEN_FOUND) | |||||
add_subdirectory(docs) | |||||
endif() | |||||
endif() | |||||
# | |||||
# AOB | |||||
# | |||||
add_custom_target( | |||||
entt_aob | |||||
SOURCES | |||||
appveyor.yml | |||||
AUTHORS | |||||
CONTRIBUTING.md | |||||
LICENSE | |||||
README.md | |||||
TODO | |||||
.travis.yml | |||||
) |
@ -0,0 +1,43 @@ | |||||
# Contributing | |||||
First of all, thank you very much for taking the time to contribute to the | |||||
`EnTT` framework.<br/> | |||||
How to do it mostly depends on the type of contribution: | |||||
* If you have a question, **please** ensure there isn't already an answer for | |||||
you by searching on GitHub under | |||||
[issues](https://github.com/skypjack/entt/issues). Do not forget to search | |||||
also through the closed ones. If you are unable to find a proper answer, feel | |||||
free to [open a new issue](https://github.com/skypjack/entt/issues/new). | |||||
Usually, questions are marked as such and closed in a few days. | |||||
* If you want to fix a typo in the inline documentation or in the README file, | |||||
if you want to add some new sections or if you want to help me with the | |||||
language by reviewing what I wrote so far (I'm not a native speaker after | |||||
all), **please** open a new | |||||
[pull request](https://github.com/skypjack/entt/pulls) with your changes. | |||||
* If you found a bug, **please** ensure there isn't already an answer for you by | |||||
searching on GitHub under [issues](https://github.com/skypjack/entt/issues). | |||||
If you are unable to find an open issue addressing the problem, feel free to | |||||
[open a new one](https://github.com/skypjack/entt/issues/new). **Please**, do | |||||
not forget to carefully describe how to reproduce the problem, then add all | |||||
the information about the system on which you are experiencing it and point | |||||
out the version of `EnTT` you are using (tag or commit). | |||||
* If you found a bug and you wrote a patch to fix it, open a new | |||||
[pull request](https://github.com/skypjack/entt/pulls) with your code. | |||||
**Please**, add some tests to avoid regressions in future if possible, it | |||||
would be really appreciated. Note that the `EnTT` framework has a | |||||
[coverage at 100%](https://coveralls.io/github/skypjack/entt?branch=master) | |||||
(at least it was at 100% at the time I wrote this file) and this is the reason | |||||
for which you can be confident with using it in a production environment. | |||||
* If you want to propose a new feature and you know how to code it, **please** | |||||
do not issue directly a pull request. Before to do it, | |||||
[create a new issue](https://github.com/skypjack/entt/issues/new) to discuss | |||||
your proposal. Other users could be interested in your idea and the discussion | |||||
that will follow can refine it and therefore give us a better solution. | |||||
* If you want to request a new feature, I'm available for hiring. Take a look at | |||||
[my profile](https://github.com/skypjack) and feel free to write me. |
@ -0,0 +1,21 @@ | |||||
The MIT License (MIT) | |||||
Copyright (c) 2017-2019 Michele Caini | |||||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
of this software and associated documentation files (the "Software"), to deal | |||||
in the Software without restriction, including without limitation the rights | |||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
copy of the Software, and to permit persons to whom the Software is | |||||
furnished to do so, subject to the following conditions: | |||||
The above copyright notice and this permission notice shall be included in all | |||||
copy or substantial portions of the Software. | |||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
SOFTWARE. |
@ -0,0 +1,411 @@ | |||||
![EnTT: Gaming meets modern C++](https://user-images.githubusercontent.com/1812216/42513718-ee6e98d0-8457-11e8-9baf-8d83f61a3097.png) | |||||
<!-- | |||||
@cond TURN_OFF_DOXYGEN | |||||
--> | |||||
[![GitHub version](https://badge.fury.io/gh/skypjack%2Fentt.svg)](http://badge.fury.io/gh/skypjack%2Fentt) | |||||
[![LoC](https://tokei.rs/b1/github/skypjack/entt)](https://github.com/skypjack/entt) | |||||
[![Build Status](https://travis-ci.org/skypjack/entt.svg?branch=master)](https://travis-ci.org/skypjack/entt) | |||||
[![Build status](https://ci.appveyor.com/api/projects/status/rvhaabjmghg715ck?svg=true)](https://ci.appveyor.com/project/skypjack/entt) | |||||
[![Coverage Status](https://coveralls.io/repos/github/skypjack/entt/badge.svg?branch=master)](https://coveralls.io/github/skypjack/entt?branch=master) | |||||
[![Gitter chat](https://badges.gitter.im/skypjack/entt.png)](https://gitter.im/skypjack/entt) | |||||
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/skypjack) | |||||
`EnTT` is a header-only, tiny and easy to use library for game programming and | |||||
much more written in **modern C++**, mainly known for its innovative | |||||
**entity-component-system (ECS)** model.<br/> | |||||
[Among others](https://github.com/skypjack/entt/wiki/EnTT-in-Action), it's used | |||||
in [**Minecraft**](https://minecraft.net/en-us/attribution/) by Mojang, the | |||||
[**ArcGIS Runtime SDKs**](https://developers.arcgis.com/arcgis-runtime/) by Esri | |||||
and [**The Forge**](https://github.com/ConfettiFX/The-Forge) by Confetti. Read | |||||
on to find out what it can offer you. | |||||
--- | |||||
Do you want to **keep up with changes** or do you have a **question** that | |||||
doesn't require you to open an issue?<br/> | |||||
Join the [gitter channel](https://gitter.im/skypjack/entt) and meet other users | |||||
like you. The more we are, the better for everyone. | |||||
If you use `EnTT` and you want to say thanks or support the project, please | |||||
**consider becoming a patron**: | |||||
[![Patreon](https://c5.patreon.com/external/logo/become_a_patron_button.png)](https://www.patreon.com/bePatron?c=1772573) | |||||
[Many thanks](https://skypjack.github.io/patreon/) to those who supported me and | |||||
still support me today. | |||||
# Table of Contents | |||||
* [Introduction](#introduction) | |||||
* [Code Example](#code-example) | |||||
* [Motivation](#motivation) | |||||
* [Performance](#performance) | |||||
* [Build Instructions](#build-instructions) | |||||
* [Requirements](#requirements) | |||||
* [Library](#library) | |||||
* [Documentation](#documentation) | |||||
* [Tests](#tests) | |||||
* [Packaging Tools](#packaging-tools) | |||||
* [EnTT in Action](#entt-in-action) | |||||
* [Contributors](#contributors) | |||||
* [License](#license) | |||||
* [Support](#support) | |||||
* [Patreon](#patreon) | |||||
* [Donation](#donation) | |||||
* [Hire me](#hire-me) | |||||
<!-- | |||||
@endcond TURN_OFF_DOXYGEN | |||||
--> | |||||
# Introduction | |||||
The entity-component-system (also known as _ECS_) is an architectural pattern | |||||
used mostly in game development. For further details: | |||||
* [Entity Systems Wiki](http://entity-systems.wikidot.com/) | |||||
* [Evolve Your Hierarchy](http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/) | |||||
* [ECS on Wikipedia](https://en.wikipedia.org/wiki/Entity%E2%80%93component%E2%80%93system) | |||||
This project started off as a pure entity-component system. Over time the | |||||
codebase has grown as more and more classes and functionalities were added.<br/> | |||||
Here is a brief, yet incomplete list of what it offers today: | |||||
* Statically generated integer **identifiers for types** (assigned either at | |||||
compile-time or at runtime). | |||||
* A `constexpr` utility for **human readable resource identifiers**. | |||||
* A minimal **configuration system** built using the monostate pattern. | |||||
* An incredibly fast **entity-component system** based on sparse sets, with its | |||||
own _pay for what you use_ policy to adjust performance and memory usage | |||||
according to the users' requirements. | |||||
* Views and groups to iterate entities and components and allow different access | |||||
patterns, from **perfect SoA** to fully random. | |||||
* A lot of **facilities** built on top of the entity-component system to support | |||||
the users and avoid reinventing the wheel (dependencies, snapshot, actor class | |||||
for those who aren't confident with the architecture and so on). | |||||
* The smallest and most basic implementation of a **service locator** ever seen. | |||||
* A built-in, non-intrusive and macro-free **runtime reflection system**. | |||||
* A **cooperative scheduler** for processes of any type. | |||||
* All that is needed for **resource management** (cache, loaders, handles). | |||||
* **Delegates**, **signal handlers** (with built-in support for collectors) and | |||||
a tiny **event dispatcher** for immediate and delayed events to integrate in | |||||
loops. | |||||
* A general purpose **event emitter** as a CRTP idiom based class template. | |||||
* And **much more**! Check out the | |||||
[**wiki**](https://github.com/skypjack/entt/wiki). | |||||
Consider this list a work in progress as well as the project. The whole API is | |||||
fully documented in-code for those who are brave enough to read it. | |||||
Currently, `EnTT` is tested on Linux, Microsoft Windows and OSX. It has proven | |||||
to work also on both Android and iOS.<br/> | |||||
Most likely it won't be problematic on other systems as well, but it hasn't been | |||||
sufficiently tested so far. | |||||
## Code Example | |||||
```cpp | |||||
#include <entt/entt.hpp> | |||||
#include <cstdint> | |||||
struct position { | |||||
float x; | |||||
float y; | |||||
}; | |||||
struct velocity { | |||||
float dx; | |||||
float dy; | |||||
}; | |||||
void update(entt::registry ®istry) { | |||||
auto view = registry.view<position, velocity>(); | |||||
for(auto entity: view) { | |||||
// gets only the components that are going to be used ... | |||||
auto &vel = view.get<velocity>(entity); | |||||
vel.dx = 0.; | |||||
vel.dy = 0.; | |||||
// ... | |||||
} | |||||
} | |||||
void update(std::uint64_t dt, entt::registry ®istry) { | |||||
registry.view<position, velocity>().each([dt](auto &pos, auto &vel) { | |||||
// gets all the components of the view at once ... | |||||
pos.x += vel.dx * dt; | |||||
pos.y += vel.dy * dt; | |||||
// ... | |||||
}); | |||||
} | |||||
int main() { | |||||
entt::registry registry; | |||||
std::uint64_t dt = 16; | |||||
for(auto i = 0; i < 10; ++i) { | |||||
auto entity = registry.create(); | |||||
registry.assign<position>(entity, i * 1.f, i * 1.f); | |||||
if(i % 2 == 0) { registry.assign<velocity>(entity, i * .1f, i * .1f); } | |||||
} | |||||
update(dt, registry); | |||||
update(registry); | |||||
// ... | |||||
} | |||||
``` | |||||
## Motivation | |||||
I started developing `EnTT` for the _wrong_ reason: my goal was to design an | |||||
entity-component system to beat another well known open source solution both in | |||||
terms of performance and possibly memory usage.<br/> | |||||
In the end, I did it, but it wasn't very satisfying. Actually it wasn't | |||||
satisfying at all. The fastest and nothing more, fairly little indeed. When I | |||||
realized it, I tried hard to keep intact the great performance of `EnTT` and to | |||||
add all the features I wanted to see in *my own library* at the same time. | |||||
Nowadays, `EnTT` is finally what I was looking for: still faster than its | |||||
_competitors_, lower memory usage in the average case, a really good API and an | |||||
amazing set of features. And even more, of course. | |||||
## Performance | |||||
As it stands right now, `EnTT` is just fast enough for my requirements when | |||||
compared to my first choice (it was already amazingly fast actually).<br/> | |||||
Below is a comparison between the two (both of them compiled with GCC 7.3.0 on a | |||||
Dell XPS 13 from mid 2014): | |||||
| Benchmark | EntityX (compile-time) | EnTT | | |||||
|-----------|-------------|-------------| | |||||
| Create 1M entities | 0.0147s | **0.0046s** | | |||||
| Destroy 1M entities | 0.0053s | **0.0045s** | | |||||
| 1M entities, one component | 0.0012s | **1.9e-07s** | | |||||
| 1M entities, two components | 0.0012s | **3.8e-07s** | | |||||
| 1M entities, two components<br/>Half of the entities have all the components | 0.0009s | **3.8e-07s** | | |||||
| 1M entities, two components<br/>One of the entities has all the components | 0.0008s | **1.0e-06s** | | |||||
| 1M entities, five components | 0.0010s | **7.0e-07s** | | |||||
| 1M entities, ten components | 0.0011s | **1.2e-06s** | | |||||
| 1M entities, ten components<br/>Half of the entities have all the components | 0.0010s | **1.2e-06s** | | |||||
| 1M entities, ten components<br/>One of the entities has all the components | 0.0008s | **1.2e-06s** | | |||||
| Sort 150k entities, one component<br/>Arrays are in reverse order | - | **0.0036s** | | |||||
| Sort 150k entities, enforce permutation<br/>Arrays are in reverse order | - | **0.0005s** | | |||||
| Sort 150k entities, one component<br/>Arrays are almost sorted, std::sort | - | **0.0035s** | | |||||
| Sort 150k entities, one component<br/>Arrays are almost sorted, insertion sort | - | **0.0007s** | | |||||
Note: The default version of `EntityX` (`master` branch) wasn't added to the | |||||
comparison because it's already much slower than its compile-time counterpart. | |||||
Pretty interesting results, aren't them? In fact, these benchmarks are the ones | |||||
used by `EntityX` to show _how fast it is_. To be honest, they aren't so good | |||||
and these results shouldn't be taken too seriously (indeed they are completely | |||||
unrealistic).<br/> | |||||
The proposed entity-component system is incredibly fast to iterate entities, | |||||
this is a fact. The compiler can make a lot of optimizations because of how | |||||
`EnTT` works, even more when components aren't used at all. This is exactly the | |||||
case for these benchmarks. On the other hand, if we consider real world cases, | |||||
`EnTT` is somewhere between a bit and much faster than the other solutions | |||||
around when users also access the components and not just the entities, although | |||||
it isn't as fast as reported by these benchmarks.<br/> | |||||
This is why they are completely wrong and cannot be used to evaluate any of the | |||||
entity-component-system libraries out there. | |||||
The choice to use `EnTT` should be based on its carefully designed API, its | |||||
set of features and the general performance, not because some single benchmark | |||||
shows it to be the fastest tool available. | |||||
In the future I'll likely try to get even better performance while still adding | |||||
new features, mainly for fun.<br/> | |||||
If you want to contribute and/or have suggestions, feel free to make a PR or | |||||
open an issue to discuss your idea. | |||||
# Build Instructions | |||||
## Requirements | |||||
To be able to use `EnTT`, users must provide a full-featured compiler that | |||||
supports at least C++17.<br/> | |||||
The requirements below are mandatory to compile the tests and to extract the | |||||
documentation: | |||||
* CMake version 3.2 or later. | |||||
* Doxygen version 1.8 or later. | |||||
If you are looking for a C++14 version of `EnTT`, check out the git tag `cpp14`. | |||||
## Library | |||||
`EnTT` is a header-only library. This means that including the `entt.hpp` header | |||||
is enough to include the library as a whole and use it. For those who are | |||||
interested only in the entity-component system, consider to include the sole | |||||
`entity/registry.hpp` header instead.<br/> | |||||
It's a matter of adding the following line to the top of a file: | |||||
```cpp | |||||
#include <entt/entt.hpp> | |||||
``` | |||||
Use the line below to include only the entity-component system instead: | |||||
```cpp | |||||
#include <entt/entity/registry.hpp> | |||||
``` | |||||
Then pass the proper `-I` argument to the compiler to add the `src` directory to | |||||
the include paths. | |||||
## Documentation | |||||
The documentation is based on [doxygen](http://www.doxygen.nl/). | |||||
To build it: | |||||
$ cd build | |||||
$ cmake .. -DBUILD_DOCS=ON | |||||
$ make | |||||
The API reference will be created in HTML format within the directory | |||||
`build/docs/html`. To navigate it with your favorite browser: | |||||
$ cd build | |||||
$ your_favorite_browser docs/html/index.html | |||||
<!-- | |||||
@cond TURN_OFF_DOXYGEN | |||||
--> | |||||
It's also available [online](https://skypjack.github.io/entt/) for the latest | |||||
version.<br/> | |||||
Finally, there exists a [wiki](https://github.com/skypjack/entt/wiki) dedicated | |||||
to the project where users can find all related documentation pages. | |||||
<!-- | |||||
@endcond TURN_OFF_DOXYGEN | |||||
--> | |||||
## Tests | |||||
To compile and run the tests, `EnTT` requires *googletest*.<br/> | |||||
`cmake` will download and compile the library before compiling anything else. | |||||
In order to build the tests, set the CMake option `BUILD_TESTING` to `ON`. | |||||
To build the most basic set of tests: | |||||
* `$ cd build` | |||||
* `$ cmake -DBUILD_TESTING=ON ..` | |||||
* `$ make` | |||||
* `$ make test` | |||||
Note that benchmarks are not part of this set. | |||||
# Packaging Tools | |||||
`EnTT` is available for some of the most known packaging tools. In particular: | |||||
* [`Conan`](https://bintray.com/skypjack/conan/entt%3Askypjack/_latestVersion), | |||||
the C/C++ Package Manager for Developers. | |||||
* [`Homebrew`](https://github.com/skypjack/homebrew-entt), the missing package | |||||
manager for macOS.<br/> | |||||
Available as a homebrew formula. Just type the following to install it: | |||||
``` | |||||
brew install skypjack/entt/entt | |||||
``` | |||||
* [`vcpkg`](https://github.com/Microsoft/vcpkg/tree/master/ports/entt), | |||||
Microsoft VC++ Packaging Tool. | |||||
Consider this list a work in progress and help me to make it longer. | |||||
<!-- | |||||
@cond TURN_OFF_DOXYGEN | |||||
--> | |||||
# EnTT in Action | |||||
`EnTT` is widely used in private and commercial applications. I cannot even | |||||
mention most of them because of some signatures I put on some documents time | |||||
ago. Fortunately, there are also people who took the time to implement open | |||||
source projects based on `EnTT` and did not hold back when it came to | |||||
documenting them. | |||||
[Here](https://github.com/skypjack/entt/wiki/EnTT-in-Action) you can find an | |||||
incomplete list of games, applications and articles that can be used as a | |||||
reference. | |||||
If you know of other resources out there that are about `EnTT`, feel free to | |||||
open an issue or a PR and I'll be glad to add them to the list. | |||||
# Contributors | |||||
`EnTT` was written initially as a faster alternative to other well known and | |||||
open source entity-component systems. Nowadays this library is moving its first | |||||
steps. Much more will come in the future and hopefully I'm going to work on it | |||||
for a long time.<br/> | |||||
Requests for features, PR, suggestions ad feedback are highly appreciated. | |||||
If you find you can help me and want to contribute to the project with your | |||||
experience or you do want to get part of the project for some other reasons, | |||||
feel free to contact me directly (you can find the mail in the | |||||
[profile](https://github.com/skypjack)).<br/> | |||||
I can't promise that each and every contribution will be accepted, but I can | |||||
assure that I'll do my best to take them all seriously. | |||||
If you decide to participate, please see the guidelines for | |||||
[contributing](CONTRIBUTING.md) before to create issues or pull | |||||
requests.<br/> | |||||
Take also a look at the | |||||
[contributors list](https://github.com/skypjack/entt/blob/master/AUTHORS) to | |||||
know who has participated so far. | |||||
<!-- | |||||
@endcond TURN_OFF_DOXYGEN | |||||
--> | |||||
# License | |||||
Code and documentation Copyright (c) 2017-2019 Michele Caini.<br/> | |||||
Logo Copyright (c) 2018-2019 Richard Caseres. | |||||
Code released under | |||||
[the MIT license](https://github.com/skypjack/entt/blob/master/LICENSE). | |||||
Documentation released under | |||||
[CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).<br/> | |||||
Logo released under | |||||
[CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/). | |||||
<!-- | |||||
@cond TURN_OFF_DOXYGEN | |||||
--> | |||||
# Support | |||||
## Patreon | |||||
Become a [patron](https://www.patreon.com/bePatron?c=1772573) and get access to | |||||
extra content, help me spend more time on the projects you love and create new | |||||
ones for you. Your support will help me to continue the work done so far and | |||||
make it more professional and feature-rich every day.<br/> | |||||
It takes very little to | |||||
[become a patron](https://www.patreon.com/bePatron?c=1772573) and thus help the | |||||
software you use every day. Don't miss the chance. | |||||
## Donation | |||||
Developing and maintaining `EnTT` takes some time and lots of coffee. I'd like | |||||
to add more and more functionalities in future and turn it in a full-featured | |||||
solution.<br/> | |||||
If you want to support this project, you can offer me an espresso. I'm from | |||||
Italy, we're used to turning the best coffee ever in code. If you find that | |||||
it's not enough, feel free to support me the way you prefer.<br/> | |||||
Take a look at the donation button at the top of the page for more details or | |||||
just click [here](https://www.paypal.me/skypjack). | |||||
## Hire me | |||||
If you start using `EnTT` and need help, if you want a new feature and want me | |||||
to give it the highest priority, if you have any other reason to contact me: | |||||
do not hesitate. I'm available for hiring.<br/> | |||||
Feel free to take a look at my [profile](https://github.com/skypjack) and | |||||
contact me by mail. | |||||
<!-- | |||||
@endcond TURN_OFF_DOXYGEN | |||||
--> |
@ -0,0 +1,25 @@ | |||||
* long term feature: templated generic vm | |||||
* long term feature: shared_ptr less locator | |||||
* long term feature: shared_ptr less resource cache | |||||
* custom allocators and EnTT allocator-aware in general (long term feature, I don't actually need it at the moment) - see #22 | |||||
* debugging tools (#60): the issue online already contains interesting tips on this, look at it | |||||
* runner proposal: https://en.wikipedia.org/wiki/Fork%E2%80%93join_model https://slide-rs.github.io/specs/03_dispatcher.html | |||||
* work stealing job system (see #100) | |||||
* meta: sort of meta view based on meta stuff to iterate entities, void * and meta info objects | |||||
* allow for built-in parallel each if possible | |||||
* allow to replace std:: with custom implementations | |||||
* remove runtime views, welcome reflection and what about snapshot? | |||||
* empty components model allows for shared components and prefabs unity-like | |||||
- each with entity return the shared component multiple times, one per entity that refers to it | |||||
- each components only return actual component, so shared components are returned only once | |||||
* types defined at runtime that refer to the same compile-time type (but to different pools) are possible, the library is almost there | |||||
* add take functionality, eg registry.take(entity, other); where it takes the entity and all its components from registry and move them to other | |||||
* add opaque input iterators to views and groups that return tuples <entity, T &...> (proxy), multi-pass guaranteed | |||||
* add fast lane for raw iterations, extend mt doc to describe allowed add/remove with pre-allocations on fast lanes | |||||
* review 64 bit id: user defined area + dedicated member on the registry to set it | |||||
* early out in views using bitmasks with bloom filter like access based on modulus | |||||
- standard each, use bitmask to speed up the whole thing and avoid accessing the pools to test for the page | |||||
- iterator based each with a couple of iterators passed from outside (use bitmask + has) | |||||
* stable component handle that isn't affected by reallocations | |||||
* multi component registry::remove and some others? | |||||
* reactive systems |
@ -0,0 +1,25 @@ | |||||
# can use variables like {build} and {branch} | |||||
version: 1.0.{build} | |||||
image: Visual Studio 2017 | |||||
environment: | |||||
BUILD_DIR: "%APPVEYOR_BUILD_FOLDER%\\build" | |||||
platform: | |||||
- Win32 | |||||
configuration: | |||||
- Release | |||||
before_build: | |||||
- cd %BUILD_DIR% | |||||
- cmake .. -DBUILD_TESTING=ON -DBUILD_LIB=ON -DCMAKE_CXX_FLAGS=/W1 -G"Visual Studio 15 2017" | |||||
after_build: | |||||
- ctest -C Release -j4 | |||||
build: | |||||
parallel: true | |||||
project: build/entt.sln | |||||
verbosity: minimal |
@ -0,0 +1,2 @@ | |||||
* | |||||
!.gitignore |
@ -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() |
@ -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@") |
@ -0,0 +1,19 @@ | |||||
project(cereal-download NONE) | |||||
cmake_minimum_required(VERSION 3.2) | |||||
include(ExternalProject) | |||||
ExternalProject_Add( | |||||
cereal | |||||
GIT_REPOSITORY https://github.com/USCiLab/cereal.git | |||||
GIT_TAG v1.2.2 | |||||
DOWNLOAD_DIR ${CEREAL_DEPS_DIR} | |||||
TMP_DIR ${CEREAL_DEPS_DIR}/tmp | |||||
STAMP_DIR ${CEREAL_DEPS_DIR}/stamp | |||||
SOURCE_DIR ${CEREAL_DEPS_DIR}/src | |||||
BINARY_DIR ${CEREAL_DEPS_DIR}/build | |||||
CONFIGURE_COMMAND "" | |||||
BUILD_COMMAND "" | |||||
INSTALL_COMMAND "" | |||||
TEST_COMMAND "" | |||||
) |
@ -0,0 +1,19 @@ | |||||
project(duktape-download NONE) | |||||
cmake_minimum_required(VERSION 3.2) | |||||
include(ExternalProject) | |||||
ExternalProject_Add( | |||||
duktape | |||||
GIT_REPOSITORY https://github.com/svaarala/duktape-releases.git | |||||
GIT_TAG v2.2.0 | |||||
DOWNLOAD_DIR ${DUKTAPE_DEPS_DIR} | |||||
TMP_DIR ${DUKTAPE_DEPS_DIR}/tmp | |||||
STAMP_DIR ${DUKTAPE_DEPS_DIR}/stamp | |||||
SOURCE_DIR ${DUKTAPE_DEPS_DIR}/src | |||||
BINARY_DIR ${DUKTAPE_DEPS_DIR}/build | |||||
CONFIGURE_COMMAND "" | |||||
BUILD_COMMAND "" | |||||
INSTALL_COMMAND "" | |||||
TEST_COMMAND "" | |||||
) |
@ -0,0 +1,19 @@ | |||||
project(googletest-download NONE) | |||||
cmake_minimum_required(VERSION 3.2) | |||||
include(ExternalProject) | |||||
ExternalProject_Add( | |||||
googletest | |||||
GIT_REPOSITORY https://github.com/google/googletest.git | |||||
GIT_TAG master | |||||
DOWNLOAD_DIR ${GOOGLETEST_DEPS_DIR} | |||||
TMP_DIR ${GOOGLETEST_DEPS_DIR}/tmp | |||||
STAMP_DIR ${GOOGLETEST_DEPS_DIR}/stamp | |||||
SOURCE_DIR ${GOOGLETEST_DEPS_DIR}/src | |||||
BINARY_DIR ${GOOGLETEST_DEPS_DIR}/build | |||||
CONFIGURE_COMMAND "" | |||||
BUILD_COMMAND "" | |||||
INSTALL_COMMAND "" | |||||
TEST_COMMAND "" | |||||
) |
@ -0,0 +1,11 @@ | |||||
#ifndef ENTT_CONFIG_VERSION_H | |||||
#define ENTT_CONFIG_VERSION_H | |||||
#define ENTT_VERSION "@PROJECT_VERSION@" | |||||
#define ENTT_VERSION_MAJOR @PROJECT_VERSION_MAJOR@ | |||||
#define ENTT_VERSION_MINOR @PROJECT_VERSION_MINOR@ | |||||
#define ENTT_VERSION_PATCH @PROJECT_VERSION_PATCH@ | |||||
#endif // ENTT_CONFIG_VERSION_H |
@ -0,0 +1,45 @@ | |||||
#!/usr/bin/env python | |||||
# -*- coding: utf-8 -*- | |||||
from cpt.packager import ConanMultiPackager | |||||
import os | |||||
if __name__ == "__main__": | |||||
login_username = os.getenv("CONAN_LOGIN_USERNAME") | |||||
username = os.getenv("CONAN_USERNAME") | |||||
tag_version = os.getenv("CONAN_PACKAGE_VERSION", os.getenv("TRAVIS_TAG")) | |||||
package_version = tag_version.replace("v", "") | |||||
package_name_unset = "SET-CONAN_PACKAGE_NAME-OR-CONAN_REFERENCE" | |||||
package_name = os.getenv("CONAN_PACKAGE_NAME", package_name_unset) | |||||
reference = "{}/{}".format(package_name, package_version) | |||||
channel = os.getenv("CONAN_CHANNEL", "stable") | |||||
upload = os.getenv("CONAN_UPLOAD") | |||||
stable_branch_pattern = os.getenv("CONAN_STABLE_BRANCH_PATTERN", r"v\d+\.\d+\.\d+.*") | |||||
test_folder = os.getenv("CPT_TEST_FOLDER", os.path.join("conan", "test_package")) | |||||
upload_only_when_stable = os.getenv("CONAN_UPLOAD_ONLY_WHEN_STABLE", True) | |||||
header_only = os.getenv("CONAN_HEADER_ONLY", False) | |||||
pure_c = os.getenv("CONAN_PURE_C", False) | |||||
disable_shared = os.getenv("CONAN_DISABLE_SHARED_BUILD", "False") | |||||
if disable_shared == "True" and package_name == package_name_unset: | |||||
raise Exception("CONAN_DISABLE_SHARED_BUILD: True is only supported when you define CONAN_PACKAGE_NAME") | |||||
builder = ConanMultiPackager(username=username, | |||||
reference=reference, | |||||
channel=channel, | |||||
login_username=login_username, | |||||
upload=upload, | |||||
stable_branch_pattern=stable_branch_pattern, | |||||
upload_only_when_stable=upload_only_when_stable, | |||||
test_folder=test_folder) | |||||
if header_only == "False": | |||||
builder.add_common_builds(pure_c=pure_c) | |||||
else: | |||||
builder.add() | |||||
filtered_builds = [] | |||||
for settings, options, env_vars, build_requires, reference in builder.items: | |||||
if disable_shared == "False" or not options["{}:shared".format(package_name)]: | |||||
filtered_builds.append([settings, options, env_vars, build_requires]) | |||||
builder.builds = filtered_builds | |||||
builder.run() |
@ -0,0 +1,7 @@ | |||||
#!/bin/bash | |||||
set -e | |||||
set -x | |||||
conan user | |||||
python conan/build.py |
@ -0,0 +1,6 @@ | |||||
#!/bin/bash | |||||
set -e | |||||
set -x | |||||
pip install -U conan_package_tools conan |
@ -0,0 +1,13 @@ | |||||
cmake_minimum_required(VERSION 3.7.2) | |||||
project(test_package) | |||||
set(CMAKE_VERBOSE_MAKEFILE TRUE) | |||||
set(CMAKE_CXX_STANDARD 17) | |||||
set(CMAKE_CXX_STANDARD_REQUIRED ON) | |||||
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) | |||||
conan_basic_setup() | |||||
add_executable(${PROJECT_NAME} test_package.cpp) | |||||
target_link_libraries(${PROJECT_NAME} ${CONAN_LIBS}) |
@ -0,0 +1,19 @@ | |||||
#!/usr/bin/env python | |||||
# -*- coding: utf-8 -*- | |||||
from conans import ConanFile, CMake | |||||
import os | |||||
class TestPackageConan(ConanFile): | |||||
settings = "os", "compiler", "build_type", "arch" | |||||
generators = "cmake" | |||||
def build(self): | |||||
cmake = CMake(self) | |||||
cmake.configure() | |||||
cmake.build() | |||||
def test(self): | |||||
bin_path = os.path.join("bin", "test_package") | |||||
self.run(bin_path, run_environment=True) |
@ -0,0 +1,56 @@ | |||||
#include <entt/entt.hpp> | |||||
#include <cstdint> | |||||
struct position { | |||||
float x; | |||||
float y; | |||||
}; | |||||
struct velocity { | |||||
float dx; | |||||
float dy; | |||||
}; | |||||
void update(entt::registry ®istry) { | |||||
auto view = registry.view<position, velocity>(); | |||||
for(auto entity: view) { | |||||
// gets only the components that are going to be used ... | |||||
auto &vel = view.get<velocity>(entity); | |||||
vel.dx = 0.; | |||||
vel.dy = 0.; | |||||
// ... | |||||
} | |||||
} | |||||
void update(std::uint64_t dt, entt::registry ®istry) { | |||||
registry.view<position, velocity>().each([dt](auto &pos, auto &vel) { | |||||
// gets all the components of the view at once ... | |||||
pos.x += vel.dx * dt; | |||||
pos.y += vel.dy * dt; | |||||
// ... | |||||
}); | |||||
} | |||||
int main() { | |||||
entt::registry registry; | |||||
std::uint64_t dt = 16; | |||||
for(auto i = 0; i < 10; ++i) { | |||||
auto entity = registry.create(); | |||||
registry.assign<position>(entity, i * 1.f, i * 1.f); | |||||
if(i % 2 == 0) { registry.assign<velocity>(entity, i * .1f, i * .1f); } | |||||
} | |||||
update(dt, registry); | |||||
update(registry); | |||||
// ... | |||||
return EXIT_SUCCESS; | |||||
} |
@ -0,0 +1,23 @@ | |||||
#!/usr/bin/env python | |||||
# -*- coding: utf-8 -*- | |||||
from conans import ConanFile | |||||
class EnttConan(ConanFile): | |||||
name = "entt" | |||||
description = "Gaming meets modern C++ - a fast and reliable entity-component system (ECS) and much more " | |||||
topics = ("conan," "entt", "gaming", "entity", "ecs") | |||||
url = "https://github.com/skypjack/entt" | |||||
homepage = url | |||||
author = "Michele Caini <michele.caini@gmail.com>" | |||||
license = "MIT" | |||||
exports = ["LICENSE"] | |||||
exports_sources = ["src/*"] | |||||
no_copy_source = True | |||||
def package(self): | |||||
self.copy(pattern="LICENSE", dst="licenses") | |||||
self.copy(pattern="*", dst="include", src="src", keep_path=True) | |||||
def package_id(self): | |||||
self.info.header_only() |
@ -0,0 +1,2 @@ | |||||
* | |||||
!.gitignore |
@ -0,0 +1,38 @@ | |||||
# | |||||
# Doxygen configuration (documentation) | |||||
# | |||||
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( | |||||
docs ALL | |||||
COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxy.cfg | |||||
WORKING_DIRECTORY ${EnTT_SOURCE_DIR} | |||||
VERBATIM | |||||
SOURCES doxy.in | |||||
) | |||||
install( | |||||
DIRECTORY ${DOXY_OUTPUT_DIRECTORY}/html | |||||
DESTINATION share/${PROJECT_NAME}-${PROJECT_VERSION}/ | |||||
) | |||||
add_custom_target( | |||||
docs_aob | |||||
SOURCES | |||||
dox/extra.dox | |||||
md/core.md | |||||
md/entity.md | |||||
md/faq.md | |||||
md/lib.md | |||||
md/links.md | |||||
md/locator.md | |||||
md/meta.md | |||||
md/process.md | |||||
md/resource.md | |||||
md/signal.md | |||||
) |
@ -0,0 +1,5 @@ | |||||
/** | |||||
* @namespace entt | |||||
* | |||||
* @brief `EnTT` default namespace. | |||||
*/ |
@ -0,0 +1,167 @@ | |||||
# Crash Course: core functionalities | |||||
<!-- | |||||
@cond TURN_OFF_DOXYGEN | |||||
--> | |||||
# Table of Contents | |||||
* [Introduction](#introduction) | |||||
* [Compile-time identifiers](#compile-time-identifiers) | |||||
* [Runtime identifiers](#runtime-identifiers) | |||||
* [Hashed strings](#hashed-strings) | |||||
* [Conflicts](#conflicts) | |||||
* [Monostate](#monostate) | |||||
<!-- | |||||
@endcond TURN_OFF_DOXYGEN | |||||
--> | |||||
# Introduction | |||||
`EnTT` comes with a bunch of core functionalities mostly used by the other parts | |||||
of the library itself.<br/> | |||||
Hardly users will include these features in their code, but it's worth | |||||
describing what `EnTT` offers so as not to reinvent the wheel in case of need. | |||||
# Compile-time identifiers | |||||
Sometimes it's useful to be able to give unique identifiers to types at | |||||
compile-time.<br/> | |||||
There are plenty of different solutions out there and I could have used one of | |||||
them. However, I decided to spend my time to define a compact and versatile tool | |||||
that fully embraces what the modern C++ has to offer. | |||||
The _result of my efforts_ is the `identifier` class template: | |||||
```cpp | |||||
#include <ident.hpp> | |||||
// defines the identifiers for the given types | |||||
using id = entt::identifier<a_type, another_type>; | |||||
// ... | |||||
switch(a_type_identifier) { | |||||
case id::type<a_type>: | |||||
// ... | |||||
break; | |||||
case id::type<another_type>: | |||||
// ... | |||||
break; | |||||
default: | |||||
// ... | |||||
} | |||||
``` | |||||
This is all what the class template has to offer: a `type` inline variable that | |||||
contains a numerical identifier for the given type. It can be used in any | |||||
context where constant expressions are required. | |||||
As long as the list remains unchanged, identifiers are also guaranteed to be the | |||||
same for every run. In case they have been used in a production environment and | |||||
a type has to be removed, one can just use a placeholder to left the other | |||||
identifiers unchanged: | |||||
```cpp | |||||
template<typename> struct ignore_type {}; | |||||
using id = entt::identifier< | |||||
a_type_still_valid, | |||||
ignore_type<a_type_no_longer_valid>, | |||||
another_type_still_valid | |||||
>; | |||||
``` | |||||
A bit ugly to see, but it works at least. | |||||
# Runtime identifiers | |||||
Sometimes it's useful to be able to give unique identifiers to types at | |||||
runtime.<br/> | |||||
There are plenty of different solutions out there and I could have used one of | |||||
them. In fact, I adapted the most common one to my requirements and used it | |||||
extensively within the entire library. | |||||
It's the `family` class. Here is an example of use directly from the | |||||
entity-component system: | |||||
```cpp | |||||
using component_family = entt::family<struct internal_registry_component_family>; | |||||
// ... | |||||
template<typename Component> | |||||
component_type component() const noexcept { | |||||
return component_family::type<Component>; | |||||
} | |||||
``` | |||||
This is all what a _family_ has to offer: a `type` inline variable that contains | |||||
a numerical identifier for the given type. | |||||
Please, note that identifiers aren't guaranteed to be the same for every run. | |||||
Indeed it mostly depends on the flow of execution. | |||||
# Hashed strings | |||||
A hashed string is a zero overhead unique identifier. Users can use | |||||
human-readable identifiers in the codebase while using their numeric | |||||
counterparts at runtime, thus without affecting performance.<br/> | |||||
The class has an implicit `constexpr` constructor that chews a bunch of | |||||
characters. Once created, all what one can do with it is getting back the | |||||
original string or converting it into a number.<br/> | |||||
The good part is that a hashed string can be used wherever a constant expression | |||||
is required and no _string-to-number_ conversion will take place at runtime if | |||||
used carefully. | |||||
Example of use: | |||||
```cpp | |||||
auto load(entt::hashed_string::hash_type resource) { | |||||
// uses the numeric representation of the resource to load and return it | |||||
} | |||||
auto resource = load(entt::hashed_string{"gui/background"}); | |||||
``` | |||||
There is also a _user defined literal_ dedicated to hashed strings to make them | |||||
more user-friendly: | |||||
```cpp | |||||
constexpr auto str = "text"_hs; | |||||
``` | |||||
## Conflicts | |||||
The hashed string class uses internally FNV-1a to compute the numeric | |||||
counterpart of a string. Because of the _pigeonhole principle_, conflicts are | |||||
possible. This is a fact.<br/> | |||||
There is no silver bullet to solve the problem of conflicts when dealing with | |||||
hashing functions. In this case, the best solution seemed to be to give up. | |||||
That's all.<br/> | |||||
After all, human-readable unique identifiers aren't something strictly defined | |||||
and over which users have not the control. Choosing a slightly different | |||||
identifier is probably the best solution to make the conflict disappear in this | |||||
case. | |||||
# Monostate | |||||
The monostate pattern is often presented as an alternative to a singleton based | |||||
configuration system. This is exactly its purpose in `EnTT`. Moreover, this | |||||
implementation is thread safe by design (hopefully).<br/> | |||||
Keys are represented by hashed strings, values are basic types like `int`s or | |||||
`bool`s. Values of different types can be associated to each key, even more than | |||||
one at a time. Because of this, users must pay attention to use the same type | |||||
both during an assignment and when they try to read back their data. Otherwise, | |||||
they will probably incur in unexpected results. | |||||
Example of use: | |||||
```cpp | |||||
entt::monostate<entt::hashed_string{"mykey"}>{} = true; | |||||
entt::monostate<"mykey"_hs>{} = 42; | |||||
// ... | |||||
const bool b = entt::monostate<"mykey"_hs>{}; | |||||
const int i = entt::monostate<entt::hashed_string{"mykey"}>{}; | |||||
``` |
@ -0,0 +1,55 @@ | |||||
# Frequently Asked Questions | |||||
<!-- | |||||
@cond TURN_OFF_DOXYGEN | |||||
--> | |||||
# Table of Contents | |||||
* [Introduction](#introduction) | |||||
* [FAQ](#faq) | |||||
* [Why is my debug build on Windows so slow?](#why-is-my-debug-build-on-windows-so-slow) | |||||
<!-- | |||||
@endcond TURN_OFF_DOXYGEN | |||||
--> | |||||
# Introduction | |||||
This is a constantly updated section where I'll try to put the answers to the | |||||
most frequently asked questions.<br/> | |||||
If you don't find your answer here, there are two cases: nobody has done it yet | |||||
or this section needs updating. In both cases, try to | |||||
[open a new issue](https://github.com/skypjack/entt/issues/new) or enter the | |||||
[gitter channel](https://gitter.im/skypjack/entt) and ask your question. | |||||
Probably someone already has an answer for you and we can then integrate this | |||||
part of the documentation. | |||||
# FAQ | |||||
## Why is my debug build on Windows so slow? | |||||
`EnTT` is an experimental project that I also use to keep me up-to-date with the | |||||
latest revision of the language and the standard library. For this reason, it's | |||||
likely that some classes you're working with are using standard containers under | |||||
the hood.<br/> | |||||
Unfortunately, it's known that the standard containers aren't particularly | |||||
performing in debugging (the reasons for this go beyond this document) and are | |||||
even less so on Windows apparently. Fortunately this can also be mitigated a | |||||
lot, achieving good results in many cases. | |||||
First of all, there are two things to do in a Windows project: | |||||
* Disable the [`/JMC`](https://docs.microsoft.com/cpp/build/reference/jmc) | |||||
option (_Just My Code_ debugging), available starting in Visual Studio 2017 | |||||
version 15.8. | |||||
* Set the [`_ITERATOR_DEBUG_LEVEL`](https://docs.microsoft.com/cpp/standard-library/iterator-debug-level) | |||||
macro to 0. This will disable checked iterators and iterator debugging. | |||||
Moreover, the macro `ENTT_DISABLE_ASSERT` should be defined to disable internal | |||||
checks made by `EnTT` in debug. These are asserts introduced to help the users, | |||||
but require to access to the underlying containers and therefore risk ruining | |||||
the performance in some cases. | |||||
With these changes, debug performance should increase enough for most cases. If | |||||
you want something more, you can can also switch to an optimization level `O0` | |||||
or preferably `O1`. |
@ -0,0 +1,155 @@ | |||||
# Push EnTT across boundaries | |||||
<!-- | |||||
@cond TURN_OFF_DOXYGEN | |||||
--> | |||||
# Table of Contents | |||||
* [Introduction](#introduction) | |||||
* [Named types and traits class](#named-types-and-traits-class) | |||||
* [Do not mix types](#do-not-mix-types) | |||||
* [Macros, macros everywhere](#macros-macros-everywhere) | |||||
* [Conflicts](#conflicts) | |||||
* [Allocations: the dark side of the force](#allocations-the-dark-side-of-the-force) | |||||
<!-- | |||||
@endcond TURN_OFF_DOXYGEN | |||||
--> | |||||
# Introduction | |||||
`EnTT` has historically had a limit when used across boundaries on Windows in | |||||
general and on GNU/Linux when default visibility was set to _hidden_. The | |||||
limitation is due mainly to a custom utility used to assign unique, sequential | |||||
identifiers to different types. Unfortunately, this tool is used by several core | |||||
classes (the `registry` among the others) that are thus almost unusable across | |||||
boundaries.<br/> | |||||
The reasons for that are beyond the purposes of this document. However, the good | |||||
news is that `EnTT` also offers now a way to overcome this limit and to push | |||||
things across boundaries without problems when needed. | |||||
# Named types and traits class | |||||
To allow a type to work properly across boundaries when used by a class that | |||||
requires to assign unique identifiers to types, users must specialize a class | |||||
template to literally give a compile-time name to the type itself.<br/> | |||||
The name of the class template is `name_type_traits` and the specialization must | |||||
be such that it exposes a static constexpr data member named `value` having type | |||||
either `ENTT_ID_TYPE` or `entt::hashed_string::hash_type`. Its value is the user | |||||
defined unique identifier assigned to the specific type.<br/> | |||||
Identifiers are not to be sequentially generated in this case. | |||||
As an example: | |||||
```cpp | |||||
struct my_type { /* ... */ }; | |||||
template<> | |||||
struct entt::named_type_traits<my_type> { | |||||
static constexpr auto value = "my_type"_hs; | |||||
}; | |||||
``` | |||||
Because of the rules of the language, the specialization must reside in the | |||||
global namespace or in the `entt` namespace. There is no way to change this rule | |||||
unfortunately, because it doesn't depend on the library itself. | |||||
The good aspect of this approach is that it's not intrusive at all. The other | |||||
way around was in fact forcing users to inherit all their classes from a common | |||||
base. Something to avoid, at least from my point of view.<br/> | |||||
However, despite the fact that it's not intrusive, it would be great if it was | |||||
also easier to use and a bit less error-prone. This is why a bunch of macros | |||||
exist to ease defining named types. | |||||
## Do not mix types | |||||
Someone might think that this trick is valid only for the types to push across | |||||
boundaries. This isn't how things work. In fact, the problem is more complex | |||||
than that.<br/> | |||||
As a rule of thumb, users should never mix named and non-named types. Whenever | |||||
a type is given a name, all the types must be given a name. As an example, | |||||
consider the `registry` class template: in case it is pushed across boundaries, | |||||
all the types of components should be assigned a name to avoid subtle bugs. | |||||
Indeed, this constraint can be relaxed in many cases. However, it is difficult | |||||
to define a general rule to follow that is not the most stringent, unless users | |||||
know exactly what they are doing. Therefore, I won't elaborate on giving further | |||||
details on the topic. | |||||
# Macros, macros everywhere | |||||
The library comes with a set of predefined macros to use to declare named types | |||||
or export already existing ones. In particular: | |||||
* `ENTT_NAMED_TYPE` can be used to assign a name to already existing types. This | |||||
macro must be used in the global namespace even when the types to be named are | |||||
not. | |||||
```cpp | |||||
ENTT_NAMED_TYPE(my_type) | |||||
ENTT_NAMED_TYPE(ns::another_type) | |||||
``` | |||||
* `ENTT_NAMED_STRUCT` can be used to define and export a struct at the same | |||||
time. It accepts also an optional namespace in which to define the given type. | |||||
This macro must be used in the global namespace. | |||||
```cpp | |||||
ENTT_NAMED_STRUCT(my_type, { /* struct definition */}) | |||||
ENTT_NAMED_STRUCT(ns, another_type, { /* struct definition */}) | |||||
``` | |||||
* `ENTT_NAMED_CLASS` can be used to define and export a class at the same | |||||
time. It accepts also an optional namespace in which to define the given type. | |||||
This macro must be used in the global namespace. | |||||
```cpp | |||||
ENTT_NAMED_CLASS(my_type, { /* class definition */}) | |||||
ENTT_NAMED_CLASS(ns, another_type, { /* class definition */}) | |||||
``` | |||||
Nested namespaces are supported out of the box as well in all cases. As an | |||||
example: | |||||
```cpp | |||||
ENTT_NAMED_STRUCT(nested::ns, my_type, { /* struct definition */}) | |||||
``` | |||||
These macros can be used to avoid specializing the `named_type_traits` class | |||||
template. In all cases, the name of the class is used also as a seed to generate | |||||
the compile-time unique identifier. | |||||
## Conflicts | |||||
When using macros, unique identifiers are 32/64 bit integers generated by | |||||
hashing strings during compilation. Therefore, conflicts are rare but still | |||||
possible. In case of conflicts, everything simply will get broken at runtime and | |||||
the strangest things will probably take place.<br/> | |||||
Unfortunately, there is no safe way to prevent it. If this happens, it will be | |||||
enough to give a different value to one of the conflicting types to solve the | |||||
problem. To do this, users can either assign a different name to the class or | |||||
directly define a specialization for the `named_type_traits` class template. | |||||
# Allocations: the dark side of the force | |||||
As long as `EnTT` won't support custom allocators, another problem with | |||||
allocations will remain alive instead. This is in fact easily solved, or at | |||||
least it is if one knows it. | |||||
To allow users to add types dynamically, the library makes extensive use of type | |||||
erasure techniques and dynamic allocations for pools (whether they are for | |||||
components, events or anything else). The problem occurs when, for example, a | |||||
registry is created on one side of a boundary and a pool is dynamically created | |||||
on the other side. In the best case, everything will crash at the exit, while at | |||||
worst it will do so at runtime.<br/> | |||||
To avoid problems, the pools must be generated from the same side of the | |||||
boundary where the object that owns them is also created. As an example, when | |||||
the registry is created in the main executable and used across boundaries for a | |||||
given type of component, the pool for that type must be created before passing | |||||
around the registry itself. To do this is fortunately quite easy, since it is | |||||
sufficient to invoke any of the methods that involve the given type (continuing | |||||
the example with the registry, a call to `reserve` or `size` is more than | |||||
enough). | |||||
Maybe one day some dedicated methods will be added that do nothing but create a | |||||
pool for a given type. Until now it has been preferred to keep the API cleaner | |||||
as they are not strictly necessary. |
@ -0,0 +1,93 @@ | |||||
# EnTT in Action | |||||
`EnTT` is widely used in private and commercial applications. I cannot even | |||||
mention most of them because of some signatures I put on some documents time | |||||
ago. Fortunately, there are also people who took the time to implement open | |||||
source projects based on `EnTT` and did not hold back when it came to | |||||
documenting them. | |||||
Below an incomplete list of games, applications and articles that can be used as | |||||
a reference. Where I put the word _apparently_ means that the use of `EnTT` is | |||||
documented but the authors didn't make explicit announcements or contacted me | |||||
directly. | |||||
I hope this list can grow much more in the future: | |||||
* Games: | |||||
* [Minecraft](https://minecraft.net/en-us/attribution/) by | |||||
[Mojang](https://mojang.com/): of course, **that** Minecraft, see the | |||||
open source attributions page for more details. | |||||
* [Face Smash](https://play.google.com/store/apps/details?id=com.gamee.facesmash): | |||||
a game to play with your face. | |||||
* [EnTT Pacman](https://github.com/Kerndog73/EnTT-Pacman): an example of how | |||||
to make Pacman with `EnTT`. | |||||
* [Classic Tower Defence](https://github.com/kerndog73/Classic-Tower-Defence): | |||||
a tiny little tower defence game featuring a homemade font. | |||||
[Check it out](https://indi-kernick.itch.io/classic-tower-defence). | |||||
* [The Machine](https://github.com/Kerndog73/The-Machine): a box pushing | |||||
puzzler with logic gates and other cool stuff. | |||||
[Check it out](https://indi-kernick.itch.io/the-machine-web-version). | |||||
* [EnttPong](https://github.com/reworks/EnttPong): an example of how to make | |||||
Pong with `EnTT`. | |||||
* [Randballs](https://github.com/gale93/randballs): simple `SFML` and `EnTT` | |||||
playground. | |||||
* [EnTT Tower Defense](https://github.com/Daivuk/tddod): a data oriented tower | |||||
defense example. | |||||
* [EnTT Breakout](https://github.com/vblanco20-1/entt-breakout): simple | |||||
example of a breakout game, using `SDL` and `EnTT`. | |||||
* Engines/Frameworks: | |||||
* [The Forge](https://github.com/ConfettiFX/The-Forge) by | |||||
[Confett](http://www.confettispecialfx.com/): a cross-platform rendering | |||||
framework. | |||||
* [Apparently](https://teamwisp.github.io/credits/) | |||||
[Wisp](https://teamwisp.github.io/product/) by | |||||
[Team Wisp](https://teamwisp.github.io/): an advanced real-time ray tracing | |||||
renderer built for the demands of video game artists. | |||||
* [starlight](https://github.com/DomRe/starlight): game programming framework | |||||
using `Allegro`, `Lua` and modern C++. | |||||
* [Apparently](https://github.com/JosiahWI/qub3d-libdeps) | |||||
[Qub3d](https://qub3d.org/): because blocks should be open source. | |||||
* [shiva](https://github.com/Milerius/shiva): modern C++ engine with | |||||
modularity. | |||||
* Emulators: | |||||
* [NovusCore](https://github.com/novuscore/NovusCore): A modern take on World | |||||
of Warcraft emulation. | |||||
* Articles and blog posts | |||||
* [Some posts](https://skypjack.github.io/tags/#entt) on my personal | |||||
[blog](https://skypjack.github.io/) are about `EnTT`, for those who want to | |||||
know **more** on this project. | |||||
* [Space Battle: Huge edition](http://victor.madtriangles.com/code%20experiment/2018/06/11/post-ecs-battle-huge.html): | |||||
huge space battle built entirely from scratch. | |||||
* [Space Battle](https://github.com/vblanco20-1/ECS_SpaceBattle): huge space | |||||
battle built on `UE4`. | |||||
* [Experimenting with ECS in UE4](http://victor.madtriangles.com/code%20experiment/2018/03/25/post-ue4-ecs-battle.html): | |||||
interesting article about `UE4` and `EnTT`. | |||||
* [Implementing ECS architecture in UE4](https://forums.unrealengine.com/development-discussion/c-gameplay-programming/1449913-implementing-ecs-architecture-in-ue4-giant-space-battle): | |||||
giant space battle. | |||||
* [Conan Adventures (SFML and EnTT in C++)](https://leinnan.github.io/blog/conan-adventuressfml-and-entt-in-c.html): | |||||
create projects in modern C++ using `SFML`, `EnTT`, `Conan` and `CMake`. | |||||
* Any Other Business: | |||||
* The [ArcGIS Runtime SDKs](https://developers.arcgis.com/arcgis-runtime/) | |||||
by [Esri](https://www.esri.com/): they use `EnTT` for the internal ECS and | |||||
the cross platform C++ rendering engine. The SDKs are utilized by a lot of | |||||
enterprise custom apps, as well as by Esri for its own public applications | |||||
such as | |||||
[Explorer](https://play.google.com/store/apps/details?id=com.esri.explorer), | |||||
[Collector](https://play.google.com/store/apps/details?id=com.esri.arcgis.collector) | |||||
and | |||||
[Navigator](https://play.google.com/store/apps/details?id=com.esri.navigator). | |||||
* [Apparently](https://www.linkedin.com/in/skypjack/) | |||||
[NIO](https://www.nio.io/): there was a collaboration to make some changes | |||||
to `EnTT`, at the time used for internal projects. | |||||
* [MatchOneEntt](https://github.com/mhaemmerle/MatchOneEntt): port of | |||||
[Match One](https://github.com/sschmid/Match-One) for `Entitas-CSharp`. | |||||
* GitHub contains also | |||||
[many other examples](https://github.com/search?o=desc&q=%22skypjack%2Fentt%22&s=indexed&type=Code) | |||||
of use of `EnTT` from which to take inspiration if interested. | |||||
If you know of other resources out there that are about `EnTT`, feel free to | |||||
open an issue or a PR and I'll be glad to add them to this page. |
@ -0,0 +1,75 @@ | |||||
# Crash Course: service locator | |||||
<!-- | |||||
@cond TURN_OFF_DOXYGEN | |||||
--> | |||||
# Table of Contents | |||||
* [Introduction](#introduction) | |||||
* [Service locator](#service-locator) | |||||
<!-- | |||||
@endcond TURN_OFF_DOXYGEN | |||||
--> | |||||
# Introduction | |||||
Usually service locators are tightly bound to the services they expose and it's | |||||
hard to define a general purpose solution. This template based implementation | |||||
tries to fill the gap and to get rid of the burden of defining a different | |||||
specific locator for each application.<br/> | |||||
This class is tiny, partially unsafe and thus risky to use. Moreover it doesn't | |||||
fit probably most of the scenarios in which a service locator is required. Look | |||||
at it as a small tool that can sometimes be useful if users know how to handle | |||||
it. | |||||
# Service locator | |||||
The API is straightforward. The basic idea is that services are implemented by | |||||
means of interfaces and rely on polymorphism.<br/> | |||||
The locator is instantiated with the base type of the service if any and a | |||||
concrete implementation is provided along with all the parameters required to | |||||
initialize it. As an example: | |||||
```cpp | |||||
// the service has no base type, a locator is used to treat it as a kind of singleton | |||||
entt::service_locator<my_service>::set(params...); | |||||
// sets up an opaque service | |||||
entt::service_locator<audio_interface>::set<audio_implementation>(params...); | |||||
// resets (destroys) the service | |||||
entt::service_locator<audio_interface>::reset(); | |||||
``` | |||||
The locator can also be queried to know if an active service is currently set | |||||
and to retrieve it if necessary (either as a pointer or as a reference): | |||||
```cpp | |||||
// no service currently set | |||||
auto empty = entt::service_locator<audio_interface>::empty(); | |||||
// gets a (possibly empty) shared pointer to the service ... | |||||
std::shared_ptr<audio_interface> ptr = entt::service_locator<audio_interface>::get(); | |||||
// ... or a reference, but it's undefined behaviour if the service isn't set yet | |||||
audio_interface &ref = entt::service_locator<audio_interface>::ref(); | |||||
``` | |||||
A common use is to wrap the different locators in a container class, creating | |||||
aliases for the various services: | |||||
```cpp | |||||
struct locator { | |||||
using camera = entt::service_locator<camera_interface>; | |||||
using audio = entt::service_locator<audio_interface>; | |||||
// ... | |||||
}; | |||||
// ... | |||||
void init() { | |||||
locator::camera::set<camera_null>(); | |||||
locator::audio::set<audio_implementation>(params...); | |||||
// ... | |||||
} | |||||
``` |
@ -0,0 +1,414 @@ | |||||
# Crash Course: reflection system | |||||
<!-- | |||||
@cond TURN_OFF_DOXYGEN | |||||
--> | |||||
# Table of Contents | |||||
* [Introduction](#introduction) | |||||
* [Reflection in a nutshell](#reflection-in-a-nutshell) | |||||
* [Any as in any type](#any-as-in-any-type) | |||||
* [Enjoy the runtime](#enjoy-the-runtime) | |||||
* [Named constants and enums](#named-constants-and-enums) | |||||
* [Properties and meta objects](#properties-and-meta-objects) | |||||
* [Unregister types](#unregister-types) | |||||
<!-- | |||||
@endcond TURN_OFF_DOXYGEN | |||||
--> | |||||
# Introduction | |||||
Reflection (or rather, its lack) is a trending topic in the C++ world and, in | |||||
the specific case of `EnTT`, a tool that can unlock a lot of other features. I | |||||
looked for a third-party library that met my needs on the subject, but I always | |||||
came across some details that I didn't like: macros, being intrusive, too many | |||||
allocations. In one word: unsatisfactory.<br/> | |||||
I finally decided to write a built-in, non-intrusive and macro-free runtime | |||||
reflection system for `EnTT`. Maybe I didn't do better than others or maybe yes, | |||||
time will tell me, but at least I can model this tool around the library to | |||||
which it belongs and not vice versa. | |||||
# Reflection in a nutshell | |||||
Reflection always starts from real types (users cannot reflect imaginary types | |||||
and it would not make much sense, we wouldn't be talking about reflection | |||||
anymore).<br/> | |||||
To _reflect_ a type, the library provides the `reflect` function: | |||||
```cpp | |||||
auto factory = entt::reflect<my_type>("reflected_type"); | |||||
``` | |||||
It accepts the type to reflect as a template parameter and an optional name as | |||||
an argument. Names are important because users can retrieve meta types at | |||||
runtime by searching for them by name. However, there are cases in which users | |||||
can be interested in adding features to a reflected type so that the reflection | |||||
system can use it correctly under the hood, but they don't want to allow | |||||
searching the type by name.<br/> | |||||
In both cases, the returned value is a factory object to use to continue | |||||
building the meta type. | |||||
A factory is such that all its member functions returns the factory itself. | |||||
It can be used to extend the reflected type and add the following: | |||||
* _Constructors_. Actual constructors can be assigned to a reflected type by | |||||
specifying their list of arguments. Free functions (namely, factories) can be | |||||
used as well, as long as the return type is the expected one. From a client's | |||||
point of view, nothing changes if a constructor is a free function or an | |||||
actual constructor.<br/> | |||||
Use the `ctor` member function for this purpose: | |||||
```cpp | |||||
entt::reflect<my_type>("reflected").ctor<int, char>().ctor<&factory>(); | |||||
``` | |||||
* _Destructors_. Free functions can be set as destructors of reflected types. | |||||
The purpose is to give users the ability to free up resources that require | |||||
special treatment before an object is actually destroyed.<br/> | |||||
Use the `dtor` member function for this purpose: | |||||
```cpp | |||||
entt::reflect<my_type>("reflected").dtor<&destroy>(); | |||||
``` | |||||
* _Data members_. Both real data members of the underlying type and static and | |||||
global variables, as well as constants of any kind, can be attached to a meta | |||||
type. From a client's point of view, all the variables associated with the | |||||
reflected type will appear as if they were part of the type itself.<br/> | |||||
Use the `data` member function for this purpose: | |||||
```cpp | |||||
entt::reflect<my_type>("reflected") | |||||
.data<&my_type::static_variable>("static") | |||||
.data<&my_type::data_member>("member") | |||||
.data<&global_variable>("global"); | |||||
``` | |||||
This function requires as an argument the name to give to the meta data once | |||||
created. Users can then access meta data at runtime by searching for them by | |||||
name.<br/> | |||||
Data members can be set also by means of a couple of functions, namely a | |||||
setter and a getter. Setters and getters can be either free functions, member | |||||
functions or mixed ones, as long as they respect the required signatures.<br/> | |||||
Refer to the inline documentation for all the details. | |||||
* _Member functions_. Both real member functions of the underlying type and free | |||||
functions can be attached to a meta type. From a client's point of view, all | |||||
the functions associated with the reflected type will appear as if they were | |||||
part of the type itself.<br/> | |||||
Use the `func` member function for this purpose: | |||||
```cpp | |||||
entt::reflect<my_type>("reflected") | |||||
.func<&my_type::static_function>("static") | |||||
.func<&my_type::member_function>("member") | |||||
.func<&free_function>("free"); | |||||
``` | |||||
This function requires as an argument the name to give to the meta function | |||||
once created. Users can then access meta functions at runtime by searching for | |||||
them by name. | |||||
* _Base classes_. A base class is such that the underlying type is actually | |||||
derived from it. In this case, the reflection system tracks the relationship | |||||
and allows for implicit casts at runtime when required.<br/> | |||||
Use the `base` member function for this purpose: | |||||
```cpp | |||||
entt::reflect<derived_type>("derived").base<base_type>(); | |||||
``` | |||||
From now on, wherever a `base_type` is required, an instance of `derived_type` | |||||
will also be accepted. | |||||
* _Conversion functions_. Actual types can be converted, this is a fact. Just | |||||
think of the relationship between a `double` and an `int` to see it. Similar | |||||
to bases, conversion functions allow users to define conversions that will be | |||||
implicitly performed by the reflection system when required.<br/> | |||||
Use the `conv` member function for this purpose: | |||||
```cpp | |||||
entt::reflect<double>().conv<int>(); | |||||
``` | |||||
That's all, everything users need to create meta types and enjoy the reflection | |||||
system. At first glance it may not seem that much, but users usually learn to | |||||
appreciate it over time.<br/> | |||||
Also, do not forget what these few lines hide under the hood: a built-in, | |||||
non-intrusive and macro-free system for reflection in C++. Features that are | |||||
definitely worth the price, at least for me. | |||||
# Any as in any type | |||||
The reflection system comes with its own meta any type. It may seem redundant | |||||
since C++17 introduced `std::any`, but it is not.<br/> | |||||
In fact, the _type_ returned by an `std::any` is a const reference to an | |||||
`std::type_info`, an implementation defined class that's not something everyone | |||||
wants to see in a software. Furthermore, the class `std::type_info` suffers from | |||||
some design flaws and there is even no way to _convert_ an `std::type_info` into | |||||
a meta type, thus linking the two worlds. | |||||
A meta any object provides an API similar to that of its most famous counterpart | |||||
and serves the same purpose of being an opaque container for any type of | |||||
value.<br/> | |||||
It minimizes the allocations required, which are almost absent thanks to _SBO_ | |||||
techniques. In fact, unless users deal with _fat types_ and create instances of | |||||
them though the reflection system, allocations are at zero. | |||||
A meta any object can be created by any other object or as an empty container | |||||
to initialize later: | |||||
```cpp | |||||
// a meta any object that contains an int | |||||
entt::meta_any any{0}; | |||||
// an empty meta any object | |||||
entt::meta_any empty{}; | |||||
``` | |||||
It can be constructed or assigned by copy and move and it takes the burden of | |||||
destroying the contained object when required.<br/> | |||||
A meta any object has a `type` member function that returns the meta type of the | |||||
contained value, if any. The member functions `can_cast` and `can_convert` are | |||||
used to know if the underlying object has a given type as a base or if it can be | |||||
converted implicitly to it. Similarly, `cast` and `convert` do what they promise | |||||
and return the expected value. | |||||
# Enjoy the runtime | |||||
Once the web of reflected types has been constructed, it's a matter of using it | |||||
at runtime where required.<br/> | |||||
All this has the great merit that, unlike the vast majority of the things | |||||
present in this library and closely linked to the compile-time, the reflection | |||||
system stands in fact as a non-intrusive tool for the runtime. | |||||
To search for a reflected type there are two options: by type or by name. In | |||||
both cases, the search can be done by means of the `resolve` function: | |||||
```cpp | |||||
// search for a reflected type by type | |||||
auto by_type = entt::resolve<my_type>(); | |||||
// search for a reflected type by name | |||||
auto by_name = entt::resolve("reflected_type"); | |||||
``` | |||||
There exits also a third overload of the `resolve` function to use to iterate | |||||
all the reflected types at once: | |||||
```cpp | |||||
resolve([](auto type) { | |||||
// ... | |||||
}); | |||||
``` | |||||
In all cases, the returned value is an instance of `meta_type`. This type of | |||||
objects offer an API to know the _runtime name_ of the type, to iterate all the | |||||
meta objects associated with them and even to build or destroy instances of the | |||||
underlying type.<br/> | |||||
Refer to the inline documentation for all the details. | |||||
The meta objects that compose a meta type are accessed in the following ways: | |||||
* _Meta constructors_. They are accessed by types of arguments: | |||||
```cpp | |||||
auto ctor = entt::resolve<my_type>().ctor<int, char>(); | |||||
``` | |||||
The returned type is `meta_ctor` and may be invalid if there is no constructor | |||||
that accepts the supplied arguments or at least some types from which they are | |||||
derived or to which they can be converted.<br/> | |||||
A meta constructor offers an API to know the number of arguments, the expected | |||||
meta types and to invoke it, therefore to construct a new instance of the | |||||
underlying type. | |||||
* _Meta destructor_. It's returned by a dedicated function: | |||||
```cpp | |||||
auto dtor = entt::resolve<my_type>().dtor(); | |||||
``` | |||||
The returned type is `meta_dtor` and may be invalid if there is no custom | |||||
destructor set for the given meta type.<br/> | |||||
All what a meta destructor has to offer is a way to invoke it on a given | |||||
instance. Be aware that the result may not be what is expected. | |||||
* _Meta data_. They are accessed by name: | |||||
```cpp | |||||
auto data = entt::resolve<my_type>().data("member"); | |||||
``` | |||||
The returned type is `meta_data` and may be invalid if there is no meta data | |||||
object associated with the given name.<br/> | |||||
A meta data object offers an API to query the underlying type (ie to know if | |||||
it's a const or a static one), to get the meta type of the variable and to set | |||||
or get the contained value. | |||||
* _Meta functions_. They are accessed by name: | |||||
```cpp | |||||
auto func = entt::resolve<my_type>().func("member"); | |||||
``` | |||||
The returned type is `meta_func` and may be invalid if there is no meta | |||||
function object associated with the given name.<br/> | |||||
A meta function object offers an API to query the underlying type (ie to know | |||||
if it's a const or a static function), to know the number of arguments, the | |||||
meta return type and the meta types of the parameters. In addition, a meta | |||||
function object can be used to invoke the underlying function and then get the | |||||
return value in the form of meta any object. | |||||
* _Meta bases_. They are accessed through the name of the base types: | |||||
```cpp | |||||
auto base = entt::resolve<derived_type>().base("base"); | |||||
``` | |||||
The returned type is `meta_base` and may be invalid if there is no meta base | |||||
object associated with the given name.<br/> | |||||
Meta bases aren't meant to be used directly, even though they are freely | |||||
accessible. They expose only a few methods to use to know the meta type of the | |||||
base class and to convert a raw pointer between types. | |||||
* _Meta conversion functions_. They are accessed by type: | |||||
```cpp | |||||
auto conv = entt::resolve<double>().conv<int>(); | |||||
``` | |||||
The returned type is `meta_conv` and may be invalid if there is no meta | |||||
conversion function associated with the given type.<br/> | |||||
The meta conversion functions are as thin as the meta bases and with a very | |||||
similar interface. The sole difference is that they return a newly created | |||||
instance wrapped in a meta any object when they convert between different | |||||
types. | |||||
All the objects thus obtained as well as the meta types can be explicitly | |||||
converted to a boolean value to check if they are valid: | |||||
```cpp | |||||
auto func = entt::resolve<my_type>().func("member"); | |||||
if(func) { | |||||
// ... | |||||
} | |||||
``` | |||||
Furthermore, all meta objects with the exception of meta destructors can be | |||||
iterated through an overload that accepts a callback through which to return | |||||
them. As an example: | |||||
```cpp | |||||
entt::resolve<my_type>().data([](auto data) { | |||||
// ... | |||||
}); | |||||
``` | |||||
A meta type can also be used to `construct` or `destroy` actual instances of the | |||||
underlying type.<br/> | |||||
In particular, the `construct` member function accepts a variable number of | |||||
arguments and searches for a match. It returns a `meta_any` object that may or | |||||
may not be initialized, depending on whether a suitable constructor has been | |||||
found or not. On the other side, the `destroy` member function accepts instances | |||||
of `meta_any` as well as actual objects by reference and invokes the registered | |||||
destructor if any or a default one.<br/> | |||||
Be aware that the result of a call to `destroy` may not be what is expected. | |||||
Meta types and meta objects in general contain much more than what is said: a | |||||
plethora of functions in addition to those listed whose purposes and uses go | |||||
unfortunately beyond the scope of this document.<br/> | |||||
I invite anyone interested in the subject to look at the code, experiment and | |||||
read the official documentation to get the best out of this powerful tool. | |||||
# Named constants and enums | |||||
A special mention should be made for constant values and enums. It wouldn't be | |||||
necessary, but it will help distracted readers. | |||||
As mentioned, the `data` member function can be used to reflect constants of any | |||||
type among the other things.<br/> | |||||
This allows users to create meta types for enums that will work exactly like any | |||||
other meta type built from a class. Similarly, arithmetic types can be enriched | |||||
with constants of special meaning where required.<br/> | |||||
Personally, I find it very useful not to export what is the difference between | |||||
enums and classes in C++ directly in the space of the reflected types. | |||||
All the values thus exported will appear to users as if they were constant data | |||||
members of the reflected types. | |||||
Exporting constant values or elements from an enum is as simple as ever: | |||||
```cpp | |||||
entt::reflect<my_enum>() | |||||
.data<my_enum::a_value>("a_value") | |||||
.data<my_enum::another_value>("another_value"); | |||||
entt::reflect<int>().data<2048>("max_int"); | |||||
``` | |||||
It goes without saying that accessing them is trivial as well. It's a matter of | |||||
doing the following, as with any other data member of a meta type: | |||||
```cpp | |||||
auto value = entt::resolve<my_enum>().data("a_value").get({}).cast<my_enum>(); | |||||
auto max = entt::resolve<int>().data("max_int").get({}).cast<int>(); | |||||
``` | |||||
As a side note, remember that all this happens behind the scenes without any | |||||
allocation because of the small object optimization performed by the meta any | |||||
class. | |||||
# Properties and meta objects | |||||
Sometimes (ie when it comes to creating an editor) it might be useful to be able | |||||
to attach properties to the meta objects created. Fortunately, this is possible | |||||
for most of them.<br/> | |||||
To attach a property to a meta object, no matter what as long as it supports | |||||
properties, it is sufficient to provide an object at the time of construction | |||||
such that `std::get<0>` and `std::get<1>` are valid for it. In other terms, the | |||||
properties are nothing more than key/value pairs users can put in an | |||||
`std::pair`. As an example: | |||||
```cpp | |||||
entt::reflect<my_type>("reflected", std::make_pair("tooltip"_hs, "message")); | |||||
``` | |||||
The meta objects that support properties offer then a couple of member functions | |||||
named `prop` to iterate them at once and to search a specific property by key: | |||||
```cpp | |||||
// iterate all the properties of a meta type | |||||
entt::resolve<my_type>().prop([](auto prop) { | |||||
// ... | |||||
}); | |||||
// search for a given property by name | |||||
auto prop = entt::resolve<my_type>().prop("tooltip"_hs); | |||||
``` | |||||
Meta properties are objects having a fairly poor interface, all in all. They | |||||
only provide the `key` and the `value` member functions to be used to retrieve | |||||
the key and the value contained in the form of meta any objects, respectively. | |||||
# Unregister types | |||||
A type registered with the reflection system can also be unregistered. This | |||||
means unregistering all its data members, member functions, conversion functions | |||||
and so on. However, the base classes won't be unregistered, since they don't | |||||
necessarily depend on it. Similarly, implicitly generated types (as an example, | |||||
the meta types implicitly generated for function parameters when needed) won't | |||||
be unregistered. | |||||
To unregister a type, users can use the `unregister` function from the global | |||||
namespace: | |||||
```cpp | |||||
entt::unregister<my_type>(); | |||||
``` | |||||
This function returns a boolean value that is true if the type is actually | |||||
registered with the reflection system, false otherwise.<br/> | |||||
The type can be re-registered later with a completely different name and form. |
@ -0,0 +1,208 @@ | |||||
# Crash Course: cooperative scheduler | |||||
<!-- | |||||
@cond TURN_OFF_DOXYGEN | |||||
--> | |||||
# Table of Contents | |||||
* [Introduction](#introduction) | |||||
* [The process](#the-process) | |||||
* [Adaptor](#adaptor) | |||||
* [The scheduler](#the-scheduler) | |||||
<!-- | |||||
@endcond TURN_OFF_DOXYGEN | |||||
--> | |||||
# Introduction | |||||
Sometimes processes are a useful tool to work around the strict definition of a | |||||
system and introduce logic in a different way, usually without resorting to the | |||||
introduction of other components. | |||||
`EnTT` offers a minimal support to this paradigm by introducing a few classes | |||||
that users can use to define and execute cooperative processes. | |||||
# The process | |||||
A typical process must inherit from the `process` class template that stays true | |||||
to the CRTP idiom. Moreover, derived classes must specify what's the intended | |||||
type for elapsed times. | |||||
A process should expose publicly the following member functions whether | |||||
required (note that it isn't required to define a function unless the derived | |||||
class wants to _override_ the default behavior): | |||||
* `void update(Delta, void *);` | |||||
It's invoked once per tick until a process is explicitly aborted or it | |||||
terminates either with or without errors. Even though it's not mandatory to | |||||
declare this member function, as a rule of thumb each process should at | |||||
least define it to work properly. The `void *` parameter is an opaque pointer | |||||
to user data (if any) forwarded directly to the process during an update. | |||||
* `void init();` | |||||
It's invoked when the process joins the running queue of a scheduler. This | |||||
happens as soon as it's attached to the scheduler if the process is a top | |||||
level one, otherwise when it replaces its parent if the process is a | |||||
continuation. | |||||
* `void succeeded();` | |||||
It's invoked in case of success, immediately after an update and during the | |||||
same tick. | |||||
* `void failed();` | |||||
It's invoked in case of errors, immediately after an update and during the | |||||
same tick. | |||||
* `void aborted();` | |||||
It's invoked only if a process is explicitly aborted. There is no guarantee | |||||
that it executes in the same tick, this depends solely on whether the | |||||
process is aborted immediately or not. | |||||
Derived classes can also change the internal state of a process by invoking | |||||
`succeed` and `fail`, as well as `pause` and `unpause` the process itself. All | |||||
these are protected member functions made available to be able to manage the | |||||
life cycle of a process from a derived class. | |||||
Here is a minimal example for the sake of curiosity: | |||||
```cpp | |||||
struct my_process: entt::process<my_process, std::uint32_t> { | |||||
using delta_type = std::uint32_t; | |||||
void update(delta_type delta, void *) { | |||||
remaining -= std::min(remaining, delta); | |||||
// ... | |||||
if(!remaining) { | |||||
succeed(); | |||||
} | |||||
} | |||||
private: | |||||
delta_type remaining{1000u}; | |||||
}; | |||||
``` | |||||
## Adaptor | |||||
Lambdas and functors can't be used directly with a scheduler for they are not | |||||
properly defined processes with managed life cycles.<br/> | |||||
This class helps in filling the gap and turning lambdas and functors into | |||||
full featured processes usable by a scheduler. | |||||
The function call operator has a signature similar to the one of the `update` | |||||
function of a process but for the fact that it receives two extra arguments to | |||||
call whenever a process is terminated with success or with an error: | |||||
```cpp | |||||
void(Delta delta, void *data, auto succeed, auto fail); | |||||
``` | |||||
Parameters have the following meaning: | |||||
* `delta` is the elapsed time. | |||||
* `data` is an opaque pointer to user data if any, `nullptr` otherwise. | |||||
* `succeed` is a function to call when a process terminates with success. | |||||
* `fail` is a function to call when a process terminates with errors. | |||||
Both `succeed` and `fail` accept no parameters at all. | |||||
Note that usually users shouldn't worry about creating adaptors at all. A | |||||
scheduler creates them internally each and every time a lambda or a functor is | |||||
used as a process. | |||||
# The scheduler | |||||
A cooperative scheduler runs different processes and helps managing their life | |||||
cycles. | |||||
Each process is invoked once per tick. If it terminates, it's removed | |||||
automatically from the scheduler and it's never invoked again. Otherwise it's | |||||
a good candidate to run one more time the next tick.<br/> | |||||
A process can also have a child. In this case, the parent process is replaced | |||||
with its child when it terminates and only if it returns with success. In case | |||||
of errors, both the parent process and its child are discarded. This way, it's | |||||
easy to create chain of processes to run sequentially. | |||||
Using a scheduler is straightforward. To create it, users must provide only the | |||||
type for the elapsed times and no arguments at all: | |||||
```cpp | |||||
entt::scheduler<std::uint32_t> scheduler; | |||||
``` | |||||
It has member functions to query its internal data structures, like `empty` or | |||||
`size`, as well as a `clear` utility to reset it to a clean state: | |||||
```cpp | |||||
// checks if there are processes still running | |||||
const auto empty = scheduler.empty(); | |||||
// gets the number of processes still running | |||||
entt::scheduler<std::uint32_t>::size_type size = scheduler.size(); | |||||
// resets the scheduler to its initial state and discards all the processes | |||||
scheduler.clear(); | |||||
``` | |||||
To attach a process to a scheduler there are mainly two ways: | |||||
* If the process inherits from the `process` class template, it's enough to | |||||
indicate its type and submit all the parameters required to construct it to | |||||
the `attach` member function: | |||||
```cpp | |||||
scheduler.attach<my_process>("foobar"); | |||||
``` | |||||
* Otherwise, in case of a lambda or a functor, it's enough to provide an | |||||
instance of the class to the `attach` member function: | |||||
```cpp | |||||
scheduler.attach([](auto...){ /* ... */ }); | |||||
``` | |||||
In both cases, the return value is an opaque object that offers a `then` member | |||||
function to use to create chains of processes to run sequentially.<br/> | |||||
As a minimal example of use: | |||||
```cpp | |||||
// schedules a task in the form of a lambda function | |||||
scheduler.attach([](auto delta, void *, auto succeed, auto fail) { | |||||
// ... | |||||
}) | |||||
// appends a child in the form of another lambda function | |||||
.then([](auto delta, void *, auto succeed, auto fail) { | |||||
// ... | |||||
}) | |||||
// appends a child in the form of a process class | |||||
.then<my_process>(); | |||||
``` | |||||
To update a scheduler and therefore all its processes, the `update` member | |||||
function is the way to go: | |||||
```cpp | |||||
// updates all the processes, no user data are provided | |||||
scheduler.update(delta); | |||||
// updates all the processes and provides them with custom data | |||||
scheduler.update(delta, &data); | |||||
``` | |||||
In addition to these functions, the scheduler offers an `abort` member function | |||||
that can be used to discard all the running processes at once: | |||||
```cpp | |||||
// aborts all the processes abruptly ... | |||||
scheduler.abort(true); | |||||
// ... or gracefully during the next tick | |||||
scheduler.abort(); | |||||
``` |
@ -0,0 +1,238 @@ | |||||
# Crash Course: resource management | |||||
<!-- | |||||
@cond TURN_OFF_DOXYGEN | |||||
--> | |||||
# Table of Contents | |||||
* [Introduction](#introduction) | |||||
* [The resource, the loader and the cache](#the-resource-the-loader-and-the-cache) | |||||
<!-- | |||||
@endcond TURN_OFF_DOXYGEN | |||||
--> | |||||
# Introduction | |||||
Resource management is usually one of the most critical part of a software like | |||||
a game. Solutions are often tuned to the particular application. There exist | |||||
several approaches and all of them are perfectly fine as long as they fit the | |||||
requirements of the piece of software in which they are used.<br/> | |||||
Examples are loading everything on start, loading on request, predictive | |||||
loading, and so on. | |||||
`EnTT` doesn't pretend to offer a _one-fits-all_ solution for the different | |||||
cases. Instead, it offers a minimal and perhaps trivial cache that can be useful | |||||
most of the time during prototyping and sometimes even in a production | |||||
environment.<br/> | |||||
For those interested in the subject, the plan is to improve it considerably over | |||||
time in terms of performance, memory usage and functionalities. Hoping to make | |||||
it, of course, one step at a time. | |||||
# The resource, the loader and the cache | |||||
There are three main actors in the model: the resource, the loader and the | |||||
cache. | |||||
The _resource_ is whatever users want it to be. An image, a video, an audio, | |||||
whatever. There are no limits.<br/> | |||||
As a minimal example: | |||||
```cpp | |||||
struct my_resource { const int value; }; | |||||
``` | |||||
A _loader_ is a class the aim of which is to load a specific resource. It has to | |||||
inherit directly from the dedicated base class as in the following example: | |||||
```cpp | |||||
struct my_loader final: entt::resource_loader<my_loader, my_resource> { | |||||
// ... | |||||
}; | |||||
``` | |||||
Where `my_resource` is the type of resources it creates.<br/> | |||||
A resource loader must also expose a public const member function named `load` | |||||
that accepts a variable number of arguments and returns a shared pointer to a | |||||
resource.<br/> | |||||
As an example: | |||||
```cpp | |||||
struct my_loader: entt::resource_loader<my_loader, my_resource> { | |||||
std::shared_ptr<my_resource> load(int value) const { | |||||
// ... | |||||
return std::shared_ptr<my_resource>(new my_resource{ value }); | |||||
} | |||||
}; | |||||
``` | |||||
In general, resource loaders should not have a state or retain data of any type. | |||||
They should let the cache manage their resources instead.<br/> | |||||
As a side note, base class and CRTP idiom aren't strictly required with the | |||||
current implementation. One could argue that a cache can easily work with | |||||
loaders of any type. However, future changes won't be breaking ones by forcing | |||||
the use of a base class today and that's why the model is already in its place. | |||||
Finally, a cache is a specialization of a class template tailored to a specific | |||||
resource: | |||||
```cpp | |||||
using my_resource_cache = entt::resource_cache<my_resource>; | |||||
// ... | |||||
my_resource_cache cache{}; | |||||
``` | |||||
The idea is to create different caches for different types of resources and to | |||||
manage each one independently in the most appropriate way.<br/> | |||||
As a (very) trivial example, audio tracks can survive in most of the scenes of | |||||
an application while meshes can be associated with a single scene and then | |||||
discarded when users leave it. | |||||
A cache offers a set of basic functionalities to query its internal state and to | |||||
_organize_ it: | |||||
```cpp | |||||
// gets the number of resources managed by a cache | |||||
const auto size = cache.size(); | |||||
// checks if a cache contains at least a valid resource | |||||
const auto empty = cache.empty(); | |||||
// clears a cache and discards its content | |||||
cache.clear(); | |||||
``` | |||||
Besides these member functions, a cache contains what is needed to load, use and | |||||
discard resources of the given type.<br/> | |||||
Before to explore this part of the interface, it makes sense to mention how | |||||
resources are identified. The type of the identifiers to use is defined as: | |||||
```cpp | |||||
entt::resource_cache<resource>::resource_type | |||||
``` | |||||
Where `resource_type` is an alias for `entt::hashed_string::hash_type`. | |||||
Therefore, resource identifiers are created explicitly as in the following | |||||
example: | |||||
```cpp | |||||
constexpr auto identifier = entt::resource_cache<resource>::resource_type{"my/resource/identifier"_hs}; | |||||
// this is equivalent to the following | |||||
constexpr auto hs = entt::hashed_string{"my/resource/identifier"}; | |||||
``` | |||||
The class `hashed_string` is described in a dedicated section, so I won't go in | |||||
details here. | |||||
Resources are loaded and thus stored in a cache through the `load` member | |||||
function. It accepts the loader to use as a template parameter, the resource | |||||
identifier and the parameters used to construct the resource as arguments: | |||||
```cpp | |||||
// uses the identifier declared above | |||||
cache.load<my_loader>(identifier, 0); | |||||
// uses a const char * directly as an identifier | |||||
cache.load<my_loader>("another/identifier"_hs, 42); | |||||
``` | |||||
The function returns a handle to the resource, whether it already exists or is | |||||
loaded. In case the loader returns an invalid pointer, the handle is invalid as | |||||
well and therefore it can be easily used with an `if` statement: | |||||
```cpp | |||||
if(auto handle = cache.load<my_loader>("another/identifier"_hs, 42); handle) { | |||||
// ... | |||||
} | |||||
``` | |||||
Before trying to load a resource, the `contains` member function can be used to | |||||
know if a cache already contains a specific resource: | |||||
```cpp | |||||
auto exists = cache.contains("my/identifier"_hs); | |||||
``` | |||||
There exists also a member function to use to force a reload of an already | |||||
existing resource if needed: | |||||
```cpp | |||||
auto handle = cache.reload<my_loader>("another/identifier"_hs, 42); | |||||
``` | |||||
As above, the function returns a handle to the resource that is invalid in case | |||||
of errors. The `reload` member function is a kind of alias of the following | |||||
snippet: | |||||
```cpp | |||||
cache.discard(identifier); | |||||
cache.load<my_loader>(identifier, 42); | |||||
``` | |||||
Where the `discard` member function is used to get rid of a resource if loaded. | |||||
In case the cache doesn't contain a resource for the given identifier, `discard` | |||||
does nothing and returns immediately. | |||||
So far, so good. Resources are finally loaded and stored within the cache.<br/> | |||||
They are returned to users in the form of handles. To get one of them later on: | |||||
```cpp | |||||
auto handle = cache.handle("my/identifier"_hs); | |||||
``` | |||||
The idea behind a handle is the same of the flyweight pattern. In other terms, | |||||
resources aren't copied around. Instead, instances are shared between handles. | |||||
Users of a resource own a handle that guarantees that a resource isn't destroyed | |||||
until all the handles are destroyed, even if the resource itself is removed from | |||||
the cache.<br/> | |||||
Handles are tiny objects both movable and copyable. They return the contained | |||||
resource as a const reference on request: | |||||
* By means of the `get` member function: | |||||
```cpp | |||||
const auto &resource = handle.get(); | |||||
``` | |||||
* Using the proper cast operator: | |||||
```cpp | |||||
const auto &resource = handle; | |||||
``` | |||||
* Through the dereference operator: | |||||
```cpp | |||||
const auto &resource = *handle; | |||||
``` | |||||
The resource can also be accessed directly using the arrow operator if required: | |||||
```cpp | |||||
auto value = handle->value; | |||||
``` | |||||
To test if a handle is still valid, the cast operator to `bool` allows users to | |||||
use it in a guard: | |||||
```cpp | |||||
if(handle) { | |||||
// ... | |||||
} | |||||
``` | |||||
Finally, in case there is the need to load a resource and thus to get a handle | |||||
without storing the resource itself in the cache, users can rely on the `temp` | |||||
member function template.<br/> | |||||
The declaration is similar to that of `load`, a (possibly invalid) handle for | |||||
the resource is returned also in this case: | |||||
```cpp | |||||
if(auto handle = cache.temp<my_loader>(42); handle) { | |||||
// ... | |||||
} | |||||
``` | |||||
Do not forget to test the handle for validity. Otherwise, getting a reference to | |||||
the resource it points may result in undefined behavior. |
@ -0,0 +1,447 @@ | |||||
# Crash Course: events, signals and everything in between | |||||
<!-- | |||||
@cond TURN_OFF_DOXYGEN | |||||
--> | |||||
# Table of Contents | |||||
* [Introduction](#introduction) | |||||
* [Delegate](#delegate) | |||||
* [Signals](#signals) | |||||
* [Event dispatcher](#event-dispatcher) | |||||
* [Event emitter](#event-emitter) | |||||
<!-- | |||||
@endcond TURN_OFF_DOXYGEN | |||||
--> | |||||
# Introduction | |||||
Signals are usually a core part of games and software architectures in | |||||
general.<br/> | |||||
Roughly speaking, they help to decouple the various parts of a system while | |||||
allowing them to communicate with each other somehow. | |||||
The so called _modern C++_ comes with a tool that can be useful in these terms, | |||||
the `std::function`. As an example, it can be used to create delegates.<br/> | |||||
However, there is no guarantee that an `std::function` does not perform | |||||
allocations under the hood and this could be problematic sometimes. Furthermore, | |||||
it solves a problem but may not adapt well to other requirements that may arise | |||||
from time to time. | |||||
In case that the flexibility and potential of an `std::function` are not | |||||
required or where you are looking for something different, `EnTT` offers a full | |||||
set of classes to solve completely different problems. | |||||
# Delegate | |||||
A delegate can be used as a general purpose invoker with no memory overhead for | |||||
free functions and members provided along with an instance on which to invoke | |||||
them.<br/> | |||||
It does not claim to be a drop-in replacement for an `std::function`, so do not | |||||
expect to use it whenever an `std::function` fits well. However, it can be used | |||||
to send opaque delegates around to be used to invoke functions as needed. | |||||
The interface is trivial. It offers a default constructor to create empty | |||||
delegates: | |||||
```cpp | |||||
entt::delegate<int(int)> delegate{}; | |||||
``` | |||||
All what is needed to create an instance is to specify the type of the function | |||||
the delegate will _contain_, that is the signature of the free function or the | |||||
member function one wants to assign to it. | |||||
Attempting to use an empty delegate by invoking its function call operator | |||||
results in undefined behavior or most likely a crash. Before to use a delegate, | |||||
it must be initialized.<br/> | |||||
There exists a bunch of overloads of the `connect` member function to do that. | |||||
As an example of use: | |||||
```cpp | |||||
int f(int i) { return i; } | |||||
struct my_struct { | |||||
int f(const int &i) { return i } | |||||
}; | |||||
// bind a free function to the delegate | |||||
delegate.connect<&f>(); | |||||
// bind a member function to the delegate | |||||
my_struct instance; | |||||
delegate.connect<&my_struct::f>(&instance); | |||||
``` | |||||
The delegate class accepts also data members, if needed. In this case, the | |||||
function type of the delegate is such that the parameter list is empty and the | |||||
value of the data member is at least convertible to the return type.<br/> | |||||
Functions having type equivalent to `void(T *, args...)` are accepted as well. | |||||
In this case, `T *` is considered a payload and the function will receive it | |||||
back every time it's invoked. In other terms, this works just fine with the | |||||
above definition: | |||||
```cpp | |||||
void g(const char *c, int i) { /* ... */ } | |||||
const char c = 'c'; | |||||
delegate.connect<&g>(&c); | |||||
delegate(42); | |||||
``` | |||||
The function `g` will be invoked with a pointer to `c` and `42`. However, the | |||||
function type of the delegate is still `void(int)`, mainly because this is also | |||||
the signature of its function call operator. | |||||
To create and initialize a delegate at once, there are also some specialized | |||||
constructors. Because of the rules of the language, the listener is provided by | |||||
means of the `entt::connect_arg` variable template: | |||||
```cpp | |||||
entt::delegate<int(int)> func{entt::connect_arg<&f>}; | |||||
``` | |||||
Aside `connect`, a `disconnect` counterpart isn't provided. Instead, there | |||||
exists a `reset` member function to use to clear a delegate.<br/> | |||||
To know if a delegate is empty, it can be used explicitly in every conditional | |||||
statement: | |||||
```cpp | |||||
if(delegate) { | |||||
// ... | |||||
} | |||||
``` | |||||
Finally, to invoke a delegate, the function call operator is the way to go as | |||||
usual: | |||||
```cpp | |||||
auto ret = delegate(42); | |||||
``` | |||||
As shown above, listeners do not have to strictly follow the signature of the | |||||
delegate. As long as a listener can be invoked with the given arguments to yield | |||||
a result that is convertible to the given result type, everything works just | |||||
fine. | |||||
Probably too much small and pretty poor of functionalities, but the delegate | |||||
class can help in a lot of cases and it has shown that it is worth keeping it | |||||
within the library. | |||||
# Signals | |||||
Signal handlers work with naked pointers, function pointers and pointers to | |||||
member functions. Listeners can be any kind of objects and users are in charge | |||||
of connecting and disconnecting them from a signal to avoid crashes due to | |||||
different lifetimes. On the other side, performance shouldn't be affected that | |||||
much by the presence of such a signal handler.<br/> | |||||
A signal handler can be used as a private data member without exposing any | |||||
_publish_ functionality to the clients of a class. The basic idea is to impose a | |||||
clear separation between the signal itself and its _sink_ class, that is a tool | |||||
to be used to connect and disconnect listeners on the fly. | |||||
The API of a signal handler is straightforward. The most important thing is that | |||||
it comes in two forms: with and without a collector. In case a signal is | |||||
associated with a collector, all the values returned by the listeners can be | |||||
literally _collected_ and used later by the caller. Otherwise it works just like | |||||
a plain signal that emits events from time to time.<br/> | |||||
**Note**: collectors are allowed only in case of function types whose the return | |||||
type isn't `void` for obvious reasons. | |||||
To create instances of signal handlers there exist mainly two ways: | |||||
```cpp | |||||
// no collector type | |||||
entt::sigh<void(int, char)> signal; | |||||
// explicit collector type | |||||
entt::sigh<void(int, char), my_collector<bool>> collector; | |||||
``` | |||||
As expected, they offer all the basic functionalities required to know how many | |||||
listeners they contain (`size`) or if they contain at least a listener (`empty`) | |||||
and even to swap two signal handlers (`swap`). | |||||
Besides them, there are member functions to use both to connect and disconnect | |||||
listeners in all their forms by means of a sink: | |||||
```cpp | |||||
void foo(int, char) { /* ... */ } | |||||
struct listener { | |||||
void bar(const int &, char) { /* ... */ } | |||||
}; | |||||
// ... | |||||
listener instance; | |||||
signal.sink().connect<&foo>(); | |||||
signal.sink().connect<&listener::bar>(&instance); | |||||
// ... | |||||
// disconnects a free function | |||||
signal.sink().disconnect<&foo>(); | |||||
// disconnect a member function of an instance | |||||
signal.sink().disconnect<&listener::bar>(&instance); | |||||
// discards all the listeners at once | |||||
signal.sink().disconnect(); | |||||
``` | |||||
As shown above, listeners do not have to strictly follow the signature of the | |||||
signal. As long as a listener can be invoked with the given arguments to yield a | |||||
result that is convertible to the given result type, everything works just fine. | |||||
Once listeners are attached (or even if there are no listeners at all), events | |||||
and data in general can be published through a signal by means of the `publish` | |||||
member function: | |||||
```cpp | |||||
signal.publish(42, 'c'); | |||||
``` | |||||
To collect data, the `collect` member function should be used instead. Below is | |||||
a minimal example to show how to use it: | |||||
```cpp | |||||
struct my_collector { | |||||
std::vector<int> vec{}; | |||||
bool operator()(int v) noexcept { | |||||
vec.push_back(v); | |||||
return true; | |||||
} | |||||
}; | |||||
int f() { return 0; } | |||||
int g() { return 1; } | |||||
// ... | |||||
entt::sigh<int(), my_collector<int>> signal; | |||||
signal.sink().connect<&f>(); | |||||
signal.sink().connect<&g>(); | |||||
my_collector collector = signal.collect(); | |||||
assert(collector.vec[0] == 0); | |||||
assert(collector.vec[1] == 1); | |||||
``` | |||||
A collector must expose a function operator that accepts as an argument a type | |||||
to which the return type of the listeners can be converted. Moreover, it has to | |||||
return a boolean value that is false to stop collecting data, true otherwise. | |||||
This way one can avoid calling all the listeners in case it isn't necessary. | |||||
# Event dispatcher | |||||
The event dispatcher class is designed so as to be used in a loop. It allows | |||||
users both to trigger immediate events or to queue events to be published all | |||||
together once per tick.<br/> | |||||
This class shares part of its API with the one of the signal handler, but it | |||||
doesn't require that all the types of events are specified when declared: | |||||
```cpp | |||||
// define a general purpose dispatcher that works with naked pointers | |||||
entt::dispatcher dispatcher{}; | |||||
``` | |||||
In order to register an instance of a class to a dispatcher, its type must | |||||
expose one or more member functions the arguments of which are such that | |||||
`const E &` can be converted to them for each type of event `E`, no matter what | |||||
the return value is.<br/> | |||||
The name of the member function aimed to receive the event must be provided to | |||||
the `connect` member function of the sink in charge for the specific event: | |||||
```cpp | |||||
struct an_event { int value; }; | |||||
struct another_event {}; | |||||
struct listener | |||||
{ | |||||
void receive(const an_event &) { /* ... */ } | |||||
void method(const another_event &) { /* ... */ } | |||||
}; | |||||
// ... | |||||
listener listener; | |||||
dispatcher.sink<an_event>().connect<&listener::receive>(&listener); | |||||
dispatcher.sink<another_event>().connect<&listener::method>(&listener); | |||||
``` | |||||
The `disconnect` member function follows the same pattern and can be used to | |||||
selectively remove listeners: | |||||
```cpp | |||||
dispatcher.sink<an_event>().disconnect<&listener::receive>(&listener); | |||||
dispatcher.sink<another_event>().disconnect<&listener::method>(&listener); | |||||
``` | |||||
The `trigger` member function serves the purpose of sending an immediate event | |||||
to all the listeners registered so far. It offers a convenient approach that | |||||
relieves users from having to create the event itself. Instead, it's enough to | |||||
specify the type of event and provide all the parameters required to construct | |||||
it.<br/> | |||||
As an example: | |||||
```cpp | |||||
dispatcher.trigger<an_event>(42); | |||||
dispatcher.trigger<another_event>(); | |||||
``` | |||||
Listeners are invoked immediately, order of execution isn't guaranteed. This | |||||
method can be used to push around urgent messages like an _is terminating_ | |||||
notification on a mobile app. | |||||
On the other hand, the `enqueue` member function queues messages together and | |||||
allows to maintain control over the moment they are sent to listeners. The | |||||
signature of this method is more or less the same of `trigger`: | |||||
```cpp | |||||
dispatcher.enqueue<an_event>(42); | |||||
dispatcher.enqueue<another_event>(); | |||||
``` | |||||
Events are stored aside until the `update` member function is invoked, then all | |||||
the messages that are still pending are sent to the listeners at once: | |||||
```cpp | |||||
// emits all the events of the given type at once | |||||
dispatcher.update<my_event>(); | |||||
// emits all the events queued so far at once | |||||
dispatcher.update(); | |||||
``` | |||||
This way users can embed the dispatcher in a loop and literally dispatch events | |||||
once per tick to their systems. | |||||
# Event emitter | |||||
A general purpose event emitter thought mainly for those cases where it comes to | |||||
working with asynchronous stuff.<br/> | |||||
Originally designed to fit the requirements of | |||||
[`uvw`](https://github.com/skypjack/uvw) (a wrapper for `libuv` written in | |||||
modern C++), it was adapted later to be included in this library. | |||||
To create a custom emitter type, derived classes must inherit directly from the | |||||
base class as: | |||||
```cpp | |||||
struct my_emitter: emitter<my_emitter> { | |||||
// ... | |||||
} | |||||
``` | |||||
The full list of accepted types of events isn't required. Handlers are created | |||||
internally on the fly and thus each type of event is accepted by default. | |||||
Whenever an event is published, an emitter provides the listeners with a | |||||
reference to itself along with a const reference to the event. Therefore | |||||
listeners have an handy way to work with it without incurring in the need of | |||||
capturing a reference to the emitter itself.<br/> | |||||
In addition, an opaque object is returned each time a connection is established | |||||
between an emitter and a listener, allowing the caller to disconnect them at a | |||||
later time.<br/> | |||||
The opaque object used to handle connections is both movable and copyable. On | |||||
the other side, an event emitter is movable but not copyable by default. | |||||
To create new instances of an emitter, no arguments are required: | |||||
```cpp | |||||
my_emitter emitter{}; | |||||
``` | |||||
Listeners must be movable and callable objects (free functions, lambdas, | |||||
functors, `std::function`s, whatever) whose function type is: | |||||
```cpp | |||||
void(const Event &, my_emitter &) | |||||
``` | |||||
Where `Event` is the type of event they want to listen.<br/> | |||||
There are two ways to attach a listener to an event emitter that differ | |||||
slightly from each other: | |||||
* To register a long-lived listener, use the `on` member function. It is meant | |||||
to register a listener designed to be invoked more than once for the given | |||||
event type.<br/> | |||||
As an example: | |||||
```cpp | |||||
auto conn = emitter.on<my_event>([](const my_event &event, my_emitter &emitter) { | |||||
// ... | |||||
}); | |||||
``` | |||||
The connection object can be freely discarded. Otherwise, it can be used later | |||||
to disconnect the listener if required. | |||||
* To register a short-lived listener, use the `once` member function. It is | |||||
meant to register a listener designed to be invoked only once for the given | |||||
event type. The listener is automatically disconnected after the first | |||||
invocation.<br/> | |||||
As an example: | |||||
```cpp | |||||
auto conn = emitter.once<my_event>([](const my_event &event, my_emitter &emitter) { | |||||
// ... | |||||
}); | |||||
``` | |||||
The connection object can be freely discarded. Otherwise, it can be used later | |||||
to disconnect the listener if required. | |||||
In both cases, the connection object can be used with the `erase` member | |||||
function: | |||||
```cpp | |||||
emitter.erase(conn); | |||||
``` | |||||
There are also two member functions to use either to disconnect all the | |||||
listeners for a given type of event or to clear the emitter: | |||||
```cpp | |||||
// removes all the listener for the specific event | |||||
emitter.clear<my_event>(); | |||||
// removes all the listeners registered so far | |||||
emitter.clear(); | |||||
``` | |||||
To send an event to all the listeners that are interested in it, the `publish` | |||||
member function offers a convenient approach that relieves users from having to | |||||
create the event: | |||||
```cpp | |||||
struct my_event { int i; }; | |||||
// ... | |||||
emitter.publish<my_event>(42); | |||||
``` | |||||
Finally, the `empty` member function tests if there exists at least either a | |||||
listener registered with the event emitter or to a given type of event: | |||||
```cpp | |||||
bool empty; | |||||
// checks if there is any listener registered for the specific event | |||||
empty = emitter.empty<my_event>(); | |||||
// checks it there are listeners registered with the event emitter | |||||
empty = emitter.empty(); | |||||
``` | |||||
In general, the event emitter is a handy tool when the derived classes _wrap_ | |||||
asynchronous operations, because it introduces a _nice-to-have_ model based on | |||||
events and listeners that kindly hides the complexity behind the scenes. However | |||||
it is not limited to such uses. |
@ -0,0 +1,299 @@ | |||||
#!/usr/bin/env python | |||||
# coding=utf-8 | |||||
# amalgamate.py - Amalgamate C source and header files. | |||||
# Copyright (c) 2012, Erik Edlund <erik.edlund@32767.se> | |||||
# | |||||
# Redistribution and use in source and binary forms, with or without modification, | |||||
# are permitted provided that the following conditions are met: | |||||
# | |||||
# * Redistributions of source code must retain the above copyright notice, | |||||
# this list of conditions and the following disclaimer. | |||||
# | |||||
# * Redistributions in binary form must reproduce the above copyright notice, | |||||
# this list of conditions and the following disclaimer in the documentation | |||||
# and/or other materials provided with the distribution. | |||||
# | |||||
# * Neither the name of Erik Edlund, nor the names of its contributors may | |||||
# be used to endorse or promote products derived from this software without | |||||
# specific prior written permission. | |||||
# | |||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | |||||
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | |||||
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||||
from __future__ import division | |||||
from __future__ import print_function | |||||
from __future__ import unicode_literals | |||||
import argparse | |||||
import datetime | |||||
import json | |||||
import os | |||||
import re | |||||
class Amalgamation(object): | |||||
# Prepends self.source_path to file_path if needed. | |||||
def actual_path(self, file_path): | |||||
if not os.path.isabs(file_path): | |||||
file_path = os.path.join(self.source_path, file_path) | |||||
return file_path | |||||
# Search included file_path in self.include_paths and | |||||
# in source_dir if specified. | |||||
def find_included_file(self, file_path, source_dir): | |||||
search_dirs = self.include_paths[:] | |||||
if source_dir: | |||||
search_dirs.insert(0, source_dir) | |||||
for search_dir in search_dirs: | |||||
search_path = os.path.join(search_dir, file_path) | |||||
if os.path.isfile(self.actual_path(search_path)): | |||||
return search_path | |||||
return None | |||||
def __init__(self, args): | |||||
with open(args.config, 'r') as f: | |||||
config = json.loads(f.read()) | |||||
for key in config: | |||||
setattr(self, key, config[key]) | |||||
self.verbose = args.verbose == "yes" | |||||
self.prologue = args.prologue | |||||
self.source_path = args.source_path | |||||
self.included_files = [] | |||||
# Generate the amalgamation and write it to the target file. | |||||
def generate(self): | |||||
amalgamation = "" | |||||
if self.prologue: | |||||
with open(self.prologue, 'r') as f: | |||||
amalgamation += datetime.datetime.now().strftime(f.read()) | |||||
if self.verbose: | |||||
print("Config:") | |||||
print(" target = {0}".format(self.target)) | |||||
print(" working_dir = {0}".format(os.getcwd())) | |||||
print(" include_paths = {0}".format(self.include_paths)) | |||||
print("Creating amalgamation:") | |||||
for file_path in self.sources: | |||||
# Do not check the include paths while processing the source | |||||
# list, all given source paths must be correct. | |||||
# actual_path = self.actual_path(file_path) | |||||
print(" - processing \"{0}\"".format(file_path)) | |||||
t = TranslationUnit(file_path, self, True) | |||||
amalgamation += t.content | |||||
with open(self.target, 'w') as f: | |||||
f.write(amalgamation) | |||||
print("...done!\n") | |||||
if self.verbose: | |||||
print("Files processed: {0}".format(self.sources)) | |||||
print("Files included: {0}".format(self.included_files)) | |||||
print("") | |||||
def _is_within(match, matches): | |||||
for m in matches: | |||||
if match.start() > m.start() and \ | |||||
match.end() < m.end(): | |||||
return True | |||||
return False | |||||
class TranslationUnit(object): | |||||
# // C++ comment. | |||||
cpp_comment_pattern = re.compile(r"//.*?\n") | |||||
# /* C comment. */ | |||||
c_comment_pattern = re.compile(r"/\*.*?\*/", re.S) | |||||
# "complex \"stri\\\ng\" value". | |||||
string_pattern = re.compile("[^']" r'".*?(?<=[^\\])"', re.S) | |||||
# Handle simple include directives. Support for advanced | |||||
# directives where macros and defines needs to expanded is | |||||
# not a concern right now. | |||||
include_pattern = re.compile( | |||||
r'#\s*include\s+(<|")(?P<path>.*?)("|>)', re.S) | |||||
# #pragma once | |||||
pragma_once_pattern = re.compile(r'#\s*pragma\s+once', re.S) | |||||
# Search for pattern in self.content, add the match to | |||||
# contexts if found and update the index accordingly. | |||||
def _search_content(self, index, pattern, contexts): | |||||
match = pattern.search(self.content, index) | |||||
if match: | |||||
contexts.append(match) | |||||
return match.end() | |||||
return index + 2 | |||||
# Return all the skippable contexts, i.e., comments and strings | |||||
def _find_skippable_contexts(self): | |||||
# Find contexts in the content in which a found include | |||||
# directive should not be processed. | |||||
skippable_contexts = [] | |||||
# Walk through the content char by char, and try to grab | |||||
# skippable contexts using regular expressions when found. | |||||
i = 1 | |||||
content_len = len(self.content) | |||||
while i < content_len: | |||||
j = i - 1 | |||||
current = self.content[i] | |||||
previous = self.content[j] | |||||
if current == '"': | |||||
# String value. | |||||
i = self._search_content(j, self.string_pattern, | |||||
skippable_contexts) | |||||
elif current == '*' and previous == '/': | |||||
# C style comment. | |||||
i = self._search_content(j, self.c_comment_pattern, | |||||
skippable_contexts) | |||||
elif current == '/' and previous == '/': | |||||
# C++ style comment. | |||||
i = self._search_content(j, self.cpp_comment_pattern, | |||||
skippable_contexts) | |||||
else: | |||||
# Skip to the next char. | |||||
i += 1 | |||||
return skippable_contexts | |||||
# Returns True if the match is within list of other matches | |||||
# Removes pragma once from content | |||||
def _process_pragma_once(self): | |||||
content_len = len(self.content) | |||||
if content_len < len("#include <x>"): | |||||
return 0 | |||||
# Find contexts in the content in which a found include | |||||
# directive should not be processed. | |||||
skippable_contexts = self._find_skippable_contexts() | |||||
pragmas = [] | |||||
pragma_once_match = self.pragma_once_pattern.search(self.content) | |||||
while pragma_once_match: | |||||
if not _is_within(pragma_once_match, skippable_contexts): | |||||
pragmas.append(pragma_once_match) | |||||
pragma_once_match = self.pragma_once_pattern.search(self.content, | |||||
pragma_once_match.end()) | |||||
# Handle all collected pragma once directives. | |||||
prev_end = 0 | |||||
tmp_content = '' | |||||
for pragma_match in pragmas: | |||||
tmp_content += self.content[prev_end:pragma_match.start()] | |||||
prev_end = pragma_match.end() | |||||
tmp_content += self.content[prev_end:] | |||||
self.content = tmp_content | |||||
# Include all trivial #include directives into self.content. | |||||
def _process_includes(self): | |||||
content_len = len(self.content) | |||||
if content_len < len("#include <x>"): | |||||
return 0 | |||||
# Find contexts in the content in which a found include | |||||
# directive should not be processed. | |||||
skippable_contexts = self._find_skippable_contexts() | |||||
# Search for include directives in the content, collect those | |||||
# which should be included into the content. | |||||
includes = [] | |||||
include_match = self.include_pattern.search(self.content) | |||||
while include_match: | |||||
if not _is_within(include_match, skippable_contexts): | |||||
include_path = include_match.group("path") | |||||
search_same_dir = include_match.group(1) == '"' | |||||
found_included_path = self.amalgamation.find_included_file( | |||||
include_path, self.file_dir if search_same_dir else None) | |||||
if found_included_path: | |||||
includes.append((include_match, found_included_path)) | |||||
include_match = self.include_pattern.search(self.content, | |||||
include_match.end()) | |||||
# Handle all collected include directives. | |||||
prev_end = 0 | |||||
tmp_content = '' | |||||
for include in includes: | |||||
include_match, found_included_path = include | |||||
tmp_content += self.content[prev_end:include_match.start()] | |||||
tmp_content += "// {0}\n".format(include_match.group(0)) | |||||
if found_included_path not in self.amalgamation.included_files: | |||||
t = TranslationUnit(found_included_path, self.amalgamation, False) | |||||
tmp_content += t.content | |||||
prev_end = include_match.end() | |||||
tmp_content += self.content[prev_end:] | |||||
self.content = tmp_content | |||||
return len(includes) | |||||
# Make all content processing | |||||
def _process(self): | |||||
if not self.is_root: | |||||
self._process_pragma_once() | |||||
self._process_includes() | |||||
def __init__(self, file_path, amalgamation, is_root): | |||||
self.file_path = file_path | |||||
self.file_dir = os.path.dirname(file_path) | |||||
self.amalgamation = amalgamation | |||||
self.is_root = is_root | |||||
self.amalgamation.included_files.append(self.file_path) | |||||
actual_path = self.amalgamation.actual_path(file_path) | |||||
if not os.path.isfile(actual_path): | |||||
raise IOError("File not found: \"{0}\"".format(file_path)) | |||||
with open(actual_path, 'r') as f: | |||||
self.content = f.read() | |||||
self._process() | |||||
def main(): | |||||
description = "Amalgamate C source and header files." | |||||
usage = " ".join([ | |||||
"amalgamate.py", | |||||
"[-v]", | |||||
"-c path/to/config.json", | |||||
"-s path/to/source/dir", | |||||
"[-p path/to/prologue.(c|h)]" | |||||
]) | |||||
argsparser = argparse.ArgumentParser( | |||||
description=description, usage=usage) | |||||
argsparser.add_argument("-v", "--verbose", dest="verbose", | |||||
choices=["yes", "no"], metavar="", help="be verbose") | |||||
argsparser.add_argument("-c", "--config", dest="config", | |||||
required=True, metavar="", help="path to a JSON config file") | |||||
argsparser.add_argument("-s", "--source", dest="source_path", | |||||
required=True, metavar="", help="source code path") | |||||
argsparser.add_argument("-p", "--prologue", dest="prologue", | |||||
required=False, metavar="", help="path to a C prologue file") | |||||
amalgamation = Amalgamation(argsparser.parse_args()) | |||||
amalgamation.generate() | |||||
if __name__ == "__main__": | |||||
main() |
@ -0,0 +1,8 @@ | |||||
{ | |||||
"project": "entt", | |||||
"target": "single_include/entt/entt.hpp", | |||||
"sources": [ | |||||
"src/entt/entt.hpp" | |||||
], | |||||
"include_paths": ["src"] | |||||
} |
@ -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 .. |
@ -0,0 +1,3 @@ | |||||
#!/bin/sh | |||||
scripts/update_homebrew.sh $1 |
@ -0,0 +1,44 @@ | |||||
#ifndef ENTT_CONFIG_CONFIG_H | |||||
#define ENTT_CONFIG_CONFIG_H | |||||
#ifndef ENTT_NOEXCEPT | |||||
#define ENTT_NOEXCEPT noexcept | |||||
#endif // ENTT_NOEXCEPT | |||||
#ifndef ENTT_HS_SUFFIX | |||||
#define ENTT_HS_SUFFIX _hs | |||||
#endif // ENTT_HS_SUFFIX | |||||
#ifndef ENTT_NO_ATOMIC | |||||
#include <atomic> | |||||
template<typename Type> | |||||
using maybe_atomic_t = std::atomic<Type>; | |||||
#else // ENTT_NO_ATOMIC | |||||
template<typename Type> | |||||
using maybe_atomic_t = Type; | |||||
#endif // ENTT_NO_ATOMIC | |||||
#ifndef ENTT_ID_TYPE | |||||
#include <cstdint> | |||||
#define ENTT_ID_TYPE std::uint32_t | |||||
#endif // ENTT_ID_TYPE | |||||
#ifndef ENTT_PAGE_SIZE | |||||
#define ENTT_PAGE_SIZE 32768 | |||||
#endif // ENTT_PAGE_SIZE | |||||
#ifndef ENTT_DISABLE_ASSERT | |||||
#include <cassert> | |||||
#define ENTT_ASSERT(condition) assert(condition) | |||||
#else // ENTT_DISABLE_ASSERT | |||||
#define ENTT_ASSERT(...) ((void)0) | |||||
#endif // ENTT_DISABLE_ASSERT | |||||
#endif // ENTT_CONFIG_CONFIG_H |
@ -0,0 +1,11 @@ | |||||
#ifndef ENTT_CONFIG_VERSION_H | |||||
#define ENTT_CONFIG_VERSION_H | |||||
#define ENTT_VERSION "3.1.0" | |||||
#define ENTT_VERSION_MAJOR 3 | |||||
#define ENTT_VERSION_MINOR 1 | |||||
#define ENTT_VERSION_PATCH 0 | |||||
#endif // ENTT_CONFIG_VERSION_H |
@ -0,0 +1,78 @@ | |||||
#ifndef ENTT_CORE_ALGORITHM_HPP | |||||
#define ENTT_CORE_ALGORITHM_HPP | |||||
#include <functional> | |||||
#include <algorithm> | |||||
#include <utility> | |||||
namespace entt { | |||||
/** | |||||
* @brief Function object to wrap `std::sort` in a class type. | |||||
* | |||||
* Unfortunately, `std::sort` cannot be passed as template argument to a class | |||||
* template or a function template.<br/> | |||||
* This class fills the gap by wrapping some flavors of `std::sort` in a | |||||
* function object. | |||||
*/ | |||||
struct std_sort { | |||||
/** | |||||
* @brief Sorts the elements in a range. | |||||
* | |||||
* Sorts the elements in a range using the given binary comparison function. | |||||
* | |||||
* @tparam It Type of random access iterator. | |||||
* @tparam Compare Type of comparison function object. | |||||
* @tparam Args Types of arguments to forward to the sort function. | |||||
* @param first An iterator to the first element of the range to sort. | |||||
* @param last An iterator past the last element of the range to sort. | |||||
* @param compare A valid comparison function object. | |||||
* @param args Arguments to forward to the sort function, if any. | |||||
*/ | |||||
template<typename It, typename Compare = std::less<>, typename... Args> | |||||
void operator()(It first, It last, Compare compare = Compare{}, Args &&... args) const { | |||||
std::sort(std::forward<Args>(args)..., std::move(first), std::move(last), std::move(compare)); | |||||
} | |||||
}; | |||||
/*! @brief Function object for performing insertion sort. */ | |||||
struct insertion_sort { | |||||
/** | |||||
* @brief Sorts the elements in a range. | |||||
* | |||||
* Sorts the elements in a range using the given binary comparison function. | |||||
* | |||||
* @tparam It Type of random access iterator. | |||||
* @tparam Compare Type of comparison function object. | |||||
* @param first An iterator to the first element of the range to sort. | |||||
* @param last An iterator past the last element of the range to sort. | |||||
* @param compare A valid comparison function object. | |||||
*/ | |||||
template<typename It, typename Compare = std::less<>> | |||||
void operator()(It first, It last, Compare compare = Compare{}) const { | |||||
if(first != last) { | |||||
auto it = first + 1; | |||||
while(it != last) { | |||||
auto pre = it++; | |||||
auto value = *pre; | |||||
while(pre-- != first && compare(value, *pre)) { | |||||
*(pre+1) = *pre; | |||||
} | |||||
*(pre+1) = value; | |||||
} | |||||
} | |||||
} | |||||
}; | |||||
} | |||||
#endif // ENTT_CORE_ALGORITHM_HPP |
@ -0,0 +1,40 @@ | |||||
#ifndef ENTT_CORE_FAMILY_HPP | |||||
#define ENTT_CORE_FAMILY_HPP | |||||
#include <type_traits> | |||||
#include "../config/config.h" | |||||
namespace entt { | |||||
/** | |||||
* @brief Dynamic identifier generator. | |||||
* | |||||
* Utility class template that can be used to assign unique identifiers to types | |||||
* at runtime. Use different specializations to create separate sets of | |||||
* identifiers. | |||||
*/ | |||||
template<typename...> | |||||
class family { | |||||
inline static maybe_atomic_t<ENTT_ID_TYPE> identifier; | |||||
template<typename...> | |||||
inline static const auto inner = identifier++; | |||||
public: | |||||
/*! @brief Unsigned integer type. */ | |||||
using family_type = ENTT_ID_TYPE; | |||||
/*! @brief Statically generated unique identifier for the given type. */ | |||||
template<typename... Type> | |||||
// at the time I'm writing, clang crashes during compilation if auto is used in place of family_type here | |||||
inline static const family_type type = inner<std::decay_t<Type>...>; | |||||
}; | |||||
} | |||||
#endif // ENTT_CORE_FAMILY_HPP |
@ -0,0 +1,213 @@ | |||||
#ifndef ENTT_CORE_HASHED_STRING_HPP | |||||
#define ENTT_CORE_HASHED_STRING_HPP | |||||
#include <cstddef> | |||||
#include "../config/config.h" | |||||
namespace entt { | |||||
/** | |||||
* @cond TURN_OFF_DOXYGEN | |||||
* Internal details not to be documented. | |||||
*/ | |||||
namespace internal { | |||||
template<typename> | |||||
struct fnv1a_traits; | |||||
template<> | |||||
struct fnv1a_traits<std::uint32_t> { | |||||
static constexpr std::uint32_t offset = 2166136261; | |||||
static constexpr std::uint32_t prime = 16777619; | |||||
}; | |||||
template<> | |||||
struct fnv1a_traits<std::uint64_t> { | |||||
static constexpr std::uint64_t offset = 14695981039346656037ull; | |||||
static constexpr std::uint64_t prime = 1099511628211ull; | |||||
}; | |||||
} | |||||
/** | |||||
* Internal details not to be documented. | |||||
* @endcond TURN_OFF_DOXYGEN | |||||
*/ | |||||
/** | |||||
* @brief Zero overhead unique identifier. | |||||
* | |||||
* A hashed string is a compile-time tool that allows users to use | |||||
* human-readable identifers in the codebase while using their numeric | |||||
* counterparts at runtime.<br/> | |||||
* Because of that, a hashed string can also be used in constant expressions if | |||||
* required. | |||||
*/ | |||||
class hashed_string { | |||||
using traits_type = internal::fnv1a_traits<ENTT_ID_TYPE>; | |||||
struct const_wrapper { | |||||
// non-explicit constructor on purpose | |||||
constexpr const_wrapper(const char *curr) ENTT_NOEXCEPT: str{curr} {} | |||||
const char *str; | |||||
}; | |||||
// Fowler–Noll–Vo hash function v. 1a - the good | |||||
inline static constexpr ENTT_ID_TYPE helper(ENTT_ID_TYPE partial, const char *curr) ENTT_NOEXCEPT { | |||||
return curr[0] == 0 ? partial : helper((partial^curr[0])*traits_type::prime, curr+1); | |||||
} | |||||
public: | |||||
/*! @brief Unsigned integer type. */ | |||||
using hash_type = ENTT_ID_TYPE; | |||||
/** | |||||
* @brief Returns directly the numeric representation of a string. | |||||
* | |||||
* Forcing template resolution avoids implicit conversions. An | |||||
* human-readable identifier can be anything but a plain, old bunch of | |||||
* characters.<br/> | |||||
* Example of use: | |||||
* @code{.cpp} | |||||
* const auto value = hashed_string::to_value("my.png"); | |||||
* @endcode | |||||
* | |||||
* @tparam N Number of characters of the identifier. | |||||
* @param str Human-readable identifer. | |||||
* @return The numeric representation of the string. | |||||
*/ | |||||
template<std::size_t N> | |||||
inline static constexpr hash_type to_value(const char (&str)[N]) ENTT_NOEXCEPT { | |||||
return helper(traits_type::offset, str); | |||||
} | |||||
/** | |||||
* @brief Returns directly the numeric representation of a string. | |||||
* @param wrapper Helps achieving the purpose by relying on overloading. | |||||
* @return The numeric representation of the string. | |||||
*/ | |||||
inline static hash_type to_value(const_wrapper wrapper) ENTT_NOEXCEPT { | |||||
return helper(traits_type::offset, wrapper.str); | |||||
} | |||||
/** | |||||
* @brief Returns directly the numeric representation of a string view. | |||||
* @param str Human-readable identifer. | |||||
* @param size Length of the string to hash. | |||||
* @return The numeric representation of the string. | |||||
*/ | |||||
inline static hash_type to_value(const char *str, std::size_t size) ENTT_NOEXCEPT { | |||||
ENTT_ID_TYPE partial{traits_type::offset}; | |||||
while(size--) { partial = (partial^(str++)[0])*traits_type::prime; } | |||||
return partial; | |||||
} | |||||
/*! @brief Constructs an empty hashed string. */ | |||||
constexpr hashed_string() ENTT_NOEXCEPT | |||||
: str{nullptr}, hash{} | |||||
{} | |||||
/** | |||||
* @brief Constructs a hashed string from an array of const chars. | |||||
* | |||||
* Forcing template resolution avoids implicit conversions. An | |||||
* human-readable identifier can be anything but a plain, old bunch of | |||||
* characters.<br/> | |||||
* Example of use: | |||||
* @code{.cpp} | |||||
* hashed_string hs{"my.png"}; | |||||
* @endcode | |||||
* | |||||
* @tparam N Number of characters of the identifier. | |||||
* @param curr Human-readable identifer. | |||||
*/ | |||||
template<std::size_t N> | |||||
constexpr hashed_string(const char (&curr)[N]) ENTT_NOEXCEPT | |||||
: str{curr}, hash{helper(traits_type::offset, curr)} | |||||
{} | |||||
/** | |||||
* @brief Explicit constructor on purpose to avoid constructing a hashed | |||||
* string directly from a `const char *`. | |||||
* @param wrapper Helps achieving the purpose by relying on overloading. | |||||
*/ | |||||
explicit constexpr hashed_string(const_wrapper wrapper) ENTT_NOEXCEPT | |||||
: str{wrapper.str}, hash{helper(traits_type::offset, wrapper.str)} | |||||
{} | |||||
/** | |||||
* @brief Returns the human-readable representation of a hashed string. | |||||
* @return The string used to initialize the instance. | |||||
*/ | |||||
constexpr const char * data() const ENTT_NOEXCEPT { | |||||
return str; | |||||
} | |||||
/** | |||||
* @brief Returns the numeric representation of a hashed string. | |||||
* @return The numeric representation of the instance. | |||||
*/ | |||||
constexpr hash_type value() const ENTT_NOEXCEPT { | |||||
return hash; | |||||
} | |||||
/** | |||||
* @brief Returns the human-readable representation of a hashed string. | |||||
* @return The string used to initialize the instance. | |||||
*/ | |||||
constexpr operator const char *() const ENTT_NOEXCEPT { return str; } | |||||
/*! @copydoc value */ | |||||
constexpr operator hash_type() const ENTT_NOEXCEPT { return hash; } | |||||
/** | |||||
* @brief Compares two hashed strings. | |||||
* @param other Hashed string with which to compare. | |||||
* @return True if the two hashed strings are identical, false otherwise. | |||||
*/ | |||||
constexpr bool operator==(const hashed_string &other) const ENTT_NOEXCEPT { | |||||
return hash == other.hash; | |||||
} | |||||
private: | |||||
const char *str; | |||||
hash_type hash; | |||||
}; | |||||
/** | |||||
* @brief Compares two hashed strings. | |||||
* @param lhs A valid hashed string. | |||||
* @param rhs A valid hashed string. | |||||
* @return True if the two hashed strings are identical, false otherwise. | |||||
*/ | |||||
constexpr bool operator!=(const hashed_string &lhs, const hashed_string &rhs) ENTT_NOEXCEPT { | |||||
return !(lhs == rhs); | |||||
} | |||||
} | |||||
/** | |||||
* @brief User defined literal for hashed strings. | |||||
* @param str The literal without its suffix. | |||||
* @return A properly initialized hashed string. | |||||
*/ | |||||
constexpr entt::hashed_string operator"" ENTT_HS_SUFFIX(const char *str, std::size_t) ENTT_NOEXCEPT { | |||||
return entt::hashed_string{str}; | |||||
} | |||||
#endif // ENTT_CORE_HASHED_STRING_HPP |
@ -0,0 +1,64 @@ | |||||
#ifndef ENTT_CORE_IDENT_HPP | |||||
#define ENTT_CORE_IDENT_HPP | |||||
#include <tuple> | |||||
#include <utility> | |||||
#include <type_traits> | |||||
#include "../config/config.h" | |||||
namespace entt { | |||||
/** | |||||
* @brief Types identifiers. | |||||
* | |||||
* Variable template used to generate identifiers at compile-time for the given | |||||
* 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} | |||||
* using id = entt::identifier<a_type, another_type>; | |||||
* | |||||
* switch(a_type_identifier) { | |||||
* case id::type<a_type>: | |||||
* // ... | |||||
* break; | |||||
* case id::type<another_type>: | |||||
* // ... | |||||
* break; | |||||
* default: | |||||
* // ... | |||||
* } | |||||
* @endcode | |||||
* | |||||
* @tparam Types List of types for which to generate identifiers. | |||||
*/ | |||||
template<typename... Types> | |||||
class identifier { | |||||
using tuple_type = std::tuple<std::decay_t<Types>...>; | |||||
template<typename Type, std::size_t... Indexes> | |||||
static constexpr ENTT_ID_TYPE get(std::index_sequence<Indexes...>) ENTT_NOEXCEPT { | |||||
static_assert(std::disjunction_v<std::is_same<Type, Types>...>); | |||||
return (0 + ... + (std::is_same_v<Type, std::tuple_element_t<Indexes, tuple_type>> ? ENTT_ID_TYPE(Indexes) : ENTT_ID_TYPE{})); | |||||
} | |||||
public: | |||||
/*! @brief Unsigned integer type. */ | |||||
using identifier_type = ENTT_ID_TYPE; | |||||
/*! @brief Statically generated unique identifier for the given type. */ | |||||
template<typename Type> | |||||
static constexpr identifier_type type = get<std::decay_t<Type>>(std::make_index_sequence<sizeof...(Types)>{}); | |||||
}; | |||||
} | |||||
#endif // ENTT_CORE_IDENT_HPP |
@ -0,0 +1,63 @@ | |||||
#ifndef ENTT_CORE_MONOSTATE_HPP | |||||
#define ENTT_CORE_MONOSTATE_HPP | |||||
#include <cassert> | |||||
#include "../config/config.h" | |||||
#include "hashed_string.hpp" | |||||
namespace entt { | |||||
/** | |||||
* @brief Minimal implementation of the monostate pattern. | |||||
* | |||||
* A minimal, yet complete configuration system built on top of the monostate | |||||
* pattern. Thread safe by design, it works only with basic types like `int`s or | |||||
* `bool`s.<br/> | |||||
* Multiple types and therefore more than one value can be associated with a | |||||
* single key. Because of this, users must pay attention to use the same type | |||||
* both during an assignment and when they try to read back their data. | |||||
* Otherwise, they can incur in unexpected results. | |||||
*/ | |||||
template<hashed_string::hash_type> | |||||
struct monostate { | |||||
/** | |||||
* @brief Assigns a value of a specific type to a given key. | |||||
* @tparam Type Type of the value to assign. | |||||
* @param val User data to assign to the given key. | |||||
*/ | |||||
template<typename Type> | |||||
void operator=(Type val) const ENTT_NOEXCEPT { | |||||
value<Type> = val; | |||||
} | |||||
/** | |||||
* @brief Gets a value of a specific type for a given key. | |||||
* @tparam Type Type of the value to get. | |||||
* @return Stored value, if any. | |||||
*/ | |||||
template<typename Type> | |||||
operator Type() const ENTT_NOEXCEPT { | |||||
return value<Type>; | |||||
} | |||||
private: | |||||
template<typename Type> | |||||
inline static maybe_atomic_t<Type> value{}; | |||||
}; | |||||
/** | |||||
* @brief Helper variable template. | |||||
* @tparam Value Value used to differentiate between different variables. | |||||
*/ | |||||
template<hashed_string::hash_type Value> | |||||
inline monostate<Value> monostate_v = {}; | |||||
} | |||||
#endif // ENTT_CORE_MONOSTATE_HPP |
@ -0,0 +1,236 @@ | |||||
#ifndef ENTT_CORE_TYPE_TRAITS_HPP | |||||
#define ENTT_CORE_TYPE_TRAITS_HPP | |||||
#include <type_traits> | |||||
#include "../core/hashed_string.hpp" | |||||
namespace entt { | |||||
/** | |||||
* @brief A class to use to push around lists of types, nothing more. | |||||
* @tparam Type Types provided by the given type list. | |||||
*/ | |||||
template<typename... Type> | |||||
struct type_list { | |||||
/*! @brief Unsigned integer type. */ | |||||
static constexpr auto size = sizeof...(Type); | |||||
}; | |||||
/*! @brief Primary template isn't defined on purpose. */ | |||||
template<typename...> | |||||
struct type_list_cat; | |||||
/*! @brief Concatenates multiple type lists. */ | |||||
template<> | |||||
struct type_list_cat<> { | |||||
/*! @brief A type list composed by the types of all the type lists. */ | |||||
using type = type_list<>; | |||||
}; | |||||
/** | |||||
* @brief Concatenates multiple type lists. | |||||
* @tparam Type Types provided by the first type list. | |||||
* @tparam Other Types provided by the second type list. | |||||
* @tparam List Other type lists, if any. | |||||
*/ | |||||
template<typename... Type, typename... Other, typename... List> | |||||
struct type_list_cat<type_list<Type...>, type_list<Other...>, List...> { | |||||
/*! @brief A type list composed by the types of all the type lists. */ | |||||
using type = typename type_list_cat<type_list<Type..., Other...>, List...>::type; | |||||
}; | |||||
/** | |||||
* @brief Concatenates multiple type lists. | |||||
* @tparam Type Types provided by the type list. | |||||
*/ | |||||
template<typename... Type> | |||||
struct type_list_cat<type_list<Type...>> { | |||||
/*! @brief A type list composed by the types of all the type lists. */ | |||||
using type = type_list<Type...>; | |||||
}; | |||||
/** | |||||
* @brief Helper type. | |||||
* @tparam List Type lists to concatenate. | |||||
*/ | |||||
template<typename... List> | |||||
using type_list_cat_t = typename type_list_cat<List...>::type; | |||||
/*! @brief Primary template isn't defined on purpose. */ | |||||
template<typename> | |||||
struct type_list_unique; | |||||
/** | |||||
* @brief Removes duplicates types from a type list. | |||||
* @tparam Type One of the types provided by the given type list. | |||||
* @tparam Other The other types provided by the given type list. | |||||
*/ | |||||
template<typename Type, typename... Other> | |||||
struct type_list_unique<type_list<Type, Other...>> { | |||||
/*! @brief A type list without duplicate types. */ | |||||
using type = std::conditional_t< | |||||
std::disjunction_v<std::is_same<Type, Other>...>, | |||||
typename type_list_unique<type_list<Other...>>::type, | |||||
type_list_cat_t<type_list<Type>, typename type_list_unique<type_list<Other...>>::type> | |||||
>; | |||||
}; | |||||
/*! @brief Removes duplicates types from a type list. */ | |||||
template<> | |||||
struct type_list_unique<type_list<>> { | |||||
/*! @brief A type list without duplicate types. */ | |||||
using type = type_list<>; | |||||
}; | |||||
/** | |||||
* @brief Helper type. | |||||
* @tparam Type A type list. | |||||
*/ | |||||
template<typename Type> | |||||
using type_list_unique_t = typename type_list_unique<Type>::type; | |||||
/*! @brief Traits class used mainly to push things across boundaries. */ | |||||
template<typename> | |||||
struct named_type_traits; | |||||
/** | |||||
* @brief Specialization used to get rid of constness. | |||||
* @tparam Type Named type. | |||||
*/ | |||||
template<typename Type> | |||||
struct named_type_traits<const Type> | |||||
: named_type_traits<Type> | |||||
{}; | |||||
/** | |||||
* @brief Helper type. | |||||
* @tparam Type Potentially named type. | |||||
*/ | |||||
template<typename Type> | |||||
using named_type_traits_t = typename named_type_traits<Type>::type; | |||||
/** | |||||
* @brief Provides the member constant `value` to true if a given type has a | |||||
* name. In all other cases, `value` is false. | |||||
*/ | |||||
template<typename, typename = std::void_t<>> | |||||
struct is_named_type: std::false_type {}; | |||||
/** | |||||
* @brief Provides the member constant `value` to true if a given type has a | |||||
* name. In all other cases, `value` is false. | |||||
* @tparam Type Potentially named type. | |||||
*/ | |||||
template<typename Type> | |||||
struct is_named_type<Type, std::void_t<named_type_traits_t<std::decay_t<Type>>>>: std::true_type {}; | |||||
/** | |||||
* @brief Helper variable template. | |||||
* | |||||
* True if a given type has a name, false otherwise. | |||||
* | |||||
* @tparam Type Potentially named type. | |||||
*/ | |||||
template<class Type> | |||||
constexpr auto is_named_type_v = is_named_type<Type>::value; | |||||
} | |||||
/** | |||||
* @brief Utility macro to deal with an issue of MSVC. | |||||
* | |||||
* See _msvc-doesnt-expand-va-args-correctly_ on SO for all the details. | |||||
* | |||||
* @param args Argument to expand. | |||||
*/ | |||||
#define ENTT_EXPAND(args) args | |||||
/** | |||||
* @brief Makes an already existing type a named type. | |||||
* @param type Type to assign a name to. | |||||
*/ | |||||
#define ENTT_NAMED_TYPE(type)\ | |||||
template<>\ | |||||
struct entt::named_type_traits<type>\ | |||||
: std::integral_constant<typename entt::hashed_string::hash_type, entt::hashed_string::to_value(#type)>\ | |||||
{\ | |||||
static_assert(std::is_same_v<std::decay_t<type>, type>);\ | |||||
}; | |||||
/** | |||||
* @brief Defines a named type (to use for structs). | |||||
* @param clazz Name of the type to define. | |||||
* @param body Body of the type to define. | |||||
*/ | |||||
#define ENTT_NAMED_STRUCT_ONLY(clazz, body)\ | |||||
struct clazz body;\ | |||||
ENTT_NAMED_TYPE(clazz) | |||||
/** | |||||
* @brief Defines a named type (to use for structs). | |||||
* @param ns Namespace where to define the named type. | |||||
* @param clazz Name of the type to define. | |||||
* @param body Body of the type to define. | |||||
*/ | |||||
#define ENTT_NAMED_STRUCT_WITH_NAMESPACE(ns, clazz, body)\ | |||||
namespace ns { struct clazz body; }\ | |||||
ENTT_NAMED_TYPE(ns::clazz) | |||||
/*! @brief Utility function to simulate macro overloading. */ | |||||
#define ENTT_NAMED_STRUCT_OVERLOAD(_1, _2, _3, FUNC, ...) FUNC | |||||
/*! @brief Defines a named type (to use for structs). */ | |||||
#define ENTT_NAMED_STRUCT(...) ENTT_EXPAND(ENTT_NAMED_STRUCT_OVERLOAD(__VA_ARGS__, ENTT_NAMED_STRUCT_WITH_NAMESPACE, ENTT_NAMED_STRUCT_ONLY,)(__VA_ARGS__)) | |||||
/** | |||||
* @brief Defines a named type (to use for classes). | |||||
* @param clazz Name of the type to define. | |||||
* @param body Body of the type to define. | |||||
*/ | |||||
#define ENTT_NAMED_CLASS_ONLY(clazz, body)\ | |||||
class clazz body;\ | |||||
ENTT_NAMED_TYPE(clazz) | |||||
/** | |||||
* @brief Defines a named type (to use for classes). | |||||
* @param ns Namespace where to define the named type. | |||||
* @param clazz Name of the type to define. | |||||
* @param body Body of the type to define. | |||||
*/ | |||||
#define ENTT_NAMED_CLASS_WITH_NAMESPACE(ns, clazz, body)\ | |||||
namespace ns { class clazz body; }\ | |||||
ENTT_NAMED_TYPE(ns::clazz) | |||||
/*! @brief Utility function to simulate macro overloading. */ | |||||
#define ENTT_NAMED_CLASS_MACRO(_1, _2, _3, FUNC, ...) FUNC | |||||
/*! @brief Defines a named type (to use for classes). */ | |||||
#define ENTT_NAMED_CLASS(...) ENTT_EXPAND(ENTT_NAMED_CLASS_MACRO(__VA_ARGS__, ENTT_NAMED_CLASS_WITH_NAMESPACE, ENTT_NAMED_CLASS_ONLY,)(__VA_ARGS__)) | |||||
#endif // ENTT_CORE_TYPE_TRAITS_HPP |
@ -0,0 +1,32 @@ | |||||
#ifndef ENTT_CORE_UTILITY_HPP | |||||
#define ENTT_CORE_UTILITY_HPP | |||||
namespace entt { | |||||
/** | |||||
* @brief Constant utility to disambiguate overloaded member functions. | |||||
* @tparam Type Function type of the desired overload. | |||||
* @tparam Class Type of class to which the member functions belong. | |||||
* @param member A valid pointer to a member function. | |||||
* @return Pointer to the member function. | |||||
*/ | |||||
template<typename Type, typename Class> | |||||
constexpr auto overload(Type Class:: *member) { return member; } | |||||
/** | |||||
* @brief Constant utility to disambiguate overloaded functions. | |||||
* @tparam Type Function type of the desired overload. | |||||
* @param func A valid pointer to a function. | |||||
* @return Pointer to the function. | |||||
*/ | |||||
template<typename Type> | |||||
constexpr auto overload(Type *func) { return func; } | |||||
} | |||||
#endif // ENTT_CORE_UTILITY_HPP |
@ -0,0 +1,180 @@ | |||||
#ifndef ENTT_ENTITY_ACTOR_HPP | |||||
#define ENTT_ENTITY_ACTOR_HPP | |||||
#include <cassert> | |||||
#include <utility> | |||||
#include <type_traits> | |||||
#include "../config/config.h" | |||||
#include "registry.hpp" | |||||
#include "entity.hpp" | |||||
#include "fwd.hpp" | |||||
namespace entt { | |||||
/** | |||||
* @brief Dedicated to those who aren't confident with entity-component systems. | |||||
* | |||||
* Tiny wrapper around a registry, for all those users that aren't confident | |||||
* with entity-component systems and prefer to iterate objects directly. | |||||
* | |||||
* @tparam Entity A valid entity type (see entt_traits for more details). | |||||
*/ | |||||
template<typename Entity> | |||||
struct basic_actor { | |||||
/*! @brief Type of registry used internally. */ | |||||
using registry_type = basic_registry<Entity>; | |||||
/*! @brief Underlying entity identifier. */ | |||||
using entity_type = Entity; | |||||
/** | |||||
* @brief Constructs an actor by using the given registry. | |||||
* @param ref An entity-component system properly initialized. | |||||
*/ | |||||
basic_actor(registry_type &ref) | |||||
: reg{&ref}, entt{ref.create()} | |||||
{} | |||||
/*! @brief Default destructor. */ | |||||
virtual ~basic_actor() { | |||||
reg->destroy(entt); | |||||
} | |||||
/** | |||||
* @brief Move constructor. | |||||
* | |||||
* After actor move construction, instances that have been moved from are | |||||
* placed in a valid but unspecified state. It's highly discouraged to | |||||
* continue using them. | |||||
* | |||||
* @param other The instance to move from. | |||||
*/ | |||||
basic_actor(basic_actor &&other) | |||||
: reg{other.reg}, entt{other.entt} | |||||
{ | |||||
other.entt = null; | |||||
} | |||||
/** | |||||
* @brief Move assignment operator. | |||||
* | |||||
* After actor move assignment, instances that have been moved from are | |||||
* placed in a valid but unspecified state. It's highly discouraged to | |||||
* continue using them. | |||||
* | |||||
* @param other The instance to move from. | |||||
* @return This actor. | |||||
*/ | |||||
basic_actor & operator=(basic_actor &&other) { | |||||
if(this != &other) { | |||||
auto tmp{std::move(other)}; | |||||
std::swap(reg, tmp.reg); | |||||
std::swap(entt, tmp.entt); | |||||
} | |||||
return *this; | |||||
} | |||||
/** | |||||
* @brief Assigns the given component to an actor. | |||||
* | |||||
* A new instance of the given component is created and initialized with the | |||||
* arguments provided (the component must have a proper constructor or be of | |||||
* aggregate type). Then the component is assigned to the actor.<br/> | |||||
* In case the actor already has a component of the given type, it's | |||||
* replaced with the new one. | |||||
* | |||||
* @tparam Component Type of the component to create. | |||||
* @tparam Args Types of arguments to use to construct the component. | |||||
* @param args Parameters to use to initialize the component. | |||||
* @return A reference to the newly created component. | |||||
*/ | |||||
template<typename Component, typename... Args> | |||||
decltype(auto) assign(Args &&... args) { | |||||
return reg->template assign_or_replace<Component>(entt, std::forward<Args>(args)...); | |||||
} | |||||
/** | |||||
* @brief Removes the given component from an actor. | |||||
* @tparam Component Type of the component to remove. | |||||
*/ | |||||
template<typename Component> | |||||
void remove() { | |||||
reg->template remove<Component>(entt); | |||||
} | |||||
/** | |||||
* @brief Checks if an actor has the given component. | |||||
* @tparam Component Type of the component for which to perform the check. | |||||
* @return True if the actor has the component, false otherwise. | |||||
*/ | |||||
template<typename Component> | |||||
bool has() const ENTT_NOEXCEPT { | |||||
return reg->template has<Component>(entt); | |||||
} | |||||
/** | |||||
* @brief Returns references to the given components for an actor. | |||||
* @tparam Component Types of components to get. | |||||
* @return References to the components owned by the actor. | |||||
*/ | |||||
template<typename... Component> | |||||
decltype(auto) get() const ENTT_NOEXCEPT { | |||||
return std::as_const(*reg).template get<Component...>(entt); | |||||
} | |||||
/*! @copydoc get */ | |||||
template<typename... Component> | |||||
decltype(auto) get() ENTT_NOEXCEPT { | |||||
return reg->template get<Component...>(entt); | |||||
} | |||||
/** | |||||
* @brief Returns pointers to the given components for an actor. | |||||
* @tparam Component Types of components to get. | |||||
* @return Pointers to the components owned by the actor. | |||||
*/ | |||||
template<typename... Component> | |||||
auto try_get() const ENTT_NOEXCEPT { | |||||
return std::as_const(*reg).template try_get<Component...>(entt); | |||||
} | |||||
/*! @copydoc try_get */ | |||||
template<typename... Component> | |||||
auto try_get() ENTT_NOEXCEPT { | |||||
return reg->template try_get<Component...>(entt); | |||||
} | |||||
/** | |||||
* @brief Returns a reference to the underlying registry. | |||||
* @return A reference to the underlying registry. | |||||
*/ | |||||
inline const registry_type & backend() const ENTT_NOEXCEPT { | |||||
return *reg; | |||||
} | |||||
/*! @copydoc backend */ | |||||
inline registry_type & backend() ENTT_NOEXCEPT { | |||||
return const_cast<registry_type &>(std::as_const(*this).backend()); | |||||
} | |||||
/** | |||||
* @brief Returns the entity associated with an actor. | |||||
* @return The entity associated with the actor. | |||||
*/ | |||||
inline entity_type entity() const ENTT_NOEXCEPT { | |||||
return entt; | |||||
} | |||||
private: | |||||
registry_type *reg; | |||||
Entity entt; | |||||
}; | |||||
} | |||||
#endif // ENTT_ENTITY_ACTOR_HPP |
@ -0,0 +1,169 @@ | |||||
#ifndef ENTT_ENTITY_ENTITY_HPP | |||||
#define ENTT_ENTITY_ENTITY_HPP | |||||
#include "../config/config.h" | |||||
namespace entt { | |||||
/** | |||||
* @brief Entity traits. | |||||
* | |||||
* Primary template isn't defined on purpose. All the specializations give a | |||||
* compile-time error unless the template parameter is an accepted entity type. | |||||
*/ | |||||
template<typename> | |||||
struct entt_traits; | |||||
/** | |||||
* @brief Entity traits for a 16 bits entity identifier. | |||||
* | |||||
* A 16 bits entity identifier guarantees: | |||||
* | |||||
* * 12 bits for the entity number (up to 4k entities). | |||||
* * 4 bit for the version (resets in [0-15]). | |||||
*/ | |||||
template<> | |||||
struct entt_traits<std::uint16_t> { | |||||
/*! @brief Underlying entity type. */ | |||||
using entity_type = std::uint16_t; | |||||
/*! @brief Underlying version type. */ | |||||
using version_type = std::uint8_t; | |||||
/*! @brief Difference type. */ | |||||
using difference_type = std::int32_t; | |||||
/*! @brief Mask to use to get the entity number out of an identifier. */ | |||||
static constexpr std::uint16_t entity_mask = 0xFFF; | |||||
/*! @brief Mask to use to get the version out of an identifier. */ | |||||
static constexpr std::uint16_t version_mask = 0xF; | |||||
/*! @brief Extent of the entity number within an identifier. */ | |||||
static constexpr auto entity_shift = 12; | |||||
}; | |||||
/** | |||||
* @brief Entity traits for a 32 bits entity identifier. | |||||
* | |||||
* A 32 bits entity identifier guarantees: | |||||
* | |||||
* * 20 bits for the entity number (suitable for almost all the games). | |||||
* * 12 bit for the version (resets in [0-4095]). | |||||
*/ | |||||
template<> | |||||
struct entt_traits<std::uint32_t> { | |||||
/*! @brief Underlying entity type. */ | |||||
using entity_type = std::uint32_t; | |||||
/*! @brief Underlying version type. */ | |||||
using version_type = std::uint16_t; | |||||
/*! @brief Difference type. */ | |||||
using difference_type = std::int64_t; | |||||
/*! @brief Mask to use to get the entity number out of an identifier. */ | |||||
static constexpr std::uint32_t entity_mask = 0xFFFFF; | |||||
/*! @brief Mask to use to get the version out of an identifier. */ | |||||
static constexpr std::uint32_t version_mask = 0xFFF; | |||||
/*! @brief Extent of the entity number within an identifier. */ | |||||
static constexpr auto entity_shift = 20; | |||||
}; | |||||
/** | |||||
* @brief Entity traits for a 64 bits entity identifier. | |||||
* | |||||
* A 64 bits entity identifier guarantees: | |||||
* | |||||
* * 32 bits for the entity number (an indecently large number). | |||||
* * 32 bit for the version (an indecently large number). | |||||
*/ | |||||
template<> | |||||
struct entt_traits<std::uint64_t> { | |||||
/*! @brief Underlying entity type. */ | |||||
using entity_type = std::uint64_t; | |||||
/*! @brief Underlying version type. */ | |||||
using version_type = std::uint32_t; | |||||
/*! @brief Difference type. */ | |||||
using difference_type = std::int64_t; | |||||
/*! @brief Mask to use to get the entity number out of an identifier. */ | |||||
static constexpr std::uint64_t entity_mask = 0xFFFFFFFF; | |||||
/*! @brief Mask to use to get the version out of an identifier. */ | |||||
static constexpr std::uint64_t version_mask = 0xFFFFFFFF; | |||||
/*! @brief Extent of the entity number within an identifier. */ | |||||
static constexpr auto entity_shift = 32; | |||||
}; | |||||
/** | |||||
* @cond TURN_OFF_DOXYGEN | |||||
* Internal details not to be documented. | |||||
*/ | |||||
namespace internal { | |||||
struct null { | |||||
template<typename Entity> | |||||
constexpr operator Entity() const ENTT_NOEXCEPT { | |||||
using traits_type = entt_traits<Entity>; | |||||
return traits_type::entity_mask | (traits_type::version_mask << traits_type::entity_shift); | |||||
} | |||||
constexpr bool operator==(null) const ENTT_NOEXCEPT { | |||||
return true; | |||||
} | |||||
constexpr bool operator!=(null) const ENTT_NOEXCEPT { | |||||
return false; | |||||
} | |||||
template<typename Entity> | |||||
constexpr bool operator==(const Entity entity) const ENTT_NOEXCEPT { | |||||
return entity == static_cast<Entity>(*this); | |||||
} | |||||
template<typename Entity> | |||||
constexpr bool operator!=(const Entity entity) const ENTT_NOEXCEPT { | |||||
return entity != static_cast<Entity>(*this); | |||||
} | |||||
}; | |||||
template<typename Entity> | |||||
constexpr bool operator==(const Entity entity, null other) ENTT_NOEXCEPT { | |||||
return other == entity; | |||||
} | |||||
template<typename Entity> | |||||
constexpr bool operator!=(const Entity entity, null other) ENTT_NOEXCEPT { | |||||
return other != entity; | |||||
} | |||||
} | |||||
/** | |||||
* Internal details not to be documented. | |||||
* @endcond TURN_OFF_DOXYGEN | |||||
*/ | |||||
/** | |||||
* @brief Null entity. | |||||
* | |||||
* There exist implicit conversions from this variable to entity identifiers of | |||||
* any allowed type. Similarly, there exist comparision operators between the | |||||
* null entity and any other entity identifier. | |||||
*/ | |||||
constexpr auto null = internal::null{}; | |||||
} | |||||
#endif // ENTT_ENTITY_ENTITY_HPP |
@ -0,0 +1,89 @@ | |||||
#ifndef ENTT_ENTITY_FWD_HPP | |||||
#define ENTT_ENTITY_FWD_HPP | |||||
#include <cstdint> | |||||
#include "../config/config.h" | |||||
namespace entt { | |||||
/*! @class basic_registry */ | |||||
template <typename> | |||||
class basic_registry; | |||||
/*! @class basic_view */ | |||||
template<typename, typename...> | |||||
class basic_view; | |||||
/*! @class basic_runtime_view */ | |||||
template<typename> | |||||
class basic_runtime_view; | |||||
/*! @class basic_group */ | |||||
template<typename...> | |||||
class basic_group; | |||||
/*! @class basic_actor */ | |||||
template <typename> | |||||
struct basic_actor; | |||||
/*! @class basic_prototype */ | |||||
template<typename> | |||||
class basic_prototype; | |||||
/*! @class basic_snapshot */ | |||||
template<typename> | |||||
class basic_snapshot; | |||||
/*! @class basic_snapshot_loader */ | |||||
template<typename> | |||||
class basic_snapshot_loader; | |||||
/*! @class basic_continuous_loader */ | |||||
template<typename> | |||||
class basic_continuous_loader; | |||||
/*! @brief Alias declaration for the most common use case. */ | |||||
using entity = std::uint32_t; | |||||
/*! @brief Alias declaration for the most common use case. */ | |||||
using registry = basic_registry<entity>; | |||||
/*! @brief Alias declaration for the most common use case. */ | |||||
using actor = basic_actor<entity>; | |||||
/*! @brief Alias declaration for the most common use case. */ | |||||
using prototype = basic_prototype<entity>; | |||||
/*! @brief Alias declaration for the most common use case. */ | |||||
using snapshot = basic_snapshot<entity>; | |||||
/*! @brief Alias declaration for the most common use case. */ | |||||
using snapshot_loader = basic_snapshot_loader<entity>; | |||||
/*! @brief Alias declaration for the most common use case. */ | |||||
using continuous_loader = basic_continuous_loader<entity>; | |||||
/** | |||||
* @brief Alias declaration for the most common use case. | |||||
* @tparam Component Types of components iterated by the view. | |||||
*/ | |||||
template<typename... Types> | |||||
using view = basic_view<entity, Types...>; | |||||
/*! @brief Alias declaration for the most common use case. */ | |||||
using runtime_view = basic_runtime_view<entity>; | |||||
/** | |||||
* @brief Alias declaration for the most common use case. | |||||
* @tparam Types Types of components iterated by the group. | |||||
*/ | |||||
template<typename... Types> | |||||
using group = basic_group<entity, Types...>; | |||||
} | |||||
#endif // ENTT_ENTITY_FWD_HPP |
@ -0,0 +1,819 @@ | |||||
#ifndef ENTT_ENTITY_GROUP_HPP | |||||
#define ENTT_ENTITY_GROUP_HPP | |||||
#include <tuple> | |||||
#include <utility> | |||||
#include <type_traits> | |||||
#include "../config/config.h" | |||||
#include "../core/type_traits.hpp" | |||||
#include "sparse_set.hpp" | |||||
#include "storage.hpp" | |||||
#include "fwd.hpp" | |||||
namespace entt { | |||||
/** | |||||
* @brief Alias for lists of observed components. | |||||
* @tparam Type List of types. | |||||
*/ | |||||
template<typename... Type> | |||||
struct get_t: type_list<Type...> {}; | |||||
/** | |||||
* @brief Variable template for lists of observed components. | |||||
* @tparam Type List of types. | |||||
*/ | |||||
template<typename... Type> | |||||
constexpr get_t<Type...> get{}; | |||||
/** | |||||
* @brief Group. | |||||
* | |||||
* Primary template isn't defined on purpose. All the specializations give a | |||||
* compile-time error, but for a few reasonable cases. | |||||
*/ | |||||
template<typename...> | |||||
class basic_group; | |||||
/** | |||||
* @brief Non-owning group. | |||||
* | |||||
* A non-owning group returns all the entities and only the entities that have | |||||
* at least the given components. Moreover, it's guaranteed that the entity list | |||||
* is tightly packed in memory for fast iterations.<br/> | |||||
* In general, non-owning groups don't stay true to the order of any set of | |||||
* components unless users explicitly sort them. | |||||
* | |||||
* @b Important | |||||
* | |||||
* Iterators aren't invalidated if: | |||||
* | |||||
* * New instances of the given components are created and assigned to entities. | |||||
* * The entity currently pointed is modified (as an example, if one of the | |||||
* given components is removed from the entity to which the iterator points). | |||||
* | |||||
* In all the other cases, modifying the pools of the given components in any | |||||
* way invalidates all the iterators and using them results in undefined | |||||
* behavior. | |||||
* | |||||
* @note | |||||
* Groups share references to the underlying data structures of the registry | |||||
* that generated them. Therefore any change to the entities and to the | |||||
* components made by means of the registry are immediately reflected by all the | |||||
* groups.<br/> | |||||
* Moreover, sorting a non-owning group affects all the instance of the same | |||||
* group (it means that users don't have to call `sort` on each instance to sort | |||||
* all of them because they share the set of entities). | |||||
* | |||||
* @warning | |||||
* Lifetime of a group must overcome the one of the registry that generated it. | |||||
* In any other case, attempting to use a group results in undefined behavior. | |||||
* | |||||
* @tparam Entity A valid entity type (see entt_traits for more details). | |||||
* @tparam Get Types of components observed by the group. | |||||
*/ | |||||
template<typename Entity, typename... Get> | |||||
class basic_group<Entity, get_t<Get...>> { | |||||
static_assert(sizeof...(Get) > 0); | |||||
/*! @brief A registry is allowed to create groups. */ | |||||
friend class basic_registry<Entity>; | |||||
template<typename Component> | |||||
using pool_type = std::conditional_t<std::is_const_v<Component>, const storage<Entity, std::remove_const_t<Component>>, storage<Entity, Component>>; | |||||
// we could use pool_type<Get> *..., but vs complains about it and refuses to compile for unknown reasons (likely a bug) | |||||
basic_group(sparse_set<Entity> *ref, storage<Entity, std::remove_const_t<Get>> *... get) ENTT_NOEXCEPT | |||||
: handler{ref}, | |||||
pools{get...} | |||||
{} | |||||
template<typename Func, typename... Weak> | |||||
inline void traverse(Func func, type_list<Weak...>) const { | |||||
for(const auto entt: *handler) { | |||||
if constexpr(std::is_invocable_v<Func, decltype(get<Weak>({}))...>) { | |||||
func(std::get<pool_type<Weak> *>(pools)->get(entt)...); | |||||
} else { | |||||
func(entt, std::get<pool_type<Weak> *>(pools)->get(entt)...); | |||||
} | |||||
}; | |||||
} | |||||
public: | |||||
/*! @brief Underlying entity identifier. */ | |||||
using entity_type = typename sparse_set<Entity>::entity_type; | |||||
/*! @brief Unsigned integer type. */ | |||||
using size_type = typename sparse_set<Entity>::size_type; | |||||
/*! @brief Input iterator type. */ | |||||
using iterator_type = typename sparse_set<Entity>::iterator_type; | |||||
/** | |||||
* @brief Returns the number of existing components of the given type. | |||||
* @tparam Component Type of component of which to return the size. | |||||
* @return Number of existing components of the given type. | |||||
*/ | |||||
template<typename Component> | |||||
size_type size() const ENTT_NOEXCEPT { | |||||
return std::get<pool_type<Component> *>(pools)->size(); | |||||
} | |||||
/** | |||||
* @brief Returns the number of entities that have the given components. | |||||
* @return Number of entities that have the given components. | |||||
*/ | |||||
size_type size() const ENTT_NOEXCEPT { | |||||
return handler->size(); | |||||
} | |||||
/** | |||||
* @brief Returns the number of elements that a group has currently | |||||
* allocated space for. | |||||
* @return Capacity of the group. | |||||
*/ | |||||
size_type capacity() const ENTT_NOEXCEPT { | |||||
return handler->capacity(); | |||||
} | |||||
/*! @brief Requests the removal of unused capacity. */ | |||||
void shrink_to_fit() { | |||||
handler->shrink_to_fit(); | |||||
} | |||||
/** | |||||
* @brief Checks whether the pool of a given component is empty. | |||||
* @tparam Component Type of component in which one is interested. | |||||
* @return True if the pool of the given component is empty, false | |||||
* otherwise. | |||||
*/ | |||||
template<typename Component> | |||||
bool empty() const ENTT_NOEXCEPT { | |||||
return std::get<pool_type<Component> *>(pools)->empty(); | |||||
} | |||||
/** | |||||
* @brief Checks whether the group is empty. | |||||
* @return True if the group is empty, false otherwise. | |||||
*/ | |||||
bool empty() const ENTT_NOEXCEPT { | |||||
return handler->empty(); | |||||
} | |||||
/** | |||||
* @brief Direct access to the list of components of a given pool. | |||||
* | |||||
* The returned pointer is such that range | |||||
* `[raw<Component>(), raw<Component>() + size<Component>()]` is always a | |||||
* valid range, even if the container is empty. | |||||
* | |||||
* @note | |||||
* There are no guarantees on the order of the components. Use `begin` and | |||||
* `end` if you want to iterate the group in the expected order. | |||||
* | |||||
* @tparam Component Type of component in which one is interested. | |||||
* @return A pointer to the array of components. | |||||
*/ | |||||
template<typename Component> | |||||
Component * raw() const ENTT_NOEXCEPT { | |||||
return std::get<pool_type<Component> *>(pools)->raw(); | |||||
} | |||||
/** | |||||
* @brief Direct access to the list of entities of a given pool. | |||||
* | |||||
* The returned pointer is such that range | |||||
* `[data<Component>(), data<Component>() + size<Component>()]` is always a | |||||
* valid range, even if the container is empty. | |||||
* | |||||
* @note | |||||
* There are no guarantees on the order of the entities. Use `begin` and | |||||
* `end` if you want to iterate the group in the expected order. | |||||
* | |||||
* @tparam Component Type of component in which one is interested. | |||||
* @return A pointer to the array of entities. | |||||
*/ | |||||
template<typename Component> | |||||
const entity_type * data() const ENTT_NOEXCEPT { | |||||
return std::get<pool_type<Component> *>(pools)->data(); | |||||
} | |||||
/** | |||||
* @brief Direct access to the list of entities. | |||||
* | |||||
* The returned pointer is such that range `[data(), data() + size()]` is | |||||
* always a valid range, even if the container is empty. | |||||
* | |||||
* @note | |||||
* There are no guarantees on the order of the entities. Use `begin` and | |||||
* `end` if you want to iterate the group in the expected order. | |||||
* | |||||
* @return A pointer to the array of entities. | |||||
*/ | |||||
const entity_type * data() const ENTT_NOEXCEPT { | |||||
return handler->data(); | |||||
} | |||||
/** | |||||
* @brief Returns an iterator to the first entity that has the given | |||||
* components. | |||||
* | |||||
* The returned iterator points to the first entity that has the given | |||||
* components. If the group is empty, the returned iterator will be equal to | |||||
* `end()`. | |||||
* | |||||
* @note | |||||
* Input iterators stay true to the order imposed to the underlying data | |||||
* structures. | |||||
* | |||||
* @return An iterator to the first entity that has the given components. | |||||
*/ | |||||
iterator_type begin() const ENTT_NOEXCEPT { | |||||
return handler->begin(); | |||||
} | |||||
/** | |||||
* @brief Returns an iterator that is past the last entity that has the | |||||
* given components. | |||||
* | |||||
* The returned iterator points to the entity following the last entity that | |||||
* has the given components. Attempting to dereference the returned iterator | |||||
* results in undefined behavior. | |||||
* | |||||
* @note | |||||
* Input iterators stay true to the order imposed to the underlying data | |||||
* structures. | |||||
* | |||||
* @return An iterator to the entity following the last entity that has the | |||||
* given components. | |||||
*/ | |||||
iterator_type end() const ENTT_NOEXCEPT { | |||||
return handler->end(); | |||||
} | |||||
/** | |||||
* @brief Finds an entity. | |||||
* @param entt A valid entity identifier. | |||||
* @return An iterator to the given entity if it's found, past the end | |||||
* iterator otherwise. | |||||
*/ | |||||
iterator_type find(const entity_type entt) const ENTT_NOEXCEPT { | |||||
const auto it = handler->find(entt); | |||||
return it != end() && *it == entt ? it : end(); | |||||
} | |||||
/** | |||||
* @brief Returns the identifier that occupies the given position. | |||||
* @param pos Position of the element to return. | |||||
* @return The identifier that occupies the given position. | |||||
*/ | |||||
entity_type operator[](const size_type pos) const ENTT_NOEXCEPT { | |||||
return begin()[pos]; | |||||
} | |||||
/** | |||||
* @brief Checks if a group contains an entity. | |||||
* @param entt A valid entity identifier. | |||||
* @return True if the group contains the given entity, false otherwise. | |||||
*/ | |||||
bool contains(const entity_type entt) const ENTT_NOEXCEPT { | |||||
return find(entt) != end(); | |||||
} | |||||
/** | |||||
* @brief Returns the components assigned to the given entity. | |||||
* | |||||
* Prefer this function instead of `registry::get` during iterations. It has | |||||
* far better performance than its companion function. | |||||
* | |||||
* @warning | |||||
* Attempting to use an invalid component type results in a compilation | |||||
* error. Attempting to use an entity that doesn't belong to the group | |||||
* results in undefined behavior.<br/> | |||||
* An assertion will abort the execution at runtime in debug mode if the | |||||
* group doesn't contain the given entity. | |||||
* | |||||
* @tparam Component Types of components to get. | |||||
* @param entt A valid entity identifier. | |||||
* @return The components assigned to the entity. | |||||
*/ | |||||
template<typename... Component> | |||||
decltype(auto) get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT { | |||||
ENTT_ASSERT(contains(entt)); | |||||
if constexpr(sizeof...(Component) == 1) { | |||||
return (std::get<pool_type<Component> *>(pools)->get(entt), ...); | |||||
} else { | |||||
return std::tuple<decltype(get<Component>(entt))...>{get<Component>(entt)...}; | |||||
} | |||||
} | |||||
/** | |||||
* @brief Iterates entities and components and applies the given function | |||||
* object to them. | |||||
* | |||||
* The function object is invoked for each entity. It is provided with the | |||||
* entity itself and a set of references to all its components. The | |||||
* _constness_ of the components is as requested.<br/> | |||||
* The signature of the function must be equivalent to one of the following | |||||
* forms: | |||||
* | |||||
* @code{.cpp} | |||||
* void(const entity_type, Get &...); | |||||
* void(Get &...); | |||||
* @endcode | |||||
* | |||||
* @note | |||||
* Empty types aren't explicitly instantiated. Therefore, temporary objects | |||||
* are returned during iterations. They can be caught only by copy or with | |||||
* const references. | |||||
* | |||||
* @tparam Func Type of the function object to invoke. | |||||
* @param func A valid function object. | |||||
*/ | |||||
template<typename Func> | |||||
inline void each(Func func) const { | |||||
traverse(std::move(func), type_list<Get...>{}); | |||||
} | |||||
/** | |||||
* @brief Iterates entities and components and applies the given function | |||||
* object to them. | |||||
* | |||||
* The function object is invoked for each entity. It is provided with the | |||||
* entity itself and a set of references to non-empty components. The | |||||
* _constness_ of the components is as requested.<br/> | |||||
* The signature of the function must be equivalent to one of the following | |||||
* forms: | |||||
* | |||||
* @code{.cpp} | |||||
* void(const entity_type, Type &...); | |||||
* void(Type &...); | |||||
* @endcode | |||||
* | |||||
* @sa each | |||||
* | |||||
* @tparam Func Type of the function object to invoke. | |||||
* @param func A valid function object. | |||||
*/ | |||||
template<typename Func> | |||||
inline void less(Func func) const { | |||||
using non_empty_get = type_list_cat_t<std::conditional_t<std::is_empty_v<Get>, type_list<>, type_list<Get>>...>; | |||||
traverse(std::move(func), non_empty_get{}); | |||||
} | |||||
/** | |||||
* @brief Sort the shared pool of entities according to the given component. | |||||
* | |||||
* Non-owning groups of the same type share with the registry a pool of | |||||
* entities with its own order that doesn't depend on the order of any pool | |||||
* of components. Users can order the underlying data structure so that it | |||||
* respects the order of the pool of the given component. | |||||
* | |||||
* @note | |||||
* The shared pool of entities and thus its order is affected by the changes | |||||
* to each and every pool that it tracks. Therefore changes to those pools | |||||
* can quickly ruin the order imposed to the pool of entities shared between | |||||
* the non-owning groups. | |||||
* | |||||
* @tparam Component Type of component to use to impose the order. | |||||
*/ | |||||
template<typename Component> | |||||
void sort() const { | |||||
handler->respect(*std::get<pool_type<Component> *>(pools)); | |||||
} | |||||
private: | |||||
sparse_set<entity_type> *handler; | |||||
const std::tuple<pool_type<Get> *...> pools; | |||||
}; | |||||
/** | |||||
* @brief Owning group. | |||||
* | |||||
* Owning groups return all the entities and only the entities that have at | |||||
* least the given components. Moreover: | |||||
* | |||||
* * It's guaranteed that the entity list is tightly packed in memory for fast | |||||
* iterations. | |||||
* * It's guaranteed that the lists of owned components are tightly packed in | |||||
* memory for even faster iterations and to allow direct access. | |||||
* * They stay true to the order of the owned components and all the owned | |||||
* components have the same order in memory. | |||||
* | |||||
* The more types of components are owned by a group, the faster it is to | |||||
* iterate them. | |||||
* | |||||
* @b Important | |||||
* | |||||
* Iterators aren't invalidated if: | |||||
* | |||||
* * New instances of the given components are created and assigned to entities. | |||||
* * The entity currently pointed is modified (as an example, if one of the | |||||
* given components is removed from the entity to which the iterator points). | |||||
* | |||||
* In all the other cases, modifying the pools of the given components in any | |||||
* way invalidates all the iterators and using them results in undefined | |||||
* behavior. | |||||
* | |||||
* @note | |||||
* Groups share references to the underlying data structures of the registry | |||||
* that generated them. Therefore any change to the entities and to the | |||||
* components made by means of the registry are immediately reflected by all the | |||||
* groups. | |||||
* Moreover, sorting an owning group affects all the instance of the same group | |||||
* (it means that users don't have to call `sort` on each instance to sort all | |||||
* of them because they share the underlying data structure). | |||||
* | |||||
* @warning | |||||
* Lifetime of a group must overcome the one of the registry that generated it. | |||||
* In any other case, attempting to use a group results in undefined behavior. | |||||
* | |||||
* @tparam Entity A valid entity type (see entt_traits for more details). | |||||
* @tparam Get Types of components observed by the group. | |||||
* @tparam Owned Types of components owned by the group. | |||||
*/ | |||||
template<typename Entity, typename... Get, typename... Owned> | |||||
class basic_group<Entity, get_t<Get...>, Owned...> { | |||||
static_assert(sizeof...(Get) + sizeof...(Owned) > 0); | |||||
/*! @brief A registry is allowed to create groups. */ | |||||
friend class basic_registry<Entity>; | |||||
template<typename Component> | |||||
using pool_type = std::conditional_t<std::is_const_v<Component>, const storage<Entity, std::remove_const_t<Component>>, storage<Entity, Component>>; | |||||
template<typename Component> | |||||
using component_iterator_type = decltype(std::declval<pool_type<Component>>().begin()); | |||||
// we could use pool_type<Type> *..., but vs complains about it and refuses to compile for unknown reasons (likely a bug) | |||||
basic_group(const typename basic_registry<Entity>::size_type *sz, storage<Entity, std::remove_const_t<Owned>> *... owned, storage<Entity, std::remove_const_t<Get>> *... get) ENTT_NOEXCEPT | |||||
: length{sz}, | |||||
pools{owned..., get...} | |||||
{} | |||||
template<typename Func, typename... Strong, typename... Weak> | |||||
inline void traverse(Func func, type_list<Strong...>, type_list<Weak...>) const { | |||||
auto raw = std::make_tuple((std::get<pool_type<Strong> *>(pools)->end() - *length)...); | |||||
[[maybe_unused]] auto data = std::get<0>(pools)->sparse_set<entity_type>::end() - *length; | |||||
for(auto next = *length; next; --next) { | |||||
if constexpr(std::is_invocable_v<Func, decltype(get<Strong>({}))..., decltype(get<Weak>({}))...>) { | |||||
if constexpr(sizeof...(Weak) == 0) { | |||||
func(*(std::get<component_iterator_type<Strong>>(raw)++)...); | |||||
} else { | |||||
const auto entt = *(data++); | |||||
func(*(std::get<component_iterator_type<Strong>>(raw)++)..., std::get<pool_type<Weak> *>(pools)->get(entt)...); | |||||
} | |||||
} else { | |||||
const auto entt = *(data++); | |||||
func(entt, *(std::get<component_iterator_type<Strong>>(raw)++)..., std::get<pool_type<Weak> *>(pools)->get(entt)...); | |||||
} | |||||
} | |||||
} | |||||
public: | |||||
/*! @brief Underlying entity identifier. */ | |||||
using entity_type = typename sparse_set<Entity>::entity_type; | |||||
/*! @brief Unsigned integer type. */ | |||||
using size_type = typename sparse_set<Entity>::size_type; | |||||
/*! @brief Input iterator type. */ | |||||
using iterator_type = typename sparse_set<Entity>::iterator_type; | |||||
/** | |||||
* @brief Returns the number of existing components of the given type. | |||||
* @tparam Component Type of component of which to return the size. | |||||
* @return Number of existing components of the given type. | |||||
*/ | |||||
template<typename Component> | |||||
size_type size() const ENTT_NOEXCEPT { | |||||
return std::get<pool_type<Component> *>(pools)->size(); | |||||
} | |||||
/** | |||||
* @brief Returns the number of entities that have the given components. | |||||
* @return Number of entities that have the given components. | |||||
*/ | |||||
size_type size() const ENTT_NOEXCEPT { | |||||
return *length; | |||||
} | |||||
/** | |||||
* @brief Checks whether the pool of a given component is empty. | |||||
* @tparam Component Type of component in which one is interested. | |||||
* @return True if the pool of the given component is empty, false | |||||
* otherwise. | |||||
*/ | |||||
template<typename Component> | |||||
bool empty() const ENTT_NOEXCEPT { | |||||
return std::get<pool_type<Component> *>(pools)->empty(); | |||||
} | |||||
/** | |||||
* @brief Checks whether the group is empty. | |||||
* @return True if the group is empty, false otherwise. | |||||
*/ | |||||
bool empty() const ENTT_NOEXCEPT { | |||||
return !*length; | |||||
} | |||||
/** | |||||
* @brief Direct access to the list of components of a given pool. | |||||
* | |||||
* The returned pointer is such that range | |||||
* `[raw<Component>(), raw<Component>() + size<Component>()]` is always a | |||||
* valid range, even if the container is empty.<br/> | |||||
* Moreover, in case the group owns the given component, the range | |||||
* `[raw<Component>(), raw<Component>() + size()]` is such that it contains | |||||
* the instances that are part of the group itself. | |||||
* | |||||
* @note | |||||
* There are no guarantees on the order of the components. Use `begin` and | |||||
* `end` if you want to iterate the group in the expected order. | |||||
* | |||||
* @tparam Component Type of component in which one is interested. | |||||
* @return A pointer to the array of components. | |||||
*/ | |||||
template<typename Component> | |||||
Component * raw() const ENTT_NOEXCEPT { | |||||
return std::get<pool_type<Component> *>(pools)->raw(); | |||||
} | |||||
/** | |||||
* @brief Direct access to the list of entities of a given pool. | |||||
* | |||||
* The returned pointer is such that range | |||||
* `[data<Component>(), data<Component>() + size<Component>()]` is always a | |||||
* valid range, even if the container is empty.<br/> | |||||
* Moreover, in case the group owns the given component, the range | |||||
* `[data<Component>(), data<Component>() + size()]` is such that it | |||||
* contains the entities that are part of the group itself. | |||||
* | |||||
* @note | |||||
* There are no guarantees on the order of the entities. Use `begin` and | |||||
* `end` if you want to iterate the group in the expected order. | |||||
* | |||||
* @tparam Component Type of component in which one is interested. | |||||
* @return A pointer to the array of entities. | |||||
*/ | |||||
template<typename Component> | |||||
const entity_type * data() const ENTT_NOEXCEPT { | |||||
return std::get<pool_type<Component> *>(pools)->data(); | |||||
} | |||||
/** | |||||
* @brief Direct access to the list of entities. | |||||
* | |||||
* The returned pointer is such that range `[data(), data() + size()]` is | |||||
* always a valid range, even if the container is empty. | |||||
* | |||||
* @note | |||||
* There are no guarantees on the order of the entities. Use `begin` and | |||||
* `end` if you want to iterate the group in the expected order. | |||||
* | |||||
* @return A pointer to the array of entities. | |||||
*/ | |||||
const entity_type * data() const ENTT_NOEXCEPT { | |||||
return std::get<0>(pools)->data(); | |||||
} | |||||
/** | |||||
* @brief Returns an iterator to the first entity that has the given | |||||
* components. | |||||
* | |||||
* The returned iterator points to the first entity that has the given | |||||
* components. If the group is empty, the returned iterator will be equal to | |||||
* `end()`. | |||||
* | |||||
* @note | |||||
* Input iterators stay true to the order imposed to the underlying data | |||||
* structures. | |||||
* | |||||
* @return An iterator to the first entity that has the given components. | |||||
*/ | |||||
iterator_type begin() const ENTT_NOEXCEPT { | |||||
return std::get<0>(pools)->sparse_set<entity_type>::end() - *length; | |||||
} | |||||
/** | |||||
* @brief Returns an iterator that is past the last entity that has the | |||||
* given components. | |||||
* | |||||
* The returned iterator points to the entity following the last entity that | |||||
* has the given components. Attempting to dereference the returned iterator | |||||
* results in undefined behavior. | |||||
* | |||||
* @note | |||||
* Input iterators stay true to the order imposed to the underlying data | |||||
* structures. | |||||
* | |||||
* @return An iterator to the entity following the last entity that has the | |||||
* given components. | |||||
*/ | |||||
iterator_type end() const ENTT_NOEXCEPT { | |||||
return std::get<0>(pools)->sparse_set<entity_type>::end(); | |||||
} | |||||
/** | |||||
* @brief Finds an entity. | |||||
* @param entt A valid entity identifier. | |||||
* @return An iterator to the given entity if it's found, past the end | |||||
* iterator otherwise. | |||||
*/ | |||||
iterator_type find(const entity_type entt) const ENTT_NOEXCEPT { | |||||
const auto it = std::get<0>(pools)->find(entt); | |||||
return it != end() && it >= begin() && *it == entt ? it : end(); | |||||
} | |||||
/** | |||||
* @brief Returns the identifier that occupies the given position. | |||||
* @param pos Position of the element to return. | |||||
* @return The identifier that occupies the given position. | |||||
*/ | |||||
entity_type operator[](const size_type pos) const ENTT_NOEXCEPT { | |||||
return begin()[pos]; | |||||
} | |||||
/** | |||||
* @brief Checks if a group contains an entity. | |||||
* @param entt A valid entity identifier. | |||||
* @return True if the group contains the given entity, false otherwise. | |||||
*/ | |||||
bool contains(const entity_type entt) const ENTT_NOEXCEPT { | |||||
return find(entt) != end(); | |||||
} | |||||
/** | |||||
* @brief Returns the components assigned to the given entity. | |||||
* | |||||
* Prefer this function instead of `registry::get` during iterations. It has | |||||
* far better performance than its companion function. | |||||
* | |||||
* @warning | |||||
* Attempting to use an invalid component type results in a compilation | |||||
* error. Attempting to use an entity that doesn't belong to the group | |||||
* results in undefined behavior.<br/> | |||||
* An assertion will abort the execution at runtime in debug mode if the | |||||
* group doesn't contain the given entity. | |||||
* | |||||
* @tparam Component Types of components to get. | |||||
* @param entt A valid entity identifier. | |||||
* @return The components assigned to the entity. | |||||
*/ | |||||
template<typename... Component> | |||||
decltype(auto) get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT { | |||||
ENTT_ASSERT(contains(entt)); | |||||
if constexpr(sizeof...(Component) == 1) { | |||||
return (std::get<pool_type<Component> *>(pools)->get(entt), ...); | |||||
} else { | |||||
return std::tuple<decltype(get<Component>(entt))...>{get<Component>(entt)...}; | |||||
} | |||||
} | |||||
/** | |||||
* @brief Iterates entities and components and applies the given function | |||||
* object to them. | |||||
* | |||||
* The function object is invoked for each entity. It is provided with the | |||||
* entity itself and a set of references to all its components. The | |||||
* _constness_ of the components is as requested.<br/> | |||||
* The signature of the function must be equivalent to one of the following | |||||
* forms: | |||||
* | |||||
* @code{.cpp} | |||||
* void(const entity_type, Owned &..., Get &...); | |||||
* void(Owned &..., Get &...); | |||||
* @endcode | |||||
* | |||||
* @note | |||||
* Empty types aren't explicitly instantiated. Therefore, temporary objects | |||||
* are returned during iterations. They can be caught only by copy or with | |||||
* const references. | |||||
* | |||||
* @tparam Func Type of the function object to invoke. | |||||
* @param func A valid function object. | |||||
*/ | |||||
template<typename Func> | |||||
inline void each(Func func) const { | |||||
traverse(std::move(func), type_list<Owned...>{}, type_list<Get...>{}); | |||||
} | |||||
/** | |||||
* @brief Iterates entities and components and applies the given function | |||||
* object to them. | |||||
* | |||||
* The function object is invoked for each entity. It is provided with the | |||||
* entity itself and a set of references to non-empty components. The | |||||
* _constness_ of the components is as requested.<br/> | |||||
* The signature of the function must be equivalent to one of the following | |||||
* forms: | |||||
* | |||||
* @code{.cpp} | |||||
* void(const entity_type, Type &...); | |||||
* void(Type &...); | |||||
* @endcode | |||||
* | |||||
* @sa each | |||||
* | |||||
* @tparam Func Type of the function object to invoke. | |||||
* @param func A valid function object. | |||||
*/ | |||||
template<typename Func> | |||||
inline void less(Func func) const { | |||||
using non_empty_owned = type_list_cat_t<std::conditional_t<std::is_empty_v<Owned>, type_list<>, type_list<Owned>>...>; | |||||
using non_empty_get = type_list_cat_t<std::conditional_t<std::is_empty_v<Get>, type_list<>, type_list<Get>>...>; | |||||
traverse(std::move(func), non_empty_owned{}, non_empty_get{}); | |||||
} | |||||
/** | |||||
* @brief Sort a group according to the given comparison function. | |||||
* | |||||
* Sort the group so that iterating it with a couple of iterators returns | |||||
* entities and components in the expected order. See `begin` and `end` for | |||||
* more details. | |||||
* | |||||
* The comparison function object must return `true` if the first element | |||||
* is _less_ than the second one, `false` otherwise. The signature of the | |||||
* comparison function should be equivalent to one of the following: | |||||
* | |||||
* @code{.cpp} | |||||
* bool(const Component &..., const Component &...); | |||||
* bool(const Entity, const Entity); | |||||
* @endcode | |||||
* | |||||
* Where `Component` are either owned types or not but still such that they | |||||
* are iterated by the group.<br/> | |||||
* Moreover, the comparison function object shall induce a | |||||
* _strict weak ordering_ on the values. | |||||
* | |||||
* The sort function oject must offer a member function template | |||||
* `operator()` that accepts three arguments: | |||||
* | |||||
* * An iterator to the first element of the range to sort. | |||||
* * An iterator past the last element of the range to sort. | |||||
* * A comparison function to use to compare the elements. | |||||
* | |||||
* The comparison function object received by the sort function object | |||||
* hasn't necessarily the type of the one passed along with the other | |||||
* parameters to this member function. | |||||
* | |||||
* @note | |||||
* Attempting to iterate elements using a raw pointer returned by a call to | |||||
* either `data` or `raw` gives no guarantees on the order, even though | |||||
* `sort` has been invoked. | |||||
* | |||||
* @tparam Component Optional types of components to compare. | |||||
* @tparam Compare Type of comparison function object. | |||||
* @tparam Sort Type of sort function object. | |||||
* @tparam Args Types of arguments to forward to the sort function object. | |||||
* @param compare A valid comparison function object. | |||||
* @param algo A valid sort function object. | |||||
* @param args Arguments to forward to the sort function object, if any. | |||||
*/ | |||||
template<typename... Component, typename Compare, typename Sort = std_sort, typename... Args> | |||||
void sort(Compare compare, Sort algo = Sort{}, Args &&... args) { | |||||
std::vector<size_type> copy(*length); | |||||
std::iota(copy.begin(), copy.end(), 0); | |||||
if constexpr(sizeof...(Component) == 0) { | |||||
algo(copy.rbegin(), copy.rend(), [compare = std::move(compare), data = data()](const auto lhs, const auto rhs) { | |||||
return compare(data[lhs], data[rhs]); | |||||
}, std::forward<Args>(args)...); | |||||
} else { | |||||
algo(copy.rbegin(), copy.rend(), [compare = std::move(compare), this](const auto lhs, const auto rhs) { | |||||
// useless this-> used to suppress a warning with clang | |||||
return compare(this->get<Component>(lhs)..., this->get<Component>(rhs)...); | |||||
}, std::forward<Args>(args)...); | |||||
} | |||||
for(size_type pos{}, last = copy.size(); pos < last; ++pos) { | |||||
auto curr = pos; | |||||
auto next = copy[curr]; | |||||
while(curr != next) { | |||||
const auto lhs = copy[curr]; | |||||
const auto rhs = copy[next]; | |||||
(std::get<pool_type<Owned> *>(pools)->swap(lhs, rhs), ...); | |||||
copy[curr] = curr; | |||||
curr = next; | |||||
next = copy[curr]; | |||||
} | |||||
} | |||||
} | |||||
private: | |||||
const typename basic_registry<Entity>::size_type *length; | |||||
const std::tuple<pool_type<Owned> *..., pool_type<Get> *...> pools; | |||||
}; | |||||
} | |||||
#endif // ENTT_ENTITY_GROUP_HPP |
@ -0,0 +1,211 @@ | |||||
#ifndef ENTT_ENTITY_HELPER_HPP | |||||
#define ENTT_ENTITY_HELPER_HPP | |||||
#include <type_traits> | |||||
#include "../config/config.h" | |||||
#include "../core/hashed_string.hpp" | |||||
#include "../signal/sigh.hpp" | |||||
#include "registry.hpp" | |||||
namespace entt { | |||||
/** | |||||
* @brief Converts a registry to a view. | |||||
* @tparam Const Constness of the accepted registry. | |||||
* @tparam Entity A valid entity type (see entt_traits for more details). | |||||
*/ | |||||
template<bool Const, typename Entity> | |||||
struct as_view { | |||||
/*! @brief Type of registry to convert. */ | |||||
using registry_type = std::conditional_t<Const, const entt::basic_registry<Entity>, entt::basic_registry<Entity>>; | |||||
/** | |||||
* @brief Constructs a converter for a given registry. | |||||
* @param source A valid reference to a registry. | |||||
*/ | |||||
as_view(registry_type &source) ENTT_NOEXCEPT: reg{source} {} | |||||
/** | |||||
* @brief Conversion function from a registry to a view. | |||||
* @tparam Component Type of components used to construct the view. | |||||
* @return A newly created view. | |||||
*/ | |||||
template<typename... Component> | |||||
inline operator entt::basic_view<Entity, Component...>() const { | |||||
return reg.template view<Component...>(); | |||||
} | |||||
private: | |||||
registry_type ® | |||||
}; | |||||
/** | |||||
* @brief Deduction guide. | |||||
* | |||||
* It allows to deduce the constness of a registry directly from the instance | |||||
* provided to the constructor. | |||||
* | |||||
* @tparam Entity A valid entity type (see entt_traits for more details). | |||||
*/ | |||||
template<typename Entity> | |||||
as_view(basic_registry<Entity> &) ENTT_NOEXCEPT -> as_view<false, Entity>; | |||||
/*! @copydoc as_view */ | |||||
template<typename Entity> | |||||
as_view(const basic_registry<Entity> &) ENTT_NOEXCEPT -> as_view<true, Entity>; | |||||
/** | |||||
* @brief Converts a registry to a group. | |||||
* @tparam Const Constness of the accepted registry. | |||||
* @tparam Entity A valid entity type (see entt_traits for more details). | |||||
*/ | |||||
template<bool Const, typename Entity> | |||||
struct as_group { | |||||
/*! @brief Type of registry to convert. */ | |||||
using registry_type = std::conditional_t<Const, const entt::basic_registry<Entity>, entt::basic_registry<Entity>>; | |||||
/** | |||||
* @brief Constructs a converter for a given registry. | |||||
* @param source A valid reference to a registry. | |||||
*/ | |||||
as_group(registry_type &source) ENTT_NOEXCEPT: reg{source} {} | |||||
/** | |||||
* @brief Conversion function from a registry to a group. | |||||
* | |||||
* @note | |||||
* Unfortunately, only full owning groups are supported because of an issue | |||||
* with msvc that doesn't manage to correctly deduce types. | |||||
* | |||||
* @tparam Owned Types of components owned by the group. | |||||
* @return A newly created group. | |||||
*/ | |||||
template<typename... Owned> | |||||
inline operator entt::basic_group<Entity, get_t<>, Owned...>() const { | |||||
return reg.template group<Owned...>(); | |||||
} | |||||
private: | |||||
registry_type ® | |||||
}; | |||||
/** | |||||
* @brief Deduction guide. | |||||
* | |||||
* It allows to deduce the constness of a registry directly from the instance | |||||
* provided to the constructor. | |||||
* | |||||
* @tparam Entity A valid entity type (see entt_traits for more details). | |||||
*/ | |||||
template<typename Entity> | |||||
as_group(basic_registry<Entity> &) ENTT_NOEXCEPT -> as_group<false, Entity>; | |||||
/*! @copydoc as_group */ | |||||
template<typename Entity> | |||||
as_group(const basic_registry<Entity> &) ENTT_NOEXCEPT -> as_group<true, Entity>; | |||||
/** | |||||
* @brief Dependency function prototype. | |||||
* | |||||
* A _dependency function_ is a built-in listener to use to automatically assign | |||||
* components to an entity when a type has a dependency on some other types. | |||||
* | |||||
* This is a prototype function to use to create dependencies.<br/> | |||||
* It isn't intended for direct use, although nothing forbids using it freely. | |||||
* | |||||
* @tparam Entity A valid entity type (see entt_traits for more details). | |||||
* @tparam Component Type of component that triggers the dependency handler. | |||||
* @tparam Dependency Types of components to assign to an entity if triggered. | |||||
* @param reg A valid reference to a registry. | |||||
* @param entt A valid entity identifier. | |||||
*/ | |||||
template<typename Entity, typename Component, typename... Dependency> | |||||
void dependency(basic_registry<Entity> ®, const Entity entt, const Component &) { | |||||
((reg.template has<Dependency>(entt) ? void() : (reg.template assign<Dependency>(entt), void())), ...); | |||||
} | |||||
/** | |||||
* @brief Connects a dependency function to the given sink. | |||||
* | |||||
* A _dependency function_ is a built-in listener to use to automatically assign | |||||
* components to an entity when a type has a dependency on some other types. | |||||
* | |||||
* The following adds components `a_type` and `another_type` whenever `my_type` | |||||
* is assigned to an entity: | |||||
* @code{.cpp} | |||||
* entt::registry registry; | |||||
* entt::connect<a_type, another_type>(registry.construction<my_type>()); | |||||
* @endcode | |||||
* | |||||
* @tparam Dependency Types of components to assign to an entity if triggered. | |||||
* @tparam Component Type of component that triggers the dependency handler. | |||||
* @tparam Entity A valid entity type (see entt_traits for more details). | |||||
* @param sink A sink object properly initialized. | |||||
*/ | |||||
template<typename... Dependency, typename Component, typename Entity> | |||||
inline void connect(sink<void(basic_registry<Entity> &, const Entity, Component &)> sink) { | |||||
sink.template connect<dependency<Entity, Component, Dependency...>>(); | |||||
} | |||||
/** | |||||
* @brief Disconnects a dependency function from the given sink. | |||||
* | |||||
* A _dependency function_ is a built-in listener to use to automatically assign | |||||
* components to an entity when a type has a dependency on some other types. | |||||
* | |||||
* The following breaks the dependency between the component `my_type` and the | |||||
* components `a_type` and `another_type`: | |||||
* @code{.cpp} | |||||
* entt::registry registry; | |||||
* entt::disconnect<a_type, another_type>(registry.construction<my_type>()); | |||||
* @endcode | |||||
* | |||||
* @tparam Dependency Types of components used to create the dependency. | |||||
* @tparam Component Type of component that triggers the dependency handler. | |||||
* @tparam Entity A valid entity type (see entt_traits for more details). | |||||
* @param sink A sink object properly initialized. | |||||
*/ | |||||
template<typename... Dependency, typename Component, typename Entity> | |||||
inline void disconnect(sink<void(basic_registry<Entity> &, const Entity, Component &)> sink) { | |||||
sink.template disconnect<dependency<Entity, Component, Dependency...>>(); | |||||
} | |||||
/** | |||||
* @brief Alias template to ease the assignment of tags to entities. | |||||
* | |||||
* If used in combination with hashed strings, it simplifies the assignment of | |||||
* tags to entities and the use of tags in general where a type would be | |||||
* required otherwise.<br/> | |||||
* As an example and where the user defined literal for hashed strings hasn't | |||||
* been changed: | |||||
* @code{.cpp} | |||||
* entt::registry registry; | |||||
* registry.assign<entt::tag<"enemy"_hs>>(entity); | |||||
* @endcode | |||||
* | |||||
* @note | |||||
* Tags are empty components and therefore candidates for the empty component | |||||
* optimization. | |||||
* | |||||
* @tparam Value The numeric representation of an instance of hashed string. | |||||
*/ | |||||
template<typename hashed_string::hash_type Value> | |||||
using tag = std::integral_constant<typename hashed_string::hash_type, Value>; | |||||
} | |||||
#endif // ENTT_ENTITY_HELPER_HPP |
@ -0,0 +1,483 @@ | |||||
#ifndef ENTT_ENTITY_PROTOTYPE_HPP | |||||
#define ENTT_ENTITY_PROTOTYPE_HPP | |||||
#include <tuple> | |||||
#include <utility> | |||||
#include <cstddef> | |||||
#include <type_traits> | |||||
#include <unordered_map> | |||||
#include "../config/config.h" | |||||
#include "registry.hpp" | |||||
#include "entity.hpp" | |||||
#include "fwd.hpp" | |||||
namespace entt { | |||||
/** | |||||
* @brief Prototype container for _concepts_. | |||||
* | |||||
* A prototype is used to define a _concept_ in terms of components.<br/> | |||||
* Prototypes act as templates for those specific types of an application which | |||||
* users would otherwise define through a series of component assignments to | |||||
* entities. In other words, prototypes can be used to assign components to | |||||
* entities of a registry at once. | |||||
* | |||||
* @note | |||||
* Components used along with prototypes must be copy constructible. Prototypes | |||||
* wrap component types with custom types, so they do not interfere with other | |||||
* users of the registry they were built with. | |||||
* | |||||
* @warning | |||||
* Prototypes directly use their underlying registries to store entities and | |||||
* components for their purposes. Users must ensure that the lifetime of a | |||||
* registry and its contents exceed that of the prototypes that use it. | |||||
* | |||||
* @tparam Entity A valid entity type (see entt_traits for more details). | |||||
*/ | |||||
template<typename Entity> | |||||
class basic_prototype { | |||||
using basic_fn_type = void(const basic_prototype &, basic_registry<Entity> &, const Entity); | |||||
using component_type = typename basic_registry<Entity>::component_type; | |||||
template<typename Component> | |||||
struct component_wrapper { Component component; }; | |||||
struct component_handler { | |||||
basic_fn_type *assign_or_replace; | |||||
basic_fn_type *assign; | |||||
}; | |||||
void release() { | |||||
if(reg->valid(entity)) { | |||||
reg->destroy(entity); | |||||
} | |||||
} | |||||
public: | |||||
/*! @brief Registry type. */ | |||||
using registry_type = basic_registry<Entity>; | |||||
/*! @brief Underlying entity identifier. */ | |||||
using entity_type = Entity; | |||||
/*! @brief Unsigned integer type. */ | |||||
using size_type = std::size_t; | |||||
/** | |||||
* @brief Constructs a prototype that is bound to a given registry. | |||||
* @param ref A valid reference to a registry. | |||||
*/ | |||||
basic_prototype(registry_type &ref) | |||||
: reg{&ref}, | |||||
entity{ref.create()} | |||||
{} | |||||
/** | |||||
* @brief Releases all its resources. | |||||
*/ | |||||
~basic_prototype() { | |||||
release(); | |||||
} | |||||
/** | |||||
* @brief Move constructor. | |||||
* | |||||
* After prototype move construction, instances that have been moved from | |||||
* are placed in a valid but unspecified state. It's highly discouraged to | |||||
* continue using them. | |||||
* | |||||
* @param other The instance to move from. | |||||
*/ | |||||
basic_prototype(basic_prototype &&other) | |||||
: handlers{std::move(other.handlers)}, | |||||
reg{other.reg}, | |||||
entity{other.entity} | |||||
{ | |||||
other.entity = null; | |||||
} | |||||
/** | |||||
* @brief Move assignment operator. | |||||
* | |||||
* After prototype move assignment, instances that have been moved from are | |||||
* placed in a valid but unspecified state. It's highly discouraged to | |||||
* continue using them. | |||||
* | |||||
* @param other The instance to move from. | |||||
* @return This prototype. | |||||
*/ | |||||
basic_prototype & operator=(basic_prototype &&other) { | |||||
if(this != &other) { | |||||
auto tmp{std::move(other)}; | |||||
handlers.swap(tmp.handlers); | |||||
std::swap(reg, tmp.reg); | |||||
std::swap(entity, tmp.entity); | |||||
} | |||||
return *this; | |||||
} | |||||
/** | |||||
* @brief Assigns to or replaces the given component of a prototype. | |||||
* @tparam Component Type of component to assign or replace. | |||||
* @tparam Args Types of arguments to use to construct the component. | |||||
* @param args Parameters to use to initialize the component. | |||||
* @return A reference to the newly created component. | |||||
*/ | |||||
template<typename Component, typename... Args> | |||||
Component & set(Args &&... args) { | |||||
component_handler handler; | |||||
handler.assign_or_replace = [](const basic_prototype &proto, registry_type &other, const Entity dst) { | |||||
const auto &wrapper = proto.reg->template get<component_wrapper<Component>>(proto.entity); | |||||
other.template assign_or_replace<Component>(dst, wrapper.component); | |||||
}; | |||||
handler.assign = [](const basic_prototype &proto, registry_type &other, const Entity dst) { | |||||
if(!other.template has<Component>(dst)) { | |||||
const auto &wrapper = proto.reg->template get<component_wrapper<Component>>(proto.entity); | |||||
other.template assign<Component>(dst, wrapper.component); | |||||
} | |||||
}; | |||||
handlers[reg->template type<Component>()] = handler; | |||||
auto &wrapper = reg->template assign_or_replace<component_wrapper<Component>>(entity, Component{std::forward<Args>(args)...}); | |||||
return wrapper.component; | |||||
} | |||||
/** | |||||
* @brief Removes the given component from a prototype. | |||||
* @tparam Component Type of component to remove. | |||||
*/ | |||||
template<typename Component> | |||||
void unset() ENTT_NOEXCEPT { | |||||
reg->template reset<component_wrapper<Component>>(entity); | |||||
handlers.erase(reg->template type<Component>()); | |||||
} | |||||
/** | |||||
* @brief Checks if a prototype owns all the given components. | |||||
* @tparam Component Components for which to perform the check. | |||||
* @return True if the prototype owns all the components, false otherwise. | |||||
*/ | |||||
template<typename... Component> | |||||
bool has() const ENTT_NOEXCEPT { | |||||
return reg->template has<component_wrapper<Component>...>(entity); | |||||
} | |||||
/** | |||||
* @brief Returns references to the given components. | |||||
* | |||||
* @warning | |||||
* Attempting to get a component from a prototype that doesn't own it | |||||
* results in undefined behavior.<br/> | |||||
* An assertion will abort the execution at runtime in debug mode if the | |||||
* prototype doesn't own an instance of the given component. | |||||
* | |||||
* @tparam Component Types of components to get. | |||||
* @return References to the components owned by the prototype. | |||||
*/ | |||||
template<typename... Component> | |||||
decltype(auto) get() const ENTT_NOEXCEPT { | |||||
if constexpr(sizeof...(Component) == 1) { | |||||
return (std::as_const(*reg).template get<component_wrapper<Component...>>(entity).component); | |||||
} else { | |||||
return std::tuple<std::add_const_t<Component> &...>{get<Component>()...}; | |||||
} | |||||
} | |||||
/*! @copydoc get */ | |||||
template<typename... Component> | |||||
inline decltype(auto) get() ENTT_NOEXCEPT { | |||||
if constexpr(sizeof...(Component) == 1) { | |||||
return (const_cast<Component &>(std::as_const(*this).template get<Component>()), ...); | |||||
} else { | |||||
return std::tuple<Component &...>{get<Component>()...}; | |||||
} | |||||
} | |||||
/** | |||||
* @brief Returns pointers to the given components. | |||||
* @tparam Component Types of components to get. | |||||
* @return Pointers to the components owned by the prototype. | |||||
*/ | |||||
template<typename... Component> | |||||
auto try_get() const ENTT_NOEXCEPT { | |||||
if constexpr(sizeof...(Component) == 1) { | |||||
const auto *wrapper = reg->template try_get<component_wrapper<Component...>>(entity); | |||||
return wrapper ? &wrapper->component : nullptr; | |||||
} else { | |||||
return std::tuple<std::add_const_t<Component> *...>{try_get<Component>()...}; | |||||
} | |||||
} | |||||
/*! @copydoc try_get */ | |||||
template<typename... Component> | |||||
inline auto try_get() ENTT_NOEXCEPT { | |||||
if constexpr(sizeof...(Component) == 1) { | |||||
return (const_cast<Component *>(std::as_const(*this).template try_get<Component>()), ...); | |||||
} else { | |||||
return std::tuple<Component *...>{try_get<Component>()...}; | |||||
} | |||||
} | |||||
/** | |||||
* @brief Creates a new entity using a given prototype. | |||||
* | |||||
* Utility shortcut, equivalent to the following snippet: | |||||
* | |||||
* @code{.cpp} | |||||
* const auto entity = registry.create(); | |||||
* prototype(registry, entity); | |||||
* @endcode | |||||
* | |||||
* @note | |||||
* The registry may or may not be different from the one already used by | |||||
* the prototype. There is also an overload that directly uses the | |||||
* underlying registry. | |||||
* | |||||
* @param other A valid reference to a registry. | |||||
* @return A valid entity identifier. | |||||
*/ | |||||
entity_type create(registry_type &other) const { | |||||
const auto entt = other.create(); | |||||
assign(other, entt); | |||||
return entt; | |||||
} | |||||
/** | |||||
* @brief Creates a new entity using a given prototype. | |||||
* | |||||
* Utility shortcut, equivalent to the following snippet: | |||||
* | |||||
* @code{.cpp} | |||||
* const auto entity = registry.create(); | |||||
* prototype(entity); | |||||
* @endcode | |||||
* | |||||
* @note | |||||
* This overload directly uses the underlying registry as a working space. | |||||
* Therefore, the components of the prototype and of the entity will share | |||||
* the same registry. | |||||
* | |||||
* @return A valid entity identifier. | |||||
*/ | |||||
inline entity_type create() const { | |||||
return create(*reg); | |||||
} | |||||
/** | |||||
* @brief Assigns the components of a prototype to a given entity. | |||||
* | |||||
* Assigning a prototype to an entity won't overwrite existing components | |||||
* under any circumstances.<br/> | |||||
* In other words, only those components that the entity doesn't own yet are | |||||
* copied over. All the other components remain unchanged. | |||||
* | |||||
* @note | |||||
* The registry may or may not be different from the one already used by | |||||
* the prototype. There is also an overload that directly uses the | |||||
* underlying registry. | |||||
* | |||||
* @warning | |||||
* Attempting to use an invalid entity results in undefined behavior.<br/> | |||||
* An assertion will abort the execution at runtime in debug mode in case of | |||||
* invalid entity. | |||||
* | |||||
* @param other A valid reference to a registry. | |||||
* @param dst A valid entity identifier. | |||||
*/ | |||||
void assign(registry_type &other, const entity_type dst) const { | |||||
for(auto &handler: handlers) { | |||||
handler.second.assign(*this, other, dst); | |||||
} | |||||
} | |||||
/** | |||||
* @brief Assigns the components of a prototype to a given entity. | |||||
* | |||||
* Assigning a prototype to an entity won't overwrite existing components | |||||
* under any circumstances.<br/> | |||||
* In other words, only those components that the entity doesn't own yet are | |||||
* copied over. All the other components remain unchanged. | |||||
* | |||||
* @note | |||||
* This overload directly uses the underlying registry as a working space. | |||||
* Therefore, the components of the prototype and of the entity will share | |||||
* the same registry. | |||||
* | |||||
* @warning | |||||
* Attempting to use an invalid entity results in undefined behavior.<br/> | |||||
* An assertion will abort the execution at runtime in debug mode in case of | |||||
* invalid entity. | |||||
* | |||||
* @param dst A valid entity identifier. | |||||
*/ | |||||
inline void assign(const entity_type dst) const { | |||||
assign(*reg, dst); | |||||
} | |||||
/** | |||||
* @brief Assigns or replaces the components of a prototype for an entity. | |||||
* | |||||
* Existing components are overwritten, if any. All the other components | |||||
* will be copied over to the target entity. | |||||
* | |||||
* @note | |||||
* The registry may or may not be different from the one already used by | |||||
* the prototype. There is also an overload that directly uses the | |||||
* underlying registry. | |||||
* | |||||
* @warning | |||||
* Attempting to use an invalid entity results in undefined behavior.<br/> | |||||
* An assertion will abort the execution at runtime in debug mode in case of | |||||
* invalid entity. | |||||
* | |||||
* @param other A valid reference to a registry. | |||||
* @param dst A valid entity identifier. | |||||
*/ | |||||
void assign_or_replace(registry_type &other, const entity_type dst) const { | |||||
for(auto &handler: handlers) { | |||||
handler.second.assign_or_replace(*this, other, dst); | |||||
} | |||||
} | |||||
/** | |||||
* @brief Assigns or replaces the components of a prototype for an entity. | |||||
* | |||||
* Existing components are overwritten, if any. All the other components | |||||
* will be copied over to the target entity. | |||||
* | |||||
* @note | |||||
* This overload directly uses the underlying registry as a working space. | |||||
* Therefore, the components of the prototype and of the entity will share | |||||
* the same registry. | |||||
* | |||||
* @warning | |||||
* Attempting to use an invalid entity results in undefined behavior.<br/> | |||||
* An assertion will abort the execution at runtime in debug mode in case of | |||||
* invalid entity. | |||||
* | |||||
* @param dst A valid entity identifier. | |||||
*/ | |||||
inline void assign_or_replace(const entity_type dst) const { | |||||
assign_or_replace(*reg, dst); | |||||
} | |||||
/** | |||||
* @brief Assigns the components of a prototype to an entity. | |||||
* | |||||
* Assigning a prototype to an entity won't overwrite existing components | |||||
* under any circumstances.<br/> | |||||
* In other words, only the components that the entity doesn't own yet are | |||||
* copied over. All the other components remain unchanged. | |||||
* | |||||
* @note | |||||
* The registry may or may not be different from the one already used by | |||||
* the prototype. There is also an overload that directly uses the | |||||
* underlying registry. | |||||
* | |||||
* @warning | |||||
* Attempting to use an invalid entity results in undefined behavior.<br/> | |||||
* An assertion will abort the execution at runtime in debug mode in case of | |||||
* invalid entity. | |||||
* | |||||
* @param other A valid reference to a registry. | |||||
* @param dst A valid entity identifier. | |||||
*/ | |||||
inline void operator()(registry_type &other, const entity_type dst) const ENTT_NOEXCEPT { | |||||
assign(other, dst); | |||||
} | |||||
/** | |||||
* @brief Assigns the components of a prototype to an entity. | |||||
* | |||||
* Assigning a prototype to an entity won't overwrite existing components | |||||
* under any circumstances.<br/> | |||||
* In other words, only the components that the entity doesn't own yet are | |||||
* copied over. All the other components remain unchanged. | |||||
* | |||||
* @note | |||||
* This overload directly uses the underlying registry as a working space. | |||||
* Therefore, the components of the prototype and of the entity will share | |||||
* the same registry. | |||||
* | |||||
* @warning | |||||
* Attempting to use an invalid entity results in undefined behavior.<br/> | |||||
* An assertion will abort the execution at runtime in debug mode in case of | |||||
* invalid entity. | |||||
* | |||||
* @param dst A valid entity identifier. | |||||
*/ | |||||
inline void operator()(const entity_type dst) const ENTT_NOEXCEPT { | |||||
assign(*reg, dst); | |||||
} | |||||
/** | |||||
* @brief Creates a new entity using a given prototype. | |||||
* | |||||
* Utility shortcut, equivalent to the following snippet: | |||||
* | |||||
* @code{.cpp} | |||||
* const auto entity = registry.create(); | |||||
* prototype(registry, entity); | |||||
* @endcode | |||||
* | |||||
* @note | |||||
* The registry may or may not be different from the one already used by | |||||
* the prototype. There is also an overload that directly uses the | |||||
* underlying registry. | |||||
* | |||||
* @param other A valid reference to a registry. | |||||
* @return A valid entity identifier. | |||||
*/ | |||||
inline entity_type operator()(registry_type &other) const ENTT_NOEXCEPT { | |||||
return create(other); | |||||
} | |||||
/** | |||||
* @brief Creates a new entity using a given prototype. | |||||
* | |||||
* Utility shortcut, equivalent to the following snippet: | |||||
* | |||||
* @code{.cpp} | |||||
* const auto entity = registry.create(); | |||||
* prototype(entity); | |||||
* @endcode | |||||
* | |||||
* @note | |||||
* This overload directly uses the underlying registry as a working space. | |||||
* Therefore, the components of the prototype and of the entity will share | |||||
* the same registry. | |||||
* | |||||
* @return A valid entity identifier. | |||||
*/ | |||||
inline entity_type operator()() const ENTT_NOEXCEPT { | |||||
return create(*reg); | |||||
} | |||||
/** | |||||
* @brief Returns a reference to the underlying registry. | |||||
* @return A reference to the underlying registry. | |||||
*/ | |||||
inline const registry_type & backend() const ENTT_NOEXCEPT { | |||||
return *reg; | |||||
} | |||||
/*! @copydoc backend */ | |||||
inline registry_type & backend() ENTT_NOEXCEPT { | |||||
return const_cast<registry_type &>(std::as_const(*this).backend()); | |||||
} | |||||
private: | |||||
std::unordered_map<component_type, component_handler> handlers; | |||||
registry_type *reg; | |||||
entity_type entity; | |||||
}; | |||||
} | |||||
#endif // ENTT_ENTITY_PROTOTYPE_HPP |
@ -0,0 +1,275 @@ | |||||
#ifndef ENTT_ENTITY_RUNTIME_VIEW_HPP | |||||
#define ENTT_ENTITY_RUNTIME_VIEW_HPP | |||||
#include <iterator> | |||||
#include <cassert> | |||||
#include <vector> | |||||
#include <utility> | |||||
#include <algorithm> | |||||
#include "../config/config.h" | |||||
#include "sparse_set.hpp" | |||||
#include "entity.hpp" | |||||
#include "fwd.hpp" | |||||
namespace entt { | |||||
/** | |||||
* @brief Runtime view. | |||||
* | |||||
* Runtime views iterate over those entities that have at least all the given | |||||
* components in their bags. During initialization, a runtime view looks at the | |||||
* number of entities available for each component and picks up a reference to | |||||
* the smallest set of candidate entities in order to get a performance boost | |||||
* when iterate.<br/> | |||||
* Order of elements during iterations are highly dependent on the order of the | |||||
* underlying data structures. See sparse_set and its specializations for more | |||||
* details. | |||||
* | |||||
* @b Important | |||||
* | |||||
* Iterators aren't invalidated if: | |||||
* | |||||
* * New instances of the given components are created and assigned to entities. | |||||
* * The entity currently pointed is modified (as an example, if one of the | |||||
* given components is removed from the entity to which the iterator points). | |||||
* | |||||
* In all the other cases, modifying the pools of the given components in any | |||||
* way invalidates all the iterators and using them results in undefined | |||||
* behavior. | |||||
* | |||||
* @note | |||||
* Views share references to the underlying data structures of the registry that | |||||
* generated them. Therefore any change to the entities and to the components | |||||
* made by means of the registry are immediately reflected by the views, unless | |||||
* a pool was missing when the view was built (in this case, the view won't | |||||
* have a valid reference and won't be updated accordingly). | |||||
* | |||||
* @warning | |||||
* Lifetime of a view must overcome the one of the registry that generated it. | |||||
* In any other case, attempting to use a view results in undefined behavior. | |||||
* | |||||
* @tparam Entity A valid entity type (see entt_traits for more details). | |||||
*/ | |||||
template<typename Entity> | |||||
class basic_runtime_view { | |||||
/*! @brief A registry is allowed to create views. */ | |||||
friend class basic_registry<Entity>; | |||||
using underlying_iterator_type = typename sparse_set<Entity>::iterator_type; | |||||
using extent_type = typename sparse_set<Entity>::size_type; | |||||
using traits_type = entt_traits<Entity>; | |||||
class iterator { | |||||
friend class basic_runtime_view<Entity>; | |||||
iterator(underlying_iterator_type first, underlying_iterator_type last, const sparse_set<Entity> * const *others, const sparse_set<Entity> * const *length, extent_type ext) ENTT_NOEXCEPT | |||||
: begin{first}, | |||||
end{last}, | |||||
from{others}, | |||||
to{length}, | |||||
extent{ext} | |||||
{ | |||||
if(begin != end && !valid()) { | |||||
++(*this); | |||||
} | |||||
} | |||||
bool valid() const ENTT_NOEXCEPT { | |||||
const auto entt = *begin; | |||||
const auto sz = size_type(entt & traits_type::entity_mask); | |||||
return sz < extent && std::all_of(from, to, [entt](const auto *view) { | |||||
return view->has(entt); | |||||
}); | |||||
} | |||||
public: | |||||
using difference_type = typename underlying_iterator_type::difference_type; | |||||
using value_type = typename underlying_iterator_type::value_type; | |||||
using pointer = typename underlying_iterator_type::pointer; | |||||
using reference = typename underlying_iterator_type::reference; | |||||
using iterator_category = std::forward_iterator_tag; | |||||
iterator() ENTT_NOEXCEPT = default; | |||||
iterator & operator++() ENTT_NOEXCEPT { | |||||
return (++begin != end && !valid()) ? ++(*this) : *this; | |||||
} | |||||
iterator operator++(int) ENTT_NOEXCEPT { | |||||
iterator orig = *this; | |||||
return ++(*this), orig; | |||||
} | |||||
bool operator==(const iterator &other) const ENTT_NOEXCEPT { | |||||
return other.begin == begin; | |||||
} | |||||
inline bool operator!=(const iterator &other) const ENTT_NOEXCEPT { | |||||
return !(*this == other); | |||||
} | |||||
pointer operator->() const ENTT_NOEXCEPT { | |||||
return begin.operator->(); | |||||
} | |||||
inline reference operator*() const ENTT_NOEXCEPT { | |||||
return *operator->(); | |||||
} | |||||
private: | |||||
underlying_iterator_type begin; | |||||
underlying_iterator_type end; | |||||
const sparse_set<Entity> * const *from; | |||||
const sparse_set<Entity> * const *to; | |||||
extent_type extent; | |||||
}; | |||||
basic_runtime_view(std::vector<const sparse_set<Entity> *> others) ENTT_NOEXCEPT | |||||
: pools{std::move(others)} | |||||
{ | |||||
const auto it = std::min_element(pools.begin(), pools.end(), [](const auto *lhs, const auto *rhs) { | |||||
return (!lhs && rhs) || (lhs && rhs && lhs->size() < rhs->size()); | |||||
}); | |||||
// brings the best candidate (if any) on front of the vector | |||||
std::rotate(pools.begin(), it, pools.end()); | |||||
} | |||||
extent_type min() const ENTT_NOEXCEPT { | |||||
extent_type extent{}; | |||||
if(valid()) { | |||||
const auto it = std::min_element(pools.cbegin(), pools.cend(), [](const auto *lhs, const auto *rhs) { | |||||
return lhs->extent() < rhs->extent(); | |||||
}); | |||||
extent = (*it)->extent(); | |||||
} | |||||
return extent; | |||||
} | |||||
inline bool valid() const ENTT_NOEXCEPT { | |||||
return !pools.empty() && pools.front(); | |||||
} | |||||
public: | |||||
/*! @brief Underlying entity identifier. */ | |||||
using entity_type = typename sparse_set<Entity>::entity_type; | |||||
/*! @brief Unsigned integer type. */ | |||||
using size_type = typename sparse_set<Entity>::size_type; | |||||
/*! @brief Input iterator type. */ | |||||
using iterator_type = iterator; | |||||
/** | |||||
* @brief Estimates the number of entities that have the given components. | |||||
* @return Estimated number of entities that have the given components. | |||||
*/ | |||||
size_type size() const ENTT_NOEXCEPT { | |||||
return valid() ? pools.front()->size() : size_type{}; | |||||
} | |||||
/** | |||||
* @brief Checks if the view is definitely empty. | |||||
* @return True if the view is definitely empty, false otherwise. | |||||
*/ | |||||
bool empty() const ENTT_NOEXCEPT { | |||||
return !valid() || pools.front()->empty(); | |||||
} | |||||
/** | |||||
* @brief Returns an iterator to the first entity that has the given | |||||
* components. | |||||
* | |||||
* The returned iterator points to the first entity that has the given | |||||
* components. If the view is empty, the returned iterator will be equal to | |||||
* `end()`. | |||||
* | |||||
* @note | |||||
* Input iterators stay true to the order imposed to the underlying data | |||||
* structures. | |||||
* | |||||
* @return An iterator to the first entity that has the given components. | |||||
*/ | |||||
iterator_type begin() const ENTT_NOEXCEPT { | |||||
iterator_type it{}; | |||||
if(valid()) { | |||||
const auto &pool = *pools.front(); | |||||
const auto * const *data = pools.data(); | |||||
it = { pool.begin(), pool.end(), data + 1, data + pools.size(), min() }; | |||||
} | |||||
return it; | |||||
} | |||||
/** | |||||
* @brief Returns an iterator that is past the last entity that has the | |||||
* given components. | |||||
* | |||||
* The returned iterator points to the entity following the last entity that | |||||
* has the given components. Attempting to dereference the returned iterator | |||||
* results in undefined behavior. | |||||
* | |||||
* @note | |||||
* Input iterators stay true to the order imposed to the underlying data | |||||
* structures. | |||||
* | |||||
* @return An iterator to the entity following the last entity that has the | |||||
* given components. | |||||
*/ | |||||
iterator_type end() const ENTT_NOEXCEPT { | |||||
iterator_type it{}; | |||||
if(valid()) { | |||||
const auto &pool = *pools.front(); | |||||
it = { pool.end(), pool.end(), nullptr, nullptr, min() }; | |||||
} | |||||
return it; | |||||
} | |||||
/** | |||||
* @brief Checks if a view contains an entity. | |||||
* @param entt A valid entity identifier. | |||||
* @return True if the view contains the given entity, false otherwise. | |||||
*/ | |||||
bool contains(const entity_type entt) const ENTT_NOEXCEPT { | |||||
return valid() && std::all_of(pools.cbegin(), pools.cend(), [entt](const auto *view) { | |||||
return view->has(entt) && view->data()[view->get(entt)] == entt; | |||||
}); | |||||
} | |||||
/** | |||||
* @brief Iterates entities and applies the given function object to them. | |||||
* | |||||
* The function object is invoked for each entity. It is provided only with | |||||
* the entity itself. To get the components, users can use the registry with | |||||
* which the view was built.<br/> | |||||
* The signature of the function should be equivalent to the following: | |||||
* | |||||
* @code{.cpp} | |||||
* void(const entity_type); | |||||
* @endcode | |||||
* | |||||
* @tparam Func Type of the function object to invoke. | |||||
* @param func A valid function object. | |||||
*/ | |||||
template<typename Func> | |||||
void each(Func func) const { | |||||
std::for_each(begin(), end(), func); | |||||
} | |||||
private: | |||||
std::vector<const sparse_set<Entity> *> pools; | |||||
}; | |||||
} | |||||
#endif // ENTT_ENTITY_RUNTIME_VIEW_HPP |
@ -0,0 +1,589 @@ | |||||
#ifndef ENTT_ENTITY_SNAPSHOT_HPP | |||||
#define ENTT_ENTITY_SNAPSHOT_HPP | |||||
#include <array> | |||||
#include <cstddef> | |||||
#include <utility> | |||||
#include <iterator> | |||||
#include <type_traits> | |||||
#include <unordered_map> | |||||
#include "../config/config.h" | |||||
#include "entity.hpp" | |||||
#include "fwd.hpp" | |||||
namespace entt { | |||||
/** | |||||
* @brief Utility class to create snapshots from a registry. | |||||
* | |||||
* A _snapshot_ can be either a dump of the entire registry or a narrower | |||||
* selection of components of interest.<br/> | |||||
* This type can be used in both cases if provided with a correctly configured | |||||
* output archive. | |||||
* | |||||
* @tparam Entity A valid entity type (see entt_traits for more details). | |||||
*/ | |||||
template<typename Entity> | |||||
class basic_snapshot { | |||||
/*! @brief A registry is allowed to create snapshots. */ | |||||
friend class basic_registry<Entity>; | |||||
using follow_fn_type = Entity(const basic_registry<Entity> &, const Entity); | |||||
basic_snapshot(const basic_registry<Entity> *source, Entity init, follow_fn_type *fn) ENTT_NOEXCEPT | |||||
: reg{source}, | |||||
seed{init}, | |||||
follow{fn} | |||||
{} | |||||
template<typename Component, typename Archive, typename It> | |||||
void get(Archive &archive, std::size_t sz, It first, It last) const { | |||||
archive(static_cast<Entity>(sz)); | |||||
while(first != last) { | |||||
const auto entt = *(first++); | |||||
if(reg->template has<Component>(entt)) { | |||||
if constexpr(std::is_empty_v<Component>) { | |||||
archive(entt); | |||||
} else { | |||||
archive(entt, reg->template get<Component>(entt)); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
template<typename... Component, typename Archive, typename It, std::size_t... Indexes> | |||||
void component(Archive &archive, It first, It last, std::index_sequence<Indexes...>) const { | |||||
std::array<std::size_t, sizeof...(Indexes)> size{}; | |||||
auto begin = first; | |||||
while(begin != last) { | |||||
const auto entt = *(begin++); | |||||
((reg->template has<Component>(entt) ? ++size[Indexes] : size[Indexes]), ...); | |||||
} | |||||
(get<Component>(archive, size[Indexes], first, last), ...); | |||||
} | |||||
public: | |||||
/*! @brief Default move constructor. */ | |||||
basic_snapshot(basic_snapshot &&) = default; | |||||
/*! @brief Default move assignment operator. @return This snapshot. */ | |||||
basic_snapshot & operator=(basic_snapshot &&) = default; | |||||
/** | |||||
* @brief Puts aside all the entities that are still in use. | |||||
* | |||||
* Entities are serialized along with their versions. Destroyed entities are | |||||
* not taken in consideration by this function. | |||||
* | |||||
* @tparam Archive Type of output archive. | |||||
* @param archive A valid reference to an output archive. | |||||
* @return An object of this type to continue creating the snapshot. | |||||
*/ | |||||
template<typename Archive> | |||||
const basic_snapshot & entities(Archive &archive) const { | |||||
archive(static_cast<Entity>(reg->alive())); | |||||
reg->each([&archive](const auto entt) { archive(entt); }); | |||||
return *this; | |||||
} | |||||
/** | |||||
* @brief Puts aside destroyed entities. | |||||
* | |||||
* Entities are serialized along with their versions. Entities that are | |||||
* still in use are not taken in consideration by this function. | |||||
* | |||||
* @tparam Archive Type of output archive. | |||||
* @param archive A valid reference to an output archive. | |||||
* @return An object of this type to continue creating the snapshot. | |||||
*/ | |||||
template<typename Archive> | |||||
const basic_snapshot & destroyed(Archive &archive) const { | |||||
auto size = reg->size() - reg->alive(); | |||||
archive(static_cast<Entity>(size)); | |||||
if(size) { | |||||
auto curr = seed; | |||||
archive(curr); | |||||
for(--size; size; --size) { | |||||
curr = follow(*reg, curr); | |||||
archive(curr); | |||||
} | |||||
} | |||||
return *this; | |||||
} | |||||
/** | |||||
* @brief Puts aside the given components. | |||||
* | |||||
* Each instance is serialized together with the entity to which it belongs. | |||||
* Entities are serialized along with their versions. | |||||
* | |||||
* @tparam Component Types of components to serialize. | |||||
* @tparam Archive Type of output archive. | |||||
* @param archive A valid reference to an output archive. | |||||
* @return An object of this type to continue creating the snapshot. | |||||
*/ | |||||
template<typename... Component, typename Archive> | |||||
const basic_snapshot & component(Archive &archive) const { | |||||
if constexpr(sizeof...(Component) == 1) { | |||||
const auto sz = reg->template size<Component...>(); | |||||
const auto *entities = reg->template data<Component...>(); | |||||
archive(static_cast<Entity>(sz)); | |||||
for(std::remove_const_t<decltype(sz)> pos{}; pos < sz; ++pos) { | |||||
const auto entt = entities[pos]; | |||||
if constexpr(std::is_empty_v<Component...>) { | |||||
archive(entt); | |||||
} else { | |||||
archive(entt, reg->template get<Component...>(entt)); | |||||
} | |||||
}; | |||||
} else { | |||||
(component<Component>(archive), ...); | |||||
} | |||||
return *this; | |||||
} | |||||
/** | |||||
* @brief Puts aside the given components for the entities in a range. | |||||
* | |||||
* Each instance is serialized together with the entity to which it belongs. | |||||
* Entities are serialized along with their versions. | |||||
* | |||||
* @tparam Component Types of components to serialize. | |||||
* @tparam Archive Type of output archive. | |||||
* @tparam It Type of input iterator. | |||||
* @param archive A valid reference to an output archive. | |||||
* @param first An iterator to the first element of the range to serialize. | |||||
* @param last An iterator past the last element of the range to serialize. | |||||
* @return An object of this type to continue creating the snapshot. | |||||
*/ | |||||
template<typename... Component, typename Archive, typename It> | |||||
const basic_snapshot & component(Archive &archive, It first, It last) const { | |||||
component<Component...>(archive, first, last, std::make_index_sequence<sizeof...(Component)>{}); | |||||
return *this; | |||||
} | |||||
private: | |||||
const basic_registry<Entity> *reg; | |||||
const Entity seed; | |||||
follow_fn_type *follow; | |||||
}; | |||||
/** | |||||
* @brief Utility class to restore a snapshot as a whole. | |||||
* | |||||
* A snapshot loader requires that the destination registry be empty and loads | |||||
* all the data at once while keeping intact the identifiers that the entities | |||||
* originally had.<br/> | |||||
* An example of use is the implementation of a save/restore utility. | |||||
* | |||||
* @tparam Entity A valid entity type (see entt_traits for more details). | |||||
*/ | |||||
template<typename Entity> | |||||
class basic_snapshot_loader { | |||||
/*! @brief A registry is allowed to create snapshot loaders. */ | |||||
friend class basic_registry<Entity>; | |||||
using force_fn_type = void(basic_registry<Entity> &, const Entity, const bool); | |||||
basic_snapshot_loader(basic_registry<Entity> *source, force_fn_type *fn) ENTT_NOEXCEPT | |||||
: reg{source}, | |||||
force{fn} | |||||
{ | |||||
// to restore a snapshot as a whole requires a clean registry | |||||
ENTT_ASSERT(reg->empty()); | |||||
} | |||||
template<typename Archive> | |||||
void assure(Archive &archive, bool destroyed) const { | |||||
Entity length{}; | |||||
archive(length); | |||||
while(length--) { | |||||
Entity entt{}; | |||||
archive(entt); | |||||
force(*reg, entt, destroyed); | |||||
} | |||||
} | |||||
template<typename Type, typename Archive, typename... Args> | |||||
void assign(Archive &archive, Args... args) const { | |||||
Entity length{}; | |||||
archive(length); | |||||
while(length--) { | |||||
static constexpr auto destroyed = false; | |||||
Entity entt{}; | |||||
if constexpr(std::is_empty_v<Type>) { | |||||
archive(entt); | |||||
force(*reg, entt, destroyed); | |||||
reg->template assign<Type>(args..., entt); | |||||
} else { | |||||
Type instance{}; | |||||
archive(entt, instance); | |||||
force(*reg, entt, destroyed); | |||||
reg->template assign<Type>(args..., entt, std::as_const(instance)); | |||||
} | |||||
} | |||||
} | |||||
public: | |||||
/*! @brief Default move constructor. */ | |||||
basic_snapshot_loader(basic_snapshot_loader &&) = default; | |||||
/*! @brief Default move assignment operator. @return This loader. */ | |||||
basic_snapshot_loader & operator=(basic_snapshot_loader &&) = default; | |||||
/** | |||||
* @brief Restores entities that were in use during serialization. | |||||
* | |||||
* This function restores the entities that were in use during serialization | |||||
* and gives them the versions they originally had. | |||||
* | |||||
* @tparam Archive Type of input archive. | |||||
* @param archive A valid reference to an input archive. | |||||
* @return A valid loader to continue restoring data. | |||||
*/ | |||||
template<typename Archive> | |||||
const basic_snapshot_loader & entities(Archive &archive) const { | |||||
static constexpr auto destroyed = false; | |||||
assure(archive, destroyed); | |||||
return *this; | |||||
} | |||||
/** | |||||
* @brief Restores entities that were destroyed during serialization. | |||||
* | |||||
* This function restores the entities that were destroyed during | |||||
* serialization and gives them the versions they originally had. | |||||
* | |||||
* @tparam Archive Type of input archive. | |||||
* @param archive A valid reference to an input archive. | |||||
* @return A valid loader to continue restoring data. | |||||
*/ | |||||
template<typename Archive> | |||||
const basic_snapshot_loader & destroyed(Archive &archive) const { | |||||
static constexpr auto destroyed = true; | |||||
assure(archive, destroyed); | |||||
return *this; | |||||
} | |||||
/** | |||||
* @brief Restores components and assigns them to the right entities. | |||||
* | |||||
* The template parameter list must be exactly the same used during | |||||
* serialization. In the event that the entity to which the component is | |||||
* assigned doesn't exist yet, the loader will take care to create it with | |||||
* the version it originally had. | |||||
* | |||||
* @tparam Component Types of components to restore. | |||||
* @tparam Archive Type of input archive. | |||||
* @param archive A valid reference to an input archive. | |||||
* @return A valid loader to continue restoring data. | |||||
*/ | |||||
template<typename... Component, typename Archive> | |||||
const basic_snapshot_loader & component(Archive &archive) const { | |||||
(assign<Component>(archive), ...); | |||||
return *this; | |||||
} | |||||
/** | |||||
* @brief Destroys those entities that have no components. | |||||
* | |||||
* In case all the entities were serialized but only part of the components | |||||
* was saved, it could happen that some of the entities have no components | |||||
* once restored.<br/> | |||||
* This functions helps to identify and destroy those entities. | |||||
* | |||||
* @return A valid loader to continue restoring data. | |||||
*/ | |||||
const basic_snapshot_loader & orphans() const { | |||||
reg->orphans([this](const auto entt) { | |||||
reg->destroy(entt); | |||||
}); | |||||
return *this; | |||||
} | |||||
private: | |||||
basic_registry<Entity> *reg; | |||||
force_fn_type *force; | |||||
}; | |||||
/** | |||||
* @brief Utility class for _continuous loading_. | |||||
* | |||||
* A _continuous loader_ is designed to load data from a source registry to a | |||||
* (possibly) non-empty destination. The loader can accomodate in a registry | |||||
* more than one snapshot in a sort of _continuous loading_ that updates the | |||||
* destination one step at a time.<br/> | |||||
* Identifiers that entities originally had are not transferred to the target. | |||||
* Instead, the loader maps remote identifiers to local ones while restoring a | |||||
* snapshot.<br/> | |||||
* An example of use is the implementation of a client-server applications with | |||||
* the requirement of transferring somehow parts of the representation side to | |||||
* side. | |||||
* | |||||
* @tparam Entity A valid entity type (see entt_traits for more details). | |||||
*/ | |||||
template<typename Entity> | |||||
class basic_continuous_loader { | |||||
using traits_type = entt_traits<Entity>; | |||||
void destroy(Entity entt) { | |||||
const auto it = remloc.find(entt); | |||||
if(it == remloc.cend()) { | |||||
const auto local = reg->create(); | |||||
remloc.emplace(entt, std::make_pair(local, true)); | |||||
reg->destroy(local); | |||||
} | |||||
} | |||||
void restore(Entity entt) { | |||||
const auto it = remloc.find(entt); | |||||
if(it == remloc.cend()) { | |||||
const auto local = reg->create(); | |||||
remloc.emplace(entt, std::make_pair(local, true)); | |||||
} else { | |||||
remloc[entt].first = reg->valid(remloc[entt].first) ? remloc[entt].first : reg->create(); | |||||
// set the dirty flag | |||||
remloc[entt].second = true; | |||||
} | |||||
} | |||||
template<typename Other, typename Type, typename Member> | |||||
void update(Other &instance, Member Type:: *member) { | |||||
if constexpr(!std::is_same_v<Other, Type>) { | |||||
return; | |||||
} else if constexpr(std::is_same_v<Member, Entity>) { | |||||
instance.*member = map(instance.*member); | |||||
} else { | |||||
// maybe a container? let's try... | |||||
for(auto &entt: instance.*member) { | |||||
entt = map(entt); | |||||
} | |||||
} | |||||
} | |||||
template<typename Archive> | |||||
void assure(Archive &archive, void(basic_continuous_loader:: *member)(Entity)) { | |||||
Entity length{}; | |||||
archive(length); | |||||
while(length--) { | |||||
Entity entt{}; | |||||
archive(entt); | |||||
(this->*member)(entt); | |||||
} | |||||
} | |||||
template<typename Component> | |||||
void reset() { | |||||
for(auto &&ref: remloc) { | |||||
const auto local = ref.second.first; | |||||
if(reg->valid(local)) { | |||||
reg->template reset<Component>(local); | |||||
} | |||||
} | |||||
} | |||||
template<typename Other, typename Archive, typename... Type, typename... Member> | |||||
void assign(Archive &archive, [[maybe_unused]] Member Type:: *... member) { | |||||
Entity length{}; | |||||
archive(length); | |||||
while(length--) { | |||||
Entity entt{}; | |||||
if constexpr(std::is_empty_v<Other>) { | |||||
archive(entt); | |||||
restore(entt); | |||||
reg->template assign_or_replace<Other>(map(entt)); | |||||
} else { | |||||
Other instance{}; | |||||
archive(entt, instance); | |||||
(update(instance, member), ...); | |||||
restore(entt); | |||||
reg->template assign_or_replace<Other>(map(entt), std::as_const(instance)); | |||||
} | |||||
} | |||||
} | |||||
public: | |||||
/*! @brief Underlying entity identifier. */ | |||||
using entity_type = Entity; | |||||
/** | |||||
* @brief Constructs a loader that is bound to a given registry. | |||||
* @param source A valid reference to a registry. | |||||
*/ | |||||
basic_continuous_loader(basic_registry<entity_type> &source) ENTT_NOEXCEPT | |||||
: reg{&source} | |||||
{} | |||||
/*! @brief Default move constructor. */ | |||||
basic_continuous_loader(basic_continuous_loader &&) = default; | |||||
/*! @brief Default move assignment operator. @return This loader. */ | |||||
basic_continuous_loader & operator=(basic_continuous_loader &&) = default; | |||||
/** | |||||
* @brief Restores entities that were in use during serialization. | |||||
* | |||||
* This function restores the entities that were in use during serialization | |||||
* and creates local counterparts for them if required. | |||||
* | |||||
* @tparam Archive Type of input archive. | |||||
* @param archive A valid reference to an input archive. | |||||
* @return A non-const reference to this loader. | |||||
*/ | |||||
template<typename Archive> | |||||
basic_continuous_loader & entities(Archive &archive) { | |||||
assure(archive, &basic_continuous_loader::restore); | |||||
return *this; | |||||
} | |||||
/** | |||||
* @brief Restores entities that were destroyed during serialization. | |||||
* | |||||
* This function restores the entities that were destroyed during | |||||
* serialization and creates local counterparts for them if required. | |||||
* | |||||
* @tparam Archive Type of input archive. | |||||
* @param archive A valid reference to an input archive. | |||||
* @return A non-const reference to this loader. | |||||
*/ | |||||
template<typename Archive> | |||||
basic_continuous_loader & destroyed(Archive &archive) { | |||||
assure(archive, &basic_continuous_loader::destroy); | |||||
return *this; | |||||
} | |||||
/** | |||||
* @brief Restores components and assigns them to the right entities. | |||||
* | |||||
* The template parameter list must be exactly the same used during | |||||
* serialization. In the event that the entity to which the component is | |||||
* assigned doesn't exist yet, the loader will take care to create a local | |||||
* counterpart for it.<br/> | |||||
* Members can be either data members of type entity_type or containers of | |||||
* entities. In both cases, the loader will visit them and update the | |||||
* entities by replacing each one with its local counterpart. | |||||
* | |||||
* @tparam Component Type of component to restore. | |||||
* @tparam Archive Type of input archive. | |||||
* @tparam Type Types of components to update with local counterparts. | |||||
* @tparam Member Types of members to update with their local counterparts. | |||||
* @param archive A valid reference to an input archive. | |||||
* @param member Members to update with their local counterparts. | |||||
* @return A non-const reference to this loader. | |||||
*/ | |||||
template<typename... Component, typename Archive, typename... Type, typename... Member> | |||||
basic_continuous_loader & component(Archive &archive, Member Type:: *... member) { | |||||
(reset<Component>(), ...); | |||||
(assign<Component>(archive, member...), ...); | |||||
return *this; | |||||
} | |||||
/** | |||||
* @brief Helps to purge entities that no longer have a conterpart. | |||||
* | |||||
* Users should invoke this member function after restoring each snapshot, | |||||
* unless they know exactly what they are doing. | |||||
* | |||||
* @return A non-const reference to this loader. | |||||
*/ | |||||
basic_continuous_loader & shrink() { | |||||
auto it = remloc.begin(); | |||||
while(it != remloc.cend()) { | |||||
const auto local = it->second.first; | |||||
bool &dirty = it->second.second; | |||||
if(dirty) { | |||||
dirty = false; | |||||
++it; | |||||
} else { | |||||
if(reg->valid(local)) { | |||||
reg->destroy(local); | |||||
} | |||||
it = remloc.erase(it); | |||||
} | |||||
} | |||||
return *this; | |||||
} | |||||
/** | |||||
* @brief Destroys those entities that have no components. | |||||
* | |||||
* In case all the entities were serialized but only part of the components | |||||
* was saved, it could happen that some of the entities have no components | |||||
* once restored.<br/> | |||||
* This functions helps to identify and destroy those entities. | |||||
* | |||||
* @return A non-const reference to this loader. | |||||
*/ | |||||
basic_continuous_loader & orphans() { | |||||
reg->orphans([this](const auto entt) { | |||||
reg->destroy(entt); | |||||
}); | |||||
return *this; | |||||
} | |||||
/** | |||||
* @brief Tests if a loader knows about a given entity. | |||||
* @param entt An entity identifier. | |||||
* @return True if `entity` is managed by the loader, false otherwise. | |||||
*/ | |||||
bool has(entity_type entt) const ENTT_NOEXCEPT { | |||||
return (remloc.find(entt) != remloc.cend()); | |||||
} | |||||
/** | |||||
* @brief Returns the identifier to which an entity refers. | |||||
* @param entt An entity identifier. | |||||
* @return The local identifier if any, the null entity otherwise. | |||||
*/ | |||||
entity_type map(entity_type entt) const ENTT_NOEXCEPT { | |||||
const auto it = remloc.find(entt); | |||||
entity_type other = null; | |||||
if(it != remloc.cend()) { | |||||
other = it->second.first; | |||||
} | |||||
return other; | |||||
} | |||||
private: | |||||
std::unordered_map<Entity, std::pair<Entity, bool>> remloc; | |||||
basic_registry<Entity> *reg; | |||||
}; | |||||
} | |||||
#endif // ENTT_ENTITY_SNAPSHOT_HPP |
@ -0,0 +1,535 @@ | |||||
#ifndef ENTT_ENTITY_SPARSE_SET_HPP | |||||
#define ENTT_ENTITY_SPARSE_SET_HPP | |||||
#include <algorithm> | |||||
#include <iterator> | |||||
#include <utility> | |||||
#include <vector> | |||||
#include <memory> | |||||
#include <cstddef> | |||||
#include "../config/config.h" | |||||
#include "entity.hpp" | |||||
namespace entt { | |||||
/** | |||||
* @brief Basic sparse set implementation. | |||||
* | |||||
* Sparse set or packed array or whatever is the name users give it.<br/> | |||||
* Two arrays: an _external_ one and an _internal_ one; a _sparse_ one and a | |||||
* _packed_ one; one used for direct access through contiguous memory, the other | |||||
* one used to get the data through an extra level of indirection.<br/> | |||||
* This is largely used by the registry to offer users the fastest access ever | |||||
* to the components. Views and groups in general are almost entirely designed | |||||
* around sparse sets. | |||||
* | |||||
* This type of data structure is widely documented in the literature and on the | |||||
* web. This is nothing more than a customized implementation suitable for the | |||||
* purpose of the framework. | |||||
* | |||||
* @note | |||||
* There are no guarantees that entities are returned in the insertion order | |||||
* when iterate a sparse set. Do not make assumption on the order in any case. | |||||
* | |||||
* @note | |||||
* Internal data structures arrange elements to maximize performance. Because of | |||||
* that, there are no guarantees that elements have the expected order when | |||||
* iterate directly the internal packed array (see `data` and `size` member | |||||
* functions for that). Use `begin` and `end` instead. | |||||
* | |||||
* @tparam Entity A valid entity type (see entt_traits for more details). | |||||
*/ | |||||
template<typename Entity> | |||||
class sparse_set { | |||||
using traits_type = entt_traits<Entity>; | |||||
static_assert(ENTT_PAGE_SIZE && ((ENTT_PAGE_SIZE & (ENTT_PAGE_SIZE - 1)) == 0)); | |||||
static constexpr auto entt_per_page = ENTT_PAGE_SIZE / sizeof(typename entt_traits<Entity>::entity_type); | |||||
class iterator { | |||||
friend class sparse_set<Entity>; | |||||
using direct_type = const std::vector<Entity>; | |||||
using index_type = typename traits_type::difference_type; | |||||
iterator(direct_type *ref, const index_type idx) ENTT_NOEXCEPT | |||||
: direct{ref}, index{idx} | |||||
{} | |||||
public: | |||||
using difference_type = index_type; | |||||
using value_type = Entity; | |||||
using pointer = const value_type *; | |||||
using reference = const value_type &; | |||||
using iterator_category = std::random_access_iterator_tag; | |||||
iterator() ENTT_NOEXCEPT = default; | |||||
iterator & operator++() ENTT_NOEXCEPT { | |||||
return --index, *this; | |||||
} | |||||
iterator operator++(int) ENTT_NOEXCEPT { | |||||
iterator orig = *this; | |||||
return ++(*this), orig; | |||||
} | |||||
iterator & operator--() ENTT_NOEXCEPT { | |||||
return ++index, *this; | |||||
} | |||||
iterator operator--(int) ENTT_NOEXCEPT { | |||||
iterator orig = *this; | |||||
return --(*this), orig; | |||||
} | |||||
iterator & operator+=(const difference_type value) ENTT_NOEXCEPT { | |||||
index -= value; | |||||
return *this; | |||||
} | |||||
iterator operator+(const difference_type value) const ENTT_NOEXCEPT { | |||||
return iterator{direct, index-value}; | |||||
} | |||||
inline iterator & operator-=(const difference_type value) ENTT_NOEXCEPT { | |||||
return (*this += -value); | |||||
} | |||||
inline iterator operator-(const difference_type value) const ENTT_NOEXCEPT { | |||||
return (*this + -value); | |||||
} | |||||
difference_type operator-(const iterator &other) const ENTT_NOEXCEPT { | |||||
return other.index - index; | |||||
} | |||||
reference operator[](const difference_type value) const ENTT_NOEXCEPT { | |||||
const auto pos = size_type(index-value-1); | |||||
return (*direct)[pos]; | |||||
} | |||||
bool operator==(const iterator &other) const ENTT_NOEXCEPT { | |||||
return other.index == index; | |||||
} | |||||
inline bool operator!=(const iterator &other) const ENTT_NOEXCEPT { | |||||
return !(*this == other); | |||||
} | |||||
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: | |||||
direct_type *direct; | |||||
index_type index; | |||||
}; | |||||
void assure(const std::size_t page) { | |||||
if(!(page < reverse.size())) { | |||||
reverse.resize(page+1); | |||||
} | |||||
if(!reverse[page].first) { | |||||
reverse[page].first = std::make_unique<entity_type[]>(entt_per_page); | |||||
// null is safe in all cases for our purposes | |||||
std::fill_n(reverse[page].first.get(), entt_per_page, null); | |||||
} | |||||
} | |||||
auto index(const Entity entt) const ENTT_NOEXCEPT { | |||||
const auto identifier = entt & traits_type::entity_mask; | |||||
const auto page = size_type(identifier / entt_per_page); | |||||
const auto offset = size_type(identifier & (entt_per_page - 1)); | |||||
return std::make_pair(page, offset); | |||||
} | |||||
public: | |||||
/*! @brief Underlying entity identifier. */ | |||||
using entity_type = Entity; | |||||
/*! @brief Unsigned integer type. */ | |||||
using size_type = std::size_t; | |||||
/*! @brief Random access iterator type. */ | |||||
using iterator_type = iterator; | |||||
/*! @brief Default constructor. */ | |||||
sparse_set() = default; | |||||
/** | |||||
* @brief Copy constructor. | |||||
* @param other The instance to copy from. | |||||
*/ | |||||
sparse_set(const sparse_set &other) | |||||
: reverse{}, | |||||
direct{other.direct} | |||||
{ | |||||
for(size_type pos{}, last = other.reverse.size(); pos < last; ++pos) { | |||||
if(other.reverse[pos].first) { | |||||
assure(pos); | |||||
std::copy_n(other.reverse[pos].first.get(), entt_per_page, reverse[pos].first.get()); | |||||
reverse[pos].second = other.reverse[pos].second; | |||||
} | |||||
} | |||||
} | |||||
/*! @brief Default move constructor. */ | |||||
sparse_set(sparse_set &&) = default; | |||||
/*! @brief Default destructor. */ | |||||
virtual ~sparse_set() ENTT_NOEXCEPT = default; | |||||
/** | |||||
* @brief Copy assignment operator. | |||||
* @param other The instance to copy from. | |||||
* @return This sparse set. | |||||
*/ | |||||
sparse_set & operator=(const sparse_set &other) { | |||||
if(&other != this) { | |||||
auto tmp{other}; | |||||
*this = std::move(tmp); | |||||
} | |||||
return *this; | |||||
} | |||||
/*! @brief Default move assignment operator. @return This sparse set. */ | |||||
sparse_set & operator=(sparse_set &&) = default; | |||||
/** | |||||
* @brief Increases the capacity of a sparse set. | |||||
* | |||||
* If the new capacity is greater than the current capacity, new storage is | |||||
* allocated, otherwise the method does nothing. | |||||
* | |||||
* @param cap Desired capacity. | |||||
*/ | |||||
virtual void reserve(const size_type cap) { | |||||
direct.reserve(cap); | |||||
} | |||||
/** | |||||
* @brief Returns the number of elements that a sparse set has currently | |||||
* allocated space for. | |||||
* @return Capacity of the sparse set. | |||||
*/ | |||||
size_type capacity() const ENTT_NOEXCEPT { | |||||
return direct.capacity(); | |||||
} | |||||
/*! @brief Requests the removal of unused capacity. */ | |||||
virtual void shrink_to_fit() { | |||||
while(!reverse.empty() && !reverse.back().second) { | |||||
reverse.pop_back(); | |||||
} | |||||
for(auto &&data: reverse) { | |||||
if(!data.second) { | |||||
data.first.reset(); | |||||
} | |||||
} | |||||
reverse.shrink_to_fit(); | |||||
direct.shrink_to_fit(); | |||||
} | |||||
/** | |||||
* @brief Returns the extent of a sparse set. | |||||
* | |||||
* The extent of a sparse set is also the size of the internal sparse array. | |||||
* There is no guarantee that the internal packed array has the same size. | |||||
* Usually the size of the internal sparse array is equal or greater than | |||||
* the one of the internal packed array. | |||||
* | |||||
* @return Extent of the sparse set. | |||||
*/ | |||||
size_type extent() const ENTT_NOEXCEPT { | |||||
return reverse.size() * entt_per_page; | |||||
} | |||||
/** | |||||
* @brief Returns the number of elements in a sparse set. | |||||
* | |||||
* The number of elements is also the size of the internal packed array. | |||||
* There is no guarantee that the internal sparse array has the same size. | |||||
* Usually the size of the internal sparse array is equal or greater than | |||||
* the one of the internal packed array. | |||||
* | |||||
* @return Number of elements. | |||||
*/ | |||||
size_type size() const ENTT_NOEXCEPT { | |||||
return direct.size(); | |||||
} | |||||
/** | |||||
* @brief Checks whether a sparse set is empty. | |||||
* @return True if the sparse set is empty, false otherwise. | |||||
*/ | |||||
bool empty() const ENTT_NOEXCEPT { | |||||
return direct.empty(); | |||||
} | |||||
/** | |||||
* @brief Direct access to the internal packed array. | |||||
* | |||||
* The returned pointer is such that range `[data(), data() + size()]` is | |||||
* always a valid range, even if the container is empty. | |||||
* | |||||
* @note | |||||
* There are no guarantees on the order, even though `respect` has been | |||||
* previously invoked. Internal data structures arrange elements to maximize | |||||
* performance. Accessing them directly gives a performance boost but less | |||||
* guarantees. Use `begin` and `end` if you want to iterate the sparse set | |||||
* in the expected order. | |||||
* | |||||
* @return A pointer to the internal packed array. | |||||
*/ | |||||
const entity_type * data() const ENTT_NOEXCEPT { | |||||
return direct.data(); | |||||
} | |||||
/** | |||||
* @brief Returns an iterator to the beginning. | |||||
* | |||||
* The returned iterator points to the first entity of the internal packed | |||||
* array. If the sparse set is empty, the returned iterator will be equal to | |||||
* `end()`. | |||||
* | |||||
* @note | |||||
* Input iterators stay true to the order imposed by a call to `respect`. | |||||
* | |||||
* @return An iterator to the first entity of the internal packed array. | |||||
*/ | |||||
iterator_type begin() const ENTT_NOEXCEPT { | |||||
const typename traits_type::difference_type pos = direct.size(); | |||||
return iterator_type{&direct, pos}; | |||||
} | |||||
/** | |||||
* @brief Returns an iterator to the end. | |||||
* | |||||
* The returned iterator points to the element following the last entity in | |||||
* the internal packed array. Attempting to dereference the returned | |||||
* iterator results in undefined behavior. | |||||
* | |||||
* @note | |||||
* Input iterators stay true to the order imposed by a call to `respect`. | |||||
* | |||||
* @return An iterator to the element following the last entity of the | |||||
* internal packed array. | |||||
*/ | |||||
iterator_type end() const ENTT_NOEXCEPT { | |||||
return iterator_type{&direct, {}}; | |||||
} | |||||
/** | |||||
* @brief Finds an entity. | |||||
* @param entt A valid entity identifier. | |||||
* @return An iterator to the given entity if it's found, past the end | |||||
* iterator otherwise. | |||||
*/ | |||||
iterator_type find(const entity_type entt) const ENTT_NOEXCEPT { | |||||
return has(entt) ? --(end() - get(entt)) : end(); | |||||
} | |||||
/** | |||||
* @brief Checks if a sparse set contains an entity. | |||||
* @param entt A valid entity identifier. | |||||
* @return True if the sparse set contains the entity, false otherwise. | |||||
*/ | |||||
bool has(const entity_type entt) const ENTT_NOEXCEPT { | |||||
auto [page, offset] = index(entt); | |||||
// testing against null permits to avoid accessing the direct vector | |||||
return (page < reverse.size() && reverse[page].second && reverse[page].first[offset] != null); | |||||
} | |||||
/** | |||||
* @brief Returns the position of an entity in a sparse set. | |||||
* | |||||
* @warning | |||||
* Attempting to get the position of an entity that doesn't belong to the | |||||
* sparse set results in undefined behavior.<br/> | |||||
* An assertion will abort the execution at runtime in debug mode if the | |||||
* sparse set doesn't contain the given entity. | |||||
* | |||||
* @param entt A valid entity identifier. | |||||
* @return The position of the entity in the sparse set. | |||||
*/ | |||||
size_type get(const entity_type entt) const ENTT_NOEXCEPT { | |||||
ENTT_ASSERT(has(entt)); | |||||
auto [page, offset] = index(entt); | |||||
return size_type(reverse[page].first[offset]); | |||||
} | |||||
/** | |||||
* @brief Assigns an entity to a sparse set. | |||||
* | |||||
* @warning | |||||
* Attempting to assign an entity that already belongs to the sparse set | |||||
* results in undefined behavior.<br/> | |||||
* An assertion will abort the execution at runtime in debug mode if the | |||||
* sparse set already contains the given entity. | |||||
* | |||||
* @param entt A valid entity identifier. | |||||
*/ | |||||
void construct(const entity_type entt) { | |||||
ENTT_ASSERT(!has(entt)); | |||||
auto [page, offset] = index(entt); | |||||
assure(page); | |||||
reverse[page].first[offset] = entity_type(direct.size()); | |||||
reverse[page].second++; | |||||
direct.push_back(entt); | |||||
} | |||||
/** | |||||
* @brief Assigns one or more entities to a sparse set. | |||||
* | |||||
* @warning | |||||
* Attempting to assign an entity that already belongs to the sparse set | |||||
* results in undefined behavior.<br/> | |||||
* An assertion will abort the execution at runtime in debug mode if the | |||||
* sparse set already contains the given entity. | |||||
* | |||||
* @tparam It Type of forward iterator. | |||||
* @param first An iterator to the first element of the range of entities. | |||||
* @param last An iterator past the last element of the range of entities. | |||||
*/ | |||||
template<typename It> | |||||
void batch(It first, It last) { | |||||
std::for_each(first, last, [next = entity_type(direct.size()), this](const auto entt) mutable { | |||||
ENTT_ASSERT(!has(entt)); | |||||
auto [page, offset] = index(entt); | |||||
assure(page); | |||||
reverse[page].first[offset] = next++; | |||||
reverse[page].second++; | |||||
}); | |||||
direct.insert(direct.end(), first, last); | |||||
} | |||||
/** | |||||
* @brief Removes an entity from a sparse set. | |||||
* | |||||
* @warning | |||||
* Attempting to remove an entity that doesn't belong to the sparse set | |||||
* results in undefined behavior.<br/> | |||||
* An assertion will abort the execution at runtime in debug mode if the | |||||
* sparse set doesn't contain the given entity. | |||||
* | |||||
* @param entt A valid entity identifier. | |||||
*/ | |||||
virtual void destroy(const entity_type entt) { | |||||
ENTT_ASSERT(has(entt)); | |||||
auto [from_page, from_offset] = index(entt); | |||||
auto [to_page, to_offset] = index(direct.back()); | |||||
direct[size_type(reverse[from_page].first[from_offset])] = direct.back(); | |||||
reverse[to_page].first[to_offset] = reverse[from_page].first[from_offset]; | |||||
reverse[from_page].first[from_offset] = null; | |||||
reverse[from_page].second--; | |||||
direct.pop_back(); | |||||
} | |||||
/** | |||||
* @brief Swaps two entities in the internal packed array. | |||||
* | |||||
* For what it's worth, this function affects both the internal sparse array | |||||
* and the internal packed array. Users should not care of that anyway. | |||||
* | |||||
* @warning | |||||
* Attempting to swap entities that don't belong to the sparse set results | |||||
* in undefined behavior.<br/> | |||||
* An assertion will abort the execution at runtime in debug mode if the | |||||
* sparse set doesn't contain the given entities. | |||||
* | |||||
* @param lhs A valid position within the sparse set. | |||||
* @param rhs A valid position within the sparse set. | |||||
*/ | |||||
void swap(const size_type lhs, const size_type rhs) ENTT_NOEXCEPT { | |||||
ENTT_ASSERT(lhs < direct.size()); | |||||
ENTT_ASSERT(rhs < direct.size()); | |||||
auto [src_page, src_offset] = index(direct[lhs]); | |||||
auto [dst_page, dst_offset] = index(direct[rhs]); | |||||
std::swap(reverse[src_page].first[src_offset], reverse[dst_page].first[dst_offset]); | |||||
std::swap(direct[lhs], direct[rhs]); | |||||
} | |||||
/** | |||||
* @brief Sort entities according to their order in another sparse set. | |||||
* | |||||
* Entities that are part of both the sparse sets are ordered internally | |||||
* according to the order they have in `other`. All the other entities goes | |||||
* to the end of the list and there are no guarantess on their order.<br/> | |||||
* In other terms, this function can be used to impose the same order on two | |||||
* sets by using one of them as a master and the other one as a slave. | |||||
* | |||||
* Iterating the sparse set with a couple of iterators returns elements in | |||||
* the expected order after a call to `respect`. See `begin` and `end` for | |||||
* more details. | |||||
* | |||||
* @note | |||||
* Attempting to iterate elements using the raw pointer returned by `data` | |||||
* gives no guarantees on the order, even though `respect` has been invoked. | |||||
* | |||||
* @param other The sparse sets that imposes the order of the entities. | |||||
*/ | |||||
virtual void respect(const sparse_set &other) ENTT_NOEXCEPT { | |||||
const auto to = other.end(); | |||||
auto from = other.begin(); | |||||
size_type pos = direct.size() - 1; | |||||
while(pos && from != to) { | |||||
if(has(*from)) { | |||||
if(*from != direct[pos]) { | |||||
swap(pos, get(*from)); | |||||
} | |||||
--pos; | |||||
} | |||||
++from; | |||||
} | |||||
} | |||||
/** | |||||
* @brief Resets a sparse set. | |||||
*/ | |||||
virtual void reset() { | |||||
reverse.clear(); | |||||
direct.clear(); | |||||
} | |||||
private: | |||||
std::vector<std::pair<std::unique_ptr<entity_type[]>, size_type>> reverse; | |||||
std::vector<entity_type> direct; | |||||
}; | |||||
} | |||||
#endif // ENTT_ENTITY_SPARSE_SET_HPP |
@ -0,0 +1,717 @@ | |||||
#ifndef ENTT_ENTITY_STORAGE_HPP | |||||
#define ENTT_ENTITY_STORAGE_HPP | |||||
#include <algorithm> | |||||
#include <iterator> | |||||
#include <numeric> | |||||
#include <utility> | |||||
#include <vector> | |||||
#include <cstddef> | |||||
#include <type_traits> | |||||
#include "../config/config.h" | |||||
#include "../core/algorithm.hpp" | |||||
#include "sparse_set.hpp" | |||||
#include "entity.hpp" | |||||
namespace entt { | |||||
/** | |||||
* @brief Basic storage implementation. | |||||
* | |||||
* This class is a refinement of a sparse set that associates an object to an | |||||
* entity. The main purpose of this class is to extend sparse sets to store | |||||
* components in a registry. It guarantees fast access both to the elements and | |||||
* to the entities. | |||||
* | |||||
* @note | |||||
* Entities and objects have the same order. It's guaranteed both in case of raw | |||||
* access (either to entities or objects) and when using input iterators. | |||||
* | |||||
* @note | |||||
* Internal data structures arrange elements to maximize performance. Because of | |||||
* that, there are no guarantees that elements have the expected order when | |||||
* iterate directly the internal packed array (see `raw` and `size` member | |||||
* functions for that). Use `begin` and `end` instead. | |||||
* | |||||
* @warning | |||||
* Empty types aren't explicitly instantiated. Temporary objects are returned in | |||||
* place of the instances of the components and raw access isn't available for | |||||
* them. | |||||
* | |||||
* @sa sparse_set<Entity> | |||||
* | |||||
* @tparam Entity A valid entity type (see entt_traits for more details). | |||||
* @tparam Type Type of objects assigned to the entities. | |||||
*/ | |||||
template<typename Entity, typename Type, typename = std::void_t<>> | |||||
class basic_storage: public sparse_set<Entity> { | |||||
using underlying_type = sparse_set<Entity>; | |||||
using traits_type = entt_traits<Entity>; | |||||
template<bool Const> | |||||
class iterator { | |||||
friend class basic_storage<Entity, Type>; | |||||
using instance_type = std::conditional_t<Const, const std::vector<Type>, std::vector<Type>>; | |||||
using index_type = typename traits_type::difference_type; | |||||
iterator(instance_type *ref, const index_type idx) ENTT_NOEXCEPT | |||||
: instances{ref}, index{idx} | |||||
{} | |||||
public: | |||||
using difference_type = index_type; | |||||
using value_type = Type; | |||||
using pointer = std::conditional_t<Const, const value_type *, value_type *>; | |||||
using reference = std::conditional_t<Const, const value_type &, value_type &>; | |||||
using iterator_category = std::random_access_iterator_tag; | |||||
iterator() ENTT_NOEXCEPT = default; | |||||
iterator & operator++() ENTT_NOEXCEPT { | |||||
return --index, *this; | |||||
} | |||||
iterator operator++(int) ENTT_NOEXCEPT { | |||||
iterator orig = *this; | |||||
return ++(*this), orig; | |||||
} | |||||
iterator & operator--() ENTT_NOEXCEPT { | |||||
return ++index, *this; | |||||
} | |||||
iterator operator--(int) ENTT_NOEXCEPT { | |||||
iterator orig = *this; | |||||
return --(*this), orig; | |||||
} | |||||
iterator & operator+=(const difference_type value) ENTT_NOEXCEPT { | |||||
index -= value; | |||||
return *this; | |||||
} | |||||
iterator operator+(const difference_type value) const ENTT_NOEXCEPT { | |||||
return iterator{instances, index-value}; | |||||
} | |||||
inline iterator & operator-=(const difference_type value) ENTT_NOEXCEPT { | |||||
return (*this += -value); | |||||
} | |||||
inline iterator operator-(const difference_type value) const ENTT_NOEXCEPT { | |||||
return (*this + -value); | |||||
} | |||||
difference_type operator-(const iterator &other) const ENTT_NOEXCEPT { | |||||
return other.index - index; | |||||
} | |||||
reference operator[](const difference_type value) const ENTT_NOEXCEPT { | |||||
const auto pos = size_type(index-value-1); | |||||
return (*instances)[pos]; | |||||
} | |||||
bool operator==(const iterator &other) const ENTT_NOEXCEPT { | |||||
return other.index == index; | |||||
} | |||||
inline bool operator!=(const iterator &other) const ENTT_NOEXCEPT { | |||||
return !(*this == other); | |||||
} | |||||
bool operator<(const iterator &other) const ENTT_NOEXCEPT { | |||||
return index > other.index; | |||||
} | |||||
bool operator>(const iterator &other) const ENTT_NOEXCEPT { | |||||
return index < other.index; | |||||
} | |||||
inline bool operator<=(const iterator &other) const ENTT_NOEXCEPT { | |||||
return !(*this > other); | |||||
} | |||||
inline bool operator>=(const iterator &other) const ENTT_NOEXCEPT { | |||||
return !(*this < other); | |||||
} | |||||
pointer operator->() const ENTT_NOEXCEPT { | |||||
const auto pos = size_type(index-1); | |||||
return &(*instances)[pos]; | |||||
} | |||||
inline reference operator*() const ENTT_NOEXCEPT { | |||||
return *operator->(); | |||||
} | |||||
private: | |||||
instance_type *instances; | |||||
index_type index; | |||||
}; | |||||
public: | |||||
/*! @brief Type of the objects associated with the entities. */ | |||||
using object_type = Type; | |||||
/*! @brief Underlying entity identifier. */ | |||||
using entity_type = typename underlying_type::entity_type; | |||||
/*! @brief Unsigned integer type. */ | |||||
using size_type = typename underlying_type::size_type; | |||||
/*! @brief Random access iterator type. */ | |||||
using iterator_type = iterator<false>; | |||||
/*! @brief Constant random access iterator type. */ | |||||
using const_iterator_type = iterator<true>; | |||||
/** | |||||
* @brief Increases the capacity of a storage. | |||||
* | |||||
* If the new capacity is greater than the current capacity, new storage is | |||||
* allocated, otherwise the method does nothing. | |||||
* | |||||
* @param cap Desired capacity. | |||||
*/ | |||||
void reserve(const size_type cap) override { | |||||
underlying_type::reserve(cap); | |||||
instances.reserve(cap); | |||||
} | |||||
/*! @brief Requests the removal of unused capacity. */ | |||||
void shrink_to_fit() override { | |||||
underlying_type::shrink_to_fit(); | |||||
instances.shrink_to_fit(); | |||||
} | |||||
/** | |||||
* @brief Direct access to the array of objects. | |||||
* | |||||
* The returned pointer is such that range `[raw(), raw() + size()]` is | |||||
* always a valid range, even if the container is empty. | |||||
* | |||||
* @note | |||||
* There are no guarantees on the order, even though either `sort` or | |||||
* `respect` has been previously invoked. Internal data structures arrange | |||||
* elements to maximize performance. Accessing them directly gives a | |||||
* performance boost but less guarantees. Use `begin` and `end` if you want | |||||
* to iterate the storage in the expected order. | |||||
* | |||||
* @return A pointer to the array of objects. | |||||
*/ | |||||
const object_type * raw() const ENTT_NOEXCEPT { | |||||
return instances.data(); | |||||
} | |||||
/*! @copydoc raw */ | |||||
object_type * raw() ENTT_NOEXCEPT { | |||||
return const_cast<object_type *>(std::as_const(*this).raw()); | |||||
} | |||||
/** | |||||
* @brief Returns an iterator to the beginning. | |||||
* | |||||
* The returned iterator points to the first instance of the given type. If | |||||
* the storage is empty, the returned iterator will be equal to `end()`. | |||||
* | |||||
* @note | |||||
* Input iterators stay true to the order imposed by a call to either `sort` | |||||
* or `respect`. | |||||
* | |||||
* @return An iterator to the first instance of the given type. | |||||
*/ | |||||
const_iterator_type cbegin() const ENTT_NOEXCEPT { | |||||
const typename traits_type::difference_type pos = underlying_type::size(); | |||||
return const_iterator_type{&instances, pos}; | |||||
} | |||||
/*! @copydoc cbegin */ | |||||
inline const_iterator_type begin() const ENTT_NOEXCEPT { | |||||
return cbegin(); | |||||
} | |||||
/*! @copydoc begin */ | |||||
iterator_type begin() ENTT_NOEXCEPT { | |||||
const typename traits_type::difference_type pos = underlying_type::size(); | |||||
return iterator_type{&instances, pos}; | |||||
} | |||||
/** | |||||
* @brief Returns an iterator to the end. | |||||
* | |||||
* The returned iterator points to the element following the last instance | |||||
* of the given type. Attempting to dereference the returned iterator | |||||
* results in undefined behavior. | |||||
* | |||||
* @note | |||||
* Input iterators stay true to the order imposed by a call to either `sort` | |||||
* or `respect`. | |||||
* | |||||
* @return An iterator to the element following the last instance of the | |||||
* given type. | |||||
*/ | |||||
const_iterator_type cend() const ENTT_NOEXCEPT { | |||||
return const_iterator_type{&instances, {}}; | |||||
} | |||||
/*! @copydoc cend */ | |||||
inline const_iterator_type end() const ENTT_NOEXCEPT { | |||||
return cend(); | |||||
} | |||||
/*! @copydoc end */ | |||||
iterator_type end() ENTT_NOEXCEPT { | |||||
return iterator_type{&instances, {}}; | |||||
} | |||||
/** | |||||
* @brief Returns the object associated with an entity. | |||||
* | |||||
* @warning | |||||
* Attempting to use an entity that doesn't belong to the storage results in | |||||
* undefined behavior.<br/> | |||||
* An assertion will abort the execution at runtime in debug mode if the | |||||
* storage doesn't contain the given entity. | |||||
* | |||||
* @param entt A valid entity identifier. | |||||
* @return The object associated with the entity. | |||||
*/ | |||||
const object_type & get(const entity_type entt) const ENTT_NOEXCEPT { | |||||
return instances[underlying_type::get(entt)]; | |||||
} | |||||
/*! @copydoc get */ | |||||
inline object_type & get(const entity_type entt) ENTT_NOEXCEPT { | |||||
return const_cast<object_type &>(std::as_const(*this).get(entt)); | |||||
} | |||||
/** | |||||
* @brief Returns a pointer to the object associated with an entity, if any. | |||||
* @param entt A valid entity identifier. | |||||
* @return The object associated with the entity, if any. | |||||
*/ | |||||
const object_type * try_get(const entity_type entt) const ENTT_NOEXCEPT { | |||||
return underlying_type::has(entt) ? (instances.data() + underlying_type::get(entt)) : nullptr; | |||||
} | |||||
/*! @copydoc try_get */ | |||||
inline object_type * try_get(const entity_type entt) ENTT_NOEXCEPT { | |||||
return const_cast<object_type *>(std::as_const(*this).try_get(entt)); | |||||
} | |||||
/** | |||||
* @brief Assigns an entity to a storage and constructs its object. | |||||
* | |||||
* This version accept both types that can be constructed in place directly | |||||
* and types like aggregates that do not work well with a placement new as | |||||
* performed usually under the hood during an _emplace back_. | |||||
* | |||||
* @warning | |||||
* Attempting to use an entity that already belongs to the storage results | |||||
* in undefined behavior.<br/> | |||||
* An assertion will abort the execution at runtime in debug mode if the | |||||
* storage already contains the given entity. | |||||
* | |||||
* @tparam Args Types of arguments to use to construct the object. | |||||
* @param entt A valid entity identifier. | |||||
* @param args Parameters to use to construct an object for the entity. | |||||
* @return The object associated with the entity. | |||||
*/ | |||||
template<typename... Args> | |||||
object_type & construct(const entity_type entt, Args &&... args) { | |||||
if constexpr(std::is_aggregate_v<object_type>) { | |||||
instances.emplace_back(Type{std::forward<Args>(args)...}); | |||||
} else { | |||||
instances.emplace_back(std::forward<Args>(args)...); | |||||
} | |||||
// entity goes after component in case constructor throws | |||||
underlying_type::construct(entt); | |||||
return instances.back(); | |||||
} | |||||
/** | |||||
* @brief Assigns one or more entities to a storage and constructs their | |||||
* objects. | |||||
* | |||||
* The object type must be at least default constructible. | |||||
* | |||||
* @warning | |||||
* Attempting to assign an entity that already belongs to the storage | |||||
* results in undefined behavior.<br/> | |||||
* An assertion will abort the execution at runtime in debug mode if the | |||||
* storage already contains the given entity. | |||||
* | |||||
* @tparam It Type of forward iterator. | |||||
* @param first An iterator to the first element of the range of entities. | |||||
* @param last An iterator past the last element of the range of entities. | |||||
* @return A pointer to the array of instances just created and sorted the | |||||
* same of the entities. | |||||
*/ | |||||
template<typename It> | |||||
object_type * batch(It first, It last) { | |||||
static_assert(std::is_default_constructible_v<object_type>); | |||||
const auto skip = instances.size(); | |||||
instances.insert(instances.end(), last-first, {}); | |||||
// entity goes after component in case constructor throws | |||||
underlying_type::batch(first, last); | |||||
return instances.data() + skip; | |||||
} | |||||
/** | |||||
* @brief Removes an entity from a storage and destroys its object. | |||||
* | |||||
* @warning | |||||
* Attempting to use an entity that doesn't belong to the storage results in | |||||
* undefined behavior.<br/> | |||||
* An assertion will abort the execution at runtime in debug mode if the | |||||
* storage doesn't contain the given entity. | |||||
* | |||||
* @param entt A valid entity identifier. | |||||
*/ | |||||
void destroy(const entity_type entt) override { | |||||
std::swap(instances[underlying_type::get(entt)], instances.back()); | |||||
instances.pop_back(); | |||||
underlying_type::destroy(entt); | |||||
} | |||||
/** | |||||
* @brief Swaps entities and objects in the internal packed arrays. | |||||
* | |||||
* @warning | |||||
* Attempting to swap entities that don't belong to the sparse set results | |||||
* in undefined behavior.<br/> | |||||
* An assertion will abort the execution at runtime in debug mode if the | |||||
* sparse set doesn't contain the given entities. | |||||
* | |||||
* @param lhs A valid position within the sparse set. | |||||
* @param rhs A valid position within the sparse set. | |||||
*/ | |||||
void swap(const size_type lhs, const size_type rhs) ENTT_NOEXCEPT { | |||||
ENTT_ASSERT(lhs < instances.size()); | |||||
ENTT_ASSERT(rhs < instances.size()); | |||||
std::swap(instances[lhs], instances[rhs]); | |||||
underlying_type::swap(lhs, rhs); | |||||
} | |||||
/** | |||||
* @brief Sort instances according to the given comparison function. | |||||
* | |||||
* Sort the elements so that iterating the storage with a couple of | |||||
* iterators returns them in the expected order. See `begin` and `end` for | |||||
* more details. | |||||
* | |||||
* The comparison function object must return `true` if the first element | |||||
* is _less_ than the second one, `false` otherwise. The signature of the | |||||
* comparison function should be equivalent to one of the following: | |||||
* | |||||
* @code{.cpp} | |||||
* bool(const Entity, const Entity); | |||||
* bool(const Type &, const Type &); | |||||
* @endcode | |||||
* | |||||
* Moreover, the comparison function object shall induce a | |||||
* _strict weak ordering_ on the values. | |||||
* | |||||
* The sort function oject must offer a member function template | |||||
* `operator()` that accepts three arguments: | |||||
* | |||||
* * An iterator to the first element of the range to sort. | |||||
* * An iterator past the last element of the range to sort. | |||||
* * A comparison function to use to compare the elements. | |||||
* | |||||
* The comparison function object received by the sort function object | |||||
* hasn't necessarily the type of the one passed along with the other | |||||
* parameters to this member function. | |||||
* | |||||
* @note | |||||
* Attempting to iterate elements using a raw pointer returned by a call to | |||||
* either `data` or `raw` gives no guarantees on the order, even though | |||||
* `sort` has been invoked. | |||||
* | |||||
* @tparam Compare Type of comparison function object. | |||||
* @tparam Sort Type of sort function object. | |||||
* @tparam Args Types of arguments to forward to the sort function object. | |||||
* @param compare A valid comparison function object. | |||||
* @param algo A valid sort function object. | |||||
* @param args Arguments to forward to the sort function object, if any. | |||||
*/ | |||||
template<typename Compare, typename Sort = std_sort, typename... Args> | |||||
void sort(Compare compare, Sort algo = Sort{}, Args &&... args) { | |||||
std::vector<size_type> copy(instances.size()); | |||||
std::iota(copy.begin(), copy.end(), 0); | |||||
if constexpr(std::is_invocable_v<Compare, const object_type &, const object_type &>) { | |||||
static_assert(!std::is_empty_v<object_type>); | |||||
algo(copy.rbegin(), copy.rend(), [this, compare = std::move(compare)](const auto lhs, const auto rhs) { | |||||
return compare(std::as_const(instances[lhs]), std::as_const(instances[rhs])); | |||||
}, std::forward<Args>(args)...); | |||||
} else { | |||||
algo(copy.rbegin(), copy.rend(), [compare = std::move(compare), data = underlying_type::data()](const auto lhs, const auto rhs) { | |||||
return compare(data[lhs], data[rhs]); | |||||
}, std::forward<Args>(args)...); | |||||
} | |||||
for(size_type pos{}, last = copy.size(); pos < last; ++pos) { | |||||
auto curr = pos; | |||||
auto next = copy[curr]; | |||||
while(curr != next) { | |||||
const auto lhs = copy[curr]; | |||||
const auto rhs = copy[next]; | |||||
std::swap(instances[lhs], instances[rhs]); | |||||
underlying_type::swap(lhs, rhs); | |||||
copy[curr] = curr; | |||||
curr = next; | |||||
next = copy[curr]; | |||||
} | |||||
} | |||||
} | |||||
/** | |||||
* @brief Sort instances according to the order of the entities in another | |||||
* sparse set. | |||||
* | |||||
* Entities that are part of both the storage are ordered internally | |||||
* according to the order they have in `other`. All the other entities goes | |||||
* to the end of the list and there are no guarantess on their order. | |||||
* Instances are sorted according to the entities to which they belong.<br/> | |||||
* In other terms, this function can be used to impose the same order on two | |||||
* sets by using one of them as a master and the other one as a slave. | |||||
* | |||||
* Iterating the storage with a couple of iterators returns elements in the | |||||
* expected order after a call to `respect`. See `begin` and `end` for more | |||||
* details. | |||||
* | |||||
* @note | |||||
* Attempting to iterate elements using a raw pointer returned by a call to | |||||
* either `data` or `raw` gives no guarantees on the order, even though | |||||
* `respect` has been invoked. | |||||
* | |||||
* @param other The sparse sets that imposes the order of the entities. | |||||
*/ | |||||
void respect(const sparse_set<Entity> &other) ENTT_NOEXCEPT override { | |||||
const auto to = other.end(); | |||||
auto from = other.begin(); | |||||
size_type pos = underlying_type::size() - 1; | |||||
const auto *local = underlying_type::data(); | |||||
while(pos && from != to) { | |||||
const auto curr = *from; | |||||
if(underlying_type::has(curr)) { | |||||
if(curr != *(local + pos)) { | |||||
auto candidate = underlying_type::get(curr); | |||||
std::swap(instances[pos], instances[candidate]); | |||||
underlying_type::swap(pos, candidate); | |||||
} | |||||
--pos; | |||||
} | |||||
++from; | |||||
} | |||||
} | |||||
/*! @brief Resets a storage. */ | |||||
void reset() override { | |||||
underlying_type::reset(); | |||||
instances.clear(); | |||||
} | |||||
private: | |||||
std::vector<object_type> instances; | |||||
}; | |||||
/*! @copydoc basic_storage */ | |||||
template<typename Entity, typename Type> | |||||
class basic_storage<Entity, Type, std::enable_if_t<std::is_empty_v<Type>>>: public sparse_set<Entity> { | |||||
using underlying_type = sparse_set<Entity>; | |||||
using traits_type = entt_traits<Entity>; | |||||
class iterator { | |||||
friend class basic_storage<Entity, Type>; | |||||
using index_type = typename traits_type::difference_type; | |||||
iterator(const index_type idx) ENTT_NOEXCEPT | |||||
: index{idx} | |||||
{} | |||||
public: | |||||
using difference_type = index_type; | |||||
using value_type = Type; | |||||
using pointer = const value_type *; | |||||
using reference = value_type; | |||||
using iterator_category = std::input_iterator_tag; | |||||
iterator() ENTT_NOEXCEPT = default; | |||||
iterator & operator++() ENTT_NOEXCEPT { | |||||
return --index, *this; | |||||
} | |||||
iterator operator++(int) ENTT_NOEXCEPT { | |||||
iterator orig = *this; | |||||
return ++(*this), orig; | |||||
} | |||||
iterator & operator--() ENTT_NOEXCEPT { | |||||
return ++index, *this; | |||||
} | |||||
iterator operator--(int) ENTT_NOEXCEPT { | |||||
iterator orig = *this; | |||||
return --(*this), orig; | |||||
} | |||||
iterator & operator+=(const difference_type value) ENTT_NOEXCEPT { | |||||
index -= value; | |||||
return *this; | |||||
} | |||||
iterator operator+(const difference_type value) const ENTT_NOEXCEPT { | |||||
return iterator{index-value}; | |||||
} | |||||
inline iterator & operator-=(const difference_type value) ENTT_NOEXCEPT { | |||||
return (*this += -value); | |||||
} | |||||
inline iterator operator-(const difference_type value) const ENTT_NOEXCEPT { | |||||
return (*this + -value); | |||||
} | |||||
difference_type operator-(const iterator &other) const ENTT_NOEXCEPT { | |||||
return other.index - index; | |||||
} | |||||
reference operator[](const difference_type) const ENTT_NOEXCEPT { | |||||
return {}; | |||||
} | |||||
bool operator==(const iterator &other) const ENTT_NOEXCEPT { | |||||
return other.index == index; | |||||
} | |||||
inline bool operator!=(const iterator &other) const ENTT_NOEXCEPT { | |||||
return !(*this == other); | |||||
} | |||||
bool operator<(const iterator &other) const ENTT_NOEXCEPT { | |||||
return index > other.index; | |||||
} | |||||
bool operator>(const iterator &other) const ENTT_NOEXCEPT { | |||||
return index < other.index; | |||||
} | |||||
inline bool operator<=(const iterator &other) const ENTT_NOEXCEPT { | |||||
return !(*this > other); | |||||
} | |||||
inline bool operator>=(const iterator &other) const ENTT_NOEXCEPT { | |||||
return !(*this < other); | |||||
} | |||||
pointer operator->() const ENTT_NOEXCEPT { | |||||
return nullptr; | |||||
} | |||||
inline reference operator*() const ENTT_NOEXCEPT { | |||||
return {}; | |||||
} | |||||
private: | |||||
index_type index; | |||||
}; | |||||
public: | |||||
/*! @brief Type of the objects associated with the entities. */ | |||||
using object_type = Type; | |||||
/*! @brief Underlying entity identifier. */ | |||||
using entity_type = typename underlying_type::entity_type; | |||||
/*! @brief Unsigned integer type. */ | |||||
using size_type = typename underlying_type::size_type; | |||||
/*! @brief Random access iterator type. */ | |||||
using iterator_type = iterator; | |||||
/** | |||||
* @brief Returns an iterator to the beginning. | |||||
* | |||||
* The returned iterator points to the first instance of the given type. If | |||||
* the storage is empty, the returned iterator will be equal to `end()`. | |||||
* | |||||
* @note | |||||
* Input iterators stay true to the order imposed by a call to either `sort` | |||||
* or `respect`. | |||||
* | |||||
* @return An iterator to the first instance of the given type. | |||||
*/ | |||||
iterator_type cbegin() const ENTT_NOEXCEPT { | |||||
const typename traits_type::difference_type pos = underlying_type::size(); | |||||
return iterator_type{pos}; | |||||
} | |||||
/*! @copydoc cbegin */ | |||||
inline iterator_type begin() const ENTT_NOEXCEPT { | |||||
return cbegin(); | |||||
} | |||||
/** | |||||
* @brief Returns an iterator to the end. | |||||
* | |||||
* The returned iterator points to the element following the last instance | |||||
* of the given type. Attempting to dereference the returned iterator | |||||
* results in undefined behavior. | |||||
* | |||||
* @note | |||||
* Input iterators stay true to the order imposed by a call to either `sort` | |||||
* or `respect`. | |||||
* | |||||
* @return An iterator to the element following the last instance of the | |||||
* given type. | |||||
*/ | |||||
iterator_type cend() const ENTT_NOEXCEPT { | |||||
return iterator_type{}; | |||||
} | |||||
/*! @copydoc cend */ | |||||
inline iterator_type end() const ENTT_NOEXCEPT { | |||||
return cend(); | |||||
} | |||||
/** | |||||
* @brief Returns the object associated with an entity. | |||||
* | |||||
* @note | |||||
* Empty types aren't explicitly instantiated. Therefore, this function | |||||
* always returns a temporary object. | |||||
* | |||||
* @warning | |||||
* Attempting to use an entity that doesn't belong to the storage results in | |||||
* undefined behavior.<br/> | |||||
* An assertion will abort the execution at runtime in debug mode if the | |||||
* storage doesn't contain the given entity. | |||||
* | |||||
* @param entt A valid entity identifier. | |||||
* @return The object associated with the entity. | |||||
*/ | |||||
object_type get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT { | |||||
ENTT_ASSERT(underlying_type::has(entt)); | |||||
return {}; | |||||
} | |||||
}; | |||||
/*! @copydoc basic_storage */ | |||||
template<typename Entity, typename Type> | |||||
struct storage: basic_storage<Entity, Type> {}; | |||||
} | |||||
#endif // ENTT_ENTITY_STORAGE_HPP |
@ -0,0 +1,777 @@ | |||||
#ifndef ENTT_ENTITY_VIEW_HPP | |||||
#define ENTT_ENTITY_VIEW_HPP | |||||
#include <iterator> | |||||
#include <array> | |||||
#include <tuple> | |||||
#include <utility> | |||||
#include <algorithm> | |||||
#include <type_traits> | |||||
#include "../config/config.h" | |||||
#include "../core/type_traits.hpp" | |||||
#include "sparse_set.hpp" | |||||
#include "storage.hpp" | |||||
#include "entity.hpp" | |||||
#include "fwd.hpp" | |||||
namespace entt { | |||||
/** | |||||
* @brief Multi component view. | |||||
* | |||||
* Multi component views iterate over those entities that have at least all the | |||||
* given components in their bags. During initialization, a multi component view | |||||
* looks at the number of entities available for each component and picks up a | |||||
* reference to the smallest set of candidate entities in order to get a | |||||
* performance boost when iterate.<br/> | |||||
* Order of elements during iterations are highly dependent on the order of the | |||||
* underlying data structures. See sparse_set and its specializations for more | |||||
* details. | |||||
* | |||||
* @b Important | |||||
* | |||||
* Iterators aren't invalidated if: | |||||
* | |||||
* * New instances of the given components are created and assigned to entities. | |||||
* * The entity currently pointed is modified (as an example, if one of the | |||||
* given components is removed from the entity to which the iterator points). | |||||
* | |||||
* In all the other cases, modifying the pools of the given components in any | |||||
* way invalidates all the iterators and using them results in undefined | |||||
* behavior. | |||||
* | |||||
* @note | |||||
* Views share references to the underlying data structures of the registry that | |||||
* generated them. Therefore any change to the entities and to the components | |||||
* made by means of the registry are immediately reflected by views. | |||||
* | |||||
* @warning | |||||
* Lifetime of a view must overcome the one of the registry that generated it. | |||||
* In any other case, attempting to use a view results in undefined behavior. | |||||
* | |||||
* @tparam Entity A valid entity type (see entt_traits for more details). | |||||
* @tparam Component Types of components iterated by the view. | |||||
*/ | |||||
template<typename Entity, typename... Component> | |||||
class basic_view { | |||||
static_assert(sizeof...(Component) > 1); | |||||
/*! @brief A registry is allowed to create views. */ | |||||
friend class basic_registry<Entity>; | |||||
template<typename Comp> | |||||
using pool_type = std::conditional_t<std::is_const_v<Comp>, const storage<Entity, std::remove_const_t<Comp>>, storage<Entity, Comp>>; | |||||
template<typename Comp> | |||||
using component_iterator_type = decltype(std::declval<pool_type<Comp>>().begin()); | |||||
using underlying_iterator_type = typename sparse_set<Entity>::iterator_type; | |||||
using unchecked_type = std::array<const sparse_set<Entity> *, (sizeof...(Component) - 1)>; | |||||
using traits_type = entt_traits<Entity>; | |||||
class iterator { | |||||
friend class basic_view<Entity, Component...>; | |||||
using extent_type = typename sparse_set<Entity>::size_type; | |||||
iterator(unchecked_type other, underlying_iterator_type first, underlying_iterator_type last) ENTT_NOEXCEPT | |||||
: unchecked{other}, | |||||
begin{first}, | |||||
end{last}, | |||||
extent{min(std::make_index_sequence<other.size()>{})} | |||||
{ | |||||
if(begin != end && !valid()) { | |||||
++(*this); | |||||
} | |||||
} | |||||
template<auto... 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 entt = *begin; | |||||
const auto sz = size_type(entt& traits_type::entity_mask); | |||||
return sz < extent && std::all_of(unchecked.cbegin(), unchecked.cend(), [entt](const sparse_set<Entity> *view) { | |||||
return view->has(entt); | |||||
}); | |||||
} | |||||
public: | |||||
using difference_type = typename underlying_iterator_type::difference_type; | |||||
using value_type = typename underlying_iterator_type::value_type; | |||||
using pointer = typename underlying_iterator_type::pointer; | |||||
using reference = typename underlying_iterator_type::reference; | |||||
using iterator_category = std::forward_iterator_tag; | |||||
iterator() ENTT_NOEXCEPT = default; | |||||
iterator & operator++() ENTT_NOEXCEPT { | |||||
return (++begin != end && !valid()) ? ++(*this) : *this; | |||||
} | |||||
iterator operator++(int) ENTT_NOEXCEPT { | |||||
iterator orig = *this; | |||||
return ++(*this), orig; | |||||
} | |||||
bool operator==(const iterator &other) const ENTT_NOEXCEPT { | |||||
return other.begin == begin; | |||||
} | |||||
inline bool operator!=(const iterator &other) const ENTT_NOEXCEPT { | |||||
return !(*this == other); | |||||
} | |||||
pointer operator->() const ENTT_NOEXCEPT { | |||||
return begin.operator->(); | |||||
} | |||||
inline reference operator*() const ENTT_NOEXCEPT { | |||||
return *operator->(); | |||||
} | |||||
private: | |||||
unchecked_type unchecked; | |||||
underlying_iterator_type begin; | |||||
underlying_iterator_type end; | |||||
extent_type extent; | |||||
}; | |||||
// we could use pool_type<Component> *..., but vs complains about it and refuses to compile for unknown reasons (likely a bug) | |||||
basic_view(storage<Entity, std::remove_const_t<Component>> *... ref) ENTT_NOEXCEPT | |||||
: pools{ref...} | |||||
{} | |||||
const sparse_set<Entity> * candidate() const ENTT_NOEXCEPT { | |||||
return std::min({ static_cast<const sparse_set<Entity> *>(std::get<pool_type<Component> *>(pools))... }, [](const auto *lhs, const auto *rhs) { | |||||
return lhs->size() < rhs->size(); | |||||
}); | |||||
} | |||||
unchecked_type unchecked(const sparse_set<Entity> *view) const ENTT_NOEXCEPT { | |||||
unchecked_type other{}; | |||||
typename unchecked_type::size_type pos{}; | |||||
((std::get<pool_type<Component> *>(pools) == view ? nullptr : (other[pos++] = std::get<pool_type<Component> *>(pools))), ...); | |||||
return other; | |||||
} | |||||
template<typename Comp, typename Other> | |||||
inline decltype(auto) get([[maybe_unused]] component_iterator_type<Comp> it, [[maybe_unused]] pool_type<Other> *cpool, [[maybe_unused]] const Entity entt) const ENTT_NOEXCEPT { | |||||
if constexpr(std::is_same_v<Comp, Other>) { | |||||
return *it; | |||||
} else { | |||||
return cpool->get(entt); | |||||
} | |||||
} | |||||
template<typename Comp, typename Func, typename... Other, typename... Type> | |||||
void traverse(Func func, type_list<Other...>, type_list<Type...>) const { | |||||
const auto end = std::get<pool_type<Comp> *>(pools)->sparse_set<Entity>::end(); | |||||
auto begin = std::get<pool_type<Comp> *>(pools)->sparse_set<Entity>::begin(); | |||||
if constexpr(std::disjunction_v<std::is_same<Comp, Type>...>) { | |||||
std::for_each(begin, end, [raw = std::get<pool_type<Comp> *>(pools)->begin(), &func, this](const auto entity) mutable { | |||||
auto curr = raw++; | |||||
if((std::get<pool_type<Other> *>(pools)->has(entity) && ...)) { | |||||
if constexpr(std::is_invocable_v<Func, decltype(get<Type>({}))...>) { | |||||
func(get<Comp, Type>(curr, std::get<pool_type<Type> *>(pools), entity)...); | |||||
} else { | |||||
func(entity, get<Comp, Type>(curr, std::get<pool_type<Type> *>(pools), entity)...); | |||||
} | |||||
} | |||||
}); | |||||
} else { | |||||
std::for_each(begin, end, [&func, this](const auto entity) mutable { | |||||
if((std::get<pool_type<Other> *>(pools)->has(entity) && ...)) { | |||||
if constexpr(std::is_invocable_v<Func, decltype(get<Type>({}))...>) { | |||||
func(std::get<pool_type<Type> *>(pools)->get(entity)...); | |||||
} else { | |||||
func(entity, std::get<pool_type<Type> *>(pools)->get(entity)...); | |||||
} | |||||
} | |||||
}); | |||||
} | |||||
} | |||||
public: | |||||
/*! @brief Underlying entity identifier. */ | |||||
using entity_type = typename sparse_set<Entity>::entity_type; | |||||
/*! @brief Unsigned integer type. */ | |||||
using size_type = typename sparse_set<Entity>::size_type; | |||||
/*! @brief Input iterator type. */ | |||||
using iterator_type = iterator; | |||||
/** | |||||
* @brief Returns the number of existing components of the given type. | |||||
* @tparam Comp Type of component of which to return the size. | |||||
* @return Number of existing components of the given type. | |||||
*/ | |||||
template<typename Comp> | |||||
size_type size() const ENTT_NOEXCEPT { | |||||
return std::get<pool_type<Comp> *>(pools)->size(); | |||||
} | |||||
/** | |||||
* @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 std::min({ std::get<pool_type<Component> *>(pools)->size()... }); | |||||
} | |||||
/** | |||||
* @brief Checks whether the pool of a given component is empty. | |||||
* @tparam Comp Type of component in which one is interested. | |||||
* @return True if the pool of the given component is empty, false | |||||
* otherwise. | |||||
*/ | |||||
template<typename Comp> | |||||
bool empty() const ENTT_NOEXCEPT { | |||||
return std::get<pool_type<Comp> *>(pools)->empty(); | |||||
} | |||||
/** | |||||
* @brief Checks if the view is definitely empty. | |||||
* @return True if the view is definitely empty, false otherwise. | |||||
*/ | |||||
bool empty() const ENTT_NOEXCEPT { | |||||
return (std::get<pool_type<Component> *>(pools)->empty() || ...); | |||||
} | |||||
/** | |||||
* @brief Direct access to the list of components of a given pool. | |||||
* | |||||
* The returned pointer is such that range | |||||
* `[raw<Comp>(), raw<Comp>() + size<Comp>()]` is always a valid range, even | |||||
* if the container is empty. | |||||
* | |||||
* @note | |||||
* There are no guarantees on the order of the components. Use `begin` and | |||||
* `end` if you want to iterate the view in the expected order. | |||||
* | |||||
* @tparam Comp Type of component in which one is interested. | |||||
* @return A pointer to the array of components. | |||||
*/ | |||||
template<typename Comp> | |||||
Comp * raw() const ENTT_NOEXCEPT { | |||||
return std::get<pool_type<Comp> *>(pools)->raw(); | |||||
} | |||||
/** | |||||
* @brief Direct access to the list of entities of a given pool. | |||||
* | |||||
* The returned pointer is such that range | |||||
* `[data<Comp>(), data<Comp>() + size<Comp>()]` is always a valid range, | |||||
* even if the container is empty. | |||||
* | |||||
* @note | |||||
* There are no guarantees on the order of the entities. Use `begin` and | |||||
* `end` if you want to iterate the view in the expected order. | |||||
* | |||||
* @tparam Comp Type of component in which one is interested. | |||||
* @return A pointer to the array of entities. | |||||
*/ | |||||
template<typename Comp> | |||||
const entity_type * data() const ENTT_NOEXCEPT { | |||||
return std::get<pool_type<Comp> *>(pools)->data(); | |||||
} | |||||
/** | |||||
* @brief Returns an iterator to the first entity that has the given | |||||
* components. | |||||
* | |||||
* The returned iterator points to the first entity that has the given | |||||
* components. If the view is empty, the returned iterator will be equal to | |||||
* `end()`. | |||||
* | |||||
* @note | |||||
* Input iterators stay true to the order imposed to the underlying data | |||||
* structures. | |||||
* | |||||
* @return An iterator to the first entity that has the given components. | |||||
*/ | |||||
iterator_type begin() const ENTT_NOEXCEPT { | |||||
const auto *view = candidate(); | |||||
return iterator_type{unchecked(view), view->begin(), view->end()}; | |||||
} | |||||
/** | |||||
* @brief Returns an iterator that is past the last entity that has the | |||||
* given components. | |||||
* | |||||
* The returned iterator points to the entity following the last entity that | |||||
* has the given components. Attempting to dereference the returned iterator | |||||
* results in undefined behavior. | |||||
* | |||||
* @note | |||||
* Input iterators stay true to the order imposed to the underlying data | |||||
* structures. | |||||
* | |||||
* @return An iterator to the entity following the last entity that has the | |||||
* given components. | |||||
*/ | |||||
iterator_type end() const ENTT_NOEXCEPT { | |||||
const auto *view = candidate(); | |||||
return iterator_type{unchecked(view), view->end(), view->end()}; | |||||
} | |||||
/** | |||||
* @brief Finds an entity. | |||||
* @param entt A valid entity identifier. | |||||
* @return An iterator to the given entity if it's found, past the end | |||||
* iterator otherwise. | |||||
*/ | |||||
iterator_type find(const entity_type entt) const ENTT_NOEXCEPT { | |||||
const auto *view = candidate(); | |||||
iterator_type it{unchecked(view), view->find(entt), view->end()}; | |||||
return (it != end() && *it == entt) ? it : end(); | |||||
} | |||||
/** | |||||
* @brief Checks if a view contains an entity. | |||||
* @param entt A valid entity identifier. | |||||
* @return True if the view contains the given entity, false otherwise. | |||||
*/ | |||||
bool contains(const entity_type entt) const ENTT_NOEXCEPT { | |||||
return find(entt) != end(); | |||||
} | |||||
/** | |||||
* @brief Returns the components assigned to the given entity. | |||||
* | |||||
* Prefer this function instead of `registry::get` during iterations. It has | |||||
* far better performance than its companion function. | |||||
* | |||||
* @warning | |||||
* Attempting to use an invalid component type results in a compilation | |||||
* error. Attempting to use an entity that doesn't belong to the view | |||||
* results in undefined behavior.<br/> | |||||
* An assertion will abort the execution at runtime in debug mode if the | |||||
* view doesn't contain the given entity. | |||||
* | |||||
* @tparam Comp Types of components to get. | |||||
* @param entt A valid entity identifier. | |||||
* @return The components assigned to the entity. | |||||
*/ | |||||
template<typename... Comp> | |||||
decltype(auto) get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT { | |||||
ENTT_ASSERT(contains(entt)); | |||||
if constexpr(sizeof...(Comp) == 1) { | |||||
return (std::get<pool_type<Comp> *>(pools)->get(entt), ...); | |||||
} else { | |||||
return std::tuple<decltype(get<Comp>(entt))...>{get<Comp>(entt)...}; | |||||
} | |||||
} | |||||
/** | |||||
* @brief Iterates entities and components and applies the given function | |||||
* object to them. | |||||
* | |||||
* The function object is invoked for each entity. It is provided with the | |||||
* entity itself and a set of references to all its components. The | |||||
* _constness_ of the components is as requested.<br/> | |||||
* The signature of the function must be equivalent to one of the following | |||||
* forms: | |||||
* | |||||
* @code{.cpp} | |||||
* void(const entity_type, Component &...); | |||||
* void(Component &...); | |||||
* @endcode | |||||
* | |||||
* @note | |||||
* Empty types aren't explicitly instantiated. Therefore, temporary objects | |||||
* are returned during iterations. They can be caught only by copy or with | |||||
* const references. | |||||
* | |||||
* @tparam Func Type of the function object to invoke. | |||||
* @param func A valid function object. | |||||
*/ | |||||
template<typename Func> | |||||
inline void each(Func func) const { | |||||
const auto *view = candidate(); | |||||
((std::get<pool_type<Component> *>(pools) == view ? each<Component>(std::move(func)) : void()), ...); | |||||
} | |||||
/** | |||||
* @brief Iterates entities and components and applies the given function | |||||
* object to them. | |||||
* | |||||
* The function object is invoked for each entity. It is provided with the | |||||
* entity itself and a set of references to all its components. The | |||||
* _constness_ of the components is as requested.<br/> | |||||
* The signature of the function must be equivalent to one of the following | |||||
* forms: | |||||
* | |||||
* @code{.cpp} | |||||
* void(const entity_type, Component &...); | |||||
* void(Component &...); | |||||
* @endcode | |||||
* | |||||
* The pool of the suggested component is used to lead the iterations. The | |||||
* returned entities will therefore respect the order of the pool associated | |||||
* with that type.<br/> | |||||
* It is no longer guaranteed that the performance is the best possible, but | |||||
* there will be greater control over the order of iteration. | |||||
* | |||||
* @note | |||||
* Empty types aren't explicitly instantiated. Therefore, temporary objects | |||||
* are returned during iterations. They can be caught only by copy or with | |||||
* const references. | |||||
* | |||||
* @tparam Comp Type of component to use to enforce the iteration order. | |||||
* @tparam Func Type of the function object to invoke. | |||||
* @param func A valid function object. | |||||
*/ | |||||
template<typename Comp, typename Func> | |||||
inline void each(Func func) const { | |||||
using other_type = type_list_cat_t<std::conditional_t<std::is_same_v<Comp, Component>, type_list<>, type_list<Component>>...>; | |||||
traverse<Comp>(std::move(func), other_type{}, type_list<Component...>{}); | |||||
} | |||||
/** | |||||
* @brief Iterates entities and components and applies the given function | |||||
* object to them. | |||||
* | |||||
* The function object is invoked for each entity. It is provided with the | |||||
* entity itself and a set of references to non-empty components. The | |||||
* _constness_ of the components is as requested.<br/> | |||||
* The signature of the function must be equivalent to one of the following | |||||
* forms: | |||||
* | |||||
* @code{.cpp} | |||||
* void(const entity_type, Type &...); | |||||
* void(Type &...); | |||||
* @endcode | |||||
* | |||||
* @sa each | |||||
* | |||||
* @tparam Func Type of the function object to invoke. | |||||
* @param func A valid function object. | |||||
*/ | |||||
template<typename Func> | |||||
inline void less(Func func) const { | |||||
const auto *view = candidate(); | |||||
((std::get<pool_type<Component> *>(pools) == view ? less<Component>(std::move(func)) : void()), ...); | |||||
} | |||||
/** | |||||
* @brief Iterates entities and components and applies the given function | |||||
* object to them. | |||||
* | |||||
* The function object is invoked for each entity. It is provided with the | |||||
* entity itself and a set of references to non-empty components. The | |||||
* _constness_ of the components is as requested.<br/> | |||||
* The signature of the function must be equivalent to one of the following | |||||
* forms: | |||||
* | |||||
* @code{.cpp} | |||||
* void(const entity_type, Type &...); | |||||
* void(Type &...); | |||||
* @endcode | |||||
* | |||||
* The pool of the suggested component is used to lead the iterations. The | |||||
* returned entities will therefore respect the order of the pool associated | |||||
* with that type.<br/> | |||||
* It is no longer guaranteed that the performance is the best possible, but | |||||
* there will be greater control over the order of iteration. | |||||
* | |||||
* @sa each | |||||
* | |||||
* @tparam Comp Type of component to use to enforce the iteration order. | |||||
* @tparam Func Type of the function object to invoke. | |||||
* @param func A valid function object. | |||||
*/ | |||||
template<typename Comp, typename Func> | |||||
inline void less(Func func) const { | |||||
using other_type = type_list_cat_t<std::conditional_t<std::is_same_v<Comp, Component>, type_list<>, type_list<Component>>...>; | |||||
using non_empty_type = type_list_cat_t<std::conditional_t<std::is_empty_v<Component>, type_list<>, type_list<Component>>...>; | |||||
traverse<Comp>(std::move(func), other_type{}, non_empty_type{}); | |||||
} | |||||
private: | |||||
const std::tuple<pool_type<Component> *...> pools; | |||||
}; | |||||
/** | |||||
* @brief Single component view specialization. | |||||
* | |||||
* Single component views are specialized in order to get a boost in terms of | |||||
* performance. This kind of views can access the underlying data structure | |||||
* directly and avoid superfluous checks.<br/> | |||||
* Order of elements during iterations are highly dependent on the order of the | |||||
* underlying data structure. See sparse_set and its specializations for more | |||||
* details. | |||||
* | |||||
* @b Important | |||||
* | |||||
* Iterators aren't invalidated if: | |||||
* | |||||
* * New instances of the given component are created and assigned to entities. | |||||
* * The entity currently pointed is modified (as an example, the given | |||||
* component is removed from the entity to which the iterator points). | |||||
* | |||||
* In all the other cases, modifying the pool of the given component in any way | |||||
* invalidates all the iterators and using them results in undefined behavior. | |||||
* | |||||
* @note | |||||
* Views share a reference to the underlying data structure of the registry that | |||||
* generated them. Therefore any change to the entities and to the components | |||||
* made by means of the registry are immediately reflected by views. | |||||
* | |||||
* @warning | |||||
* Lifetime of a view must overcome the one of the registry that generated it. | |||||
* In any other case, attempting to use a view results in undefined behavior. | |||||
* | |||||
* @tparam Entity A valid entity type (see entt_traits for more details). | |||||
* @tparam Component Type of component iterated by the view. | |||||
*/ | |||||
template<typename Entity, typename Component> | |||||
class basic_view<Entity, Component> { | |||||
/*! @brief A registry is allowed to create views. */ | |||||
friend class basic_registry<Entity>; | |||||
using pool_type = std::conditional_t<std::is_const_v<Component>, const storage<Entity, std::remove_const_t<Component>>, storage<Entity, Component>>; | |||||
basic_view(pool_type *ref) ENTT_NOEXCEPT | |||||
: pool{ref} | |||||
{} | |||||
public: | |||||
/*! @brief Type of component iterated by the view. */ | |||||
using raw_type = Component; | |||||
/*! @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 Input iterator type. */ | |||||
using iterator_type = typename sparse_set<Entity>::iterator_type; | |||||
/** | |||||
* @brief Returns the number of entities that have the given component. | |||||
* @return Number of entities that have the given component. | |||||
*/ | |||||
size_type size() const ENTT_NOEXCEPT { | |||||
return pool->size(); | |||||
} | |||||
/** | |||||
* @brief Checks whether the view is empty. | |||||
* @return True if the view is empty, false otherwise. | |||||
*/ | |||||
bool empty() const ENTT_NOEXCEPT { | |||||
return pool->empty(); | |||||
} | |||||
/** | |||||
* @brief Direct access to the list of components. | |||||
* | |||||
* The returned pointer is such that range `[raw(), raw() + size()]` is | |||||
* always a valid range, even if the container is empty. | |||||
* | |||||
* @note | |||||
* There are no guarantees on the order of the components. Use `begin` and | |||||
* `end` if you want to iterate the view in the expected order. | |||||
* | |||||
* @return A pointer to the array of components. | |||||
*/ | |||||
raw_type * raw() const ENTT_NOEXCEPT { | |||||
return pool->raw(); | |||||
} | |||||
/** | |||||
* @brief Direct access to the list of entities. | |||||
* | |||||
* The returned pointer is such that range `[data(), data() + size()]` is | |||||
* always a valid range, even if the container is empty. | |||||
* | |||||
* @note | |||||
* There are no guarantees on the order of the entities. Use `begin` and | |||||
* `end` if you want to iterate the view in the expected order. | |||||
* | |||||
* @return A pointer to the array of entities. | |||||
*/ | |||||
const entity_type * data() const ENTT_NOEXCEPT { | |||||
return pool->data(); | |||||
} | |||||
/** | |||||
* @brief Returns an iterator to the first entity that has the given | |||||
* component. | |||||
* | |||||
* The returned iterator points to the first entity that has the given | |||||
* component. 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 component. | |||||
*/ | |||||
iterator_type begin() const ENTT_NOEXCEPT { | |||||
return pool->sparse_set<Entity>::begin(); | |||||
} | |||||
/** | |||||
* @brief Returns an iterator that is past the last entity that has the | |||||
* given component. | |||||
* | |||||
* The returned iterator points to the entity following the last entity that | |||||
* has the given component. 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 component. | |||||
*/ | |||||
iterator_type end() const ENTT_NOEXCEPT { | |||||
return pool->sparse_set<Entity>::end(); | |||||
} | |||||
/** | |||||
* @brief Finds an entity. | |||||
* @param entt A valid entity identifier. | |||||
* @return An iterator to the given entity if it's found, past the end | |||||
* iterator otherwise. | |||||
*/ | |||||
iterator_type find(const entity_type entt) const ENTT_NOEXCEPT { | |||||
const auto it = pool->find(entt); | |||||
return it != end() && *it == entt ? it : end(); | |||||
} | |||||
/** | |||||
* @brief Returns the identifier that occupies the given position. | |||||
* @param pos Position of the element to return. | |||||
* @return The identifier that occupies the given position. | |||||
*/ | |||||
entity_type operator[](const size_type pos) const ENTT_NOEXCEPT { | |||||
return begin()[pos]; | |||||
} | |||||
/** | |||||
* @brief Checks if a view contains an entity. | |||||
* @param entt A valid entity identifier. | |||||
* @return True if the view contains the given entity, false otherwise. | |||||
*/ | |||||
bool contains(const entity_type entt) const ENTT_NOEXCEPT { | |||||
return find(entt) != end(); | |||||
} | |||||
/** | |||||
* @brief Returns the component assigned to the given entity. | |||||
* | |||||
* Prefer this function instead of `registry::get` during iterations. It has | |||||
* far better performance than its companion function. | |||||
* | |||||
* @warning | |||||
* Attempting to use an entity that doesn't belong to the view results in | |||||
* undefined behavior.<br/> | |||||
* An assertion will abort the execution at runtime in debug mode if the | |||||
* view doesn't contain the given entity. | |||||
* | |||||
* @param entt A valid entity identifier. | |||||
* @return The component assigned to the entity. | |||||
*/ | |||||
decltype(auto) get(const entity_type entt) const ENTT_NOEXCEPT { | |||||
ENTT_ASSERT(contains(entt)); | |||||
return pool->get(entt); | |||||
} | |||||
/** | |||||
* @brief Iterates entities and components and applies the given function | |||||
* object to them. | |||||
* | |||||
* The function object is invoked for each entity. It is provided with the | |||||
* entity itself and a reference to its component. The _constness_ of the | |||||
* component is as requested.<br/> | |||||
* The signature of the function must be equivalent to one of the following | |||||
* forms: | |||||
* | |||||
* @code{.cpp} | |||||
* void(const entity_type, Component &); | |||||
* void(Component &); | |||||
* @endcode | |||||
* | |||||
* @note | |||||
* Empty types aren't explicitly instantiated. Therefore, temporary objects | |||||
* are returned during iterations. They can be caught only by copy or with | |||||
* const references. | |||||
* | |||||
* @tparam Func Type of the function object to invoke. | |||||
* @param func A valid function object. | |||||
*/ | |||||
template<typename Func> | |||||
inline void each(Func func) const { | |||||
if constexpr(std::is_invocable_v<Func, decltype(get({}))>) { | |||||
std::for_each(pool->begin(), pool->end(), std::move(func)); | |||||
} else { | |||||
std::for_each(pool->sparse_set<Entity>::begin(), pool->sparse_set<Entity>::end(), [&func, raw = pool->begin()](const auto entt) mutable { | |||||
func(entt, *(raw++)); | |||||
}); | |||||
} | |||||
} | |||||
/** | |||||
* @brief Iterates entities and components and applies the given function | |||||
* object to them. | |||||
* | |||||
* The function object is invoked for each entity. It is provided with the | |||||
* entity itself and a reference to its component if it's a non-empty one. | |||||
* The _constness_ of the component is as requested.<br/> | |||||
* The signature of the function must be equivalent to one of the following | |||||
* forms in case the component isn't an empty one: | |||||
* | |||||
* @code{.cpp} | |||||
* void(const entity_type, Component &); | |||||
* void(Component &); | |||||
* @endcode | |||||
* | |||||
* In case the component is an empty one instead, the following forms are | |||||
* accepted: | |||||
* | |||||
* @code{.cpp} | |||||
* void(const entity_type); | |||||
* void(); | |||||
* @endcode | |||||
* | |||||
* @sa each | |||||
* | |||||
* @tparam Func Type of the function object to invoke. | |||||
* @param func A valid function object. | |||||
*/ | |||||
template<typename Func> | |||||
inline void less(Func func) const { | |||||
if constexpr(std::is_empty_v<Component>) { | |||||
if constexpr(std::is_invocable_v<Func>) { | |||||
for(auto pos = pool->size(); pos; --pos) { | |||||
func(); | |||||
} | |||||
} else { | |||||
std::for_each(pool->sparse_set<Entity>::begin(), pool->sparse_set<Entity>::end(), std::move(func)); | |||||
} | |||||
} else { | |||||
each(std::move(func)); | |||||
} | |||||
} | |||||
private: | |||||
pool_type *pool; | |||||
}; | |||||
} | |||||
#endif // ENTT_ENTITY_VIEW_HPP |
@ -0,0 +1,30 @@ | |||||
#include "core/algorithm.hpp" | |||||
#include "core/family.hpp" | |||||
#include "core/hashed_string.hpp" | |||||
#include "core/ident.hpp" | |||||
#include "core/monostate.hpp" | |||||
#include "core/type_traits.hpp" | |||||
#include "core/utility.hpp" | |||||
#include "entity/actor.hpp" | |||||
#include "entity/entity.hpp" | |||||
#include "entity/group.hpp" | |||||
#include "entity/helper.hpp" | |||||
#include "entity/prototype.hpp" | |||||
#include "entity/registry.hpp" | |||||
#include "entity/runtime_view.hpp" | |||||
#include "entity/snapshot.hpp" | |||||
#include "entity/sparse_set.hpp" | |||||
#include "entity/storage.hpp" | |||||
#include "entity/view.hpp" | |||||
#include "locator/locator.hpp" | |||||
#include "meta/factory.hpp" | |||||
#include "meta/meta.hpp" | |||||
#include "process/process.hpp" | |||||
#include "process/scheduler.hpp" | |||||
#include "resource/cache.hpp" | |||||
#include "resource/handle.hpp" | |||||
#include "resource/loader.hpp" | |||||
#include "signal/delegate.hpp" | |||||
#include "signal/dispatcher.hpp" | |||||
#include "signal/emitter.hpp" | |||||
#include "signal/sigh.hpp" |
@ -0,0 +1,3 @@ | |||||
#include "entity/fwd.hpp" | |||||
#include "resource/fwd.hpp" | |||||
#include "signal/fwd.hpp" |
@ -0,0 +1,111 @@ | |||||
#ifndef ENTT_LOCATOR_LOCATOR_HPP | |||||
#define ENTT_LOCATOR_LOCATOR_HPP | |||||
#include <memory> | |||||
#include <utility> | |||||
#include "../config/config.h" | |||||
namespace entt { | |||||
/** | |||||
* @brief Service locator, nothing more. | |||||
* | |||||
* A service locator can be used to do what it promises: locate services.<br/> | |||||
* Usually service locators are tightly bound to the services they expose and | |||||
* thus it's hard to define a general purpose class to do that. This template | |||||
* based implementation tries to fill the gap and to get rid of the burden of | |||||
* defining a different specific locator for each application. | |||||
* | |||||
* @tparam Service Type of service managed by the locator. | |||||
*/ | |||||
template<typename Service> | |||||
struct service_locator { | |||||
/*! @brief Type of service offered. */ | |||||
using service_type = Service; | |||||
/*! @brief Default constructor, deleted on purpose. */ | |||||
service_locator() = delete; | |||||
/*! @brief Default destructor, deleted on purpose. */ | |||||
~service_locator() = delete; | |||||
/** | |||||
* @brief Tests if a valid service implementation is set. | |||||
* @return True if the service is set, false otherwise. | |||||
*/ | |||||
inline static bool empty() ENTT_NOEXCEPT { | |||||
return !static_cast<bool>(service); | |||||
} | |||||
/** | |||||
* @brief Returns a weak pointer to a service implementation, if any. | |||||
* | |||||
* Clients of a service shouldn't retain references to it. The recommended | |||||
* way is to retrieve the service implementation currently set each and | |||||
* every time the need of using it arises. Otherwise users can incur in | |||||
* unexpected behaviors. | |||||
* | |||||
* @return A reference to the service implementation currently set, if any. | |||||
*/ | |||||
inline static std::weak_ptr<Service> get() ENTT_NOEXCEPT { | |||||
return service; | |||||
} | |||||
/** | |||||
* @brief Returns a weak reference to a service implementation, if any. | |||||
* | |||||
* Clients of a service shouldn't retain references to it. The recommended | |||||
* way is to retrieve the service implementation currently set each and | |||||
* every time the need of using it arises. Otherwise users can incur in | |||||
* unexpected behaviors. | |||||
* | |||||
* @warning | |||||
* In case no service implementation has been set, a call to this function | |||||
* results in undefined behavior. | |||||
* | |||||
* @return A reference to the service implementation currently set, if any. | |||||
*/ | |||||
inline static Service & ref() ENTT_NOEXCEPT { | |||||
return *service; | |||||
} | |||||
/** | |||||
* @brief Sets or replaces a service. | |||||
* @tparam Impl Type of the new service to use. | |||||
* @tparam Args Types of arguments to use to construct the service. | |||||
* @param args Parameters to use to construct the service. | |||||
*/ | |||||
template<typename Impl = Service, typename... Args> | |||||
inline static void set(Args &&... args) { | |||||
service = std::make_shared<Impl>(std::forward<Args>(args)...); | |||||
} | |||||
/** | |||||
* @brief Sets or replaces a service. | |||||
* @param ptr Service to use to replace the current one. | |||||
*/ | |||||
inline static void set(std::shared_ptr<Service> ptr) { | |||||
ENTT_ASSERT(static_cast<bool>(ptr)); | |||||
service = std::move(ptr); | |||||
} | |||||
/** | |||||
* @brief Resets a service. | |||||
* | |||||
* The service is no longer valid after a reset. | |||||
*/ | |||||
inline static void reset() { | |||||
service.reset(); | |||||
} | |||||
private: | |||||
inline static std::shared_ptr<Service> service = nullptr; | |||||
}; | |||||
} | |||||
#endif // ENTT_LOCATOR_LOCATOR_HPP |
@ -0,0 +1,687 @@ | |||||
#ifndef ENTT_META_FACTORY_HPP | |||||
#define ENTT_META_FACTORY_HPP | |||||
#include <utility> | |||||
#include <algorithm> | |||||
#include <type_traits> | |||||
#include "../config/config.h" | |||||
#include "../core/hashed_string.hpp" | |||||
#include "meta.hpp" | |||||
namespace entt { | |||||
template<typename> | |||||
class meta_factory; | |||||
template<typename Type, typename... Property> | |||||
meta_factory<Type> reflect(const char *str, Property &&... property) ENTT_NOEXCEPT; | |||||
template<typename Type> | |||||
bool unregister() ENTT_NOEXCEPT; | |||||
/** | |||||
* @brief A meta factory to be used for reflection purposes. | |||||
* | |||||
* A meta factory is an utility class used to reflect types, data and functions | |||||
* of all sorts. This class ensures that the underlying web of types is built | |||||
* correctly and performs some checks in debug mode to ensure that there are no | |||||
* subtle errors at runtime. | |||||
* | |||||
* @tparam Type Reflected type for which the factory was created. | |||||
*/ | |||||
template<typename Type> | |||||
class meta_factory { | |||||
static_assert(std::is_object_v<Type> && !(std::is_const_v<Type> || std::is_volatile_v<Type>)); | |||||
template<typename Node> | |||||
inline bool duplicate(const hashed_string &name, const Node *node) ENTT_NOEXCEPT { | |||||
return node ? node->name == name || duplicate(name, node->next) : false; | |||||
} | |||||
inline bool duplicate(const meta_any &key, const internal::meta_prop_node *node) ENTT_NOEXCEPT { | |||||
return node ? node->key() == key || duplicate(key, node->next) : false; | |||||
} | |||||
template<typename> | |||||
internal::meta_prop_node * properties() { | |||||
return nullptr; | |||||
} | |||||
template<typename Owner, typename Property, typename... Other> | |||||
internal::meta_prop_node * properties(Property &&property, Other &&... other) { | |||||
static std::remove_cv_t<std::remove_reference_t<Property>> prop{}; | |||||
static internal::meta_prop_node node{ | |||||
nullptr, | |||||
[]() -> meta_any { | |||||
return std::get<0>(prop); | |||||
}, | |||||
[]() -> meta_any { | |||||
return std::get<1>(prop); | |||||
}, | |||||
[]() -> meta_prop { | |||||
return &node; | |||||
} | |||||
}; | |||||
prop = std::forward<Property>(property); | |||||
node.next = properties<Owner>(std::forward<Other>(other)...); | |||||
ENTT_ASSERT(!duplicate(meta_any{std::get<0>(prop)}, node.next)); | |||||
return &node; | |||||
} | |||||
template<typename... Property> | |||||
meta_factory type(hashed_string name, Property &&... property) ENTT_NOEXCEPT { | |||||
static internal::meta_type_node node{ | |||||
{}, | |||||
nullptr, | |||||
nullptr, | |||||
std::is_void_v<Type>, | |||||
std::is_integral_v<Type>, | |||||
std::is_floating_point_v<Type>, | |||||
std::is_array_v<Type>, | |||||
std::is_enum_v<Type>, | |||||
std::is_union_v<Type>, | |||||
std::is_class_v<Type>, | |||||
std::is_pointer_v<Type>, | |||||
std::is_function_v<Type>, | |||||
std::is_member_object_pointer_v<Type>, | |||||
std::is_member_function_pointer_v<Type>, | |||||
std::extent_v<Type>, | |||||
[]() -> meta_type { | |||||
return internal::meta_info<std::remove_pointer_t<Type>>::resolve(); | |||||
}, | |||||
&internal::destroy<Type>, | |||||
[]() -> meta_type { | |||||
return &node; | |||||
} | |||||
}; | |||||
node.name = name; | |||||
node.next = internal::meta_info<>::type; | |||||
node.prop = properties<Type>(std::forward<Property>(property)...); | |||||
ENTT_ASSERT(!duplicate(name, node.next)); | |||||
ENTT_ASSERT(!internal::meta_info<Type>::type); | |||||
internal::meta_info<Type>::type = &node; | |||||
internal::meta_info<>::type = &node; | |||||
return *this; | |||||
} | |||||
void unregister_prop(internal::meta_prop_node **prop) { | |||||
while(*prop) { | |||||
auto *node = *prop; | |||||
*prop = node->next; | |||||
node->next = nullptr; | |||||
} | |||||
} | |||||
void unregister_dtor() { | |||||
if(auto node = internal::meta_info<Type>::type->dtor; node) { | |||||
internal::meta_info<Type>::type->dtor = nullptr; | |||||
*node->underlying = nullptr; | |||||
} | |||||
} | |||||
template<auto Member> | |||||
auto unregister_all(int) | |||||
-> decltype((internal::meta_info<Type>::type->*Member)->prop, void()) { | |||||
while(internal::meta_info<Type>::type->*Member) { | |||||
auto node = internal::meta_info<Type>::type->*Member; | |||||
internal::meta_info<Type>::type->*Member = node->next; | |||||
unregister_prop(&node->prop); | |||||
node->next = nullptr; | |||||
*node->underlying = nullptr; | |||||
} | |||||
} | |||||
template<auto Member> | |||||
void unregister_all(char) { | |||||
while(internal::meta_info<Type>::type->*Member) { | |||||
auto node = internal::meta_info<Type>::type->*Member; | |||||
internal::meta_info<Type>::type->*Member = node->next; | |||||
node->next = nullptr; | |||||
*node->underlying = nullptr; | |||||
} | |||||
} | |||||
bool unregister() ENTT_NOEXCEPT { | |||||
const auto registered = internal::meta_info<Type>::type; | |||||
if(registered) { | |||||
if(auto *curr = internal::meta_info<>::type; curr == internal::meta_info<Type>::type) { | |||||
internal::meta_info<>::type = internal::meta_info<Type>::type->next; | |||||
} else { | |||||
while(curr && curr->next != internal::meta_info<Type>::type) { | |||||
curr = curr->next; | |||||
} | |||||
if(curr) { | |||||
curr->next = internal::meta_info<Type>::type->next; | |||||
} | |||||
} | |||||
unregister_prop(&internal::meta_info<Type>::type->prop); | |||||
unregister_all<&internal::meta_type_node::base>(0); | |||||
unregister_all<&internal::meta_type_node::conv>(0); | |||||
unregister_all<&internal::meta_type_node::ctor>(0); | |||||
unregister_all<&internal::meta_type_node::data>(0); | |||||
unregister_all<&internal::meta_type_node::func>(0); | |||||
unregister_dtor(); | |||||
internal::meta_info<Type>::type->name = {}; | |||||
internal::meta_info<Type>::type->next = nullptr; | |||||
internal::meta_info<Type>::type = nullptr; | |||||
} | |||||
return registered; | |||||
} | |||||
meta_factory() ENTT_NOEXCEPT = default; | |||||
public: | |||||
template<typename Other, typename... Property> | |||||
friend meta_factory<Other> reflect(const char *str, Property &&... property) ENTT_NOEXCEPT; | |||||
template<typename Other> | |||||
friend bool unregister() ENTT_NOEXCEPT; | |||||
/** | |||||
* @brief Assigns a meta base to a meta type. | |||||
* | |||||
* A reflected base class must be a real base class of the reflected type. | |||||
* | |||||
* @tparam Base Type of the base class to assign to the meta type. | |||||
* @return A meta factory for the parent type. | |||||
*/ | |||||
template<typename Base> | |||||
meta_factory base() ENTT_NOEXCEPT { | |||||
static_assert(std::is_base_of_v<Base, Type>); | |||||
auto * const type = internal::meta_info<Type>::resolve(); | |||||
static internal::meta_base_node node{ | |||||
&internal::meta_info<Type>::template base<Base>, | |||||
type, | |||||
nullptr, | |||||
&internal::meta_info<Base>::resolve, | |||||
[](void *instance) -> void * { | |||||
return static_cast<Base *>(static_cast<Type *>(instance)); | |||||
}, | |||||
[]() -> meta_base { | |||||
return &node; | |||||
} | |||||
}; | |||||
node.next = type->base; | |||||
ENTT_ASSERT((!internal::meta_info<Type>::template base<Base>)); | |||||
internal::meta_info<Type>::template base<Base> = &node; | |||||
type->base = &node; | |||||
return *this; | |||||
} | |||||
/** | |||||
* @brief Assigns a meta conversion function to a meta type. | |||||
* | |||||
* The given type must be such that an instance of the reflected type can be | |||||
* converted to it. | |||||
* | |||||
* @tparam To Type of the conversion function to assign to the meta type. | |||||
* @return A meta factory for the parent type. | |||||
*/ | |||||
template<typename To> | |||||
meta_factory conv() ENTT_NOEXCEPT { | |||||
static_assert(std::is_convertible_v<Type, To>); | |||||
auto * const type = internal::meta_info<Type>::resolve(); | |||||
static internal::meta_conv_node node{ | |||||
&internal::meta_info<Type>::template conv<To>, | |||||
type, | |||||
nullptr, | |||||
&internal::meta_info<To>::resolve, | |||||
[](void *instance) -> meta_any { | |||||
return static_cast<To>(*static_cast<Type *>(instance)); | |||||
}, | |||||
[]() -> meta_conv { | |||||
return &node; | |||||
} | |||||
}; | |||||
node.next = type->conv; | |||||
ENTT_ASSERT((!internal::meta_info<Type>::template conv<To>)); | |||||
internal::meta_info<Type>::template conv<To> = &node; | |||||
type->conv = &node; | |||||
return *this; | |||||
} | |||||
/** | |||||
* @brief Assigns a meta constructor to a meta type. | |||||
* | |||||
* Free functions can be assigned to meta types in the role of | |||||
* constructors. All that is required is that they return an instance of the | |||||
* underlying type.<br/> | |||||
* From a client's point of view, nothing changes if a constructor of a meta | |||||
* type is a built-in one or a free function. | |||||
* | |||||
* @tparam Func The actual function to use as a constructor. | |||||
* @tparam Property Types of properties to assign to the meta data. | |||||
* @param property Properties to assign to the meta data. | |||||
* @return A meta factory for the parent type. | |||||
*/ | |||||
template<auto Func, typename... Property> | |||||
meta_factory ctor(Property &&... property) ENTT_NOEXCEPT { | |||||
using helper_type = internal::meta_function_helper<std::integral_constant<decltype(Func), Func>>; | |||||
static_assert(std::is_same_v<typename helper_type::return_type, Type>); | |||||
auto * const type = internal::meta_info<Type>::resolve(); | |||||
static internal::meta_ctor_node node{ | |||||
&internal::meta_info<Type>::template ctor<typename helper_type::args_type>, | |||||
type, | |||||
nullptr, | |||||
nullptr, | |||||
helper_type::size, | |||||
&helper_type::arg, | |||||
[](meta_any * const any) { | |||||
return internal::invoke<Type, Func>(nullptr, any, std::make_index_sequence<helper_type::size>{}); | |||||
}, | |||||
[]() -> meta_ctor { | |||||
return &node; | |||||
} | |||||
}; | |||||
node.next = type->ctor; | |||||
node.prop = properties<typename helper_type::args_type>(std::forward<Property>(property)...); | |||||
ENTT_ASSERT((!internal::meta_info<Type>::template ctor<typename helper_type::args_type>)); | |||||
internal::meta_info<Type>::template ctor<typename helper_type::args_type> = &node; | |||||
type->ctor = &node; | |||||
return *this; | |||||
} | |||||
/** | |||||
* @brief Assigns a meta constructor to a meta type. | |||||
* | |||||
* A meta constructor is uniquely identified by the types of its arguments | |||||
* and is such that there exists an actual constructor of the underlying | |||||
* type that can be invoked with parameters whose types are those given. | |||||
* | |||||
* @tparam Args Types of arguments to use to construct an instance. | |||||
* @tparam Property Types of properties to assign to the meta data. | |||||
* @param property Properties to assign to the meta data. | |||||
* @return A meta factory for the parent type. | |||||
*/ | |||||
template<typename... Args, typename... Property> | |||||
meta_factory ctor(Property &&... property) ENTT_NOEXCEPT { | |||||
using helper_type = internal::meta_function_helper<Type(Args...)>; | |||||
auto * const type = internal::meta_info<Type>::resolve(); | |||||
static internal::meta_ctor_node node{ | |||||
&internal::meta_info<Type>::template ctor<typename helper_type::args_type>, | |||||
type, | |||||
nullptr, | |||||
nullptr, | |||||
helper_type::size, | |||||
&helper_type::arg, | |||||
[](meta_any * const any) { | |||||
return internal::construct<Type, Args...>(any, std::make_index_sequence<helper_type::size>{}); | |||||
}, | |||||
[]() -> meta_ctor { | |||||
return &node; | |||||
} | |||||
}; | |||||
node.next = type->ctor; | |||||
node.prop = properties<typename helper_type::args_type>(std::forward<Property>(property)...); | |||||
ENTT_ASSERT((!internal::meta_info<Type>::template ctor<typename helper_type::args_type>)); | |||||
internal::meta_info<Type>::template ctor<typename helper_type::args_type> = &node; | |||||
type->ctor = &node; | |||||
return *this; | |||||
} | |||||
/** | |||||
* @brief Assigns a meta destructor to a meta type. | |||||
* | |||||
* Free functions can be assigned to meta types in the role of destructors. | |||||
* The signature of the function should identical to the following: | |||||
* | |||||
* @code{.cpp} | |||||
* void(Type *); | |||||
* @endcode | |||||
* | |||||
* From a client's point of view, nothing changes if the destructor of a | |||||
* meta type is the default one or a custom one. | |||||
* | |||||
* @tparam Func The actual function to use as a destructor. | |||||
* @return A meta factory for the parent type. | |||||
*/ | |||||
template<auto *Func> | |||||
meta_factory dtor() ENTT_NOEXCEPT { | |||||
static_assert(std::is_invocable_v<decltype(Func), Type *>); | |||||
auto * const type = internal::meta_info<Type>::resolve(); | |||||
static internal::meta_dtor_node node{ | |||||
&internal::meta_info<Type>::template dtor<Func>, | |||||
type, | |||||
[](meta_handle handle) { | |||||
return handle.type() == internal::meta_info<Type>::resolve()->meta() | |||||
? ((*Func)(static_cast<Type *>(handle.data())), true) | |||||
: false; | |||||
}, | |||||
[]() -> meta_dtor { | |||||
return &node; | |||||
} | |||||
}; | |||||
ENTT_ASSERT(!internal::meta_info<Type>::type->dtor); | |||||
ENTT_ASSERT((!internal::meta_info<Type>::template dtor<Func>)); | |||||
internal::meta_info<Type>::template dtor<Func> = &node; | |||||
internal::meta_info<Type>::type->dtor = &node; | |||||
return *this; | |||||
} | |||||
/** | |||||
* @brief Assigns a meta data to a meta type. | |||||
* | |||||
* Both data members and static and global variables, as well as constants | |||||
* of any kind, can be assigned to a meta type.<br/> | |||||
* From a client's point of view, all the variables associated with the | |||||
* reflected object will appear as if they were part of the type itself. | |||||
* | |||||
* @tparam Data The actual variable to attach to the meta type. | |||||
* @tparam Property Types of properties to assign to the meta data. | |||||
* @param str The name to assign to the meta data. | |||||
* @param property Properties to assign to the meta data. | |||||
* @return A meta factory for the parent type. | |||||
*/ | |||||
template<auto Data, typename... Property> | |||||
meta_factory data(const char *str, Property &&... property) ENTT_NOEXCEPT { | |||||
auto * const type = internal::meta_info<Type>::resolve(); | |||||
internal::meta_data_node *curr = nullptr; | |||||
if constexpr(std::is_same_v<Type, decltype(Data)>) { | |||||
static internal::meta_data_node node{ | |||||
&internal::meta_info<Type>::template data<Data>, | |||||
{}, | |||||
type, | |||||
nullptr, | |||||
nullptr, | |||||
true, | |||||
true, | |||||
&internal::meta_info<Type>::resolve, | |||||
[](meta_handle, meta_any, meta_any) { return false; }, | |||||
[](meta_handle, meta_any) -> meta_any { return Data; }, | |||||
[]() -> meta_data { | |||||
return &node; | |||||
} | |||||
}; | |||||
node.prop = properties<std::integral_constant<Type, Data>>(std::forward<Property>(property)...); | |||||
curr = &node; | |||||
} else if constexpr(std::is_member_object_pointer_v<decltype(Data)>) { | |||||
using data_type = std::remove_reference_t<decltype(std::declval<Type>().*Data)>; | |||||
static internal::meta_data_node node{ | |||||
&internal::meta_info<Type>::template data<Data>, | |||||
{}, | |||||
type, | |||||
nullptr, | |||||
nullptr, | |||||
std::is_const_v<data_type>, | |||||
!std::is_member_object_pointer_v<decltype(Data)>, | |||||
&internal::meta_info<data_type>::resolve, | |||||
&internal::setter<std::is_const_v<data_type>, Type, Data>, | |||||
&internal::getter<Type, Data>, | |||||
[]() -> meta_data { | |||||
return &node; | |||||
} | |||||
}; | |||||
node.prop = properties<std::integral_constant<decltype(Data), Data>>(std::forward<Property>(property)...); | |||||
curr = &node; | |||||
} else { | |||||
static_assert(std::is_pointer_v<decltype(Data)>); | |||||
using data_type = std::remove_pointer_t<decltype(Data)>; | |||||
static internal::meta_data_node node{ | |||||
&internal::meta_info<Type>::template data<Data>, | |||||
{}, | |||||
type, | |||||
nullptr, | |||||
nullptr, | |||||
std::is_const_v<data_type>, | |||||
!std::is_member_object_pointer_v<decltype(Data)>, | |||||
&internal::meta_info<data_type>::resolve, | |||||
&internal::setter<std::is_const_v<data_type>, Type, Data>, | |||||
&internal::getter<Type, Data>, | |||||
[]() -> meta_data { | |||||
return &node; | |||||
} | |||||
}; | |||||
node.prop = properties<std::integral_constant<decltype(Data), Data>>(std::forward<Property>(property)...); | |||||
curr = &node; | |||||
} | |||||
curr->name = hashed_string{str}; | |||||
curr->next = type->data; | |||||
ENTT_ASSERT(!duplicate(hashed_string{str}, curr->next)); | |||||
ENTT_ASSERT((!internal::meta_info<Type>::template data<Data>)); | |||||
internal::meta_info<Type>::template data<Data> = curr; | |||||
type->data = curr; | |||||
return *this; | |||||
} | |||||
/** | |||||
* @brief Assigns a meta data to a meta type by means of its setter and | |||||
* getter. | |||||
* | |||||
* Setters and getters can be either free functions, member functions or a | |||||
* mix of them.<br/> | |||||
* In case of free functions, setters and getters must accept a pointer to | |||||
* an instance of the parent type as their first argument. A setter has then | |||||
* an extra argument of a type convertible to that of the parameter to | |||||
* set.<br/> | |||||
* In case of member functions, getters have no arguments at all, while | |||||
* setters has an argument of a type convertible to that of the parameter to | |||||
* set. | |||||
* | |||||
* @tparam Setter The actual function to use as a setter. | |||||
* @tparam Getter The actual function to use as a getter. | |||||
* @tparam Property Types of properties to assign to the meta data. | |||||
* @param str The name to assign to the meta data. | |||||
* @param property Properties to assign to the meta data. | |||||
* @return A meta factory for the parent type. | |||||
*/ | |||||
template<auto Setter, auto Getter, typename... Property> | |||||
meta_factory data(const char *str, Property &&... property) ENTT_NOEXCEPT { | |||||
using owner_type = std::tuple<std::integral_constant<decltype(Setter), Setter>, std::integral_constant<decltype(Getter), Getter>>; | |||||
using underlying_type = std::invoke_result_t<decltype(Getter), Type *>; | |||||
static_assert(std::is_invocable_v<decltype(Setter), Type *, underlying_type>); | |||||
auto * const type = internal::meta_info<Type>::resolve(); | |||||
static internal::meta_data_node node{ | |||||
&internal::meta_info<Type>::template data<Setter, Getter>, | |||||
{}, | |||||
type, | |||||
nullptr, | |||||
nullptr, | |||||
false, | |||||
false, | |||||
&internal::meta_info<underlying_type>::resolve, | |||||
&internal::setter<false, Type, Setter>, | |||||
&internal::getter<Type, Getter>, | |||||
[]() -> meta_data { | |||||
return &node; | |||||
} | |||||
}; | |||||
node.name = hashed_string{str}; | |||||
node.next = type->data; | |||||
node.prop = properties<owner_type>(std::forward<Property>(property)...); | |||||
ENTT_ASSERT(!duplicate(hashed_string{str}, node.next)); | |||||
ENTT_ASSERT((!internal::meta_info<Type>::template data<Setter, Getter>)); | |||||
internal::meta_info<Type>::template data<Setter, Getter> = &node; | |||||
type->data = &node; | |||||
return *this; | |||||
} | |||||
/** | |||||
* @brief Assigns a meta funcion to a meta type. | |||||
* | |||||
* Both member functions and free functions can be assigned to a meta | |||||
* type.<br/> | |||||
* From a client's point of view, all the functions associated with the | |||||
* reflected object will appear as if they were part of the type itself. | |||||
* | |||||
* @tparam Func The actual function to attach to the meta type. | |||||
* @tparam Property Types of properties to assign to the meta function. | |||||
* @param str The name to assign to the meta function. | |||||
* @param property Properties to assign to the meta function. | |||||
* @return A meta factory for the parent type. | |||||
*/ | |||||
template<auto Func, typename... Property> | |||||
meta_factory func(const char *str, Property &&... property) ENTT_NOEXCEPT { | |||||
using owner_type = std::integral_constant<decltype(Func), Func>; | |||||
using func_type = internal::meta_function_helper<std::integral_constant<decltype(Func), Func>>; | |||||
auto * const type = internal::meta_info<Type>::resolve(); | |||||
static internal::meta_func_node node{ | |||||
&internal::meta_info<Type>::template func<Func>, | |||||
{}, | |||||
type, | |||||
nullptr, | |||||
nullptr, | |||||
func_type::size, | |||||
func_type::is_const, | |||||
func_type::is_static, | |||||
&internal::meta_info<typename func_type::return_type>::resolve, | |||||
&func_type::arg, | |||||
[](meta_handle handle, meta_any *any) { | |||||
return internal::invoke<Type, Func>(handle, any, std::make_index_sequence<func_type::size>{}); | |||||
}, | |||||
[]() -> meta_func { | |||||
return &node; | |||||
} | |||||
}; | |||||
node.name = hashed_string{str}; | |||||
node.next = type->func; | |||||
node.prop = properties<owner_type>(std::forward<Property>(property)...); | |||||
ENTT_ASSERT(!duplicate(hashed_string{str}, node.next)); | |||||
ENTT_ASSERT((!internal::meta_info<Type>::template func<Func>)); | |||||
internal::meta_info<Type>::template func<Func> = &node; | |||||
type->func = &node; | |||||
return *this; | |||||
} | |||||
}; | |||||
/** | |||||
* @brief Basic function to use for reflection. | |||||
* | |||||
* This is the point from which everything starts.<br/> | |||||
* By invoking this function with a type that is not yet reflected, a meta type | |||||
* is created to which it will be possible to attach data and functions through | |||||
* a dedicated factory. | |||||
* | |||||
* @tparam Type Type to reflect. | |||||
* @tparam Property Types of properties to assign to the reflected type. | |||||
* @param str The name to assign to the reflected type. | |||||
* @param property Properties to assign to the reflected type. | |||||
* @return A meta factory for the given type. | |||||
*/ | |||||
template<typename Type, typename... Property> | |||||
inline meta_factory<Type> reflect(const char *str, Property &&... property) ENTT_NOEXCEPT { | |||||
return meta_factory<Type>{}.type(hashed_string{str}, std::forward<Property>(property)...); | |||||
} | |||||
/** | |||||
* @brief Basic function to use for reflection. | |||||
* | |||||
* This is the point from which everything starts.<br/> | |||||
* By invoking this function with a type that is not yet reflected, a meta type | |||||
* is created to which it will be possible to attach data and functions through | |||||
* a dedicated factory. | |||||
* | |||||
* @tparam Type Type to reflect. | |||||
* @return A meta factory for the given type. | |||||
*/ | |||||
template<typename Type> | |||||
inline meta_factory<Type> reflect() ENTT_NOEXCEPT { | |||||
return meta_factory<Type>{}; | |||||
} | |||||
/** | |||||
* @brief Basic function to unregister a type. | |||||
* | |||||
* This function unregisters a type and all its data members, member functions | |||||
* and properties, as well as its constructors, destructors and conversion | |||||
* functions if any.<br/> | |||||
* Base classes aren't unregistered but the link between the two types is | |||||
* removed. | |||||
* | |||||
* @tparam Type Type to unregister. | |||||
* @return True if the type to unregister exists, false otherwise. | |||||
*/ | |||||
template<typename Type> | |||||
inline bool unregister() ENTT_NOEXCEPT { | |||||
return meta_factory<Type>().unregister(); | |||||
} | |||||
/** | |||||
* @brief Returns the meta type associated with a given type. | |||||
* @tparam Type Type to use to search for a meta type. | |||||
* @return The meta type associated with the given type, if any. | |||||
*/ | |||||
template<typename Type> | |||||
inline meta_type resolve() ENTT_NOEXCEPT { | |||||
return internal::meta_info<Type>::resolve()->meta(); | |||||
} | |||||
/** | |||||
* @brief Returns the meta type associated with a given name. | |||||
* @param str The name to use to search for a meta type. | |||||
* @return The meta type associated with the given name, if any. | |||||
*/ | |||||
inline meta_type resolve(const char *str) ENTT_NOEXCEPT { | |||||
const auto *curr = internal::find_if([name = hashed_string{str}](auto *node) { | |||||
return node->name == name; | |||||
}, internal::meta_info<>::type); | |||||
return curr ? curr->meta() : meta_type{}; | |||||
} | |||||
/** | |||||
* @brief Iterates all the reflected types. | |||||
* @tparam Op Type of the function object to invoke. | |||||
* @param op A valid function object. | |||||
*/ | |||||
template<typename Op> | |||||
void resolve(Op op) ENTT_NOEXCEPT { | |||||
internal::iterate([op = std::move(op)](auto *node) { | |||||
op(node->meta()); | |||||
}, internal::meta_info<>::type); | |||||
} | |||||
} | |||||
#endif // ENTT_META_FACTORY_HPP |
@ -0,0 +1,340 @@ | |||||
#ifndef ENTT_PROCESS_PROCESS_HPP | |||||
#define ENTT_PROCESS_PROCESS_HPP | |||||
#include <utility> | |||||
#include <type_traits> | |||||
#include "../config/config.h" | |||||
namespace entt { | |||||
/** | |||||
* @brief Base class for processes. | |||||
* | |||||
* This class stays true to the CRTP idiom. Derived classes must specify what's | |||||
* the intended type for elapsed times.<br/> | |||||
* A process should expose publicly the following member functions whether | |||||
* required: | |||||
* | |||||
* * @code{.cpp} | |||||
* void update(Delta, void *); | |||||
* @endcode | |||||
* | |||||
* It's invoked once per tick until a process is explicitly aborted or it | |||||
* terminates either with or without errors. Even though it's not mandatory to | |||||
* declare this member function, as a rule of thumb each process should at | |||||
* least define it to work properly. The `void *` parameter is an opaque | |||||
* pointer to user data (if any) forwarded directly to the process during an | |||||
* update. | |||||
* | |||||
* * @code{.cpp} | |||||
* void init(); | |||||
* @endcode | |||||
* | |||||
* It's invoked when the process joins the running queue of a scheduler. This | |||||
* happens as soon as it's attached to the scheduler if the process is a top | |||||
* level one, otherwise when it replaces its parent if the process is a | |||||
* continuation. | |||||
* | |||||
* * @code{.cpp} | |||||
* void succeeded(); | |||||
* @endcode | |||||
* | |||||
* It's invoked in case of success, immediately after an update and during the | |||||
* same tick. | |||||
* | |||||
* * @code{.cpp} | |||||
* void failed(); | |||||
* @endcode | |||||
* | |||||
* It's invoked in case of errors, immediately after an update and during the | |||||
* same tick. | |||||
* | |||||
* * @code{.cpp} | |||||
* void aborted(); | |||||
* @endcode | |||||
* | |||||
* It's invoked only if a process is explicitly aborted. There is no guarantee | |||||
* that it executes in the same tick, this depends solely on whether the | |||||
* process is aborted immediately or not. | |||||
* | |||||
* Derived classes can change the internal state of a process by invoking the | |||||
* `succeed` and `fail` protected member functions and even pause or unpause the | |||||
* process itself. | |||||
* | |||||
* @sa scheduler | |||||
* | |||||
* @tparam Derived Actual type of process that extends the class template. | |||||
* @tparam Delta Type to use to provide elapsed time. | |||||
*/ | |||||
template<typename Derived, typename Delta> | |||||
class process { | |||||
enum class state: unsigned int { | |||||
UNINITIALIZED = 0, | |||||
RUNNING, | |||||
PAUSED, | |||||
SUCCEEDED, | |||||
FAILED, | |||||
ABORTED, | |||||
FINISHED | |||||
}; | |||||
template<state value> | |||||
using state_value_t = std::integral_constant<state, value>; | |||||
template<typename Target = Derived> | |||||
auto tick(int, state_value_t<state::UNINITIALIZED>) | |||||
-> decltype(std::declval<Target>().init()) { | |||||
static_cast<Target *>(this)->init(); | |||||
} | |||||
template<typename Target = Derived> | |||||
auto tick(int, state_value_t<state::RUNNING>, Delta delta, void *data) | |||||
-> decltype(std::declval<Target>().update(delta, data)) { | |||||
static_cast<Target *>(this)->update(delta, data); | |||||
} | |||||
template<typename Target = Derived> | |||||
auto tick(int, state_value_t<state::SUCCEEDED>) | |||||
-> decltype(std::declval<Target>().succeeded()) { | |||||
static_cast<Target *>(this)->succeeded(); | |||||
} | |||||
template<typename Target = Derived> | |||||
auto tick(int, state_value_t<state::FAILED>) | |||||
-> decltype(std::declval<Target>().failed()) { | |||||
static_cast<Target *>(this)->failed(); | |||||
} | |||||
template<typename Target = Derived> | |||||
auto tick(int, state_value_t<state::ABORTED>) | |||||
-> decltype(std::declval<Target>().aborted()) { | |||||
static_cast<Target *>(this)->aborted(); | |||||
} | |||||
template<state value, typename... Args> | |||||
void tick(char, state_value_t<value>, Args &&...) const ENTT_NOEXCEPT {} | |||||
protected: | |||||
/** | |||||
* @brief Terminates a process with success if it's still alive. | |||||
* | |||||
* The function is idempotent and it does nothing if the process isn't | |||||
* alive. | |||||
*/ | |||||
void succeed() ENTT_NOEXCEPT { | |||||
if(alive()) { | |||||
current = state::SUCCEEDED; | |||||
} | |||||
} | |||||
/** | |||||
* @brief Terminates a process with errors if it's still alive. | |||||
* | |||||
* The function is idempotent and it does nothing if the process isn't | |||||
* alive. | |||||
*/ | |||||
void fail() ENTT_NOEXCEPT { | |||||
if(alive()) { | |||||
current = state::FAILED; | |||||
} | |||||
} | |||||
/** | |||||
* @brief Stops a process if it's in a running state. | |||||
* | |||||
* The function is idempotent and it does nothing if the process isn't | |||||
* running. | |||||
*/ | |||||
void pause() ENTT_NOEXCEPT { | |||||
if(current == state::RUNNING) { | |||||
current = state::PAUSED; | |||||
} | |||||
} | |||||
/** | |||||
* @brief Restarts a process if it's paused. | |||||
* | |||||
* The function is idempotent and it does nothing if the process isn't | |||||
* paused. | |||||
*/ | |||||
void unpause() ENTT_NOEXCEPT { | |||||
if(current == state::PAUSED) { | |||||
current = state::RUNNING; | |||||
} | |||||
} | |||||
public: | |||||
/*! @brief Type used to provide elapsed time. */ | |||||
using delta_type = Delta; | |||||
/*! @brief Default destructor. */ | |||||
virtual ~process() ENTT_NOEXCEPT { | |||||
static_assert(std::is_base_of_v<process, Derived>); | |||||
} | |||||
/** | |||||
* @brief Aborts a process if it's still alive. | |||||
* | |||||
* The function is idempotent and it does nothing if the process isn't | |||||
* alive. | |||||
* | |||||
* @param immediately Requests an immediate operation. | |||||
*/ | |||||
void abort(const bool immediately = false) ENTT_NOEXCEPT { | |||||
if(alive()) { | |||||
current = state::ABORTED; | |||||
if(immediately) { | |||||
tick(0); | |||||
} | |||||
} | |||||
} | |||||
/** | |||||
* @brief Returns true if a process is either running or paused. | |||||
* @return True if the process is still alive, false otherwise. | |||||
*/ | |||||
bool alive() const ENTT_NOEXCEPT { | |||||
return current == state::RUNNING || current == state::PAUSED; | |||||
} | |||||
/** | |||||
* @brief Returns true if a process is already terminated. | |||||
* @return True if the process is terminated, false otherwise. | |||||
*/ | |||||
bool dead() const ENTT_NOEXCEPT { | |||||
return current == state::FINISHED; | |||||
} | |||||
/** | |||||
* @brief Returns true if a process is currently paused. | |||||
* @return True if the process is paused, false otherwise. | |||||
*/ | |||||
bool paused() const ENTT_NOEXCEPT { | |||||
return current == state::PAUSED; | |||||
} | |||||
/** | |||||
* @brief Returns true if a process terminated with errors. | |||||
* @return True if the process terminated with errors, false otherwise. | |||||
*/ | |||||
bool rejected() const ENTT_NOEXCEPT { | |||||
return stopped; | |||||
} | |||||
/** | |||||
* @brief Updates a process and its internal state if required. | |||||
* @param delta Elapsed time. | |||||
* @param data Optional data. | |||||
*/ | |||||
void tick(const Delta delta, void *data = nullptr) { | |||||
switch (current) { | |||||
case state::UNINITIALIZED: | |||||
tick(0, state_value_t<state::UNINITIALIZED>{}); | |||||
current = state::RUNNING; | |||||
break; | |||||
case state::RUNNING: | |||||
tick(0, state_value_t<state::RUNNING>{}, delta, data); | |||||
break; | |||||
default: | |||||
// suppress warnings | |||||
break; | |||||
} | |||||
// if it's dead, it must be notified and removed immediately | |||||
switch(current) { | |||||
case state::SUCCEEDED: | |||||
tick(0, state_value_t<state::SUCCEEDED>{}); | |||||
current = state::FINISHED; | |||||
break; | |||||
case state::FAILED: | |||||
tick(0, state_value_t<state::FAILED>{}); | |||||
current = state::FINISHED; | |||||
stopped = true; | |||||
break; | |||||
case state::ABORTED: | |||||
tick(0, state_value_t<state::ABORTED>{}); | |||||
current = state::FINISHED; | |||||
stopped = true; | |||||
break; | |||||
default: | |||||
// suppress warnings | |||||
break; | |||||
} | |||||
} | |||||
private: | |||||
state current{state::UNINITIALIZED}; | |||||
bool stopped{false}; | |||||
}; | |||||
/** | |||||
* @brief Adaptor for lambdas and functors to turn them into processes. | |||||
* | |||||
* 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 signature of the function call operator should be equivalent to the | |||||
* following: | |||||
* | |||||
* @code{.cpp} | |||||
* void(Delta delta, void *data, auto succeed, auto fail); | |||||
* @endcode | |||||
* | |||||
* Where: | |||||
* | |||||
* * `delta` is the elapsed time. | |||||
* * `data` is an opaque pointer to user data if any, `nullptr` otherwise. | |||||
* * `succeed` is a function to call when a process terminates with success. | |||||
* * `fail` is a function to call when a process terminates with errors. | |||||
* | |||||
* The signature of the function call operator of both `succeed` and `fail` | |||||
* is equivalent to the following: | |||||
* | |||||
* @code{.cpp} | |||||
* void(); | |||||
* @endcode | |||||
* | |||||
* Usually users shouldn't worry about creating adaptors. A scheduler will | |||||
* create them internally each and avery time a lambda or a functor is used as | |||||
* a process. | |||||
* | |||||
* @sa process | |||||
* @sa scheduler | |||||
* | |||||
* @tparam Func Actual type of process. | |||||
* @tparam Delta Type to use to provide elapsed time. | |||||
*/ | |||||
template<typename Func, typename Delta> | |||||
struct process_adaptor: process<process_adaptor<Func, Delta>, Delta>, private Func { | |||||
/** | |||||
* @brief Constructs a process adaptor from a lambda or a functor. | |||||
* @tparam Args Types of arguments to use to initialize the actual process. | |||||
* @param args Parameters to use to initialize the actual process. | |||||
*/ | |||||
template<typename... Args> | |||||
process_adaptor(Args &&... args) | |||||
: Func{std::forward<Args>(args)...} | |||||
{} | |||||
/** | |||||
* @brief Updates a process and its internal state if required. | |||||
* @param delta Elapsed time. | |||||
* @param data Optional data. | |||||
*/ | |||||
void update(const Delta delta, void *data) { | |||||
Func::operator()(delta, data, [this]() { this->succeed(); }, [this]() { this->fail(); }); | |||||
} | |||||
}; | |||||
} | |||||
#endif // ENTT_PROCESS_PROCESS_HPP |
@ -0,0 +1,300 @@ | |||||
#ifndef ENTT_PROCESS_SCHEDULER_HPP | |||||
#define ENTT_PROCESS_SCHEDULER_HPP | |||||
#include <vector> | |||||
#include <memory> | |||||
#include <utility> | |||||
#include <algorithm> | |||||
#include <type_traits> | |||||
#include "../config/config.h" | |||||
#include "process.hpp" | |||||
namespace entt { | |||||
/** | |||||
* @brief Cooperative scheduler for processes. | |||||
* | |||||
* A cooperative scheduler runs processes and helps managing their life cycles. | |||||
* | |||||
* Each process is invoked once per tick. If a process terminates, it's | |||||
* removed automatically from the scheduler and it's never invoked again.<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. | |||||
* | |||||
* Example of use (pseudocode): | |||||
* | |||||
* @code{.cpp} | |||||
* scheduler.attach([](auto delta, void *, auto succeed, auto fail) { | |||||
* // code | |||||
* }).then<my_process>(arguments...); | |||||
* @endcode | |||||
* | |||||
* In order to invoke all scheduled processes, call the `update` member function | |||||
* passing it the elapsed time to forward to the tasks. | |||||
* | |||||
* @sa process | |||||
* | |||||
* @tparam Delta Type to use to provide elapsed time. | |||||
*/ | |||||
template<typename Delta> | |||||
class scheduler { | |||||
struct process_handler { | |||||
using instance_type = std::unique_ptr<void, void(*)(void *)>; | |||||
using update_fn_type = bool(process_handler &, Delta, void *); | |||||
using abort_fn_type = void(process_handler &, bool); | |||||
using next_type = std::unique_ptr<process_handler>; | |||||
instance_type instance; | |||||
update_fn_type *update; | |||||
abort_fn_type *abort; | |||||
next_type next; | |||||
}; | |||||
struct continuation { | |||||
continuation(process_handler *ref) | |||||
: handler{ref} | |||||
{ | |||||
ENTT_ASSERT(handler); | |||||
} | |||||
template<typename Proc, typename... Args> | |||||
continuation then(Args &&... args) { | |||||
static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>); | |||||
auto proc = typename process_handler::instance_type{new Proc{std::forward<Args>(args)...}, &scheduler::deleter<Proc>}; | |||||
handler->next.reset(new process_handler{std::move(proc), &scheduler::update<Proc>, &scheduler::abort<Proc>, nullptr}); | |||||
handler = handler->next.get(); | |||||
return *this; | |||||
} | |||||
template<typename Func> | |||||
continuation then(Func &&func) { | |||||
return then<process_adaptor<std::decay_t<Func>, Delta>>(std::forward<Func>(func)); | |||||
} | |||||
private: | |||||
process_handler *handler; | |||||
}; | |||||
template<typename Proc> | |||||
static bool update(process_handler &handler, const Delta delta, void *data) { | |||||
auto *process = static_cast<Proc *>(handler.instance.get()); | |||||
process->tick(delta, data); | |||||
auto dead = process->dead(); | |||||
if(dead) { | |||||
if(handler.next && !process->rejected()) { | |||||
handler = std::move(*handler.next); | |||||
// forces the process to exit the uninitialized state | |||||
dead = handler.update(handler, {}, nullptr); | |||||
} else { | |||||
handler.instance.reset(); | |||||
} | |||||
} | |||||
return dead; | |||||
} | |||||
template<typename Proc> | |||||
static void abort(process_handler &handler, const bool immediately) { | |||||
static_cast<Proc *>(handler.instance.get())->abort(immediately); | |||||
} | |||||
template<typename Proc> | |||||
static void deleter(void *proc) { | |||||
delete static_cast<Proc *>(proc); | |||||
} | |||||
public: | |||||
/*! @brief Unsigned integer type. */ | |||||
using size_type = typename std::vector<process_handler>::size_type; | |||||
/*! @brief Default constructor. */ | |||||
scheduler() ENTT_NOEXCEPT = default; | |||||
/*! @brief Default move constructor. */ | |||||
scheduler(scheduler &&) = default; | |||||
/*! @brief Default move assignment operator. @return This scheduler. */ | |||||
scheduler & operator=(scheduler &&) = default; | |||||
/** | |||||
* @brief Number of processes currently scheduled. | |||||
* @return Number of processes currently scheduled. | |||||
*/ | |||||
size_type size() const ENTT_NOEXCEPT { | |||||
return handlers.size(); | |||||
} | |||||
/** | |||||
* @brief Returns true if at least a process is currently scheduled. | |||||
* @return True if there are scheduled processes, false otherwise. | |||||
*/ | |||||
bool empty() const ENTT_NOEXCEPT { | |||||
return handlers.empty(); | |||||
} | |||||
/** | |||||
* @brief Discards all scheduled processes. | |||||
* | |||||
* Processes aren't aborted. They are discarded along with their children | |||||
* and never executed again. | |||||
*/ | |||||
void clear() { | |||||
handlers.clear(); | |||||
} | |||||
/** | |||||
* @brief Schedules a process for the next tick. | |||||
* | |||||
* Returned value is an opaque object that can be used to attach a child to | |||||
* the given process. The child is automatically scheduled when the process | |||||
* terminates and only if the process returns with success. | |||||
* | |||||
* Example of use (pseudocode): | |||||
* | |||||
* @code{.cpp} | |||||
* // schedules a task in the form of a process class | |||||
* scheduler.attach<my_process>(arguments...) | |||||
* // appends a child in the form of a lambda function | |||||
* .then([](auto delta, void *, auto succeed, auto fail) { | |||||
* // code | |||||
* }) | |||||
* // appends a child in the form of another process class | |||||
* .then<my_other_process>(); | |||||
* @endcode | |||||
* | |||||
* @tparam Proc Type of process to schedule. | |||||
* @tparam Args Types of arguments to use to initialize the process. | |||||
* @param args Parameters to use to initialize the process. | |||||
* @return An opaque object to use to concatenate processes. | |||||
*/ | |||||
template<typename Proc, typename... Args> | |||||
auto attach(Args &&... args) { | |||||
static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>); | |||||
auto proc = typename process_handler::instance_type{new Proc{std::forward<Args>(args)...}, &scheduler::deleter<Proc>}; | |||||
process_handler handler{std::move(proc), &scheduler::update<Proc>, &scheduler::abort<Proc>, nullptr}; | |||||
// forces the process to exit the uninitialized state | |||||
handler.update(handler, {}, nullptr); | |||||
return continuation{&handlers.emplace_back(std::move(handler))}; | |||||
} | |||||
/** | |||||
* @brief Schedules a process for the next tick. | |||||
* | |||||
* A process can be either a lambda or a functor. The scheduler wraps both | |||||
* of them in a process adaptor internally.<br/> | |||||
* The signature of the function call operator should be equivalent to the | |||||
* following: | |||||
* | |||||
* @code{.cpp} | |||||
* void(Delta delta, void *data, auto succeed, auto fail); | |||||
* @endcode | |||||
* | |||||
* Where: | |||||
* | |||||
* * `delta` is the elapsed time. | |||||
* * `data` is an opaque pointer to user data if any, `nullptr` otherwise. | |||||
* * `succeed` is a function to call when a process terminates with success. | |||||
* * `fail` is a function to call when a process terminates with errors. | |||||
* | |||||
* The signature of the function call operator of both `succeed` and `fail` | |||||
* is equivalent to the following: | |||||
* | |||||
* @code{.cpp} | |||||
* void(); | |||||
* @endcode | |||||
* | |||||
* Returned value is an opaque object that can be used to attach a child to | |||||
* the given process. The child is automatically scheduled when the process | |||||
* terminates and only if the process returns with success. | |||||
* | |||||
* Example of use (pseudocode): | |||||
* | |||||
* @code{.cpp} | |||||
* // schedules a task in the form of a lambda function | |||||
* scheduler.attach([](auto delta, void *, auto succeed, auto fail) { | |||||
* // code | |||||
* }) | |||||
* // appends a child in the form of another lambda function | |||||
* .then([](auto delta, void *, auto succeed, auto fail) { | |||||
* // code | |||||
* }) | |||||
* // appends a child in the form of a process class | |||||
* .then<my_process>(arguments...); | |||||
* @endcode | |||||
* | |||||
* @sa process_adaptor | |||||
* | |||||
* @tparam Func Type of process to schedule. | |||||
* @param func Either a lambda or a functor to use as a process. | |||||
* @return An opaque object to use to concatenate processes. | |||||
*/ | |||||
template<typename Func> | |||||
auto attach(Func &&func) { | |||||
using Proc = process_adaptor<std::decay_t<Func>, Delta>; | |||||
return attach<Proc>(std::forward<Func>(func)); | |||||
} | |||||
/** | |||||
* @brief Updates all scheduled processes. | |||||
* | |||||
* All scheduled processes are executed in no specific order.<br/> | |||||
* If a process terminates with success, it's replaced with its child, if | |||||
* any. Otherwise, if a process terminates with an error, it's removed along | |||||
* with its child. | |||||
* | |||||
* @param delta Elapsed time. | |||||
* @param data Optional data. | |||||
*/ | |||||
void update(const Delta delta, void *data = nullptr) { | |||||
bool clean = false; | |||||
for(auto pos = handlers.size(); pos; --pos) { | |||||
auto &handler = handlers[pos-1]; | |||||
const bool dead = handler.update(handler, delta, data); | |||||
clean = clean || dead; | |||||
} | |||||
if(clean) { | |||||
handlers.erase(std::remove_if(handlers.begin(), handlers.end(), [](auto &handler) { | |||||
return !handler.instance; | |||||
}), handlers.end()); | |||||
} | |||||
} | |||||
/** | |||||
* @brief Aborts all scheduled processes. | |||||
* | |||||
* Unless an immediate operation is requested, the abort is scheduled for | |||||
* the next tick. Processes won't be executed anymore in any case.<br/> | |||||
* Once a process is fully aborted and thus finished, it's discarded along | |||||
* with its child, if any. | |||||
* | |||||
* @param immediately Requests an immediate operation. | |||||
*/ | |||||
void abort(const bool immediately = false) { | |||||
decltype(handlers) exec; | |||||
exec.swap(handlers); | |||||
std::for_each(exec.begin(), exec.end(), [immediately](auto &handler) { | |||||
handler.abort(handler, immediately); | |||||
}); | |||||
std::move(handlers.begin(), handlers.end(), std::back_inserter(exec)); | |||||
handlers.swap(exec); | |||||
} | |||||
private: | |||||
std::vector<process_handler> handlers{}; | |||||
}; | |||||
} | |||||
#endif // ENTT_PROCESS_SCHEDULER_HPP |
@ -0,0 +1,207 @@ | |||||
#ifndef ENTT_RESOURCE_CACHE_HPP | |||||
#define ENTT_RESOURCE_CACHE_HPP | |||||
#include <memory> | |||||
#include <utility> | |||||
#include <type_traits> | |||||
#include <unordered_map> | |||||
#include "../config/config.h" | |||||
#include "../core/hashed_string.hpp" | |||||
#include "handle.hpp" | |||||
#include "loader.hpp" | |||||
#include "fwd.hpp" | |||||
namespace entt { | |||||
/** | |||||
* @brief Simple cache for resources of a given type. | |||||
* | |||||
* Minimal implementation of a cache for resources of a given type. It doesn't | |||||
* offer much functionalities but it's suitable for small or medium sized | |||||
* applications and can be freely inherited to add targeted functionalities for | |||||
* large sized applications. | |||||
* | |||||
* @tparam Resource Type of resources managed by a cache. | |||||
*/ | |||||
template<typename Resource> | |||||
class resource_cache { | |||||
using container_type = std::unordered_map<hashed_string::hash_type, std::shared_ptr<Resource>>; | |||||
public: | |||||
/*! @brief Unsigned integer type. */ | |||||
using size_type = typename container_type::size_type; | |||||
/*! @brief Type of resources managed by a cache. */ | |||||
using resource_type = typename hashed_string::hash_type; | |||||
/*! @brief Default constructor. */ | |||||
resource_cache() = default; | |||||
/*! @brief Default move constructor. */ | |||||
resource_cache(resource_cache &&) = default; | |||||
/*! @brief Default move assignment operator. @return This cache. */ | |||||
resource_cache & operator=(resource_cache &&) = default; | |||||
/** | |||||
* @brief Number of resources managed by a cache. | |||||
* @return Number of resources currently stored. | |||||
*/ | |||||
size_type size() const ENTT_NOEXCEPT { | |||||
return resources.size(); | |||||
} | |||||
/** | |||||
* @brief Returns true if a cache contains no resources, false otherwise. | |||||
* @return True if the cache contains no resources, false otherwise. | |||||
*/ | |||||
bool empty() const ENTT_NOEXCEPT { | |||||
return resources.empty(); | |||||
} | |||||
/** | |||||
* @brief Clears a cache and discards all its resources. | |||||
* | |||||
* Handles are not invalidated and the memory used by a resource isn't | |||||
* freed as long as at least a handle keeps the resource itself alive. | |||||
*/ | |||||
void clear() ENTT_NOEXCEPT { | |||||
resources.clear(); | |||||
} | |||||
/** | |||||
* @brief Loads the resource that corresponds to a given identifier. | |||||
* | |||||
* In case an identifier isn't already present in the cache, it loads its | |||||
* resource and stores it aside for future uses. Arguments are forwarded | |||||
* directly to the loader in order to construct properly the requested | |||||
* resource. | |||||
* | |||||
* @note | |||||
* If the identifier is already present in the cache, this function does | |||||
* nothing and the arguments are simply discarded. | |||||
* | |||||
* @warning | |||||
* If the resource cannot be loaded correctly, the returned handle will be | |||||
* invalid and any use of it will result in undefined behavior. | |||||
* | |||||
* @tparam Loader Type of loader to use to load the resource if required. | |||||
* @tparam Args Types of arguments to use to load the resource if required. | |||||
* @param id Unique resource identifier. | |||||
* @param args Arguments to use to load the resource if required. | |||||
* @return A handle for the given resource. | |||||
*/ | |||||
template<typename Loader, typename... Args> | |||||
resource_handle<Resource> load(const resource_type id, Args &&... args) { | |||||
static_assert(std::is_base_of_v<resource_loader<Loader, Resource>, Loader>); | |||||
resource_handle<Resource> handle{}; | |||||
if(auto it = resources.find(id); it == resources.cend()) { | |||||
if(auto resource = Loader{}.get(std::forward<Args>(args)...); resource) { | |||||
resources[id] = resource; | |||||
handle = std::move(resource); | |||||
} | |||||
} else { | |||||
handle = it->second; | |||||
} | |||||
return handle; | |||||
} | |||||
/** | |||||
* @brief Reloads a resource or loads it for the first time if not present. | |||||
* | |||||
* Equivalent to the following snippet (pseudocode): | |||||
* | |||||
* @code{.cpp} | |||||
* cache.discard(id); | |||||
* cache.load(id, args...); | |||||
* @endcode | |||||
* | |||||
* Arguments are forwarded directly to the loader in order to construct | |||||
* properly the requested resource. | |||||
* | |||||
* @warning | |||||
* If the resource cannot be loaded correctly, the returned handle will be | |||||
* invalid and any use of it will result in undefined behavior. | |||||
* | |||||
* @tparam Loader Type of loader to use to load the resource. | |||||
* @tparam Args Types of arguments to use to load the resource. | |||||
* @param id Unique resource identifier. | |||||
* @param args Arguments to use to load the resource. | |||||
* @return A handle for the given resource. | |||||
*/ | |||||
template<typename Loader, typename... Args> | |||||
resource_handle<Resource> reload(const resource_type id, Args &&... args) { | |||||
return (discard(id), load<Loader>(id, std::forward<Args>(args)...)); | |||||
} | |||||
/** | |||||
* @brief Creates a temporary handle for a resource. | |||||
* | |||||
* Arguments are forwarded directly to the loader in order to construct | |||||
* properly the requested resource. The handle isn't stored aside and the | |||||
* cache isn't in charge of the lifetime of the resource itself. | |||||
* | |||||
* @tparam Loader Type of loader to use to load the resource. | |||||
* @tparam Args Types of arguments to use to load the resource. | |||||
* @param args Arguments to use to load the resource. | |||||
* @return A handle for the given resource. | |||||
*/ | |||||
template<typename Loader, typename... Args> | |||||
resource_handle<Resource> temp(Args &&... args) const { | |||||
return { Loader{}.get(std::forward<Args>(args)...) }; | |||||
} | |||||
/** | |||||
* @brief Creates a handle for a given resource identifier. | |||||
* | |||||
* A resource handle can be in a either valid or invalid state. In other | |||||
* terms, a resource handle is properly initialized with a resource if the | |||||
* cache contains the resource itself. Otherwise the returned handle is | |||||
* uninitialized and accessing it results in undefined behavior. | |||||
* | |||||
* @sa resource_handle | |||||
* | |||||
* @param id Unique resource identifier. | |||||
* @return A handle for the given resource. | |||||
*/ | |||||
resource_handle<Resource> handle(const resource_type id) const { | |||||
auto it = resources.find(id); | |||||
return { it == resources.end() ? nullptr : it->second }; | |||||
} | |||||
/** | |||||
* @brief Checks if a cache contains a given identifier. | |||||
* @param id Unique resource identifier. | |||||
* @return True if the cache contains the resource, false otherwise. | |||||
*/ | |||||
bool contains(const resource_type id) const ENTT_NOEXCEPT { | |||||
return (resources.find(id) != resources.cend()); | |||||
} | |||||
/** | |||||
* @brief Discards the resource that corresponds to a given identifier. | |||||
* | |||||
* Handles are not invalidated and the memory used by the resource isn't | |||||
* freed as long as at least a handle keeps the resource itself alive. | |||||
* | |||||
* @param id Unique resource identifier. | |||||
*/ | |||||
void discard(const resource_type id) ENTT_NOEXCEPT { | |||||
if(auto it = resources.find(id); it != resources.end()) { | |||||
resources.erase(it); | |||||
} | |||||
} | |||||
private: | |||||
container_type resources; | |||||
}; | |||||
} | |||||
#endif // ENTT_RESOURCE_CACHE_HPP |
@ -0,0 +1,27 @@ | |||||
#ifndef ENTT_RESOURCE_FWD_HPP | |||||
#define ENTT_RESOURCE_FWD_HPP | |||||
#include "../config/config.h" | |||||
namespace entt { | |||||
/*! @class resource_cache */ | |||||
template<typename> | |||||
class resource_cache; | |||||
/*! @class resource_handle */ | |||||
template<typename> | |||||
class resource_handle; | |||||
/*! @class resource_loader */ | |||||
template<typename, typename> | |||||
class resource_loader; | |||||
} | |||||
#endif // ENTT_RESOURCE_FWD_HPP |
@ -0,0 +1,106 @@ | |||||
#ifndef ENTT_RESOURCE_HANDLE_HPP | |||||
#define ENTT_RESOURCE_HANDLE_HPP | |||||
#include <memory> | |||||
#include <utility> | |||||
#include "../config/config.h" | |||||
#include "fwd.hpp" | |||||
namespace entt { | |||||
/** | |||||
* @brief Shared resource handle. | |||||
* | |||||
* A shared resource handle is a small class that wraps a resource and keeps it | |||||
* alive even if it's deleted from the cache. It can be either copied or | |||||
* moved. A handle shares a reference to the same resource with all the other | |||||
* handles constructed for the same identifier.<br/> | |||||
* As a rule of thumb, resources should never be copied nor moved. Handles are | |||||
* the way to go to keep references to them. | |||||
* | |||||
* @tparam Resource Type of resource managed by a handle. | |||||
*/ | |||||
template<typename Resource> | |||||
class resource_handle { | |||||
/*! @brief Resource handles are friends of their caches. */ | |||||
friend class resource_cache<Resource>; | |||||
resource_handle(std::shared_ptr<Resource> res) ENTT_NOEXCEPT | |||||
: resource{std::move(res)} | |||||
{} | |||||
public: | |||||
/*! @brief Default constructor. */ | |||||
resource_handle() ENTT_NOEXCEPT = default; | |||||
/** | |||||
* @brief Gets a reference to the managed resource. | |||||
* | |||||
* @warning | |||||
* The behavior is undefined if the handle doesn't contain a resource.<br/> | |||||
* An assertion will abort the execution at runtime in debug mode if the | |||||
* handle is empty. | |||||
* | |||||
* @return A reference to the managed resource. | |||||
*/ | |||||
const Resource & get() const ENTT_NOEXCEPT { | |||||
ENTT_ASSERT(static_cast<bool>(resource)); | |||||
return *resource; | |||||
} | |||||
/*! @copydoc get */ | |||||
Resource & get() ENTT_NOEXCEPT { | |||||
return const_cast<Resource &>(std::as_const(*this).get()); | |||||
} | |||||
/*! @copydoc get */ | |||||
inline operator const Resource & () const ENTT_NOEXCEPT { return get(); } | |||||
/*! @copydoc get */ | |||||
inline operator Resource & () ENTT_NOEXCEPT { return get(); } | |||||
/*! @copydoc get */ | |||||
inline const Resource & operator *() const ENTT_NOEXCEPT { return get(); } | |||||
/*! @copydoc get */ | |||||
inline Resource & operator *() ENTT_NOEXCEPT { return get(); } | |||||
/** | |||||
* @brief Gets a pointer to the managed resource. | |||||
* | |||||
* @warning | |||||
* The behavior is undefined if the handle doesn't contain a resource.<br/> | |||||
* An assertion will abort the execution at runtime in debug mode if the | |||||
* handle is empty. | |||||
* | |||||
* @return A pointer to the managed resource or `nullptr` if the handle | |||||
* contains no resource at all. | |||||
*/ | |||||
inline const Resource * operator->() const ENTT_NOEXCEPT { | |||||
ENTT_ASSERT(static_cast<bool>(resource)); | |||||
return resource.get(); | |||||
} | |||||
/*! @copydoc operator-> */ | |||||
inline Resource * operator->() ENTT_NOEXCEPT { | |||||
return const_cast<Resource *>(std::as_const(*this).operator->()); | |||||
} | |||||
/** | |||||
* @brief Returns true if a handle contains a resource, false otherwise. | |||||
* @return True if the handle contains a resource, false otherwise. | |||||
*/ | |||||
explicit operator bool() const { return static_cast<bool>(resource); } | |||||
private: | |||||
std::shared_ptr<Resource> resource; | |||||
}; | |||||
} | |||||
#endif // ENTT_RESOURCE_HANDLE_HPP |
@ -0,0 +1,65 @@ | |||||
#ifndef ENTT_RESOURCE_LOADER_HPP | |||||
#define ENTT_RESOURCE_LOADER_HPP | |||||
#include <memory> | |||||
#include "fwd.hpp" | |||||
namespace entt { | |||||
/** | |||||
* @brief Base class for resource loaders. | |||||
* | |||||
* Resource loaders must inherit from this class and stay true to the CRTP | |||||
* idiom. Moreover, a resource loader must expose a public, const member | |||||
* function named `load` that accepts a variable number of arguments and returns | |||||
* a shared pointer to the resource just created.<br/> | |||||
* As an example: | |||||
* | |||||
* @code{.cpp} | |||||
* struct my_resource {}; | |||||
* | |||||
* struct my_loader: entt::resource_loader<my_loader, my_resource> { | |||||
* std::shared_ptr<my_resource> load(int) const { | |||||
* // use the integer value somehow | |||||
* return std::make_shared<my_resource>(); | |||||
* } | |||||
* }; | |||||
* @endcode | |||||
* | |||||
* In general, resource loaders should not have a state or retain data of any | |||||
* type. They should let the cache manage their resources instead. | |||||
* | |||||
* @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. | |||||
* | |||||
* @tparam Loader Type of the derived class. | |||||
* @tparam Resource Type of resource for which to use the loader. | |||||
*/ | |||||
template<typename Loader, typename Resource> | |||||
class resource_loader { | |||||
/*! @brief Resource loaders are friends of their caches. */ | |||||
friend class resource_cache<Resource>; | |||||
/** | |||||
* @brief Loads the resource and returns it. | |||||
* @tparam Args Types of arguments for the loader. | |||||
* @param args Arguments for the loader. | |||||
* @return The resource just loaded or an empty pointer in case of errors. | |||||
*/ | |||||
template<typename... Args> | |||||
std::shared_ptr<Resource> get(Args &&... args) const { | |||||
return static_cast<const Loader *>(this)->load(std::forward<Args>(args)...); | |||||
} | |||||
}; | |||||
} | |||||
#endif // ENTT_RESOURCE_LOADER_HPP |
@ -0,0 +1,282 @@ | |||||
#ifndef ENTT_SIGNAL_DELEGATE_HPP | |||||
#define ENTT_SIGNAL_DELEGATE_HPP | |||||
#include <cstring> | |||||
#include <algorithm> | |||||
#include <functional> | |||||
#include <type_traits> | |||||
#include "../config/config.h" | |||||
namespace entt { | |||||
/** | |||||
* @cond TURN_OFF_DOXYGEN | |||||
* Internal details not to be documented. | |||||
*/ | |||||
namespace internal { | |||||
template<typename Ret, typename... Args> | |||||
auto to_function_pointer(Ret(*)(Args...)) -> Ret(*)(Args...); | |||||
template<typename Ret, typename... Args, typename Type> | |||||
auto to_function_pointer(Ret(*)(Type *, Args...), Type *) -> Ret(*)(Args...); | |||||
template<typename Class, typename Ret, typename... Args> | |||||
auto to_function_pointer(Ret(Class:: *)(Args...), Class *) -> Ret(*)(Args...); | |||||
template<typename Class, typename Ret, typename... Args> | |||||
auto to_function_pointer(Ret(Class:: *)(Args...) const, Class *) -> Ret(*)(Args...); | |||||
} | |||||
/** | |||||
* Internal details not to be documented. | |||||
* @endcond TURN_OFF_DOXYGEN | |||||
*/ | |||||
/*! @brief Used to wrap a function or a member of a specified type. */ | |||||
template<auto> | |||||
struct connect_arg_t {}; | |||||
/*! @brief Constant of type connect_arg_t used to disambiguate calls. */ | |||||
template<auto Func> | |||||
constexpr connect_arg_t<Func> connect_arg{}; | |||||
/** | |||||
* @brief Basic delegate implementation. | |||||
* | |||||
* Primary template isn't defined on purpose. All the specializations give a | |||||
* compile-time error unless the template parameter is a function type. | |||||
*/ | |||||
template<typename> | |||||
class delegate; | |||||
/** | |||||
* @brief Utility class to use to send around functions and members. | |||||
* | |||||
* Unmanaged delegate for function pointers and members. Users of this class are | |||||
* in charge of disconnecting instances before deleting them. | |||||
* | |||||
* A delegate can be used as general purpose invoker with no memory overhead for | |||||
* free functions (with or without payload) and members provided along with an | |||||
* instance on which to invoke them. | |||||
* | |||||
* @tparam Ret Return type of a function type. | |||||
* @tparam Args Types of arguments of a function type. | |||||
*/ | |||||
template<typename Ret, typename... Args> | |||||
class delegate<Ret(Args...)> { | |||||
using proto_fn_type = Ret(const void *, Args...); | |||||
public: | |||||
/*! @brief Function type of the delegate. */ | |||||
using function_type = Ret(Args...); | |||||
/*! @brief Default constructor. */ | |||||
delegate() ENTT_NOEXCEPT | |||||
: fn{nullptr}, data{nullptr} | |||||
{} | |||||
/** | |||||
* @brief Constructs a delegate and connects a free function to it. | |||||
* @tparam Function A valid free function pointer. | |||||
*/ | |||||
template<auto Function> | |||||
delegate(connect_arg_t<Function>) ENTT_NOEXCEPT | |||||
: delegate{} | |||||
{ | |||||
connect<Function>(); | |||||
} | |||||
/** | |||||
* @brief Constructs a delegate and connects a member for a given instance | |||||
* or a free function with payload. | |||||
* @tparam Candidate Member or free function to connect to the delegate. | |||||
* @tparam Type Type of class or type of payload. | |||||
* @param value_or_instance A valid pointer that fits the purpose. | |||||
*/ | |||||
template<auto Candidate, typename Type> | |||||
delegate(connect_arg_t<Candidate>, Type *value_or_instance) ENTT_NOEXCEPT | |||||
: delegate{} | |||||
{ | |||||
connect<Candidate>(value_or_instance); | |||||
} | |||||
/** | |||||
* @brief Connects a free function to a delegate. | |||||
* @tparam Function A valid free function pointer. | |||||
*/ | |||||
template<auto Function> | |||||
void connect() ENTT_NOEXCEPT { | |||||
static_assert(std::is_invocable_r_v<Ret, decltype(Function), Args...>); | |||||
data = nullptr; | |||||
fn = [](const void *, Args... args) -> Ret { | |||||
// this allows void(...) to eat return values and avoid errors | |||||
return Ret(std::invoke(Function, args...)); | |||||
}; | |||||
} | |||||
/** | |||||
* @brief Connects a member function for a given instance or a free function | |||||
* with payload to a delegate. | |||||
* | |||||
* The delegate isn't responsible for the connected object or the payload. | |||||
* Users must always guarantee that the lifetime of the instance overcomes | |||||
* the one of the delegate.<br/> | |||||
* When used to connect a free function with payload, its signature must be | |||||
* such that the instance is the first argument before the ones used to | |||||
* define the delegate itself. | |||||
* | |||||
* @tparam Candidate Member or free function to connect to the delegate. | |||||
* @tparam Type Type of class or type of payload. | |||||
* @param value_or_instance A valid pointer that fits the purpose. | |||||
*/ | |||||
template<auto Candidate, typename Type> | |||||
void connect(Type *value_or_instance) ENTT_NOEXCEPT { | |||||
static_assert(std::is_invocable_r_v<Ret, decltype(Candidate), Type *, Args...>); | |||||
data = value_or_instance; | |||||
fn = [](const void *payload, Args... args) -> Ret { | |||||
Type *curr = nullptr; | |||||
if constexpr(std::is_const_v<Type>) { | |||||
curr = static_cast<Type *>(payload); | |||||
} else { | |||||
curr = static_cast<Type *>(const_cast<void *>(payload)); | |||||
} | |||||
// this allows void(...) to eat return values and avoid errors | |||||
return Ret(std::invoke(Candidate, curr, args...)); | |||||
}; | |||||
} | |||||
/** | |||||
* @brief Resets a delegate. | |||||
* | |||||
* After a reset, a delegate cannot be invoked anymore. | |||||
*/ | |||||
void reset() ENTT_NOEXCEPT { | |||||
fn = nullptr; | |||||
data = nullptr; | |||||
} | |||||
/** | |||||
* @brief Returns the instance linked to a delegate, if any. | |||||
* @return An opaque pointer to the instance linked to the delegate, if any. | |||||
*/ | |||||
const void * instance() const ENTT_NOEXCEPT { | |||||
return data; | |||||
} | |||||
/** | |||||
* @brief Triggers a delegate. | |||||
* | |||||
* The delegate invokes the underlying function and returns the result. | |||||
* | |||||
* @warning | |||||
* Attempting to trigger an invalid delegate results in undefined | |||||
* behavior.<br/> | |||||
* An assertion will abort the execution at runtime in debug mode if the | |||||
* delegate has not yet been set. | |||||
* | |||||
* @param args Arguments to use to invoke the underlying function. | |||||
* @return The value returned by the underlying function. | |||||
*/ | |||||
Ret operator()(Args... args) const { | |||||
ENTT_ASSERT(fn); | |||||
return fn(data, args...); | |||||
} | |||||
/** | |||||
* @brief Checks whether a delegate actually stores a listener. | |||||
* @return False if the delegate is empty, true otherwise. | |||||
*/ | |||||
explicit operator bool() const ENTT_NOEXCEPT { | |||||
// no need to test also data | |||||
return fn; | |||||
} | |||||
/** | |||||
* @brief Checks if the connected functions differ. | |||||
* | |||||
* Instances connected to delegates are ignored by this operator. Use the | |||||
* `instance` member function instead. | |||||
* | |||||
* @param other Delegate with which to compare. | |||||
* @return False if the connected functions differ, true otherwise. | |||||
*/ | |||||
bool operator==(const delegate<Ret(Args...)> &other) const ENTT_NOEXCEPT { | |||||
return fn == other.fn; | |||||
} | |||||
private: | |||||
proto_fn_type *fn; | |||||
const void *data; | |||||
}; | |||||
/** | |||||
* @brief Checks if the connected functions differ. | |||||
* | |||||
* Instances connected to delegates are ignored by this operator. Use the | |||||
* `instance` member function instead. | |||||
* | |||||
* @tparam Ret Return type of a function type. | |||||
* @tparam Args Types of arguments of a function type. | |||||
* @param lhs A valid delegate object. | |||||
* @param rhs A valid delegate object. | |||||
* @return True if the connected functions differ, false otherwise. | |||||
*/ | |||||
template<typename Ret, typename... Args> | |||||
bool operator!=(const delegate<Ret(Args...)> &lhs, const delegate<Ret(Args...)> &rhs) ENTT_NOEXCEPT { | |||||
return !(lhs == rhs); | |||||
} | |||||
/** | |||||
* @brief Deduction guide. | |||||
* | |||||
* It allows to deduce the function type of the delegate directly from a | |||||
* function provided to the constructor. | |||||
* | |||||
* @tparam Function A valid free function pointer. | |||||
*/ | |||||
template<auto Function> | |||||
delegate(connect_arg_t<Function>) ENTT_NOEXCEPT | |||||
-> delegate<std::remove_pointer_t<decltype(internal::to_function_pointer(Function))>>; | |||||
/** | |||||
* @brief Deduction guide. | |||||
* | |||||
* It allows to deduce the function type of the delegate directly from a member | |||||
* or a free function with payload provided to the constructor. | |||||
* | |||||
* @tparam Candidate Member or free function to connect to the delegate. | |||||
* @tparam Type Type of class or type of payload. | |||||
*/ | |||||
template<auto Candidate, typename Type> | |||||
delegate(connect_arg_t<Candidate>, Type *) ENTT_NOEXCEPT | |||||
-> delegate<std::remove_pointer_t<decltype(internal::to_function_pointer(Candidate, std::declval<Type *>()))>>; | |||||
} | |||||
#endif // ENTT_SIGNAL_DELEGATE_HPP |
@ -0,0 +1,247 @@ | |||||
#ifndef ENTT_SIGNAL_DISPATCHER_HPP | |||||
#define ENTT_SIGNAL_DISPATCHER_HPP | |||||
#include <vector> | |||||
#include <memory> | |||||
#include <utility> | |||||
#include <type_traits> | |||||
#include "../config/config.h" | |||||
#include "../core/family.hpp" | |||||
#include "../core/type_traits.hpp" | |||||
#include "sigh.hpp" | |||||
namespace entt { | |||||
/** | |||||
* @brief Basic dispatcher implementation. | |||||
* | |||||
* A dispatcher can be used either to trigger an immediate event or to enqueue | |||||
* events to be published all together once per tick.<br/> | |||||
* Listeners are provided in the form of member functions. For each event of | |||||
* type `Event`, listeners are such that they can be invoked with an argument of | |||||
* type `const Event &`, no matter what the return type is. | |||||
* | |||||
* The type of the instances is `Class *` (a naked pointer). It means that users | |||||
* must guarantee that the lifetimes of the instances overcome the one of the | |||||
* dispatcher itself to avoid crashes. | |||||
*/ | |||||
class dispatcher { | |||||
using event_family = family<struct internal_dispatcher_event_family>; | |||||
template<typename Class, typename Event> | |||||
using instance_type = typename sigh<void(const Event &)>::template instance_type<Class>; | |||||
struct base_wrapper { | |||||
virtual ~base_wrapper() = default; | |||||
virtual void publish() = 0; | |||||
}; | |||||
template<typename Event> | |||||
struct signal_wrapper: base_wrapper { | |||||
using signal_type = sigh<void(const Event &)>; | |||||
using sink_type = typename signal_type::sink_type; | |||||
void publish() override { | |||||
for(const auto &event: events[current]) { | |||||
signal.publish(event); | |||||
} | |||||
events[current++].clear(); | |||||
current %= std::extent<decltype(events)>::value; | |||||
} | |||||
inline sink_type sink() ENTT_NOEXCEPT { | |||||
return signal.sink(); | |||||
} | |||||
template<typename... Args> | |||||
inline void trigger(Args &&... args) { | |||||
signal.publish({ std::forward<Args>(args)... }); | |||||
} | |||||
template<typename... Args> | |||||
inline void enqueue(Args &&... args) { | |||||
events[current].emplace_back(std::forward<Args>(args)...); | |||||
} | |||||
private: | |||||
signal_type signal{}; | |||||
std::vector<Event> events[2]; | |||||
int current{}; | |||||
}; | |||||
struct wrapper_data { | |||||
std::unique_ptr<base_wrapper> wrapper; | |||||
ENTT_ID_TYPE runtime_type; | |||||
}; | |||||
template<typename Event> | |||||
static auto type() ENTT_NOEXCEPT { | |||||
if constexpr(is_named_type_v<Event>) { | |||||
return named_type_traits<Event>::value; | |||||
} else { | |||||
return event_family::type<Event>; | |||||
} | |||||
} | |||||
template<typename Event> | |||||
signal_wrapper<Event> & assure() { | |||||
const auto wtype = type<Event>(); | |||||
wrapper_data *wdata = nullptr; | |||||
if constexpr(is_named_type_v<Event>) { | |||||
const auto it = std::find_if(wrappers.begin(), wrappers.end(), [wtype](const auto &candidate) { | |||||
return candidate.wrapper && candidate.runtime_type == wtype; | |||||
}); | |||||
wdata = (it == wrappers.cend() ? &wrappers.emplace_back() : &(*it)); | |||||
} else { | |||||
if(!(wtype < wrappers.size())) { | |||||
wrappers.resize(wtype+1); | |||||
} | |||||
wdata = &wrappers[wtype]; | |||||
if(wdata->wrapper && wdata->runtime_type != wtype) { | |||||
wrappers.emplace_back(); | |||||
std::swap(wrappers[wtype], wrappers.back()); | |||||
wdata = &wrappers[wtype]; | |||||
} | |||||
} | |||||
if(!wdata->wrapper) { | |||||
wdata->wrapper = std::make_unique<signal_wrapper<Event>>(); | |||||
wdata->runtime_type = wtype; | |||||
} | |||||
return static_cast<signal_wrapper<Event> &>(*wdata->wrapper); | |||||
} | |||||
public: | |||||
/*! @brief Type of sink for the given event. */ | |||||
template<typename Event> | |||||
using sink_type = typename signal_wrapper<Event>::sink_type; | |||||
/** | |||||
* @brief Returns a sink object for the given event. | |||||
* | |||||
* A sink is an opaque object used to connect listeners to events. | |||||
* | |||||
* The function type for a listener is: | |||||
* @code{.cpp} | |||||
* void(const Event &); | |||||
* @endcode | |||||
* | |||||
* The order of invocation of the listeners isn't guaranteed. | |||||
* | |||||
* @sa sink | |||||
* | |||||
* @tparam Event Type of event of which to get the sink. | |||||
* @return A temporary sink object. | |||||
*/ | |||||
template<typename Event> | |||||
inline sink_type<Event> sink() ENTT_NOEXCEPT { | |||||
return assure<Event>().sink(); | |||||
} | |||||
/** | |||||
* @brief Triggers an immediate event of the given type. | |||||
* | |||||
* All the listeners registered for the given type are immediately notified. | |||||
* The event is discarded after the execution. | |||||
* | |||||
* @tparam Event Type of event to trigger. | |||||
* @tparam Args Types of arguments to use to construct the event. | |||||
* @param args Arguments to use to construct the event. | |||||
*/ | |||||
template<typename Event, typename... Args> | |||||
inline void trigger(Args &&... args) { | |||||
assure<Event>().trigger(std::forward<Args>(args)...); | |||||
} | |||||
/** | |||||
* @brief Triggers an immediate event of the given type. | |||||
* | |||||
* All the listeners registered for the given type are immediately notified. | |||||
* The event is discarded after the execution. | |||||
* | |||||
* @tparam Event Type of event to trigger. | |||||
* @param event An instance of the given type of event. | |||||
*/ | |||||
template<typename Event> | |||||
inline void trigger(Event &&event) { | |||||
assure<std::decay_t<Event>>().trigger(std::forward<Event>(event)); | |||||
} | |||||
/** | |||||
* @brief Enqueues an event of the given type. | |||||
* | |||||
* An event of the given type is queued. No listener is invoked. Use the | |||||
* `update` member function to notify listeners when ready. | |||||
* | |||||
* @tparam Event Type of event to enqueue. | |||||
* @tparam Args Types of arguments to use to construct the event. | |||||
* @param args Arguments to use to construct the event. | |||||
*/ | |||||
template<typename Event, typename... Args> | |||||
inline void enqueue(Args &&... args) { | |||||
assure<Event>().enqueue(std::forward<Args>(args)...); | |||||
} | |||||
/** | |||||
* @brief Enqueues an event of the given type. | |||||
* | |||||
* An event of the given type is queued. No listener is invoked. Use the | |||||
* `update` member function to notify listeners when ready. | |||||
* | |||||
* @tparam Event Type of event to enqueue. | |||||
* @param event An instance of the given type of event. | |||||
*/ | |||||
template<typename Event> | |||||
inline void enqueue(Event &&event) { | |||||
assure<std::decay_t<Event>>().enqueue(std::forward<Event>(event)); | |||||
} | |||||
/** | |||||
* @brief Delivers all the pending events of the given type. | |||||
* | |||||
* This method is blocking and it doesn't return until all the events are | |||||
* delivered to the registered listeners. It's responsibility of the users | |||||
* to reduce at a minimum the time spent in the bodies of the listeners. | |||||
* | |||||
* @tparam Event Type of events to send. | |||||
*/ | |||||
template<typename Event> | |||||
inline void update() { | |||||
assure<Event>().publish(); | |||||
} | |||||
/** | |||||
* @brief Delivers all the pending events. | |||||
* | |||||
* This method is blocking and it doesn't return until all the events are | |||||
* delivered to the registered listeners. It's responsibility of the users | |||||
* to reduce at a minimum the time spent in the bodies of the listeners. | |||||
*/ | |||||
inline void update() const { | |||||
for(auto pos = wrappers.size(); pos; --pos) { | |||||
auto &wdata = wrappers[pos-1]; | |||||
if(wdata.wrapper) { | |||||
wdata.wrapper->publish(); | |||||
} | |||||
} | |||||
} | |||||
private: | |||||
std::vector<wrapper_data> wrappers; | |||||
}; | |||||
} | |||||
#endif // ENTT_SIGNAL_DISPATCHER_HPP |
@ -0,0 +1,343 @@ | |||||
#ifndef ENTT_SIGNAL_EMITTER_HPP | |||||
#define ENTT_SIGNAL_EMITTER_HPP | |||||
#include <type_traits> | |||||
#include <functional> | |||||
#include <algorithm> | |||||
#include <utility> | |||||
#include <memory> | |||||
#include <vector> | |||||
#include <list> | |||||
#include "../config/config.h" | |||||
#include "../core/family.hpp" | |||||
#include "../core/type_traits.hpp" | |||||
namespace entt { | |||||
/** | |||||
* @brief General purpose event emitter. | |||||
* | |||||
* The emitter class template follows the CRTP idiom. To create a custom emitter | |||||
* type, derived classes must inherit directly from the base class as: | |||||
* | |||||
* @code{.cpp} | |||||
* struct my_emitter: emitter<my_emitter> { | |||||
* // ... | |||||
* } | |||||
* @endcode | |||||
* | |||||
* Handlers for the type of events are created internally on the fly. It's not | |||||
* required to specify in advance the full list of accepted types.<br/> | |||||
* Moreover, 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. | |||||
* | |||||
* @tparam Derived Actual type of emitter that extends the class template. | |||||
*/ | |||||
template<typename Derived> | |||||
class emitter { | |||||
using handler_family = family<struct internal_emitter_handler_family>; | |||||
struct base_handler { | |||||
virtual ~base_handler() = default; | |||||
virtual bool empty() const ENTT_NOEXCEPT = 0; | |||||
virtual void clear() ENTT_NOEXCEPT = 0; | |||||
}; | |||||
template<typename Event> | |||||
struct event_handler: base_handler { | |||||
using listener_type = std::function<void(const Event &, Derived &)>; | |||||
using element_type = std::pair<bool, listener_type>; | |||||
using container_type = std::list<element_type>; | |||||
using connection_type = typename container_type::iterator; | |||||
bool empty() const ENTT_NOEXCEPT override { | |||||
auto pred = [](auto &&element) { return element.first; }; | |||||
return std::all_of(once_list.cbegin(), once_list.cend(), pred) && | |||||
std::all_of(on_list.cbegin(), on_list.cend(), pred); | |||||
} | |||||
void clear() ENTT_NOEXCEPT override { | |||||
if(publishing) { | |||||
auto func = [](auto &&element) { element.first = true; }; | |||||
std::for_each(once_list.begin(), once_list.end(), func); | |||||
std::for_each(on_list.begin(), on_list.end(), func); | |||||
} else { | |||||
once_list.clear(); | |||||
on_list.clear(); | |||||
} | |||||
} | |||||
inline connection_type once(listener_type listener) { | |||||
return once_list.emplace(once_list.cend(), false, std::move(listener)); | |||||
} | |||||
inline connection_type on(listener_type listener) { | |||||
return on_list.emplace(on_list.cend(), false, std::move(listener)); | |||||
} | |||||
void erase(connection_type conn) ENTT_NOEXCEPT { | |||||
conn->first = true; | |||||
if(!publishing) { | |||||
auto pred = [](auto &&element) { return element.first; }; | |||||
once_list.remove_if(pred); | |||||
on_list.remove_if(pred); | |||||
} | |||||
} | |||||
void publish(const Event &event, Derived &ref) { | |||||
container_type swap_list; | |||||
once_list.swap(swap_list); | |||||
auto func = [&event, &ref](auto &&element) { | |||||
return element.first ? void() : element.second(event, ref); | |||||
}; | |||||
publishing = true; | |||||
std::for_each(on_list.rbegin(), on_list.rend(), func); | |||||
std::for_each(swap_list.rbegin(), swap_list.rend(), func); | |||||
publishing = false; | |||||
on_list.remove_if([](auto &&element) { return element.first; }); | |||||
} | |||||
private: | |||||
bool publishing{false}; | |||||
container_type once_list{}; | |||||
container_type on_list{}; | |||||
}; | |||||
struct handler_data { | |||||
std::unique_ptr<base_handler> handler; | |||||
ENTT_ID_TYPE runtime_type; | |||||
}; | |||||
template<typename Event> | |||||
static auto type() ENTT_NOEXCEPT { | |||||
if constexpr(is_named_type_v<Event>) { | |||||
return named_type_traits<Event>::value; | |||||
} else { | |||||
return handler_family::type<Event>; | |||||
} | |||||
} | |||||
template<typename Event> | |||||
event_handler<Event> * assure() const ENTT_NOEXCEPT { | |||||
const auto htype = type<Event>(); | |||||
handler_data *hdata = nullptr; | |||||
if constexpr(is_named_type_v<Event>) { | |||||
const auto it = std::find_if(handlers.begin(), handlers.end(), [htype](const auto &candidate) { | |||||
return candidate.handler && candidate.runtime_type == htype; | |||||
}); | |||||
hdata = (it == handlers.cend() ? &handlers.emplace_back() : &(*it)); | |||||
} else { | |||||
if(!(htype < handlers.size())) { | |||||
handlers.resize(htype+1); | |||||
} | |||||
hdata = &handlers[htype]; | |||||
if(hdata->handler && hdata->runtime_type != htype) { | |||||
handlers.emplace_back(); | |||||
std::swap(handlers[htype], handlers.back()); | |||||
hdata = &handlers[htype]; | |||||
} | |||||
} | |||||
if(!hdata->handler) { | |||||
hdata->handler = std::make_unique<event_handler<Event>>(); | |||||
hdata->runtime_type = htype; | |||||
} | |||||
return static_cast<event_handler<Event> *>(hdata->handler.get()); | |||||
} | |||||
public: | |||||
/** @brief Type of listeners accepted for the given event. */ | |||||
template<typename Event> | |||||
using listener = typename event_handler<Event>::listener_type; | |||||
/** | |||||
* @brief Generic connection type for events. | |||||
* | |||||
* Type of the connection object returned by the event emitter whenever a | |||||
* listener for the given type is registered.<br/> | |||||
* It can be used to break connections still in use. | |||||
* | |||||
* @tparam Event Type of event for which the connection is created. | |||||
*/ | |||||
template<typename Event> | |||||
struct connection: private event_handler<Event>::connection_type { | |||||
/** @brief Event emitters are friend classes of connections. */ | |||||
friend class emitter; | |||||
/*! @brief Default constructor. */ | |||||
connection() ENTT_NOEXCEPT = default; | |||||
/** | |||||
* @brief Creates a connection that wraps its underlying instance. | |||||
* @param conn A connection object to wrap. | |||||
*/ | |||||
connection(typename event_handler<Event>::connection_type conn) | |||||
: event_handler<Event>::connection_type{std::move(conn)} | |||||
{} | |||||
}; | |||||
/*! @brief Default constructor. */ | |||||
emitter() ENTT_NOEXCEPT = default; | |||||
/*! @brief Default destructor. */ | |||||
virtual ~emitter() ENTT_NOEXCEPT { | |||||
static_assert(std::is_base_of_v<emitter<Derived>, Derived>); | |||||
} | |||||
/*! @brief Default move constructor. */ | |||||
emitter(emitter &&) = default; | |||||
/*! @brief Default move assignment operator. @return This emitter. */ | |||||
emitter & operator=(emitter &&) = default; | |||||
/** | |||||
* @brief Emits the given event. | |||||
* | |||||
* All the listeners registered for the specific event type are invoked with | |||||
* the given event. The event type must either have a proper constructor for | |||||
* the arguments provided or be an aggregate type. | |||||
* | |||||
* @tparam Event Type of event to publish. | |||||
* @tparam Args Types of arguments to use to construct the event. | |||||
* @param args Parameters to use to initialize the event. | |||||
*/ | |||||
template<typename Event, typename... Args> | |||||
void publish(Args &&... args) { | |||||
assure<Event>()->publish({ std::forward<Args>(args)... }, *static_cast<Derived *>(this)); | |||||
} | |||||
/** | |||||
* @brief Registers a long-lived listener with the event emitter. | |||||
* | |||||
* This method can be used to register a listener designed to be invoked | |||||
* more than once for the given event type.<br/> | |||||
* The connection returned by the method can be freely discarded. It's meant | |||||
* to be used later to disconnect the listener if required. | |||||
* | |||||
* The listener is as a callable object that can be moved and the type of | |||||
* which is `void(const Event &, Derived &)`. | |||||
* | |||||
* @note | |||||
* Whenever an event is emitted, the emitter provides the listener with a | |||||
* reference to the derived class. Listeners don't have to capture those | |||||
* instances for later uses. | |||||
* | |||||
* @tparam Event Type of event to which to connect the listener. | |||||
* @param instance The listener to register. | |||||
* @return Connection object that can be used to disconnect the listener. | |||||
*/ | |||||
template<typename Event> | |||||
connection<Event> on(listener<Event> instance) { | |||||
return assure<Event>()->on(std::move(instance)); | |||||
} | |||||
/** | |||||
* @brief Registers a short-lived listener with the event emitter. | |||||
* | |||||
* This method can be used to register a listener designed to be invoked | |||||
* only once for the given event type.<br/> | |||||
* The connection returned by the method can be freely discarded. It's meant | |||||
* to be used later to disconnect the listener if required. | |||||
* | |||||
* The listener is as a callable object that can be moved and the type of | |||||
* which is `void(const Event &, Derived &)`. | |||||
* | |||||
* @note | |||||
* Whenever an event is emitted, the emitter provides the listener with a | |||||
* reference to the derived class. Listeners don't have to capture those | |||||
* instances for later uses. | |||||
* | |||||
* @tparam Event Type of event to which to connect the listener. | |||||
* @param instance The listener to register. | |||||
* @return Connection object that can be used to disconnect the listener. | |||||
*/ | |||||
template<typename Event> | |||||
connection<Event> once(listener<Event> instance) { | |||||
return assure<Event>()->once(std::move(instance)); | |||||
} | |||||
/** | |||||
* @brief Disconnects a listener from the event emitter. | |||||
* | |||||
* Do not use twice the same connection to disconnect a listener, it results | |||||
* in undefined behavior. Once used, discard the connection object. | |||||
* | |||||
* @tparam Event Type of event of the connection. | |||||
* @param conn A valid connection. | |||||
*/ | |||||
template<typename Event> | |||||
void erase(connection<Event> conn) ENTT_NOEXCEPT { | |||||
assure<Event>()->erase(std::move(conn)); | |||||
} | |||||
/** | |||||
* @brief Disconnects all the listeners for the given event type. | |||||
* | |||||
* All the connections previously returned for the given event are | |||||
* invalidated. Using them results in undefined behavior. | |||||
* | |||||
* @tparam Event Type of event to reset. | |||||
*/ | |||||
template<typename Event> | |||||
void clear() ENTT_NOEXCEPT { | |||||
assure<Event>()->clear(); | |||||
} | |||||
/** | |||||
* @brief Disconnects all the listeners. | |||||
* | |||||
* All the connections previously returned are invalidated. Using them | |||||
* results in undefined behavior. | |||||
*/ | |||||
void clear() ENTT_NOEXCEPT { | |||||
std::for_each(handlers.begin(), handlers.end(), [](auto &&hdata) { | |||||
return hdata.handler ? hdata.handler->clear() : void(); | |||||
}); | |||||
} | |||||
/** | |||||
* @brief Checks if there are listeners registered for the specific event. | |||||
* @tparam Event Type of event to test. | |||||
* @return True if there are no listeners registered, false otherwise. | |||||
*/ | |||||
template<typename Event> | |||||
bool empty() const ENTT_NOEXCEPT { | |||||
return assure<Event>()->empty(); | |||||
} | |||||
/** | |||||
* @brief Checks if there are listeners registered with the event emitter. | |||||
* @return True if there are no listeners registered, false otherwise. | |||||
*/ | |||||
bool empty() const ENTT_NOEXCEPT { | |||||
return std::all_of(handlers.cbegin(), handlers.cend(), [](auto &&hdata) { | |||||
return !hdata.handler || hdata.handler->empty(); | |||||
}); | |||||
} | |||||
private: | |||||
mutable std::vector<handler_data> handlers{}; | |||||
}; | |||||
} | |||||
#endif // ENTT_SIGNAL_EMITTER_HPP |
@ -0,0 +1,27 @@ | |||||
#ifndef ENTT_SIGNAL_FWD_HPP | |||||
#define ENTT_SIGNAL_FWD_HPP | |||||
#include "../config/config.h" | |||||
namespace entt { | |||||
/*! @class delegate */ | |||||
template<typename> | |||||
class delegate; | |||||
/*! @class sink */ | |||||
template<typename> | |||||
class sink; | |||||
/*! @class sigh */ | |||||
template<typename, typename> | |||||
struct sigh; | |||||
} | |||||
#endif // ENTT_SIGNAL_FWD_HPP |
@ -0,0 +1,357 @@ | |||||
#ifndef ENTT_SIGNAL_SIGH_HPP | |||||
#define ENTT_SIGNAL_SIGH_HPP | |||||
#include <algorithm> | |||||
#include <utility> | |||||
#include <vector> | |||||
#include <functional> | |||||
#include <type_traits> | |||||
#include "../config/config.h" | |||||
#include "delegate.hpp" | |||||
#include "fwd.hpp" | |||||
namespace entt { | |||||
/** | |||||
* @cond TURN_OFF_DOXYGEN | |||||
* Internal details not to be documented. | |||||
*/ | |||||
namespace internal { | |||||
template<typename, typename> | |||||
struct invoker; | |||||
template<typename Ret, typename... Args, typename Collector> | |||||
struct invoker<Ret(Args...), Collector> { | |||||
virtual ~invoker() = default; | |||||
bool invoke(Collector &collector, const delegate<Ret(Args...)> &delegate, Args... args) const { | |||||
return collector(delegate(args...)); | |||||
} | |||||
}; | |||||
template<typename... Args, typename Collector> | |||||
struct invoker<void(Args...), Collector> { | |||||
virtual ~invoker() = default; | |||||
bool invoke(Collector &, const delegate<void(Args...)> &delegate, Args... args) const { | |||||
return (delegate(args...), true); | |||||
} | |||||
}; | |||||
template<typename Ret> | |||||
struct null_collector { | |||||
using result_type = Ret; | |||||
bool operator()(result_type) const ENTT_NOEXCEPT { return true; } | |||||
}; | |||||
template<> | |||||
struct null_collector<void> { | |||||
using result_type = void; | |||||
bool operator()() const ENTT_NOEXCEPT { return true; } | |||||
}; | |||||
template<typename> | |||||
struct default_collector; | |||||
template<typename Ret, typename... Args> | |||||
struct default_collector<Ret(Args...)> { | |||||
using collector_type = null_collector<Ret>; | |||||
}; | |||||
template<typename Function> | |||||
using default_collector_type = typename default_collector<Function>::collector_type; | |||||
} | |||||
/** | |||||
* Internal details not to be documented. | |||||
* @endcond TURN_OFF_DOXYGEN | |||||
*/ | |||||
/** | |||||
* @brief Sink implementation. | |||||
* | |||||
* Primary template isn't defined on purpose. All the specializations give a | |||||
* compile-time error unless the template parameter is a function type. | |||||
* | |||||
* @tparam Function A valid function type. | |||||
*/ | |||||
template<typename Function> | |||||
class sink; | |||||
/** | |||||
* @brief Unmanaged signal handler declaration. | |||||
* | |||||
* Primary template isn't defined on purpose. All the specializations give a | |||||
* compile-time error unless the template parameter is a function type. | |||||
* | |||||
* @tparam Function A valid function type. | |||||
* @tparam Collector Type of collector to use, if any. | |||||
*/ | |||||
template<typename Function, typename Collector = internal::default_collector_type<Function>> | |||||
struct sigh; | |||||
/** | |||||
* @brief Sink implementation. | |||||
* | |||||
* A sink is an opaque object used to connect listeners to signals.<br/> | |||||
* The function type for a listener is the one of the signal to which it | |||||
* belongs. | |||||
* | |||||
* The clear separation between a signal and a sink permits to store the former | |||||
* as private data member without exposing the publish functionality to the | |||||
* users of a class. | |||||
* | |||||
* @tparam Ret Return type of a function type. | |||||
* @tparam Args Types of arguments of a function type. | |||||
*/ | |||||
template<typename Ret, typename... Args> | |||||
class sink<Ret(Args...)> { | |||||
/*! @brief A signal is allowed to create sinks. */ | |||||
template<typename, typename> | |||||
friend struct sigh; | |||||
template<typename Type> | |||||
Type * payload_type(Ret(*)(Type *, Args...)); | |||||
sink(std::vector<delegate<Ret(Args...)>> *ref) ENTT_NOEXCEPT | |||||
: calls{ref} | |||||
{} | |||||
public: | |||||
/** | |||||
* @brief Returns false if at least a listener is connected to the sink. | |||||
* @return True if the sink has no listeners connected, false otherwise. | |||||
*/ | |||||
bool empty() const ENTT_NOEXCEPT { | |||||
return calls->empty(); | |||||
} | |||||
/** | |||||
* @brief Connects a free function to a signal. | |||||
* | |||||
* The signal handler performs checks to avoid multiple connections for free | |||||
* functions. | |||||
* | |||||
* @tparam Function A valid free function pointer. | |||||
*/ | |||||
template<auto Function> | |||||
void connect() { | |||||
disconnect<Function>(); | |||||
delegate<Ret(Args...)> delegate{}; | |||||
delegate.template connect<Function>(); | |||||
calls->emplace_back(std::move(delegate)); | |||||
} | |||||
/** | |||||
* @brief Connects a member function or a free function with payload to a | |||||
* signal. | |||||
* | |||||
* The signal isn't responsible for the connected object or the payload. | |||||
* Users must always guarantee that the lifetime of the instance overcomes | |||||
* the one of the delegate. On the other side, the signal handler performs | |||||
* checks to avoid multiple connections for the same function.<br/> | |||||
* When used to connect a free function with payload, its signature must be | |||||
* such that the instance is the first argument before the ones used to | |||||
* define the delegate itself. | |||||
* | |||||
* @tparam Candidate Member or free function to connect to the delegate. | |||||
* @tparam Type Type of class or type of payload. | |||||
* @param value_or_instance A valid pointer that fits the purpose. | |||||
*/ | |||||
template<auto Candidate, typename Type> | |||||
void connect(Type *value_or_instance) { | |||||
if constexpr(std::is_member_function_pointer_v<decltype(Candidate)>) { | |||||
disconnect<Candidate>(value_or_instance); | |||||
} else { | |||||
disconnect<Candidate>(); | |||||
} | |||||
delegate<Ret(Args...)> delegate{}; | |||||
delegate.template connect<Candidate>(value_or_instance); | |||||
calls->emplace_back(std::move(delegate)); | |||||
} | |||||
/** | |||||
* @brief Disconnects a free function from a signal. | |||||
* @tparam Function A valid free function pointer. | |||||
*/ | |||||
template<auto Function> | |||||
void disconnect() { | |||||
delegate<Ret(Args...)> delegate{}; | |||||
if constexpr(std::is_invocable_r_v<Ret, decltype(Function), Args...>) { | |||||
delegate.template connect<Function>(); | |||||
} else { | |||||
decltype(payload_type(Function)) payload = nullptr; | |||||
delegate.template connect<Function>(payload); | |||||
} | |||||
calls->erase(std::remove(calls->begin(), calls->end(), std::move(delegate)), calls->end()); | |||||
} | |||||
/** | |||||
* @brief Disconnects a given member function from a signal. | |||||
* @tparam Member Member function to disconnect from the signal. | |||||
* @tparam Class Type of class to which the member function belongs. | |||||
* @param instance A valid instance of type pointer to `Class`. | |||||
*/ | |||||
template<auto Member, typename Class> | |||||
void disconnect(Class *instance) { | |||||
static_assert(std::is_member_function_pointer_v<decltype(Member)>); | |||||
delegate<Ret(Args...)> delegate{}; | |||||
delegate.template connect<Member>(instance); | |||||
calls->erase(std::remove_if(calls->begin(), calls->end(), [&delegate](const auto &other) { | |||||
return other == delegate && other.instance() == delegate.instance(); | |||||
}), calls->end()); | |||||
} | |||||
/** | |||||
* @brief Disconnects all the listeners from a signal. | |||||
*/ | |||||
void disconnect() { | |||||
calls->clear(); | |||||
} | |||||
private: | |||||
std::vector<delegate<Ret(Args...)>> *calls; | |||||
}; | |||||
/** | |||||
* @brief Unmanaged signal handler definition. | |||||
* | |||||
* Unmanaged signal handler. It works directly with naked pointers to classes | |||||
* and pointers to member functions as well as pointers to free functions. Users | |||||
* of this class are in charge of disconnecting instances before deleting them. | |||||
* | |||||
* This class serves mainly two purposes: | |||||
* | |||||
* * Creating signals used later to notify a bunch of listeners. | |||||
* * Collecting results from a set of functions like in a voting system. | |||||
* | |||||
* The default collector does nothing. To properly collect data, define and use | |||||
* a class that has a call operator the signature of which is `bool(Param)` and: | |||||
* | |||||
* * `Param` is a type to which `Ret` can be converted. | |||||
* * The return type is true if the handler must stop collecting data, false | |||||
* otherwise. | |||||
* | |||||
* @tparam Ret Return type of a function type. | |||||
* @tparam Args Types of arguments of a function type. | |||||
* @tparam Collector Type of collector to use, if any. | |||||
*/ | |||||
template<typename Ret, typename... Args, typename Collector> | |||||
struct sigh<Ret(Args...), Collector>: private internal::invoker<Ret(Args...), Collector> { | |||||
/*! @brief Unsigned integer type. */ | |||||
using size_type = typename std::vector<delegate<Ret(Args...)>>::size_type; | |||||
/*! @brief Collector type. */ | |||||
using collector_type = Collector; | |||||
/*! @brief Sink type. */ | |||||
using sink_type = entt::sink<Ret(Args...)>; | |||||
/** | |||||
* @brief Instance type when it comes to connecting member functions. | |||||
* @tparam Class Type of class to which the member function belongs. | |||||
*/ | |||||
template<typename Class> | |||||
using instance_type = Class *; | |||||
/** | |||||
* @brief Number of listeners connected to the signal. | |||||
* @return Number of listeners currently connected. | |||||
*/ | |||||
size_type size() const ENTT_NOEXCEPT { | |||||
return calls.size(); | |||||
} | |||||
/** | |||||
* @brief Returns false if at least a listener is connected to the signal. | |||||
* @return True if the signal has no listeners connected, false otherwise. | |||||
*/ | |||||
bool empty() const ENTT_NOEXCEPT { | |||||
return calls.empty(); | |||||
} | |||||
/** | |||||
* @brief Returns a sink object for the given signal. | |||||
* | |||||
* A sink is an opaque object used to connect listeners to signals.<br/> | |||||
* The function type for a listener is the one of the signal to which it | |||||
* belongs. The order of invocation of the listeners isn't guaranteed. | |||||
* | |||||
* @return A temporary sink object. | |||||
*/ | |||||
sink_type sink() ENTT_NOEXCEPT { | |||||
return { &calls }; | |||||
} | |||||
/** | |||||
* @brief Triggers a signal. | |||||
* | |||||
* All the listeners are notified. Order isn't guaranteed. | |||||
* | |||||
* @param args Arguments to use to invoke listeners. | |||||
*/ | |||||
void publish(Args... args) const { | |||||
for(auto pos = calls.size(); pos; --pos) { | |||||
auto &call = calls[pos-1]; | |||||
call(args...); | |||||
} | |||||
} | |||||
/** | |||||
* @brief Collects return values from the listeners. | |||||
* @param args Arguments to use to invoke listeners. | |||||
* @return An instance of the collector filled with collected data. | |||||
*/ | |||||
collector_type collect(Args... args) const { | |||||
collector_type collector; | |||||
for(auto &&call: calls) { | |||||
if(!this->invoke(collector, call, args...)) { | |||||
break; | |||||
} | |||||
} | |||||
return collector; | |||||
} | |||||
/** | |||||
* @brief Swaps listeners between the two signals. | |||||
* @param lhs A valid signal object. | |||||
* @param rhs A valid signal object. | |||||
*/ | |||||
friend void swap(sigh &lhs, sigh &rhs) { | |||||
using std::swap; | |||||
swap(lhs.calls, rhs.calls); | |||||
} | |||||
private: | |||||
std::vector<delegate<Ret(Args...)>> calls; | |||||
}; | |||||
} | |||||
#endif // ENTT_SIGNAL_SIGH_HPP |
@ -0,0 +1,129 @@ | |||||
# | |||||
# Tests configuration | |||||
# | |||||
include_directories($<TARGET_PROPERTY:EnTT,INTERFACE_INCLUDE_DIRECTORIES>) | |||||
add_compile_options($<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_OPTIONS>) | |||||
macro(SETUP_LIBRARY_TARGET LIB_TARGET) | |||||
set_target_properties(${LIB_TARGET} PROPERTIES CXX_EXTENSIONS OFF) | |||||
target_compile_definitions(${LIB_TARGET} PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_DEFINITIONS>) | |||||
target_compile_features(${LIB_TARGET} PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_FEATURES>) | |||||
target_compile_options(${LIB_TARGET} PRIVATE $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-pedantic -Wall>) | |||||
target_compile_options(${LIB_TARGET} PRIVATE $<$<CXX_COMPILER_ID:MSVC>:/EHsc>) | |||||
endmacro() | |||||
add_library(odr OBJECT odr.cpp) | |||||
SETUP_LIBRARY_TARGET(odr) | |||||
macro(SETUP_AND_ADD_TEST TEST_NAME TEST_SOURCE) | |||||
add_executable(${TEST_NAME} $<TARGET_OBJECTS:odr> ${TEST_SOURCE}) | |||||
set_target_properties(${TEST_NAME} PROPERTIES CXX_EXTENSIONS OFF) | |||||
target_link_libraries(${TEST_NAME} PRIVATE EnTT GTest::Main Threads::Threads) | |||||
target_compile_definitions(${TEST_NAME} PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_DEFINITIONS>) | |||||
target_compile_features(${TEST_NAME} PRIVATE $<TARGET_PROPERTY:EnTT,INTERFACE_COMPILE_FEATURES>) | |||||
target_compile_options(${TEST_NAME} PRIVATE $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-pedantic -Wall>) | |||||
target_compile_options(${TEST_NAME} PRIVATE $<$<CXX_COMPILER_ID:MSVC>:/EHsc>) | |||||
add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}) | |||||
endmacro() | |||||
# Test benchmark | |||||
if(BUILD_BENCHMARK) | |||||
SETUP_AND_ADD_TEST(benchmark benchmark/benchmark.cpp) | |||||
endif() | |||||
# Test lib | |||||
if(BUILD_LIB) | |||||
add_library(a_module_shared SHARED lib/a_module.cpp) | |||||
add_library(a_module_static STATIC lib/a_module.cpp) | |||||
add_library(another_module_shared SHARED lib/another_module.cpp) | |||||
add_library(another_module_static STATIC lib/another_module.cpp) | |||||
SETUP_LIBRARY_TARGET(a_module_shared) | |||||
SETUP_LIBRARY_TARGET(a_module_static) | |||||
SETUP_LIBRARY_TARGET(another_module_shared) | |||||
SETUP_LIBRARY_TARGET(another_module_static) | |||||
SETUP_AND_ADD_TEST(lib_shared lib/lib.cpp) | |||||
target_link_libraries(lib_shared PRIVATE a_module_shared another_module_shared) | |||||
SETUP_AND_ADD_TEST(lib_static lib/lib.cpp) | |||||
target_link_libraries(lib_static PRIVATE a_module_static another_module_static) | |||||
endif() | |||||
# Test mod | |||||
if(BUILD_MOD) | |||||
set(DUKTAPE_DEPS_DIR ${EnTT_SOURCE_DIR}/deps/duktape) | |||||
configure_file(${EnTT_SOURCE_DIR}/cmake/in/duktape.in ${DUKTAPE_DEPS_DIR}/CMakeLists.txt) | |||||
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . WORKING_DIRECTORY ${DUKTAPE_DEPS_DIR}) | |||||
execute_process(COMMAND ${CMAKE_COMMAND} --build . WORKING_DIRECTORY ${DUKTAPE_DEPS_DIR}) | |||||
set(DUKTAPE_SRC_DIR ${DUKTAPE_DEPS_DIR}/src/src) | |||||
set(MOD_TEST_SOURCE ${DUKTAPE_SRC_DIR}/duktape.c mod/mod.cpp) | |||||
SETUP_AND_ADD_TEST(mod "${MOD_TEST_SOURCE}") | |||||
target_include_directories(mod PRIVATE ${DUKTAPE_SRC_DIR}) | |||||
endif() | |||||
# Test snapshot | |||||
if(BUILD_SNAPSHOT) | |||||
set(CEREAL_DEPS_DIR ${EnTT_SOURCE_DIR}/deps/cereal) | |||||
configure_file(${EnTT_SOURCE_DIR}/cmake/in/cereal.in ${CEREAL_DEPS_DIR}/CMakeLists.txt) | |||||
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . WORKING_DIRECTORY ${CEREAL_DEPS_DIR}) | |||||
execute_process(COMMAND ${CMAKE_COMMAND} --build . WORKING_DIRECTORY ${CEREAL_DEPS_DIR}) | |||||
set(CEREAL_SRC_DIR ${CEREAL_DEPS_DIR}/src/include) | |||||
SETUP_AND_ADD_TEST(cereal snapshot/snapshot.cpp) | |||||
target_include_directories(cereal PRIVATE ${CEREAL_SRC_DIR}) | |||||
endif() | |||||
# Test core | |||||
SETUP_AND_ADD_TEST(algorithm entt/core/algorithm.cpp) | |||||
SETUP_AND_ADD_TEST(family entt/core/family.cpp) | |||||
SETUP_AND_ADD_TEST(hashed_string entt/core/hashed_string.cpp) | |||||
SETUP_AND_ADD_TEST(ident entt/core/ident.cpp) | |||||
SETUP_AND_ADD_TEST(monostate entt/core/monostate.cpp) | |||||
SETUP_AND_ADD_TEST(type_traits entt/core/type_traits.cpp) | |||||
SETUP_AND_ADD_TEST(utility entt/core/utility.cpp) | |||||
# Test entity | |||||
SETUP_AND_ADD_TEST(actor entt/entity/actor.cpp) | |||||
SETUP_AND_ADD_TEST(entity entt/entity/entity.cpp) | |||||
SETUP_AND_ADD_TEST(group entt/entity/group.cpp) | |||||
SETUP_AND_ADD_TEST(helper entt/entity/helper.cpp) | |||||
SETUP_AND_ADD_TEST(prototype entt/entity/prototype.cpp) | |||||
SETUP_AND_ADD_TEST(registry entt/entity/registry.cpp) | |||||
SETUP_AND_ADD_TEST(runtime_view entt/entity/runtime_view.cpp) | |||||
SETUP_AND_ADD_TEST(snapshot entt/entity/snapshot.cpp) | |||||
SETUP_AND_ADD_TEST(sparse_set entt/entity/sparse_set.cpp) | |||||
SETUP_AND_ADD_TEST(storage entt/entity/storage.cpp) | |||||
SETUP_AND_ADD_TEST(view entt/entity/view.cpp) | |||||
# Test locator | |||||
SETUP_AND_ADD_TEST(locator entt/locator/locator.cpp) | |||||
# Test meta | |||||
SETUP_AND_ADD_TEST(meta entt/meta/meta.cpp) | |||||
# Test process | |||||
SETUP_AND_ADD_TEST(process entt/process/process.cpp) | |||||
SETUP_AND_ADD_TEST(scheduler entt/process/scheduler.cpp) | |||||
# Test resource | |||||
SETUP_AND_ADD_TEST(resource entt/resource/resource.cpp) | |||||
# Test signal | |||||
SETUP_AND_ADD_TEST(delegate entt/signal/delegate.cpp) | |||||
SETUP_AND_ADD_TEST(dispatcher entt/signal/dispatcher.cpp) | |||||
SETUP_AND_ADD_TEST(emitter entt/signal/emitter.cpp) | |||||
SETUP_AND_ADD_TEST(sigh entt/signal/sigh.cpp) |
@ -0,0 +1,33 @@ | |||||
#include <array> | |||||
#include <gtest/gtest.h> | |||||
#include <entt/core/algorithm.hpp> | |||||
TEST(Algorithm, StdSort) { | |||||
// well, I'm pretty sure it works, it's std::sort!! | |||||
std::array<int, 5> arr{{4, 1, 3, 2, 0}}; | |||||
entt::std_sort sort; | |||||
sort(arr.begin(), arr.end()); | |||||
for(typename decltype(arr)::size_type i = 0; i < (arr.size() - 1); ++i) { | |||||
ASSERT_LT(arr[i], arr[i+1]); | |||||
} | |||||
} | |||||
TEST(Algorithm, InsertionSort) { | |||||
std::array<int, 5> arr{{4, 1, 3, 2, 0}}; | |||||
entt::insertion_sort sort; | |||||
sort(arr.begin(), arr.end()); | |||||
for(typename decltype(arr)::size_type i = 0; i < (arr.size() - 1); ++i) { | |||||
ASSERT_LT(arr[i], arr[i+1]); | |||||
} | |||||
} | |||||
TEST(Algorithm, InsertionSortEmptyContainer) { | |||||
std::vector<int> vec{}; | |||||
entt::insertion_sort sort; | |||||
// this should crash with asan enabled if we break the constraint | |||||
sort(vec.begin(), vec.end()); | |||||
} |
@ -0,0 +1,22 @@ | |||||
#include <gtest/gtest.h> | |||||
#include <entt/core/family.hpp> | |||||
using a_family = entt::family<struct a_family_type>; | |||||
using another_family = entt::family<struct another_family_type>; | |||||
TEST(Family, Functionalities) { | |||||
auto t1 = a_family::type<int>; | |||||
auto t2 = a_family::type<int>; | |||||
auto t3 = a_family::type<char>; | |||||
auto t4 = another_family::type<double>; | |||||
ASSERT_EQ(t1, t2); | |||||
ASSERT_NE(t1, t3); | |||||
ASSERT_EQ(t1, t4); | |||||
} | |||||
TEST(Family, Uniqueness) { | |||||
ASSERT_EQ(a_family::type<int>, a_family::type<int &>); | |||||
ASSERT_EQ(a_family::type<int>, a_family::type<int &&>); | |||||
ASSERT_EQ(a_family::type<int>, a_family::type<const int &>); | |||||
} |
@ -0,0 +1,64 @@ | |||||
#include <string> | |||||
#include <string_view> | |||||
#include <type_traits> | |||||
#include <gtest/gtest.h> | |||||
#include <entt/core/hashed_string.hpp> | |||||
TEST(HashedString, Functionalities) { | |||||
using hash_type = entt::hashed_string::hash_type; | |||||
const char *bar = "bar"; | |||||
auto foo_hs = entt::hashed_string{"foo"}; | |||||
auto bar_hs = entt::hashed_string{bar}; | |||||
ASSERT_NE(static_cast<hash_type>(foo_hs), static_cast<hash_type>(bar_hs)); | |||||
ASSERT_STREQ(static_cast<const char *>(foo_hs), "foo"); | |||||
ASSERT_STREQ(static_cast<const char *>(bar_hs), bar); | |||||
ASSERT_STREQ(foo_hs.data(), "foo"); | |||||
ASSERT_STREQ(bar_hs.data(), bar); | |||||
ASSERT_EQ(foo_hs, foo_hs); | |||||
ASSERT_NE(foo_hs, bar_hs); | |||||
entt::hashed_string hs{"foobar"}; | |||||
ASSERT_EQ(static_cast<hash_type>(hs), 0xbf9cf968); | |||||
ASSERT_EQ(hs.value(), 0xbf9cf968); | |||||
ASSERT_EQ(foo_hs, "foo"_hs); | |||||
ASSERT_NE(bar_hs, "foo"_hs); | |||||
} | |||||
TEST(HashedString, Empty) { | |||||
using hash_type = entt::hashed_string::hash_type; | |||||
entt::hashed_string hs{}; | |||||
ASSERT_EQ(static_cast<hash_type>(hs), hash_type{}); | |||||
ASSERT_EQ(static_cast<const char *>(hs), nullptr); | |||||
} | |||||
TEST(HashedString, Constexprness) { | |||||
using hash_type = entt::hashed_string::hash_type; | |||||
// how would you test a constexpr otherwise? | |||||
(void)std::integral_constant<hash_type, entt::hashed_string{"quux"}>{}; | |||||
(void)std::integral_constant<hash_type, "quux"_hs>{}; | |||||
ASSERT_TRUE(true); | |||||
} | |||||
TEST(HashedString, ToValue) { | |||||
using hash_type = entt::hashed_string::hash_type; | |||||
const char *foobar = "foobar"; | |||||
ASSERT_EQ(entt::hashed_string::to_value(foobar), 0xbf9cf968); | |||||
// how would you test a constexpr otherwise? | |||||
(void)std::integral_constant<hash_type, entt::hashed_string::to_value("quux")>{}; | |||||
} | |||||
TEST(HashedString, StringView) { | |||||
std::string str{"__foobar__"}; | |||||
std::string_view view{str.data()+2, 6}; | |||||
ASSERT_EQ(entt::hashed_string::to_value(view.data(), view.size()), 0xbf9cf968); | |||||
} |
@ -0,0 +1,32 @@ | |||||
#include <type_traits> | |||||
#include <gtest/gtest.h> | |||||
#include <entt/core/ident.hpp> | |||||
struct a_type {}; | |||||
struct another_type {}; | |||||
TEST(Identifier, Uniqueness) { | |||||
using id = entt::identifier<a_type, another_type>; | |||||
constexpr a_type an_instance; | |||||
constexpr another_type another_instance; | |||||
ASSERT_NE(id::type<a_type>, id::type<another_type>); | |||||
ASSERT_EQ(id::type<a_type>, id::type<decltype(an_instance)>); | |||||
ASSERT_NE(id::type<a_type>, id::type<decltype(another_instance)>); | |||||
ASSERT_EQ(id::type<a_type>, id::type<a_type>); | |||||
ASSERT_EQ(id::type<another_type>, id::type<another_type>); | |||||
// test uses in constant expressions | |||||
switch(id::type<another_type>) { | |||||
case id::type<a_type>: | |||||
FAIL(); | |||||
case id::type<another_type>: | |||||
SUCCEED(); | |||||
} | |||||
} | |||||
TEST(Identifier, SingleType) { | |||||
using id = entt::identifier<a_type>; | |||||
std::integral_constant<id::identifier_type, id::type<a_type>> ic; | |||||
(void)ic; | |||||
} |
@ -0,0 +1,20 @@ | |||||
#include <gtest/gtest.h> | |||||
#include <entt/core/hashed_string.hpp> | |||||
#include <entt/core/monostate.hpp> | |||||
TEST(Monostate, Functionalities) { | |||||
const bool b_pre = entt::monostate<entt::hashed_string{"foobar"}>{}; | |||||
const int i_pre = entt::monostate<"foobar"_hs>{}; | |||||
ASSERT_FALSE(b_pre); | |||||
ASSERT_EQ(i_pre, int{}); | |||||
entt::monostate<"foobar"_hs>{} = true; | |||||
entt::monostate_v<"foobar"_hs> = 42; | |||||
const bool &b_post = entt::monostate<"foobar"_hs>{}; | |||||
const int &i_post = entt::monostate_v<entt::hashed_string{"foobar"}>; | |||||
ASSERT_TRUE(b_post); | |||||
ASSERT_EQ(i_post, 42); | |||||
} |
@ -0,0 +1,14 @@ | |||||
#include <gtest/gtest.h> | |||||
#include <entt/core/type_traits.hpp> | |||||
TEST(TypeList, Functionalities) { | |||||
using type = entt::type_list<int, char>; | |||||
using other = entt::type_list<double>; | |||||
ASSERT_EQ((type::size), (decltype(type::size){2})); | |||||
ASSERT_EQ((other::size), (decltype(other::size){1})); | |||||
ASSERT_TRUE((std::is_same_v<entt::type_list_cat_t<type, other, type, other>, entt::type_list<int, char, double, int, char, double>>)); | |||||
ASSERT_TRUE((std::is_same_v<entt::type_list_cat_t<type, other>, entt::type_list<int, char, double>>)); | |||||
ASSERT_TRUE((std::is_same_v<entt::type_list_cat_t<type, type>, entt::type_list<int, char, int, char>>)); | |||||
ASSERT_TRUE((std::is_same_v<entt::type_list_unique_t<entt::type_list_cat_t<type, type>>, entt::type_list<int, char>>)); | |||||
} |
@ -0,0 +1,19 @@ | |||||
#include <gtest/gtest.h> | |||||
#include <entt/core/utility.hpp> | |||||
struct Functions { | |||||
static void foo(int) {} | |||||
static void foo() {} | |||||
void bar(int) {} | |||||
void bar() {} | |||||
}; | |||||
TEST(Utility, Overload) { | |||||
ASSERT_EQ(entt::overload<void(int)>(&Functions::foo), static_cast<void(*)(int)>(&Functions::foo)); | |||||
ASSERT_EQ(entt::overload<void()>(&Functions::foo), static_cast<void(*)()>(&Functions::foo)); | |||||
ASSERT_EQ(entt::overload<void(int)>(&Functions::bar), static_cast<void(Functions:: *)(int)>(&Functions::bar)); | |||||
ASSERT_EQ(entt::overload<void()>(&Functions::bar), static_cast<void(Functions:: *)()>(&Functions::bar)); | |||||
} |
@ -0,0 +1,59 @@ | |||||
#include <functional> | |||||
#include <gtest/gtest.h> | |||||
#include <entt/entity/actor.hpp> | |||||
#include <entt/entity/registry.hpp> | |||||
TEST(Actor, Component) { | |||||
entt::registry registry; | |||||
entt::actor actor{registry}; | |||||
ASSERT_EQ(®istry, &actor.backend()); | |||||
ASSERT_EQ(®istry, &std::as_const(actor).backend()); | |||||
ASSERT_TRUE(registry.empty<int>()); | |||||
ASSERT_FALSE(registry.empty()); | |||||
ASSERT_FALSE(actor.has<int>()); | |||||
const auto &cint = actor.assign<int>(); | |||||
const auto &cchar = actor.assign<char>(); | |||||
ASSERT_EQ(&cint, &actor.get<int>()); | |||||
ASSERT_EQ(&cchar, &std::as_const(actor).get<char>()); | |||||
ASSERT_EQ(&cint, &std::get<0>(actor.get<int, char>())); | |||||
ASSERT_EQ(&cchar, &std::get<1>(actor.get<int, char>())); | |||||
ASSERT_EQ(&cint, std::get<0>(actor.try_get<int, char, double>())); | |||||
ASSERT_EQ(&cchar, std::get<1>(actor.try_get<int, char, double>())); | |||||
ASSERT_EQ(nullptr, std::get<2>(actor.try_get<int, char, double>())); | |||||
ASSERT_EQ(nullptr, actor.try_get<double>()); | |||||
ASSERT_EQ(&cchar, actor.try_get<char>()); | |||||
ASSERT_EQ(&cint, actor.try_get<int>()); | |||||
ASSERT_FALSE(registry.empty<int>()); | |||||
ASSERT_FALSE(registry.empty()); | |||||
ASSERT_TRUE(actor.has<int>()); | |||||
ASSERT_TRUE(actor.has<char>()); | |||||
ASSERT_FALSE(actor.has<double>()); | |||||
actor.remove<int>(); | |||||
ASSERT_TRUE(registry.empty<int>()); | |||||
ASSERT_FALSE(registry.empty()); | |||||
ASSERT_FALSE(actor.has<int>()); | |||||
} | |||||
TEST(Actor, EntityLifetime) { | |||||
entt::registry registry; | |||||
auto *actor = new entt::actor{registry}; | |||||
actor->assign<int>(); | |||||
ASSERT_FALSE(registry.empty<int>()); | |||||
ASSERT_FALSE(registry.empty()); | |||||
registry.each([actor](const auto entity) { | |||||
ASSERT_EQ(actor->entity(), entity); | |||||
}); | |||||
delete actor; | |||||
ASSERT_TRUE(registry.empty<int>()); | |||||
ASSERT_TRUE(registry.empty()); | |||||
} |
@ -0,0 +1,24 @@ | |||||
#include <functional> | |||||
#include <gtest/gtest.h> | |||||
#include <entt/entity/entity.hpp> | |||||
#include <entt/entity/registry.hpp> | |||||
TEST(Traits, Null) { | |||||
entt::registry registry{}; | |||||
const auto entity = registry.create(); | |||||
registry.assign<int>(entity, 42); | |||||
ASSERT_TRUE(~entt::entity{} == entt::null); | |||||
ASSERT_TRUE(entt::null == entt::null); | |||||
ASSERT_FALSE(entt::null != entt::null); | |||||
ASSERT_FALSE(entity == entt::null); | |||||
ASSERT_FALSE(entt::null == entity); | |||||
ASSERT_TRUE(entity != entt::null); | |||||
ASSERT_TRUE(entt::null != entity); | |||||
ASSERT_FALSE(registry.valid(entt::null)); | |||||
} |
@ -0,0 +1,939 @@ | |||||
#include <utility> | |||||
#include <iterator> | |||||
#include <algorithm> | |||||
#include <gtest/gtest.h> | |||||
#include <entt/entity/helper.hpp> | |||||
#include <entt/entity/registry.hpp> | |||||
#include <entt/entity/group.hpp> | |||||
struct empty_type {}; | |||||
struct boxed_int { int value; }; | |||||
TEST(NonOwningGroup, Functionalities) { | |||||
entt::registry registry; | |||||
auto group = registry.group(entt::get<int, char>); | |||||
auto cgroup = std::as_const(registry).group(entt::get<const int, const char>); | |||||
ASSERT_TRUE(group.empty()); | |||||
ASSERT_TRUE(group.empty<int>()); | |||||
ASSERT_TRUE(cgroup.empty<const char>()); | |||||
const auto e0 = registry.create(); | |||||
registry.assign<char>(e0); | |||||
const auto e1 = registry.create(); | |||||
registry.assign<int>(e1); | |||||
registry.assign<char>(e1); | |||||
ASSERT_FALSE(group.empty()); | |||||
ASSERT_FALSE(group.empty<int>()); | |||||
ASSERT_FALSE(cgroup.empty<const char>()); | |||||
ASSERT_NO_THROW((group.begin()++)); | |||||
ASSERT_NO_THROW((++cgroup.begin())); | |||||
ASSERT_NE(group.begin(), group.end()); | |||||
ASSERT_NE(cgroup.begin(), cgroup.end()); | |||||
ASSERT_EQ(group.size(), typename decltype(group)::size_type{1}); | |||||
ASSERT_EQ(group.size<int>(), typename decltype(group)::size_type{1}); | |||||
ASSERT_EQ(cgroup.size<const char>(), typename decltype(group)::size_type{2}); | |||||
registry.assign<int>(e0); | |||||
ASSERT_EQ(group.size(), typename decltype(group)::size_type{2}); | |||||
ASSERT_EQ(group.size<int>(), typename decltype(group)::size_type{2}); | |||||
ASSERT_EQ(cgroup.size<const char>(), typename decltype(group)::size_type{2}); | |||||
registry.remove<int>(e0); | |||||
ASSERT_EQ(group.size(), typename decltype(group)::size_type{1}); | |||||
ASSERT_EQ(group.size<int>(), typename decltype(group)::size_type{1}); | |||||
ASSERT_EQ(cgroup.size<const char>(), typename decltype(group)::size_type{2}); | |||||
registry.get<char>(e0) = '1'; | |||||
registry.get<char>(e1) = '2'; | |||||
registry.get<int>(e1) = 42; | |||||
for(auto entity: group) { | |||||
ASSERT_EQ(std::get<0>(cgroup.get<const int, const char>(entity)), 42); | |||||
ASSERT_EQ(std::get<1>(group.get<int, char>(entity)), '2'); | |||||
ASSERT_EQ(cgroup.get<const char>(entity), '2'); | |||||
} | |||||
ASSERT_EQ(*(group.data() + 0), e1); | |||||
ASSERT_EQ(*(group.data<int>() + 0), e1); | |||||
ASSERT_EQ(*(group.data<char>() + 0), e0); | |||||
ASSERT_EQ(*(cgroup.data<const char>() + 1), e1); | |||||
ASSERT_EQ(*(group.raw<int>() + 0), 42); | |||||
ASSERT_EQ(*(group.raw<char>() + 0), '1'); | |||||
ASSERT_EQ(*(cgroup.raw<const char>() + 1), '2'); | |||||
registry.remove<char>(e0); | |||||
registry.remove<char>(e1); | |||||
ASSERT_EQ(group.begin(), group.end()); | |||||
ASSERT_EQ(cgroup.begin(), cgroup.end()); | |||||
ASSERT_TRUE(group.empty()); | |||||
ASSERT_TRUE(group.capacity()); | |||||
group.shrink_to_fit(); | |||||
ASSERT_FALSE(group.capacity()); | |||||
} | |||||
TEST(NonOwningGroup, ElementAccess) { | |||||
entt::registry registry; | |||||
auto group = registry.group(entt::get<int, char>); | |||||
auto cgroup = std::as_const(registry).group(entt::get<const int, const char>); | |||||
const auto e0 = registry.create(); | |||||
registry.assign<int>(e0); | |||||
registry.assign<char>(e0); | |||||
const auto e1 = registry.create(); | |||||
registry.assign<int>(e1); | |||||
registry.assign<char>(e1); | |||||
for(typename decltype(group)::size_type i{}; i < group.size(); ++i) { | |||||
ASSERT_EQ(group[i], i ? e0 : e1); | |||||
ASSERT_EQ(cgroup[i], i ? e0 : e1); | |||||
} | |||||
} | |||||
TEST(NonOwningGroup, Contains) { | |||||
entt::registry registry; | |||||
auto group = registry.group(entt::get<int, char>); | |||||
const auto e0 = registry.create(); | |||||
registry.assign<int>(e0); | |||||
registry.assign<char>(e0); | |||||
const auto e1 = registry.create(); | |||||
registry.assign<int>(e1); | |||||
registry.assign<char>(e1); | |||||
registry.destroy(e0); | |||||
ASSERT_FALSE(group.contains(e0)); | |||||
ASSERT_TRUE(group.contains(e1)); | |||||
} | |||||
TEST(NonOwningGroup, Empty) { | |||||
entt::registry registry; | |||||
const auto e0 = registry.create(); | |||||
registry.assign<double>(e0); | |||||
registry.assign<int>(e0); | |||||
registry.assign<float>(e0); | |||||
const auto e1 = registry.create(); | |||||
registry.assign<char>(e1); | |||||
registry.assign<float>(e1); | |||||
for(auto entity: registry.group(entt::get<char, int, float>)) { | |||||
(void)entity; | |||||
FAIL(); | |||||
} | |||||
for(auto entity: registry.group(entt::get<double, char, int, float>)) { | |||||
(void)entity; | |||||
FAIL(); | |||||
} | |||||
} | |||||
TEST(NonOwningGroup, Each) { | |||||
entt::registry registry; | |||||
auto group = registry.group(entt::get<int, char>); | |||||
const auto e0 = registry.create(); | |||||
registry.assign<int>(e0); | |||||
registry.assign<char>(e0); | |||||
const auto e1 = registry.create(); | |||||
registry.assign<int>(e1); | |||||
registry.assign<char>(e1); | |||||
auto cgroup = std::as_const(registry).group(entt::get<const int, const char>); | |||||
std::size_t cnt = 0; | |||||
group.each([&cnt](auto, int &, char &) { ++cnt; }); | |||||
group.each([&cnt](int &, char &) { ++cnt; }); | |||||
ASSERT_EQ(cnt, std::size_t{4}); | |||||
cgroup.each([&cnt](auto, const int &, const char &) { --cnt; }); | |||||
cgroup.each([&cnt](const int &, const char &) { --cnt; }); | |||||
ASSERT_EQ(cnt, std::size_t{0}); | |||||
} | |||||
TEST(NonOwningGroup, Sort) { | |||||
entt::registry registry; | |||||
auto group = registry.group(entt::get<const int, unsigned int>); | |||||
const auto e0 = registry.create(); | |||||
const auto e1 = registry.create(); | |||||
const auto e2 = registry.create(); | |||||
auto uval = 0u; | |||||
auto ival = 0; | |||||
registry.assign<unsigned int>(e0, uval++); | |||||
registry.assign<unsigned int>(e1, uval++); | |||||
registry.assign<unsigned int>(e2, uval++); | |||||
registry.assign<int>(e0, ival++); | |||||
registry.assign<int>(e1, ival++); | |||||
registry.assign<int>(e2, ival++); | |||||
for(auto entity: group) { | |||||
ASSERT_EQ(group.get<unsigned int>(entity), --uval); | |||||
ASSERT_EQ(group.get<const int>(entity), --ival); | |||||
} | |||||
registry.sort<unsigned int>(std::less<unsigned int>{}); | |||||
group.sort<unsigned int>(); | |||||
for(auto entity: group) { | |||||
ASSERT_EQ(group.get<unsigned int>(entity), uval++); | |||||
ASSERT_EQ(group.get<const int>(entity), ival++); | |||||
} | |||||
} | |||||
TEST(NonOwningGroup, IndexRebuiltOnDestroy) { | |||||
entt::registry registry; | |||||
auto group = registry.group(entt::get<int, unsigned int>); | |||||
const auto e0 = registry.create(); | |||||
const auto e1 = registry.create(); | |||||
registry.assign<unsigned int>(e0, 0u); | |||||
registry.assign<unsigned int>(e1, 1u); | |||||
registry.assign<int>(e0, 0); | |||||
registry.assign<int>(e1, 1); | |||||
registry.destroy(e0); | |||||
registry.assign<int>(registry.create(), 42); | |||||
ASSERT_EQ(group.size(), typename decltype(group)::size_type{1}); | |||||
ASSERT_EQ(group[{}], e1); | |||||
ASSERT_EQ(group.get<int>(e1), 1); | |||||
ASSERT_EQ(group.get<unsigned int>(e1), 1u); | |||||
group.each([e1](auto entity, auto ivalue, auto uivalue) { | |||||
ASSERT_EQ(entity, e1); | |||||
ASSERT_EQ(ivalue, 1); | |||||
ASSERT_EQ(uivalue, 1u); | |||||
}); | |||||
} | |||||
TEST(NonOwningGroup, ConstNonConstAndAllInBetween) { | |||||
entt::registry registry; | |||||
auto group = registry.group(entt::get<int, const char>); | |||||
ASSERT_EQ(group.size(), decltype(group.size()){0}); | |||||
const auto entity = registry.create(); | |||||
registry.assign<int>(entity, 0); | |||||
registry.assign<char>(entity, 'c'); | |||||
ASSERT_EQ(group.size(), decltype(group.size()){1}); | |||||
ASSERT_TRUE((std::is_same_v<decltype(group.get<int>(0)), int &>)); | |||||
ASSERT_TRUE((std::is_same_v<decltype(group.get<const char>(0)), const char &>)); | |||||
ASSERT_TRUE((std::is_same_v<decltype(group.get<int, const char>(0)), std::tuple<int &, const char &>>)); | |||||
ASSERT_TRUE((std::is_same_v<decltype(group.raw<const char>()), const char *>)); | |||||
ASSERT_TRUE((std::is_same_v<decltype(group.raw<int>()), int *>)); | |||||
group.each([](auto, auto &&i, auto &&c) { | |||||
ASSERT_TRUE((std::is_same_v<decltype(i), int &>)); | |||||
ASSERT_TRUE((std::is_same_v<decltype(c), const char &>)); | |||||
}); | |||||
} | |||||
TEST(NonOwningGroup, Find) { | |||||
entt::registry registry; | |||||
auto group = registry.group(entt::get<int, const char>); | |||||
const auto e0 = registry.create(); | |||||
registry.assign<int>(e0); | |||||
registry.assign<char>(e0); | |||||
const auto e1 = registry.create(); | |||||
registry.assign<int>(e1); | |||||
registry.assign<char>(e1); | |||||
const auto e2 = registry.create(); | |||||
registry.assign<int>(e2); | |||||
registry.assign<char>(e2); | |||||
const auto e3 = registry.create(); | |||||
registry.assign<int>(e3); | |||||
registry.assign<char>(e3); | |||||
registry.remove<int>(e1); | |||||
ASSERT_NE(group.find(e0), group.end()); | |||||
ASSERT_EQ(group.find(e1), group.end()); | |||||
ASSERT_NE(group.find(e2), group.end()); | |||||
ASSERT_NE(group.find(e3), group.end()); | |||||
auto it = group.find(e2); | |||||
ASSERT_EQ(*it, e2); | |||||
ASSERT_EQ(*(++it), e3); | |||||
ASSERT_EQ(*(++it), e0); | |||||
ASSERT_EQ(++it, group.end()); | |||||
ASSERT_EQ(++group.find(e0), group.end()); | |||||
const auto e4 = registry.create(); | |||||
registry.destroy(e4); | |||||
const auto e5 = registry.create(); | |||||
registry.assign<int>(e5); | |||||
registry.assign<char>(e5); | |||||
ASSERT_NE(group.find(e5), group.end()); | |||||
ASSERT_EQ(group.find(e4), group.end()); | |||||
} | |||||
TEST(NonOwningGroup, ExcludedComponents) { | |||||
entt::registry registry; | |||||
const auto e0 = registry.create(); | |||||
registry.assign<int>(e0, 0); | |||||
const auto e1 = registry.create(); | |||||
registry.assign<int>(e1, 1); | |||||
registry.assign<char>(e1); | |||||
const auto group = registry.group(entt::get<int>, entt::exclude<char>); | |||||
const auto e2 = registry.create(); | |||||
registry.assign<int>(e2, 2); | |||||
const auto e3 = registry.create(); | |||||
registry.assign<int>(e3, 3); | |||||
registry.assign<char>(e3); | |||||
for(const auto entity: group) { | |||||
if(entity == e0) { | |||||
ASSERT_EQ(group.get<int>(e0), 0); | |||||
} else if(entity == e2) { | |||||
ASSERT_EQ(group.get<int>(e2), 2); | |||||
} else { | |||||
FAIL(); | |||||
} | |||||
} | |||||
registry.assign<char>(e0); | |||||
registry.assign<char>(e2); | |||||
ASSERT_TRUE(group.empty()); | |||||
registry.remove<char>(e1); | |||||
registry.remove<char>(e3); | |||||
for(const auto entity: group) { | |||||
if(entity == e1) { | |||||
ASSERT_EQ(group.get<int>(e1), 1); | |||||
} else if(entity == e3) { | |||||
ASSERT_EQ(group.get<int>(e3), 3); | |||||
} else { | |||||
FAIL(); | |||||
} | |||||
} | |||||
} | |||||
TEST(NonOwningGroup, EmptyAndNonEmptyTypes) { | |||||
entt::registry registry; | |||||
const auto group = registry.group(entt::get<int, empty_type>); | |||||
const auto e0 = registry.create(); | |||||
registry.assign<empty_type>(e0); | |||||
registry.assign<int>(e0); | |||||
const auto e1 = registry.create(); | |||||
registry.assign<empty_type>(e1); | |||||
registry.assign<int>(e1); | |||||
registry.assign<int>(registry.create()); | |||||
for(const auto entity: group) { | |||||
ASSERT_TRUE(entity == e0 || entity == e1); | |||||
} | |||||
group.each([e0, e1](const auto entity, const int &, empty_type) { | |||||
ASSERT_TRUE(entity == e0 || entity == e1); | |||||
}); | |||||
ASSERT_EQ(group.size(), typename decltype(group)::size_type{2}); | |||||
} | |||||
TEST(NonOwningGroup, TrackEntitiesOnComponentDestruction) { | |||||
entt::registry registry; | |||||
const auto group = registry.group(entt::get<int>, entt::exclude<char>); | |||||
const auto cgroup = std::as_const(registry).group(entt::get<const int>, entt::exclude<char>); | |||||
const auto entity = registry.create(); | |||||
registry.assign<int>(entity); | |||||
registry.assign<char>(entity); | |||||
ASSERT_TRUE(group.empty()); | |||||
ASSERT_TRUE(cgroup.empty()); | |||||
registry.remove<char>(entity); | |||||
ASSERT_FALSE(group.empty()); | |||||
ASSERT_FALSE(cgroup.empty()); | |||||
} | |||||
TEST(NonOwningGroup, Less) { | |||||
entt::registry registry; | |||||
const auto entity = std::get<0>(registry.create<int, entt::tag<"empty"_hs>>()); | |||||
registry.create<char>(); | |||||
registry.group(entt::get<int, char, entt::tag<"empty"_hs>>).less([entity](const auto entt, int, char) { | |||||
ASSERT_EQ(entity, entt); | |||||
}); | |||||
registry.group(entt::get<int, entt::tag<"empty"_hs>, char>).less([check = true](int, char) mutable { | |||||
ASSERT_TRUE(check); | |||||
check = false; | |||||
}); | |||||
registry.group(entt::get<entt::tag<"empty"_hs>, int, char>).less([entity](const auto entt, int, char) { | |||||
ASSERT_EQ(entity, entt); | |||||
}); | |||||
registry.group(entt::get<int, char, double>).less([entity](const auto entt, int, char, double) { | |||||
ASSERT_EQ(entity, entt); | |||||
}); | |||||
} | |||||
TEST(OwningGroup, Functionalities) { | |||||
entt::registry registry; | |||||
auto group = registry.group<int>(entt::get<char>); | |||||
auto cgroup = std::as_const(registry).group<const int>(entt::get<const char>); | |||||
ASSERT_TRUE(group.empty()); | |||||
ASSERT_TRUE(group.empty<int>()); | |||||
ASSERT_TRUE(cgroup.empty<const char>()); | |||||
const auto e0 = registry.create(); | |||||
registry.assign<char>(e0); | |||||
const auto e1 = registry.create(); | |||||
registry.assign<int>(e1); | |||||
registry.assign<char>(e1); | |||||
ASSERT_FALSE(group.empty()); | |||||
ASSERT_FALSE(group.empty<int>()); | |||||
ASSERT_FALSE(cgroup.empty<const char>()); | |||||
ASSERT_NO_THROW((group.begin()++)); | |||||
ASSERT_NO_THROW((++cgroup.begin())); | |||||
ASSERT_NE(group.begin(), group.end()); | |||||
ASSERT_NE(cgroup.begin(), cgroup.end()); | |||||
ASSERT_EQ(group.size(), typename decltype(group)::size_type{1}); | |||||
ASSERT_EQ(group.size<int>(), typename decltype(group)::size_type{1}); | |||||
ASSERT_EQ(cgroup.size<const char>(), typename decltype(group)::size_type{2}); | |||||
registry.assign<int>(e0); | |||||
ASSERT_EQ(group.size(), typename decltype(group)::size_type{2}); | |||||
ASSERT_EQ(group.size<int>(), typename decltype(group)::size_type{2}); | |||||
ASSERT_EQ(cgroup.size<const char>(), typename decltype(group)::size_type{2}); | |||||
registry.remove<int>(e0); | |||||
ASSERT_EQ(group.size(), typename decltype(group)::size_type{1}); | |||||
ASSERT_EQ(group.size<int>(), typename decltype(group)::size_type{1}); | |||||
ASSERT_EQ(cgroup.size<const char>(), typename decltype(group)::size_type{2}); | |||||
registry.get<char>(e0) = '1'; | |||||
registry.get<char>(e1) = '2'; | |||||
registry.get<int>(e1) = 42; | |||||
ASSERT_EQ(*(cgroup.raw<const int>() + 0), 42); | |||||
ASSERT_EQ(*(group.raw<int>() + 0), 42); | |||||
for(auto entity: group) { | |||||
ASSERT_EQ(std::get<0>(cgroup.get<const int, const char>(entity)), 42); | |||||
ASSERT_EQ(std::get<1>(group.get<int, char>(entity)), '2'); | |||||
ASSERT_EQ(cgroup.get<const char>(entity), '2'); | |||||
} | |||||
ASSERT_EQ(*(group.data() + 0), e1); | |||||
ASSERT_EQ(*(group.data<int>() + 0), e1); | |||||
ASSERT_EQ(*(group.data<char>() + 0), e0); | |||||
ASSERT_EQ(*(cgroup.data<const char>() + 1), e1); | |||||
ASSERT_EQ(*(group.raw<int>() + 0), 42); | |||||
ASSERT_EQ(*(group.raw<char>() + 0), '1'); | |||||
ASSERT_EQ(*(cgroup.raw<const char>() + 1), '2'); | |||||
registry.remove<char>(e0); | |||||
registry.remove<char>(e1); | |||||
ASSERT_EQ(group.begin(), group.end()); | |||||
ASSERT_EQ(cgroup.begin(), cgroup.end()); | |||||
ASSERT_TRUE(group.empty()); | |||||
} | |||||
TEST(OwningGroup, ElementAccess) { | |||||
entt::registry registry; | |||||
auto group = registry.group<int>(entt::get<char>); | |||||
auto cgroup = std::as_const(registry).group<const int>(entt::get<const char>); | |||||
const auto e0 = registry.create(); | |||||
registry.assign<int>(e0); | |||||
registry.assign<char>(e0); | |||||
const auto e1 = registry.create(); | |||||
registry.assign<int>(e1); | |||||
registry.assign<char>(e1); | |||||
for(typename decltype(group)::size_type i{}; i < group.size(); ++i) { | |||||
ASSERT_EQ(group[i], i ? e0 : e1); | |||||
ASSERT_EQ(cgroup[i], i ? e0 : e1); | |||||
} | |||||
} | |||||
TEST(OwningGroup, Contains) { | |||||
entt::registry registry; | |||||
auto group = registry.group<int>(entt::get<char>); | |||||
const auto e0 = registry.create(); | |||||
registry.assign<int>(e0); | |||||
registry.assign<char>(e0); | |||||
const auto e1 = registry.create(); | |||||
registry.assign<int>(e1); | |||||
registry.assign<char>(e1); | |||||
registry.destroy(e0); | |||||
ASSERT_FALSE(group.contains(e0)); | |||||
ASSERT_TRUE(group.contains(e1)); | |||||
} | |||||
TEST(OwningGroup, Empty) { | |||||
entt::registry registry; | |||||
const auto e0 = registry.create(); | |||||
registry.assign<double>(e0); | |||||
registry.assign<int>(e0); | |||||
registry.assign<float>(e0); | |||||
const auto e1 = registry.create(); | |||||
registry.assign<char>(e1); | |||||
registry.assign<float>(e1); | |||||
for(auto entity: registry.group<char, int>(entt::get<float>)) { | |||||
(void)entity; | |||||
FAIL(); | |||||
} | |||||
for(auto entity: registry.group<double, float>(entt::get<char, int>)) { | |||||
(void)entity; | |||||
FAIL(); | |||||
} | |||||
} | |||||
TEST(OwningGroup, Each) { | |||||
entt::registry registry; | |||||
auto group = registry.group<int>(entt::get<char>); | |||||
const auto e0 = registry.create(); | |||||
registry.assign<int>(e0); | |||||
registry.assign<char>(e0); | |||||
const auto e1 = registry.create(); | |||||
registry.assign<int>(e1); | |||||
registry.assign<char>(e1); | |||||
auto cgroup = std::as_const(registry).group<const int>(entt::get<const char>); | |||||
std::size_t cnt = 0; | |||||
group.each([&cnt](auto, int &, char &) { ++cnt; }); | |||||
group.each([&cnt](int &, char &) { ++cnt; }); | |||||
ASSERT_EQ(cnt, std::size_t{4}); | |||||
cgroup.each([&cnt](auto, const int &, const char &) { --cnt; }); | |||||
cgroup.each([&cnt](const int &, const char &) { --cnt; }); | |||||
ASSERT_EQ(cnt, std::size_t{0}); | |||||
} | |||||
TEST(OwningGroup, SortOrdered) { | |||||
entt::registry registry; | |||||
auto group = registry.group<boxed_int, char>(); | |||||
entt::entity entities[5] = { | |||||
registry.create(), | |||||
registry.create(), | |||||
registry.create(), | |||||
registry.create(), | |||||
registry.create() | |||||
}; | |||||
registry.assign<boxed_int>(entities[0], 12); | |||||
registry.assign<char>(entities[0], 'a'); | |||||
registry.assign<boxed_int>(entities[1], 9); | |||||
registry.assign<char>(entities[1], 'b'); | |||||
registry.assign<boxed_int>(entities[2], 6); | |||||
registry.assign<char>(entities[2], 'c'); | |||||
registry.assign<boxed_int>(entities[3], 1); | |||||
registry.assign<boxed_int>(entities[4], 2); | |||||
group.sort([&group](const auto lhs, const auto rhs) { | |||||
return group.get<boxed_int>(lhs).value < group.get<boxed_int>(rhs).value; | |||||
}); | |||||
ASSERT_EQ(*(group.data() + 0u), entities[0]); | |||||
ASSERT_EQ(*(group.data() + 1u), entities[1]); | |||||
ASSERT_EQ(*(group.data() + 2u), entities[2]); | |||||
ASSERT_EQ(*(group.data() + 3u), entities[3]); | |||||
ASSERT_EQ(*(group.data() + 4u), entities[4]); | |||||
ASSERT_EQ((group.raw<boxed_int>() + 0u)->value, 12); | |||||
ASSERT_EQ((group.raw<boxed_int>() + 1u)->value, 9); | |||||
ASSERT_EQ((group.raw<boxed_int>() + 2u)->value, 6); | |||||
ASSERT_EQ((group.raw<boxed_int>() + 3u)->value, 1); | |||||
ASSERT_EQ((group.raw<boxed_int>() + 4u)->value, 2); | |||||
ASSERT_EQ(*(group.raw<char>() + 0u), 'a'); | |||||
ASSERT_EQ(*(group.raw<char>() + 1u), 'b'); | |||||
ASSERT_EQ(*(group.raw<char>() + 2u), 'c'); | |||||
} | |||||
TEST(OwningGroup, SortReverse) { | |||||
entt::registry registry; | |||||
auto group = registry.group<boxed_int, char>(); | |||||
entt::entity entities[5] = { | |||||
registry.create(), | |||||
registry.create(), | |||||
registry.create(), | |||||
registry.create(), | |||||
registry.create() | |||||
}; | |||||
registry.assign<boxed_int>(entities[0], 6); | |||||
registry.assign<char>(entities[0], 'a'); | |||||
registry.assign<boxed_int>(entities[1], 9); | |||||
registry.assign<char>(entities[1], 'b'); | |||||
registry.assign<boxed_int>(entities[2], 12); | |||||
registry.assign<char>(entities[2], 'c'); | |||||
registry.assign<boxed_int>(entities[3], 1); | |||||
registry.assign<boxed_int>(entities[4], 2); | |||||
group.sort<boxed_int>([](const auto &lhs, const auto &rhs) { | |||||
return lhs.value < rhs.value; | |||||
}); | |||||
ASSERT_EQ(*(group.data() + 0u), entities[2]); | |||||
ASSERT_EQ(*(group.data() + 1u), entities[1]); | |||||
ASSERT_EQ(*(group.data() + 2u), entities[0]); | |||||
ASSERT_EQ(*(group.data() + 3u), entities[3]); | |||||
ASSERT_EQ(*(group.data() + 4u), entities[4]); | |||||
ASSERT_EQ((group.raw<boxed_int>() + 0u)->value, 12); | |||||
ASSERT_EQ((group.raw<boxed_int>() + 1u)->value, 9); | |||||
ASSERT_EQ((group.raw<boxed_int>() + 2u)->value, 6); | |||||
ASSERT_EQ((group.raw<boxed_int>() + 3u)->value, 1); | |||||
ASSERT_EQ((group.raw<boxed_int>() + 4u)->value, 2); | |||||
ASSERT_EQ(*(group.raw<char>() + 0u), 'c'); | |||||
ASSERT_EQ(*(group.raw<char>() + 1u), 'b'); | |||||
ASSERT_EQ(*(group.raw<char>() + 2u), 'a'); | |||||
} | |||||
TEST(OwningGroup, SortUnordered) { | |||||
entt::registry registry; | |||||
auto group = registry.group<boxed_int>(entt::get<char>); | |||||
entt::entity entities[7] = { | |||||
registry.create(), | |||||
registry.create(), | |||||
registry.create(), | |||||
registry.create(), | |||||
registry.create(), | |||||
registry.create(), | |||||
registry.create() | |||||
}; | |||||
registry.assign<boxed_int>(entities[0], 6); | |||||
registry.assign<char>(entities[0], 'c'); | |||||
registry.assign<boxed_int>(entities[1], 3); | |||||
registry.assign<char>(entities[1], 'b'); | |||||
registry.assign<boxed_int>(entities[2], 1); | |||||
registry.assign<char>(entities[2], 'a'); | |||||
registry.assign<boxed_int>(entities[3], 9); | |||||
registry.assign<char>(entities[3], 'd'); | |||||
registry.assign<boxed_int>(entities[4], 12); | |||||
registry.assign<char>(entities[4], 'e'); | |||||
registry.assign<boxed_int>(entities[5], 4); | |||||
registry.assign<boxed_int>(entities[6], 5); | |||||
group.sort<char>([](const auto lhs, const auto rhs) { | |||||
return lhs < rhs; | |||||
}); | |||||
ASSERT_EQ(*(group.data() + 0u), entities[4]); | |||||
ASSERT_EQ(*(group.data() + 1u), entities[3]); | |||||
ASSERT_EQ(*(group.data() + 2u), entities[0]); | |||||
ASSERT_EQ(*(group.data() + 3u), entities[1]); | |||||
ASSERT_EQ(*(group.data() + 4u), entities[2]); | |||||
ASSERT_EQ(*(group.data() + 5u), entities[5]); | |||||
ASSERT_EQ(*(group.data() + 6u), entities[6]); | |||||
ASSERT_EQ((group.raw<boxed_int>() + 0u)->value, 12); | |||||
ASSERT_EQ((group.raw<boxed_int>() + 1u)->value, 9); | |||||
ASSERT_EQ((group.raw<boxed_int>() + 2u)->value, 6); | |||||
ASSERT_EQ((group.raw<boxed_int>() + 3u)->value, 3); | |||||
ASSERT_EQ((group.raw<boxed_int>() + 4u)->value, 1); | |||||
ASSERT_EQ((group.raw<boxed_int>() + 5u)->value, 4); | |||||
ASSERT_EQ((group.raw<boxed_int>() + 6u)->value, 5); | |||||
ASSERT_EQ(*(group.raw<char>() + 0u), 'c'); | |||||
ASSERT_EQ(*(group.raw<char>() + 1u), 'b'); | |||||
ASSERT_EQ(*(group.raw<char>() + 2u), 'a'); | |||||
ASSERT_EQ(*(group.raw<char>() + 3u), 'd'); | |||||
ASSERT_EQ(*(group.raw<char>() + 4u), 'e'); | |||||
} | |||||
TEST(OwningGroup, IndexRebuiltOnDestroy) { | |||||
entt::registry registry; | |||||
auto group = registry.group<int>(entt::get<unsigned int>); | |||||
const auto e0 = registry.create(); | |||||
const auto e1 = registry.create(); | |||||
registry.assign<unsigned int>(e0, 0u); | |||||
registry.assign<unsigned int>(e1, 1u); | |||||
registry.assign<int>(e0, 0); | |||||
registry.assign<int>(e1, 1); | |||||
registry.destroy(e0); | |||||
registry.assign<int>(registry.create(), 42); | |||||
ASSERT_EQ(group.size(), typename decltype(group)::size_type{1}); | |||||
ASSERT_EQ(group[{}], e1); | |||||
ASSERT_EQ(group.get<int>(e1), 1); | |||||
ASSERT_EQ(group.get<unsigned int>(e1), 1u); | |||||
group.each([e1](auto entity, auto ivalue, auto uivalue) { | |||||
ASSERT_EQ(entity, e1); | |||||
ASSERT_EQ(ivalue, 1); | |||||
ASSERT_EQ(uivalue, 1u); | |||||
}); | |||||
} | |||||
TEST(OwningGroup, ConstNonConstAndAllInBetween) { | |||||
entt::registry registry; | |||||
auto group = registry.group<int, const char>(entt::get<double, const float>); | |||||
ASSERT_EQ(group.size(), decltype(group.size()){0}); | |||||
const auto entity = registry.create(); | |||||
registry.assign<int>(entity, 0); | |||||
registry.assign<char>(entity, 'c'); | |||||
registry.assign<double>(entity, 0.); | |||||
registry.assign<float>(entity, 0.f); | |||||
ASSERT_EQ(group.size(), decltype(group.size()){1}); | |||||
ASSERT_TRUE((std::is_same_v<decltype(group.get<int>(0)), int &>)); | |||||
ASSERT_TRUE((std::is_same_v<decltype(group.get<const char>(0)), const char &>)); | |||||
ASSERT_TRUE((std::is_same_v<decltype(group.get<double>(0)), double &>)); | |||||
ASSERT_TRUE((std::is_same_v<decltype(group.get<const float>(0)), const float &>)); | |||||
ASSERT_TRUE((std::is_same_v<decltype(group.get<int, const char, double, const float>(0)), std::tuple<int &, const char &, double &, const float &>>)); | |||||
ASSERT_TRUE((std::is_same_v<decltype(group.raw<const float>()), const float *>)); | |||||
ASSERT_TRUE((std::is_same_v<decltype(group.raw<double>()), double *>)); | |||||
ASSERT_TRUE((std::is_same_v<decltype(group.raw<const char>()), const char *>)); | |||||
ASSERT_TRUE((std::is_same_v<decltype(group.raw<int>()), int *>)); | |||||
group.each([](auto, auto &&i, auto &&c, auto &&d, auto &&f) { | |||||
ASSERT_TRUE((std::is_same_v<decltype(i), int &>)); | |||||
ASSERT_TRUE((std::is_same_v<decltype(c), const char &>)); | |||||
ASSERT_TRUE((std::is_same_v<decltype(d), double &>)); | |||||
ASSERT_TRUE((std::is_same_v<decltype(f), const float &>)); | |||||
}); | |||||
} | |||||
TEST(OwningGroup, Find) { | |||||
entt::registry registry; | |||||
auto group = registry.group<int>(entt::get<const char>); | |||||
const auto e0 = registry.create(); | |||||
registry.assign<int>(e0); | |||||
registry.assign<char>(e0); | |||||
const auto e1 = registry.create(); | |||||
registry.assign<int>(e1); | |||||
registry.assign<char>(e1); | |||||
const auto e2 = registry.create(); | |||||
registry.assign<int>(e2); | |||||
registry.assign<char>(e2); | |||||
const auto e3 = registry.create(); | |||||
registry.assign<int>(e3); | |||||
registry.assign<char>(e3); | |||||
registry.remove<int>(e1); | |||||
ASSERT_NE(group.find(e0), group.end()); | |||||
ASSERT_EQ(group.find(e1), group.end()); | |||||
ASSERT_NE(group.find(e2), group.end()); | |||||
ASSERT_NE(group.find(e3), group.end()); | |||||
auto it = group.find(e2); | |||||
ASSERT_EQ(*it, e2); | |||||
ASSERT_EQ(*(++it), e3); | |||||
ASSERT_EQ(*(++it), e0); | |||||
ASSERT_EQ(++it, group.end()); | |||||
ASSERT_EQ(++group.find(e0), group.end()); | |||||
const auto e4 = registry.create(); | |||||
registry.destroy(e4); | |||||
const auto e5 = registry.create(); | |||||
registry.assign<int>(e5); | |||||
registry.assign<char>(e5); | |||||
ASSERT_NE(group.find(e5), group.end()); | |||||
ASSERT_EQ(group.find(e4), group.end()); | |||||
} | |||||
TEST(OwningGroup, ExcludedComponents) { | |||||
entt::registry registry; | |||||
const auto e0 = registry.create(); | |||||
registry.assign<int>(e0, 0); | |||||
const auto e1 = registry.create(); | |||||
registry.assign<int>(e1, 1); | |||||
registry.assign<char>(e1); | |||||
const auto group = registry.group<int>(entt::exclude<char, double>); | |||||
const auto e2 = registry.create(); | |||||
registry.assign<int>(e2, 2); | |||||
const auto e3 = registry.create(); | |||||
registry.assign<int>(e3, 3); | |||||
registry.assign<double>(e3); | |||||
for(const auto entity: group) { | |||||
if(entity == e0) { | |||||
ASSERT_EQ(group.get<int>(e0), 0); | |||||
} else if(entity == e2) { | |||||
ASSERT_EQ(group.get<int>(e2), 2); | |||||
} else { | |||||
FAIL(); | |||||
} | |||||
} | |||||
registry.assign<char>(e0); | |||||
registry.assign<double>(e2); | |||||
ASSERT_TRUE(group.empty()); | |||||
registry.remove<char>(e1); | |||||
registry.remove<double>(e3); | |||||
for(const auto entity: group) { | |||||
if(entity == e1) { | |||||
ASSERT_EQ(group.get<int>(e1), 1); | |||||
} else if(entity == e3) { | |||||
ASSERT_EQ(group.get<int>(e3), 3); | |||||
} else { | |||||
FAIL(); | |||||
} | |||||
} | |||||
} | |||||
TEST(OwningGroup, EmptyAndNonEmptyTypes) { | |||||
entt::registry registry; | |||||
const auto group = registry.group<empty_type>(entt::get<int>); | |||||
const auto e0 = registry.create(); | |||||
registry.assign<empty_type>(e0); | |||||
registry.assign<int>(e0); | |||||
const auto e1 = registry.create(); | |||||
registry.assign<empty_type>(e1); | |||||
registry.assign<int>(e1); | |||||
registry.assign<int>(registry.create()); | |||||
for(const auto entity: group) { | |||||
ASSERT_TRUE(entity == e0 || entity == e1); | |||||
} | |||||
group.each([e0, e1](const auto entity, empty_type, const int &) { | |||||
ASSERT_TRUE(entity == e0 || entity == e1); | |||||
}); | |||||
ASSERT_EQ(group.size(), typename decltype(group)::size_type{2}); | |||||
} | |||||
TEST(OwningGroup, TrackEntitiesOnComponentDestruction) { | |||||
entt::registry registry; | |||||
const auto group = registry.group<int>(entt::exclude<char>); | |||||
const auto cgroup = std::as_const(registry).group<const int>(entt::exclude<char>); | |||||
const auto entity = registry.create(); | |||||
registry.assign<int>(entity); | |||||
registry.assign<char>(entity); | |||||
ASSERT_TRUE(group.empty()); | |||||
ASSERT_TRUE(cgroup.empty()); | |||||
registry.remove<char>(entity); | |||||
ASSERT_FALSE(group.empty()); | |||||
ASSERT_FALSE(cgroup.empty()); | |||||
} | |||||
TEST(OwningGroup, Less) { | |||||
entt::registry registry; | |||||
const auto entity = std::get<0>(registry.create<int, entt::tag<"empty"_hs>>()); | |||||
registry.create<char>(); | |||||
registry.group<int>(entt::get<char, entt::tag<"empty"_hs>>).less([entity](const auto entt, int, char) { | |||||
ASSERT_EQ(entity, entt); | |||||
}); | |||||
registry.group<char>(entt::get<entt::tag<"empty"_hs>, int>).less([check = true](int, char) mutable { | |||||
ASSERT_TRUE(check); | |||||
check = false; | |||||
}); | |||||
registry.group<entt::tag<"empty"_hs>>(entt::get<int, char>).less([entity](const auto entt, int, char) { | |||||
ASSERT_EQ(entity, entt); | |||||
}); | |||||
registry.group<double>(entt::get<int, char>).less([entity](const auto entt, int, char, double) { | |||||
ASSERT_EQ(entity, entt); | |||||
}); | |||||
} |