forked from Green-Sky/tomato
Squashed 'external/entt/entt/' content from commit fef92113
git-subtree-dir: external/entt/entt git-subtree-split: fef921132cae7588213d0f9bcd2fb9c8ffd8b7fc
This commit is contained in:
commit
5c7231b7a3
41
.clang-format
Normal file
41
.clang-format
Normal file
@ -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
|
4
.github/FUNDING.yml
vendored
Normal file
4
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: skypjack
|
||||||
|
custom: https://www.paypal.me/skypjack
|
55
.github/workflows/analyzer.yml
vendored
Normal file
55
.github/workflows/analyzer.yml
vendored
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
name: analyzer
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- wip
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
iwyu:
|
||||||
|
timeout-minutes: 30
|
||||||
|
|
||||||
|
env:
|
||||||
|
IWYU: 0.18
|
||||||
|
LLVM: 14
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Install llvm/clang
|
||||||
|
# see: https://apt.llvm.org/
|
||||||
|
run: |
|
||||||
|
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
|
||||||
|
sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-$LLVM main"
|
||||||
|
sudo apt update
|
||||||
|
sudo apt remove -y "llvm*"
|
||||||
|
sudo apt remove -y "libclang-dev*"
|
||||||
|
sudo apt remove -y "clang*"
|
||||||
|
sudo apt install -y llvm-$LLVM-dev
|
||||||
|
sudo apt install -y libclang-$LLVM-dev
|
||||||
|
sudo apt install -y clang-$LLVM
|
||||||
|
- name: Compile iwyu
|
||||||
|
# see: https://github.com/include-what-you-use/include-what-you-use
|
||||||
|
working-directory: build
|
||||||
|
run: |
|
||||||
|
git clone https://github.com/include-what-you-use/include-what-you-use.git --branch $IWYU --depth 1
|
||||||
|
mkdir include-what-you-use/build
|
||||||
|
cd include-what-you-use/build
|
||||||
|
cmake -DCMAKE_C_COMPILER=clang-$LLVM -DCMAKE_CXX_COMPILER=clang++-$LLVM -DCMAKE_INSTALL_PREFIX=./ ..
|
||||||
|
make -j4
|
||||||
|
bin/include-what-you-use --version
|
||||||
|
- name: Compile tests
|
||||||
|
working-directory: build
|
||||||
|
run: |
|
||||||
|
export PATH=$PATH:${GITHUB_WORKSPACE}/build/include-what-you-use/build/bin
|
||||||
|
cmake -DENTT_BUILD_TESTING=ON \
|
||||||
|
-DENTT_BUILD_BENCHMARK=ON \
|
||||||
|
-DENTT_BUILD_EXAMPLE=ON \
|
||||||
|
-DENTT_BUILD_LIB=ON \
|
||||||
|
-DENTT_BUILD_SNAPSHOT=ON \
|
||||||
|
-DCMAKE_CXX_INCLUDE_WHAT_YOU_USE="include-what-you-use;-Xiwyu;--mapping_file=${GITHUB_WORKSPACE}/entt.imp;-Xiwyu;--no_fwd_decls;-Xiwyu;--verbose=1" ..
|
||||||
|
make -j4
|
169
.github/workflows/build.yml
vendored
Normal file
169
.github/workflows/build.yml
vendored
Normal file
@ -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
|
38
.github/workflows/coverage.yml
vendored
Normal file
38
.github/workflows/coverage.yml
vendored
Normal file
@ -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
|
39
.github/workflows/deploy.yml
vendored
Normal file
39
.github/workflows/deploy.yml
vendored
Normal file
@ -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
|
31
.github/workflows/sanitizer.yml
vendored
Normal file
31
.github/workflows/sanitizer.yml
vendored
Normal file
@ -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
|
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Conan
|
||||||
|
conan/test_package/build
|
||||||
|
|
||||||
|
# IDEs
|
||||||
|
*.user
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
.vs
|
||||||
|
CMakeSettings.json
|
||||||
|
cpp.hint
|
||||||
|
|
||||||
|
# Bazel
|
||||||
|
/bazel-*
|
56
AUTHORS
Normal file
56
AUTHORS
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# Author
|
||||||
|
|
||||||
|
skypjack
|
||||||
|
|
||||||
|
# Contributors
|
||||||
|
|
||||||
|
alexames
|
||||||
|
BenediktConze
|
||||||
|
bjadamson
|
||||||
|
ceeac
|
||||||
|
ColinH
|
||||||
|
corystegel
|
||||||
|
Croydon
|
||||||
|
cschreib
|
||||||
|
cugone
|
||||||
|
dbacchet
|
||||||
|
dBagrat
|
||||||
|
djarek
|
||||||
|
DNKpp
|
||||||
|
DonKult
|
||||||
|
drglove
|
||||||
|
eliasdaler
|
||||||
|
erez-o
|
||||||
|
eugeneko
|
||||||
|
gale83
|
||||||
|
ghost
|
||||||
|
grdowns
|
||||||
|
Green-Sky
|
||||||
|
Innokentiy-Alaytsev
|
||||||
|
Kerndog73
|
||||||
|
Koward
|
||||||
|
Lawrencemm
|
||||||
|
markand
|
||||||
|
mhammerc
|
||||||
|
Milerius
|
||||||
|
Minimonium
|
||||||
|
morbo84
|
||||||
|
m-waka
|
||||||
|
netpoetica
|
||||||
|
NixAJ
|
||||||
|
Oortonaut
|
||||||
|
Paolo-Oliverio
|
||||||
|
pgruenbacher
|
||||||
|
prowolf
|
||||||
|
Qix-
|
||||||
|
stefanofiorentino
|
||||||
|
suVrik
|
||||||
|
szunhammer
|
||||||
|
The5-1
|
||||||
|
vblanco20-1
|
||||||
|
willtunnels
|
||||||
|
WizardIke
|
||||||
|
WoLfulus
|
||||||
|
w1th0utnam3
|
||||||
|
xissburg
|
||||||
|
zaucy
|
14
BUILD.bazel
Normal file
14
BUILD.bazel
Normal file
@ -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,
|
||||||
|
}),
|
||||||
|
)
|
325
CMakeLists.txt
Normal file
325
CMakeLists.txt
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
#
|
||||||
|
# EnTT
|
||||||
|
#
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.12.4)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Read project version
|
||||||
|
#
|
||||||
|
|
||||||
|
set(ENTT_VERSION_REGEX "#define ENTT_VERSION_.*[ \t]+(.+)")
|
||||||
|
file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/src/entt/config/version.h" ENTT_VERSION REGEX ${ENTT_VERSION_REGEX})
|
||||||
|
list(TRANSFORM ENTT_VERSION REPLACE ${ENTT_VERSION_REGEX} "\\1")
|
||||||
|
string(JOIN "." ENTT_VERSION ${ENTT_VERSION})
|
||||||
|
|
||||||
|
#
|
||||||
|
# Project configuration
|
||||||
|
#
|
||||||
|
|
||||||
|
project(
|
||||||
|
EnTT
|
||||||
|
VERSION ${ENTT_VERSION}
|
||||||
|
DESCRIPTION "Gaming meets modern C++ - a fast and reliable entity-component system (ECS) and much more"
|
||||||
|
HOMEPAGE_URL "https://github.com/skypjack/entt"
|
||||||
|
LANGUAGES C CXX
|
||||||
|
)
|
||||||
|
|
||||||
|
if(NOT CMAKE_BUILD_TYPE)
|
||||||
|
set(CMAKE_BUILD_TYPE Debug)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
message(VERBOSE "*")
|
||||||
|
message(VERBOSE "* ${PROJECT_NAME} v${PROJECT_VERSION} (${CMAKE_BUILD_TYPE})")
|
||||||
|
message(VERBOSE "* Copyright (c) 2017-2022 Michele Caini <michele.caini@gmail.com>")
|
||||||
|
message(VERBOSE "*")
|
||||||
|
|
||||||
|
#
|
||||||
|
# CMake stuff
|
||||||
|
#
|
||||||
|
|
||||||
|
list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Compiler stuff
|
||||||
|
#
|
||||||
|
|
||||||
|
option(ENTT_USE_LIBCPP "Use libc++ by adding -stdlib=libc++ flag if available." OFF)
|
||||||
|
option(ENTT_USE_SANITIZER "Enable sanitizers by adding -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined flags if available." OFF)
|
||||||
|
|
||||||
|
if(ENTT_USE_LIBCPP)
|
||||||
|
if(NOT WIN32)
|
||||||
|
include(CheckCXXSourceCompiles)
|
||||||
|
include(CMakePushCheckState)
|
||||||
|
|
||||||
|
cmake_push_check_state()
|
||||||
|
|
||||||
|
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -stdlib=libc++")
|
||||||
|
|
||||||
|
check_cxx_source_compiles("
|
||||||
|
#include<type_traits>
|
||||||
|
int main() { return std::is_same_v<int, char>; }
|
||||||
|
" ENTT_HAS_LIBCPP)
|
||||||
|
|
||||||
|
cmake_pop_check_state()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT ENTT_HAS_LIBCPP)
|
||||||
|
message(VERBOSE "The option ENTT_USE_LIBCPP is set but libc++ is not available. The flag will not be added to the target.")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(ENTT_USE_SANITIZER)
|
||||||
|
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
|
||||||
|
set(ENTT_HAS_SANITIZER TRUE CACHE BOOL "" FORCE)
|
||||||
|
mark_as_advanced(ENTT_HAS_SANITIZER)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT ENTT_HAS_SANITIZER)
|
||||||
|
message(VERBOSE "The option ENTT_USE_SANITIZER is set but sanitizer support is not available. The flags will not be added to the target.")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
#
|
||||||
|
# Add EnTT target
|
||||||
|
#
|
||||||
|
|
||||||
|
option(ENTT_INCLUDE_HEADERS "Add all EnTT headers to the EnTT target." OFF)
|
||||||
|
option(ENTT_INCLUDE_NATVIS "Add EnTT natvis files to the EnTT target." OFF)
|
||||||
|
|
||||||
|
if(ENTT_INCLUDE_NATVIS)
|
||||||
|
if(MSVC)
|
||||||
|
set(ENTT_HAS_NATVIS TRUE CACHE BOOL "" FORCE)
|
||||||
|
mark_as_advanced(ENTT_HAS_NATVIS)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT ENTT_HAS_NATVIS)
|
||||||
|
message(VERBOSE "The option ENTT_INCLUDE_NATVIS is set but natvis files are not supported. They will not be added to the target.")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
|
add_library(EnTT INTERFACE)
|
||||||
|
add_library(EnTT::EnTT ALIAS EnTT)
|
||||||
|
|
||||||
|
target_include_directories(
|
||||||
|
EnTT
|
||||||
|
INTERFACE
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src>
|
||||||
|
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_features(EnTT INTERFACE cxx_std_17)
|
||||||
|
|
||||||
|
if(ENTT_INCLUDE_HEADERS)
|
||||||
|
target_sources(
|
||||||
|
EnTT
|
||||||
|
INTERFACE
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/config/config.h>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/config/macro.h>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/config/version.h>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/container/dense_map.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/container/dense_set.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/container/fwd.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/core/algorithm.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/core/any.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/core/attribute.h>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/core/compressed_pair.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/core/enum.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/core/family.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/core/fwd.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/core/hashed_string.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/core/ident.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/core/iterator.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/core/memory.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/core/monostate.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/core/tuple.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/core/type_info.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/core/type_traits.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/core/utility.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/component.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/entity.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/fwd.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/group.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/handle.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/helper.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/observer.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/organizer.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/registry.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/runtime_view.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/snapshot.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/sparse_set.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/storage.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/storage_mixin.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/view.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/graph/adjacency_matrix.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/graph/dot.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/graph/flow.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/graph/fwd.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/locator/locator.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/meta/adl_pointer.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/meta/container.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/meta/context.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/meta/factory.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/meta/fwd.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/meta/meta.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/meta/node.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/meta/pointer.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/meta/policy.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/meta/range.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/meta/resolve.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/meta/template.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/meta/type_traits.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/meta/utility.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/platform/android-ndk-r17.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/poly/fwd.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/poly/poly.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/process/fwd.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/process/process.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/process/scheduler.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/resource/cache.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/resource/fwd.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/resource/loader.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/resource/resource.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/signal/delegate.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/signal/dispatcher.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/signal/emitter.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/signal/fwd.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/signal/sigh.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entt.hpp>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/fwd.hpp>
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(ENTT_HAS_NATVIS)
|
||||||
|
target_sources(
|
||||||
|
EnTT
|
||||||
|
INTERFACE
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/natvis/entt/config.natvis>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/natvis/entt/container.natvis>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/natvis/entt/core.natvis>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/natvis/entt/entity.natvis>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/natvis/entt/graph.natvis>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/natvis/entt/locator.natvis>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/natvis/entt/meta.natvis>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/natvis/entt/platform.natvis>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/natvis/entt/poly.natvis>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/natvis/entt/process.natvis>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/natvis/entt/resource.natvis>
|
||||||
|
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/natvis/entt/signal.natvis>
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(ENTT_HAS_SANITIZER)
|
||||||
|
target_compile_options(EnTT INTERFACE $<$<CONFIG:Debug>:-fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined>)
|
||||||
|
target_link_libraries(EnTT INTERFACE $<$<CONFIG:Debug>:-fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined>)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(ENTT_HAS_LIBCPP)
|
||||||
|
target_compile_options(EnTT BEFORE INTERFACE -stdlib=libc++)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
#
|
||||||
|
# Install pkg-config file
|
||||||
|
#
|
||||||
|
|
||||||
|
include(JoinPaths)
|
||||||
|
|
||||||
|
set(EnTT_PKGCONFIG ${CMAKE_CURRENT_BINARY_DIR}/entt.pc)
|
||||||
|
|
||||||
|
join_paths(EnTT_PKGCONFIG_INCLUDEDIR "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||||
|
|
||||||
|
configure_file(
|
||||||
|
${EnTT_SOURCE_DIR}/cmake/in/entt.pc.in
|
||||||
|
${EnTT_PKGCONFIG}
|
||||||
|
@ONLY
|
||||||
|
)
|
||||||
|
|
||||||
|
install(
|
||||||
|
FILES ${EnTT_PKGCONFIG}
|
||||||
|
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig
|
||||||
|
)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Install EnTT
|
||||||
|
#
|
||||||
|
|
||||||
|
include(CMakePackageConfigHelpers)
|
||||||
|
|
||||||
|
install(
|
||||||
|
TARGETS EnTT
|
||||||
|
EXPORT EnTTTargets
|
||||||
|
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
write_basic_package_version_file(
|
||||||
|
EnTTConfigVersion.cmake
|
||||||
|
VERSION ${PROJECT_VERSION}
|
||||||
|
COMPATIBILITY AnyNewerVersion
|
||||||
|
)
|
||||||
|
|
||||||
|
configure_package_config_file(
|
||||||
|
${EnTT_SOURCE_DIR}/cmake/in/EnTTConfig.cmake.in
|
||||||
|
EnTTConfig.cmake
|
||||||
|
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/EnTT/cmake
|
||||||
|
)
|
||||||
|
|
||||||
|
export(
|
||||||
|
EXPORT EnTTTargets
|
||||||
|
FILE ${CMAKE_CURRENT_BINARY_DIR}/EnTTTargets.cmake
|
||||||
|
NAMESPACE EnTT::
|
||||||
|
)
|
||||||
|
|
||||||
|
install(
|
||||||
|
EXPORT EnTTTargets
|
||||||
|
FILE EnTTTargets.cmake
|
||||||
|
DESTINATION ${CMAKE_INSTALL_LIBDIR}/EnTT/cmake
|
||||||
|
NAMESPACE EnTT::
|
||||||
|
)
|
||||||
|
|
||||||
|
install(
|
||||||
|
FILES
|
||||||
|
${PROJECT_BINARY_DIR}/EnTTConfig.cmake
|
||||||
|
${PROJECT_BINARY_DIR}/EnTTConfigVersion.cmake
|
||||||
|
DESTINATION ${CMAKE_INSTALL_LIBDIR}/EnTT/cmake
|
||||||
|
)
|
||||||
|
|
||||||
|
install(DIRECTORY src/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||||
|
|
||||||
|
export(PACKAGE EnTT)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Tests
|
||||||
|
#
|
||||||
|
|
||||||
|
option(ENTT_BUILD_TESTING "Enable building tests." OFF)
|
||||||
|
|
||||||
|
if(ENTT_BUILD_TESTING)
|
||||||
|
option(ENTT_FIND_GTEST_PACKAGE "Enable finding gtest package." OFF)
|
||||||
|
option(ENTT_BUILD_BENCHMARK "Build benchmark." OFF)
|
||||||
|
option(ENTT_BUILD_EXAMPLE "Build examples." OFF)
|
||||||
|
option(ENTT_BUILD_LIB "Build lib tests." OFF)
|
||||||
|
option(ENTT_BUILD_SNAPSHOT "Build snapshot test with Cereal." OFF)
|
||||||
|
|
||||||
|
set(ENTT_ID_TYPE std::uint32_t CACHE STRING "Type of identifiers to use for the tests")
|
||||||
|
set(ENTT_CXX_STD cxx_std_17 CACHE STRING "C++ standard revision to use for the tests")
|
||||||
|
|
||||||
|
include(CTest)
|
||||||
|
enable_testing()
|
||||||
|
add_subdirectory(test)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
#
|
||||||
|
# Documentation
|
||||||
|
#
|
||||||
|
|
||||||
|
option(ENTT_BUILD_DOCS "Enable building with documentation." OFF)
|
||||||
|
|
||||||
|
if(ENTT_BUILD_DOCS)
|
||||||
|
find_package(Doxygen 1.8)
|
||||||
|
|
||||||
|
if(DOXYGEN_FOUND)
|
||||||
|
add_subdirectory(docs)
|
||||||
|
endif()
|
||||||
|
endif()
|
43
CONTRIBUTING.md
Normal file
43
CONTRIBUTING.md
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
First of all, thank you very much for taking the time to contribute to the
|
||||||
|
`EnTT` library.<br/>
|
||||||
|
How to do it mostly depends on the type of contribution:
|
||||||
|
|
||||||
|
* If you have a question, **please** ensure there isn't already an answer for
|
||||||
|
you by searching on GitHub under
|
||||||
|
[issues](https://github.com/skypjack/entt/issues). Do not forget to search
|
||||||
|
also through the closed ones. If you are unable to find a proper answer, feel
|
||||||
|
free to [open a new issue](https://github.com/skypjack/entt/issues/new).
|
||||||
|
Usually, questions are marked as such and closed in a few days.
|
||||||
|
|
||||||
|
* If you want to fix a typo in the inline documentation or in the README file,
|
||||||
|
if you want to add some new sections or if you want to help me with the
|
||||||
|
language by reviewing what I wrote so far (I'm not a native speaker after
|
||||||
|
all), **please** open a new
|
||||||
|
[pull request](https://github.com/skypjack/entt/pulls) with your changes.
|
||||||
|
|
||||||
|
* If you found a bug, **please** ensure there isn't already an answer for you by
|
||||||
|
searching on GitHub under [issues](https://github.com/skypjack/entt/issues).
|
||||||
|
If you are unable to find an open issue addressing the problem, feel free to
|
||||||
|
[open a new one](https://github.com/skypjack/entt/issues/new). **Please**, do
|
||||||
|
not forget to carefully describe how to reproduce the problem, then add all
|
||||||
|
the information about the system on which you are experiencing it and point
|
||||||
|
out the version of `EnTT` you are using (tag or commit).
|
||||||
|
|
||||||
|
* If you found a bug and you wrote a patch to fix it, open a new
|
||||||
|
[pull request](https://github.com/skypjack/entt/pulls) with your code.
|
||||||
|
**Please**, add some tests to avoid regressions in future if possible, it
|
||||||
|
would be really appreciated. Note that the `EnTT` library has a
|
||||||
|
[coverage at 100%](https://coveralls.io/github/skypjack/entt?branch=master)
|
||||||
|
(at least it was at 100% at the time I wrote this file) and this is the reason
|
||||||
|
for which you can be confident with using it in a production environment.
|
||||||
|
|
||||||
|
* If you want to propose a new feature and you know how to code it, **please**
|
||||||
|
do not issue directly a pull request. Before to do it,
|
||||||
|
[create a new issue](https://github.com/skypjack/entt/issues/new) to discuss
|
||||||
|
your proposal. Other users could be interested in your idea and the discussion
|
||||||
|
that will follow can refine it and therefore give us a better solution.
|
||||||
|
|
||||||
|
* If you want to request a new feature, I'm available for hiring. Take a look at
|
||||||
|
[my profile](https://github.com/skypjack) and feel free to write me.
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2017-2022 Michele Caini, author of EnTT
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copy of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copy or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
431
README.md
Normal file
431
README.md
Normal file
@ -0,0 +1,431 @@
|
|||||||
|
![EnTT: Gaming meets modern C++](https://user-images.githubusercontent.com/1812216/103550016-90752280-4ea8-11eb-8667-12ed2219e137.png)
|
||||||
|
|
||||||
|
<!--
|
||||||
|
@cond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
[![Build Status](https://github.com/skypjack/entt/workflows/build/badge.svg)](https://github.com/skypjack/entt/actions)
|
||||||
|
[![Coverage](https://codecov.io/gh/skypjack/entt/branch/master/graph/badge.svg)](https://codecov.io/gh/skypjack/entt)
|
||||||
|
[![Try online](https://img.shields.io/badge/try-online-brightgreen)](https://godbolt.org/z/zxW73f)
|
||||||
|
[![Documentation](https://img.shields.io/badge/docs-docsforge-blue)](http://entt.docsforge.com/)
|
||||||
|
[![Gitter chat](https://badges.gitter.im/skypjack/entt.png)](https://gitter.im/skypjack/entt)
|
||||||
|
[![Discord channel](https://img.shields.io/discord/707607951396962417?logo=discord)](https://discord.gg/5BjPWBd)
|
||||||
|
[![Donate](https://img.shields.io/badge/donate-paypal-blue.svg)](https://www.paypal.me/skypjack)
|
||||||
|
|
||||||
|
> `EnTT` has been a dream so far, we haven't found a single bug to date and it's
|
||||||
|
> super easy to work with
|
||||||
|
>
|
||||||
|
> -- Every EnTT User Ever
|
||||||
|
|
||||||
|
`EnTT` is a header-only, tiny and easy to use library for game programming and
|
||||||
|
much more written in **modern C++**.<br/>
|
||||||
|
[Among others](https://github.com/skypjack/entt/wiki/EnTT-in-Action), it's used
|
||||||
|
in [**Minecraft**](https://minecraft.net/en-us/attribution/) by Mojang, the
|
||||||
|
[**ArcGIS Runtime SDKs**](https://developers.arcgis.com/arcgis-runtime/) by Esri
|
||||||
|
and the amazing [**Ragdoll**](https://ragdolldynamics.com/).<br/>
|
||||||
|
If you don't see your project in the list, please open an issue, submit a PR or
|
||||||
|
add the [#entt](https://github.com/topics/entt) tag to your _topics_! :+1:
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Do you want to **keep up with changes** or do you have a **question** that
|
||||||
|
doesn't require you to open an issue?<br/>
|
||||||
|
Join the [gitter channel](https://gitter.im/skypjack/entt) and the
|
||||||
|
[discord server](https://discord.gg/5BjPWBd), meet other users like you. The
|
||||||
|
more we are, the better for everyone.<br/>
|
||||||
|
Don't forget to check the
|
||||||
|
[FAQs](https://github.com/skypjack/entt/wiki/Frequently-Asked-Questions) and the
|
||||||
|
[wiki](https://github.com/skypjack/entt/wiki) too. Your answers may already be
|
||||||
|
there.
|
||||||
|
|
||||||
|
Do you want to support `EnTT`? Consider becoming a
|
||||||
|
[**sponsor**](https://github.com/users/skypjack/sponsorship).
|
||||||
|
Many thanks to [these people](https://skypjack.github.io/sponsorship/) and
|
||||||
|
**special** thanks to:
|
||||||
|
|
||||||
|
[![mojang](https://user-images.githubusercontent.com/1812216/106253145-67ca1980-6217-11eb-9c0b-d93561b37098.png)](https://mojang.com)
|
||||||
|
[![imgly](https://user-images.githubusercontent.com/1812216/106253726-271ed000-6218-11eb-98e0-c9c681925770.png)](https://img.ly/)
|
||||||
|
|
||||||
|
# Table of Contents
|
||||||
|
|
||||||
|
* [Introduction](#introduction)
|
||||||
|
* [Code Example](#code-example)
|
||||||
|
* [Motivation](#motivation)
|
||||||
|
* [Performance](#performance)
|
||||||
|
* [Integration](#integration)
|
||||||
|
* [Requirements](#requirements)
|
||||||
|
* [CMake](#cmake)
|
||||||
|
* [Natvis support](#natvis-support)
|
||||||
|
* [Packaging Tools](#packaging-tools)
|
||||||
|
* [pkg-config](#pkg-config)
|
||||||
|
* [Documentation](#documentation)
|
||||||
|
* [Tests](#tests)
|
||||||
|
* [EnTT in Action](#entt-in-action)
|
||||||
|
* [Contributors](#contributors)
|
||||||
|
* [License](#license)
|
||||||
|
<!--
|
||||||
|
@endcond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Introduction
|
||||||
|
|
||||||
|
The entity-component-system (also known as _ECS_) is an architectural pattern
|
||||||
|
used mostly in game development. For further details:
|
||||||
|
|
||||||
|
* [Entity Systems Wiki](http://entity-systems.wikidot.com/)
|
||||||
|
* [Evolve Your Hierarchy](http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/)
|
||||||
|
* [ECS on Wikipedia](https://en.wikipedia.org/wiki/Entity%E2%80%93component%E2%80%93system)
|
||||||
|
|
||||||
|
This project started off as a pure entity-component system. Over time the
|
||||||
|
codebase has grown as more and more classes and functionalities were added.<br/>
|
||||||
|
Here is a brief, yet incomplete list of what it offers today:
|
||||||
|
|
||||||
|
* Built-in **RTTI system** mostly similar to the standard one.
|
||||||
|
* A `constexpr` utility for human-readable **resource names**.
|
||||||
|
* Minimal **configuration system** built using the monostate pattern.
|
||||||
|
* Incredibly fast **entity-component system** with its own _pay for what you
|
||||||
|
use_ policy, unconstrained component types with optional pointer stability and
|
||||||
|
hooks for storage customization.
|
||||||
|
* Views and groups to iterate entities and components and allow different access
|
||||||
|
patterns, from **perfect SoA** to fully random.
|
||||||
|
* A lot of **facilities** built on top of the entity-component system to help
|
||||||
|
the users and avoid reinventing the wheel.
|
||||||
|
* General purpose **execution graph builder** for optimal scheduling.
|
||||||
|
* The smallest and most basic implementation of a **service locator** ever seen.
|
||||||
|
* A built-in, non-intrusive and macro-free runtime **reflection system**.
|
||||||
|
* **Static polymorphism** made simple and within everyone's reach.
|
||||||
|
* A few homemade containers, like a sparse set based **hash map**.
|
||||||
|
* A **cooperative scheduler** for processes of any type.
|
||||||
|
* All that is needed for **resource management** (cache, loaders, handles).
|
||||||
|
* Delegates, **signal handlers** and a tiny event dispatcher.
|
||||||
|
* A general purpose **event emitter** as a CRTP idiom based class template.
|
||||||
|
* And **much more**! Check out the
|
||||||
|
[**wiki**](https://github.com/skypjack/entt/wiki).
|
||||||
|
|
||||||
|
Consider this list a work in progress as well as the project. The whole API is
|
||||||
|
fully documented in-code for those who are brave enough to read it.<br/>
|
||||||
|
Please, do note that all tools are also DLL-friendly now and run smoothly across
|
||||||
|
boundaries.
|
||||||
|
|
||||||
|
One thing known to most is that `EnTT` is also used in **Minecraft**.<br/>
|
||||||
|
Given that the game is available literally everywhere, I can confidently say
|
||||||
|
that the library has been sufficiently tested on every platform that can come to
|
||||||
|
mind.
|
||||||
|
|
||||||
|
## Code Example
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <entt/entt.hpp>
|
||||||
|
|
||||||
|
struct position {
|
||||||
|
float x;
|
||||||
|
float y;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct velocity {
|
||||||
|
float dx;
|
||||||
|
float dy;
|
||||||
|
};
|
||||||
|
|
||||||
|
void update(entt::registry ®istry) {
|
||||||
|
auto view = registry.view<const position, velocity>();
|
||||||
|
|
||||||
|
// use a callback
|
||||||
|
view.each([](const auto &pos, auto &vel) { /* ... */ });
|
||||||
|
|
||||||
|
// use an extended callback
|
||||||
|
view.each([](const auto entity, const auto &pos, auto &vel) { /* ... */ });
|
||||||
|
|
||||||
|
// use a range-for
|
||||||
|
for(auto [entity, pos, vel]: view.each()) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// use forward iterators and get only the components of interest
|
||||||
|
for(auto entity: view) {
|
||||||
|
auto &vel = view.get<velocity>(entity);
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
entt::registry registry;
|
||||||
|
|
||||||
|
for(auto i = 0u; i < 10u; ++i) {
|
||||||
|
const auto entity = registry.create();
|
||||||
|
registry.emplace<position>(entity, i * 1.f, i * 1.f);
|
||||||
|
if(i % 2 == 0) { registry.emplace<velocity>(entity, i * .1f, i * .1f); }
|
||||||
|
}
|
||||||
|
|
||||||
|
update(registry);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
I started developing `EnTT` for the _wrong_ reason: my goal was to design an
|
||||||
|
entity-component system to beat another well known open source library both in
|
||||||
|
terms of performance and possibly memory usage.<br/>
|
||||||
|
In the end, I did it, but it wasn't very satisfying. Actually it wasn't
|
||||||
|
satisfying at all. The fastest and nothing more, fairly little indeed. When I
|
||||||
|
realized it, I tried hard to keep intact the great performance of `EnTT` and to
|
||||||
|
add all the features I wanted to see in *my own library* at the same time.
|
||||||
|
|
||||||
|
Nowadays, `EnTT` is finally what I was looking for: still faster than its
|
||||||
|
_competitors_, lower memory usage in the average case, a really good API and an
|
||||||
|
amazing set of features. And even more, of course.
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
The proposed entity-component system is incredibly fast to iterate entities and
|
||||||
|
components, this is a fact. Some compilers make a lot of optimizations because
|
||||||
|
of how `EnTT` works, some others aren't that good. In general, if we consider
|
||||||
|
real world cases, `EnTT` is somewhere between a bit and much faster than many of
|
||||||
|
the other solutions around, although I couldn't check them all for obvious
|
||||||
|
reasons.
|
||||||
|
|
||||||
|
If you are interested, you can compile the `benchmark` test in release mode (to
|
||||||
|
enable compiler optimizations, otherwise it would make little sense) by setting
|
||||||
|
the `ENTT_BUILD_BENCHMARK` option of `CMake` to `ON`, then evaluate yourself
|
||||||
|
whether you're satisfied with the results or not.
|
||||||
|
|
||||||
|
Honestly I got tired of updating the README file whenever there is an
|
||||||
|
improvement.<br/>
|
||||||
|
There are already a lot of projects out there that use `EnTT` as a basis for
|
||||||
|
comparison (this should already tell you a lot). Many of these benchmarks are
|
||||||
|
completely wrong, many others are simply incomplete, good at omitting some
|
||||||
|
information and using the wrong function to compare a given feature. Certainly
|
||||||
|
there are also good ones but they age quickly if nobody updates them, especially
|
||||||
|
when the library they are dealing with is actively developed.
|
||||||
|
|
||||||
|
The choice to use `EnTT` should be based on its carefully designed API, its
|
||||||
|
set of features and the general performance, **not** because some single
|
||||||
|
benchmark shows it to be the fastest tool available.
|
||||||
|
|
||||||
|
In the future I'll likely try to get even better performance while still adding
|
||||||
|
new features, mainly for fun.<br/>
|
||||||
|
If you want to contribute and/or have suggestions, feel free to make a PR or
|
||||||
|
open an issue to discuss your idea.
|
||||||
|
|
||||||
|
# Integration
|
||||||
|
|
||||||
|
`EnTT` is a header-only library. This means that including the `entt.hpp` header
|
||||||
|
is enough to include the library as a whole and use it. For those who are
|
||||||
|
interested only in the entity-component system, consider to include the sole
|
||||||
|
`entity/registry.hpp` header instead.<br/>
|
||||||
|
It's a matter of adding the following line to the top of a file:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <entt/entt.hpp>
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the line below to include only the entity-component system instead:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <entt/entity/registry.hpp>
|
||||||
|
```
|
||||||
|
|
||||||
|
Then pass the proper `-I` argument to the compiler to add the `src` directory to
|
||||||
|
the include paths.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
To be able to use `EnTT`, users must provide a full-featured compiler that
|
||||||
|
supports at least C++17.<br/>
|
||||||
|
The requirements below are mandatory to compile the tests and to extract the
|
||||||
|
documentation:
|
||||||
|
|
||||||
|
* `CMake` version 3.7 or later.
|
||||||
|
* `Doxygen` version 1.8 or later.
|
||||||
|
|
||||||
|
Alternatively, [Bazel](https://bazel.build) is also supported as a build system
|
||||||
|
(credits to [zaucy](https://github.com/zaucy) who offered to maintain it).<br/>
|
||||||
|
In the documentation below I'll still refer to `CMake`, this being the official
|
||||||
|
build system of the library.
|
||||||
|
|
||||||
|
## CMake
|
||||||
|
|
||||||
|
To use `EnTT` from a `CMake` project, just link an existing target to the
|
||||||
|
`EnTT::EnTT` alias.<br/>
|
||||||
|
The library offers everything you need for locating (as in `find_package`),
|
||||||
|
embedding (as in `add_subdirectory`), fetching (as in `FetchContent`) or using
|
||||||
|
it in many of the ways that you can think of and that involve `CMake`.<br/>
|
||||||
|
Covering all possible cases would require a treaty and not a simple README file,
|
||||||
|
but I'm confident that anyone reading this section also knows what it's about
|
||||||
|
and can use `EnTT` from a `CMake` project without problems.
|
||||||
|
|
||||||
|
## Natvis support
|
||||||
|
|
||||||
|
When using `CMake`, just enable the option `ENTT_INCLUDE_NATVIS` and enjoy
|
||||||
|
it.<br/>
|
||||||
|
Otherwise, most of the tools are covered via Natvis and all files can be found
|
||||||
|
in the `natvis` directory, divided by module.<br/>
|
||||||
|
If you spot errors or have suggestions, any contribution is welcome!
|
||||||
|
|
||||||
|
## Packaging Tools
|
||||||
|
|
||||||
|
`EnTT` is available for some of the most known packaging tools. In particular:
|
||||||
|
|
||||||
|
* [`Conan`](https://github.com/conan-io/conan-center-index), the C/C++ Package
|
||||||
|
Manager for Developers.
|
||||||
|
|
||||||
|
* [`vcpkg`](https://github.com/Microsoft/vcpkg), Microsoft VC++ Packaging
|
||||||
|
Tool.<br/>
|
||||||
|
You can download and install `EnTT` in just a few simple steps:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ git clone https://github.com/Microsoft/vcpkg.git
|
||||||
|
$ cd vcpkg
|
||||||
|
$ ./bootstrap-vcpkg.sh
|
||||||
|
$ ./vcpkg integrate install
|
||||||
|
$ vcpkg install entt
|
||||||
|
```
|
||||||
|
|
||||||
|
Or you can use the `experimental` feature to test the latest changes:
|
||||||
|
|
||||||
|
```
|
||||||
|
vcpkg install entt[experimental] --head
|
||||||
|
```
|
||||||
|
|
||||||
|
The `EnTT` port in `vcpkg` is kept up to date by Microsoft team members and
|
||||||
|
community contributors.<br/>
|
||||||
|
If the version is out of date, please
|
||||||
|
[create an issue or pull request](https://github.com/Microsoft/vcpkg) on the
|
||||||
|
`vcpkg` repository.
|
||||||
|
|
||||||
|
* [`Homebrew`](https://github.com/skypjack/homebrew-entt), the missing package
|
||||||
|
manager for macOS.<br/>
|
||||||
|
Available as a homebrew formula. Just type the following to install it:
|
||||||
|
|
||||||
|
```
|
||||||
|
brew install skypjack/entt/entt
|
||||||
|
```
|
||||||
|
|
||||||
|
* [`build2`](https://build2.org), build toolchain for developing and packaging C
|
||||||
|
and C++ code.<br/>
|
||||||
|
In order to use the [`entt`](https://cppget.org/entt) package in a `build2`
|
||||||
|
project, add the following line or a similar one to the `manifest` file:
|
||||||
|
|
||||||
|
```
|
||||||
|
depends: entt ^3.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Also check that the configuration refers to a valid repository, so that the
|
||||||
|
package can be found by `build2`:
|
||||||
|
|
||||||
|
* [`cppget.org`](https://cppget.org), the open-source community central
|
||||||
|
repository, accessible as `https://pkg.cppget.org/1/stable`.
|
||||||
|
|
||||||
|
* [Package source repository](https://github.com/build2-packaging/entt):
|
||||||
|
accessible as either `https://github.com/build2-packaging/entt.git` or
|
||||||
|
`ssh://git@github.com/build2-packaging/entt.git`.
|
||||||
|
Feel free to [report issues](https://github.com/build2-packaging/entt) with
|
||||||
|
this package.
|
||||||
|
|
||||||
|
Both can be used with `bpkg add-repo` or added in a project
|
||||||
|
`repositories.manifest`. See the official
|
||||||
|
[documentation](https://build2.org/build2-toolchain/doc/build2-toolchain-intro.xhtml#guide-repositories)
|
||||||
|
for more details.
|
||||||
|
|
||||||
|
Consider this list a work in progress and help me to make it longer if you like.
|
||||||
|
|
||||||
|
## pkg-config
|
||||||
|
|
||||||
|
`EnTT` also supports `pkg-config` (for some definition of _supports_ at least).
|
||||||
|
A suitable file called `entt.pc` is generated and installed in a proper
|
||||||
|
directory when running `CMake`.<br/>
|
||||||
|
This should also make it easier to use with tools such as `Meson` or similar.
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
|
||||||
|
The documentation is based on [doxygen](http://www.doxygen.nl/). To build it:
|
||||||
|
|
||||||
|
$ cd build
|
||||||
|
$ cmake .. -DENTT_BUILD_DOCS=ON
|
||||||
|
$ make
|
||||||
|
|
||||||
|
The API reference will be created in HTML format within the directory
|
||||||
|
`build/docs/html`. To navigate it with your favorite browser:
|
||||||
|
|
||||||
|
$ cd build
|
||||||
|
$ your_favorite_browser docs/html/index.html
|
||||||
|
|
||||||
|
<!--
|
||||||
|
@cond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
The same version is also available [online](https://skypjack.github.io/entt/)
|
||||||
|
for the latest release, that is the last stable tag. If you are looking for
|
||||||
|
something more pleasing to the eye, consider reading the nice-looking version
|
||||||
|
available on [docsforge](https://entt.docsforge.com/): same documentation, much
|
||||||
|
more pleasant to read.<br/>
|
||||||
|
Moreover, there exists a [wiki](https://github.com/skypjack/entt/wiki) dedicated
|
||||||
|
to the project where users can find all related documentation pages.
|
||||||
|
<!--
|
||||||
|
@endcond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
|
||||||
|
To compile and run the tests, `EnTT` requires *googletest*.<br/>
|
||||||
|
`cmake` will download and compile the library before compiling anything else.
|
||||||
|
In order to build the tests, set the `CMake` option `ENTT_BUILD_TESTING` to
|
||||||
|
`ON`.
|
||||||
|
|
||||||
|
To build the most basic set of tests:
|
||||||
|
|
||||||
|
* `$ cd build`
|
||||||
|
* `$ cmake -DENTT_BUILD_TESTING=ON ..`
|
||||||
|
* `$ make`
|
||||||
|
* `$ make test`
|
||||||
|
|
||||||
|
Note that benchmarks are not part of this set.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
@cond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
# EnTT in Action
|
||||||
|
|
||||||
|
`EnTT` is widely used in private and commercial applications. I cannot even
|
||||||
|
mention most of them because of some signatures I put on some documents time
|
||||||
|
ago. Fortunately, there are also people who took the time to implement open
|
||||||
|
source projects based on `EnTT` and did not hold back when it came to
|
||||||
|
documenting them.
|
||||||
|
|
||||||
|
[Here](https://github.com/skypjack/entt/wiki/EnTT-in-Action) you can find an
|
||||||
|
incomplete list of games, applications and articles that can be used as a
|
||||||
|
reference.
|
||||||
|
|
||||||
|
If you know of other resources out there that are about `EnTT`, feel free to
|
||||||
|
open an issue or a PR and I'll be glad to add them to the list.
|
||||||
|
|
||||||
|
# Contributors
|
||||||
|
|
||||||
|
Requests for features, PRs, suggestions ad feedback are highly appreciated.
|
||||||
|
|
||||||
|
If you find you can help and want to contribute to the project with your
|
||||||
|
experience or you do want to get part of the project for some other reason, feel
|
||||||
|
free to contact me directly (you can find the mail in the
|
||||||
|
[profile](https://github.com/skypjack)).<br/>
|
||||||
|
I can't promise that each and every contribution will be accepted, but I can
|
||||||
|
assure that I'll do my best to take them all as soon as possible.
|
||||||
|
|
||||||
|
If you decide to participate, please see the guidelines for
|
||||||
|
[contributing](CONTRIBUTING.md) before to create issues or pull
|
||||||
|
requests.<br/>
|
||||||
|
Take also a look at the
|
||||||
|
[contributors list](https://github.com/skypjack/entt/blob/master/AUTHORS) to
|
||||||
|
know who has participated so far.
|
||||||
|
<!--
|
||||||
|
@endcond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
|
||||||
|
# License
|
||||||
|
|
||||||
|
Code and documentation Copyright (c) 2017-2022 Michele Caini.<br/>
|
||||||
|
Colorful logo Copyright (c) 2018-2021 Richard Caseres.
|
||||||
|
|
||||||
|
Code released under
|
||||||
|
[the MIT license](https://github.com/skypjack/entt/blob/master/LICENSE).<br/>
|
||||||
|
Documentation released under
|
||||||
|
[CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).<br/>
|
||||||
|
All logos released under
|
||||||
|
[CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/).
|
27
TODO
Normal file
27
TODO
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
* debugging tools (#60): the issue online already contains interesting tips on this, look at it
|
||||||
|
* work stealing job system (see #100) + mt scheduler based on const awareness for types
|
||||||
|
|
||||||
|
EXAMPLES
|
||||||
|
* filter on runtime values/variables (not only types)
|
||||||
|
* support to polymorphic types (see #859)
|
||||||
|
|
||||||
|
DOC:
|
||||||
|
* storage<void>
|
||||||
|
* custom storage/view
|
||||||
|
* examples (and credits) from @alanjfs :)
|
||||||
|
* update entity doc when the storage based model is in place
|
||||||
|
|
||||||
|
TODO (high prio):
|
||||||
|
* remove the static storage from the const assure in the registry
|
||||||
|
|
||||||
|
WIP:
|
||||||
|
* get rid of observers, storage based views made them pointless - document alternatives
|
||||||
|
* add storage getter for filters to views and groups
|
||||||
|
* exploit the tombstone mechanism to allow enabling/disabling entities (see bump, compact and clear for further details)
|
||||||
|
* basic_storage::bind for cross-registry setups (see and remove todo from entity_copy.cpp)
|
||||||
|
* process scheduler: reviews, use free lists internally
|
||||||
|
* dedicated entity storage, in-place O(1) release/destroy for non-orphaned entities, out-of-sync model
|
||||||
|
* entity-only and exclude-only views (both solved with entity storage and storage<void>)
|
||||||
|
* custom allocators all over (registry, ...)
|
||||||
|
* add test for maximum number of entities reached
|
||||||
|
* deprecate non-owning groups in favor of owning views and view packs, introduce lazy owning views
|
2
build/.gitignore
vendored
Normal file
2
build/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
5
cmake/in/EnTTConfig.cmake.in
Normal file
5
cmake/in/EnTTConfig.cmake.in
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
@PACKAGE_INIT@
|
||||||
|
|
||||||
|
set(EnTT_VERSION "@PROJECT_VERSION@")
|
||||||
|
include("${CMAKE_CURRENT_LIST_DIR}/EnTTTargets.cmake")
|
||||||
|
check_required_components("@PROJECT_NAME@")
|
8
cmake/in/entt.pc.in
Normal file
8
cmake/in/entt.pc.in
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
prefix=@CMAKE_INSTALL_PREFIX@
|
||||||
|
includedir=@EnTT_PKGCONFIG_INCLUDEDIR@
|
||||||
|
|
||||||
|
Name: EnTT
|
||||||
|
Description: Gaming meets modern C++
|
||||||
|
Url: https://github.com/skypjack/entt
|
||||||
|
Version: @ENTT_VERSION@
|
||||||
|
Cflags: -I${includedir}
|
23
cmake/modules/JoinPaths.cmake
Normal file
23
cmake/modules/JoinPaths.cmake
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# This module provides function for joining paths
|
||||||
|
# known from most languages
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: (MIT OR CC0-1.0)
|
||||||
|
# Copyright 2020 Jan Tojnar
|
||||||
|
# https://github.com/jtojnar/cmake-snips
|
||||||
|
#
|
||||||
|
# Modelled after Python’s os.path.join
|
||||||
|
# https://docs.python.org/3.7/library/os.path.html#os.path.join
|
||||||
|
# Windows not supported
|
||||||
|
function(join_paths joined_path first_path_segment)
|
||||||
|
set(temp_path "${first_path_segment}")
|
||||||
|
foreach(current_segment IN LISTS ARGN)
|
||||||
|
if(NOT ("${current_segment}" STREQUAL ""))
|
||||||
|
if(IS_ABSOLUTE "${current_segment}")
|
||||||
|
set(temp_path "${current_segment}")
|
||||||
|
else()
|
||||||
|
set(temp_path "${temp_path}/${current_segment}")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
set(${joined_path} "${temp_path}" PARENT_SCOPE)
|
||||||
|
endfunction()
|
37
conan/build.py
Normal file
37
conan/build.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from cpt.packager import ConanMultiPackager
|
||||||
|
import os
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
username = os.getenv("GITHUB_ACTOR")
|
||||||
|
tag_version = os.getenv("GITHUB_REF")
|
||||||
|
tag_package = os.getenv("GITHUB_REPOSITORY")
|
||||||
|
login_username = os.getenv("CONAN_LOGIN_USERNAME")
|
||||||
|
package_version = tag_version.replace("refs/tags/v", "")
|
||||||
|
package_name = tag_package.replace("skypjack/", "")
|
||||||
|
reference = "{}/{}".format(package_name, package_version)
|
||||||
|
channel = os.getenv("CONAN_CHANNEL", "stable")
|
||||||
|
upload = os.getenv("CONAN_UPLOAD")
|
||||||
|
stable_branch_pattern = os.getenv("CONAN_STABLE_BRANCH_PATTERN", r"v\d+\.\d+\.\d+.*")
|
||||||
|
test_folder = os.getenv("CPT_TEST_FOLDER", os.path.join("conan", "test_package"))
|
||||||
|
upload_only_when_stable = os.getenv("CONAN_UPLOAD_ONLY_WHEN_STABLE", True)
|
||||||
|
disable_shared = os.getenv("CONAN_DISABLE_SHARED_BUILD", "False")
|
||||||
|
|
||||||
|
builder = ConanMultiPackager(username=username,
|
||||||
|
reference=reference,
|
||||||
|
channel=channel,
|
||||||
|
login_username=login_username,
|
||||||
|
upload=upload,
|
||||||
|
stable_branch_pattern=stable_branch_pattern,
|
||||||
|
upload_only_when_stable=upload_only_when_stable,
|
||||||
|
test_folder=test_folder)
|
||||||
|
builder.add()
|
||||||
|
|
||||||
|
filtered_builds = []
|
||||||
|
for settings, options, env_vars, build_requires, reference in builder.items:
|
||||||
|
if disable_shared == "False" or not options["{}:shared".format(package_name)]:
|
||||||
|
filtered_builds.append([settings, options, env_vars, build_requires])
|
||||||
|
builder.builds = filtered_builds
|
||||||
|
|
||||||
|
builder.run()
|
7
conan/ci/build.sh
Normal file
7
conan/ci/build.sh
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
set -x
|
||||||
|
|
||||||
|
conan user
|
||||||
|
python conan/build.py
|
6
conan/ci/install.sh
Normal file
6
conan/ci/install.sh
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
set -x
|
||||||
|
|
||||||
|
pip install -U conan_package_tools conan
|
13
conan/test_package/CMakeLists.txt
Normal file
13
conan/test_package/CMakeLists.txt
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.7.2)
|
||||||
|
project(test_package)
|
||||||
|
|
||||||
|
set(CMAKE_VERBOSE_MAKEFILE TRUE)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
|
||||||
|
conan_basic_setup()
|
||||||
|
|
||||||
|
add_executable(${PROJECT_NAME} test_package.cpp)
|
||||||
|
target_link_libraries(${PROJECT_NAME} ${CONAN_LIBS})
|
19
conan/test_package/conanfile.py
Normal file
19
conan/test_package/conanfile.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from conans import ConanFile, CMake
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
class TestPackageConan(ConanFile):
|
||||||
|
settings = "os", "compiler", "build_type", "arch"
|
||||||
|
generators = "cmake"
|
||||||
|
|
||||||
|
def build(self):
|
||||||
|
cmake = CMake(self)
|
||||||
|
cmake.configure()
|
||||||
|
cmake.build()
|
||||||
|
|
||||||
|
def test(self):
|
||||||
|
bin_path = os.path.join("bin", "test_package")
|
||||||
|
self.run(bin_path, run_environment=True)
|
56
conan/test_package/test_package.cpp
Normal file
56
conan/test_package/test_package.cpp
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
#include <entt/entt.hpp>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
struct position {
|
||||||
|
float x;
|
||||||
|
float y;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct velocity {
|
||||||
|
float dx;
|
||||||
|
float dy;
|
||||||
|
};
|
||||||
|
|
||||||
|
void update(entt::registry ®istry) {
|
||||||
|
auto view = registry.view<position, velocity>();
|
||||||
|
|
||||||
|
for(auto entity: view) {
|
||||||
|
// gets only the components that are going to be used ...
|
||||||
|
|
||||||
|
auto &vel = view.get<velocity>(entity);
|
||||||
|
|
||||||
|
vel.dx = 0.;
|
||||||
|
vel.dy = 0.;
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void update(std::uint64_t dt, entt::registry ®istry) {
|
||||||
|
registry.view<position, velocity>().each([dt](auto &pos, auto &vel) {
|
||||||
|
// gets all the components of the view at once ...
|
||||||
|
|
||||||
|
pos.x += vel.dx * dt;
|
||||||
|
pos.y += vel.dy * dt;
|
||||||
|
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
entt::registry registry;
|
||||||
|
std::uint64_t dt = 16;
|
||||||
|
|
||||||
|
for(auto i = 0; i < 10; ++i) {
|
||||||
|
auto entity = registry.create();
|
||||||
|
registry.emplace<position>(entity, i * 1.f, i * 1.f);
|
||||||
|
if(i % 2 == 0) { registry.emplace<velocity>(entity, i * .1f, i * .1f); }
|
||||||
|
}
|
||||||
|
|
||||||
|
update(dt, registry);
|
||||||
|
update(registry);
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
27
conanfile.py
Normal file
27
conanfile.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from conans import ConanFile
|
||||||
|
|
||||||
|
|
||||||
|
class EnttConan(ConanFile):
|
||||||
|
name = "entt"
|
||||||
|
description = "Gaming meets modern C++ - a fast and reliable entity-component system (ECS) and much more "
|
||||||
|
topics = ("conan," "entt", "gaming", "entity", "ecs")
|
||||||
|
url = "https://github.com/skypjack/entt"
|
||||||
|
homepage = url
|
||||||
|
author = "Michele Caini <michele.caini@gmail.com>"
|
||||||
|
license = "MIT"
|
||||||
|
exports = ["LICENSE"]
|
||||||
|
exports_sources = ["src/*"]
|
||||||
|
no_copy_source = True
|
||||||
|
|
||||||
|
def package(self):
|
||||||
|
self.copy(pattern="LICENSE", dst="licenses")
|
||||||
|
self.copy(pattern="*", dst="include", src="src", keep_path=True)
|
||||||
|
|
||||||
|
def package_info(self):
|
||||||
|
if not self.in_local_cache:
|
||||||
|
self.cpp_info.includedirs = ["src"]
|
||||||
|
|
||||||
|
def package_id(self):
|
||||||
|
self.info.header_only()
|
40
docs/CMakeLists.txt
Normal file
40
docs/CMakeLists.txt
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#
|
||||||
|
# Doxygen configuration (documentation)
|
||||||
|
#
|
||||||
|
|
||||||
|
set(DOXY_DEPS_DIRECTORY ${EnTT_SOURCE_DIR}/deps)
|
||||||
|
set(DOXY_SOURCE_DIRECTORY ${EnTT_SOURCE_DIR}/src)
|
||||||
|
set(DOXY_DOCS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
set(DOXY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
|
||||||
|
configure_file(doxy.in doxy.cfg @ONLY)
|
||||||
|
|
||||||
|
add_custom_target(
|
||||||
|
docs ALL
|
||||||
|
COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxy.cfg
|
||||||
|
WORKING_DIRECTORY ${EnTT_SOURCE_DIR}
|
||||||
|
VERBATIM
|
||||||
|
SOURCES
|
||||||
|
dox/extra.dox
|
||||||
|
md/config.md
|
||||||
|
md/container.md
|
||||||
|
md/core.md
|
||||||
|
md/entity.md
|
||||||
|
md/faq.md
|
||||||
|
md/lib.md
|
||||||
|
md/links.md
|
||||||
|
md/locator.md
|
||||||
|
md/meta.md
|
||||||
|
md/poly.md
|
||||||
|
md/process.md
|
||||||
|
md/reference.md
|
||||||
|
md/resource.md
|
||||||
|
md/signal.md
|
||||||
|
md/unreal.md
|
||||||
|
doxy.in
|
||||||
|
)
|
||||||
|
|
||||||
|
install(
|
||||||
|
DIRECTORY ${DOXY_OUTPUT_DIRECTORY}/html
|
||||||
|
DESTINATION share/${PROJECT_NAME}-${PROJECT_VERSION}/
|
||||||
|
)
|
5
docs/dox/extra.dox
Normal file
5
docs/dox/extra.dox
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/**
|
||||||
|
* @namespace entt
|
||||||
|
*
|
||||||
|
* @brief `EnTT` default namespace.
|
||||||
|
*/
|
2682
docs/doxy.in
Normal file
2682
docs/doxy.in
Normal file
File diff suppressed because it is too large
Load Diff
121
docs/md/config.md
Normal file
121
docs/md/config.md
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
# Crash Course: configuration
|
||||||
|
|
||||||
|
<!--
|
||||||
|
@cond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
# Table of Contents
|
||||||
|
|
||||||
|
* [Introduction](#introduction)
|
||||||
|
* [Definitions](#definitions)
|
||||||
|
* [ENTT_NOEXCEPTION](#entt_noexception)
|
||||||
|
* [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_ASSERT_CONSTEXPR](#entt_assert_constexpr)
|
||||||
|
* [ENTT_DISABLE_ASSERT](#entt_disable_assert)
|
||||||
|
* [ENTT_NO_ETO](#entt_no_eto)
|
||||||
|
* [ENTT_STANDARD_CPP](#entt_standard_cpp)
|
||||||
|
|
||||||
|
<!--
|
||||||
|
@endcond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Introduction
|
||||||
|
|
||||||
|
`EnTT` has become almost completely customizable over time, in many
|
||||||
|
respects. These variables are just one of the many ways to customize how it
|
||||||
|
works.<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
|
||||||
|
|
||||||
|
Define this variable without assigning any value to it to turn off exception
|
||||||
|
handling in `EnTT`.<br/>
|
||||||
|
This is roughly equivalent to setting the compiler flag `-fno-exceptions` but is
|
||||||
|
also limited to this library only.
|
||||||
|
|
||||||
|
## 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 one who knows if a synchronization point is required.<br/>
|
||||||
|
However, some features aren't easily accessible to users and are 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
|
||||||
|
|
||||||
|
As it happens with sparse arrays, packed arrays are also paginated. However, in
|
||||||
|
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. Users
|
||||||
|
are allowed to overwrite its behavior by setting this variable.
|
||||||
|
|
||||||
|
### ENTT_ASSERT_CONSTEXPR
|
||||||
|
|
||||||
|
Usually, an assert within a `constexpr` function isn't a big deal. However, in
|
||||||
|
case of extreme customizations, it might be useful to differentiate.<br/>
|
||||||
|
For this purpose, `EnTT` introduces an admittedly badly named variable to make
|
||||||
|
the job easier in this regard. By default, this variable forwards its arguments
|
||||||
|
to `ENTT_ASSERT`.
|
||||||
|
|
||||||
|
### ENTT_DISABLE_ASSERT
|
||||||
|
|
||||||
|
Assertions may in turn affect performance to an extent when enabled. Whether
|
||||||
|
`ENTT_ASSERT` and `ENTT_ASSERT_CONSTEXPR` are 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 the
|
||||||
|
other variables 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 instantiated nor 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 prevents 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.
|
67
docs/md/container.md
Normal file
67
docs/md/container.md
Normal file
@ -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.
|
1011
docs/md/core.md
Normal file
1011
docs/md/core.md
Normal file
File diff suppressed because it is too large
Load Diff
2252
docs/md/entity.md
Normal file
2252
docs/md/entity.md
Normal file
File diff suppressed because it is too large
Load Diff
215
docs/md/faq.md
Normal file
215
docs/md/faq.md
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
# Frequently Asked Questions
|
||||||
|
|
||||||
|
<!--
|
||||||
|
@cond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
# Table of Contents
|
||||||
|
|
||||||
|
* [Introduction](#introduction)
|
||||||
|
* [FAQ](#faq)
|
||||||
|
* [Why is my debug build on Windows so slow?](#why-is-my-debug-build-on-windows-so-slow)
|
||||||
|
* [How can I represent hierarchies with my components?](#how-can-i-represent-hierarchies-with-my-components)
|
||||||
|
* [Custom entity identifiers: yay or nay?](#custom-entity-identifiers-yay-or-nay)
|
||||||
|
* [Warning C4307: integral constant overflow](#warning-C4307-integral-constant-overflow)
|
||||||
|
* [Warning C4003: the min, the max and the macro](#warning-C4003-the-min-the-max-and-the-macro)
|
||||||
|
* [The standard and the non-copyable types](#the-standard-and-the-non-copyable-types)
|
||||||
|
* [Which functions trigger which signals](#which-functions-trigger-which-signals)
|
||||||
|
<!--
|
||||||
|
@endcond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Introduction
|
||||||
|
|
||||||
|
This is a constantly updated section where I'm trying to put the answers to the
|
||||||
|
most frequently asked questions.<br/>
|
||||||
|
If you don't find your answer here, there are two cases: nobody has done it yet
|
||||||
|
or this section needs updating. In both cases, you can
|
||||||
|
[open a new issue](https://github.com/skypjack/entt/issues/new) or enter either
|
||||||
|
the [gitter channel](https://gitter.im/skypjack/entt) or the
|
||||||
|
[discord server](https://discord.gg/5BjPWBd) to ask for help.<br/>
|
||||||
|
Probably someone already has an answer for you and we can then integrate this
|
||||||
|
part of the documentation.
|
||||||
|
|
||||||
|
# FAQ
|
||||||
|
|
||||||
|
## Why is my debug build on Windows so slow?
|
||||||
|
|
||||||
|
`EnTT` is an experimental project that I also use to keep me up-to-date with the
|
||||||
|
latest revision of the language and the standard library. For this reason, it's
|
||||||
|
likely that some classes you're working with are using standard containers under
|
||||||
|
the hood.<br/>
|
||||||
|
Unfortunately, it's known that the standard containers aren't particularly
|
||||||
|
performing in debugging (the reasons for this go beyond this document) and are
|
||||||
|
even less so on Windows apparently. Fortunately this can also be mitigated a
|
||||||
|
lot, achieving good results in many cases.
|
||||||
|
|
||||||
|
First of all, there are two things to do in a Windows project:
|
||||||
|
|
||||||
|
* Disable the [`/JMC`](https://docs.microsoft.com/cpp/build/reference/jmc)
|
||||||
|
option (_Just My Code_ debugging), available starting with Visual Studio 2017
|
||||||
|
version 15.8.
|
||||||
|
|
||||||
|
* Set the [`_ITERATOR_DEBUG_LEVEL`](https://docs.microsoft.com/cpp/standard-library/iterator-debug-level)
|
||||||
|
macro to 0. This will disable checked iterators and iterator debugging.
|
||||||
|
|
||||||
|
Moreover, set the `ENTT_DISABLE_ASSERT` variable or redefine the `ENTT_ASSERT`
|
||||||
|
macro to disable internal debug checks in `EnTT`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#define ENTT_ASSERT(...) ((void)0)
|
||||||
|
```
|
||||||
|
|
||||||
|
These asserts are introduced to help the users but they require to access to the
|
||||||
|
underlying containers and therefore risk ruining the performance in some cases.
|
||||||
|
|
||||||
|
With these changes, debug performance should increase enough in most cases. If
|
||||||
|
you want something more, you can also switch to an optimization level `O0` or
|
||||||
|
preferably `O1`.
|
||||||
|
|
||||||
|
## How can I represent hierarchies with my components?
|
||||||
|
|
||||||
|
This is one of the first questions that anyone makes when starting to work with
|
||||||
|
the entity-component-system architectural pattern.<br/>
|
||||||
|
There are several approaches to the problem and the best one depends mainly on
|
||||||
|
the real problem one is facing. In all cases, how to do it doesn't strictly
|
||||||
|
depend on the library in use, but the latter certainly allows or not different
|
||||||
|
techniques depending on how the data are laid out.
|
||||||
|
|
||||||
|
I tried to describe some of the approaches that fit well with the model of
|
||||||
|
`EnTT`. [This](https://skypjack.github.io/2019-06-25-ecs-baf-part-4/) is the
|
||||||
|
first post of a series that tries to _explore_ the problem. More will probably
|
||||||
|
come in future.<br/>
|
||||||
|
In addition, `EnTT` also offers the possibility to create stable storage types
|
||||||
|
and therefore have pointer stability for one, all or some components. This is by
|
||||||
|
far the most convenient solution when it comes to creating hierarchies and
|
||||||
|
whatnot. See the documentation for the ECS part of the library and in particular
|
||||||
|
what concerns the `component_traits` class for further details.
|
||||||
|
|
||||||
|
## Custom entity identifiers: yay or nay?
|
||||||
|
|
||||||
|
Custom entity identifiers are definitely a good idea in two cases at least:
|
||||||
|
|
||||||
|
* If `std::uint32_t` isn't large enough for your purposes, since this is the
|
||||||
|
underlying type of `entt::entity`.
|
||||||
|
|
||||||
|
* If you want to avoid conflicts when using multiple registries.
|
||||||
|
|
||||||
|
Identifiers can be defined through enum classes and class types that define an
|
||||||
|
`entity_type` member of type `std::uint32_t` or `std::uint64_t`.<br/>
|
||||||
|
In fact, this is a definition equivalent to that of `entt::entity`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
enum class entity: std::uint32_t {};
|
||||||
|
```
|
||||||
|
|
||||||
|
There is no limit to the number of identifiers that can be defined.
|
||||||
|
|
||||||
|
## Warning C4307: integral constant overflow
|
||||||
|
|
||||||
|
According to [this](https://github.com/skypjack/entt/issues/121) issue, using a
|
||||||
|
hashed string under VS (toolset v141) could generate a warning.<br/>
|
||||||
|
First of all, I want to reassure you: it's expected and harmless. However, it
|
||||||
|
can be annoying.
|
||||||
|
|
||||||
|
To suppress it and if you don't want to suppress all the other warnings as well,
|
||||||
|
here is a workaround in the form of a macro:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
#define HS(str) __pragma(warning(suppress:4307)) entt::hashed_string{str}
|
||||||
|
#else
|
||||||
|
#define HS(str) entt::hashed_string{str}
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
With an example of use included:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
constexpr auto identifier = HS("my/resource/identifier");
|
||||||
|
```
|
||||||
|
|
||||||
|
Thanks to [huwpascoe](https://github.com/huwpascoe) for the courtesy.
|
||||||
|
|
||||||
|
## Warning C4003: the min, the max and the macro
|
||||||
|
|
||||||
|
On Windows, a header file defines two macros `min` and `max` which may result in
|
||||||
|
conflicts with their counterparts in the standard library and therefore in
|
||||||
|
errors during compilation.
|
||||||
|
|
||||||
|
It's a pretty big problem but fortunately it's not a problem of `EnTT` and there
|
||||||
|
is a fairly simple solution to it.<br/>
|
||||||
|
It consists in defining the `NOMINMAX` macro before including any other header
|
||||||
|
so as to get rid of the extra definitions:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#define NOMINMAX
|
||||||
|
```
|
||||||
|
|
||||||
|
Please refer to [this](https://github.com/skypjack/entt/issues/96) issue for
|
||||||
|
more details.
|
||||||
|
|
||||||
|
## The standard and the non-copyable types
|
||||||
|
|
||||||
|
`EnTT` uses internally the trait `std::is_copy_constructible_v` to check if a
|
||||||
|
component is actually copyable. However, this trait doesn't really check whether
|
||||||
|
a type is actually copyable. Instead, it just checks that a suitable copy
|
||||||
|
constructor and copy operator exist.<br/>
|
||||||
|
This can lead to surprising results due to some idiosyncrasies of the standard.
|
||||||
|
|
||||||
|
For example, `std::vector` defines a copy constructor that is conditionally
|
||||||
|
enabled depending on whether the value type is copyable or not. As a result,
|
||||||
|
`std::is_copy_constructible_v` returns true for the following specialization:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct type {
|
||||||
|
std::vector<std::unique_ptr<action>> vec;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
However, the copy constructor is effectively disabled upon specialization.
|
||||||
|
Therefore, trying to assign an instance of this type to an entity may trigger a
|
||||||
|
compilation error.<br/>
|
||||||
|
As a workaround, users can mark the type explicitly as non-copyable. This also
|
||||||
|
suppresses the implicit generation of the move constructor and operator, which
|
||||||
|
will therefore have to be defaulted accordingly:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct type {
|
||||||
|
type(const type &) = delete;
|
||||||
|
type(type &&) = default;
|
||||||
|
|
||||||
|
type & operator=(const type &) = delete;
|
||||||
|
type & operator=(type &&) = default;
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<action>> vec;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that aggregate initialization is also disabled as a consequence.<br/>
|
||||||
|
Fortunately, this type of trick is quite rare. The bad news is that there is no
|
||||||
|
way to deal with it at the library level, this being due to the design of the
|
||||||
|
language. On the other hand, the fact that the language itself also offers a way
|
||||||
|
to mitigate the problem makes it manageable.
|
||||||
|
|
||||||
|
## Which functions trigger which signals
|
||||||
|
|
||||||
|
The `registry` class offers three signals that are emitted following specific
|
||||||
|
operations. Maybe not everyone knows what these operations are, though.<br/>
|
||||||
|
If this isn't clear, below you can find a _vademecum_ for this purpose:
|
||||||
|
|
||||||
|
* `on_created` is invoked when a component is first added (neither modified nor
|
||||||
|
replaced) to an entity.
|
||||||
|
|
||||||
|
* `on_update` is called whenever an existing component is modified or replaced.
|
||||||
|
|
||||||
|
* `on_destroyed` is called when a component is explicitly or implicitly removed
|
||||||
|
from an entity.
|
||||||
|
|
||||||
|
Among the most controversial functions can be found `emplace_or_replace` and
|
||||||
|
`destroy`. However, following the above rules, it's quite simple to know what
|
||||||
|
will happen.<br/>
|
||||||
|
In the first case, `on_created` is invoked if the entity has not the component,
|
||||||
|
otherwise the latter is replaced and therefore `on_update` is triggered. As for
|
||||||
|
the second case, components are removed from their entities and thus freed when
|
||||||
|
they are recycled. It means that `on_destroyed` is triggered for every component
|
||||||
|
owned by the entity that is destroyed.
|
299
docs/md/graph.md
Normal file
299
docs/md/graph.md
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
# Crash Course: graph
|
||||||
|
|
||||||
|
<!--
|
||||||
|
@cond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
# Table of Contents
|
||||||
|
|
||||||
|
* [Introduction](#introduction)
|
||||||
|
* [Data structures](#data-structures)
|
||||||
|
* [Adjacency matrix](#adjacency-matrix)
|
||||||
|
* [Graphviz dot language](#graphviz-dot-language)
|
||||||
|
* [Flow builder](#flow-builder)
|
||||||
|
* [Tasks and resources](#tasks-and-resources)
|
||||||
|
* [Fake resources and order of execution](#fake-resources-and-order-of-execution)
|
||||||
|
* [Sync points](#sync-points)
|
||||||
|
* [Execution graph](#execution-graph)
|
||||||
|
|
||||||
|
<!--
|
||||||
|
@endcond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Introduction
|
||||||
|
|
||||||
|
`EnTT` doesn't aim to offer everything one needs to work with graphs. Therefore,
|
||||||
|
anyone looking for this in the _graph_ submodule will be disappointed.<br/>
|
||||||
|
Quite the opposite is true. This submodule is minimal and contains only the data
|
||||||
|
structures and algorithms strictly necessary for the development of some tools
|
||||||
|
such as the _flow builder_.
|
||||||
|
|
||||||
|
# Data structures
|
||||||
|
|
||||||
|
As anticipated in the introduction, the aim isn't to offer all possible data
|
||||||
|
structures suitable for representing and working with graphs. Many will likely
|
||||||
|
be added or refined over time, however I want to discourage anyone expecting
|
||||||
|
tight scheduling on the subject.<br/>
|
||||||
|
The data structures presented in this section are mainly useful for the
|
||||||
|
development and support of some tools which are also part of the same submodule.
|
||||||
|
|
||||||
|
## Adjacency matrix
|
||||||
|
|
||||||
|
The adjacency matrix is designed to represent either a directed or an undirected
|
||||||
|
graph:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{};
|
||||||
|
```
|
||||||
|
|
||||||
|
The `directed_tag` type _creates_ the graph as directed. There is also an
|
||||||
|
`undirected_tag` counterpart which creates it as undirected.<br/>
|
||||||
|
The interface deviates slightly from the typical double indexing of C and offers
|
||||||
|
an API that is perhaps more familiar to a C++ programmer. Therefore, the access
|
||||||
|
and modification of an element will take place via the `contains`, `insert` and
|
||||||
|
`erase` functions rather than a double call to an `operator[]`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
if(adjacency_matrix.contains(0u, 1u)) {
|
||||||
|
adjacency_matrix.erase(0u, 1u);
|
||||||
|
} else {
|
||||||
|
adjacency_matrix.insert(0u, 1u);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Both `insert` and` erase` are idempotent functions which have no effect if the
|
||||||
|
element already exists or has already been deleted.<br/>
|
||||||
|
The first one returns an `std::pair` containing the iterator to the element and
|
||||||
|
a boolean value indicating whether the element has been inserted or was already
|
||||||
|
present. The second one instead returns the number of deleted elements (0 or 1).
|
||||||
|
|
||||||
|
An adjacency matrix must be initialized with the number of elements (vertices)
|
||||||
|
when constructing it but can also be resized later using the `resize` function:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{3u};
|
||||||
|
```
|
||||||
|
|
||||||
|
To visit all vertices, the class offers a function named `vertices` that returns
|
||||||
|
an iterable object suitable for the purpose:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
for(auto &&vertex: adjacency_matrix.vertices()) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the same result can be obtained with the following snippet, since the
|
||||||
|
vertices are unsigned integral values:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
for(auto last = adjacency_matrix.size(), pos = {}; pos < last; ++pos) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
As for visiting the edges, a few functions are available.<br/>
|
||||||
|
When the purpose is to visit all the edges of a given adjacency matrix, the
|
||||||
|
`edges` function returns an iterable object that can be used to get them as
|
||||||
|
pairs of vertices:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
for(auto [lhs, rhs]: adjacency_matrix.edges()) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
On the other hand, if the goal is to visit all the in- or out-edges of a given
|
||||||
|
vertex, the `in_edges` and `out_edges` functions are meant for that:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
for(auto [lhs, rhs]: adjacency_matrix.out_edges(3u)) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
As might be expected, these functions expect the vertex to visit (that is, to
|
||||||
|
return the in- or out-edges for) as an argument.<br/>
|
||||||
|
Finally, the adjacency matrix is an allocator-aware container and offers most of
|
||||||
|
the functionality one would expect from this type of containers, such as `clear`
|
||||||
|
or 'get_allocator` and so on.
|
||||||
|
|
||||||
|
## Graphviz dot language
|
||||||
|
|
||||||
|
As it's one of the most popular formats, the library offers minimal support for
|
||||||
|
converting a graph to a Graphviz dot snippet.<br/>
|
||||||
|
The simplest way is to pass both an output stream and a graph to the `dot`
|
||||||
|
function:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
std::ostringstream output{};
|
||||||
|
entt::dot(output, adjacency_matrix);
|
||||||
|
```
|
||||||
|
|
||||||
|
However, there is also the option of providing a callback to which the vertices
|
||||||
|
are passed and which can be used to add (`dot`) properties to the output from
|
||||||
|
time to time:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
std::ostringstream output{};
|
||||||
|
entt::dot(output, adjacency_matrix, [](auto &output, auto vertex) {
|
||||||
|
out << "label=\"v\"" << vertex << ",shape=\"box\"";
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
This second mode is particularly convenient when the user wants to associate
|
||||||
|
data managed externally to the graph being converted.
|
||||||
|
|
||||||
|
# Flow builder
|
||||||
|
|
||||||
|
A flow builder is used to create execution graphs from tasks and resources.<br/>
|
||||||
|
The implementation is as generic as possible and doesn't bind to any other part
|
||||||
|
of the library.
|
||||||
|
|
||||||
|
This class is designed as a sort of _state machine_ to which a specific task is
|
||||||
|
attached for which the resources accessed in read-only or read-write mode are
|
||||||
|
specified.<br/>
|
||||||
|
Most of the functions in the API also return the flow builder itself, according
|
||||||
|
to what is the common sense API when it comes to builder classes.
|
||||||
|
|
||||||
|
Once all tasks have been registered and resources assigned to them, an execution
|
||||||
|
graph in the form of an adjacency matrix is returned to the user.<br/>
|
||||||
|
This graph contains all the tasks assigned to the flow builder in the form of
|
||||||
|
_vertices_. The _vertex_ itself can be used as an index to get the identifier
|
||||||
|
passed during registration.
|
||||||
|
|
||||||
|
## Tasks and resources
|
||||||
|
|
||||||
|
Although these terms are used extensively in the documentation, the flow builder
|
||||||
|
has no real concept of tasks and resources.<br/>
|
||||||
|
This class works mainly with _identifiers_, that is, values of type `id_type`.
|
||||||
|
That is, both tasks and resources are identified by integral values.<br/>
|
||||||
|
This allows not to couple the class itself to the rest of the library or to any
|
||||||
|
particular data structure. On the other hand, it requires the user to keep track
|
||||||
|
of the association between identifiers and operations or actual data.
|
||||||
|
|
||||||
|
Once a flow builder has been created (which requires no constructor arguments),
|
||||||
|
the first thing to do is to bind a task. This will indicate to the builder who
|
||||||
|
intends to consume the resources that will be specified immediately after:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
entt::flow builder{};
|
||||||
|
builder.bind("task_1"_hs);
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the example uses the `EnTT` hashed string to generate an identifier
|
||||||
|
for the task.<br/>
|
||||||
|
Indeed, the use of `id_type` as an identifier type is not by accident. In fact,
|
||||||
|
it matches well with the internal hashed string class. Moreover, it's also the
|
||||||
|
same type returned by the hash function of the internal RTTI system, in case the
|
||||||
|
user wants to rely on that.<br/>
|
||||||
|
However, being an integral value, it leaves the user full freedom to rely on his
|
||||||
|
own tools if he deems it necessary.
|
||||||
|
|
||||||
|
Once a task has been associated with the flow builder, it can be assigned
|
||||||
|
read-only or read-write resources, as appropriate:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
builder
|
||||||
|
.bind("task_1"_hs)
|
||||||
|
.ro("resource_1"_hs)
|
||||||
|
.ro("resource_2"_hs)
|
||||||
|
.bind("task_2"_hs)
|
||||||
|
.rw("resource_2"_hs)
|
||||||
|
```
|
||||||
|
|
||||||
|
As mentioned, many functions return the builder itself and it's therefore easy
|
||||||
|
to concatenate the different calls.<br/>
|
||||||
|
Also in the case of resources, these are identified by numeric values of type
|
||||||
|
`id_type`. As above, the choice is not entirely random. This goes well with the
|
||||||
|
tools offered by the library while leaving room for maximum flexibility.
|
||||||
|
|
||||||
|
Finally, both the `ro` and` rw` functions also offer an overload that accepts a
|
||||||
|
pair of iterators, so that one can pass a range of resources in one go.
|
||||||
|
|
||||||
|
## Fake resources and order of execution
|
||||||
|
|
||||||
|
The flow builder doesn't offer the ability to specify when a task should execute
|
||||||
|
before or after another task.<br/>
|
||||||
|
In fact, the order of _registration_ on the resources also determines the order
|
||||||
|
in which the tasks are processed during the generation of the execution graph.
|
||||||
|
|
||||||
|
However, there is a way to force the execution order of two processes.<br/>
|
||||||
|
Briefly, since accessing a resource in opposite modes requires sequential rather
|
||||||
|
than parallel scheduling, it's possible to make use of fake resources to force
|
||||||
|
the order execution:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
builder
|
||||||
|
.bind("task_1"_hs)
|
||||||
|
.ro("resource_1"_hs)
|
||||||
|
.rw("fake"_hs)
|
||||||
|
.bind("task_2"_hs)
|
||||||
|
.ro("resource_2"_hs)
|
||||||
|
.ro("fake"_hs)
|
||||||
|
.bind("task_3"_hs)
|
||||||
|
.ro("resource_2"_hs)
|
||||||
|
.ro("fake"_hs)
|
||||||
|
```
|
||||||
|
|
||||||
|
This snippet forces the execution of `task_2` and `task_3` **after** `task_1`.
|
||||||
|
This is due to the fact that the latter sets a read-write requirement on a fake
|
||||||
|
resource that the other tasks also want to access in read-only mode.<br/>
|
||||||
|
Similarly, it's possible to force a task to run after a certain group:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
builder
|
||||||
|
.bind("task_1"_hs)
|
||||||
|
.ro("resource_1"_hs)
|
||||||
|
.ro("fake"_hs)
|
||||||
|
.bind("task_2"_hs)
|
||||||
|
.ro("resource_1"_hs)
|
||||||
|
.ro("fake"_hs)
|
||||||
|
.bind("task_3"_hs)
|
||||||
|
.ro("resource_2"_hs)
|
||||||
|
.rw("fake"_hs)
|
||||||
|
```
|
||||||
|
|
||||||
|
In this case, since there are a number of processes that want to read a specific
|
||||||
|
resource, they will do so in parallel by forcing `task_3` to run after all the
|
||||||
|
others tasks.
|
||||||
|
|
||||||
|
## Sync points
|
||||||
|
|
||||||
|
Sometimes it's useful to assign the role of _sync point_ to a node.<br/>
|
||||||
|
Whether it accesses new resources or is simply a watershed, the procedure for
|
||||||
|
assigning this role to a vertex is always the same: first it's tied to the flow
|
||||||
|
builder, then the `sync` function is invoked:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
builder.bind("sync_point"_hs).sync();
|
||||||
|
```
|
||||||
|
|
||||||
|
The choice to assign an _identity_ to this type of nodes lies in the fact that,
|
||||||
|
more often than not, they also perform operations on resources.<br/>
|
||||||
|
If this isn't the case, it will still be possible to create no-op vertices to
|
||||||
|
which empty tasks are assigned.
|
||||||
|
|
||||||
|
## Execution graph
|
||||||
|
|
||||||
|
Once both the resources and their consumers have been properly registered, the
|
||||||
|
purpose of this tool is to generate an execution graph that takes into account
|
||||||
|
all specified constraints to return the best scheduling for the vertices:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
entt::adjacency_matrix<entt::directed_tag> graph = builder.graph();
|
||||||
|
```
|
||||||
|
|
||||||
|
The search for the main vertices, that is those without in-edges, is usually the
|
||||||
|
first thing required:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
for(auto &&vertex: graph) {
|
||||||
|
if(auto in_edges = graph.in_edges(vertex); in_edges.begin() == in_edges.end()) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Starting from them, using the other functions appropriately (such as `out_edges`
|
||||||
|
to retrieve the children of a given task or `edges` to access their identifiers)
|
||||||
|
it will be possible to instantiate an execution graph.
|
99
docs/md/lib.md
Normal file
99
docs/md/lib.md
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
# Push EnTT across boundaries
|
||||||
|
|
||||||
|
<!--
|
||||||
|
@cond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
# Table of Contents
|
||||||
|
|
||||||
|
* [Working across boundaries](#working-across-boundaries)
|
||||||
|
* [Smooth until proven otherwise](#smooth-until-proven-otherwise)
|
||||||
|
* [Meta context](#meta-context)
|
||||||
|
* [Memory management](#memory-management)
|
||||||
|
<!--
|
||||||
|
@endcond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Working across boundaries
|
||||||
|
|
||||||
|
`EnTT` has historically had a limit when used across boundaries on Windows in
|
||||||
|
general and on GNU/Linux when default visibility was set to hidden. The
|
||||||
|
limitation was mainly due to a custom utility used to assign unique, sequential
|
||||||
|
identifiers with different types.<br/>
|
||||||
|
Fortunately, nowadays using `EnTT` across boundaries is much easier.
|
||||||
|
|
||||||
|
## Smooth until proven otherwise
|
||||||
|
|
||||||
|
Many classes in `EnTT` make extensive use of type erasure for their purposes.
|
||||||
|
This isn't a problem on itself (in fact, it's the basis of an API so convenient
|
||||||
|
to use). However, a way is needed to recognize the objects whose type has been
|
||||||
|
erased on the other side of a boundary.<br/>
|
||||||
|
The `type_hash` class template is how identifiers are generated and thus made
|
||||||
|
available to the rest of the library. In general, this class doesn't arouse much
|
||||||
|
interest. The only exception is when a conflict between identifiers occurs
|
||||||
|
(definitely uncommon though) or when the default solution proposed by `EnTT`
|
||||||
|
isn't suitable for the user's purposes.<br/>
|
||||||
|
The section dedicated to `type_info` contains all the details to get around the
|
||||||
|
issue in a concise and elegant way. Please refer to the specific documentation.
|
||||||
|
|
||||||
|
When working with linked libraries, compile definitions `ENTT_API_EXPORT` and
|
||||||
|
`ENTT_API_IMPORT` can be used where there is a need to import or export symbols,
|
||||||
|
so as to make everything work nicely across boundaries.<br/>
|
||||||
|
On the other hand, everything should run smoothly when working with plugins or
|
||||||
|
shared libraries that don't export any symbols.
|
||||||
|
|
||||||
|
For anyone who needs more details, the test suite contains multiple examples
|
||||||
|
covering the most common cases (see the `lib` directory for all details).<br/>
|
||||||
|
It goes without saying that it's impossible to cover **all** possible cases.
|
||||||
|
However, what is offered should hopefully serve as a basis for all of them.
|
||||||
|
|
||||||
|
## Meta context
|
||||||
|
|
||||||
|
The runtime reflection system deserves a special mention when it comes to using
|
||||||
|
it across boundaries.<br/>
|
||||||
|
Since it's linked already to a static context to which the elements are attached
|
||||||
|
and different contexts don't relate to each other, they must be _shared_ to
|
||||||
|
allow the use of meta types across boundaries.
|
||||||
|
|
||||||
|
Fortunately, sharing a context is also trivial to do. First of all, the local
|
||||||
|
one is acquired in the main space:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto handle = entt::locator<entt::meta_ctx>::handle();
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, it's passed to the receiving space that sets it as its default context,
|
||||||
|
thus discarding or storing aside the local one:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
entt::locator<entt::meta_ctx>::reset(handle);
|
||||||
|
```
|
||||||
|
|
||||||
|
From now on, both spaces refer to the same context and on it are attached all
|
||||||
|
new meta types, no matter where they are created.<br/>
|
||||||
|
Note that resetting the main context doesn't also propagate changes across
|
||||||
|
boundaries. In other words, resetting a context results in the decoupling of the
|
||||||
|
two sides and therefore a divergence in the contents.
|
||||||
|
|
||||||
|
## Memory Management
|
||||||
|
|
||||||
|
There is another subtle problem due to memory management that can lead to
|
||||||
|
headaches.<br/>
|
||||||
|
It can occur where there are pools of objects (such as components or events)
|
||||||
|
dynamically created on demand. This is usually not a problem when working with
|
||||||
|
linked libraries that rely on the same dynamic runtime. However, it can occur in
|
||||||
|
the case of plugins or statically linked runtimes.
|
||||||
|
|
||||||
|
As an example, imagine creating an instance of `registry` in the main executable
|
||||||
|
and sharing it with a plugin. If the latter starts working with a component that
|
||||||
|
is unknown to the former, a dedicated pool is created within the registry on
|
||||||
|
first use.<br/>
|
||||||
|
As one can guess, this pool is instantiated on a different side of the boundary
|
||||||
|
from the `registry`. Therefore, the instance is now managing memory from
|
||||||
|
different spaces and this can quickly lead to crashes if not properly addressed.
|
||||||
|
|
||||||
|
To overcome the risk, it's recommended to use well-defined interfaces that make
|
||||||
|
fundamental types pass through the boundaries, isolating the instances of the
|
||||||
|
`EnTT` classes from time to time and as appropriate.<br/>
|
||||||
|
Refer to the test suite for some examples, read the documentation available
|
||||||
|
online about this type of issues or consult someone who has already had such
|
||||||
|
experiences to avoid problems.
|
266
docs/md/links.md
Normal file
266
docs/md/links.md
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
# EnTT in Action
|
||||||
|
|
||||||
|
`EnTT` is widely used in private and commercial applications. I cannot even
|
||||||
|
mention most of them because of some signatures I put on some documents time
|
||||||
|
ago. Fortunately, there are also people who took the time to implement open
|
||||||
|
source projects based on `EnTT` and didn't hold back when it came to documenting
|
||||||
|
them.
|
||||||
|
|
||||||
|
Below an incomplete list of games, applications and articles that can be used as
|
||||||
|
a reference. Where I put the word _apparently_ means that the use of `EnTT` is
|
||||||
|
documented but the authors didn't make explicit announcements or contacted me
|
||||||
|
directly.
|
||||||
|
|
||||||
|
I hope this list can grow much more in the future:
|
||||||
|
|
||||||
|
* Games:
|
||||||
|
* [Minecraft](https://minecraft.net/en-us/attribution/) by
|
||||||
|
[Mojang](https://mojang.com/): of course, **that** Minecraft, see the
|
||||||
|
open source attributions page for more details.
|
||||||
|
* [Minecraft Earth](https://www.minecraft.net/en-us/about-earth) by
|
||||||
|
[Mojang](https://mojang.com/): an augmented reality game for mobile, that
|
||||||
|
lets users bring Minecraft into the real world.
|
||||||
|
* [Ember Sword](https://embersword.com/): a modern Free-to-Play MMORPG with a
|
||||||
|
player-driven economy, a classless combat system, and scarce, tradable
|
||||||
|
cosmetic collectibles.
|
||||||
|
* Apparently [Diablo II: Resurrected](https://diablo2.blizzard.com/) by
|
||||||
|
[Blizzard](https://www.blizzard.com/): monsters, heroes, items, spells, all
|
||||||
|
resurrected. Thanks unknown insider.
|
||||||
|
* [Apparently](https://www.youtube.com/watch?v=P8xvOA3ikrQ&t=1105s)
|
||||||
|
[Call of Duty: Vanguard](https://www.callofduty.com/vanguard) by
|
||||||
|
[Sledgehammer Games](https://www.sledgehammergames.com/): I can neither
|
||||||
|
confirm nor deny but there is a license I know in the credits.
|
||||||
|
* Apparently [D&D Dark Alliance](https://darkalliance.wizards.com) by
|
||||||
|
[Wizards of the Coast](https://company.wizards.com): your party, their
|
||||||
|
funeral.
|
||||||
|
* [TiltedOnline](https://github.com/tiltedphoques/TiltedOnline) by
|
||||||
|
[Tilted Phoques](https://github.com/tiltedphoques): Skyrim and Fallout 4 mod
|
||||||
|
to play online.
|
||||||
|
* [Antkeeper](https://github.com/antkeeper/antkeeper-source): an ant colony
|
||||||
|
simulation [game](https://antkeeper.com/).
|
||||||
|
* [Openblack](https://github.com/openblack/openblack): open source
|
||||||
|
reimplementation of the game _Black & White_ (2001).
|
||||||
|
* [Land of the Rair](https://github.com/LandOfTheRair/core2): the new backend
|
||||||
|
of [a retro-style MUD](https://rair.land/) for the new age.
|
||||||
|
* [Face Smash](https://play.google.com/store/apps/details?id=com.gamee.facesmash):
|
||||||
|
a game to play with your face.
|
||||||
|
* [EnTT Pacman](https://github.com/Kerndog73/EnTT-Pacman): an example of how
|
||||||
|
to make Pacman with `EnTT`.
|
||||||
|
* [Wacman](https://github.com/carlfindahl/wacman): a pacman clone with OpenGL.
|
||||||
|
* [Classic Tower Defence](https://github.com/kerndog73/Classic-Tower-Defence):
|
||||||
|
a tiny little tower defence game featuring a homemade font.
|
||||||
|
[Check it out](https://indi-kernick.itch.io/classic-tower-defence).
|
||||||
|
* [The Machine](https://github.com/Kerndog73/The-Machine): a box pushing
|
||||||
|
puzzler with logic gates and other cool stuff.
|
||||||
|
[Check it out](https://indi-kernick.itch.io/the-machine-web-version).
|
||||||
|
* [EnTTPong](https://github.com/DomRe/EnttPong): a basic game made to showcase
|
||||||
|
different parts of `EnTT` and C++17.
|
||||||
|
* [Randballs](https://github.com/gale93/randballs): simple `SFML` and `EnTT`
|
||||||
|
playground.
|
||||||
|
* [EnTT Tower Defense](https://github.com/Daivuk/tddod): a data oriented tower
|
||||||
|
defense example.
|
||||||
|
* [EnTT Breakout](https://github.com/vblanco20-1/entt-breakout): simple
|
||||||
|
example of a breakout game, using `SDL` and `EnTT`.
|
||||||
|
* [Arcade puzzle game with EnTT](https://github.com/MasonRG/ArcadePuzzleGame):
|
||||||
|
arcade puzzle game made in C++ using the `SDL2` and `EnTT` libraries.
|
||||||
|
* [Snake with EnTT](https://github.com/MasonRG/SnakeGame): simple snake game
|
||||||
|
made in C++ with the `SDL2` and `EnTT` libraries.
|
||||||
|
* [Mirrors lasers and robots](https://github.com/guillaume-haerinck/imac-tower-defense):
|
||||||
|
a small tower defense game based on mirror orientation.
|
||||||
|
* [PopHead](https://github.com/SPC-Some-Polish-Coders/PopHead/): 2D, Zombie,
|
||||||
|
RPG game made from scratch in C++.
|
||||||
|
* [Robotligan](https://github.com/Trisslotten/robotligan): multiplayer
|
||||||
|
football game.
|
||||||
|
* [DungeonSlayer](https://github.com/alohaeee/DungeonSlayer): 2D game made
|
||||||
|
from scratch in C++.
|
||||||
|
* [3DGame](https://github.com/kwarkGorny/3DGame): 2.5D top-down space shooter.
|
||||||
|
* [Pulcher](https://github.com/AODQ/pulcher): 2D cross-platform game inspired
|
||||||
|
by Quake.
|
||||||
|
* [Destroid](https://github.com/tyrannicaltoucan/destroid): _one-bazillionth_
|
||||||
|
arcade game about shooting dirty rocks in space, inspired by Asteroids.
|
||||||
|
* [Wanderer](https://github.com/albin-johansson/wanderer): a 2D exploration
|
||||||
|
based indie game.
|
||||||
|
* [Spelunky® Classic remake](https://github.com/dbeef/spelunky-psp): a truly
|
||||||
|
multiplatform experience with a rewrite from scratch.
|
||||||
|
* [CubbyTower](https://github.com/utilForever/CubbyTower): a simple tower
|
||||||
|
defense game using C++ with Entity Component System (ECS).
|
||||||
|
* [Runeterra](https://github.com/utilForever/Runeterra): Legends of Runeterra
|
||||||
|
simulator using C++ with some reinforcement learning.
|
||||||
|
* [Black Sun](https://store.steampowered.com/app/1670930/Black_Sun/): fly your
|
||||||
|
space ship through a large 2D open world.
|
||||||
|
* [PokeMaster](https://github.com/utilForever/PokeMaster): Pokemon Battle
|
||||||
|
simulator using C++ with some reinforcement learning.
|
||||||
|
* [HomeHearth](https://youtu.be/GrEWl8npL9Y): choose your hero, protect the
|
||||||
|
town, before it's too late.
|
||||||
|
* [City Builder Game](https://github.com/PhiGei2000/CityBuilderGame): a simple
|
||||||
|
city-building game using C++ and OpenGL.
|
||||||
|
* [BattleSub](https://github.com/bfeldpw/battlesub): two player 2D submarine
|
||||||
|
game with some fluid dynamics.
|
||||||
|
* [Crimson Rush](https://github.com/WilKam01/LuaCGame): a dungeon-crawler and
|
||||||
|
rougelike inspired game about exploring and surviving as long as possible.
|
||||||
|
* [Space Fight](https://github.com/cholushkin/SpaceFight): one screen
|
||||||
|
multi-player arcade shooter game prototype.
|
||||||
|
* [Confetti Party](https://github.com/hexerei/entt-confetti): C++ sample
|
||||||
|
application as a starting point using `EnTT` and `SDL2`.
|
||||||
|
|
||||||
|
* Engines and the like:
|
||||||
|
* [Aether Engine](https://hadean.com/spatial-simulation/)
|
||||||
|
[v1.1+](https://docs.hadean.com/v1.1/Licenses/) by
|
||||||
|
[Hadean](https://hadean.com/): a library designed for spatially partitioning
|
||||||
|
agent-based simulations.
|
||||||
|
* [Fling Engine](https://github.com/flingengine/FlingEngine): a Vulkan game
|
||||||
|
engine with a focus on data oriented design.
|
||||||
|
* [NovusCore](https://github.com/novuscore/NovusCore): a modern take on World
|
||||||
|
of Warcraft emulation.
|
||||||
|
* [Chrysalis](https://github.com/ivanhawkes/Chrysalis): action RPG SDK for
|
||||||
|
CRYENGINE games.
|
||||||
|
* [LM-Engine](https://github.com/Lawrencemm/LM-Engine): the Vim of game
|
||||||
|
engines.
|
||||||
|
* [Edyn](https://github.com/xissburg/edyn): a real-time physics engine
|
||||||
|
organized as an ECS.
|
||||||
|
* [MushMachine](https://github.com/MadeOfJelly/MushMachine): engine...
|
||||||
|
vrooooommm.
|
||||||
|
* [Antara Gaming SDK](https://github.com/KomodoPlatform/antara-gaming-sdk):
|
||||||
|
the Komodo Gaming Software Development Kit.
|
||||||
|
* [XVP](https://ravingbots.com/xvp-expansive-vehicle-physics-for-unreal-engine/):
|
||||||
|
[_eXpansive Vehicle Physics_](https://github.com/raving-bots/xvp/wiki/Plugin-integration-guide)
|
||||||
|
plugin for Unreal Engine.
|
||||||
|
* [Apparently](https://teamwisp.github.io/credits/)
|
||||||
|
[Wisp](https://teamwisp.github.io/product/) by
|
||||||
|
[Team Wisp](https://teamwisp.github.io/): an advanced real-time ray tracing
|
||||||
|
renderer built for the demands of video game artists.
|
||||||
|
* [shiva](https://github.com/Milerius/shiva): modern C++ engine with
|
||||||
|
modularity.
|
||||||
|
* [ImGui/EnTT editor](https://github.com/Green-Sky/imgui_entt_entity_editor):
|
||||||
|
a drop-in, single-file entity editor for `EnTT` that uses `ImGui` as
|
||||||
|
graphical backend (with
|
||||||
|
[demo code](https://github.com/Green-Sky/imgui_entt_entity_editor_demo)).
|
||||||
|
* [SgOgl](https://github.com/stwe/SgOgl): a game engine library for OpenGL
|
||||||
|
developed for educational purposes.
|
||||||
|
* [Lumos](https://github.com/jmorton06/Lumos): game engine written in C++
|
||||||
|
using OpenGL and Vulkan.
|
||||||
|
* [Silvanus](https://github.com/hobbyistmaker/silvanus): Silvanus Fusion 360
|
||||||
|
Box Generator.
|
||||||
|
* [Lina Engine](https://github.com/inanevin/LinaEngine): an open-source,
|
||||||
|
modular, tiny and fast C++ game engine, aimed to develop 3D desktop games.
|
||||||
|
* [Spike](https://github.com/FahimFuad/Spike): a powerful game engine which
|
||||||
|
can run on a toaster.
|
||||||
|
* [Helena Framework](https://github.com/NIKEA-SOFT/HelenaFramework): a modern
|
||||||
|
framework in C++17 for backend development.
|
||||||
|
* [Unity/EnTT](https://github.com/TongTungGiang/unity-entt): tech demo of a
|
||||||
|
native simulation layer using `EnTT` and `Unity` as a rendering engine.
|
||||||
|
* [OverEngine](https://github.com/OverShifted/OverEngine): an over-engineered
|
||||||
|
game engine.
|
||||||
|
* [Electro](https://github.com/Electro-Technologies/Electro): high performance
|
||||||
|
3D game engine with a high emphasis on rendering.
|
||||||
|
* [Kawaii](https://github.com/Mathieu-Lala/Kawaii_Engine): a modern data
|
||||||
|
oriented game engine.
|
||||||
|
* [Becketron](https://github.com/Doctor-Foxling/Becketron): a game engine
|
||||||
|
written mostly in C++.
|
||||||
|
* [Spatial Engine](https://github.com/luizgabriel/Spatial.Engine): a
|
||||||
|
cross-platform engine created on top of google's filament rendering engine.
|
||||||
|
* [Kaguya](https://github.com/KaiH0717/Kaguya): D3D12 Rendering Engine.
|
||||||
|
* [OpenAWE](https://github.com/OpenAWE-Project/OpenAWE): open implementation
|
||||||
|
of the Alan Wake Engine.
|
||||||
|
* [Nazara Engine](https://github.com/DigitalPulseSoftware/NazaraEngine): fast,
|
||||||
|
cross-platform, object-oriented API to help in daily developer life.
|
||||||
|
* [Billy Engine](https://github.com/billy4479/BillyEngine): some kind of a 2D
|
||||||
|
engine based on `SDL2` and `EnTT`.
|
||||||
|
* [Ducktape](https://github.com/DucktapeEngine/Ducktape): an open source C++
|
||||||
|
2D & 3D game engine that focuses on being fast and powerful.
|
||||||
|
|
||||||
|
* Articles, videos and blog posts:
|
||||||
|
* [Some posts](https://skypjack.github.io/tags/#entt) on my personal
|
||||||
|
[blog](https://skypjack.github.io/) are about `EnTT`, for those who want to
|
||||||
|
know **more** on this project.
|
||||||
|
* [Game Engine series](https://www.youtube.com/c/TheChernoProject/videos) by
|
||||||
|
[The Cherno](https://github.com/TheCherno) (not only about `EnTT` but also
|
||||||
|
on the use of an ECS in general):
|
||||||
|
- [Intro to EnTT](https://www.youtube.com/watch?v=D4hz0wEB978).
|
||||||
|
- [Entities and Components](https://www.youtube.com/watch?v=-B1iu4QJTUc).
|
||||||
|
- [The ENTITY Class](https://www.youtube.com/watch?v=GfSzeAcsBb0).
|
||||||
|
- [Camera Systems](https://www.youtube.com/watch?v=ubZn7BlrnTU).
|
||||||
|
- [Scene Camera](https://www.youtube.com/watch?v=UKVFRRufKzo).
|
||||||
|
- [Native Scripting](https://www.youtube.com/watch?v=iIUhg88MK5M).
|
||||||
|
- [Native Scripting (now with virtual functions!)](https://www.youtube.com/watch?v=1cHEcrIn8IQ).
|
||||||
|
- [Scene Hierarchy Panel](https://www.youtube.com/watch?v=wziDnE8guvI).
|
||||||
|
- [Properties Panel](https://www.youtube.com/watch?v=NBpB0qscF3E).
|
||||||
|
- [Camera Component UI](https://www.youtube.com/watch?v=RIMt_6agUiU).
|
||||||
|
- [Drawing Component UI](https://www.youtube.com/watch?v=u3yq8s3KuSE).
|
||||||
|
- [Transform Component UI](https://www.youtube.com/watch?v=8JqcXYbzPJc).
|
||||||
|
- [Adding/Removing Entities and Components UI](https://www.youtube.com/watch?v=PsyGmsIgp9M).
|
||||||
|
- [Saving and Loading Scenes](https://www.youtube.com/watch?v=IEiOP7Y-Mbc).
|
||||||
|
- ... And so on.
|
||||||
|
[Check out](https://www.youtube.com/channel/UCQ-W1KE9EYfdxhL6S4twUNw) the
|
||||||
|
_Game Engine Series_ by The Cherno for more videos.
|
||||||
|
* [Space Battle: Huge edition](http://victor.madtriangles.com/code%20experiment/2018/06/11/post-ecs-battle-huge.html):
|
||||||
|
huge space battle built entirely from scratch.
|
||||||
|
* [Space Battle](https://github.com/vblanco20-1/ECS_SpaceBattle): huge space
|
||||||
|
battle built on `UE4`.
|
||||||
|
* [Experimenting with ECS in UE4](http://victor.madtriangles.com/code%20experiment/2018/03/25/post-ue4-ecs-battle.html):
|
||||||
|
interesting article about `UE4` and `EnTT`.
|
||||||
|
* [Implementing ECS architecture in UE4](https://forums.unrealengine.com/development-discussion/c-gameplay-programming/1449913-implementing-ecs-architecture-in-ue4-giant-space-battle):
|
||||||
|
giant space battle.
|
||||||
|
* [Conan Adventures (SFML and EnTT in C++)](https://leinnan.github.io/blog/conan-adventuressfml-and-entt-in-c.html):
|
||||||
|
create projects in modern C++ using `SFML`, `EnTT`, `Conan` and `CMake`.
|
||||||
|
* [Adding EnTT ECS to Chrysalis](https://www.tauradius.com/post/adding-an-ecs-to-chrysalis/):
|
||||||
|
a blog entry (and its
|
||||||
|
[follow-up](https://www.tauradius.com/post/chrysalis-update-2020-08-02/))
|
||||||
|
about the integration of `EnTT` into `Chrysalis`, an action RPG SDK for
|
||||||
|
CRYENGINE games.
|
||||||
|
* [Creating Minecraft in One Week with C++ and Vulkan](https://vazgriz.com/189/creating-minecraft-in-one-week-with-c-and-vulkan/):
|
||||||
|
a crack at recreating Minecraft in one week using a custom C++ engine and
|
||||||
|
Vulkan ([code included](https://github.com/vazgriz/VoxelGame)).
|
||||||
|
* [Ability Creator](https://www.erichildebrand.net/blog/ability-creator-project-retrospect):
|
||||||
|
project retrospect by [Eric Hildebrand](https://www.erichildebrand.net/).
|
||||||
|
* [EnTT Entity Component System Gaming Library](https://gamefromscratch.com/entt-entity-component-system-gaming-library/):
|
||||||
|
`EnTT` on GameFromScratch.com.
|
||||||
|
* [Custom C++ server for UE5](https://youtu.be/fbXZVNCOvjM) optimized for
|
||||||
|
MMO(RPG)s and its [follow-up](https://youtu.be/yGlZeopx2hU) episode about
|
||||||
|
player bots and full external ECS: a series definitely worth looking at.
|
||||||
|
|
||||||
|
* Any Other Business:
|
||||||
|
* [ArcGIS Runtime SDKs](https://developers.arcgis.com/arcgis-runtime/) by
|
||||||
|
[Esri](https://www.esri.com/): they use `EnTT` for the internal ECS and the
|
||||||
|
cross platform C++ rendering engine. The SDKs are utilized by a lot of
|
||||||
|
enterprise custom apps, as well as by Esri for its own public applications
|
||||||
|
such as
|
||||||
|
[Explorer](https://play.google.com/store/apps/details?id=com.esri.explorer),
|
||||||
|
[Collector](https://play.google.com/store/apps/details?id=com.esri.arcgis.collector)
|
||||||
|
and
|
||||||
|
[Navigator](https://play.google.com/store/apps/details?id=com.esri.navigator).
|
||||||
|
* [FASTSUITE Edition 2](https://www.fastsuite.com/en_EN/fastsuite/fastsuite-edition-2.html)
|
||||||
|
by [Cenit](http://www.cenit.com/en_EN/about-us/overview.html): they use
|
||||||
|
`EnTT` to drive their simulation, that is, the communication between robot
|
||||||
|
controller emulator and renderer.
|
||||||
|
* [Ragdoll](https://ragdolldynamics.com/): real-time physics for Autodesk Maya
|
||||||
|
2020.
|
||||||
|
* [Project Lagrange](https://github.com/adobe/lagrange): a robust geometry
|
||||||
|
processing library by [Adobe](https://github.com/adobe).
|
||||||
|
* [AtomicDEX](https://github.com/KomodoPlatform/atomicDEX-Desktop): a secure
|
||||||
|
wallet and non-custodial decentralized exchange rolled into one application.
|
||||||
|
* [Apparently](https://www.linkedin.com/in/skypjack/)
|
||||||
|
[NIO](https://www.nio.io/): there was a collaboration to make some changes
|
||||||
|
to `EnTT`, at the time used for internal projects.
|
||||||
|
* [Apparently](https://www.linkedin.com/jobs/view/architekt-c%2B%2B-at-tieto-1219512333/)
|
||||||
|
[Tieto](https://www.tieto.com/): they published a job post where `EnTT` was
|
||||||
|
listed on their software stack.
|
||||||
|
* [Sequentity](https://github.com/alanjfs/sequentity): A MIDI-like
|
||||||
|
sequencer/tracker for C++ and `ImGui` (with `Magnum` and `EnTT`).
|
||||||
|
* [EnTT meets Sol2](https://github.com/skaarj1989/entt-meets-sol2): freely
|
||||||
|
available examples of how to combine `EnTT` and `Sol2`.
|
||||||
|
* [Godot meets EnTT](https://github.com/portaloffreedom/godot_entt_example/):
|
||||||
|
a simple example on how to use `EnTT` within
|
||||||
|
[`Godot`](https://godotengine.org/).
|
||||||
|
* [Godot and GameNetworkingSockets meet EnTT](https://github.com/portaloffreedom/godot_entt_net_example):
|
||||||
|
a simple example on how to use `EnTT` and
|
||||||
|
[`GameNetworkingSockets`](https://github.com/ValveSoftware/GameNetworkingSockets)
|
||||||
|
within [`Godot`](https://godotengine.org/).
|
||||||
|
* [MatchOneEntt](https://github.com/mhaemmerle/MatchOneEntt): port of
|
||||||
|
[Match One](https://github.com/sschmid/Match-One) for `Entitas-CSharp`.
|
||||||
|
* GitHub contains also
|
||||||
|
[many other examples](https://github.com/search?o=desc&q=%22skypjack%2Fentt%22&s=indexed&type=Code)
|
||||||
|
of use of `EnTT` from which to take inspiration if interested.
|
||||||
|
|
||||||
|
If you know of other resources out there that are about `EnTT`, feel free to
|
||||||
|
open an issue or a PR and I'll be glad to add them to this page.
|
88
docs/md/locator.md
Normal file
88
docs/md/locator.md
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
# Crash Course: service locator
|
||||||
|
|
||||||
|
<!--
|
||||||
|
@cond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
# Table of Contents
|
||||||
|
|
||||||
|
* [Introduction](#introduction)
|
||||||
|
* [Service locator](#service-locator)
|
||||||
|
* [Opaque handles](#opaque-handles)
|
||||||
|
<!--
|
||||||
|
@endcond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Introduction
|
||||||
|
|
||||||
|
Usually, service locators are tightly bound to the services they expose and it's
|
||||||
|
hard to define a general purpose solution.<br/>
|
||||||
|
This tiny class tries to fill the gap and gets rid of the burden of defining a
|
||||||
|
different specific locator for each application.
|
||||||
|
|
||||||
|
# Service locator
|
||||||
|
|
||||||
|
The service locator API tries to mimic that of `std::optional` and adds some
|
||||||
|
extra functionalities on top of it such as allocator support.<br/>
|
||||||
|
There are a couple of functions to set up a service, namely `emplace` and
|
||||||
|
`allocate_emplace`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
entt::locator<interface>::emplace<service>(argument);
|
||||||
|
entt::locator<interface>::allocate_emplace<service>(allocator, argument);
|
||||||
|
```
|
||||||
|
|
||||||
|
The difference is that the latter expects an allocator as the first argument and
|
||||||
|
uses it to allocate the service itself.<br/>
|
||||||
|
Once a service is set up, it's retrieved using the `value` function:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
interface &service = entt::locator<interface>::value();
|
||||||
|
```
|
||||||
|
|
||||||
|
Since the service may not be set (and therefore this function may result in an
|
||||||
|
undefined behavior), the `has_value` and `value_or` functions are also available
|
||||||
|
to test a service locator and to get a fallback service in case there is none:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
if(entt::locator<interface>::has_value()) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
interface &service = entt::locator<interface>::value_or<fallback_impl>(argument);
|
||||||
|
```
|
||||||
|
|
||||||
|
All arguments are used only if necessary, that is, if a service doesn't already
|
||||||
|
exist and therefore the fallback service is constructed and returned. In all
|
||||||
|
other cases, they are discarded.<br/>
|
||||||
|
Finally, to reset a service, use the `reset` function.
|
||||||
|
|
||||||
|
## Opaque handles
|
||||||
|
|
||||||
|
Sometimes it's useful to _transfer_ a copy of a service to another locator. For
|
||||||
|
example, when working across boundaries it's common to _share_ a service with a
|
||||||
|
dynamically loaded module.<br/>
|
||||||
|
Options aren't much in this case. Among these is the possibility of _exporting_
|
||||||
|
services and assigning them to a different locator.
|
||||||
|
|
||||||
|
This is what the `handle` and `reset` functions are meant for.<br/>
|
||||||
|
The former returns an opaque object useful for _exporting_ (or rather, obtaining
|
||||||
|
a reference to) a service. The latter also accepts an optional argument to a
|
||||||
|
handle which then allows users to reset a service by initializing it with an
|
||||||
|
opaque handle:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto handle = entt::locator<interface>::handle();
|
||||||
|
entt::locator<interface>::reset(handle);
|
||||||
|
```
|
||||||
|
|
||||||
|
It's worth noting that it's possible to get handles for uninitialized services
|
||||||
|
and use them with other locators. Of course, all a user will get is to have an
|
||||||
|
uninitialized service elsewhere as well.
|
||||||
|
|
||||||
|
Note that exporting a service allows users to _share_ the object currently set
|
||||||
|
in a locator. Replacing it won't replace the element even where a service has
|
||||||
|
been configured with a handle to the previous item.<br/>
|
||||||
|
In other words, if an audio service is replaced with a null object to silence an
|
||||||
|
application and the original service was shared, this operation won't propagate
|
||||||
|
to the other locators. Therefore, a module that share the ownership of the
|
||||||
|
original audio service is still able to emit sounds.
|
1023
docs/md/meta.md
Normal file
1023
docs/md/meta.md
Normal file
File diff suppressed because it is too large
Load Diff
359
docs/md/poly.md
Normal file
359
docs/md/poly.md
Normal file
@ -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.
|
212
docs/md/process.md
Normal file
212
docs/md/process.md
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
# Crash Course: cooperative scheduler
|
||||||
|
|
||||||
|
<!--
|
||||||
|
@cond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
# Table of Contents
|
||||||
|
|
||||||
|
* [Introduction](#introduction)
|
||||||
|
* [The process](#the-process)
|
||||||
|
* [Adaptor](#adaptor)
|
||||||
|
* [The scheduler](#the-scheduler)
|
||||||
|
<!--
|
||||||
|
@endcond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Introduction
|
||||||
|
|
||||||
|
Sometimes processes are a useful tool to work around the strict definition of a
|
||||||
|
system and introduce logic in a different way, usually without resorting to the
|
||||||
|
introduction of other components.
|
||||||
|
|
||||||
|
`EnTT` offers a minimal support to this paradigm by introducing a few classes
|
||||||
|
that users can use to define and execute cooperative processes.
|
||||||
|
|
||||||
|
# The process
|
||||||
|
|
||||||
|
A typical process must inherit from the `process` class template that stays true
|
||||||
|
to the CRTP idiom. Moreover, derived classes must specify what's the intended
|
||||||
|
type for elapsed times.
|
||||||
|
|
||||||
|
A process should expose publicly the following member functions whether needed
|
||||||
|
(note that it isn't required to define a function unless the derived class wants
|
||||||
|
to _override_ the default behavior):
|
||||||
|
|
||||||
|
* `void update(Delta, void *);`
|
||||||
|
|
||||||
|
It's invoked once per tick until a process is explicitly aborted or it
|
||||||
|
terminates either with or without errors. Even though it's not mandatory to
|
||||||
|
declare this member function, as a rule of thumb each process should at
|
||||||
|
least define it to work properly. The `void *` parameter is an opaque pointer
|
||||||
|
to user data (if any) forwarded directly to the process during an update.
|
||||||
|
|
||||||
|
* `void init();`
|
||||||
|
|
||||||
|
It's invoked when the process joins the running queue of a scheduler. This
|
||||||
|
happens as soon as it's attached to the scheduler if the process is a top
|
||||||
|
level one, otherwise when it replaces its parent if the process is a
|
||||||
|
continuation.
|
||||||
|
|
||||||
|
* `void succeeded();`
|
||||||
|
|
||||||
|
It's invoked in case of success, immediately after an update and during the
|
||||||
|
same tick.
|
||||||
|
|
||||||
|
* `void failed();`
|
||||||
|
|
||||||
|
It's invoked in case of errors, immediately after an update and during the
|
||||||
|
same tick.
|
||||||
|
|
||||||
|
* `void aborted();`
|
||||||
|
|
||||||
|
It's invoked only if a process is explicitly aborted. There is no guarantee
|
||||||
|
that it executes in the same tick, this depends solely on whether the
|
||||||
|
process is aborted immediately or not.
|
||||||
|
|
||||||
|
Derived classes can also change the internal state of a process by invoking
|
||||||
|
`succeed` and `fail`, as well as `pause` and `unpause` the process itself. All
|
||||||
|
these are protected member functions made available to be able to manage the
|
||||||
|
life cycle of a process from a derived class.
|
||||||
|
|
||||||
|
Here is a minimal example for the sake of curiosity:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct my_process: entt::process<my_process, std::uint32_t> {
|
||||||
|
using delta_type = std::uint32_t;
|
||||||
|
|
||||||
|
my_process(delta_type delay)
|
||||||
|
: remaining{delay}
|
||||||
|
{}
|
||||||
|
|
||||||
|
void update(delta_type delta, void *) {
|
||||||
|
remaining -= std::min(remaining, delta);
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
if(!remaining) {
|
||||||
|
succeed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
delta_type remaining;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adaptor
|
||||||
|
|
||||||
|
Lambdas and functors can't be used directly with a scheduler for they are not
|
||||||
|
properly defined processes with managed life cycles.<br/>
|
||||||
|
This class helps in filling the gap and turning lambdas and functors into
|
||||||
|
full-featured processes usable by a scheduler.
|
||||||
|
|
||||||
|
The function call operator has a signature similar to the one of the `update`
|
||||||
|
function of a process but for the fact that it receives two extra arguments to
|
||||||
|
call whenever a process is terminated with success or with an error:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void(Delta delta, void *data, auto succeed, auto fail);
|
||||||
|
```
|
||||||
|
|
||||||
|
Parameters have the following meaning:
|
||||||
|
|
||||||
|
* `delta` is the elapsed time.
|
||||||
|
* `data` is an opaque pointer to user data if any, `nullptr` otherwise.
|
||||||
|
* `succeed` is a function to call when a process terminates with success.
|
||||||
|
* `fail` is a function to call when a process terminates with errors.
|
||||||
|
|
||||||
|
Both `succeed` and `fail` accept no parameters at all.
|
||||||
|
|
||||||
|
Note that usually users shouldn't worry about creating adaptors at all. A
|
||||||
|
scheduler creates them internally each and every time a lambda or a functor is
|
||||||
|
used as a process.
|
||||||
|
|
||||||
|
# The scheduler
|
||||||
|
|
||||||
|
A cooperative scheduler runs different processes and helps managing their life
|
||||||
|
cycles.
|
||||||
|
|
||||||
|
Each process is invoked once per tick. If it terminates, it's removed
|
||||||
|
automatically from the scheduler and it's never invoked again. Otherwise it's
|
||||||
|
a good candidate to run one more time the next tick.<br/>
|
||||||
|
A process can also have a child. In this case, the parent process is replaced
|
||||||
|
with its child when it terminates and only if it returns with success. In case
|
||||||
|
of errors, both the parent process and its child are discarded. This way, it's
|
||||||
|
easy to create chain of processes to run sequentially.
|
||||||
|
|
||||||
|
Using a scheduler is straightforward. To create it, users must provide only the
|
||||||
|
type for the elapsed times and no arguments at all:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
entt::scheduler<std::uint32_t> scheduler;
|
||||||
|
```
|
||||||
|
|
||||||
|
It has member functions to query its internal data structures, like `empty` or
|
||||||
|
`size`, as well as a `clear` utility to reset it to a clean state:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// checks if there are processes still running
|
||||||
|
const auto empty = scheduler.empty();
|
||||||
|
|
||||||
|
// gets the number of processes still running
|
||||||
|
entt::scheduler<std::uint32_t>::size_type size = scheduler.size();
|
||||||
|
|
||||||
|
// resets the scheduler to its initial state and discards all the processes
|
||||||
|
scheduler.clear();
|
||||||
|
```
|
||||||
|
|
||||||
|
To attach a process to a scheduler there are mainly two ways:
|
||||||
|
|
||||||
|
* If the process inherits from the `process` class template, it's enough to
|
||||||
|
indicate its type and submit all the parameters required to construct it to
|
||||||
|
the `attach` member function:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
scheduler.attach<my_process>(1000u);
|
||||||
|
```
|
||||||
|
|
||||||
|
* Otherwise, in case of a lambda or a functor, it's enough to provide an
|
||||||
|
instance of the class to the `attach` member function:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
scheduler.attach([](auto...){ /* ... */ });
|
||||||
|
```
|
||||||
|
|
||||||
|
In both cases, the return value is an opaque object that offers a `then` member
|
||||||
|
function to use to create chains of processes to run sequentially.<br/>
|
||||||
|
As a minimal example of use:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// schedules a task in the form of a lambda function
|
||||||
|
scheduler.attach([](auto delta, void *, auto succeed, auto fail) {
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
// appends a child in the form of another lambda function
|
||||||
|
.then([](auto delta, void *, auto succeed, auto fail) {
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
// appends a child in the form of a process class
|
||||||
|
.then<my_process>(1000u);
|
||||||
|
```
|
||||||
|
|
||||||
|
To update a scheduler and therefore all its processes, the `update` member
|
||||||
|
function is the way to go:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// updates all the processes, no user data are provided
|
||||||
|
scheduler.update(delta);
|
||||||
|
|
||||||
|
// updates all the processes and provides them with custom data
|
||||||
|
scheduler.update(delta, &data);
|
||||||
|
```
|
||||||
|
|
||||||
|
In addition to these functions, the scheduler offers an `abort` member function
|
||||||
|
that can be used to discard all the running processes at once:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// aborts all the processes abruptly ...
|
||||||
|
scheduler.abort(true);
|
||||||
|
|
||||||
|
// ... or gracefully during the next tick
|
||||||
|
scheduler.abort();
|
||||||
|
```
|
75
docs/md/reference.md
Normal file
75
docs/md/reference.md
Normal file
@ -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.
|
191
docs/md/resource.md
Normal file
191
docs/md/resource.md
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
# Crash Course: resource management
|
||||||
|
|
||||||
|
<!--
|
||||||
|
@cond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
# Table of Contents
|
||||||
|
|
||||||
|
* [Introduction](#introduction)
|
||||||
|
* [The resource, the loader and the cache](#the-resource-the-loader-and-the-cache)
|
||||||
|
* [Resource handle](#resource-handle)
|
||||||
|
* [Loaders](#loader)
|
||||||
|
* [The cache class](#the-cache)
|
||||||
|
<!--
|
||||||
|
@endcond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Introduction
|
||||||
|
|
||||||
|
Resource management is usually one of the most critical parts of a game.
|
||||||
|
Solutions are often tuned to the particular application. There exist several
|
||||||
|
approaches and all of them are perfectly fine as long as they fit the
|
||||||
|
requirements of the piece of software in which they are used.<br/>
|
||||||
|
Examples are loading everything on start, loading on request, predictive
|
||||||
|
loading, and so on.
|
||||||
|
|
||||||
|
`EnTT` doesn't pretend to offer a _one-fits-all_ solution for the different
|
||||||
|
cases.<br/>
|
||||||
|
Instead, the library comes with a minimal, general purpose resource cache that
|
||||||
|
might be useful in many cases.
|
||||||
|
|
||||||
|
# The resource, the loader and the cache
|
||||||
|
|
||||||
|
Resource, loader and cache are the three main actors for the purpose.<br/>
|
||||||
|
The _resource_ is an image, an audio, a video or any other type:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct my_resource { const int value; };
|
||||||
|
```
|
||||||
|
|
||||||
|
The _loader_ is a callable type the aim of which is to load a specific resource:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct my_loader final {
|
||||||
|
using result_type = std::shared_ptr<my_resource>;
|
||||||
|
|
||||||
|
result_type operator()(int value) const {
|
||||||
|
// ...
|
||||||
|
return std::make_shared<my_resource>(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Its function operator can accept any arguments and should return a value of the
|
||||||
|
declared result type (`std::shared_ptr<my_resource>` in the example).<br/>
|
||||||
|
A loader can also overload its function call operator to make it possible to
|
||||||
|
construct the same or another resource from different lists of arguments.
|
||||||
|
|
||||||
|
Finally, a cache is a specialization of a class template tailored to a specific
|
||||||
|
resource and (optionally) a loader:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using my_cache = entt::resource_cache<my_resource, my_loader>;
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
my_cache cache{};
|
||||||
|
```
|
||||||
|
|
||||||
|
The class is designed to create different caches for different resource types
|
||||||
|
and to manage each one independently in the most appropriate way.<br/>
|
||||||
|
As a (very) trivial example, audio tracks can survive in most of the scenes of
|
||||||
|
an application while meshes can be associated with a single scene only, then
|
||||||
|
discarded when a player leaves it.
|
||||||
|
|
||||||
|
## Resource handle
|
||||||
|
|
||||||
|
Resources aren't returned directly to the caller. Instead, they are wrapped in a
|
||||||
|
_resource handle_, an instance of the `entt::resource` class template.<br/>
|
||||||
|
For those who know the _flyweight design pattern_ already, that's exactly what
|
||||||
|
it is. To all others, this is the time to brush up on some notions instead.
|
||||||
|
|
||||||
|
A shared pointer could have been used as a resource handle. In fact, the default
|
||||||
|
implementation mostly maps the interface of its standard counterpart and only
|
||||||
|
adds a few things on top of it.<br/>
|
||||||
|
However, the handle in `EnTT` is designed as a standalone class template. This
|
||||||
|
is due to the fact that specializing a class in the standard library is often
|
||||||
|
undefined behavior while having the ability to specialize the handle for one,
|
||||||
|
more or all resource types could help over time.
|
||||||
|
|
||||||
|
## Loaders
|
||||||
|
|
||||||
|
A loader is responsible for _loading_ resources (quite obviously).<br/>
|
||||||
|
By default, it's just a callable object that forwards its arguments to the
|
||||||
|
resource itself. That is, a _passthrough type_. All the work is demanded to the
|
||||||
|
constructor(s) of the resource itself.<br/>
|
||||||
|
Loaders also are fully customizable as expected.
|
||||||
|
|
||||||
|
A custom loader is a class with at least one function call operator and a member
|
||||||
|
type named `result_type`.<br/>
|
||||||
|
The loader isn't required to return a resource handle. As long as `return_type`
|
||||||
|
is suitable for constructing a handle, that's fine.
|
||||||
|
|
||||||
|
When using the default handle, it expects a resource type which is convertible
|
||||||
|
to or suitable for constructing an `std::shared_ptr<Type>` (where `Type` is the
|
||||||
|
actual resource type).<br/>
|
||||||
|
In other terms, the loader should return shared pointers to the given resource
|
||||||
|
type. However, this isn't mandatory. Users can easily get around this constraint
|
||||||
|
by specializing both the handle and the loader.
|
||||||
|
|
||||||
|
A cache forwards all its arguments to the loader if required. This means that
|
||||||
|
loaders can also support tag dispatching to offer different loading policies:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct my_loader {
|
||||||
|
using result_type = std::shared_ptr<my_resource>;
|
||||||
|
|
||||||
|
struct from_disk_tag{};
|
||||||
|
struct from_network_tag{};
|
||||||
|
|
||||||
|
template<typename Args>
|
||||||
|
result_type operator()(from_disk_tag, Args&&... args) {
|
||||||
|
// ...
|
||||||
|
return std::make_shared<my_resource>(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Args>
|
||||||
|
result_type operator()(from_network_tag, Args&&... args) {
|
||||||
|
// ...
|
||||||
|
return std::make_shared<my_resource>(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This makes the whole loading logic quite flexible and easy to extend over time.
|
||||||
|
|
||||||
|
## The cache class
|
||||||
|
|
||||||
|
The cache is the class that is asked to _connect the dots_.<br/>
|
||||||
|
It loads the resources, stores them aside and returns handles as needed:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
entt::resource_cache<my_resource, my_loader> cache{};
|
||||||
|
```
|
||||||
|
|
||||||
|
Under the hood, a cache is nothing more than a map where the key value has type
|
||||||
|
`entt::id_type` while the mapped value is whatever type its loader returns.<br/>
|
||||||
|
For this reason, it offers most of the functionalities a user would expect from
|
||||||
|
a map, such as `empty` or `size` and so on. Similarly, it's an iterable type
|
||||||
|
that also supports indexing by resource id:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
for(auto [id, res]: cache) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
if(entt::resource<my_resource> res = cache["resource/id"_hs]; res) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Please, refer to the inline documentation for all the details about the other
|
||||||
|
functions (such as `contains` or `erase`).
|
||||||
|
|
||||||
|
Set aside the part of the API that this class _shares_ with a map, it also adds
|
||||||
|
something on top of it in order to address the most common requirements of a
|
||||||
|
resource cache.<br/>
|
||||||
|
In particular, it doesn't have an `emplace` member function which is replaced by
|
||||||
|
`load` and `force_load` instead (where the former loads a new resource only if
|
||||||
|
not present while the second triggers a forced loading in any case):
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto ret = cache.load("resource/id"_hs);
|
||||||
|
|
||||||
|
// true only if the resource was not already present
|
||||||
|
const bool loaded = ret.second;
|
||||||
|
|
||||||
|
// takes the resource handle pointed to by the returned iterator
|
||||||
|
entt::resource<my_resource> res = ret.first->second;
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the hashed string is used for convenience in the example above.<br/>
|
||||||
|
Resource identifiers are nothing more than integral values. Therefore, plain
|
||||||
|
numbers as well as non-class enum value are accepted.
|
||||||
|
|
||||||
|
It's worth mentioning that the iterators of a cache as well as its indexing
|
||||||
|
operators return resource handles rather than instances of the mapped type.<br/>
|
||||||
|
Since the cache has no control over the loader and a resource isn't required to
|
||||||
|
also be convertible to bool, these handles can be invalid. This usually means an
|
||||||
|
error in the user logic but it may also be an _expected_ event.<br/>
|
||||||
|
It's therefore recommended to verify handles validity with a check in debug (for
|
||||||
|
example, when loading) or an appropriate logic in retail.
|
549
docs/md/signal.md
Normal file
549
docs/md/signal.md
Normal file
@ -0,0 +1,549 @@
|
|||||||
|
# Crash Course: events, signals and everything in between
|
||||||
|
|
||||||
|
<!--
|
||||||
|
@cond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
# Table of Contents
|
||||||
|
|
||||||
|
* [Introduction](#introduction)
|
||||||
|
* [Delegate](#delegate)
|
||||||
|
* [Runtime arguments](#runtime-arguments)
|
||||||
|
* [Lambda support](#lambda-support)
|
||||||
|
* [Signals](#signals)
|
||||||
|
* [Event dispatcher](#event-dispatcher)
|
||||||
|
* [Named queues](#named-queues)
|
||||||
|
* [Event emitter](#event-emitter)
|
||||||
|
<!--
|
||||||
|
@endcond TURN_OFF_DOXYGEN
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Introduction
|
||||||
|
|
||||||
|
Signals are more often than not a core part of games and software architectures
|
||||||
|
in general.<br/>
|
||||||
|
They help to decouple the various parts of a system while allowing them to
|
||||||
|
communicate with each other somehow.
|
||||||
|
|
||||||
|
The so called _modern C++_ comes with a tool that can be useful in this regard,
|
||||||
|
the `std::function`. As an example, it can be used to create delegates.<br/>
|
||||||
|
However, there is no guarantee that an `std::function` doesn't perform
|
||||||
|
allocations under the hood and this could be problematic sometimes. Furthermore,
|
||||||
|
it solves a problem but may not adapt well to other requirements that may arise
|
||||||
|
from time to time.
|
||||||
|
|
||||||
|
In case that the flexibility and power of an `std::function` isn't required or
|
||||||
|
if the price to pay for them is too high, `EnTT` offers a complete set of
|
||||||
|
lightweight classes to solve the same and many other problems.
|
||||||
|
|
||||||
|
# Delegate
|
||||||
|
|
||||||
|
A delegate can be used as a general purpose invoker with no memory overhead for
|
||||||
|
free functions and member functions provided along with an instance on which to
|
||||||
|
invoke them.<br/>
|
||||||
|
It doesn't claim to be a drop-in replacement for an `std::function`, so don't
|
||||||
|
expect to use it whenever an `std::function` fits well. That said, it's most
|
||||||
|
likely even a better fit than an `std::function` in a lot of cases, so expect to
|
||||||
|
use it quite a lot anyway.
|
||||||
|
|
||||||
|
The interface is trivial. It offers a default constructor to create empty
|
||||||
|
delegates:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
entt::delegate<int(int)> delegate{};
|
||||||
|
```
|
||||||
|
|
||||||
|
What is needed to create an instance is to specify the type of the function the
|
||||||
|
delegate _accepts_, that is the signature of the functions it models.<br/>
|
||||||
|
However, attempting to use an empty delegate by invoking its function call
|
||||||
|
operator results in undefined behavior or most likely a crash.
|
||||||
|
|
||||||
|
There exist a few overloads of the `connect` member function to initialize a
|
||||||
|
delegate:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
int f(int i) { return i; }
|
||||||
|
|
||||||
|
struct my_struct {
|
||||||
|
int f(const int &i) const { return i; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// bind a free function to the delegate
|
||||||
|
delegate.connect<&f>();
|
||||||
|
|
||||||
|
// bind a member function to the delegate
|
||||||
|
my_struct instance;
|
||||||
|
delegate.connect<&my_struct::f>(instance);
|
||||||
|
```
|
||||||
|
|
||||||
|
The delegate class also accepts data members, if needed. In this case, the
|
||||||
|
function type of the delegate is such that the parameter list is empty and the
|
||||||
|
value of the data member is at least convertible to the return type.
|
||||||
|
|
||||||
|
Free functions having type equivalent to `void(T &, args...)` are accepted as
|
||||||
|
well. The first argument `T &` is considered a payload and the function will
|
||||||
|
receive it back every time it's invoked. In other terms, this works just fine
|
||||||
|
with the above definition:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void g(const char &c, int i) { /* ... */ }
|
||||||
|
const char c = 'c';
|
||||||
|
|
||||||
|
delegate.connect<&g>(c);
|
||||||
|
delegate(42);
|
||||||
|
```
|
||||||
|
|
||||||
|
The function `g` is invoked with a reference to `c` and `42`. However, the
|
||||||
|
function type of the delegate is still `void(int)`. This is also the signature
|
||||||
|
of its function call operator.<br/>
|
||||||
|
Another interesting aspect of the delegate class is that it accepts functions
|
||||||
|
with a list of parameters that is shorter than that of its function type:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void g() { /* ... */ }
|
||||||
|
delegate.connect<&g>();
|
||||||
|
delegate(42);
|
||||||
|
```
|
||||||
|
|
||||||
|
Where the function type of the delegate is `void(int)` as above. It goes without
|
||||||
|
saying that the extra arguments are silently discarded internally.<br/>
|
||||||
|
This is a nice-to-have feature in a lot of cases, as an example when the
|
||||||
|
`delegate` class is used as a building block of a signal-slot system.
|
||||||
|
|
||||||
|
To create and initialize a delegate at once, there are a few specialized
|
||||||
|
constructors. Because of the rules of the language, the listener is provided by
|
||||||
|
means of the `entt::connect_arg` variable template:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
entt::delegate<int(int)> func{entt::connect_arg<&f>};
|
||||||
|
```
|
||||||
|
|
||||||
|
Aside `connect`, a `disconnect` counterpart isn't provided. Instead, there
|
||||||
|
exists a `reset` member function to use to clear a delegate.<br/>
|
||||||
|
To know if a delegate is empty, it can be used explicitly in every conditional
|
||||||
|
statement:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
if(delegate) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, to invoke a delegate, the function call operator is the way to go as
|
||||||
|
already shown in the examples above:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto ret = delegate(42);
|
||||||
|
```
|
||||||
|
|
||||||
|
In all cases, listeners don't have to strictly follow the signature of the
|
||||||
|
delegate. As long as a listener can be invoked with the given arguments to yield
|
||||||
|
a result that is convertible to the given result type, everything works just
|
||||||
|
fine.
|
||||||
|
|
||||||
|
As a side note, members of classes may or may not be associated with instances.
|
||||||
|
If they are not, the first argument of the function type must be that of the
|
||||||
|
class on which the members operate and an instance of this class must obviously
|
||||||
|
be passed when invoking the delegate:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
entt::delegate<void(my_struct &, int)> delegate;
|
||||||
|
delegate.connect<&my_struct::f>();
|
||||||
|
|
||||||
|
my_struct instance;
|
||||||
|
delegate(instance, 42);
|
||||||
|
```
|
||||||
|
|
||||||
|
In this case, it's not possible to _deduce_ the function type since the first
|
||||||
|
argument doesn't necessarily have to be a reference (for example, it can be a
|
||||||
|
pointer, as well as a const reference).<br/>
|
||||||
|
Therefore, the function type must be declared explicitly for unbound members.
|
||||||
|
|
||||||
|
## Runtime arguments
|
||||||
|
|
||||||
|
The `delegate` class is meant to be used primarily with template arguments.
|
||||||
|
However, as a consequence of its design, it also offers minimal support for
|
||||||
|
runtime arguments.<br/>
|
||||||
|
When used like this, some features aren't supported though. In particular:
|
||||||
|
|
||||||
|
* Curried functions aren't accepted.
|
||||||
|
* Functions with an argument list that differs from that of the delegate aren't
|
||||||
|
supported.
|
||||||
|
* Return type and types of arguments **must** coincide with those of the
|
||||||
|
delegate and _being at least convertible_ isn't enough anymore.
|
||||||
|
|
||||||
|
Moreover, for a given function type `Ret(Args...)`, the signature of the
|
||||||
|
functions connected at runtime must necessarily be `Ret(const void *, Args...)`.
|
||||||
|
|
||||||
|
Runtime arguments can be passed both to the constructor of a delegate and to the
|
||||||
|
`connect` member function. An optional parameter is also accepted in both cases.
|
||||||
|
This argument is used to pass arbitrary user data back and forth as a
|
||||||
|
`const void *` upon invocation.<br/>
|
||||||
|
To connect a function to a delegate _in the hard way_:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
int func(const void *ptr, int i) { return *static_cast<const int *>(ptr) * i; }
|
||||||
|
const int value = 42;
|
||||||
|
|
||||||
|
// use the constructor ...
|
||||||
|
entt::delegate delegate{&func, &value};
|
||||||
|
|
||||||
|
// ... or the connect member function
|
||||||
|
delegate.connect(&func, &value);
|
||||||
|
```
|
||||||
|
|
||||||
|
The type of the delegate is deduced from the function if possible. In this case,
|
||||||
|
since the first argument is an implementation detail, the deduced function type
|
||||||
|
is `int(int)`.<br/>
|
||||||
|
Invoking a delegate built in this way follows the same rules as previously
|
||||||
|
explained.
|
||||||
|
|
||||||
|
## Lambda support
|
||||||
|
|
||||||
|
In general, the `delegate` class doesn't fully support lambda functions in all
|
||||||
|
their nuances. The reason is pretty simple: a `delegate` isn't a drop-in
|
||||||
|
replacement for an `std::function`. Instead, it tries to overcome the problems
|
||||||
|
with the latter.<br/>
|
||||||
|
That being said, non-capturing lambda functions are supported, even though some
|
||||||
|
features aren't available in this case.
|
||||||
|
|
||||||
|
This is a logical consequence of the support for connecting functions at
|
||||||
|
runtime. Therefore, lambda functions undergo the same rules and
|
||||||
|
limitations.<br/>
|
||||||
|
In fact, since non-capturing lambda functions decay to pointers to functions,
|
||||||
|
they can be used with a `delegate` as if they were _normal functions_ with
|
||||||
|
optional payload:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
my_struct instance;
|
||||||
|
|
||||||
|
// use the constructor ...
|
||||||
|
entt::delegate delegate{+[](const void *ptr, int value) {
|
||||||
|
return static_cast<const my_struct *>(ptr)->f(value);
|
||||||
|
}, &instance};
|
||||||
|
|
||||||
|
// ... or the connect member function
|
||||||
|
delegate.connect([](const void *ptr, int value) {
|
||||||
|
return static_cast<const my_struct *>(ptr)->f(value);
|
||||||
|
}, &instance);
|
||||||
|
```
|
||||||
|
|
||||||
|
As above, the first parameter (`const void *`) isn't part of the function type
|
||||||
|
of the delegate and is used to dispatch arbitrary user data back and forth. In
|
||||||
|
other terms, the function type of the delegate above is `int(int)`.
|
||||||
|
|
||||||
|
# Signals
|
||||||
|
|
||||||
|
Signal handlers work with references to classes, function pointers and pointers
|
||||||
|
to members. Listeners can be any kind of objects and users are in charge of
|
||||||
|
connecting and disconnecting them from a signal to avoid crashes due to
|
||||||
|
different lifetimes. On the other side, performance shouldn't be affected that
|
||||||
|
much by the presence of such a signal handler.<br/>
|
||||||
|
Signals make use of delegates internally and therefore they undergo the same
|
||||||
|
rules and offer similar functionalities. It may be a good idea to consult the
|
||||||
|
documentation of the `delegate` class for further information.
|
||||||
|
|
||||||
|
A signal handler is can be used as a private data member without exposing any
|
||||||
|
_publish_ functionality to the clients of a class.<br/>
|
||||||
|
The basic idea is to impose a clear separation between the signal itself and the
|
||||||
|
`sink` class, that is a tool to be used to connect and disconnect listeners on
|
||||||
|
the fly.
|
||||||
|
|
||||||
|
The API of a signal handler is straightforward. If a collector is supplied to
|
||||||
|
the signal when something is published, all the values returned by its listeners
|
||||||
|
are literally _collected_ and used later by the caller. Otherwise, the class
|
||||||
|
works just like a plain signal that emits events from time to time.<br/>
|
||||||
|
To create instances of signal handlers it's sufficient to provide the type of
|
||||||
|
function to which they refer:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
entt::sigh<void(int, char)> signal;
|
||||||
|
```
|
||||||
|
|
||||||
|
Signals offer all the basic functionalities required to know how many listeners
|
||||||
|
they contain (`size`) or if they contain at least a listener (`empty`), as well
|
||||||
|
as a function to use to swap handlers (`swap`).
|
||||||
|
|
||||||
|
Besides them, there are member functions to use both to connect and disconnect
|
||||||
|
listeners in all their forms by means of a sink:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void foo(int, char) { /* ... */ }
|
||||||
|
|
||||||
|
struct listener {
|
||||||
|
void bar(const int &, char) { /* ... */ }
|
||||||
|
};
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
entt::sink sink{signal};
|
||||||
|
listener instance;
|
||||||
|
|
||||||
|
sink.connect<&foo>();
|
||||||
|
sink.connect<&listener::bar>(instance);
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// disconnects a free function
|
||||||
|
sink.disconnect<&foo>();
|
||||||
|
|
||||||
|
// disconnect a member function of an instance
|
||||||
|
sink.disconnect<&listener::bar>(instance);
|
||||||
|
|
||||||
|
// disconnect all member functions of an instance, if any
|
||||||
|
sink.disconnect(instance);
|
||||||
|
|
||||||
|
// discards all listeners at once
|
||||||
|
sink.disconnect();
|
||||||
|
```
|
||||||
|
|
||||||
|
As shown above, listeners don't have to strictly follow the signature of the
|
||||||
|
signal. As long as a listener can be invoked with the given arguments to yield a
|
||||||
|
result that is convertible to the given return type, everything works just
|
||||||
|
fine.<br/>
|
||||||
|
It's also possible to connect a listener before other elements already contained
|
||||||
|
by the signal. The `before` function returns a `sink` object that is correctly
|
||||||
|
initialized for the purpose and can be used to connect one or more listeners in
|
||||||
|
order and in the desired position:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
sink.before<&foo>().connect<&listener::bar>(instance);
|
||||||
|
```
|
||||||
|
|
||||||
|
In all cases, the `connect` member function returns by default a `connection`
|
||||||
|
object to be used as an alternative to break a connection by means of its
|
||||||
|
`release` member function.<br/>
|
||||||
|
A `scoped_connection` can also be created from a connection. In this case, the
|
||||||
|
link is broken automatically as soon as the object goes out of scope.
|
||||||
|
|
||||||
|
Once listeners are attached (or even if there are no listeners at all), events
|
||||||
|
and data in general are published through a signal by means of the `publish`
|
||||||
|
member function:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
signal.publish(42, 'c');
|
||||||
|
```
|
||||||
|
|
||||||
|
To collect data, the `collect` member function is used instead:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
int f() { return 0; }
|
||||||
|
int g() { return 1; }
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
entt::sigh<int()> signal;
|
||||||
|
entt::sink sink{signal};
|
||||||
|
|
||||||
|
sink.connect<&f>();
|
||||||
|
sink.connect<&g>();
|
||||||
|
|
||||||
|
std::vector<int> vec{};
|
||||||
|
signal.collect([&vec](int value) { vec.push_back(value); });
|
||||||
|
|
||||||
|
assert(vec[0] == 0);
|
||||||
|
assert(vec[1] == 1);
|
||||||
|
```
|
||||||
|
|
||||||
|
A collector must expose a function operator that accepts as an argument a type
|
||||||
|
to which the return type of the listeners can be converted. Moreover, it can
|
||||||
|
optionally return a boolean value that is true to stop collecting data, false
|
||||||
|
otherwise. This way one can avoid calling all the listeners in case it isn't
|
||||||
|
necessary.<br/>
|
||||||
|
Functors can also be used in place of a lambda. Since the collector is copied
|
||||||
|
when invoking the `collect` member function, `std::ref` is the way to go in this
|
||||||
|
case:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct my_collector {
|
||||||
|
std::vector<int> vec{};
|
||||||
|
|
||||||
|
bool operator()(int v) {
|
||||||
|
vec.push_back(v);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
my_collector collector;
|
||||||
|
signal.collect(std::ref(collector));
|
||||||
|
```
|
||||||
|
|
||||||
|
# Event dispatcher
|
||||||
|
|
||||||
|
The event dispatcher class allows users to trigger immediate events or to queue
|
||||||
|
and publish them all together later.<br/>
|
||||||
|
This class lazily instantiates its queues. Therefore, it's not necessary to
|
||||||
|
_announce_ the event types in advance:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// define a general purpose dispatcher
|
||||||
|
entt::dispatcher dispatcher{};
|
||||||
|
```
|
||||||
|
|
||||||
|
A listener registered with a dispatcher is such that its type offers one or more
|
||||||
|
member functions that take arguments of type `Event &` for any type of event,
|
||||||
|
regardless of the return value.<br/>
|
||||||
|
These functions are linked directly via `connect` to a _sink_:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct an_event { int value; };
|
||||||
|
struct another_event {};
|
||||||
|
|
||||||
|
struct listener {
|
||||||
|
void receive(const an_event &) { /* ... */ }
|
||||||
|
void method(const another_event &) { /* ... */ }
|
||||||
|
};
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
listener listener;
|
||||||
|
dispatcher.sink<an_event>().connect<&listener::receive>(listener);
|
||||||
|
dispatcher.sink<another_event>().connect<&listener::method>(listener);
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that connecting listeners within event handlers can result in undefined
|
||||||
|
behavior.<br/>
|
||||||
|
The `disconnect` member function is used to remove one listener at a time or all
|
||||||
|
of them at once:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
dispatcher.sink<an_event>().disconnect<&listener::receive>(listener);
|
||||||
|
dispatcher.sink<another_event>().disconnect(listener);
|
||||||
|
```
|
||||||
|
|
||||||
|
The `trigger` member function serves the purpose of sending an immediate event
|
||||||
|
to all the listeners registered so far:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
dispatcher.trigger(an_event{42});
|
||||||
|
dispatcher.trigger<another_event>();
|
||||||
|
```
|
||||||
|
|
||||||
|
Listeners are invoked immediately, order of execution isn't guaranteed. This
|
||||||
|
method can be used to push around urgent messages like an _is terminating_
|
||||||
|
notification on a mobile app.
|
||||||
|
|
||||||
|
On the other hand, the `enqueue` member function queues messages together and
|
||||||
|
helps to maintain control over the moment they are sent to listeners:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
dispatcher.enqueue<an_event>(42);
|
||||||
|
dispatcher.enqueue(another_event{});
|
||||||
|
```
|
||||||
|
|
||||||
|
Events are stored aside until the `update` member function is invoked:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// emits all the events of the given type at once
|
||||||
|
dispatcher.update<an_event>();
|
||||||
|
|
||||||
|
// emits all the events queued so far at once
|
||||||
|
dispatcher.update();
|
||||||
|
```
|
||||||
|
|
||||||
|
This way users can embed the dispatcher in a loop and literally dispatch events
|
||||||
|
once per tick to their systems.
|
||||||
|
|
||||||
|
## Named queues
|
||||||
|
|
||||||
|
All queues within a dispatcher are associated by default with an event type and
|
||||||
|
then retrieved from it.<br/>
|
||||||
|
However, it's possible to create queues with different _names_ (and therefore
|
||||||
|
also multiple queues for a single type). In fact, more or less all functions
|
||||||
|
also take an additional parameter. As an example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
dispatcher.sink<an_event>("custom"_hs).connect<&listener::receive>(listener);
|
||||||
|
```
|
||||||
|
|
||||||
|
In this case, the term _name_ is misused as these are actual numeric identifiers
|
||||||
|
of type `id_type`.<br/>
|
||||||
|
An exception to this rule is the `enqueue` function. There is no additional
|
||||||
|
parameter for it but rather a different function:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
dispatcher.enqueue_hint<an_event>("custom"_hs, 42);
|
||||||
|
```
|
||||||
|
|
||||||
|
This is mainly due to the template argument deduction rules and unfortunately
|
||||||
|
there is no real (elegant) way to avoid it.
|
||||||
|
|
||||||
|
# Event emitter
|
||||||
|
|
||||||
|
A general purpose event emitter thought mainly for those cases where it comes to
|
||||||
|
working with asynchronous stuff.<br/>
|
||||||
|
Originally designed to fit the requirements of
|
||||||
|
[`uvw`](https://github.com/skypjack/uvw) (a wrapper for `libuv` written in
|
||||||
|
modern C++), it was adapted later to be included in this library.
|
||||||
|
|
||||||
|
To create an emitter type, derived classes must inherit from the base as:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct my_emitter: emitter<my_emitter> {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Handlers for the different events are created internally on the fly. It's not
|
||||||
|
required to specify in advance the full list of accepted events.<br/>
|
||||||
|
Moreover, whenever an event is published, an emitter also passes a reference
|
||||||
|
to itself to its listeners.
|
||||||
|
|
||||||
|
To create new instances of an emitter, no arguments are required:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
my_emitter emitter{};
|
||||||
|
```
|
||||||
|
|
||||||
|
Listeners are movable and callable objects (free functions, lambdas, functors,
|
||||||
|
`std::function`s, whatever) whose function type is compatible with:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void(Type &, my_emitter &)
|
||||||
|
```
|
||||||
|
|
||||||
|
Where `Type` is the type of event they want to receive.<br/>
|
||||||
|
To attach a listener to an emitter, there exists the `on` member function:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
emitter.on<my_event>([](const my_event &event, my_emitter &emitter) {
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Similarly, the `reset` member function is used to disconnect listeners given a
|
||||||
|
type while `clear` is used to disconnect all listeners at once:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// resets the listener for my_event
|
||||||
|
emitter.erase<my_event>();
|
||||||
|
|
||||||
|
// resets all listeners
|
||||||
|
emitter.clear()
|
||||||
|
```
|
||||||
|
|
||||||
|
To send an event to the listener registered on a given type, the `publish`
|
||||||
|
function is the way to go:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct my_event { int i; };
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
emitter.publish(my_event{42});
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, the `empty` member function tests if there exists at least a listener
|
||||||
|
registered with the event emitter while `contains` is used to check if a given
|
||||||
|
event type is associated with a valid listener:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
if(emitter.contains<my_event>()) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This class introduces a _nice-to-have_ model based on events and listeners.<br/>
|
||||||
|
More in general, it's a handy tool when the derived classes _wrap_ asynchronous
|
||||||
|
operations but it's not limited to such uses.
|
107
docs/md/unreal.md
Normal file
107
docs/md/unreal.md
Normal file
@ -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.
|
34
entt.imp
Normal file
34
entt.imp
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
[
|
||||||
|
{ "include": [ "@<gtest/internal/.*>", "private", "<gtest/gtest.h>", "public" ] },
|
||||||
|
{ "include": [ "@<gtest/gtest-.*>", "private", "<gtest/gtest.h>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/container/fwd.hpp[\">]", "private", "<entt/container/dense_map.hpp>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/container/fwd.hpp[\">]", "private", "<entt/container/dense_set.hpp>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/core/fwd.hpp[\">]", "private", "<entt/core/any.hpp>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/core/fwd.hpp[\">]", "private", "<entt/core/family.hpp>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/core/fwd.hpp[\">]", "private", "<entt/core/hashed_string.hpp>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/core/fwd.hpp[\">]", "private", "<entt/core/ident.hpp>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/core/fwd.hpp[\">]", "private", "<entt/core/monostate.hpp>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/core/fwd.hpp[\">]", "private", "<entt/core/type_info.hpp>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/core/fwd.hpp[\">]", "private", "<entt/core/type_traits.hpp>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/entity/fwd.hpp[\">]", "private", "<entt/entity/entity.hpp>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/entity/fwd.hpp[\">]", "private", "<entt/entity/group.hpp>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/entity/fwd.hpp[\">]", "private", "<entt/entity/handle.hpp>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/entity/fwd.hpp[\">]", "private", "<entt/entity/helper.hpp>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/entity/fwd.hpp[\">]", "private", "<entt/entity/observer.hpp>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/entity/fwd.hpp[\">]", "private", "<entt/entity/organizer.hpp>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/entity/fwd.hpp[\">]", "private", "<entt/entity/registry.hpp>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/entity/fwd.hpp[\">]", "private", "<entt/entity/runtime_view.hpp>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/entity/fwd.hpp[\">]", "private", "<entt/entity/snapshot.hpp>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/entity/fwd.hpp[\">]", "private", "<entt/entity/sparse_set.hpp>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/entity/fwd.hpp[\">]", "private", "<entt/entity/storage.hpp>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/entity/fwd.hpp[\">]", "private", "<entt/entity/view.hpp>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/meta/fwd.hpp[\">]", "private", "<entt/meta/meta.hpp>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/poly/fwd.hpp[\">]", "private", "<entt/poly/poly.hpp>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/resource/fwd.hpp[\">]", "private", "<entt/resource/cache.hpp>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/resource/fwd.hpp[\">]", "private", "<entt/resource/loader.hpp>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/resource/fwd.hpp[\">]", "private", "<entt/resource/resource.hpp>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/signal/fwd.hpp[\">]", "private", "<entt/signal/delegate.hpp>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/signal/fwd.hpp[\">]", "private", "<entt/signal/dispatcher.hpp>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/signal/fwd.hpp[\">]", "private", "<entt/signal/emitter.hpp>", "public" ] },
|
||||||
|
{ "include": [ "@[\"<].*/signal/fwd.hpp[\">]", "private", "<entt/signal/sigh.hpp>", "public" ] }
|
||||||
|
]
|
3
natvis/entt/config.natvis
Normal file
3
natvis/entt/config.natvis
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
|
||||||
|
</AutoVisualizer>
|
33
natvis/entt/container.natvis
Normal file
33
natvis/entt/container.natvis
Normal file
@ -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>
|
32
natvis/entt/core.natvis
Normal file
32
natvis/entt/core.natvis
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?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>{{ name={ alias,na } }}</DisplayString>
|
||||||
|
<Expand>
|
||||||
|
<Item Name="[hash]">identifier</Item>
|
||||||
|
<Item Name="[index]">seq</Item>
|
||||||
|
</Expand>
|
||||||
|
</Type>
|
||||||
|
</AutoVisualizer>
|
153
natvis/entt/entity.natvis
Normal file
153
natvis/entt/entity.natvis
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
<?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.ctx.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={ epool.size() } }}</DisplayString>
|
||||||
|
<Expand>
|
||||||
|
<Item IncludeView="simple" Name="[epool]">epool,view(simple)nr</Item>
|
||||||
|
<Synthetic Name="[epool]" ExcludeView="simple">
|
||||||
|
<DisplayString>{ epool.size() }</DisplayString>
|
||||||
|
<Expand>
|
||||||
|
<CustomListItems>
|
||||||
|
<Variable Name="pos" InitialValue="0" />
|
||||||
|
<Variable Name="last" InitialValue="epool.size()"/>
|
||||||
|
<Loop>
|
||||||
|
<Break Condition="pos == last"/>
|
||||||
|
<If Condition="to_entity(epool[pos]) == pos">
|
||||||
|
<Item Name="[{ pos }]">epool[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 }]">epool[it]</Item>
|
||||||
|
<Exec>it = to_entity(epool[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.ctx.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 }:{ base_type::packed[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::basic_runtime_view<*>">
|
||||||
|
<DisplayString Condition="pools.size() != 0u">{{ size_hint={ pools[0]->packed.size() } }}</DisplayString>
|
||||||
|
<DisplayString>{{ size_hint=0 }}</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>
|
19
natvis/entt/graph.natvis
Normal file
19
natvis/entt/graph.natvis
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
|
||||||
|
<Type Name="entt::adjacency_matrix<*>">
|
||||||
|
<DisplayString>{{ size={ vert } }}</DisplayString>
|
||||||
|
<Expand>
|
||||||
|
<CustomListItems>
|
||||||
|
<Variable Name="pos" InitialValue="0" />
|
||||||
|
<Variable Name="last" InitialValue="vert * vert"/>
|
||||||
|
<Loop>
|
||||||
|
<Break Condition="pos == last"/>
|
||||||
|
<If Condition="matrix[pos] != 0u">
|
||||||
|
<Item Name="{pos / vert}">pos % vert</Item>
|
||||||
|
</If>
|
||||||
|
<Exec>++pos</Exec>
|
||||||
|
</Loop>
|
||||||
|
</CustomListItems>
|
||||||
|
</Expand>
|
||||||
|
</Type>
|
||||||
|
</AutoVisualizer>
|
3
natvis/entt/locator.natvis
Normal file
3
natvis/entt/locator.natvis
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
|
||||||
|
</AutoVisualizer>
|
121
natvis/entt/meta.natvis
Normal file
121
natvis/entt/meta.natvis
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
|
||||||
|
<Type Name="entt::internal::meta_base_node">
|
||||||
|
<DisplayString>{{}}</DisplayString>
|
||||||
|
</Type>
|
||||||
|
<Type Name="entt::internal::meta_conv_node">
|
||||||
|
<DisplayString>{{}}</DisplayString>
|
||||||
|
</Type>
|
||||||
|
<Type Name="entt::internal::meta_ctor_node">
|
||||||
|
<DisplayString>{{ arity={ arity } }}</DisplayString>
|
||||||
|
</Type>
|
||||||
|
<Type Name="entt::internal::meta_data_node">
|
||||||
|
<Intrinsic Name="has_property" Expression="!!(traits & property)">
|
||||||
|
<Parameter Name="property" Type="int"/>
|
||||||
|
</Intrinsic>
|
||||||
|
<DisplayString>{{ arity={ arity } }}</DisplayString>
|
||||||
|
<Expand>
|
||||||
|
<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>
|
||||||
|
<Item Name="[prop]">prop</Item>
|
||||||
|
</Expand>
|
||||||
|
</Type>
|
||||||
|
<Type Name="entt::internal::meta_func_node" >
|
||||||
|
<Intrinsic Name="has_property" Expression="!!(traits & property)">
|
||||||
|
<Parameter Name="property" Type="int"/>
|
||||||
|
</Intrinsic>
|
||||||
|
<DisplayString>{{ arity={ arity } }}</DisplayString>
|
||||||
|
<Expand>
|
||||||
|
<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>
|
||||||
|
<Item Name="[next]" Condition="next != nullptr">*next</Item>
|
||||||
|
<Item Name="[prop]">prop</Item>
|
||||||
|
</Expand>
|
||||||
|
</Type>
|
||||||
|
<Type Name="entt::internal::meta_prop_node">
|
||||||
|
<DisplayString>{ value }</DisplayString>
|
||||||
|
</Type>
|
||||||
|
<Type Name="entt::internal::meta_template_node">
|
||||||
|
<DisplayString>{{ arity={ arity } }}</DisplayString>
|
||||||
|
</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_integral]">has_property(entt::internal::meta_traits::is_integral)</Item>
|
||||||
|
<Item Name="[is_signed]">has_property(entt::internal::meta_traits::is_signed)</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_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="[from_void]">from_void != nullptr</Item>
|
||||||
|
<Item Name="[template_info]">templ</Item>
|
||||||
|
<Item Name="[details]" Condition="details != nullptr">*details</Item>
|
||||||
|
</Expand>
|
||||||
|
</Type>
|
||||||
|
<Type Name="entt::meta_any">
|
||||||
|
<DisplayString Condition="node.info != nullptr">{{ type={ node.info->alias,na }, policy={ storage.mode,en } }}</DisplayString>
|
||||||
|
<DisplayString>{{}}</DisplayString>
|
||||||
|
<Expand>
|
||||||
|
<ExpandedItem>node</ExpandedItem>
|
||||||
|
<Item Name="[context]" Condition="ctx != nullptr">ctx->value</Item>
|
||||||
|
</Expand>
|
||||||
|
</Type>
|
||||||
|
<Type Name="entt::meta_handle">
|
||||||
|
<DisplayString>{ any }</DisplayString>
|
||||||
|
</Type>
|
||||||
|
<Type Name="entt::meta_associative_container">
|
||||||
|
<DisplayString>{ storage }</DisplayString>
|
||||||
|
<Expand>
|
||||||
|
<Item Name="[context]" Condition="ctx != nullptr">ctx->value</Item>
|
||||||
|
</Expand>
|
||||||
|
</Type>
|
||||||
|
<Type Name="entt::meta_sequence_container">
|
||||||
|
<DisplayString>{ storage }</DisplayString>
|
||||||
|
<Expand>
|
||||||
|
<Item Name="[context]" Condition="ctx != nullptr">ctx->value</Item>
|
||||||
|
</Expand>
|
||||||
|
</Type>
|
||||||
|
<Type Name="entt::meta_data">
|
||||||
|
<DisplayString Condition="node != nullptr">{ *node }</DisplayString>
|
||||||
|
<DisplayString>{{}}</DisplayString>
|
||||||
|
<Expand>
|
||||||
|
<ExpandedItem Condition="node != nullptr">node</ExpandedItem>
|
||||||
|
<Item Name="[context]" Condition="ctx != nullptr">ctx->value</Item>
|
||||||
|
</Expand>
|
||||||
|
</Type>
|
||||||
|
<Type Name="entt::meta_func">
|
||||||
|
<DisplayString Condition="node != nullptr">{ *node }</DisplayString>
|
||||||
|
<DisplayString>{{}}</DisplayString>
|
||||||
|
<Expand>
|
||||||
|
<ExpandedItem Condition="node != nullptr">node</ExpandedItem>
|
||||||
|
<Item Name="[context]" Condition="ctx != nullptr">ctx->value</Item>
|
||||||
|
</Expand>
|
||||||
|
</Type>
|
||||||
|
<Type Name="entt::meta_prop">
|
||||||
|
<DisplayString Condition="node != nullptr">{ *node }</DisplayString>
|
||||||
|
<DisplayString>{{}}</DisplayString>
|
||||||
|
<Expand>
|
||||||
|
<ExpandedItem Condition="node != nullptr">node</ExpandedItem>
|
||||||
|
<Item Name="[context]" Condition="ctx != nullptr">ctx->value</Item>
|
||||||
|
</Expand>
|
||||||
|
</Type>
|
||||||
|
<Type Name="entt::meta_type">
|
||||||
|
<DisplayString>{ node }</DisplayString>
|
||||||
|
<Expand>
|
||||||
|
<ExpandedItem>node</ExpandedItem>
|
||||||
|
<Item Name="[context]" Condition="ctx != nullptr">ctx->value</Item>
|
||||||
|
</Expand>
|
||||||
|
</Type>
|
||||||
|
</AutoVisualizer>
|
3
natvis/entt/platform.natvis
Normal file
3
natvis/entt/platform.natvis
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
|
||||||
|
</AutoVisualizer>
|
6
natvis/entt/poly.natvis
Normal file
6
natvis/entt/poly.natvis
Normal file
@ -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>
|
3
natvis/entt/process.natvis
Normal file
3
natvis/entt/process.natvis
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
|
||||||
|
</AutoVisualizer>
|
15
natvis/entt/resource.natvis
Normal file
15
natvis/entt/resource.natvis
Normal file
@ -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>
|
56
natvis/entt/signal.natvis
Normal file
56
natvis/entt/signal.natvis
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
|
||||||
|
<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::basic_dispatcher<*>">
|
||||||
|
<Intrinsic Name="size" Expression="pools.first_base::value.packed.first_base::value.size()"/>
|
||||||
|
<DisplayString>{{ size={ size() } }}</DisplayString>
|
||||||
|
<Expand>
|
||||||
|
<Synthetic Name="[pools]">
|
||||||
|
<DisplayString>{ size() }</DisplayString>
|
||||||
|
<Expand>
|
||||||
|
<IndexListItems>
|
||||||
|
<Size>size()</Size>
|
||||||
|
<ValueNode>*pools.first_base::value.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::emitter<*>">
|
||||||
|
<DisplayString>{{ size={ handlers.first_base::value.packed.first_base::value.size() } }}</DisplayString>
|
||||||
|
</Type>
|
||||||
|
<Type Name="entt::connection">
|
||||||
|
<DisplayString>{{ bound={ signal != nullptr } }}</DisplayString>
|
||||||
|
</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>
|
299
scripts/amalgamate.py
Normal file
299
scripts/amalgamate.py
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# coding=utf-8
|
||||||
|
|
||||||
|
# amalgamate.py - Amalgamate C source and header files.
|
||||||
|
# Copyright (c) 2012, Erik Edlund <erik.edlund@32767.se>
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
# are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# * Neither the name of Erik Edlund, nor the names of its contributors may
|
||||||
|
# be used to endorse or promote products derived from this software without
|
||||||
|
# specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
from __future__ import division
|
||||||
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
class Amalgamation(object):
|
||||||
|
|
||||||
|
# Prepends self.source_path to file_path if needed.
|
||||||
|
def actual_path(self, file_path):
|
||||||
|
if not os.path.isabs(file_path):
|
||||||
|
file_path = os.path.join(self.source_path, file_path)
|
||||||
|
return file_path
|
||||||
|
|
||||||
|
# Search included file_path in self.include_paths and
|
||||||
|
# in source_dir if specified.
|
||||||
|
def find_included_file(self, file_path, source_dir):
|
||||||
|
search_dirs = self.include_paths[:]
|
||||||
|
if source_dir:
|
||||||
|
search_dirs.insert(0, source_dir)
|
||||||
|
|
||||||
|
for search_dir in search_dirs:
|
||||||
|
search_path = os.path.join(search_dir, file_path)
|
||||||
|
if os.path.isfile(self.actual_path(search_path)):
|
||||||
|
return search_path
|
||||||
|
return None
|
||||||
|
|
||||||
|
def __init__(self, args):
|
||||||
|
with open(args.config, 'r') as f:
|
||||||
|
config = json.loads(f.read())
|
||||||
|
for key in config:
|
||||||
|
setattr(self, key, config[key])
|
||||||
|
|
||||||
|
self.verbose = args.verbose == "yes"
|
||||||
|
self.prologue = args.prologue
|
||||||
|
self.source_path = args.source_path
|
||||||
|
self.included_files = []
|
||||||
|
|
||||||
|
# Generate the amalgamation and write it to the target file.
|
||||||
|
def generate(self):
|
||||||
|
amalgamation = ""
|
||||||
|
|
||||||
|
if self.prologue:
|
||||||
|
with open(self.prologue, 'r') as f:
|
||||||
|
amalgamation += datetime.datetime.now().strftime(f.read())
|
||||||
|
|
||||||
|
if self.verbose:
|
||||||
|
print("Config:")
|
||||||
|
print(" target = {0}".format(self.target))
|
||||||
|
print(" working_dir = {0}".format(os.getcwd()))
|
||||||
|
print(" include_paths = {0}".format(self.include_paths))
|
||||||
|
print("Creating amalgamation:")
|
||||||
|
for file_path in self.sources:
|
||||||
|
# Do not check the include paths while processing the source
|
||||||
|
# list, all given source paths must be correct.
|
||||||
|
# actual_path = self.actual_path(file_path)
|
||||||
|
print(" - processing \"{0}\"".format(file_path))
|
||||||
|
t = TranslationUnit(file_path, self, True)
|
||||||
|
amalgamation += t.content
|
||||||
|
|
||||||
|
with open(self.target, 'w') as f:
|
||||||
|
f.write(amalgamation)
|
||||||
|
|
||||||
|
print("...done!\n")
|
||||||
|
if self.verbose:
|
||||||
|
print("Files processed: {0}".format(self.sources))
|
||||||
|
print("Files included: {0}".format(self.included_files))
|
||||||
|
print("")
|
||||||
|
|
||||||
|
|
||||||
|
def _is_within(match, matches):
|
||||||
|
for m in matches:
|
||||||
|
if match.start() > m.start() and \
|
||||||
|
match.end() < m.end():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class TranslationUnit(object):
|
||||||
|
# // C++ comment.
|
||||||
|
cpp_comment_pattern = re.compile(r"//.*?\n")
|
||||||
|
|
||||||
|
# /* C comment. */
|
||||||
|
c_comment_pattern = re.compile(r"/\*.*?\*/", re.S)
|
||||||
|
|
||||||
|
# "complex \"stri\\\ng\" value".
|
||||||
|
string_pattern = re.compile("[^']" r'".*?(?<=[^\\])"', re.S)
|
||||||
|
|
||||||
|
# Handle simple include directives. Support for advanced
|
||||||
|
# directives where macros and defines needs to expanded is
|
||||||
|
# not a concern right now.
|
||||||
|
include_pattern = re.compile(
|
||||||
|
r'#\s*include\s+(<|")(?P<path>.*?)("|>)', re.S)
|
||||||
|
|
||||||
|
# #pragma once
|
||||||
|
pragma_once_pattern = re.compile(r'#\s*pragma\s+once', re.S)
|
||||||
|
|
||||||
|
# Search for pattern in self.content, add the match to
|
||||||
|
# contexts if found and update the index accordingly.
|
||||||
|
def _search_content(self, index, pattern, contexts):
|
||||||
|
match = pattern.search(self.content, index)
|
||||||
|
if match:
|
||||||
|
contexts.append(match)
|
||||||
|
return match.end()
|
||||||
|
return index + 2
|
||||||
|
|
||||||
|
# Return all the skippable contexts, i.e., comments and strings
|
||||||
|
def _find_skippable_contexts(self):
|
||||||
|
# Find contexts in the content in which a found include
|
||||||
|
# directive should not be processed.
|
||||||
|
skippable_contexts = []
|
||||||
|
|
||||||
|
# Walk through the content char by char, and try to grab
|
||||||
|
# skippable contexts using regular expressions when found.
|
||||||
|
i = 1
|
||||||
|
content_len = len(self.content)
|
||||||
|
while i < content_len:
|
||||||
|
j = i - 1
|
||||||
|
current = self.content[i]
|
||||||
|
previous = self.content[j]
|
||||||
|
|
||||||
|
if current == '"':
|
||||||
|
# String value.
|
||||||
|
i = self._search_content(j, self.string_pattern,
|
||||||
|
skippable_contexts)
|
||||||
|
elif current == '*' and previous == '/':
|
||||||
|
# C style comment.
|
||||||
|
i = self._search_content(j, self.c_comment_pattern,
|
||||||
|
skippable_contexts)
|
||||||
|
elif current == '/' and previous == '/':
|
||||||
|
# C++ style comment.
|
||||||
|
i = self._search_content(j, self.cpp_comment_pattern,
|
||||||
|
skippable_contexts)
|
||||||
|
else:
|
||||||
|
# Skip to the next char.
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
return skippable_contexts
|
||||||
|
|
||||||
|
# Returns True if the match is within list of other matches
|
||||||
|
|
||||||
|
# Removes pragma once from content
|
||||||
|
def _process_pragma_once(self):
|
||||||
|
content_len = len(self.content)
|
||||||
|
if content_len < len("#include <x>"):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Find contexts in the content in which a found include
|
||||||
|
# directive should not be processed.
|
||||||
|
skippable_contexts = self._find_skippable_contexts()
|
||||||
|
|
||||||
|
pragmas = []
|
||||||
|
pragma_once_match = self.pragma_once_pattern.search(self.content)
|
||||||
|
while pragma_once_match:
|
||||||
|
if not _is_within(pragma_once_match, skippable_contexts):
|
||||||
|
pragmas.append(pragma_once_match)
|
||||||
|
|
||||||
|
pragma_once_match = self.pragma_once_pattern.search(self.content,
|
||||||
|
pragma_once_match.end())
|
||||||
|
|
||||||
|
# Handle all collected pragma once directives.
|
||||||
|
prev_end = 0
|
||||||
|
tmp_content = ''
|
||||||
|
for pragma_match in pragmas:
|
||||||
|
tmp_content += self.content[prev_end:pragma_match.start()]
|
||||||
|
prev_end = pragma_match.end()
|
||||||
|
tmp_content += self.content[prev_end:]
|
||||||
|
self.content = tmp_content
|
||||||
|
|
||||||
|
# Include all trivial #include directives into self.content.
|
||||||
|
def _process_includes(self):
|
||||||
|
content_len = len(self.content)
|
||||||
|
if content_len < len("#include <x>"):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Find contexts in the content in which a found include
|
||||||
|
# directive should not be processed.
|
||||||
|
skippable_contexts = self._find_skippable_contexts()
|
||||||
|
|
||||||
|
# Search for include directives in the content, collect those
|
||||||
|
# which should be included into the content.
|
||||||
|
includes = []
|
||||||
|
include_match = self.include_pattern.search(self.content)
|
||||||
|
while include_match:
|
||||||
|
if not _is_within(include_match, skippable_contexts):
|
||||||
|
include_path = include_match.group("path")
|
||||||
|
search_same_dir = include_match.group(1) == '"'
|
||||||
|
found_included_path = self.amalgamation.find_included_file(
|
||||||
|
include_path, self.file_dir if search_same_dir else None)
|
||||||
|
if found_included_path:
|
||||||
|
includes.append((include_match, found_included_path))
|
||||||
|
|
||||||
|
include_match = self.include_pattern.search(self.content,
|
||||||
|
include_match.end())
|
||||||
|
|
||||||
|
# Handle all collected include directives.
|
||||||
|
prev_end = 0
|
||||||
|
tmp_content = ''
|
||||||
|
for include in includes:
|
||||||
|
include_match, found_included_path = include
|
||||||
|
tmp_content += self.content[prev_end:include_match.start()]
|
||||||
|
tmp_content += "// {0}\n".format(include_match.group(0))
|
||||||
|
if found_included_path not in self.amalgamation.included_files:
|
||||||
|
t = TranslationUnit(found_included_path, self.amalgamation, False)
|
||||||
|
tmp_content += t.content
|
||||||
|
prev_end = include_match.end()
|
||||||
|
tmp_content += self.content[prev_end:]
|
||||||
|
self.content = tmp_content
|
||||||
|
|
||||||
|
return len(includes)
|
||||||
|
|
||||||
|
# Make all content processing
|
||||||
|
def _process(self):
|
||||||
|
if not self.is_root:
|
||||||
|
self._process_pragma_once()
|
||||||
|
self._process_includes()
|
||||||
|
|
||||||
|
def __init__(self, file_path, amalgamation, is_root):
|
||||||
|
self.file_path = file_path
|
||||||
|
self.file_dir = os.path.dirname(file_path)
|
||||||
|
self.amalgamation = amalgamation
|
||||||
|
self.is_root = is_root
|
||||||
|
|
||||||
|
self.amalgamation.included_files.append(self.file_path)
|
||||||
|
|
||||||
|
actual_path = self.amalgamation.actual_path(file_path)
|
||||||
|
if not os.path.isfile(actual_path):
|
||||||
|
raise IOError("File not found: \"{0}\"".format(file_path))
|
||||||
|
with open(actual_path, 'r') as f:
|
||||||
|
self.content = f.read()
|
||||||
|
self._process()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
description = "Amalgamate C source and header files."
|
||||||
|
usage = " ".join([
|
||||||
|
"amalgamate.py",
|
||||||
|
"[-v]",
|
||||||
|
"-c path/to/config.json",
|
||||||
|
"-s path/to/source/dir",
|
||||||
|
"[-p path/to/prologue.(c|h)]"
|
||||||
|
])
|
||||||
|
argsparser = argparse.ArgumentParser(
|
||||||
|
description=description, usage=usage)
|
||||||
|
|
||||||
|
argsparser.add_argument("-v", "--verbose", dest="verbose",
|
||||||
|
choices=["yes", "no"], metavar="", help="be verbose")
|
||||||
|
|
||||||
|
argsparser.add_argument("-c", "--config", dest="config",
|
||||||
|
required=True, metavar="", help="path to a JSON config file")
|
||||||
|
|
||||||
|
argsparser.add_argument("-s", "--source", dest="source_path",
|
||||||
|
required=True, metavar="", help="source code path")
|
||||||
|
|
||||||
|
argsparser.add_argument("-p", "--prologue", dest="prologue",
|
||||||
|
required=False, metavar="", help="path to a C prologue file")
|
||||||
|
|
||||||
|
amalgamation = Amalgamation(argsparser.parse_args())
|
||||||
|
amalgamation.generate()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
8
scripts/config.json
Normal file
8
scripts/config.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"project": "entt",
|
||||||
|
"target": "single_include/entt/entt.hpp",
|
||||||
|
"sources": [
|
||||||
|
"src/entt/entt.hpp"
|
||||||
|
],
|
||||||
|
"include_paths": ["src"]
|
||||||
|
}
|
60
scripts/update_homebrew.sh
Executable file
60
scripts/update_homebrew.sh
Executable file
@ -0,0 +1,60 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# only argument should be the version to upgrade to
|
||||||
|
if [ $# != 1 ]
|
||||||
|
then
|
||||||
|
echo "Expected a version tag like v2.7.1"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
VERSION="$1"
|
||||||
|
URL="https://github.com/skypjack/entt/archive/$VERSION.tar.gz"
|
||||||
|
FORMULA="entt.rb"
|
||||||
|
|
||||||
|
echo "Updating homebrew package to $VERSION"
|
||||||
|
|
||||||
|
echo "Cloning..."
|
||||||
|
git clone https://github.com/skypjack/homebrew-entt.git
|
||||||
|
if [ $? != 0 ]
|
||||||
|
then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
cd homebrew-entt
|
||||||
|
|
||||||
|
# download the repo at the version
|
||||||
|
# exit with error messages if curl fails
|
||||||
|
echo "Curling..."
|
||||||
|
curl "$URL" --location --fail --silent --show-error --output archive.tar.gz
|
||||||
|
if [ $? != 0 ]
|
||||||
|
then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# compute sha256 hash
|
||||||
|
echo "Hashing..."
|
||||||
|
HASH="$(openssl sha256 archive.tar.gz | cut -d " " -f 2)"
|
||||||
|
|
||||||
|
# delete the archive
|
||||||
|
rm archive.tar.gz
|
||||||
|
|
||||||
|
echo "Sedding..."
|
||||||
|
|
||||||
|
# change the url in the formula file
|
||||||
|
# the slashes in the URL must be escaped
|
||||||
|
ESCAPED_URL="$(echo "$URL" | sed -e 's/[\/&]/\\&/g')"
|
||||||
|
sed -i -e '/url/s/".*"/"'$ESCAPED_URL'"/' $FORMULA
|
||||||
|
|
||||||
|
# change the hash in the formula file
|
||||||
|
sed -i -e '/sha256/s/".*"/"'$HASH'"/' $FORMULA
|
||||||
|
|
||||||
|
# delete temporary file created by sed
|
||||||
|
rm -rf "$FORMULA-e"
|
||||||
|
|
||||||
|
# update remote repo
|
||||||
|
echo "Gitting..."
|
||||||
|
git add entt.rb
|
||||||
|
git commit -m "Update to $VERSION"
|
||||||
|
git push origin master
|
||||||
|
|
||||||
|
# out of homebrew-entt dir
|
||||||
|
cd ..
|
82838
single_include/entt/entt.hpp
Normal file
82838
single_include/entt/entt.hpp
Normal file
File diff suppressed because it is too large
Load Diff
81
src/entt/config/config.h
Normal file
81
src/entt/config/config.h
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
#ifndef ENTT_CONFIG_CONFIG_H
|
||||||
|
#define ENTT_CONFIG_CONFIG_H
|
||||||
|
|
||||||
|
#include "version.h"
|
||||||
|
|
||||||
|
#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION)
|
||||||
|
# define ENTT_CONSTEXPR
|
||||||
|
# define ENTT_THROW throw
|
||||||
|
# define ENTT_TRY try
|
||||||
|
# define ENTT_CATCH catch(...)
|
||||||
|
#else
|
||||||
|
# define ENTT_CONSTEXPR constexpr // use only with throwing functions (waiting for C++20)
|
||||||
|
# define ENTT_THROW
|
||||||
|
# define ENTT_TRY if(true)
|
||||||
|
# define ENTT_CATCH if(false)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#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
|
||||||
|
|
||||||
|
#ifndef ENTT_SPARSE_PAGE
|
||||||
|
# define ENTT_SPARSE_PAGE 4096
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ENTT_PACKED_PAGE
|
||||||
|
# define ENTT_PACKED_PAGE 1024
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ENTT_DISABLE_ASSERT
|
||||||
|
# undef ENTT_ASSERT
|
||||||
|
# define ENTT_ASSERT(condition, msg) (void(0))
|
||||||
|
#elif !defined ENTT_ASSERT
|
||||||
|
# include <cassert>
|
||||||
|
# define ENTT_ASSERT(condition, msg) assert(condition)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ENTT_DISABLE_ASSERT
|
||||||
|
# undef ENTT_ASSERT_CONSTEXPR
|
||||||
|
# define ENTT_ASSERT_CONSTEXPR(condition, msg) (void(0))
|
||||||
|
#elif !defined ENTT_ASSERT_CONSTEXPR
|
||||||
|
# define ENTT_ASSERT_CONSTEXPR(condition, msg) ENTT_ASSERT(condition, msg)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ENTT_NO_ETO
|
||||||
|
# define ENTT_ETO_TYPE(Type) void
|
||||||
|
#else
|
||||||
|
# define ENTT_ETO_TYPE(Type) Type
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#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
|
7
src/entt/config/macro.h
Normal file
7
src/entt/config/macro.h
Normal file
@ -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
|
14
src/entt/config/version.h
Normal file
14
src/entt/config/version.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#ifndef ENTT_CONFIG_VERSION_H
|
||||||
|
#define ENTT_CONFIG_VERSION_H
|
||||||
|
|
||||||
|
#include "macro.h"
|
||||||
|
|
||||||
|
#define ENTT_VERSION_MAJOR 3
|
||||||
|
#define ENTT_VERSION_MINOR 11
|
||||||
|
#define ENTT_VERSION_PATCH 1
|
||||||
|
|
||||||
|
#define ENTT_VERSION \
|
||||||
|
ENTT_XSTR(ENTT_VERSION_MAJOR) \
|
||||||
|
"." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH)
|
||||||
|
|
||||||
|
#endif
|
1060
src/entt/container/dense_map.hpp
Normal file
1060
src/entt/container/dense_map.hpp
Normal file
File diff suppressed because it is too large
Load Diff
895
src/entt/container/dense_set.hpp
Normal file
895
src/entt/container/dense_set.hpp
Normal file
@ -0,0 +1,895 @@
|
|||||||
|
#ifndef ENTT_CONTAINER_DENSE_SET_HPP
|
||||||
|
#define ENTT_CONTAINER_DENSE_SET_HPP
|
||||||
|
|
||||||
|
#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;
|
||||||
|
|
||||||
|
constexpr dense_set_iterator() noexcept
|
||||||
|
: it{} {}
|
||||||
|
|
||||||
|
constexpr dense_set_iterator(const It iter) noexcept
|
||||||
|
: it{iter} {}
|
||||||
|
|
||||||
|
template<typename Other, typename = std::enable_if_t<!std::is_same_v<It, Other> && std::is_constructible_v<It, Other>>>
|
||||||
|
constexpr dense_set_iterator(const dense_set_iterator<Other> &other) noexcept
|
||||||
|
: it{other.it} {}
|
||||||
|
|
||||||
|
constexpr dense_set_iterator &operator++() noexcept {
|
||||||
|
return ++it, *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr dense_set_iterator operator++(int) noexcept {
|
||||||
|
dense_set_iterator orig = *this;
|
||||||
|
return ++(*this), orig;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr dense_set_iterator &operator--() noexcept {
|
||||||
|
return --it, *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr dense_set_iterator operator--(int) noexcept {
|
||||||
|
dense_set_iterator orig = *this;
|
||||||
|
return operator--(), orig;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr dense_set_iterator &operator+=(const difference_type value) noexcept {
|
||||||
|
it += value;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr dense_set_iterator operator+(const difference_type value) const noexcept {
|
||||||
|
dense_set_iterator copy = *this;
|
||||||
|
return (copy += value);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr dense_set_iterator &operator-=(const difference_type value) noexcept {
|
||||||
|
return (*this += -value);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr dense_set_iterator operator-(const difference_type value) const noexcept {
|
||||||
|
return (*this + -value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept {
|
||||||
|
return it[value].second;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr pointer operator->() const noexcept {
|
||||||
|
return std::addressof(it->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr reference operator*() const noexcept {
|
||||||
|
return *operator->();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ILhs, typename IRhs>
|
||||||
|
friend constexpr std::ptrdiff_t operator-(const dense_set_iterator<ILhs> &, const dense_set_iterator<IRhs> &) noexcept;
|
||||||
|
|
||||||
|
template<typename ILhs, typename IRhs>
|
||||||
|
friend constexpr bool operator==(const dense_set_iterator<ILhs> &, const dense_set_iterator<IRhs> &) noexcept;
|
||||||
|
|
||||||
|
template<typename ILhs, typename IRhs>
|
||||||
|
friend constexpr bool operator<(const dense_set_iterator<ILhs> &, const dense_set_iterator<IRhs> &) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
It it;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename ILhs, typename IRhs>
|
||||||
|
[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) noexcept {
|
||||||
|
return lhs.it - rhs.it;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ILhs, typename IRhs>
|
||||||
|
[[nodiscard]] constexpr bool operator==(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) noexcept {
|
||||||
|
return lhs.it == rhs.it;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ILhs, typename IRhs>
|
||||||
|
[[nodiscard]] constexpr bool operator!=(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) noexcept {
|
||||||
|
return !(lhs == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ILhs, typename IRhs>
|
||||||
|
[[nodiscard]] constexpr bool operator<(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) noexcept {
|
||||||
|
return lhs.it < rhs.it;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ILhs, typename IRhs>
|
||||||
|
[[nodiscard]] constexpr bool operator>(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) noexcept {
|
||||||
|
return rhs < lhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ILhs, typename IRhs>
|
||||||
|
[[nodiscard]] constexpr bool operator<=(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) noexcept {
|
||||||
|
return !(lhs > rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ILhs, typename IRhs>
|
||||||
|
[[nodiscard]] constexpr bool operator>=(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) 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;
|
||||||
|
|
||||||
|
constexpr dense_set_local_iterator() noexcept
|
||||||
|
: it{},
|
||||||
|
offset{} {}
|
||||||
|
|
||||||
|
constexpr dense_set_local_iterator(It iter, const std::size_t pos) 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>>>
|
||||||
|
constexpr dense_set_local_iterator(const dense_set_local_iterator<Other> &other) noexcept
|
||||||
|
: it{other.it},
|
||||||
|
offset{other.offset} {}
|
||||||
|
|
||||||
|
constexpr dense_set_local_iterator &operator++() noexcept {
|
||||||
|
return offset = it[offset].first, *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr dense_set_local_iterator operator++(int) noexcept {
|
||||||
|
dense_set_local_iterator orig = *this;
|
||||||
|
return ++(*this), orig;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr pointer operator->() const noexcept {
|
||||||
|
return std::addressof(it[offset].second);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr reference operator*() const noexcept {
|
||||||
|
return *operator->();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr std::size_t index() const noexcept {
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
It it;
|
||||||
|
std::size_t offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename ILhs, typename IRhs>
|
||||||
|
[[nodiscard]] constexpr bool operator==(const dense_set_local_iterator<ILhs> &lhs, const dense_set_local_iterator<IRhs> &rhs) noexcept {
|
||||||
|
return lhs.index() == rhs.index();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ILhs, typename IRhs>
|
||||||
|
[[nodiscard]] constexpr bool operator!=(const dense_set_local_iterator<ILhs> &lhs, const dense_set_local_iterator<IRhs> &rhs) 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 noexcept {
|
||||||
|
return fast_mod(static_cast<size_type>(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) {
|
||||||
|
size_type *curr = sparse.first().data() + value_to_bucket(packed.first().back().second);
|
||||||
|
packed.first()[pos] = std::move(packed.first().back());
|
||||||
|
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 cnt Minimal number of buckets.
|
||||||
|
* @param allocator The allocator to use.
|
||||||
|
*/
|
||||||
|
dense_set(const size_type cnt, const allocator_type &allocator)
|
||||||
|
: dense_set{cnt, hasher{}, key_equal{}, allocator} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs an empty container with a given allocator, hash
|
||||||
|
* function and user supplied minimal number of buckets.
|
||||||
|
* @param cnt Minimal number of buckets.
|
||||||
|
* @param hash Hash function to use.
|
||||||
|
* @param allocator The allocator to use.
|
||||||
|
*/
|
||||||
|
dense_set(const size_type cnt, const hasher &hash, const allocator_type &allocator)
|
||||||
|
: dense_set{cnt, 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 cnt 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 cnt, 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(cnt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @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 &&) noexcept(std::is_nothrow_move_constructible_v<compressed_pair<sparse_container_type, hasher>> &&std::is_nothrow_move_constructible_v<compressed_pair<packed_container_type, key_equal>>) = 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 &&) noexcept(std::is_nothrow_move_assignable_v<compressed_pair<sparse_container_type, hasher>> &&std::is_nothrow_move_assignable_v<compressed_pair<packed_container_type, key_equal>>) = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the associated allocator.
|
||||||
|
* @return The associated allocator.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] constexpr allocator_type get_allocator() const 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 noexcept {
|
||||||
|
return packed.first().begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc cbegin */
|
||||||
|
[[nodiscard]] const_iterator begin() const noexcept {
|
||||||
|
return cbegin();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc begin */
|
||||||
|
[[nodiscard]] iterator begin() 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 noexcept {
|
||||||
|
return packed.first().end();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc cend */
|
||||||
|
[[nodiscard]] const_iterator end() const noexcept {
|
||||||
|
return cend();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc end */
|
||||||
|
[[nodiscard]] iterator end() 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 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 noexcept {
|
||||||
|
return packed.first().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the maximum possible number of elements.
|
||||||
|
* @return Maximum possible number of elements.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] size_type max_size() const noexcept {
|
||||||
|
return packed.first().max_size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @brief Clears the container. */
|
||||||
|
void clear() 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::decay_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 Returns the number of elements matching a value (either 1 or 0).
|
||||||
|
* @param key Key value of an element to search for.
|
||||||
|
* @return Number of elements matching the key (either 1 or 0).
|
||||||
|
*/
|
||||||
|
[[nodiscard]] size_type count(const value_type &key) const {
|
||||||
|
return find(key) != end();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the number of elements matching a key (either 1 or 0).
|
||||||
|
* @tparam Other Type of the key value of an element to search for.
|
||||||
|
* @param key Key value of an element to search for.
|
||||||
|
* @return Number of elements matching the key (either 1 or 0).
|
||||||
|
*/
|
||||||
|
template<typename Other>
|
||||||
|
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, size_type>>
|
||||||
|
count(const Other &key) const {
|
||||||
|
return find(key) != end();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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 Returns a range containing all elements with a given value.
|
||||||
|
* @param value Value of an element to search for.
|
||||||
|
* @return A pair of iterators pointing to the first element and past the
|
||||||
|
* last element of the range.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::pair<iterator, iterator> equal_range(const value_type &value) {
|
||||||
|
const auto it = find(value);
|
||||||
|
return {it, it + !(it == end())};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc equal_range */
|
||||||
|
[[nodiscard]] std::pair<const_iterator, const_iterator> equal_range(const value_type &value) const {
|
||||||
|
const auto it = find(value);
|
||||||
|
return {it, it + !(it == cend())};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a range containing all elements that compare _equivalent_
|
||||||
|
* to a given value.
|
||||||
|
* @tparam Other Type of an element to search for.
|
||||||
|
* @param value Value of an element to search for.
|
||||||
|
* @return A pair of iterators pointing to the first element and past the
|
||||||
|
* last element of the range.
|
||||||
|
*/
|
||||||
|
template<typename Other>
|
||||||
|
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, std::pair<iterator, iterator>>>
|
||||||
|
equal_range(const Other &value) {
|
||||||
|
const auto it = find(value);
|
||||||
|
return {it, it + !(it == end())};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc equal_range */
|
||||||
|
template<class Other>
|
||||||
|
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, std::pair<const_iterator, const_iterator>>>
|
||||||
|
equal_range(const Other &value) const {
|
||||||
|
const auto it = find(value);
|
||||||
|
return {it, it + !(it == cend())};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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(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 cnt New number of buckets.
|
||||||
|
*/
|
||||||
|
void rehash(const size_type cnt) {
|
||||||
|
auto value = cnt > minimum_capacity ? cnt : minimum_capacity;
|
||||||
|
const auto cap = static_cast<size_type>(size() / max_load_factor());
|
||||||
|
value = value > cap ? value : cap;
|
||||||
|
|
||||||
|
if(const auto sz = next_power_of_two(value); sz != bucket_count()) {
|
||||||
|
sparse.first().resize(sz);
|
||||||
|
|
||||||
|
for(auto &&elem: sparse.first()) {
|
||||||
|
elem = 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 cnt New number of elements.
|
||||||
|
*/
|
||||||
|
void reserve(const size_type cnt) {
|
||||||
|
packed.first().reserve(cnt);
|
||||||
|
rehash(static_cast<size_type>(std::ceil(cnt / 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
|
26
src/entt/container/fwd.hpp
Normal file
26
src/entt/container/fwd.hpp
Normal file
@ -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
|
137
src/entt/core/algorithm.hpp
Normal file
137
src/entt/core/algorithm.hpp
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
#ifndef ENTT_CORE_ALGORITHM_HPP
|
||||||
|
#define ENTT_CORE_ALGORITHM_HPP
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <functional>
|
||||||
|
#include <iterator>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
#include "utility.hpp"
|
||||||
|
|
||||||
|
namespace entt {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Function object to wrap `std::sort` in a class type.
|
||||||
|
*
|
||||||
|
* Unfortunately, `std::sort` cannot be passed as template argument to a class
|
||||||
|
* template or a function template.<br/>
|
||||||
|
* This class fills the gap by wrapping some flavors of `std::sort` in a
|
||||||
|
* function object.
|
||||||
|
*/
|
||||||
|
struct std_sort {
|
||||||
|
/**
|
||||||
|
* @brief Sorts the elements in a range.
|
||||||
|
*
|
||||||
|
* Sorts the elements in a range using the given binary comparison function.
|
||||||
|
*
|
||||||
|
* @tparam It Type of random access iterator.
|
||||||
|
* @tparam Compare Type of comparison function object.
|
||||||
|
* @tparam Args Types of arguments to forward to the sort function.
|
||||||
|
* @param first An iterator to the first element of the range to sort.
|
||||||
|
* @param last An iterator past the last element of the range to sort.
|
||||||
|
* @param compare A valid comparison function object.
|
||||||
|
* @param args Arguments to forward to the sort function, if any.
|
||||||
|
*/
|
||||||
|
template<typename It, typename Compare = std::less<>, typename... Args>
|
||||||
|
void operator()(It first, It last, Compare compare = Compare{}, Args &&...args) const {
|
||||||
|
std::sort(std::forward<Args>(args)..., std::move(first), std::move(last), std::move(compare));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! @brief Function object for performing insertion sort. */
|
||||||
|
struct insertion_sort {
|
||||||
|
/**
|
||||||
|
* @brief Sorts the elements in a range.
|
||||||
|
*
|
||||||
|
* Sorts the elements in a range using the given binary comparison function.
|
||||||
|
*
|
||||||
|
* @tparam It Type of random access iterator.
|
||||||
|
* @tparam Compare Type of comparison function object.
|
||||||
|
* @param first An iterator to the first element of the range to sort.
|
||||||
|
* @param last An iterator past the last element of the range to sort.
|
||||||
|
* @param compare A valid comparison function object.
|
||||||
|
*/
|
||||||
|
template<typename It, typename Compare = std::less<>>
|
||||||
|
void operator()(It first, It last, Compare compare = Compare{}) const {
|
||||||
|
if(first < last) {
|
||||||
|
for(auto it = first + 1; it < last; ++it) {
|
||||||
|
auto value = std::move(*it);
|
||||||
|
auto pre = it;
|
||||||
|
|
||||||
|
for(; pre > first && compare(value, *(pre - 1)); --pre) {
|
||||||
|
*pre = std::move(*(pre - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
*pre = std::move(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Function object for performing LSD radix sort.
|
||||||
|
* @tparam Bit Number of bits processed per pass.
|
||||||
|
* @tparam N Maximum number of bits to sort.
|
||||||
|
*/
|
||||||
|
template<std::size_t Bit, std::size_t N>
|
||||||
|
struct radix_sort {
|
||||||
|
static_assert((N % Bit) == 0, "The maximum number of bits to sort must be a multiple of the number of bits processed per pass");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sorts the elements in a range.
|
||||||
|
*
|
||||||
|
* Sorts the elements in a range using the given _getter_ to access the
|
||||||
|
* actual data to be sorted.
|
||||||
|
*
|
||||||
|
* This implementation is inspired by the online book
|
||||||
|
* [Physically Based Rendering](http://www.pbr-book.org/3ed-2018/Primitives_and_Intersection_Acceleration/Bounding_Volume_Hierarchies.html#RadixSort).
|
||||||
|
*
|
||||||
|
* @tparam It Type of random access iterator.
|
||||||
|
* @tparam Getter Type of _getter_ function object.
|
||||||
|
* @param first An iterator to the first element of the range to sort.
|
||||||
|
* @param last An iterator past the last element of the range to sort.
|
||||||
|
* @param getter A valid _getter_ function object.
|
||||||
|
*/
|
||||||
|
template<typename It, typename Getter = identity>
|
||||||
|
void operator()(It first, It last, Getter getter = Getter{}) const {
|
||||||
|
if(first < last) {
|
||||||
|
static constexpr auto mask = (1 << Bit) - 1;
|
||||||
|
static constexpr auto buckets = 1 << Bit;
|
||||||
|
static constexpr auto passes = N / Bit;
|
||||||
|
|
||||||
|
using value_type = typename std::iterator_traits<It>::value_type;
|
||||||
|
std::vector<value_type> aux(std::distance(first, last));
|
||||||
|
|
||||||
|
auto part = [getter = std::move(getter)](auto from, auto to, auto out, auto start) {
|
||||||
|
std::size_t index[buckets]{};
|
||||||
|
std::size_t count[buckets]{};
|
||||||
|
|
||||||
|
for(auto it = from; it != to; ++it) {
|
||||||
|
++count[(getter(*it) >> start) & mask];
|
||||||
|
}
|
||||||
|
|
||||||
|
for(std::size_t pos{}, end = buckets - 1u; pos < end; ++pos) {
|
||||||
|
index[pos + 1u] = index[pos] + count[pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto it = from; it != to; ++it) {
|
||||||
|
out[index[(getter(*it) >> start) & mask]++] = std::move(*it);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for(std::size_t pass = 0; pass < (passes & ~1); pass += 2) {
|
||||||
|
part(first, last, aux.begin(), pass * Bit);
|
||||||
|
part(aux.begin(), aux.end(), first, (pass + 1) * Bit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if constexpr(passes & 1) {
|
||||||
|
part(first, last, aux.begin(), (passes - 1) * Bit);
|
||||||
|
std::move(aux.begin(), aux.end(), first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace entt
|
||||||
|
|
||||||
|
#endif
|
510
src/entt/core/any.hpp
Normal file
510
src/entt/core/any.hpp
Normal file
@ -0,0 +1,510 @@
|
|||||||
|
#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 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cond TURN_OFF_DOXYGEN
|
||||||
|
* Internal details not to be documented.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
enum class any_operation : std::uint8_t {
|
||||||
|
copy,
|
||||||
|
move,
|
||||||
|
transfer,
|
||||||
|
assign,
|
||||||
|
destroy,
|
||||||
|
compare,
|
||||||
|
get
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class any_policy : std::uint8_t {
|
||||||
|
owner,
|
||||||
|
ref,
|
||||||
|
cref
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal details not to be documented.
|
||||||
|
* @endcond
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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 {
|
||||||
|
using operation = internal::any_operation;
|
||||||
|
using policy = internal::any_policy;
|
||||||
|
using vtable_type = const void *(const operation, const basic_any &, const void *);
|
||||||
|
|
||||||
|
struct storage_type {
|
||||||
|
alignas(Align) std::byte data[Len + !Len];
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
static constexpr bool in_situ = Len && alignof(Type) <= Align && sizeof(Type) <= Len &&std::is_nothrow_move_constructible_v<Type>;
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
static const void *basic_vtable(const operation op, const basic_any &value, 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 *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) {
|
||||||
|
info = &type_id<std::remove_cv_t<std::remove_reference_t<Type>>>();
|
||||||
|
|
||||||
|
if constexpr(!std::is_void_v<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<std::remove_cv_t<std::remove_reference_t<Type>>>) {
|
||||||
|
if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v<std::remove_cv_t<std::remove_reference_t<Type>>>) {
|
||||||
|
new(&storage) std::remove_cv_t<std::remove_reference_t<Type>>{std::forward<Args>(args)...};
|
||||||
|
} else {
|
||||||
|
new(&storage) std::remove_cv_t<std::remove_reference_t<Type>>(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v<std::remove_cv_t<std::remove_reference_t<Type>>>) {
|
||||||
|
instance = new std::remove_cv_t<std::remove_reference_t<Type>>{std::forward<Args>(args)...};
|
||||||
|
} else {
|
||||||
|
instance = new std::remove_cv_t<std::remove_reference_t<Type>>(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
basic_any(const basic_any &other, const policy pol) 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() noexcept
|
||||||
|
: basic_any{std::in_place_type<void>} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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)
|
||||||
|
: instance{},
|
||||||
|
info{},
|
||||||
|
vtable{},
|
||||||
|
mode{policy::owner} {
|
||||||
|
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{std::in_place_type<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) 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) 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 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 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 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() noexcept {
|
||||||
|
return mode == policy::cref ? nullptr : const_cast<void *>(std::as_const(*this).data());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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) noexcept {
|
||||||
|
return mode == policy::cref ? nullptr : const_cast<void *>(std::as_const(*this).data(req));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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 basic_any &other) {
|
||||||
|
if(vtable && mode != policy::cref && *info == *other.info) {
|
||||||
|
return (vtable(operation::assign, *this, other.data()) != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc assign */
|
||||||
|
bool assign(basic_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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// unnecessary but it helps to detect nasty bugs
|
||||||
|
ENTT_ASSERT((instance = nullptr) == 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 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.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool operator==(const basic_any &other) const noexcept {
|
||||||
|
if(vtable && *info == *other.info) {
|
||||||
|
return (vtable(operation::compare, *this, other.data()) != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (!vtable && !other.vtable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks if two wrappers differ in their content.
|
||||||
|
* @param other Wrapper with which to compare.
|
||||||
|
* @return True if the two objects differ in their content, false otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool operator!=(const basic_any &other) const noexcept {
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Aliasing constructor.
|
||||||
|
* @return A wrapper that shares a reference to an unmanaged object.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] basic_any as_ref() noexcept {
|
||||||
|
return basic_any{*this, (mode == policy::cref ? policy::cref : policy::ref)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc as_ref */
|
||||||
|
[[nodiscard]] basic_any as_ref() const 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 noexcept {
|
||||||
|
return (mode == policy::owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
union {
|
||||||
|
const void *instance;
|
||||||
|
storage_type storage;
|
||||||
|
};
|
||||||
|
const type_info *info;
|
||||||
|
vtable_type *vtable;
|
||||||
|
policy mode;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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) 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) 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) 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) noexcept {
|
||||||
|
const auto &info = type_id<std::remove_cv_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) noexcept {
|
||||||
|
if constexpr(std::is_const_v<Type>) {
|
||||||
|
// last attempt to make wrappers for const references return their values
|
||||||
|
return any_cast<Type>(&std::as_const(*data));
|
||||||
|
} else {
|
||||||
|
const auto &info = type_id<std::remove_cv_t<Type>>();
|
||||||
|
return static_cast<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<Type &&>, std::forward<Type>(value)};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace entt
|
||||||
|
|
||||||
|
#endif
|
30
src/entt/core/attribute.h
Normal file
30
src/entt/core/attribute.h
Normal file
@ -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
|
279
src/entt/core/compressed_pair.hpp
Normal file
279
src/entt/core/compressed_pair.hpp
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
#ifndef ENTT_CORE_COMPRESSED_PAIR_HPP
|
||||||
|
#define ENTT_CORE_COMPRESSED_PAIR_HPP
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <tuple>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#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>>>
|
||||||
|
constexpr compressed_pair_element() noexcept(std::is_nothrow_default_constructible_v<Type>)
|
||||||
|
: value{} {}
|
||||||
|
|
||||||
|
template<typename Arg, typename = std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_reference_t<Arg>>, compressed_pair_element>>>
|
||||||
|
constexpr compressed_pair_element(Arg &&arg) noexcept(std::is_nothrow_constructible_v<Type, Arg>)
|
||||||
|
: value{std::forward<Arg>(arg)} {}
|
||||||
|
|
||||||
|
template<typename... Args, std::size_t... Index>
|
||||||
|
constexpr compressed_pair_element(std::tuple<Args...> args, std::index_sequence<Index...>) noexcept(std::is_nothrow_constructible_v<Type, Args...>)
|
||||||
|
: value{std::forward<Args>(std::get<Index>(args))...} {}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr reference get() noexcept {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr const_reference get() const 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>>>
|
||||||
|
constexpr compressed_pair_element() noexcept(std::is_nothrow_default_constructible_v<base_type>)
|
||||||
|
: base_type{} {}
|
||||||
|
|
||||||
|
template<typename Arg, typename = std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_reference_t<Arg>>, compressed_pair_element>>>
|
||||||
|
constexpr compressed_pair_element(Arg &&arg) noexcept(std::is_nothrow_constructible_v<base_type, Arg>)
|
||||||
|
: base_type{std::forward<Arg>(arg)} {}
|
||||||
|
|
||||||
|
template<typename... Args, std::size_t... Index>
|
||||||
|
constexpr compressed_pair_element(std::tuple<Args...> args, std::index_sequence<Index...>) noexcept(std::is_nothrow_constructible_v<base_type, Args...>)
|
||||||
|
: base_type{std::forward<Args>(std::get<Index>(args))...} {}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr reference get() noexcept {
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr const_reference get() const 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() noexcept(std::is_nothrow_default_constructible_v<first_base> &&std::is_nothrow_default_constructible_v<second_base>)
|
||||||
|
: first_base{},
|
||||||
|
second_base{} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy constructor.
|
||||||
|
* @param other The instance to copy from.
|
||||||
|
*/
|
||||||
|
constexpr compressed_pair(const compressed_pair &other) noexcept(std::is_nothrow_copy_constructible_v<first_base> &&std::is_nothrow_copy_constructible_v<second_base>) = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move constructor.
|
||||||
|
* @param other The instance to move from.
|
||||||
|
*/
|
||||||
|
constexpr compressed_pair(compressed_pair &&other) noexcept(std::is_nothrow_move_constructible_v<first_base> &&std::is_nothrow_move_constructible_v<second_base>) = 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) noexcept(std::is_nothrow_constructible_v<first_base, Arg> &&std::is_nothrow_constructible_v<second_base, 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) noexcept(std::is_nothrow_constructible_v<first_base, Args...> &&std::is_nothrow_constructible_v<second_base, 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) noexcept(std::is_nothrow_copy_assignable_v<first_base> &&std::is_nothrow_copy_assignable_v<second_base>) = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move assignment operator.
|
||||||
|
* @param other The instance to move from.
|
||||||
|
* @return This compressed pair object.
|
||||||
|
*/
|
||||||
|
constexpr compressed_pair &operator=(compressed_pair &&other) noexcept(std::is_nothrow_move_assignable_v<first_base> &&std::is_nothrow_move_assignable_v<second_base>) = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the first element that a pair stores.
|
||||||
|
* @return The first element that a pair stores.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] constexpr first_type &first() noexcept {
|
||||||
|
return static_cast<first_base &>(*this).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc first */
|
||||||
|
[[nodiscard]] constexpr const first_type &first() const 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]] constexpr second_type &second() noexcept {
|
||||||
|
return static_cast<second_base &>(*this).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc second */
|
||||||
|
[[nodiscard]] constexpr const second_type &second() const noexcept {
|
||||||
|
return static_cast<const second_base &>(*this).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Swaps two compressed pair objects.
|
||||||
|
* @param other The compressed pair to swap with.
|
||||||
|
*/
|
||||||
|
constexpr void swap(compressed_pair &other) noexcept(std::is_nothrow_swappable_v<first_type> &&std::is_nothrow_swappable_v<second_type>) {
|
||||||
|
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>
|
||||||
|
constexpr decltype(auto) get() 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>
|
||||||
|
constexpr decltype(auto) get() const 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 constexpr 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
|
97
src/entt/core/enum.hpp
Normal file
97
src/entt/core/enum.hpp
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
#ifndef ENTT_CORE_ENUM_HPP
|
||||||
|
#define ENTT_CORE_ENUM_HPP
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
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) 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) 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) 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) 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) 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) 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) 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) noexcept {
|
||||||
|
return (lhs = (lhs ^ rhs));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
32
src/entt/core/family.hpp
Normal file
32
src/entt/core/family.hpp
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#ifndef ENTT_CORE_FAMILY_HPP
|
||||||
|
#define ENTT_CORE_FAMILY_HPP
|
||||||
|
|
||||||
|
#include "../config/config.h"
|
||||||
|
#include "fwd.hpp"
|
||||||
|
|
||||||
|
namespace entt {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Dynamic identifier generator.
|
||||||
|
*
|
||||||
|
* Utility class template that can be used to assign unique identifiers to types
|
||||||
|
* at runtime. Use different specializations to create separate sets of
|
||||||
|
* identifiers.
|
||||||
|
*/
|
||||||
|
template<typename...>
|
||||||
|
class family {
|
||||||
|
inline static ENTT_MAYBE_ATOMIC(id_type) identifier{};
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! @brief Unsigned integer type. */
|
||||||
|
using value_type = id_type;
|
||||||
|
|
||||||
|
/*! @brief Statically generated unique identifier for the given type. */
|
||||||
|
template<typename... Type>
|
||||||
|
// at the time I'm writing, clang crashes during compilation if auto is used instead of family_type
|
||||||
|
inline static const value_type value = identifier++;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace entt
|
||||||
|
|
||||||
|
#endif
|
20
src/entt/core/fwd.hpp
Normal file
20
src/entt/core/fwd.hpp
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#ifndef ENTT_CORE_FWD_HPP
|
||||||
|
#define ENTT_CORE_FWD_HPP
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include "../config/config.h"
|
||||||
|
|
||||||
|
namespace entt {
|
||||||
|
|
||||||
|
template<std::size_t Len = sizeof(double[2]), std::size_t = alignof(double[2])>
|
||||||
|
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
|
334
src/entt/core/hashed_string.hpp
Normal file
334
src/entt/core/hashed_string.hpp
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
#ifndef ENTT_CORE_HASHED_STRING_HPP
|
||||||
|
#define ENTT_CORE_HASHED_STRING_HPP
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#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
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Zero overhead unique identifier.
|
||||||
|
*
|
||||||
|
* A hashed string is a compile-time tool that allows users to use
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
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 *str) noexcept
|
||||||
|
: repr{str} {}
|
||||||
|
|
||||||
|
const Char *repr;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fowler–Noll–Vo hash function v. 1a - the good
|
||||||
|
[[nodiscard]] static constexpr auto helper(const Char *str) 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) 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 = typename base_type::hash_type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] static constexpr hash_type value(const value_type *str, const size_type len) noexcept {
|
||||||
|
return basic_hashed_string{str, len};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns directly the numeric representation of a string.
|
||||||
|
* @tparam N Number of characters of the identifier.
|
||||||
|
* @param str Human-readable identifier.
|
||||||
|
* @return The numeric representation of the string.
|
||||||
|
*/
|
||||||
|
template<std::size_t N>
|
||||||
|
[[nodiscard]] static constexpr hash_type value(const value_type (&str)[N]) noexcept {
|
||||||
|
return basic_hashed_string{str};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns directly the numeric representation of a string.
|
||||||
|
* @param wrapper Helps achieving the purpose by relying on overloading.
|
||||||
|
* @return The numeric representation of the string.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] static constexpr hash_type value(const_wrapper wrapper) noexcept {
|
||||||
|
return basic_hashed_string{wrapper};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @brief Constructs an empty hashed string. */
|
||||||
|
constexpr basic_hashed_string() noexcept
|
||||||
|
: base_type{} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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) 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 str Human-readable identifier.
|
||||||
|
*/
|
||||||
|
template<std::size_t N>
|
||||||
|
constexpr basic_hashed_string(const value_type (&str)[N]) noexcept
|
||||||
|
: base_type{helper(str)} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Explicit constructor on purpose to avoid constructing a hashed
|
||||||
|
* 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 basic_hashed_string(const_wrapper wrapper) noexcept
|
||||||
|
: base_type{helper(wrapper.repr)} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the size a hashed string.
|
||||||
|
* @return The size of the hashed string.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] constexpr size_type size() const noexcept {
|
||||||
|
return base_type::length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the human-readable representation of a hashed string.
|
||||||
|
* @return The string used to initialize the hashed string.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] constexpr const value_type *data() const noexcept {
|
||||||
|
return base_type::repr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the numeric representation of a hashed string.
|
||||||
|
* @return The numeric representation of the hashed string.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] constexpr hash_type value() const noexcept {
|
||||||
|
return base_type::hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc data */
|
||||||
|
[[nodiscard]] constexpr operator const value_type *() const noexcept {
|
||||||
|
return data();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the numeric representation of a hashed string.
|
||||||
|
* @return The numeric representation of the hashed string.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] constexpr operator hash_type() const noexcept {
|
||||||
|
return value();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
template<typename Char>
|
||||||
|
[[nodiscard]] constexpr bool operator==(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) 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) 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) 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) 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) 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) 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.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] constexpr hashed_string operator"" _hs(const char *str, std::size_t) 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) noexcept {
|
||||||
|
return hashed_wstring{str};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace literals
|
||||||
|
|
||||||
|
} // namespace entt
|
||||||
|
|
||||||
|
#endif
|
35
src/entt/core/ident.hpp
Normal file
35
src/entt/core/ident.hpp
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#ifndef ENTT_CORE_IDENT_HPP
|
||||||
|
#define ENTT_CORE_IDENT_HPP
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include "fwd.hpp"
|
||||||
|
#include "type_traits.hpp"
|
||||||
|
|
||||||
|
namespace entt {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Type integral identifiers.
|
||||||
|
* @tparam Type List of types for which to generate identifiers.
|
||||||
|
*/
|
||||||
|
template<typename... Type>
|
||||||
|
class ident {
|
||||||
|
template<typename Curr, std::size_t... Index>
|
||||||
|
[[nodiscard]] static constexpr id_type get(std::index_sequence<Index...>) noexcept {
|
||||||
|
static_assert((std::is_same_v<Curr, Type> || ...), "Invalid type");
|
||||||
|
return (0 + ... + (std::is_same_v<Curr, type_list_element_t<Index, type_list<std::decay_t<Type>...>>> ? id_type{Index} : id_type{}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! @brief Unsigned integer type. */
|
||||||
|
using value_type = id_type;
|
||||||
|
|
||||||
|
/*! @brief Statically generated unique identifier for the given type. */
|
||||||
|
template<typename Curr>
|
||||||
|
static constexpr value_type value = get<std::decay_t<Curr>>(std::index_sequence_for<Type...>{});
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace entt
|
||||||
|
|
||||||
|
#endif
|
197
src/entt/core/iterator.hpp
Normal file
197
src/entt/core/iterator.hpp
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
#ifndef ENTT_CORE_ITERATOR_HPP
|
||||||
|
#define ENTT_CORE_ITERATOR_HPP
|
||||||
|
|
||||||
|
#include <iterator>
|
||||||
|
#include <memory>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
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 Value type. */
|
||||||
|
using value_type = Type;
|
||||||
|
/*! @brief Pointer type. */
|
||||||
|
using pointer = Type *;
|
||||||
|
/*! @brief Reference type. */
|
||||||
|
using reference = Type &;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs a proxy object by move.
|
||||||
|
* @param val Value to use to initialize the proxy object.
|
||||||
|
*/
|
||||||
|
constexpr input_iterator_pointer(value_type &&val) noexcept(std::is_nothrow_move_constructible_v<value_type>)
|
||||||
|
: value{std::move(val)} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Access operator for accessing wrapped values.
|
||||||
|
* @return A pointer to the wrapped value.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] constexpr pointer operator->() noexcept {
|
||||||
|
return std::addressof(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Dereference operator for accessing wrapped values.
|
||||||
|
* @return A reference to the wrapped value.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] constexpr reference operator*() noexcept {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Type value;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Plain iota iterator (waiting for C++20).
|
||||||
|
* @tparam Type Value type.
|
||||||
|
*/
|
||||||
|
template<typename Type>
|
||||||
|
class iota_iterator final {
|
||||||
|
static_assert(std::is_integral_v<Type>, "Not an integral type");
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! @brief Value type, likely an integral one. */
|
||||||
|
using value_type = Type;
|
||||||
|
/*! @brief Invalid pointer type. */
|
||||||
|
using pointer = void;
|
||||||
|
/*! @brief Non-reference type, same as value type. */
|
||||||
|
using reference = value_type;
|
||||||
|
/*! @brief Difference type. */
|
||||||
|
using difference_type = std::ptrdiff_t;
|
||||||
|
/*! @brief Iterator category. */
|
||||||
|
using iterator_category = std::input_iterator_tag;
|
||||||
|
|
||||||
|
/*! @brief Default constructor. */
|
||||||
|
constexpr iota_iterator() noexcept
|
||||||
|
: current{} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs an iota iterator from a given value.
|
||||||
|
* @param init The initial value assigned to the iota iterator.
|
||||||
|
*/
|
||||||
|
constexpr iota_iterator(const value_type init) noexcept
|
||||||
|
: current{init} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Pre-increment operator.
|
||||||
|
* @return This iota iterator.
|
||||||
|
*/
|
||||||
|
constexpr iota_iterator &operator++() noexcept {
|
||||||
|
return ++current, *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Post-increment operator.
|
||||||
|
* @return This iota iterator.
|
||||||
|
*/
|
||||||
|
constexpr iota_iterator operator++(int) noexcept {
|
||||||
|
iota_iterator orig = *this;
|
||||||
|
return ++(*this), orig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Dereference operator.
|
||||||
|
* @return The underlying value.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] constexpr reference operator*() const noexcept {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
value_type current;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Comparison operator.
|
||||||
|
* @tparam Type Value type of the iota iterator.
|
||||||
|
* @param lhs A properly initialized iota iterator.
|
||||||
|
* @param rhs A properly initialized iota iterator.
|
||||||
|
* @return True if the two iterators are identical, false otherwise.
|
||||||
|
*/
|
||||||
|
template<typename Type>
|
||||||
|
[[nodiscard]] constexpr bool operator==(const iota_iterator<Type> &lhs, const iota_iterator<Type> &rhs) noexcept {
|
||||||
|
return *lhs == *rhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Comparison operator.
|
||||||
|
* @tparam Type Value type of the iota iterator.
|
||||||
|
* @param lhs A properly initialized iota iterator.
|
||||||
|
* @param rhs A properly initialized iota iterator.
|
||||||
|
* @return True if the two iterators differ, false otherwise.
|
||||||
|
*/
|
||||||
|
template<typename Type>
|
||||||
|
[[nodiscard]] constexpr bool operator!=(const iota_iterator<Type> &lhs, const iota_iterator<Type> &rhs) noexcept {
|
||||||
|
return !(lhs == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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. */
|
||||||
|
constexpr iterable_adaptor() noexcept(std::is_nothrow_default_constructible_v<iterator> &&std::is_nothrow_default_constructible_v<sentinel>)
|
||||||
|
: first{},
|
||||||
|
last{} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Creates an iterable object from a pair of iterators.
|
||||||
|
* @param from Begin iterator.
|
||||||
|
* @param to End iterator.
|
||||||
|
*/
|
||||||
|
constexpr iterable_adaptor(iterator from, sentinel to) noexcept(std::is_nothrow_move_constructible_v<iterator> &&std::is_nothrow_move_constructible_v<sentinel>)
|
||||||
|
: first{std::move(from)},
|
||||||
|
last{std::move(to)} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an iterator to the beginning.
|
||||||
|
* @return An iterator to the first element of the range.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] constexpr iterator begin() const noexcept {
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an iterator to the end.
|
||||||
|
* @return An iterator to the element following the last element of the
|
||||||
|
* range.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] constexpr sentinel end() const noexcept {
|
||||||
|
return last;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc begin */
|
||||||
|
[[nodiscard]] constexpr iterator cbegin() const noexcept {
|
||||||
|
return begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc end */
|
||||||
|
[[nodiscard]] constexpr sentinel cend() const noexcept {
|
||||||
|
return end();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
It first;
|
||||||
|
Sentinel last;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace entt
|
||||||
|
|
||||||
|
#endif
|
289
src/entt/core/memory.hpp
Normal file
289
src/entt/core/memory.hpp
Normal file
@ -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 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) 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) noexcept {
|
||||||
|
ENTT_ASSERT_CONSTEXPR(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) noexcept {
|
||||||
|
ENTT_ASSERT_CONSTEXPR(is_power_of_two(mod), "Value must be a power of two");
|
||||||
|
return value & (mod - 1u);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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) noexcept {
|
||||||
|
if constexpr(std::is_pointer_v<std::decay_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) 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) 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) noexcept {
|
||||||
|
if constexpr(std::allocator_traits<Allocator>::propagate_on_container_swap::value) {
|
||||||
|
using std::swap;
|
||||||
|
swap(lhs, rhs);
|
||||||
|
} else {
|
||||||
|
ENTT_ASSERT_CONSTEXPR(lhs == rhs, "Cannot swap the containers");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
constexpr allocation_deleter(const allocator_type &alloc) noexcept(std::is_nothrow_copy_constructible_v<allocator_type>)
|
||||||
|
: Allocator{alloc} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroys the pointed object and deallocates its memory.
|
||||||
|
* @param ptr A valid pointer to an object of the given type.
|
||||||
|
*/
|
||||||
|
constexpr void operator()(pointer ptr) noexcept(std::is_nothrow_destructible_v<typename allocator_type::value_type>) {
|
||||||
|
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>
|
||||||
|
ENTT_CONSTEXPR 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) 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) 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) 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) 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) 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) 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) 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([value](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
|
56
src/entt/core/monostate.hpp
Normal file
56
src/entt/core/monostate.hpp
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
#ifndef ENTT_CORE_MONOSTATE_HPP
|
||||||
|
#define ENTT_CORE_MONOSTATE_HPP
|
||||||
|
|
||||||
|
#include "../config/config.h"
|
||||||
|
#include "fwd.hpp"
|
||||||
|
|
||||||
|
namespace entt {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Minimal implementation of the monostate pattern.
|
||||||
|
*
|
||||||
|
* A minimal, yet complete configuration system built on top of the monostate
|
||||||
|
* pattern. Thread safe by design, it works only with basic types like `int`s or
|
||||||
|
* `bool`s.<br/>
|
||||||
|
* Multiple types and therefore more than one value can be associated with a
|
||||||
|
* single key. Because of this, users must pay attention to use the same type
|
||||||
|
* both during an assignment and when they try to read back their data.
|
||||||
|
* Otherwise, they can incur in unexpected results.
|
||||||
|
*/
|
||||||
|
template<id_type>
|
||||||
|
struct monostate {
|
||||||
|
/**
|
||||||
|
* @brief Assigns a value of a specific type to a given key.
|
||||||
|
* @tparam Type Type of the value to assign.
|
||||||
|
* @param val User data to assign to the given key.
|
||||||
|
*/
|
||||||
|
template<typename Type>
|
||||||
|
void operator=(Type val) const noexcept {
|
||||||
|
value<Type> = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets a value of a specific type for a given key.
|
||||||
|
* @tparam Type Type of the value to get.
|
||||||
|
* @return Stored value, if any.
|
||||||
|
*/
|
||||||
|
template<typename Type>
|
||||||
|
operator Type() const noexcept {
|
||||||
|
return value<Type>;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template<typename Type>
|
||||||
|
inline static ENTT_MAYBE_ATOMIC(Type) value{};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper variable template.
|
||||||
|
* @tparam Value Value used to differentiate between different variables.
|
||||||
|
*/
|
||||||
|
template<id_type Value>
|
||||||
|
inline monostate<Value> monostate_v = {};
|
||||||
|
|
||||||
|
} // namespace entt
|
||||||
|
|
||||||
|
#endif
|
103
src/entt/core/tuple.hpp
Normal file
103
src/entt/core/tuple.hpp
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
#ifndef ENTT_CORE_TUPLE_HPP
|
||||||
|
#define ENTT_CORE_TUPLE_HPP
|
||||||
|
|
||||||
|
#include <tuple>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace entt {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cond TURN_OFF_DOXYGEN
|
||||||
|
* Internal details not to be documented.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<typename>
|
||||||
|
struct is_tuple_impl: std::false_type {};
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
struct is_tuple_impl<std::tuple<Args...>>: std::true_type {};
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal details not to be documented.
|
||||||
|
* @endcond
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Provides the member constant `value` to true if a given type is a
|
||||||
|
* tuple, false otherwise.
|
||||||
|
* @tparam Type The type to test.
|
||||||
|
*/
|
||||||
|
template<typename Type>
|
||||||
|
struct is_tuple: internal::is_tuple_impl<std::remove_cv_t<Type>> {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper variable template.
|
||||||
|
* @tparam Type The type to test.
|
||||||
|
*/
|
||||||
|
template<typename Type>
|
||||||
|
inline constexpr bool is_tuple_v = is_tuple<Type>::value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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) 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Utility class to forward-and-apply tuple objects.
|
||||||
|
* @tparam Func Type of underlying invocable object.
|
||||||
|
*/
|
||||||
|
template<typename Func>
|
||||||
|
struct forward_apply: private Func {
|
||||||
|
/**
|
||||||
|
* @brief Constructs a forward-and-apply object.
|
||||||
|
* @tparam Args Types of arguments to use to construct the new instance.
|
||||||
|
* @param args Parameters to use to construct the instance.
|
||||||
|
*/
|
||||||
|
template<class... Args>
|
||||||
|
constexpr forward_apply(Args &&...args) noexcept(std::is_nothrow_constructible_v<Func, Args...>)
|
||||||
|
: Func{std::forward<Args>(args)...} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Forwards and applies the arguments with the underlying function.
|
||||||
|
* @tparam Type Tuple-like type to forward to the underlying function.
|
||||||
|
* @param args Parameters to forward to the underlying function.
|
||||||
|
* @return Return value of the underlying function, if any.
|
||||||
|
*/
|
||||||
|
template<class Type>
|
||||||
|
constexpr decltype(auto) operator()(Type &&args) noexcept(noexcept(std::apply(std::declval<Func &>(), args))) {
|
||||||
|
return std::apply(static_cast<Func &>(*this), std::forward<Type>(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc operator()() */
|
||||||
|
template<class Type>
|
||||||
|
constexpr decltype(auto) operator()(Type &&args) const noexcept(noexcept(std::apply(std::declval<const Func &>(), args))) {
|
||||||
|
return std::apply(static_cast<const Func &>(*this), std::forward<Type>(args));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deduction guide.
|
||||||
|
* @tparam Func Type of underlying invocable object.
|
||||||
|
*/
|
||||||
|
template<typename Func>
|
||||||
|
forward_apply(Func) -> forward_apply<std::remove_reference_t<std::remove_cv_t<Func>>>;
|
||||||
|
|
||||||
|
} // namespace entt
|
||||||
|
|
||||||
|
#endif
|
274
src/entt/core/type_info.hpp
Normal file
274
src/entt/core/type_info.hpp
Normal file
@ -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() noexcept {
|
||||||
|
static ENTT_MAYBE_ATOMIC(id_type) value{};
|
||||||
|
return value++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
[[nodiscard]] constexpr auto stripped_type_name() 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) noexcept {
|
||||||
|
constexpr auto value = stripped_type_name<Type>();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
[[nodiscard]] static std::string_view type_name(char) 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) 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) 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() noexcept {
|
||||||
|
static const id_type value = internal::type_index::next();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc value */
|
||||||
|
[[nodiscard]] constexpr operator id_type() const 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() noexcept {
|
||||||
|
return internal::type_hash<Type>(0);
|
||||||
|
#else
|
||||||
|
[[nodiscard]] static constexpr id_type value() noexcept {
|
||||||
|
return type_index<Type>::value();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc value */
|
||||||
|
[[nodiscard]] constexpr operator id_type() const 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() noexcept {
|
||||||
|
return internal::type_name<Type>(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc value */
|
||||||
|
[[nodiscard]] constexpr operator std::string_view() const 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>) 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 noexcept {
|
||||||
|
return seq;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Type hash.
|
||||||
|
* @return Type hash.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] constexpr id_type hash() const noexcept {
|
||||||
|
return identifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Type name.
|
||||||
|
* @return Type name.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] constexpr std::string_view name() const 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) 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) 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) 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) 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) 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) 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() 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 &&) noexcept {
|
||||||
|
return type_id<std::remove_cv_t<std::remove_reference_t<Type>>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace entt
|
||||||
|
|
||||||
|
#endif
|
758
src/entt/core/type_traits.hpp
Normal file
758
src/entt/core/type_traits.hpp
Normal file
@ -0,0 +1,758 @@
|
|||||||
|
#ifndef ENTT_CORE_TYPE_TRAITS_HPP
|
||||||
|
#define ENTT_CORE_TYPE_TRAITS_HPP
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <iterator>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include "../config/config.h"
|
||||||
|
#include "fwd.hpp"
|
||||||
|
|
||||||
|
namespace entt {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Utility class to disambiguate overloaded functions.
|
||||||
|
* @tparam N Number of choices available.
|
||||||
|
*/
|
||||||
|
template<std::size_t N>
|
||||||
|
struct choice_t
|
||||||
|
// Unfortunately, doxygen cannot parse such a construct.
|
||||||
|
: /*! @cond TURN_OFF_DOXYGEN */ choice_t<N - 1> /*! @endcond */
|
||||||
|
{};
|
||||||
|
|
||||||
|
/*! @copybrief choice_t */
|
||||||
|
template<>
|
||||||
|
struct choice_t<0> {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Variable template for the choice trick.
|
||||||
|
* @tparam N Number of choices available.
|
||||||
|
*/
|
||||||
|
template<std::size_t N>
|
||||||
|
inline constexpr choice_t<N> choice{};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Identity type trait.
|
||||||
|
*
|
||||||
|
* Useful to establish non-deduced contexts in template argument deduction
|
||||||
|
* (waiting for C++20) or to provide types through function arguments.
|
||||||
|
*
|
||||||
|
* @tparam Type A type.
|
||||||
|
*/
|
||||||
|
template<typename Type>
|
||||||
|
struct type_identity {
|
||||||
|
/*! @brief Identity type. */
|
||||||
|
using type = Type;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper type.
|
||||||
|
* @tparam Type A type.
|
||||||
|
*/
|
||||||
|
template<typename Type>
|
||||||
|
using type_identity_t = typename type_identity<Type>::type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains.
|
||||||
|
* @tparam Type The type of which to return the size.
|
||||||
|
* @tparam The size of the type if `sizeof` accepts it, 0 otherwise.
|
||||||
|
*/
|
||||||
|
template<typename Type, typename = void>
|
||||||
|
struct size_of: std::integral_constant<std::size_t, 0u> {};
|
||||||
|
|
||||||
|
/*! @copydoc size_of */
|
||||||
|
template<typename Type>
|
||||||
|
struct size_of<Type, std::void_t<decltype(sizeof(Type))>>
|
||||||
|
: std::integral_constant<std::size_t, sizeof(Type)> {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper variable template.
|
||||||
|
* @tparam Type The type of which to return the size.
|
||||||
|
*/
|
||||||
|
template<typename Type>
|
||||||
|
inline constexpr std::size_t size_of_v = size_of<Type>::value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Using declaration to be used to _repeat_ the same type a number of
|
||||||
|
* times equal to the size of a given parameter pack.
|
||||||
|
* @tparam Type A type to repeat.
|
||||||
|
*/
|
||||||
|
template<typename Type, typename>
|
||||||
|
using unpack_as_type = Type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper variable template to be used to _repeat_ the same value a
|
||||||
|
* number of times equal to the size of a given parameter pack.
|
||||||
|
* @tparam Value A value to repeat.
|
||||||
|
*/
|
||||||
|
template<auto Value, typename>
|
||||||
|
inline constexpr auto unpack_as_value = Value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Wraps a static constant.
|
||||||
|
* @tparam Value A static constant.
|
||||||
|
*/
|
||||||
|
template<auto Value>
|
||||||
|
using integral_constant = std::integral_constant<decltype(Value), Value>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Alias template to facilitate the creation of named values.
|
||||||
|
* @tparam Value A constant value at least convertible to `id_type`.
|
||||||
|
*/
|
||||||
|
template<id_type Value>
|
||||||
|
using tag = integral_constant<Value>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A class to use to push around lists of types, nothing more.
|
||||||
|
* @tparam Type Types provided by the type list.
|
||||||
|
*/
|
||||||
|
template<typename... Type>
|
||||||
|
struct type_list {
|
||||||
|
/*! @brief Type list type. */
|
||||||
|
using type = type_list;
|
||||||
|
/*! @brief Compile-time number of elements in the type list. */
|
||||||
|
static constexpr auto size = sizeof...(Type);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! @brief Primary template isn't defined on purpose. */
|
||||||
|
template<std::size_t, typename>
|
||||||
|
struct type_list_element;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Provides compile-time indexed access to the types of a type list.
|
||||||
|
* @tparam Index Index of the type to return.
|
||||||
|
* @tparam First First type provided by the type list.
|
||||||
|
* @tparam Other Other types provided by the type list.
|
||||||
|
*/
|
||||||
|
template<std::size_t Index, typename First, typename... Other>
|
||||||
|
struct type_list_element<Index, type_list<First, Other...>>
|
||||||
|
: type_list_element<Index - 1u, type_list<Other...>> {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Provides compile-time indexed access to the types of a type list.
|
||||||
|
* @tparam First First type provided by the type list.
|
||||||
|
* @tparam Other Other types provided by the type list.
|
||||||
|
*/
|
||||||
|
template<typename First, typename... Other>
|
||||||
|
struct type_list_element<0u, type_list<First, Other...>> {
|
||||||
|
/*! @brief Searched type. */
|
||||||
|
using type = First;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper type.
|
||||||
|
* @tparam Index Index of the type to return.
|
||||||
|
* @tparam List Type list to search into.
|
||||||
|
*/
|
||||||
|
template<std::size_t Index, typename List>
|
||||||
|
using type_list_element_t = typename type_list_element<Index, List>::type;
|
||||||
|
|
||||||
|
/*! @brief Primary template isn't defined on purpose. */
|
||||||
|
template<typename, typename>
|
||||||
|
struct type_list_index;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Provides compile-time type access to the types of a type list.
|
||||||
|
* @tparam Type Type to look for and for which to return the index.
|
||||||
|
* @tparam First First type provided by the type list.
|
||||||
|
* @tparam Other Other types provided by the type list.
|
||||||
|
*/
|
||||||
|
template<typename Type, typename First, typename... Other>
|
||||||
|
struct type_list_index<Type, type_list<First, Other...>> {
|
||||||
|
/*! @brief Unsigned integer type. */
|
||||||
|
using value_type = std::size_t;
|
||||||
|
/*! @brief Compile-time position of the given type in the sublist. */
|
||||||
|
static constexpr value_type value = 1u + type_list_index<Type, type_list<Other...>>::value;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Provides compile-time type access to the types of a type list.
|
||||||
|
* @tparam Type Type to look for and for which to return the index.
|
||||||
|
* @tparam Other Other types provided by the type list.
|
||||||
|
*/
|
||||||
|
template<typename Type, typename... Other>
|
||||||
|
struct type_list_index<Type, type_list<Type, Other...>> {
|
||||||
|
static_assert(type_list_index<Type, type_list<Other...>>::value == sizeof...(Other), "Non-unique type");
|
||||||
|
/*! @brief Unsigned integer type. */
|
||||||
|
using value_type = std::size_t;
|
||||||
|
/*! @brief Compile-time position of the given type in the sublist. */
|
||||||
|
static constexpr value_type value = 0u;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Provides compile-time type access to the types of a type list.
|
||||||
|
* @tparam Type Type to look for and for which to return the index.
|
||||||
|
*/
|
||||||
|
template<typename Type>
|
||||||
|
struct type_list_index<Type, type_list<>> {
|
||||||
|
/*! @brief Unsigned integer type. */
|
||||||
|
using value_type = std::size_t;
|
||||||
|
/*! @brief Compile-time position of the given type in the sublist. */
|
||||||
|
static constexpr value_type value = 0u;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper variable template.
|
||||||
|
* @tparam List Type list.
|
||||||
|
* @tparam Type Type to look for and for which to return the index.
|
||||||
|
*/
|
||||||
|
template<typename Type, typename List>
|
||||||
|
inline constexpr std::size_t type_list_index_v = type_list_index<Type, List>::value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Concatenates multiple type lists.
|
||||||
|
* @tparam Type Types provided by the first type list.
|
||||||
|
* @tparam Other Types provided by the second type list.
|
||||||
|
* @return A type list composed by the types of both the type lists.
|
||||||
|
*/
|
||||||
|
template<typename... Type, typename... Other>
|
||||||
|
constexpr type_list<Type..., Other...> operator+(type_list<Type...>, type_list<Other...>) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @brief Primary template isn't defined on purpose. */
|
||||||
|
template<typename...>
|
||||||
|
struct type_list_cat;
|
||||||
|
|
||||||
|
/*! @brief Concatenates multiple type lists. */
|
||||||
|
template<>
|
||||||
|
struct type_list_cat<> {
|
||||||
|
/*! @brief A type list composed by the types of all the type lists. */
|
||||||
|
using type = type_list<>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Concatenates multiple type lists.
|
||||||
|
* @tparam Type Types provided by the first type list.
|
||||||
|
* @tparam Other Types provided by the second type list.
|
||||||
|
* @tparam List Other type lists, if any.
|
||||||
|
*/
|
||||||
|
template<typename... Type, typename... Other, typename... List>
|
||||||
|
struct type_list_cat<type_list<Type...>, type_list<Other...>, List...> {
|
||||||
|
/*! @brief A type list composed by the types of all the type lists. */
|
||||||
|
using type = typename type_list_cat<type_list<Type..., Other...>, List...>::type;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Concatenates multiple type lists.
|
||||||
|
* @tparam Type Types provided by the type list.
|
||||||
|
*/
|
||||||
|
template<typename... Type>
|
||||||
|
struct type_list_cat<type_list<Type...>> {
|
||||||
|
/*! @brief A type list composed by the types of all the type lists. */
|
||||||
|
using type = type_list<Type...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper type.
|
||||||
|
* @tparam List Type lists to concatenate.
|
||||||
|
*/
|
||||||
|
template<typename... List>
|
||||||
|
using type_list_cat_t = typename type_list_cat<List...>::type;
|
||||||
|
|
||||||
|
/*! @brief Primary template isn't defined on purpose. */
|
||||||
|
template<typename>
|
||||||
|
struct type_list_unique;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Removes duplicates types from a type list.
|
||||||
|
* @tparam Type One of the types provided by the given type list.
|
||||||
|
* @tparam Other The other types provided by the given type list.
|
||||||
|
*/
|
||||||
|
template<typename Type, typename... Other>
|
||||||
|
struct type_list_unique<type_list<Type, Other...>> {
|
||||||
|
/*! @brief A type list without duplicate types. */
|
||||||
|
using type = std::conditional_t<
|
||||||
|
(std::is_same_v<Type, Other> || ...),
|
||||||
|
typename type_list_unique<type_list<Other...>>::type,
|
||||||
|
type_list_cat_t<type_list<Type>, typename type_list_unique<type_list<Other...>>::type>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! @brief Removes duplicates types from a type list. */
|
||||||
|
template<>
|
||||||
|
struct type_list_unique<type_list<>> {
|
||||||
|
/*! @brief A type list without duplicate types. */
|
||||||
|
using type = type_list<>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper type.
|
||||||
|
* @tparam Type A type list.
|
||||||
|
*/
|
||||||
|
template<typename Type>
|
||||||
|
using type_list_unique_t = typename type_list_unique<Type>::type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Provides the member constant `value` to true if a type list contains a
|
||||||
|
* given type, false otherwise.
|
||||||
|
* @tparam List Type list.
|
||||||
|
* @tparam Type Type to look for.
|
||||||
|
*/
|
||||||
|
template<typename List, typename Type>
|
||||||
|
struct type_list_contains;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copybrief type_list_contains
|
||||||
|
* @tparam Type Types provided by the type list.
|
||||||
|
* @tparam Other Type to look for.
|
||||||
|
*/
|
||||||
|
template<typename... Type, typename Other>
|
||||||
|
struct type_list_contains<type_list<Type...>, Other>: std::disjunction<std::is_same<Type, Other>...> {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper variable template.
|
||||||
|
* @tparam List Type list.
|
||||||
|
* @tparam Type Type to look for.
|
||||||
|
*/
|
||||||
|
template<typename List, typename Type>
|
||||||
|
inline constexpr bool type_list_contains_v = type_list_contains<List, Type>::value;
|
||||||
|
|
||||||
|
/*! @brief Primary template isn't defined on purpose. */
|
||||||
|
template<typename...>
|
||||||
|
struct type_list_diff;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Computes the difference between two type lists.
|
||||||
|
* @tparam Type Types provided by the first type list.
|
||||||
|
* @tparam Other Types provided by the second type list.
|
||||||
|
*/
|
||||||
|
template<typename... Type, typename... Other>
|
||||||
|
struct type_list_diff<type_list<Type...>, type_list<Other...>> {
|
||||||
|
/*! @brief A type list that is the difference between the two type lists. */
|
||||||
|
using type = type_list_cat_t<std::conditional_t<type_list_contains_v<type_list<Other...>, Type>, type_list<>, type_list<Type>>...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper type.
|
||||||
|
* @tparam List Type lists between which to compute the difference.
|
||||||
|
*/
|
||||||
|
template<typename... List>
|
||||||
|
using type_list_diff_t = typename type_list_diff<List...>::type;
|
||||||
|
|
||||||
|
/*! @brief Primary template isn't defined on purpose. */
|
||||||
|
template<typename, template<typename...> class>
|
||||||
|
struct type_list_transform;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Applies a given _function_ to a type list and generate a new list.
|
||||||
|
* @tparam Type Types provided by the type list.
|
||||||
|
* @tparam Op Unary operation as template class with a type member named `type`.
|
||||||
|
*/
|
||||||
|
template<typename... Type, template<typename...> class Op>
|
||||||
|
struct type_list_transform<type_list<Type...>, Op> {
|
||||||
|
/*! @brief Resulting type list after applying the transform function. */
|
||||||
|
using type = type_list<typename Op<Type>::type...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper type.
|
||||||
|
* @tparam List Type list.
|
||||||
|
* @tparam Op Unary operation as template class with a type member named `type`.
|
||||||
|
*/
|
||||||
|
template<typename List, template<typename...> class Op>
|
||||||
|
using type_list_transform_t = typename type_list_transform<List, Op>::type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A class to use to push around lists of constant values, nothing more.
|
||||||
|
* @tparam Value Values provided by the value list.
|
||||||
|
*/
|
||||||
|
template<auto... Value>
|
||||||
|
struct value_list {
|
||||||
|
/*! @brief Value list type. */
|
||||||
|
using type = value_list;
|
||||||
|
/*! @brief Compile-time number of elements in the value list. */
|
||||||
|
static constexpr auto size = sizeof...(Value);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! @brief Primary template isn't defined on purpose. */
|
||||||
|
template<std::size_t, typename>
|
||||||
|
struct value_list_element;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Provides compile-time indexed access to the values of a value list.
|
||||||
|
* @tparam Index Index of the value to return.
|
||||||
|
* @tparam Value First value provided by the value list.
|
||||||
|
* @tparam Other Other values provided by the value list.
|
||||||
|
*/
|
||||||
|
template<std::size_t Index, auto Value, auto... Other>
|
||||||
|
struct value_list_element<Index, value_list<Value, Other...>>
|
||||||
|
: value_list_element<Index - 1u, value_list<Other...>> {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Provides compile-time indexed access to the types of a type list.
|
||||||
|
* @tparam Value First value provided by the value list.
|
||||||
|
* @tparam Other Other values provided by the value list.
|
||||||
|
*/
|
||||||
|
template<auto Value, auto... Other>
|
||||||
|
struct value_list_element<0u, value_list<Value, Other...>> {
|
||||||
|
/*! @brief Searched value. */
|
||||||
|
static constexpr auto value = Value;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper type.
|
||||||
|
* @tparam Index Index of the value to return.
|
||||||
|
* @tparam List Value list to search into.
|
||||||
|
*/
|
||||||
|
template<std::size_t Index, typename List>
|
||||||
|
inline constexpr auto value_list_element_v = value_list_element<Index, List>::value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Concatenates multiple value lists.
|
||||||
|
* @tparam Value Values provided by the first value list.
|
||||||
|
* @tparam Other Values provided by the second value list.
|
||||||
|
* @return A value list composed by the values of both the value lists.
|
||||||
|
*/
|
||||||
|
template<auto... Value, auto... Other>
|
||||||
|
constexpr value_list<Value..., Other...> operator+(value_list<Value...>, value_list<Other...>) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @brief Primary template isn't defined on purpose. */
|
||||||
|
template<typename...>
|
||||||
|
struct value_list_cat;
|
||||||
|
|
||||||
|
/*! @brief Concatenates multiple value lists. */
|
||||||
|
template<>
|
||||||
|
struct value_list_cat<> {
|
||||||
|
/*! @brief A value list composed by the values of all the value lists. */
|
||||||
|
using type = value_list<>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Concatenates multiple value lists.
|
||||||
|
* @tparam Value Values provided by the first value list.
|
||||||
|
* @tparam Other Values provided by the second value list.
|
||||||
|
* @tparam List Other value lists, if any.
|
||||||
|
*/
|
||||||
|
template<auto... Value, auto... Other, typename... List>
|
||||||
|
struct value_list_cat<value_list<Value...>, value_list<Other...>, List...> {
|
||||||
|
/*! @brief A value list composed by the values of all the value lists. */
|
||||||
|
using type = typename value_list_cat<value_list<Value..., Other...>, List...>::type;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Concatenates multiple value lists.
|
||||||
|
* @tparam Value Values provided by the value list.
|
||||||
|
*/
|
||||||
|
template<auto... Value>
|
||||||
|
struct value_list_cat<value_list<Value...>> {
|
||||||
|
/*! @brief A value list composed by the values of all the value lists. */
|
||||||
|
using type = value_list<Value...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper type.
|
||||||
|
* @tparam List Value lists to concatenate.
|
||||||
|
*/
|
||||||
|
template<typename... List>
|
||||||
|
using value_list_cat_t = typename value_list_cat<List...>::type;
|
||||||
|
|
||||||
|
/*! @brief Same as std::is_invocable, but with tuples. */
|
||||||
|
template<typename, typename>
|
||||||
|
struct is_applicable: std::false_type {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copybrief is_applicable
|
||||||
|
* @tparam Func A valid function type.
|
||||||
|
* @tparam Tuple Tuple-like type.
|
||||||
|
* @tparam Args The list of arguments to use to probe the function type.
|
||||||
|
*/
|
||||||
|
template<typename Func, template<typename...> class Tuple, typename... Args>
|
||||||
|
struct is_applicable<Func, Tuple<Args...>>: std::is_invocable<Func, Args...> {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copybrief is_applicable
|
||||||
|
* @tparam Func A valid function type.
|
||||||
|
* @tparam Tuple Tuple-like type.
|
||||||
|
* @tparam Args The list of arguments to use to probe the function type.
|
||||||
|
*/
|
||||||
|
template<typename Func, template<typename...> class Tuple, typename... Args>
|
||||||
|
struct is_applicable<Func, const Tuple<Args...>>: std::is_invocable<Func, Args...> {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper variable template.
|
||||||
|
* @tparam Func A valid function type.
|
||||||
|
* @tparam Args The list of arguments to use to probe the function type.
|
||||||
|
*/
|
||||||
|
template<typename Func, typename Args>
|
||||||
|
inline constexpr bool is_applicable_v = is_applicable<Func, Args>::value;
|
||||||
|
|
||||||
|
/*! @brief Same as std::is_invocable_r, but with tuples for arguments. */
|
||||||
|
template<typename, typename, typename>
|
||||||
|
struct is_applicable_r: std::false_type {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copybrief is_applicable_r
|
||||||
|
* @tparam Ret The type to which the return type of the function should be
|
||||||
|
* convertible.
|
||||||
|
* @tparam Func A valid function type.
|
||||||
|
* @tparam Args The list of arguments to use to probe the function type.
|
||||||
|
*/
|
||||||
|
template<typename Ret, typename Func, typename... Args>
|
||||||
|
struct is_applicable_r<Ret, Func, std::tuple<Args...>>: std::is_invocable_r<Ret, Func, Args...> {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper variable template.
|
||||||
|
* @tparam Ret The type to which the return type of the function should be
|
||||||
|
* convertible.
|
||||||
|
* @tparam Func A valid function type.
|
||||||
|
* @tparam Args The list of arguments to use to probe the function type.
|
||||||
|
*/
|
||||||
|
template<typename Ret, typename Func, typename Args>
|
||||||
|
inline constexpr bool is_applicable_r_v = is_applicable_r<Ret, Func, Args>::value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Provides the member constant `value` to true if a given type is
|
||||||
|
* complete, false otherwise.
|
||||||
|
* @tparam Type The type to test.
|
||||||
|
*/
|
||||||
|
template<typename Type, typename = void>
|
||||||
|
struct is_complete: std::false_type {};
|
||||||
|
|
||||||
|
/*! @copydoc is_complete */
|
||||||
|
template<typename Type>
|
||||||
|
struct is_complete<Type, std::void_t<decltype(sizeof(Type))>>: std::true_type {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper variable template.
|
||||||
|
* @tparam Type The type to test.
|
||||||
|
*/
|
||||||
|
template<typename Type>
|
||||||
|
inline constexpr bool is_complete_v = is_complete<Type>::value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Provides the member constant `value` to true if a given type is an
|
||||||
|
* iterator, false otherwise.
|
||||||
|
* @tparam Type The type to test.
|
||||||
|
*/
|
||||||
|
template<typename Type, typename = void>
|
||||||
|
struct is_iterator: std::false_type {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cond TURN_OFF_DOXYGEN
|
||||||
|
* Internal details not to be documented.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<typename, typename = void>
|
||||||
|
struct has_iterator_category: std::false_type {};
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
struct has_iterator_category<Type, std::void_t<typename std::iterator_traits<Type>::iterator_category>>: std::true_type {};
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal details not to be documented.
|
||||||
|
* @endcond
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! @copydoc is_iterator */
|
||||||
|
template<typename Type>
|
||||||
|
struct is_iterator<Type, std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_pointer_t<Type>>, void>>>
|
||||||
|
: internal::has_iterator_category<Type> {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper variable template.
|
||||||
|
* @tparam Type The type to test.
|
||||||
|
*/
|
||||||
|
template<typename Type>
|
||||||
|
inline constexpr bool is_iterator_v = is_iterator<Type>::value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Provides the member constant `value` to true if a given type is both
|
||||||
|
* an empty and non-final class, false otherwise.
|
||||||
|
* @tparam Type The type to test
|
||||||
|
*/
|
||||||
|
template<typename Type>
|
||||||
|
struct is_ebco_eligible
|
||||||
|
: std::conjunction<std::is_empty<Type>, std::negation<std::is_final<Type>>> {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper variable template.
|
||||||
|
* @tparam Type The type to test.
|
||||||
|
*/
|
||||||
|
template<typename Type>
|
||||||
|
inline constexpr bool is_ebco_eligible_v = is_ebco_eligible<Type>::value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Provides the member constant `value` to true if `Type::is_transparent`
|
||||||
|
* is valid and denotes a type, false otherwise.
|
||||||
|
* @tparam Type The type to test.
|
||||||
|
*/
|
||||||
|
template<typename Type, typename = void>
|
||||||
|
struct is_transparent: std::false_type {};
|
||||||
|
|
||||||
|
/*! @copydoc is_transparent */
|
||||||
|
template<typename Type>
|
||||||
|
struct is_transparent<Type, std::void_t<typename Type::is_transparent>>: std::true_type {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper variable template.
|
||||||
|
* @tparam Type The type to test.
|
||||||
|
*/
|
||||||
|
template<typename Type>
|
||||||
|
inline constexpr bool is_transparent_v = is_transparent<Type>::value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Provides the member constant `value` to true if a given type is
|
||||||
|
* equality comparable, false otherwise.
|
||||||
|
* @tparam Type The type to test.
|
||||||
|
*/
|
||||||
|
template<typename Type, typename = void>
|
||||||
|
struct is_equality_comparable: std::false_type {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cond TURN_OFF_DOXYGEN
|
||||||
|
* Internal details not to be documented.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<typename, typename = void>
|
||||||
|
struct has_tuple_size_value: std::false_type {};
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
struct has_tuple_size_value<Type, std::void_t<decltype(std::tuple_size<const Type>::value)>>: std::true_type {};
|
||||||
|
|
||||||
|
template<typename Type, std::size_t... Index>
|
||||||
|
[[nodiscard]] constexpr bool unpack_maybe_equality_comparable(std::index_sequence<Index...>) {
|
||||||
|
return (is_equality_comparable<std::tuple_element_t<Index, Type>>::value && ...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename>
|
||||||
|
[[nodiscard]] constexpr bool maybe_equality_comparable(choice_t<0>) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
[[nodiscard]] constexpr auto maybe_equality_comparable(choice_t<1>) -> decltype(std::declval<typename Type::value_type>(), bool{}) {
|
||||||
|
if constexpr(is_iterator_v<Type>) {
|
||||||
|
return true;
|
||||||
|
} else if constexpr(std::is_same_v<typename Type::value_type, Type>) {
|
||||||
|
return maybe_equality_comparable<Type>(choice<0>);
|
||||||
|
} else {
|
||||||
|
return is_equality_comparable<typename Type::value_type>::value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
[[nodiscard]] constexpr std::enable_if_t<is_complete_v<std::tuple_size<std::remove_cv_t<Type>>>, bool> maybe_equality_comparable(choice_t<2>) {
|
||||||
|
if constexpr(has_tuple_size_value<Type>::value) {
|
||||||
|
return unpack_maybe_equality_comparable<Type>(std::make_index_sequence<std::tuple_size<Type>::value>{});
|
||||||
|
} else {
|
||||||
|
return maybe_equality_comparable<Type>(choice<1>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal details not to be documented.
|
||||||
|
* @endcond
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! @copydoc is_equality_comparable */
|
||||||
|
template<typename Type>
|
||||||
|
struct is_equality_comparable<Type, std::void_t<decltype(std::declval<Type>() == std::declval<Type>())>>
|
||||||
|
: std::bool_constant<internal::maybe_equality_comparable<Type>(choice<2>)> {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper variable template.
|
||||||
|
* @tparam Type The type to test.
|
||||||
|
*/
|
||||||
|
template<typename Type>
|
||||||
|
inline constexpr bool is_equality_comparable_v = is_equality_comparable<Type>::value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Transcribes the constness of a type to another type.
|
||||||
|
* @tparam To The type to which to transcribe the constness.
|
||||||
|
* @tparam From The type from which to transcribe the constness.
|
||||||
|
*/
|
||||||
|
template<typename To, typename From>
|
||||||
|
struct constness_as {
|
||||||
|
/*! @brief The type resulting from the transcription of the constness. */
|
||||||
|
using type = std::remove_const_t<To>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! @copydoc constness_as */
|
||||||
|
template<typename To, typename From>
|
||||||
|
struct constness_as<To, const From> {
|
||||||
|
/*! @brief The type resulting from the transcription of the constness. */
|
||||||
|
using type = const To;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Alias template to facilitate the transcription of the constness.
|
||||||
|
* @tparam To The type to which to transcribe the constness.
|
||||||
|
* @tparam From The type from which to transcribe the constness.
|
||||||
|
*/
|
||||||
|
template<typename To, typename From>
|
||||||
|
using constness_as_t = typename constness_as<To, From>::type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Extracts the class of a non-static member object or function.
|
||||||
|
* @tparam Member A pointer to a non-static member object or function.
|
||||||
|
*/
|
||||||
|
template<typename Member>
|
||||||
|
class member_class {
|
||||||
|
static_assert(std::is_member_pointer_v<Member>, "Invalid pointer type to non-static member object or function");
|
||||||
|
|
||||||
|
template<typename Class, typename Ret, typename... Args>
|
||||||
|
static Class *clazz(Ret (Class::*)(Args...));
|
||||||
|
|
||||||
|
template<typename Class, typename Ret, typename... Args>
|
||||||
|
static Class *clazz(Ret (Class::*)(Args...) const);
|
||||||
|
|
||||||
|
template<typename Class, typename Type>
|
||||||
|
static Class *clazz(Type Class::*);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! @brief The class of the given non-static member object or function. */
|
||||||
|
using type = std::remove_pointer_t<decltype(clazz(std::declval<Member>()))>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper type.
|
||||||
|
* @tparam Member A pointer to a non-static member object or function.
|
||||||
|
*/
|
||||||
|
template<typename Member>
|
||||||
|
using member_class_t = typename member_class<Member>::type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Extracts the n-th argument of a given function or member function.
|
||||||
|
* @tparam Index The index of the argument to extract.
|
||||||
|
* @tparam Candidate A valid function, member function or data member.
|
||||||
|
*/
|
||||||
|
template<std::size_t Index, auto Candidate>
|
||||||
|
class nth_argument {
|
||||||
|
template<typename Ret, typename... Args>
|
||||||
|
static constexpr type_list<Args...> pick_up(Ret (*)(Args...));
|
||||||
|
|
||||||
|
template<typename Ret, typename Class, typename... Args>
|
||||||
|
static constexpr type_list<Args...> pick_up(Ret (Class ::*)(Args...));
|
||||||
|
|
||||||
|
template<typename Ret, typename Class, typename... Args>
|
||||||
|
static constexpr type_list<Args...> pick_up(Ret (Class ::*)(Args...) const);
|
||||||
|
|
||||||
|
template<typename Type, typename Class>
|
||||||
|
static constexpr type_list<Type> pick_up(Type Class ::*);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! @brief N-th argument of the given function or member function. */
|
||||||
|
using type = type_list_element_t<Index, decltype(pick_up(Candidate))>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper type.
|
||||||
|
* @tparam Index The index of the argument to extract.
|
||||||
|
* @tparam Candidate A valid function, member function or data member.
|
||||||
|
*/
|
||||||
|
template<std::size_t Index, auto Candidate>
|
||||||
|
using nth_argument_t = typename nth_argument<Index, Candidate>::type;
|
||||||
|
|
||||||
|
} // namespace entt
|
||||||
|
|
||||||
|
#endif
|
101
src/entt/core/utility.hpp
Normal file
101
src/entt/core/utility.hpp
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
#ifndef ENTT_CORE_UTILITY_HPP
|
||||||
|
#define ENTT_CORE_UTILITY_HPP
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
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 noexcept {
|
||||||
|
return std::forward<Type>(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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>
|
||||||
|
[[nodiscard]] constexpr auto overload(Type Class::*member) noexcept {
|
||||||
|
return member;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constant utility to disambiguate overloaded functions.
|
||||||
|
* @tparam Func Function type of the desired overload.
|
||||||
|
* @param func A valid pointer to a function.
|
||||||
|
* @return Pointer to the function.
|
||||||
|
*/
|
||||||
|
template<typename Func>
|
||||||
|
[[nodiscard]] constexpr auto overload(Func *func) 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.
|
||||||
|
*/
|
||||||
|
constexpr y_combinator(Func recursive) noexcept(std::is_nothrow_move_constructible_v<Func>)
|
||||||
|
: 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>
|
||||||
|
constexpr decltype(auto) operator()(Args &&...args) const noexcept(std::is_nothrow_invocable_v<Func, const y_combinator &, Args...>) {
|
||||||
|
return func(*this, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc operator()() */
|
||||||
|
template<class... Args>
|
||||||
|
constexpr decltype(auto) operator()(Args &&...args) noexcept(std::is_nothrow_invocable_v<Func, y_combinator &, Args...>) {
|
||||||
|
return func(*this, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Func func;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace entt
|
||||||
|
|
||||||
|
#endif
|
64
src/entt/entity/component.hpp
Normal file
64
src/entt/entity/component.hpp
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
#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 Type, typename = void>
|
||||||
|
struct in_place_delete: std::bool_constant<!(std::is_move_constructible_v<Type> && std::is_move_assignable_v<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, !std::is_empty_v<ENTT_ETO_TYPE(Type)> * 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 Component type. */
|
||||||
|
using type = 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 = (std::is_void_v<Type> || component_traits<Type>::page_size == 0u);
|
||||||
|
|
||||||
|
} // namespace entt
|
||||||
|
|
||||||
|
#endif
|
339
src/entt/entity/entity.hpp
Normal file
339
src/entt/entity/entity.hpp
Normal file
@ -0,0 +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 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cond TURN_OFF_DOXYGEN
|
||||||
|
* Internal details not to be documented.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
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> {};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct entt_traits<std::uint32_t> {
|
||||||
|
using entity_type = std::uint32_t;
|
||||||
|
using version_type = std::uint16_t;
|
||||||
|
|
||||||
|
static constexpr entity_type entity_mask = 0xFFFFF;
|
||||||
|
static constexpr entity_type version_mask = 0xFFF;
|
||||||
|
static constexpr std::size_t entity_shift = 20u;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct entt_traits<std::uint64_t> {
|
||||||
|
using entity_type = std::uint64_t;
|
||||||
|
using version_type = std::uint32_t;
|
||||||
|
|
||||||
|
static constexpr entity_type entity_mask = 0xFFFFFFFF;
|
||||||
|
static constexpr entity_type version_mask = 0xFFFFFFFF;
|
||||||
|
static constexpr std::size_t entity_shift = 32u;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) noexcept {
|
||||||
|
return static_cast<entity_type>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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) 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) 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) noexcept {
|
||||||
|
return value_type{(entity & base_type::entity_mask) | (static_cast<entity_type>(version) << base_type::entity_shift)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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) 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) 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) 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) 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>
|
||||||
|
[[nodiscard]] constexpr operator Entity() const noexcept {
|
||||||
|
using entity_traits = entt_traits<Entity>;
|
||||||
|
return entity_traits::combine(entity_traits::reserved, entity_traits::reserved);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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 noexcept {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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 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>
|
||||||
|
[[nodiscard]] constexpr bool operator==(const Entity entity) const 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>
|
||||||
|
[[nodiscard]] constexpr bool operator!=(const Entity entity) const 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>
|
||||||
|
[[nodiscard]] constexpr bool operator==(const Entity entity, const null_t other) 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>
|
||||||
|
[[nodiscard]] constexpr bool operator!=(const Entity entity, const null_t other) 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 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 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 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 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 noexcept {
|
||||||
|
return !(entity == *this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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) 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) noexcept {
|
||||||
|
return !(other == entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Compile-time constant for null entities.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
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
|
206
src/entt/entity/fwd.hpp
Normal file
206
src/entt/entity/fwd.hpp
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
#ifndef ENTT_ENTITY_FWD_HPP
|
||||||
|
#define ENTT_ENTITY_FWD_HPP
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <type_traits>
|
||||||
|
#include "../core/fwd.hpp"
|
||||||
|
#include "../core/type_traits.hpp"
|
||||||
|
|
||||||
|
namespace entt {
|
||||||
|
|
||||||
|
/*! @brief Default entity identifier. */
|
||||||
|
enum class entity : id_type {};
|
||||||
|
|
||||||
|
template<typename Entity = entity, typename = std::allocator<Entity>>
|
||||||
|
class basic_sparse_set;
|
||||||
|
|
||||||
|
template<typename Type, typename = entity, typename = std::allocator<Type>, typename = void>
|
||||||
|
class basic_storage;
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
class sigh_storage_mixin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Provides a common way to define storage types.
|
||||||
|
* @tparam Type Storage value type.
|
||||||
|
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||||
|
* @tparam Allocator Type of allocator used to manage memory and elements.
|
||||||
|
*/
|
||||||
|
template<typename Type, typename Entity = entity, typename Allocator = std::allocator<Type>, typename = void>
|
||||||
|
struct storage_type {
|
||||||
|
/*! @brief Type-to-storage conversion result. */
|
||||||
|
using type = sigh_storage_mixin<basic_storage<Type, Entity, Allocator>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper type.
|
||||||
|
* @tparam Args Arguments to forward.
|
||||||
|
*/
|
||||||
|
template<typename... Args>
|
||||||
|
using storage_type_t = typename storage_type<Args...>::type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type-to-storage conversion utility that preserves constness.
|
||||||
|
* @tparam Type Storage value type, eventually const.
|
||||||
|
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||||
|
* @tparam Allocator Type of allocator used to manage memory and elements.
|
||||||
|
*/
|
||||||
|
template<typename Type, typename Entity = entity, typename Allocator = std::allocator<std::remove_const_t<Type>>>
|
||||||
|
struct storage_for {
|
||||||
|
/*! @brief Type-to-storage conversion result. */
|
||||||
|
using type = constness_as_t<storage_type_t<std::remove_const_t<Type>, Entity, Allocator>, Type>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper type.
|
||||||
|
* @tparam Args Arguments to forward.
|
||||||
|
*/
|
||||||
|
template<typename... Args>
|
||||||
|
using storage_for_t = typename storage_for<Args...>::type;
|
||||||
|
|
||||||
|
template<typename Entity = entity, typename = std::allocator<Entity>>
|
||||||
|
class basic_registry;
|
||||||
|
|
||||||
|
template<typename, typename, typename = void>
|
||||||
|
class basic_view;
|
||||||
|
|
||||||
|
template<typename Type, typename = std::allocator<Type *>>
|
||||||
|
class basic_runtime_view;
|
||||||
|
|
||||||
|
template<typename, typename, typename>
|
||||||
|
class basic_group;
|
||||||
|
|
||||||
|
template<typename>
|
||||||
|
class basic_observer;
|
||||||
|
|
||||||
|
template<typename>
|
||||||
|
class basic_organizer;
|
||||||
|
|
||||||
|
template<typename, typename...>
|
||||||
|
struct basic_handle;
|
||||||
|
|
||||||
|
template<typename>
|
||||||
|
class basic_snapshot;
|
||||||
|
|
||||||
|
template<typename>
|
||||||
|
class basic_snapshot_loader;
|
||||||
|
|
||||||
|
template<typename>
|
||||||
|
class basic_continuous_loader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Alias for exclusion lists.
|
||||||
|
* @tparam Type List of types.
|
||||||
|
*/
|
||||||
|
template<typename... Type>
|
||||||
|
using exclude_t = type_list<Type...>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Variable template for exclusion lists.
|
||||||
|
* @tparam Type List of types.
|
||||||
|
*/
|
||||||
|
template<typename... Type>
|
||||||
|
inline constexpr exclude_t<Type...> exclude{};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Alias for lists of observed components.
|
||||||
|
* @tparam Type List of types.
|
||||||
|
*/
|
||||||
|
template<typename... Type>
|
||||||
|
using get_t = type_list<Type...>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Variable template for lists of observed components.
|
||||||
|
* @tparam Type List of types.
|
||||||
|
*/
|
||||||
|
template<typename... Type>
|
||||||
|
inline constexpr get_t<Type...> get{};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Alias for lists of owned components.
|
||||||
|
* @tparam Type List of types.
|
||||||
|
*/
|
||||||
|
template<typename... Type>
|
||||||
|
using owned_t = type_list<Type...>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Variable template for lists of owned components.
|
||||||
|
* @tparam Type List of types.
|
||||||
|
*/
|
||||||
|
template<typename... Type>
|
||||||
|
inline constexpr owned_t<Type...> owned{};
|
||||||
|
|
||||||
|
/*! @brief Alias declaration for the most common use case. */
|
||||||
|
using sparse_set = basic_sparse_set<>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Alias declaration for the most common use case.
|
||||||
|
* @tparam Type Type of objects assigned to the entities.
|
||||||
|
*/
|
||||||
|
template<typename Type>
|
||||||
|
using storage = basic_storage<Type>;
|
||||||
|
|
||||||
|
/*! @brief Alias declaration for the most common use case. */
|
||||||
|
using registry = basic_registry<>;
|
||||||
|
|
||||||
|
/*! @brief Alias declaration for the most common use case. */
|
||||||
|
using observer = basic_observer<registry>;
|
||||||
|
|
||||||
|
/*! @brief Alias declaration for the most common use case. */
|
||||||
|
using organizer = basic_organizer<registry>;
|
||||||
|
|
||||||
|
/*! @brief Alias declaration for the most common use case. */
|
||||||
|
using handle = basic_handle<registry>;
|
||||||
|
|
||||||
|
/*! @brief Alias declaration for the most common use case. */
|
||||||
|
using const_handle = basic_handle<const registry>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Alias declaration for the most common use case.
|
||||||
|
* @tparam Args Other template parameters.
|
||||||
|
*/
|
||||||
|
template<typename... Args>
|
||||||
|
using handle_view = basic_handle<registry, Args...>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Alias declaration for the most common use case.
|
||||||
|
* @tparam Args Other template parameters.
|
||||||
|
*/
|
||||||
|
template<typename... Args>
|
||||||
|
using const_handle_view = basic_handle<const registry, Args...>;
|
||||||
|
|
||||||
|
/*! @brief Alias declaration for the most common use case. */
|
||||||
|
using snapshot = basic_snapshot<registry>;
|
||||||
|
|
||||||
|
/*! @brief Alias declaration for the most common use case. */
|
||||||
|
using snapshot_loader = basic_snapshot_loader<registry>;
|
||||||
|
|
||||||
|
/*! @brief Alias declaration for the most common use case. */
|
||||||
|
using continuous_loader = basic_continuous_loader<registry>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Alias declaration for the most common use case.
|
||||||
|
* @tparam Get Types of storage iterated by the view.
|
||||||
|
* @tparam Exclude Types of storage used to filter the view.
|
||||||
|
*/
|
||||||
|
template<typename Get, typename Exclude = exclude_t<>>
|
||||||
|
using view = basic_view<type_list_transform_t<Get, storage_for>, type_list_transform_t<Exclude, storage_for>>;
|
||||||
|
|
||||||
|
/*! @brief Alias declaration for the most common use case. */
|
||||||
|
using runtime_view = basic_runtime_view<sparse_set>;
|
||||||
|
|
||||||
|
/*! @brief Alias declaration for the most common use case. */
|
||||||
|
using const_runtime_view = basic_runtime_view<const sparse_set>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Alias declaration for the most common use case.
|
||||||
|
* @tparam Owned Types of storage _owned_ by the group.
|
||||||
|
* @tparam Get Types of storage _observed_ by the group.
|
||||||
|
* @tparam Exclude Types of storage used to filter the group.
|
||||||
|
*/
|
||||||
|
template<typename Owned, typename Get, typename Exclude>
|
||||||
|
using group = basic_group<type_list_transform_t<Owned, storage_for>, type_list_transform_t<Get, storage_for>, type_list_transform_t<Exclude, storage_for>>;
|
||||||
|
|
||||||
|
} // namespace entt
|
||||||
|
|
||||||
|
#endif
|
852
src/entt/entity/group.hpp
Normal file
852
src/entt/entity/group.hpp
Normal file
@ -0,0 +1,852 @@
|
|||||||
|
#ifndef ENTT_ENTITY_GROUP_HPP
|
||||||
|
#define ENTT_ENTITY_GROUP_HPP
|
||||||
|
|
||||||
|
#include <tuple>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include "../config/config.h"
|
||||||
|
#include "../core/iterator.hpp"
|
||||||
|
#include "../core/type_traits.hpp"
|
||||||
|
#include "component.hpp"
|
||||||
|
#include "entity.hpp"
|
||||||
|
#include "fwd.hpp"
|
||||||
|
#include "sparse_set.hpp"
|
||||||
|
#include "storage.hpp"
|
||||||
|
|
||||||
|
namespace entt {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cond TURN_OFF_DOXYGEN
|
||||||
|
* Internal details not to be documented.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<typename, typename, typename>
|
||||||
|
class extended_group_iterator;
|
||||||
|
|
||||||
|
template<typename It, typename... Owned, typename... Get>
|
||||||
|
class extended_group_iterator<It, owned_t<Owned...>, get_t<Get...>> {
|
||||||
|
template<typename Type>
|
||||||
|
auto index_to_element([[maybe_unused]] Type &cpool) const {
|
||||||
|
if constexpr(ignore_as_empty_v<typename Type::value_type>) {
|
||||||
|
return std::make_tuple();
|
||||||
|
} else {
|
||||||
|
return std::forward_as_tuple(cpool.rbegin()[it.index()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
using difference_type = std::ptrdiff_t;
|
||||||
|
using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval<It>()), std::declval<Owned>().get_as_tuple({})..., std::declval<Get>().get_as_tuple({})...));
|
||||||
|
using pointer = input_iterator_pointer<value_type>;
|
||||||
|
using reference = value_type;
|
||||||
|
using iterator_category = std::input_iterator_tag;
|
||||||
|
|
||||||
|
constexpr extended_group_iterator()
|
||||||
|
: it{},
|
||||||
|
pools{} {}
|
||||||
|
|
||||||
|
extended_group_iterator(It from, const std::tuple<Owned *..., Get *...> &cpools)
|
||||||
|
: it{from},
|
||||||
|
pools{cpools} {}
|
||||||
|
|
||||||
|
extended_group_iterator &operator++() noexcept {
|
||||||
|
return ++it, *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
extended_group_iterator operator++(int) noexcept {
|
||||||
|
extended_group_iterator orig = *this;
|
||||||
|
return ++(*this), orig;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] reference operator*() const noexcept {
|
||||||
|
return std::tuple_cat(std::make_tuple(*it), index_to_element(*std::get<Owned *>(pools))..., std::get<Get *>(pools)->get_as_tuple(*it)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] pointer operator->() const noexcept {
|
||||||
|
return operator*();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... Lhs, typename... Rhs>
|
||||||
|
friend constexpr bool operator==(const extended_group_iterator<Lhs...> &, const extended_group_iterator<Rhs...> &) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
It it;
|
||||||
|
std::tuple<Owned *..., Get *...> pools;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Lhs, typename... Rhs>
|
||||||
|
[[nodiscard]] constexpr bool operator==(const extended_group_iterator<Lhs...> &lhs, const extended_group_iterator<Rhs...> &rhs) noexcept {
|
||||||
|
return lhs.it == rhs.it;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... Lhs, typename... Rhs>
|
||||||
|
[[nodiscard]] constexpr bool operator!=(const extended_group_iterator<Lhs...> &lhs, const extended_group_iterator<Rhs...> &rhs) noexcept {
|
||||||
|
return !(lhs == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal details not to be documented.
|
||||||
|
* @endcond
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Group.
|
||||||
|
*
|
||||||
|
* Primary template isn't defined on purpose. All the specializations give a
|
||||||
|
* compile-time error, but for a few reasonable cases.
|
||||||
|
*/
|
||||||
|
template<typename, typename, typename>
|
||||||
|
class basic_group;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Non-owning group.
|
||||||
|
*
|
||||||
|
* A non-owning group returns all entities and only the entities that are at
|
||||||
|
* least in the given storage. Moreover, it's guaranteed that the entity list is
|
||||||
|
* tightly packed in memory for fast iterations.
|
||||||
|
*
|
||||||
|
* @b Important
|
||||||
|
*
|
||||||
|
* Iterators aren't invalidated if:
|
||||||
|
*
|
||||||
|
* * New elements are added to the storage.
|
||||||
|
* * The entity currently pointed is modified (for example, components are added
|
||||||
|
* or removed from it).
|
||||||
|
* * The entity currently pointed is destroyed.
|
||||||
|
*
|
||||||
|
* In all other cases, modifying the pools iterated by the group in any way
|
||||||
|
* invalidates all the iterators and using them results in undefined behavior.
|
||||||
|
*
|
||||||
|
* @tparam Get Types of storage _observed_ by the group.
|
||||||
|
* @tparam Exclude Types of storage used to filter the group.
|
||||||
|
*/
|
||||||
|
template<typename... Get, typename... Exclude>
|
||||||
|
class basic_group<owned_t<>, get_t<Get...>, exclude_t<Exclude...>> {
|
||||||
|
using underlying_type = std::common_type_t<typename Get::entity_type..., typename Exclude::entity_type...>;
|
||||||
|
using basic_common_type = std::common_type_t<typename Get::base_type..., typename Exclude::base_type...>;
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
static constexpr std::size_t index_of = type_list_index_v<std::remove_const_t<Type>, type_list<typename Get::value_type...>>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! @brief Underlying entity identifier. */
|
||||||
|
using entity_type = underlying_type;
|
||||||
|
/*! @brief Unsigned integer type. */
|
||||||
|
using size_type = std::size_t;
|
||||||
|
/*! @brief Common type among all storage types. */
|
||||||
|
using base_type = basic_common_type;
|
||||||
|
/*! @brief Random access iterator type. */
|
||||||
|
using iterator = typename base_type::iterator;
|
||||||
|
/*! @brief Reversed iterator type. */
|
||||||
|
using reverse_iterator = typename base_type::reverse_iterator;
|
||||||
|
/*! @brief Iterable group type. */
|
||||||
|
using iterable = iterable_adaptor<internal::extended_group_iterator<iterator, owned_t<>, get_t<Get...>>>;
|
||||||
|
|
||||||
|
/*! @brief Default constructor to use to create empty, invalid groups. */
|
||||||
|
basic_group() noexcept
|
||||||
|
: handler{} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs a group from a set of storage classes.
|
||||||
|
* @param ref The actual entities to iterate.
|
||||||
|
* @param gpool Storage types to iterate _observed_ by the group.
|
||||||
|
*/
|
||||||
|
basic_group(basic_common_type &ref, Get &...gpool) noexcept
|
||||||
|
: handler{&ref},
|
||||||
|
pools{&gpool...} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a const reference to the underlying handler.
|
||||||
|
* @return A const reference to the underlying handler.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] const base_type &handle() const noexcept {
|
||||||
|
return *handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the storage for a given component type.
|
||||||
|
* @tparam Type Type of component of which to return the storage.
|
||||||
|
* @return The storage for the given component type.
|
||||||
|
*/
|
||||||
|
template<typename Type>
|
||||||
|
[[nodiscard]] decltype(auto) storage() const noexcept {
|
||||||
|
return storage<index_of<Type>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the storage for a given index.
|
||||||
|
* @tparam Index Index of the storage to return.
|
||||||
|
* @return The storage for the given index.
|
||||||
|
*/
|
||||||
|
template<std::size_t Index>
|
||||||
|
[[nodiscard]] decltype(auto) storage() const noexcept {
|
||||||
|
return *std::get<Index>(pools);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the number of entities that are part of the group.
|
||||||
|
* @return Number of entities that are part of the group.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] size_type size() const noexcept {
|
||||||
|
return *this ? handler->size() : size_type{};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the number of elements that a group has currently
|
||||||
|
* allocated space for.
|
||||||
|
* @return Capacity of the group.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] size_type capacity() const noexcept {
|
||||||
|
return *this ? handler->capacity() : size_type{};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @brief Requests the removal of unused capacity. */
|
||||||
|
void shrink_to_fit() {
|
||||||
|
if(*this) {
|
||||||
|
handler->shrink_to_fit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks whether a group is empty.
|
||||||
|
* @return True if the group is empty, false otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool empty() const noexcept {
|
||||||
|
return !*this || handler->empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an iterator to the first entity of the group.
|
||||||
|
*
|
||||||
|
* The returned iterator points to the first entity of the group. If the
|
||||||
|
* group is empty, the returned iterator will be equal to `end()`.
|
||||||
|
*
|
||||||
|
* @return An iterator to the first entity of the group.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] iterator begin() const noexcept {
|
||||||
|
return *this ? handler->begin() : iterator{};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an iterator that is past the last entity of the group.
|
||||||
|
*
|
||||||
|
* The returned iterator points to the entity following the last entity of
|
||||||
|
* the group. Attempting to dereference the returned iterator results in
|
||||||
|
* undefined behavior.
|
||||||
|
*
|
||||||
|
* @return An iterator to the entity following the last entity of the
|
||||||
|
* group.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] iterator end() const noexcept {
|
||||||
|
return *this ? handler->end() : iterator{};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an iterator to the first entity of the reversed group.
|
||||||
|
*
|
||||||
|
* The returned iterator points to the first entity of the reversed group.
|
||||||
|
* If the group is empty, the returned iterator will be equal to `rend()`.
|
||||||
|
*
|
||||||
|
* @return An iterator to the first entity of the reversed group.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] reverse_iterator rbegin() const noexcept {
|
||||||
|
return *this ? handler->rbegin() : reverse_iterator{};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an iterator that is past the last entity of the reversed
|
||||||
|
* group.
|
||||||
|
*
|
||||||
|
* The returned iterator points to the entity following the last entity of
|
||||||
|
* the reversed group. Attempting to dereference the returned iterator
|
||||||
|
* results in undefined behavior.
|
||||||
|
*
|
||||||
|
* @return An iterator to the entity following the last entity of the
|
||||||
|
* reversed group.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] reverse_iterator rend() const noexcept {
|
||||||
|
return *this ? handler->rend() : reverse_iterator{};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the first entity of the group, if any.
|
||||||
|
* @return The first entity of the group if one exists, the null entity
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] entity_type front() const noexcept {
|
||||||
|
const auto it = begin();
|
||||||
|
return it != end() ? *it : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the last entity of the group, if any.
|
||||||
|
* @return The last entity of the group if one exists, the null entity
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] entity_type back() const noexcept {
|
||||||
|
const auto it = rbegin();
|
||||||
|
return it != rend() ? *it : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Finds an entity.
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @return An iterator to the given entity if it's found, past the end
|
||||||
|
* iterator otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] iterator find(const entity_type entt) const noexcept {
|
||||||
|
const auto it = *this ? handler->find(entt) : iterator{};
|
||||||
|
return it != end() && *it == entt ? it : end();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the identifier that occupies the given position.
|
||||||
|
* @param pos Position of the element to return.
|
||||||
|
* @return The identifier that occupies the given position.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] entity_type operator[](const size_type pos) const {
|
||||||
|
return begin()[pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks if a group is properly initialized.
|
||||||
|
* @return True if the group is properly initialized, false otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] explicit operator bool() const noexcept {
|
||||||
|
return handler != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks if a group contains an entity.
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @return True if the group contains the given entity, false otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool contains(const entity_type entt) const noexcept {
|
||||||
|
return *this && handler->contains(entt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the components assigned to the given entity.
|
||||||
|
*
|
||||||
|
* Prefer this function instead of `registry::get` during iterations. It has
|
||||||
|
* far better performance than its counterpart.
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* Attempting to use an invalid component type results in a compilation
|
||||||
|
* error. Attempting to use an entity that doesn't belong to the group
|
||||||
|
* results in undefined behavior.
|
||||||
|
*
|
||||||
|
* @tparam Type Types of components to get.
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @return The components assigned to the entity.
|
||||||
|
*/
|
||||||
|
template<typename... Type>
|
||||||
|
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
|
||||||
|
if constexpr(sizeof...(Type) == 0) {
|
||||||
|
return std::apply([entt](auto *...curr) { return std::tuple_cat(curr->get_as_tuple(entt)...); }, pools);
|
||||||
|
} else if constexpr(sizeof...(Type) == 1) {
|
||||||
|
return (std::get<index_of<Type>>(pools)->get(entt), ...);
|
||||||
|
} else {
|
||||||
|
return std::tuple_cat(std::get<index_of<Type>>(pools)->get_as_tuple(entt)...);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Iterates entities and components and applies the given function
|
||||||
|
* object to them.
|
||||||
|
*
|
||||||
|
* The function object is invoked for each entity. It is provided with the
|
||||||
|
* entity itself and a set of references to non-empty components. The
|
||||||
|
* _constness_ of the components is as requested.<br/>
|
||||||
|
* The signature of the function must be equivalent to one of the following
|
||||||
|
* forms:
|
||||||
|
*
|
||||||
|
* @code{.cpp}
|
||||||
|
* void(const entity_type, Type &...);
|
||||||
|
* void(Type &...);
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* @note
|
||||||
|
* Empty types aren't explicitly instantiated and therefore they are never
|
||||||
|
* returned during iterations.
|
||||||
|
*
|
||||||
|
* @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 entt: *this) {
|
||||||
|
if constexpr(is_applicable_v<Func, decltype(std::tuple_cat(std::tuple<entity_type>{}, std::declval<basic_group>().get({})))>) {
|
||||||
|
std::apply(func, std::tuple_cat(std::make_tuple(entt), get(entt)));
|
||||||
|
} else {
|
||||||
|
std::apply(func, get(entt));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an iterable object to use to _visit_ a group.
|
||||||
|
*
|
||||||
|
* The iterable object returns tuples that contain the current entity and a
|
||||||
|
* set of references to its non-empty components. The _constness_ of the
|
||||||
|
* components is as requested.
|
||||||
|
*
|
||||||
|
* @note
|
||||||
|
* Empty types aren't explicitly instantiated and therefore they are never
|
||||||
|
* returned during iterations.
|
||||||
|
*
|
||||||
|
* @return An iterable object to use to _visit_ the group.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] iterable each() const noexcept {
|
||||||
|
return iterable{{begin(), pools}, {end(), pools}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sort a group according to the given comparison function.
|
||||||
|
*
|
||||||
|
* Sort the group so that iterating it with a couple of iterators returns
|
||||||
|
* entities and components in the expected order. See `begin` and `end` for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* The comparison function object must return `true` if the first element
|
||||||
|
* is _less_ than the second one, `false` otherwise. The signature of the
|
||||||
|
* comparison function should be equivalent to one of the following:
|
||||||
|
*
|
||||||
|
* @code{.cpp}
|
||||||
|
* bool(std::tuple<Type &...>, std::tuple<Type &...>);
|
||||||
|
* bool(const Type &..., const Type &...);
|
||||||
|
* bool(const Entity, const Entity);
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* Where `Type` are such that they are iterated by the group.<br/>
|
||||||
|
* Moreover, the comparison function object shall induce a
|
||||||
|
* _strict weak ordering_ on the values.
|
||||||
|
*
|
||||||
|
* The sort function object must offer a member function template
|
||||||
|
* `operator()` that accepts three arguments:
|
||||||
|
*
|
||||||
|
* * An iterator to the first element of the range to sort.
|
||||||
|
* * An iterator past the last element of the range to sort.
|
||||||
|
* * A comparison function to use to compare the elements.
|
||||||
|
*
|
||||||
|
* @tparam Type Optional types of components to compare.
|
||||||
|
* @tparam Compare Type of comparison function object.
|
||||||
|
* @tparam Sort Type of sort function object.
|
||||||
|
* @tparam Args Types of arguments to forward to the sort function object.
|
||||||
|
* @param compare A valid comparison function object.
|
||||||
|
* @param algo A valid sort function object.
|
||||||
|
* @param args Arguments to forward to the sort function object, if any.
|
||||||
|
*/
|
||||||
|
template<typename... Type, typename Compare, typename Sort = std_sort, typename... Args>
|
||||||
|
void sort(Compare compare, Sort algo = Sort{}, Args &&...args) {
|
||||||
|
if(*this) {
|
||||||
|
if constexpr(sizeof...(Type) == 0) {
|
||||||
|
static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>, "Invalid comparison function");
|
||||||
|
handler->sort(std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||||
|
} else {
|
||||||
|
auto comp = [this, &compare](const entity_type lhs, const entity_type rhs) {
|
||||||
|
if constexpr(sizeof...(Type) == 1) {
|
||||||
|
return compare((std::get<index_of<Type>>(pools)->get(lhs), ...), (std::get<index_of<Type>>(pools)->get(rhs), ...));
|
||||||
|
} else {
|
||||||
|
return compare(std::forward_as_tuple(std::get<index_of<Type>>(pools)->get(lhs)...), std::forward_as_tuple(std::get<index_of<Type>>(pools)->get(rhs)...));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handler->sort(std::move(comp), std::move(algo), std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sort the shared pool of entities according to the given component.
|
||||||
|
*
|
||||||
|
* Non-owning groups of the same type share with the registry a pool of
|
||||||
|
* entities with its own order that doesn't depend on the order of any pool
|
||||||
|
* of components. Users can order the underlying data structure so that it
|
||||||
|
* respects the order of the pool of the given component.
|
||||||
|
*
|
||||||
|
* @note
|
||||||
|
* The shared pool of entities and thus its order is affected by the changes
|
||||||
|
* to each and every pool that it tracks. Therefore changes to those pools
|
||||||
|
* can quickly ruin the order imposed to the pool of entities shared between
|
||||||
|
* the non-owning groups.
|
||||||
|
*
|
||||||
|
* @tparam Type Type of component to use to impose the order.
|
||||||
|
*/
|
||||||
|
template<typename Type>
|
||||||
|
void sort() const {
|
||||||
|
if(*this) {
|
||||||
|
handler->respect(*std::get<index_of<Type>>(pools));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
base_type *const handler;
|
||||||
|
const std::tuple<Get *...> pools;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Owning group.
|
||||||
|
*
|
||||||
|
* Owning groups returns all entities and only the entities that are at
|
||||||
|
* least in the given storage. Moreover:
|
||||||
|
*
|
||||||
|
* * It's guaranteed that the entity list is tightly packed in memory for fast
|
||||||
|
* iterations.
|
||||||
|
* * It's guaranteed that all components in the owned storage are tightly packed
|
||||||
|
* in memory for even faster iterations and to allow direct access.
|
||||||
|
* * They stay true to the order of the owned storage and all instances have the
|
||||||
|
* same order in memory.
|
||||||
|
*
|
||||||
|
* The more types of storage are owned, the faster it is to iterate a group.
|
||||||
|
*
|
||||||
|
* @b Important
|
||||||
|
*
|
||||||
|
* Iterators aren't invalidated if:
|
||||||
|
*
|
||||||
|
* * New elements are added to the storage.
|
||||||
|
* * The entity currently pointed is modified (for example, components are added
|
||||||
|
* or removed from it).
|
||||||
|
* * The entity currently pointed is destroyed.
|
||||||
|
*
|
||||||
|
* In all other cases, modifying the pools iterated by the group in any way
|
||||||
|
* invalidates all the iterators and using them results in undefined behavior.
|
||||||
|
*
|
||||||
|
* @tparam Owned Types of storage _owned_ by the group.
|
||||||
|
* @tparam Get Types of storage _observed_ by the group.
|
||||||
|
* @tparam Exclude Types of storage used to filter the group.
|
||||||
|
*/
|
||||||
|
template<typename... Owned, typename... Get, typename... Exclude>
|
||||||
|
class basic_group<owned_t<Owned...>, get_t<Get...>, exclude_t<Exclude...>> {
|
||||||
|
using underlying_type = std::common_type_t<typename Owned::entity_type..., typename Get::entity_type..., typename Exclude::entity_type...>;
|
||||||
|
using basic_common_type = std::common_type_t<typename Owned::base_type..., typename Get::base_type..., typename Exclude::base_type...>;
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
static constexpr std::size_t index_of = type_list_index_v<std::remove_const_t<Type>, type_list<typename Owned::value_type..., typename Get::value_type...>>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! @brief Underlying entity identifier. */
|
||||||
|
using entity_type = underlying_type;
|
||||||
|
/*! @brief Unsigned integer type. */
|
||||||
|
using size_type = std::size_t;
|
||||||
|
/*! @brief Common type among all storage types. */
|
||||||
|
using base_type = basic_common_type;
|
||||||
|
/*! @brief Random access iterator type. */
|
||||||
|
using iterator = typename base_type::iterator;
|
||||||
|
/*! @brief Reversed iterator type. */
|
||||||
|
using reverse_iterator = typename base_type::reverse_iterator;
|
||||||
|
/*! @brief Iterable group type. */
|
||||||
|
using iterable = iterable_adaptor<internal::extended_group_iterator<iterator, owned_t<Owned...>, get_t<Get...>>>;
|
||||||
|
|
||||||
|
/*! @brief Default constructor to use to create empty, invalid groups. */
|
||||||
|
basic_group() noexcept
|
||||||
|
: length{} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs a group from a set of storage classes.
|
||||||
|
* @param extent The actual number of entities to iterate.
|
||||||
|
* @param opool Storage types to iterate _owned_ by the group.
|
||||||
|
* @param gpool Storage types to iterate _observed_ by the group.
|
||||||
|
*/
|
||||||
|
basic_group(const std::size_t &extent, Owned &...opool, Get &...gpool) noexcept
|
||||||
|
: pools{&opool..., &gpool...},
|
||||||
|
length{&extent} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the storage for a given component type.
|
||||||
|
* @tparam Type Type of component of which to return the storage.
|
||||||
|
* @return The storage for the given component type.
|
||||||
|
*/
|
||||||
|
template<typename Type>
|
||||||
|
[[nodiscard]] decltype(auto) storage() const noexcept {
|
||||||
|
return storage<index_of<Type>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the storage for a given index.
|
||||||
|
* @tparam Index Index of the storage to return.
|
||||||
|
* @return The storage for the given index.
|
||||||
|
*/
|
||||||
|
template<std::size_t Index>
|
||||||
|
[[nodiscard]] decltype(auto) storage() const noexcept {
|
||||||
|
return *std::get<Index>(pools);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the number of entities that that are part of the group.
|
||||||
|
* @return Number of entities that that are part of the group.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] size_type size() const noexcept {
|
||||||
|
return *this ? *length : size_type{};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks whether a group is empty.
|
||||||
|
* @return True if the group is empty, false otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool empty() const noexcept {
|
||||||
|
return !*this || !*length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an iterator to the first entity of the group.
|
||||||
|
*
|
||||||
|
* The returned iterator points to the first entity of the group. If the
|
||||||
|
* group is empty, the returned iterator will be equal to `end()`.
|
||||||
|
*
|
||||||
|
* @return An iterator to the first entity of the group.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] iterator begin() const noexcept {
|
||||||
|
return *this ? (std::get<0>(pools)->base_type::end() - *length) : iterator{};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an iterator that is past the last entity of the group.
|
||||||
|
*
|
||||||
|
* The returned iterator points to the entity following the last entity of
|
||||||
|
* the group. Attempting to dereference the returned iterator results in
|
||||||
|
* undefined behavior.
|
||||||
|
*
|
||||||
|
* @return An iterator to the entity following the last entity of the
|
||||||
|
* group.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] iterator end() const noexcept {
|
||||||
|
return *this ? std::get<0>(pools)->base_type::end() : iterator{};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an iterator to the first entity of the reversed group.
|
||||||
|
*
|
||||||
|
* The returned iterator points to the first entity of the reversed group.
|
||||||
|
* If the group is empty, the returned iterator will be equal to `rend()`.
|
||||||
|
*
|
||||||
|
* @return An iterator to the first entity of the reversed group.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] reverse_iterator rbegin() const noexcept {
|
||||||
|
return *this ? std::get<0>(pools)->base_type::rbegin() : reverse_iterator{};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an iterator that is past the last entity of the reversed
|
||||||
|
* group.
|
||||||
|
*
|
||||||
|
* The returned iterator points to the entity following the last entity of
|
||||||
|
* the reversed group. Attempting to dereference the returned iterator
|
||||||
|
* results in undefined behavior.
|
||||||
|
*
|
||||||
|
* @return An iterator to the entity following the last entity of the
|
||||||
|
* reversed group.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] reverse_iterator rend() const noexcept {
|
||||||
|
return *this ? (std::get<0>(pools)->base_type::rbegin() + *length) : reverse_iterator{};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the first entity of the group, if any.
|
||||||
|
* @return The first entity of the group if one exists, the null entity
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] entity_type front() const noexcept {
|
||||||
|
const auto it = begin();
|
||||||
|
return it != end() ? *it : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the last entity of the group, if any.
|
||||||
|
* @return The last entity of the group if one exists, the null entity
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] entity_type back() const noexcept {
|
||||||
|
const auto it = rbegin();
|
||||||
|
return it != rend() ? *it : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Finds an entity.
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @return An iterator to the given entity if it's found, past the end
|
||||||
|
* iterator otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] iterator find(const entity_type entt) const noexcept {
|
||||||
|
const auto it = *this ? std::get<0>(pools)->find(entt) : iterator{};
|
||||||
|
return it != end() && it >= begin() && *it == entt ? it : end();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the identifier that occupies the given position.
|
||||||
|
* @param pos Position of the element to return.
|
||||||
|
* @return The identifier that occupies the given position.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] entity_type operator[](const size_type pos) const {
|
||||||
|
return begin()[pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks if a group is properly initialized.
|
||||||
|
* @return True if the group is properly initialized, false otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] explicit operator bool() const noexcept {
|
||||||
|
return length != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks if a group contains an entity.
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @return True if the group contains the given entity, false otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool contains(const entity_type entt) const noexcept {
|
||||||
|
return *this && std::get<0>(pools)->contains(entt) && (std::get<0>(pools)->index(entt) < (*length));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the components assigned to the given entity.
|
||||||
|
*
|
||||||
|
* Prefer this function instead of `registry::get` during iterations. It has
|
||||||
|
* far better performance than its counterpart.
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* Attempting to use an invalid component type results in a compilation
|
||||||
|
* error. Attempting to use an entity that doesn't belong to the group
|
||||||
|
* results in undefined behavior.
|
||||||
|
*
|
||||||
|
* @tparam Type Types of components to get.
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @return The components assigned to the entity.
|
||||||
|
*/
|
||||||
|
template<typename... Type>
|
||||||
|
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
|
||||||
|
if constexpr(sizeof...(Type) == 0) {
|
||||||
|
return std::apply([entt](auto *...curr) { return std::tuple_cat(curr->get_as_tuple(entt)...); }, pools);
|
||||||
|
} else if constexpr(sizeof...(Type) == 1) {
|
||||||
|
return (std::get<index_of<Type>>(pools)->get(entt), ...);
|
||||||
|
} else {
|
||||||
|
return std::tuple_cat(std::get<index_of<Type>>(pools)->get_as_tuple(entt)...);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Iterates entities and components and applies the given function
|
||||||
|
* object to them.
|
||||||
|
*
|
||||||
|
* The function object is invoked for each entity. It is provided with the
|
||||||
|
* entity itself and a set of references to non-empty components. The
|
||||||
|
* _constness_ of the components is as requested.<br/>
|
||||||
|
* The signature of the function must be equivalent to one of the following
|
||||||
|
* forms:
|
||||||
|
*
|
||||||
|
* @code{.cpp}
|
||||||
|
* void(const entity_type, Type &...);
|
||||||
|
* void(Type &...);
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* @note
|
||||||
|
* Empty types aren't explicitly instantiated and therefore they are never
|
||||||
|
* returned during iterations.
|
||||||
|
*
|
||||||
|
* @tparam Func Type of the function object to invoke.
|
||||||
|
* @param func A valid function object.
|
||||||
|
*/
|
||||||
|
template<typename Func>
|
||||||
|
void each(Func func) const {
|
||||||
|
for(auto args: each()) {
|
||||||
|
if constexpr(is_applicable_v<Func, decltype(std::tuple_cat(std::tuple<entity_type>{}, std::declval<basic_group>().get({})))>) {
|
||||||
|
std::apply(func, args);
|
||||||
|
} else {
|
||||||
|
std::apply([&func](auto, auto &&...less) { func(std::forward<decltype(less)>(less)...); }, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an iterable object to use to _visit_ a group.
|
||||||
|
*
|
||||||
|
* The iterable object returns tuples that contain the current entity and a
|
||||||
|
* set of references to its non-empty components. The _constness_ of the
|
||||||
|
* components is as requested.
|
||||||
|
*
|
||||||
|
* @note
|
||||||
|
* Empty types aren't explicitly instantiated and therefore they are never
|
||||||
|
* returned during iterations.
|
||||||
|
*
|
||||||
|
* @return An iterable object to use to _visit_ the group.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] iterable each() const noexcept {
|
||||||
|
return {{begin(), pools}, {end(), pools}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sort a group according to the given comparison function.
|
||||||
|
*
|
||||||
|
* Sort the group so that iterating it with a couple of iterators returns
|
||||||
|
* entities and components in the expected order. See `begin` and `end` for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* The comparison function object must return `true` if the first element
|
||||||
|
* is _less_ than the second one, `false` otherwise. The signature of the
|
||||||
|
* comparison function should be equivalent to one of the following:
|
||||||
|
*
|
||||||
|
* @code{.cpp}
|
||||||
|
* bool(std::tuple<Type &...>, std::tuple<Type &...>);
|
||||||
|
* bool(const Type &, const Type &);
|
||||||
|
* bool(const Entity, const Entity);
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* Where `Type` are either owned types or not but still such that they are
|
||||||
|
* iterated by the group.<br/>
|
||||||
|
* Moreover, the comparison function object shall induce a
|
||||||
|
* _strict weak ordering_ on the values.
|
||||||
|
*
|
||||||
|
* The sort function object must offer a member function template
|
||||||
|
* `operator()` that accepts three arguments:
|
||||||
|
*
|
||||||
|
* * An iterator to the first element of the range to sort.
|
||||||
|
* * An iterator past the last element of the range to sort.
|
||||||
|
* * A comparison function to use to compare the elements.
|
||||||
|
*
|
||||||
|
* @tparam Type Optional types of components to compare.
|
||||||
|
* @tparam Compare Type of comparison function object.
|
||||||
|
* @tparam Sort Type of sort function object.
|
||||||
|
* @tparam Args Types of arguments to forward to the sort function object.
|
||||||
|
* @param compare A valid comparison function object.
|
||||||
|
* @param algo A valid sort function object.
|
||||||
|
* @param args Arguments to forward to the sort function object, if any.
|
||||||
|
*/
|
||||||
|
template<typename... Type, typename Compare, typename Sort = std_sort, typename... Args>
|
||||||
|
void sort(Compare compare, Sort algo = Sort{}, Args &&...args) const {
|
||||||
|
if constexpr(sizeof...(Type) == 0) {
|
||||||
|
static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>, "Invalid comparison function");
|
||||||
|
std::get<0>(pools)->sort_n(*length, std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||||
|
} else {
|
||||||
|
auto comp = [this, &compare](const entity_type lhs, const entity_type rhs) {
|
||||||
|
if constexpr(sizeof...(Type) == 1) {
|
||||||
|
return compare((std::get<index_of<Type>>(pools)->get(lhs), ...), (std::get<index_of<Type>>(pools)->get(rhs), ...));
|
||||||
|
} else {
|
||||||
|
return compare(std::forward_as_tuple(std::get<index_of<Type>>(pools)->get(lhs)...), std::forward_as_tuple(std::get<index_of<Type>>(pools)->get(rhs)...));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::get<0>(pools)->sort_n(*length, std::move(comp), std::move(algo), std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::apply([this](auto *head, auto *...other) {
|
||||||
|
for(auto next = *length; next; --next) {
|
||||||
|
const auto pos = next - 1;
|
||||||
|
[[maybe_unused]] const auto entt = head->data()[pos];
|
||||||
|
(other->swap_elements(other->data()[pos], entt), ...);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pools);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::tuple<Owned *..., Get *...> pools;
|
||||||
|
const size_type *const length;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace entt
|
||||||
|
|
||||||
|
#endif
|
382
src/entt/entity/handle.hpp
Normal file
382
src/entt/entity/handle.hpp
Normal file
@ -0,0 +1,382 @@
|
|||||||
|
#ifndef ENTT_ENTITY_HANDLE_HPP
|
||||||
|
#define ENTT_ENTITY_HANDLE_HPP
|
||||||
|
|
||||||
|
#include <iterator>
|
||||||
|
#include <tuple>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include "../core/iterator.hpp"
|
||||||
|
#include "../core/type_traits.hpp"
|
||||||
|
#include "entity.hpp"
|
||||||
|
#include "fwd.hpp"
|
||||||
|
|
||||||
|
namespace entt {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cond TURN_OFF_DOXYGEN
|
||||||
|
* Internal details not to be documented.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<typename It>
|
||||||
|
class handle_storage_iterator final {
|
||||||
|
template<typename Other>
|
||||||
|
friend class handle_storage_iterator;
|
||||||
|
|
||||||
|
using underlying_type = std::remove_reference_t<typename It::value_type::second_type>;
|
||||||
|
using entity_type = typename underlying_type::entity_type;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using value_type = typename std::iterator_traits<It>::value_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;
|
||||||
|
|
||||||
|
constexpr handle_storage_iterator() noexcept
|
||||||
|
: entt{null},
|
||||||
|
it{},
|
||||||
|
last{} {}
|
||||||
|
|
||||||
|
constexpr handle_storage_iterator(entity_type value, It from, It to) noexcept
|
||||||
|
: entt{value},
|
||||||
|
it{from},
|
||||||
|
last{to} {
|
||||||
|
while(it != last && !it->second.contains(entt)) { ++it; }
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr handle_storage_iterator &operator++() noexcept {
|
||||||
|
while(++it != last && !it->second.contains(entt)) {}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr handle_storage_iterator operator++(int) noexcept {
|
||||||
|
handle_storage_iterator orig = *this;
|
||||||
|
return ++(*this), orig;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr reference operator*() const noexcept {
|
||||||
|
return *it;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr pointer operator->() const noexcept {
|
||||||
|
return operator*();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ILhs, typename IRhs>
|
||||||
|
friend constexpr bool operator==(const handle_storage_iterator<ILhs> &, const handle_storage_iterator<IRhs> &) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
entity_type entt;
|
||||||
|
It it;
|
||||||
|
It last;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename ILhs, typename IRhs>
|
||||||
|
[[nodiscard]] constexpr bool operator==(const handle_storage_iterator<ILhs> &lhs, const handle_storage_iterator<IRhs> &rhs) noexcept {
|
||||||
|
return lhs.it == rhs.it;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ILhs, typename IRhs>
|
||||||
|
[[nodiscard]] constexpr bool operator!=(const handle_storage_iterator<ILhs> &lhs, const handle_storage_iterator<IRhs> &rhs) noexcept {
|
||||||
|
return !(lhs == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal details not to be documented.
|
||||||
|
* @endcond
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Non-owning handle to an entity.
|
||||||
|
*
|
||||||
|
* Tiny wrapper around a registry and an entity.
|
||||||
|
*
|
||||||
|
* @tparam Registry Basic registry type.
|
||||||
|
* @tparam Scope Types to which to restrict the scope of a handle.
|
||||||
|
*/
|
||||||
|
template<typename Registry, typename... Scope>
|
||||||
|
struct basic_handle {
|
||||||
|
/*! @brief Type of registry accepted by the handle. */
|
||||||
|
using registry_type = Registry;
|
||||||
|
/*! @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() 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) noexcept
|
||||||
|
: reg{&ref},
|
||||||
|
entt{value} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an iterable object to use to _visit_ a handle.
|
||||||
|
*
|
||||||
|
* The iterable object returns a pair that contains the name and a reference
|
||||||
|
* to the current storage.<br/>
|
||||||
|
* Returned storage are those that contain the entity associated with the
|
||||||
|
* handle.
|
||||||
|
*
|
||||||
|
* @return An iterable object to use to _visit_ the handle.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] auto storage() const noexcept {
|
||||||
|
auto iterable = reg->storage();
|
||||||
|
using iterator_type = internal::handle_storage_iterator<typename decltype(iterable)::iterator>;
|
||||||
|
return iterable_adaptor{iterator_type{entt, iterable.begin(), iterable.end()}, iterator_type{entt, iterable.end(), iterable.end()}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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 noexcept {
|
||||||
|
static_assert(std::is_same_v<Other, Registry> || std::is_same_v<std::remove_const_t<Other>, Registry>, "Invalid conversion between different handles");
|
||||||
|
static_assert((sizeof...(Scope) == 0 || ((sizeof...(Args) != 0 && sizeof...(Args) <= sizeof...(Scope)) && ... && (type_list_contains_v<type_list<Scope...>, 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 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 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 noexcept {
|
||||||
|
return reg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the entity associated with a handle.
|
||||||
|
* @return The entity associated with the handle.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] entity_type entity() const noexcept {
|
||||||
|
return entt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @brief Destroys the entity associated with a handle. */
|
||||||
|
void destroy() {
|
||||||
|
reg->destroy(entt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroys the entity associated with a handle.
|
||||||
|
* @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.
|
||||||
|
* @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...(Scope) == 0) || ... || std::is_same_v<Component, Scope>), "Invalid type");
|
||||||
|
return reg->template emplace<Component>(entt, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Assigns or replaces the given component for a handle.
|
||||||
|
* @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...(Scope) == 0) || ... || std::is_same_v<Component, Scope>), "Invalid type");
|
||||||
|
return reg->template emplace_or_replace<Component>(entt, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Patches the given component for a handle.
|
||||||
|
* @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...(Scope) == 0) || ... || std::is_same_v<Component, Scope>), "Invalid type");
|
||||||
|
return reg->template patch<Component>(entt, std::forward<Func>(func)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Replaces the given component for a handle.
|
||||||
|
* @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...(Scope) == 0) || ... || std::is_same_v<Component, Scope>), "Invalid type");
|
||||||
|
return reg->template replace<Component>(entt, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Removes the given components from a handle.
|
||||||
|
* @tparam Component Types of components to remove.
|
||||||
|
* @return The number of components actually removed.
|
||||||
|
*/
|
||||||
|
template<typename... Component>
|
||||||
|
size_type remove() const {
|
||||||
|
static_assert(sizeof...(Scope) == 0 || (type_list_contains_v<type_list<Scope...>, Component> && ...), "Invalid type");
|
||||||
|
return reg->template remove<Component...>(entt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Erases the given components from a handle.
|
||||||
|
* @tparam Component Types of components to erase.
|
||||||
|
*/
|
||||||
|
template<typename... Component>
|
||||||
|
void erase() const {
|
||||||
|
static_assert(sizeof...(Scope) == 0 || (type_list_contains_v<type_list<Scope...>, Component> && ...), "Invalid type");
|
||||||
|
reg->template erase<Component...>(entt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks if a handle has all the given components.
|
||||||
|
* @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.
|
||||||
|
* @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.
|
||||||
|
* @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...(Scope) == 0 || (type_list_contains_v<type_list<Scope...>, Component> && ...), "Invalid type");
|
||||||
|
return reg->template get<Component...>(entt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a reference to the given component for a handle.
|
||||||
|
* @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...(Scope) == 0) || ... || std::is_same_v<Component, Scope>), "Invalid type");
|
||||||
|
return reg->template get_or_emplace<Component>(entt, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns pointers to the given components for a handle.
|
||||||
|
* @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...(Scope) == 0 || (type_list_contains_v<type_list<Scope...>, 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) 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) noexcept {
|
||||||
|
return !(lhs == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace entt
|
||||||
|
|
||||||
|
#endif
|
144
src/entt/entity/helper.hpp
Normal file
144
src/entt/entity/helper.hpp
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
#ifndef ENTT_ENTITY_HELPER_HPP
|
||||||
|
#define ENTT_ENTITY_HELPER_HPP
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <type_traits>
|
||||||
|
#include "../core/fwd.hpp"
|
||||||
|
#include "../core/type_traits.hpp"
|
||||||
|
#include "../signal/delegate.hpp"
|
||||||
|
#include "component.hpp"
|
||||||
|
#include "fwd.hpp"
|
||||||
|
#include "group.hpp"
|
||||||
|
#include "view.hpp"
|
||||||
|
|
||||||
|
namespace entt {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Converts a registry to a view.
|
||||||
|
* @tparam Registry Basic registry type.
|
||||||
|
*/
|
||||||
|
template<typename Registry>
|
||||||
|
class as_view {
|
||||||
|
template<typename... Get, typename... Exclude>
|
||||||
|
auto dispatch(get_t<Get...>, exclude_t<Exclude...>) const {
|
||||||
|
return reg.template view<constness_as_t<typename Get::value_type, Get>...>(exclude_t<constness_as_t<typename Exclude::value_type, Exclude>...>{});
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! @brief Type of registry to convert. */
|
||||||
|
using registry_type = Registry;
|
||||||
|
/*! @brief Underlying entity identifier. */
|
||||||
|
using entity_type = std::remove_const_t<typename registry_type::entity_type>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs a converter for a given registry.
|
||||||
|
* @param source A valid reference to a registry.
|
||||||
|
*/
|
||||||
|
as_view(registry_type &source) noexcept
|
||||||
|
: reg{source} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Conversion function from a registry to a view.
|
||||||
|
* @tparam Get Type of storage used to construct the view.
|
||||||
|
* @tparam Exclude Types of storage used to filter the view.
|
||||||
|
* @return A newly created view.
|
||||||
|
*/
|
||||||
|
template<typename Get, typename Exclude>
|
||||||
|
operator basic_view<Get, Exclude>() const {
|
||||||
|
return dispatch(Get{}, Exclude{});
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
registry_type ®
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Converts a registry to a group.
|
||||||
|
* @tparam Registry Basic registry type.
|
||||||
|
*/
|
||||||
|
template<typename Registry>
|
||||||
|
class as_group {
|
||||||
|
template<typename... Owned, typename... Get, typename... Exclude>
|
||||||
|
auto dispatch(owned_t<Owned...>, get_t<Get...>, exclude_t<Exclude...>) const {
|
||||||
|
if constexpr(std::is_const_v<registry_type>) {
|
||||||
|
return reg.template group_if_exists<typename Owned::value_type...>(get_t<typename Get::value_type...>{}, exclude_t<typename Exclude::value_type...>{});
|
||||||
|
} else {
|
||||||
|
return reg.template group<constness_as_t<typename Owned::value_type, Owned>...>(get_t<constness_as_t<typename Get::value_type, Get>...>{}, exclude_t<constness_as_t<typename Exclude::value_type, Exclude>...>{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! @brief Type of registry to convert. */
|
||||||
|
using registry_type = Registry;
|
||||||
|
/*! @brief Underlying entity identifier. */
|
||||||
|
using entity_type = std::remove_const_t<typename registry_type::entity_type>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs a converter for a given registry.
|
||||||
|
* @param source A valid reference to a registry.
|
||||||
|
*/
|
||||||
|
as_group(registry_type &source) noexcept
|
||||||
|
: reg{source} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Conversion function from a registry to a group.
|
||||||
|
* @tparam Owned Types of _owned_ by the group.
|
||||||
|
* @tparam Get Types of storage _observed_ by the group.
|
||||||
|
* @tparam Exclude Types of storage used to filter the group.
|
||||||
|
* @return A newly created group.
|
||||||
|
*/
|
||||||
|
template<typename Owned, typename Get, typename Exclude>
|
||||||
|
operator basic_group<Owned, Get, Exclude>() const {
|
||||||
|
return dispatch(Owned{}, Get{}, Exclude{});
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
registry_type ®
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper to create a listener that directly invokes a member function.
|
||||||
|
* @tparam Member Member function to invoke on a component of the given type.
|
||||||
|
* @tparam Registry Basic registry type.
|
||||||
|
* @param reg A registry that contains the given entity and its components.
|
||||||
|
* @param entt Entity from which to get the component.
|
||||||
|
*/
|
||||||
|
template<auto Member, typename Registry = std::decay_t<nth_argument_t<0u, Member>>>
|
||||||
|
void invoke(Registry ®, const typename Registry::entity_type entt) {
|
||||||
|
static_assert(std::is_member_function_pointer_v<decltype(Member)>, "Invalid pointer to non-static member function");
|
||||||
|
delegate<void(Registry &, const typename Registry::entity_type)> func;
|
||||||
|
func.template connect<Member>(reg.template get<member_class_t<decltype(Member)>>(entt));
|
||||||
|
func(reg, entt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the entity associated with a given component.
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* Currently, this function only works correctly with the default pool as it
|
||||||
|
* makes assumptions about how the components are laid out.
|
||||||
|
*
|
||||||
|
* @tparam Registry Basic registry type.
|
||||||
|
* @tparam Component Type of component.
|
||||||
|
* @param reg A registry that contains the given entity and its components.
|
||||||
|
* @param instance A valid component instance.
|
||||||
|
* @return The entity associated with the given component.
|
||||||
|
*/
|
||||||
|
template<typename Registry, typename Component>
|
||||||
|
typename Registry::entity_type to_entity(const Registry ®, const Component &instance) {
|
||||||
|
const auto &storage = reg.template storage<Component>();
|
||||||
|
const typename Registry::base_type &base = storage;
|
||||||
|
const auto *addr = std::addressof(instance);
|
||||||
|
|
||||||
|
for(auto it = base.rbegin(), last = base.rend(); it < last; it += component_traits<Component>::page_size) {
|
||||||
|
if(const auto dist = (addr - std::addressof(storage.get(*it))); dist >= 0 && dist < static_cast<decltype(dist)>(component_traits<Component>::page_size)) {
|
||||||
|
return *(it + dist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace entt
|
||||||
|
|
||||||
|
#endif
|
432
src/entt/entity/observer.hpp
Normal file
432
src/entt/entity/observer.hpp
Normal file
@ -0,0 +1,432 @@
|
|||||||
|
#ifndef ENTT_ENTITY_OBSERVER_HPP
|
||||||
|
#define ENTT_ENTITY_OBSERVER_HPP
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <limits>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include "../core/type_traits.hpp"
|
||||||
|
#include "../signal/delegate.hpp"
|
||||||
|
#include "fwd.hpp"
|
||||||
|
#include "storage.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...> = {}) 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() 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...> = {}) 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() 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...> = {}) 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 Registry Basic registry type.
|
||||||
|
*/
|
||||||
|
template<typename Registry>
|
||||||
|
class basic_observer: private basic_storage<std::uint32_t, typename Registry::entity_type> {
|
||||||
|
using base_type = basic_storage<std::uint32_t, typename Registry::entity_type>;
|
||||||
|
|
||||||
|
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, Registry ®, const typename Registry::entity_type entt) {
|
||||||
|
if(reg.template all_of<Require...>(entt) && !reg.template any_of<Reject...>(entt)) {
|
||||||
|
if(!obs.contains(entt)) {
|
||||||
|
obs.emplace(entt);
|
||||||
|
}
|
||||||
|
|
||||||
|
obs.get(entt) |= (1 << Index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<std::size_t Index>
|
||||||
|
static void discard_if(basic_observer &obs, Registry &, const typename Registry::entity_type entt) {
|
||||||
|
if(obs.contains(entt) && !(obs.get(entt) &= (~(1 << Index)))) {
|
||||||
|
obs.erase(entt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<std::size_t Index>
|
||||||
|
static void connect(basic_observer &obs, Registry ®) {
|
||||||
|
(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, Registry ®) {
|
||||||
|
(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, Registry ®, const typename Registry::entity_type 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.contains(entt)) {
|
||||||
|
obs.emplace(entt);
|
||||||
|
}
|
||||||
|
|
||||||
|
obs.get(entt) |= (1 << Index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<std::size_t Index>
|
||||||
|
static void discard_if(basic_observer &obs, Registry &, const typename Registry::entity_type entt) {
|
||||||
|
if(obs.contains(entt) && !(obs.get(entt) &= (~(1 << Index)))) {
|
||||||
|
obs.erase(entt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<std::size_t Index>
|
||||||
|
static void connect(basic_observer &obs, Registry ®) {
|
||||||
|
(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, Registry ®) {
|
||||||
|
(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(Registry ®, basic_observer &obs) {
|
||||||
|
(matcher_handler<Matcher>::disconnect(obs, reg), ...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... Matcher, std::size_t... Index>
|
||||||
|
void connect(Registry ®, std::index_sequence<Index...>) {
|
||||||
|
static_assert(sizeof...(Matcher) < std::numeric_limits<typename base_type::value_type>::digits, "Too many matchers");
|
||||||
|
(matcher_handler<Matcher>::template connect<Index>(*this, reg), ...);
|
||||||
|
release.template connect<&basic_observer::disconnect<Matcher...>>(reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! Basic registry type. */
|
||||||
|
using registry_type = Registry;
|
||||||
|
/*! @brief Underlying entity identifier. */
|
||||||
|
using entity_type = typename registry_type::entity_type;
|
||||||
|
/*! @brief Unsigned integer type. */
|
||||||
|
using size_type = std::size_t;
|
||||||
|
/*! @brief Random access iterator type. */
|
||||||
|
using iterator = typename registry_type::base_type::iterator;
|
||||||
|
|
||||||
|
/*! @brief Default constructor. */
|
||||||
|
basic_observer()
|
||||||
|
: release{} {}
|
||||||
|
|
||||||
|
/*! @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(registry_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(registry_type ®, basic_collector<Matcher...>) {
|
||||||
|
disconnect();
|
||||||
|
connect<Matcher...>(reg, std::index_sequence_for<Matcher...>{});
|
||||||
|
base_type::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 noexcept {
|
||||||
|
return base_type::size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks whether an observer is empty.
|
||||||
|
* @return True if the observer is empty, false otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool empty() const noexcept {
|
||||||
|
return base_type::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 noexcept {
|
||||||
|
return base_type::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 noexcept {
|
||||||
|
return base_type::base_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 noexcept {
|
||||||
|
return base_type::base_type::end();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @brief Clears the underlying container. */
|
||||||
|
void clear() noexcept {
|
||||||
|
base_type::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;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace entt
|
||||||
|
|
||||||
|
#endif
|
412
src/entt/entity/organizer.hpp
Normal file
412
src/entt/entity/organizer.hpp
Normal file
@ -0,0 +1,412 @@
|
|||||||
|
#ifndef ENTT_ENTITY_ORGANIZER_HPP
|
||||||
|
#define ENTT_ENTITY_ORGANIZER_HPP
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
#include "../core/type_info.hpp"
|
||||||
|
#include "../core/type_traits.hpp"
|
||||||
|
#include "../core/utility.hpp"
|
||||||
|
#include "../graph/adjacency_matrix.hpp"
|
||||||
|
#include "../graph/flow.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... Args>
|
||||||
|
struct is_view<basic_view<Args...>>: 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, const 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, const Type>),
|
||||||
|
type_list<Type>,
|
||||||
|
type_list<>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Args, typename... Override>
|
||||||
|
struct unpack_type<basic_registry<Args...>, type_list<Override...>> {
|
||||||
|
using ro = type_list<>;
|
||||||
|
using rw = type_list<>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Args, typename... Override>
|
||||||
|
struct unpack_type<const basic_registry<Args...>, type_list<Override...>>
|
||||||
|
: unpack_type<basic_registry<Args...>, type_list<Override...>> {};
|
||||||
|
|
||||||
|
template<typename... Get, typename... Exclude, typename... Override>
|
||||||
|
struct unpack_type<basic_view<get_t<Get...>, exclude_t<Exclude...>>, type_list<Override...>> {
|
||||||
|
using ro = type_list_cat_t<type_list<typename Exclude::value_type...>, typename unpack_type<constness_as_t<typename Get::value_type, Get>, type_list<Override...>>::ro...>;
|
||||||
|
using rw = type_list_cat_t<typename unpack_type<constness_as_t<typename Get::value_type, Get>, type_list<Override...>>::rw...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Get, typename... Exclude, typename... Override>
|
||||||
|
struct unpack_type<const basic_view<get_t<Get...>, exclude_t<Exclude...>>, type_list<Override...>>
|
||||||
|
: unpack_type<basic_view<get_t<Get...>, 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 Registry Basic registry type.
|
||||||
|
*/
|
||||||
|
template<typename Registry>
|
||||||
|
class basic_organizer final {
|
||||||
|
using callback_type = void(const void *, Registry &);
|
||||||
|
using prepare_type = void(Registry &);
|
||||||
|
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(Registry ®) {
|
||||||
|
if constexpr(std::is_same_v<Type, Registry>) {
|
||||||
|
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(Registry ®, 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 = count < sizeof...(Type) ? count : sizeof...(Type);
|
||||||
|
|
||||||
|
for(std::size_t pos{}; pos < length; ++pos) {
|
||||||
|
buffer[pos] = info[pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... RO, typename... RW>
|
||||||
|
void track_dependencies(std::size_t index, const bool requires_registry, type_list<RO...>, type_list<RW...>) {
|
||||||
|
builder.bind(static_cast<id_type>(index));
|
||||||
|
builder.set(type_hash<Registry>::value(), requires_registry || (sizeof...(RO) + sizeof...(RW) == 0u));
|
||||||
|
(builder.ro(type_hash<RO>::value()), ...);
|
||||||
|
(builder.rw(type_hash<RW>::value()), ...);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! Basic registry type. */
|
||||||
|
using registry_type = Registry;
|
||||||
|
/*! @brief Underlying entity identifier. */
|
||||||
|
using entity_type = typename registry_type::entity_type;
|
||||||
|
/*! @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 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 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 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 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 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 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 noexcept {
|
||||||
|
return node.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the function associated with a vertex.
|
||||||
|
* @return The function associated with the vertex.
|
||||||
|
*/
|
||||||
|
function_type *callback() const 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 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 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(registry_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, registry_type>;
|
||||||
|
|
||||||
|
callback_type *callback = +[](const void *, registry_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); },
|
||||||
|
+[](registry_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, registry_type>;
|
||||||
|
|
||||||
|
callback_type *callback = +[](const void *payload, registry_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); },
|
||||||
|
+[](registry_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() {
|
||||||
|
std::vector<vertex> adjacency_list{};
|
||||||
|
adjacency_list.reserve(vertices.size());
|
||||||
|
auto adjacency_matrix = builder.graph();
|
||||||
|
|
||||||
|
for(auto curr: adjacency_matrix.vertices()) {
|
||||||
|
const auto iterable = adjacency_matrix.in_edges(curr);
|
||||||
|
std::vector<std::size_t> reachable{};
|
||||||
|
|
||||||
|
for(auto &&edge: adjacency_matrix.out_edges(curr)) {
|
||||||
|
reachable.push_back(edge.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
adjacency_list.emplace_back(iterable.cbegin() == iterable.cend(), vertices[curr], std::move(reachable));
|
||||||
|
}
|
||||||
|
|
||||||
|
return adjacency_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @brief Erases all elements from a container. */
|
||||||
|
void clear() {
|
||||||
|
builder.clear();
|
||||||
|
vertices.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<vertex_data> vertices;
|
||||||
|
flow builder;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace entt
|
||||||
|
|
||||||
|
#endif
|
1530
src/entt/entity/registry.hpp
Normal file
1530
src/entt/entity/registry.hpp
Normal file
File diff suppressed because it is too large
Load Diff
335
src/entt/entity/runtime_view.hpp
Normal file
335
src/entt/entity/runtime_view.hpp
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
#ifndef ENTT_ENTITY_RUNTIME_VIEW_HPP
|
||||||
|
#define ENTT_ENTITY_RUNTIME_VIEW_HPP
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <iterator>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
#include "entity.hpp"
|
||||||
|
#include "fwd.hpp"
|
||||||
|
|
||||||
|
namespace entt {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cond TURN_OFF_DOXYGEN
|
||||||
|
* Internal details not to be documented.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<typename Set>
|
||||||
|
class runtime_view_iterator final {
|
||||||
|
using iterator_type = typename Set::iterator;
|
||||||
|
|
||||||
|
[[nodiscard]] bool valid() const {
|
||||||
|
return (!tombstone_check || *it != tombstone)
|
||||||
|
&& std::all_of(++pools->begin(), pools->end(), [entt = *it](const auto *curr) { return curr->contains(entt); })
|
||||||
|
&& std::none_of(filter->cbegin(), filter->cend(), [entt = *it](const auto *curr) { return curr && curr->contains(entt); });
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
using difference_type = typename iterator_type::difference_type;
|
||||||
|
using value_type = typename iterator_type::value_type;
|
||||||
|
using pointer = typename iterator_type::pointer;
|
||||||
|
using reference = typename iterator_type::reference;
|
||||||
|
using iterator_category = std::bidirectional_iterator_tag;
|
||||||
|
|
||||||
|
constexpr runtime_view_iterator() noexcept
|
||||||
|
: pools{},
|
||||||
|
filter{},
|
||||||
|
it{},
|
||||||
|
tombstone_check{} {}
|
||||||
|
|
||||||
|
runtime_view_iterator(const std::vector<Set *> &cpools, const std::vector<Set *> &ignore, iterator_type curr) noexcept
|
||||||
|
: pools{&cpools},
|
||||||
|
filter{&ignore},
|
||||||
|
it{curr},
|
||||||
|
tombstone_check{pools->size() == 1u && (*pools)[0u]->policy() == deletion_policy::in_place} {
|
||||||
|
if(it != (*pools)[0]->end() && !valid()) {
|
||||||
|
++(*this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime_view_iterator &operator++() {
|
||||||
|
while(++it != (*pools)[0]->end() && !valid()) {}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime_view_iterator operator++(int) {
|
||||||
|
runtime_view_iterator orig = *this;
|
||||||
|
return ++(*this), orig;
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime_view_iterator &operator--() {
|
||||||
|
while(--it != (*pools)[0]->begin() && !valid()) {}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime_view_iterator operator--(int) {
|
||||||
|
runtime_view_iterator orig = *this;
|
||||||
|
return operator--(), orig;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] pointer operator->() const noexcept {
|
||||||
|
return it.operator->();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] reference operator*() const noexcept {
|
||||||
|
return *operator->();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr bool operator==(const runtime_view_iterator &other) const noexcept {
|
||||||
|
return it == other.it;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr bool operator!=(const runtime_view_iterator &other) const noexcept {
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::vector<Set *> *pools;
|
||||||
|
const std::vector<Set *> *filter;
|
||||||
|
iterator_type it;
|
||||||
|
bool tombstone_check;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal details not to be documented.
|
||||||
|
* @endcond
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generic runtime view.
|
||||||
|
*
|
||||||
|
* Runtime views iterate over those entities that have at least all the given
|
||||||
|
* components in their bags. During initialization, a runtime view looks at the
|
||||||
|
* number of entities available for each component and picks up a reference to
|
||||||
|
* the smallest set of candidate entities in order to get a performance boost
|
||||||
|
* when iterate.<br/>
|
||||||
|
* Order of elements during iterations are highly dependent on the order of the
|
||||||
|
* underlying data structures. See sparse_set and its specializations for more
|
||||||
|
* details.
|
||||||
|
*
|
||||||
|
* @b Important
|
||||||
|
*
|
||||||
|
* Iterators aren't invalidated if:
|
||||||
|
*
|
||||||
|
* * New instances of the given components are created and assigned to entities.
|
||||||
|
* * The entity currently pointed is modified (as an example, if one of the
|
||||||
|
* given components is removed from the entity to which the iterator points).
|
||||||
|
* * 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.
|
||||||
|
*
|
||||||
|
* @note
|
||||||
|
* Views share references to the underlying data structures of the registry that
|
||||||
|
* generated them. Therefore any change to the entities and to the components
|
||||||
|
* made by means of the registry are immediately reflected by the views, unless
|
||||||
|
* a pool was missing when the view was built (in this case, the view won't
|
||||||
|
* have a valid reference and won't be updated accordingly).
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* Lifetime of a view must not overcome that of the registry that generated it.
|
||||||
|
* In any other case, attempting to use a view results in undefined behavior.
|
||||||
|
*
|
||||||
|
* @tparam Type Common base type.
|
||||||
|
* @tparam Allocator Type of allocator used to manage memory and elements.
|
||||||
|
*/
|
||||||
|
template<typename Type, typename Allocator>
|
||||||
|
class basic_runtime_view {
|
||||||
|
using alloc_traits = std::allocator_traits<Allocator>;
|
||||||
|
static_assert(std::is_same_v<typename alloc_traits::value_type, Type *>, "Invalid value type");
|
||||||
|
using container_type = std::vector<Type *, Allocator>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! @brief Allocator type. */
|
||||||
|
using allocator_type = Allocator;
|
||||||
|
/*! @brief Underlying entity identifier. */
|
||||||
|
using entity_type = typename Type::entity_type;
|
||||||
|
/*! @brief Unsigned integer type. */
|
||||||
|
using size_type = std::size_t;
|
||||||
|
/*! @brief Common type among all storage types. */
|
||||||
|
using base_type = Type;
|
||||||
|
/*! @brief Bidirectional iterator type. */
|
||||||
|
using iterator = internal::runtime_view_iterator<base_type>;
|
||||||
|
|
||||||
|
/*! @brief Default constructor to use to create empty, invalid views. */
|
||||||
|
basic_runtime_view() noexcept
|
||||||
|
: basic_runtime_view{allocator_type{}} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs an empty, invalid view with a given allocator.
|
||||||
|
* @param allocator The allocator to use.
|
||||||
|
*/
|
||||||
|
explicit basic_runtime_view(const allocator_type &allocator)
|
||||||
|
: pools{allocator},
|
||||||
|
filter{allocator} {}
|
||||||
|
|
||||||
|
/*! @brief Default copy constructor. */
|
||||||
|
basic_runtime_view(const basic_runtime_view &) = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Allocator-extended copy constructor.
|
||||||
|
* @param other The instance to copy from.
|
||||||
|
* @param allocator The allocator to use.
|
||||||
|
*/
|
||||||
|
basic_runtime_view(const basic_runtime_view &other, const allocator_type &allocator)
|
||||||
|
: pools{other.pools, allocator},
|
||||||
|
filter{other.filter, allocator} {}
|
||||||
|
|
||||||
|
/*! @brief Default move constructor. */
|
||||||
|
basic_runtime_view(basic_runtime_view &&) noexcept(std::is_nothrow_move_constructible_v<container_type>) = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Allocator-extended move constructor.
|
||||||
|
* @param other The instance to move from.
|
||||||
|
* @param allocator The allocator to use.
|
||||||
|
*/
|
||||||
|
basic_runtime_view(basic_runtime_view &&other, const allocator_type &allocator)
|
||||||
|
: pools{std::move(other.pools), allocator},
|
||||||
|
filter{std::move(other.filter), allocator} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Default copy assignment operator.
|
||||||
|
* @return This container.
|
||||||
|
*/
|
||||||
|
basic_runtime_view &operator=(const basic_runtime_view &) = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Default move assignment operator.
|
||||||
|
* @return This container.
|
||||||
|
*/
|
||||||
|
basic_runtime_view &operator=(basic_runtime_view &&) noexcept(std::is_nothrow_move_assignable_v<container_type>) = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Exchanges the contents with those of a given view.
|
||||||
|
* @param other View to exchange the content with.
|
||||||
|
*/
|
||||||
|
void swap(basic_runtime_view &other) {
|
||||||
|
using std::swap;
|
||||||
|
swap(pools, other.pools);
|
||||||
|
swap(filter, other.filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the associated allocator.
|
||||||
|
* @return The associated allocator.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
|
||||||
|
return pools.get_allocator();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @brief Clears the view. */
|
||||||
|
void clear() {
|
||||||
|
pools.clear();
|
||||||
|
filter.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Appends an opaque storage object to a runtime view.
|
||||||
|
* @param base An opaque reference to a storage object.
|
||||||
|
* @return This runtime view.
|
||||||
|
*/
|
||||||
|
basic_runtime_view &iterate(base_type &base) {
|
||||||
|
if(pools.empty() || !(base.size() < pools[0u]->size())) {
|
||||||
|
pools.push_back(&base);
|
||||||
|
} else {
|
||||||
|
pools.push_back(std::exchange(pools[0u], &base));
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Adds an opaque storage object as a filter of a runtime view.
|
||||||
|
* @param base An opaque reference to a storage object.
|
||||||
|
* @return This runtime view.
|
||||||
|
*/
|
||||||
|
basic_runtime_view &exclude(base_type &base) {
|
||||||
|
filter.push_back(&base);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Estimates the number of entities iterated by the view.
|
||||||
|
* @return Estimated number of entities iterated by the view.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] size_type size_hint() const {
|
||||||
|
return pools.empty() ? size_type{} : pools.front()->size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an iterator to the first entity that has the given
|
||||||
|
* components.
|
||||||
|
*
|
||||||
|
* The returned iterator points to the first entity that has the given
|
||||||
|
* components. If the view is empty, the returned iterator will be equal to
|
||||||
|
* `end()`.
|
||||||
|
*
|
||||||
|
* @return An iterator to the first entity that has the given components.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] iterator begin() const {
|
||||||
|
return pools.empty() ? iterator{} : iterator{pools, filter, pools[0]->begin()};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an iterator that is past the last entity that has the
|
||||||
|
* given components.
|
||||||
|
*
|
||||||
|
* The returned iterator points to the entity following the last entity that
|
||||||
|
* has the given components. Attempting to dereference the returned iterator
|
||||||
|
* results in undefined behavior.
|
||||||
|
*
|
||||||
|
* @return An iterator to the entity following the last entity that has the
|
||||||
|
* given components.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] iterator end() const {
|
||||||
|
return pools.empty() ? iterator{} : iterator{pools, filter, pools[0]->end()};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks if a view contains an entity.
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @return True if the view contains the given entity, false otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool contains(const entity_type entt) const {
|
||||||
|
return !pools.empty()
|
||||||
|
&& std::all_of(pools.cbegin(), pools.cend(), [entt](const auto *curr) { return curr->contains(entt); })
|
||||||
|
&& std::none_of(filter.cbegin(), filter.cend(), [entt](const auto *curr) { return curr && curr->contains(entt); });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Iterates entities and applies the given function object to them.
|
||||||
|
*
|
||||||
|
* The function object is invoked for each entity. It is provided only with
|
||||||
|
* the entity itself. To get the components, users can use the registry with
|
||||||
|
* which the view was built.<br/>
|
||||||
|
* The signature of the function should be equivalent to the following:
|
||||||
|
*
|
||||||
|
* @code{.cpp}
|
||||||
|
* void(const entity_type);
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* @tparam Func Type of the function object to invoke.
|
||||||
|
* @param func A valid function object.
|
||||||
|
*/
|
||||||
|
template<typename Func>
|
||||||
|
void each(Func func) const {
|
||||||
|
for(const auto entity: *this) {
|
||||||
|
func(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
container_type pools;
|
||||||
|
container_type filter;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace entt
|
||||||
|
|
||||||
|
#endif
|
567
src/entt/entity/snapshot.hpp
Normal file
567
src/entt/entity/snapshot.hpp
Normal file
@ -0,0 +1,567 @@
|
|||||||
|
#ifndef ENTT_ENTITY_SNAPSHOT_HPP
|
||||||
|
#define ENTT_ENTITY_SNAPSHOT_HPP
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <iterator>
|
||||||
|
#include <tuple>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
#include "../config/config.h"
|
||||||
|
#include "../container/dense_map.hpp"
|
||||||
|
#include "../core/type_traits.hpp"
|
||||||
|
#include "component.hpp"
|
||||||
|
#include "entity.hpp"
|
||||||
|
#include "fwd.hpp"
|
||||||
|
#include "view.hpp"
|
||||||
|
|
||||||
|
namespace entt {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Utility class to create snapshots from a registry.
|
||||||
|
*
|
||||||
|
* A _snapshot_ can be either a dump of the entire registry or a narrower
|
||||||
|
* selection of components of interest.<br/>
|
||||||
|
* This type can be used in both cases if provided with a correctly configured
|
||||||
|
* output archive.
|
||||||
|
*
|
||||||
|
* @tparam Registry Basic registry type.
|
||||||
|
*/
|
||||||
|
template<typename Registry>
|
||||||
|
class basic_snapshot {
|
||||||
|
using entity_traits = entt_traits<typename Registry::entity_type>;
|
||||||
|
|
||||||
|
template<typename Component, typename Archive, typename It>
|
||||||
|
void get(Archive &archive, std::size_t sz, It first, It last) const {
|
||||||
|
const auto view = reg->template view<const Component>();
|
||||||
|
archive(typename entity_traits::entity_type(sz));
|
||||||
|
|
||||||
|
while(first != last) {
|
||||||
|
const auto entt = *(first++);
|
||||||
|
|
||||||
|
if(reg->template all_of<Component>(entt)) {
|
||||||
|
std::apply(archive, std::tuple_cat(std::make_tuple(entt), view.get(entt)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... Component, typename Archive, typename It, std::size_t... Index>
|
||||||
|
void component(Archive &archive, It first, It last, std::index_sequence<Index...>) const {
|
||||||
|
std::array<std::size_t, sizeof...(Index)> size{};
|
||||||
|
auto begin = first;
|
||||||
|
|
||||||
|
while(begin != last) {
|
||||||
|
const auto entt = *(begin++);
|
||||||
|
((reg->template all_of<Component>(entt) ? ++size[Index] : 0u), ...);
|
||||||
|
}
|
||||||
|
|
||||||
|
(get<Component>(archive, size[Index], first, last), ...);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! Basic registry type. */
|
||||||
|
using registry_type = Registry;
|
||||||
|
/*! @brief Underlying entity identifier. */
|
||||||
|
using entity_type = typename registry_type::entity_type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs an instance that is bound to a given registry.
|
||||||
|
* @param source A valid reference to a registry.
|
||||||
|
*/
|
||||||
|
basic_snapshot(const registry_type &source) noexcept
|
||||||
|
: reg{&source} {}
|
||||||
|
|
||||||
|
/*! @brief Default move constructor. */
|
||||||
|
basic_snapshot(basic_snapshot &&) noexcept = default;
|
||||||
|
|
||||||
|
/*! @brief Default move assignment operator. @return This snapshot. */
|
||||||
|
basic_snapshot &operator=(basic_snapshot &&) noexcept = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Puts aside all the entities from the underlying registry.
|
||||||
|
*
|
||||||
|
* Entities are serialized along with their versions. Destroyed entities are
|
||||||
|
* taken in consideration as well by this function.
|
||||||
|
*
|
||||||
|
* @tparam Archive Type of output archive.
|
||||||
|
* @param archive A valid reference to an output archive.
|
||||||
|
* @return An object of this type to continue creating the snapshot.
|
||||||
|
*/
|
||||||
|
template<typename Archive>
|
||||||
|
const basic_snapshot &entities(Archive &archive) const {
|
||||||
|
const auto sz = reg->size();
|
||||||
|
|
||||||
|
archive(typename entity_traits::entity_type(sz + 1u));
|
||||||
|
archive(reg->released());
|
||||||
|
|
||||||
|
for(auto first = reg->data(), last = first + sz; first != last; ++first) {
|
||||||
|
archive(*first);
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Puts aside the given components.
|
||||||
|
*
|
||||||
|
* Each instance is serialized together with the entity to which it belongs.
|
||||||
|
* Entities are serialized along with their versions.
|
||||||
|
*
|
||||||
|
* @tparam Component Types of components to serialize.
|
||||||
|
* @tparam Archive Type of output archive.
|
||||||
|
* @param archive A valid reference to an output archive.
|
||||||
|
* @return An object of this type to continue creating the snapshot.
|
||||||
|
*/
|
||||||
|
template<typename... Component, typename Archive>
|
||||||
|
const basic_snapshot &component(Archive &archive) const {
|
||||||
|
if constexpr(sizeof...(Component) == 1u) {
|
||||||
|
const auto view = reg->template view<const Component...>();
|
||||||
|
(component<Component>(archive, view.rbegin(), view.rend()), ...);
|
||||||
|
return *this;
|
||||||
|
} else {
|
||||||
|
(component<Component>(archive), ...);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Puts aside the given components for the entities in a range.
|
||||||
|
*
|
||||||
|
* Each instance is serialized together with the entity to which it belongs.
|
||||||
|
* Entities are serialized along with their versions.
|
||||||
|
*
|
||||||
|
* @tparam Component Types of components to serialize.
|
||||||
|
* @tparam Archive Type of output archive.
|
||||||
|
* @tparam It Type of input iterator.
|
||||||
|
* @param archive A valid reference to an output archive.
|
||||||
|
* @param first An iterator to the first element of the range to serialize.
|
||||||
|
* @param last An iterator past the last element of the range to serialize.
|
||||||
|
* @return An object of this type to continue creating the snapshot.
|
||||||
|
*/
|
||||||
|
template<typename... Component, typename Archive, typename It>
|
||||||
|
const basic_snapshot &component(Archive &archive, It first, It last) const {
|
||||||
|
component<Component...>(archive, first, last, std::index_sequence_for<Component...>{});
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const registry_type *reg;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Utility class to restore a snapshot as a whole.
|
||||||
|
*
|
||||||
|
* A snapshot loader requires that the destination registry be empty and loads
|
||||||
|
* all the data at once while keeping intact the identifiers that the entities
|
||||||
|
* originally had.<br/>
|
||||||
|
* An example of use is the implementation of a save/restore utility.
|
||||||
|
*
|
||||||
|
* @tparam Registry Basic registry type.
|
||||||
|
*/
|
||||||
|
template<typename Registry>
|
||||||
|
class basic_snapshot_loader {
|
||||||
|
using entity_traits = entt_traits<typename Registry::entity_type>;
|
||||||
|
|
||||||
|
template<typename Component, typename Archive>
|
||||||
|
void assign(Archive &archive) const {
|
||||||
|
typename entity_traits::entity_type length{};
|
||||||
|
entity_type entt;
|
||||||
|
|
||||||
|
archive(length);
|
||||||
|
|
||||||
|
if constexpr(ignore_as_empty_v<Component>) {
|
||||||
|
while(length--) {
|
||||||
|
archive(entt);
|
||||||
|
const auto entity = reg->valid(entt) ? entt : reg->create(entt);
|
||||||
|
ENTT_ASSERT(entity == entt, "Entity not available for use");
|
||||||
|
reg->template emplace<Component>(entt);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Component instance;
|
||||||
|
|
||||||
|
while(length--) {
|
||||||
|
archive(entt, instance);
|
||||||
|
const auto entity = reg->valid(entt) ? entt : reg->create(entt);
|
||||||
|
ENTT_ASSERT(entity == entt, "Entity not available for use");
|
||||||
|
reg->template emplace<Component>(entt, std::move(instance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! Basic registry type. */
|
||||||
|
using registry_type = Registry;
|
||||||
|
/*! @brief Underlying entity identifier. */
|
||||||
|
using entity_type = typename registry_type::entity_type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs an instance that is bound to a given registry.
|
||||||
|
* @param source A valid reference to a registry.
|
||||||
|
*/
|
||||||
|
basic_snapshot_loader(registry_type &source) noexcept
|
||||||
|
: reg{&source} {
|
||||||
|
// restoring a snapshot as a whole requires a clean registry
|
||||||
|
ENTT_ASSERT(reg->empty(), "Registry must be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @brief Default move constructor. */
|
||||||
|
basic_snapshot_loader(basic_snapshot_loader &&) noexcept = default;
|
||||||
|
|
||||||
|
/*! @brief Default move assignment operator. @return This loader. */
|
||||||
|
basic_snapshot_loader &operator=(basic_snapshot_loader &&) noexcept = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Restores entities that were in use during serialization.
|
||||||
|
*
|
||||||
|
* This function restores the entities that were in use during serialization
|
||||||
|
* and gives them the versions they originally had.
|
||||||
|
*
|
||||||
|
* @tparam Archive Type of input archive.
|
||||||
|
* @param archive A valid reference to an input archive.
|
||||||
|
* @return A valid loader to continue restoring data.
|
||||||
|
*/
|
||||||
|
template<typename Archive>
|
||||||
|
const basic_snapshot_loader &entities(Archive &archive) const {
|
||||||
|
typename entity_traits::entity_type length{};
|
||||||
|
|
||||||
|
archive(length);
|
||||||
|
std::vector<entity_type> all(length);
|
||||||
|
|
||||||
|
for(std::size_t pos{}; pos < length; ++pos) {
|
||||||
|
archive(all[pos]);
|
||||||
|
}
|
||||||
|
|
||||||
|
reg->assign(++all.cbegin(), all.cend(), all[0u]);
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Restores components and assigns them to the right entities.
|
||||||
|
*
|
||||||
|
* The template parameter list must be exactly the same used during
|
||||||
|
* serialization. In the event that the entity to which the component is
|
||||||
|
* assigned doesn't exist yet, the loader will take care to create it with
|
||||||
|
* the version it originally had.
|
||||||
|
*
|
||||||
|
* @tparam Component Types of components to restore.
|
||||||
|
* @tparam Archive Type of input archive.
|
||||||
|
* @param archive A valid reference to an input archive.
|
||||||
|
* @return A valid loader to continue restoring data.
|
||||||
|
*/
|
||||||
|
template<typename... Component, typename Archive>
|
||||||
|
const basic_snapshot_loader &component(Archive &archive) const {
|
||||||
|
(assign<Component>(archive), ...);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroys those entities that have no components.
|
||||||
|
*
|
||||||
|
* In case all the entities were serialized but only part of the components
|
||||||
|
* was saved, it could happen that some of the entities have no components
|
||||||
|
* once restored.<br/>
|
||||||
|
* This functions helps to identify and destroy those entities.
|
||||||
|
*
|
||||||
|
* @return A valid loader to continue restoring data.
|
||||||
|
*/
|
||||||
|
const basic_snapshot_loader &orphans() const {
|
||||||
|
reg->each([this](const auto entt) {
|
||||||
|
if(reg->orphan(entt)) {
|
||||||
|
reg->release(entt);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
registry_type *reg;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Utility class for _continuous loading_.
|
||||||
|
*
|
||||||
|
* A _continuous loader_ is designed to load data from a source registry to a
|
||||||
|
* (possibly) non-empty destination. The loader can accommodate in a registry
|
||||||
|
* more than one snapshot in a sort of _continuous loading_ that updates the
|
||||||
|
* destination one step at a time.<br/>
|
||||||
|
* Identifiers that entities originally had are not transferred to the target.
|
||||||
|
* Instead, the loader maps remote identifiers to local ones while restoring a
|
||||||
|
* snapshot.<br/>
|
||||||
|
* An example of use is the implementation of a client-server applications with
|
||||||
|
* the requirement of transferring somehow parts of the representation side to
|
||||||
|
* side.
|
||||||
|
*
|
||||||
|
* @tparam Registry Basic registry type.
|
||||||
|
*/
|
||||||
|
template<typename Registry>
|
||||||
|
class basic_continuous_loader {
|
||||||
|
using entity_traits = entt_traits<typename Registry::entity_type>;
|
||||||
|
|
||||||
|
void destroy(typename Registry::entity_type entt) {
|
||||||
|
if(const auto it = remloc.find(entt); it == remloc.cend()) {
|
||||||
|
const auto local = reg->create();
|
||||||
|
remloc.emplace(entt, std::make_pair(local, true));
|
||||||
|
reg->destroy(local);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void restore(typename Registry::entity_type entt) {
|
||||||
|
const auto it = remloc.find(entt);
|
||||||
|
|
||||||
|
if(it == remloc.cend()) {
|
||||||
|
const auto local = reg->create();
|
||||||
|
remloc.emplace(entt, std::make_pair(local, true));
|
||||||
|
} else {
|
||||||
|
if(!reg->valid(remloc[entt].first)) {
|
||||||
|
remloc[entt].first = reg->create();
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the dirty flag
|
||||||
|
remloc[entt].second = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Container>
|
||||||
|
auto update(int, Container &container) -> decltype(typename Container::mapped_type{}, void()) {
|
||||||
|
// map like container
|
||||||
|
Container other;
|
||||||
|
|
||||||
|
for(auto &&pair: container) {
|
||||||
|
using first_type = std::remove_const_t<typename std::decay_t<decltype(pair)>::first_type>;
|
||||||
|
using second_type = typename std::decay_t<decltype(pair)>::second_type;
|
||||||
|
|
||||||
|
if constexpr(std::is_same_v<first_type, entity_type> && std::is_same_v<second_type, entity_type>) {
|
||||||
|
other.emplace(map(pair.first), map(pair.second));
|
||||||
|
} else if constexpr(std::is_same_v<first_type, entity_type>) {
|
||||||
|
other.emplace(map(pair.first), std::move(pair.second));
|
||||||
|
} else {
|
||||||
|
static_assert(std::is_same_v<second_type, entity_type>, "Neither the key nor the value are of entity type");
|
||||||
|
other.emplace(std::move(pair.first), map(pair.second));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using std::swap;
|
||||||
|
swap(container, other);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Container>
|
||||||
|
auto update(char, Container &container) -> decltype(typename Container::value_type{}, void()) {
|
||||||
|
// vector like container
|
||||||
|
static_assert(std::is_same_v<typename Container::value_type, entity_type>, "Invalid value type");
|
||||||
|
|
||||||
|
for(auto &&entt: container) {
|
||||||
|
entt = map(entt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Component, typename Other, typename Member>
|
||||||
|
void update([[maybe_unused]] Component &instance, [[maybe_unused]] Member Other::*member) {
|
||||||
|
if constexpr(!std::is_same_v<Component, Other>) {
|
||||||
|
return;
|
||||||
|
} else if constexpr(std::is_same_v<Member, entity_type>) {
|
||||||
|
instance.*member = map(instance.*member);
|
||||||
|
} else {
|
||||||
|
// maybe a container? let's try...
|
||||||
|
update(0, instance.*member);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Component>
|
||||||
|
void remove_if_exists() {
|
||||||
|
for(auto &&ref: remloc) {
|
||||||
|
const auto local = ref.second.first;
|
||||||
|
|
||||||
|
if(reg->valid(local)) {
|
||||||
|
reg->template remove<Component>(local);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Component, typename Archive, typename... Other, typename... Member>
|
||||||
|
void assign(Archive &archive, [[maybe_unused]] Member Other::*...member) {
|
||||||
|
typename entity_traits::entity_type length{};
|
||||||
|
entity_type entt;
|
||||||
|
|
||||||
|
archive(length);
|
||||||
|
|
||||||
|
if constexpr(ignore_as_empty_v<Component>) {
|
||||||
|
while(length--) {
|
||||||
|
archive(entt);
|
||||||
|
restore(entt);
|
||||||
|
reg->template emplace_or_replace<Component>(map(entt));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Component instance;
|
||||||
|
|
||||||
|
while(length--) {
|
||||||
|
archive(entt, instance);
|
||||||
|
(update(instance, member), ...);
|
||||||
|
restore(entt);
|
||||||
|
reg->template emplace_or_replace<Component>(map(entt), std::move(instance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! Basic registry type. */
|
||||||
|
using registry_type = Registry;
|
||||||
|
/*! @brief Underlying entity identifier. */
|
||||||
|
using entity_type = typename registry_type::entity_type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs an instance that is bound to a given registry.
|
||||||
|
* @param source A valid reference to a registry.
|
||||||
|
*/
|
||||||
|
basic_continuous_loader(registry_type &source) noexcept
|
||||||
|
: reg{&source} {}
|
||||||
|
|
||||||
|
/*! @brief Default move constructor. */
|
||||||
|
basic_continuous_loader(basic_continuous_loader &&) = default;
|
||||||
|
|
||||||
|
/*! @brief Default move assignment operator. @return This loader. */
|
||||||
|
basic_continuous_loader &operator=(basic_continuous_loader &&) = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Restores entities that were in use during serialization.
|
||||||
|
*
|
||||||
|
* This function restores the entities that were in use during serialization
|
||||||
|
* and creates local counterparts for them if required.
|
||||||
|
*
|
||||||
|
* @tparam Archive Type of input archive.
|
||||||
|
* @param archive A valid reference to an input archive.
|
||||||
|
* @return A non-const reference to this loader.
|
||||||
|
*/
|
||||||
|
template<typename Archive>
|
||||||
|
basic_continuous_loader &entities(Archive &archive) {
|
||||||
|
typename entity_traits::entity_type length{};
|
||||||
|
entity_type entt{};
|
||||||
|
|
||||||
|
archive(length);
|
||||||
|
// discards the head of the list of destroyed entities
|
||||||
|
archive(entt);
|
||||||
|
|
||||||
|
for(std::size_t pos{}, last = length - 1u; pos < last; ++pos) {
|
||||||
|
archive(entt);
|
||||||
|
|
||||||
|
if(const auto entity = entity_traits::to_entity(entt); entity == pos) {
|
||||||
|
restore(entt);
|
||||||
|
} else {
|
||||||
|
destroy(entt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Restores components and assigns them to the right entities.
|
||||||
|
*
|
||||||
|
* The template parameter list must be exactly the same used during
|
||||||
|
* serialization. In the event that the entity to which the component is
|
||||||
|
* assigned doesn't exist yet, the loader will take care to create a local
|
||||||
|
* counterpart for it.<br/>
|
||||||
|
* Members can be either data members of type entity_type or containers of
|
||||||
|
* entities. In both cases, the loader will visit them and update the
|
||||||
|
* entities by replacing each one with its local counterpart.
|
||||||
|
*
|
||||||
|
* @tparam Component Type of component to restore.
|
||||||
|
* @tparam Archive Type of input archive.
|
||||||
|
* @tparam Other Types of components to update with local counterparts.
|
||||||
|
* @tparam Member Types of members to update with their local counterparts.
|
||||||
|
* @param archive A valid reference to an input archive.
|
||||||
|
* @param member Members to update with their local counterparts.
|
||||||
|
* @return A non-const reference to this loader.
|
||||||
|
*/
|
||||||
|
template<typename... Component, typename Archive, typename... Other, typename... Member>
|
||||||
|
basic_continuous_loader &component(Archive &archive, Member Other::*...member) {
|
||||||
|
(remove_if_exists<Component>(), ...);
|
||||||
|
(assign<Component>(archive, member...), ...);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helps to purge entities that no longer have a conterpart.
|
||||||
|
*
|
||||||
|
* Users should invoke this member function after restoring each snapshot,
|
||||||
|
* unless they know exactly what they are doing.
|
||||||
|
*
|
||||||
|
* @return A non-const reference to this loader.
|
||||||
|
*/
|
||||||
|
basic_continuous_loader &shrink() {
|
||||||
|
auto it = remloc.begin();
|
||||||
|
|
||||||
|
while(it != remloc.cend()) {
|
||||||
|
const auto local = it->second.first;
|
||||||
|
bool &dirty = it->second.second;
|
||||||
|
|
||||||
|
if(dirty) {
|
||||||
|
dirty = false;
|
||||||
|
++it;
|
||||||
|
} else {
|
||||||
|
if(reg->valid(local)) {
|
||||||
|
reg->destroy(local);
|
||||||
|
}
|
||||||
|
|
||||||
|
it = remloc.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroys those entities that have no components.
|
||||||
|
*
|
||||||
|
* In case all the entities were serialized but only part of the components
|
||||||
|
* was saved, it could happen that some of the entities have no components
|
||||||
|
* once restored.<br/>
|
||||||
|
* This functions helps to identify and destroy those entities.
|
||||||
|
*
|
||||||
|
* @return A non-const reference to this loader.
|
||||||
|
*/
|
||||||
|
basic_continuous_loader &orphans() {
|
||||||
|
reg->each([this](const auto entt) {
|
||||||
|
if(reg->orphan(entt)) {
|
||||||
|
reg->release(entt);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Tests if a loader knows about a given entity.
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @return True if `entity` is managed by the loader, false otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool contains(entity_type entt) const noexcept {
|
||||||
|
return (remloc.find(entt) != remloc.cend());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the identifier to which an entity refers.
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @return The local identifier if any, the null entity otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] entity_type map(entity_type entt) const noexcept {
|
||||||
|
const auto it = remloc.find(entt);
|
||||||
|
entity_type other = null;
|
||||||
|
|
||||||
|
if(it != remloc.cend()) {
|
||||||
|
other = it->second.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
return other;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
dense_map<entity_type, std::pair<entity_type, bool>> remloc;
|
||||||
|
registry_type *reg;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace entt
|
||||||
|
|
||||||
|
#endif
|
976
src/entt/entity/sparse_set.hpp
Normal file
976
src/entt/entity/sparse_set.hpp
Normal file
@ -0,0 +1,976 @@
|
|||||||
|
#ifndef ENTT_ENTITY_SPARSE_SET_HPP
|
||||||
|
#define ENTT_ENTITY_SPARSE_SET_HPP
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <iterator>
|
||||||
|
#include <memory>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
#include "../config/config.h"
|
||||||
|
#include "../core/algorithm.hpp"
|
||||||
|
#include "../core/any.hpp"
|
||||||
|
#include "../core/memory.hpp"
|
||||||
|
#include "../core/type_info.hpp"
|
||||||
|
#include "entity.hpp"
|
||||||
|
#include "fwd.hpp"
|
||||||
|
|
||||||
|
namespace entt {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cond TURN_OFF_DOXYGEN
|
||||||
|
* Internal details not to be documented.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<typename Container>
|
||||||
|
struct sparse_set_iterator final {
|
||||||
|
using value_type = typename Container::value_type;
|
||||||
|
using pointer = typename Container::const_pointer;
|
||||||
|
using reference = typename Container::const_reference;
|
||||||
|
using difference_type = typename Container::difference_type;
|
||||||
|
using iterator_category = std::random_access_iterator_tag;
|
||||||
|
|
||||||
|
constexpr sparse_set_iterator() noexcept
|
||||||
|
: packed{},
|
||||||
|
offset{} {}
|
||||||
|
|
||||||
|
constexpr sparse_set_iterator(const Container &ref, const difference_type idx) noexcept
|
||||||
|
: packed{std::addressof(ref)},
|
||||||
|
offset{idx} {}
|
||||||
|
|
||||||
|
constexpr sparse_set_iterator &operator++() noexcept {
|
||||||
|
return --offset, *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr sparse_set_iterator operator++(int) noexcept {
|
||||||
|
sparse_set_iterator orig = *this;
|
||||||
|
return ++(*this), orig;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr sparse_set_iterator &operator--() noexcept {
|
||||||
|
return ++offset, *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr sparse_set_iterator operator--(int) noexcept {
|
||||||
|
sparse_set_iterator orig = *this;
|
||||||
|
return operator--(), orig;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr sparse_set_iterator &operator+=(const difference_type value) noexcept {
|
||||||
|
offset -= value;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr sparse_set_iterator operator+(const difference_type value) const noexcept {
|
||||||
|
sparse_set_iterator copy = *this;
|
||||||
|
return (copy += value);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr sparse_set_iterator &operator-=(const difference_type value) noexcept {
|
||||||
|
return (*this += -value);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr sparse_set_iterator operator-(const difference_type value) const noexcept {
|
||||||
|
return (*this + -value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept {
|
||||||
|
return packed->data()[index() - value];
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr pointer operator->() const noexcept {
|
||||||
|
return packed->data() + index();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr reference operator*() const noexcept {
|
||||||
|
return *operator->();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr difference_type index() const noexcept {
|
||||||
|
return offset - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const Container *packed;
|
||||||
|
difference_type offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Type, typename Other>
|
||||||
|
[[nodiscard]] constexpr std::ptrdiff_t operator-(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) noexcept {
|
||||||
|
return rhs.index() - lhs.index();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Type, typename Other>
|
||||||
|
[[nodiscard]] constexpr bool operator==(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) noexcept {
|
||||||
|
return lhs.index() == rhs.index();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Type, typename Other>
|
||||||
|
[[nodiscard]] constexpr bool operator!=(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) noexcept {
|
||||||
|
return !(lhs == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Type, typename Other>
|
||||||
|
[[nodiscard]] constexpr bool operator<(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) noexcept {
|
||||||
|
return lhs.index() > rhs.index();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Type, typename Other>
|
||||||
|
[[nodiscard]] constexpr bool operator>(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) noexcept {
|
||||||
|
return lhs.index() < rhs.index();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Type, typename Other>
|
||||||
|
[[nodiscard]] constexpr bool operator<=(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) noexcept {
|
||||||
|
return !(lhs > rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Type, typename Other>
|
||||||
|
[[nodiscard]] constexpr bool operator>=(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) noexcept {
|
||||||
|
return !(lhs < rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal details not to be documented.
|
||||||
|
* @endcond
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! @brief Sparse set deletion policy. */
|
||||||
|
enum class deletion_policy : std::uint8_t {
|
||||||
|
/*! @brief Swap-and-pop deletion policy. */
|
||||||
|
swap_and_pop = 0u,
|
||||||
|
/*! @brief In-place deletion policy. */
|
||||||
|
in_place = 1u
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Basic sparse set implementation.
|
||||||
|
*
|
||||||
|
* Sparse set or packed array or whatever is the name users give it.<br/>
|
||||||
|
* Two arrays: an _external_ one and an _internal_ one; a _sparse_ one and a
|
||||||
|
* _packed_ one; one used for direct access through contiguous memory, the other
|
||||||
|
* one used to get the data through an extra level of indirection.<br/>
|
||||||
|
* This is largely used by the registry to offer users the fastest access ever
|
||||||
|
* to the components. Views and groups in general are almost entirely designed
|
||||||
|
* around sparse sets.
|
||||||
|
*
|
||||||
|
* This type of data structure is widely documented in the literature and on the
|
||||||
|
* web. This is nothing more than a customized implementation suitable for the
|
||||||
|
* purpose of the framework.
|
||||||
|
*
|
||||||
|
* @note
|
||||||
|
* Internal data structures arrange elements to maximize performance. There are
|
||||||
|
* no guarantees that entities are returned in the insertion order when iterate
|
||||||
|
* a sparse set. Do not make assumption on the order in any case.
|
||||||
|
*
|
||||||
|
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||||
|
* @tparam Allocator Type of allocator used to manage memory and elements.
|
||||||
|
*/
|
||||||
|
template<typename Entity, typename Allocator>
|
||||||
|
class basic_sparse_set {
|
||||||
|
using alloc_traits = std::allocator_traits<Allocator>;
|
||||||
|
static_assert(std::is_same_v<typename alloc_traits::value_type, Entity>, "Invalid value type");
|
||||||
|
using sparse_container_type = std::vector<typename alloc_traits::pointer, typename alloc_traits::template rebind_alloc<typename alloc_traits::pointer>>;
|
||||||
|
using packed_container_type = std::vector<Entity, Allocator>;
|
||||||
|
using entity_traits = entt_traits<Entity>;
|
||||||
|
|
||||||
|
[[nodiscard]] auto sparse_ptr(const Entity entt) const {
|
||||||
|
const auto pos = static_cast<size_type>(entity_traits::to_entity(entt));
|
||||||
|
const auto page = pos / entity_traits::page_size;
|
||||||
|
return (page < sparse.size() && sparse[page]) ? (sparse[page] + fast_mod(pos, entity_traits::page_size)) : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto &sparse_ref(const Entity entt) const {
|
||||||
|
ENTT_ASSERT(sparse_ptr(entt), "Invalid element");
|
||||||
|
const auto pos = static_cast<size_type>(entity_traits::to_entity(entt));
|
||||||
|
return sparse[pos / entity_traits::page_size][fast_mod(pos, entity_traits::page_size)];
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto &assure_at_least(const Entity entt) {
|
||||||
|
const auto pos = static_cast<size_type>(entity_traits::to_entity(entt));
|
||||||
|
const auto page = pos / entity_traits::page_size;
|
||||||
|
|
||||||
|
if(!(page < sparse.size())) {
|
||||||
|
sparse.resize(page + 1u, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!sparse[page]) {
|
||||||
|
auto page_allocator{packed.get_allocator()};
|
||||||
|
sparse[page] = alloc_traits::allocate(page_allocator, entity_traits::page_size);
|
||||||
|
std::uninitialized_fill(sparse[page], sparse[page] + entity_traits::page_size, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto &elem = sparse[page][fast_mod(pos, entity_traits::page_size)];
|
||||||
|
ENTT_ASSERT(elem == null, "Slot not available");
|
||||||
|
return elem;
|
||||||
|
}
|
||||||
|
|
||||||
|
void release_sparse_pages() {
|
||||||
|
auto page_allocator{packed.get_allocator()};
|
||||||
|
|
||||||
|
for(auto &&page: sparse) {
|
||||||
|
if(page != nullptr) {
|
||||||
|
std::destroy(page, page + entity_traits::page_size);
|
||||||
|
alloc_traits::deallocate(page_allocator, page, entity_traits::page_size);
|
||||||
|
page = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual const void *get_at(const std::size_t) const {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void swap_at(const std::size_t, const std::size_t) {}
|
||||||
|
virtual void move_element(const std::size_t, const std::size_t) {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/*! @brief Random access iterator type. */
|
||||||
|
using basic_iterator = internal::sparse_set_iterator<packed_container_type>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Erases an entity from a sparse set.
|
||||||
|
* @param it An iterator to the element to pop.
|
||||||
|
*/
|
||||||
|
void swap_and_pop(const basic_iterator it) {
|
||||||
|
ENTT_ASSERT(mode == deletion_policy::swap_and_pop, "Deletion policy mismatched");
|
||||||
|
auto &self = sparse_ref(*it);
|
||||||
|
const auto entt = entity_traits::to_entity(self);
|
||||||
|
sparse_ref(packed.back()) = entity_traits::combine(entt, entity_traits::to_integral(packed.back()));
|
||||||
|
packed[static_cast<size_type>(entt)] = packed.back();
|
||||||
|
// unnecessary but it helps to detect nasty bugs
|
||||||
|
ENTT_ASSERT((packed.back() = null, true), "");
|
||||||
|
// lazy self-assignment guard
|
||||||
|
self = null;
|
||||||
|
packed.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Erases an entity from a sparse set.
|
||||||
|
* @param it An iterator to the element to pop.
|
||||||
|
*/
|
||||||
|
void in_place_pop(const basic_iterator it) {
|
||||||
|
ENTT_ASSERT(mode == deletion_policy::in_place, "Deletion policy mismatched");
|
||||||
|
const auto entt = entity_traits::to_entity(std::exchange(sparse_ref(*it), null));
|
||||||
|
packed[static_cast<size_type>(entt)] = std::exchange(free_list, entity_traits::combine(entt, entity_traits::reserved));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* @brief Erases entities from a sparse set.
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
virtual void pop(basic_iterator first, basic_iterator last) {
|
||||||
|
if(mode == deletion_policy::swap_and_pop) {
|
||||||
|
for(; first != last; ++first) {
|
||||||
|
swap_and_pop(first);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for(; first != last; ++first) {
|
||||||
|
in_place_pop(first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Assigns an entity to a sparse set.
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @param force_back Force back insertion.
|
||||||
|
* @return Iterator pointing to the emplaced element.
|
||||||
|
*/
|
||||||
|
virtual basic_iterator try_emplace(const Entity entt, const bool force_back, const void * = nullptr) {
|
||||||
|
ENTT_ASSERT(!contains(entt), "Set already contains entity");
|
||||||
|
|
||||||
|
if(auto &elem = assure_at_least(entt); free_list == null || force_back) {
|
||||||
|
packed.push_back(entt);
|
||||||
|
elem = entity_traits::combine(static_cast<typename entity_traits::entity_type>(packed.size() - 1u), entity_traits::to_integral(entt));
|
||||||
|
return begin();
|
||||||
|
} else {
|
||||||
|
const auto pos = static_cast<size_type>(entity_traits::to_entity(free_list));
|
||||||
|
elem = entity_traits::combine(entity_traits::to_integral(free_list), entity_traits::to_integral(entt));
|
||||||
|
free_list = std::exchange(packed[pos], entt);
|
||||||
|
return --(end() - pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! @brief Allocator type. */
|
||||||
|
using allocator_type = Allocator;
|
||||||
|
/*! @brief Underlying entity identifier. */
|
||||||
|
using entity_type = typename entity_traits::value_type;
|
||||||
|
/*! @brief Underlying version type. */
|
||||||
|
using version_type = typename entity_traits::version_type;
|
||||||
|
/*! @brief Unsigned integer type. */
|
||||||
|
using size_type = std::size_t;
|
||||||
|
/*! @brief Pointer type to contained entities. */
|
||||||
|
using pointer = typename packed_container_type::const_pointer;
|
||||||
|
/*! @brief Random access iterator type. */
|
||||||
|
using iterator = basic_iterator;
|
||||||
|
/*! @brief Constant random access iterator type. */
|
||||||
|
using const_iterator = iterator;
|
||||||
|
/*! @brief Reverse iterator type. */
|
||||||
|
using reverse_iterator = std::reverse_iterator<iterator>;
|
||||||
|
/*! @brief Constant reverse iterator type. */
|
||||||
|
using const_reverse_iterator = reverse_iterator;
|
||||||
|
|
||||||
|
/*! @brief Default constructor. */
|
||||||
|
basic_sparse_set()
|
||||||
|
: basic_sparse_set{type_id<void>()} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs an empty container with a given allocator.
|
||||||
|
* @param allocator The allocator to use.
|
||||||
|
*/
|
||||||
|
explicit basic_sparse_set(const allocator_type &allocator)
|
||||||
|
: basic_sparse_set{type_id<void>(), deletion_policy::swap_and_pop, allocator} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs an empty container with the given policy and allocator.
|
||||||
|
* @param pol Type of deletion policy.
|
||||||
|
* @param allocator The allocator to use (possibly default-constructed).
|
||||||
|
*/
|
||||||
|
explicit basic_sparse_set(deletion_policy pol, const allocator_type &allocator = {})
|
||||||
|
: basic_sparse_set{type_id<void>(), pol, allocator} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs an empty container with the given value type, policy
|
||||||
|
* and allocator.
|
||||||
|
* @param value Returned value type, if any.
|
||||||
|
* @param pol Type of deletion policy.
|
||||||
|
* @param allocator The allocator to use (possibly default-constructed).
|
||||||
|
*/
|
||||||
|
explicit basic_sparse_set(const type_info &value, deletion_policy pol = deletion_policy::swap_and_pop, const allocator_type &allocator = {})
|
||||||
|
: sparse{allocator},
|
||||||
|
packed{allocator},
|
||||||
|
info{&value},
|
||||||
|
free_list{tombstone},
|
||||||
|
mode{pol} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move constructor.
|
||||||
|
* @param other The instance to move from.
|
||||||
|
*/
|
||||||
|
basic_sparse_set(basic_sparse_set &&other) noexcept
|
||||||
|
: sparse{std::move(other.sparse)},
|
||||||
|
packed{std::move(other.packed)},
|
||||||
|
info{other.info},
|
||||||
|
free_list{std::exchange(other.free_list, tombstone)},
|
||||||
|
mode{other.mode} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Allocator-extended move constructor.
|
||||||
|
* @param other The instance to move from.
|
||||||
|
* @param allocator The allocator to use.
|
||||||
|
*/
|
||||||
|
basic_sparse_set(basic_sparse_set &&other, const allocator_type &allocator) noexcept
|
||||||
|
: sparse{std::move(other.sparse), allocator},
|
||||||
|
packed{std::move(other.packed), allocator},
|
||||||
|
info{other.info},
|
||||||
|
free_list{std::exchange(other.free_list, tombstone)},
|
||||||
|
mode{other.mode} {
|
||||||
|
ENTT_ASSERT(alloc_traits::is_always_equal::value || packed.get_allocator() == other.packed.get_allocator(), "Copying a sparse set is not allowed");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @brief Default destructor. */
|
||||||
|
virtual ~basic_sparse_set() {
|
||||||
|
release_sparse_pages();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move assignment operator.
|
||||||
|
* @param other The instance to move from.
|
||||||
|
* @return This sparse set.
|
||||||
|
*/
|
||||||
|
basic_sparse_set &operator=(basic_sparse_set &&other) noexcept {
|
||||||
|
ENTT_ASSERT(alloc_traits::is_always_equal::value || packed.get_allocator() == other.packed.get_allocator(), "Copying a sparse set is not allowed");
|
||||||
|
|
||||||
|
release_sparse_pages();
|
||||||
|
sparse = std::move(other.sparse);
|
||||||
|
packed = std::move(other.packed);
|
||||||
|
info = other.info;
|
||||||
|
free_list = std::exchange(other.free_list, tombstone);
|
||||||
|
mode = other.mode;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Exchanges the contents with those of a given sparse set.
|
||||||
|
* @param other Sparse set to exchange the content with.
|
||||||
|
*/
|
||||||
|
void swap(basic_sparse_set &other) {
|
||||||
|
using std::swap;
|
||||||
|
swap(sparse, other.sparse);
|
||||||
|
swap(packed, other.packed);
|
||||||
|
swap(info, other.info);
|
||||||
|
swap(free_list, other.free_list);
|
||||||
|
swap(mode, other.mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the associated allocator.
|
||||||
|
* @return The associated allocator.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
|
||||||
|
return packed.get_allocator();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the deletion policy of a sparse set.
|
||||||
|
* @return The deletion policy of the sparse set.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] deletion_policy policy() const noexcept {
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Increases the capacity of a sparse set.
|
||||||
|
*
|
||||||
|
* If the new capacity is greater than the current capacity, new storage is
|
||||||
|
* allocated, otherwise the method does nothing.
|
||||||
|
*
|
||||||
|
* @param cap Desired capacity.
|
||||||
|
*/
|
||||||
|
virtual void reserve(const size_type cap) {
|
||||||
|
packed.reserve(cap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the number of elements that a sparse set has currently
|
||||||
|
* allocated space for.
|
||||||
|
* @return Capacity of the sparse set.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual size_type capacity() const noexcept {
|
||||||
|
return packed.capacity();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @brief Requests the removal of unused capacity. */
|
||||||
|
virtual void shrink_to_fit() {
|
||||||
|
packed.shrink_to_fit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the extent of a sparse set.
|
||||||
|
*
|
||||||
|
* The extent of a sparse set is also the size of the internal sparse array.
|
||||||
|
* There is no guarantee that the internal packed array has the same size.
|
||||||
|
* Usually the size of the internal sparse array is equal or greater than
|
||||||
|
* the one of the internal packed array.
|
||||||
|
*
|
||||||
|
* @return Extent of the sparse set.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] size_type extent() const noexcept {
|
||||||
|
return sparse.size() * entity_traits::page_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the number of elements in a sparse set.
|
||||||
|
*
|
||||||
|
* The number of elements is also the size of the internal packed array.
|
||||||
|
* There is no guarantee that the internal sparse array has the same size.
|
||||||
|
* Usually the size of the internal sparse array is equal or greater than
|
||||||
|
* the one of the internal packed array.
|
||||||
|
*
|
||||||
|
* @return Number of elements.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] size_type size() const noexcept {
|
||||||
|
return packed.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks whether a sparse set is empty.
|
||||||
|
* @return True if the sparse set is empty, false otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool empty() const noexcept {
|
||||||
|
return packed.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Direct access to the internal packed array.
|
||||||
|
* @return A pointer to the internal packed array.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] pointer data() const noexcept {
|
||||||
|
return packed.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an iterator to the beginning.
|
||||||
|
*
|
||||||
|
* The returned iterator points to the first entity of the internal packed
|
||||||
|
* array. If the sparse set is empty, the returned iterator will be equal to
|
||||||
|
* `end()`.
|
||||||
|
*
|
||||||
|
* @return An iterator to the first entity of the sparse set.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] const_iterator begin() const noexcept {
|
||||||
|
const auto pos = static_cast<typename iterator::difference_type>(packed.size());
|
||||||
|
return iterator{packed, pos};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc begin */
|
||||||
|
[[nodiscard]] const_iterator cbegin() const noexcept {
|
||||||
|
return begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an iterator to the end.
|
||||||
|
*
|
||||||
|
* The returned iterator points to the element following the last entity in
|
||||||
|
* a sparse set. Attempting to dereference the returned iterator results in
|
||||||
|
* undefined behavior.
|
||||||
|
*
|
||||||
|
* @return An iterator to the element following the last entity of a sparse
|
||||||
|
* set.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] iterator end() const noexcept {
|
||||||
|
return iterator{packed, {}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc end */
|
||||||
|
[[nodiscard]] const_iterator cend() const noexcept {
|
||||||
|
return end();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a reverse iterator to the beginning.
|
||||||
|
*
|
||||||
|
* The returned iterator points to the first entity of the reversed internal
|
||||||
|
* packed array. If the sparse set is empty, the returned iterator will be
|
||||||
|
* equal to `rend()`.
|
||||||
|
*
|
||||||
|
* @return An iterator to the first entity of the reversed internal packed
|
||||||
|
* array.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] const_reverse_iterator rbegin() const noexcept {
|
||||||
|
return std::make_reverse_iterator(end());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc rbegin */
|
||||||
|
[[nodiscard]] const_reverse_iterator crbegin() const noexcept {
|
||||||
|
return rbegin();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a reverse iterator to the end.
|
||||||
|
*
|
||||||
|
* The returned iterator points to the element following the last entity in
|
||||||
|
* the reversed sparse set. Attempting to dereference the returned iterator
|
||||||
|
* results in undefined behavior.
|
||||||
|
*
|
||||||
|
* @return An iterator to the element following the last entity of the
|
||||||
|
* reversed sparse set.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] reverse_iterator rend() const noexcept {
|
||||||
|
return std::make_reverse_iterator(begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc rend */
|
||||||
|
[[nodiscard]] const_reverse_iterator crend() const noexcept {
|
||||||
|
return rend();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Finds an entity.
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @return An iterator to the given entity if it's found, past the end
|
||||||
|
* iterator otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] iterator find(const entity_type entt) const noexcept {
|
||||||
|
return contains(entt) ? --(end() - index(entt)) : end();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks if a sparse set contains an entity.
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @return True if the sparse set contains the entity, false otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool contains(const entity_type entt) const noexcept {
|
||||||
|
const auto elem = sparse_ptr(entt);
|
||||||
|
constexpr auto cap = entity_traits::to_entity(null);
|
||||||
|
// testing versions permits to avoid accessing the packed array
|
||||||
|
return elem && (((~cap & entity_traits::to_integral(entt)) ^ entity_traits::to_integral(*elem)) < cap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the contained version for an identifier.
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @return The version for the given identifier if present, the tombstone
|
||||||
|
* version otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] version_type current(const entity_type entt) const noexcept {
|
||||||
|
const auto elem = sparse_ptr(entt);
|
||||||
|
constexpr auto fallback = entity_traits::to_version(tombstone);
|
||||||
|
return elem ? entity_traits::to_version(*elem) : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the position of an entity in a sparse set.
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* Attempting to get the position of an entity that doesn't belong to the
|
||||||
|
* sparse set results in undefined behavior.
|
||||||
|
*
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @return The position of the entity in the sparse set.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] size_type index(const entity_type entt) const noexcept {
|
||||||
|
ENTT_ASSERT(contains(entt), "Set does not contain entity");
|
||||||
|
return static_cast<size_type>(entity_traits::to_entity(sparse_ref(entt)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the entity at specified location, with bounds checking.
|
||||||
|
* @param pos The position for which to return the entity.
|
||||||
|
* @return The entity at specified location if any, a null entity otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] entity_type at(const size_type pos) const noexcept {
|
||||||
|
return pos < packed.size() ? packed[pos] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the entity at specified location, without bounds checking.
|
||||||
|
* @param pos The position for which to return the entity.
|
||||||
|
* @return The entity at specified location.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] entity_type operator[](const size_type pos) const noexcept {
|
||||||
|
ENTT_ASSERT(pos < packed.size(), "Position is out of bounds");
|
||||||
|
return packed[pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the element assigned to an entity, if any.
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* Attempting to use an entity that doesn't belong to the sparse set results
|
||||||
|
* in undefined behavior.
|
||||||
|
*
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @return An opaque pointer to the element assigned to the entity, if any.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] const void *get(const entity_type entt) const noexcept {
|
||||||
|
return get_at(index(entt));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc get */
|
||||||
|
[[nodiscard]] void *get(const entity_type entt) noexcept {
|
||||||
|
return const_cast<void *>(std::as_const(*this).get(entt));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Assigns an entity to a sparse set.
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* Attempting to assign an entity that already belongs to the sparse set
|
||||||
|
* results in undefined behavior.
|
||||||
|
*
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @param value Optional opaque value to forward to mixins, if any.
|
||||||
|
* @return Iterator pointing to the emplaced element in case of success, the
|
||||||
|
* `end()` iterator otherwise.
|
||||||
|
*/
|
||||||
|
iterator emplace(const entity_type entt, const void *value = nullptr) {
|
||||||
|
return try_emplace(entt, false, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Bump the version number of an entity.
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* Attempting to bump the version of an entity that doesn't belong to the
|
||||||
|
* sparse set results in undefined behavior.
|
||||||
|
*
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
*/
|
||||||
|
void bump(const entity_type entt) {
|
||||||
|
auto &entity = sparse_ref(entt);
|
||||||
|
ENTT_ASSERT(entt != tombstone && entity != null, "Cannot set the required version");
|
||||||
|
entity = entity_traits::combine(entity_traits::to_integral(entity), entity_traits::to_integral(entt));
|
||||||
|
packed[static_cast<size_type>(entity_traits::to_entity(entity))] = entt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Assigns one or more entities to a sparse set.
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* Attempting to assign an entity that already belongs to the sparse set
|
||||||
|
* results in undefined behavior.
|
||||||
|
*
|
||||||
|
* @tparam It Type of input iterator.
|
||||||
|
* @param first An iterator to the first element of the range of entities.
|
||||||
|
* @param last An iterator past the last element of the range of entities.
|
||||||
|
* @return Iterator pointing to the first element inserted in case of
|
||||||
|
* success, the `end()` iterator otherwise.
|
||||||
|
*/
|
||||||
|
template<typename It>
|
||||||
|
iterator insert(It first, It last) {
|
||||||
|
for(auto it = first; it != last; ++it) {
|
||||||
|
try_emplace(*it, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return first == last ? end() : find(*first);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Erases an entity from a sparse set.
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* Attempting to erase an entity that doesn't belong to the sparse set
|
||||||
|
* results in undefined behavior.
|
||||||
|
*
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
*/
|
||||||
|
void erase(const entity_type entt) {
|
||||||
|
const auto it = --(end() - index(entt));
|
||||||
|
pop(it, it + 1u);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Erases entities from a set.
|
||||||
|
*
|
||||||
|
* @sa erase
|
||||||
|
*
|
||||||
|
* @tparam It Type of input iterator.
|
||||||
|
* @param first An iterator to the first element of the range of entities.
|
||||||
|
* @param last An iterator past the last element of the range of entities.
|
||||||
|
*/
|
||||||
|
template<typename It>
|
||||||
|
void erase(It first, It last) {
|
||||||
|
if constexpr(std::is_same_v<It, basic_iterator>) {
|
||||||
|
pop(first, last);
|
||||||
|
} else {
|
||||||
|
for(; first != last; ++first) {
|
||||||
|
erase(*first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Removes an entity from a sparse set if it exists.
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @return True if the entity is actually removed, false otherwise.
|
||||||
|
*/
|
||||||
|
bool remove(const entity_type entt) {
|
||||||
|
return contains(entt) && (erase(entt), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Removes entities from a sparse set if they exist.
|
||||||
|
* @tparam It Type of input iterator.
|
||||||
|
* @param first An iterator to the first element of the range of entities.
|
||||||
|
* @param last An iterator past the last element of the range of entities.
|
||||||
|
* @return The number of entities actually removed.
|
||||||
|
*/
|
||||||
|
template<typename It>
|
||||||
|
size_type remove(It first, It last) {
|
||||||
|
size_type count{};
|
||||||
|
|
||||||
|
for(; first != last; ++first) {
|
||||||
|
count += remove(*first);
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @brief Removes all tombstones from the packed array of a sparse set. */
|
||||||
|
void compact() {
|
||||||
|
size_type from = packed.size();
|
||||||
|
for(; from && packed[from - 1u] == tombstone; --from) {}
|
||||||
|
|
||||||
|
for(auto *it = &free_list; *it != null && from; it = std::addressof(packed[entity_traits::to_entity(*it)])) {
|
||||||
|
if(const size_type to = entity_traits::to_entity(*it); to < from) {
|
||||||
|
--from;
|
||||||
|
move_element(from, to);
|
||||||
|
|
||||||
|
using std::swap;
|
||||||
|
swap(packed[from], packed[to]);
|
||||||
|
|
||||||
|
const auto entity = static_cast<typename entity_traits::entity_type>(to);
|
||||||
|
sparse_ref(packed[to]) = entity_traits::combine(entity, entity_traits::to_integral(packed[to]));
|
||||||
|
*it = entity_traits::combine(static_cast<typename entity_traits::entity_type>(from), entity_traits::reserved);
|
||||||
|
for(; from && packed[from - 1u] == tombstone; --from) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free_list = tombstone;
|
||||||
|
packed.resize(from);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Swaps two entities in a sparse set.
|
||||||
|
*
|
||||||
|
* For what it's worth, this function affects both the internal sparse array
|
||||||
|
* and the internal packed array. Users should not care of that anyway.
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* Attempting to swap entities that don't belong to the sparse set results
|
||||||
|
* in undefined behavior.
|
||||||
|
*
|
||||||
|
* @param lhs A valid identifier.
|
||||||
|
* @param rhs A valid identifier.
|
||||||
|
*/
|
||||||
|
void swap_elements(const entity_type lhs, const entity_type rhs) {
|
||||||
|
ENTT_ASSERT(contains(lhs) && contains(rhs), "Set does not contain entities");
|
||||||
|
|
||||||
|
auto &entt = sparse_ref(lhs);
|
||||||
|
auto &other = sparse_ref(rhs);
|
||||||
|
|
||||||
|
const auto from = entity_traits::to_entity(entt);
|
||||||
|
const auto to = entity_traits::to_entity(other);
|
||||||
|
|
||||||
|
// basic no-leak guarantee (with invalid state) if swapping throws
|
||||||
|
swap_at(static_cast<size_type>(from), static_cast<size_type>(to));
|
||||||
|
entt = entity_traits::combine(to, entity_traits::to_integral(packed[from]));
|
||||||
|
other = entity_traits::combine(from, entity_traits::to_integral(packed[to]));
|
||||||
|
|
||||||
|
using std::swap;
|
||||||
|
swap(packed[from], packed[to]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sort the first count elements according to the given comparison
|
||||||
|
* function.
|
||||||
|
*
|
||||||
|
* The comparison function object must return `true` if the first element
|
||||||
|
* is _less_ than the second one, `false` otherwise. The signature of the
|
||||||
|
* comparison function should be equivalent to the following:
|
||||||
|
*
|
||||||
|
* @code{.cpp}
|
||||||
|
* bool(const Entity, const Entity);
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* Moreover, the comparison function object shall induce a
|
||||||
|
* _strict weak ordering_ on the values.
|
||||||
|
*
|
||||||
|
* The sort function object must offer a member function template
|
||||||
|
* `operator()` that accepts three arguments:
|
||||||
|
*
|
||||||
|
* * An iterator to the first element of the range to sort.
|
||||||
|
* * An iterator past the last element of the range to sort.
|
||||||
|
* * A comparison function to use to compare the elements.
|
||||||
|
*
|
||||||
|
* @tparam Compare Type of comparison function object.
|
||||||
|
* @tparam Sort Type of sort function object.
|
||||||
|
* @tparam Args Types of arguments to forward to the sort function object.
|
||||||
|
* @param length Number of elements to sort.
|
||||||
|
* @param compare A valid comparison function object.
|
||||||
|
* @param algo A valid sort function object.
|
||||||
|
* @param args Arguments to forward to the sort function object, if any.
|
||||||
|
*/
|
||||||
|
template<typename Compare, typename Sort = std_sort, typename... Args>
|
||||||
|
void sort_n(const size_type length, Compare compare, Sort algo = Sort{}, Args &&...args) {
|
||||||
|
ENTT_ASSERT(!(length > packed.size()), "Length exceeds the number of elements");
|
||||||
|
ENTT_ASSERT(free_list == null, "Partial sorting with tombstones is not supported");
|
||||||
|
|
||||||
|
algo(packed.rend() - length, packed.rend(), std::move(compare), std::forward<Args>(args)...);
|
||||||
|
|
||||||
|
for(size_type pos{}; pos < length; ++pos) {
|
||||||
|
auto curr = pos;
|
||||||
|
auto next = index(packed[curr]);
|
||||||
|
|
||||||
|
while(curr != next) {
|
||||||
|
const auto idx = index(packed[next]);
|
||||||
|
const auto entt = packed[curr];
|
||||||
|
|
||||||
|
swap_at(next, idx);
|
||||||
|
const auto entity = static_cast<typename entity_traits::entity_type>(curr);
|
||||||
|
sparse_ref(entt) = entity_traits::combine(entity, entity_traits::to_integral(packed[curr]));
|
||||||
|
curr = std::exchange(next, idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sort all elements according to the given comparison function.
|
||||||
|
*
|
||||||
|
* @sa sort_n
|
||||||
|
*
|
||||||
|
* @tparam Compare Type of comparison function object.
|
||||||
|
* @tparam Sort Type of sort function object.
|
||||||
|
* @tparam Args Types of arguments to forward to the sort function object.
|
||||||
|
* @param compare A valid comparison function object.
|
||||||
|
* @param algo A valid sort function object.
|
||||||
|
* @param args Arguments to forward to the sort function object, if any.
|
||||||
|
*/
|
||||||
|
template<typename Compare, typename Sort = std_sort, typename... Args>
|
||||||
|
void sort(Compare compare, Sort algo = Sort{}, Args &&...args) {
|
||||||
|
compact();
|
||||||
|
sort_n(packed.size(), std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sort entities according to their order in another sparse set.
|
||||||
|
*
|
||||||
|
* Entities that are part of both the sparse sets are ordered internally
|
||||||
|
* according to the order they have in `other`. All the other entities goes
|
||||||
|
* to the end of the list and there are no guarantees on their order.<br/>
|
||||||
|
* In other terms, this function can be used to impose the same order on two
|
||||||
|
* sets by using one of them as a master and the other one as a slave.
|
||||||
|
*
|
||||||
|
* Iterating the sparse set with a couple of iterators returns elements in
|
||||||
|
* the expected order after a call to `respect`. See `begin` and `end` for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* @param other The sparse sets that imposes the order of the entities.
|
||||||
|
*/
|
||||||
|
void respect(const basic_sparse_set &other) {
|
||||||
|
compact();
|
||||||
|
|
||||||
|
const auto to = other.end();
|
||||||
|
auto from = other.begin();
|
||||||
|
|
||||||
|
for(size_type pos = packed.size() - 1; pos && from != to; ++from) {
|
||||||
|
if(contains(*from)) {
|
||||||
|
if(*from != packed[pos]) {
|
||||||
|
// basic no-leak guarantee (with invalid state) if swapping throws
|
||||||
|
swap_elements(packed[pos], *from);
|
||||||
|
}
|
||||||
|
|
||||||
|
--pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @brief Clears a sparse set. */
|
||||||
|
void clear() {
|
||||||
|
if(const auto last = end(); free_list == null) {
|
||||||
|
pop(begin(), last);
|
||||||
|
} else {
|
||||||
|
for(auto &&entity: *this) {
|
||||||
|
// tombstone filter on itself
|
||||||
|
if(const auto it = find(entity); it != last) {
|
||||||
|
pop(it, it + 1u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free_list = tombstone;
|
||||||
|
packed.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returned value type, if any.
|
||||||
|
* @return Returned value type, if any.
|
||||||
|
*/
|
||||||
|
const type_info &type() const noexcept {
|
||||||
|
return *info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @brief Forwards variables to derived classes, if any. */
|
||||||
|
virtual void bind(any) noexcept {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
sparse_container_type sparse;
|
||||||
|
packed_container_type packed;
|
||||||
|
const type_info *info;
|
||||||
|
entity_type free_list;
|
||||||
|
deletion_policy mode;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace entt
|
||||||
|
|
||||||
|
#endif
|
903
src/entt/entity/storage.hpp
Normal file
903
src/entt/entity/storage.hpp
Normal file
@ -0,0 +1,903 @@
|
|||||||
|
#ifndef ENTT_ENTITY_STORAGE_HPP
|
||||||
|
#define ENTT_ENTITY_STORAGE_HPP
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <iterator>
|
||||||
|
#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_info.hpp"
|
||||||
|
#include "component.hpp"
|
||||||
|
#include "entity.hpp"
|
||||||
|
#include "fwd.hpp"
|
||||||
|
#include "sparse_set.hpp"
|
||||||
|
#include "storage_mixin.hpp"
|
||||||
|
|
||||||
|
namespace entt {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cond TURN_OFF_DOXYGEN
|
||||||
|
* Internal details not to be documented.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<typename Container>
|
||||||
|
class storage_iterator final {
|
||||||
|
friend storage_iterator<const Container>;
|
||||||
|
|
||||||
|
using container_type = std::remove_const_t<Container>;
|
||||||
|
using alloc_traits = std::allocator_traits<typename container_type::allocator_type>;
|
||||||
|
using comp_traits = component_traits<std::remove_pointer_t<typename container_type::value_type>>;
|
||||||
|
|
||||||
|
using iterator_traits = std::iterator_traits<std::conditional_t<
|
||||||
|
std::is_const_v<Container>,
|
||||||
|
typename alloc_traits::template rebind_traits<typename std::pointer_traits<typename container_type::value_type>::element_type>::const_pointer,
|
||||||
|
typename alloc_traits::template rebind_traits<typename std::pointer_traits<typename container_type::value_type>::element_type>::pointer>>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using value_type = typename iterator_traits::value_type;
|
||||||
|
using pointer = typename iterator_traits::pointer;
|
||||||
|
using reference = typename iterator_traits::reference;
|
||||||
|
using difference_type = typename iterator_traits::difference_type;
|
||||||
|
using iterator_category = std::random_access_iterator_tag;
|
||||||
|
|
||||||
|
constexpr storage_iterator() noexcept = default;
|
||||||
|
|
||||||
|
constexpr storage_iterator(Container *ref, const difference_type idx) noexcept
|
||||||
|
: packed{ref},
|
||||||
|
offset{idx} {}
|
||||||
|
|
||||||
|
template<bool Const = std::is_const_v<Container>, typename = std::enable_if_t<Const>>
|
||||||
|
constexpr storage_iterator(const storage_iterator<std::remove_const_t<Container>> &other) noexcept
|
||||||
|
: storage_iterator{other.packed, other.offset} {}
|
||||||
|
|
||||||
|
constexpr storage_iterator &operator++() noexcept {
|
||||||
|
return --offset, *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr storage_iterator operator++(int) noexcept {
|
||||||
|
storage_iterator orig = *this;
|
||||||
|
return ++(*this), orig;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr storage_iterator &operator--() noexcept {
|
||||||
|
return ++offset, *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr storage_iterator operator--(int) noexcept {
|
||||||
|
storage_iterator orig = *this;
|
||||||
|
return operator--(), orig;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr storage_iterator &operator+=(const difference_type value) noexcept {
|
||||||
|
offset -= value;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr storage_iterator operator+(const difference_type value) const noexcept {
|
||||||
|
storage_iterator copy = *this;
|
||||||
|
return (copy += value);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr storage_iterator &operator-=(const difference_type value) noexcept {
|
||||||
|
return (*this += -value);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr storage_iterator operator-(const difference_type value) const noexcept {
|
||||||
|
return (*this + -value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept {
|
||||||
|
const auto pos = index() - value;
|
||||||
|
return (*packed)[pos / comp_traits::page_size][fast_mod(pos, comp_traits::page_size)];
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr pointer operator->() const noexcept {
|
||||||
|
const auto pos = index();
|
||||||
|
return (*packed)[pos / comp_traits::page_size] + fast_mod(pos, comp_traits::page_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr reference operator*() const noexcept {
|
||||||
|
return *operator->();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr difference_type index() const noexcept {
|
||||||
|
return offset - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Container *packed;
|
||||||
|
difference_type offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename CLhs, typename CRhs>
|
||||||
|
[[nodiscard]] constexpr std::ptrdiff_t operator-(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) noexcept {
|
||||||
|
return rhs.index() - lhs.index();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename CLhs, typename CRhs>
|
||||||
|
[[nodiscard]] constexpr bool operator==(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) noexcept {
|
||||||
|
return lhs.index() == rhs.index();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename CLhs, typename CRhs>
|
||||||
|
[[nodiscard]] constexpr bool operator!=(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) noexcept {
|
||||||
|
return !(lhs == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename CLhs, typename CRhs>
|
||||||
|
[[nodiscard]] constexpr bool operator<(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) noexcept {
|
||||||
|
return lhs.index() > rhs.index();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename CLhs, typename CRhs>
|
||||||
|
[[nodiscard]] constexpr bool operator>(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) noexcept {
|
||||||
|
return lhs.index() < rhs.index();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename CLhs, typename CRhs>
|
||||||
|
[[nodiscard]] constexpr bool operator<=(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) noexcept {
|
||||||
|
return !(lhs > rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename CLhs, typename CRhs>
|
||||||
|
[[nodiscard]] constexpr bool operator>=(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) noexcept {
|
||||||
|
return !(lhs < rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename It, typename... Other>
|
||||||
|
class extended_storage_iterator final {
|
||||||
|
template<typename Iter, typename... Args>
|
||||||
|
friend class extended_storage_iterator;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval<It>()), std::forward_as_tuple(*std::declval<Other>()...)));
|
||||||
|
using pointer = input_iterator_pointer<value_type>;
|
||||||
|
using reference = value_type;
|
||||||
|
using difference_type = std::ptrdiff_t;
|
||||||
|
using iterator_category = std::input_iterator_tag;
|
||||||
|
|
||||||
|
constexpr extended_storage_iterator()
|
||||||
|
: it{} {}
|
||||||
|
|
||||||
|
constexpr extended_storage_iterator(It base, Other... other)
|
||||||
|
: it{base, other...} {}
|
||||||
|
|
||||||
|
template<typename... Args, typename = std::enable_if_t<(!std::is_same_v<Other, Args> && ...) && (std::is_constructible_v<Other, Args> && ...)>>
|
||||||
|
constexpr extended_storage_iterator(const extended_storage_iterator<It, Args...> &other)
|
||||||
|
: it{other.it} {}
|
||||||
|
|
||||||
|
constexpr extended_storage_iterator &operator++() noexcept {
|
||||||
|
return ++std::get<It>(it), (++std::get<Other>(it), ...), *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr extended_storage_iterator operator++(int) noexcept {
|
||||||
|
extended_storage_iterator orig = *this;
|
||||||
|
return ++(*this), orig;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr pointer operator->() const noexcept {
|
||||||
|
return operator*();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr reference operator*() const noexcept {
|
||||||
|
return {*std::get<It>(it), *std::get<Other>(it)...};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... CLhs, typename... CRhs>
|
||||||
|
friend constexpr bool operator==(const extended_storage_iterator<CLhs...> &, const extended_storage_iterator<CRhs...> &) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::tuple<It, Other...> it;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... CLhs, typename... CRhs>
|
||||||
|
[[nodiscard]] constexpr bool operator==(const extended_storage_iterator<CLhs...> &lhs, const extended_storage_iterator<CRhs...> &rhs) noexcept {
|
||||||
|
return std::get<0>(lhs.it) == std::get<0>(rhs.it);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... CLhs, typename... CRhs>
|
||||||
|
[[nodiscard]] constexpr bool operator!=(const extended_storage_iterator<CLhs...> &lhs, const extended_storage_iterator<CRhs...> &rhs) noexcept {
|
||||||
|
return !(lhs == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal details not to be documented.
|
||||||
|
* @endcond
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Basic storage implementation.
|
||||||
|
*
|
||||||
|
* Internal data structures arrange elements to maximize performance. There are
|
||||||
|
* no guarantees that objects are returned in the insertion order when iterate
|
||||||
|
* a storage. Do not make assumption on the order in any case.
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* Empty types aren't explicitly instantiated. Therefore, many of the functions
|
||||||
|
* normally available for non-empty types will not be available for empty ones.
|
||||||
|
*
|
||||||
|
* @tparam Type Type of objects assigned to the entities.
|
||||||
|
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||||
|
* @tparam Allocator Type of allocator used to manage memory and elements.
|
||||||
|
*/
|
||||||
|
template<typename Type, typename Entity, typename Allocator, typename>
|
||||||
|
class basic_storage: public basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>> {
|
||||||
|
using alloc_traits = std::allocator_traits<Allocator>;
|
||||||
|
static_assert(std::is_same_v<typename alloc_traits::value_type, Type>, "Invalid value type");
|
||||||
|
using underlying_type = basic_sparse_set<Entity, typename alloc_traits::template rebind_alloc<Entity>>;
|
||||||
|
using container_type = std::vector<typename alloc_traits::pointer, typename alloc_traits::template rebind_alloc<typename alloc_traits::pointer>>;
|
||||||
|
using comp_traits = component_traits<Type>;
|
||||||
|
|
||||||
|
static constexpr bool is_pinned_type_v = !(std::is_move_constructible_v<Type> && std::is_move_assignable_v<Type>);
|
||||||
|
|
||||||
|
[[nodiscard]] auto &element_at(const std::size_t pos) const {
|
||||||
|
return packed.first()[pos / comp_traits::page_size][fast_mod(pos, comp_traits::page_size)];
|
||||||
|
}
|
||||||
|
|
||||||
|
auto assure_at_least(const std::size_t pos) {
|
||||||
|
auto &&container = packed.first();
|
||||||
|
const auto idx = pos / comp_traits::page_size;
|
||||||
|
|
||||||
|
if(!(idx < container.size())) {
|
||||||
|
auto curr = container.size();
|
||||||
|
container.resize(idx + 1u, nullptr);
|
||||||
|
|
||||||
|
ENTT_TRY {
|
||||||
|
for(const auto last = container.size(); curr < last; ++curr) {
|
||||||
|
container[curr] = alloc_traits::allocate(packed.second(), comp_traits::page_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ENTT_CATCH {
|
||||||
|
container.resize(curr);
|
||||||
|
ENTT_THROW;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return container[idx] + fast_mod(pos, comp_traits::page_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
auto emplace_element(const Entity entt, const bool force_back, Args &&...args) {
|
||||||
|
const auto it = base_type::try_emplace(entt, force_back);
|
||||||
|
|
||||||
|
ENTT_TRY {
|
||||||
|
auto elem = assure_at_least(static_cast<size_type>(it.index()));
|
||||||
|
entt::uninitialized_construct_using_allocator(to_address(elem), packed.second(), std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
ENTT_CATCH {
|
||||||
|
base_type::pop(it, it + 1u);
|
||||||
|
ENTT_THROW;
|
||||||
|
}
|
||||||
|
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
void shrink_to_size(const std::size_t sz) {
|
||||||
|
for(auto pos = sz, length = base_type::size(); pos < length; ++pos) {
|
||||||
|
if constexpr(comp_traits::in_place_delete) {
|
||||||
|
if(base_type::at(pos) != tombstone) {
|
||||||
|
std::destroy_at(std::addressof(element_at(pos)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::destroy_at(std::addressof(element_at(pos)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto &&container = packed.first();
|
||||||
|
auto page_allocator{packed.second()};
|
||||||
|
const auto from = (sz + comp_traits::page_size - 1u) / comp_traits::page_size;
|
||||||
|
|
||||||
|
for(auto pos = from, last = container.size(); pos < last; ++pos) {
|
||||||
|
alloc_traits::deallocate(page_allocator, container[pos], comp_traits::page_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
container.resize(from);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const void *get_at(const std::size_t pos) const final {
|
||||||
|
return std::addressof(element_at(pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
void swap_at([[maybe_unused]] const std::size_t lhs, [[maybe_unused]] const std::size_t rhs) final {
|
||||||
|
// use a runtime value to avoid compile-time suppression that drives the code coverage tool crazy
|
||||||
|
ENTT_ASSERT((lhs + 1u) && !is_pinned_type_v, "Pinned type");
|
||||||
|
|
||||||
|
if constexpr(!is_pinned_type_v) {
|
||||||
|
using std::swap;
|
||||||
|
swap(element_at(lhs), element_at(rhs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void move_element([[maybe_unused]] const std::size_t from, [[maybe_unused]] const std::size_t to) final {
|
||||||
|
// use a runtime value to avoid compile-time suppression that drives the code coverage tool crazy
|
||||||
|
ENTT_ASSERT((from + 1u) && !is_pinned_type_v, "Pinned type");
|
||||||
|
|
||||||
|
if constexpr(!is_pinned_type_v) {
|
||||||
|
auto &elem = element_at(from);
|
||||||
|
entt::uninitialized_construct_using_allocator(to_address(assure_at_least(to)), packed.second(), std::move(elem));
|
||||||
|
std::destroy_at(std::addressof(elem));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/*! @brief Random access iterator type. */
|
||||||
|
using basic_iterator = typename underlying_type::basic_iterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Erases entities from a sparse set.
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
void pop(basic_iterator first, basic_iterator last) override {
|
||||||
|
for(; first != last; ++first) {
|
||||||
|
// cannot use first.index() because it would break with cross iterators
|
||||||
|
auto &elem = element_at(base_type::index(*first));
|
||||||
|
|
||||||
|
if constexpr(comp_traits::in_place_delete) {
|
||||||
|
base_type::in_place_pop(first);
|
||||||
|
std::destroy_at(std::addressof(elem));
|
||||||
|
} else {
|
||||||
|
auto &other = element_at(base_type::size() - 1u);
|
||||||
|
// destroying on exit allows reentrant destructors
|
||||||
|
[[maybe_unused]] auto unused = std::exchange(elem, std::move(other));
|
||||||
|
std::destroy_at(std::addressof(other));
|
||||||
|
base_type::swap_and_pop(first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Assigns an entity to a storage.
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @param value Optional opaque value.
|
||||||
|
* @param force_back Force back insertion.
|
||||||
|
* @return Iterator pointing to the emplaced element.
|
||||||
|
*/
|
||||||
|
basic_iterator try_emplace([[maybe_unused]] const Entity entt, [[maybe_unused]] const bool force_back, const void *value) override {
|
||||||
|
if(value) {
|
||||||
|
if constexpr(std::is_copy_constructible_v<value_type>) {
|
||||||
|
return emplace_element(entt, force_back, *static_cast<const value_type *>(value));
|
||||||
|
} else {
|
||||||
|
return base_type::end();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if constexpr(std::is_default_constructible_v<value_type>) {
|
||||||
|
return emplace_element(entt, force_back);
|
||||||
|
} else {
|
||||||
|
return base_type::end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! @brief Base type. */
|
||||||
|
using base_type = underlying_type;
|
||||||
|
/*! @brief Allocator type. */
|
||||||
|
using allocator_type = Allocator;
|
||||||
|
/*! @brief Type of the objects assigned to entities. */
|
||||||
|
using value_type = Type;
|
||||||
|
/*! @brief Underlying entity identifier. */
|
||||||
|
using entity_type = Entity;
|
||||||
|
/*! @brief Unsigned integer type. */
|
||||||
|
using size_type = std::size_t;
|
||||||
|
/*! @brief Pointer type to contained elements. */
|
||||||
|
using pointer = typename container_type::pointer;
|
||||||
|
/*! @brief Constant pointer type to contained elements. */
|
||||||
|
using const_pointer = typename alloc_traits::template rebind_traits<typename alloc_traits::const_pointer>::const_pointer;
|
||||||
|
/*! @brief Random access iterator type. */
|
||||||
|
using iterator = internal::storage_iterator<container_type>;
|
||||||
|
/*! @brief Constant random access iterator type. */
|
||||||
|
using const_iterator = internal::storage_iterator<const container_type>;
|
||||||
|
/*! @brief Reverse iterator type. */
|
||||||
|
using reverse_iterator = std::reverse_iterator<iterator>;
|
||||||
|
/*! @brief Constant reverse iterator type. */
|
||||||
|
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
|
||||||
|
/*! @brief Extended iterable storage proxy. */
|
||||||
|
using iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::iterator, iterator>>;
|
||||||
|
/*! @brief Constant extended iterable storage proxy. */
|
||||||
|
using const_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_iterator, const_iterator>>;
|
||||||
|
|
||||||
|
/*! @brief Default constructor. */
|
||||||
|
basic_storage()
|
||||||
|
: basic_storage{allocator_type{}} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs an empty storage with a given allocator.
|
||||||
|
* @param allocator The allocator to use.
|
||||||
|
*/
|
||||||
|
explicit basic_storage(const allocator_type &allocator)
|
||||||
|
: base_type{type_id<value_type>(), deletion_policy{comp_traits::in_place_delete}, allocator},
|
||||||
|
packed{container_type{allocator}, allocator} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move constructor.
|
||||||
|
* @param other The instance to move from.
|
||||||
|
*/
|
||||||
|
basic_storage(basic_storage &&other) noexcept
|
||||||
|
: base_type{std::move(other)},
|
||||||
|
packed{std::move(other.packed)} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Allocator-extended move constructor.
|
||||||
|
* @param other The instance to move from.
|
||||||
|
* @param allocator The allocator to use.
|
||||||
|
*/
|
||||||
|
basic_storage(basic_storage &&other, const allocator_type &allocator) noexcept
|
||||||
|
: base_type{std::move(other), allocator},
|
||||||
|
packed{container_type{std::move(other.packed.first()), allocator}, allocator} {
|
||||||
|
ENTT_ASSERT(alloc_traits::is_always_equal::value || packed.second() == other.packed.second(), "Copying a storage is not allowed");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @brief Default destructor. */
|
||||||
|
~basic_storage() override {
|
||||||
|
shrink_to_size(0u);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move assignment operator.
|
||||||
|
* @param other The instance to move from.
|
||||||
|
* @return This storage.
|
||||||
|
*/
|
||||||
|
basic_storage &operator=(basic_storage &&other) noexcept {
|
||||||
|
ENTT_ASSERT(alloc_traits::is_always_equal::value || packed.second() == other.packed.second(), "Copying a storage is not allowed");
|
||||||
|
|
||||||
|
shrink_to_size(0u);
|
||||||
|
base_type::operator=(std::move(other));
|
||||||
|
packed.first() = std::move(other.packed.first());
|
||||||
|
propagate_on_container_move_assignment(packed.second(), other.packed.second());
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Exchanges the contents with those of a given storage.
|
||||||
|
* @param other Storage to exchange the content with.
|
||||||
|
*/
|
||||||
|
void swap(basic_storage &other) {
|
||||||
|
using std::swap;
|
||||||
|
underlying_type::swap(other);
|
||||||
|
propagate_on_container_swap(packed.second(), other.packed.second());
|
||||||
|
swap(packed.first(), other.packed.first());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the associated allocator.
|
||||||
|
* @return The associated allocator.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
|
||||||
|
return allocator_type{packed.second()};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Increases the capacity of a storage.
|
||||||
|
*
|
||||||
|
* If the new capacity is greater than the current capacity, new storage is
|
||||||
|
* allocated, otherwise the method does nothing.
|
||||||
|
*
|
||||||
|
* @param cap Desired capacity.
|
||||||
|
*/
|
||||||
|
void reserve(const size_type cap) override {
|
||||||
|
if(cap != 0u) {
|
||||||
|
base_type::reserve(cap);
|
||||||
|
assure_at_least(cap - 1u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the number of elements that a storage has currently
|
||||||
|
* allocated space for.
|
||||||
|
* @return Capacity of the storage.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] size_type capacity() const noexcept override {
|
||||||
|
return packed.first().size() * comp_traits::page_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @brief Requests the removal of unused capacity. */
|
||||||
|
void shrink_to_fit() override {
|
||||||
|
base_type::shrink_to_fit();
|
||||||
|
shrink_to_size(base_type::size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Direct access to the array of objects.
|
||||||
|
* @return A pointer to the array of objects.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] const_pointer raw() const noexcept {
|
||||||
|
return packed.first().data();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc raw */
|
||||||
|
[[nodiscard]] pointer raw() noexcept {
|
||||||
|
return packed.first().data();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an iterator to the beginning.
|
||||||
|
*
|
||||||
|
* The returned iterator points to the first instance of the internal array.
|
||||||
|
* If the storage 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 noexcept {
|
||||||
|
const auto pos = static_cast<typename iterator::difference_type>(base_type::size());
|
||||||
|
return const_iterator{&packed.first(), pos};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc cbegin */
|
||||||
|
[[nodiscard]] const_iterator begin() const noexcept {
|
||||||
|
return cbegin();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc begin */
|
||||||
|
[[nodiscard]] iterator begin() noexcept {
|
||||||
|
const auto pos = static_cast<typename iterator::difference_type>(base_type::size());
|
||||||
|
return iterator{&packed.first(), pos};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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 noexcept {
|
||||||
|
return const_iterator{&packed.first(), {}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc cend */
|
||||||
|
[[nodiscard]] const_iterator end() const noexcept {
|
||||||
|
return cend();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc end */
|
||||||
|
[[nodiscard]] iterator end() noexcept {
|
||||||
|
return iterator{&packed.first(), {}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a reverse iterator to the beginning.
|
||||||
|
*
|
||||||
|
* The returned iterator points to the first instance of the reversed
|
||||||
|
* internal array. If the storage is empty, the returned iterator will be
|
||||||
|
* equal to `rend()`.
|
||||||
|
*
|
||||||
|
* @return An iterator to the first instance of the reversed internal array.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] const_reverse_iterator crbegin() const noexcept {
|
||||||
|
return std::make_reverse_iterator(cend());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc crbegin */
|
||||||
|
[[nodiscard]] const_reverse_iterator rbegin() const noexcept {
|
||||||
|
return crbegin();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc rbegin */
|
||||||
|
[[nodiscard]] reverse_iterator rbegin() noexcept {
|
||||||
|
return std::make_reverse_iterator(end());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a reverse iterator to the end.
|
||||||
|
*
|
||||||
|
* The returned iterator points to the element following the last instance
|
||||||
|
* of the reversed internal array. Attempting to dereference the returned
|
||||||
|
* iterator results in undefined behavior.
|
||||||
|
*
|
||||||
|
* @return An iterator to the element following the last instance of the
|
||||||
|
* reversed internal array.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] const_reverse_iterator crend() const noexcept {
|
||||||
|
return std::make_reverse_iterator(cbegin());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc crend */
|
||||||
|
[[nodiscard]] const_reverse_iterator rend() const noexcept {
|
||||||
|
return crend();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc rend */
|
||||||
|
[[nodiscard]] reverse_iterator rend() noexcept {
|
||||||
|
return std::make_reverse_iterator(begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the object assigned to an entity.
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* Attempting to use an entity that doesn't belong to the storage results in
|
||||||
|
* undefined behavior.
|
||||||
|
*
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @return The object assigned to the entity.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] const value_type &get(const entity_type entt) const noexcept {
|
||||||
|
return element_at(base_type::index(entt));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc get */
|
||||||
|
[[nodiscard]] value_type &get(const entity_type entt) noexcept {
|
||||||
|
return const_cast<value_type &>(std::as_const(*this).get(entt));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the object assigned to an entity as a tuple.
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @return The object assigned to the entity as a tuple.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::tuple<const value_type &> get_as_tuple(const entity_type entt) const noexcept {
|
||||||
|
return std::forward_as_tuple(get(entt));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc get_as_tuple */
|
||||||
|
[[nodiscard]] std::tuple<value_type &> get_as_tuple(const entity_type entt) noexcept {
|
||||||
|
return std::forward_as_tuple(get(entt));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Assigns an entity to a storage and constructs its object.
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* Attempting to use an entity that already belongs to the storage results
|
||||||
|
* in undefined behavior.
|
||||||
|
*
|
||||||
|
* @tparam Args Types of arguments to use to construct the object.
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @param args Parameters to use to construct an object for the entity.
|
||||||
|
* @return A reference to the newly created object.
|
||||||
|
*/
|
||||||
|
template<typename... Args>
|
||||||
|
value_type &emplace(const entity_type entt, Args &&...args) {
|
||||||
|
if constexpr(std::is_aggregate_v<value_type>) {
|
||||||
|
const auto it = emplace_element(entt, false, Type{std::forward<Args>(args)...});
|
||||||
|
return element_at(static_cast<size_type>(it.index()));
|
||||||
|
} else {
|
||||||
|
const auto it = emplace_element(entt, false, std::forward<Args>(args)...);
|
||||||
|
return element_at(static_cast<size_type>(it.index()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Updates the instance assigned to a given entity in-place.
|
||||||
|
* @tparam Func Types of the function objects to invoke.
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @param func Valid function objects.
|
||||||
|
* @return A reference to the updated instance.
|
||||||
|
*/
|
||||||
|
template<typename... Func>
|
||||||
|
value_type &patch(const entity_type entt, Func &&...func) {
|
||||||
|
const auto idx = base_type::index(entt);
|
||||||
|
auto &elem = element_at(idx);
|
||||||
|
(std::forward<Func>(func)(elem), ...);
|
||||||
|
return elem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Assigns one or more entities to a storage and constructs their
|
||||||
|
* objects from a given instance.
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* Attempting to assign an entity that already belongs to the storage
|
||||||
|
* results in undefined behavior.
|
||||||
|
*
|
||||||
|
* @tparam It Type of input iterator.
|
||||||
|
* @param first An iterator to the first element of the range of entities.
|
||||||
|
* @param last An iterator past the last element of the range of entities.
|
||||||
|
* @param value An instance of the object to construct.
|
||||||
|
*/
|
||||||
|
template<typename It>
|
||||||
|
void insert(It first, It last, const value_type &value = {}) {
|
||||||
|
for(; first != last; ++first) {
|
||||||
|
emplace_element(*first, true, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Assigns one or more entities to a storage and constructs their
|
||||||
|
* objects from a given range.
|
||||||
|
*
|
||||||
|
* @sa construct
|
||||||
|
*
|
||||||
|
* @tparam EIt Type of input iterator.
|
||||||
|
* @tparam CIt Type of input iterator.
|
||||||
|
* @param first An iterator to the first element of the range of entities.
|
||||||
|
* @param last An iterator past the last element of the range of entities.
|
||||||
|
* @param from An iterator to the first element of the range of objects.
|
||||||
|
*/
|
||||||
|
template<typename EIt, typename CIt, typename = std::enable_if_t<std::is_same_v<typename std::iterator_traits<CIt>::value_type, value_type>>>
|
||||||
|
void insert(EIt first, EIt last, CIt from) {
|
||||||
|
for(; first != last; ++first, ++from) {
|
||||||
|
emplace_element(*first, true, *from);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an iterable object to use to _visit_ a storage.
|
||||||
|
*
|
||||||
|
* The iterable object returns a tuple that contains the current entity and
|
||||||
|
* a reference to its component.
|
||||||
|
*
|
||||||
|
* @return An iterable object to use to _visit_ the storage.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] iterable each() noexcept {
|
||||||
|
return {internal::extended_storage_iterator{base_type::begin(), begin()}, internal::extended_storage_iterator{base_type::end(), end()}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc each */
|
||||||
|
[[nodiscard]] const_iterable each() const noexcept {
|
||||||
|
return {internal::extended_storage_iterator{base_type::cbegin(), cbegin()}, internal::extended_storage_iterator{base_type::cend(), cend()}};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
compressed_pair<container_type, allocator_type> packed;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! @copydoc basic_storage */
|
||||||
|
template<typename Type, typename Entity, typename Allocator>
|
||||||
|
class basic_storage<Type, Entity, Allocator, std::enable_if_t<ignore_as_empty_v<Type>>>
|
||||||
|
: public basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>> {
|
||||||
|
using alloc_traits = std::allocator_traits<Allocator>;
|
||||||
|
static_assert(std::is_same_v<typename alloc_traits::value_type, Type>, "Invalid value type");
|
||||||
|
using underlying_type = basic_sparse_set<Entity, typename alloc_traits::template rebind_alloc<Entity>>;
|
||||||
|
using comp_traits = component_traits<Type>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! @brief Base type. */
|
||||||
|
using base_type = underlying_type;
|
||||||
|
/*! @brief Allocator type. */
|
||||||
|
using allocator_type = Allocator;
|
||||||
|
/*! @brief Type of the objects assigned to entities. */
|
||||||
|
using value_type = Type;
|
||||||
|
/*! @brief Underlying entity identifier. */
|
||||||
|
using entity_type = Entity;
|
||||||
|
/*! @brief Unsigned integer type. */
|
||||||
|
using size_type = std::size_t;
|
||||||
|
/*! @brief Extended iterable storage proxy. */
|
||||||
|
using iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::iterator>>;
|
||||||
|
/*! @brief Constant extended iterable storage proxy. */
|
||||||
|
using const_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_iterator>>;
|
||||||
|
|
||||||
|
/*! @brief Default constructor. */
|
||||||
|
basic_storage()
|
||||||
|
: basic_storage{allocator_type{}} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs an empty container with a given allocator.
|
||||||
|
* @param allocator The allocator to use.
|
||||||
|
*/
|
||||||
|
explicit basic_storage(const allocator_type &allocator)
|
||||||
|
: base_type{type_id<value_type>(), deletion_policy{comp_traits::in_place_delete}, allocator} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move constructor.
|
||||||
|
* @param other The instance to move from.
|
||||||
|
*/
|
||||||
|
basic_storage(basic_storage &&other) noexcept = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Allocator-extended move constructor.
|
||||||
|
* @param other The instance to move from.
|
||||||
|
* @param allocator The allocator to use.
|
||||||
|
*/
|
||||||
|
basic_storage(basic_storage &&other, const allocator_type &allocator) noexcept
|
||||||
|
: base_type{std::move(other), allocator} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move assignment operator.
|
||||||
|
* @param other The instance to move from.
|
||||||
|
* @return This storage.
|
||||||
|
*/
|
||||||
|
basic_storage &operator=(basic_storage &&other) noexcept = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the associated allocator.
|
||||||
|
* @return The associated allocator.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
|
||||||
|
return allocator_type{base_type::get_allocator()};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the object assigned to an entity, that is `void`.
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* Attempting to use an entity that doesn't belong to the storage results in
|
||||||
|
* undefined behavior.
|
||||||
|
*
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
*/
|
||||||
|
void get([[maybe_unused]] const entity_type entt) const noexcept {
|
||||||
|
ENTT_ASSERT(base_type::contains(entt), "Storage does not contain entity");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an empty tuple.
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* Attempting to use an entity that doesn't belong to the storage results in
|
||||||
|
* undefined behavior.
|
||||||
|
*
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @return Returns an empty tuple.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::tuple<> get_as_tuple([[maybe_unused]] const entity_type entt) const noexcept {
|
||||||
|
ENTT_ASSERT(base_type::contains(entt), "Storage does not contain entity");
|
||||||
|
return std::tuple{};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Assigns an entity to a storage and constructs its object.
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* Attempting to use an entity that already belongs to the storage results
|
||||||
|
* in undefined behavior.
|
||||||
|
*
|
||||||
|
* @tparam Args Types of arguments to use to construct the object.
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
*/
|
||||||
|
template<typename... Args>
|
||||||
|
void emplace(const entity_type entt, Args &&...) {
|
||||||
|
base_type::try_emplace(entt, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Updates the instance assigned to a given entity in-place.
|
||||||
|
* @tparam Func Types of the function objects to invoke.
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @param func Valid function objects.
|
||||||
|
*/
|
||||||
|
template<typename... Func>
|
||||||
|
void patch([[maybe_unused]] const entity_type entt, Func &&...func) {
|
||||||
|
ENTT_ASSERT(base_type::contains(entt), "Storage does not contain entity");
|
||||||
|
(std::forward<Func>(func)(), ...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Assigns entities to a storage.
|
||||||
|
* @tparam It Type of input iterator.
|
||||||
|
* @tparam Args Types of optional arguments.
|
||||||
|
* @param first An iterator to the first element of the range of entities.
|
||||||
|
* @param last An iterator past the last element of the range of entities.
|
||||||
|
*/
|
||||||
|
template<typename It, typename... Args>
|
||||||
|
void insert(It first, It last, Args &&...) {
|
||||||
|
for(; first != last; ++first) {
|
||||||
|
base_type::try_emplace(*first, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an iterable object to use to _visit_ a storage.
|
||||||
|
*
|
||||||
|
* The iterable object returns a tuple that contains the current entity.
|
||||||
|
*
|
||||||
|
* @return An iterable object to use to _visit_ the storage.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] iterable each() noexcept {
|
||||||
|
return {internal::extended_storage_iterator{base_type::begin()}, internal::extended_storage_iterator{base_type::end()}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc each */
|
||||||
|
[[nodiscard]] const_iterable each() const noexcept {
|
||||||
|
return {internal::extended_storage_iterator{base_type::cbegin()}, internal::extended_storage_iterator{base_type::cend()}};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace entt
|
||||||
|
|
||||||
|
#endif
|
236
src/entt/entity/storage_mixin.hpp
Normal file
236
src/entt/entity/storage_mixin.hpp
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
#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_registry_type = basic_registry<typename Type::entity_type, typename Type::base_type::allocator_type>;
|
||||||
|
using sigh_type = sigh<void(basic_registry_type &, const typename Type::entity_type), typename Type::allocator_type>;
|
||||||
|
using basic_iterator = typename Type::basic_iterator;
|
||||||
|
|
||||||
|
void pop(basic_iterator first, basic_iterator last) override {
|
||||||
|
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);
|
||||||
|
Type::pop(it, it + 1u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
basic_iterator try_emplace(const typename basic_registry_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 Allocator type. */
|
||||||
|
using allocator_type = typename Type::allocator_type;
|
||||||
|
/*! @brief Underlying entity identifier. */
|
||||||
|
using entity_type = typename Type::entity_type;
|
||||||
|
/*! @brief Expected registry type. */
|
||||||
|
using registry_type = basic_registry_type;
|
||||||
|
|
||||||
|
/*! @brief Default constructor. */
|
||||||
|
sigh_storage_mixin()
|
||||||
|
: sigh_storage_mixin{allocator_type{}} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs an empty storage with a given allocator.
|
||||||
|
* @param allocator The allocator to use.
|
||||||
|
*/
|
||||||
|
explicit sigh_storage_mixin(const allocator_type &allocator)
|
||||||
|
: Type{allocator},
|
||||||
|
owner{},
|
||||||
|
construction{allocator},
|
||||||
|
destruction{allocator},
|
||||||
|
update{allocator} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move constructor.
|
||||||
|
* @param other The instance to move from.
|
||||||
|
*/
|
||||||
|
sigh_storage_mixin(sigh_storage_mixin &&other) noexcept
|
||||||
|
: Type{std::move(other)},
|
||||||
|
owner{other.owner},
|
||||||
|
construction{std::move(other.construction)},
|
||||||
|
destruction{std::move(other.destruction)},
|
||||||
|
update{std::move(other.update)} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Allocator-extended move constructor.
|
||||||
|
* @param other The instance to move from.
|
||||||
|
* @param allocator The allocator to use.
|
||||||
|
*/
|
||||||
|
sigh_storage_mixin(sigh_storage_mixin &&other, const allocator_type &allocator) noexcept
|
||||||
|
: Type{std::move(other), allocator},
|
||||||
|
owner{other.owner},
|
||||||
|
construction{std::move(other.construction), allocator},
|
||||||
|
destruction{std::move(other.destruction), allocator},
|
||||||
|
update{std::move(other.update), allocator} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move assignment operator.
|
||||||
|
* @param other The instance to move from.
|
||||||
|
* @return This storage.
|
||||||
|
*/
|
||||||
|
sigh_storage_mixin &operator=(sigh_storage_mixin &&other) noexcept {
|
||||||
|
Type::operator=(std::move(other));
|
||||||
|
owner = other.owner;
|
||||||
|
construction = std::move(other.construction);
|
||||||
|
destruction = std::move(other.destruction);
|
||||||
|
update = std::move(other.update);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Exchanges the contents with those of a given storage.
|
||||||
|
* @param other Storage to exchange the content with.
|
||||||
|
*/
|
||||||
|
void swap(sigh_storage_mixin &other) {
|
||||||
|
using std::swap;
|
||||||
|
Type::swap(other);
|
||||||
|
swap(owner, other.owner);
|
||||||
|
swap(construction, other.construction);
|
||||||
|
swap(destruction, other.destruction);
|
||||||
|
swap(update, other.update);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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() 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() 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() 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 derived classes, if any.
|
||||||
|
* @param value A variable wrapped in an opaque container.
|
||||||
|
*/
|
||||||
|
void bind(any value) noexcept final {
|
||||||
|
auto *reg = any_cast<basic_registry_type>(&value);
|
||||||
|
owner = reg ? reg : owner;
|
||||||
|
Type::bind(std::move(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
basic_registry_type *owner;
|
||||||
|
sigh_type construction;
|
||||||
|
sigh_type destruction;
|
||||||
|
sigh_type update;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace entt
|
||||||
|
|
||||||
|
#endif
|
861
src/entt/entity/view.hpp
Normal file
861
src/entt/entity/view.hpp
Normal file
@ -0,0 +1,861 @@
|
|||||||
|
#ifndef ENTT_ENTITY_VIEW_HPP
|
||||||
|
#define ENTT_ENTITY_VIEW_HPP
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <iterator>
|
||||||
|
#include <tuple>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include "../config/config.h"
|
||||||
|
#include "../core/iterator.hpp"
|
||||||
|
#include "../core/type_traits.hpp"
|
||||||
|
#include "component.hpp"
|
||||||
|
#include "entity.hpp"
|
||||||
|
#include "fwd.hpp"
|
||||||
|
#include "sparse_set.hpp"
|
||||||
|
#include "storage.hpp"
|
||||||
|
|
||||||
|
namespace entt {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cond TURN_OFF_DOXYGEN
|
||||||
|
* Internal details not to be documented.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<typename Type, std::size_t Get, std::size_t Exclude>
|
||||||
|
class view_iterator final {
|
||||||
|
using iterator_type = typename Type::const_iterator;
|
||||||
|
|
||||||
|
[[nodiscard]] bool valid() const noexcept {
|
||||||
|
return ((Get != 0u) || (*it != tombstone))
|
||||||
|
&& std::apply([entt = *it](const auto *...curr) { return (curr->contains(entt) && ...); }, pools)
|
||||||
|
&& std::apply([entt = *it](const auto *...curr) { return (!curr->contains(entt) && ...); }, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
using value_type = typename iterator_type::value_type;
|
||||||
|
using pointer = typename iterator_type::pointer;
|
||||||
|
using reference = typename iterator_type::reference;
|
||||||
|
using difference_type = typename iterator_type::difference_type;
|
||||||
|
using iterator_category = std::forward_iterator_tag;
|
||||||
|
|
||||||
|
constexpr view_iterator() noexcept
|
||||||
|
: it{},
|
||||||
|
last{},
|
||||||
|
pools{},
|
||||||
|
filter{} {}
|
||||||
|
|
||||||
|
view_iterator(iterator_type curr, iterator_type to, std::array<const Type *, Get> all_of, std::array<const Type *, Exclude> none_of) noexcept
|
||||||
|
: it{curr},
|
||||||
|
last{to},
|
||||||
|
pools{all_of},
|
||||||
|
filter{none_of} {
|
||||||
|
while(it != last && !valid()) {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
view_iterator &operator++() noexcept {
|
||||||
|
while(++it != last && !valid()) {}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
view_iterator operator++(int) noexcept {
|
||||||
|
view_iterator orig = *this;
|
||||||
|
return ++(*this), orig;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] pointer operator->() const noexcept {
|
||||||
|
return &*it;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] reference operator*() const noexcept {
|
||||||
|
return *operator->();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename LhsType, auto... LhsArgs, typename RhsType, auto... RhsArgs>
|
||||||
|
friend constexpr bool operator==(const view_iterator<LhsType, LhsArgs...> &, const view_iterator<RhsType, RhsArgs...> &) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
iterator_type it;
|
||||||
|
iterator_type last;
|
||||||
|
std::array<const Type *, Get> pools;
|
||||||
|
std::array<const Type *, Exclude> filter;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename LhsType, auto... LhsArgs, typename RhsType, auto... RhsArgs>
|
||||||
|
[[nodiscard]] constexpr bool operator==(const view_iterator<LhsType, LhsArgs...> &lhs, const view_iterator<RhsType, RhsArgs...> &rhs) noexcept {
|
||||||
|
return lhs.it == rhs.it;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename LhsType, auto... LhsArgs, typename RhsType, auto... RhsArgs>
|
||||||
|
[[nodiscard]] constexpr bool operator!=(const view_iterator<LhsType, LhsArgs...> &lhs, const view_iterator<RhsType, RhsArgs...> &rhs) noexcept {
|
||||||
|
return !(lhs == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename It, typename... Type>
|
||||||
|
struct extended_view_iterator final {
|
||||||
|
using difference_type = std::ptrdiff_t;
|
||||||
|
using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval<It>()), std::declval<Type>().get_as_tuple({})...));
|
||||||
|
using pointer = input_iterator_pointer<value_type>;
|
||||||
|
using reference = value_type;
|
||||||
|
using iterator_category = std::input_iterator_tag;
|
||||||
|
|
||||||
|
constexpr extended_view_iterator()
|
||||||
|
: it{},
|
||||||
|
pools{} {}
|
||||||
|
|
||||||
|
extended_view_iterator(It from, std::tuple<Type *...> storage)
|
||||||
|
: it{from},
|
||||||
|
pools{storage} {}
|
||||||
|
|
||||||
|
extended_view_iterator &operator++() noexcept {
|
||||||
|
return ++it, *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
extended_view_iterator operator++(int) noexcept {
|
||||||
|
extended_view_iterator orig = *this;
|
||||||
|
return ++(*this), orig;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] reference operator*() const noexcept {
|
||||||
|
return std::apply([entt = *it](auto *...curr) { return std::tuple_cat(std::make_tuple(entt), curr->get_as_tuple(entt)...); }, pools);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] pointer operator->() const noexcept {
|
||||||
|
return operator*();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... Lhs, typename... Rhs>
|
||||||
|
friend bool constexpr operator==(const extended_view_iterator<Lhs...> &, const extended_view_iterator<Rhs...> &) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
It it;
|
||||||
|
std::tuple<Type *...> pools;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Lhs, typename... Rhs>
|
||||||
|
[[nodiscard]] constexpr bool operator==(const extended_view_iterator<Lhs...> &lhs, const extended_view_iterator<Rhs...> &rhs) noexcept {
|
||||||
|
return lhs.it == rhs.it;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... Lhs, typename... Rhs>
|
||||||
|
[[nodiscard]] constexpr bool operator!=(const extended_view_iterator<Lhs...> &lhs, const extended_view_iterator<Rhs...> &rhs) noexcept {
|
||||||
|
return !(lhs == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal details not to be documented.
|
||||||
|
* @endcond
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief View implementation.
|
||||||
|
*
|
||||||
|
* Primary template isn't defined on purpose. All the specializations give a
|
||||||
|
* compile-time error, but for a few reasonable cases.
|
||||||
|
*/
|
||||||
|
template<typename, typename, typename>
|
||||||
|
class basic_view;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Multi component view.
|
||||||
|
*
|
||||||
|
* Multi component views iterate over those entities that are at least in the
|
||||||
|
* given storage. During initialization, a multi component view looks at the
|
||||||
|
* number of entities available for each component and uses the smallest set in
|
||||||
|
* order to get a performance boost when iterating.
|
||||||
|
*
|
||||||
|
* @b Important
|
||||||
|
*
|
||||||
|
* Iterators aren't invalidated if:
|
||||||
|
*
|
||||||
|
* * New elements are added to the storage.
|
||||||
|
* * The entity currently pointed is modified (for example, components are added
|
||||||
|
* or removed from it).
|
||||||
|
* * The entity currently pointed is destroyed.
|
||||||
|
*
|
||||||
|
* In all other cases, modifying the pools iterated by the view in any way
|
||||||
|
* invalidates all the iterators and using them results in undefined behavior.
|
||||||
|
*
|
||||||
|
* @tparam Get Types of storage iterated by the view.
|
||||||
|
* @tparam Exclude Types of storage used to filter the view.
|
||||||
|
*/
|
||||||
|
template<typename... Get, typename... Exclude>
|
||||||
|
class basic_view<get_t<Get...>, exclude_t<Exclude...>> {
|
||||||
|
using underlying_type = std::common_type_t<typename Get::entity_type..., typename Exclude::entity_type...>;
|
||||||
|
using basic_common_type = std::common_type_t<typename Get::base_type..., typename Exclude::base_type...>;
|
||||||
|
|
||||||
|
template<typename, typename, typename>
|
||||||
|
friend class basic_view;
|
||||||
|
|
||||||
|
template<typename Type>
|
||||||
|
static constexpr std::size_t index_of = type_list_index_v<std::remove_const_t<Type>, type_list<typename Get::value_type...>>;
|
||||||
|
|
||||||
|
[[nodiscard]] auto opaque_check_set() const noexcept {
|
||||||
|
std::array<const base_type *, sizeof...(Get) - 1u> other{};
|
||||||
|
std::apply([&other, pos = 0u, view = view](const auto *...curr) mutable { ((curr == view ? void() : void(other[pos++] = curr)), ...); }, pools);
|
||||||
|
return other;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto filter_as_array() const noexcept {
|
||||||
|
return std::apply([](const auto *...curr) { return std::array<const base_type *, sizeof...(Exclude)>{curr...}; }, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<std::size_t Curr, std::size_t Other, typename... Args>
|
||||||
|
[[nodiscard]] auto dispatch_get(const std::tuple<underlying_type, Args...> &curr) const {
|
||||||
|
if constexpr(Curr == Other) {
|
||||||
|
return std::forward_as_tuple(std::get<Args>(curr)...);
|
||||||
|
} else {
|
||||||
|
return storage<Other>().get_as_tuple(std::get<0>(curr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto reject(const underlying_type entt) const noexcept {
|
||||||
|
return std::apply([entt](const auto *...curr) { return (curr->contains(entt) || ...); }, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<std::size_t Curr, typename Func, std::size_t... Index>
|
||||||
|
void each(Func &func, std::index_sequence<Index...>) const {
|
||||||
|
for(const auto curr: storage<Curr>().each()) {
|
||||||
|
if(const auto entt = std::get<0>(curr); ((sizeof...(Get) != 1u) || (entt != tombstone)) && ((Curr == Index || storage<Index>().contains(entt)) && ...) && !reject(entt)) {
|
||||||
|
if constexpr(is_applicable_v<Func, decltype(std::tuple_cat(std::tuple<entity_type>{}, std::declval<basic_view>().get({})))>) {
|
||||||
|
std::apply(func, std::tuple_cat(std::make_tuple(entt), dispatch_get<Curr, Index>(curr)...));
|
||||||
|
} else {
|
||||||
|
std::apply(func, std::tuple_cat(dispatch_get<Curr, Index>(curr)...));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Func, std::size_t... Index>
|
||||||
|
void pick_and_each(Func &func, std::index_sequence<Index...> seq) const {
|
||||||
|
((&storage<Index>() == view ? each<Index>(func, seq) : void()), ...);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! @brief Underlying entity identifier. */
|
||||||
|
using entity_type = underlying_type;
|
||||||
|
/*! @brief Unsigned integer type. */
|
||||||
|
using size_type = std::size_t;
|
||||||
|
/*! @brief Common type among all storage types. */
|
||||||
|
using base_type = basic_common_type;
|
||||||
|
/*! @brief Bidirectional iterator type. */
|
||||||
|
using iterator = internal::view_iterator<base_type, sizeof...(Get) - 1u, sizeof...(Exclude)>;
|
||||||
|
/*! @brief Iterable view type. */
|
||||||
|
using iterable = iterable_adaptor<internal::extended_view_iterator<iterator, Get...>>;
|
||||||
|
|
||||||
|
/*! @brief Default constructor to use to create empty, invalid views. */
|
||||||
|
basic_view() noexcept
|
||||||
|
: pools{},
|
||||||
|
filter{},
|
||||||
|
view{} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs a multi-type view from a set of storage classes.
|
||||||
|
* @param value The storage for the types to iterate.
|
||||||
|
* @param exclude The storage for the types used to filter the view.
|
||||||
|
*/
|
||||||
|
basic_view(Get &...value, Exclude &...exclude) noexcept
|
||||||
|
: pools{&value...},
|
||||||
|
filter{&exclude...},
|
||||||
|
view{[](const base_type *first, const auto *...other) { ((first = other->size() < first->size() ? other : first), ...); return first; }(&value...)} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs a multi-type view from a set of storage classes.
|
||||||
|
* @param value The storage for the types to iterate.
|
||||||
|
* @param excl The storage for the types used to filter the view.
|
||||||
|
*/
|
||||||
|
basic_view(std::tuple<Get &...> value, std::tuple<Exclude &...> excl = {}) noexcept
|
||||||
|
: pools{std::apply([](auto &...curr) { return std::make_tuple(&curr...); }, value)},
|
||||||
|
filter{std::apply([](auto &...curr) { return std::make_tuple(&curr...); }, excl)},
|
||||||
|
view{std::apply([](const base_type *first, const auto *...other) { ((first = other->size() < first->size() ? other : first), ...); return first; }, pools)} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Creates a new view driven by a given component in its iterations.
|
||||||
|
* @tparam Type Type of component used to drive the iteration.
|
||||||
|
* @return A new view driven by the given component in its iterations.
|
||||||
|
*/
|
||||||
|
template<typename Type>
|
||||||
|
[[nodiscard]] basic_view use() const noexcept {
|
||||||
|
return use<index_of<Type>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Creates a new view driven by a given component in its iterations.
|
||||||
|
* @tparam Index Index of the component used to drive the iteration.
|
||||||
|
* @return A new view driven by the given component in its iterations.
|
||||||
|
*/
|
||||||
|
template<std::size_t Index>
|
||||||
|
[[nodiscard]] basic_view use() const noexcept {
|
||||||
|
basic_view other{*this};
|
||||||
|
other.view = &storage<Index>();
|
||||||
|
return other;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Updates the internal leading view if required.
|
||||||
|
* @return A newly created and internally optimized view.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] basic_view refresh() const noexcept {
|
||||||
|
return std::apply([](auto *...elem) { return basic_view{*elem...}; }, std::tuple_cat(pools, filter));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the leading storage of a view.
|
||||||
|
* @return The leading storage of the view.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] const base_type &handle() const noexcept {
|
||||||
|
return *view;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the storage for a given component type.
|
||||||
|
* @tparam Comp Type of component of which to return the storage.
|
||||||
|
* @return The storage for the given component type.
|
||||||
|
*/
|
||||||
|
template<typename Type>
|
||||||
|
[[nodiscard]] decltype(auto) storage() const noexcept {
|
||||||
|
return storage<index_of<Type>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the storage for a given index.
|
||||||
|
* @tparam Index Index of the storage to return.
|
||||||
|
* @return The storage for the given index.
|
||||||
|
*/
|
||||||
|
template<std::size_t Index>
|
||||||
|
[[nodiscard]] decltype(auto) storage() const noexcept {
|
||||||
|
return *std::get<Index>(pools);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Estimates the number of entities iterated by the view.
|
||||||
|
* @return Estimated number of entities iterated by the view.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] size_type size_hint() const noexcept {
|
||||||
|
return view->size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an iterator to the first entity of the view.
|
||||||
|
*
|
||||||
|
* The returned iterator points to the first entity of the view. If the view
|
||||||
|
* is empty, the returned iterator will be equal to `end()`.
|
||||||
|
*
|
||||||
|
* @return An iterator to the first entity of the view.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] iterator begin() const noexcept {
|
||||||
|
return iterator{view->begin(), view->end(), opaque_check_set(), filter_as_array()};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an iterator that is past the last entity of the view.
|
||||||
|
*
|
||||||
|
* The returned iterator points to the entity following the last entity of
|
||||||
|
* the view. Attempting to dereference the returned iterator results in
|
||||||
|
* undefined behavior.
|
||||||
|
*
|
||||||
|
* @return An iterator to the entity following the last entity of the view.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] iterator end() const noexcept {
|
||||||
|
return iterator{view->end(), view->end(), opaque_check_set(), filter_as_array()};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the first entity of the view, if any.
|
||||||
|
* @return The first entity of the view if one exists, the null entity
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] entity_type front() const noexcept {
|
||||||
|
const auto it = begin();
|
||||||
|
return it != end() ? *it : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the last entity of the view, if any.
|
||||||
|
* @return The last entity of the view if one exists, the null entity
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] entity_type back() const noexcept {
|
||||||
|
auto it = view->rbegin();
|
||||||
|
for(const auto last = view->rend(); it != last && !contains(*it); ++it) {}
|
||||||
|
return it == view->rend() ? null : *it;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Finds an entity.
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @return An iterator to the given entity if it's found, past the end
|
||||||
|
* iterator otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] iterator find(const entity_type entt) const noexcept {
|
||||||
|
return contains(entt) ? iterator{view->find(entt), view->end(), opaque_check_set(), filter_as_array()} : end();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the components assigned to the given entity.
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @return The components assigned to the given entity.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] decltype(auto) operator[](const entity_type entt) const {
|
||||||
|
return get(entt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks if a view is properly initialized.
|
||||||
|
* @return True if the view is properly initialized, false otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] explicit operator bool() const noexcept {
|
||||||
|
return view != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks if a view contains an entity.
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @return True if the view contains the given entity, false otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool contains(const entity_type entt) const noexcept {
|
||||||
|
return std::apply([entt](const auto *...curr) { return (curr->contains(entt) && ...); }, pools)
|
||||||
|
&& std::apply([entt](const auto *...curr) { return (!curr->contains(entt) && ...); }, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the components assigned to the given entity.
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* Attempting to use an entity that doesn't belong to the view results in
|
||||||
|
* undefined behavior.
|
||||||
|
*
|
||||||
|
* @tparam Type Types of components to get.
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @return The components assigned to the entity.
|
||||||
|
*/
|
||||||
|
template<typename... Type>
|
||||||
|
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
|
||||||
|
if constexpr(sizeof...(Type) == 0) {
|
||||||
|
return std::apply([entt](auto *...curr) { return std::tuple_cat(curr->get_as_tuple(entt)...); }, pools);
|
||||||
|
} else if constexpr(sizeof...(Type) == 1) {
|
||||||
|
return (storage<index_of<Type>>().get(entt), ...);
|
||||||
|
} else {
|
||||||
|
return std::tuple_cat(storage<index_of<Type>>().get_as_tuple(entt)...);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the components assigned to the given entity.
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* Attempting to use an entity that doesn't belong to the view results in
|
||||||
|
* undefined behavior.
|
||||||
|
*
|
||||||
|
* @tparam First Index of a component to get.
|
||||||
|
* @tparam Other Indexes of other components to get.
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @return The components assigned to the entity.
|
||||||
|
*/
|
||||||
|
template<std::size_t First, std::size_t... Other>
|
||||||
|
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
|
||||||
|
if constexpr(sizeof...(Other) == 0) {
|
||||||
|
return storage<First>().get(entt);
|
||||||
|
} else {
|
||||||
|
return std::tuple_cat(storage<First>().get_as_tuple(entt), storage<Other>().get_as_tuple(entt)...);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Iterates entities and components and applies the given function
|
||||||
|
* object to them.
|
||||||
|
*
|
||||||
|
* The function object is invoked for each entity. It is provided with the
|
||||||
|
* entity itself and a set of references to non-empty components. The
|
||||||
|
* _constness_ of the components is as requested.<br/>
|
||||||
|
* The signature of the function must be equivalent to one of the following
|
||||||
|
* forms:
|
||||||
|
*
|
||||||
|
* @code{.cpp}
|
||||||
|
* void(const entity_type, Type &...);
|
||||||
|
* void(Type &...);
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* @tparam Func Type of the function object to invoke.
|
||||||
|
* @param func A valid function object.
|
||||||
|
*/
|
||||||
|
template<typename Func>
|
||||||
|
void each(Func func) const {
|
||||||
|
pick_and_each(func, std::index_sequence_for<Get...>{});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an iterable object to use to _visit_ a view.
|
||||||
|
*
|
||||||
|
* The iterable object returns a tuple that contains the current entity and
|
||||||
|
* a set of references to its non-empty components. The _constness_ of the
|
||||||
|
* components is as requested.
|
||||||
|
*
|
||||||
|
* @return An iterable object to use to _visit_ the view.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] iterable each() const noexcept {
|
||||||
|
return {internal::extended_view_iterator{begin(), pools}, internal::extended_view_iterator{end(), pools}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Combines two views in a _more specific_ one (friend function).
|
||||||
|
* @tparam OGet Component list of the view to combine with.
|
||||||
|
* @tparam OExclude Filter list of the view to combine with.
|
||||||
|
* @param other The view to combine with.
|
||||||
|
* @return A more specific view.
|
||||||
|
*/
|
||||||
|
template<typename... OGet, typename... OExclude>
|
||||||
|
[[nodiscard]] auto operator|(const basic_view<get_t<OGet...>, exclude_t<OExclude...>> &other) const noexcept {
|
||||||
|
return std::make_from_tuple<basic_view<get_t<Get..., OGet...>, exclude_t<Exclude..., OExclude...>>>(
|
||||||
|
std::apply([](auto *...curr) { return std::forward_as_tuple(*curr...); }, std::tuple_cat(pools, other.pools, filter, other.filter)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::tuple<Get *...> pools;
|
||||||
|
std::tuple<Exclude *...> filter;
|
||||||
|
const base_type *view;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Single component view specialization.
|
||||||
|
*
|
||||||
|
* Single component views are specialized in order to get a boost in terms of
|
||||||
|
* performance. This kind of views can access the underlying data structure
|
||||||
|
* directly and avoid superfluous checks.
|
||||||
|
*
|
||||||
|
* @b Important
|
||||||
|
*
|
||||||
|
* Iterators aren't invalidated if:
|
||||||
|
*
|
||||||
|
* * New elements are added to the storage.
|
||||||
|
* * The entity currently pointed is modified (for example, components are added
|
||||||
|
* or removed from it).
|
||||||
|
* * The entity currently pointed is destroyed.
|
||||||
|
*
|
||||||
|
* In all other cases, modifying the pool iterated by the view in any way
|
||||||
|
* invalidates all the iterators and using them results in undefined behavior.
|
||||||
|
*
|
||||||
|
* @tparam Get Type of storage iterated by the view.
|
||||||
|
*/
|
||||||
|
template<typename Get>
|
||||||
|
class basic_view<get_t<Get>, exclude_t<>, std::void_t<std::enable_if_t<!component_traits<typename Get::value_type>::in_place_delete>>> {
|
||||||
|
template<typename, typename, typename>
|
||||||
|
friend class basic_view;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! @brief Underlying entity identifier. */
|
||||||
|
using entity_type = typename Get::entity_type;
|
||||||
|
/*! @brief Unsigned integer type. */
|
||||||
|
using size_type = std::size_t;
|
||||||
|
/*! @brief Common type among all storage types. */
|
||||||
|
using base_type = typename Get::base_type;
|
||||||
|
/*! @brief Random access iterator type. */
|
||||||
|
using iterator = typename base_type::iterator;
|
||||||
|
/*! @brief Reversed iterator type. */
|
||||||
|
using reverse_iterator = typename base_type::reverse_iterator;
|
||||||
|
/*! @brief Iterable view type. */
|
||||||
|
using iterable = decltype(std::declval<Get>().each());
|
||||||
|
|
||||||
|
/*! @brief Default constructor to use to create empty, invalid views. */
|
||||||
|
basic_view() noexcept
|
||||||
|
: pools{},
|
||||||
|
filter{} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs a single-type view from a storage class.
|
||||||
|
* @param ref The storage for the type to iterate.
|
||||||
|
*/
|
||||||
|
basic_view(Get &ref) noexcept
|
||||||
|
: pools{&ref},
|
||||||
|
filter{} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs a single-type view from a storage class.
|
||||||
|
* @param ref The storage for the type to iterate.
|
||||||
|
*/
|
||||||
|
basic_view(std::tuple<Get &> ref, std::tuple<> = {}) noexcept
|
||||||
|
: pools{&std::get<0>(ref)},
|
||||||
|
filter{} {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the leading storage of a view.
|
||||||
|
* @return The leading storage of the view.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] const base_type &handle() const noexcept {
|
||||||
|
return storage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the storage for a given component type.
|
||||||
|
* @tparam Type Type of component of which to return the storage.
|
||||||
|
* @return The storage for the given component type.
|
||||||
|
*/
|
||||||
|
template<typename Type = typename Get::value_type>
|
||||||
|
[[nodiscard]] decltype(auto) storage() const noexcept {
|
||||||
|
static_assert(std::is_same_v<std::remove_const_t<Type>, typename Get::value_type>, "Invalid component type");
|
||||||
|
return storage<0>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the storage for a given index.
|
||||||
|
* @tparam Index Index of the storage to return.
|
||||||
|
* @return The storage for the given index.
|
||||||
|
*/
|
||||||
|
template<std::size_t Index>
|
||||||
|
[[nodiscard]] decltype(auto) storage() const noexcept {
|
||||||
|
return *std::get<Index>(pools);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the number of entities that have the given component.
|
||||||
|
* @return Number of entities that have the given component.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] size_type size() const noexcept {
|
||||||
|
return handle().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks whether a view is empty.
|
||||||
|
* @return True if the view is empty, false otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool empty() const noexcept {
|
||||||
|
return handle().empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an iterator to the first entity of the view.
|
||||||
|
*
|
||||||
|
* The returned iterator points to the first entity of the view. If the view
|
||||||
|
* is empty, the returned iterator will be equal to `end()`.
|
||||||
|
*
|
||||||
|
* @return An iterator to the first entity of the view.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] iterator begin() const noexcept {
|
||||||
|
return handle().begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an iterator that is past the last entity of the view.
|
||||||
|
*
|
||||||
|
* The returned iterator points to the entity following the last entity of
|
||||||
|
* the view. Attempting to dereference the returned iterator results in
|
||||||
|
* undefined behavior.
|
||||||
|
*
|
||||||
|
* @return An iterator to the entity following the last entity of the view.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] iterator end() const noexcept {
|
||||||
|
return handle().end();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an iterator to the first entity of the reversed view.
|
||||||
|
*
|
||||||
|
* The returned iterator points to the first entity of the reversed view. If
|
||||||
|
* the view is empty, the returned iterator will be equal to `rend()`.
|
||||||
|
*
|
||||||
|
* @return An iterator to the first entity of the reversed view.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] reverse_iterator rbegin() const noexcept {
|
||||||
|
return handle().rbegin();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an iterator that is past the last entity of the reversed
|
||||||
|
* view.
|
||||||
|
*
|
||||||
|
* The returned iterator points to the entity following the last entity of
|
||||||
|
* the reversed view. Attempting to dereference the returned iterator
|
||||||
|
* results in undefined behavior.
|
||||||
|
*
|
||||||
|
* @return An iterator to the entity following the last entity of the
|
||||||
|
* reversed view.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] reverse_iterator rend() const noexcept {
|
||||||
|
return handle().rend();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the first entity of the view, if any.
|
||||||
|
* @return The first entity of the view if one exists, the null entity
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] entity_type front() const noexcept {
|
||||||
|
return empty() ? null : *begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the last entity of the view, if any.
|
||||||
|
* @return The last entity of the view if one exists, the null entity
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] entity_type back() const noexcept {
|
||||||
|
return empty() ? null : *rbegin();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Finds an entity.
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @return An iterator to the given entity if it's found, past the end
|
||||||
|
* iterator otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] iterator find(const entity_type entt) const noexcept {
|
||||||
|
return contains(entt) ? handle().find(entt) : end();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the identifier that occupies the given position.
|
||||||
|
* @param pos Position of the element to return.
|
||||||
|
* @return The identifier that occupies the given position.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] entity_type operator[](const size_type pos) const {
|
||||||
|
return begin()[pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the component assigned to the given entity.
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @return The component assigned to the given entity.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] decltype(auto) operator[](const entity_type entt) const {
|
||||||
|
return storage().get(entt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks if a view is properly initialized.
|
||||||
|
* @return True if the view is properly initialized, false otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] explicit operator bool() const noexcept {
|
||||||
|
return std::get<0>(pools) != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks if a view contains an entity.
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @return True if the view contains the given entity, false otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool contains(const entity_type entt) const noexcept {
|
||||||
|
return handle().contains(entt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the component assigned to the given entity.
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* Attempting to use an entity that doesn't belong to the view results in
|
||||||
|
* undefined behavior.
|
||||||
|
*
|
||||||
|
* @tparam Type Type or index of the component to get.
|
||||||
|
* @param entt A valid identifier.
|
||||||
|
* @return The component assigned to the entity.
|
||||||
|
*/
|
||||||
|
template<typename... Type>
|
||||||
|
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
|
||||||
|
if constexpr(sizeof...(Type) == 0) {
|
||||||
|
return storage().get_as_tuple(entt);
|
||||||
|
} else {
|
||||||
|
static_assert((std::is_same_v<std::remove_const_t<Type>, typename Get::value_type> && ...), "Invalid component type");
|
||||||
|
return storage().get(entt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @copydoc get */
|
||||||
|
template<std::size_t Index>
|
||||||
|
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
|
||||||
|
return storage().get(entt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Iterates entities and components and applies the given function
|
||||||
|
* object to them.
|
||||||
|
*
|
||||||
|
* The function object is invoked for each entity. It is provided with the
|
||||||
|
* entity itself and a reference to the component if it's a non-empty one.
|
||||||
|
* The _constness_ of the component is as requested.<br/>
|
||||||
|
* The signature of the function must be equivalent to one of the following
|
||||||
|
* forms:
|
||||||
|
*
|
||||||
|
* @code{.cpp}
|
||||||
|
* void(const entity_type, Type &);
|
||||||
|
* void(typename Type &);
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* @note
|
||||||
|
* Empty types aren't explicitly instantiated and therefore they are never
|
||||||
|
* returned during iterations.
|
||||||
|
*
|
||||||
|
* @tparam Func Type of the function object to invoke.
|
||||||
|
* @param func A valid function object.
|
||||||
|
*/
|
||||||
|
template<typename Func>
|
||||||
|
void each(Func func) const {
|
||||||
|
if constexpr(is_applicable_v<Func, decltype(*each().begin())>) {
|
||||||
|
for(const auto pack: each()) {
|
||||||
|
std::apply(func, pack);
|
||||||
|
}
|
||||||
|
} else if constexpr(ignore_as_empty_v<typename Get::value_type>) {
|
||||||
|
for(size_type pos{}, last = size(); pos < last; ++pos) {
|
||||||
|
func();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for(auto &&component: storage()) {
|
||||||
|
func(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns an iterable object to use to _visit_ a view.
|
||||||
|
*
|
||||||
|
* The iterable object returns a tuple that contains the current entity and
|
||||||
|
* a reference to its component if it's a non-empty one. The _constness_ of
|
||||||
|
* the component is as requested.
|
||||||
|
*
|
||||||
|
* @return An iterable object to use to _visit_ the view.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] iterable each() const noexcept {
|
||||||
|
return storage().each();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Combines two views in a _more specific_ one (friend function).
|
||||||
|
* @tparam OGet Component list of the view to combine with.
|
||||||
|
* @tparam OExclude Filter list of the view to combine with.
|
||||||
|
* @param other The view to combine with.
|
||||||
|
* @return A more specific view.
|
||||||
|
*/
|
||||||
|
template<typename... OGet, typename... OExclude>
|
||||||
|
[[nodiscard]] auto operator|(const basic_view<get_t<OGet...>, exclude_t<OExclude...>> &other) const noexcept {
|
||||||
|
return std::make_from_tuple<basic_view<get_t<Get, OGet...>, exclude_t<OExclude...>>>(
|
||||||
|
std::apply([](auto *...curr) { return std::forward_as_tuple(*curr...); }, std::tuple_cat(pools, other.pools, other.filter)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::tuple<Get *> pools;
|
||||||
|
std::tuple<> filter;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deduction guide.
|
||||||
|
* @tparam Type Type of storage classes used to create the view.
|
||||||
|
* @param storage The storage for the types to iterate.
|
||||||
|
*/
|
||||||
|
template<typename... Type>
|
||||||
|
basic_view(Type &...storage) -> basic_view<get_t<Type...>, exclude_t<>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deduction guide.
|
||||||
|
* @tparam Get Types of components iterated by the view.
|
||||||
|
* @tparam Exclude Types of components used to filter the view.
|
||||||
|
*/
|
||||||
|
template<typename... Get, typename... Exclude>
|
||||||
|
basic_view(std::tuple<Get &...>, std::tuple<Exclude &...> = {}) -> basic_view<get_t<Get...>, exclude_t<Exclude...>>;
|
||||||
|
|
||||||
|
} // namespace entt
|
||||||
|
|
||||||
|
#endif
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user