@ -1 +1 @@ | |||||
Subproject commit 5a11f8e3db0b7b014b99d91be196cea582e1688a | |||||
Subproject commit 6d3707094704ec12466068fd9c2c2fdc88a1da1b |
@ -1 +1 @@ | |||||
Subproject commit 66f114dbf78bdbe42a5ea4905b0414c347ae45c5 | |||||
Subproject commit 96c678e5fc141df2440e8000d47dcf5cb942cd7c |
@ -0,0 +1,41 @@ | |||||
BasedOnStyle: llvm | |||||
--- | |||||
AccessModifierOffset: -4 | |||||
AlignEscapedNewlines: DontAlign | |||||
AllowShortBlocksOnASingleLine: Empty | |||||
AllowShortEnumsOnASingleLine: true | |||||
AllowShortFunctionsOnASingleLine: Empty | |||||
AllowShortIfStatementsOnASingleLine: WithoutElse | |||||
AllowShortLoopsOnASingleLine: true | |||||
AlwaysBreakTemplateDeclarations: Yes | |||||
BreakBeforeBinaryOperators: NonAssignment | |||||
BreakBeforeTernaryOperators: true | |||||
ColumnLimit: 0 | |||||
DerivePointerAlignment: false | |||||
IncludeCategories: | |||||
- Regex: '<[[:alnum:]_]+>' | |||||
Priority: 1 | |||||
- Regex: '<(gtest|gmock)/' | |||||
Priority: 2 | |||||
- Regex: '<[[:alnum:]_./]+>' | |||||
Priority: 3 | |||||
- Regex: '<entt/' | |||||
Priority: 4 | |||||
- Regex: '.*' | |||||
Priority: 5 | |||||
IndentPPDirectives: AfterHash | |||||
IndentWidth: 4 | |||||
KeepEmptyLinesAtTheStartOfBlocks: false | |||||
Language: Cpp | |||||
PointerAlignment: Right | |||||
SpaceAfterCStyleCast: false | |||||
SpaceAfterTemplateKeyword: false | |||||
SpaceAroundPointerQualifiers: After | |||||
SpaceBeforeCaseColon: false | |||||
SpaceBeforeCtorInitializerColon: false | |||||
SpaceBeforeInheritanceColon: false | |||||
SpaceBeforeParens: Never | |||||
SpaceBeforeRangeBasedForLoopColon: false | |||||
Standard: Latest | |||||
TabWidth: 4 | |||||
UseTab: Never |
@ -0,0 +1,4 @@ | |||||
# These are supported funding model platforms | |||||
github: skypjack | |||||
custom: https://www.paypal.me/skypjack |
@ -0,0 +1,169 @@ | |||||
name: build | |||||
on: [push, pull_request] | |||||
jobs: | |||||
linux: | |||||
timeout-minutes: 15 | |||||
strategy: | |||||
matrix: | |||||
compiler: | |||||
- pkg: g++-7 | |||||
exe: g++-7 | |||||
- pkg: g++-8 | |||||
exe: g++-8 | |||||
- pkg: g++-9 | |||||
exe: g++-9 | |||||
- pkg: g++-10 | |||||
exe: g++-10 | |||||
- pkg: clang-8 | |||||
exe: clang++-8 | |||||
- pkg: clang-9 | |||||
exe: clang++-9 | |||||
- pkg: clang-10 | |||||
exe: clang++-10 | |||||
- pkg: clang-11 | |||||
exe: clang++-11 | |||||
- pkg: clang-12 | |||||
exe: clang++-12 | |||||
runs-on: ubuntu-latest | |||||
steps: | |||||
- uses: actions/checkout@v2 | |||||
- name: Install compiler | |||||
run: | | |||||
sudo apt update | |||||
sudo apt install -y ${{ matrix.compiler.pkg }} | |||||
- name: Compile tests | |||||
working-directory: build | |||||
env: | |||||
CXX: ${{ matrix.compiler.exe }} | |||||
run: | | |||||
cmake -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON .. | |||||
make -j4 | |||||
- name: Run tests | |||||
working-directory: build | |||||
env: | |||||
CTEST_OUTPUT_ON_FAILURE: 1 | |||||
run: ctest --timeout 30 -C Debug -j4 | |||||
linux-extra: | |||||
timeout-minutes: 15 | |||||
strategy: | |||||
matrix: | |||||
compiler: [g++, clang++] | |||||
id_type: ["std::uint32_t", "std::uint64_t"] | |||||
cxx_std: [cxx_std_17, cxx_std_20] | |||||
runs-on: ubuntu-latest | |||||
steps: | |||||
- uses: actions/checkout@v2 | |||||
- name: Compile tests | |||||
working-directory: build | |||||
env: | |||||
CXX: ${{ matrix.compiler }} | |||||
run: | | |||||
cmake -DENTT_BUILD_TESTING=ON -DENTT_CXX_STD=${{ matrix.cxx_std }} -DENTT_ID_TYPE=${{ matrix.id_type }} .. | |||||
make -j4 | |||||
- name: Run tests | |||||
working-directory: build | |||||
env: | |||||
CTEST_OUTPUT_ON_FAILURE: 1 | |||||
run: ctest --timeout 30 -C Debug -j4 | |||||
windows: | |||||
timeout-minutes: 15 | |||||
strategy: | |||||
matrix: | |||||
toolset: [default, v141, v142, clang-cl] | |||||
include: | |||||
- toolset: v141 | |||||
toolset_option: -T"v141" | |||||
- toolset: v142 | |||||
toolset_option: -T"v142" | |||||
- toolset: clang-cl | |||||
toolset_option: -T"ClangCl" | |||||
runs-on: windows-latest | |||||
steps: | |||||
- uses: actions/checkout@v2 | |||||
- name: Compile tests | |||||
working-directory: build | |||||
run: | | |||||
cmake -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON ${{ matrix.toolset_option }} .. | |||||
cmake --build . -j 4 | |||||
- name: Run tests | |||||
working-directory: build | |||||
env: | |||||
CTEST_OUTPUT_ON_FAILURE: 1 | |||||
run: ctest --timeout 30 -C Debug -j4 | |||||
windows-extra: | |||||
timeout-minutes: 15 | |||||
strategy: | |||||
matrix: | |||||
id_type: ["std::uint32_t", "std::uint64_t"] | |||||
cxx_std: [cxx_std_17, cxx_std_20] | |||||
runs-on: windows-latest | |||||
steps: | |||||
- uses: actions/checkout@v2 | |||||
- name: Compile tests | |||||
working-directory: build | |||||
run: | | |||||
cmake -DENTT_BUILD_TESTING=ON -DENTT_CXX_STD=${{ matrix.cxx_std }} -DENTT_ID_TYPE=${{ matrix.id_type }} .. | |||||
cmake --build . -j 4 | |||||
- name: Run tests | |||||
working-directory: build | |||||
env: | |||||
CTEST_OUTPUT_ON_FAILURE: 1 | |||||
run: ctest --timeout 30 -C Debug -j4 | |||||
macos: | |||||
timeout-minutes: 15 | |||||
runs-on: macOS-latest | |||||
steps: | |||||
- uses: actions/checkout@v2 | |||||
- name: Compile tests | |||||
working-directory: build | |||||
run: | | |||||
cmake -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON .. | |||||
make -j4 | |||||
- name: Run tests | |||||
working-directory: build | |||||
env: | |||||
CTEST_OUTPUT_ON_FAILURE: 1 | |||||
run: ctest --timeout 30 -C Debug -j4 | |||||
macos-extra: | |||||
timeout-minutes: 15 | |||||
strategy: | |||||
matrix: | |||||
id_type: ["std::uint32_t", "std::uint64_t"] | |||||
cxx_std: [cxx_std_17, cxx_std_20] | |||||
runs-on: macOS-latest | |||||
steps: | |||||
- uses: actions/checkout@v2 | |||||
- name: Compile tests | |||||
working-directory: build | |||||
run: | | |||||
cmake -DENTT_BUILD_TESTING=ON -DENTT_CXX_STD=${{ matrix.cxx_std }} -DENTT_ID_TYPE=${{ matrix.id_type }} .. | |||||
make -j4 | |||||
- name: Run tests | |||||
working-directory: build | |||||
env: | |||||
CTEST_OUTPUT_ON_FAILURE: 1 | |||||
run: ctest --timeout 30 -C Debug -j4 |
@ -0,0 +1,38 @@ | |||||
name: coverage | |||||
on: [push, pull_request] | |||||
jobs: | |||||
codecov: | |||||
timeout-minutes: 15 | |||||
runs-on: ubuntu-latest | |||||
steps: | |||||
- uses: actions/checkout@v2 | |||||
- name: Compile tests | |||||
working-directory: build | |||||
env: | |||||
CXXFLAGS: "--coverage -fno-inline" | |||||
CXX: g++ | |||||
run: | | |||||
cmake -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON .. | |||||
make -j4 | |||||
- name: Run tests | |||||
working-directory: build | |||||
env: | |||||
CTEST_OUTPUT_ON_FAILURE: 1 | |||||
run: ctest --timeout 30 -C Debug -j4 | |||||
- name: Collect data | |||||
working-directory: build | |||||
run: | | |||||
sudo apt install lcov | |||||
lcov -c -d . -o coverage.info | |||||
lcov -l coverage.info | |||||
- name: Upload coverage to Codecov | |||||
uses: codecov/codecov-action@v2 | |||||
with: | |||||
token: ${{ secrets.CODECOV_TOKEN }} | |||||
files: build/coverage.info | |||||
name: EnTT | |||||
fail_ci_if_error: true |
@ -0,0 +1,39 @@ | |||||
name: deploy | |||||
on: | |||||
release: | |||||
types: published | |||||
jobs: | |||||
homebrew-entt: | |||||
timeout-minutes: 5 | |||||
runs-on: ubuntu-latest | |||||
env: | |||||
GH_REPO: homebrew-entt | |||||
FORMULA: entt.rb | |||||
steps: | |||||
- uses: actions/checkout@v2 | |||||
- name: Clone repository | |||||
working-directory: build | |||||
env: | |||||
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} | |||||
run: git clone https://$GITHUB_ACTOR:$PERSONAL_ACCESS_TOKEN@github.com/$GITHUB_ACTOR/$GH_REPO.git | |||||
- name: Prepare formula | |||||
working-directory: build | |||||
run: | | |||||
cd $GH_REPO | |||||
curl "https://github.com/${{ github.repository }}/archive/${{ github.ref }}.tar.gz" --location --fail --silent --show-error --output archive.tar.gz | |||||
sed -i -e '/url/s/".*"/"'$(echo "https://github.com/${{ github.repository }}/archive/${{ github.ref }}.tar.gz" | sed -e 's/[\/&]/\\&/g')'"/' $FORMULA | |||||
sed -i -e '/sha256/s/".*"/"'$(openssl sha256 archive.tar.gz | cut -d " " -f 2)'"/' $FORMULA | |||||
- name: Update remote | |||||
working-directory: build | |||||
run: | | |||||
cd $GH_REPO | |||||
git config --local user.email "action@github.com" | |||||
git config --local user.name "$GITHUB_ACTOR" | |||||
git add $FORMULA | |||||
git commit -m "Update to ${{ github.ref }}" | |||||
git push origin master |
@ -0,0 +1,31 @@ | |||||
name: sanitizer | |||||
on: [push, pull_request] | |||||
jobs: | |||||
clang: | |||||
timeout-minutes: 15 | |||||
strategy: | |||||
matrix: | |||||
compiler: [clang++] | |||||
id_type: ["std::uint32_t", "std::uint64_t"] | |||||
cxx_std: [cxx_std_17, cxx_std_20] | |||||
runs-on: ubuntu-latest | |||||
steps: | |||||
- uses: actions/checkout@v2 | |||||
- name: Compile tests | |||||
working-directory: build | |||||
env: | |||||
CXX: ${{ matrix.compiler }} | |||||
run: | | |||||
cmake -DENTT_USE_SANITIZER=ON -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON -DENTT_CXX_STD=${{ matrix.cxx_std }} -DENTT_ID_TYPE=${{ matrix.id_type }} .. | |||||
make -j4 | |||||
- name: Run tests | |||||
working-directory: build | |||||
env: | |||||
CTEST_OUTPUT_ON_FAILURE: 1 | |||||
run: ctest --timeout 30 -C Debug -j4 |
@ -1,5 +1,13 @@ | |||||
*.user | |||||
# Conan | |||||
conan/test_package/build | conan/test_package/build | ||||
# IDEs | # IDEs | ||||
*.user | |||||
.idea | .idea | ||||
.vscode | |||||
.vs | |||||
CMakeSettings.json | |||||
cpp.hint | |||||
# Bazel | |||||
/bazel-* |
@ -1,97 +0,0 @@ | |||||
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,14 @@ | |||||
_msvc_copts = ["/std:c++17"] | |||||
_gcc_copts = ["-std=c++17"] | |||||
cc_library( | |||||
name = "entt", | |||||
visibility = ["//visibility:public"], | |||||
strip_include_prefix = "src", | |||||
hdrs = glob(["src/**/*.h", "src/**/*.hpp"]), | |||||
copts = select({ | |||||
"@bazel_tools//src/conditions:windows": _msvc_copts, | |||||
"@bazel_tools//src/conditions:windows_msvc": _msvc_copts, | |||||
"//conditions:default": _gcc_copts, | |||||
}), | |||||
) |
@ -1,25 +1,26 @@ | |||||
* 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 | * 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 | |||||
* work stealing job system (see #100) + mt scheduler based on const awareness for types | |||||
* add examples (and credits) from @alanjfs :) | |||||
EXAMPLES | |||||
* filter on runtime values/variables (not only types) | |||||
* support to polymorphic types (see #859) | |||||
WIP: | |||||
* view/group: no storage_traits dependency -> use storage instead of components for the definition | |||||
* basic_storage::bind for cross-registry setups | |||||
* uses-allocator construction: any (with allocator support), poly, ... | |||||
* process scheduler: reviews, use free lists internally | |||||
* iterator based try_emplace vs try_insert for perf reasons | |||||
* dedicated entity storage, in-place O(1) release/destroy for non-orphaned entities, out-of-sync model | |||||
* entity-only and exclude-only views | |||||
* custom allocators all over (sigh storage mixin, registry, ...) | |||||
* consider removing ENTT_NOEXCEPT, use ENTT_NOEXCEPT_IF (or noexcept(...)) as appropriate in any case (ie make compressed_pair conditionally noexcept) | |||||
* add test for maximum number of entities reached | |||||
WIP: | |||||
* add user data to type_info | |||||
* write documentation for custom storages and views!! | |||||
* make runtime views use opaque storage and therefore return also elements. | |||||
* entity-aware observer, add observer functions aside observer class | |||||
* deprecate non-owning groups in favor of owning views and view packs, introduce lazy owning views |
@ -0,0 +1 @@ | |||||
workspace(name = "com_github_skypjack_entt") |
@ -1,25 +0,0 @@ | |||||
# can use variables like {build} and {branch} | |||||
version: 1.0.{build} | |||||
image: Visual Studio 2017 | |||||
environment: | |||||
BUILD_DIR: "%APPVEYOR_BUILD_FOLDER%\\build" | |||||
platform: | |||||
- Win32 | |||||
configuration: | |||||
- Release | |||||
before_build: | |||||
- cd %BUILD_DIR% | |||||
- cmake .. -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 |
@ -1,6 +0,0 @@ | |||||
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() |
@ -1,11 +1,5 @@ | |||||
set(ENTT_VERSION "@PROJECT_VERSION@") | |||||
@PACKAGE_INIT@ | @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() | |||||
set(EnTT_VERSION "@PROJECT_VERSION@") | |||||
include("${CMAKE_CURRENT_LIST_DIR}/EnTTTargets.cmake") | |||||
check_required_components("@PROJECT_NAME@") | check_required_components("@PROJECT_NAME@") |
@ -1,19 +0,0 @@ | |||||
project(cereal-download NONE) | |||||
cmake_minimum_required(VERSION 3.2) | |||||
include(ExternalProject) | |||||
ExternalProject_Add( | |||||
cereal | |||||
GIT_REPOSITORY https://github.com/USCiLab/cereal.git | |||||
GIT_TAG v1.2.2 | |||||
DOWNLOAD_DIR ${CEREAL_DEPS_DIR} | |||||
TMP_DIR ${CEREAL_DEPS_DIR}/tmp | |||||
STAMP_DIR ${CEREAL_DEPS_DIR}/stamp | |||||
SOURCE_DIR ${CEREAL_DEPS_DIR}/src | |||||
BINARY_DIR ${CEREAL_DEPS_DIR}/build | |||||
CONFIGURE_COMMAND "" | |||||
BUILD_COMMAND "" | |||||
INSTALL_COMMAND "" | |||||
TEST_COMMAND "" | |||||
) |
@ -1,19 +0,0 @@ | |||||
project(duktape-download NONE) | |||||
cmake_minimum_required(VERSION 3.2) | |||||
include(ExternalProject) | |||||
ExternalProject_Add( | |||||
duktape | |||||
GIT_REPOSITORY https://github.com/svaarala/duktape-releases.git | |||||
GIT_TAG v2.2.0 | |||||
DOWNLOAD_DIR ${DUKTAPE_DEPS_DIR} | |||||
TMP_DIR ${DUKTAPE_DEPS_DIR}/tmp | |||||
STAMP_DIR ${DUKTAPE_DEPS_DIR}/stamp | |||||
SOURCE_DIR ${DUKTAPE_DEPS_DIR}/src | |||||
BINARY_DIR ${DUKTAPE_DEPS_DIR}/build | |||||
CONFIGURE_COMMAND "" | |||||
BUILD_COMMAND "" | |||||
INSTALL_COMMAND "" | |||||
TEST_COMMAND "" | |||||
) |
@ -0,0 +1,8 @@ | |||||
prefix=@CMAKE_INSTALL_PREFIX@ | |||||
includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ | |||||
Name: EnTT | |||||
Description: Gaming meets modern C++ | |||||
Url: https://github.com/skypjack/entt | |||||
Version: @ENTT_VERSION@ | |||||
Cflags: -I${includedir} |
@ -1,19 +0,0 @@ | |||||
project(googletest-download NONE) | |||||
cmake_minimum_required(VERSION 3.2) | |||||
include(ExternalProject) | |||||
ExternalProject_Add( | |||||
googletest | |||||
GIT_REPOSITORY https://github.com/google/googletest.git | |||||
GIT_TAG master | |||||
DOWNLOAD_DIR ${GOOGLETEST_DEPS_DIR} | |||||
TMP_DIR ${GOOGLETEST_DEPS_DIR}/tmp | |||||
STAMP_DIR ${GOOGLETEST_DEPS_DIR}/stamp | |||||
SOURCE_DIR ${GOOGLETEST_DEPS_DIR}/src | |||||
BINARY_DIR ${GOOGLETEST_DEPS_DIR}/build | |||||
CONFIGURE_COMMAND "" | |||||
BUILD_COMMAND "" | |||||
INSTALL_COMMAND "" | |||||
TEST_COMMAND "" | |||||
) |
@ -1,11 +0,0 @@ | |||||
#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 |
@ -1,2 +0,0 @@ | |||||
* | |||||
!.gitignore |
@ -0,0 +1,111 @@ | |||||
# Crash Course: configuration | |||||
<!-- | |||||
@cond TURN_OFF_DOXYGEN | |||||
--> | |||||
# Table of Contents | |||||
* [Introduction](#introduction) | |||||
* [Definitions](#definitions) | |||||
* [ENTT_NOEXCEPTION](#entt_noexcept) | |||||
* [ENTT_USE_ATOMIC](#entt_use_atomic) | |||||
* [ENTT_ID_TYPE](#entt_id_type) | |||||
* [ENTT_SPARSE_PAGE](#entt_sparse_page) | |||||
* [ENTT_PACKED_PAGE](#entt_packed_page) | |||||
* [ENTT_ASSERT](#entt_assert) | |||||
* [ENTT_DISABLE_ASSERT](#entt_disable_assert) | |||||
* [ENTT_NO_ETO](#entt_no_eto) | |||||
* [ENTT_STANDARD_CPP](#entt_standard_cpp) | |||||
<!-- | |||||
@endcond TURN_OFF_DOXYGEN | |||||
--> | |||||
# Introduction | |||||
`EnTT` doesn't offer many hooks for customization but it certainly offers | |||||
some.<br/> | |||||
In the vast majority of cases, users will have no interest in changing the | |||||
default parameters. For all other cases, the list of possible configurations | |||||
with which it's possible to adjust the behavior of the library at runtime can be | |||||
found below. | |||||
# Definitions | |||||
All options are intended as parameters to the compiler (or user-defined macros | |||||
within the compilation units, if preferred).<br/> | |||||
Each parameter can result in internal library definitions. It's not recommended | |||||
to try to also modify these definitions, since there is no guarantee that they | |||||
will remain stable over time unlike the options below. | |||||
## ENTT_NOEXCEPTION | |||||
This parameter can be used to switch off exception handling in `EnTT`.<br/> | |||||
To do this, simply define the variable without assigning any value to it. This | |||||
is roughly equivalent to setting the compiler flag `-ff-noexceptions`. | |||||
## ENTT_USE_ATOMIC | |||||
In general, `EnTT` doesn't offer primitives to support multi-threading. Many of | |||||
the features can be split over multiple threads without any explicit control and | |||||
the user is the only one who knows if and when a synchronization point is | |||||
required.<br/> | |||||
However, some features aren't easily accessible to users and can be made | |||||
thread-safe by means of this definition. | |||||
## ENTT_ID_TYPE | |||||
`entt::id_type` is directly controlled by this definition and widely used within | |||||
the library.<br/> | |||||
By default, its type is `std::uint32_t`. However, users can define a different | |||||
default type if necessary. | |||||
## ENTT_SPARSE_PAGE | |||||
It's known that the ECS module of `EnTT` is based on _sparse sets_. What is less | |||||
known perhaps is that the sparse arrays are paged to reduce memory usage.<br/> | |||||
Default size of pages (that is, the number of elements they contain) is 4096 but | |||||
users can adjust it if appropriate. In all case, the chosen value **must** be a | |||||
power of 2. | |||||
## ENTT_PACKED_PAGE | |||||
Similar to sparse arrays, packed arrays of components are paginated as well. In | |||||
However, int this case the aim isn't to reduce memory usage but to have pointer | |||||
stability upon component creation.<br/> | |||||
Default size of pages (that is, the number of elements they contain) is 1024 but | |||||
users can adjust it if appropriate. In all case, the chosen value **must** be a | |||||
power of 2. | |||||
## ENTT_ASSERT | |||||
For performance reasons, `EnTT` doesn't use exceptions or any other control | |||||
structures. In fact, it offers many features that result in undefined behavior | |||||
if not used correctly.<br/> | |||||
To get around this, the library relies on a lot of asserts for the purpose of | |||||
detecting errors in debug builds. By default, it uses `assert` internally, but | |||||
users are allowed to overwrite its behavior by setting this variable. | |||||
### ENTT_DISABLE_ASSERT | |||||
Assertions may in turn affect performance to an extent when enabled. Whether | |||||
`ENTT_ASSERT` is redefined or not, all asserts can be disabled at once by means | |||||
of this definition.<br/> | |||||
Note that `ENTT_DISABLE_ASSERT` takes precedence over the redefinition of | |||||
`ENTT_ASSERT` and is therefore meant to disable all controls no matter what. | |||||
## ENTT_NO_ETO | |||||
In order to reduce memory consumption and increase performance, empty types are | |||||
never stored by the ECS module of `EnTT`.<br/> | |||||
Use this variable to treat these types like all others and therefore to create a | |||||
dedicated storage for them. | |||||
## ENTT_STANDARD_CPP | |||||
`EnTT` mixes non-standard language features with others that are perfectly | |||||
compliant to offer some of its functionalities.<br/> | |||||
This definition will prevent the library from using non-standard techniques, | |||||
that is, functionalities that aren't fully compliant with the standard C++.<br/> | |||||
While there are no known portability issues at the time of this writing, this | |||||
should make the library fully portable anyway if needed. |
@ -0,0 +1,67 @@ | |||||
# Crash Course: containers | |||||
<!-- | |||||
@cond TURN_OFF_DOXYGEN | |||||
--> | |||||
# Table of Contents | |||||
* [Introduction](#introduction) | |||||
* [Containers](#containers) | |||||
* [Dense map](#dense-map) | |||||
* [Dense set](#dense-set) | |||||
<!-- | |||||
@endcond TURN_OFF_DOXYGEN | |||||
--> | |||||
# Introduction | |||||
The standard C++ library offers a wide range of containers and it's really | |||||
difficult to do better (although it's very easy to do worse, as many examples | |||||
available online demonstrate).<br/> | |||||
`EnTT` doesn't try in any way to replace what is offered by the standard. Quite | |||||
the opposite, given the widespread use that is made of standard containers.<br/> | |||||
However, the library also tries to fill a gap in features and functionality by | |||||
making available some containers initially developed for internal use. | |||||
This section of the library is likely to grow larger over time. However, for the | |||||
moment it's quite small and mainly aimed at satisfying some internal needs.<br/> | |||||
For all containers made available, full test coverage and stability over time is | |||||
guaranteed as usual. | |||||
# Containers | |||||
## Dense map | |||||
The dense map made available in `EnTT` is a hash map that aims to return a | |||||
packed array of elements, so as to reduce the number of jumps in memory during | |||||
iterations.<br/> | |||||
The implementation is based on _sparse sets_ and each bucket is identified by an | |||||
implicit list within the packed array itself. | |||||
The interface is very close to its counterpart in the standard library, that is, | |||||
`std::unordered_map`.<br/> | |||||
However, both local and non-local iterators returned by a dense map belong to | |||||
the input iterator category although they respectively model the concepts of a | |||||
_forward iterator_ type and a _random access iterator_ type.<br/> | |||||
This is because they return a pair of references rather than a reference to a | |||||
pair. In other words, dense maps return a so called _proxy iterator_ the value | |||||
type of which is: | |||||
* `std::pair<const Key &, Type &>` for non-const iterator types. | |||||
* `std::pair<const Key &, const Type &>` for const iterator types. | |||||
This is quite different from what any standard library map returns and should be | |||||
taken into account when looking for a drop-in replacement. | |||||
## Dense set | |||||
The dense set made available in `EnTT` is a hash set that aims to return a | |||||
packed array of elements, so as to reduce the number of jumps in memory during | |||||
iterations.<br/> | |||||
The implementation is based on _sparse sets_ and each bucket is identified by an | |||||
implicit list within the packed array itself. | |||||
The interface is in all respects similar to its counterpart in the standard | |||||
library, that is, `std::unordered_set`.<br/> | |||||
Therefore, there is no need to go into the API description. |
@ -0,0 +1,359 @@ | |||||
# Crash Course: poly | |||||
<!-- | |||||
@cond TURN_OFF_DOXYGEN | |||||
--> | |||||
# Table of Contents | |||||
* [Introduction](#introduction) | |||||
* [Other libraries](#other-libraries) | |||||
* [Concept and implementation](#concept-and-implementation) | |||||
* [Deduced interface](#deduced-interface) | |||||
* [Defined interface](#defined-interface) | |||||
* [Fulfill a concept](#fulfill-a-concept) | |||||
* [Inheritance](#inheritance) | |||||
* [Static polymorphism in the wild](#static-polymorphism-in-the-wild) | |||||
* [Storage size and alignment requirement](#storage-size-and-alignment-requirement) | |||||
<!-- | |||||
@endcond TURN_OFF_DOXYGEN | |||||
--> | |||||
# Introduction | |||||
Static polymorphism is a very powerful tool in C++, albeit sometimes cumbersome | |||||
to obtain.<br/> | |||||
This module aims to make it simple and easy to use. | |||||
The library allows to define _concepts_ as interfaces to fulfill with concrete | |||||
classes without having to inherit from a common base.<br/> | |||||
This is, among others, one of the advantages of static polymorphism in general | |||||
and of a generic wrapper like that offered by the `poly` class template in | |||||
particular.<br/> | |||||
What users get is an object that can be passed around as such and not through a | |||||
reference or a pointer, as happens when it comes to working with dynamic | |||||
polymorphism. | |||||
Since the `poly` class template makes use of `entt::any` internally, it also | |||||
supports most of its feature. Among the most important, the possibility to | |||||
create aliases to existing and thus unmanaged objects. This allows users to | |||||
exploit the static polymorphism while maintaining ownership of objects.<br/> | |||||
Likewise, the `poly` class template also benefits from the small buffer | |||||
optimization offered by the `entt::any` class and therefore minimizes the number | |||||
of allocations, avoiding them altogether where possible. | |||||
## Other libraries | |||||
There are some very interesting libraries regarding static polymorphism.<br/> | |||||
Among all, the two that I prefer are: | |||||
* [`dyno`](https://github.com/ldionne/dyno): runtime polymorphism done right. | |||||
* [`Poly`](https://github.com/facebook/folly/blob/master/folly/docs/Poly.md): | |||||
a class template that makes it easy to define a type-erasing polymorphic | |||||
object wrapper. | |||||
The former is admittedly an experimental library, with many interesting ideas. | |||||
I've some doubts about the usefulness of some feature in real world projects, | |||||
but perhaps my lack of experience comes into play here. In my opinion, its only | |||||
flaw is the API which I find slightly more cumbersome than other solutions.<br/> | |||||
The latter was undoubtedly a source of inspiration for this module, although I | |||||
opted for different choices in the implementation of both the final API and some | |||||
feature. | |||||
Either way, the authors are gurus of the C++ community, people I only have to | |||||
learn from. | |||||
# Concept and implementation | |||||
The first thing to do to create a _type-erasing polymorphic object wrapper_ (to | |||||
use the terminology introduced by Eric Niebler) is to define a _concept_ that | |||||
types will have to adhere to.<br/> | |||||
For this purpose, the library offers a single class that supports both deduced | |||||
and fully defined interfaces. Although having interfaces deduced automatically | |||||
is convenient and allows users to write less code in most cases, this has some | |||||
limitations and it's therefore useful to be able to get around the deduction by | |||||
providing a custom definition for the static virtual table. | |||||
Once the interface is defined, it will be sufficient to provide a generic | |||||
implementation to fulfill the concept.<br/> | |||||
Also in this case, the library allows customizations based on types or families | |||||
of types, so as to be able to go beyond the generic case where necessary. | |||||
## Deduced interface | |||||
This is how a concept with a deduced interface is introduced: | |||||
```cpp | |||||
struct Drawable: entt::type_list<> { | |||||
template<typename Base> | |||||
struct type: Base { | |||||
void draw() { this->template invoke<0>(*this); } | |||||
}; | |||||
// ... | |||||
}; | |||||
``` | |||||
It's recognizable by the fact that it inherits from an empty type list.<br/> | |||||
Functions can also be const, accept any number of parameters and return a type | |||||
other than `void`: | |||||
```cpp | |||||
struct Drawable: entt::type_list<> { | |||||
template<typename Base> | |||||
struct type: Base { | |||||
bool draw(int pt) const { return this->template invoke<0>(*this, pt); } | |||||
}; | |||||
// ... | |||||
}; | |||||
``` | |||||
In this case, all parameters must be passed to `invoke` after the reference to | |||||
`this` and the return value is whatever the internal call returns.<br/> | |||||
As for `invoke`, this is a name that is injected into the _concept_ through | |||||
`Base`, from which one must necessarily inherit. Since it's also a dependent | |||||
name, the `this-> template` form is unfortunately necessary due to the rules of | |||||
the language. However, there exists also an alternative that goes through an | |||||
external call: | |||||
```cpp | |||||
struct Drawable: entt::type_list<> { | |||||
template<typename Base> | |||||
struct type: Base { | |||||
void draw() const { entt::poly_call<0>(*this); } | |||||
}; | |||||
// ... | |||||
}; | |||||
``` | |||||
Once the _concept_ is defined, users must provide a generic implementation of it | |||||
in order to tell the system how any type can satisfy its requirements. This is | |||||
done via an alias template within the concept itself.<br/> | |||||
The index passed as a template parameter to either `invoke` or `poly_call` | |||||
refers to how this alias is defined. | |||||
## Defined interface | |||||
A fully defined concept is no different to one for which the interface is | |||||
deduced, with the only difference that the list of types is not empty this time: | |||||
```cpp | |||||
struct Drawable: entt::type_list<void()> { | |||||
template<typename Base> | |||||
struct type: Base { | |||||
void draw() { entt::poly_call<0>(*this); } | |||||
}; | |||||
// ... | |||||
}; | |||||
``` | |||||
Again, parameters and return values other than `void` are allowed. Also, the | |||||
function type must be const when the method to bind to it is const: | |||||
```cpp | |||||
struct Drawable: entt::type_list<bool(int) const> { | |||||
template<typename Base> | |||||
struct type: Base { | |||||
bool draw(int pt) const { return entt::poly_call<0>(*this, pt); } | |||||
}; | |||||
// ... | |||||
}; | |||||
``` | |||||
Why should a user fully define a concept if the function types are the same as | |||||
the deduced ones?<br> | |||||
Because, in fact, this is exactly the limitation that can be worked around by | |||||
manually defining the static virtual table. | |||||
When things are deduced, there is an implicit constraint.<br/> | |||||
If the concept exposes a member function called `draw` with function type | |||||
`void()`, a concept can be satisfied: | |||||
* Either by a class that exposes a member function with the same name and the | |||||
same signature. | |||||
* Or through a lambda that makes use of existing member functions from the | |||||
interface itself. | |||||
In other words, it's not possible to make use of functions not belonging to the | |||||
interface, even if they are present in the types that fulfill the concept.<br/> | |||||
Similarly, it's not possible to deduce a function in the static virtual table | |||||
with a function type different from that of the associated member function in | |||||
the interface itself. | |||||
Explicitly defining a static virtual table suppresses the deduction step and | |||||
allows maximum flexibility when providing the implementation for a concept. | |||||
## Fulfill a concept | |||||
The `impl` alias template of a concept is used to define how it's fulfilled: | |||||
```cpp | |||||
struct Drawable: entt::type_list<> { | |||||
// ... | |||||
template<typename Type> | |||||
using impl = entt::value_list<&Type::draw>; | |||||
}; | |||||
``` | |||||
In this case, it's stated that the `draw` method of a generic type will be | |||||
enough to satisfy the requirements of the `Drawable` concept.<br/> | |||||
Both member functions and free functions are supported to fulfill concepts: | |||||
```cpp | |||||
template<typename Type> | |||||
void print(Type &self) { self.print(); } | |||||
struct Drawable: entt::type_list<void()> { | |||||
// ... | |||||
template<typename Type> | |||||
using impl = entt::value_list<&print<Type>>; | |||||
}; | |||||
``` | |||||
Likewise, as long as the parameter types and return type support conversions to | |||||
and from those of the function type referenced in the static virtual table, the | |||||
actual implementation may differ in its function type since it's erased | |||||
internally.<br/> | |||||
Moreover, the `self` parameter isn't strictly required by the system and can be | |||||
left out for free functions if not required. | |||||
Refer to the inline documentation for more details. | |||||
# Inheritance | |||||
_Concept inheritance_ is straightforward due to how poly looks like in `EnTT`. | |||||
Therefore, it's quite easy to build hierarchies of concepts if necessary.<br/> | |||||
The only constraint is that all concepts in a hierarchy must belong to the same | |||||
_family_, that is, they must be either all deduced or all defined. | |||||
For a deduced concept, inheritance is achieved in a few steps: | |||||
```cpp | |||||
struct DrawableAndErasable: entt::type_list<> { | |||||
template<typename Base> | |||||
struct type: typename Drawable::template type<Base> { | |||||
static constexpr auto base = std::tuple_size_v<typename entt::poly_vtable<Drawable>::type>; | |||||
void erase() { entt::poly_call<base + 0>(*this); } | |||||
}; | |||||
template<typename Type> | |||||
using impl = entt::value_list_cat_t< | |||||
typename Drawable::impl<Type>, | |||||
entt::value_list<&Type::erase> | |||||
>; | |||||
}; | |||||
``` | |||||
The static virtual table is empty and must remain so.<br/> | |||||
On the other hand, `type` no longer inherits from `Base` and instead forwards | |||||
its template parameter to the type exposed by the _base class_. Internally, the | |||||
size of the static virtual table of the base class is used as an offset for the | |||||
local indexes.<br/> | |||||
Finally, by means of the `value_list_cat_t` utility, the implementation consists | |||||
in appending the new functions to the previous list. | |||||
As for a defined concept instead, also the list of types must be extended, in a | |||||
similar way to what is shown for the implementation of the above concept.<br/> | |||||
To do this, it's useful to declare a function that allows to convert a _concept_ | |||||
into its underlying `type_list` object: | |||||
```cpp | |||||
template<typename... Type> | |||||
entt::type_list<Type...> as_type_list(const entt::type_list<Type...> &); | |||||
``` | |||||
The definition isn't strictly required, since the function will only be used | |||||
through a `decltype` as it follows: | |||||
```cpp | |||||
struct DrawableAndErasable: entt::type_list_cat_t< | |||||
decltype(as_type_list(std::declval<Drawable>())), | |||||
entt::type_list<void()> | |||||
> { | |||||
// ... | |||||
}; | |||||
``` | |||||
Similar to above, `type_list_cat_t` is used to concatenate the underlying static | |||||
virtual table with the new function types.<br/> | |||||
Everything else is the same as already shown instead. | |||||
# Static polymorphism in the wild | |||||
Once the _concept_ and implementation have been introduced, it will be possible | |||||
to use the `poly` class template to contain instances that meet the | |||||
requirements: | |||||
```cpp | |||||
using drawable = entt::poly<Drawable>; | |||||
struct circle { | |||||
void draw() { /* ... */ } | |||||
}; | |||||
struct square { | |||||
void draw() { /* ... */ } | |||||
}; | |||||
// ... | |||||
drawable instance{circle{}}; | |||||
instance->draw(); | |||||
instance = square{}; | |||||
instance->draw(); | |||||
``` | |||||
The `poly` class template offers a wide range of constructors, from the default | |||||
one (which will return an uninitialized `poly` object) to the copy and move | |||||
constructors, as well as the ability to create objects in-place.<br/> | |||||
Among others, there is also a constructor that allows users to wrap unmanaged | |||||
objects in a `poly` instance (either const or non-const ones): | |||||
```cpp | |||||
circle shape; | |||||
drawable instance{std::in_place_type<circle &>, shape}; | |||||
``` | |||||
Similarly, it's possible to create non-owning copies of `poly` from an existing | |||||
object: | |||||
```cpp | |||||
drawable other = instance.as_ref(); | |||||
``` | |||||
In both cases, although the interface of the `poly` object doesn't change, it | |||||
won't construct any element or take care of destroying the referenced objects. | |||||
Note also how the underlying concept is accessed via a call to `operator->` and | |||||
not directly as `instance.draw()`.<br/> | |||||
This allows users to decouple the API of the wrapper from that of the concept. | |||||
Therefore, where `instance.data()` will invoke the `data` member function of the | |||||
poly object, `instance->data()` will map directly to the functionality exposed | |||||
by the underlying concept. | |||||
# Storage size and alignment requirement | |||||
Under the hood, the `poly` class template makes use of `entt::any`. Therefore, | |||||
it can take advantage of the possibility of defining at compile-time the size of | |||||
the storage suitable for the small buffer optimization as well as the alignment | |||||
requirements: | |||||
```cpp | |||||
entt::basic_poly<Drawable, sizeof(double[4]), alignof(double[4])> | |||||
``` | |||||
The default size is `sizeof(double[2])`, which seems like a good compromise | |||||
between a buffer that is too large and one unable to hold anything larger than | |||||
an integer. The alignment requirement is optional instead and by default such | |||||
that it's the most stringent (the largest) for any object whose size is at most | |||||
equal to the one provided.<br/> | |||||
It's worth noting that providing a size of 0 (which is an accepted value in all | |||||
respects) will force the system to dynamically allocate the contained objects in | |||||
all cases. |
@ -0,0 +1,75 @@ | |||||
# Similar projects | |||||
There are many projects similar to `EnTT`, both open source and not.<br/> | |||||
Some even borrowed some ideas from this library and expressed them in different | |||||
languages.<br/> | |||||
Others developed different architectures from scratch and therefore offer | |||||
alternative solutions with their pros and cons. | |||||
Below an incomplete list of those that I've come across so far.<br/> | |||||
If some terms or designs aren't clear, I recommend referring to the | |||||
[_ECS Back and Forth_](https://skypjack.github.io/tags/#ecs) series for all the | |||||
details. | |||||
I hope this list can grow much more in the future: | |||||
* C: | |||||
* [destral_ecs](https://github.com/roig/destral_ecs): a single-file ECS based | |||||
on sparse sets. | |||||
* [Diana](https://github.com/discoloda/Diana): an ECS that uses sparse sets to | |||||
keep track of entities in systems. | |||||
* [Flecs](https://github.com/SanderMertens/flecs): a multithreaded archetype | |||||
ECS based on semi-contiguous arrays rather than chunks. | |||||
* [lent](https://github.com/nem0/lent): the Donald Trump of the ECS libraries. | |||||
* C++: | |||||
* [decs](https://github.com/vblanco20-1/decs): a chunk based archetype ECS. | |||||
* [ecst](https://github.com/SuperV1234/ecst): a multithreaded compile-time | |||||
ECS that uses sparse sets to keep track of entities in systems. | |||||
* [EntityX](https://github.com/alecthomas/entityx): a bitset based ECS that | |||||
uses a single large matrix of components indexed with entities. | |||||
* [Gaia-ECS](https://github.com/richardbiely/gaia-ecs): a chunk based | |||||
archetype ECS. | |||||
* [Polypropylene](https://github.com/pmbittner/Polypropylene): a hybrid | |||||
solution between an ECS and dynamic mixins. | |||||
* C# | |||||
* [Entitas](https://github.com/sschmid/Entitas-CSharp): the ECS framework for | |||||
C# and Unity, where _reactive systems_ were invented. | |||||
* [LeoECS](https://github.com/Leopotam/ecs): simple lightweight C# Entity | |||||
Component System framework. | |||||
* [Svelto.ECS](https://github.com/sebas77/Svelto.ECS): a very interesting | |||||
platform agnostic and table based ECS framework. | |||||
* Go: | |||||
* [gecs](https://github.com/tutumagi/gecs): a sparse sets based ECS inspired | |||||
by `EnTT`. | |||||
* Javascript: | |||||
* [\@javelin/ecs](https://github.com/3mcd/javelin/tree/master/packages/ecs): | |||||
an archetype ECS in TypeScript. | |||||
* [ecsy](https://github.com/MozillaReality/ecsy): I haven't had the time to | |||||
investigate the underlying design of `ecsy` but it looks cool anyway. | |||||
* Perl: | |||||
* [Game::Entities](https://gitlab.com/jjatria/perl-game-entities): a simple | |||||
entity registry for ECS designs inspired by `EnTT`. | |||||
* Raku: | |||||
* [Game::Entities](https://gitlab.com/jjatria/raku-game-entities): a simple | |||||
entity registry for ECS designs inspired by `EnTT`. | |||||
* Rust: | |||||
* [Legion](https://github.com/TomGillen/legion): a chunk based archetype ECS. | |||||
* [Shipyard](https://github.com/leudz/shipyard): it borrows some ideas from | |||||
`EnTT` and offers a sparse sets based ECS with grouping functionalities. | |||||
* [Sparsey](https://github.com/LechintanTudor/sparsey): sparse set based ECS | |||||
written in Rust. | |||||
* [Specs](https://github.com/amethyst/specs): a parallel ECS based mainly on | |||||
hierarchical bitsets that allows different types of storage as needed. | |||||
* Zig | |||||
* [zig-ecs](https://github.com/prime31/zig-ecs): a _zig-ification_ of `EnTT`. | |||||
If you know of other resources out there that can be of interest for the reader, | |||||
feel free to open an issue or a PR and I'll be glad to add them to this page. |
@ -0,0 +1,107 @@ | |||||
# EnTT and Unreal Engine | |||||
<!-- | |||||
@cond TURN_OFF_DOXYGEN | |||||
--> | |||||
# Table of Contents | |||||
* [Enable Cpp17](#enable-cpp17) | |||||
* [EnTT as a third party module](#entt-as-a-third-party-module) | |||||
* [Include EnTT](#include-entt) | |||||
<!-- | |||||
@endcond TURN_OFF_DOXYGEN | |||||
--> | |||||
## Enable Cpp17 | |||||
As of writing (Unreal Engine v4.25), the default C++ standard of Unreal Engine | |||||
is C++14.<br/> | |||||
On the other hand, note that `EnTT` requires C++17 to compile. To enable it, in | |||||
the main module of the project there should be a `<Game Name>.Build.cs` file, | |||||
the constructor of which must contain the following lines: | |||||
```cs | |||||
PCHUsage = PCHUsageMode.NoSharedPCHs; | |||||
PrivatePCHHeaderFile = "<PCH filename>.h"; | |||||
CppStandard = CppStandardVersion.Cpp17; | |||||
``` | |||||
Replace `<PCH filename>.h` with the name of the already existing PCH header | |||||
file, if any.<br/> | |||||
In case the project doesn't already contain a file of this type, it's possible | |||||
to create one with the following content: | |||||
```cpp | |||||
#pragma once | |||||
#include "CoreMinimal.h" | |||||
``` | |||||
Remember to remove any old `PCHUsage = <...>` line that was previously there. At | |||||
this point, C++17 support should be in place.<br/> | |||||
Try to compile the project to ensure it works as expected before following | |||||
further steps. | |||||
Note that updating a *project* to C++17 doesn't necessarily mean that the IDE in | |||||
use will also start to recognize its syntax.<br/> | |||||
If the plan is to use C++17 in the project too, check the specific instructions | |||||
for the IDE in use. | |||||
## EnTT as a third party module | |||||
Once this point is reached, the `Source` directory should look like this: | |||||
``` | |||||
Source | |||||
| MyGame.Target.cs | |||||
| MyGameEditor.Target.cs | |||||
| | |||||
+---MyGame | |||||
| | MyGame.Build.cs | |||||
| | MyGame.h (PCH Header file) | |||||
| | |||||
\---ThirdParty | |||||
\---EnTT | |||||
| EnTT.Build.cs | |||||
| | |||||
\---entt (GitHub repository content inside) | |||||
``` | |||||
To make this happen, create the folder `ThirdParty` under `Source` if it doesn't | |||||
exist already. Then, add an `EnTT` folder under `ThirdParty`.<br/> | |||||
Within the latter, create a new file `EnTT.Build.cs` with the following content: | |||||
```cs | |||||
using System.IO; | |||||
using UnrealBuildTool; | |||||
public class EnTT: ModuleRules { | |||||
public EnTT(ReadOnlyTargetRules Target) : base(Target) { | |||||
Type = ModuleType.External; | |||||
PublicIncludePaths.Add(Path.Combine(ModuleDirectory, "entt", "src", "entt")); | |||||
} | |||||
} | |||||
``` | |||||
The last line indicates that the actual files will be found in the directory | |||||
`EnTT/entt/src/entt`.<br/> | |||||
Download the repository for `EnTT` and place it next to `EnTT.Build.cs` or | |||||
update the path above accordingly. | |||||
Finally, open the `<Game Name>.Build.cs` file and add `EnTT` as a dependency at | |||||
the end of the list: | |||||
```cs | |||||
PublicDependencyModuleNames.AddRange(new[] { | |||||
"Core", "CoreUObject", "Engine", "InputCore", [...], "EnTT" | |||||
}); | |||||
``` | |||||
Note that some IDEs might require a restart to start recognizing the new module | |||||
for code-highlighting features and such. | |||||
## Include EnTT | |||||
In any source file of the project, add `#include "entt.hpp"` or any other path | |||||
to the file from `EnTT` to use it.<br/> | |||||
Try to create a registry as `entt::registry registry;` to make sure everything | |||||
compiles fine. |
@ -0,0 +1,3 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> | |||||
</AutoVisualizer> |
@ -0,0 +1,33 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> | |||||
<Type Name="entt::dense_map<*>"> | |||||
<Intrinsic Name="size" Expression="packed.first_base::value.size()"/> | |||||
<Intrinsic Name="bucket_count" Expression="sparse.first_base::value.size()"/> | |||||
<DisplayString>{{ size={ size() } }}</DisplayString> | |||||
<Expand> | |||||
<Item Name="[capacity]" ExcludeView="simple">packed.first_base::value.capacity()</Item> | |||||
<Item Name="[bucket_count]" ExcludeView="simple">bucket_count()</Item> | |||||
<Item Name="[load_factor]" ExcludeView="simple">(float)size() / (float)bucket_count()</Item> | |||||
<Item Name="[max_load_factor]" ExcludeView="simple">threshold</Item> | |||||
<IndexListItems> | |||||
<Size>size()</Size> | |||||
<ValueNode>packed.first_base::value[$i].element</ValueNode> | |||||
</IndexListItems> | |||||
</Expand> | |||||
</Type> | |||||
<Type Name="entt::dense_set<*>"> | |||||
<Intrinsic Name="size" Expression="packed.first_base::value.size()"/> | |||||
<Intrinsic Name="bucket_count" Expression="sparse.first_base::value.size()"/> | |||||
<DisplayString>{{ size={ size() } }}</DisplayString> | |||||
<Expand> | |||||
<Item Name="[capacity]" ExcludeView="simple">packed.first_base::value.capacity()</Item> | |||||
<Item Name="[bucket_count]" ExcludeView="simple">bucket_count()</Item> | |||||
<Item Name="[load_factor]" ExcludeView="simple">(float)size() / (float)bucket_count()</Item> | |||||
<Item Name="[max_load_factor]" ExcludeView="simple">threshold</Item> | |||||
<IndexListItems> | |||||
<Size>size()</Size> | |||||
<ValueNode>packed.first_base::value[$i].second</ValueNode> | |||||
</IndexListItems> | |||||
</Expand> | |||||
</Type> | |||||
</AutoVisualizer> |
@ -0,0 +1,33 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> | |||||
<Type Name="entt::basic_any<*>"> | |||||
<DisplayString>{{ type={ info->alias,na }, policy={ mode,en } }}</DisplayString> | |||||
</Type> | |||||
<Type Name="entt::compressed_pair<*>"> | |||||
<Intrinsic Name="first" Optional="true" Expression="((first_base*)this)->value"/> | |||||
<Intrinsic Name="first" Optional="true" Expression="*(first_base::base_type*)this"/> | |||||
<Intrinsic Name="second" Optional="true" Expression="((second_base*)this)->value"/> | |||||
<Intrinsic Name="second" Optional="true" Expression="*(second_base::base_type*)this"/> | |||||
<DisplayString >({ first() }, { second() })</DisplayString> | |||||
<Expand> | |||||
<Item Name="[first]">first()</Item> | |||||
<Item Name="[second]">second()</Item> | |||||
</Expand> | |||||
</Type> | |||||
<Type Name="entt::basic_hashed_string<*>"> | |||||
<DisplayString Condition="base_type::repr != nullptr">{{ hash={ base_type::hash } }}</DisplayString> | |||||
<DisplayString>{{}}</DisplayString> | |||||
<Expand> | |||||
<Item Name="[data]">base_type::repr,na</Item> | |||||
<Item Name="[length]">base_type::length</Item> | |||||
</Expand> | |||||
</Type> | |||||
<Type Name="entt::type_info"> | |||||
<DisplayString Condition="seq != 0u">{{ name={ alias,na } }}</DisplayString> | |||||
<DisplayString>{{}}</DisplayString> | |||||
<Expand> | |||||
<Item Name="[hash]">identifier</Item> | |||||
<Item Name="[index]">seq</Item> | |||||
</Expand> | |||||
</Type> | |||||
</AutoVisualizer> |
@ -0,0 +1,145 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> | |||||
<Type Name="entt::basic_registry<*>"> | |||||
<Intrinsic Name="pools_size" Expression="pools.packed.first_base::value.size()"/> | |||||
<Intrinsic Name="vars_size" Expression="vars.data.packed.first_base::value.size()"/> | |||||
<Intrinsic Name="to_entity" Expression="*((entity_traits::entity_type *)&entity) & entity_traits::entity_mask"> | |||||
<Parameter Name="entity" Type="entity_traits::value_type &"/> | |||||
</Intrinsic> | |||||
<DisplayString>{{ size={ entities.size() } }}</DisplayString> | |||||
<Expand> | |||||
<Item IncludeView="simple" Name="[entities]">entities,view(simple)nr</Item> | |||||
<Synthetic Name="[entities]" ExcludeView="simple"> | |||||
<DisplayString>{ entities.size() }</DisplayString> | |||||
<Expand> | |||||
<CustomListItems> | |||||
<Variable Name="pos" InitialValue="0" /> | |||||
<Variable Name="last" InitialValue="entities.size()"/> | |||||
<Loop> | |||||
<Break Condition="pos == last"/> | |||||
<If Condition="to_entity(entities[pos]) == pos"> | |||||
<Item Name="[{ pos }]">entities[pos]</Item> | |||||
</If> | |||||
<Exec>++pos</Exec> | |||||
</Loop> | |||||
</CustomListItems> | |||||
</Expand> | |||||
</Synthetic> | |||||
<Synthetic Name="[destroyed]" ExcludeView="simple"> | |||||
<DisplayString>{ to_entity(free_list) != entity_traits::entity_mask }</DisplayString> | |||||
<Expand> | |||||
<CustomListItems> | |||||
<Variable Name="it" InitialValue="to_entity(free_list)" /> | |||||
<Loop> | |||||
<Break Condition="it == entity_traits::entity_mask"/> | |||||
<Item Name="[{ it }]">entities[it]</Item> | |||||
<Exec>it = to_entity(entities[it])</Exec> | |||||
</Loop> | |||||
</CustomListItems> | |||||
</Expand> | |||||
</Synthetic> | |||||
<Synthetic Name="[pools]"> | |||||
<DisplayString>{ pools_size() }</DisplayString> | |||||
<Expand> | |||||
<IndexListItems ExcludeView="simple"> | |||||
<Size>pools_size()</Size> | |||||
<ValueNode>*pools.packed.first_base::value[$i].element.second</ValueNode> | |||||
</IndexListItems> | |||||
<IndexListItems IncludeView="simple"> | |||||
<Size>pools_size()</Size> | |||||
<ValueNode>*pools.packed.first_base::value[$i].element.second,view(simple)</ValueNode> | |||||
</IndexListItems> | |||||
</Expand> | |||||
</Synthetic> | |||||
<Item Name="[groups]" ExcludeView="simple">groups.size()</Item> | |||||
<Synthetic Name="[vars]"> | |||||
<DisplayString>{ vars_size() }</DisplayString> | |||||
<Expand> | |||||
<IndexListItems> | |||||
<Size>vars_size()</Size> | |||||
<ValueNode>vars.data.packed.first_base::value[$i].element.second</ValueNode> | |||||
</IndexListItems> | |||||
</Expand> | |||||
</Synthetic> | |||||
</Expand> | |||||
</Type> | |||||
<Type Name="entt::basic_sparse_set<*>"> | |||||
<DisplayString>{{ size={ packed.size() }, type={ info->alias,na } }}</DisplayString> | |||||
<Expand> | |||||
<Item Name="[capacity]" ExcludeView="simple">packed.capacity()</Item> | |||||
<Item Name="[policy]">mode,en</Item> | |||||
<Synthetic Name="[sparse]"> | |||||
<DisplayString>{ sparse.size() * entity_traits::page_size }</DisplayString> | |||||
<Expand> | |||||
<ExpandedItem IncludeView="simple">sparse,view(simple)</ExpandedItem> | |||||
<CustomListItems ExcludeView="simple"> | |||||
<Variable Name="pos" InitialValue="0"/> | |||||
<Variable Name="page" InitialValue="0"/> | |||||
<Variable Name="offset" InitialValue="0"/> | |||||
<Variable Name="last" InitialValue="sparse.size() * entity_traits::page_size"/> | |||||
<Loop> | |||||
<Break Condition="pos == last"/> | |||||
<Exec>page = pos / entity_traits::page_size</Exec> | |||||
<Exec>offset = pos & (entity_traits::page_size - 1)</Exec> | |||||
<If Condition="sparse[page] && (*((entity_traits::entity_type *)&sparse[page][offset]) < ~entity_traits::entity_mask)"> | |||||
<Item Name="[{ pos }]">*((entity_traits::entity_type *)&sparse[page][offset]) & entity_traits::entity_mask</Item> | |||||
</If> | |||||
<Exec>++pos</Exec> | |||||
</Loop> | |||||
</CustomListItems> | |||||
</Expand> | |||||
</Synthetic> | |||||
<Synthetic Name="[packed]"> | |||||
<DisplayString>{ packed.size() }</DisplayString> | |||||
<Expand> | |||||
<ExpandedItem IncludeView="simple">packed,view(simple)</ExpandedItem> | |||||
<CustomListItems ExcludeView="simple"> | |||||
<Variable Name="pos" InitialValue="0"/> | |||||
<Variable Name="last" InitialValue="packed.size()"/> | |||||
<Loop> | |||||
<Break Condition="pos == last"/> | |||||
<If Condition="*((entity_traits::entity_type *)&packed[pos]) < ~entity_traits::entity_mask"> | |||||
<Item Name="[{ pos }]">packed[pos]</Item> | |||||
</If> | |||||
<Exec>++pos</Exec> | |||||
</Loop> | |||||
</CustomListItems> | |||||
</Expand> | |||||
</Synthetic> | |||||
</Expand> | |||||
</Type> | |||||
<Type Name="entt::basic_storage<*>"> | |||||
<DisplayString>{{ size={ base_type::packed.size() }, type={ base_type::info->alias,na } }}</DisplayString> | |||||
<Expand> | |||||
<Item Name="[capacity]" Optional="true" ExcludeView="simple">packed.first_base::value.capacity() * comp_traits::page_size</Item> | |||||
<Item Name="[page size]" Optional="true" ExcludeView="simple">comp_traits::page_size</Item> | |||||
<Item Name="[base]" ExcludeView="simple">(base_type*)this,nand</Item> | |||||
<Item Name="[base]" IncludeView="simple">(base_type*)this,view(simple)nand</Item> | |||||
<!-- having SFINAE-like techniques in natvis is priceless :) --> | |||||
<CustomListItems Condition="packed.first_base::value.size() != 0" Optional="true"> | |||||
<Variable Name="pos" InitialValue="0" /> | |||||
<Variable Name="last" InitialValue="base_type::packed.size()"/> | |||||
<Loop> | |||||
<Break Condition="pos == last"/> | |||||
<If Condition="*((base_type::entity_traits::entity_type *)&base_type::packed[pos]) < ~base_type::entity_traits::entity_mask"> | |||||
<Item Name="[{ pos }]">packed.first_base::value[pos / comp_traits::page_size][pos & (comp_traits::page_size - 1)]</Item> | |||||
</If> | |||||
<Exec>++pos</Exec> | |||||
</Loop> | |||||
</CustomListItems> | |||||
</Expand> | |||||
</Type> | |||||
<Type Name="entt::basic_view<*>"> | |||||
<DisplayString>{{ size_hint={ view->packed.size() } }}</DisplayString> | |||||
<Expand> | |||||
<Item Name="[pools]">pools,na</Item> | |||||
<Item Name="[filter]">filter,na</Item> | |||||
</Expand> | |||||
</Type> | |||||
<Type Name="entt::null_t"> | |||||
<DisplayString><null></DisplayString> | |||||
</Type> | |||||
<Type Name="entt::tombstone_t"> | |||||
<DisplayString><tombstone></DisplayString> | |||||
</Type> | |||||
</AutoVisualizer> |
@ -0,0 +1,3 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> | |||||
</AutoVisualizer> |
@ -0,0 +1,197 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> | |||||
<Type Name="entt::meta_any"> | |||||
<DisplayString Condition="node != nullptr">{{ type={ node->info->alias,na }, policy={ storage.mode,en } }}</DisplayString> | |||||
<DisplayString>{{}}</DisplayString> | |||||
<Expand> | |||||
<ExpandedItem Condition="node != nullptr">*node</ExpandedItem> | |||||
</Expand> | |||||
</Type> | |||||
<Type Name="entt::meta_associative_container"> | |||||
<DisplayString Condition="mapped_type_node != nullptr">{{ key_type={ key_type_node->info->alias,na }, mapped_type={ mapped_type_node->info->alias,na } }}</DisplayString> | |||||
<DisplayString Condition="key_type_node != nullptr">{{ key_type={ key_type_node->info->alias,na } }}</DisplayString> | |||||
<DisplayString>{{}}</DisplayString> | |||||
</Type> | |||||
<Type Name="entt::internal::meta_base_node"> | |||||
<DisplayString Condition="type != nullptr">{{ type={ type->info->alias,na } }}</DisplayString> | |||||
<DisplayString>{{}}</DisplayString> | |||||
</Type> | |||||
<Type Name="entt::internal::meta_conv_node"> | |||||
<DisplayString Condition="type != nullptr">{{ type={ type->info->alias,na } }}</DisplayString> | |||||
<DisplayString>{{}}</DisplayString> | |||||
</Type> | |||||
<Type Name="entt::internal::meta_ctor_node"> | |||||
<DisplayString Condition="arg != nullptr">{{ arity={ arity } }}</DisplayString> | |||||
<DisplayString>{{}}</DisplayString> | |||||
</Type> | |||||
<Type Name="entt::internal::meta_data_node"> | |||||
<Intrinsic Name="has_property" Expression="!!(traits & property)"> | |||||
<Parameter Name="property" Type="int"/> | |||||
</Intrinsic> | |||||
<DisplayString Condition="type != nullptr">{{ type={ type->info->alias,na } }}</DisplayString> | |||||
<DisplayString>{{}}</DisplayString> | |||||
<Expand> | |||||
<Item Name="[id]">id</Item> | |||||
<Item Name="[arity]">arity</Item> | |||||
<Item Name="[is_const]">has_property(entt::internal::meta_traits::is_const)</Item> | |||||
<Item Name="[is_static]">has_property(entt::internal::meta_traits::is_static)</Item> | |||||
<Synthetic Name="[prop]" Condition="prop != nullptr"> | |||||
<Expand> | |||||
<LinkedListItems> | |||||
<HeadPointer>prop</HeadPointer> | |||||
<NextPointer>next</NextPointer> | |||||
<ValueNode>*this</ValueNode> | |||||
</LinkedListItems> | |||||
</Expand> | |||||
</Synthetic> | |||||
</Expand> | |||||
</Type> | |||||
<Type Name="entt::meta_data"> | |||||
<DisplayString Condition="node != nullptr">{ *node }</DisplayString> | |||||
<DisplayString>{{}}</DisplayString> | |||||
<Expand> | |||||
<ExpandedItem Condition="node != nullptr">node</ExpandedItem> | |||||
</Expand> | |||||
</Type> | |||||
<Type Name="entt::internal::meta_func_node" > | |||||
<Intrinsic Name="has_property" Expression="!!(traits & property)"> | |||||
<Parameter Name="property" Type="int"/> | |||||
</Intrinsic> | |||||
<DisplayString Condition="ret != nullptr">{{ arity={ arity }, ret={ ret->info->alias,na } }}</DisplayString> | |||||
<DisplayString>{{}}</DisplayString> | |||||
<Expand> | |||||
<Item Name="[id]">id</Item> | |||||
<Item Name="[is_const]">has_property(entt::internal::meta_traits::is_const)</Item> | |||||
<Item Name="[is_static]">has_property(entt::internal::meta_traits::is_static)</Item> | |||||
<Synthetic Name="[prop]" Condition="prop != nullptr"> | |||||
<Expand> | |||||
<LinkedListItems> | |||||
<HeadPointer>prop</HeadPointer> | |||||
<NextPointer>next</NextPointer> | |||||
<ValueNode>*this</ValueNode> | |||||
</LinkedListItems> | |||||
</Expand> | |||||
</Synthetic> | |||||
</Expand> | |||||
</Type> | |||||
<Type Name="entt::meta_func"> | |||||
<DisplayString Condition="node != nullptr">{ *node }</DisplayString> | |||||
<DisplayString>{{}}</DisplayString> | |||||
<Expand> | |||||
<ExpandedItem Condition="node != nullptr">node</ExpandedItem> | |||||
</Expand> | |||||
</Type> | |||||
<Type Name="entt::meta_handle"> | |||||
<DisplayString>{ any }</DisplayString> | |||||
</Type> | |||||
<Type Name="entt::internal::meta_prop_node"> | |||||
<DisplayString Condition="value.node != nullptr">{{ key_type={ id.node->info->alias,na }, mapped_type={ value.node->info->alias,na } }}</DisplayString> | |||||
<DisplayString Condition="id.node != nullptr">{{ key_type={ id.node->info->alias,na } }}</DisplayString> | |||||
<DisplayString>{{}}</DisplayString> | |||||
<Expand> | |||||
<Item Name="[key]">id</Item> | |||||
<Item Name="[value]">value</Item> | |||||
</Expand> | |||||
</Type> | |||||
<Type Name="entt::meta_prop"> | |||||
<DisplayString Condition="node != nullptr">{ *node }</DisplayString> | |||||
<DisplayString>{{}}</DisplayString> | |||||
<Expand> | |||||
<ExpandedItem Condition="node != nullptr">node</ExpandedItem> | |||||
</Expand> | |||||
</Type> | |||||
<Type Name="entt::meta_sequence_container"> | |||||
<DisplayString Condition="value_type_node != nullptr">{{ value_type={ value_type_node->info->alias,na } }}</DisplayString> | |||||
<DisplayString>{{}}</DisplayString> | |||||
</Type> | |||||
<Type Name="entt::internal::meta_template_node"> | |||||
<DisplayString Condition="type != nullptr">{{ type={ type->info->alias,na } }}</DisplayString> | |||||
<DisplayString>{{}}</DisplayString> | |||||
<Expand> | |||||
<Item Name="[arity]">arity</Item> | |||||
</Expand> | |||||
</Type> | |||||
<Type Name="entt::internal::meta_type_node"> | |||||
<Intrinsic Name="has_property" Expression="!!(traits & property)"> | |||||
<Parameter Name="property" Type="int"/> | |||||
</Intrinsic> | |||||
<DisplayString Condition="info != nullptr">{{ type={ info->alias,na } }}</DisplayString> | |||||
<DisplayString>{{}}</DisplayString> | |||||
<Expand> | |||||
<Item Name="[id]">id</Item> | |||||
<Item Name="[sizeof]">size_of</Item> | |||||
<Item Name="[is_arithmetic]">has_property(entt::internal::meta_traits::is_arithmetic)</Item> | |||||
<Item Name="[is_array]">has_property(entt::internal::meta_traits::is_array)</Item> | |||||
<Item Name="[is_enum]">has_property(entt::internal::meta_traits::is_enum)</Item> | |||||
<Item Name="[is_class]">has_property(entt::internal::meta_traits::is_class)</Item> | |||||
<Item Name="[is_pointer]">has_property(entt::internal::meta_traits::is_pointer)</Item> | |||||
<Item Name="[is_meta_pointer_like]">has_property(entt::internal::meta_traits::is_meta_pointer_like)</Item> | |||||
<Item Name="[is_meta_sequence_container]">has_property(entt::internal::meta_traits::is_meta_sequence_container)</Item> | |||||
<Item Name="[is_meta_associative_container]">has_property(entt::internal::meta_traits::is_meta_associative_container)</Item> | |||||
<Item Name="[default_constructor]">default_constructor != nullptr</Item> | |||||
<Item Name="[conversion_helper]">conversion_helper != nullptr</Item> | |||||
<Item Name="[template_info]" Condition="templ != nullptr">*templ</Item> | |||||
<Synthetic Name="[ctor]" Condition="ctor != nullptr"> | |||||
<Expand> | |||||
<LinkedListItems> | |||||
<HeadPointer>ctor</HeadPointer> | |||||
<NextPointer>next</NextPointer> | |||||
<ValueNode>*this</ValueNode> | |||||
</LinkedListItems> | |||||
</Expand> | |||||
</Synthetic> | |||||
<Synthetic Name="[base]" Condition="base != nullptr"> | |||||
<Expand> | |||||
<LinkedListItems> | |||||
<HeadPointer>base</HeadPointer> | |||||
<NextPointer>next</NextPointer> | |||||
<ValueNode>*this</ValueNode> | |||||
</LinkedListItems> | |||||
</Expand> | |||||
</Synthetic> | |||||
<Synthetic Name="[conv]" Condition="conv != nullptr"> | |||||
<Expand> | |||||
<LinkedListItems> | |||||
<HeadPointer>conv</HeadPointer> | |||||
<NextPointer>next</NextPointer> | |||||
<ValueNode>*this</ValueNode> | |||||
</LinkedListItems> | |||||
</Expand> | |||||
</Synthetic> | |||||
<Synthetic Name="[data]" Condition="data != nullptr"> | |||||
<Expand> | |||||
<LinkedListItems> | |||||
<HeadPointer>data</HeadPointer> | |||||
<NextPointer>next</NextPointer> | |||||
<ValueNode>*this</ValueNode> | |||||
</LinkedListItems> | |||||
</Expand> | |||||
</Synthetic> | |||||
<Synthetic Name="[func]" Condition="func != nullptr"> | |||||
<Expand> | |||||
<LinkedListItems> | |||||
<HeadPointer>func</HeadPointer> | |||||
<NextPointer>next</NextPointer> | |||||
<ValueNode>*this</ValueNode> | |||||
</LinkedListItems> | |||||
</Expand> | |||||
</Synthetic> | |||||
<Synthetic Name="[prop]" Condition="prop != nullptr"> | |||||
<Expand> | |||||
<LinkedListItems> | |||||
<HeadPointer>prop</HeadPointer> | |||||
<NextPointer>next</NextPointer> | |||||
<ValueNode>*this</ValueNode> | |||||
</LinkedListItems> | |||||
</Expand> | |||||
</Synthetic> | |||||
</Expand> | |||||
</Type> | |||||
<Type Name="entt::meta_type"> | |||||
<DisplayString Condition="node != nullptr">{ *node }</DisplayString> | |||||
<DisplayString>{{}}</DisplayString> | |||||
<Expand> | |||||
<ExpandedItem Condition="node != nullptr">node</ExpandedItem> | |||||
</Expand> | |||||
</Type> | |||||
</AutoVisualizer> |
@ -0,0 +1,3 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> | |||||
</AutoVisualizer> |
@ -0,0 +1,6 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> | |||||
<Type Name="entt::basic_poly<*>"> | |||||
<DisplayString>{ storage }</DisplayString> | |||||
</Type> | |||||
</AutoVisualizer> |
@ -0,0 +1,3 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> | |||||
</AutoVisualizer> |
@ -0,0 +1,15 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> | |||||
<Type Name="entt::resource<*>"> | |||||
<DisplayString>{ value }</DisplayString> | |||||
<Expand> | |||||
<ExpandedItem>value</ExpandedItem> | |||||
</Expand> | |||||
</Type> | |||||
<Type Name="entt::resource_cache<*>"> | |||||
<DisplayString>{ pool.first_base::value }</DisplayString> | |||||
<Expand> | |||||
<ExpandedItem>pool.first_base::value</ExpandedItem> | |||||
</Expand> | |||||
</Type> | |||||
</AutoVisualizer> |
@ -0,0 +1,52 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> | |||||
<Type Name="entt::connection"> | |||||
<DisplayString>{{ bound={ signal != nullptr } }}</DisplayString> | |||||
</Type> | |||||
<Type Name="entt::delegate<*>"> | |||||
<DisplayString>{{ type={ "$T1" } }}</DisplayString> | |||||
<Expand> | |||||
<Item Name="[empty]">fn == nullptr</Item> | |||||
<Item Name="[data]">instance</Item> | |||||
</Expand> | |||||
</Type> | |||||
<Type Name="entt::dispatcher"> | |||||
<DisplayString>{{ size={ pools.size() } }}</DisplayString> | |||||
<Expand> | |||||
<Synthetic Name="[pools]"> | |||||
<DisplayString>{ pools.size() }</DisplayString> | |||||
<Expand> | |||||
<IndexListItems> | |||||
<Size>pools.size()</Size> | |||||
<ValueNode>*pools.packed.first_base::value[$i].element.second</ValueNode> | |||||
</IndexListItems> | |||||
</Expand> | |||||
</Synthetic> | |||||
</Expand> | |||||
</Type> | |||||
<Type Name="entt::internal::dispatcher_handler<*>"> | |||||
<DisplayString>{{ size={ events.size() }, event={ "$T1" } }}</DisplayString> | |||||
<Expand> | |||||
<Item Name="[signal]">signal</Item> | |||||
</Expand> | |||||
</Type> | |||||
<Type Name="entt::scoped_connection"> | |||||
<DisplayString>{ conn }</DisplayString> | |||||
</Type> | |||||
<Type Name="entt::sigh<*>"> | |||||
<DisplayString>{{ size={ calls.size() }, type={ "$T1" } }}</DisplayString> | |||||
<Expand> | |||||
<IndexListItems> | |||||
<Size>calls.size()</Size> | |||||
<ValueNode>calls[$i]</ValueNode> | |||||
</IndexListItems> | |||||
</Expand> | |||||
</Type> | |||||
<Type Name="entt::sink<*>"> | |||||
<DisplayString>{{ type={ "$T1" } }}</DisplayString> | |||||
<Expand> | |||||
<Item Name="[signal]">signal,na</Item> | |||||
<Item Name="[offset]">offset</Item> | |||||
</Expand> | |||||
</Type> | |||||
</AutoVisualizer> |
@ -1,3 +0,0 @@ | |||||
#!/bin/sh | |||||
scripts/update_homebrew.sh $1 |
@ -1,44 +1,79 @@ | |||||
#ifndef ENTT_CONFIG_CONFIG_H | #ifndef ENTT_CONFIG_CONFIG_H | ||||
#define ENTT_CONFIG_CONFIG_H | #define ENTT_CONFIG_CONFIG_H | ||||
#include "version.h" | |||||
#ifndef ENTT_NOEXCEPT | |||||
#define ENTT_NOEXCEPT noexcept | |||||
#endif // ENTT_NOEXCEPT | |||||
#ifndef ENTT_HS_SUFFIX | |||||
#define ENTT_HS_SUFFIX _hs | |||||
#endif // ENTT_HS_SUFFIX | |||||
#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) | |||||
# define ENTT_THROW throw | |||||
# define ENTT_TRY try | |||||
# define ENTT_CATCH catch(...) | |||||
#else | |||||
# define ENTT_THROW | |||||
# define ENTT_TRY if(true) | |||||
# define ENTT_CATCH if(false) | |||||
#endif | |||||
#ifndef ENTT_NOEXCEPT | |||||
# define ENTT_NOEXCEPT noexcept | |||||
# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) | |||||
# else | |||||
# define ENTT_NOEXCEPT_IF(...) | |||||
#endif | |||||
#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 | |||||
#ifdef ENTT_USE_ATOMIC | |||||
# include <atomic> | |||||
# define ENTT_MAYBE_ATOMIC(Type) std::atomic<Type> | |||||
#else | |||||
# define ENTT_MAYBE_ATOMIC(Type) Type | |||||
#endif | |||||
#ifndef ENTT_ID_TYPE | #ifndef ENTT_ID_TYPE | ||||
#include <cstdint> | |||||
#define ENTT_ID_TYPE std::uint32_t | |||||
#endif // ENTT_ID_TYPE | |||||
# include <cstdint> | |||||
# define ENTT_ID_TYPE std::uint32_t | |||||
#endif | |||||
#ifndef ENTT_SPARSE_PAGE | |||||
# define ENTT_SPARSE_PAGE 4096 | |||||
#endif | |||||
#ifndef ENTT_PACKED_PAGE | |||||
# define ENTT_PACKED_PAGE 1024 | |||||
#endif | |||||
#ifndef ENTT_PAGE_SIZE | |||||
#define ENTT_PAGE_SIZE 32768 | |||||
#endif // ENTT_PAGE_SIZE | |||||
#ifdef ENTT_DISABLE_ASSERT | |||||
# undef ENTT_ASSERT | |||||
# define ENTT_ASSERT(...) (void(0)) | |||||
#elif !defined ENTT_ASSERT | |||||
# include <cassert> | |||||
# define ENTT_ASSERT(condition, ...) assert(condition) | |||||
#endif | |||||
#ifdef ENTT_NO_ETO | |||||
# define ENTT_IGNORE_IF_EMPTY false | |||||
#else | |||||
# define ENTT_IGNORE_IF_EMPTY true | |||||
#endif | |||||
#ifndef ENTT_DISABLE_ASSERT | |||||
#include <cassert> | |||||
#define ENTT_ASSERT(condition) assert(condition) | |||||
#else // ENTT_DISABLE_ASSERT | |||||
#define ENTT_ASSERT(...) ((void)0) | |||||
#endif // ENTT_DISABLE_ASSERT | |||||
#ifdef ENTT_STANDARD_CPP | |||||
# define ENTT_NONSTD false | |||||
#else | |||||
# define ENTT_NONSTD true | |||||
# if defined __clang__ || defined __GNUC__ | |||||
# define ENTT_PRETTY_FUNCTION __PRETTY_FUNCTION__ | |||||
# define ENTT_PRETTY_FUNCTION_PREFIX '=' | |||||
# define ENTT_PRETTY_FUNCTION_SUFFIX ']' | |||||
# elif defined _MSC_VER | |||||
# define ENTT_PRETTY_FUNCTION __FUNCSIG__ | |||||
# define ENTT_PRETTY_FUNCTION_PREFIX '<' | |||||
# define ENTT_PRETTY_FUNCTION_SUFFIX '>' | |||||
# endif | |||||
#endif | |||||
#if defined _MSC_VER | |||||
# pragma detect_mismatch("entt.version", ENTT_VERSION) | |||||
# pragma detect_mismatch("entt.noexcept", ENTT_XSTR(ENTT_TRY)) | |||||
# pragma detect_mismatch("entt.id", ENTT_XSTR(ENTT_ID_TYPE)) | |||||
# pragma detect_mismatch("entt.nonstd", ENTT_XSTR(ENTT_NONSTD)) | |||||
#endif | |||||
#endif // ENTT_CONFIG_CONFIG_H | |||||
#endif |
@ -0,0 +1,7 @@ | |||||
#ifndef ENTT_CONFIG_MACRO_H | |||||
#define ENTT_CONFIG_MACRO_H | |||||
#define ENTT_STR(arg) #arg | |||||
#define ENTT_XSTR(arg) ENTT_STR(arg) | |||||
#endif |
@ -1,11 +1,14 @@ | |||||
#ifndef ENTT_CONFIG_VERSION_H | #ifndef ENTT_CONFIG_VERSION_H | ||||
#define ENTT_CONFIG_VERSION_H | #define ENTT_CONFIG_VERSION_H | ||||
#include "macro.h" | |||||
#define ENTT_VERSION "3.1.0" | |||||
#define ENTT_VERSION_MAJOR 3 | #define ENTT_VERSION_MAJOR 3 | ||||
#define ENTT_VERSION_MINOR 1 | |||||
#define ENTT_VERSION_PATCH 0 | |||||
#define ENTT_VERSION_MINOR 10 | |||||
#define ENTT_VERSION_PATCH 3 | |||||
#define ENTT_VERSION \ | |||||
ENTT_XSTR(ENTT_VERSION_MAJOR) \ | |||||
"." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) | |||||
#endif // ENTT_CONFIG_VERSION_H | |||||
#endif |
@ -0,0 +1,988 @@ | |||||
#ifndef ENTT_CONTAINER_DENSE_MAP_HPP | |||||
#define ENTT_CONTAINER_DENSE_MAP_HPP | |||||
#include <algorithm> | |||||
#include <cmath> | |||||
#include <cstddef> | |||||
#include <functional> | |||||
#include <iterator> | |||||
#include <limits> | |||||
#include <memory> | |||||
#include <tuple> | |||||
#include <type_traits> | |||||
#include <utility> | |||||
#include <vector> | |||||
#include "../config/config.h" | |||||
#include "../core/compressed_pair.hpp" | |||||
#include "../core/iterator.hpp" | |||||
#include "../core/memory.hpp" | |||||
#include "../core/type_traits.hpp" | |||||
#include "fwd.hpp" | |||||
namespace entt { | |||||
/** | |||||
* @cond TURN_OFF_DOXYGEN | |||||
* Internal details not to be documented. | |||||
*/ | |||||
namespace internal { | |||||
template<typename Key, typename Type> | |||||
struct dense_map_node final { | |||||
using value_type = std::pair<Key, Type>; | |||||
template<typename... Args> | |||||
dense_map_node(const std::size_t pos, Args &&...args) | |||||
: next{pos}, | |||||
element{std::forward<Args>(args)...} {} | |||||
template<typename Allocator, typename... Args> | |||||
dense_map_node(std::allocator_arg_t, const Allocator &allocator, const std::size_t pos, Args &&...args) | |||||
: next{pos}, | |||||
element{entt::make_obj_using_allocator<value_type>(allocator, std::forward<Args>(args)...)} {} | |||||
template<typename Allocator> | |||||
dense_map_node(std::allocator_arg_t, const Allocator &allocator, const dense_map_node &other) | |||||
: next{other.next}, | |||||
element{entt::make_obj_using_allocator<value_type>(allocator, other.element)} {} | |||||
template<typename Allocator> | |||||
dense_map_node(std::allocator_arg_t, const Allocator &allocator, dense_map_node &&other) | |||||
: next{other.next}, | |||||
element{entt::make_obj_using_allocator<value_type>(allocator, std::move(other.element))} {} | |||||
std::size_t next; | |||||
value_type element; | |||||
}; | |||||
template<typename It> | |||||
class dense_map_iterator final { | |||||
template<typename> | |||||
friend class dense_map_iterator; | |||||
using first_type = decltype(std::as_const(std::declval<It>()->element.first)); | |||||
using second_type = decltype((std::declval<It>()->element.second)); | |||||
public: | |||||
using value_type = std::pair<first_type, second_type>; | |||||
using pointer = input_iterator_pointer<value_type>; | |||||
using reference = value_type; | |||||
using difference_type = std::ptrdiff_t; | |||||
using iterator_category = std::input_iterator_tag; | |||||
dense_map_iterator() ENTT_NOEXCEPT | |||||
: it{} {} | |||||
dense_map_iterator(const It iter) ENTT_NOEXCEPT | |||||
: it{iter} {} | |||||
template<typename Other, typename = std::enable_if_t<!std::is_same_v<It, Other> && std::is_constructible_v<It, Other>>> | |||||
dense_map_iterator(const dense_map_iterator<Other> &other) ENTT_NOEXCEPT | |||||
: it{other.it} {} | |||||
dense_map_iterator &operator++() ENTT_NOEXCEPT { | |||||
return ++it, *this; | |||||
} | |||||
dense_map_iterator operator++(int) ENTT_NOEXCEPT { | |||||
dense_map_iterator orig = *this; | |||||
return ++(*this), orig; | |||||
} | |||||
dense_map_iterator &operator--() ENTT_NOEXCEPT { | |||||
return --it, *this; | |||||
} | |||||
dense_map_iterator operator--(int) ENTT_NOEXCEPT { | |||||
dense_map_iterator orig = *this; | |||||
return operator--(), orig; | |||||
} | |||||
dense_map_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT { | |||||
it += value; | |||||
return *this; | |||||
} | |||||
dense_map_iterator operator+(const difference_type value) const ENTT_NOEXCEPT { | |||||
dense_map_iterator copy = *this; | |||||
return (copy += value); | |||||
} | |||||
dense_map_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT { | |||||
return (*this += -value); | |||||
} | |||||
dense_map_iterator operator-(const difference_type value) const ENTT_NOEXCEPT { | |||||
return (*this + -value); | |||||
} | |||||
[[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT { | |||||
return {it[value].element.first, it[value].element.second}; | |||||
} | |||||
[[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { | |||||
return operator*(); | |||||
} | |||||
[[nodiscard]] reference operator*() const ENTT_NOEXCEPT { | |||||
return {it->element.first, it->element.second}; | |||||
} | |||||
template<typename ILhs, typename IRhs> | |||||
friend std::ptrdiff_t operator-(const dense_map_iterator<ILhs> &, const dense_map_iterator<IRhs> &) ENTT_NOEXCEPT; | |||||
template<typename ILhs, typename IRhs> | |||||
friend bool operator==(const dense_map_iterator<ILhs> &, const dense_map_iterator<IRhs> &) ENTT_NOEXCEPT; | |||||
template<typename ILhs, typename IRhs> | |||||
friend bool operator<(const dense_map_iterator<ILhs> &, const dense_map_iterator<IRhs> &) ENTT_NOEXCEPT; | |||||
private: | |||||
It it; | |||||
}; | |||||
template<typename ILhs, typename IRhs> | |||||
[[nodiscard]] std::ptrdiff_t operator-(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT { | |||||
return lhs.it - rhs.it; | |||||
} | |||||
template<typename ILhs, typename IRhs> | |||||
[[nodiscard]] bool operator==(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT { | |||||
return lhs.it == rhs.it; | |||||
} | |||||
template<typename ILhs, typename IRhs> | |||||
[[nodiscard]] bool operator!=(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT { | |||||
return !(lhs == rhs); | |||||
} | |||||
template<typename ILhs, typename IRhs> | |||||
[[nodiscard]] bool operator<(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT { | |||||
return lhs.it < rhs.it; | |||||
} | |||||
template<typename ILhs, typename IRhs> | |||||
[[nodiscard]] bool operator>(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT { | |||||
return rhs < lhs; | |||||
} | |||||
template<typename ILhs, typename IRhs> | |||||
[[nodiscard]] bool operator<=(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT { | |||||
return !(lhs > rhs); | |||||
} | |||||
template<typename ILhs, typename IRhs> | |||||
[[nodiscard]] bool operator>=(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT { | |||||
return !(lhs < rhs); | |||||
} | |||||
template<typename It> | |||||
class dense_map_local_iterator final { | |||||
template<typename> | |||||
friend class dense_map_local_iterator; | |||||
using first_type = decltype(std::as_const(std::declval<It>()->element.first)); | |||||
using second_type = decltype((std::declval<It>()->element.second)); | |||||
public: | |||||
using value_type = std::pair<first_type, second_type>; | |||||
using pointer = input_iterator_pointer<value_type>; | |||||
using reference = value_type; | |||||
using difference_type = std::ptrdiff_t; | |||||
using iterator_category = std::input_iterator_tag; | |||||
dense_map_local_iterator() ENTT_NOEXCEPT | |||||
: it{}, | |||||
offset{} {} | |||||
dense_map_local_iterator(It iter, const std::size_t pos) ENTT_NOEXCEPT | |||||
: it{iter}, | |||||
offset{pos} {} | |||||
template<typename Other, typename = std::enable_if_t<!std::is_same_v<It, Other> && std::is_constructible_v<It, Other>>> | |||||
dense_map_local_iterator(const dense_map_local_iterator<Other> &other) ENTT_NOEXCEPT | |||||
: it{other.it}, | |||||
offset{other.offset} {} | |||||
dense_map_local_iterator &operator++() ENTT_NOEXCEPT { | |||||
return offset = it[offset].next, *this; | |||||
} | |||||
dense_map_local_iterator operator++(int) ENTT_NOEXCEPT { | |||||
dense_map_local_iterator orig = *this; | |||||
return ++(*this), orig; | |||||
} | |||||
[[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { | |||||
return operator*(); | |||||
} | |||||
[[nodiscard]] reference operator*() const ENTT_NOEXCEPT { | |||||
return {it[offset].element.first, it[offset].element.second}; | |||||
} | |||||
[[nodiscard]] std::size_t index() const ENTT_NOEXCEPT { | |||||
return offset; | |||||
} | |||||
private: | |||||
It it; | |||||
std::size_t offset; | |||||
}; | |||||
template<typename ILhs, typename IRhs> | |||||
[[nodiscard]] bool operator==(const dense_map_local_iterator<ILhs> &lhs, const dense_map_local_iterator<IRhs> &rhs) ENTT_NOEXCEPT { | |||||
return lhs.index() == rhs.index(); | |||||
} | |||||
template<typename ILhs, typename IRhs> | |||||
[[nodiscard]] bool operator!=(const dense_map_local_iterator<ILhs> &lhs, const dense_map_local_iterator<IRhs> &rhs) ENTT_NOEXCEPT { | |||||
return !(lhs == rhs); | |||||
} | |||||
} // namespace internal | |||||
/** | |||||
* Internal details not to be documented. | |||||
* @endcond | |||||
*/ | |||||
/** | |||||
* @brief Associative container for key-value pairs with unique keys. | |||||
* | |||||
* Internally, elements are organized into buckets. Which bucket an element is | |||||
* placed into depends entirely on the hash of its key. Keys with the same hash | |||||
* code appear in the same bucket. | |||||
* | |||||
* @tparam Key Key type of the associative container. | |||||
* @tparam Type Mapped type of the associative container. | |||||
* @tparam Hash Type of function to use to hash the keys. | |||||
* @tparam KeyEqual Type of function to use to compare the keys for equality. | |||||
* @tparam Allocator Type of allocator used to manage memory and elements. | |||||
*/ | |||||
template<typename Key, typename Type, typename Hash, typename KeyEqual, typename Allocator> | |||||
class dense_map { | |||||
static constexpr float default_threshold = 0.875f; | |||||
static constexpr std::size_t minimum_capacity = 8u; | |||||
using node_type = internal::dense_map_node<Key, Type>; | |||||
using alloc_traits = typename std::allocator_traits<Allocator>; | |||||
static_assert(std::is_same_v<typename alloc_traits::value_type, std::pair<const Key, Type>>, "Invalid value type"); | |||||
using sparse_container_type = std::vector<std::size_t, typename alloc_traits::template rebind_alloc<std::size_t>>; | |||||
using packed_container_type = std::vector<node_type, typename alloc_traits::template rebind_alloc<node_type>>; | |||||
template<typename Other> | |||||
[[nodiscard]] std::size_t key_to_bucket(const Other &key) const ENTT_NOEXCEPT { | |||||
return fast_mod(sparse.second()(key), bucket_count()); | |||||
} | |||||
template<typename Other> | |||||
[[nodiscard]] auto constrained_find(const Other &key, std::size_t bucket) { | |||||
for(auto it = begin(bucket), last = end(bucket); it != last; ++it) { | |||||
if(packed.second()(it->first, key)) { | |||||
return begin() + static_cast<typename iterator::difference_type>(it.index()); | |||||
} | |||||
} | |||||
return end(); | |||||
} | |||||
template<typename Other> | |||||
[[nodiscard]] auto constrained_find(const Other &key, std::size_t bucket) const { | |||||
for(auto it = cbegin(bucket), last = cend(bucket); it != last; ++it) { | |||||
if(packed.second()(it->first, key)) { | |||||
return cbegin() + static_cast<typename iterator::difference_type>(it.index()); | |||||
} | |||||
} | |||||
return cend(); | |||||
} | |||||
template<typename Other, typename... Args> | |||||
[[nodiscard]] auto insert_or_do_nothing(Other &&key, Args &&...args) { | |||||
const auto index = key_to_bucket(key); | |||||
if(auto it = constrained_find(key, index); it != end()) { | |||||
return std::make_pair(it, false); | |||||
} | |||||
packed.first().emplace_back(sparse.first()[index], std::piecewise_construct, std::forward_as_tuple(std::forward<Other>(key)), std::forward_as_tuple(std::forward<Args>(args)...)); | |||||
sparse.first()[index] = packed.first().size() - 1u; | |||||
rehash_if_required(); | |||||
return std::make_pair(--end(), true); | |||||
} | |||||
template<typename Other, typename Arg> | |||||
[[nodiscard]] auto insert_or_overwrite(Other &&key, Arg &&value) { | |||||
const auto index = key_to_bucket(key); | |||||
if(auto it = constrained_find(key, index); it != end()) { | |||||
it->second = std::forward<Arg>(value); | |||||
return std::make_pair(it, false); | |||||
} | |||||
packed.first().emplace_back(sparse.first()[index], std::forward<Other>(key), std::forward<Arg>(value)); | |||||
sparse.first()[index] = packed.first().size() - 1u; | |||||
rehash_if_required(); | |||||
return std::make_pair(--end(), true); | |||||
} | |||||
void move_and_pop(const std::size_t pos) { | |||||
if(const auto last = size() - 1u; pos != last) { | |||||
packed.first()[pos] = std::move(packed.first().back()); | |||||
size_type *curr = sparse.first().data() + key_to_bucket(packed.first().back().element.first); | |||||
for(; *curr != last; curr = &packed.first()[*curr].next) {} | |||||
*curr = pos; | |||||
} | |||||
packed.first().pop_back(); | |||||
} | |||||
void rehash_if_required() { | |||||
if(size() > (bucket_count() * max_load_factor())) { | |||||
rehash(bucket_count() * 2u); | |||||
} | |||||
} | |||||
public: | |||||
/*! @brief Key type of the container. */ | |||||
using key_type = Key; | |||||
/*! @brief Mapped type of the container. */ | |||||
using mapped_type = Type; | |||||
/*! @brief Key-value type of the container. */ | |||||
using value_type = std::pair<const Key, Type>; | |||||
/*! @brief Unsigned integer type. */ | |||||
using size_type = std::size_t; | |||||
/*! @brief Type of function to use to hash the keys. */ | |||||
using hasher = Hash; | |||||
/*! @brief Type of function to use to compare the keys for equality. */ | |||||
using key_equal = KeyEqual; | |||||
/*! @brief Allocator type. */ | |||||
using allocator_type = Allocator; | |||||
/*! @brief Input iterator type. */ | |||||
using iterator = internal::dense_map_iterator<typename packed_container_type::iterator>; | |||||
/*! @brief Constant input iterator type. */ | |||||
using const_iterator = internal::dense_map_iterator<typename packed_container_type::const_iterator>; | |||||
/*! @brief Input iterator type. */ | |||||
using local_iterator = internal::dense_map_local_iterator<typename packed_container_type::iterator>; | |||||
/*! @brief Constant input iterator type. */ | |||||
using const_local_iterator = internal::dense_map_local_iterator<typename packed_container_type::const_iterator>; | |||||
/*! @brief Default constructor. */ | |||||
dense_map() | |||||
: dense_map(minimum_capacity) {} | |||||
/** | |||||
* @brief Constructs an empty container with a given allocator. | |||||
* @param allocator The allocator to use. | |||||
*/ | |||||
explicit dense_map(const allocator_type &allocator) | |||||
: dense_map{minimum_capacity, hasher{}, key_equal{}, allocator} {} | |||||
/** | |||||
* @brief Constructs an empty container with a given allocator and user | |||||
* supplied minimal number of buckets. | |||||
* @param bucket_count Minimal number of buckets. | |||||
* @param allocator The allocator to use. | |||||
*/ | |||||
dense_map(const size_type bucket_count, const allocator_type &allocator) | |||||
: dense_map{bucket_count, hasher{}, key_equal{}, allocator} {} | |||||
/** | |||||
* @brief Constructs an empty container with a given allocator, hash | |||||
* function and user supplied minimal number of buckets. | |||||
* @param bucket_count Minimal number of buckets. | |||||
* @param hash Hash function to use. | |||||
* @param allocator The allocator to use. | |||||
*/ | |||||
dense_map(const size_type bucket_count, const hasher &hash, const allocator_type &allocator) | |||||
: dense_map{bucket_count, hash, key_equal{}, allocator} {} | |||||
/** | |||||
* @brief Constructs an empty container with a given allocator, hash | |||||
* function, compare function and user supplied minimal number of buckets. | |||||
* @param bucket_count Minimal number of buckets. | |||||
* @param hash Hash function to use. | |||||
* @param equal Compare function to use. | |||||
* @param allocator The allocator to use. | |||||
*/ | |||||
explicit dense_map(const size_type bucket_count, const hasher &hash = hasher{}, const key_equal &equal = key_equal{}, const allocator_type &allocator = allocator_type{}) | |||||
: sparse{allocator, hash}, | |||||
packed{allocator, equal}, | |||||
threshold{default_threshold} { | |||||
rehash(bucket_count); | |||||
} | |||||
/*! @brief Default copy constructor. */ | |||||
dense_map(const dense_map &) = default; | |||||
/** | |||||
* @brief Allocator-extended copy constructor. | |||||
* @param other The instance to copy from. | |||||
* @param allocator The allocator to use. | |||||
*/ | |||||
dense_map(const dense_map &other, const allocator_type &allocator) | |||||
: sparse{std::piecewise_construct, std::forward_as_tuple(other.sparse.first(), allocator), std::forward_as_tuple(other.sparse.second())}, | |||||
packed{std::piecewise_construct, std::forward_as_tuple(other.packed.first(), allocator), std::forward_as_tuple(other.packed.second())}, | |||||
threshold{other.threshold} {} | |||||
/*! @brief Default move constructor. */ | |||||
dense_map(dense_map &&) = default; | |||||
/** | |||||
* @brief Allocator-extended move constructor. | |||||
* @param other The instance to move from. | |||||
* @param allocator The allocator to use. | |||||
*/ | |||||
dense_map(dense_map &&other, const allocator_type &allocator) | |||||
: sparse{std::piecewise_construct, std::forward_as_tuple(std::move(other.sparse.first()), allocator), std::forward_as_tuple(std::move(other.sparse.second()))}, | |||||
packed{std::piecewise_construct, std::forward_as_tuple(std::move(other.packed.first()), allocator), std::forward_as_tuple(std::move(other.packed.second()))}, | |||||
threshold{other.threshold} {} | |||||
/** | |||||
* @brief Default copy assignment operator. | |||||
* @return This container. | |||||
*/ | |||||
dense_map &operator=(const dense_map &) = default; | |||||
/** | |||||
* @brief Default move assignment operator. | |||||
* @return This container. | |||||
*/ | |||||
dense_map &operator=(dense_map &&) = default; | |||||
/** | |||||
* @brief Returns the associated allocator. | |||||
* @return The associated allocator. | |||||
*/ | |||||
[[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT { | |||||
return sparse.first().get_allocator(); | |||||
} | |||||
/** | |||||
* @brief Returns an iterator to the beginning. | |||||
* | |||||
* The returned iterator points to the first instance of the internal array. | |||||
* If the array is empty, the returned iterator will be equal to `end()`. | |||||
* | |||||
* @return An iterator to the first instance of the internal array. | |||||
*/ | |||||
[[nodiscard]] const_iterator cbegin() const ENTT_NOEXCEPT { | |||||
return packed.first().begin(); | |||||
} | |||||
/*! @copydoc cbegin */ | |||||
[[nodiscard]] const_iterator begin() const ENTT_NOEXCEPT { | |||||
return cbegin(); | |||||
} | |||||
/*! @copydoc begin */ | |||||
[[nodiscard]] iterator begin() ENTT_NOEXCEPT { | |||||
return packed.first().begin(); | |||||
} | |||||
/** | |||||
* @brief Returns an iterator to the end. | |||||
* | |||||
* The returned iterator points to the element following the last instance | |||||
* of the internal array. Attempting to dereference the returned iterator | |||||
* results in undefined behavior. | |||||
* | |||||
* @return An iterator to the element following the last instance of the | |||||
* internal array. | |||||
*/ | |||||
[[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT { | |||||
return packed.first().end(); | |||||
} | |||||
/*! @copydoc cend */ | |||||
[[nodiscard]] const_iterator end() const ENTT_NOEXCEPT { | |||||
return cend(); | |||||
} | |||||
/*! @copydoc end */ | |||||
[[nodiscard]] iterator end() ENTT_NOEXCEPT { | |||||
return packed.first().end(); | |||||
} | |||||
/** | |||||
* @brief Checks whether a container is empty. | |||||
* @return True if the container is empty, false otherwise. | |||||
*/ | |||||
[[nodiscard]] bool empty() const ENTT_NOEXCEPT { | |||||
return packed.first().empty(); | |||||
} | |||||
/** | |||||
* @brief Returns the number of elements in a container. | |||||
* @return Number of elements in a container. | |||||
*/ | |||||
[[nodiscard]] size_type size() const ENTT_NOEXCEPT { | |||||
return packed.first().size(); | |||||
} | |||||
/*! @brief Clears the container. */ | |||||
void clear() ENTT_NOEXCEPT { | |||||
sparse.first().clear(); | |||||
packed.first().clear(); | |||||
rehash(0u); | |||||
} | |||||
/** | |||||
* @brief Inserts an element into the container, if the key does not exist. | |||||
* @param value A key-value pair eventually convertible to the value type. | |||||
* @return A pair consisting of an iterator to the inserted element (or to | |||||
* the element that prevented the insertion) and a bool denoting whether the | |||||
* insertion took place. | |||||
*/ | |||||
std::pair<iterator, bool> insert(const value_type &value) { | |||||
return insert_or_do_nothing(value.first, value.second); | |||||
} | |||||
/*! @copydoc insert */ | |||||
std::pair<iterator, bool> insert(value_type &&value) { | |||||
return insert_or_do_nothing(std::move(value.first), std::move(value.second)); | |||||
} | |||||
/** | |||||
* @copydoc insert | |||||
* @tparam Arg Type of the key-value pair to insert into the container. | |||||
*/ | |||||
template<typename Arg> | |||||
std::enable_if_t<std::is_constructible_v<value_type, Arg &&>, std::pair<iterator, bool>> | |||||
insert(Arg &&value) { | |||||
return insert_or_do_nothing(std::forward<Arg>(value).first, std::forward<Arg>(value).second); | |||||
} | |||||
/** | |||||
* @brief Inserts elements into the container, if their keys do not exist. | |||||
* @tparam It Type of input iterator. | |||||
* @param first An iterator to the first element of the range of elements. | |||||
* @param last An iterator past the last element of the range of elements. | |||||
*/ | |||||
template<typename It> | |||||
void insert(It first, It last) { | |||||
for(; first != last; ++first) { | |||||
insert(*first); | |||||
} | |||||
} | |||||
/** | |||||
* @brief Inserts an element into the container or assigns to the current | |||||
* element if the key already exists. | |||||
* @tparam Arg Type of the value to insert or assign. | |||||
* @param key A key used both to look up and to insert if not found. | |||||
* @param value A value to insert or assign. | |||||
* @return A pair consisting of an iterator to the element and a bool | |||||
* denoting whether the insertion took place. | |||||
*/ | |||||
template<typename Arg> | |||||
std::pair<iterator, bool> insert_or_assign(const key_type &key, Arg &&value) { | |||||
return insert_or_overwrite(key, std::forward<Arg>(value)); | |||||
} | |||||
/*! @copydoc insert_or_assign */ | |||||
template<typename Arg> | |||||
std::pair<iterator, bool> insert_or_assign(key_type &&key, Arg &&value) { | |||||
return insert_or_overwrite(std::move(key), std::forward<Arg>(value)); | |||||
} | |||||
/** | |||||
* @brief Constructs an element in-place, if the key does not exist. | |||||
* | |||||
* The element is also constructed when the container already has the key, | |||||
* in which case the newly constructed object is destroyed immediately. | |||||
* | |||||
* @tparam Args Types of arguments to forward to the constructor of the | |||||
* element. | |||||
* @param args Arguments to forward to the constructor of the element. | |||||
* @return A pair consisting of an iterator to the inserted element (or to | |||||
* the element that prevented the insertion) and a bool denoting whether the | |||||
* insertion took place. | |||||
*/ | |||||
template<typename... Args> | |||||
std::pair<iterator, bool> emplace([[maybe_unused]] Args &&...args) { | |||||
if constexpr(sizeof...(Args) == 0u) { | |||||
return insert_or_do_nothing(key_type{}); | |||||
} else if constexpr(sizeof...(Args) == 1u) { | |||||
return insert_or_do_nothing(std::forward<Args>(args).first..., std::forward<Args>(args).second...); | |||||
} else if constexpr(sizeof...(Args) == 2u) { | |||||
return insert_or_do_nothing(std::forward<Args>(args)...); | |||||
} else { | |||||
auto &node = packed.first().emplace_back(packed.first().size(), std::forward<Args>(args)...); | |||||
const auto index = key_to_bucket(node.element.first); | |||||
if(auto it = constrained_find(node.element.first, index); it != end()) { | |||||
packed.first().pop_back(); | |||||
return std::make_pair(it, false); | |||||
} | |||||
std::swap(node.next, sparse.first()[index]); | |||||
rehash_if_required(); | |||||
return std::make_pair(--end(), true); | |||||
} | |||||
} | |||||
/** | |||||
* @brief Inserts in-place if the key does not exist, does nothing if the | |||||
* key exists. | |||||
* @tparam Args Types of arguments to forward to the constructor of the | |||||
* element. | |||||
* @param key A key used both to look up and to insert if not found. | |||||
* @param args Arguments to forward to the constructor of the element. | |||||
* @return A pair consisting of an iterator to the inserted element (or to | |||||
* the element that prevented the insertion) and a bool denoting whether the | |||||
* insertion took place. | |||||
*/ | |||||
template<typename... Args> | |||||
std::pair<iterator, bool> try_emplace(const key_type &key, Args &&...args) { | |||||
return insert_or_do_nothing(key, std::forward<Args>(args)...); | |||||
} | |||||
/*! @copydoc try_emplace */ | |||||
template<typename... Args> | |||||
std::pair<iterator, bool> try_emplace(key_type &&key, Args &&...args) { | |||||
return insert_or_do_nothing(std::move(key), std::forward<Args>(args)...); | |||||
} | |||||
/** | |||||
* @brief Removes an element from a given position. | |||||
* @param pos An iterator to the element to remove. | |||||
* @return An iterator following the removed element. | |||||
*/ | |||||
iterator erase(const_iterator pos) { | |||||
const auto diff = pos - cbegin(); | |||||
erase(pos->first); | |||||
return begin() + diff; | |||||
} | |||||
/** | |||||
* @brief Removes the given elements from a container. | |||||
* @param first An iterator to the first element of the range of elements. | |||||
* @param last An iterator past the last element of the range of elements. | |||||
* @return An iterator following the last removed element. | |||||
*/ | |||||
iterator erase(const_iterator first, const_iterator last) { | |||||
const auto dist = first - cbegin(); | |||||
for(auto from = last - cbegin(); from != dist; --from) { | |||||
erase(packed.first()[from - 1u].element.first); | |||||
} | |||||
return (begin() + dist); | |||||
} | |||||
/** | |||||
* @brief Removes the element associated with a given key. | |||||
* @param key A key value of an element to remove. | |||||
* @return Number of elements removed (either 0 or 1). | |||||
*/ | |||||
size_type erase(const key_type &key) { | |||||
for(size_type *curr = sparse.first().data() + key_to_bucket(key); *curr != (std::numeric_limits<size_type>::max)(); curr = &packed.first()[*curr].next) { | |||||
if(packed.second()(packed.first()[*curr].element.first, key)) { | |||||
const auto index = *curr; | |||||
*curr = packed.first()[*curr].next; | |||||
move_and_pop(index); | |||||
return 1u; | |||||
} | |||||
} | |||||
return 0u; | |||||
} | |||||
/** | |||||
* @brief Exchanges the contents with those of a given container. | |||||
* @param other Container to exchange the content with. | |||||
*/ | |||||
void swap(dense_map &other) { | |||||
using std::swap; | |||||
swap(sparse, other.sparse); | |||||
swap(packed, other.packed); | |||||
swap(threshold, other.threshold); | |||||
} | |||||
/** | |||||
* @brief Accesses a given element with bounds checking. | |||||
* @param key A key of an element to find. | |||||
* @return A reference to the mapped value of the requested element. | |||||
*/ | |||||
[[nodiscard]] mapped_type &at(const key_type &key) { | |||||
auto it = find(key); | |||||
ENTT_ASSERT(it != end(), "Invalid key"); | |||||
return it->second; | |||||
} | |||||
/*! @copydoc at */ | |||||
[[nodiscard]] const mapped_type &at(const key_type &key) const { | |||||
auto it = find(key); | |||||
ENTT_ASSERT(it != cend(), "Invalid key"); | |||||
return it->second; | |||||
} | |||||
/** | |||||
* @brief Accesses or inserts a given element. | |||||
* @param key A key of an element to find or insert. | |||||
* @return A reference to the mapped value of the requested element. | |||||
*/ | |||||
[[nodiscard]] mapped_type &operator[](const key_type &key) { | |||||
return insert_or_do_nothing(key).first->second; | |||||
} | |||||
/** | |||||
* @brief Accesses or inserts a given element. | |||||
* @param key A key of an element to find or insert. | |||||
* @return A reference to the mapped value of the requested element. | |||||
*/ | |||||
[[nodiscard]] mapped_type &operator[](key_type &&key) { | |||||
return insert_or_do_nothing(std::move(key)).first->second; | |||||
} | |||||
/** | |||||
* @brief Finds an element with a given key. | |||||
* @param key Key value of an element to search for. | |||||
* @return An iterator to an element with the given key. If no such element | |||||
* is found, a past-the-end iterator is returned. | |||||
*/ | |||||
[[nodiscard]] iterator find(const key_type &key) { | |||||
return constrained_find(key, key_to_bucket(key)); | |||||
} | |||||
/*! @copydoc find */ | |||||
[[nodiscard]] const_iterator find(const key_type &key) const { | |||||
return constrained_find(key, key_to_bucket(key)); | |||||
} | |||||
/** | |||||
* @brief Finds an element with a key that compares _equivalent_ to a given | |||||
* value. | |||||
* @tparam Other Type of the key value of an element to search for. | |||||
* @param key Key value of an element to search for. | |||||
* @return An iterator to an element with the given key. If no such element | |||||
* is found, a past-the-end iterator is returned. | |||||
*/ | |||||
template<typename Other> | |||||
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, iterator>> | |||||
find(const Other &key) { | |||||
return constrained_find(key, key_to_bucket(key)); | |||||
} | |||||
/*! @copydoc find */ | |||||
template<typename Other> | |||||
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, const_iterator>> | |||||
find(const Other &key) const { | |||||
return constrained_find(key, key_to_bucket(key)); | |||||
} | |||||
/** | |||||
* @brief Checks if the container contains an element with a given key. | |||||
* @param key Key value of an element to search for. | |||||
* @return True if there is such an element, false otherwise. | |||||
*/ | |||||
[[nodiscard]] bool contains(const key_type &key) const { | |||||
return (find(key) != cend()); | |||||
} | |||||
/** | |||||
* @brief Checks if the container contains an element with a key that | |||||
* compares _equivalent_ to a given value. | |||||
* @tparam Other Type of the key value of an element to search for. | |||||
* @param key Key value of an element to search for. | |||||
* @return True if there is such an element, false otherwise. | |||||
*/ | |||||
template<typename Other> | |||||
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, bool>> | |||||
contains(const Other &key) const { | |||||
return (find(key) != cend()); | |||||
} | |||||
/** | |||||
* @brief Returns an iterator to the beginning of a given bucket. | |||||
* @param index An index of a bucket to access. | |||||
* @return An iterator to the beginning of the given bucket. | |||||
*/ | |||||
[[nodiscard]] const_local_iterator cbegin(const size_type index) const { | |||||
return {packed.first().begin(), sparse.first()[index]}; | |||||
} | |||||
/** | |||||
* @brief Returns an iterator to the beginning of a given bucket. | |||||
* @param index An index of a bucket to access. | |||||
* @return An iterator to the beginning of the given bucket. | |||||
*/ | |||||
[[nodiscard]] const_local_iterator begin(const size_type index) const { | |||||
return cbegin(index); | |||||
} | |||||
/** | |||||
* @brief Returns an iterator to the beginning of a given bucket. | |||||
* @param index An index of a bucket to access. | |||||
* @return An iterator to the beginning of the given bucket. | |||||
*/ | |||||
[[nodiscard]] local_iterator begin(const size_type index) { | |||||
return {packed.first().begin(), sparse.first()[index]}; | |||||
} | |||||
/** | |||||
* @brief Returns an iterator to the end of a given bucket. | |||||
* @param index An index of a bucket to access. | |||||
* @return An iterator to the end of the given bucket. | |||||
*/ | |||||
[[nodiscard]] const_local_iterator cend([[maybe_unused]] const size_type index) const { | |||||
return {packed.first().begin(), (std::numeric_limits<size_type>::max)()}; | |||||
} | |||||
/** | |||||
* @brief Returns an iterator to the end of a given bucket. | |||||
* @param index An index of a bucket to access. | |||||
* @return An iterator to the end of the given bucket. | |||||
*/ | |||||
[[nodiscard]] const_local_iterator end([[maybe_unused]] const size_type index) const { | |||||
return cend(index); | |||||
} | |||||
/** | |||||
* @brief Returns an iterator to the end of a given bucket. | |||||
* @param index An index of a bucket to access. | |||||
* @return An iterator to the end of the given bucket. | |||||
*/ | |||||
[[nodiscard]] local_iterator end([[maybe_unused]] const size_type index) { | |||||
return {packed.first().begin(), (std::numeric_limits<size_type>::max)()}; | |||||
} | |||||
/** | |||||
* @brief Returns the number of buckets. | |||||
* @return The number of buckets. | |||||
*/ | |||||
[[nodiscard]] size_type bucket_count() const { | |||||
return sparse.first().size(); | |||||
} | |||||
/** | |||||
* @brief Returns the maximum number of buckets. | |||||
* @return The maximum number of buckets. | |||||
*/ | |||||
[[nodiscard]] size_type max_bucket_count() const { | |||||
return sparse.first().max_size(); | |||||
} | |||||
/** | |||||
* @brief Returns the number of elements in a given bucket. | |||||
* @param index The index of the bucket to examine. | |||||
* @return The number of elements in the given bucket. | |||||
*/ | |||||
[[nodiscard]] size_type bucket_size(const size_type index) const { | |||||
return static_cast<size_type>(std::distance(begin(index), end(index))); | |||||
} | |||||
/** | |||||
* @brief Returns the bucket for a given key. | |||||
* @param key The value of the key to examine. | |||||
* @return The bucket for the given key. | |||||
*/ | |||||
[[nodiscard]] size_type bucket(const key_type &key) const { | |||||
return key_to_bucket(key); | |||||
} | |||||
/** | |||||
* @brief Returns the average number of elements per bucket. | |||||
* @return The average number of elements per bucket. | |||||
*/ | |||||
[[nodiscard]] float load_factor() const { | |||||
return size() / static_cast<float>(bucket_count()); | |||||
} | |||||
/** | |||||
* @brief Returns the maximum average number of elements per bucket. | |||||
* @return The maximum average number of elements per bucket. | |||||
*/ | |||||
[[nodiscard]] float max_load_factor() const { | |||||
return threshold; | |||||
} | |||||
/** | |||||
* @brief Sets the desired maximum average number of elements per bucket. | |||||
* @param value A desired maximum average number of elements per bucket. | |||||
*/ | |||||
void max_load_factor(const float value) { | |||||
ENTT_ASSERT(value > 0.f, "Invalid load factor"); | |||||
threshold = value; | |||||
rehash(0u); | |||||
} | |||||
/** | |||||
* @brief Reserves at least the specified number of buckets and regenerates | |||||
* the hash table. | |||||
* @param count New number of buckets. | |||||
*/ | |||||
void rehash(const size_type count) { | |||||
auto value = (std::max)(count, minimum_capacity); | |||||
value = (std::max)(value, static_cast<size_type>(size() / max_load_factor())); | |||||
if(const auto sz = next_power_of_two(value); sz != bucket_count()) { | |||||
sparse.first().resize(sz); | |||||
std::fill(sparse.first().begin(), sparse.first().end(), (std::numeric_limits<size_type>::max)()); | |||||
for(size_type pos{}, last = size(); pos < last; ++pos) { | |||||
const auto index = key_to_bucket(packed.first()[pos].element.first); | |||||
packed.first()[pos].next = std::exchange(sparse.first()[index], pos); | |||||
} | |||||
} | |||||
} | |||||
/** | |||||
* @brief Reserves space for at least the specified number of elements and | |||||
* regenerates the hash table. | |||||
* @param count New number of elements. | |||||
*/ | |||||
void reserve(const size_type count) { | |||||
packed.first().reserve(count); | |||||
rehash(static_cast<size_type>(std::ceil(count / max_load_factor()))); | |||||
} | |||||
/** | |||||
* @brief Returns the function used to hash the keys. | |||||
* @return The function used to hash the keys. | |||||
*/ | |||||
[[nodiscard]] hasher hash_function() const { | |||||
return sparse.second(); | |||||
} | |||||
/** | |||||
* @brief Returns the function used to compare keys for equality. | |||||
* @return The function used to compare keys for equality. | |||||
*/ | |||||
[[nodiscard]] key_equal key_eq() const { | |||||
return packed.second(); | |||||
} | |||||
private: | |||||
compressed_pair<sparse_container_type, hasher> sparse; | |||||
compressed_pair<packed_container_type, key_equal> packed; | |||||
float threshold; | |||||
}; | |||||
} // namespace entt | |||||
/** | |||||
* @cond TURN_OFF_DOXYGEN | |||||
* Internal details not to be documented. | |||||
*/ | |||||
namespace std { | |||||
template<typename Key, typename Value, typename Allocator> | |||||
struct uses_allocator<entt::internal::dense_map_node<Key, Value>, Allocator> | |||||
: std::true_type {}; | |||||
} // namespace std | |||||
/** | |||||
* Internal details not to be documented. | |||||
* @endcond | |||||
*/ | |||||
#endif |
@ -0,0 +1,823 @@ | |||||
#ifndef ENTT_CONTAINER_DENSE_SET_HPP | |||||
#define ENTT_CONTAINER_DENSE_SET_HPP | |||||
#include <algorithm> | |||||
#include <cmath> | |||||
#include <cstddef> | |||||
#include <functional> | |||||
#include <iterator> | |||||
#include <limits> | |||||
#include <memory> | |||||
#include <tuple> | |||||
#include <type_traits> | |||||
#include <utility> | |||||
#include <vector> | |||||
#include "../config/config.h" | |||||
#include "../core/compressed_pair.hpp" | |||||
#include "../core/memory.hpp" | |||||
#include "../core/type_traits.hpp" | |||||
#include "fwd.hpp" | |||||
namespace entt { | |||||
/** | |||||
* @cond TURN_OFF_DOXYGEN | |||||
* Internal details not to be documented. | |||||
*/ | |||||
namespace internal { | |||||
template<typename It> | |||||
class dense_set_iterator final { | |||||
template<typename> | |||||
friend class dense_set_iterator; | |||||
public: | |||||
using value_type = typename It::value_type::second_type; | |||||
using pointer = const value_type *; | |||||
using reference = const value_type &; | |||||
using difference_type = std::ptrdiff_t; | |||||
using iterator_category = std::random_access_iterator_tag; | |||||
dense_set_iterator() ENTT_NOEXCEPT | |||||
: it{} {} | |||||
dense_set_iterator(const It iter) ENTT_NOEXCEPT | |||||
: it{iter} {} | |||||
template<typename Other, typename = std::enable_if_t<!std::is_same_v<It, Other> && std::is_constructible_v<It, Other>>> | |||||
dense_set_iterator(const dense_set_iterator<Other> &other) ENTT_NOEXCEPT | |||||
: it{other.it} {} | |||||
dense_set_iterator &operator++() ENTT_NOEXCEPT { | |||||
return ++it, *this; | |||||
} | |||||
dense_set_iterator operator++(int) ENTT_NOEXCEPT { | |||||
dense_set_iterator orig = *this; | |||||
return ++(*this), orig; | |||||
} | |||||
dense_set_iterator &operator--() ENTT_NOEXCEPT { | |||||
return --it, *this; | |||||
} | |||||
dense_set_iterator operator--(int) ENTT_NOEXCEPT { | |||||
dense_set_iterator orig = *this; | |||||
return operator--(), orig; | |||||
} | |||||
dense_set_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT { | |||||
it += value; | |||||
return *this; | |||||
} | |||||
dense_set_iterator operator+(const difference_type value) const ENTT_NOEXCEPT { | |||||
dense_set_iterator copy = *this; | |||||
return (copy += value); | |||||
} | |||||
dense_set_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT { | |||||
return (*this += -value); | |||||
} | |||||
dense_set_iterator operator-(const difference_type value) const ENTT_NOEXCEPT { | |||||
return (*this + -value); | |||||
} | |||||
[[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT { | |||||
return it[value].second; | |||||
} | |||||
[[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { | |||||
return std::addressof(it->second); | |||||
} | |||||
[[nodiscard]] reference operator*() const ENTT_NOEXCEPT { | |||||
return *operator->(); | |||||
} | |||||
template<typename ILhs, typename IRhs> | |||||
friend std::ptrdiff_t operator-(const dense_set_iterator<ILhs> &, const dense_set_iterator<IRhs> &) ENTT_NOEXCEPT; | |||||
template<typename ILhs, typename IRhs> | |||||
friend bool operator==(const dense_set_iterator<ILhs> &, const dense_set_iterator<IRhs> &) ENTT_NOEXCEPT; | |||||
template<typename ILhs, typename IRhs> | |||||
friend bool operator<(const dense_set_iterator<ILhs> &, const dense_set_iterator<IRhs> &) ENTT_NOEXCEPT; | |||||
private: | |||||
It it; | |||||
}; | |||||
template<typename ILhs, typename IRhs> | |||||
[[nodiscard]] std::ptrdiff_t operator-(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT { | |||||
return lhs.it - rhs.it; | |||||
} | |||||
template<typename ILhs, typename IRhs> | |||||
[[nodiscard]] bool operator==(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT { | |||||
return lhs.it == rhs.it; | |||||
} | |||||
template<typename ILhs, typename IRhs> | |||||
[[nodiscard]] bool operator!=(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT { | |||||
return !(lhs == rhs); | |||||
} | |||||
template<typename ILhs, typename IRhs> | |||||
[[nodiscard]] bool operator<(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT { | |||||
return lhs.it < rhs.it; | |||||
} | |||||
template<typename ILhs, typename IRhs> | |||||
[[nodiscard]] bool operator>(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT { | |||||
return rhs < lhs; | |||||
} | |||||
template<typename ILhs, typename IRhs> | |||||
[[nodiscard]] bool operator<=(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT { | |||||
return !(lhs > rhs); | |||||
} | |||||
template<typename ILhs, typename IRhs> | |||||
[[nodiscard]] bool operator>=(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT { | |||||
return !(lhs < rhs); | |||||
} | |||||
template<typename It> | |||||
class dense_set_local_iterator final { | |||||
template<typename> | |||||
friend class dense_set_local_iterator; | |||||
public: | |||||
using value_type = typename It::value_type::second_type; | |||||
using pointer = const value_type *; | |||||
using reference = const value_type &; | |||||
using difference_type = std::ptrdiff_t; | |||||
using iterator_category = std::forward_iterator_tag; | |||||
dense_set_local_iterator() ENTT_NOEXCEPT | |||||
: it{}, | |||||
offset{} {} | |||||
dense_set_local_iterator(It iter, const std::size_t pos) ENTT_NOEXCEPT | |||||
: it{iter}, | |||||
offset{pos} {} | |||||
template<typename Other, typename = std::enable_if_t<!std::is_same_v<It, Other> && std::is_constructible_v<It, Other>>> | |||||
dense_set_local_iterator(const dense_set_local_iterator<Other> &other) ENTT_NOEXCEPT | |||||
: it{other.it}, | |||||
offset{other.offset} {} | |||||
dense_set_local_iterator &operator++() ENTT_NOEXCEPT { | |||||
return offset = it[offset].first, *this; | |||||
} | |||||
dense_set_local_iterator operator++(int) ENTT_NOEXCEPT { | |||||
dense_set_local_iterator orig = *this; | |||||
return ++(*this), orig; | |||||
} | |||||
[[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { | |||||
return std::addressof(it[offset].second); | |||||
} | |||||
[[nodiscard]] reference operator*() const ENTT_NOEXCEPT { | |||||
return *operator->(); | |||||
} | |||||
[[nodiscard]] std::size_t index() const ENTT_NOEXCEPT { | |||||
return offset; | |||||
} | |||||
private: | |||||
It it; | |||||
std::size_t offset; | |||||
}; | |||||
template<typename ILhs, typename IRhs> | |||||
[[nodiscard]] bool operator==(const dense_set_local_iterator<ILhs> &lhs, const dense_set_local_iterator<IRhs> &rhs) ENTT_NOEXCEPT { | |||||
return lhs.index() == rhs.index(); | |||||
} | |||||
template<typename ILhs, typename IRhs> | |||||
[[nodiscard]] bool operator!=(const dense_set_local_iterator<ILhs> &lhs, const dense_set_local_iterator<IRhs> &rhs) ENTT_NOEXCEPT { | |||||
return !(lhs == rhs); | |||||
} | |||||
} // namespace internal | |||||
/** | |||||
* Internal details not to be documented. | |||||
* @endcond | |||||
*/ | |||||
/** | |||||
* @brief Associative container for unique objects of a given type. | |||||
* | |||||
* Internally, elements are organized into buckets. Which bucket an element is | |||||
* placed into depends entirely on its hash. Elements with the same hash code | |||||
* appear in the same bucket. | |||||
* | |||||
* @tparam Type Value type of the associative container. | |||||
* @tparam Hash Type of function to use to hash the values. | |||||
* @tparam KeyEqual Type of function to use to compare the values for equality. | |||||
* @tparam Allocator Type of allocator used to manage memory and elements. | |||||
*/ | |||||
template<typename Type, typename Hash, typename KeyEqual, typename Allocator> | |||||
class dense_set { | |||||
static constexpr float default_threshold = 0.875f; | |||||
static constexpr std::size_t minimum_capacity = 8u; | |||||
using node_type = std::pair<std::size_t, Type>; | |||||
using alloc_traits = std::allocator_traits<Allocator>; | |||||
static_assert(std::is_same_v<typename alloc_traits::value_type, Type>, "Invalid value type"); | |||||
using sparse_container_type = std::vector<std::size_t, typename alloc_traits::template rebind_alloc<std::size_t>>; | |||||
using packed_container_type = std::vector<node_type, typename alloc_traits::template rebind_alloc<node_type>>; | |||||
template<typename Other> | |||||
[[nodiscard]] std::size_t value_to_bucket(const Other &value) const ENTT_NOEXCEPT { | |||||
return fast_mod(sparse.second()(value), bucket_count()); | |||||
} | |||||
template<typename Other> | |||||
[[nodiscard]] auto constrained_find(const Other &value, std::size_t bucket) { | |||||
for(auto it = begin(bucket), last = end(bucket); it != last; ++it) { | |||||
if(packed.second()(*it, value)) { | |||||
return begin() + static_cast<typename iterator::difference_type>(it.index()); | |||||
} | |||||
} | |||||
return end(); | |||||
} | |||||
template<typename Other> | |||||
[[nodiscard]] auto constrained_find(const Other &value, std::size_t bucket) const { | |||||
for(auto it = cbegin(bucket), last = cend(bucket); it != last; ++it) { | |||||
if(packed.second()(*it, value)) { | |||||
return cbegin() + static_cast<typename iterator::difference_type>(it.index()); | |||||
} | |||||
} | |||||
return cend(); | |||||
} | |||||
template<typename Other> | |||||
[[nodiscard]] auto insert_or_do_nothing(Other &&value) { | |||||
const auto index = value_to_bucket(value); | |||||
if(auto it = constrained_find(value, index); it != end()) { | |||||
return std::make_pair(it, false); | |||||
} | |||||
packed.first().emplace_back(sparse.first()[index], std::forward<Other>(value)); | |||||
sparse.first()[index] = packed.first().size() - 1u; | |||||
rehash_if_required(); | |||||
return std::make_pair(--end(), true); | |||||
} | |||||
void move_and_pop(const std::size_t pos) { | |||||
if(const auto last = size() - 1u; pos != last) { | |||||
packed.first()[pos] = std::move(packed.first().back()); | |||||
size_type *curr = sparse.first().data() + value_to_bucket(packed.first().back().second); | |||||
for(; *curr != last; curr = &packed.first()[*curr].first) {} | |||||
*curr = pos; | |||||
} | |||||
packed.first().pop_back(); | |||||
} | |||||
void rehash_if_required() { | |||||
if(size() > (bucket_count() * max_load_factor())) { | |||||
rehash(bucket_count() * 2u); | |||||
} | |||||
} | |||||
public: | |||||
/*! @brief Key type of the container. */ | |||||
using key_type = Type; | |||||
/*! @brief Value type of the container. */ | |||||
using value_type = Type; | |||||
/*! @brief Unsigned integer type. */ | |||||
using size_type = std::size_t; | |||||
/*! @brief Type of function to use to hash the elements. */ | |||||
using hasher = Hash; | |||||
/*! @brief Type of function to use to compare the elements for equality. */ | |||||
using key_equal = KeyEqual; | |||||
/*! @brief Allocator type. */ | |||||
using allocator_type = Allocator; | |||||
/*! @brief Random access iterator type. */ | |||||
using iterator = internal::dense_set_iterator<typename packed_container_type::iterator>; | |||||
/*! @brief Constant random access iterator type. */ | |||||
using const_iterator = internal::dense_set_iterator<typename packed_container_type::const_iterator>; | |||||
/*! @brief Forward iterator type. */ | |||||
using local_iterator = internal::dense_set_local_iterator<typename packed_container_type::iterator>; | |||||
/*! @brief Constant forward iterator type. */ | |||||
using const_local_iterator = internal::dense_set_local_iterator<typename packed_container_type::const_iterator>; | |||||
/*! @brief Default constructor. */ | |||||
dense_set() | |||||
: dense_set(minimum_capacity) {} | |||||
/** | |||||
* @brief Constructs an empty container with a given allocator. | |||||
* @param allocator The allocator to use. | |||||
*/ | |||||
explicit dense_set(const allocator_type &allocator) | |||||
: dense_set{minimum_capacity, hasher{}, key_equal{}, allocator} {} | |||||
/** | |||||
* @brief Constructs an empty container with a given allocator and user | |||||
* supplied minimal number of buckets. | |||||
* @param bucket_count Minimal number of buckets. | |||||
* @param allocator The allocator to use. | |||||
*/ | |||||
dense_set(const size_type bucket_count, const allocator_type &allocator) | |||||
: dense_set{bucket_count, hasher{}, key_equal{}, allocator} {} | |||||
/** | |||||
* @brief Constructs an empty container with a given allocator, hash | |||||
* function and user supplied minimal number of buckets. | |||||
* @param bucket_count Minimal number of buckets. | |||||
* @param hash Hash function to use. | |||||
* @param allocator The allocator to use. | |||||
*/ | |||||
dense_set(const size_type bucket_count, const hasher &hash, const allocator_type &allocator) | |||||
: dense_set{bucket_count, hash, key_equal{}, allocator} {} | |||||
/** | |||||
* @brief Constructs an empty container with a given allocator, hash | |||||
* function, compare function and user supplied minimal number of buckets. | |||||
* @param bucket_count Minimal number of buckets. | |||||
* @param hash Hash function to use. | |||||
* @param equal Compare function to use. | |||||
* @param allocator The allocator to use. | |||||
*/ | |||||
explicit dense_set(const size_type bucket_count, const hasher &hash = hasher{}, const key_equal &equal = key_equal{}, const allocator_type &allocator = allocator_type{}) | |||||
: sparse{allocator, hash}, | |||||
packed{allocator, equal}, | |||||
threshold{default_threshold} { | |||||
rehash(bucket_count); | |||||
} | |||||
/*! @brief Default copy constructor. */ | |||||
dense_set(const dense_set &) = default; | |||||
/** | |||||
* @brief Allocator-extended copy constructor. | |||||
* @param other The instance to copy from. | |||||
* @param allocator The allocator to use. | |||||
*/ | |||||
dense_set(const dense_set &other, const allocator_type &allocator) | |||||
: sparse{std::piecewise_construct, std::forward_as_tuple(other.sparse.first(), allocator), std::forward_as_tuple(other.sparse.second())}, | |||||
packed{std::piecewise_construct, std::forward_as_tuple(other.packed.first(), allocator), std::forward_as_tuple(other.packed.second())}, | |||||
threshold{other.threshold} {} | |||||
/*! @brief Default move constructor. */ | |||||
dense_set(dense_set &&) = default; | |||||
/** | |||||
* @brief Allocator-extended move constructor. | |||||
* @param other The instance to move from. | |||||
* @param allocator The allocator to use. | |||||
*/ | |||||
dense_set(dense_set &&other, const allocator_type &allocator) | |||||
: sparse{std::piecewise_construct, std::forward_as_tuple(std::move(other.sparse.first()), allocator), std::forward_as_tuple(std::move(other.sparse.second()))}, | |||||
packed{std::piecewise_construct, std::forward_as_tuple(std::move(other.packed.first()), allocator), std::forward_as_tuple(std::move(other.packed.second()))}, | |||||
threshold{other.threshold} {} | |||||
/** | |||||
* @brief Default copy assignment operator. | |||||
* @return This container. | |||||
*/ | |||||
dense_set &operator=(const dense_set &) = default; | |||||
/** | |||||
* @brief Default move assignment operator. | |||||
* @return This container. | |||||
*/ | |||||
dense_set &operator=(dense_set &&) = default; | |||||
/** | |||||
* @brief Returns the associated allocator. | |||||
* @return The associated allocator. | |||||
*/ | |||||
[[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT { | |||||
return sparse.first().get_allocator(); | |||||
} | |||||
/** | |||||
* @brief Returns an iterator to the beginning. | |||||
* | |||||
* The returned iterator points to the first instance of the internal array. | |||||
* If the array is empty, the returned iterator will be equal to `end()`. | |||||
* | |||||
* @return An iterator to the first instance of the internal array. | |||||
*/ | |||||
[[nodiscard]] const_iterator cbegin() const ENTT_NOEXCEPT { | |||||
return packed.first().begin(); | |||||
} | |||||
/*! @copydoc cbegin */ | |||||
[[nodiscard]] const_iterator begin() const ENTT_NOEXCEPT { | |||||
return cbegin(); | |||||
} | |||||
/*! @copydoc begin */ | |||||
[[nodiscard]] iterator begin() ENTT_NOEXCEPT { | |||||
return packed.first().begin(); | |||||
} | |||||
/** | |||||
* @brief Returns an iterator to the end. | |||||
* | |||||
* The returned iterator points to the element following the last instance | |||||
* of the internal array. Attempting to dereference the returned iterator | |||||
* results in undefined behavior. | |||||
* | |||||
* @return An iterator to the element following the last instance of the | |||||
* internal array. | |||||
*/ | |||||
[[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT { | |||||
return packed.first().end(); | |||||
} | |||||
/*! @copydoc cend */ | |||||
[[nodiscard]] const_iterator end() const ENTT_NOEXCEPT { | |||||
return cend(); | |||||
} | |||||
/*! @copydoc end */ | |||||
[[nodiscard]] iterator end() ENTT_NOEXCEPT { | |||||
return packed.first().end(); | |||||
} | |||||
/** | |||||
* @brief Checks whether a container is empty. | |||||
* @return True if the container is empty, false otherwise. | |||||
*/ | |||||
[[nodiscard]] bool empty() const ENTT_NOEXCEPT { | |||||
return packed.first().empty(); | |||||
} | |||||
/** | |||||
* @brief Returns the number of elements in a container. | |||||
* @return Number of elements in a container. | |||||
*/ | |||||
[[nodiscard]] size_type size() const ENTT_NOEXCEPT { | |||||
return packed.first().size(); | |||||
} | |||||
/*! @brief Clears the container. */ | |||||
void clear() ENTT_NOEXCEPT { | |||||
sparse.first().clear(); | |||||
packed.first().clear(); | |||||
rehash(0u); | |||||
} | |||||
/** | |||||
* @brief Inserts an element into the container, if it does not exist. | |||||
* @param value An element to insert into the container. | |||||
* @return A pair consisting of an iterator to the inserted element (or to | |||||
* the element that prevented the insertion) and a bool denoting whether the | |||||
* insertion took place. | |||||
*/ | |||||
std::pair<iterator, bool> insert(const value_type &value) { | |||||
return insert_or_do_nothing(value); | |||||
} | |||||
/*! @copydoc insert */ | |||||
std::pair<iterator, bool> insert(value_type &&value) { | |||||
return insert_or_do_nothing(std::move(value)); | |||||
} | |||||
/** | |||||
* @brief Inserts elements into the container, if they do not exist. | |||||
* @tparam It Type of input iterator. | |||||
* @param first An iterator to the first element of the range of elements. | |||||
* @param last An iterator past the last element of the range of elements. | |||||
*/ | |||||
template<typename It> | |||||
void insert(It first, It last) { | |||||
for(; first != last; ++first) { | |||||
insert(*first); | |||||
} | |||||
} | |||||
/** | |||||
* @brief Constructs an element in-place, if it does not exist. | |||||
* | |||||
* The element is also constructed when the container already has the key, | |||||
* in which case the newly constructed object is destroyed immediately. | |||||
* | |||||
* @tparam Args Types of arguments to forward to the constructor of the | |||||
* element. | |||||
* @param args Arguments to forward to the constructor of the element. | |||||
* @return A pair consisting of an iterator to the inserted element (or to | |||||
* the element that prevented the insertion) and a bool denoting whether the | |||||
* insertion took place. | |||||
*/ | |||||
template<typename... Args> | |||||
std::pair<iterator, bool> emplace(Args &&...args) { | |||||
if constexpr(((sizeof...(Args) == 1u) && ... && std::is_same_v<std::remove_cv_t<std::remove_reference_t<Args>>, value_type>)) { | |||||
return insert_or_do_nothing(std::forward<Args>(args)...); | |||||
} else { | |||||
auto &node = packed.first().emplace_back(std::piecewise_construct, std::make_tuple(packed.first().size()), std::forward_as_tuple(std::forward<Args>(args)...)); | |||||
const auto index = value_to_bucket(node.second); | |||||
if(auto it = constrained_find(node.second, index); it != end()) { | |||||
packed.first().pop_back(); | |||||
return std::make_pair(it, false); | |||||
} | |||||
std::swap(node.first, sparse.first()[index]); | |||||
rehash_if_required(); | |||||
return std::make_pair(--end(), true); | |||||
} | |||||
} | |||||
/** | |||||
* @brief Removes an element from a given position. | |||||
* @param pos An iterator to the element to remove. | |||||
* @return An iterator following the removed element. | |||||
*/ | |||||
iterator erase(const_iterator pos) { | |||||
const auto diff = pos - cbegin(); | |||||
erase(*pos); | |||||
return begin() + diff; | |||||
} | |||||
/** | |||||
* @brief Removes the given elements from a container. | |||||
* @param first An iterator to the first element of the range of elements. | |||||
* @param last An iterator past the last element of the range of elements. | |||||
* @return An iterator following the last removed element. | |||||
*/ | |||||
iterator erase(const_iterator first, const_iterator last) { | |||||
const auto dist = first - cbegin(); | |||||
for(auto from = last - cbegin(); from != dist; --from) { | |||||
erase(packed.first()[from - 1u].second); | |||||
} | |||||
return (begin() + dist); | |||||
} | |||||
/** | |||||
* @brief Removes the element associated with a given value. | |||||
* @param value Value of an element to remove. | |||||
* @return Number of elements removed (either 0 or 1). | |||||
*/ | |||||
size_type erase(const value_type &value) { | |||||
for(size_type *curr = sparse.first().data() + value_to_bucket(value); *curr != (std::numeric_limits<size_type>::max)(); curr = &packed.first()[*curr].first) { | |||||
if(packed.second()(packed.first()[*curr].second, value)) { | |||||
const auto index = *curr; | |||||
*curr = packed.first()[*curr].first; | |||||
move_and_pop(index); | |||||
return 1u; | |||||
} | |||||
} | |||||
return 0u; | |||||
} | |||||
/** | |||||
* @brief Exchanges the contents with those of a given container. | |||||
* @param other Container to exchange the content with. | |||||
*/ | |||||
void swap(dense_set &other) { | |||||
using std::swap; | |||||
swap(sparse, other.sparse); | |||||
swap(packed, other.packed); | |||||
swap(threshold, other.threshold); | |||||
} | |||||
/** | |||||
* @brief Finds an element with a given value. | |||||
* @param value Value of an element to search for. | |||||
* @return An iterator to an element with the given value. If no such | |||||
* element is found, a past-the-end iterator is returned. | |||||
*/ | |||||
[[nodiscard]] iterator find(const value_type &value) { | |||||
return constrained_find(value, value_to_bucket(value)); | |||||
} | |||||
/*! @copydoc find */ | |||||
[[nodiscard]] const_iterator find(const value_type &value) const { | |||||
return constrained_find(value, value_to_bucket(value)); | |||||
} | |||||
/** | |||||
* @brief Finds an element that compares _equivalent_ to a given value. | |||||
* @tparam Other Type of an element to search for. | |||||
* @param value Value of an element to search for. | |||||
* @return An iterator to an element with the given value. If no such | |||||
* element is found, a past-the-end iterator is returned. | |||||
*/ | |||||
template<typename Other> | |||||
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, iterator>> | |||||
find(const Other &value) { | |||||
return constrained_find(value, value_to_bucket(value)); | |||||
} | |||||
/*! @copydoc find */ | |||||
template<typename Other> | |||||
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, const_iterator>> | |||||
find(const Other &value) const { | |||||
return constrained_find(value, value_to_bucket(value)); | |||||
} | |||||
/** | |||||
* @brief Checks if the container contains an element with a given value. | |||||
* @param value Value of an element to search for. | |||||
* @return True if there is such an element, false otherwise. | |||||
*/ | |||||
[[nodiscard]] bool contains(const value_type &value) const { | |||||
return (find(value) != cend()); | |||||
} | |||||
/** | |||||
* @brief Checks if the container contains an element that compares | |||||
* _equivalent_ to a given value. | |||||
* @tparam Other Type of an element to search for. | |||||
* @param value Value of an element to search for. | |||||
* @return True if there is such an element, false otherwise. | |||||
*/ | |||||
template<typename Other> | |||||
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, bool>> | |||||
contains(const Other &value) const { | |||||
return (find(value) != cend()); | |||||
} | |||||
/** | |||||
* @brief Returns an iterator to the beginning of a given bucket. | |||||
* @param index An index of a bucket to access. | |||||
* @return An iterator to the beginning of the given bucket. | |||||
*/ | |||||
[[nodiscard]] const_local_iterator cbegin(const size_type index) const { | |||||
return {packed.first().begin(), sparse.first()[index]}; | |||||
} | |||||
/** | |||||
* @brief Returns an iterator to the beginning of a given bucket. | |||||
* @param index An index of a bucket to access. | |||||
* @return An iterator to the beginning of the given bucket. | |||||
*/ | |||||
[[nodiscard]] const_local_iterator begin(const size_type index) const { | |||||
return cbegin(index); | |||||
} | |||||
/** | |||||
* @brief Returns an iterator to the beginning of a given bucket. | |||||
* @param index An index of a bucket to access. | |||||
* @return An iterator to the beginning of the given bucket. | |||||
*/ | |||||
[[nodiscard]] local_iterator begin(const size_type index) { | |||||
return {packed.first().begin(), sparse.first()[index]}; | |||||
} | |||||
/** | |||||
* @brief Returns an iterator to the end of a given bucket. | |||||
* @param index An index of a bucket to access. | |||||
* @return An iterator to the end of the given bucket. | |||||
*/ | |||||
[[nodiscard]] const_local_iterator cend([[maybe_unused]] const size_type index) const { | |||||
return {packed.first().begin(), (std::numeric_limits<size_type>::max)()}; | |||||
} | |||||
/** | |||||
* @brief Returns an iterator to the end of a given bucket. | |||||
* @param index An index of a bucket to access. | |||||
* @return An iterator to the end of the given bucket. | |||||
*/ | |||||
[[nodiscard]] const_local_iterator end([[maybe_unused]] const size_type index) const { | |||||
return cend(index); | |||||
} | |||||
/** | |||||
* @brief Returns an iterator to the end of a given bucket. | |||||
* @param index An index of a bucket to access. | |||||
* @return An iterator to the end of the given bucket. | |||||
*/ | |||||
[[nodiscard]] local_iterator end([[maybe_unused]] const size_type index) { | |||||
return {packed.first().begin(), (std::numeric_limits<size_type>::max)()}; | |||||
} | |||||
/** | |||||
* @brief Returns the number of buckets. | |||||
* @return The number of buckets. | |||||
*/ | |||||
[[nodiscard]] size_type bucket_count() const { | |||||
return sparse.first().size(); | |||||
} | |||||
/** | |||||
* @brief Returns the maximum number of buckets. | |||||
* @return The maximum number of buckets. | |||||
*/ | |||||
[[nodiscard]] size_type max_bucket_count() const { | |||||
return sparse.first().max_size(); | |||||
} | |||||
/** | |||||
* @brief Returns the number of elements in a given bucket. | |||||
* @param index The index of the bucket to examine. | |||||
* @return The number of elements in the given bucket. | |||||
*/ | |||||
[[nodiscard]] size_type bucket_size(const size_type index) const { | |||||
return static_cast<size_type>(std::distance(begin(index), end(index))); | |||||
} | |||||
/** | |||||
* @brief Returns the bucket for a given element. | |||||
* @param value The value of the element to examine. | |||||
* @return The bucket for the given element. | |||||
*/ | |||||
[[nodiscard]] size_type bucket(const value_type &value) const { | |||||
return value_to_bucket(value); | |||||
} | |||||
/** | |||||
* @brief Returns the average number of elements per bucket. | |||||
* @return The average number of elements per bucket. | |||||
*/ | |||||
[[nodiscard]] float load_factor() const { | |||||
return size() / static_cast<float>(bucket_count()); | |||||
} | |||||
/** | |||||
* @brief Returns the maximum average number of elements per bucket. | |||||
* @return The maximum average number of elements per bucket. | |||||
*/ | |||||
[[nodiscard]] float max_load_factor() const { | |||||
return threshold; | |||||
} | |||||
/** | |||||
* @brief Sets the desired maximum average number of elements per bucket. | |||||
* @param value A desired maximum average number of elements per bucket. | |||||
*/ | |||||
void max_load_factor(const float value) { | |||||
ENTT_ASSERT(value > 0.f, "Invalid load factor"); | |||||
threshold = value; | |||||
rehash(0u); | |||||
} | |||||
/** | |||||
* @brief Reserves at least the specified number of buckets and regenerates | |||||
* the hash table. | |||||
* @param count New number of buckets. | |||||
*/ | |||||
void rehash(const size_type count) { | |||||
auto value = (std::max)(count, minimum_capacity); | |||||
value = (std::max)(value, static_cast<size_type>(size() / max_load_factor())); | |||||
if(const auto sz = next_power_of_two(value); sz != bucket_count()) { | |||||
sparse.first().resize(sz); | |||||
std::fill(sparse.first().begin(), sparse.first().end(), (std::numeric_limits<size_type>::max)()); | |||||
for(size_type pos{}, last = size(); pos < last; ++pos) { | |||||
const auto index = value_to_bucket(packed.first()[pos].second); | |||||
packed.first()[pos].first = std::exchange(sparse.first()[index], pos); | |||||
} | |||||
} | |||||
} | |||||
/** | |||||
* @brief Reserves space for at least the specified number of elements and | |||||
* regenerates the hash table. | |||||
* @param count New number of elements. | |||||
*/ | |||||
void reserve(const size_type count) { | |||||
packed.first().reserve(count); | |||||
rehash(static_cast<size_type>(std::ceil(count / max_load_factor()))); | |||||
} | |||||
/** | |||||
* @brief Returns the function used to hash the elements. | |||||
* @return The function used to hash the elements. | |||||
*/ | |||||
[[nodiscard]] hasher hash_function() const { | |||||
return sparse.second(); | |||||
} | |||||
/** | |||||
* @brief Returns the function used to compare elements for equality. | |||||
* @return The function used to compare elements for equality. | |||||
*/ | |||||
[[nodiscard]] key_equal key_eq() const { | |||||
return packed.second(); | |||||
} | |||||
private: | |||||
compressed_pair<sparse_container_type, hasher> sparse; | |||||
compressed_pair<packed_container_type, key_equal> packed; | |||||
float threshold; | |||||
}; | |||||
} // namespace entt | |||||
#endif |
@ -0,0 +1,26 @@ | |||||
#ifndef ENTT_CONTAINER_FWD_HPP | |||||
#define ENTT_CONTAINER_FWD_HPP | |||||
#include <functional> | |||||
#include <memory> | |||||
namespace entt { | |||||
template< | |||||
typename Key, | |||||
typename Type, | |||||
typename = std::hash<Key>, | |||||
typename = std::equal_to<Key>, | |||||
typename = std::allocator<std::pair<const Key, Type>>> | |||||
class dense_map; | |||||
template< | |||||
typename Type, | |||||
typename = std::hash<Type>, | |||||
typename = std::equal_to<Type>, | |||||
typename = std::allocator<Type>> | |||||
class dense_set; | |||||
} // namespace entt | |||||
#endif |
@ -0,0 +1,490 @@ | |||||
#ifndef ENTT_CORE_ANY_HPP | |||||
#define ENTT_CORE_ANY_HPP | |||||
#include <cstddef> | |||||
#include <memory> | |||||
#include <type_traits> | |||||
#include <utility> | |||||
#include "../config/config.h" | |||||
#include "../core/utility.hpp" | |||||
#include "fwd.hpp" | |||||
#include "type_info.hpp" | |||||
#include "type_traits.hpp" | |||||
namespace entt { | |||||
/** | |||||
* @brief A SBO friendly, type-safe container for single values of any type. | |||||
* @tparam Len Size of the storage reserved for the small buffer optimization. | |||||
* @tparam Align Optional alignment requirement. | |||||
*/ | |||||
template<std::size_t Len, std::size_t Align> | |||||
class basic_any { | |||||
enum class operation : std::uint8_t { | |||||
copy, | |||||
move, | |||||
transfer, | |||||
assign, | |||||
destroy, | |||||
compare, | |||||
get | |||||
}; | |||||
enum class policy : std::uint8_t { | |||||
owner, | |||||
ref, | |||||
cref | |||||
}; | |||||
using storage_type = std::aligned_storage_t<Len + !Len, Align>; | |||||
using vtable_type = const void *(const operation, const basic_any &, const void *); | |||||
template<typename Type> | |||||
static constexpr bool in_situ = Len && alignof(Type) <= alignof(storage_type) && sizeof(Type) <= sizeof(storage_type) && std::is_nothrow_move_constructible_v<Type>; | |||||
template<typename Type> | |||||
static const void *basic_vtable([[maybe_unused]] const operation op, [[maybe_unused]] const basic_any &value, [[maybe_unused]] const void *other) { | |||||
static_assert(!std::is_same_v<Type, void> && std::is_same_v<std::remove_cv_t<std::remove_reference_t<Type>>, Type>, "Invalid type"); | |||||
const Type *element = nullptr; | |||||
if constexpr(in_situ<Type>) { | |||||
element = value.owner() ? reinterpret_cast<const Type *>(&value.storage) : static_cast<const Type *>(value.instance); | |||||
} else { | |||||
element = static_cast<const Type *>(value.instance); | |||||
} | |||||
switch(op) { | |||||
case operation::copy: | |||||
if constexpr(std::is_copy_constructible_v<Type>) { | |||||
static_cast<basic_any *>(const_cast<void *>(other))->initialize<Type>(*element); | |||||
} | |||||
break; | |||||
case operation::move: | |||||
if constexpr(in_situ<Type>) { | |||||
if(value.owner()) { | |||||
return new(&static_cast<basic_any *>(const_cast<void *>(other))->storage) Type{std::move(*const_cast<Type *>(element))}; | |||||
} | |||||
} | |||||
return (static_cast<basic_any *>(const_cast<void *>(other))->instance = std::exchange(const_cast<basic_any &>(value).instance, nullptr)); | |||||
case operation::transfer: | |||||
if constexpr(std::is_move_assignable_v<Type>) { | |||||
*const_cast<Type *>(element) = std::move(*static_cast<Type *>(const_cast<void *>(other))); | |||||
return other; | |||||
} | |||||
[[fallthrough]]; | |||||
case operation::assign: | |||||
if constexpr(std::is_copy_assignable_v<Type>) { | |||||
*const_cast<Type *>(element) = *static_cast<const Type *>(other); | |||||
return other; | |||||
} | |||||
break; | |||||
case operation::destroy: | |||||
if constexpr(in_situ<Type>) { | |||||
element->~Type(); | |||||
} else if constexpr(std::is_array_v<Type>) { | |||||
delete[] element; | |||||
} else { | |||||
delete element; | |||||
} | |||||
break; | |||||
case operation::compare: | |||||
if constexpr(!std::is_function_v<Type> && !std::is_array_v<Type> && is_equality_comparable_v<Type>) { | |||||
return *static_cast<const Type *>(element) == *static_cast<const Type *>(other) ? other : nullptr; | |||||
} else { | |||||
return (element == other) ? other : nullptr; | |||||
} | |||||
case operation::get: | |||||
return element; | |||||
} | |||||
return nullptr; | |||||
} | |||||
template<typename Type, typename... Args> | |||||
void initialize([[maybe_unused]] Args &&...args) { | |||||
if constexpr(!std::is_void_v<Type>) { | |||||
info = &type_id<std::remove_cv_t<std::remove_reference_t<Type>>>(); | |||||
vtable = basic_vtable<std::remove_cv_t<std::remove_reference_t<Type>>>; | |||||
if constexpr(std::is_lvalue_reference_v<Type>) { | |||||
static_assert(sizeof...(Args) == 1u && (std::is_lvalue_reference_v<Args> && ...), "Invalid arguments"); | |||||
mode = std::is_const_v<std::remove_reference_t<Type>> ? policy::cref : policy::ref; | |||||
instance = (std::addressof(args), ...); | |||||
} else if constexpr(in_situ<Type>) { | |||||
if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v<Type>) { | |||||
new(&storage) Type{std::forward<Args>(args)...}; | |||||
} else { | |||||
new(&storage) Type(std::forward<Args>(args)...); | |||||
} | |||||
} else { | |||||
if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v<Type>) { | |||||
instance = new Type{std::forward<Args>(args)...}; | |||||
} else { | |||||
instance = new Type(std::forward<Args>(args)...); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
basic_any(const basic_any &other, const policy pol) ENTT_NOEXCEPT | |||||
: instance{other.data()}, | |||||
info{other.info}, | |||||
vtable{other.vtable}, | |||||
mode{pol} {} | |||||
public: | |||||
/*! @brief Size of the internal storage. */ | |||||
static constexpr auto length = Len; | |||||
/*! @brief Alignment requirement. */ | |||||
static constexpr auto alignment = Align; | |||||
/*! @brief Default constructor. */ | |||||
constexpr basic_any() ENTT_NOEXCEPT | |||||
: instance{}, | |||||
info{&type_id<void>()}, | |||||
vtable{}, | |||||
mode{policy::owner} {} | |||||
/** | |||||
* @brief Constructs a wrapper by directly initializing the new object. | |||||
* @tparam Type Type of object to use to initialize the wrapper. | |||||
* @tparam Args Types of arguments to use to construct the new instance. | |||||
* @param args Parameters to use to construct the instance. | |||||
*/ | |||||
template<typename Type, typename... Args> | |||||
explicit basic_any(std::in_place_type_t<Type>, Args &&...args) | |||||
: basic_any{} { | |||||
initialize<Type>(std::forward<Args>(args)...); | |||||
} | |||||
/** | |||||
* @brief Constructs a wrapper from a given value. | |||||
* @tparam Type Type of object to use to initialize the wrapper. | |||||
* @param value An instance of an object to use to initialize the wrapper. | |||||
*/ | |||||
template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::decay_t<Type>, basic_any>>> | |||||
basic_any(Type &&value) | |||||
: basic_any{} { | |||||
initialize<std::decay_t<Type>>(std::forward<Type>(value)); | |||||
} | |||||
/** | |||||
* @brief Copy constructor. | |||||
* @param other The instance to copy from. | |||||
*/ | |||||
basic_any(const basic_any &other) | |||||
: basic_any{} { | |||||
if(other.vtable) { | |||||
other.vtable(operation::copy, other, this); | |||||
} | |||||
} | |||||
/** | |||||
* @brief Move constructor. | |||||
* @param other The instance to move from. | |||||
*/ | |||||
basic_any(basic_any &&other) ENTT_NOEXCEPT | |||||
: instance{}, | |||||
info{other.info}, | |||||
vtable{other.vtable}, | |||||
mode{other.mode} { | |||||
if(other.vtable) { | |||||
other.vtable(operation::move, other, this); | |||||
} | |||||
} | |||||
/*! @brief Frees the internal storage, whatever it means. */ | |||||
~basic_any() { | |||||
if(vtable && owner()) { | |||||
vtable(operation::destroy, *this, nullptr); | |||||
} | |||||
} | |||||
/** | |||||
* @brief Copy assignment operator. | |||||
* @param other The instance to copy from. | |||||
* @return This any object. | |||||
*/ | |||||
basic_any &operator=(const basic_any &other) { | |||||
reset(); | |||||
if(other.vtable) { | |||||
other.vtable(operation::copy, other, this); | |||||
} | |||||
return *this; | |||||
} | |||||
/** | |||||
* @brief Move assignment operator. | |||||
* @param other The instance to move from. | |||||
* @return This any object. | |||||
*/ | |||||
basic_any &operator=(basic_any &&other) ENTT_NOEXCEPT { | |||||
reset(); | |||||
if(other.vtable) { | |||||
other.vtable(operation::move, other, this); | |||||
info = other.info; | |||||
vtable = other.vtable; | |||||
mode = other.mode; | |||||
} | |||||
return *this; | |||||
} | |||||
/** | |||||
* @brief Value assignment operator. | |||||
* @tparam Type Type of object to use to initialize the wrapper. | |||||
* @param value An instance of an object to use to initialize the wrapper. | |||||
* @return This any object. | |||||
*/ | |||||
template<typename Type> | |||||
std::enable_if_t<!std::is_same_v<std::decay_t<Type>, basic_any>, basic_any &> | |||||
operator=(Type &&value) { | |||||
emplace<std::decay_t<Type>>(std::forward<Type>(value)); | |||||
return *this; | |||||
} | |||||
/** | |||||
* @brief Returns the object type if any, `type_id<void>()` otherwise. | |||||
* @return The object type if any, `type_id<void>()` otherwise. | |||||
*/ | |||||
[[nodiscard]] const type_info &type() const ENTT_NOEXCEPT { | |||||
return *info; | |||||
} | |||||
/** | |||||
* @brief Returns an opaque pointer to the contained instance. | |||||
* @return An opaque pointer the contained instance, if any. | |||||
*/ | |||||
[[nodiscard]] const void *data() const ENTT_NOEXCEPT { | |||||
return vtable ? vtable(operation::get, *this, nullptr) : nullptr; | |||||
} | |||||
/** | |||||
* @brief Returns an opaque pointer to the contained instance. | |||||
* @param req Expected type. | |||||
* @return An opaque pointer the contained instance, if any. | |||||
*/ | |||||
[[nodiscard]] const void *data(const type_info &req) const ENTT_NOEXCEPT { | |||||
return *info == req ? data() : nullptr; | |||||
} | |||||
/** | |||||
* @brief Returns an opaque pointer to the contained instance. | |||||
* @return An opaque pointer the contained instance, if any. | |||||
*/ | |||||
[[nodiscard]] void *data() ENTT_NOEXCEPT { | |||||
return (!vtable || mode == policy::cref) ? nullptr : const_cast<void *>(vtable(operation::get, *this, nullptr)); | |||||
} | |||||
/** | |||||
* @brief Returns an opaque pointer to the contained instance. | |||||
* @param req Expected type. | |||||
* @return An opaque pointer the contained instance, if any. | |||||
*/ | |||||
[[nodiscard]] void *data(const type_info &req) ENTT_NOEXCEPT { | |||||
return *info == req ? data() : nullptr; | |||||
} | |||||
/** | |||||
* @brief Replaces the contained object by creating a new instance directly. | |||||
* @tparam Type Type of object to use to initialize the wrapper. | |||||
* @tparam Args Types of arguments to use to construct the new instance. | |||||
* @param args Parameters to use to construct the instance. | |||||
*/ | |||||
template<typename Type, typename... Args> | |||||
void emplace(Args &&...args) { | |||||
reset(); | |||||
initialize<Type>(std::forward<Args>(args)...); | |||||
} | |||||
/** | |||||
* @brief Assigns a value to the contained object without replacing it. | |||||
* @param other The value to assign to the contained object. | |||||
* @return True in case of success, false otherwise. | |||||
*/ | |||||
bool assign(const any &other) { | |||||
if(vtable && mode != policy::cref && *info == *other.info) { | |||||
return (vtable(operation::assign, *this, other.data()) != nullptr); | |||||
} | |||||
return false; | |||||
} | |||||
/*! @copydoc assign */ | |||||
bool assign(any &&other) { | |||||
if(vtable && mode != policy::cref && *info == *other.info) { | |||||
if(auto *val = other.data(); val) { | |||||
return (vtable(operation::transfer, *this, val) != nullptr); | |||||
} else { | |||||
return (vtable(operation::assign, *this, std::as_const(other).data()) != nullptr); | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
/*! @brief Destroys contained object */ | |||||
void reset() { | |||||
if(vtable && owner()) { | |||||
vtable(operation::destroy, *this, nullptr); | |||||
} | |||||
info = &type_id<void>(); | |||||
vtable = nullptr; | |||||
mode = policy::owner; | |||||
} | |||||
/** | |||||
* @brief Returns false if a wrapper is empty, true otherwise. | |||||
* @return False if the wrapper is empty, true otherwise. | |||||
*/ | |||||
[[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { | |||||
return vtable != nullptr; | |||||
} | |||||
/** | |||||
* @brief Checks if two wrappers differ in their content. | |||||
* @param other Wrapper with which to compare. | |||||
* @return False if the two objects differ in their content, true otherwise. | |||||
*/ | |||||
bool operator==(const basic_any &other) const ENTT_NOEXCEPT { | |||||
if(vtable && *info == *other.info) { | |||||
return (vtable(operation::compare, *this, other.data()) != nullptr); | |||||
} | |||||
return (!vtable && !other.vtable); | |||||
} | |||||
/** | |||||
* @brief Aliasing constructor. | |||||
* @return A wrapper that shares a reference to an unmanaged object. | |||||
*/ | |||||
[[nodiscard]] basic_any as_ref() ENTT_NOEXCEPT { | |||||
return basic_any{*this, (mode == policy::cref ? policy::cref : policy::ref)}; | |||||
} | |||||
/*! @copydoc as_ref */ | |||||
[[nodiscard]] basic_any as_ref() const ENTT_NOEXCEPT { | |||||
return basic_any{*this, policy::cref}; | |||||
} | |||||
/** | |||||
* @brief Returns true if a wrapper owns its object, false otherwise. | |||||
* @return True if the wrapper owns its object, false otherwise. | |||||
*/ | |||||
[[nodiscard]] bool owner() const ENTT_NOEXCEPT { | |||||
return (mode == policy::owner); | |||||
} | |||||
private: | |||||
union { | |||||
const void *instance; | |||||
storage_type storage; | |||||
}; | |||||
const type_info *info; | |||||
vtable_type *vtable; | |||||
policy mode; | |||||
}; | |||||
/** | |||||
* @brief Checks if two wrappers differ in their content. | |||||
* @tparam Len Size of the storage reserved for the small buffer optimization. | |||||
* @tparam Align Alignment requirement. | |||||
* @param lhs A wrapper, either empty or not. | |||||
* @param rhs A wrapper, either empty or not. | |||||
* @return True if the two wrappers differ in their content, false otherwise. | |||||
*/ | |||||
template<std::size_t Len, std::size_t Align> | |||||
[[nodiscard]] inline bool operator!=(const basic_any<Len, Align> &lhs, const basic_any<Len, Align> &rhs) ENTT_NOEXCEPT { | |||||
return !(lhs == rhs); | |||||
} | |||||
/** | |||||
* @brief Performs type-safe access to the contained object. | |||||
* @tparam Type Type to which conversion is required. | |||||
* @tparam Len Size of the storage reserved for the small buffer optimization. | |||||
* @tparam Align Alignment requirement. | |||||
* @param data Target any object. | |||||
* @return The element converted to the requested type. | |||||
*/ | |||||
template<typename Type, std::size_t Len, std::size_t Align> | |||||
Type any_cast(const basic_any<Len, Align> &data) ENTT_NOEXCEPT { | |||||
const auto *const instance = any_cast<std::remove_reference_t<Type>>(&data); | |||||
ENTT_ASSERT(instance, "Invalid instance"); | |||||
return static_cast<Type>(*instance); | |||||
} | |||||
/*! @copydoc any_cast */ | |||||
template<typename Type, std::size_t Len, std::size_t Align> | |||||
Type any_cast(basic_any<Len, Align> &data) ENTT_NOEXCEPT { | |||||
// forces const on non-reference types to make them work also with wrappers for const references | |||||
auto *const instance = any_cast<std::remove_reference_t<const Type>>(&data); | |||||
ENTT_ASSERT(instance, "Invalid instance"); | |||||
return static_cast<Type>(*instance); | |||||
} | |||||
/*! @copydoc any_cast */ | |||||
template<typename Type, std::size_t Len, std::size_t Align> | |||||
Type any_cast(basic_any<Len, Align> &&data) ENTT_NOEXCEPT { | |||||
if constexpr(std::is_copy_constructible_v<std::remove_cv_t<std::remove_reference_t<Type>>>) { | |||||
if(auto *const instance = any_cast<std::remove_reference_t<Type>>(&data); instance) { | |||||
return static_cast<Type>(std::move(*instance)); | |||||
} else { | |||||
return any_cast<Type>(data); | |||||
} | |||||
} else { | |||||
auto *const instance = any_cast<std::remove_reference_t<Type>>(&data); | |||||
ENTT_ASSERT(instance, "Invalid instance"); | |||||
return static_cast<Type>(std::move(*instance)); | |||||
} | |||||
} | |||||
/*! @copydoc any_cast */ | |||||
template<typename Type, std::size_t Len, std::size_t Align> | |||||
const Type *any_cast(const basic_any<Len, Align> *data) ENTT_NOEXCEPT { | |||||
const auto &info = type_id<std::remove_cv_t<std::remove_reference_t<Type>>>(); | |||||
return static_cast<const Type *>(data->data(info)); | |||||
} | |||||
/*! @copydoc any_cast */ | |||||
template<typename Type, std::size_t Len, std::size_t Align> | |||||
Type *any_cast(basic_any<Len, Align> *data) ENTT_NOEXCEPT { | |||||
const auto &info = type_id<std::remove_cv_t<std::remove_reference_t<Type>>>(); | |||||
// last attempt to make wrappers for const references return their values | |||||
return static_cast<Type *>(static_cast<constness_as_t<basic_any<Len, Align>, Type> *>(data)->data(info)); | |||||
} | |||||
/** | |||||
* @brief Constructs a wrapper from a given type, passing it all arguments. | |||||
* @tparam Type Type of object to use to initialize the wrapper. | |||||
* @tparam Len Size of the storage reserved for the small buffer optimization. | |||||
* @tparam Align Optional alignment requirement. | |||||
* @tparam Args Types of arguments to use to construct the new instance. | |||||
* @param args Parameters to use to construct the instance. | |||||
* @return A properly initialized wrapper for an object of the given type. | |||||
*/ | |||||
template<typename Type, std::size_t Len = basic_any<>::length, std::size_t Align = basic_any<Len>::alignment, typename... Args> | |||||
basic_any<Len, Align> make_any(Args &&...args) { | |||||
return basic_any<Len, Align>{std::in_place_type<Type>, std::forward<Args>(args)...}; | |||||
} | |||||
/** | |||||
* @brief Forwards its argument and avoids copies for lvalue references. | |||||
* @tparam Len Size of the storage reserved for the small buffer optimization. | |||||
* @tparam Align Optional alignment requirement. | |||||
* @tparam Type Type of argument to use to construct the new instance. | |||||
* @param value Parameter to use to construct the instance. | |||||
* @return A properly initialized and not necessarily owning wrapper. | |||||
*/ | |||||
template<std::size_t Len = basic_any<>::length, std::size_t Align = basic_any<Len>::alignment, typename Type> | |||||
basic_any<Len, Align> forward_as_any(Type &&value) { | |||||
return basic_any<Len, Align>{std::in_place_type<std::conditional_t<std::is_rvalue_reference_v<Type>, std::decay_t<Type>, Type>>, std::forward<Type>(value)}; | |||||
} | |||||
} // namespace entt | |||||
#endif |
@ -0,0 +1,30 @@ | |||||
#ifndef ENTT_CORE_ATTRIBUTE_H | |||||
#define ENTT_CORE_ATTRIBUTE_H | |||||
#ifndef ENTT_EXPORT | |||||
# if defined _WIN32 || defined __CYGWIN__ || defined _MSC_VER | |||||
# define ENTT_EXPORT __declspec(dllexport) | |||||
# define ENTT_IMPORT __declspec(dllimport) | |||||
# define ENTT_HIDDEN | |||||
# elif defined __GNUC__ && __GNUC__ >= 4 | |||||
# define ENTT_EXPORT __attribute__((visibility("default"))) | |||||
# define ENTT_IMPORT __attribute__((visibility("default"))) | |||||
# define ENTT_HIDDEN __attribute__((visibility("hidden"))) | |||||
# else /* Unsupported compiler */ | |||||
# define ENTT_EXPORT | |||||
# define ENTT_IMPORT | |||||
# define ENTT_HIDDEN | |||||
# endif | |||||
#endif | |||||
#ifndef ENTT_API | |||||
# if defined ENTT_API_EXPORT | |||||
# define ENTT_API ENTT_EXPORT | |||||
# elif defined ENTT_API_IMPORT | |||||
# define ENTT_API ENTT_IMPORT | |||||
# else /* No API */ | |||||
# define ENTT_API | |||||
# endif | |||||
#endif | |||||
#endif |
@ -0,0 +1,280 @@ | |||||
#ifndef ENTT_CORE_COMPRESSED_PAIR_HPP | |||||
#define ENTT_CORE_COMPRESSED_PAIR_HPP | |||||
#include <cstddef> | |||||
#include <tuple> | |||||
#include <type_traits> | |||||
#include <utility> | |||||
#include "../config/config.h" | |||||
#include "type_traits.hpp" | |||||
namespace entt { | |||||
/** | |||||
* @cond TURN_OFF_DOXYGEN | |||||
* Internal details not to be documented. | |||||
*/ | |||||
namespace internal { | |||||
template<typename Type, std::size_t, typename = void> | |||||
struct compressed_pair_element { | |||||
using reference = Type &; | |||||
using const_reference = const Type &; | |||||
template<bool Dummy = true, typename = std::enable_if_t<Dummy && std::is_default_constructible_v<Type>>> | |||||
compressed_pair_element() | |||||
: value{} {} | |||||
template<typename Args, typename = std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_reference_t<Args>>, compressed_pair_element>>> | |||||
compressed_pair_element(Args &&args) | |||||
: value{std::forward<Args>(args)} {} | |||||
template<typename... Args, std::size_t... Index> | |||||
compressed_pair_element(std::tuple<Args...> args, std::index_sequence<Index...>) | |||||
: value{std::forward<Args>(std::get<Index>(args))...} {} | |||||
[[nodiscard]] reference get() ENTT_NOEXCEPT { | |||||
return value; | |||||
} | |||||
[[nodiscard]] const_reference get() const ENTT_NOEXCEPT { | |||||
return value; | |||||
} | |||||
private: | |||||
Type value; | |||||
}; | |||||
template<typename Type, std::size_t Tag> | |||||
struct compressed_pair_element<Type, Tag, std::enable_if_t<is_ebco_eligible_v<Type>>>: Type { | |||||
using reference = Type &; | |||||
using const_reference = const Type &; | |||||
using base_type = Type; | |||||
template<bool Dummy = true, typename = std::enable_if_t<Dummy && std::is_default_constructible_v<base_type>>> | |||||
compressed_pair_element() | |||||
: base_type{} {} | |||||
template<typename Args, typename = std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_reference_t<Args>>, compressed_pair_element>>> | |||||
compressed_pair_element(Args &&args) | |||||
: base_type{std::forward<Args>(args)} {} | |||||
template<typename... Args, std::size_t... Index> | |||||
compressed_pair_element(std::tuple<Args...> args, std::index_sequence<Index...>) | |||||
: base_type{std::forward<Args>(std::get<Index>(args))...} {} | |||||
[[nodiscard]] reference get() ENTT_NOEXCEPT { | |||||
return *this; | |||||
} | |||||
[[nodiscard]] const_reference get() const ENTT_NOEXCEPT { | |||||
return *this; | |||||
} | |||||
}; | |||||
} // namespace internal | |||||
/** | |||||
* Internal details not to be documented. | |||||
* @endcond | |||||
*/ | |||||
/** | |||||
* @brief A compressed pair. | |||||
* | |||||
* A pair that exploits the _Empty Base Class Optimization_ (or _EBCO_) to | |||||
* reduce its final size to a minimum. | |||||
* | |||||
* @tparam First The type of the first element that the pair stores. | |||||
* @tparam Second The type of the second element that the pair stores. | |||||
*/ | |||||
template<typename First, typename Second> | |||||
class compressed_pair final | |||||
: internal::compressed_pair_element<First, 0u>, | |||||
internal::compressed_pair_element<Second, 1u> { | |||||
using first_base = internal::compressed_pair_element<First, 0u>; | |||||
using second_base = internal::compressed_pair_element<Second, 1u>; | |||||
public: | |||||
/*! @brief The type of the first element that the pair stores. */ | |||||
using first_type = First; | |||||
/*! @brief The type of the second element that the pair stores. */ | |||||
using second_type = Second; | |||||
/** | |||||
* @brief Default constructor, conditionally enabled. | |||||
* | |||||
* This constructor is only available when the types that the pair stores | |||||
* are both at least default constructible. | |||||
* | |||||
* @tparam Dummy Dummy template parameter used for internal purposes. | |||||
*/ | |||||
template<bool Dummy = true, typename = std::enable_if_t<Dummy && std::is_default_constructible_v<first_type> && std::is_default_constructible_v<second_type>>> | |||||
constexpr compressed_pair() | |||||
: first_base{}, | |||||
second_base{} {} | |||||
/** | |||||
* @brief Copy constructor. | |||||
* @param other The instance to copy from. | |||||
*/ | |||||
constexpr compressed_pair(const compressed_pair &other) = default; | |||||
/** | |||||
* @brief Move constructor. | |||||
* @param other The instance to move from. | |||||
*/ | |||||
constexpr compressed_pair(compressed_pair &&other) = default; | |||||
/** | |||||
* @brief Constructs a pair from its values. | |||||
* @tparam Arg Type of value to use to initialize the first element. | |||||
* @tparam Other Type of value to use to initialize the second element. | |||||
* @param arg Value to use to initialize the first element. | |||||
* @param other Value to use to initialize the second element. | |||||
*/ | |||||
template<typename Arg, typename Other> | |||||
constexpr compressed_pair(Arg &&arg, Other &&other) | |||||
: first_base{std::forward<Arg>(arg)}, | |||||
second_base{std::forward<Other>(other)} {} | |||||
/** | |||||
* @brief Constructs a pair by forwarding the arguments to its parts. | |||||
* @tparam Args Types of arguments to use to initialize the first element. | |||||
* @tparam Other Types of arguments to use to initialize the second element. | |||||
* @param args Arguments to use to initialize the first element. | |||||
* @param other Arguments to use to initialize the second element. | |||||
*/ | |||||
template<typename... Args, typename... Other> | |||||
constexpr compressed_pair(std::piecewise_construct_t, std::tuple<Args...> args, std::tuple<Other...> other) | |||||
: first_base{std::move(args), std::index_sequence_for<Args...>{}}, | |||||
second_base{std::move(other), std::index_sequence_for<Other...>{}} {} | |||||
/** | |||||
* @brief Copy assignment operator. | |||||
* @param other The instance to copy from. | |||||
* @return This compressed pair object. | |||||
*/ | |||||
constexpr compressed_pair &operator=(const compressed_pair &other) = default; | |||||
/** | |||||
* @brief Move assignment operator. | |||||
* @param other The instance to move from. | |||||
* @return This compressed pair object. | |||||
*/ | |||||
constexpr compressed_pair &operator=(compressed_pair &&other) = default; | |||||
/** | |||||
* @brief Returns the first element that a pair stores. | |||||
* @return The first element that a pair stores. | |||||
*/ | |||||
[[nodiscard]] first_type &first() ENTT_NOEXCEPT { | |||||
return static_cast<first_base &>(*this).get(); | |||||
} | |||||
/*! @copydoc first */ | |||||
[[nodiscard]] const first_type &first() const ENTT_NOEXCEPT { | |||||
return static_cast<const first_base &>(*this).get(); | |||||
} | |||||
/** | |||||
* @brief Returns the second element that a pair stores. | |||||
* @return The second element that a pair stores. | |||||
*/ | |||||
[[nodiscard]] second_type &second() ENTT_NOEXCEPT { | |||||
return static_cast<second_base &>(*this).get(); | |||||
} | |||||
/*! @copydoc second */ | |||||
[[nodiscard]] const second_type &second() const ENTT_NOEXCEPT { | |||||
return static_cast<const second_base &>(*this).get(); | |||||
} | |||||
/** | |||||
* @brief Swaps two compressed pair objects. | |||||
* @param other The compressed pair to swap with. | |||||
*/ | |||||
void swap(compressed_pair &other) { | |||||
using std::swap; | |||||
swap(first(), other.first()); | |||||
swap(second(), other.second()); | |||||
} | |||||
/** | |||||
* @brief Extracts an element from the compressed pair. | |||||
* @tparam Index An integer value that is either 0 or 1. | |||||
* @return Returns a reference to the first element if `Index` is 0 and a | |||||
* reference to the second element if `Index` is 1. | |||||
*/ | |||||
template<std::size_t Index> | |||||
decltype(auto) get() ENTT_NOEXCEPT { | |||||
if constexpr(Index == 0u) { | |||||
return first(); | |||||
} else { | |||||
static_assert(Index == 1u, "Index out of bounds"); | |||||
return second(); | |||||
} | |||||
} | |||||
/*! @copydoc get */ | |||||
template<std::size_t Index> | |||||
decltype(auto) get() const ENTT_NOEXCEPT { | |||||
if constexpr(Index == 0u) { | |||||
return first(); | |||||
} else { | |||||
static_assert(Index == 1u, "Index out of bounds"); | |||||
return second(); | |||||
} | |||||
} | |||||
}; | |||||
/** | |||||
* @brief Deduction guide. | |||||
* @tparam Type Type of value to use to initialize the first element. | |||||
* @tparam Other Type of value to use to initialize the second element. | |||||
*/ | |||||
template<typename Type, typename Other> | |||||
compressed_pair(Type &&, Other &&) -> compressed_pair<std::decay_t<Type>, std::decay_t<Other>>; | |||||
/** | |||||
* @brief Swaps two compressed pair objects. | |||||
* @tparam First The type of the first element that the pairs store. | |||||
* @tparam Second The type of the second element that the pairs store. | |||||
* @param lhs A valid compressed pair object. | |||||
* @param rhs A valid compressed pair object. | |||||
*/ | |||||
template<typename First, typename Second> | |||||
inline void swap(compressed_pair<First, Second> &lhs, compressed_pair<First, Second> &rhs) { | |||||
lhs.swap(rhs); | |||||
} | |||||
} // namespace entt | |||||
// disable structured binding support for clang 6, it messes when specializing tuple_size | |||||
#if !defined __clang_major__ || __clang_major__ > 6 | |||||
namespace std { | |||||
/** | |||||
* @brief `std::tuple_size` specialization for `compressed_pair`s. | |||||
* @tparam First The type of the first element that the pair stores. | |||||
* @tparam Second The type of the second element that the pair stores. | |||||
*/ | |||||
template<typename First, typename Second> | |||||
struct tuple_size<entt::compressed_pair<First, Second>>: integral_constant<size_t, 2u> {}; | |||||
/** | |||||
* @brief `std::tuple_element` specialization for `compressed_pair`s. | |||||
* @tparam Index The index of the type to return. | |||||
* @tparam First The type of the first element that the pair stores. | |||||
* @tparam Second The type of the second element that the pair stores. | |||||
*/ | |||||
template<size_t Index, typename First, typename Second> | |||||
struct tuple_element<Index, entt::compressed_pair<First, Second>>: conditional<Index == 0u, First, Second> { | |||||
static_assert(Index < 2u, "Index out of bounds"); | |||||
}; | |||||
} // namespace std | |||||
#endif | |||||
#endif |
@ -0,0 +1,98 @@ | |||||
#ifndef ENTT_CORE_ENUM_HPP | |||||
#define ENTT_CORE_ENUM_HPP | |||||
#include <type_traits> | |||||
#include "../config/config.h" | |||||
namespace entt { | |||||
/** | |||||
* @brief Enable bitmask support for enum classes. | |||||
* @tparam Type The enum type for which to enable bitmask support. | |||||
*/ | |||||
template<typename Type, typename = void> | |||||
struct enum_as_bitmask: std::false_type {}; | |||||
/*! @copydoc enum_as_bitmask */ | |||||
template<typename Type> | |||||
struct enum_as_bitmask<Type, std::void_t<decltype(Type::_entt_enum_as_bitmask)>>: std::is_enum<Type> {}; | |||||
/** | |||||
* @brief Helper variable template. | |||||
* @tparam Type The enum class type for which to enable bitmask support. | |||||
*/ | |||||
template<typename Type> | |||||
inline constexpr bool enum_as_bitmask_v = enum_as_bitmask<Type>::value; | |||||
} // namespace entt | |||||
/** | |||||
* @brief Operator available for enums for which bitmask support is enabled. | |||||
* @tparam Type Enum class type. | |||||
* @param lhs The first value to use. | |||||
* @param rhs The second value to use. | |||||
* @return The result of invoking the operator on the underlying types of the | |||||
* two values provided. | |||||
*/ | |||||
template<typename Type> | |||||
[[nodiscard]] constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type> | |||||
operator|(const Type lhs, const Type rhs) ENTT_NOEXCEPT { | |||||
return static_cast<Type>(static_cast<std::underlying_type_t<Type>>(lhs) | static_cast<std::underlying_type_t<Type>>(rhs)); | |||||
} | |||||
/*! @copydoc operator| */ | |||||
template<typename Type> | |||||
[[nodiscard]] constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type> | |||||
operator&(const Type lhs, const Type rhs) ENTT_NOEXCEPT { | |||||
return static_cast<Type>(static_cast<std::underlying_type_t<Type>>(lhs) & static_cast<std::underlying_type_t<Type>>(rhs)); | |||||
} | |||||
/*! @copydoc operator| */ | |||||
template<typename Type> | |||||
[[nodiscard]] constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type> | |||||
operator^(const Type lhs, const Type rhs) ENTT_NOEXCEPT { | |||||
return static_cast<Type>(static_cast<std::underlying_type_t<Type>>(lhs) ^ static_cast<std::underlying_type_t<Type>>(rhs)); | |||||
} | |||||
/** | |||||
* @brief Operator available for enums for which bitmask support is enabled. | |||||
* @tparam Type Enum class type. | |||||
* @param value The value to use. | |||||
* @return The result of invoking the operator on the underlying types of the | |||||
* value provided. | |||||
*/ | |||||
template<typename Type> | |||||
[[nodiscard]] constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type> | |||||
operator~(const Type value) ENTT_NOEXCEPT { | |||||
return static_cast<Type>(~static_cast<std::underlying_type_t<Type>>(value)); | |||||
} | |||||
/*! @copydoc operator~ */ | |||||
template<typename Type> | |||||
[[nodiscard]] constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, bool> | |||||
operator!(const Type value) ENTT_NOEXCEPT { | |||||
return !static_cast<std::underlying_type_t<Type>>(value); | |||||
} | |||||
/*! @copydoc operator| */ | |||||
template<typename Type> | |||||
constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type &> | |||||
operator|=(Type &lhs, const Type rhs) ENTT_NOEXCEPT { | |||||
return (lhs = (lhs | rhs)); | |||||
} | |||||
/*! @copydoc operator| */ | |||||
template<typename Type> | |||||
constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type &> | |||||
operator&=(Type &lhs, const Type rhs) ENTT_NOEXCEPT { | |||||
return (lhs = (lhs & rhs)); | |||||
} | |||||
/*! @copydoc operator| */ | |||||
template<typename Type> | |||||
constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type &> | |||||
operator^=(Type &lhs, const Type rhs) ENTT_NOEXCEPT { | |||||
return (lhs = (lhs ^ rhs)); | |||||
} | |||||
#endif |
@ -0,0 +1,21 @@ | |||||
#ifndef ENTT_CORE_FWD_HPP | |||||
#define ENTT_CORE_FWD_HPP | |||||
#include <cstdint> | |||||
#include <type_traits> | |||||
#include "../config/config.h" | |||||
namespace entt { | |||||
template<std::size_t Len = sizeof(double[2]), std::size_t = alignof(typename std::aligned_storage_t<Len + !Len>)> | |||||
class basic_any; | |||||
/*! @brief Alias declaration for type identifiers. */ | |||||
using id_type = ENTT_ID_TYPE; | |||||
/*! @brief Alias declaration for the most common use case. */ | |||||
using any = basic_any<>; | |||||
} // namespace entt | |||||
#endif |
@ -1,213 +1,333 @@ | |||||
#ifndef ENTT_CORE_HASHED_STRING_HPP | #ifndef ENTT_CORE_HASHED_STRING_HPP | ||||
#define ENTT_CORE_HASHED_STRING_HPP | #define ENTT_CORE_HASHED_STRING_HPP | ||||
#include <cstddef> | #include <cstddef> | ||||
#include <cstdint> | |||||
#include "../config/config.h" | #include "../config/config.h" | ||||
#include "fwd.hpp" | |||||
namespace entt { | namespace entt { | ||||
/** | /** | ||||
* @cond TURN_OFF_DOXYGEN | * @cond TURN_OFF_DOXYGEN | ||||
* Internal details not to be documented. | * Internal details not to be documented. | ||||
*/ | */ | ||||
namespace internal { | namespace internal { | ||||
template<typename> | template<typename> | ||||
struct fnv1a_traits; | struct fnv1a_traits; | ||||
template<> | template<> | ||||
struct fnv1a_traits<std::uint32_t> { | struct fnv1a_traits<std::uint32_t> { | ||||
using type = std::uint32_t; | |||||
static constexpr std::uint32_t offset = 2166136261; | static constexpr std::uint32_t offset = 2166136261; | ||||
static constexpr std::uint32_t prime = 16777619; | static constexpr std::uint32_t prime = 16777619; | ||||
}; | }; | ||||
template<> | template<> | ||||
struct fnv1a_traits<std::uint64_t> { | struct fnv1a_traits<std::uint64_t> { | ||||
using type = std::uint64_t; | |||||
static constexpr std::uint64_t offset = 14695981039346656037ull; | static constexpr std::uint64_t offset = 14695981039346656037ull; | ||||
static constexpr std::uint64_t prime = 1099511628211ull; | static constexpr std::uint64_t prime = 1099511628211ull; | ||||
}; | }; | ||||
template<typename Char> | |||||
struct basic_hashed_string { | |||||
using value_type = Char; | |||||
using size_type = std::size_t; | |||||
using hash_type = id_type; | |||||
} | |||||
const value_type *repr; | |||||
size_type length; | |||||
hash_type hash; | |||||
}; | |||||
} // namespace internal | |||||
/** | /** | ||||
* Internal details not to be documented. | * Internal details not to be documented. | ||||
* @endcond TURN_OFF_DOXYGEN | |||||
* @endcond | |||||
*/ | */ | ||||
/** | /** | ||||
* @brief Zero overhead unique identifier. | * @brief Zero overhead unique identifier. | ||||
* | * | ||||
* A hashed string is a compile-time tool that allows users to use | * A hashed string is a compile-time tool that allows users to use | ||||
* human-readable identifers in the codebase while using their numeric | |||||
* human-readable identifiers in the codebase while using their numeric | |||||
* counterparts at runtime.<br/> | * counterparts at runtime.<br/> | ||||
* Because of that, a hashed string can also be used in constant expressions if | * Because of that, a hashed string can also be used in constant expressions if | ||||
* required. | * required. | ||||
* | |||||
* @warning | |||||
* This class doesn't take ownership of user-supplied strings nor does it make a | |||||
* copy of them. | |||||
* | |||||
* @tparam Char Character type. | |||||
*/ | */ | ||||
class hashed_string { | |||||
using traits_type = internal::fnv1a_traits<ENTT_ID_TYPE>; | |||||
template<typename Char> | |||||
class basic_hashed_string: internal::basic_hashed_string<Char> { | |||||
using base_type = internal::basic_hashed_string<Char>; | |||||
using hs_traits = internal::fnv1a_traits<id_type>; | |||||
struct const_wrapper { | struct const_wrapper { | ||||
// non-explicit constructor on purpose | // non-explicit constructor on purpose | ||||
constexpr const_wrapper(const char *curr) ENTT_NOEXCEPT: str{curr} {} | |||||
const char *str; | |||||
constexpr const_wrapper(const Char *str) ENTT_NOEXCEPT: repr{str} {} | |||||
const Char *repr; | |||||
}; | }; | ||||
// Fowler–Noll–Vo hash function v. 1a - the good | // 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); | |||||
[[nodiscard]] static constexpr auto helper(const Char *str) ENTT_NOEXCEPT { | |||||
base_type base{str, 0u, hs_traits::offset}; | |||||
for(; str[base.length]; ++base.length) { | |||||
base.hash = (base.hash ^ static_cast<hs_traits::type>(str[base.length])) * hs_traits::prime; | |||||
} | |||||
return base; | |||||
} | |||||
// Fowler–Noll–Vo hash function v. 1a - the good | |||||
[[nodiscard]] static constexpr auto helper(const Char *str, const std::size_t len) ENTT_NOEXCEPT { | |||||
base_type base{str, len, hs_traits::offset}; | |||||
for(size_type pos{}; pos < len; ++pos) { | |||||
base.hash = (base.hash ^ static_cast<hs_traits::type>(str[pos])) * hs_traits::prime; | |||||
} | |||||
return base; | |||||
} | } | ||||
public: | public: | ||||
/*! @brief Character type. */ | |||||
using value_type = typename base_type::value_type; | |||||
/*! @brief Unsigned integer type. */ | |||||
using size_type = typename base_type::size_type; | |||||
/*! @brief Unsigned integer type. */ | /*! @brief Unsigned integer type. */ | ||||
using hash_type = ENTT_ID_TYPE; | |||||
using hash_type = typename base_type::hash_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. | |||||
* @brief Returns directly the numeric representation of a string view. | |||||
* @param str Human-readable identifier. | |||||
* @param len Length of the string to hash. | |||||
* @return The numeric representation of the string. | * @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); | |||||
[[nodiscard]] static constexpr hash_type value(const value_type *str, const size_type len) ENTT_NOEXCEPT { | |||||
return basic_hashed_string{str, len}; | |||||
} | } | ||||
/** | /** | ||||
* @brief Returns directly the numeric representation of a string. | * @brief Returns directly the numeric representation of a string. | ||||
* @param wrapper Helps achieving the purpose by relying on overloading. | |||||
* @tparam N Number of characters of the identifier. | |||||
* @param str Human-readable identifier. | |||||
* @return The numeric representation of the string. | * @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); | |||||
template<std::size_t N> | |||||
[[nodiscard]] static constexpr hash_type value(const value_type (&str)[N]) ENTT_NOEXCEPT { | |||||
return basic_hashed_string{str}; | |||||
} | } | ||||
/** | /** | ||||
* @brief Returns directly the numeric representation of a string view. | |||||
* @param str Human-readable identifer. | |||||
* @param size Length of the string to hash. | |||||
* @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. | * @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; | |||||
[[nodiscard]] static constexpr hash_type value(const_wrapper wrapper) ENTT_NOEXCEPT { | |||||
return basic_hashed_string{wrapper}; | |||||
} | } | ||||
/*! @brief Constructs an empty hashed string. */ | /*! @brief Constructs an empty hashed string. */ | ||||
constexpr hashed_string() ENTT_NOEXCEPT | |||||
: str{nullptr}, hash{} | |||||
{} | |||||
constexpr basic_hashed_string() ENTT_NOEXCEPT | |||||
: base_type{} {} | |||||
/** | /** | ||||
* @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 | |||||
* | |||||
* @brief Constructs a hashed string from a string view. | |||||
* @param str Human-readable identifier. | |||||
* @param len Length of the string to hash. | |||||
*/ | |||||
constexpr basic_hashed_string(const value_type *str, const size_type len) ENTT_NOEXCEPT | |||||
: base_type{helper(str, len)} {} | |||||
/** | |||||
* @brief Constructs a hashed string from an array of const characters. | |||||
* @tparam N Number of characters of the identifier. | * @tparam N Number of characters of the identifier. | ||||
* @param curr Human-readable identifer. | |||||
* @param str Human-readable identifier. | |||||
*/ | */ | ||||
template<std::size_t N> | template<std::size_t N> | ||||
constexpr hashed_string(const char (&curr)[N]) ENTT_NOEXCEPT | |||||
: str{curr}, hash{helper(traits_type::offset, curr)} | |||||
{} | |||||
constexpr basic_hashed_string(const value_type (&str)[N]) ENTT_NOEXCEPT | |||||
: base_type{helper(str)} {} | |||||
/** | /** | ||||
* @brief Explicit constructor on purpose to avoid constructing a hashed | * @brief Explicit constructor on purpose to avoid constructing a hashed | ||||
* string directly from a `const char *`. | |||||
* string directly from a `const value_type *`. | |||||
* | |||||
* @warning | |||||
* The lifetime of the string is not extended nor is it copied. | |||||
* | |||||
* @param wrapper Helps achieving the purpose by relying on overloading. | * @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)} | |||||
{} | |||||
explicit constexpr basic_hashed_string(const_wrapper wrapper) ENTT_NOEXCEPT | |||||
: base_type{helper(wrapper.repr)} {} | |||||
/** | /** | ||||
* @brief Returns the human-readable representation of a hashed string. | |||||
* @return The string used to initialize the instance. | |||||
* @brief Returns the size a hashed string. | |||||
* @return The size of the hashed string. | |||||
*/ | */ | ||||
constexpr const char * data() const ENTT_NOEXCEPT { | |||||
return str; | |||||
[[nodiscard]] constexpr size_type size() const ENTT_NOEXCEPT { | |||||
return base_type::length; | |||||
} | } | ||||
/** | /** | ||||
* @brief Returns the numeric representation of a hashed string. | |||||
* @return The numeric representation of the instance. | |||||
* @brief Returns the human-readable representation of a hashed string. | |||||
* @return The string used to initialize the hashed string. | |||||
*/ | */ | ||||
constexpr hash_type value() const ENTT_NOEXCEPT { | |||||
return hash; | |||||
[[nodiscard]] constexpr const value_type *data() const ENTT_NOEXCEPT { | |||||
return base_type::repr; | |||||
} | } | ||||
/** | /** | ||||
* @brief Returns the human-readable representation of a hashed string. | |||||
* @return The string used to initialize the instance. | |||||
* @brief Returns the numeric representation of a hashed string. | |||||
* @return The numeric representation of the hashed string. | |||||
*/ | */ | ||||
constexpr operator const char *() const ENTT_NOEXCEPT { return str; } | |||||
[[nodiscard]] constexpr hash_type value() const ENTT_NOEXCEPT { | |||||
return base_type::hash; | |||||
} | |||||
/*! @copydoc value */ | |||||
constexpr operator hash_type() const ENTT_NOEXCEPT { return hash; } | |||||
/*! @copydoc data */ | |||||
[[nodiscard]] constexpr operator const value_type *() const ENTT_NOEXCEPT { | |||||
return data(); | |||||
} | |||||
/** | /** | ||||
* @brief Compares two hashed strings. | |||||
* @param other Hashed string with which to compare. | |||||
* @return True if the two hashed strings are identical, false otherwise. | |||||
* @brief Returns the numeric representation of a hashed string. | |||||
* @return The numeric representation of the hashed string. | |||||
*/ | */ | ||||
constexpr bool operator==(const hashed_string &other) const ENTT_NOEXCEPT { | |||||
return hash == other.hash; | |||||
[[nodiscard]] constexpr operator hash_type() const ENTT_NOEXCEPT { | |||||
return value(); | |||||
} | } | ||||
private: | |||||
const char *str; | |||||
hash_type hash; | |||||
}; | }; | ||||
/** | |||||
* @brief Deduction guide. | |||||
* @tparam Char Character type. | |||||
* @param str Human-readable identifier. | |||||
* @param len Length of the string to hash. | |||||
*/ | |||||
template<typename Char> | |||||
basic_hashed_string(const Char *str, const std::size_t len) -> basic_hashed_string<Char>; | |||||
/** | |||||
* @brief Deduction guide. | |||||
* @tparam Char Character type. | |||||
* @tparam N Number of characters of the identifier. | |||||
* @param str Human-readable identifier. | |||||
*/ | |||||
template<typename Char, std::size_t N> | |||||
basic_hashed_string(const Char (&str)[N]) -> basic_hashed_string<Char>; | |||||
/** | /** | ||||
* @brief Compares two hashed strings. | * @brief Compares two hashed strings. | ||||
* @tparam Char Character type. | |||||
* @param lhs A valid hashed string. | * @param lhs A valid hashed string. | ||||
* @param rhs A valid hashed string. | * @param rhs A valid hashed string. | ||||
* @return True if the two hashed strings are identical, false otherwise. | * @return True if the two hashed strings are identical, false otherwise. | ||||
*/ | */ | ||||
constexpr bool operator!=(const hashed_string &lhs, const hashed_string &rhs) ENTT_NOEXCEPT { | |||||
template<typename Char> | |||||
[[nodiscard]] constexpr bool operator==(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) ENTT_NOEXCEPT { | |||||
return lhs.value() == rhs.value(); | |||||
} | |||||
/** | |||||
* @brief Compares two hashed strings. | |||||
* @tparam Char Character type. | |||||
* @param lhs A valid hashed string. | |||||
* @param rhs A valid hashed string. | |||||
* @return True if the two hashed strings differ, false otherwise. | |||||
*/ | |||||
template<typename Char> | |||||
[[nodiscard]] constexpr bool operator!=(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) ENTT_NOEXCEPT { | |||||
return !(lhs == rhs); | return !(lhs == rhs); | ||||
} | } | ||||
/** | |||||
* @brief Compares two hashed strings. | |||||
* @tparam Char Character type. | |||||
* @param lhs A valid hashed string. | |||||
* @param rhs A valid hashed string. | |||||
* @return True if the first element is less than the second, false otherwise. | |||||
*/ | |||||
template<typename Char> | |||||
[[nodiscard]] constexpr bool operator<(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) ENTT_NOEXCEPT { | |||||
return lhs.value() < rhs.value(); | |||||
} | |||||
/** | |||||
* @brief Compares two hashed strings. | |||||
* @tparam Char Character type. | |||||
* @param lhs A valid hashed string. | |||||
* @param rhs A valid hashed string. | |||||
* @return True if the first element is less than or equal to the second, false | |||||
* otherwise. | |||||
*/ | |||||
template<typename Char> | |||||
[[nodiscard]] constexpr bool operator<=(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) ENTT_NOEXCEPT { | |||||
return !(rhs < lhs); | |||||
} | } | ||||
/** | |||||
* @brief Compares two hashed strings. | |||||
* @tparam Char Character type. | |||||
* @param lhs A valid hashed string. | |||||
* @param rhs A valid hashed string. | |||||
* @return True if the first element is greater than the second, false | |||||
* otherwise. | |||||
*/ | |||||
template<typename Char> | |||||
[[nodiscard]] constexpr bool operator>(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) ENTT_NOEXCEPT { | |||||
return rhs < lhs; | |||||
} | |||||
/** | |||||
* @brief Compares two hashed strings. | |||||
* @tparam Char Character type. | |||||
* @param lhs A valid hashed string. | |||||
* @param rhs A valid hashed string. | |||||
* @return True if the first element is greater than or equal to the second, | |||||
* false otherwise. | |||||
*/ | |||||
template<typename Char> | |||||
[[nodiscard]] constexpr bool operator>=(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) ENTT_NOEXCEPT { | |||||
return !(lhs < rhs); | |||||
} | |||||
/*! @brief Aliases for common character types. */ | |||||
using hashed_string = basic_hashed_string<char>; | |||||
/*! @brief Aliases for common character types. */ | |||||
using hashed_wstring = basic_hashed_string<wchar_t>; | |||||
inline namespace literals { | |||||
/** | /** | ||||
* @brief User defined literal for hashed strings. | * @brief User defined literal for hashed strings. | ||||
* @param str The literal without its suffix. | * @param str The literal without its suffix. | ||||
* @return A properly initialized hashed string. | * @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}; | |||||
[[nodiscard]] constexpr hashed_string operator"" _hs(const char *str, std::size_t) ENTT_NOEXCEPT { | |||||
return hashed_string{str}; | |||||
} | } | ||||
/** | |||||
* @brief User defined literal for hashed wstrings. | |||||
* @param str The literal without its suffix. | |||||
* @return A properly initialized hashed wstring. | |||||
*/ | |||||
[[nodiscard]] constexpr hashed_wstring operator"" _hws(const wchar_t *str, std::size_t) ENTT_NOEXCEPT { | |||||
return hashed_wstring{str}; | |||||
} | |||||
} // namespace literals | |||||
} // namespace entt | |||||
#endif // ENTT_CORE_HASHED_STRING_HPP | |||||
#endif |
@ -0,0 +1,117 @@ | |||||
#ifndef ENTT_CORE_ITERATOR_HPP | |||||
#define ENTT_CORE_ITERATOR_HPP | |||||
#include <iterator> | |||||
#include <memory> | |||||
#include <utility> | |||||
#include "../config/config.h" | |||||
namespace entt { | |||||
/** | |||||
* @brief Helper type to use as pointer with input iterators. | |||||
* @tparam Type of wrapped value. | |||||
*/ | |||||
template<typename Type> | |||||
struct input_iterator_pointer final { | |||||
/*! @brief Pointer type. */ | |||||
using pointer = Type *; | |||||
/*! @brief Default copy constructor, deleted on purpose. */ | |||||
input_iterator_pointer(const input_iterator_pointer &) = delete; | |||||
/*! @brief Default move constructor. */ | |||||
input_iterator_pointer(input_iterator_pointer &&) = default; | |||||
/** | |||||
* @brief Constructs a proxy object by move. | |||||
* @param val Value to use to initialize the proxy object. | |||||
*/ | |||||
input_iterator_pointer(Type &&val) | |||||
: value{std::move(val)} {} | |||||
/** | |||||
* @brief Default copy assignment operator, deleted on purpose. | |||||
* @return This proxy object. | |||||
*/ | |||||
input_iterator_pointer &operator=(const input_iterator_pointer &) = delete; | |||||
/** | |||||
* @brief Default move assignment operator. | |||||
* @return This proxy object. | |||||
*/ | |||||
input_iterator_pointer &operator=(input_iterator_pointer &&) = default; | |||||
/** | |||||
* @brief Access operator for accessing wrapped values. | |||||
* @return A pointer to the wrapped value. | |||||
*/ | |||||
[[nodiscard]] pointer operator->() ENTT_NOEXCEPT { | |||||
return std::addressof(value); | |||||
} | |||||
private: | |||||
Type value; | |||||
}; | |||||
/** | |||||
* @brief Utility class to create an iterable object from a pair of iterators. | |||||
* @tparam It Type of iterator. | |||||
* @tparam Sentinel Type of sentinel. | |||||
*/ | |||||
template<typename It, typename Sentinel = It> | |||||
struct iterable_adaptor final { | |||||
/*! @brief Value type. */ | |||||
using value_type = typename std::iterator_traits<It>::value_type; | |||||
/*! @brief Iterator type. */ | |||||
using iterator = It; | |||||
/*! @brief Sentinel type. */ | |||||
using sentinel = Sentinel; | |||||
/*! @brief Default constructor. */ | |||||
iterable_adaptor() = default; | |||||
/** | |||||
* @brief Creates an iterable object from a pair of iterators. | |||||
* @param from Begin iterator. | |||||
* @param to End iterator. | |||||
*/ | |||||
iterable_adaptor(iterator from, sentinel to) | |||||
: first{from}, | |||||
last{to} {} | |||||
/** | |||||
* @brief Returns an iterator to the beginning. | |||||
* @return An iterator to the first element of the range. | |||||
*/ | |||||
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT { | |||||
return first; | |||||
} | |||||
/** | |||||
* @brief Returns an iterator to the end. | |||||
* @return An iterator to the element following the last element of the | |||||
* range. | |||||
*/ | |||||
[[nodiscard]] sentinel end() const ENTT_NOEXCEPT { | |||||
return last; | |||||
} | |||||
/*! @copydoc begin */ | |||||
[[nodiscard]] iterator cbegin() const ENTT_NOEXCEPT { | |||||
return begin(); | |||||
} | |||||
/*! @copydoc end */ | |||||
[[nodiscard]] sentinel cend() const ENTT_NOEXCEPT { | |||||
return end(); | |||||
} | |||||
private: | |||||
It first; | |||||
Sentinel last; | |||||
}; | |||||
} // namespace entt | |||||
#endif |
@ -0,0 +1,289 @@ | |||||
#ifndef ENTT_CORE_MEMORY_HPP | |||||
#define ENTT_CORE_MEMORY_HPP | |||||
#include <cstddef> | |||||
#include <limits> | |||||
#include <memory> | |||||
#include <tuple> | |||||
#include <type_traits> | |||||
#include <utility> | |||||
#include "../config/config.h" | |||||
namespace entt { | |||||
/** | |||||
* @brief Unwraps fancy pointers, does nothing otherwise (waiting for C++20). | |||||
* @tparam Type Pointer type. | |||||
* @param ptr Fancy or raw pointer. | |||||
* @return A raw pointer that represents the address of the original pointer. | |||||
*/ | |||||
template<typename Type> | |||||
[[nodiscard]] constexpr auto to_address(Type &&ptr) ENTT_NOEXCEPT { | |||||
if constexpr(std::is_pointer_v<std::remove_cv_t<std::remove_reference_t<Type>>>) { | |||||
return ptr; | |||||
} else { | |||||
return to_address(std::forward<Type>(ptr).operator->()); | |||||
} | |||||
} | |||||
/** | |||||
* @brief Utility function to design allocation-aware containers. | |||||
* @tparam Allocator Type of allocator. | |||||
* @param lhs A valid allocator. | |||||
* @param rhs Another valid allocator. | |||||
*/ | |||||
template<typename Allocator> | |||||
constexpr void propagate_on_container_copy_assignment([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) ENTT_NOEXCEPT { | |||||
if constexpr(std::allocator_traits<Allocator>::propagate_on_container_copy_assignment::value) { | |||||
lhs = rhs; | |||||
} | |||||
} | |||||
/** | |||||
* @brief Utility function to design allocation-aware containers. | |||||
* @tparam Allocator Type of allocator. | |||||
* @param lhs A valid allocator. | |||||
* @param rhs Another valid allocator. | |||||
*/ | |||||
template<typename Allocator> | |||||
constexpr void propagate_on_container_move_assignment([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) ENTT_NOEXCEPT { | |||||
if constexpr(std::allocator_traits<Allocator>::propagate_on_container_move_assignment::value) { | |||||
lhs = std::move(rhs); | |||||
} | |||||
} | |||||
/** | |||||
* @brief Utility function to design allocation-aware containers. | |||||
* @tparam Allocator Type of allocator. | |||||
* @param lhs A valid allocator. | |||||
* @param rhs Another valid allocator. | |||||
*/ | |||||
template<typename Allocator> | |||||
constexpr void propagate_on_container_swap([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) ENTT_NOEXCEPT { | |||||
ENTT_ASSERT(std::allocator_traits<Allocator>::propagate_on_container_swap::value || lhs == rhs, "Cannot swap the containers"); | |||||
if constexpr(std::allocator_traits<Allocator>::propagate_on_container_swap::value) { | |||||
using std::swap; | |||||
swap(lhs, rhs); | |||||
} | |||||
} | |||||
/** | |||||
* @brief Checks whether a value is a power of two or not. | |||||
* @param value A value that may or may not be a power of two. | |||||
* @return True if the value is a power of two, false otherwise. | |||||
*/ | |||||
[[nodiscard]] inline constexpr bool is_power_of_two(const std::size_t value) ENTT_NOEXCEPT { | |||||
return value && ((value & (value - 1)) == 0); | |||||
} | |||||
/** | |||||
* @brief Computes the smallest power of two greater than or equal to a value. | |||||
* @param value The value to use. | |||||
* @return The smallest power of two greater than or equal to the given value. | |||||
*/ | |||||
[[nodiscard]] inline constexpr std::size_t next_power_of_two(const std::size_t value) ENTT_NOEXCEPT { | |||||
ENTT_ASSERT(value < (std::size_t{1u} << (std::numeric_limits<std::size_t>::digits - 1)), "Numeric limits exceeded"); | |||||
std::size_t curr = value - (value != 0u); | |||||
for(int next = 1; next < std::numeric_limits<std::size_t>::digits; next = next * 2) { | |||||
curr |= curr >> next; | |||||
} | |||||
return ++curr; | |||||
} | |||||
/** | |||||
* @brief Fast module utility function (powers of two only). | |||||
* @param value A value for which to calculate the modulus. | |||||
* @param mod _Modulus_, it must be a power of two. | |||||
* @return The common remainder. | |||||
*/ | |||||
[[nodiscard]] inline constexpr std::size_t fast_mod(const std::size_t value, const std::size_t mod) ENTT_NOEXCEPT { | |||||
ENTT_ASSERT(is_power_of_two(mod), "Value must be a power of two"); | |||||
return value & (mod - 1u); | |||||
} | |||||
/** | |||||
* @brief Deleter for allocator-aware unique pointers (waiting for C++20). | |||||
* @tparam Args Types of arguments to use to construct the object. | |||||
*/ | |||||
template<typename Allocator> | |||||
struct allocation_deleter: private Allocator { | |||||
/*! @brief Allocator type. */ | |||||
using allocator_type = Allocator; | |||||
/*! @brief Pointer type. */ | |||||
using pointer = typename std::allocator_traits<Allocator>::pointer; | |||||
/** | |||||
* @brief Inherited constructors. | |||||
* @param alloc The allocator to use. | |||||
*/ | |||||
allocation_deleter(const allocator_type &alloc) | |||||
: Allocator{alloc} {} | |||||
/** | |||||
* @brief Destroys the pointed object and deallocates its memory. | |||||
* @param ptr A valid pointer to an object of the given type. | |||||
*/ | |||||
void operator()(pointer ptr) { | |||||
using alloc_traits = typename std::allocator_traits<Allocator>; | |||||
alloc_traits::destroy(*this, to_address(ptr)); | |||||
alloc_traits::deallocate(*this, ptr, 1u); | |||||
} | |||||
}; | |||||
/** | |||||
* @brief Allows `std::unique_ptr` to use allocators (waiting for C++20). | |||||
* @tparam Type Type of object to allocate for and to construct. | |||||
* @tparam Allocator Type of allocator used to manage memory and elements. | |||||
* @tparam Args Types of arguments to use to construct the object. | |||||
* @param allocator The allocator to use. | |||||
* @param args Parameters to use to construct the object. | |||||
* @return A properly initialized unique pointer with a custom deleter. | |||||
*/ | |||||
template<typename Type, typename Allocator, typename... Args> | |||||
auto allocate_unique(Allocator &allocator, Args &&...args) { | |||||
static_assert(!std::is_array_v<Type>, "Array types are not supported"); | |||||
using alloc_traits = typename std::allocator_traits<Allocator>::template rebind_traits<Type>; | |||||
using allocator_type = typename alloc_traits::allocator_type; | |||||
allocator_type alloc{allocator}; | |||||
auto ptr = alloc_traits::allocate(alloc, 1u); | |||||
ENTT_TRY { | |||||
alloc_traits::construct(alloc, to_address(ptr), std::forward<Args>(args)...); | |||||
} | |||||
ENTT_CATCH { | |||||
alloc_traits::deallocate(alloc, ptr, 1u); | |||||
ENTT_THROW; | |||||
} | |||||
return std::unique_ptr<Type, allocation_deleter<allocator_type>>{ptr, alloc}; | |||||
} | |||||
/** | |||||
* @cond TURN_OFF_DOXYGEN | |||||
* Internal details not to be documented. | |||||
*/ | |||||
namespace internal { | |||||
template<typename Type> | |||||
struct uses_allocator_construction { | |||||
template<typename Allocator, typename... Params> | |||||
static constexpr auto args([[maybe_unused]] const Allocator &allocator, Params &&...params) ENTT_NOEXCEPT { | |||||
if constexpr(!std::uses_allocator_v<Type, Allocator> && std::is_constructible_v<Type, Params...>) { | |||||
return std::forward_as_tuple(std::forward<Params>(params)...); | |||||
} else { | |||||
static_assert(std::uses_allocator_v<Type, Allocator>, "Ill-formed request"); | |||||
if constexpr(std::is_constructible_v<Type, std::allocator_arg_t, const Allocator &, Params...>) { | |||||
return std::tuple<std::allocator_arg_t, const Allocator &, Params &&...>(std::allocator_arg, allocator, std::forward<Params>(params)...); | |||||
} else { | |||||
static_assert(std::is_constructible_v<Type, Params..., const Allocator &>, "Ill-formed request"); | |||||
return std::forward_as_tuple(std::forward<Params>(params)..., allocator); | |||||
} | |||||
} | |||||
} | |||||
}; | |||||
template<typename Type, typename Other> | |||||
struct uses_allocator_construction<std::pair<Type, Other>> { | |||||
using type = std::pair<Type, Other>; | |||||
template<typename Allocator, typename First, typename Second> | |||||
static constexpr auto args(const Allocator &allocator, std::piecewise_construct_t, First &&first, Second &&second) ENTT_NOEXCEPT { | |||||
return std::make_tuple( | |||||
std::piecewise_construct, | |||||
std::apply([&allocator](auto &&...curr) { return uses_allocator_construction<Type>::args(allocator, std::forward<decltype(curr)>(curr)...); }, std::forward<First>(first)), | |||||
std::apply([&allocator](auto &&...curr) { return uses_allocator_construction<Other>::args(allocator, std::forward<decltype(curr)>(curr)...); }, std::forward<Second>(second))); | |||||
} | |||||
template<typename Allocator> | |||||
static constexpr auto args(const Allocator &allocator) ENTT_NOEXCEPT { | |||||
return uses_allocator_construction<type>::args(allocator, std::piecewise_construct, std::tuple<>{}, std::tuple<>{}); | |||||
} | |||||
template<typename Allocator, typename First, typename Second> | |||||
static constexpr auto args(const Allocator &allocator, First &&first, Second &&second) ENTT_NOEXCEPT { | |||||
return uses_allocator_construction<type>::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::forward<First>(first)), std::forward_as_tuple(std::forward<Second>(second))); | |||||
} | |||||
template<typename Allocator, typename First, typename Second> | |||||
static constexpr auto args(const Allocator &allocator, const std::pair<First, Second> &value) ENTT_NOEXCEPT { | |||||
return uses_allocator_construction<type>::args(allocator, std::piecewise_construct, std::forward_as_tuple(value.first), std::forward_as_tuple(value.second)); | |||||
} | |||||
template<typename Allocator, typename First, typename Second> | |||||
static constexpr auto args(const Allocator &allocator, std::pair<First, Second> &&value) ENTT_NOEXCEPT { | |||||
return uses_allocator_construction<type>::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::move(value.first)), std::forward_as_tuple(std::move(value.second))); | |||||
} | |||||
}; | |||||
} // namespace internal | |||||
/** | |||||
* Internal details not to be documented. | |||||
* @endcond | |||||
*/ | |||||
/** | |||||
* @brief Uses-allocator construction utility (waiting for C++20). | |||||
* | |||||
* Primarily intended for internal use. Prepares the argument list needed to | |||||
* create an object of a given type by means of uses-allocator construction. | |||||
* | |||||
* @tparam Type Type to return arguments for. | |||||
* @tparam Allocator Type of allocator used to manage memory and elements. | |||||
* @tparam Args Types of arguments to use to construct the object. | |||||
* @param allocator The allocator to use. | |||||
* @param args Parameters to use to construct the object. | |||||
* @return The arguments needed to create an object of the given type. | |||||
*/ | |||||
template<typename Type, typename Allocator, typename... Args> | |||||
constexpr auto uses_allocator_construction_args(const Allocator &allocator, Args &&...args) ENTT_NOEXCEPT { | |||||
return internal::uses_allocator_construction<Type>::args(allocator, std::forward<Args>(args)...); | |||||
} | |||||
/** | |||||
* @brief Uses-allocator construction utility (waiting for C++20). | |||||
* | |||||
* Primarily intended for internal use. Creates an object of a given type by | |||||
* means of uses-allocator construction. | |||||
* | |||||
* @tparam Type Type of object to create. | |||||
* @tparam Allocator Type of allocator used to manage memory and elements. | |||||
* @tparam Args Types of arguments to use to construct the object. | |||||
* @param allocator The allocator to use. | |||||
* @param args Parameters to use to construct the object. | |||||
* @return A newly created object of the given type. | |||||
*/ | |||||
template<typename Type, typename Allocator, typename... Args> | |||||
constexpr Type make_obj_using_allocator(const Allocator &allocator, Args &&...args) { | |||||
return std::make_from_tuple<Type>(internal::uses_allocator_construction<Type>::args(allocator, std::forward<Args>(args)...)); | |||||
} | |||||
/** | |||||
* @brief Uses-allocator construction utility (waiting for C++20). | |||||
* | |||||
* Primarily intended for internal use. Creates an object of a given type by | |||||
* means of uses-allocator construction at an uninitialized memory location. | |||||
* | |||||
* @tparam Type Type of object to create. | |||||
* @tparam Allocator Type of allocator used to manage memory and elements. | |||||
* @tparam Args Types of arguments to use to construct the object. | |||||
* @param value Memory location in which to place the object. | |||||
* @param allocator The allocator to use. | |||||
* @param args Parameters to use to construct the object. | |||||
* @return A pointer to the newly created object of the given type. | |||||
*/ | |||||
template<typename Type, typename Allocator, typename... Args> | |||||
constexpr Type *uninitialized_construct_using_allocator(Type *value, const Allocator &allocator, Args &&...args) { | |||||
return std::apply([&](auto &&...curr) { return new(value) Type(std::forward<decltype(curr)>(curr)...); }, internal::uses_allocator_construction<Type>::args(allocator, std::forward<Args>(args)...)); | |||||
} | |||||
} // namespace entt | |||||
#endif |
@ -0,0 +1,29 @@ | |||||
#ifndef ENTT_CORE_TUPLE_HPP | |||||
#define ENTT_CORE_TUPLE_HPP | |||||
#include <tuple> | |||||
#include <type_traits> | |||||
#include <utility> | |||||
#include "../config/config.h" | |||||
namespace entt { | |||||
/** | |||||
* @brief Utility function to unwrap tuples of a single element. | |||||
* @tparam Type Tuple type of any sizes. | |||||
* @param value A tuple object of the given type. | |||||
* @return The tuple itself if it contains more than one element, the first | |||||
* element otherwise. | |||||
*/ | |||||
template<typename Type> | |||||
constexpr decltype(auto) unwrap_tuple(Type &&value) ENTT_NOEXCEPT { | |||||
if constexpr(std::tuple_size_v<std::remove_reference_t<Type>> == 1u) { | |||||
return std::get<0>(std::forward<Type>(value)); | |||||
} else { | |||||
return std::forward<Type>(value); | |||||
} | |||||
} | |||||
} // namespace entt | |||||
#endif |
@ -0,0 +1,274 @@ | |||||
#ifndef ENTT_CORE_TYPE_INFO_HPP | |||||
#define ENTT_CORE_TYPE_INFO_HPP | |||||
#include <string_view> | |||||
#include <type_traits> | |||||
#include <utility> | |||||
#include "../config/config.h" | |||||
#include "../core/attribute.h" | |||||
#include "fwd.hpp" | |||||
#include "hashed_string.hpp" | |||||
namespace entt { | |||||
/** | |||||
* @cond TURN_OFF_DOXYGEN | |||||
* Internal details not to be documented. | |||||
*/ | |||||
namespace internal { | |||||
struct ENTT_API type_index final { | |||||
[[nodiscard]] static id_type next() ENTT_NOEXCEPT { | |||||
static ENTT_MAYBE_ATOMIC(id_type) value{}; | |||||
return value++; | |||||
} | |||||
}; | |||||
template<typename Type> | |||||
[[nodiscard]] constexpr auto stripped_type_name() ENTT_NOEXCEPT { | |||||
#if defined ENTT_PRETTY_FUNCTION | |||||
std::string_view pretty_function{ENTT_PRETTY_FUNCTION}; | |||||
auto first = pretty_function.find_first_not_of(' ', pretty_function.find_first_of(ENTT_PRETTY_FUNCTION_PREFIX) + 1); | |||||
auto value = pretty_function.substr(first, pretty_function.find_last_of(ENTT_PRETTY_FUNCTION_SUFFIX) - first); | |||||
return value; | |||||
#else | |||||
return std::string_view{""}; | |||||
#endif | |||||
} | |||||
template<typename Type, auto = stripped_type_name<Type>().find_first_of('.')> | |||||
[[nodiscard]] static constexpr std::string_view type_name(int) ENTT_NOEXCEPT { | |||||
constexpr auto value = stripped_type_name<Type>(); | |||||
return value; | |||||
} | |||||
template<typename Type> | |||||
[[nodiscard]] static std::string_view type_name(char) ENTT_NOEXCEPT { | |||||
static const auto value = stripped_type_name<Type>(); | |||||
return value; | |||||
} | |||||
template<typename Type, auto = stripped_type_name<Type>().find_first_of('.')> | |||||
[[nodiscard]] static constexpr id_type type_hash(int) ENTT_NOEXCEPT { | |||||
constexpr auto stripped = stripped_type_name<Type>(); | |||||
constexpr auto value = hashed_string::value(stripped.data(), stripped.size()); | |||||
return value; | |||||
} | |||||
template<typename Type> | |||||
[[nodiscard]] static id_type type_hash(char) ENTT_NOEXCEPT { | |||||
static const auto value = [](const auto stripped) { | |||||
return hashed_string::value(stripped.data(), stripped.size()); | |||||
}(stripped_type_name<Type>()); | |||||
return value; | |||||
} | |||||
} // namespace internal | |||||
/** | |||||
* Internal details not to be documented. | |||||
* @endcond | |||||
*/ | |||||
/** | |||||
* @brief Type sequential identifier. | |||||
* @tparam Type Type for which to generate a sequential identifier. | |||||
*/ | |||||
template<typename Type, typename = void> | |||||
struct ENTT_API type_index final { | |||||
/** | |||||
* @brief Returns the sequential identifier of a given type. | |||||
* @return The sequential identifier of a given type. | |||||
*/ | |||||
[[nodiscard]] static id_type value() ENTT_NOEXCEPT { | |||||
static const id_type value = internal::type_index::next(); | |||||
return value; | |||||
} | |||||
/*! @copydoc value */ | |||||
[[nodiscard]] constexpr operator id_type() const ENTT_NOEXCEPT { | |||||
return value(); | |||||
} | |||||
}; | |||||
/** | |||||
* @brief Type hash. | |||||
* @tparam Type Type for which to generate a hash value. | |||||
*/ | |||||
template<typename Type, typename = void> | |||||
struct type_hash final { | |||||
/** | |||||
* @brief Returns the numeric representation of a given type. | |||||
* @return The numeric representation of the given type. | |||||
*/ | |||||
#if defined ENTT_PRETTY_FUNCTION | |||||
[[nodiscard]] static constexpr id_type value() ENTT_NOEXCEPT { | |||||
return internal::type_hash<Type>(0); | |||||
#else | |||||
[[nodiscard]] static constexpr id_type value() ENTT_NOEXCEPT { | |||||
return type_index<Type>::value(); | |||||
#endif | |||||
} | |||||
/*! @copydoc value */ | |||||
[[nodiscard]] constexpr operator id_type() const ENTT_NOEXCEPT { | |||||
return value(); | |||||
} | |||||
}; | |||||
/** | |||||
* @brief Type name. | |||||
* @tparam Type Type for which to generate a name. | |||||
*/ | |||||
template<typename Type, typename = void> | |||||
struct type_name final { | |||||
/** | |||||
* @brief Returns the name of a given type. | |||||
* @return The name of the given type. | |||||
*/ | |||||
[[nodiscard]] static constexpr std::string_view value() ENTT_NOEXCEPT { | |||||
return internal::type_name<Type>(0); | |||||
} | |||||
/*! @copydoc value */ | |||||
[[nodiscard]] constexpr operator std::string_view() const ENTT_NOEXCEPT { | |||||
return value(); | |||||
} | |||||
}; | |||||
/*! @brief Implementation specific information about a type. */ | |||||
struct type_info final { | |||||
/** | |||||
* @brief Constructs a type info object for a given type. | |||||
* @tparam Type Type for which to construct a type info object. | |||||
*/ | |||||
template<typename Type> | |||||
constexpr type_info(std::in_place_type_t<Type>) ENTT_NOEXCEPT | |||||
: seq{type_index<std::remove_cv_t<std::remove_reference_t<Type>>>::value()}, | |||||
identifier{type_hash<std::remove_cv_t<std::remove_reference_t<Type>>>::value()}, | |||||
alias{type_name<std::remove_cv_t<std::remove_reference_t<Type>>>::value()} {} | |||||
/** | |||||
* @brief Type index. | |||||
* @return Type index. | |||||
*/ | |||||
[[nodiscard]] constexpr id_type index() const ENTT_NOEXCEPT { | |||||
return seq; | |||||
} | |||||
/** | |||||
* @brief Type hash. | |||||
* @return Type hash. | |||||
*/ | |||||
[[nodiscard]] constexpr id_type hash() const ENTT_NOEXCEPT { | |||||
return identifier; | |||||
} | |||||
/** | |||||
* @brief Type name. | |||||
* @return Type name. | |||||
*/ | |||||
[[nodiscard]] constexpr std::string_view name() const ENTT_NOEXCEPT { | |||||
return alias; | |||||
} | |||||
private: | |||||
id_type seq; | |||||
id_type identifier; | |||||
std::string_view alias; | |||||
}; | |||||
/** | |||||
* @brief Compares the contents of two type info objects. | |||||
* @param lhs A type info object. | |||||
* @param rhs A type info object. | |||||
* @return True if the two type info objects are identical, false otherwise. | |||||
*/ | |||||
[[nodiscard]] inline constexpr bool operator==(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { | |||||
return lhs.hash() == rhs.hash(); | |||||
} | |||||
/** | |||||
* @brief Compares the contents of two type info objects. | |||||
* @param lhs A type info object. | |||||
* @param rhs A type info object. | |||||
* @return True if the two type info objects differ, false otherwise. | |||||
*/ | |||||
[[nodiscard]] inline constexpr bool operator!=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { | |||||
return !(lhs == rhs); | |||||
} | |||||
/** | |||||
* @brief Compares two type info objects. | |||||
* @param lhs A valid type info object. | |||||
* @param rhs A valid type info object. | |||||
* @return True if the first element is less than the second, false otherwise. | |||||
*/ | |||||
[[nodiscard]] constexpr bool operator<(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { | |||||
return lhs.index() < rhs.index(); | |||||
} | |||||
/** | |||||
* @brief Compares two type info objects. | |||||
* @param lhs A valid type info object. | |||||
* @param rhs A valid type info object. | |||||
* @return True if the first element is less than or equal to the second, false | |||||
* otherwise. | |||||
*/ | |||||
[[nodiscard]] constexpr bool operator<=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { | |||||
return !(rhs < lhs); | |||||
} | |||||
/** | |||||
* @brief Compares two type info objects. | |||||
* @param lhs A valid type info object. | |||||
* @param rhs A valid type info object. | |||||
* @return True if the first element is greater than the second, false | |||||
* otherwise. | |||||
*/ | |||||
[[nodiscard]] constexpr bool operator>(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { | |||||
return rhs < lhs; | |||||
} | |||||
/** | |||||
* @brief Compares two type info objects. | |||||
* @param lhs A valid type info object. | |||||
* @param rhs A valid type info object. | |||||
* @return True if the first element is greater than or equal to the second, | |||||
* false otherwise. | |||||
*/ | |||||
[[nodiscard]] constexpr bool operator>=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { | |||||
return !(lhs < rhs); | |||||
} | |||||
/** | |||||
* @brief Returns the type info object associated to a given type. | |||||
* | |||||
* The returned element refers to an object with static storage duration.<br/> | |||||
* The type doesn't need to be a complete type. If the type is a reference, the | |||||
* result refers to the referenced type. In all cases, top-level cv-qualifiers | |||||
* are ignored. | |||||
* | |||||
* @tparam Type Type for which to generate a type info object. | |||||
* @return A reference to a properly initialized type info object. | |||||
*/ | |||||
template<typename Type> | |||||
[[nodiscard]] const type_info &type_id() ENTT_NOEXCEPT { | |||||
if constexpr(std::is_same_v<Type, std::remove_cv_t<std::remove_reference_t<Type>>>) { | |||||
static type_info instance{std::in_place_type<Type>}; | |||||
return instance; | |||||
} else { | |||||
return type_id<std::remove_cv_t<std::remove_reference_t<Type>>>(); | |||||
} | |||||
} | |||||
/*! @copydoc type_id */ | |||||
template<typename Type> | |||||
[[nodiscard]] const type_info &type_id(Type &&) ENTT_NOEXCEPT { | |||||
return type_id<std::remove_cv_t<std::remove_reference_t<Type>>>(); | |||||
} | |||||
} // namespace entt | |||||
#endif |
@ -1,32 +1,101 @@ | |||||
#ifndef ENTT_CORE_UTILITY_HPP | #ifndef ENTT_CORE_UTILITY_HPP | ||||
#define ENTT_CORE_UTILITY_HPP | #define ENTT_CORE_UTILITY_HPP | ||||
#include <utility> | |||||
#include "../config/config.h" | |||||
namespace entt { | namespace entt { | ||||
/*! @brief Identity function object (waiting for C++20). */ | |||||
struct identity { | |||||
/*! @brief Indicates that this is a transparent function object. */ | |||||
using is_transparent = void; | |||||
/** | |||||
* @brief Returns its argument unchanged. | |||||
* @tparam Type Type of the argument. | |||||
* @param value The actual argument. | |||||
* @return The submitted value as-is. | |||||
*/ | |||||
template<class Type> | |||||
[[nodiscard]] constexpr Type &&operator()(Type &&value) const ENTT_NOEXCEPT { | |||||
return std::forward<Type>(value); | |||||
} | |||||
}; | |||||
/** | /** | ||||
* @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. | |||||
* @brief Constant utility to disambiguate overloaded members of a class. | |||||
* @tparam Type Type of the desired overload. | |||||
* @tparam Class Type of class to which the member belongs. | |||||
* @param member A valid pointer to a member. | |||||
* @return Pointer to the member. | |||||
*/ | */ | ||||
template<typename Type, typename Class> | template<typename Type, typename Class> | ||||
constexpr auto overload(Type Class:: *member) { return member; } | |||||
[[nodiscard]] constexpr auto overload(Type Class::*member) ENTT_NOEXCEPT { | |||||
return member; | |||||
} | |||||
/** | /** | ||||
* @brief Constant utility to disambiguate overloaded functions. | * @brief Constant utility to disambiguate overloaded functions. | ||||
* @tparam Type Function type of the desired overload. | |||||
* @tparam Func Function type of the desired overload. | |||||
* @param func A valid pointer to a function. | * @param func A valid pointer to a function. | ||||
* @return Pointer to the function. | * @return Pointer to the function. | ||||
*/ | */ | ||||
template<typename Type> | |||||
constexpr auto overload(Type *func) { return func; } | |||||
template<typename Func> | |||||
[[nodiscard]] constexpr auto overload(Func *func) ENTT_NOEXCEPT { | |||||
return func; | |||||
} | |||||
/** | |||||
* @brief Helper type for visitors. | |||||
* @tparam Func Types of function objects. | |||||
*/ | |||||
template<class... Func> | |||||
struct overloaded: Func... { | |||||
using Func::operator()...; | |||||
}; | |||||
} | |||||
/** | |||||
* @brief Deduction guide. | |||||
* @tparam Func Types of function objects. | |||||
*/ | |||||
template<class... Func> | |||||
overloaded(Func...) -> overloaded<Func...>; | |||||
/** | |||||
* @brief Basic implementation of a y-combinator. | |||||
* @tparam Func Type of a potentially recursive function. | |||||
*/ | |||||
template<class Func> | |||||
struct y_combinator { | |||||
/** | |||||
* @brief Constructs a y-combinator from a given function. | |||||
* @param recursive A potentially recursive function. | |||||
*/ | |||||
y_combinator(Func recursive) | |||||
: func{std::move(recursive)} {} | |||||
/** | |||||
* @brief Invokes a y-combinator and therefore its underlying function. | |||||
* @tparam Args Types of arguments to use to invoke the underlying function. | |||||
* @param args Parameters to use to invoke the underlying function. | |||||
* @return Return value of the underlying function, if any. | |||||
*/ | |||||
template<class... Args> | |||||
decltype(auto) operator()(Args &&...args) const { | |||||
return func(*this, std::forward<Args>(args)...); | |||||
} | |||||
/*! @copydoc operator()() */ | |||||
template<class... Args> | |||||
decltype(auto) operator()(Args &&...args) { | |||||
return func(*this, std::forward<Args>(args)...); | |||||
} | |||||
private: | |||||
Func func; | |||||
}; | |||||
} // namespace entt | |||||
#endif // ENTT_CORE_UTILITY_HPP | |||||
#endif |
@ -1,180 +0,0 @@ | |||||
#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,61 @@ | |||||
#ifndef ENTT_ENTITY_COMPONENT_HPP | |||||
#define ENTT_ENTITY_COMPONENT_HPP | |||||
#include <cstddef> | |||||
#include <type_traits> | |||||
#include "../config/config.h" | |||||
namespace entt { | |||||
/** | |||||
* @cond TURN_OFF_DOXYGEN | |||||
* Internal details not to be documented. | |||||
*/ | |||||
namespace internal { | |||||
template<typename, typename = void> | |||||
struct in_place_delete: std::false_type {}; | |||||
template<typename Type> | |||||
struct in_place_delete<Type, std::enable_if_t<Type::in_place_delete>> | |||||
: std::true_type {}; | |||||
template<typename Type, typename = void> | |||||
struct page_size: std::integral_constant<std::size_t, (ENTT_IGNORE_IF_EMPTY && std::is_empty_v<Type>) ? 0u : ENTT_PACKED_PAGE> {}; | |||||
template<typename Type> | |||||
struct page_size<Type, std::enable_if_t<std::is_convertible_v<decltype(Type::page_size), std::size_t>>> | |||||
: std::integral_constant<std::size_t, Type::page_size> {}; | |||||
} // namespace internal | |||||
/** | |||||
* Internal details not to be documented. | |||||
* @endcond | |||||
*/ | |||||
/** | |||||
* @brief Common way to access various properties of components. | |||||
* @tparam Type Type of component. | |||||
*/ | |||||
template<typename Type, typename = void> | |||||
struct component_traits { | |||||
static_assert(std::is_same_v<std::decay_t<Type>, Type>, "Unsupported type"); | |||||
/*! @brief Pointer stability, default is `false`. */ | |||||
static constexpr bool in_place_delete = internal::in_place_delete<Type>::value; | |||||
/*! @brief Page size, default is `ENTT_PACKED_PAGE` for non-empty types. */ | |||||
static constexpr std::size_t page_size = internal::page_size<Type>::value; | |||||
}; | |||||
/** | |||||
* @brief Helper variable template. | |||||
* @tparam Type Type of component. | |||||
*/ | |||||
template<class Type> | |||||
inline constexpr bool ignore_as_empty_v = (component_traits<Type>::page_size == 0u); | |||||
} // namespace entt | |||||
#endif |
@ -1,169 +1,339 @@ | |||||
#ifndef ENTT_ENTITY_ENTITY_HPP | #ifndef ENTT_ENTITY_ENTITY_HPP | ||||
#define ENTT_ENTITY_ENTITY_HPP | #define ENTT_ENTITY_ENTITY_HPP | ||||
#include <cstddef> | |||||
#include <cstdint> | |||||
#include <type_traits> | |||||
#include "../config/config.h" | #include "../config/config.h" | ||||
#include "fwd.hpp" | |||||
namespace entt { | 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. | |||||
* @cond TURN_OFF_DOXYGEN | |||||
* Internal details not to be documented. | |||||
*/ | */ | ||||
template<typename> | |||||
struct entt_traits; | |||||
namespace internal { | |||||
/** | |||||
* @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; | |||||
}; | |||||
template<typename, typename = void> | |||||
struct entt_traits; | |||||
template<typename Type> | |||||
struct entt_traits<Type, std::enable_if_t<std::is_enum_v<Type>>> | |||||
: entt_traits<std::underlying_type_t<Type>> {}; | |||||
template<typename Type> | |||||
struct entt_traits<Type, std::enable_if_t<std::is_class_v<Type>>> | |||||
: entt_traits<typename Type::entity_type> {}; | |||||
/** | |||||
* @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<> | template<> | ||||
struct entt_traits<std::uint32_t> { | struct entt_traits<std::uint32_t> { | ||||
/*! @brief Underlying entity type. */ | |||||
using entity_type = std::uint32_t; | using entity_type = std::uint32_t; | ||||
/*! @brief Underlying version type. */ | |||||
using version_type = std::uint16_t; | using version_type = std::uint16_t; | ||||
/*! @brief Difference type. */ | |||||
using difference_type = std::int64_t; | |||||
/*! @brief Mask to use to get the entity number out of an identifier. */ | |||||
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; | |||||
}; | |||||
static constexpr entity_type entity_mask = 0xFFFFF; | |||||
static constexpr entity_type version_mask = 0xFFF; | |||||
static constexpr std::size_t entity_shift = 20u; | |||||
}; | |||||
/** | |||||
* @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<> | template<> | ||||
struct entt_traits<std::uint64_t> { | struct entt_traits<std::uint64_t> { | ||||
/*! @brief Underlying entity type. */ | |||||
using entity_type = std::uint64_t; | using entity_type = std::uint64_t; | ||||
/*! @brief Underlying version type. */ | |||||
using version_type = std::uint32_t; | using version_type = std::uint32_t; | ||||
/*! @brief Difference type. */ | |||||
using difference_type = std::int64_t; | |||||
/*! @brief Mask to use to get the entity number out of an identifier. */ | |||||
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; | |||||
static constexpr entity_type entity_mask = 0xFFFFFFFF; | |||||
static constexpr entity_type version_mask = 0xFFFFFFFF; | |||||
static constexpr std::size_t entity_shift = 32u; | |||||
}; | }; | ||||
} // namespace internal | |||||
/** | /** | ||||
* @cond TURN_OFF_DOXYGEN | |||||
* Internal details not to be documented. | * Internal details not to be documented. | ||||
* @endcond | |||||
*/ | |||||
/** | |||||
* @brief Entity traits. | |||||
* @tparam Type Type of identifier. | |||||
*/ | */ | ||||
template<typename Type> | |||||
class entt_traits: internal::entt_traits<Type> { | |||||
using base_type = internal::entt_traits<Type>; | |||||
public: | |||||
/*! @brief Value type. */ | |||||
using value_type = Type; | |||||
/*! @brief Underlying entity type. */ | |||||
using entity_type = typename base_type::entity_type; | |||||
/*! @brief Underlying version type. */ | |||||
using version_type = typename base_type::version_type; | |||||
/*! @brief Reserved identifier. */ | |||||
static constexpr entity_type reserved = base_type::entity_mask | (base_type::version_mask << base_type::entity_shift); | |||||
/*! @brief Page size, default is `ENTT_SPARSE_PAGE`. */ | |||||
static constexpr auto page_size = ENTT_SPARSE_PAGE; | |||||
/** | |||||
* @brief Converts an entity to its underlying type. | |||||
* @param value The value to convert. | |||||
* @return The integral representation of the given value. | |||||
*/ | |||||
[[nodiscard]] static constexpr entity_type to_integral(const value_type value) ENTT_NOEXCEPT { | |||||
return static_cast<entity_type>(value); | |||||
} | |||||
namespace internal { | |||||
/** | |||||
* @brief Returns the entity part once converted to the underlying type. | |||||
* @param value The value to convert. | |||||
* @return The integral representation of the entity part. | |||||
*/ | |||||
[[nodiscard]] static constexpr entity_type to_entity(const value_type value) ENTT_NOEXCEPT { | |||||
return (to_integral(value) & base_type::entity_mask); | |||||
} | |||||
/** | |||||
* @brief Returns the version part once converted to the underlying type. | |||||
* @param value The value to convert. | |||||
* @return The integral representation of the version part. | |||||
*/ | |||||
[[nodiscard]] static constexpr version_type to_version(const value_type value) ENTT_NOEXCEPT { | |||||
return (to_integral(value) >> base_type::entity_shift); | |||||
} | |||||
/** | |||||
* @brief Constructs an identifier from its parts. | |||||
* | |||||
* If the version part is not provided, a tombstone is returned.<br/> | |||||
* If the entity part is not provided, a null identifier is returned. | |||||
* | |||||
* @param entity The entity part of the identifier. | |||||
* @param version The version part of the identifier. | |||||
* @return A properly constructed identifier. | |||||
*/ | |||||
[[nodiscard]] static constexpr value_type construct(const entity_type entity, const version_type version) ENTT_NOEXCEPT { | |||||
return value_type{(entity & base_type::entity_mask) | (static_cast<entity_type>(version) << base_type::entity_shift)}; | |||||
} | |||||
struct null { | |||||
/** | |||||
* @brief Combines two identifiers in a single one. | |||||
* | |||||
* The returned identifier is a copy of the first element except for its | |||||
* version, which is taken from the second element. | |||||
* | |||||
* @param lhs The identifier from which to take the entity part. | |||||
* @param rhs The identifier from which to take the version part. | |||||
* @return A properly constructed identifier. | |||||
*/ | |||||
[[nodiscard]] static constexpr value_type combine(const entity_type lhs, const entity_type rhs) ENTT_NOEXCEPT { | |||||
constexpr auto mask = (base_type::version_mask << base_type::entity_shift); | |||||
return value_type{(lhs & base_type::entity_mask) | (rhs & mask)}; | |||||
} | |||||
}; | |||||
/** | |||||
* @copydoc entt_traits<Entity>::to_integral | |||||
* @tparam Entity The value type. | |||||
*/ | |||||
template<typename Entity> | |||||
[[nodiscard]] constexpr typename entt_traits<Entity>::entity_type to_integral(const Entity value) ENTT_NOEXCEPT { | |||||
return entt_traits<Entity>::to_integral(value); | |||||
} | |||||
/** | |||||
* @copydoc entt_traits<Entity>::to_entity | |||||
* @tparam Entity The value type. | |||||
*/ | |||||
template<typename Entity> | |||||
[[nodiscard]] constexpr typename entt_traits<Entity>::entity_type to_entity(const Entity value) ENTT_NOEXCEPT { | |||||
return entt_traits<Entity>::to_entity(value); | |||||
} | |||||
/** | |||||
* @copydoc entt_traits<Entity>::to_version | |||||
* @tparam Entity The value type. | |||||
*/ | |||||
template<typename Entity> | |||||
[[nodiscard]] constexpr typename entt_traits<Entity>::version_type to_version(const Entity value) ENTT_NOEXCEPT { | |||||
return entt_traits<Entity>::to_version(value); | |||||
} | |||||
/*! @brief Null object for all identifiers. */ | |||||
struct null_t { | |||||
/** | |||||
* @brief Converts the null object to identifiers of any type. | |||||
* @tparam Entity Type of identifier. | |||||
* @return The null representation for the given type. | |||||
*/ | |||||
template<typename Entity> | 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); | |||||
[[nodiscard]] constexpr operator Entity() const ENTT_NOEXCEPT { | |||||
using entity_traits = entt_traits<Entity>; | |||||
return entity_traits::combine(entity_traits::reserved, entity_traits::reserved); | |||||
} | } | ||||
constexpr bool operator==(null) const ENTT_NOEXCEPT { | |||||
/** | |||||
* @brief Compares two null objects. | |||||
* @param other A null object. | |||||
* @return True in all cases. | |||||
*/ | |||||
[[nodiscard]] constexpr bool operator==([[maybe_unused]] const null_t other) const ENTT_NOEXCEPT { | |||||
return true; | return true; | ||||
} | } | ||||
constexpr bool operator!=(null) const ENTT_NOEXCEPT { | |||||
/** | |||||
* @brief Compares two null objects. | |||||
* @param other A null object. | |||||
* @return False in all cases. | |||||
*/ | |||||
[[nodiscard]] constexpr bool operator!=([[maybe_unused]] const null_t other) const ENTT_NOEXCEPT { | |||||
return false; | return false; | ||||
} | } | ||||
/** | |||||
* @brief Compares a null object and an identifier of any type. | |||||
* @tparam Entity Type of identifier. | |||||
* @param entity Identifier with which to compare. | |||||
* @return False if the two elements differ, true otherwise. | |||||
*/ | |||||
template<typename Entity> | template<typename Entity> | ||||
constexpr bool operator==(const Entity entity) const ENTT_NOEXCEPT { | |||||
return entity == static_cast<Entity>(*this); | |||||
[[nodiscard]] constexpr bool operator==(const Entity entity) const ENTT_NOEXCEPT { | |||||
using entity_traits = entt_traits<Entity>; | |||||
return entity_traits::to_entity(entity) == entity_traits::to_entity(*this); | |||||
} | } | ||||
/** | |||||
* @brief Compares a null object and an identifier of any type. | |||||
* @tparam Entity Type of identifier. | |||||
* @param entity Identifier with which to compare. | |||||
* @return True if the two elements differ, false otherwise. | |||||
*/ | |||||
template<typename Entity> | template<typename Entity> | ||||
constexpr bool operator!=(const Entity entity) const ENTT_NOEXCEPT { | |||||
return entity != static_cast<Entity>(*this); | |||||
[[nodiscard]] constexpr bool operator!=(const Entity entity) const ENTT_NOEXCEPT { | |||||
return !(entity == *this); | |||||
} | } | ||||
}; | }; | ||||
/** | |||||
* @brief Compares a null object and an identifier of any type. | |||||
* @tparam Entity Type of identifier. | |||||
* @param entity Identifier with which to compare. | |||||
* @param other A null object yet to be converted. | |||||
* @return False if the two elements differ, true otherwise. | |||||
*/ | |||||
template<typename Entity> | template<typename Entity> | ||||
constexpr bool operator==(const Entity entity, null other) ENTT_NOEXCEPT { | |||||
return other == entity; | |||||
[[nodiscard]] constexpr bool operator==(const Entity entity, const null_t other) ENTT_NOEXCEPT { | |||||
return other.operator==(entity); | |||||
} | } | ||||
/** | |||||
* @brief Compares a null object and an identifier of any type. | |||||
* @tparam Entity Type of identifier. | |||||
* @param entity Identifier with which to compare. | |||||
* @param other A null object yet to be converted. | |||||
* @return True if the two elements differ, false otherwise. | |||||
*/ | |||||
template<typename Entity> | template<typename Entity> | ||||
constexpr bool operator!=(const Entity entity, null other) ENTT_NOEXCEPT { | |||||
return other != entity; | |||||
[[nodiscard]] constexpr bool operator!=(const Entity entity, const null_t other) ENTT_NOEXCEPT { | |||||
return !(other == entity); | |||||
} | } | ||||
/*! @brief Tombstone object for all identifiers. */ | |||||
struct tombstone_t { | |||||
/** | |||||
* @brief Converts the tombstone object to identifiers of any type. | |||||
* @tparam Entity Type of identifier. | |||||
* @return The tombstone representation for the given type. | |||||
*/ | |||||
template<typename Entity> | |||||
[[nodiscard]] constexpr operator Entity() const ENTT_NOEXCEPT { | |||||
using entity_traits = entt_traits<Entity>; | |||||
return entity_traits::combine(entity_traits::reserved, entity_traits::reserved); | |||||
} | |||||
/** | |||||
* @brief Compares two tombstone objects. | |||||
* @param other A tombstone object. | |||||
* @return True in all cases. | |||||
*/ | |||||
[[nodiscard]] constexpr bool operator==([[maybe_unused]] const tombstone_t other) const ENTT_NOEXCEPT { | |||||
return true; | |||||
} | |||||
} | |||||
/** | |||||
* @brief Compares two tombstone objects. | |||||
* @param other A tombstone object. | |||||
* @return False in all cases. | |||||
*/ | |||||
[[nodiscard]] constexpr bool operator!=([[maybe_unused]] const tombstone_t other) const ENTT_NOEXCEPT { | |||||
return false; | |||||
} | |||||
/** | |||||
* @brief Compares a tombstone object and an identifier of any type. | |||||
* @tparam Entity Type of identifier. | |||||
* @param entity Identifier with which to compare. | |||||
* @return False if the two elements differ, true otherwise. | |||||
*/ | |||||
template<typename Entity> | |||||
[[nodiscard]] constexpr bool operator==(const Entity entity) const ENTT_NOEXCEPT { | |||||
using entity_traits = entt_traits<Entity>; | |||||
return entity_traits::to_version(entity) == entity_traits::to_version(*this); | |||||
} | |||||
/** | |||||
* @brief Compares a tombstone object and an identifier of any type. | |||||
* @tparam Entity Type of identifier. | |||||
* @param entity Identifier with which to compare. | |||||
* @return True if the two elements differ, false otherwise. | |||||
*/ | |||||
template<typename Entity> | |||||
[[nodiscard]] constexpr bool operator!=(const Entity entity) const ENTT_NOEXCEPT { | |||||
return !(entity == *this); | |||||
} | |||||
}; | |||||
/** | /** | ||||
* Internal details not to be documented. | |||||
* @endcond TURN_OFF_DOXYGEN | |||||
* @brief Compares a tombstone object and an identifier of any type. | |||||
* @tparam Entity Type of identifier. | |||||
* @param entity Identifier with which to compare. | |||||
* @param other A tombstone object yet to be converted. | |||||
* @return False if the two elements differ, true otherwise. | |||||
*/ | */ | ||||
template<typename Entity> | |||||
[[nodiscard]] constexpr bool operator==(const Entity entity, const tombstone_t other) ENTT_NOEXCEPT { | |||||
return other.operator==(entity); | |||||
} | |||||
/** | |||||
* @brief Compares a tombstone object and an identifier of any type. | |||||
* @tparam Entity Type of identifier. | |||||
* @param entity Identifier with which to compare. | |||||
* @param other A tombstone object yet to be converted. | |||||
* @return True if the two elements differ, false otherwise. | |||||
*/ | |||||
template<typename Entity> | |||||
[[nodiscard]] constexpr bool operator!=(const Entity entity, const tombstone_t other) ENTT_NOEXCEPT { | |||||
return !(other == entity); | |||||
} | |||||
/** | /** | ||||
* @brief Null entity. | |||||
* @brief Compile-time constant for null entities. | |||||
* | * | ||||
* There exist implicit conversions from this variable to entity identifiers of | |||||
* any allowed type. Similarly, there exist comparision operators between the | |||||
* null entity and any other entity identifier. | |||||
* There exist implicit conversions from this variable to identifiers of any | |||||
* allowed type. Similarly, there exist comparison operators between the null | |||||
* entity and any other identifier. | |||||
*/ | */ | ||||
constexpr auto null = internal::null{}; | |||||
inline constexpr null_t null{}; | |||||
} | |||||
/** | |||||
* @brief Compile-time constant for tombstone entities. | |||||
* | |||||
* There exist implicit conversions from this variable to identifiers of any | |||||
* allowed type. Similarly, there exist comparison operators between the | |||||
* tombstone entity and any other identifier. | |||||
*/ | |||||
inline constexpr tombstone_t tombstone{}; | |||||
} // namespace entt | |||||
#endif // ENTT_ENTITY_ENTITY_HPP | |||||
#endif |
@ -0,0 +1,340 @@ | |||||
#ifndef ENTT_ENTITY_HANDLE_HPP | |||||
#define ENTT_ENTITY_HANDLE_HPP | |||||
#include <tuple> | |||||
#include <type_traits> | |||||
#include <utility> | |||||
#include "../config/config.h" | |||||
#include "../core/type_traits.hpp" | |||||
#include "fwd.hpp" | |||||
#include "registry.hpp" | |||||
namespace entt { | |||||
/** | |||||
* @brief Non-owning handle to an entity. | |||||
* | |||||
* Tiny wrapper around a registry and an entity. | |||||
* | |||||
* @tparam Entity A valid entity type (see entt_traits for more details). | |||||
* @tparam Type Types to which to restrict the scope of a handle. | |||||
*/ | |||||
template<typename Entity, typename... Type> | |||||
struct basic_handle { | |||||
/*! @brief Type of registry accepted by the handle. */ | |||||
using registry_type = constness_as_t<basic_registry<std::remove_const_t<Entity>>, Entity>; | |||||
/*! @brief Underlying entity identifier. */ | |||||
using entity_type = typename registry_type::entity_type; | |||||
/*! @brief Underlying version type. */ | |||||
using version_type = typename registry_type::version_type; | |||||
/*! @brief Unsigned integer type. */ | |||||
using size_type = typename registry_type::size_type; | |||||
/*! @brief Constructs an invalid handle. */ | |||||
basic_handle() ENTT_NOEXCEPT | |||||
: reg{}, | |||||
entt{null} {} | |||||
/** | |||||
* @brief Constructs a handle from a given registry and entity. | |||||
* @param ref An instance of the registry class. | |||||
* @param value A valid identifier. | |||||
*/ | |||||
basic_handle(registry_type &ref, entity_type value) ENTT_NOEXCEPT | |||||
: reg{&ref}, | |||||
entt{value} {} | |||||
/** | |||||
* @brief Constructs a const handle from a non-const one. | |||||
* @tparam Other A valid entity type (see entt_traits for more details). | |||||
* @tparam Args Scope of the handle to construct. | |||||
* @return A const handle referring to the same registry and the same | |||||
* entity. | |||||
*/ | |||||
template<typename Other, typename... Args> | |||||
operator basic_handle<Other, Args...>() const ENTT_NOEXCEPT { | |||||
static_assert(std::is_same_v<Other, Entity> || std::is_same_v<std::remove_const_t<Other>, Entity>, "Invalid conversion between different handles"); | |||||
static_assert((sizeof...(Type) == 0 || ((sizeof...(Args) != 0 && sizeof...(Args) <= sizeof...(Type)) && ... && (type_list_contains_v<type_list<Type...>, Args>))), "Invalid conversion between different handles"); | |||||
return reg ? basic_handle<Other, Args...>{*reg, entt} : basic_handle<Other, Args...>{}; | |||||
} | |||||
/** | |||||
* @brief Converts a handle to its underlying entity. | |||||
* @return The contained identifier. | |||||
*/ | |||||
[[nodiscard]] operator entity_type() const ENTT_NOEXCEPT { | |||||
return entity(); | |||||
} | |||||
/** | |||||
* @brief Checks if a handle refers to non-null registry pointer and entity. | |||||
* @return True if the handle refers to non-null registry and entity, false otherwise. | |||||
*/ | |||||
[[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { | |||||
return reg && reg->valid(entt); | |||||
} | |||||
/** | |||||
* @brief Checks if a handle refers to a valid entity or not. | |||||
* @return True if the handle refers to a valid entity, false otherwise. | |||||
*/ | |||||
[[nodiscard]] bool valid() const { | |||||
return reg->valid(entt); | |||||
} | |||||
/** | |||||
* @brief Returns a pointer to the underlying registry, if any. | |||||
* @return A pointer to the underlying registry, if any. | |||||
*/ | |||||
[[nodiscard]] registry_type *registry() const ENTT_NOEXCEPT { | |||||
return reg; | |||||
} | |||||
/** | |||||
* @brief Returns the entity associated with a handle. | |||||
* @return The entity associated with the handle. | |||||
*/ | |||||
[[nodiscard]] entity_type entity() const ENTT_NOEXCEPT { | |||||
return entt; | |||||
} | |||||
/** | |||||
* @brief Destroys the entity associated with a handle. | |||||
* @sa basic_registry::destroy | |||||
*/ | |||||
void destroy() { | |||||
reg->destroy(entt); | |||||
} | |||||
/** | |||||
* @brief Destroys the entity associated with a handle. | |||||
* @sa basic_registry::destroy | |||||
* @param version A desired version upon destruction. | |||||
*/ | |||||
void destroy(const version_type version) { | |||||
reg->destroy(entt, version); | |||||
} | |||||
/** | |||||
* @brief Assigns the given component to a handle. | |||||
* @sa basic_registry::emplace | |||||
* @tparam Component Type of 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) emplace(Args &&...args) const { | |||||
static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v<Component, Type>), "Invalid type"); | |||||
return reg->template emplace<Component>(entt, std::forward<Args>(args)...); | |||||
} | |||||
/** | |||||
* @brief Assigns or replaces the given component for a handle. | |||||
* @sa basic_registry::emplace_or_replace | |||||
* @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> | |||||
decltype(auto) emplace_or_replace(Args &&...args) const { | |||||
static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v<Component, Type>), "Invalid type"); | |||||
return reg->template emplace_or_replace<Component>(entt, std::forward<Args>(args)...); | |||||
} | |||||
/** | |||||
* @brief Patches the given component for a handle. | |||||
* @sa basic_registry::patch | |||||
* @tparam Component Type of component to patch. | |||||
* @tparam Func Types of the function objects to invoke. | |||||
* @param func Valid function objects. | |||||
* @return A reference to the patched component. | |||||
*/ | |||||
template<typename Component, typename... Func> | |||||
decltype(auto) patch(Func &&...func) const { | |||||
static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v<Component, Type>), "Invalid type"); | |||||
return reg->template patch<Component>(entt, std::forward<Func>(func)...); | |||||
} | |||||
/** | |||||
* @brief Replaces the given component for a handle. | |||||
* @sa basic_registry::replace | |||||
* @tparam Component Type of component to 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 component being replaced. | |||||
*/ | |||||
template<typename Component, typename... Args> | |||||
decltype(auto) replace(Args &&...args) const { | |||||
static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v<Component, Type>), "Invalid type"); | |||||
return reg->template replace<Component>(entt, std::forward<Args>(args)...); | |||||
} | |||||
/** | |||||
* @brief Removes the given components from a handle. | |||||
* @sa basic_registry::remove | |||||
* @tparam Component Types of components to remove. | |||||
* @return The number of components actually removed. | |||||
*/ | |||||
template<typename... Component> | |||||
size_type remove() const { | |||||
static_assert(sizeof...(Type) == 0 || (type_list_contains_v<type_list<Type...>, Component> && ...), "Invalid type"); | |||||
return reg->template remove<Component...>(entt); | |||||
} | |||||
/** | |||||
* @brief Erases the given components from a handle. | |||||
* @sa basic_registry::erase | |||||
* @tparam Component Types of components to erase. | |||||
*/ | |||||
template<typename... Component> | |||||
void erase() const { | |||||
static_assert(sizeof...(Type) == 0 || (type_list_contains_v<type_list<Type...>, Component> && ...), "Invalid type"); | |||||
reg->template erase<Component...>(entt); | |||||
} | |||||
/** | |||||
* @brief Checks if a handle has all the given components. | |||||
* @sa basic_registry::all_of | |||||
* @tparam Component Components for which to perform the check. | |||||
* @return True if the handle has all the components, false otherwise. | |||||
*/ | |||||
template<typename... Component> | |||||
[[nodiscard]] decltype(auto) all_of() const { | |||||
return reg->template all_of<Component...>(entt); | |||||
} | |||||
/** | |||||
* @brief Checks if a handle has at least one of the given components. | |||||
* @sa basic_registry::any_of | |||||
* @tparam Component Components for which to perform the check. | |||||
* @return True if the handle has at least one of the given components, | |||||
* false otherwise. | |||||
*/ | |||||
template<typename... Component> | |||||
[[nodiscard]] decltype(auto) any_of() const { | |||||
return reg->template any_of<Component...>(entt); | |||||
} | |||||
/** | |||||
* @brief Returns references to the given components for a handle. | |||||
* @sa basic_registry::get | |||||
* @tparam Component Types of components to get. | |||||
* @return References to the components owned by the handle. | |||||
*/ | |||||
template<typename... Component> | |||||
[[nodiscard]] decltype(auto) get() const { | |||||
static_assert(sizeof...(Type) == 0 || (type_list_contains_v<type_list<Type...>, Component> && ...), "Invalid type"); | |||||
return reg->template get<Component...>(entt); | |||||
} | |||||
/** | |||||
* @brief Returns a reference to the given component for a handle. | |||||
* @sa basic_registry::get_or_emplace | |||||
* @tparam Component Type of component to get. | |||||
* @tparam Args Types of arguments to use to construct the component. | |||||
* @param args Parameters to use to initialize the component. | |||||
* @return Reference to the component owned by the handle. | |||||
*/ | |||||
template<typename Component, typename... Args> | |||||
[[nodiscard]] decltype(auto) get_or_emplace(Args &&...args) const { | |||||
static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v<Component, Type>), "Invalid type"); | |||||
return reg->template get_or_emplace<Component>(entt, std::forward<Args>(args)...); | |||||
} | |||||
/** | |||||
* @brief Returns pointers to the given components for a handle. | |||||
* @sa basic_registry::try_get | |||||
* @tparam Component Types of components to get. | |||||
* @return Pointers to the components owned by the handle. | |||||
*/ | |||||
template<typename... Component> | |||||
[[nodiscard]] auto try_get() const { | |||||
static_assert(sizeof...(Type) == 0 || (type_list_contains_v<type_list<Type...>, Component> && ...), "Invalid type"); | |||||
return reg->template try_get<Component...>(entt); | |||||
} | |||||
/** | |||||
* @brief Checks if a handle has components assigned. | |||||
* @return True if the handle has no components assigned, false otherwise. | |||||
*/ | |||||
[[nodiscard]] bool orphan() const { | |||||
return reg->orphan(entt); | |||||
} | |||||
/** | |||||
* @brief Visits a handle and returns the pools for its components. | |||||
* | |||||
* The signature of the function should be equivalent to the following: | |||||
* | |||||
* @code{.cpp} | |||||
* void(id_type, const basic_sparse_set<entity_type> &); | |||||
* @endcode | |||||
* | |||||
* Returned pools are those that contain the entity associated with the | |||||
* handle. | |||||
* | |||||
* @tparam Func Type of the function object to invoke. | |||||
* @param func A valid function object. | |||||
*/ | |||||
template<typename Func> | |||||
void visit(Func &&func) const { | |||||
for(auto [id, storage]: reg->storage()) { | |||||
if(storage.contains(entt)) { | |||||
func(id, storage); | |||||
} | |||||
} | |||||
} | |||||
private: | |||||
registry_type *reg; | |||||
entity_type entt; | |||||
}; | |||||
/** | |||||
* @brief Compares two handles. | |||||
* @tparam Args Scope of the first handle. | |||||
* @tparam Other Scope of the second handle. | |||||
* @param lhs A valid handle. | |||||
* @param rhs A valid handle. | |||||
* @return True if both handles refer to the same registry and the same | |||||
* entity, false otherwise. | |||||
*/ | |||||
template<typename... Args, typename... Other> | |||||
[[nodiscard]] bool operator==(const basic_handle<Args...> &lhs, const basic_handle<Other...> &rhs) ENTT_NOEXCEPT { | |||||
return lhs.registry() == rhs.registry() && lhs.entity() == rhs.entity(); | |||||
} | |||||
/** | |||||
* @brief Compares two handles. | |||||
* @tparam Args Scope of the first handle. | |||||
* @tparam Other Scope of the second handle. | |||||
* @param lhs A valid handle. | |||||
* @param rhs A valid handle. | |||||
* @return False if both handles refer to the same registry and the same | |||||
* entity, true otherwise. | |||||
*/ | |||||
template<typename... Args, typename... Other> | |||||
[[nodiscard]] bool operator!=(const basic_handle<Args...> &lhs, const basic_handle<Other...> &rhs) ENTT_NOEXCEPT { | |||||
return !(lhs == rhs); | |||||
} | |||||
/** | |||||
* @brief Deduction guide. | |||||
* @tparam Entity A valid entity type (see entt_traits for more details). | |||||
*/ | |||||
template<typename Entity> | |||||
basic_handle(basic_registry<Entity> &, Entity) -> basic_handle<Entity>; | |||||
/** | |||||
* @brief Deduction guide. | |||||
* @tparam Entity A valid entity type (see entt_traits for more details). | |||||
*/ | |||||
template<typename Entity> | |||||
basic_handle(const basic_registry<Entity> &, Entity) -> basic_handle<const Entity>; | |||||
} // namespace entt | |||||
#endif |
@ -0,0 +1,436 @@ | |||||
#ifndef ENTT_ENTITY_OBSERVER_HPP | |||||
#define ENTT_ENTITY_OBSERVER_HPP | |||||
#include <cstddef> | |||||
#include <cstdint> | |||||
#include <limits> | |||||
#include <type_traits> | |||||
#include <utility> | |||||
#include "../config/config.h" | |||||
#include "../core/type_traits.hpp" | |||||
#include "../signal/delegate.hpp" | |||||
#include "entity.hpp" | |||||
#include "fwd.hpp" | |||||
#include "registry.hpp" | |||||
#include "storage.hpp" | |||||
#include "utility.hpp" | |||||
namespace entt { | |||||
/*! @brief Grouping matcher. */ | |||||
template<typename...> | |||||
struct matcher {}; | |||||
/** | |||||
* @brief Collector. | |||||
* | |||||
* Primary template isn't defined on purpose. All the specializations give a | |||||
* compile-time error, but for a few reasonable cases. | |||||
*/ | |||||
template<typename...> | |||||
struct basic_collector; | |||||
/** | |||||
* @brief Collector. | |||||
* | |||||
* A collector contains a set of rules (literally, matchers) to use to track | |||||
* entities.<br/> | |||||
* Its main purpose is to generate a descriptor that allows an observer to know | |||||
* how to connect to a registry. | |||||
*/ | |||||
template<> | |||||
struct basic_collector<> { | |||||
/** | |||||
* @brief Adds a grouping matcher to the collector. | |||||
* @tparam AllOf Types of components tracked by the matcher. | |||||
* @tparam NoneOf Types of components used to filter out entities. | |||||
* @return The updated collector. | |||||
*/ | |||||
template<typename... AllOf, typename... NoneOf> | |||||
static constexpr auto group(exclude_t<NoneOf...> = {}) ENTT_NOEXCEPT { | |||||
return basic_collector<matcher<type_list<>, type_list<>, type_list<NoneOf...>, AllOf...>>{}; | |||||
} | |||||
/** | |||||
* @brief Adds an observing matcher to the collector. | |||||
* @tparam AnyOf Type of component for which changes should be detected. | |||||
* @return The updated collector. | |||||
*/ | |||||
template<typename AnyOf> | |||||
static constexpr auto update() ENTT_NOEXCEPT { | |||||
return basic_collector<matcher<type_list<>, type_list<>, AnyOf>>{}; | |||||
} | |||||
}; | |||||
/** | |||||
* @brief Collector. | |||||
* @copydetails basic_collector<> | |||||
* @tparam Reject Untracked types used to filter out entities. | |||||
* @tparam Require Untracked types required by the matcher. | |||||
* @tparam Rule Specific details of the current matcher. | |||||
* @tparam Other Other matchers. | |||||
*/ | |||||
template<typename... Reject, typename... Require, typename... Rule, typename... Other> | |||||
struct basic_collector<matcher<type_list<Reject...>, type_list<Require...>, Rule...>, Other...> { | |||||
/*! @brief Current matcher. */ | |||||
using current_type = matcher<type_list<Reject...>, type_list<Require...>, Rule...>; | |||||
/** | |||||
* @brief Adds a grouping matcher to the collector. | |||||
* @tparam AllOf Types of components tracked by the matcher. | |||||
* @tparam NoneOf Types of components used to filter out entities. | |||||
* @return The updated collector. | |||||
*/ | |||||
template<typename... AllOf, typename... NoneOf> | |||||
static constexpr auto group(exclude_t<NoneOf...> = {}) ENTT_NOEXCEPT { | |||||
return basic_collector<matcher<type_list<>, type_list<>, type_list<NoneOf...>, AllOf...>, current_type, Other...>{}; | |||||
} | |||||
/** | |||||
* @brief Adds an observing matcher to the collector. | |||||
* @tparam AnyOf Type of component for which changes should be detected. | |||||
* @return The updated collector. | |||||
*/ | |||||
template<typename AnyOf> | |||||
static constexpr auto update() ENTT_NOEXCEPT { | |||||
return basic_collector<matcher<type_list<>, type_list<>, AnyOf>, current_type, Other...>{}; | |||||
} | |||||
/** | |||||
* @brief Updates the filter of the last added matcher. | |||||
* @tparam AllOf Types of components required by the matcher. | |||||
* @tparam NoneOf Types of components used to filter out entities. | |||||
* @return The updated collector. | |||||
*/ | |||||
template<typename... AllOf, typename... NoneOf> | |||||
static constexpr auto where(exclude_t<NoneOf...> = {}) ENTT_NOEXCEPT { | |||||
using extended_type = matcher<type_list<Reject..., NoneOf...>, type_list<Require..., AllOf...>, Rule...>; | |||||
return basic_collector<extended_type, Other...>{}; | |||||
} | |||||
}; | |||||
/*! @brief Variable template used to ease the definition of collectors. */ | |||||
inline constexpr basic_collector<> collector{}; | |||||
/** | |||||
* @brief Observer. | |||||
* | |||||
* An observer returns all the entities and only the entities that fit the | |||||
* requirements of at least one matcher. Moreover, it's guaranteed that the | |||||
* entity list is tightly packed in memory for fast iterations.<br/> | |||||
* In general, observers don't stay true to the order of any set of components. | |||||
* | |||||
* Observers work mainly with two types of matchers, provided through a | |||||
* collector: | |||||
* | |||||
* * Observing matcher: an observer will return at least all the living entities | |||||
* for which one or more of the given components have been updated and not yet | |||||
* destroyed. | |||||
* * Grouping matcher: an observer will return at least all the living entities | |||||
* that would have entered the given group if it existed and that would have | |||||
* not yet left it. | |||||
* | |||||
* If an entity respects the requirements of multiple matchers, it will be | |||||
* returned once and only once by the observer in any case. | |||||
* | |||||
* Matchers support also filtering by means of a _where_ clause that accepts | |||||
* both a list of types and an exclusion list.<br/> | |||||
* Whenever a matcher finds that an entity matches its requirements, the | |||||
* condition of the filter is verified before to register the entity itself. | |||||
* Moreover, a registered entity isn't returned by the observer if the condition | |||||
* set by the filter is broken in the meantime. | |||||
* | |||||
* @b Important | |||||
* | |||||
* Iterators aren't invalidated if: | |||||
* | |||||
* * New instances of the given components are created and assigned to entities. | |||||
* * The entity currently pointed is modified (as an example, if one of the | |||||
* given components is removed from the entity to which the iterator points). | |||||
* * The entity currently pointed is destroyed. | |||||
* | |||||
* In all the other cases, modifying the pools of the given components in any | |||||
* way invalidates all the iterators and using them results in undefined | |||||
* behavior. | |||||
* | |||||
* @warning | |||||
* Lifetime of an observer doesn't necessarily have to overcome that of the | |||||
* registry to which it is connected. However, the observer must be disconnected | |||||
* from the registry before being destroyed to avoid crashes due to dangling | |||||
* pointers. | |||||
* | |||||
* @tparam Entity A valid entity type (see entt_traits for more details). | |||||
*/ | |||||
template<typename Entity> | |||||
class basic_observer { | |||||
using payload_type = std::uint32_t; | |||||
template<typename> | |||||
struct matcher_handler; | |||||
template<typename... Reject, typename... Require, typename AnyOf> | |||||
struct matcher_handler<matcher<type_list<Reject...>, type_list<Require...>, AnyOf>> { | |||||
template<std::size_t Index> | |||||
static void maybe_valid_if(basic_observer &obs, basic_registry<Entity> ®, const Entity entt) { | |||||
if(reg.template all_of<Require...>(entt) && !reg.template any_of<Reject...>(entt)) { | |||||
if(!obs.storage.contains(entt)) { | |||||
obs.storage.emplace(entt); | |||||
} | |||||
obs.storage.get(entt) |= (1 << Index); | |||||
} | |||||
} | |||||
template<std::size_t Index> | |||||
static void discard_if(basic_observer &obs, basic_registry<Entity> &, const Entity entt) { | |||||
if(obs.storage.contains(entt) && !(obs.storage.get(entt) &= (~(1 << Index)))) { | |||||
obs.storage.erase(entt); | |||||
} | |||||
} | |||||
template<std::size_t Index> | |||||
static void connect(basic_observer &obs, basic_registry<Entity> ®) { | |||||
(reg.template on_destroy<Require>().template connect<&discard_if<Index>>(obs), ...); | |||||
(reg.template on_construct<Reject>().template connect<&discard_if<Index>>(obs), ...); | |||||
reg.template on_update<AnyOf>().template connect<&maybe_valid_if<Index>>(obs); | |||||
reg.template on_destroy<AnyOf>().template connect<&discard_if<Index>>(obs); | |||||
} | |||||
static void disconnect(basic_observer &obs, basic_registry<Entity> ®) { | |||||
(reg.template on_destroy<Require>().disconnect(obs), ...); | |||||
(reg.template on_construct<Reject>().disconnect(obs), ...); | |||||
reg.template on_update<AnyOf>().disconnect(obs); | |||||
reg.template on_destroy<AnyOf>().disconnect(obs); | |||||
} | |||||
}; | |||||
template<typename... Reject, typename... Require, typename... NoneOf, typename... AllOf> | |||||
struct matcher_handler<matcher<type_list<Reject...>, type_list<Require...>, type_list<NoneOf...>, AllOf...>> { | |||||
template<std::size_t Index, typename... Ignore> | |||||
static void maybe_valid_if(basic_observer &obs, basic_registry<Entity> ®, const Entity entt) { | |||||
auto condition = [®, entt]() { | |||||
if constexpr(sizeof...(Ignore) == 0) { | |||||
return reg.template all_of<AllOf..., Require...>(entt) && !reg.template any_of<NoneOf..., Reject...>(entt); | |||||
} else { | |||||
return reg.template all_of<AllOf..., Require...>(entt) && ((std::is_same_v<Ignore..., NoneOf> || !reg.template any_of<NoneOf>(entt)) && ...) && !reg.template any_of<Reject...>(entt); | |||||
} | |||||
}; | |||||
if(condition()) { | |||||
if(!obs.storage.contains(entt)) { | |||||
obs.storage.emplace(entt); | |||||
} | |||||
obs.storage.get(entt) |= (1 << Index); | |||||
} | |||||
} | |||||
template<std::size_t Index> | |||||
static void discard_if(basic_observer &obs, basic_registry<Entity> &, const Entity entt) { | |||||
if(obs.storage.contains(entt) && !(obs.storage.get(entt) &= (~(1 << Index)))) { | |||||
obs.storage.erase(entt); | |||||
} | |||||
} | |||||
template<std::size_t Index> | |||||
static void connect(basic_observer &obs, basic_registry<Entity> ®) { | |||||
(reg.template on_destroy<Require>().template connect<&discard_if<Index>>(obs), ...); | |||||
(reg.template on_construct<Reject>().template connect<&discard_if<Index>>(obs), ...); | |||||
(reg.template on_construct<AllOf>().template connect<&maybe_valid_if<Index>>(obs), ...); | |||||
(reg.template on_destroy<NoneOf>().template connect<&maybe_valid_if<Index, NoneOf>>(obs), ...); | |||||
(reg.template on_destroy<AllOf>().template connect<&discard_if<Index>>(obs), ...); | |||||
(reg.template on_construct<NoneOf>().template connect<&discard_if<Index>>(obs), ...); | |||||
} | |||||
static void disconnect(basic_observer &obs, basic_registry<Entity> ®) { | |||||
(reg.template on_destroy<Require>().disconnect(obs), ...); | |||||
(reg.template on_construct<Reject>().disconnect(obs), ...); | |||||
(reg.template on_construct<AllOf>().disconnect(obs), ...); | |||||
(reg.template on_destroy<NoneOf>().disconnect(obs), ...); | |||||
(reg.template on_destroy<AllOf>().disconnect(obs), ...); | |||||
(reg.template on_construct<NoneOf>().disconnect(obs), ...); | |||||
} | |||||
}; | |||||
template<typename... Matcher> | |||||
static void disconnect(basic_registry<Entity> ®, basic_observer &obs) { | |||||
(matcher_handler<Matcher>::disconnect(obs, reg), ...); | |||||
} | |||||
template<typename... Matcher, std::size_t... Index> | |||||
void connect(basic_registry<Entity> ®, std::index_sequence<Index...>) { | |||||
static_assert(sizeof...(Matcher) < std::numeric_limits<payload_type>::digits, "Too many matchers"); | |||||
(matcher_handler<Matcher>::template connect<Index>(*this, reg), ...); | |||||
release.template connect<&basic_observer::disconnect<Matcher...>>(reg); | |||||
} | |||||
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 = typename basic_sparse_set<Entity>::iterator; | |||||
/*! @brief Default constructor. */ | |||||
basic_observer() | |||||
: release{}, | |||||
storage{} {} | |||||
/*! @brief Default copy constructor, deleted on purpose. */ | |||||
basic_observer(const basic_observer &) = delete; | |||||
/*! @brief Default move constructor, deleted on purpose. */ | |||||
basic_observer(basic_observer &&) = delete; | |||||
/** | |||||
* @brief Creates an observer and connects it to a given registry. | |||||
* @tparam Matcher Types of matchers to use to initialize the observer. | |||||
* @param reg A valid reference to a registry. | |||||
*/ | |||||
template<typename... Matcher> | |||||
basic_observer(basic_registry<entity_type> ®, basic_collector<Matcher...>) | |||||
: basic_observer{} { | |||||
connect<Matcher...>(reg, std::index_sequence_for<Matcher...>{}); | |||||
} | |||||
/*! @brief Default destructor. */ | |||||
~basic_observer() = default; | |||||
/** | |||||
* @brief Default copy assignment operator, deleted on purpose. | |||||
* @return This observer. | |||||
*/ | |||||
basic_observer &operator=(const basic_observer &) = delete; | |||||
/** | |||||
* @brief Default move assignment operator, deleted on purpose. | |||||
* @return This observer. | |||||
*/ | |||||
basic_observer &operator=(basic_observer &&) = delete; | |||||
/** | |||||
* @brief Connects an observer to a given registry. | |||||
* @tparam Matcher Types of matchers to use to initialize the observer. | |||||
* @param reg A valid reference to a registry. | |||||
*/ | |||||
template<typename... Matcher> | |||||
void connect(basic_registry<entity_type> ®, basic_collector<Matcher...>) { | |||||
disconnect(); | |||||
connect<Matcher...>(reg, std::index_sequence_for<Matcher...>{}); | |||||
storage.clear(); | |||||
} | |||||
/*! @brief Disconnects an observer from the registry it keeps track of. */ | |||||
void disconnect() { | |||||
if(release) { | |||||
release(*this); | |||||
release.reset(); | |||||
} | |||||
} | |||||
/** | |||||
* @brief Returns the number of elements in an observer. | |||||
* @return Number of elements. | |||||
*/ | |||||
[[nodiscard]] size_type size() const ENTT_NOEXCEPT { | |||||
return storage.size(); | |||||
} | |||||
/** | |||||
* @brief Checks whether an observer is empty. | |||||
* @return True if the observer is empty, false otherwise. | |||||
*/ | |||||
[[nodiscard]] bool empty() const ENTT_NOEXCEPT { | |||||
return storage.empty(); | |||||
} | |||||
/** | |||||
* @brief Direct access to the list of entities of the observer. | |||||
* | |||||
* The returned pointer is such that range `[data(), data() + size())` is | |||||
* always a valid range, even if the container is empty. | |||||
* | |||||
* @note | |||||
* Entities are in the reverse order as returned by the `begin`/`end` | |||||
* iterators. | |||||
* | |||||
* @return A pointer to the array of entities. | |||||
*/ | |||||
[[nodiscard]] const entity_type *data() const ENTT_NOEXCEPT { | |||||
return storage.data(); | |||||
} | |||||
/** | |||||
* @brief Returns an iterator to the first entity of the observer. | |||||
* | |||||
* The returned iterator points to the first entity of the observer. If the | |||||
* container is empty, the returned iterator will be equal to `end()`. | |||||
* | |||||
* @return An iterator to the first entity of the observer. | |||||
*/ | |||||
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT { | |||||
return storage.basic_sparse_set<entity_type>::begin(); | |||||
} | |||||
/** | |||||
* @brief Returns an iterator that is past the last entity of the observer. | |||||
* | |||||
* The returned iterator points to the entity following the last entity of | |||||
* the observer. Attempting to dereference the returned iterator results in | |||||
* undefined behavior. | |||||
* | |||||
* @return An iterator to the entity following the last entity of the | |||||
* observer. | |||||
*/ | |||||
[[nodiscard]] iterator end() const ENTT_NOEXCEPT { | |||||
return storage.basic_sparse_set<entity_type>::end(); | |||||
} | |||||
/*! @brief Clears the underlying container. */ | |||||
void clear() ENTT_NOEXCEPT { | |||||
storage.clear(); | |||||
} | |||||
/** | |||||
* @brief Iterates entities and applies the given function object to them. | |||||
* | |||||
* The function object is invoked for each entity.<br/> | |||||
* The signature of the function must be equivalent to the following form: | |||||
* | |||||
* @code{.cpp} | |||||
* void(const entity_type); | |||||
* @endcode | |||||
* | |||||
* @tparam Func Type of the function object to invoke. | |||||
* @param func A valid function object. | |||||
*/ | |||||
template<typename Func> | |||||
void each(Func func) const { | |||||
for(const auto entity: *this) { | |||||
func(entity); | |||||
} | |||||
} | |||||
/** | |||||
* @brief Iterates entities and applies the given function object to them, | |||||
* then clears the observer. | |||||
* | |||||
* @sa each | |||||
* | |||||
* @tparam Func Type of the function object to invoke. | |||||
* @param func A valid function object. | |||||
*/ | |||||
template<typename Func> | |||||
void each(Func func) { | |||||
std::as_const(*this).each(std::move(func)); | |||||
clear(); | |||||
} | |||||
private: | |||||
delegate<void(basic_observer &)> release; | |||||
basic_storage<entity_type, payload_type> storage; | |||||
}; | |||||
} // namespace entt | |||||
#endif |
@ -0,0 +1,484 @@ | |||||
#ifndef ENTT_ENTITY_ORGANIZER_HPP | |||||
#define ENTT_ENTITY_ORGANIZER_HPP | |||||
#include <algorithm> | |||||
#include <cstddef> | |||||
#include <type_traits> | |||||
#include <utility> | |||||
#include <vector> | |||||
#include "../container/dense_map.hpp" | |||||
#include "../core/type_info.hpp" | |||||
#include "../core/type_traits.hpp" | |||||
#include "../core/utility.hpp" | |||||
#include "fwd.hpp" | |||||
#include "helper.hpp" | |||||
namespace entt { | |||||
/** | |||||
* @cond TURN_OFF_DOXYGEN | |||||
* Internal details not to be documented. | |||||
*/ | |||||
namespace internal { | |||||
template<typename> | |||||
struct is_view: std::false_type {}; | |||||
template<typename Entity, typename... Component, typename... Exclude> | |||||
struct is_view<basic_view<Entity, get_t<Component...>, exclude_t<Exclude...>>>: std::true_type {}; | |||||
template<typename Type> | |||||
inline constexpr bool is_view_v = is_view<Type>::value; | |||||
template<typename Type, typename Override> | |||||
struct unpack_type { | |||||
using ro = std::conditional_t< | |||||
type_list_contains_v<Override, std::add_const_t<Type>> || (std::is_const_v<Type> && !type_list_contains_v<Override, std::remove_const_t<Type>>), | |||||
type_list<std::remove_const_t<Type>>, | |||||
type_list<>>; | |||||
using rw = std::conditional_t< | |||||
type_list_contains_v<Override, std::remove_const_t<Type>> || (!std::is_const_v<Type> && !type_list_contains_v<Override, std::add_const_t<Type>>), | |||||
type_list<Type>, | |||||
type_list<>>; | |||||
}; | |||||
template<typename Entity, typename... Override> | |||||
struct unpack_type<basic_registry<Entity>, type_list<Override...>> { | |||||
using ro = type_list<>; | |||||
using rw = type_list<>; | |||||
}; | |||||
template<typename Entity, typename... Override> | |||||
struct unpack_type<const basic_registry<Entity>, type_list<Override...>> | |||||
: unpack_type<basic_registry<Entity>, type_list<Override...>> {}; | |||||
template<typename Entity, typename... Component, typename... Exclude, typename... Override> | |||||
struct unpack_type<basic_view<Entity, get_t<Component...>, exclude_t<Exclude...>>, type_list<Override...>> { | |||||
using ro = type_list_cat_t<type_list<Exclude...>, typename unpack_type<Component, type_list<Override...>>::ro...>; | |||||
using rw = type_list_cat_t<typename unpack_type<Component, type_list<Override...>>::rw...>; | |||||
}; | |||||
template<typename Entity, typename... Component, typename... Exclude, typename... Override> | |||||
struct unpack_type<const basic_view<Entity, get_t<Component...>, exclude_t<Exclude...>>, type_list<Override...>> | |||||
: unpack_type<basic_view<Entity, get_t<Component...>, exclude_t<Exclude...>>, type_list<Override...>> {}; | |||||
template<typename, typename> | |||||
struct resource_traits; | |||||
template<typename... Args, typename... Req> | |||||
struct resource_traits<type_list<Args...>, type_list<Req...>> { | |||||
using args = type_list<std::remove_const_t<Args>...>; | |||||
using ro = type_list_cat_t<typename unpack_type<Args, type_list<Req...>>::ro..., typename unpack_type<Req, type_list<>>::ro...>; | |||||
using rw = type_list_cat_t<typename unpack_type<Args, type_list<Req...>>::rw..., typename unpack_type<Req, type_list<>>::rw...>; | |||||
}; | |||||
template<typename... Req, typename Ret, typename... Args> | |||||
resource_traits<type_list<std::remove_reference_t<Args>...>, type_list<Req...>> free_function_to_resource_traits(Ret (*)(Args...)); | |||||
template<typename... Req, typename Ret, typename Type, typename... Args> | |||||
resource_traits<type_list<std::remove_reference_t<Args>...>, type_list<Req...>> constrained_function_to_resource_traits(Ret (*)(Type &, Args...)); | |||||
template<typename... Req, typename Ret, typename Class, typename... Args> | |||||
resource_traits<type_list<std::remove_reference_t<Args>...>, type_list<Req...>> constrained_function_to_resource_traits(Ret (Class::*)(Args...)); | |||||
template<typename... Req, typename Ret, typename Class, typename... Args> | |||||
resource_traits<type_list<std::remove_reference_t<Args>...>, type_list<Req...>> constrained_function_to_resource_traits(Ret (Class::*)(Args...) const); | |||||
} // namespace internal | |||||
/** | |||||
* Internal details not to be documented. | |||||
* @endcond | |||||
*/ | |||||
/** | |||||
* @brief Utility class for creating a static task graph. | |||||
* | |||||
* This class offers minimal support (but sufficient in many cases) for creating | |||||
* an execution graph from functions and their requirements on resources.<br/> | |||||
* Note that the resulting tasks aren't executed in any case. This isn't the | |||||
* goal of the tool. Instead, they are returned to the user in the form of a | |||||
* graph that allows for safe execution. | |||||
* | |||||
* @tparam Entity A valid entity type (see entt_traits for more details). | |||||
*/ | |||||
template<typename Entity> | |||||
class basic_organizer final { | |||||
using callback_type = void(const void *, basic_registry<Entity> &); | |||||
using prepare_type = void(basic_registry<Entity> &); | |||||
using dependency_type = std::size_t(const bool, const type_info **, const std::size_t); | |||||
struct vertex_data final { | |||||
std::size_t ro_count{}; | |||||
std::size_t rw_count{}; | |||||
const char *name{}; | |||||
const void *payload{}; | |||||
callback_type *callback{}; | |||||
dependency_type *dependency; | |||||
prepare_type *prepare{}; | |||||
const type_info *info{}; | |||||
}; | |||||
template<typename Type> | |||||
[[nodiscard]] static decltype(auto) extract(basic_registry<Entity> ®) { | |||||
if constexpr(std::is_same_v<Type, basic_registry<Entity>>) { | |||||
return reg; | |||||
} else if constexpr(internal::is_view_v<Type>) { | |||||
return as_view{reg}; | |||||
} else { | |||||
return reg.ctx().template emplace<std::remove_reference_t<Type>>(); | |||||
} | |||||
} | |||||
template<typename... Args> | |||||
[[nodiscard]] static auto to_args(basic_registry<Entity> ®, type_list<Args...>) { | |||||
return std::tuple<decltype(extract<Args>(reg))...>(extract<Args>(reg)...); | |||||
} | |||||
template<typename... Type> | |||||
static std::size_t fill_dependencies(type_list<Type...>, [[maybe_unused]] const type_info **buffer, [[maybe_unused]] const std::size_t count) { | |||||
if constexpr(sizeof...(Type) == 0u) { | |||||
return {}; | |||||
} else { | |||||
const type_info *info[sizeof...(Type)]{&type_id<Type>()...}; | |||||
const auto length = (std::min)(count, sizeof...(Type)); | |||||
std::copy_n(info, length, buffer); | |||||
return length; | |||||
} | |||||
} | |||||
template<typename... RO, typename... RW> | |||||
void track_dependencies(std::size_t index, const bool requires_registry, type_list<RO...>, type_list<RW...>) { | |||||
dependencies[type_hash<basic_registry<Entity>>::value()].emplace_back(index, requires_registry || (sizeof...(RO) + sizeof...(RW) == 0u)); | |||||
(dependencies[type_hash<RO>::value()].emplace_back(index, false), ...); | |||||
(dependencies[type_hash<RW>::value()].emplace_back(index, true), ...); | |||||
} | |||||
[[nodiscard]] std::vector<bool> adjacency_matrix() { | |||||
const auto length = vertices.size(); | |||||
std::vector<bool> edges(length * length, false); | |||||
// creates the adjacency matrix | |||||
for(const auto &deps: dependencies) { | |||||
const auto last = deps.second.cend(); | |||||
auto it = deps.second.cbegin(); | |||||
while(it != last) { | |||||
if(it->second) { | |||||
// rw item | |||||
if(auto curr = it++; it != last) { | |||||
if(it->second) { | |||||
edges[curr->first * length + it->first] = true; | |||||
} else { | |||||
if(const auto next = std::find_if(it, last, [](const auto &elem) { return elem.second; }); next != last) { | |||||
for(; it != next; ++it) { | |||||
edges[curr->first * length + it->first] = true; | |||||
edges[it->first * length + next->first] = true; | |||||
} | |||||
} else { | |||||
for(; it != next; ++it) { | |||||
edges[curr->first * length + it->first] = true; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} else { | |||||
// ro item, possibly only on first iteration | |||||
if(const auto next = std::find_if(it, last, [](const auto &elem) { return elem.second; }); next != last) { | |||||
for(; it != next; ++it) { | |||||
edges[it->first * length + next->first] = true; | |||||
} | |||||
} else { | |||||
it = last; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
// computes the transitive closure | |||||
for(std::size_t vk{}; vk < length; ++vk) { | |||||
for(std::size_t vi{}; vi < length; ++vi) { | |||||
for(std::size_t vj{}; vj < length; ++vj) { | |||||
edges[vi * length + vj] = edges[vi * length + vj] || (edges[vi * length + vk] && edges[vk * length + vj]); | |||||
} | |||||
} | |||||
} | |||||
// applies the transitive reduction | |||||
for(std::size_t vert{}; vert < length; ++vert) { | |||||
edges[vert * length + vert] = false; | |||||
} | |||||
for(std::size_t vj{}; vj < length; ++vj) { | |||||
for(std::size_t vi{}; vi < length; ++vi) { | |||||
if(edges[vi * length + vj]) { | |||||
for(std::size_t vk{}; vk < length; ++vk) { | |||||
if(edges[vj * length + vk]) { | |||||
edges[vi * length + vk] = false; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
return edges; | |||||
} | |||||
public: | |||||
/*! @brief Underlying entity identifier. */ | |||||
using entity_type = Entity; | |||||
/*! @brief Unsigned integer type. */ | |||||
using size_type = std::size_t; | |||||
/*! @brief Raw task function type. */ | |||||
using function_type = callback_type; | |||||
/*! @brief Vertex type of a task graph defined as an adjacency list. */ | |||||
struct vertex { | |||||
/** | |||||
* @brief Constructs a vertex of the task graph. | |||||
* @param vtype True if the vertex is a top-level one, false otherwise. | |||||
* @param data The data associated with the vertex. | |||||
* @param edges The indices of the children in the adjacency list. | |||||
*/ | |||||
vertex(const bool vtype, vertex_data data, std::vector<std::size_t> edges) | |||||
: is_top_level{vtype}, | |||||
node{std::move(data)}, | |||||
reachable{std::move(edges)} {} | |||||
/** | |||||
* @brief Fills a buffer with the type info objects for the writable | |||||
* resources of a vertex. | |||||
* @param buffer A buffer pre-allocated by the user. | |||||
* @param length The length of the user-supplied buffer. | |||||
* @return The number of type info objects written to the buffer. | |||||
*/ | |||||
size_type ro_dependency(const type_info **buffer, const std::size_t length) const ENTT_NOEXCEPT { | |||||
return node.dependency(false, buffer, length); | |||||
} | |||||
/** | |||||
* @brief Fills a buffer with the type info objects for the read-only | |||||
* resources of a vertex. | |||||
* @param buffer A buffer pre-allocated by the user. | |||||
* @param length The length of the user-supplied buffer. | |||||
* @return The number of type info objects written to the buffer. | |||||
*/ | |||||
size_type rw_dependency(const type_info **buffer, const std::size_t length) const ENTT_NOEXCEPT { | |||||
return node.dependency(true, buffer, length); | |||||
} | |||||
/** | |||||
* @brief Returns the number of read-only resources of a vertex. | |||||
* @return The number of read-only resources of the vertex. | |||||
*/ | |||||
size_type ro_count() const ENTT_NOEXCEPT { | |||||
return node.ro_count; | |||||
} | |||||
/** | |||||
* @brief Returns the number of writable resources of a vertex. | |||||
* @return The number of writable resources of the vertex. | |||||
*/ | |||||
size_type rw_count() const ENTT_NOEXCEPT { | |||||
return node.rw_count; | |||||
} | |||||
/** | |||||
* @brief Checks if a vertex is also a top-level one. | |||||
* @return True if the vertex is a top-level one, false otherwise. | |||||
*/ | |||||
bool top_level() const ENTT_NOEXCEPT { | |||||
return is_top_level; | |||||
} | |||||
/** | |||||
* @brief Returns a type info object associated with a vertex. | |||||
* @return A properly initialized type info object. | |||||
*/ | |||||
const type_info &info() const ENTT_NOEXCEPT { | |||||
return *node.info; | |||||
} | |||||
/** | |||||
* @brief Returns a user defined name associated with a vertex, if any. | |||||
* @return The user defined name associated with the vertex, if any. | |||||
*/ | |||||
const char *name() const ENTT_NOEXCEPT { | |||||
return node.name; | |||||
} | |||||
/** | |||||
* @brief Returns the function associated with a vertex. | |||||
* @return The function associated with the vertex. | |||||
*/ | |||||
function_type *callback() const ENTT_NOEXCEPT { | |||||
return node.callback; | |||||
} | |||||
/** | |||||
* @brief Returns the payload associated with a vertex, if any. | |||||
* @return The payload associated with the vertex, if any. | |||||
*/ | |||||
const void *data() const ENTT_NOEXCEPT { | |||||
return node.payload; | |||||
} | |||||
/** | |||||
* @brief Returns the list of nodes reachable from a given vertex. | |||||
* @return The list of nodes reachable from the vertex. | |||||
*/ | |||||
const std::vector<std::size_t> &children() const ENTT_NOEXCEPT { | |||||
return reachable; | |||||
} | |||||
/** | |||||
* @brief Prepares a registry and assures that all required resources | |||||
* are properly instantiated before using them. | |||||
* @param reg A valid registry. | |||||
*/ | |||||
void prepare(basic_registry<entity_type> ®) const { | |||||
node.prepare ? node.prepare(reg) : void(); | |||||
} | |||||
private: | |||||
bool is_top_level; | |||||
vertex_data node; | |||||
std::vector<std::size_t> reachable; | |||||
}; | |||||
/** | |||||
* @brief Adds a free function to the task list. | |||||
* @tparam Candidate Function to add to the task list. | |||||
* @tparam Req Additional requirements and/or override resource access mode. | |||||
* @param name Optional name to associate with the task. | |||||
*/ | |||||
template<auto Candidate, typename... Req> | |||||
void emplace(const char *name = nullptr) { | |||||
using resource_type = decltype(internal::free_function_to_resource_traits<Req...>(Candidate)); | |||||
constexpr auto requires_registry = type_list_contains_v<typename resource_type::args, basic_registry<entity_type>>; | |||||
callback_type *callback = +[](const void *, basic_registry<entity_type> ®) { | |||||
std::apply(Candidate, to_args(reg, typename resource_type::args{})); | |||||
}; | |||||
vertex_data vdata{ | |||||
resource_type::ro::size, | |||||
resource_type::rw::size, | |||||
name, | |||||
nullptr, | |||||
callback, | |||||
+[](const bool rw, const type_info **buffer, const std::size_t length) { return rw ? fill_dependencies(typename resource_type::rw{}, buffer, length) : fill_dependencies(typename resource_type::ro{}, buffer, length); }, | |||||
+[](basic_registry<entity_type> ®) { void(to_args(reg, typename resource_type::args{})); }, | |||||
&type_id<std::integral_constant<decltype(Candidate), Candidate>>()}; | |||||
track_dependencies(vertices.size(), requires_registry, typename resource_type::ro{}, typename resource_type::rw{}); | |||||
vertices.push_back(std::move(vdata)); | |||||
} | |||||
/** | |||||
* @brief Adds a free function with payload or a member function with an | |||||
* instance to the task list. | |||||
* @tparam Candidate Function or member to add to the task list. | |||||
* @tparam Req Additional requirements and/or override resource access mode. | |||||
* @tparam Type Type of class or type of payload. | |||||
* @param value_or_instance A valid object that fits the purpose. | |||||
* @param name Optional name to associate with the task. | |||||
*/ | |||||
template<auto Candidate, typename... Req, typename Type> | |||||
void emplace(Type &value_or_instance, const char *name = nullptr) { | |||||
using resource_type = decltype(internal::constrained_function_to_resource_traits<Req...>(Candidate)); | |||||
constexpr auto requires_registry = type_list_contains_v<typename resource_type::args, basic_registry<entity_type>>; | |||||
callback_type *callback = +[](const void *payload, basic_registry<entity_type> ®) { | |||||
Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload)); | |||||
std::apply(Candidate, std::tuple_cat(std::forward_as_tuple(*curr), to_args(reg, typename resource_type::args{}))); | |||||
}; | |||||
vertex_data vdata{ | |||||
resource_type::ro::size, | |||||
resource_type::rw::size, | |||||
name, | |||||
&value_or_instance, | |||||
callback, | |||||
+[](const bool rw, const type_info **buffer, const std::size_t length) { return rw ? fill_dependencies(typename resource_type::rw{}, buffer, length) : fill_dependencies(typename resource_type::ro{}, buffer, length); }, | |||||
+[](basic_registry<entity_type> ®) { void(to_args(reg, typename resource_type::args{})); }, | |||||
&type_id<std::integral_constant<decltype(Candidate), Candidate>>()}; | |||||
track_dependencies(vertices.size(), requires_registry, typename resource_type::ro{}, typename resource_type::rw{}); | |||||
vertices.push_back(std::move(vdata)); | |||||
} | |||||
/** | |||||
* @brief Adds an user defined function with optional payload to the task | |||||
* list. | |||||
* @tparam Req Additional requirements and/or override resource access mode. | |||||
* @param func Function to add to the task list. | |||||
* @param payload User defined arbitrary data. | |||||
* @param name Optional name to associate with the task. | |||||
*/ | |||||
template<typename... Req> | |||||
void emplace(function_type *func, const void *payload = nullptr, const char *name = nullptr) { | |||||
using resource_type = internal::resource_traits<type_list<>, type_list<Req...>>; | |||||
track_dependencies(vertices.size(), true, typename resource_type::ro{}, typename resource_type::rw{}); | |||||
vertex_data vdata{ | |||||
resource_type::ro::size, | |||||
resource_type::rw::size, | |||||
name, | |||||
payload, | |||||
func, | |||||
+[](const bool rw, const type_info **buffer, const std::size_t length) { return rw ? fill_dependencies(typename resource_type::rw{}, buffer, length) : fill_dependencies(typename resource_type::ro{}, buffer, length); }, | |||||
nullptr, | |||||
&type_id<void>()}; | |||||
vertices.push_back(std::move(vdata)); | |||||
} | |||||
/** | |||||
* @brief Generates a task graph for the current content. | |||||
* @return The adjacency list of the task graph. | |||||
*/ | |||||
std::vector<vertex> graph() { | |||||
const auto edges = adjacency_matrix(); | |||||
// creates the adjacency list | |||||
std::vector<vertex> adjacency_list{}; | |||||
adjacency_list.reserve(vertices.size()); | |||||
for(std::size_t col{}, length = vertices.size(); col < length; ++col) { | |||||
std::vector<std::size_t> reachable{}; | |||||
const auto row = col * length; | |||||
bool is_top_level = true; | |||||
for(std::size_t next{}; next < length; ++next) { | |||||
if(edges[row + next]) { | |||||
reachable.push_back(next); | |||||
} | |||||
} | |||||
for(std::size_t next{}; next < length && is_top_level; ++next) { | |||||
is_top_level = !edges[next * length + col]; | |||||
} | |||||
adjacency_list.emplace_back(is_top_level, vertices[col], std::move(reachable)); | |||||
} | |||||
return adjacency_list; | |||||
} | |||||
/*! @brief Erases all elements from a container. */ | |||||
void clear() { | |||||
dependencies.clear(); | |||||
vertices.clear(); | |||||
} | |||||
private: | |||||
dense_map<id_type, std::vector<std::pair<std::size_t, bool>>, identity> dependencies; | |||||
std::vector<vertex_data> vertices; | |||||
}; | |||||
} // namespace entt | |||||
#endif |
@ -1,483 +0,0 @@ | |||||
#ifndef ENTT_ENTITY_PROTOTYPE_HPP | |||||
#define ENTT_ENTITY_PROTOTYPE_HPP | |||||
#include <tuple> | |||||
#include <utility> | |||||
#include <cstddef> | |||||
#include <type_traits> | |||||
#include <unordered_map> | |||||
#include "../config/config.h" | |||||
#include "registry.hpp" | |||||
#include "entity.hpp" | |||||
#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,177 @@ | |||||
#ifndef ENTT_ENTITY_SIGH_STORAGE_MIXIN_HPP | |||||
#define ENTT_ENTITY_SIGH_STORAGE_MIXIN_HPP | |||||
#include <utility> | |||||
#include "../config/config.h" | |||||
#include "../core/any.hpp" | |||||
#include "../signal/sigh.hpp" | |||||
#include "fwd.hpp" | |||||
namespace entt { | |||||
/** | |||||
* @brief Mixin type used to add signal support to storage types. | |||||
* | |||||
* The function type of a listener is equivalent to: | |||||
* | |||||
* @code{.cpp} | |||||
* void(basic_registry<entity_type> &, entity_type); | |||||
* @endcode | |||||
* | |||||
* This applies to all signals made available. | |||||
* | |||||
* @tparam Type The type of the underlying storage. | |||||
*/ | |||||
template<typename Type> | |||||
class sigh_storage_mixin final: public Type { | |||||
using basic_iterator = typename Type::basic_iterator; | |||||
template<typename Func> | |||||
void notify_destruction(basic_iterator first, basic_iterator last, Func func) { | |||||
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); | |||||
for(; first != last; ++first) { | |||||
const auto entt = *first; | |||||
destruction.publish(*owner, entt); | |||||
const auto it = Type::find(entt); | |||||
func(it, it + 1u); | |||||
} | |||||
} | |||||
void swap_and_pop(basic_iterator first, basic_iterator last) final { | |||||
notify_destruction(std::move(first), std::move(last), [this](auto... args) { Type::swap_and_pop(args...); }); | |||||
} | |||||
void in_place_pop(basic_iterator first, basic_iterator last) final { | |||||
notify_destruction(std::move(first), std::move(last), [this](auto... args) { Type::in_place_pop(args...); }); | |||||
} | |||||
basic_iterator try_emplace(const typename Type::entity_type entt, const bool force_back, const void *value) final { | |||||
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); | |||||
Type::try_emplace(entt, force_back, value); | |||||
construction.publish(*owner, entt); | |||||
return Type::find(entt); | |||||
} | |||||
public: | |||||
/*! @brief Underlying entity identifier. */ | |||||
using entity_type = typename Type::entity_type; | |||||
/*! @brief Inherited constructors. */ | |||||
using Type::Type; | |||||
/** | |||||
* @brief Returns a sink object. | |||||
* | |||||
* The sink returned by this function can be used to receive notifications | |||||
* whenever a new instance is created and assigned to an entity.<br/> | |||||
* Listeners are invoked after the object has been assigned to the entity. | |||||
* | |||||
* @sa sink | |||||
* | |||||
* @return A temporary sink object. | |||||
*/ | |||||
[[nodiscard]] auto on_construct() ENTT_NOEXCEPT { | |||||
return sink{construction}; | |||||
} | |||||
/** | |||||
* @brief Returns a sink object. | |||||
* | |||||
* The sink returned by this function can be used to receive notifications | |||||
* whenever an instance is explicitly updated.<br/> | |||||
* Listeners are invoked after the object has been updated. | |||||
* | |||||
* @sa sink | |||||
* | |||||
* @return A temporary sink object. | |||||
*/ | |||||
[[nodiscard]] auto on_update() ENTT_NOEXCEPT { | |||||
return sink{update}; | |||||
} | |||||
/** | |||||
* @brief Returns a sink object. | |||||
* | |||||
* The sink returned by this function can be used to receive notifications | |||||
* whenever an instance is removed from an entity and thus destroyed.<br/> | |||||
* Listeners are invoked before the object has been removed from the entity. | |||||
* | |||||
* @sa sink | |||||
* | |||||
* @return A temporary sink object. | |||||
*/ | |||||
[[nodiscard]] auto on_destroy() ENTT_NOEXCEPT { | |||||
return sink{destruction}; | |||||
} | |||||
/** | |||||
* @brief Assigns entities to a storage. | |||||
* @tparam Args Types of arguments to use to construct the object. | |||||
* @param entt A valid identifier. | |||||
* @param args Parameters to use to initialize the object. | |||||
* @return A reference to the newly created object. | |||||
*/ | |||||
template<typename... Args> | |||||
decltype(auto) emplace(const entity_type entt, Args &&...args) { | |||||
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); | |||||
Type::emplace(entt, std::forward<Args>(args)...); | |||||
construction.publish(*owner, entt); | |||||
return this->get(entt); | |||||
} | |||||
/** | |||||
* @brief Patches the given instance for an entity. | |||||
* @tparam Func Types of the function objects to invoke. | |||||
* @param entt A valid identifier. | |||||
* @param func Valid function objects. | |||||
* @return A reference to the patched instance. | |||||
*/ | |||||
template<typename... Func> | |||||
decltype(auto) patch(const entity_type entt, Func &&...func) { | |||||
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); | |||||
Type::patch(entt, std::forward<Func>(func)...); | |||||
update.publish(*owner, entt); | |||||
return this->get(entt); | |||||
} | |||||
/** | |||||
* @brief Assigns entities to a storage. | |||||
* @tparam It Type of input iterator. | |||||
* @tparam Args Types of arguments to use to construct the objects assigned | |||||
* to the entities. | |||||
* @param first An iterator to the first element of the range of entities. | |||||
* @param last An iterator past the last element of the range of entities. | |||||
* @param args Parameters to use to initialize the objects assigned to the | |||||
* entities. | |||||
*/ | |||||
template<typename It, typename... Args> | |||||
void insert(It first, It last, Args &&...args) { | |||||
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); | |||||
Type::insert(first, last, std::forward<Args>(args)...); | |||||
for(auto it = construction.empty() ? last : first; it != last; ++it) { | |||||
construction.publish(*owner, *it); | |||||
} | |||||
} | |||||
/** | |||||
* @brief Forwards variables to mixins, if any. | |||||
* @param value A variable wrapped in an opaque container. | |||||
*/ | |||||
void bind(any value) ENTT_NOEXCEPT final { | |||||
auto *reg = any_cast<basic_registry<entity_type>>(&value); | |||||
owner = reg ? reg : owner; | |||||
Type::bind(std::move(value)); | |||||
} | |||||
private: | |||||
sigh<void(basic_registry<entity_type> &, const entity_type)> construction{}; | |||||
sigh<void(basic_registry<entity_type> &, const entity_type)> destruction{}; | |||||
sigh<void(basic_registry<entity_type> &, const entity_type)> update{}; | |||||
basic_registry<entity_type> *owner{}; | |||||
}; | |||||
} // namespace entt | |||||
#endif |