@ -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 | |||
# IDEs | |||
*.user | |||
.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 | |||
* 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@ | |||
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@") |
@ -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 | |||
#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 | |||
#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 | |||
#define ENTT_CONFIG_VERSION_H | |||
#include "macro.h" | |||
#define ENTT_VERSION "3.1.0" | |||
#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 | |||
#define ENTT_CORE_HASHED_STRING_HPP | |||
#include <cstddef> | |||
#include <cstdint> | |||
#include "../config/config.h" | |||
#include "fwd.hpp" | |||
namespace entt { | |||
/** | |||
* @cond TURN_OFF_DOXYGEN | |||
* Internal details not to be documented. | |||
*/ | |||
namespace internal { | |||
template<typename> | |||
struct fnv1a_traits; | |||
template<> | |||
struct fnv1a_traits<std::uint32_t> { | |||
using type = std::uint32_t; | |||
static constexpr std::uint32_t offset = 2166136261; | |||
static constexpr std::uint32_t prime = 16777619; | |||
}; | |||
template<> | |||
struct fnv1a_traits<std::uint64_t> { | |||
using type = std::uint64_t; | |||
static constexpr std::uint64_t offset = 14695981039346656037ull; | |||
static constexpr std::uint64_t prime = 1099511628211ull; | |||
}; | |||
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. | |||
* @endcond TURN_OFF_DOXYGEN | |||
* @endcond | |||
*/ | |||
/** | |||
* @brief Zero overhead unique identifier. | |||
* | |||
* A hashed string is a compile-time tool that allows users to use | |||
* human-readable identifers in the codebase while using their numeric | |||
* human-readable identifiers in the codebase while using their numeric | |||
* counterparts at runtime.<br/> | |||
* Because of that, a hashed string can also be used in constant expressions if | |||
* required. | |||
* | |||
* @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 { | |||
// 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 | |||
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: | |||
/*! @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. */ | |||
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. | |||
*/ | |||
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. | |||
* @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. | |||
*/ | |||
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. | |||
*/ | |||
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. */ | |||
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. | |||
* @param curr Human-readable identifer. | |||
* @param str Human-readable identifier. | |||
*/ | |||
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 | |||
* 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. | |||
*/ | |||
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. | |||
* @tparam Char Character type. | |||
* @param lhs A valid hashed string. | |||
* @param rhs A valid hashed string. | |||
* @return True if the two hashed strings are identical, false otherwise. | |||
*/ | |||
constexpr bool operator!=(const 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); | |||
} | |||
/** | |||
* @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. | |||
* @param str The literal without its suffix. | |||
* @return A properly initialized hashed string. | |||
*/ | |||
constexpr entt::hashed_string operator"" ENTT_HS_SUFFIX(const char *str, std::size_t) ENTT_NOEXCEPT { | |||
return entt::hashed_string{str}; | |||
[[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 | |||
#define ENTT_CORE_UTILITY_HPP | |||
#include <utility> | |||
#include "../config/config.h" | |||
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> | |||
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. | |||
* @tparam Type Function type of the desired overload. | |||
* @tparam Func Function type of the desired overload. | |||
* @param func A valid pointer to a function. | |||
* @return Pointer to the function. | |||
*/ | |||
template<typename Type> | |||
constexpr auto overload(Type *func) { return func; } | |||
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 | |||
#define ENTT_ENTITY_ENTITY_HPP | |||
#include <cstddef> | |||
#include <cstdint> | |||
#include <type_traits> | |||
#include "../config/config.h" | |||
#include "fwd.hpp" | |||
namespace entt { | |||
/** | |||
* @brief Entity traits. | |||
* | |||
* Primary template isn't defined on purpose. All the specializations give a | |||
* compile-time error unless the template parameter is an accepted entity type. | |||
* @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<> | |||
struct entt_traits<std::uint32_t> { | |||
/*! @brief Underlying entity type. */ | |||
using entity_type = std::uint32_t; | |||
/*! @brief Underlying version type. */ | |||
using version_type = std::uint16_t; | |||
/*! @brief Difference type. */ | |||
using difference_type = std::int64_t; | |||
/*! @brief Mask to use to get the entity number out of an identifier. */ | |||
static constexpr std::uint32_t entity_mask = 0xFFFFF; | |||
/*! @brief Mask to use to get the version out of an identifier. */ | |||
static constexpr std::uint32_t version_mask = 0xFFF; | |||
/*! @brief Extent of the entity number within an identifier. */ | |||
static constexpr auto entity_shift = 20; | |||
}; | |||
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<> | |||
struct entt_traits<std::uint64_t> { | |||
/*! @brief Underlying entity type. */ | |||
using entity_type = std::uint64_t; | |||
/*! @brief Underlying version type. */ | |||
using version_type = std::uint32_t; | |||
/*! @brief Difference type. */ | |||
using difference_type = std::int64_t; | |||
/*! @brief Mask to use to get the entity number out of an identifier. */ | |||
static constexpr std::uint64_t entity_mask = 0xFFFFFFFF; | |||
/*! @brief Mask to use to get the version out of an identifier. */ | |||
static constexpr std::uint64_t version_mask = 0xFFFFFFFF; | |||
/*! @brief Extent of the entity number within an identifier. */ | |||
static constexpr auto entity_shift = 32; | |||
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. | |||
* @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> | |||
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; | |||
} | |||
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; | |||
} | |||
/** | |||
* @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> | |||
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> | |||
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> | |||
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> | |||
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 |