Compare commits

..

15 Commits

Author SHA1 Message Date
Michele Caini
e4ccb878f4 updated single include file 2022-08-02 09:23:51 +02:00
Michele Caini
268723c87b storage: thanks msvc for accepting invalid C++ code :( 2022-08-02 09:23:20 +02:00
Michele Caini
9b8ab3494b updated single include file 2022-08-02 09:11:15 +02:00
Michele Caini
357c850130 ready to cut version 3.10.3 2022-08-02 09:10:36 +02:00
Michele Caini
c09661536b storage: fix cross range-erase can break when using built-in iterators (close #914) 2022-08-02 09:10:10 +02:00
Michele Caini
f8de85b961 update single include file 2022-08-01 10:34:07 +02:00
Michele Caini
90742a72b0 ready to cut version 3.10.2 2022-08-01 10:33:05 +02:00
Michele Caini
7bbd7d82b0 sparse_set: fix cross range-erase can break when using built-in iterators (close #913) 2022-08-01 10:32:20 +02:00
Michele Caini
965c440d86 update single include file 2022-04-28 09:16:13 +02:00
Michele Caini
6b2aff821c ready to cut version 3.10.1 2022-04-28 09:15:38 +02:00
Michele Caini
84d9125ea1 meta: re-added meta_type::remove_pointer (close #878) 2022-04-28 09:14:03 +02:00
Michele Caini
9b969e762c meta (close #872):
* container traits don't really support plain arrays anymore (if they ever did)
* fix an issue with insert/erase of meta sequence containers
* add non-regression tests
2022-04-28 09:13:51 +02:00
Corentin Schreiber
2695d48ba7 registry: overflow of version and max number of entities (#864) 2022-04-28 09:12:25 +02:00
Michele Caini
aba2a6b17d config: decouple ENTT_NOEXCEPT and exceptions handling (close #867) 2022-04-28 09:12:13 +02:00
Michele Caini
7b87d17d22 resource: added more comparison operators for resource handles 2022-04-28 09:11:58 +02:00
341 changed files with 50955 additions and 97226 deletions

View File

@@ -1 +0,0 @@
test

View File

@@ -1 +0,0 @@
USE_BAZEL_VERSION=6.x

View File

@@ -1,16 +0,0 @@
common --enable_bzlmod
build --enable_platform_specific_config
build --incompatible_enable_cc_toolchain_resolution
build --enable_runfiles
build --incompatible_strict_action_env
# required for googletest
build:linux --cxxopt=-std=c++20
build:macos --cxxopt=-std=c++20
common:ci --announce_rc
common:ci --verbose_failures
common:ci --keep_going
test:ci --test_output=errors
try-import %workspace%/user.bazelrc

View File

@@ -2,17 +2,13 @@ BasedOnStyle: llvm
--- ---
AccessModifierOffset: -4 AccessModifierOffset: -4
AlignEscapedNewlines: DontAlign AlignEscapedNewlines: DontAlign
AllowShortBlocksOnASingleLine: Always AllowShortBlocksOnASingleLine: Empty
AllowShortCompoundRequirementOnASingleLine: true
AllowShortEnumsOnASingleLine: true AllowShortEnumsOnASingleLine: true
AllowShortFunctionsOnASingleLine: Empty AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: WithoutElse AllowShortIfStatementsOnASingleLine: WithoutElse
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: true AllowShortLoopsOnASingleLine: true
AlwaysBreakTemplateDeclarations: Yes AlwaysBreakTemplateDeclarations: Yes
BreakBeforeBinaryOperators: NonAssignment BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Attach
BreakBeforeConceptDeclarations: Always
BreakBeforeTernaryOperators: true BreakBeforeTernaryOperators: true
ColumnLimit: 0 ColumnLimit: 0
DerivePointerAlignment: false DerivePointerAlignment: false
@@ -27,35 +23,18 @@ IncludeCategories:
Priority: 4 Priority: 4
- Regex: '.*' - Regex: '.*'
Priority: 5 Priority: 5
IncludeIsMainRegex: "^$"
IndentPPDirectives: AfterHash IndentPPDirectives: AfterHash
IndentRequiresClause: false
IndentWidth: 4 IndentWidth: 4
InsertBraces: true
InsertNewlineAtEOF: true
KeepEmptyLinesAtTheStartOfBlocks: false KeepEmptyLinesAtTheStartOfBlocks: false
Language: Cpp Language: Cpp
PointerAlignment: Right PointerAlignment: Right
RequiresClausePosition: OwnLineWithBrace
RequiresExpressionIndentation: OuterScope
SpaceAfterCStyleCast: false SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: false SpaceAfterTemplateKeyword: false
SpaceAroundPointerQualifiers: After SpaceAroundPointerQualifiers: After
SpaceBeforeCaseColon: false SpaceBeforeCaseColon: false
SpaceBeforeCtorInitializerColon: false SpaceBeforeCtorInitializerColon: false
SpaceBeforeInheritanceColon: false SpaceBeforeInheritanceColon: false
SpaceBeforeParens: Custom SpaceBeforeParens: Never
SpaceBeforeParensOptions:
AfterControlStatements: false
AfterForeachMacros: false
AfterFunctionDeclarationName: false
AfterFunctionDefinitionName: false
AfterIfMacros: false
AfterOverloadedOperator: false
AfterPlacementOperator: false
AfterRequiresInClause: true
AfterRequiresInExpression: false
BeforeNonEmptyParentheses: false
SpaceBeforeRangeBasedForLoopColon: false SpaceBeforeRangeBasedForLoopColon: false
Standard: Latest Standard: Latest
TabWidth: 4 TabWidth: 4

View File

@@ -1,58 +0,0 @@
Checks: >
bugprone-*,
clang-analyzer-*,
-clang-analyzer-optin.core.EnumCastOutOfRange,
concurrency-*,
cppcoreguidelines-*,
-cppcoreguidelines-owning-memory,
-cppcoreguidelines-pro-bounds-constant-array-index,
-cppcoreguidelines-pro-type-const-cast,
-cppcoreguidelines-pro-type-member-init,
-cppcoreguidelines-pro-type-reinterpret-cast,
-cppcoreguidelines-pro-type-union-access,
misc-*,
-misc-include-cleaner,
-misc-no-recursion,
modernize-*,
-modernize-use-trailing-return-type,
performance-*,
portability-*,
readability-*,
-readability-else-after-return,
-readability-function-cognitive-complexity,
-readability-named-parameter,
-readability-redundant-member-init,
-readability-uppercase-literal-suffix,
CheckOptions:
- key: cppcoreguidelines-avoid-magic-numbers.IgnoreAllFloatingPointValues
value: true
- key: cppcoreguidelines-avoid-magic-numbers.IgnorePowersOf2IntegerValues
value: true
- key: cppcoreguidelines-rvalue-reference-param-not-moved.AllowPartialMove
value: true
- key: cppcoreguidelines-rvalue-reference-param-not-moved.IgnoreUnnamedParams
value: true
- key: cppcoreguidelines-special-member-functions.AllowMissingMoveFunctions
value: true
- key: cppcoreguidelines-special-member-functions.AllowMissingMoveFunctionsWhenCopyIsDeleted
value: true
- key: cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor
value: true
- key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic
value: true
- key: misc-non-private-member-variables-in-classes.IgnorePublicMemberVariables
value: true
- key: modernize-avoid-c-arrays.AllowStringArrays
value: true
- key: performance-enum-size.EnumIgnoreList
value: meta_traits
- key: readability-function-cognitive-complexity.IgnoreMacros
value: true
- key: readability-identifier-length.MinimumParameterNameLength
value: 2
- key: readability-identifier-length.MinimumVariableNameLength
value: 2
- key: readability-magic-numbers.IgnoreAllFloatingPointValues
value: true
- key: readability-magic-numbers.IgnorePowersOf2IntegerValues
value: true

View File

@@ -1,84 +0,0 @@
name: analyzer
on:
push:
branches:
- analyzer
jobs:
iwyu:
timeout-minutes: 60
env:
IWYU: "0.24"
LLVM: "20"
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4
- 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/noble/ llvm-toolchain-noble-$LLVM main"
sudo apt update
sudo apt remove -y "llvm*"
sudo apt remove -y "libclang*"
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 -DCMAKE_C_COMPILER=clang-$LLVM \
-DCMAKE_CXX_COMPILER=clang++-$LLVM \
-DENTT_BUILD_TESTING=ON \
-DENTT_BUILD_BENCHMARK=ON \
-DENTT_BUILD_EXAMPLE=ON \
-DENTT_BUILD_LIB=ON \
-DENTT_BUILD_SNAPSHOT=ON \
-DENTT_BUILD_TESTBED=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
clang-tidy:
timeout-minutes: 60
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4
- name: Compile tests
working-directory: build
env:
CXX: clang++
run: |
cmake -DENTT_BUILD_TESTING=ON \
-DENTT_BUILD_BENCHMARK=ON \
-DENTT_BUILD_EXAMPLE=ON \
-DENTT_BUILD_LIB=ON \
-DENTT_BUILD_SNAPSHOT=ON \
-DENTT_BUILD_TESTBED=ON \
-DENTT_USE_CLANG_TIDY=ON \
..
make -j4

View File

@@ -1,23 +0,0 @@
name: Bazel Release
on:
release:
types: [published]
jobs:
# A release archive is required for bzlmod
# See: https://blog.bazel.build/2023/02/15/github-archive-checksum.html
bazel-release-archive:
runs-on: ubuntu-latest
continue-on-error: true
permissions:
contents: write
steps:
- uses: actions/setup-go@v5
- run: go install github.com/bazelbuild/buildtools/buildozer@latest
- uses: actions/checkout@v4
- run: ./scripts/sync_bzlmod_version.sh
- run: git archive $GITHUB_REF -o "entt-${GITHUB_REF:10}.tar.gz"
- run: gh release upload ${GITHUB_REF:10} "entt-${GITHUB_REF:10}.tar.gz"
env:
GH_TOKEN: ${{ github.token }}

View File

@@ -1,23 +0,0 @@
name: bazel
on: [push, pull_request]
jobs:
test:
strategy:
matrix:
os:
- ubuntu-latest
- windows-latest
- macos-latest
runs-on: ${{ matrix.os }}
continue-on-error: true
steps:
- uses: actions/checkout@v4
- run: bazelisk test --config=ci ...
working-directory: test
env:
USE_BAZEL_VERSION: 6.x

View File

@@ -5,29 +5,42 @@ on: [push, pull_request]
jobs: jobs:
linux: linux:
timeout-minutes: 15
strategy: strategy:
matrix: matrix:
compiler: compiler:
- { pkg: g++, exe: 'g++', version: 12 } - pkg: g++-7
- { pkg: g++, exe: 'g++', version: 13 } exe: g++-7
- { pkg: g++, exe: 'g++', version: 14 } - pkg: g++-8
- { pkg: clang, exe: 'clang++', version: 16 } exe: g++-8
- { pkg: clang, exe: 'clang++', version: 17 } - pkg: g++-9
- { pkg: clang, exe: 'clang++', version: 18 } 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
timeout-minutes: 15
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v2
- name: Install compiler - name: Install compiler
run: | run: |
sudo apt update sudo apt update
sudo apt install -y ${{ matrix.compiler.pkg }}-${{ matrix.compiler.version }} sudo apt install -y ${{ matrix.compiler.pkg }}
- name: Compile tests - name: Compile tests
working-directory: build working-directory: build
env: env:
CXX: ${{ matrix.compiler.exe }}-${{ matrix.compiler.version }} CXX: ${{ matrix.compiler.exe }}
run: | run: |
cmake -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON .. cmake -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON ..
make -j4 make -j4
@@ -35,23 +48,52 @@ jobs:
working-directory: build working-directory: build
env: env:
CTEST_OUTPUT_ON_FAILURE: 1 CTEST_OUTPUT_ON_FAILURE: 1
run: ctest -C Debug -j4 run: ctest --timeout 30 -C Debug -j4
linux-extra:
timeout-minutes: 15
windows:
strategy: strategy:
matrix: matrix:
toolset: [default, v143, clang-cl] 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: include:
- toolset: v143 - toolset: v141
toolset_option: -T"v143" toolset_option: -T"v141"
- toolset: v142
toolset_option: -T"v142"
- toolset: clang-cl - toolset: clang-cl
toolset_option: -T"ClangCl" toolset_option: -T"ClangCl"
timeout-minutes: 15
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v2
- name: Compile tests - name: Compile tests
working-directory: build working-directory: build
run: | run: |
@@ -61,37 +103,20 @@ jobs:
working-directory: build working-directory: build
env: env:
CTEST_OUTPUT_ON_FAILURE: 1 CTEST_OUTPUT_ON_FAILURE: 1
run: ctest -C Debug -j4 run: ctest --timeout 30 -C Debug -j4
macos: windows-extra:
timeout-minutes: 15 timeout-minutes: 15
runs-on: macOS-latest
steps:
- uses: actions/checkout@v4
- 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 -C Debug -j4
extra:
strategy: strategy:
matrix: matrix:
os: [windows-latest, macOS-latest, ubuntu-latest]
id_type: ["std::uint32_t", "std::uint64_t"] id_type: ["std::uint32_t", "std::uint64_t"]
cxx_std: [cxx_std_20, cxx_std_23] cxx_std: [cxx_std_17, cxx_std_20]
timeout-minutes: 15 runs-on: windows-latest
runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v2
- name: Compile tests - name: Compile tests
working-directory: build working-directory: build
run: | run: |
@@ -101,4 +126,44 @@ jobs:
working-directory: build working-directory: build
env: env:
CTEST_OUTPUT_ON_FAILURE: 1 CTEST_OUTPUT_ON_FAILURE: 1
run: ctest -C Debug -j4 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

View File

@@ -9,11 +9,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v2
- name: Compile tests - name: Compile tests
working-directory: build working-directory: build
env: env:
CXXFLAGS: "--coverage -fno-elide-constructors -fno-inline -fno-default-inline" CXXFLAGS: "--coverage -fno-inline"
CXX: g++ CXX: g++
run: | run: |
cmake -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON .. cmake -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON ..
@@ -22,15 +22,15 @@ jobs:
working-directory: build working-directory: build
env: env:
CTEST_OUTPUT_ON_FAILURE: 1 CTEST_OUTPUT_ON_FAILURE: 1
run: ctest -C Debug -j4 run: ctest --timeout 30 -C Debug -j4
- name: Collect data - name: Collect data
working-directory: build working-directory: build
run: | run: |
sudo apt install lcov sudo apt install lcov
lcov -c -d . -o coverage.info --ignore-errors gcov,gcov,mismatch,mismatch lcov -c -d . -o coverage.info
lcov -l coverage.info lcov -l coverage.info
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v2
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
files: build/coverage.info files: build/coverage.info

View File

@@ -2,7 +2,7 @@ name: deploy
on: on:
release: release:
types: [published] types: published
jobs: jobs:
@@ -15,7 +15,7 @@ jobs:
FORMULA: entt.rb FORMULA: entt.rb
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Clone repository - name: Clone repository
working-directory: build working-directory: build
env: env:

View File

@@ -11,12 +11,12 @@ jobs:
matrix: matrix:
compiler: [clang++] compiler: [clang++]
id_type: ["std::uint32_t", "std::uint64_t"] id_type: ["std::uint32_t", "std::uint64_t"]
cxx_std: [cxx_std_20, cxx_std_23] cxx_std: [cxx_std_17, cxx_std_20]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v2
- name: Compile tests - name: Compile tests
working-directory: build working-directory: build
env: env:
@@ -28,4 +28,4 @@ jobs:
working-directory: build working-directory: build
env: env:
CTEST_OUTPUT_ON_FAILURE: 1 CTEST_OUTPUT_ON_FAILURE: 1
run: ctest -C Debug -j4 run: ctest --timeout 30 -C Debug -j4

View File

@@ -1,79 +0,0 @@
name: testbed
on: [push]
jobs:
linux:
timeout-minutes: 15
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install required packages
run: |
sudo apt update
sudo apt install -y \
build-essential \
git \
make \
pkg-config \
cmake \
ninja-build \
gnome-desktop-testing \
libasound2-dev \
libpulse-dev \
libaudio-dev \
libjack-dev \
libsndio-dev \
libx11-dev \
libxext-dev \
libxrandr-dev \
libxcursor-dev \
libxfixes-dev \
libxi-dev \
libxss-dev \
libxtst-dev \
libxkbcommon-dev \
libdrm-dev \
libgbm-dev \
libgl1-mesa-dev \
libgles2-mesa-dev \
libegl1-mesa-dev \
libdbus-1-dev \
libibus-1.0-dev \
libudev-dev \
libpipewire-0.3-dev \
libwayland-dev \
libdecor-0-dev \
liburing-dev
- name: Compile testbed
working-directory: build
run: |
cmake -DENTT_BUILD_TESTBED=ON ..
make -j4
windows:
timeout-minutes: 15
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: seanmiddleditch/gha-setup-ninja@master
- name: Compile testbed
working-directory: build
run: |
cmake -DENTT_BUILD_TESTBED=ON .. -G Ninja
cmake --build . -j 4
macos:
timeout-minutes: 15
runs-on: macOS-latest
steps:
- uses: actions/checkout@v4
- name: Compile testbed
working-directory: build
run: |
cmake -DENTT_BUILD_TESTBED=ON ..
make -j4

3
.gitignore vendored
View File

@@ -11,6 +11,3 @@ cpp.hint
# Bazel # Bazel
/bazel-* /bazel-*
/test/bazel-*
/user.bazelrc
*.bazel.lock

View File

@@ -16,7 +16,6 @@ cugone
dbacchet dbacchet
dBagrat dBagrat
djarek djarek
DNKpp
DonKult DonKult
drglove drglove
eliasdaler eliasdaler
@@ -42,7 +41,6 @@ Oortonaut
Paolo-Oliverio Paolo-Oliverio
pgruenbacher pgruenbacher
prowolf prowolf
Qix-
stefanofiorentino stefanofiorentino
suVrik suVrik
szunhammer szunhammer

View File

@@ -1,6 +1,14 @@
package(default_visibility = ["//visibility:public"]) _msvc_copts = ["/std:c++17"]
_gcc_copts = ["-std=c++17"]
alias( cc_library(
name = "entt", name = "entt",
actual = "//src: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,
}),
) )

View File

@@ -1,15 +1,21 @@
#
# EnTT # EnTT
#
cmake_minimum_required(VERSION 3.28) cmake_minimum_required(VERSION 3.12.4)
#
# Read project version # Read project version
#
set(ENTT_VERSION_REGEX "#define ENTT_VERSION_.*[ \t]+(.+)") 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}) 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") list(TRANSFORM ENTT_VERSION REPLACE ${ENTT_VERSION_REGEX} "\\1")
string(JOIN "." ENTT_VERSION ${ENTT_VERSION}) string(JOIN "." ENTT_VERSION ${ENTT_VERSION})
#
# Project configuration # Project configuration
#
project( project(
EnTT EnTT
@@ -22,21 +28,18 @@ project(
if(NOT CMAKE_BUILD_TYPE) if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug) set(CMAKE_BUILD_TYPE Debug)
endif() endif()
message(VERBOSE "*") message(VERBOSE "*")
message(VERBOSE "* ${PROJECT_NAME} v${PROJECT_VERSION} (${CMAKE_BUILD_TYPE})") message(VERBOSE "* ${PROJECT_NAME} v${PROJECT_VERSION} (${CMAKE_BUILD_TYPE})")
message(VERBOSE "* Copyright (c) 2017-2026 Michele Caini <michele.caini@gmail.com>") message(VERBOSE "* Copyright (c) 2017-2022 Michele Caini <michele.caini@gmail.com>")
message(VERBOSE "*") message(VERBOSE "*")
# CMake stuff #
list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules)
# Compiler stuff # Compiler stuff
#
option(ENTT_USE_LIBCPP "Use libc++ by adding -stdlib=libc++ flag if available." OFF) 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) option(ENTT_USE_SANITIZER "Enable sanitizers by adding -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined flags if available." OFF)
option(ENTT_USE_CLANG_TIDY "Enable static analysis with clang-tidy" OFF)
if(ENTT_USE_LIBCPP) if(ENTT_USE_LIBCPP)
if(NOT WIN32) if(NOT WIN32)
@@ -56,7 +59,7 @@ if(ENTT_USE_LIBCPP)
endif() endif()
if(NOT ENTT_HAS_LIBCPP) if(NOT ENTT_HAS_LIBCPP)
message(VERBOSE "The option ENTT_USE_LIBCPP is set but libc++ is not available.") 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()
endif() endif()
@@ -67,19 +70,27 @@ if(ENTT_USE_SANITIZER)
endif() endif()
if(NOT ENTT_HAS_SANITIZER) if(NOT ENTT_HAS_SANITIZER)
message(VERBOSE "The option ENTT_USE_SANITIZER is set but sanitizer support is not available.") 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()
if(ENTT_USE_CLANG_TIDY)
find_program(ENTT_CLANG_TIDY_EXECUTABLE "clang-tidy")
if(NOT ENTT_CLANG_TIDY_EXECUTABLE)
message(VERBOSE "The option ENTT_USE_CLANG_TIDY is set but clang-tidy executable is not available.")
endif() endif()
endif() endif()
#
# Add EnTT target # 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) include(GNUInstallDirs)
@@ -93,10 +104,100 @@ target_include_directories(
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
) )
target_compile_features(EnTT INTERFACE cxx_std_20) target_compile_features(EnTT INTERFACE cxx_std_17)
if(ENTT_HAS_LIBCPP) if(ENTT_INCLUDE_HEADERS)
target_compile_options(EnTT BEFORE INTERFACE -stdlib=libc++) 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/sigh_storage_mixin.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/utility.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/view.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/ctx.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/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/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() endif()
if(ENTT_HAS_SANITIZER) if(ENTT_HAS_SANITIZER)
@@ -104,259 +205,125 @@ if(ENTT_HAS_SANITIZER)
target_link_libraries(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() endif()
if(ENTT_CLANG_TIDY_EXECUTABLE) if(ENTT_HAS_LIBCPP)
set(ENTT_CLANG_TIDY_OPTIONS ";--config-file=${EnTT_SOURCE_DIR}/.clang-tidy;--header-filter=${EnTT_SOURCE_DIR}/src/entt/.*") target_compile_options(EnTT BEFORE INTERFACE -stdlib=libc++)
if(MSVC AND NOT (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang"))
set(ENTT_CLANG_TIDY_OPTIONS "${ENTT_CLANG_TIDY_OPTIONS};--extra-arg=/EHsc;--extra-arg=/wd4996")
endif()
set(CMAKE_CXX_CLANG_TIDY "${ENTT_CLANG_TIDY_EXECUTABLE}${ENTT_CLANG_TIDY_OPTIONS}")
endif()
# Add EnTT goodies
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_HEADERS)
set(
HEADERS_FILES
config/config.h
config/macro.h
config/version.h
container/dense_map.hpp
container/dense_set.hpp
container/table.hpp
container/fwd.hpp
core/algorithm.hpp
core/any.hpp
core/bit.hpp
core/compressed_pair.hpp
core/concepts.hpp
core/enum.hpp
core/family.hpp
core/fwd.hpp
core/hashed_string.hpp
core/ident.hpp
core/iterator.hpp
core/memory.hpp
core/monostate.hpp
core/ranges.hpp
core/tuple.hpp
core/type_info.hpp
core/type_traits.hpp
core/utility.hpp
entity/component.hpp
entity/entity.hpp
entity/fwd.hpp
entity/group.hpp
entity/handle.hpp
entity/mixin.hpp
entity/helper.hpp
entity/organizer.hpp
entity/ranges.hpp
entity/registry.hpp
entity/runtime_view.hpp
entity/snapshot.hpp
entity/sparse_set.hpp
entity/storage.hpp
entity/view.hpp
graph/adjacency_matrix.hpp
graph/dot.hpp
graph/flow.hpp
graph/fwd.hpp
locator/locator.hpp
meta/adl_pointer.hpp
meta/container.hpp
meta/context.hpp
meta/factory.hpp
meta/fwd.hpp
meta/meta.hpp
meta/node.hpp
meta/pointer.hpp
meta/policy.hpp
meta/range.hpp
meta/resolve.hpp
meta/template.hpp
meta/type_traits.hpp
meta/utility.hpp
poly/fwd.hpp
poly/poly.hpp
process/fwd.hpp
process/process.hpp
process/scheduler.hpp
resource/cache.hpp
resource/fwd.hpp
resource/loader.hpp
resource/resource.hpp
signal/delegate.hpp
signal/dispatcher.hpp
signal/emitter.hpp
signal/fwd.hpp
signal/sigh.hpp
stl/functional.hpp
stl/iterator.hpp
stl/memory.hpp
tools/davey.hpp
entt.hpp
fwd.hpp
tools.hpp
)
list(TRANSFORM HEADERS_FILES APPEND ">" OUTPUT_VARIABLE HEADERS_BUILD_INTERFACE)
list(TRANSFORM HEADERS_BUILD_INTERFACE PREPEND "$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/")
list(TRANSFORM HEADERS_FILES APPEND ">" OUTPUT_VARIABLE HEADERS_INSTALL_INTERFACE)
list(TRANSFORM HEADERS_INSTALL_INTERFACE PREPEND "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/entt/")
target_sources(EnTT INTERFACE ${HEADERS_BUILD_INTERFACE} ${HEADERS_INSTALL_INTERFACE})
endif() endif()
if(ENTT_INCLUDE_NATVIS) #
if(MSVC) # Install pkg-config file
set(ENTT_HAS_NATVIS TRUE CACHE BOOL "" FORCE) #
mark_as_advanced(ENTT_HAS_NATVIS)
endif()
if(NOT ENTT_HAS_NATVIS) set(EnTT_PKGCONFIG ${CMAKE_CURRENT_BINARY_DIR}/entt.pc)
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()
if(ENTT_HAS_NATVIS) configure_file(
set( ${EnTT_SOURCE_DIR}/cmake/in/entt.pc.in
NATVIS_FILES ${EnTT_PKGCONFIG}
config.natvis @ONLY
container.natvis )
core.natvis
entity.natvis
graph.natvis
locator.natvis
meta.natvis
poly.natvis
process.natvis
resource.natvis
signal.natvis
)
list(TRANSFORM NATVIS_FILES APPEND ">" OUTPUT_VARIABLE NATVIS_BUILD_INTERFACE) install(
list(TRANSFORM NATVIS_BUILD_INTERFACE PREPEND "$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/natvis/") FILES ${EnTT_PKGCONFIG}
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig
)
list(TRANSFORM NATVIS_FILES APPEND ">" OUTPUT_VARIABLE NATVIS_INSTALL_INTERFACE) #
list(TRANSFORM NATVIS_INSTALL_INTERFACE PREPEND "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/entt/natvis/") # Install EnTT
#
target_sources(EnTT INTERFACE ${NATVIS_BUILD_INTERFACE} ${NATVIS_INSTALL_INTERFACE}) include(CMakePackageConfigHelpers)
endif()
# Install EnTT and all related files install(
TARGETS EnTT
EXPORT EnTTTargets
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
option(ENTT_INSTALL "Install EnTT and all related files." OFF) write_basic_package_version_file(
EnTTConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY AnyNewerVersion
)
if(ENTT_INSTALL) configure_package_config_file(
# Install pkg-config file ${EnTT_SOURCE_DIR}/cmake/in/EnTTConfig.cmake.in
EnTTConfig.cmake
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/EnTT/cmake
)
include(JoinPaths) export(
EXPORT EnTTTargets
FILE ${CMAKE_CURRENT_BINARY_DIR}/EnTTTargets.cmake
NAMESPACE EnTT::
)
set(EnTT_PKGCONFIG ${CMAKE_CURRENT_BINARY_DIR}/entt.pc) install(
EXPORT EnTTTargets
FILE EnTTTargets.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/EnTT/cmake
NAMESPACE EnTT::
)
join_paths(EnTT_PKGCONFIG_INCLUDEDIR "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}") install(
FILES
${PROJECT_BINARY_DIR}/EnTTConfig.cmake
${PROJECT_BINARY_DIR}/EnTTConfigVersion.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/EnTT/cmake
)
configure_file( install(DIRECTORY src/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
${EnTT_SOURCE_DIR}/cmake/in/entt.pc.in
${EnTT_PKGCONFIG}
@ONLY
)
install( export(PACKAGE EnTT)
FILES ${EnTT_PKGCONFIG}
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig
)
# Install EnTT #
# Tests
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}
FILES_MATCHING
PATTERN "*.h"
PATTERN "*.hpp"
PATTERN "*.natvis"
)
export(PACKAGE EnTT)
endif()
# Tests and testbed
option(ENTT_BUILD_TESTING "Enable building tests." OFF) option(ENTT_BUILD_TESTING "Enable building tests." OFF)
option(ENTT_BUILD_TESTBED "Enable building testbed." OFF)
if(ENTT_BUILD_TESTING OR ENTT_BUILD_TESTBED) if(ENTT_BUILD_TESTING)
set(ENTT_ID_TYPE std::uint32_t CACHE STRING "Type of identifiers to use for tests and testbed") option(ENTT_FIND_GTEST_PACKAGE "Enable finding gtest package." OFF)
set(ENTT_CXX_STD cxx_std_20 CACHE STRING "C++ standard revision to use for tests and testbed") 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)
# Tests and tesetbed do not work together because SDL gets confused with EnTT tests set(ENTT_ID_TYPE std::uint32_t CACHE STRING "Type of identifiers to use for the tests")
if(ENTT_BUILD_TESTING) set(ENTT_CXX_STD cxx_std_17 CACHE STRING "C++ standard revision to use for the tests")
option(ENTT_FIND_GTEST_PACKAGE "Enable finding gtest package." OFF)
include(CTest)
option(ENTT_BUILD_BENCHMARK "Build benchmark." OFF) enable_testing()
option(ENTT_BUILD_EXAMPLE "Build examples." OFF) add_subdirectory(test)
option(ENTT_BUILD_LIB "Build lib tests." OFF)
option(ENTT_BUILD_SNAPSHOT "Build snapshot test with Cereal." OFF)
include(CTest)
enable_testing()
add_subdirectory(test)
elseif(ENTT_BUILD_TESTBED)
add_subdirectory(testbed)
endif()
endif() endif()
#
# Documentation # Documentation
#
option(ENTT_BUILD_DOCS "Enable building with documentation." OFF) option(ENTT_BUILD_DOCS "Enable building with documentation." OFF)
if(ENTT_BUILD_DOCS) if(ENTT_BUILD_DOCS)
add_subdirectory(docs) find_package(Doxygen 1.8)
if(DOXYGEN_FOUND)
add_subdirectory(docs)
endif()
endif() endif()
#
# AOB
#
add_custom_target(
aob
SOURCES
.github/workflows/build.yml
.github/workflows/coverage.yml
.github/workflows/deploy.yml
.github/workflows/sanitizer.yml
.github/FUNDING.yml
AUTHORS
CONTRIBUTING.md
LICENSE
README.md
TODO
)

View File

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

View File

@@ -1,4 +0,0 @@
module(name = "entt")
bazel_dep(name = "rules_cc", version = "0.0.8")
bazel_dep(name = "bazel_skylib", version = "1.4.2")

127
README.md
View File

@@ -1,18 +1,18 @@
![EnTT: Gaming meets modern C++](https://user-images.githubusercontent.com/1812216/103550016-90752280-4ea8-11eb-8667-12ed2219e137.png) ![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) [![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) [![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) [![Try online](https://img.shields.io/badge/try-online-brightgreen)](https://godbolt.org/z/zxW73f)
[![Documentation](https://img.shields.io/badge/docs-doxygen-blue)](https://skypjack.github.io/entt/) [![Documentation](https://img.shields.io/badge/docs-docsforge-blue)](http://entt.docsforge.com/)
[![Vcpkg port](https://img.shields.io/vcpkg/v/entt)](https://vcpkg.link/ports/entt)
[![Conan Center](https://img.shields.io/conan/v/entt)](https://conan.io/center/recipes/entt)
[![Gitter chat](https://badges.gitter.im/skypjack/entt.png)](https://gitter.im/skypjack/entt) [![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) [![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 > `EnTT` has been a dream so far, we haven't found a single bug to date and it's
> super easy to work with > super easy to work with
>
> -- Every EnTT User Ever
`EnTT` is a header-only, tiny and easy to use library for game programming and `EnTT` is a header-only, tiny and easy to use library for game programming and
much more written in **modern C++**.<br/> much more written in **modern C++**.<br/>
@@ -21,7 +21,7 @@ in [**Minecraft**](https://minecraft.net/en-us/attribution/) by Mojang, the
[**ArcGIS Runtime SDKs**](https://developers.arcgis.com/arcgis-runtime/) by Esri [**ArcGIS Runtime SDKs**](https://developers.arcgis.com/arcgis-runtime/) by Esri
and the amazing [**Ragdoll**](https://ragdolldynamics.com/).<br/> 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 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: add the [#entt](https://github.com/topics/entt) tag to your _topics_! :+1:
--- ---
@@ -36,8 +36,7 @@ Don't forget to check the
there. there.
Do you want to support `EnTT`? Consider becoming a Do you want to support `EnTT`? Consider becoming a
[**sponsor**](https://github.com/users/skypjack/sponsorship) or making a [**sponsor**](https://github.com/users/skypjack/sponsorship).
donation via [**PayPal**](https://www.paypal.me/skypjack).<br/>
Many thanks to [these people](https://skypjack.github.io/sponsorship/) and Many thanks to [these people](https://skypjack.github.io/sponsorship/) and
**special** thanks to: **special** thanks to:
@@ -49,7 +48,7 @@ Many thanks to [these people](https://skypjack.github.io/sponsorship/) and
* [Introduction](#introduction) * [Introduction](#introduction)
* [Code Example](#code-example) * [Code Example](#code-example)
* [Motivation](#motivation) * [Motivation](#motivation)
* [Benchmark](#benchmark) * [Performance](#performance)
* [Integration](#integration) * [Integration](#integration)
* [Requirements](#requirements) * [Requirements](#requirements)
* [CMake](#cmake) * [CMake](#cmake)
@@ -61,6 +60,9 @@ Many thanks to [these people](https://skypjack.github.io/sponsorship/) and
* [EnTT in Action](#entt-in-action) * [EnTT in Action](#entt-in-action)
* [Contributors](#contributors) * [Contributors](#contributors)
* [License](#license) * [License](#license)
<!--
@endcond TURN_OFF_DOXYGEN
-->
# Introduction # Introduction
@@ -76,16 +78,14 @@ 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: Here is a brief, yet incomplete list of what it offers today:
* Built-in **RTTI system** mostly similar to the standard one. * Built-in **RTTI system** mostly similar to the standard one.
* A `constexpr` utility for human-readable **resource names**. * A `constexpr` utility for human readable **resource names**.
* Minimal **configuration system** built using the monostate pattern. * Minimal **configuration system** built using the monostate pattern.
* Incredibly fast **entity-component system** with its own _pay for what you * Incredibly fast **entity-component system** with its own _pay for what you
use_ policy, unconstrained component types with optional pointer stability and use_ policy.
hooks for storage customization.
* Views and groups to iterate entities and components and allow different access * Views and groups to iterate entities and components and allow different access
patterns, from **perfect SoA** to fully random. patterns, from **perfect SoA** to fully random.
* A lot of **facilities** built on top of the entity-component system to help * A lot of **facilities** built on top of the entity-component system to help
the users and avoid reinventing the wheel. 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. * The smallest and most basic implementation of a **service locator** ever seen.
* A built-in, non-intrusive and macro-free runtime **reflection system**. * A built-in, non-intrusive and macro-free runtime **reflection system**.
* **Static polymorphism** made simple and within everyone's reach. * **Static polymorphism** made simple and within everyone's reach.
@@ -97,7 +97,7 @@ Here is a brief, yet incomplete list of what it offers today:
* And **much more**! Check out the * And **much more**! Check out the
[**wiki**](https://github.com/skypjack/entt/wiki). [**wiki**](https://github.com/skypjack/entt/wiki).
Consider this list a work in progress as well as the project. The whole API is Consider these lists 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/> 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 Please, do note that all tools are also DLL-friendly now and run smoothly across
boundaries. boundaries.
@@ -170,28 +170,37 @@ 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 _competitors_, lower memory usage in the average case, a really good API and an
amazing set of features. And even more, of course. amazing set of features. And even more, of course.
## Benchmark ## Performance
For what it's worth, you'll **never** see me trying to make other projects look The proposed entity-component system is incredibly fast to iterate entities and
bad or offer dubious comparisons just to make this library seem cooler.<br/> components, this is a fact. Some compilers make a lot of optimizations because
I leave this activity to others, if they enjoy it (and it seems that some people of how `EnTT` works, some others aren't that good. In general, if we consider
actually like it). I prefer to make better use of my time. 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 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 enable compiler optimizations, otherwise it would make little sense) by setting
the `ENTT_BUILD_BENCHMARK` option of `CMake` to `ON`, then evaluate yourself the `ENTT_BUILD_BENCHMARK` option of `CMake` to `ON`, then evaluate yourself
whether you're satisfied with the results or not. whether you're satisfied with the results or not.
There are also a lot of projects out there that use `EnTT` as a basis for 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 comparison (this should already tell you a lot). Many of these benchmarks are
completely wrong, many others are simply incomplete, good at omitting some completely wrong, many others are simply incomplete, good at omitting some
information and using the wrong function to compare a given feature. Certainly 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 there are also good ones but they age quickly if nobody updates them, especially
when the library they are dealing with is actively developed.<br/> when the library they are dealing with is actively developed.
Out of all of them, [this](https://github.com/abeimler/ecs_benchmark) seems like
the most up-to-date project and also covers a certain number of libraries. I The choice to use `EnTT` should be based on its carefully designed API, its
can't say exactly whether `EnTT` is used correctly or not. However, even if used set of features and the general performance, **not** because some single
poorly, it should still give the reader an idea of where it's going to operate. 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 # Integration
@@ -217,12 +226,12 @@ the include paths.
## Requirements ## Requirements
To be able to use `EnTT`, users must provide a full-featured compiler that To be able to use `EnTT`, users must provide a full-featured compiler that
supports at least C++20.<br/> supports at least C++17.<br/>
The requirements below are mandatory to compile the tests and to extract the The requirements below are mandatory to compile the tests and to extract the
documentation: documentation:
* `CMake` version 3.28 or later. * `CMake` version 3.7 or later.
* `Doxygen` version 1.14 or later. * `Doxygen` version 1.8 or later.
Alternatively, [Bazel](https://bazel.build) is also supported as a build system 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/> (credits to [zaucy](https://github.com/zaucy) who offered to maintain it).<br/>
@@ -232,26 +241,20 @@ build system of the library.
## CMake ## CMake
To use `EnTT` from a `CMake` project, just link an existing target to the To use `EnTT` from a `CMake` project, just link an existing target to the
`EnTT::EnTT` alias. `EnTT::EnTT` alias.<br/>
The library offers everything you need for locating (as in `find_package`), The library offers everything you need for locating (as in `find_package`),
embedding (as in `add_subdirectory`), fetching (as in `FetchContent`) or using 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/> it in many of the ways that you can think of and that involve `CMake`.<br/>
Covering all possible cases would require a treatise and not a simple README Covering all possible cases would require a treaty and not a simple README file,
file, but I'm confident that anyone reading this section also knows what it's but I'm confident that anyone reading this section also knows what it's about
about and can use `EnTT` from a `CMake` project without problems. and can use `EnTT` from a `CMake` project without problems.
Note that all `install` calls are guarded by the `ENTT_INSTALL` option to allow
using `EnTT` as a submodule without conflicting with user logic.<br/>
It is therefore necessary to set the option to true to take advantage of the
installation logic provided by this library.
## Natvis support ## Natvis support
When using `CMake`, just enable the option `ENTT_INCLUDE_NATVIS` and enjoy When using `CMake`, just enable the option `ENTT_INCLUDE_NATVIS` and enjoy
it.<br/> it.<br/>
Otherwise, most of the tools are covered via Natvis and all files can be found Otherwise, most of the tools are covered via Natvis and all files can be found
in the `natvis` subdirectory, divided by module.<br/> in the `natvis` directory, divided by module.<br/>
If you spot errors or have suggestions, any contribution is welcome! If you spot errors or have suggestions, any contribution is welcome!
## Packaging Tools ## Packaging Tools
@@ -319,18 +322,6 @@ If you spot errors or have suggestions, any contribution is welcome!
[documentation](https://build2.org/build2-toolchain/doc/build2-toolchain-intro.xhtml#guide-repositories) [documentation](https://build2.org/build2-toolchain/doc/build2-toolchain-intro.xhtml#guide-repositories)
for more details. for more details.
* [`bzlmod`](https://bazel.build/external/overview#bzlmod), Bazel's external
dependency management system.<br/>
To use the [`entt`](https://registry.bazel.build/modules/entt) module in a
`bazel` project, add the following to your `MODULE.bazel` file:
```starlark
bazel_dep(name = "entt", version = "3.16.0")
```
EnTT will now be available as `@entt` (short for `@entt//:entt`) to be used
in your `cc_*` rule `deps`.
Consider this list a work in progress and help me to make it longer if you like. Consider this list a work in progress and help me to make it longer if you like.
## pkg-config ## pkg-config
@@ -348,22 +339,32 @@ The documentation is based on [doxygen](http://www.doxygen.nl/). To build it:
$ cmake .. -DENTT_BUILD_DOCS=ON $ cmake .. -DENTT_BUILD_DOCS=ON
$ make $ make
The API reference is created in HTML format in the `build/docs/html` directory. The API reference will be created in HTML format within the directory
To navigate it with your favorite browser: `build/docs/html`. To navigate it with your favorite browser:
$ cd build $ cd build
$ your_favorite_browser docs/html/index.html $ your_favorite_browser docs/html/index.html
<!--
@cond TURN_OFF_DOXYGEN
-->
The same version is also available [online](https://skypjack.github.io/entt/) The same version is also available [online](https://skypjack.github.io/entt/)
for the latest release, that is the last stable tag.<br/> 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 Moreover, there exists a [wiki](https://github.com/skypjack/entt/wiki) dedicated
to the project where users can find all related documentation pages. to the project where users can find all related documentation pages.
<!--
@endcond TURN_OFF_DOXYGEN
-->
# Tests # Tests
To compile and run the tests, `EnTT` requires *googletest*.<br/> To compile and run the tests, `EnTT` requires *googletest*.<br/>
`cmake` downloads and compiles the library before compiling anything else. In `cmake` will download and compile the library before compiling anything else.
order to build the tests, set the `CMake` option `ENTT_BUILD_TESTING` to `ON`. In order to build the tests, set the `CMake` option `ENTT_BUILD_TESTING` to
`ON`.
To build the most basic set of tests: To build the most basic set of tests:
@@ -374,6 +375,9 @@ To build the most basic set of tests:
Note that benchmarks are not part of this set. Note that benchmarks are not part of this set.
<!--
@cond TURN_OFF_DOXYGEN
-->
# EnTT in Action # EnTT in Action
`EnTT` is widely used in private and commercial applications. I cannot even `EnTT` is widely used in private and commercial applications. I cannot even
@@ -391,7 +395,7 @@ open an issue or a PR and I'll be glad to add them to the list.
# Contributors # Contributors
Requests for features, PRs, suggestions and feedback are highly appreciated. 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 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 experience or you do want to get part of the project for some other reason, feel
@@ -401,15 +405,18 @@ 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. 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 If you decide to participate, please see the guidelines for
[contributing](https://github.com/skypjack/entt/blob/master/CONTRIBUTING.md) [contributing](CONTRIBUTING.md) before to create issues or pull
before to create issues or pull requests.<br/> requests.<br/>
Take also a look at the Take also a look at the
[contributors list](https://github.com/skypjack/entt/blob/master/AUTHORS) to [contributors list](https://github.com/skypjack/entt/blob/master/AUTHORS) to
know who has participated so far. know who has participated so far.
<!--
@endcond TURN_OFF_DOXYGEN
-->
# License # License
Code and documentation Copyright (c) 2017-2026 Michele Caini.<br/> Code and documentation Copyright (c) 2017-2022 Michele Caini.<br/>
Colorful logo Copyright (c) 2018-2021 Richard Caseres. Colorful logo Copyright (c) 2018-2021 Richard Caseres.
Code released under Code released under

54
TODO
View File

@@ -1,38 +1,26 @@
* 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
* add examples (and credits) from @alanjfs :)
EXAMPLES EXAMPLES
* filter on runtime values/variables (not only types) * filter on runtime values/variables (not only types)
* support to polymorphic types (see #859) * support to polymorphic types (see #859)
DOC: WIP:
* custom storage/view * view/group: no storage_traits dependency -> use storage instead of components for the definition
* update entity doc when the storage based model is in place * basic_storage::bind for cross-registry setups
* in-place O(1) release/destroy for non-orphaned entities, out-of-sync model * uses-allocator construction: any (with allocator support), poly, ...
* view: single vs multi type views are no longer a thing actually * process scheduler: reviews, use free lists internally
* bump entities, reserved bits on identifiers * iterator based try_emplace vs try_insert for perf reasons
* dedicated entity storage, in-place O(1) release/destroy for non-orphaned entities, out-of-sync model
* entity-only and exclude-only views
* custom allocators all over (sigh storage mixin, registry, ...)
* consider removing ENTT_NOEXCEPT, use ENTT_NOEXCEPT_IF (or noexcept(...)) as appropriate in any case (ie make compressed_pair conditionally noexcept)
* add test for maximum number of entities reached
TODO: WIP:
* review all NOLINT * add user data to type_info
* bring nested groups back in place (see bd34e7f) * write documentation for custom storages and views!!
* work stealing job system (see #100) + mt scheduler based on const awareness for types * make runtime views use opaque storage and therefore return also elements.
* combine version-mask-vs-version-bits tricks with reserved bits to allow things like enabling/disabling * entity-aware observer, add observer functions aside observer class
* self contained entity traits to avoid explicit specializations (ie enum constants) * deprecate non-owning groups in favor of owning views and view packs, introduce lazy owning views
* auto type info data from types if present
* storage entity: fast range-push from above
* table: pop back to support swap and pop, single column access, empty type optimization
* suppress -Wself-move on CI with g++13
* runtime types support for meta for types that aren't backed by C++ types
* built-in no-pagination storage - no_pagination page size as limits::max
* any cdynamic to support const ownership construction
* allow passing arguments to meta setter/getter (we can fallback on meta invoke probably)
* meta non-const allow_cast overloads: (const int &) to (int &) is not allowed, but (const int &) to (double &) is allowed (support only for convertibles)
* review build process for testbed (i.e. tests first due to SDL)
* use unique_ptr or any for meta_custom_node
* paged vector as a standalone class
* resource: shared_from_this?
* finish the imgui viewer/editor!
* archetype-like a-là EnTT support (see my own notes)
* organizer: view/storage only based model, no registry
* introduce a way to inject stl from outside too
* redesign snapshot as a whole
* explore "runtime" mode for hashed string where the source is copied internally
* storage: shrink_to_fit does not work with reentrant destructor?
* test trivially_destructible optimization

1
WORKSPACE Normal file
View File

@@ -0,0 +1 @@
workspace(name = "com_github_skypjack_entt")

View File

@@ -1 +0,0 @@
# SEE MODULE.bazel

View File

View File

@@ -1,13 +0,0 @@
load("@bazel_skylib//lib:selects.bzl", "selects")
COPTS = selects.with_or({
("//conditions:default", "@rules_cc//cc/compiler:clang", "@rules_cc//cc/compiler:gcc", "@rules_cc//cc/compiler:mingw-gcc"): [
"-std=c++20",
"-w",
],
("@rules_cc//cc/compiler:msvc-cl", "@rules_cc//cc/compiler:clang-cl"): [
"/std:c++20",
"/permissive-",
"/w",
],
})

View File

@@ -1,5 +1,5 @@
prefix=@CMAKE_INSTALL_PREFIX@ prefix=@CMAKE_INSTALL_PREFIX@
includedir=@EnTT_PKGCONFIG_INCLUDEDIR@ includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
Name: EnTT Name: EnTT
Description: Gaming meets modern C++ Description: Gaming meets modern C++

View File

@@ -1,23 +0,0 @@
# 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 Pythons 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()

View File

@@ -1,9 +1,9 @@
cmake_minimum_required(VERSION 3.28) cmake_minimum_required(VERSION 3.7.2)
project(test_package) project(test_package)
set(CMAKE_VERBOSE_MAKEFILE TRUE) set(CMAKE_VERBOSE_MAKEFILE TRUE)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)

View File

@@ -15,7 +15,7 @@ void update(entt::registry &registry) {
auto view = registry.view<position, velocity>(); auto view = registry.view<position, velocity>();
for(auto entity: view) { for(auto entity: view) {
// gets only the elements that are going to be used ... // gets only the components that are going to be used ...
auto &vel = view.get<velocity>(entity); auto &vel = view.get<velocity>(entity);
@@ -28,7 +28,7 @@ void update(entt::registry &registry) {
void update(std::uint64_t dt, entt::registry &registry) { void update(std::uint64_t dt, entt::registry &registry) {
registry.view<position, velocity>().each([dt](auto &pos, auto &vel) { registry.view<position, velocity>().each([dt](auto &pos, auto &vel) {
// gets all the elements of the view at once ... // gets all the components of the view at once ...
pos.x += vel.dx * dt; pos.x += vel.dx * dt;
pos.y += vel.dy * dt; pos.y += vel.dy * dt;

View File

@@ -1,54 +1,40 @@
#
# Doxygen configuration (documentation) # Doxygen configuration (documentation)
#
find_package(Doxygen 1.14) 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})
if(DOXYGEN_FOUND) configure_file(doxy.in doxy.cfg @ONLY)
include(FetchContent)
FetchContent_Declare( add_custom_target(
doxygen-awesome-css docs ALL
GIT_REPOSITORY https://github.com/jothepro/doxygen-awesome-css COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxy.cfg
GIT_TAG main WORKING_DIRECTORY ${EnTT_SOURCE_DIR}
GIT_SHALLOW 1 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
)
FetchContent_MakeAvailable(doxygen-awesome-css) install(
DIRECTORY ${DOXY_OUTPUT_DIRECTORY}/html
set(DOXY_SOURCE_DIRECTORY ${EnTT_SOURCE_DIR}/src) DESTINATION share/${PROJECT_NAME}-${PROJECT_VERSION}/
set(DOXY_CSS_DIRECTORY ${doxygen-awesome-css_SOURCE_DIR}) )
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
md/config.md
md/container.md
md/core.md
md/entity.md
md/faq.md
md/graph.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
doxy.in
)
if(ENTT_INSTALL)
install(
DIRECTORY ${DOXY_OUTPUT_DIRECTORY}/html
DESTINATION share/${PROJECT_NAME}-${PROJECT_VERSION}/
)
endif()
endif()

5
docs/dox/extra.dox Normal file
View File

@@ -0,0 +1,5 @@
/**
* @namespace entt
*
* @brief `EnTT` default namespace.
*/

File diff suppressed because it is too large Load Diff

View File

@@ -1,63 +1,57 @@
# Crash Course: configuration # Crash Course: configuration
<!--
@cond TURN_OFF_DOXYGEN
-->
# Table of Contents # Table of Contents
* [Introduction](#introduction) * [Introduction](#introduction)
* [Definitions](#definitions) * [Definitions](#definitions)
* [ENTT_USE_STL](#entt_use_stl) * [ENTT_NOEXCEPTION](#entt_noexcept)
* [ENTT_NO_EXCEPTION](#entt_no_exception)
* [ENTT_USE_ATOMIC](#entt_use_atomic) * [ENTT_USE_ATOMIC](#entt_use_atomic)
* [ENTT_ID_TYPE](#entt_id_type) * [ENTT_ID_TYPE](#entt_id_type)
* [ENTT_SPARSE_PAGE](#entt_sparse_page) * [ENTT_SPARSE_PAGE](#entt_sparse_page)
* [ENTT_PACKED_PAGE](#entt_packed_page) * [ENTT_PACKED_PAGE](#entt_packed_page)
* [ENTT_ASSERT](#entt_assert) * [ENTT_ASSERT](#entt_assert)
* [ENTT_ASSERT_CONSTEXPR](#entt_assert_constexpr)
* [ENTT_DISABLE_ASSERT](#entt_disable_assert) * [ENTT_DISABLE_ASSERT](#entt_disable_assert)
* [ENTT_NO_ETO](#entt_no_eto) * [ENTT_NO_ETO](#entt_no_eto)
* [ENTT_NO_MIXIN](#entt_no_mixin)
* [ENTT_STANDARD_CPP](#entt_standard_cpp) * [ENTT_STANDARD_CPP](#entt_standard_cpp)
* [Configuration injection](#configuration-injection)
<!--
@endcond TURN_OFF_DOXYGEN
-->
# Introduction # Introduction
`EnTT` has become almost completely customizable over time, in many `EnTT` doesn't offer many hooks for customization but it certainly offers
respects. These variables are just one of the many ways to customize how it some.<br/>
works.<br/>
In the vast majority of cases, users will have no interest in changing the 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 default parameters. For all other cases, the list of possible configurations
with which it is possible to adjust the behavior of the library at runtime can with which it's possible to adjust the behavior of the library at runtime can be
be found below. found below.
# Definitions # Definitions
All options are intended as parameters to the compiler (or user-defined macros All options are intended as parameters to the compiler (or user-defined macros
within the compilation units, if preferred).<br/> within the compilation units, if preferred).<br/>
Each parameter can result in internal library definitions. It is not recommended 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 to try to also modify these definitions, since there is no guarantee that they
will remain stable over time unlike the options below. will remain stable over time unlike the options below.
## ENTT_USE_STL ## ENTT_NOEXCEPTION
Intended for testing purposes, it forces the use of built-in replacements of This parameter can be used to switch off exception handling in `EnTT`.<br/>
some parts of the standard library that aren't always available otherwise.<br/> To do this, simply define the variable without assigning any value to it. This
`EnTT` _detects_ these cases on its own, and users should never define this is roughly equivalent to setting the compiler flag `-ff-noexceptions`.
variable explicitly. However, it's still possible if desired.
## ENTT_NO_EXCEPTION
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 ## ENTT_USE_ATOMIC
In general, `EnTT` does not offer primitives to support multi-threading. Many of 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 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/> the user is the only one who knows if and when a synchronization point is
However, some internal static data shared between threads should be atomic when required.<br/>
using `EnTT` from multiple threads, even when dealing with local storage. Define However, some features aren't easily accessible to users and can be made
this macro without assigning any value to it to get the job done. thread-safe by means of this definition.
## ENTT_ID_TYPE ## ENTT_ID_TYPE
@@ -68,77 +62,50 @@ default type if necessary.
## ENTT_SPARSE_PAGE ## ENTT_SPARSE_PAGE
It is known that the ECS module of `EnTT` is based on _sparse sets_. What is It's known that the ECS module of `EnTT` is based on _sparse sets_. What is less
less known perhaps is that the sparse arrays are paged to reduce memory known perhaps is that the sparse arrays are paged to reduce memory usage.<br/>
usage.<br/>
Default size of pages (that is, the number of elements they contain) is 4096 but Default size of pages (that is, the number of elements they contain) is 4096 but
users can adjust it if appropriate. In all cases, the chosen value **must** be a users can adjust it if appropriate. In all case, the chosen value **must** be a
power of 2. power of 2.
## ENTT_PACKED_PAGE ## ENTT_PACKED_PAGE
As it happens with sparse arrays, packed arrays are also paginated. However, in Similar to sparse arrays, packed arrays of components are paginated as well. In
this case the aim is not to reduce memory usage but to have pointer stability However, int this case the aim isn't to reduce memory usage but to have pointer
upon component creation.<br/> stability upon component creation.<br/>
Default size of pages (that is, the number of elements they contain) is 1024 but Default size of pages (that is, the number of elements they contain) is 1024 but
users can adjust it if appropriate. In all cases, the chosen value **must** be a users can adjust it if appropriate. In all case, the chosen value **must** be a
power of 2. power of 2.
## ENTT_ASSERT ## ENTT_ASSERT
For performance reasons, `EnTT` does not use exceptions or any other control For performance reasons, `EnTT` doesn't use exceptions or any other control
structures. In fact, it offers many features that result in undefined behavior structures. In fact, it offers many features that result in undefined behavior
if not used correctly.<br/> if not used correctly.<br/>
To get around this, the library relies on a lot of asserts for the purpose of 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 detecting errors in debug builds. By default, it uses `assert` internally, but
are allowed to overwrite its behavior by setting this variable. users are allowed to overwrite its behavior by setting this variable.
### ENTT_ASSERT_CONSTEXPR
Usually, an assert within a `constexpr` function is not 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 ### ENTT_DISABLE_ASSERT
Assertions may in turn affect performance to an extent when enabled. Whether 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 `ENTT_ASSERT` is redefined or not, all asserts can be disabled at once by means
be disabled at once by means of this definition.<br/> of this definition.<br/>
Note that `ENTT_DISABLE_ASSERT` takes precedence over the redefinition of the Note that `ENTT_DISABLE_ASSERT` takes precedence over the redefinition of
other variables and is therefore meant to disable all controls no matter what. `ENTT_ASSERT` and is therefore meant to disable all controls no matter what.
## ENTT_NO_ETO ## ENTT_NO_ETO
In order to reduce memory consumption and increase performance, empty types are In order to reduce memory consumption and increase performance, empty types are
never instantiated nor stored by the ECS module of `EnTT`.<br/> never stored by the ECS module of `EnTT`.<br/>
Use this variable to treat these types like all others and therefore to create a Use this variable to treat these types like all others and therefore to create a
dedicated storage for them. dedicated storage for them.
## ENTT_NO_MIXIN
`EnTT` automatically assigns mixins to all storage types to support signaling
when creating, destroying, and modifying elements.<br/>
Mixins can have a (most likely negligible) cost in terms of performance and
compilation time. If unwanted, this macro suppresses automatic generation.
## ENTT_STANDARD_CPP ## ENTT_STANDARD_CPP
`EnTT` mixes non-standard language features with others that are perfectly `EnTT` mixes non-standard language features with others that are perfectly
compliant to offer some of its functionalities.<br/> compliant to offer some of its functionalities.<br/>
This definition prevents the library from using non-standard techniques, that This definition will prevent the library from using non-standard techniques,
is, functionalities that are not fully compliant with the standard C++.<br/> 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 While there are no known portability issues at the time of this writing, this
should make the library fully portable anyway if needed. should make the library fully portable anyway if needed.
# Configuration injection
Configuration variables are provided via code or injected directly from the
outside via a dedicated file.<br/>
`EnTT` uses `__has_include` internally and looks for a specific path, namely
`<entt/ext/config.h>`. This can be provided by the user by setting the include
paths appropriately.<br/>
For example, `CMake` allows users to _bind_ additional include directories to a
target with `target_include_directories`. See the test suite, and in particular
the `config_ext` test for a practical example.

View File

@@ -1,30 +1,33 @@
# Crash Course: containers # Crash Course: containers
<!--
@cond TURN_OFF_DOXYGEN
-->
# Table of Contents # Table of Contents
* [Introduction](#introduction) * [Introduction](#introduction)
* [Containers](#containers) * [Containers](#containers)
* [Dense map](#dense-map) * [Dense map](#dense-map)
* [Dense set](#dense-set) * [Dense set](#dense-set)
* [Adaptors](#adaptors)
* [Table](#table) <!--
@endcond TURN_OFF_DOXYGEN
-->
# Introduction # Introduction
The standard C++ library offers a wide range of containers and adaptors already. The standard C++ library offers a wide range of containers and it's really
It is really difficult to do better (although it is very easy to do worse, as difficult to do better (although it's very easy to do worse, as many examples
many examples available online demonstrate).<br/> available online demonstrate).<br/>
`EnTT` does not try in any way to replace what is offered by the standard. Quite `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/> 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 functionalities by However, the library also tries to fill a gap in features and functionality by
making available some containers and adaptors initially developed for internal making available some containers initially developed for internal use.
use.
This section of the library is likely to grow larger over time. However, for the This section of the library is likely to grow larger over time. However, for the
moment it is quite small and mainly aimed at satisfying some internal moment it's quite small and mainly aimed at satisfying some internal needs.<br/>
needs.<br/> For all containers made available, full test coverage and stability over time is
For all containers and adaptors made available, full test coverage and stability guaranteed as usual.
over time is guaranteed as usual.
# Containers # Containers
@@ -37,7 +40,7 @@ The implementation is based on _sparse sets_ and each bucket is identified by an
implicit list within the packed array itself. implicit list within the packed array itself.
The interface is very close to its counterpart in the standard library, that is, The interface is very close to its counterpart in the standard library, that is,
the `std::unordered_map` class.<br/> `std::unordered_map`.<br/>
However, both local and non-local iterators returned by a dense map belong to 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 the input iterator category although they respectively model the concepts of a
_forward iterator_ type and a _random access iterator_ type.<br/> _forward iterator_ type and a _random access iterator_ type.<br/>
@@ -60,26 +63,5 @@ The implementation is based on _sparse sets_ and each bucket is identified by an
implicit list within the packed array itself. implicit list within the packed array itself.
The interface is in all respects similar to its counterpart in the standard The interface is in all respects similar to its counterpart in the standard
library, that is, the `std::unordered_set` class.<br/> library, that is, `std::unordered_set`.<br/>
However, this type of set also supports reverse iteration and therefore offers Therefore, there is no need to go into the API description.
all the functions necessary for the purpose (such as `rbegin` and `rend`).
# Adaptors
## Table
The `basic_table` class is a container adaptor which manages multiple sequential
containers together, treating them as different columns of the same table.<br/>
The `table` alias allows users to provide only the types to handle, using
`std::vector` as the default sequential container.
Only a small set of functions is provided, although very close to what the API
of the `std::vector` class offers.<br/>
The internal implementation is purposely supported by a tuple of containers
rather than a container of tuples. The purpose is to allow efficient access to
single columns and not just access to the entire data set of the table.
When a row is accessed, all data is returned in the form of a tuple containing
(possibly const) references to the elements of the row itself.<br/>
Similarly, when a table is iterated, tuples of references to table elements are
returned for each row.

View File

@@ -1,22 +1,21 @@
# Crash Course: core functionalities # Crash Course: core functionalities
<!--
@cond TURN_OFF_DOXYGEN
-->
# Table of Contents # Table of Contents
* [Introduction](#introduction) * [Introduction](#introduction)
* [Any as in any type](#any-as-in-any-type) * [Any as in any type](#any-as-in-any-type)
* [Small buffer optimization](#small-buffer-optimization) * [Small buffer optimization](#small-buffer-optimization)
* [Alignment requirement](#alignment-requirement) * [Alignment requirement](#alignment-requirement)
* [Bit](#bit)
* [Compressed pair](#compressed-pair) * [Compressed pair](#compressed-pair)
* [Enum as bitmask](#enum-as-bitmask) * [Enum as bitmask](#enum-as-bitmask)
* [Hashed strings](#hashed-strings) * [Hashed strings](#hashed-strings)
* [Wide characters](#wide-characters) * [Wide characters](wide-characters)
* [Conflicts](#conflicts) * [Conflicts](#conflicts)
* [Iterators](#iterators)
* [Input iterator pointer](#input-iterator-pointer)
* [Iota iterator](#iota-iterator)
* [Iterable adaptor](#iterable-adaptor)
* [Memory](#memory) * [Memory](#memory)
* [Power of two and fast modulus](#power-of-two-and-fast-modulus)
* [Allocator aware unique pointers](#allocator-aware-unique-pointers) * [Allocator aware unique pointers](#allocator-aware-unique-pointers)
* [Monostate](#monostate) * [Monostate](#monostate)
* [Type support](#type-support) * [Type support](#type-support)
@@ -28,7 +27,6 @@
* [Is applicable](#is-applicable) * [Is applicable](#is-applicable)
* [Constness as](#constness-as) * [Constness as](#constness-as)
* [Member class type](#member-class-type) * [Member class type](#member-class-type)
* [N-th argument](#n-th-argument)
* [Integral constant](#integral-constant) * [Integral constant](#integral-constant)
* [Tag](#tag) * [Tag](#tag)
* [Type list and value list](#type-list-and-value-list) * [Type list and value list](#type-list-and-value-list)
@@ -36,28 +34,32 @@
* [Compile-time generator](#compile-time-generator) * [Compile-time generator](#compile-time-generator)
* [Runtime generator](#runtime-generator) * [Runtime generator](#runtime-generator)
* [Utilities](#utilities) * [Utilities](#utilities)
<!--
@endcond TURN_OFF_DOXYGEN
-->
# Introduction # Introduction
`EnTT` comes with a bunch of core functionalities mostly used by the other parts `EnTT` comes with a bunch of core functionalities mostly used by the other parts
of the library.<br/> of the library itself.<br/>
Many of these tools are also useful in everyday work. Therefore, it is worth Hardly users will include these features in their code, but it's worth
describing them so as not to reinvent the wheel in case of need. describing what `EnTT` offers so as not to reinvent the wheel in case of need.
# Any as in any type # Any as in any type
`EnTT` offers its own `any` type. It may seem redundant considering that C++17 `EnTT` comes with its own `any` type. It may seem redundant considering that
introduced `std::any`, but it is not (hopefully).<br/> C++17 introduced `std::any`, but it is not (hopefully).<br/>
First of all, the _type_ returned by an `std::any` is a const reference to an First of all, the _type_ returned by an `std::any` is a const reference to an
`std::type_info`, an implementation defined class that is not something everyone `std::type_info`, an implementation defined class that's not something everyone
wants to see in a software. Furthermore, there is no way to bind it to the type wants to see in a software. Furthermore, there is no way to connect it with the
system of the library and therefore with its integrated RTTI support. type system of the library and therefore with its integrated RTTI support.<br/>
Note that this class is largely used internally by the library itself.
The `any` API is very similar to that of its most famous counterpart, mainly The API is very similar to that of its most famous counterpart, mainly because
because this class serves the same purpose of being an opaque container for any this class serves the same purpose of being an opaque container for any type of
type of value.<br/> value.<br/>
Instances also minimize the number of allocations by relying on a well known Instances of `any` also minimize the number of allocations by relying on a well
technique called _small buffer optimization_ and a fake vtable. known technique called _small buffer optimization_ and a fake vtable.
Creating an object of the `any` type, whether empty or not, is trivial: Creating an object of the `any` type, whether empty or not, is trivial:
@@ -68,29 +70,26 @@ entt::any empty{};
// a container for an int // a container for an int
entt::any any{0}; entt::any any{0};
// in place type construction // in place construction
entt::any in_place_type{std::in_place_type<int>, 42}; entt::any in_place{std::in_place_type<int>, 42};
// take ownership of already existing, dynamically allocated objects
entt::any in_place{std::in_place, std::make_unique<int>(42).release()};
``` ```
Alternatively, the `make_any` function serves the same purpose. It requires to Alternatively, the `make_any` function serves the same purpose but requires to
always be explicit about the type and does not support taking ownership: always be explicit about the type:
```cpp ```cpp
entt::any any = entt::make_any<int>(42); entt::any any = entt::make_any<int>(42);
``` ```
In all cases, the `any` class takes the burden of destroying the contained In both cases, the `any` class takes the burden of destroying the contained
element when required, regardless of the storage strategy used for the specific element when required, regardless of the storage strategy used for the specific
object.<br/> object.<br/>
Furthermore, an instance of `any` is not tied to an actual type. Therefore, the Furthermore, an instance of `any` isn't tied to an actual type. Therefore, the
wrapper is reconfigured when it is assigned a new object of a type other than wrapper is reconfigured when it's assigned a new object of a type other than
the one it contains. the one it contains.
There is also a way to directly assign a value to the variable contained by an There exists also a way to directly assign a value to the variable contained by
`entt::any`, without necessarily replacing it. This is especially useful when an `entt::any`, without necessarily replacing it. This is especially useful when
the object is used in _aliasing mode_, as described below: the object is used in _aliasing mode_, as described below:
```cpp ```cpp
@@ -104,24 +103,24 @@ any.assign(value);
any.assign(std::move(value)); any.assign(std::move(value));
``` ```
The `any` class performs a check on the type information and whether or not the The `any` class will also perform a check on the type information and whether or
original type was copy or move assignable, as appropriate.<br/> not the original type was copy or move assignable, as appropriate.<br/>
In all cases, the `assign` function returns a boolean value that is true in case In all cases, the `assign` function returns a boolean value to indicate the
of success and false otherwise. success or failure of the operation.
When in doubt about the type of object contained, the `type` member function When in doubt about the type of object contained, the `type` member function of
returns a const reference to the `type_info` associated with its element, or `any` returns a const reference to the `type_info` associated with its element,
`type_id<void>()` if the container is empty.<br/> or `type_id<void>()` if the container is empty. The type is also used internally
The type is also used internally when comparing two `any` objects: when comparing two `any` objects:
```cpp ```cpp
if(any == empty) { /* ... */ } if(any == empty) { /* ... */ }
``` ```
In this case, before proceeding with a comparison, it is verified that the In this case, before proceeding with a comparison, it's verified that the _type_
_type_ of the two objects is actually the same.<br/> of the two objects is actually the same.<br/>
Refer to the `EnTT` type system documentation for more details about how Refer to the `EnTT` type system documentation for more details about how
`type_info` works and the possible risks of a comparison. `type_info` works and on possible risks of a comparison.
A particularly interesting feature of this class is that it can also be used as A particularly interesting feature of this class is that it can also be used as
an opaque container for const and non-const references: an opaque container for const and non-const references:
@@ -138,9 +137,9 @@ any.emplace<const int &>(value);
In other words, whenever `any` is explicitly told to construct an _alias_, it In other words, whenever `any` is explicitly told to construct an _alias_, it
acts as a pointer to the original instance rather than making a copy of it or acts as a pointer to the original instance rather than making a copy of it or
moving it internally. The contained object is never destroyed, and users must moving it internally. The contained object is never destroyed and users must
ensure that its lifetime exceeds that of the container.<br/> ensure that its lifetime exceeds that of the container.<br/>
Similarly, it is possible to create non-owning copies of `any` from an existing Similarly, it's possible to create non-owning copies of `any` from an existing
object: object:
```cpp ```cpp
@@ -148,22 +147,25 @@ object:
entt::any ref = other.as_ref(); entt::any ref = other.as_ref();
``` ```
In this case, it does not matter if the original container actually holds an In this case, it doesn't matter if the original container actually holds an
object or is as a reference for unmanaged elements already. The new instance object or acts already as a reference for unmanaged elements, the new instance
thus created does not create copies and only serves as a reference for the thus created won't create copies and will only serve as a reference for the
original item. original item.<br/>
This means that, starting from the example above, both `ref` and `other` will
point to the same object, whether it's initially contained in `other` or already
an unmanaged element.
It is worth mentioning that, while everything works transparently when it comes As a side note, it's worth mentioning that, while everything works transparently
to non-const references, there are some exceptions when it comes to const when it comes to non-const references, there are some exceptions when it comes
references.<br/> to const references.<br/>
In particular, the `data` member function invoked on a non-const instance of In particular, the `data` member function invoked on a non-const instance of
`any` that wraps a const reference returns a null pointer in all cases. `any` that wraps a const reference will return a null pointer in all cases.
To cast an instance of `any` to a type, the library offers a set of `any_cast` To cast an instance of `any` to a type, the library offers a set of `any_cast`
functions in all respects similar to their most famous counterparts.<br/> functions in all respects similar to their most famous counterparts.<br/>
The only difference is that, in the case of `EnTT`, they will not raise The only difference is that, in the case of `EnTT`, these won't raise exceptions
exceptions but will only trigger an assert in debug mode, otherwise resulting in but will only trigger an assert in debug mode, otherwise resulting in undefined
undefined behavior in case of misuse in release mode. behavior in case of misuse in release mode.
## Small buffer optimization ## Small buffer optimization
@@ -181,35 +183,31 @@ using my_any = entt::basic_any<sizeof(double[4])>;
This feature, in addition to allowing the choice of a size that best suits the This feature, in addition to allowing the choice of a size that best suits the
needs of an application, also offers the possibility of forcing dynamic creation needs of an application, also offers the possibility of forcing dynamic creation
of objects during construction.<br/> of objects during construction.<br/>
In other terms, if the size is 0, `any` suppresses the small buffer optimization In other terms, if the size is 0, `any` avoids the use of any optimization and
and always dynamically allocates objects (except for aliasing cases). always dynamically allocates objects (except for aliasing cases).
Note that the size of the internal storage as well as the alignment requirements
are directly part of the type and therefore contribute to define different types
that won't be able to interoperate with each other.
## Alignment requirement ## Alignment requirement
The alignment requirement is optional and by default the most stringent (the The alignment requirement is optional and by default the most stringent (the
largest) for any object whose size is at most equal to the one provided.<br/> largest) for any object whose size is at most equal to the one provided.<br/>
It is provided as an optional second parameter following the desired size for The `basic_any` class template inspects the alignment requirements in each case,
the internal storage: even when not provided and may decide not to use the small buffer optimization
in order to meet them.
The alignment requirement is provided as an optional second parameter following
the desired size for the internal storage:
```cpp ```cpp
using my_any = entt::basic_any<sizeof(double[4]), alignof(double[4])>; using my_any = entt::basic_any<sizeof(double[4]), alignof(double[4])>;
``` ```
The `basic_any` class template inspects the alignment requirements in each case, Note that the alignment requirements as well as the size of the internal storage
even when not provided and may decide not to use the small buffer optimization are directly part of the type and therefore contribute to define different types
in order to meet them. that won't be able to interoperate with each other.
# Bit
Some general purpose utilities, such as the fast module function:
```cpp
const std::size_t result = entt::fast_mod(value, modulus);
```
Where `modulus` is necessarily a power of two. This type of operation is far
superior in terms of performance to the basic modulus and for this reason
preferred in many areas.
# Compressed pair # Compressed pair
@@ -222,29 +220,30 @@ is more important than having some cool and probably useless feature.
Although the API is very close to that of `std::pair` (apart from the fact that Although the API is very close to that of `std::pair` (apart from the fact that
the template parameters are inferred from the constructor and therefore there is the template parameters are inferred from the constructor and therefore there is
no `entt::make_compressed_pair`), the major difference is that `first` and no` entt::make_compressed_pair`), the major difference is that `first` and
`second` are functions for implementation requirements: `second` are functions for implementation needs:
```cpp ```cpp
entt::compressed_pair pair{0, 3.}; entt::compressed_pair pair{0, 3.};
pair.first() = 42; pair.first() = 42;
``` ```
There is not much to describe then. It is recommended to rely on documentation There isn't much to describe then. It's recommended to rely on documentation and
and intuition. At the end of the day, it is just a pair and nothing more. intuition. At the end of the day, it's just a pair and nothing more.
# Enum as bitmask # Enum as bitmask
Sometimes it is useful to be able to use enums as bitmasks. However, enum Sometimes it's useful to be able to use enums as bitmasks. However, enum classes
classes are not really suitable for the purpose. Main problem is that they do aren't really suitable for the purpose out of the box. Main problem is that they
not convert implicitly to their underlying type.<br/> don't convert implicitly to their underlying type.<br/>
The choice is then between using old-fashioned enums (with all their problems All that remains is to make a choice between using old-fashioned enums (with all
that I do not want to discuss here) or writing _ugly_ code. their problems that I don't want to discuss here) or writing _ugly_ code.
Fortunately, there is also a third way: adding enough operators in the global Fortunately, there is also a third way: adding enough operators in the global
scope to treat enum classes as bitmasks transparently.<br/> scope to treat enum classes as bitmask transparently.<br/>
The ultimate goal is to write code like the following (or maybe something more The ultimate goal is to be able to write code like the following (or maybe
meaningful, but this should give a grasp and remain simple at the same time): something more meaningful, but this should give a grasp and remain simple at the
same time):
```cpp ```cpp
enum class my_flag { enum class my_flag {
@@ -257,11 +256,11 @@ const my_flag flags = my_flag::enabled;
const bool is_enabled = !!(flags & my_flag::enabled); const bool is_enabled = !!(flags & my_flag::enabled);
``` ```
The problem with adding all operators to the global scope is that these come The problem with adding all operators to the global scope is that these will
into play even when not required, with the risk of introducing errors that are come into play even when not required, with the risk of introducing errors that
difficult to deal with.<br/> are difficult to deal with.<br/>
However, C++ offers enough tools to get around this problem. In particular, the However, C++ offers enough tools to get around this problem. In particular, the
library requires users to register the enum classes for which bitmask support library requires users to register all enum classes for which bitmask support
should be enabled: should be enabled:
```cpp ```cpp
@@ -272,7 +271,7 @@ struct entt::enum_as_bitmask<my_flag>
``` ```
This is handy when dealing with enum classes defined by third party libraries This is handy when dealing with enum classes defined by third party libraries
and over which the user has no control. However, it is also verbose and can be and over which the users have no control. However, it's also verbose and can be
avoided by adding a specific value to the enum class itself: avoided by adding a specific value to the enum class itself:
```cpp ```cpp
@@ -285,21 +284,23 @@ enum class my_flag {
``` ```
In this case, there is no need to specialize the `enum_as_bitmask` traits, since In this case, there is no need to specialize the `enum_as_bitmask` traits, since
`EnTT` automatically detects the flag and enables the bitmask support.<br/> `EnTT` will automatically detect the flag and enable the bitmask support.<br/>
Once the enum class is registered (in one way or the other), the most common Once the enum class has been registered (in one way or the other) all the most
operators such as `&`, `|` but also `&=` and `|=` are available for use. common operators will be available, such as `&`, `|` but also `&=` and `|=`.
Refer to the official documentation for the full list of operators. Refer to the official documentation for the full list of operators.
# Hashed strings # Hashed strings
Hashed strings are human-readable identifiers in the codebase that turn into A hashed string is a zero overhead unique identifier. Users can use
numeric values at runtime, thus without affecting performance.<br/> human-readable identifiers in the codebase while using their numeric
counterparts at runtime, thus without affecting performance.<br/>
The class has an implicit `constexpr` constructor that chews a bunch of The class has an implicit `constexpr` constructor that chews a bunch of
characters. Once created, one can get the original string by means of the `data` characters. Once created, all what one can do with it is getting back the
member function or convert the instance into a number.<br/> original string through the `data` member function or converting the instance
A hashed string is well suited wherever a constant expression is required. No into a number.<br/>
_string-to-number_ conversion will take place at runtime if used carefully. The good part is that a hashed string can be used wherever a constant expression
is required and no _string-to-number_ conversion will take place at runtime if
used carefully.
Example of use: Example of use:
@@ -312,18 +313,19 @@ auto resource = load(entt::hashed_string{"gui/background"});
``` ```
There is also a _user defined literal_ dedicated to hashed strings to make them There is also a _user defined literal_ dedicated to hashed strings to make them
more _user-friendly_: more user-friendly:
```cpp ```cpp
using namespace entt::literals; using namespace entt::literals;
constexpr auto str = "text"_hs; constexpr auto str = "text"_hs;
``` ```
User defined literals in `EnTT` are enclosed in the `entt::literals` namespace. To use it, remember that all user defined literals in `EnTT` are enclosed in the
Therefore, the entire namespace or selectively the literal of interest must be `entt::literals` namespace. Therefore, the entire namespace or selectively the
explicitly included before each use, a bit like `std::literals`.<br/> literal of interest must be explicitly included before each use, a bit like
The class also offers the necessary functionalities to create hashed strings at `std::literals`.<br/>
runtime: Finally, in case users need to create hashed strings at runtime, this class also
offers the necessary functionalities:
```cpp ```cpp
std::string orig{"text"}; std::string orig{"text"};
@@ -335,15 +337,17 @@ entt::hashed_string str{orig.c_str()};
const auto hash = entt::hashed_string::value(orig.c_str()); const auto hash = entt::hashed_string::value(orig.c_str());
``` ```
This possibility should not be exploited in tight loops, since the computation This possibility shouldn't be exploited in tight loops, since the computation
takes place at runtime and no longer at compile-time. It could therefore affect takes place at runtime and no longer at compile-time and could therefore impact
performance to some degrees. performance to some degrees.
## Wide characters ## Wide characters
The `hashed_string` class is an alias for `basic_hashed_string<char>`. To use The hashed string has a design that is close to that of an `std::basic_string`.
the C++ type for wide character representations, there exists also the alias It means that `hashed_string` is nothing more than an alias for
`hashed_wstring` for `basic_hashed_string<wchar_t>`.<br/> `basic_hashed_string<char>`. For those who want to use the C++ type for wide
character representation, there exists also the alias `hashed_wstring` for
`basic_hashed_string<wchar_t>`.<br/>
In this case, the user defined literal to use to create hashed strings on the In this case, the user defined literal to use to create hashed strings on the
fly is `_hws`: fly is `_hws`:
@@ -351,118 +355,63 @@ fly is `_hws`:
constexpr auto str = L"text"_hws; constexpr auto str = L"text"_hws;
``` ```
The hash type of `hashed_wstring` is the same as its counterpart. Note that the hash type of the `hashed_wstring` is the same of its counterpart.
## Conflicts ## Conflicts
The hashed string class uses FNV-1a internally to hash strings. Because of the The hashed string class uses internally FNV-1a to compute the numeric
_pigeonhole principle_, conflicts are possible. This is a fact.<br/> counterpart of a string. Because of the _pigeonhole principle_, conflicts are
possible. This is a fact.<br/>
There is no silver bullet to solve the problem of conflicts when dealing with There is no silver bullet to solve the problem of conflicts when dealing with
hashing functions. In this case, the best solution is likely to give up. That is hashing functions. In this case, the best solution seemed to be to give up.
all.<br/> That's all.<br/>
After all, human-readable unique identifiers are not something strictly defined After all, human-readable unique identifiers aren't something strictly defined
and over which users have not the control. Choosing a slightly different and over which users have not the control. Choosing a slightly different
identifier is probably the best solution to make the conflict disappear in this identifier is probably the best solution to make the conflict disappear in this
case. case.
# Iterators
Writing and working with iterators is not always easy. More often than not it
also leads to duplicated code.<br/>
`EnTT` tries to overcome this problem by offering some utilities designed to
make this hard work easier.
## Input iterator pointer
When writing an input iterator that returns in-place constructed values if
dereferenced, it is not always straightforward to figure out what `value_type`
is and how to make it behave like a full-fledged pointer.<br/>
Conversely, it would be very useful to have an `operator->` available on the
iterator itself that always works without too much complexity.
The input iterator pointer is meant for this. It is a small class that wraps the
in-place constructed value and adds some functions on top of it to make it
suitable for use with input iterators:
```cpp
struct iterator_type {
using value_type = std::pair<first_type, second_type>;
using pointer = input_iterator_pointer<value_type>;
using reference = value_type;
using difference_type = std::ptrdiff_t;
using iterator_category = std::input_iterator_tag;
// ...
}
```
The library makes extensive use of this class internally. In many cases, the
`value_type` of the returned iterators is just an input iterator pointer.
## Iota iterator
Waiting for C++20, this iterator accepts an integral value and returns all
elements in a certain range:
```cpp
entt::iota_iterator first{0};
entt::iota_iterator last{100};
for(; first != last; ++first) {
int value = *first;
// ...
}
```
In the future, views will replace this class. Meanwhile, the library makes some
interesting uses of it when a range of integral values is to be returned to the
user.
## Iterable adaptor
Typically, a container class provides `begin` and `end` member functions (with
their const counterparts) for iteration.<br/>
However, it can happen that a class offers multiple iteration methods or allows
users to iterate different sets of _elements_.
The iterable adaptor is a utility class that makes it easier to use and access
data in this case.<br/>
It accepts a couple of iterators (or an iterator and a sentinel) and offers an
_iterable_ object with all the expected methods like `begin`, `end` and whatnot.
The library uses this class extensively.<br/>
Think for example of views, which can be iterated to access entities but also
offer a method for obtaining an iterable object that returns tuples of entities
and components at once.<br/>
Another example is the registry class which allows users to iterate its storage
by returning an iterable object for the purpose.
# Memory # Memory
There are a handful of tools within `EnTT` to interact with memory in one way or There are a handful of tools within EnTT to interact with memory in one way or
another.<br/> another.<br/>
Some are geared towards simplifying the implementation of (internal or external) Some are geared towards simplifying the implementation of (internal or external)
allocator aware containers. Others are designed to help the developer with allocator aware containers. Others, on the other hand, are designed to help the
everyday problems. developer with everyday problems.
The former are very specific and for niche problems. For example, there are The former are very specific and for niche problems. These are tools designed to
tools designed to help forget the meaning of acronyms like _POCCA_, _POCMA_ or unwrap fancy or plain pointers (`to_address`) or to help forget the meaning of
_POCS_.<br/> acronyms like _POCCA_, _POCMA_ or _POCS_.<br/>
I will not describe them here in detail. Instead, I recommend reading the inline I won't describe them here in detail. Instead, I recommend reading the inline
documentation to those interested in the subject. documentation to those interested in the subject.
## Power of two and fast modulus
Finding out if a number is a power of two (`is_power_of_two`) or what the next
power of two is given a random value (`next_power_of_two`) is very useful at
times.<br/>
For example, it helps to allocate memory in pages having a size suitable for the
fast modulus:
```cpp
const std::size_t result = entt::fast_mod(value, modulus);
```
Where `modulus` is necessarily a power of two. Perhaps not everyone knows that
this type of operation is far superior in terms of performance to the basic
modulus and for this reason preferred in many areas.
## Allocator aware unique pointers ## Allocator aware unique pointers
A nasty thing in C++ (at least up to C++20) is the fact that shared pointers A nasty thing in C++ (at least up to C++20) is the fact that shared pointers
support allocators while unique pointers do not.<br/> support allocators while unique pointers don't.<br/>
There is a proposal at the moment that also shows (among the other things) how There is a proposal at the moment that also shows among the other things how
this can be implemented without any compiler support. this can be implemented without any compiler support.
The `allocate_unique` function follows this proposal, making a virtue out of The `allocate_unique` function follows this proposal, making a virtue out of
necessity: necessity:
```cpp ```cpp
std::unique_ptr<my_type, entt::allocation_deleter<allocator_type>> ptr = entt::allocate_unique<my_type>(allocator, arguments); std::unique_ptr<my_type, entt::allocation_deleter<my_type>> ptr = entt::allocate_unique<my_type>(allocator, arguments);
``` ```
Although the internal implementation is slightly different from what is proposed Although the internal implementation is slightly different from what is proposed
@@ -472,16 +421,13 @@ the same feature.
# Monostate # Monostate
The monostate pattern is often presented as an alternative to a singleton based The monostate pattern is often presented as an alternative to a singleton based
configuration system.<br/> configuration system. This is exactly its purpose in `EnTT`. Moreover, this
This is exactly its purpose in `EnTT`. Moreover, this implementation is thread implementation is thread safe by design (hopefully).<br/>
safe by design (hopefully). Keys are represented by hashed strings, values are basic types like `int`s or
`bool`s. Values of different types can be associated to each key, even more than
Keys are integral values (easily obtained by hashed strings), values are basic one at a time. Because of this, users must pay attention to use the same type
types like `int`s or `bool`s. Values of different types can be associated with both during an assignment and when they try to read back their data. Otherwise,
each key, even more than one at a time.<br/> they will probably incur in unexpected results.
Because of this, one should pay attention to use the same type both during an
assignment and when trying to read back the data. Otherwise, there is the risk
to incur in unexpected results.
Example of use: Example of use:
@@ -505,9 +451,9 @@ library or that will never be.
Runtime type identification support (or RTTI) is one of the most frequently Runtime type identification support (or RTTI) is one of the most frequently
disabled features in the C++ world, especially in the gaming sector. Regardless disabled features in the C++ world, especially in the gaming sector. Regardless
of the reasons for this, it is often a shame not to be able to rely on opaque of the reasons for this, it's often a shame not to be able to rely on opaque
type information at runtime.<br/> type information at runtime.<br/>
The library tries to fill this gap by offering a built-in system that does not The library tries to fill this gap by offering a built-in system that doesn't
serve as a replacement but comes very close to being one and offers similar serve as a replacement but comes very close to being one and offers similar
information to that provided by its counterpart. information to that provided by its counterpart.
@@ -519,25 +465,31 @@ Basically, the whole system relies on a handful of classes. In particular:
auto index = entt::type_index<a_type>::value(); auto index = entt::type_index<a_type>::value();
``` ```
The returned value is not guaranteed to be stable across different runs.<br/> The returned value isn't guaranteed to be stable across different runs.
However, it can be very useful as index in associative and unordered However, it can be very useful as index in associative and unordered
associative containers or for positional accesses in a vector or an array. associative containers or for positional accesses in a vector or an array.
So as not to conflict with the other tools available, the `family` class isn't
used to generate these indexes. Therefore, the numeric identifiers returned by
the two tools may differ.<br/>
On the other hand, this leaves users with full powers over the `family` class
and therefore the generation of custom runtime sequences of indices for their
own purposes, if necessary.
An external generator can also be used if needed. In fact, `type_index` can be An external generator can also be used if needed. In fact, `type_index` can be
specialized by type or constrained with a concept in order to allow more specialized by type and is also _sfinae-friendly_ in order to allow more
refined specializations such as: refined specializations such as:
```cpp ```cpp
template<typename Type> template<typename Type>
requires requires { { Type::index() } -> std::same_as<entt::id_type>; } struct entt::type_index<Type, std::void_d<decltype(Type::index())>> {
struct entt::type_index<Type> { static entt::id_type value() ENTT_NOEXCEPT {
static entt::id_type value() noexcept {
return Type::index(); return Type::index();
} }
}; };
``` ```
Indexes **must** be sequentially generated in this case.<br/> Note that indexes **must** still be generated sequentially in this case.<br/>
The tool is widely used within `EnTT`. Generating indices not sequentially The tool is widely used within `EnTT`. Generating indices not sequentially
would break an assumption and would likely lead to undesired behaviors. would break an assumption and would likely lead to undesired behaviors.
@@ -548,20 +500,20 @@ Basically, the whole system relies on a handful of classes. In particular:
``` ```
In general, the `value` function exposed by `type_hash` is also `constexpr` In general, the `value` function exposed by `type_hash` is also `constexpr`
but this is not guaranteed for all compilers and platforms (although it is but this isn't guaranteed for all compilers and platforms (although it's valid
valid with the most well-known and popular ones). with the most well-known and popular ones).
This function **can** use non-standard features of the language for its own This function **can** use non-standard features of the language for its own
purposes. This makes it possible to provide compile-time identifiers that purposes. This makes it possible to provide compile-time identifiers that
remain stable across different runs.<br/> remain stable across different runs.<br/>
Users can prevent the library from using these features by means of the In all cases, users can prevent the library from using these features by means
`ENTT_STANDARD_CPP` definition. In this case, there is no guarantee that of the `ENTT_STANDARD_CPP` definition. In this case, there is no guarantee
identifiers remain stable across executions. Moreover, they are generated that identifiers remain stable across executions. Moreover, they are generated
at runtime and are no longer a compile-time thing. at runtime and are no longer a compile-time thing.
As it happens with `type_index`, also `type_hash` can be specialized or As for `type_index`, also `type_hash` is a _sfinae-friendly_ class that can be
constrained with a concept in order to customize its behavior globally or on a specialized in order to customize its behavior globally or on a per-type or
per-type or per-traits basis. per-traits basis.
* The name associated with a given type: * The name associated with a given type:
@@ -569,9 +521,10 @@ Basically, the whole system relies on a handful of classes. In particular:
auto name = entt::type_name<a_type>::value(); auto name = entt::type_name<a_type>::value();
``` ```
This value is extracted from some information generally made available by the The name associated with a type is extracted from some information generally
compiler in use. Therefore, it may differ depending on the compiler and may be made available by the compiler in use. Therefore, it may differ depending on
empty in the event that this information is not available.<br/> the compiler and may be empty in the event that this information isn't
available.<br/>
For example, given the following class: For example, given the following class:
```cpp ```cpp
@@ -582,25 +535,26 @@ Basically, the whole system relies on a handful of classes. In particular:
when MSVC is in use.<br/> when MSVC is in use.<br/>
Most of the time the name is also retrieved at compile-time and is therefore Most of the time the name is also retrieved at compile-time and is therefore
always returned through an `std::string_view`. Users can easily access it and always returned through an `std::string_view`. Users can easily access it and
modify it as needed, for example by removing the word `struct` to normalize modify it as needed, for example by removing the word `struct` to standardize
the result. `EnTT` does not do this for obvious reasons, since it would be the result. `EnTT` won't do this for obvious reasons, since it requires
creating a new string at runtime otherwise. copying and creating a new string potentially at runtime.
This function **can** use non-standard features of the language for its own This function **can** use non-standard features of the language for its own
purposes. Users can prevent the library from using these features by means of purposes. Users can prevent the library from using non-standard features by
the `ENTT_STANDARD_CPP` definition. In this case, the name is just empty. means of the `ENTT_STANDARD_CPP` definition. In this case, the name will be
empty by default.
As it happens with `type_index`, also `type_name` can be specialized or As for `type_index`, also `type_name` is a _sfinae-friendly_ class that can be
constrained with a concept in order to customize its behavior globally or on a specialized in order to customize its behavior globally or on a per-type or
per-type or per-traits basis. per-traits basis.
These are then combined into utilities that aim to offer an API that is somewhat These are then combined into utilities that aim to offer an API that is somewhat
similar to that made available by the standard library. similar to that offered by the language.
### Type info ### Type info
The `type_info` class is not a drop-in replacement for `std::type_info` but can The `type_info` class isn't a drop-in replacement for `std::type_info` but can
provide similar information which are not implementation defined and do not provide similar information which are not implementation defined and don't
require to enable RTTI.<br/> require to enable RTTI.<br/>
Therefore, they can sometimes be even more reliable than those obtained Therefore, they can sometimes be even more reliable than those obtained
otherwise. otherwise.
@@ -636,7 +590,7 @@ These are the information made available by `type_info`:
This is also an alias for the following: This is also an alias for the following:
```cpp ```cpp
auto idx = entt::type_index<std::remove_cvref_t<a_type>>::value(); auto idx = entt::type_index<std::remove_cv_t<std::remove_reference_t<a_type>>>::value();
``` ```
* The hash value associated with a given type: * The hash value associated with a given type:
@@ -648,7 +602,7 @@ These are the information made available by `type_info`:
This is also an alias for the following: This is also an alias for the following:
```cpp ```cpp
auto hash = entt::type_hash<std::remove_cvref_t<a_type>>::value(); auto hash = entt::type_hash<std::remove_cv_t<std::remove_reference_t<a_type>>>::value();
``` ```
* The name associated with a given type: * The name associated with a given type:
@@ -660,7 +614,7 @@ These are the information made available by `type_info`:
This is also an alias for the following: This is also an alias for the following:
```cpp ```cpp
auto name = entt::type_name<std::remove_cvref_t<a_type>>::value(); auto name = entt::type_name<std::remove_cv_t<std::remove_reference_t<a_type>>>::value();
``` ```
Where all accessed features are available at compile-time, the `type_info` class Where all accessed features are available at compile-time, the `type_info` class
@@ -673,18 +627,17 @@ described above.
Since the default non-standard, compile-time implementation of `type_hash` makes Since the default non-standard, compile-time implementation of `type_hash` makes
use of hashed strings, it may happen that two types are assigned the same hash use of hashed strings, it may happen that two types are assigned the same hash
value.<br/> value.<br/>
In fact, although this is quite rare, it is not entirely excluded. In fact, although this is quite rare, it's not entirely excluded.
Another case where two types are assigned the same identifier is when classes Another case where two types are assigned the same identifier is when classes
from different contexts (for example two or more libraries loaded at runtime) from different contexts (for example two or more libraries loaded at runtime)
have the same fully qualified name. In this case, `type_name` returns the same have the same fully qualified name. In this case, also `type_name` will return
value for the two types.<br/> the same value for the two types.<br/>
Fortunately, there are several easy ways to deal with this: Fortunately, there are several easy ways to deal with this:
* The most trivial one is to define the `ENTT_STANDARD_CPP` macro. Runtime * The most trivial one is to define the `ENTT_STANDARD_CPP` macro. Runtime
identifiers do not suffer from the same problem in fact. However, this identifiers don't suffer from the same problem in fact. However, this solution
solution does not work well with a plugin system, where the libraries are not doesn't work well with a plugin system, where the libraries aren't linked.
linked.
* Another possibility is to specialize the `type_name` class for one of the * Another possibility is to specialize the `type_name` class for one of the
conflicting types, in order to assign it a custom identifier. This is probably conflicting types, in order to assign it a custom identifier. This is probably
@@ -693,8 +646,8 @@ Fortunately, there are several easy ways to deal with this:
* A fully customized identifier generation policy (based for example on enum * A fully customized identifier generation policy (based for example on enum
classes or preprocessing steps) may represent yet another option. classes or preprocessing steps) may represent yet another option.
These are just some examples of possible approaches to the problem, but there These are just some examples of possible approaches to the problem but there are
are many others. As already mentioned above, since users have full control over many others. As already mentioned above, since users have full control over
their types, this problem is in any case easy to solve and should not worry too their types, this problem is in any case easy to solve and should not worry too
much.<br/> much.<br/>
In all likelihood, it will never happen to run into a conflict anyway. In all likelihood, it will never happen to run into a conflict anyway.
@@ -709,11 +662,11 @@ offered by this module.
### Size of ### Size of
The standard operator `sizeof` complains if users provide it with functions or The standard operator `sizeof` complains when users provide it for example with
incomplete types. On the other hand, it is guaranteed that its result is always function or incomplete types. On the other hand, it's guaranteed that its result
non-zero, even if applied to an empty class type.<br/> is always nonzero, even if applied to an empty class type.<br/>
This small class combines the two and offers an alternative to `sizeof` that This small class combines the two and offers an alternative to `sizeof` that
works under all circumstances, returning zero if the type is not supported: works under all circumstances, returning zero if the type isn't supported:
```cpp ```cpp
const auto size = entt::size_of_v<void>; const auto size = entt::size_of_v<void>;
@@ -740,15 +693,15 @@ tuple-like type and simplify the code at the call site.
### Constness as ### Constness as
A utility to easily transfer the constness of a type to another type: An utility to easily transfer the constness of a type to another type:
```cpp ```cpp
// type is const dst_type because of the constness of src_type // type is const dst_type because of the constness of src_type
using type = entt::constness_as_t<dst_type, const src_type>; using type = entt::constness_as_t<dst_type, const src_type>;
``` ```
The trait is subject to the rules of the language. For example, _transferring_ The trait is subject to the rules of the language. Therefore, for example,
constness between references will not give the desired effect. transferring constness between references won't give the desired effect.
### Member class type ### Member class type
@@ -762,18 +715,6 @@ template<typename Member>
using clazz = entt::member_class_t<Member>; using clazz = entt::member_class_t<Member>;
``` ```
### N-th argument
A utility to quickly find the n-th argument of a function, member function or
data member (for blind operations on opaque types):
```cpp
using type = entt::nth_argument_t<1u, decltype(&clazz::member)>;
```
Disambiguation of overloaded functions is the responsibility of the user, should
it be needed.
### Integral constant ### Integral constant
Since `std::integral_constant` may be annoying because of its form that requires Since `std::integral_constant` may be annoying because of its form that requires
@@ -795,8 +736,8 @@ registry.emplace<enemy_tag>(entity);
### Tag ### Tag
Type `id_type` is very important and widely used in `EnTT`. Therefore, there is Since `id_type` is very important and widely used in `EnTT`, there is a more
a more user-friendly shortcut for the creation of constants based on it.<br/> user-friendly shortcut for the creation of integral constants based on it.<br/>
This shortcut is the alias template `entt::tag`. This shortcut is the alias template `entt::tag`.
If used in combination with hashed strings, it helps to use human-readable names If used in combination with hashed strings, it helps to use human-readable names
@@ -806,7 +747,7 @@ where types would be required otherwise. As an example:
registry.emplace<entt::tag<"enemy"_hs>>(entity); registry.emplace<entt::tag<"enemy"_hs>>(entity);
``` ```
However, this is not the only permitted use. Literally any value convertible to However, this isn't the only permitted use. Literally any value convertible to
`id_type` is a good candidate, such as the named constants of an unscoped enum. `id_type` is a good candidate, such as the named constants of an unscoped enum.
### Type list and value list ### Type list and value list
@@ -821,14 +762,12 @@ Here is a (possibly incomplete) list of the functionalities that come with a
type list: type list:
* `type_list_element[_t]` to get the N-th element of a type list. * `type_list_element[_t]` to get the N-th element of a type list.
* `type_list_index[_v]` to get the index of a given element of a type list.
* `type_list_cat[_t]` and a handy `operator+` to concatenate type lists. * `type_list_cat[_t]` and a handy `operator+` to concatenate type lists.
* `type_list_unique[_t]` to remove duplicate types from a type list. * `type_list_unique[_t]` to remove duplicate types from a type list.
* `type_list_contains[_v]` to know if a type list contains a given type. * `type_list_contains[_v]` to know if a type list contains a given type.
* `type_list_diff[_t]` to remove types from type lists. * `type_list_diff[_t]` to remove types from type lists.
* `type_list_transform[_t]` to _transform_ a range and create another type list.
I am also pretty sure that more and more utilities will be added over time as I'm also pretty sure that more and more utilities will be added over time as
needs become apparent.<br/> needs become apparent.<br/>
Many of these functionalities also exist in their version dedicated to value Many of these functionalities also exist in their version dedicated to value
lists. We therefore have `value_list_element[_v]` as well as lists. We therefore have `value_list_element[_v]` as well as
@@ -836,28 +775,28 @@ lists. We therefore have `value_list_element[_v]` as well as
# Unique sequential identifiers # Unique sequential identifiers
Sometimes it is useful to be able to give unique, sequential numeric identifiers Sometimes it's useful to be able to give unique, sequential numeric identifiers
to types either at compile-time or runtime.<br/> to types either at compile-time or runtime.<br/>
There are plenty of different solutions for this out there, and I could have There are plenty of different solutions for this out there and I could have used
used one of them. However, I decided to spend my time to define a couple of one of them. However, I decided to spend my time to define a couple of tools
tools that fully embrace what modern C++ has to offer. that fully embraces what the modern C++ has to offer.
## Compile-time generator ## Compile-time generator
To generate sequential numeric identifiers at compile-time, `EnTT` offers the To generate sequential numeric identifiers at compile-time, `EnTT` offers the
`ident` class template: `identifier` class template:
```cpp ```cpp
// defines the identifiers for the given types // defines the identifiers for the given types
using id = entt::ident<a_type, another_type>; using id = entt::identifier<a_type, another_type>;
// ... // ...
switch(a_type_identifier) { switch(a_type_identifier) {
case id::value<a_type>: case id::type<a_type>:
// ... // ...
break; break;
case id::value<another_type>: case id::type<another_type>:
// ... // ...
break; break;
default: default:
@@ -865,32 +804,31 @@ default:
} }
``` ```
This is what this class template has to offer: a `value` inline variable that This is all what this class template has to offer: a `type` inline variable that
contains a numeric identifier for the given type. It can be used in any context contains a numeric identifier for the given type. It can be used in any context
where constant expressions are required. where constant expressions are required.
As long as the list remains unchanged, identifiers are also guaranteed to be As long as the list remains unchanged, identifiers are also guaranteed to be
stable across different runs. If used in a production environment where a type stable across different runs. In case they have been used in a production
needs to be removed, a placeholder can help to leave the other identifiers the environment and a type has to be removed, one can just use a placeholder to left
same: the other identifiers unchanged:
```cpp ```cpp
template<typename> template<typename> struct ignore_type {};
struct ignore_type {};
using id = entt::ident< using id = entt::identifier<
a_type_still_valid, a_type_still_valid,
ignore_type<no_longer_valid_type>, ignore_type<a_type_no_longer_valid>,
another_type_still_valid another_type_still_valid
>; >;
``` ```
Perhaps a bit ugly to see in a codebase, but it gets the job done at least. Perhaps a bit ugly to see in a codebase but it gets the job done at least.
## Runtime generator ## Runtime generator
The `family` class template helps to generate sequential numeric identifiers for To generate sequential numeric identifiers at runtime, `EnTT` offers the
types at runtime: `family` class template:
```cpp ```cpp
// defines a custom generator // defines a custom generator
@@ -898,24 +836,28 @@ using id = entt::family<struct my_tag>;
// ... // ...
const auto a_type_id = id::value<a_type>; const auto a_type_id = id::type<a_type>;
const auto another_type_id = id::value<another_type>; const auto another_type_id = id::type<another_type>;
``` ```
This is what a _family_ has to offer: a `value` inline variable that contains a This is all what a _family_ has to offer: a `type` inline variable that contains
numeric identifier for the given type.<br/> a numeric identifier for the given type.<br/>
The generator is customizable, so as to get different _sequences_ for different The generator is customizable, so as to get different _sequences_ for different
purposes if needed. purposes if needed.
Identifiers are not guaranteed to be stable across different runs. Indeed it Please, note that identifiers aren't guaranteed to be stable across different
mostly depends on the flow of execution. runs. Indeed it mostly depends on the flow of execution.
# Utilities # Utilities
It is not possible to escape the temptation to add utilities of some kind to a It's not possible to escape the temptation to add utilities of some kind to a
library. In fact, `EnTT` also provides a handful of tools to simplify the library. In fact, `EnTT` also provides a handful of tools to simplify the
life of developers: life of developers:
* `entt::identity`: the identity function object that will be available with
C++20. It returns its argument unchanged and nothing more. It's useful as a
sort of _do nothing_ function in template programming.
* `entt::overload`: a tool to disambiguate different overloads from their * `entt::overload`: a tool to disambiguate different overloads from their
function type. It works with both free and member functions.<br/> function type. It works with both free and member functions.<br/>
Consider the following definition: Consider the following definition:
@@ -959,8 +901,7 @@ life of developers:
callable object that supports multiple types at once. callable object that supports multiple types at once.
* `entt::y_combinator`: this is a C++ implementation of **the** _y-combinator_. * `entt::y_combinator`: this is a C++ implementation of **the** _y-combinator_.
If it is not clear what it is, there is probably no need for this If it's not clear what it is, there is probably no need for this utility.<br/>
utility.<br/>
Below is a small example to show its use: Below is a small example to show its use:
```cpp ```cpp
@@ -971,9 +912,9 @@ life of developers:
const auto result = gauss(3u); const auto result = gauss(3u);
``` ```
Maybe convoluted at first glance but certainly effective. Unfortunately, Maybe convoluted at a first glance but certainly effective. Unfortunately,
the language does not make it possible to do much better. the language doesn't make it possible to do much better.
This is a rundown of the (actually few) utilities made available by `EnTT`. The This is a rundown of the (actually few) utilities made available by `EnTT`. The
list will probably grow over time, but the size of each will remain rather list will probably grow over time but the size of each will remain rather small,
small, as has been the case so far. as has been the case so far.

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,8 @@
# Frequently Asked Questions # Frequently Asked Questions
<!--
@cond TURN_OFF_DOXYGEN
-->
# Table of Contents # Table of Contents
* [Introduction](#introduction) * [Introduction](#introduction)
@@ -7,20 +10,22 @@
* [Why is my debug build on Windows so slow?](#why-is-my-debug-build-on-windows-so-slow) * [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) * [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) * [Custom entity identifiers: yay or nay?](#custom-entity-identifiers-yay-or-nay)
* [Warning C4003: the min, the max and the macro](#warning-c4003-the-min-the-max-and-the-macro) * [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) * [The standard and the non-copyable types](#the-standard-and-the-non-copyable-types)
* [Which functions trigger which signals](#which-functions-trigger-which-signals) * [Which functions trigger which signals](#which-functions-trigger-which-signals)
* [Duplicate storage for the same component](#duplicate-storage-for-the-same-component) <!--
@endcond TURN_OFF_DOXYGEN
-->
# Introduction # Introduction
This is a constantly updated section where I am trying to put the answers to the This is a constantly updated section where I'll try to put the answers to the
most frequently asked questions.<br/> most frequently asked questions.<br/>
If you do not find your answer here, there are two cases: nobody has done it If you don't find your answer here, there are two cases: nobody has done it yet
yet, or this section needs updating. In both cases, you can or this section needs updating. In both cases, try to
[open a new issue](https://github.com/skypjack/entt/issues/new) or enter either [open a new issue](https://github.com/skypjack/entt/issues/new) or enter the
the [gitter channel](https://gitter.im/skypjack/entt) or the [gitter channel](https://gitter.im/skypjack/entt) and ask your question.
[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 Probably someone already has an answer for you and we can then integrate this
part of the documentation. part of the documentation.
@@ -29,50 +34,50 @@ part of the documentation.
## Why is my debug build on Windows so slow? ## 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 `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 is latest revision of the language and the standard library. For this reason, it's
likely that some classes you are working with are using standard containers likely that some classes you're working with are using standard containers under
under the hood.<br/> the hood.<br/>
Unfortunately, it is known that the standard containers are not particularly Unfortunately, it's known that the standard containers aren't particularly
performing in debugging (the reasons for this go beyond this document) and are 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 even less so on Windows apparently. Fortunately this can also be mitigated a
lot, achieving good results in many cases. lot, achieving good results in many cases.
First of all, there are two things to do in a Windows project: First of all, there are two things to do in a Windows project:
* Disable the [`/JMC`](https://docs.microsoft.com/cpp/build/reference/jmc) * Disable the [`/JMC`](https://docs.microsoft.com/cpp/build/reference/jmc)
option (_Just My Code_ debugging), available starting with Visual Studio 2017 option (_Just My Code_ debugging), available starting in Visual Studio 2017
version 15.8. version 15.8.
* Set the [`_ITERATOR_DEBUG_LEVEL`](https://docs.microsoft.com/cpp/standard-library/iterator-debug-level) * 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. macro to 0. This will disable checked iterators and iterator debugging.
Moreover, set the `ENTT_DISABLE_ASSERT` variable or redefine the `ENTT_ASSERT` Moreover, the macro `ENTT_ASSERT` should be redefined to disable internal checks
macro to disable internal debug checks in `EnTT`: made by `EnTT` in debug:
```cpp ```cpp
#define ENTT_ASSERT(...) ((void)0) #define ENTT_ASSERT(...) ((void)0)
``` ```
These asserts are introduced to help the users, but they require access to the 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. underlying containers and therefore risk ruining the performance in some cases.
With these changes, debug performance should increase enough in most cases. If With these changes, debug performance should increase enough for most cases. If
you want something more, you can also switch to an optimization level `O0` or you want something more, you can can also switch to an optimization level `O0`
preferably `O1`. or preferably `O1`.
## How can I represent hierarchies with my components? ## How can I represent hierarchies with my components?
This is one of the first questions that anyone makes when starting to work with This is one of the first questions that anyone makes when starting to work with
the entity-component-system architectural pattern.<br/> the entity-component-system architectural pattern.<br/>
There are several approaches to the problem, and the best one depends mainly on There are several approaches to the problem and whats the best one depends
the real problem one is facing. In all cases, how to do it does not strictly mainly on the real problem one is facing. In all cases, how to do it doesn't
depend on the library in use, but the latter certainly allows or not different strictly depend on the library in use, but the latter can certainly allow or
techniques depending on how the data are laid out. 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 I tried to describe some of the techniques that fit well with the model of
`EnTT`. [This](https://skypjack.github.io/2019-06-25-ecs-baf-part-4/) is the `EnTT`. [Here](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 first post of a series that tries to explore the problem. More will probably
come in the future.<br/> come in future.<br/>
In addition, `EnTT` also offers the possibility to create stable storage types 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 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 far the most convenient solution when it comes to creating hierarchies and
@@ -83,9 +88,8 @@ what concerns the `component_traits` class for further details.
Custom entity identifiers are definitely a good idea in two cases at least: Custom entity identifiers are definitely a good idea in two cases at least:
* If `std::uint32_t` is not large enough for your purposes, since this is the * If `std::uint32_t` isn't large enough for your purposes, since this is the
underlying type of `entt::entity`. underlying type of `entt::entity`.
* If you want to avoid conflicts when using multiple registries. * If you want to avoid conflicts when using multiple registries.
Identifiers can be defined through enum classes and class types that define an Identifiers can be defined through enum classes and class types that define an
@@ -98,15 +102,41 @@ enum class entity: std::uint32_t {};
There is no limit to the number of identifiers that can be defined. 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 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 ## Warning C4003: the min, the max and the macro
On Windows, a header file defines two macros `min` and `max` which may result in 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 conflicts with their counterparts in the standard library and therefore in
errors during compilation. errors during compilation.
It is a pretty big problem. However, fortunately it is not a problem of `EnTT` It's a pretty big problem but fortunately it's not a problem of `EnTT` and there
and there is a fairly simple solution to it.<br/> is a fairly simple solution to it.<br/>
It consists in defining the `NOMINMAX` macro before including any other header It consists in defining the `NOMINMAX` macro before to include any other header
so as to get rid of the extra definitions: so as to get rid of the extra definitions:
```cpp ```cpp
@@ -119,9 +149,9 @@ more details.
## The standard and the non-copyable types ## The standard and the non-copyable types
`EnTT` uses internally the trait `std::is_copy_constructible_v` to check if a `EnTT` uses internally the trait `std::is_copy_constructible_v` to check if a
component is actually copyable. However, this trait does not really check component is actually copyable. However, this trait doesn't really check whether
whether a type is actually copyable. Instead, it just checks that a suitable a type is actually copyable. Instead, it just checks that a suitable copy
copy constructor and copy operator exist.<br/> constructor and copy operator exist.<br/>
This can lead to surprising results due to some idiosyncrasies of the standard. This can lead to surprising results due to some idiosyncrasies of the standard.
For example, `std::vector` defines a copy constructor that is conditionally For example, `std::vector` defines a copy constructor that is conditionally
@@ -161,48 +191,21 @@ to mitigate the problem makes it manageable.
## Which functions trigger which signals ## Which functions trigger which signals
Storage classes offer three _signals_ that are emitted following specific The `registry` class offers three signals that are emitted following specific
operations. Maybe not everyone knows what these operations are, though.<br/> operations. Maybe not everyone knows what these operations are, though.<br/>
If this is not clear, below you can find a _vademecum_ for this purpose: 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 * `on_created` is invoked when a component is first added (neither modified nor
replaced) to an entity. replaced) to an entity.
* `on_update` is called whenever an existing component is modified or replaced. * `on_update` is called whenever an existing component is modified or replaced.
* `on_destroyed` is called when a component is explicitly or implicitly removed * `on_destroyed` is called when a component is explicitly or implicitly removed
from an entity. from an entity.
Among the most controversial functions can be found `emplace_or_replace` and Among the most controversial functions can be found `emplace_or_replace` and
`destroy`. However, following the above rules, it is quite simple to know what `destroy`. However, following the above rules, it's quite simple to know what
will happen.<br/> will happen.<br/>
In the first case, `on_created` is invoked if the entity has not the component, 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 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 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 they are recycled. It means that `on_destroyed` is triggered for every component
owned by the entity that is destroyed. owned by the entity that is destroyed.
## Duplicate storage for the same component
It is rare, but you can see double sometimes, especially when it comes to
storage. This can be caused by a conflict in the hash assigned to the various
component types (one of a kind) or by bugs in your compiler
([more common](https://github.com/skypjack/entt/issues/1063) apparently).<br/>
Regardless of the cause, `EnTT` offers a customization point that also serves as
a solution in this case:
```cpp
template<>
struct entt::type_hash<Type> final {
[[nodiscard]] static consteval id_type value() noexcept {
return hashed_string::value("Type");
}
[[nodiscard]] consteval operator id_type() const noexcept {
return value();
}
};
```
Specializing `type_hash` directly bypasses the default implementation offered by
`EnTT`, thus avoiding any possible conflicts or compiler bugs.

View File

@@ -1,367 +0,0 @@
# Crash Course: graph
# 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)
# Introduction
`EnTT` does not 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 though. 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 is not 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 that 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 takes 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 was newly inserted or not. The
second one returns the number of deleted elements (0 or 1).
An adjacency matrix is 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()) {
// ...
}
```
The same result is obtained with the following snippet, since the vertices are
plain 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 is used to get them as pairs of
vertices:
```cpp
for(auto [lhs, rhs]: adjacency_matrix.edges()) {
// ...
}
```
If the goal is to visit all the in- or out-edges of a given vertex instead, the
`in_edges` and `out_edges` functions are meant for that:
```cpp
for(auto [lhs, rhs]: adjacency_matrix.out_edges(3u)) {
// ...
}
```
Both the 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 functionalities one would expect from this type of containers, such as
`clear` or `get_allocator` and so on.
## Graphviz dot language
As it is 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);
```
It is also possible to provide a callback to which the vertices are passed and
which can be used to add (`dot`) properties to the output as needed:
```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
externally managed data 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 does not 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 are 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 is 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`.
In other terms, 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 is created (which requires no constructor arguments), the
first thing to do is to bind a task. This tells the builder _who_ intends to
consume the resources that are specified immediately after:
```cpp
entt::flow builder{};
builder.bind("task_1"_hs);
```
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 is 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 necessary.
Once a task is associated with the flow builder, it has also 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 is therefore easy
to concatenate the different calls.<br/>
Also in the case of resources, they 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.
### Rebinding
The `flow` class is resource based rather than task based. This means that graph
generation is driven by resources and not by the order of _appearance_ of tasks
during flow definition.<br/>
Although this concept is particularly important, it is almost irrelevant for the
vast majority of cases. However, it becomes relevant when _rebinding_ resources
or tasks.
In fact, nothing prevents rebinding elements to a flow.<br/>
However, the behavior changes slightly from case to case and has some nuances
that it is worth knowing about.
Directly rebinding a resource without the task being replaced trivially results
in the task's access mode for that resource being updated:
```cpp
builder.bind("task"_hs).rw("resource"_hs).ro("resource"_hs)
```
In this case, the resource is accessed in read-only mode, regardless of the
first call to `rw`.<br/>
Behind the scenes, the call does not actually _replace_ the previous one but is
queued after it instead, overwriting it when generating the graph. Thus, a large
number of resource rebindings may even impact processing times (very difficult
to observe but theoretically possible).
Rebinding resources and also combining it with changes to tasks has far more
implications instead.<br/>
As mentioned, graph generation takes place starting from resources and not from
tasks. Therefore, the result may not be as expected:
```cpp
builder
.bind("task_1"_hs)
.ro("resource"_hs)
.bind("task_2"_hs)
.ro("resource"_hs)
.bind("task_1"_hs)
.rw("resource"_hs);
```
What happens here is that the resource first _sees_ a read-only access request
from the first task, then a read-only request from the second task and finally
a read-write request from the first task.<br/>
Although this definition would probably be counted as an error, the resulting
graph may be unexpected. In fact, this consists of a single edge outgoing from
the second task and directed to the first task.<br/>
To intuitively understand what happens, it is enough to think of the fact that a
task never has an edge pointing to itself.
While not obvious, this approach has its pros and cons like any other solution.
For example, creating loops is actually simple in the context of resource-based
graph generations:
```cpp
builder
.bind("task_1"_hs)
.rw("resource"_hs)
.bind("task_2"_hs)
.rw("resource"_hs)
.bind("task_1"_hs)
.rw("resource"_hs);
```
As expected, this definition leads to the creation of two edges that define a
loop between the two tasks.
As a general rule, rebinding resources and tasks is highly discouraged because
it could lead to subtle bugs if users do not know what they are doing.<br/>
However, once the mechanisms of resource-based graph generation are understood,
it can offer to the expert user flexibility and a range of possibilities
otherwise inaccessible.
## Fake resources and order of execution
The flow builder does not offer the ability to specify when a task should run
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 is possible to make use of fake resources to rule
on the execution order:
```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_1` **before** `task_2` and `task_3`.
This is due to the fact that the former sets a read-write requirement on a fake
resource that the other tasks also want to access in read-only mode.<br/>
Similarly, it is 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
other tasks.
## Sync points
Sometimes it is 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 is 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 node lies in the fact that,
more often than not, they also perform operations on resources.<br/>
If this is not 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();
```
Searching 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()) {
// ...
}
}
```
Then it is possible to instantiate an execution graph by means of other
functions such as `out_edges` to retrieve the children of a given task or
`edges` to get the identifiers.

View File

@@ -1,11 +1,17 @@
# Push EnTT across boundaries # Push EnTT across boundaries
<!--
@cond TURN_OFF_DOXYGEN
-->
# Table of Contents # Table of Contents
* [Working across boundaries](#working-across-boundaries) * [Working across boundaries](#working-across-boundaries)
* [Smooth until proven otherwise](#smooth-until-proven-otherwise) * [Smooth until proven otherwise](#smooth-until-proven-otherwise)
* [Meta context](#meta-context) * [Meta context](#meta-context)
* [Memory management](#memory-management) * [Memory management](#memory-management)
<!--
@endcond TURN_OFF_DOXYGEN
-->
# Working across boundaries # Working across boundaries
@@ -13,58 +19,71 @@
general and on GNU/Linux when default visibility was set to hidden. The 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 limitation was mainly due to a custom utility used to assign unique, sequential
identifiers with different types.<br/> identifiers with different types.<br/>
Fortunately, nowadays `EnTT` works smoothly across boundaries. Fortunately, nowadays using `EnTT` across boundaries is much easier.
## Smooth until proven otherwise ## Smooth until proven otherwise
Many classes in `EnTT` make extensive use of type erasure for their purposes. Many classes in `EnTT` make extensive use of type erasure for their purposes.
This raises the need to identify objects whose type has been erased.<br/> 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 The `type_hash` class template is how identifiers are generated and thus made
available to the rest of the library. In general, this class arouses little 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 interest. The only exception is when a conflict between identifiers occurs
(definitely uncommon though) or when the default solution proposed by `EnTT` is (definitely uncommon though) or when the default solution proposed by `EnTT`
not suitable for the user's purposes.<br/> isn't suitable for the user's purposes.<br/>
The section dedicated to `type_info` contains all the details to get around the 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. issue in a concise and elegant way. Please refer to the specific documentation.
When working with linked libraries, compile definitions `ENTT_API_EXPORT` and When working with linked libraries, compile definitions `ENTT_API_EXPORT` and
`ENTT_API_IMPORT` are there to import or export symbols, so as to make `ENTT_API_IMPORT` can be used where there is a need to import or export symbols,
everything work nicely across boundaries.<br/> so as to make everything work nicely across boundaries.<br/>
On the other hand, everything should run smoothly when working with plugins or On the other hand, everything should run smoothly when working with plugins or
shared libraries that do not export any symbols. shared libraries that don't export any symbols.
For those who need more details, the test suite contains many examples covering For anyone who needs more details, the test suite contains multiple examples
the most common cases (see the `lib` directory for all details).<br/> covering the most common cases (see the `lib` directory for all details).<br/>
It goes without saying that it is impossible to cover **all** possible cases. 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. However, what is offered should hopefully serve as a basis for all of them.
## Meta context ## Meta context
The runtime reflection system deserves a special mention when it comes to using The runtime reflection system deserves a special mention when it comes to using
it across boundaries.<br/> it across boundaries.<br/>
Since it is linked already to a static context to which the elements are Since it's linked already to a static context to which the visible components
attached and different contexts do not relate to each other, they must be are attached and different contexts don't relate to each other, they must be
_shared_ to allow the use of meta types across boundaries. _shared_ to allow the use of meta types across boundaries.
Fortunately, sharing a context is also trivial to do. First of all, the local Sharing a context is trivial though. First of all, the local one must be
one is acquired in the main space: acquired in the main space:
```cpp ```cpp
auto handle = entt::locator<entt::meta_ctx>::handle(); entt::meta_ctx ctx{};
``` ```
Then, it is passed to the receiving space that sets it as its default context, Then, it must passed to the receiving space that will set it as its global
thus discarding or storing aside the local one: context, thus releasing the local one that remains available but is no longer
referred to by the runtime reflection system:
```cpp ```cpp
entt::locator<entt::meta_ctx>::reset(handle); entt::meta_ctx::bind(ctx);
``` ```
From now on, both spaces refer to the same context and to it are all new meta From now on, both spaces will refer to the same context and on it will be
types attached, no matter where they are created.<br/> attached the new visible meta types, no matter where they are created.<br/>
Note that _replacing_ the main context does not also propagate changes across A context can also be reset and then associated again locally as:
boundaries. In other words, replacing a context results in the decoupling of the
two sides and therefore a divergence in the contents. ```cpp
entt::meta_ctx::bind(entt::meta_ctx{});
```
This is allowed because local and global contexts are separated. Therefore, it's
always possible to make the local context the current one again.
Before to release a context, all locally registered types should be reset to
avoid dangling references. Otherwise, if a type is accessed from another space
by name, there could be an attempt to address its parts that are no longer
available.
## Memory Management ## Memory Management
@@ -81,10 +100,9 @@ is unknown to the former, a dedicated pool is created within the registry on
first use.<br/> first use.<br/>
As one can guess, this pool is instantiated on a different side of the boundary 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 from the `registry`. Therefore, the instance is now managing memory from
different spaces, and this can quickly lead to crashes if not properly different spaces and this can quickly lead to crashes if not properly addressed.
addressed.
To overcome the risk, it is recommended to use well-defined interfaces that make 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 fundamental types pass through the boundaries, isolating the instances of the
`EnTT` classes from time to time and as appropriate.<br/> `EnTT` classes from time to time and as appropriate.<br/>
Refer to the test suite for some examples, read the documentation available Refer to the test suite for some examples, read the documentation available

View File

@@ -1,41 +1,22 @@
# EnTT in Action # EnTT in Action
# Table of Contents
* [Introduction](#introduction)
* [EnTT in Action](#entt-in-action)
* [Games](#games)
* [Engines and the like](#engines-and-the-like)
* [Articles, videos and blog posts](#articles-videos-and-blog-posts)
* [Any Other Business](#any-other-business)
# Introduction
`EnTT` is widely used in private and commercial applications. I cannot even `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 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 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 source projects based on `EnTT` and didn't hold back when it came to documenting
documenting them. them.
Below an incomplete list of games, applications and articles that can be used as Below an incomplete list of games, applications and articles that can be used as
a reference.<br/> a reference. Where I put the word _apparently_ means that the use of `EnTT` is
Where I put the word _apparently_ means that the use of `EnTT` is documented but documented but the authors didn't make explicit announcements or contacted me
the authors did not make explicit announcements or contacted me directly. directly.
If you know of other resources out there that are about `EnTT`, feel free to I hope this list can grow much more in the future:
open an issue or a PR. I will be glad to add them to this page.<br/>
I hope the following lists can grow much more in the future.
# EnTT in Action
## Games
* Games:
* [Minecraft](https://minecraft.net/en-us/attribution/) by * [Minecraft](https://minecraft.net/en-us/attribution/) by
[Mojang](https://mojang.com/): of course, **that** Minecraft, see the [Mojang](https://mojang.com/): of course, **that** Minecraft, see the
open source attributions page for more details. open source attributions page for more details.
* [Minecraft Legends](https://www.minecraft.net/it-it/about-legends) by
[Mojang](https://mojang.com/): an action strategy game where users have to
fight to defend the Overworld.
* [Minecraft Earth](https://www.minecraft.net/en-us/about-earth) by * [Minecraft Earth](https://www.minecraft.net/en-us/about-earth) by
[Mojang](https://mojang.com/): an augmented reality game for mobile, that [Mojang](https://mojang.com/): an augmented reality game for mobile, that
lets users bring Minecraft into the real world. lets users bring Minecraft into the real world.
@@ -48,11 +29,11 @@ I hope the following lists can grow much more in the future.
* [Apparently](https://www.youtube.com/watch?v=P8xvOA3ikrQ&t=1105s) * [Apparently](https://www.youtube.com/watch?v=P8xvOA3ikrQ&t=1105s)
[Call of Duty: Vanguard](https://www.callofduty.com/vanguard) by [Call of Duty: Vanguard](https://www.callofduty.com/vanguard) by
[Sledgehammer Games](https://www.sledgehammergames.com/): I can neither [Sledgehammer Games](https://www.sledgehammergames.com/): I can neither
confirm nor deny, but there is a license I know in the credits. confirm nor deny but there is a license I know in the credits.
* Apparently [D&D Dark Alliance](https://darkalliance.wizards.com) by * Apparently [D&D Dark Alliance](https://darkalliance.wizards.com) by
[Wizards of the Coast](https://company.wizards.com): your party, their [Wizards of the Coast](https://company.wizards.com): your party, their
funeral. funeral.
* [TiltedEvolution](https://github.com/tiltedphoques/TiltedEvolution) by * [TiltedOnline](https://github.com/tiltedphoques/TiltedOnline) by
[Tilted Phoques](https://github.com/tiltedphoques): Skyrim and Fallout 4 mod [Tilted Phoques](https://github.com/tiltedphoques): Skyrim and Fallout 4 mod
to play online. to play online.
* [Antkeeper](https://github.com/antkeeper/antkeeper-source): an ant colony * [Antkeeper](https://github.com/antkeeper/antkeeper-source): an ant colony
@@ -73,7 +54,7 @@ I hope the following lists can grow much more in the future.
puzzler with logic gates and other cool stuff. puzzler with logic gates and other cool stuff.
[Check it out](https://indi-kernick.itch.io/the-machine-web-version). [Check it out](https://indi-kernick.itch.io/the-machine-web-version).
* [EnTTPong](https://github.com/DomRe/EnttPong): a basic game made to showcase * [EnTTPong](https://github.com/DomRe/EnttPong): a basic game made to showcase
different parts of `EnTT` and C++17. different parts of EnTT and C++17.
* [Randballs](https://github.com/gale93/randballs): simple `SFML` and `EnTT` * [Randballs](https://github.com/gale93/randballs): simple `SFML` and `EnTT`
playground. playground.
* [EnTT Tower Defense](https://github.com/Daivuk/tddod): a data oriented tower * [EnTT Tower Defense](https://github.com/Daivuk/tddod): a data oriented tower
@@ -97,64 +78,24 @@ I hope the following lists can grow much more in the future.
by Quake. by Quake.
* [Destroid](https://github.com/tyrannicaltoucan/destroid): _one-bazillionth_ * [Destroid](https://github.com/tyrannicaltoucan/destroid): _one-bazillionth_
arcade game about shooting dirty rocks in space, inspired by Asteroids. arcade game about shooting dirty rocks in space, inspired by Asteroids.
* [Wanderer](https://github.com/albin-johansson/wanderer): a 2D * [Wanderer](https://github.com/albin-johansson/wanderer): a 2D exploration
exploration-based indie game. based indie game.
* [Spelunky® Classic remake](https://github.com/dbeef/spelunky-psp): a truly * [Spelunky® Classic remake](https://github.com/dbeef/spelunky-psp): a truly
multiplatform experience with a rewrite from scratch. multiplatform experience with a rewrite from scratch.
* [CubbyTower](https://github.com/utilForever/CubbyTower): a simple tower * [CubbyTower](https://github.com/utilForever/CubbyTower): a simple tower
defense game using C++ with Entity Component System (ECS). defense game using C++ with Entity Component System (ECS).
* [Runeterra](https://github.com/utilForever/Runeterra): Legends of Runeterra * [Runeterra](https://github.com/utilForever/Runeterra): Legends of Runeterra
simulator using C++ with some reinforcement learning. simulator using C++ with some reinforcement learning.
* [Black Sun](https://store.steampowered.com/app/1670930/Black_Sun/): fly your * [Black Sun](https://store.steampowered.com/app/1670930/Black_Sun/): fly your
spaceship through a large 2D open world. space ship through a large 2D open world.
* [PokeMaster](https://github.com/utilForever/PokeMaster): Pokémon Battle * [PokeMaster](https://github.com/utilForever/PokeMaster): Pokemon Battle
simulator using C++ with some reinforcement learning. simulator using C++ with some reinforcement learning.
* [HomeHearth](https://youtu.be/GrEWl8npL9Y): choose your hero, protect the * [HomeHearth](https://youtu.be/GrEWl8npL9Y): choose your hero, protect the
town, before it is too late. town, before it's too late.
* [City Builder Game](https://github.com/PhiGei2000/CityBuilderGame): a simple * [City Builder Game](https://github.com/PhiGei2000/CityBuilderGame): a simple
city-building game using C++ and OpenGL. 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`.
* [Hellbound](https://buas.itch.io/hellbound): a top-down action rogue-like
where to fight colossal demons in procedurally generated levels of hell.
* [Saurian Sorcery](https://github.com/cajallen/spellbook): a tower defense
game where to assemble a tribe of lizards to defend against robot invaders.
* [robotfindskitten](https://github.com/autogalkin/robotfindskitten): a clone
of `robotfindskitten` inside `Notepad.exe`, powered by `EnTT`.
* [Orion](https://github.com/alekskoloch/Orion): Outer-space Research and
Interstellar Observation Network (a space shooter game).
* [EnTT Boids](https://github.com/DanielEliasib/entt_boids): a simple boids
implementation using `EnTT` and `Raylib`.
* [PalmRide: After Flight](https://store.steampowered.com/app/2812540/PalmRide_After_Flight/):
an on-rails shooter with retro outrun aesthetics.
* [Exhibition of Speed](https://store.steampowered.com/app/2947450/Exhibition_of_Speed/):
build your own car and go racing.
* [Lichgate](https://buas.itch.io/lichgate): top-down action rogue-like where
users unlock abilities to fight horde of enemies in an endless world.
* [Letalka](https://github.com/dviglo2d/dviglo2d/tree/main/games/letalka):
small demo game with ships and bullets flying everywhere on the screen.
* [Lichgate](https://buas.itch.io/lichgate): step into the robes of a powerful
mage determined to halt the relentless hordes of undead.
* [You Are Circle](https://store.steampowered.com/app/3578190/You_Are_Circle/):
a roguelite top-down shooter with a high-contrast vector line aesthetic.
* [EnTT Dino](https://github.com/omgitsaheadcrab/entt_dino): a Dinosaur Game
clone in C++ using only `SDL2` and `EnTT`.
* [Bim!](https://github.com/j-jorge/bim): a last-man-standing arcade online
game for Android.
* [MonsterWar](https://github.com/WispSnow/MonsterWar): a tower defense game
developed in C++ with `SDL3`, `EnTT`, and a few other libraries.
## Engines and the like: * Engines and the like:
* [Hazel Engine](https://github.com/TheCherno/Hazel): a work in progress
engine created by [The Cherno](https://github.com/TheCherno/Hazel) during
one of his most famous video series.
* [Aether Engine](https://hadean.com/spatial-simulation/) * [Aether Engine](https://hadean.com/spatial-simulation/)
[v1.1+](https://docs.hadean.com/v1.1/Licenses/) by [v1.1+](https://docs.hadean.com/v1.1/Licenses/) by
[Hadean](https://hadean.com/): a library designed for spatially partitioning [Hadean](https://hadean.com/): a library designed for spatially partitioning
@@ -200,7 +141,7 @@ I hope the following lists can grow much more in the future.
framework in C++17 for backend development. framework in C++17 for backend development.
* [Unity/EnTT](https://github.com/TongTungGiang/unity-entt): tech demo of a * [Unity/EnTT](https://github.com/TongTungGiang/unity-entt): tech demo of a
native simulation layer using `EnTT` and `Unity` as a rendering engine. native simulation layer using `EnTT` and `Unity` as a rendering engine.
* [OverEngine](https://github.com/OverShifted/OverEngine): an overengineered * [OverEngine](https://github.com/OverShifted/OverEngine): an over-engineered
game engine. game engine.
* [Electro](https://github.com/Electro-Technologies/Electro): high performance * [Electro](https://github.com/Electro-Technologies/Electro): high performance
3D game engine with a high emphasis on rendering. 3D game engine with a high emphasis on rendering.
@@ -209,54 +150,14 @@ I hope the following lists can grow much more in the future.
* [Becketron](https://github.com/Doctor-Foxling/Becketron): a game engine * [Becketron](https://github.com/Doctor-Foxling/Becketron): a game engine
written mostly in C++. written mostly in C++.
* [Spatial Engine](https://github.com/luizgabriel/Spatial.Engine): a * [Spatial Engine](https://github.com/luizgabriel/Spatial.Engine): a
cross-platform engine created on top of Google's filament rendering engine. cross-platform engine created on top of google's filament rendering engine.
* [Kaguya](https://github.com/KaiH0717/Kaguya): D3D12 Rendering Engine. * [Kaguya](https://github.com/KaiH0717/Kaguya): D3D12 Rendering Engine.
* [OpenAWE](https://github.com/OpenAWE-Project/OpenAWE): open implementation * [OpenAWE](https://github.com/OpenAWE-Project/OpenAWE): open implementation
of the Alan Wake Engine. of the Alan Wake Engine.
* [Nazara Engine](https://github.com/DigitalPulseSoftware/NazaraEngine): fast, * [Nazara Engine](https://github.com/DigitalPulseSoftware/NazaraEngine): fast,
cross-platform, object-oriented API to help in daily developer life. cross-platform, object-oriented API to help in daily developer life.
* [Billy Engine](https://github.com/billy4479/BillyEngine): some kind of 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.
* [The Worst Engine](https://github.com/Parasik72/TWE): a game engine based on
OpenGL.
* [Ecsact](https://ecsact.dev/): a language aimed at describing ECS, with a
[runtime implementation](https://github.com/ecsact-dev/ecsact_rt_entt) based
on `EnTT`.
* [AGE (Arc Game Engine)](https://github.com/MohitSethi99/ArcGameEngine): an
open-source engine for building 2D & 3D real-time rendering and interactive
contents.
* [Kengine](https://github.com/phisko/kengine): the _Koala engine_ is a game
engine entirely implemented as an entity-component-system.
* [Scion2D](https://github.com/dwjclark11/Scion2D): 2D game engine with
[YouTube series](https://www.youtube.com/playlist?list=PL3HUvSWOJR7XRDwVVQqqWO-zyyscb8L-v)
included.
* [EnTT Editor](https://github.com/TheDimin/EnttEditor): an editor for `EnTT`
libary that combines its built-in reflection system with `ImGui`.
* [Era Game Engine](https://github.com/EldarMuradov/EraGameEngine): a modern
ECS-based game engine.
* [Core SDK of Trollworks engine](https://github.com/trollworks/sdk-core): 2D
game engine based on procrastination.
* [Rocky](https://github.com/pelicanmapping/rocky): 3D geospatial application
engine.
* [Donner](https://github.com/jwmcglynn/donner): a modern C++20 SVG2 rendering
API with CSS3.
* [Coral Engine](https://github.com/GuusKemperman/CoralEngine): open-source
student engine with the tools to make games in C++ and Visual scripting.
* [Star Engine](https://github.com/HODAKdev/StarEngine): an Advanced C++
DirectX 11 game engine.
* [Darmok](https://github.com/miguelibero/darmok): another C++ game engine.
* [Magique](https://github.com/gk646/magique): 2D game engine for programmers
(or those yet to be).
* [Physecs](https://github.com/thfProjects/Physecs): real-time 3D rigid body
physics simulation built on `EnTT`.
* [KODZA](https://gitlab.com/arqitek/kodza/): A work in progress game engine.
* [Omnix](https://github.com/Ace-codes-swift/Omnix): An under-development,
multi-purpose 3D engine for `macOS`, using `EnTT` for the ECS.
## Articles, videos and blog posts:
* Articles, videos and blog posts:
* [Some posts](https://skypjack.github.io/tags/#entt) on my personal * [Some posts](https://skypjack.github.io/tags/#entt) on my personal
[blog](https://skypjack.github.io/) are about `EnTT`, for those who want to [blog](https://skypjack.github.io/) are about `EnTT`, for those who want to
know **more** on this project. know **more** on this project.
@@ -280,23 +181,6 @@ I hope the following lists can grow much more in the future.
- ... And so on. - ... And so on.
[Check out](https://www.youtube.com/channel/UCQ-W1KE9EYfdxhL6S4twUNw) the [Check out](https://www.youtube.com/channel/UCQ-W1KE9EYfdxhL6S4twUNw) the
_Game Engine Series_ by The Cherno for more videos. _Game Engine Series_ by The Cherno for more videos.
* [Game Engine series](https://www.youtube.com/@JADE-iteGames/videos) by
[dwjclark11](https://github.com/dwjclark11) (not just `EnTT` but a lot of
it):
- [Getting into ECS](https://youtu.be/k9CbonLopJU?si=za3Tisyc96_92DWM)
- [Creating ECS Wrapper Classes](https://youtu.be/yetyuMJRdbo?si=PJTkmap4Ysqbzb_M)
- [Runtime Reflection using EnTT meta](https://youtu.be/GrXV5A07GTY?si=fKdWTj9AOhnhtiXq)
- [Adding entt::meta and Sol2 bindings](https://youtu.be/IM55JgxOqFA?si=rsbb4AG_NVh4IUmD)
(with [part two](https://youtu.be/-PTt-b1tzRw?si=zPJ4vEluyheMcNgO) too)
- ... And so on.
[Check it out](https://www.youtube.com/playlist?list=PL3HUvSWOJR7XRDwVVQqqWO-zyyscb8L-v)
for more videos.
* [Warmonger Dynasty devlog series](https://david-delassus.medium.com/list/warmonger-dynasty-devlogs-f64b71f556de)
by [linkdd](https://github.com/linkdd): an interesting walkthrough of
developing a game (also) with EnTT.
* [Use EnTT When You Need An ECS](https://www.codingwiththomas.com/blog/use-entt-when-you-need-an-ecs)
by [Thomas](https://www.codingwiththomas.com/): I could not have said it
better.
* [Space Battle: Huge edition](http://victor.madtriangles.com/code%20experiment/2018/06/11/post-ecs-battle-huge.html): * [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. huge space battle built entirely from scratch.
* [Space Battle](https://github.com/vblanco20-1/ECS_SpaceBattle): huge space * [Space Battle](https://github.com/vblanco20-1/ECS_SpaceBattle): huge space
@@ -319,24 +203,17 @@ I hope the following lists can grow much more in the future.
project retrospect by [Eric Hildebrand](https://www.erichildebrand.net/). 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 Entity Component System Gaming Library](https://gamefromscratch.com/entt-entity-component-system-gaming-library/):
`EnTT` on GameFromScratch.com. `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:
* Any Other Business:
* [ArcGIS Runtime SDKs](https://developers.arcgis.com/arcgis-runtime/) by * [ArcGIS Runtime SDKs](https://developers.arcgis.com/arcgis-runtime/) by
[Esri](https://www.esri.com/): they use `EnTT` for the internal ECS and the [Esri](https://www.esri.com/): they use `EnTT` for the internal ECS and the
cross-platform C++ rendering engine. The SDKs are used by a lot of 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 enterprise custom apps, as well as by Esri for its own public applications
such as such as
[Explorer](https://play.google.com/store/apps/details?id=com.esri.explorer), [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) [Collector](https://play.google.com/store/apps/details?id=com.esri.arcgis.collector)
and and
[Navigator](https://play.google.com/store/apps/details?id=com.esri.navigator). [Navigator](https://play.google.com/store/apps/details?id=com.esri.navigator).
* [OneArc](https://onearc.com/): [licenses](https://onearc.com/third-party-licensors/)
do not lie. Their products use EnTT in some way, but it is not known _what_
way.
* [FASTSUITE Edition 2](https://www.fastsuite.com/en_EN/fastsuite/fastsuite-edition-2.html) * [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 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 `EnTT` to drive their simulation, that is, the communication between robot
@@ -346,7 +223,7 @@ I hope the following lists can grow much more in the future.
* [Project Lagrange](https://github.com/adobe/lagrange): a robust geometry * [Project Lagrange](https://github.com/adobe/lagrange): a robust geometry
processing library by [Adobe](https://github.com/adobe). processing library by [Adobe](https://github.com/adobe).
* [AtomicDEX](https://github.com/KomodoPlatform/atomicDEX-Desktop): a secure * [AtomicDEX](https://github.com/KomodoPlatform/atomicDEX-Desktop): a secure
wallet and noncustodial decentralized exchange rolled into one application. wallet and non-custodial decentralized exchange rolled into one application.
* [Apparently](https://www.linkedin.com/in/skypjack/) * [Apparently](https://www.linkedin.com/in/skypjack/)
[NIO](https://www.nio.io/): there was a collaboration to make some changes [NIO](https://www.nio.io/): there was a collaboration to make some changes
to `EnTT`, at the time used for internal projects. to `EnTT`, at the time used for internal projects.
@@ -369,3 +246,6 @@ I hope the following lists can grow much more in the future.
* GitHub contains also * GitHub contains also
[many other examples](https://github.com/search?o=desc&q=%22skypjack%2Fentt%22&s=indexed&type=Code) [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. 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.

View File

@@ -1,16 +1,21 @@
# Crash Course: service locator # Crash Course: service locator
<!--
@cond TURN_OFF_DOXYGEN
-->
# Table of Contents # Table of Contents
* [Introduction](#introduction) * [Introduction](#introduction)
* [Service locator](#service-locator) * [Service locator](#service-locator)
* [Opaque handles](#opaque-handles) <!--
@endcond TURN_OFF_DOXYGEN
-->
# Introduction # Introduction
Usually, service locators are tightly bound to the services they expose. It is Usually service locators are tightly bound to the services they expose and it's
hard to define a general purpose solution.<br/> 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 This tiny class tries to fill the gap and to get rid of the burden of defining a
different specific locator for each application. different specific locator for each application.
# Service locator # Service locator
@@ -27,7 +32,7 @@ entt::locator<interface>::allocate_emplace<service>(allocator, argument);
The difference is that the latter expects an allocator as the first argument and The difference is that the latter expects an allocator as the first argument and
uses it to allocate the service itself.<br/> uses it to allocate the service itself.<br/>
Once a service is set up, it is retrieved using the `value` function: Once a service has been set up, it's retrieved using the value function:
```cpp ```cpp
interface &service = entt::locator<interface>::value(); interface &service = entt::locator<interface>::value();
@@ -45,38 +50,7 @@ if(entt::locator<interface>::has_value()) {
interface &service = entt::locator<interface>::value_or<fallback_impl>(argument); interface &service = entt::locator<interface>::value_or<fallback_impl>(argument);
``` ```
All arguments are used only if necessary, that is, if a service does not already 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 exist and therefore the fallback service is constructed and returned. In all
other cases, they are discarded.<br/> other cases, they are discarded.<br/>
Finally, to reset a service, use the `reset` function. Finally, to reset a service, use the `reset` function.
## Opaque handles
Sometimes it is useful to _transfer_ a copy of a service to another locator. For
example, when working across boundaries it is common to _share_ a service with a
dynamically loaded module.<br/>
Options are not 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 is worth noting that it is 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 will not 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 will not
propagate to the other locators. Therefore, a module that shares the ownership
of the original audio service is still able to emit sounds.

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,8 @@
# Crash Course: poly # Crash Course: poly
<!--
@cond TURN_OFF_DOXYGEN
-->
# Table of Contents # Table of Contents
* [Introduction](#introduction) * [Introduction](#introduction)
@@ -11,6 +14,9 @@
* [Inheritance](#inheritance) * [Inheritance](#inheritance)
* [Static polymorphism in the wild](#static-polymorphism-in-the-wild) * [Static polymorphism in the wild](#static-polymorphism-in-the-wild)
* [Storage size and alignment requirement](#storage-size-and-alignment-requirement) * [Storage size and alignment requirement](#storage-size-and-alignment-requirement)
<!--
@endcond TURN_OFF_DOXYGEN
-->
# Introduction # Introduction
@@ -20,16 +26,17 @@ This module aims to make it simple and easy to use.
The library allows to define _concepts_ as interfaces to fulfill with concrete The library allows to define _concepts_ as interfaces to fulfill with concrete
classes without having to inherit from a common base.<br/> classes without having to inherit from a common base.<br/>
Among others, this is one of the advantages of static polymorphism in general 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 and of a generic wrapper like that offered by the `poly` class template in
particular.<br/> particular.<br/>
The result is an object to pass around as such and not through a reference or a What users get is an object that can be passed around as such and not through a
pointer, as it happens when it comes to working with dynamic polymorphism. 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 Since the `poly` class template makes use of `entt::any` internally, it also
supports most of its feature. For example, the possibility to create aliases to supports most of its feature. Among the most important, the possibility to
existing and thus unmanaged objects. This allows users to exploit the static create aliases to existing and thus unmanaged objects. This allows users to
polymorphism while maintaining ownership of objects.<br/> exploit the static polymorphism while maintaining ownership of objects.<br/>
Likewise, the `poly` class template also benefits from the small buffer Likewise, the `poly` class template also benefits from the small buffer
optimization offered by the `entt::any` class and therefore minimizes the number optimization offered by the `entt::any` class and therefore minimizes the number
of allocations, avoiding them altogether where possible. of allocations, avoiding them altogether where possible.
@@ -37,7 +44,7 @@ of allocations, avoiding them altogether where possible.
## Other libraries ## Other libraries
There are some very interesting libraries regarding static polymorphism.<br/> There are some very interesting libraries regarding static polymorphism.<br/>
The ones that I like more are: Among all, the two that I prefer are:
* [`dyno`](https://github.com/ldionne/dyno): runtime polymorphism done right. * [`dyno`](https://github.com/ldionne/dyno): runtime polymorphism done right.
* [`Poly`](https://github.com/facebook/folly/blob/master/folly/docs/Poly.md): * [`Poly`](https://github.com/facebook/folly/blob/master/folly/docs/Poly.md):
@@ -45,12 +52,12 @@ The ones that I like more are:
object wrapper. object wrapper.
The former is admittedly an experimental library, with many interesting ideas. The former is admittedly an experimental library, with many interesting ideas.
I have some doubts about the usefulness of some features in real world projects, 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 but perhaps my lack of experience comes into play here. In my opinion, its only
flaw is the API that I find slightly more cumbersome than other solutions.<br/> 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 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 opted for different choices in the implementation of both the final API and some
features. feature.
Either way, the authors are gurus of the C++ community, people I only have to Either way, the authors are gurus of the C++ community, people I only have to
learn from. learn from.
@@ -62,18 +69,18 @@ use the terminology introduced by Eric Niebler) is to define a _concept_ that
types will have to adhere to.<br/> types will have to adhere to.<br/>
For this purpose, the library offers a single class that supports both deduced For this purpose, the library offers a single class that supports both deduced
and fully defined interfaces. Although having interfaces deduced automatically and fully defined interfaces. Although having interfaces deduced automatically
is convenient and allows users to write less code in most cases, it has some is convenient and allows users to write less code in most cases, this has some
limitations. It is therefore useful to be able to get around the deduction by limitations and it's therefore useful to be able to get around the deduction by
providing a custom definition for the static virtual table. providing a custom definition for the static virtual table.
Once the interface is defined, a generic implementation is needed to fulfill the Once the interface is defined, it will be sufficient to provide a generic
concept itself.<br/> implementation to fulfill the concept.<br/>
Also in this case, the library allows customizations based on types or families 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. of types, so as to be able to go beyond the generic case where necessary.
## Deduced interface ## Deduced interface
This is how a concept with a deduced interface is defined: This is how a concept with a deduced interface is introduced:
```cpp ```cpp
struct Drawable: entt::type_list<> { struct Drawable: entt::type_list<> {
@@ -86,7 +93,7 @@ struct Drawable: entt::type_list<> {
}; };
``` ```
It is recognizable by the fact that it inherits from an empty type list.<br/> 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 Functions can also be const, accept any number of parameters and return a type
other than `void`: other than `void`:
@@ -101,12 +108,12 @@ struct Drawable: entt::type_list<> {
}; };
``` ```
In this case, all parameters are passed to `invoke` after the reference to 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/> `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 As for `invoke`, this is a name that is injected into the _concept_ through
`Base`, from which one must necessarily inherit. Since it is also a dependent `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 name, the `this-> template` form is unfortunately necessary due to the rules of
the language. However, there also exists an alternative that goes through an the language. However, there exists also an alternative that goes through an
external call: external call:
```cpp ```cpp
@@ -158,12 +165,12 @@ struct Drawable: entt::type_list<bool(int) const> {
Why should a user fully define a concept if the function types are the same as Why should a user fully define a concept if the function types are the same as
the deduced ones?<br> the deduced ones?<br>
In fact, this is the limitation that can be worked around by manually defining Because, in fact, this is exactly the limitation that can be worked around by
the static virtual table. manually defining the static virtual table.
When things are deduced, there is an implicit constraint.<br/> When things are deduced, there is an implicit constraint.<br/>
If the concept exposes a member function called `draw` with function type If the concept exposes a member function called `draw` with function type
`void()`, a concept is satisfied: `void()`, a concept can be satisfied:
* Either by a class that exposes a member function with the same name and the * Either by a class that exposes a member function with the same name and the
same signature. same signature.
@@ -171,9 +178,9 @@ If the concept exposes a member function called `draw` with function type
* Or through a lambda that makes use of existing member functions from the * Or through a lambda that makes use of existing member functions from the
interface itself. interface itself.
In other words, it is not possible to make use of functions not belonging to the In other words, it's not possible to make use of functions not belonging to the
interface, even if they are part of the types that fulfill the concept.<br/> interface, even if they are present in the types that fulfill the concept.<br/>
Similarly, it is not possible to deduce a function in the static virtual table 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 with a function type different from that of the associated member function in
the interface itself. the interface itself.
@@ -182,7 +189,7 @@ allows maximum flexibility when providing the implementation for a concept.
## Fulfill a concept ## Fulfill a concept
The `impl` alias template of a concept is used to define how it is fulfilled: The `impl` alias template of a concept is used to define how it's fulfilled:
```cpp ```cpp
struct Drawable: entt::type_list<> { struct Drawable: entt::type_list<> {
@@ -193,8 +200,8 @@ struct Drawable: entt::type_list<> {
}; };
``` ```
In this case, it is stated that the `draw` method of a generic type is enough to In this case, it's stated that the `draw` method of a generic type will be
satisfy the requirements of the `Drawable` concept.<br/> enough to satisfy the requirements of the `Drawable` concept.<br/>
Both member functions and free functions are supported to fulfill concepts: Both member functions and free functions are supported to fulfill concepts:
```cpp ```cpp
@@ -211,9 +218,9 @@ struct Drawable: entt::type_list<void()> {
Likewise, as long as the parameter types and return type support conversions to 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 and from those of the function type referenced in the static virtual table, the
actual implementation may differ in its function type since it is erased actual implementation may differ in its function type since it's erased
internally.<br/> internally.<br/>
Moreover, the `self` parameter is not strictly required by the system and can be Moreover, the `self` parameter isn't strictly required by the system and can be
left out for free functions if not required. left out for free functions if not required.
Refer to the inline documentation for more details. Refer to the inline documentation for more details.
@@ -221,7 +228,7 @@ Refer to the inline documentation for more details.
# Inheritance # Inheritance
_Concept inheritance_ is straightforward due to how poly looks like in `EnTT`. _Concept inheritance_ is straightforward due to how poly looks like in `EnTT`.
Therefore, it is quite easy to build hierarchies of concepts if necessary.<br/> 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 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. _family_, that is, they must be either all deduced or all defined.
@@ -230,44 +237,45 @@ For a deduced concept, inheritance is achieved in a few steps:
```cpp ```cpp
struct DrawableAndErasable: entt::type_list<> { struct DrawableAndErasable: entt::type_list<> {
template<typename Base> template<typename Base>
struct type: Drawable::type<Base> { struct type: typename Drawable::template type<Base> {
static constexpr auto base = Drawable::impl<Drawable::type<entt::poly_inspector>>::size; static constexpr auto base = std::tuple_size_v<typename entt::poly_vtable<Drawable>::type>;
void erase() { entt::poly_call<base + 0>(*this); } void erase() { entt::poly_call<base + 0>(*this); }
}; };
template<typename Type> template<typename Type>
using impl = entt::value_list_cat_t< using impl = entt::value_list_cat_t<
Drawable::impl<Type>, typename Drawable::impl<Type>,
entt::value_list<&Type::erase> entt::value_list<&Type::erase>
>; >;
}; };
``` ```
The static virtual table is empty and must remain so.<br/> The static virtual table is empty and must remain so.<br/>
On the other hand, `type` no longer inherits from `Base`. Instead, it forwards 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 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 size of the static virtual table of the base class is used as an offset for the
the local indexes.<br/> local indexes.<br/>
Finally, by means of the `value_list_cat_t` utility, the implementation consists Finally, by means of the `value_list_cat_t` utility, the implementation consists
in appending the new functions to the previous list. in appending the new functions to the previous list.
As for a defined concept instead, the list of types is _extended_ in a similar As for a defined concept instead, also the list of types must be extended, in a
way to what is shown for the implementation of the above concept.<br/> similar way to what is shown for the implementation of the above concept.<br/>
To do this, it is useful to declare a function that allows to convert a To do this, it's useful to declare a function that allows to convert a _concept_
_concept_ into its underlying `type_list` object: into its underlying `type_list` object:
```cpp ```cpp
template<typename... Type> template<typename... Type>
entt::type_list<Type...> as_type_list(const entt::type_list<Type...> &); entt::type_list<Type...> as_type_list(const entt::type_list<Type...> &);
``` ```
The definition is not strictly required, since the function is only used through The definition isn't strictly required, since the function will only be used
a `decltype` as it follows: through a `decltype` as it follows:
```cpp ```cpp
struct DrawableAndErasable: entt::type_list_cat_t< struct DrawableAndErasable: entt::type_list_cat_t<
decltype(as_type_list(std::declval<Drawable>())), decltype(as_type_list(std::declval<Drawable>())),
entt::type_list<void()>> { entt::type_list<void()>
> {
// ... // ...
}; };
``` ```
@@ -278,8 +286,9 @@ Everything else is the same as already shown instead.
# Static polymorphism in the wild # Static polymorphism in the wild
Once the _concept_ and implementation are defined, it is possible to use the Once the _concept_ and implementation have been introduced, it will be possible
`poly` class template to _wrap_ instances that meet the requirements: to use the `poly` class template to contain instances that meet the
requirements:
```cpp ```cpp
using drawable = entt::poly<Drawable>; using drawable = entt::poly<Drawable>;
@@ -301,9 +310,9 @@ instance = square{};
instance->draw(); instance->draw();
``` ```
This class offers a wide range of constructors, from the default one (which The `poly` class template offers a wide range of constructors, from the default
returns an uninitialized `poly` object) to the copy and move constructors, as one (which will return an uninitialized `poly` object) to the copy and move
well as the ability to create objects in-place.<br/> 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 Among others, there is also a constructor that allows users to wrap unmanaged
objects in a `poly` instance (either const or non-const ones): objects in a `poly` instance (either const or non-const ones):
@@ -312,23 +321,22 @@ circle shape;
drawable instance{std::in_place_type<circle &>, shape}; drawable instance{std::in_place_type<circle &>, shape};
``` ```
Similarly, it is possible to create non-owning copies of `poly` from an existing Similarly, it's possible to create non-owning copies of `poly` from an existing
object: object:
```cpp ```cpp
drawable other = instance.as_ref(); drawable other = instance.as_ref();
``` ```
In both cases, although the interface of the `poly` object does not change, it In both cases, although the interface of the `poly` object doesn't change, it
does not construct any element or take care of destroying the referenced won't construct any element or take care of destroying the referenced objects.
objects.
Note also how the underlying concept is accessed via a call to `operator->` and Note also how the underlying concept is accessed via a call to `operator->` and
not directly as `instance.draw()`.<br/> not directly as `instance.draw()`.<br/>
This allows users to decouple the API of the wrapper from that of the concept. This allows users to decouple the API of the wrapper from that of the concept.
Therefore, where `instance.data()` invokes the `data` member function of the Therefore, where `instance.data()` will invoke the `data` member function of the
poly object, `instance->data()` maps directly to the functionality exposed by poly object, `instance->data()` will map directly to the functionality exposed
the underlying concept. by the underlying concept.
# Storage size and alignment requirement # Storage size and alignment requirement
@@ -343,9 +351,9 @@ entt::basic_poly<Drawable, sizeof(double[4]), alignof(double[4])>
The default size is `sizeof(double[2])`, which seems like a good compromise 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 between a buffer that is too large and one unable to hold anything larger than
an integer. The alignment requirement is optional, and by default such that it an integer. The alignment requirement is optional instead and by default such
is the most stringent (the largest) for any object whose size is at most equal that it's the most stringent (the largest) for any object whose size is at most
to the one provided.<br/> equal to the one provided.<br/>
It is worth noting that providing a size of 0 (which is an accepted value in all 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 respects) will force the system to dynamically allocate the contained objects in
all cases. all cases.

View File

@@ -1,68 +1,81 @@
# Crash Course: cooperative scheduler # Crash Course: cooperative scheduler
<!--
@cond TURN_OFF_DOXYGEN
-->
# Table of Contents # Table of Contents
* [Introduction](#introduction) * [Introduction](#introduction)
* [The process](#the-process) * [The process](#the-process)
* [Continuation](#continuation) * [Adaptor](#adaptor)
* [Shared process](#shared-process)
* [The scheduler](#the-scheduler) * [The scheduler](#the-scheduler)
<!--
@endcond TURN_OFF_DOXYGEN
-->
# Introduction # Introduction
Processes are a useful tool to work around the strict definition of a system and Sometimes processes are a useful tool to work around the strict definition of a
introduce logic in a different way, usually without resorting to other component system and introduce logic in a different way, usually without resorting to the
types.<br/> introduction of other components.
`EnTT` offers minimal support to this paradigm by introducing a few classes used
to define and execute cooperative processes. `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 # The process
A typical task inherits from the `process` class template. Derived classes also A typical process must inherit from the `process` class template that stays true
specify what the intended type for elapsed times is. to the CRTP idiom. Moreover, derived classes must specify what's the intended
type for elapsed times.
A process should implement the following member functions whether needed (note A process should expose publicly the following member functions whether needed
that it is not required to define a function unless the derived class wants to (note that it isn't required to define a function unless the derived class wants
_override_ the default behavior): to _override_ the default behavior):
* `void update(Delta, void *);` * `void update(Delta, void *);`
This is invoked once per tick until a process is explicitly aborted or ends It's invoked once per tick until a process is explicitly aborted or it
either with or without errors. Each process should at least define it to work terminates either with or without errors. Even though it's not mandatory to
_properly_. The `void *` parameter is an opaque pointer to user data (if any) declare this member function, as a rule of thumb each process should at
forwarded directly to the process during an update. 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();` * `void succeeded();`
This is invoked in case of success, immediately after an update and during the It's invoked in case of success, immediately after an update and during the
same tick. same tick.
* `void failed();` * `void failed();`
This is invoked in case of errors, immediately after an update and during the It's invoked in case of errors, immediately after an update and during the
same tick. same tick.
* `void aborted();` * `void aborted();`
This is invoked only if a process is explicitly aborted. There is no guarantee It's invoked only if a process is explicitly aborted. There is no guarantee
that it executes in the same tick. It depends solely on whether the process is that it executes in the same tick, this depends solely on whether the
aborted immediately or not. process is aborted immediately or not.
A class can also change the state of a process by invoking `succeed` and `fail`, Derived classes can also change the internal state of a process by invoking
as well as `pause` and `unpause` the process itself.<br/> `succeed` and `fail`, as well as `pause` and `unpause` the process itself. All
All these are public member functions made available to manage the life cycle of these are protected member functions made available to be able to manage the
a process easily. life cycle of a process from a derived class.
Here is a minimal example for the sake of curiosity: Here is a minimal example for the sake of curiosity:
```cpp ```cpp
struct my_process: entt::process { struct my_process: entt::process<my_process, std::uint32_t> {
using allocator_type = entt::process::allocator_type; using delta_type = std::uint32_t;
using delta_type = entt::process::delta_type;
my_process(const allocator_type &allocator, delta_type delay) my_process(delta_type delay)
: entt::process{allocator}, : remaining{delay}
remaining{delay}
{} {}
void update(delta_type delta, void *) { void update(delta_type delta, void *) {
@@ -80,118 +93,96 @@ private:
}; };
``` ```
## Continuation ## Adaptor
A process may be followed by other processes upon successful termination.<br/> Lambdas and functors can't be used directly with a scheduler for they are not
This pairing can be set up at creation time, keeping the processes conceptually properly defined processes with managed life cycles.<br/>
separate from each other while still combining them at runtime: 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 ```cpp
my_process process{}; void(Delta delta, void *data, auto succeed, auto fail);
process.then<my_other_process>();
``` ```
This approach allows processes to be developed in isolation and combined to Parameters have the following meaning:
define complex actions.<br/>
For example, a delayed operation where a parent process (such as a timer)
_schedules_ a child process (the deferred task) once the time is over.
The `then` function also accepts lambdas, which are associated with a dedicated * `delta` is the elapsed time.
process internally: * `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.
```cpp Both `succeed` and `fail` accept no parameters at all.
process.then([](entt::process &proc, std::uint32_t delta, void *data) {
// ...
})
```
The lambda function is such that it accepts a reference to the process that Note that usually users shouldn't worry about creating adaptors at all. A
manages it (to be able to terminate it, pause it and so on), plus the usual scheduler creates them internally each and every time a lambda or a functor is
values also passed to the `update` function. used as a process.
## Shared process
All processes inherit from `std::enable_shared_from_this` to allow sharing with
the caller.<br/>
The returned smart pointer was created using the allocator associated with the
scheduler and therefore all its processes. This same allocator is available by
invoking `get_allocator` on the process itself.
As far as possible, sharing a process is not intended to allow the caller to
manage it. This could actually compromise the proper functioning of the
scheduler and the process itself.<br/>
Rather, the purpose is to allow the callers to save a valid reference to the
process, allowing them to intervene in its lifecycle through calls like `pause`
and the like.
# The scheduler # The scheduler
A cooperative scheduler runs different processes and helps manage their life A cooperative scheduler runs different processes and helps managing their life
cycles. cycles.
Each process is invoked once per tick. If it terminates, it is removed Each process is invoked once per tick. If it terminates, it's removed
automatically from the scheduler, and it is never invoked again. Otherwise, automatically from the scheduler and it's never invoked again. Otherwise it's
it is a good candidate to run one more time the next tick.<br/> 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 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 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 is of errors, both the parent process and its child are discarded. This way, it's
easy to create a _chain of processes_ to run sequentially. easy to create chain of processes to run sequentially.
Using a scheduler is straightforward. To create it, users must provide only the Using a scheduler is straightforward. To create it, users must provide only the
type for the elapsed times and no arguments at all: type for the elapsed times and no arguments at all:
```cpp ```cpp
entt::basic_scheduler<std::uint64_t> scheduler; entt::scheduler<std::uint32_t> scheduler;
``` ```
Otherwise, the `scheduler` alias is also available for the most common cases. It It has member functions to query its internal data structures, like `empty` or
uses `std::uint32_t` as a default type: `size`, as well as a `clear` utility to reset it to a clean state:
```cpp
entt::scheduler scheduler{};
```
The class 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 ```cpp
// checks if there are processes still running // checks if there are processes still running
const auto empty = scheduler.empty(); const auto empty = scheduler.empty();
// gets the number of processes still running // gets the number of processes still running
entt::scheduler::size_type size = scheduler.size(); entt::scheduler<std::uint32_t>::size_type size = scheduler.size();
// resets the scheduler to its initial state and discards all the processes // resets the scheduler to its initial state and discards all the processes
scheduler.clear(); scheduler.clear();
``` ```
To attach a process to a scheduler, invoke the `attach` function with a process To attach a process to a scheduler there are mainly two ways:
type and the arguments to use to construct it:
```cpp * If the process inherits from the `process` class template, it's enough to
scheduler.attach<my_process>(_1000u); indicate its type and submit all the parameters required to construct it to
``` the `attach` member function:
The scheduler will also provide the process with its allocator as the first ```cpp
argument.<br> scheduler.attach<my_process>(1000u);
In case of lambdas or functors, the required signature is the one already seen ```
for the `then` function of a process:
```cpp * Otherwise, in case of a lambda or a functor, it's enough to provide an
scheduler.attach([](entt::process &, std::uint32_t, void *){ /* ... */ }); instance of the class to the `attach` member function:
```
In both cases, the newly created process is returned by reference and its `then` ```cpp
member function is used to create chains of processes to run sequentially.<br/> 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: As a minimal example of use:
```cpp ```cpp
// schedules a task in the form of a lambda function // schedules a task in the form of a lambda function
scheduler.attach([](entt::process &, std::uint32_t, void *) { scheduler.attach([](auto delta, void *, auto succeed, auto fail) {
// ... // ...
}) })
// appends a child in the form of another lambda function // appends a child in the form of another lambda function
.then([](entt::process &, std::uint32_t, void *) { .then([](auto delta, void *, auto succeed, auto fail) {
// ... // ...
}) })
// appends a child in the form of a process class // appends a child in the form of a process class
@@ -210,7 +201,7 @@ scheduler.update(delta, &data);
``` ```
In addition to these functions, the scheduler offers an `abort` member function In addition to these functions, the scheduler offers an `abort` member function
that is used to discard all the running processes at once: that can be used to discard all the running processes at once:
```cpp ```cpp
// aborts all the processes abruptly ... // aborts all the processes abruptly ...
@@ -219,6 +210,3 @@ scheduler.abort(true);
// ... or gracefully during the next tick // ... or gracefully during the next tick
scheduler.abort(); scheduler.abort();
``` ```
The argument passed to the `abort` function indicates whether execution should
be stopped immediately or processes should be notified on the next tick.

View File

@@ -1,30 +1,18 @@
# Similar projects # Similar projects
# Table of Contents
* [Introduction](#introduction)
* [Similar projects](#similar-projects)
# Introduction
There are many projects similar to `EnTT`, both open source and not.<br/> 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 Some even borrowed some ideas from this library and expressed them in different
languages.<br/> languages.<br/>
Others developed different architectures from scratch and therefore offer Others developed different architectures from scratch and therefore offer
alternative solutions with their pros and cons. alternative solutions with their pros and cons.
If you know of other similar projects out there, feel free to open an issue or a Below an incomplete list of those that I've come across so far.<br/>
PR, and I will be glad to add them to this page.<br/> If some terms or designs aren't clear, I recommend referring to the
I hope the following lists can grow much more in the future.
# Similar projects
Below an incomplete list of similar projects that I have come across so
far.<br/>
If some terms or designs are not clear, I recommend referring to the
[_ECS Back and Forth_](https://skypjack.github.io/tags/#ecs) series for all the [_ECS Back and Forth_](https://skypjack.github.io/tags/#ecs) series for all the
details. details.
I hope this list can grow much more in the future:
* C: * C:
* [destral_ecs](https://github.com/roig/destral_ecs): a single-file ECS based * [destral_ecs](https://github.com/roig/destral_ecs): a single-file ECS based
on sparse sets. on sparse sets.
@@ -35,31 +23,23 @@ details.
* [lent](https://github.com/nem0/lent): the Donald Trump of the ECS libraries. * [lent](https://github.com/nem0/lent): the Donald Trump of the ECS libraries.
* C++: * C++:
* [decs](https://github.com/vblanco20-1/decs): a chunk-based archetype ECS. * [decs](https://github.com/vblanco20-1/decs): a chunk based archetype ECS.
* [ecst](https://github.com/SuperV1234/ecst): a multithreaded compile-time * [ecst](https://github.com/SuperV1234/ecst): a multithreaded compile-time
ECS that uses sparse sets to keep track of entities in systems. ECS that uses sparse sets to keep track of entities in systems.
* [EntityX](https://github.com/alecthomas/entityx): a bitset based ECS that * [EntityX](https://github.com/alecthomas/entityx): a bitset based ECS that
uses a single large matrix of components indexed with entities. uses a single large matrix of components indexed with entities.
* [Gaia-ECS](https://github.com/richardbiely/gaia-ecs): a chunk-based * [Gaia-ECS](https://github.com/richardbiely/gaia-ecs): a chunk based
archetype ECS. archetype ECS.
* [Polypropylene](https://github.com/pmbittner/Polypropylene): a hybrid * [Polypropylene](https://github.com/pmbittner/Polypropylene): a hybrid
solution between an ECS and dynamic mixins. solution between an ECS and dynamic mixins.
* C# * C#
* [Arch](https://github.com/genaray/Arch): a simple, fast and _unity entities_
inspired archetype ECS with optional multithreading.
* [Entitas](https://github.com/sschmid/Entitas-CSharp): the ECS framework for * [Entitas](https://github.com/sschmid/Entitas-CSharp): the ECS framework for
C# and Unity, where _reactive systems_ were invented. C# and Unity, where _reactive systems_ were invented.
* [Fennecs](https://github.com/outfox/fennecs): the little archetype ECS that
loves you back.
* [Friflo ECS](https://github.com/friflo/Friflo.Engine.ECS): an archetype ECS
with focus on performance and minimal GC allocations.
* [LeoECS](https://github.com/Leopotam/ecs): simple lightweight C# Entity * [LeoECS](https://github.com/Leopotam/ecs): simple lightweight C# Entity
Component System framework. Component System framework.
* [Massive ECS](https://github.com/nilpunch/massive): sparse set based ECS
featuring rollbacks.
* [Svelto.ECS](https://github.com/sebas77/Svelto.ECS): a very interesting * [Svelto.ECS](https://github.com/sebas77/Svelto.ECS): a very interesting
platform-agnostic and table-based ECS framework. platform agnostic and table based ECS framework.
* Go: * Go:
* [gecs](https://github.com/tutumagi/gecs): a sparse sets based ECS inspired * [gecs](https://github.com/tutumagi/gecs): a sparse sets based ECS inspired
@@ -68,7 +48,7 @@ details.
* Javascript: * Javascript:
* [\@javelin/ecs](https://github.com/3mcd/javelin/tree/master/packages/ecs): * [\@javelin/ecs](https://github.com/3mcd/javelin/tree/master/packages/ecs):
an archetype ECS in TypeScript. an archetype ECS in TypeScript.
* [ecsy](https://github.com/MozillaReality/ecsy): I have not had the time to * [ecsy](https://github.com/MozillaReality/ecsy): I haven't had the time to
investigate the underlying design of `ecsy` but it looks cool anyway. investigate the underlying design of `ecsy` but it looks cool anyway.
* Perl: * Perl:
@@ -80,6 +60,7 @@ details.
entity registry for ECS designs inspired by `EnTT`. entity registry for ECS designs inspired by `EnTT`.
* Rust: * Rust:
* [Legion](https://github.com/TomGillen/legion): a chunk based archetype ECS.
* [Shipyard](https://github.com/leudz/shipyard): it borrows some ideas from * [Shipyard](https://github.com/leudz/shipyard): it borrows some ideas from
`EnTT` and offers a sparse sets based ECS with grouping functionalities. `EnTT` and offers a sparse sets based ECS with grouping functionalities.
* [Sparsey](https://github.com/LechintanTudor/sparsey): sparse set based ECS * [Sparsey](https://github.com/LechintanTudor/sparsey): sparse set based ECS
@@ -89,3 +70,6 @@ details.
* Zig * Zig
* [zig-ecs](https://github.com/prime31/zig-ecs): a _zig-ification_ of `EnTT`. * [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.

View File

@@ -1,26 +1,32 @@
# Crash Course: resource management # Crash Course: resource management
<!--
@cond TURN_OFF_DOXYGEN
-->
# Table of Contents # Table of Contents
* [Introduction](#introduction) * [Introduction](#introduction)
* [The resource, the loader and the cache](#the-resource-the-loader-and-the-cache) * [The resource, the loader and the cache](#the-resource-the-loader-and-the-cache)
* [Resource handle](#resource-handle) * [Resource handle](#resource-handle)
* [Loaders](#loaders) * [Loaders](#loader)
* [The cache class](#the-cache-class) * [The cache class](#the-cache)
<!--
@endcond TURN_OFF_DOXYGEN
-->
# Introduction # Introduction
Resource management is usually one of the most critical parts of a game. Resource management is usually one of the most critical part of a software like
Solutions are often tuned to the particular application. There exist several a game. Solutions are often tuned to the particular application. There exist
approaches and all of them are perfectly fine as long as they fit the 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/> requirements of the piece of software in which they are used.<br/>
Examples are loading everything on start, loading on request, predictive Examples are loading everything on start, loading on request, predictive
loading, and so on. loading, and so on.
`EnTT` does not pretend to offer a _one-fits-all_ solution for the different `EnTT` doesn't pretend to offer a _one-fits-all_ solution for the different
cases.<br/> cases.<br/>
Instead, the library comes with a minimal, general purpose resource cache that Instead, the library offers a minimal, general purpose resource cache that might
might be useful in many cases. be useful in many cases.
# The resource, the loader and the cache # The resource, the loader and the cache
@@ -31,8 +37,7 @@ The _resource_ is an image, an audio, a video or any other type:
struct my_resource { const int value; }; struct my_resource { const int value; };
``` ```
The _loader_ is a callable type, the aim of which is to load a specific The _loader_ is a callable type the aim of which is to load a specific resource:
resource:
```cpp ```cpp
struct my_loader final { struct my_loader final {
@@ -61,46 +66,46 @@ using my_cache = entt::resource_cache<my_resource, my_loader>;
my_cache cache{}; my_cache cache{};
``` ```
The class is designed to create different caches for different resource types The cache is meant to be used to create different caches for different types of
and to manage each one independently in the most appropriate way.<br/> resources and to manage each one independently in the most appropriate way.<br/>
As a (very) trivial example, audio tracks can survive in most of the scenes of 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 an application while meshes can be associated with a single scene only, then
discarded when a player leaves it. discarded when a player leaves it.
## Resource handle ## Resource handle
Resources are not returned directly to the caller. Instead, they are wrapped in Resources aren't returned directly to the caller. Instead, they are wrapped in a
a _resource handle_, an instance of the `entt::resource` class template.<br/> _resource handle_ identified by the `entt::resource` class template.<br/>
For those who know the _flyweight design pattern_ already, that is exactly what 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. 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 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 handle mostly maps the interface of its standard counterpart and only adds a few
adds a few things on top of it.<br/> things to it.<br/>
However, the handle in `EnTT` is designed as a standalone class template. This However, the handle in `EnTT` is designed as a standalone class template named
is due to the fact that specializing a class in the standard library is often `resource`. It boils down to the fact that specializing a class in the standard
undefined behavior while having the ability to specialize the handle for one, is often undefined behavior while having the ability to specialize the handle
more or all resource types could help over time. for one, more or all resource types could help over time.
## Loaders ## Loaders
A loader is responsible for _loading_ resources (quite obviously).<br/> A loader is a class that is responsible for _loading_ the resources.<br/>
By default, it is just a callable object that forwards its arguments to the By default, it's just a callable object which forwards its arguments to the
resource itself. That is, a _passthrough type_. All the work is demanded to the resource itself. That is, a _passthrough type_. All the work is demanded to the
constructor(s) of the resource itself.<br/> constructor(s) of the resource itself.<br/>
Loaders also are fully customizable as expected. Loaders also are fully customizable as expected.
A custom loader is a class with at least one function call operator and a member A custom loader is a class with at least one function call operator and a member
type named `result_type`.<br/> type named `result_type`.<br/>
The loader is not required to return a resource handle. As long as `return_type` The loader isn't required to return a resource handle. As long as `return_type`
is suitable for constructing a handle, that is fine. is suitable for constructing a handle, that's fine.
When using the default handle, it expects a resource type which is convertible 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 to or suitable for constructing an `std::shared_ptr<Type>` (where `Type` is the
actual resource type).<br/> actual resource type).<br/>
In other terms, the loader should return shared pointers to the given resource In other terms, the loader should return shared pointers to the given resource
type. However, this is not mandatory. Users can easily get around this type. However, it isn't mandatory. Users can easily get around this constraint
constraint by specializing both the handle and the loader. by specializing both the handle and the loader.
A cache forwards all its arguments to the loader if required. This means that 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: loaders can also support tag dispatching to offer different loading policies:
@@ -112,13 +117,13 @@ struct my_loader {
struct from_disk_tag{}; struct from_disk_tag{};
struct from_network_tag{}; struct from_network_tag{};
template<typename... Args> template<typename Args>
result_type operator()(from_disk_tag, Args&&... args) { result_type operator()(from_disk_tag, Args&&... args) {
// ... // ...
return std::make_shared<my_resource>(std::forward<Args>(args)...); return std::make_shared<my_resource>(std::forward<Args>(args)...);
} }
template<typename... Args> template<typename Args>
result_type operator()(from_network_tag, Args&&... args) { result_type operator()(from_network_tag, Args&&... args) {
// ... // ...
return std::make_shared<my_resource>(std::forward<Args>(args)...); return std::make_shared<my_resource>(std::forward<Args>(args)...);
@@ -131,7 +136,7 @@ This makes the whole loading logic quite flexible and easy to extend over time.
## The cache class ## The cache class
The cache is the class that is asked to _connect the dots_.<br/> 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: It loads the resources, store them aside and returns handles as needed:
```cpp ```cpp
entt::resource_cache<my_resource, my_loader> cache{}; entt::resource_cache<my_resource, my_loader> cache{};
@@ -139,12 +144,12 @@ 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 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/> `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 For this reason, it offers most of the functionality a user would expect from a
a map, such as `empty` or `size` and so on. Similarly, it is an iterable type map, such as `empty` or `size` and so on. Similarly, it's an iterable type that
that also supports indexing by resource id: also supports indexing by resource id:
```cpp ```cpp
for(auto [id, res]: cache) { for(entt::resource<my_resource> curr: cache) {
// ... // ...
} }
@@ -154,14 +159,14 @@ if(entt::resource<my_resource> res = cache["resource/id"_hs]; res) {
``` ```
Please, refer to the inline documentation for all the details about the other Please, refer to the inline documentation for all the details about the other
functions (such as `contains` or `erase`). functions (for example `contains` or `erase`).
Set aside the part of the API that this class _shares_ with a map, it also adds 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 something on top of it in order to address the most common requirements of a
resource cache.<br/> resource cache.<br/>
In particular, it does not have an `emplace` member function which is replaced In particular, it doesn't have an `emplace` member function which is replaced by
by `load` and `force_load` instead (where the former loads a new resource only `load` and `force_load` instead (where the former loads a new resource only if
if not present while the second triggers a forced loading in any case): not present while the second triggers a forced loading in any case):
```cpp ```cpp
auto ret = cache.load("resource/id"_hs); auto ret = cache.load("resource/id"_hs);
@@ -170,17 +175,18 @@ auto ret = cache.load("resource/id"_hs);
const bool loaded = ret.second; const bool loaded = ret.second;
// takes the resource handle pointed to by the returned iterator // takes the resource handle pointed to by the returned iterator
entt::resource<my_resource> res = ret.first->second; entt::resource<my_resource> res = *ret.first;
``` ```
Note that the hashed string is used for convenience in the example above.<br/> Note that the hashed string is used for convenience in the example above.<br/>
Resource identifiers are nothing more than integral values. Therefore, plain Resource identifiers are nothing more than integral values. Therefore, plain
numbers as well as non-class enum value are accepted. numbers as well as non-class enum value are accepted.
It is worth mentioning that the iterators of a cache as well as its indexing Moreover, it's worth mentioning that both the iterators of a cache and its
operators return resource handles rather than instances of the mapped type.<br/> indexing operators return resource handles rather than instances of the mapped
Since the cache has no control over the loader and a resource is not required to 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 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/> error in the user logic but it may also be an _expected_ event.<br/>
It is therefore recommended to verify handles validity with a check in debug It's therefore recommended to verify handles validity with a check in debug (for
(for example, when loading) or an appropriate logic in retail. example, when loading) or an appropriate logic in retail.

View File

@@ -1,43 +1,47 @@
# Crash Course: events, signals and everything in between # Crash Course: events, signals and everything in between
<!--
@cond TURN_OFF_DOXYGEN
-->
# Table of Contents # Table of Contents
* [Introduction](#introduction) * [Introduction](#introduction)
* [Delegate](#delegate) * [Delegate](#delegate)
* [Runtime arguments](#runtime-arguments) * [Runtime arguments](#runtime-arguments)
* [Lambda support](#lambda-support) * [Lambda support](#lambda-support)
* [Raw access](#raw-access)
* [Signals](#signals) * [Signals](#signals)
* [Event dispatcher](#event-dispatcher) * [Event dispatcher](#event-dispatcher)
* [Connect, disconnect, publish](#connect-disconnect-publish)]
* [Named queues](#named-queues) * [Named queues](#named-queues)
* [Event emitter](#event-emitter) * [Event emitter](#event-emitter)
<!--
@endcond TURN_OFF_DOXYGEN
-->
# Introduction # Introduction
Signals are more often than not a core part of games and software architectures Signals are usually a core part of games and software architectures in
in general.<br/> general.<br/>
They help to decouple the various parts of a system while allowing them to Roughly speaking, they help to decouple the various parts of a system while
communicate with each other somehow. 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 so called _modern C++_ comes with a tool that can be useful in these terms,
the `std::function`. As an example, it can be used to create delegates.<br/> the `std::function`. As an example, it can be used to create delegates.<br/>
However, there is no guarantee that an `std::function` does not perform However, there is no guarantee that an `std::function` does not perform
allocations under the hood and this could be problematic sometimes. Furthermore, 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 it solves a problem but may not adapt well to other requirements that may arise
from time to time. from time to time.
In case that the flexibility and power of an `std::function` is not required or 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 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. lightweight classes to solve the same and many other problems.
# Delegate # Delegate
A delegate can be used as a general purpose invoker with no memory overhead for A delegate can be used as a general purpose invoker with no memory overhead for
free functions, lambdas and members provided along with an instance on which to free functions and members provided along with an instance on which to invoke
invoke them.<br/> them.<br/>
It does not claim to be a drop-in replacement for an `std::function`, so do not 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 is most 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 likely even a better fit than an `std::function` in a lot of cases, so expect to
use it quite a lot anyway. use it quite a lot anyway.
@@ -48,13 +52,15 @@ delegates:
entt::delegate<int(int)> delegate{}; entt::delegate<int(int)> delegate{};
``` ```
What is needed to create an instance is to specify the type of the function the All what is needed to create an instance is to specify the type of the function
delegate _accepts_, that is the signature of the functions it models.<br/> the delegate will _contain_, that is the signature of the free function or the
However, attempting to use an empty delegate by invoking its function call member one wants to assign to it.
operator results in undefined behavior or most likely a crash.
There exist a few overloads of the `connect` member function to initialize a Attempting to use an empty delegate by invoking its function call operator
delegate: results in undefined behavior or most likely a crash. Before to use a delegate,
it must be initialized.<br/>
There exists a bunch of overloads of the `connect` member function to do that.
As an example of use:
```cpp ```cpp
int f(int i) { return i; } int f(int i) { return i; }
@@ -71,13 +77,13 @@ my_struct instance;
delegate.connect<&my_struct::f>(instance); delegate.connect<&my_struct::f>(instance);
``` ```
The delegate class also accepts data members, if needed. In this case, the The delegate class accepts also data members, if needed. In this case, the
function type of the delegate is such that the parameter list is empty and the 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. 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 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 well. The first argument `T &` is considered a payload and the function will
receive it back every time it is invoked. In other terms, this works just fine receive it back every time it's invoked. In other terms, this works just fine
with the above definition: with the above definition:
```cpp ```cpp
@@ -88,11 +94,14 @@ delegate.connect<&g>(c);
delegate(42); delegate(42);
``` ```
Function `g` is invoked with a reference to `c` and `42`. However, the function The function `g` will be invoked with a reference to `c` and `42`. However, the
type of the delegate is still `void(int)`. This is also the signature of its function type of the delegate is still `void(int)`. This is also the signature
function call operator.<br/> of its function call operator.
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: Another interesting aspect of the delegate class is that it accepts also
functions with a list of parameters that is shorter than that of the function
type used to specialize the delegate itself.<br/>
The following code is therefore perfectly valid:
```cpp ```cpp
void g() { /* ... */ } void g() { /* ... */ }
@@ -101,15 +110,9 @@ delegate(42);
``` ```
Where the function type of the delegate is `void(int)` as above. It goes without Where the function type of the delegate is `void(int)` as above. It goes without
saying that the extra arguments are silently discarded internally. This is a saying that the extra arguments are silently discarded internally.<br/>
nice-to-have feature in a lot of cases, as an example when the `delegate` class This is a nice-to-have feature in a lot of cases, as an example when the
is used as a building block of a signal-slot system.<br/> `delegate` class is used as a building block of a signal-slot system.
In fact, this filtering works both ways. The class tries to pass its first
_count_ arguments **first**, then the last _count_. Watch out for conversion
rules if in doubt when connecting a listener!<br/>
Arbitrary functions that pull random arguments from the delegate list are not
supported instead. Other features were preferred, such as support for functions
with compatible argument lists although not equal to those of the delegate.
To create and initialize a delegate at once, there are a few specialized 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 constructors. Because of the rules of the language, the listener is provided by
@@ -119,7 +122,7 @@ means of the `entt::connect_arg` variable template:
entt::delegate<int(int)> func{entt::connect_arg<&f>}; entt::delegate<int(int)> func{entt::connect_arg<&f>};
``` ```
Aside `connect`, a `disconnect` counterpart is not provided. Instead, there Aside `connect`, a `disconnect` counterpart isn't provided. Instead, there
exists a `reset` member function to use to clear a delegate.<br/> 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 To know if a delegate is empty, it can be used explicitly in every conditional
statement: statement:
@@ -137,14 +140,14 @@ already shown in the examples above:
auto ret = delegate(42); auto ret = delegate(42);
``` ```
In all cases, listeners do not have to strictly follow the signature of the In all cases, the 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 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 a result that is convertible to the given result type, everything works just
fine. fine.
As a side note, members of classes may or may not be associated with instances. 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 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 class on which the members operate and an instance of this class must obviously
be passed when invoking the delegate: be passed when invoking the delegate:
```cpp ```cpp
@@ -155,23 +158,23 @@ my_struct instance;
delegate(instance, 42); delegate(instance, 42);
``` ```
In this case, it is not possible to _deduce_ the function type since the first In this case, it's not possible to deduce the function type since the first
argument does not necessarily have to be a reference (for example, it can be a argument doesn't necessarily have to be a reference (for example, it can be a
pointer, as well as a const reference).<br/> pointer, as well as a const reference).<br/>
Therefore, the function type must be declared explicitly for unbound members. Therefore, the function type must be declared explicitly for unbound members.
## Runtime arguments ## Runtime arguments
The `delegate` class is meant to be used primarily with template 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 However, as a consequence of its design, it can also offer minimal support for
runtime arguments.<br/> runtime arguments.<br/>
When used like this, some features are not supported though. In particular: When used in this modality, some feature aren't supported though. In particular:
* Curried functions are not accepted. * Curried functions aren't accepted.
* Functions with an argument list that differs from that of the delegate are not * Functions with an argument list that differs from that of the delegate aren't
supported. supported.
* Return type and types of arguments **must** coincide with those of the * Return type and types of arguments **must** coincide with those of the
delegate and _being at least convertible_ is not enough anymore. delegate and _being at least convertible_ isn't enough anymore.
Moreover, for a given function type `Ret(Args...)`, the signature of the Moreover, for a given function type `Ret(Args...)`, the signature of the
functions connected at runtime must necessarily be `Ret(const void *, Args...)`. functions connected at runtime must necessarily be `Ret(const void *, Args...)`.
@@ -201,12 +204,12 @@ explained.
## Lambda support ## Lambda support
In general, the `delegate` class does not fully support lambda functions in all In general, the `delegate` class doesn't fully support lambda functions in all
their nuances. The reason is pretty simple: a `delegate` is not a drop-in 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 replacement for an `std::function`. Instead, it tries to overcome the problems
with the latter.<br/> with the latter.<br/>
That being said, non-capturing lambda functions are supported, even though some That being said, non-capturing lambda functions are supported, even though some
features are not available in this case. feature aren't available in this case.
This is a logical consequence of the support for connecting functions at This is a logical consequence of the support for connecting functions at
runtime. Therefore, lambda functions undergo the same rules and runtime. Therefore, lambda functions undergo the same rules and
@@ -229,48 +232,29 @@ delegate.connect([](const void *ptr, int value) {
}, &instance); }, &instance);
``` ```
As above, the first parameter (`const void *`) is not part of the function type 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 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)`. other terms, the function type of the delegate above is `int(int)`.
## Raw access
While not recommended, a delegate also allows direct access to the stored
callable function target and underlying data, if any.<br/>
This makes it possible to bypass the behavior of the delegate itself and force
calls on different instances:
```cpp
my_struct other;
delegate.target(&other, 42);
```
It goes without saying that this type of approach is **very** risky, especially
since there is no way of knowing whether the contained function was originally a
member function of some class, a free function or a lambda.<br/>
Another possible (and meaningful) use of this feature is that of identifying a
particular delegate through its descriptive _traits_ instead.
# Signals # Signals
Signal handlers work with references to classes, function pointers, and pointers 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 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 connecting and disconnecting them from a signal to avoid crashes due to
different lifetimes. On the other side, performance should not be affected that different lifetimes. On the other side, performance shouldn't be affected that
much by the presence of such a signal handler.<br/> much by the presence of such a signal handler.<br/>
Signals make use of delegates internally, and therefore they undergo the same 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 rules and offer similar functionalities. It may be a good idea to consult the
documentation of the `delegate` class for further information. documentation of the `delegate` class for further information.
A signal handler is can be used as a private data member without exposing any A signal handler can be used as a private data member without exposing any
_publish_ functionality to the clients of a class.<br/> _publish_ functionality to the clients of a class. The basic idea is to impose a
The basic idea is to impose a clear separation between the signal itself and the clear separation between the signal itself and the `sink` class, that is a tool
`sink` class, that is a tool to be used to connect and disconnect listeners on to be used to connect and disconnect listeners on the fly.
the fly.
The API of a signal handler is straightforward. If a collector is supplied to 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 the signal when something is published, all the values returned by the listeners
are literally _collected_ and used later by the caller. Otherwise, the class can be 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/> works just like a plain signal that emits events from time to time.<br/>
To create instances of signal handlers it is sufficient to provide the type of To create instances of signal handlers it is sufficient to provide the type of
function to which they refer: function to which they refer:
@@ -310,31 +294,41 @@ sink.disconnect<&foo>();
sink.disconnect<&listener::bar>(instance); sink.disconnect<&listener::bar>(instance);
// disconnect all member functions of an instance, if any // disconnect all member functions of an instance, if any
sink.disconnect(&instance); sink.disconnect(instance);
// discards all listeners at once // discards all listeners at once
sink.disconnect(); sink.disconnect();
``` ```
As shown above, listeners do not have to strictly follow the signature of the As shown above, the 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 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 result that is convertible to the given return type, everything works just
fine.<br/> fine.<br/>
It's also possible to connect a listener before other listeners already
contained by the signal. The `before` function returns a `sink` object correctly
initialized for the purpose that 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` 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 object to be used as an alternative to break a connection by means of its
`release` member function.<br/> `release` member function. A `scoped_connection` can also be created from a
A `scoped_connection` can also be created from a connection. In this case, the connection. In this case, the link is broken automatically as soon as the object
link is broken automatically as soon as the object goes out of scope. goes out of scope.
Once listeners are attached (or even if there are no listeners at all), events 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` and data in general can be published through a signal by means of the `publish`
member function: member function:
```cpp ```cpp
signal.publish(42, 'c'); signal.publish(42, 'c');
``` ```
To collect data, the `collect` member function is used instead: To collect data, the `collect` member function should be used instead. Below is
a minimal example to show how to use it:
```cpp ```cpp
int f() { return 0; } int f() { return 0; }
@@ -358,7 +352,7 @@ assert(vec[1] == 1);
A collector must expose a function operator that accepts as an argument a type 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 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 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 is not otherwise. This way one can avoid calling all the listeners in case it isn't
necessary.<br/> necessary.<br/>
Functors can also be used in place of a lambda. Since the collector is copied 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 when invoking the `collect` member function, `std::ref` is the way to go in this
@@ -383,61 +377,42 @@ signal.collect(std::ref(collector));
# Event dispatcher # Event dispatcher
The event dispatcher class allows users to trigger immediate events or to queue The event dispatcher class allows users to trigger immediate events or to queue
and publish them all together later: 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 ```cpp
// define a general purpose dispatcher // define a general purpose dispatcher
entt::dispatcher dispatcher{}; entt::dispatcher dispatcher{};
``` ```
This class lazily instantiates its queues. Therefore, it is not necessary to A listener registered with a dispatcher is such that its type offers one or more
_announce_ the event types in advance. member functions that take arguments of type `Event &` for any type of event,
regardless of the return value.<br/>
## Connect, disconnect, publish These functions are linked directly via `connect` to a _sink_:
Listeners registered with a dispatcher are mainly of two types: free and member
functions. Lambdas as template functions are also accepted and belong to the
first group.<br/>
In all cases, a listener accepts an argument of type `Event &` for any type
of event, regardless of the return value.
Listeners are linked directly via `connect` to a _sink_ object:
```cpp ```cpp
struct an_event { int value; }; struct an_event { int value; };
struct another_event {}; struct another_event {};
void on_event(const an_event &event) { /* ... */ }
struct listener { struct listener {
// Member function listener void receive(const an_event &) { /* ... */ }
void on_event(const another_event &) { /* ... */ } void method(const another_event &) { /* ... */ }
}; };
// ... // ...
// free function listener
dispatcher.sink<an_event>().connect<&on_event>();
listener listener; listener listener;
// member function listener dispatcher.sink<an_event>().connect<&listener::receive>(listener);
dispatcher.sink<another_event>().connect<&listener::on_event>(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 The `disconnect` member function is used to remove one listener at a time or all
of them at once: of them at once:
```cpp ```cpp
// disconnects a free function dispatcher.sink<an_event>().disconnect<&listener::receive>(listener);
dispatcher.sink<an_event>().disconnect<&on_event>(); dispatcher.sink<another_event>().disconnect(listener);
// disconnect a member function of an instance
dispatcher.sink<another_event>().disconnect<&listener::on_event>(listener);
// disconnect all member functions of an instance, if any
dispatcher.sink<another_event>().disconnect(&listener);
``` ```
The `trigger` member function serves the purpose of sending an immediate event The `trigger` member function serves the purpose of sending an immediate event
@@ -445,10 +420,10 @@ to all the listeners registered so far:
```cpp ```cpp
dispatcher.trigger(an_event{42}); dispatcher.trigger(an_event{42});
dispatcher.trigger(another_event{}); dispatcher.trigger<another_event>();
``` ```
Listeners are invoked immediately, order of execution is not guaranteed. This Listeners are invoked immediately, order of execution isn't guaranteed. This
method can be used to push around urgent messages like an _is terminating_ method can be used to push around urgent messages like an _is terminating_
notification on a mobile app. notification on a mobile app.
@@ -477,7 +452,7 @@ once per tick to their systems.
All queues within a dispatcher are associated by default with an event type and All queues within a dispatcher are associated by default with an event type and
then retrieved from it.<br/> then retrieved from it.<br/>
However, it is possible to create queues with different _names_ (and therefore 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 multiple queues for a single type). In fact, more or less all functions
also take an additional parameter. As an example: also take an additional parameter. As an example:
@@ -494,8 +469,8 @@ parameter for it but rather a different function:
dispatcher.enqueue_hint<an_event>("custom"_hs, 42); dispatcher.enqueue_hint<an_event>("custom"_hs, 42);
``` ```
This is mainly due to the template argument deduction rules, and there is no This is mainly due to the template argument deduction rules and unfortunately
real (elegant) way to avoid it. there is no real (elegant) way to avoid it.
# Event emitter # Event emitter
@@ -505,7 +480,8 @@ Originally designed to fit the requirements of
[`uvw`](https://github.com/skypjack/uvw) (a wrapper for `libuv` written in [`uvw`](https://github.com/skypjack/uvw) (a wrapper for `libuv` written in
modern C++), it was adapted later to be included in this library. 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: To create a custom emitter type, derived classes must inherit directly from the
base class as:
```cpp ```cpp
struct my_emitter: emitter<my_emitter> { struct my_emitter: emitter<my_emitter> {
@@ -513,10 +489,18 @@ struct my_emitter: emitter<my_emitter> {
} }
``` ```
Handlers for the different events are created internally on the fly. It is not The full list of accepted types of events isn't required. Handlers are created
required to specify in advance the full list of accepted events.<br/> internally on the fly and thus each type of event is accepted by default.
Moreover, whenever an event is published, an emitter also passes a reference
to itself to its listeners. Whenever an event is published, an emitter provides the listeners with a
reference to itself along with a reference to the event. Therefore listeners
have an handy way to work with it without incurring in the need of capturing a
reference to the emitter itself.<br/>
In addition, an opaque object is returned each time a connection is established
between an emitter and a listener, allowing the caller to disconnect them at a
later time.<br/>
The opaque object used to handle connections is both movable and copyable. On
the other side, an event emitter is movable but not copyable by default.
To create new instances of an emitter, no arguments are required: To create new instances of an emitter, no arguments are required:
@@ -524,54 +508,90 @@ To create new instances of an emitter, no arguments are required:
my_emitter emitter{}; my_emitter emitter{};
``` ```
Listeners are movable and callable objects (free functions, lambdas, functors, Listeners must be movable and callable objects (free functions, lambdas,
`std::function`s, whatever) whose function type is compatible with: functors, `std::function`s, whatever) whose function type is compatible with:
```cpp ```cpp
void(Type &, my_emitter &) void(Event &, my_emitter &)
``` ```
Where `Type` is the type of event they want to receive.<br/> Where `Event` is the type of event they want to listen.<br/>
To attach a listener to an emitter, there exists the `on` member function: There are two ways to attach a listener to an event emitter that differ
slightly from each other:
* To register a long-lived listener, use the `on` member function. It is meant
to register a listener designed to be invoked more than once for the given
event type.<br/>
As an example:
```cpp
auto conn = emitter.on<my_event>([](const my_event &event, my_emitter &emitter) {
// ...
});
```
The connection object can be freely discarded. Otherwise, it can be used later
to disconnect the listener if required.
* To register a short-lived listener, use the `once` member function. It is
meant to register a listener designed to be invoked only once for the given
event type. The listener is automatically disconnected after the first
invocation.<br/>
As an example:
```cpp
auto conn = emitter.once<my_event>([](const my_event &event, my_emitter &emitter) {
// ...
});
```
The connection object can be freely discarded. Otherwise, it can be used later
to disconnect the listener if required.
In both cases, the connection object can be used with the `erase` member
function:
```cpp ```cpp
emitter.on<my_event>([](const my_event &event, my_emitter &emitter) { emitter.erase(conn);
// ...
});
``` ```
Similarly, the `reset` member function is used to disconnect listeners given a There are also two member functions to use either to disconnect all the
type while `clear` is used to disconnect all listeners at once: listeners for a given type of event or to clear the emitter:
```cpp ```cpp
// resets the listener for my_event // removes all the listener for the specific event
emitter.erase<my_event>(); emitter.clear<my_event>();
// resets all listeners // removes all the listeners registered so far
emitter.clear() emitter.clear();
``` ```
To send an event to the listener registered on a given type, the `publish` To send an event to all the listeners that are interested in it, the `publish`
function is the way to go: member function offers a convenient approach that relieves users from having to
create the event:
```cpp ```cpp
struct my_event { int i; }; struct my_event { int i; };
// ... // ...
emitter.publish(my_event{42}); emitter.publish<my_event>(42);
``` ```
Finally, the `empty` member function tests if there exists at least a listener Finally, the `empty` member function tests if there exists at least either a
registered with the event emitter while `contains` is used to check if a given listener registered with the event emitter or to a given type of event:
event type is associated with a valid listener:
```cpp ```cpp
if(emitter.contains<my_event>()) { bool empty;
// ...
} // checks if there is any listener registered for the specific event
empty = emitter.empty<my_event>();
// checks it there are listeners registered with the event emitter
empty = emitter.empty();
``` ```
This class introduces a _nice-to-have_ model based on events and listeners.<br/> In general, the event emitter is a handy tool when the derived classes _wrap_
More in general, it is a handy tool when the derived classes _wrap_ asynchronous asynchronous operations, because it introduces a _nice-to-have_ model based on
operations, but it is not limited to such uses. events and listeners that kindly hides the complexity behind the scenes. However
it is not limited to such uses.

107
docs/md/unreal.md Normal file
View 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.

View File

@@ -1,52 +0,0 @@
[
# gtest only
{ "include": [ "@<gtest/internal/.*>", "private", "<gtest/gtest.h>", "public" ] },
{ "include": [ "@<gtest/gtest-.*>", "private", "<gtest/gtest.h>", "public" ] },
# forward files
{ "include": [ "@[\"<].*/container/fwd\\.hpp[\">]", "private", "<entt/container/dense_map.hpp>", "public" ] },
{ "include": [ "@[\"<].*/container/fwd\\.hpp[\">]", "private", "<entt/container/dense_set.hpp>", "public" ] },
{ "include": [ "@[\"<].*/container/fwd\\.hpp[\">]", "private", "<entt/container/table.hpp>", "public" ] },
{ "include": [ "@[\"<].*/core/fwd\\.hpp[\">]", "private", "<entt/core/any.hpp>", "public" ] },
{ "include": [ "@[\"<].*/core/fwd\\.hpp[\">]", "private", "<entt/core/compressed_pair.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/component.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/mixin.hpp>", "public" ] },
{ "include": [ "@[\"<].*/entity/fwd\\.hpp[\">]", "private", "<entt/entity/organizer.hpp>", "public" ] },
{ "include": [ "@[\"<].*/entity/fwd\\.hpp[\">]", "private", "<entt/entity/ranges.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": [ "@[\"<].*/graph/fwd\\.hpp[\">]", "private", "<entt/graph/adjacency_matrix.hpp>", "public" ] },
{ "include": [ "@[\"<].*/graph/fwd\\.hpp[\">]", "private", "<entt/graph/dot.hpp>", "public" ] },
{ "include": [ "@[\"<].*/graph/fwd\\.hpp[\">]", "private", "<entt/graph/flow.hpp>", "public" ] },
{ "include": [ "@[\"<].*/meta/fwd\\.hpp[\">]", "private", "<entt/meta/meta.hpp>", "public" ] },
{ "include": [ "@[\"<].*/poly/fwd\\.hpp[\">]", "private", "<entt/poly/poly.hpp>", "public" ] },
{ "include": [ "@[\"<].*/process/fwd\\.hpp[\">]", "private", "<entt/process/process.hpp>", "public" ] },
{ "include": [ "@[\"<].*/process/fwd\\.hpp[\">]", "private", "<entt/process/scheduler.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" ] },
# symbols
{ symbol: [ "std::allocator", private, "<entt/container/fwd.hpp>", public ] },
{ symbol: [ "std::allocator", private, "<entt/entity/fwd.hpp>", public ] },
{ symbol: [ "std::allocator", private, "<entt/graph/fwd.hpp>", public ] },
{ symbol: [ "std::allocator", private, "<entt/process/fwd.hpp>", public ] },
{ symbol: [ "std::allocator", private, "<entt/resource/fwd.hpp>", public ] },
{ symbol: [ "std::allocator", private, "<entt/signal/fwd.hpp>", public ] }
]

View File

@@ -30,10 +30,4 @@
</IndexListItems> </IndexListItems>
</Expand> </Expand>
</Type> </Type>
<Type Name="entt::basic_table&lt;*&gt;">
<DisplayString>{ payload }</DisplayString>
<Expand>
<ExpandedItem>payload</ExpandedItem>
</Expand>
</Type>
</AutoVisualizer> </AutoVisualizer>

View File

@@ -1,14 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> <AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<Type Name="entt::basic_any&lt;*&gt;"> <Type Name="entt::basic_any&lt;*&gt;">
<DisplayString>{{ policy={ mode,en } }}</DisplayString> <DisplayString>{{ type={ info->alias,na }, policy={ mode,en } }}</DisplayString>
</Type> </Type>
<Type Name="entt::compressed_pair&lt;*&gt;"> <Type Name="entt::compressed_pair&lt;*&gt;">
<Intrinsic Name="first" Optional="true" Expression="((first_base*)this)->value"/> <Intrinsic Name="first" Optional="true" Expression="((first_base*)this)->value"/>
<Intrinsic Name="first" Optional="true" Expression="*(first_base::base_type*)this"/> <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*)this)->value"/>
<Intrinsic Name="second" Optional="true" Expression="*(second_base::base_type*)this"/> <Intrinsic Name="second" Optional="true" Expression="*(second_base::base_type*)this"/>
<DisplayString>({ first() }, { second() })</DisplayString> <DisplayString >({ first() }, { second() })</DisplayString>
<Expand> <Expand>
<Item Name="[first]">first()</Item> <Item Name="[first]">first()</Item>
<Item Name="[second]">second()</Item> <Item Name="[second]">second()</Item>
@@ -23,7 +23,8 @@
</Expand> </Expand>
</Type> </Type>
<Type Name="entt::type_info"> <Type Name="entt::type_info">
<DisplayString>{{ name={ alias,na } }}</DisplayString> <DisplayString Condition="seq != 0u">{{ name={ alias,na } }}</DisplayString>
<DisplayString>{{}}</DisplayString>
<Expand> <Expand>
<Item Name="[hash]">identifier</Item> <Item Name="[hash]">identifier</Item>
<Item Name="[index]">seq</Item> <Item Name="[index]">seq</Item>

145
natvis/entt/entity.natvis Normal file
View File

@@ -0,0 +1,145 @@
<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<Type Name="entt::basic_registry&lt;*&gt;">
<Intrinsic Name="pools_size" Expression="pools.packed.first_base::value.size()"/>
<Intrinsic Name="vars_size" Expression="vars.data.packed.first_base::value.size()"/>
<Intrinsic Name="to_entity" Expression="*((entity_traits::entity_type *)&amp;entity) &amp; entity_traits::entity_mask">
<Parameter Name="entity" Type="entity_traits::value_type &amp;"/>
</Intrinsic>
<DisplayString>{{ size={ entities.size() } }}</DisplayString>
<Expand>
<Item IncludeView="simple" Name="[entities]">entities,view(simple)nr</Item>
<Synthetic Name="[entities]" ExcludeView="simple">
<DisplayString>{ entities.size() }</DisplayString>
<Expand>
<CustomListItems>
<Variable Name="pos" InitialValue="0" />
<Variable Name="last" InitialValue="entities.size()"/>
<Loop>
<Break Condition="pos == last"/>
<If Condition="to_entity(entities[pos]) == pos">
<Item Name="[{ pos }]">entities[pos]</Item>
</If>
<Exec>++pos</Exec>
</Loop>
</CustomListItems>
</Expand>
</Synthetic>
<Synthetic Name="[destroyed]" ExcludeView="simple">
<DisplayString>{ to_entity(free_list) != entity_traits::entity_mask }</DisplayString>
<Expand>
<CustomListItems>
<Variable Name="it" InitialValue="to_entity(free_list)" />
<Loop>
<Break Condition="it == entity_traits::entity_mask"/>
<Item Name="[{ it }]">entities[it]</Item>
<Exec>it = to_entity(entities[it])</Exec>
</Loop>
</CustomListItems>
</Expand>
</Synthetic>
<Synthetic Name="[pools]">
<DisplayString>{ pools_size() }</DisplayString>
<Expand>
<IndexListItems ExcludeView="simple">
<Size>pools_size()</Size>
<ValueNode>*pools.packed.first_base::value[$i].element.second</ValueNode>
</IndexListItems>
<IndexListItems IncludeView="simple">
<Size>pools_size()</Size>
<ValueNode>*pools.packed.first_base::value[$i].element.second,view(simple)</ValueNode>
</IndexListItems>
</Expand>
</Synthetic>
<Item Name="[groups]" ExcludeView="simple">groups.size()</Item>
<Synthetic Name="[vars]">
<DisplayString>{ vars_size() }</DisplayString>
<Expand>
<IndexListItems>
<Size>vars_size()</Size>
<ValueNode>vars.data.packed.first_base::value[$i].element.second</ValueNode>
</IndexListItems>
</Expand>
</Synthetic>
</Expand>
</Type>
<Type Name="entt::basic_sparse_set&lt;*&gt;">
<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 &amp; (entity_traits::page_size - 1)</Exec>
<If Condition="sparse[page] &amp;&amp; (*((entity_traits::entity_type *)&amp;sparse[page][offset]) &lt; ~entity_traits::entity_mask)">
<Item Name="[{ pos }]">*((entity_traits::entity_type *)&amp;sparse[page][offset]) &amp; 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 *)&amp;packed[pos]) &lt; ~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&lt;*&gt;">
<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 *)&amp;base_type::packed[pos]) &lt; ~base_type::entity_traits::entity_mask">
<Item Name="[{ pos }]">packed.first_base::value[pos / comp_traits::page_size][pos &amp; (comp_traits::page_size - 1)]</Item>
</If>
<Exec>++pos</Exec>
</Loop>
</CustomListItems>
</Expand>
</Type>
<Type Name="entt::basic_view&lt;*&gt;">
<DisplayString>{{ size_hint={ view->packed.size() } }}</DisplayString>
<Expand>
<Item Name="[pools]">pools,na</Item>
<Item Name="[filter]">filter,na</Item>
</Expand>
</Type>
<Type Name="entt::null_t">
<DisplayString>&lt;null&gt;</DisplayString>
</Type>
<Type Name="entt::tombstone_t">
<DisplayString>&lt;tombstone&gt;</DisplayString>
</Type>
</AutoVisualizer>

197
natvis/entt/meta.natvis Normal file
View File

@@ -0,0 +1,197 @@
<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<Type Name="entt::meta_any">
<DisplayString Condition="node != nullptr">{{ type={ node->info->alias,na }, policy={ storage.mode,en } }}</DisplayString>
<DisplayString>{{}}</DisplayString>
<Expand>
<ExpandedItem Condition="node != nullptr">*node</ExpandedItem>
</Expand>
</Type>
<Type Name="entt::meta_associative_container">
<DisplayString Condition="mapped_type_node != nullptr">{{ key_type={ key_type_node->info->alias,na }, mapped_type={ mapped_type_node->info->alias,na } }}</DisplayString>
<DisplayString Condition="key_type_node != nullptr">{{ key_type={ key_type_node->info->alias,na } }}</DisplayString>
<DisplayString>{{}}</DisplayString>
</Type>
<Type Name="entt::internal::meta_base_node">
<DisplayString Condition="type != nullptr">{{ type={ type->info->alias,na } }}</DisplayString>
<DisplayString>{{}}</DisplayString>
</Type>
<Type Name="entt::internal::meta_conv_node">
<DisplayString Condition="type != nullptr">{{ type={ type->info->alias,na } }}</DisplayString>
<DisplayString>{{}}</DisplayString>
</Type>
<Type Name="entt::internal::meta_ctor_node">
<DisplayString Condition="arg != nullptr">{{ arity={ arity } }}</DisplayString>
<DisplayString>{{}}</DisplayString>
</Type>
<Type Name="entt::internal::meta_data_node">
<Intrinsic Name="has_property" Expression="!!(traits &amp; property)">
<Parameter Name="property" Type="int"/>
</Intrinsic>
<DisplayString Condition="type != nullptr">{{ type={ type->info->alias,na } }}</DisplayString>
<DisplayString>{{}}</DisplayString>
<Expand>
<Item Name="[id]">id</Item>
<Item Name="[arity]">arity</Item>
<Item Name="[is_const]">has_property(entt::internal::meta_traits::is_const)</Item>
<Item Name="[is_static]">has_property(entt::internal::meta_traits::is_static)</Item>
<Synthetic Name="[prop]" Condition="prop != nullptr">
<Expand>
<LinkedListItems>
<HeadPointer>prop</HeadPointer>
<NextPointer>next</NextPointer>
<ValueNode>*this</ValueNode>
</LinkedListItems>
</Expand>
</Synthetic>
</Expand>
</Type>
<Type Name="entt::meta_data">
<DisplayString Condition="node != nullptr">{ *node }</DisplayString>
<DisplayString>{{}}</DisplayString>
<Expand>
<ExpandedItem Condition="node != nullptr">node</ExpandedItem>
</Expand>
</Type>
<Type Name="entt::internal::meta_func_node" >
<Intrinsic Name="has_property" Expression="!!(traits &amp; property)">
<Parameter Name="property" Type="int"/>
</Intrinsic>
<DisplayString Condition="ret != nullptr">{{ arity={ arity }, ret={ ret->info->alias,na } }}</DisplayString>
<DisplayString>{{}}</DisplayString>
<Expand>
<Item Name="[id]">id</Item>
<Item Name="[is_const]">has_property(entt::internal::meta_traits::is_const)</Item>
<Item Name="[is_static]">has_property(entt::internal::meta_traits::is_static)</Item>
<Synthetic Name="[prop]" Condition="prop != nullptr">
<Expand>
<LinkedListItems>
<HeadPointer>prop</HeadPointer>
<NextPointer>next</NextPointer>
<ValueNode>*this</ValueNode>
</LinkedListItems>
</Expand>
</Synthetic>
</Expand>
</Type>
<Type Name="entt::meta_func">
<DisplayString Condition="node != nullptr">{ *node }</DisplayString>
<DisplayString>{{}}</DisplayString>
<Expand>
<ExpandedItem Condition="node != nullptr">node</ExpandedItem>
</Expand>
</Type>
<Type Name="entt::meta_handle">
<DisplayString>{ any }</DisplayString>
</Type>
<Type Name="entt::internal::meta_prop_node">
<DisplayString Condition="value.node != nullptr">{{ key_type={ id.node->info->alias,na }, mapped_type={ value.node->info->alias,na } }}</DisplayString>
<DisplayString Condition="id.node != nullptr">{{ key_type={ id.node->info->alias,na } }}</DisplayString>
<DisplayString>{{}}</DisplayString>
<Expand>
<Item Name="[key]">id</Item>
<Item Name="[value]">value</Item>
</Expand>
</Type>
<Type Name="entt::meta_prop">
<DisplayString Condition="node != nullptr">{ *node }</DisplayString>
<DisplayString>{{}}</DisplayString>
<Expand>
<ExpandedItem Condition="node != nullptr">node</ExpandedItem>
</Expand>
</Type>
<Type Name="entt::meta_sequence_container">
<DisplayString Condition="value_type_node != nullptr">{{ value_type={ value_type_node->info->alias,na } }}</DisplayString>
<DisplayString>{{}}</DisplayString>
</Type>
<Type Name="entt::internal::meta_template_node">
<DisplayString Condition="type != nullptr">{{ type={ type->info->alias,na } }}</DisplayString>
<DisplayString>{{}}</DisplayString>
<Expand>
<Item Name="[arity]">arity</Item>
</Expand>
</Type>
<Type Name="entt::internal::meta_type_node">
<Intrinsic Name="has_property" Expression="!!(traits &amp; property)">
<Parameter Name="property" Type="int"/>
</Intrinsic>
<DisplayString Condition="info != nullptr">{{ type={ info->alias,na } }}</DisplayString>
<DisplayString>{{}}</DisplayString>
<Expand>
<Item Name="[id]">id</Item>
<Item Name="[sizeof]">size_of</Item>
<Item Name="[is_arithmetic]">has_property(entt::internal::meta_traits::is_arithmetic)</Item>
<Item Name="[is_array]">has_property(entt::internal::meta_traits::is_array)</Item>
<Item Name="[is_enum]">has_property(entt::internal::meta_traits::is_enum)</Item>
<Item Name="[is_class]">has_property(entt::internal::meta_traits::is_class)</Item>
<Item Name="[is_pointer]">has_property(entt::internal::meta_traits::is_pointer)</Item>
<Item Name="[is_meta_pointer_like]">has_property(entt::internal::meta_traits::is_meta_pointer_like)</Item>
<Item Name="[is_meta_sequence_container]">has_property(entt::internal::meta_traits::is_meta_sequence_container)</Item>
<Item Name="[is_meta_associative_container]">has_property(entt::internal::meta_traits::is_meta_associative_container)</Item>
<Item Name="[default_constructor]">default_constructor != nullptr</Item>
<Item Name="[conversion_helper]">conversion_helper != nullptr</Item>
<Item Name="[template_info]" Condition="templ != nullptr">*templ</Item>
<Synthetic Name="[ctor]" Condition="ctor != nullptr">
<Expand>
<LinkedListItems>
<HeadPointer>ctor</HeadPointer>
<NextPointer>next</NextPointer>
<ValueNode>*this</ValueNode>
</LinkedListItems>
</Expand>
</Synthetic>
<Synthetic Name="[base]" Condition="base != nullptr">
<Expand>
<LinkedListItems>
<HeadPointer>base</HeadPointer>
<NextPointer>next</NextPointer>
<ValueNode>*this</ValueNode>
</LinkedListItems>
</Expand>
</Synthetic>
<Synthetic Name="[conv]" Condition="conv != nullptr">
<Expand>
<LinkedListItems>
<HeadPointer>conv</HeadPointer>
<NextPointer>next</NextPointer>
<ValueNode>*this</ValueNode>
</LinkedListItems>
</Expand>
</Synthetic>
<Synthetic Name="[data]" Condition="data != nullptr">
<Expand>
<LinkedListItems>
<HeadPointer>data</HeadPointer>
<NextPointer>next</NextPointer>
<ValueNode>*this</ValueNode>
</LinkedListItems>
</Expand>
</Synthetic>
<Synthetic Name="[func]" Condition="func != nullptr">
<Expand>
<LinkedListItems>
<HeadPointer>func</HeadPointer>
<NextPointer>next</NextPointer>
<ValueNode>*this</ValueNode>
</LinkedListItems>
</Expand>
</Synthetic>
<Synthetic Name="[prop]" Condition="prop != nullptr">
<Expand>
<LinkedListItems>
<HeadPointer>prop</HeadPointer>
<NextPointer>next</NextPointer>
<ValueNode>*this</ValueNode>
</LinkedListItems>
</Expand>
</Synthetic>
</Expand>
</Type>
<Type Name="entt::meta_type">
<DisplayString Condition="node != nullptr">{ *node }</DisplayString>
<DisplayString>{{}}</DisplayString>
<Expand>
<ExpandedItem Condition="node != nullptr">node</ExpandedItem>
</Expand>
</Type>
</AutoVisualizer>

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
</AutoVisualizer>

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
</AutoVisualizer>

View 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&lt;*&gt;">
<DisplayString>{ value }</DisplayString>
<Expand>
<ExpandedItem>value</ExpandedItem>
</Expand>
</Type>
<Type Name="entt::resource_cache&lt;*&gt;">
<DisplayString>{ pool.first_base::value }</DisplayString>
<Expand>
<ExpandedItem>pool.first_base::value</ExpandedItem>
</Expand>
</Type>
</AutoVisualizer>

View File

@@ -1,5 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> <AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<Type Name="entt::connection">
<DisplayString>{{ bound={ signal != nullptr } }}</DisplayString>
</Type>
<Type Name="entt::delegate&lt;*&gt;"> <Type Name="entt::delegate&lt;*&gt;">
<DisplayString>{{ type={ "$T1" } }}</DisplayString> <DisplayString>{{ type={ "$T1" } }}</DisplayString>
<Expand> <Expand>
@@ -7,29 +10,26 @@
<Item Name="[data]">instance</Item> <Item Name="[data]">instance</Item>
</Expand> </Expand>
</Type> </Type>
<Type Name="entt::basic_dispatcher&lt;*&gt;"> <Type Name="entt::dispatcher">
<Intrinsic Name="size" Expression="pools.first_base::value.size()"/> <DisplayString>{{ size={ pools.size() } }}</DisplayString>
<DisplayString>{{ size={ size() } }}</DisplayString>
<Expand> <Expand>
<IndexListItems> <Synthetic Name="[pools]">
<Size>size()</Size> <DisplayString>{ pools.size() }</DisplayString>
<ValueNode>*pools.first_base::value.packed.first_base::value[$i].element.second</ValueNode> <Expand>
</IndexListItems> <IndexListItems>
<Size>pools.size()</Size>
<ValueNode>*pools.packed.first_base::value[$i].element.second</ValueNode>
</IndexListItems>
</Expand>
</Synthetic>
</Expand> </Expand>
</Type> </Type>
<Type Name="entt::internal::dispatcher_handler&lt;*&gt;"> <Type Name="entt::internal::dispatcher_handler&lt;*&gt;">
<DisplayString>{{ size={ events.size() }, event={ "$T1" } }}</DisplayString> <DisplayString>{{ size={ events.size() }, event={ "$T1" } }}</DisplayString>
<Expand> <Expand>
<Item Name="[signal]">signal</Item> <Item Name="[signal]">signal</Item>
<Item Name="[events]">events,view(simple)</Item>
</Expand> </Expand>
</Type> </Type>
<Type Name="entt::emitter&lt;*&gt;">
<DisplayString>{{ size={ handlers.first_base::value.size() } }}</DisplayString>
</Type>
<Type Name="entt::connection">
<DisplayString>{{ bound={ signal != nullptr } }}</DisplayString>
</Type>
<Type Name="entt::scoped_connection"> <Type Name="entt::scoped_connection">
<DisplayString>{ conn }</DisplayString> <DisplayString>{ conn }</DisplayString>
</Type> </Type>
@@ -46,6 +46,7 @@
<DisplayString>{{ type={ "$T1" } }}</DisplayString> <DisplayString>{{ type={ "$T1" } }}</DisplayString>
<Expand> <Expand>
<Item Name="[signal]">signal,na</Item> <Item Name="[signal]">signal,na</Item>
<Item Name="[offset]">offset</Item>
</Expand> </Expand>
</Type> </Type>
</AutoVisualizer> </AutoVisualizer>

View File

@@ -1,28 +0,0 @@
#!/usr/bin/env bash
set -e
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)
VERSION_HEADER=$(realpath "$SCRIPT_DIR/../src/entt/config/version.h" --relative-to=$(pwd))
BAZEL_MODULE=$(realpath "$SCRIPT_DIR/../MODULE.bazel" --relative-to=$(pwd))
if [[ -z "${VERSION_HEADER}" ]]; then
echo "Cannot find version header"
exit 1
fi
echo "Getting version from $VERSION_HEADER ..."
ENTT_MAJOR_VERSION=$(sed -nr 's/#define ENTT_VERSION_MAJOR ([0-9]+)/\1/p' $VERSION_HEADER)
ENTT_MINOR_VERSION=$(sed -nr 's/#define ENTT_VERSION_MINOR ([0-9]+)/\1/p' $VERSION_HEADER)
ENTT_PATCH_VERSION=$(sed -nr 's/#define ENTT_VERSION_PATCH ([0-9]+)/\1/p' $VERSION_HEADER)
VERSION="$ENTT_MAJOR_VERSION.$ENTT_MINOR_VERSION.$ENTT_PATCH_VERSION"
echo "Found $VERSION"
buildozer "set version $VERSION" //MODULE.bazel:%module
# a commit is needed for 'git archive'
git add $BAZEL_MODULE
git commit -m "chore: update MODULE.bazel version to $VERSION"

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +0,0 @@
load("@bazel_skylib//lib:selects.bzl", "selects")
load("//bazel:copts.bzl", "COPTS")
package(default_visibility = ["//:__subpackages__"])
cc_library(
name = "entt",
includes = ["."],
hdrs = glob(["**/*.h", "**/*.hpp"]),
copts = COPTS,
)

View File

@@ -1,19 +1,9 @@
#ifndef ENTT_CONFIG_CONFIG_H #ifndef ENTT_CONFIG_CONFIG_H
#define ENTT_CONFIG_CONFIG_H #define ENTT_CONFIG_CONFIG_H
#if __has_include(<entt/ext/config.h>)
# include <entt/ext/config.h>
#endif
#include "version.h" #include "version.h"
// NOLINTBEGIN(cppcoreguidelines-macro-usage) #if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION)
#ifdef ENTT_USE_STL
# define ENTT_FORCE_STL
#endif
#if defined(__cpp_exceptions) && !defined(ENTT_NO_EXCEPTION)
# define ENTT_THROW throw # define ENTT_THROW throw
# define ENTT_TRY try # define ENTT_TRY try
# define ENTT_CATCH catch(...) # define ENTT_CATCH catch(...)
@@ -23,16 +13,11 @@
# define ENTT_CATCH if(false) # define ENTT_CATCH if(false)
#endif #endif
#if __has_include(<version>) #ifndef ENTT_NOEXCEPT
# include <version> # define ENTT_NOEXCEPT noexcept
# # define ENTT_NOEXCEPT_IF(expr) noexcept(expr)
# if defined(__cpp_consteval) # else
# define ENTT_CONSTEVAL consteval # define ENTT_NOEXCEPT_IF(...)
# endif
#endif
#ifndef ENTT_CONSTEVAL
# define ENTT_CONSTEVAL constexpr
#endif #endif
#ifdef ENTT_USE_ATOMIC #ifdef ENTT_USE_ATOMIC
@@ -45,8 +30,6 @@
#ifndef ENTT_ID_TYPE #ifndef ENTT_ID_TYPE
# include <cstdint> # include <cstdint>
# define ENTT_ID_TYPE std::uint32_t # define ENTT_ID_TYPE std::uint32_t
#else
# include <cstdint> // provides coverage for types in the std namespace
#endif #endif
#ifndef ENTT_SPARSE_PAGE #ifndef ENTT_SPARSE_PAGE
@@ -59,31 +42,16 @@
#ifdef ENTT_DISABLE_ASSERT #ifdef ENTT_DISABLE_ASSERT
# undef ENTT_ASSERT # undef ENTT_ASSERT
# define ENTT_ASSERT(condition, msg) (void(0)) # define ENTT_ASSERT(...) (void(0))
#elif !defined ENTT_ASSERT #elif !defined ENTT_ASSERT
# include <cassert> # include <cassert>
# define ENTT_ASSERT(condition, msg) assert(((condition) && (msg))) # define ENTT_ASSERT(condition, ...) assert(condition)
#endif #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
#define ENTT_FAIL(msg) ENTT_ASSERT(false, msg);
#ifdef ENTT_NO_ETO #ifdef ENTT_NO_ETO
# define ENTT_ETO_TYPE(Type) void # define ENTT_IGNORE_IF_EMPTY false
#else #else
# define ENTT_ETO_TYPE(Type) Type # define ENTT_IGNORE_IF_EMPTY true
#endif
#ifdef ENTT_NO_MIXIN
# define ENTT_STORAGE(Mixin, ...) __VA_ARGS__
#else
# define ENTT_STORAGE(Mixin, ...) Mixin<__VA_ARGS__>
#endif #endif
#ifdef ENTT_STANDARD_CPP #ifdef ENTT_STANDARD_CPP
@@ -101,32 +69,6 @@
# endif # endif
#endif #endif
#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
#if defined _MSC_VER #if defined _MSC_VER
# pragma detect_mismatch("entt.version", ENTT_VERSION) # pragma detect_mismatch("entt.version", ENTT_VERSION)
# pragma detect_mismatch("entt.noexcept", ENTT_XSTR(ENTT_TRY)) # pragma detect_mismatch("entt.noexcept", ENTT_XSTR(ENTT_TRY))
@@ -134,6 +76,4 @@
# pragma detect_mismatch("entt.nonstd", ENTT_XSTR(ENTT_NONSTD)) # pragma detect_mismatch("entt.nonstd", ENTT_XSTR(ENTT_NONSTD))
#endif #endif
// NOLINTEND(cppcoreguidelines-macro-usage)
#endif #endif

View File

@@ -1,11 +1,7 @@
#ifndef ENTT_CONFIG_MACRO_H #ifndef ENTT_CONFIG_MACRO_H
#define ENTT_CONFIG_MACRO_H #define ENTT_CONFIG_MACRO_H
// NOLINTBEGIN(cppcoreguidelines-macro-usage)
#define ENTT_STR(arg) #arg #define ENTT_STR(arg) #arg
#define ENTT_XSTR(arg) ENTT_STR(arg) #define ENTT_XSTR(arg) ENTT_STR(arg)
// NOLINTEND(cppcoreguidelines-macro-usage)
#endif #endif

View File

@@ -3,16 +3,12 @@
#include "macro.h" #include "macro.h"
// NOLINTBEGIN(cppcoreguidelines-macro-*,modernize-macro-*) #define ENTT_VERSION_MAJOR 3
#define ENTT_VERSION_MINOR 10
#define ENTT_VERSION_MAJOR 4 #define ENTT_VERSION_PATCH 3
#define ENTT_VERSION_MINOR 0
#define ENTT_VERSION_PATCH 0
#define ENTT_VERSION \ #define ENTT_VERSION \
ENTT_XSTR(ENTT_VERSION_MAJOR) \ ENTT_XSTR(ENTT_VERSION_MAJOR) \
"." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH)
// NOLINTEND(cppcoreguidelines-macro-*,modernize-macro-*)
#endif #endif

View File

@@ -1,10 +1,8 @@
#ifndef ENTT_CONTAINER_DENSE_MAP_HPP #ifndef ENTT_CONTAINER_DENSE_MAP_HPP
#define ENTT_CONTAINER_DENSE_MAP_HPP #define ENTT_CONTAINER_DENSE_MAP_HPP
#include <bit> #include <algorithm>
#include <cmath> #include <cmath>
#include <compare>
#include <concepts>
#include <cstddef> #include <cstddef>
#include <functional> #include <functional>
#include <iterator> #include <iterator>
@@ -15,20 +13,20 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "../config/config.h" #include "../config/config.h"
#include "../core/bit.hpp"
#include "../core/compressed_pair.hpp" #include "../core/compressed_pair.hpp"
#include "../core/iterator.hpp" #include "../core/iterator.hpp"
#include "../core/memory.hpp" #include "../core/memory.hpp"
#include "../core/type_traits.hpp" #include "../core/type_traits.hpp"
#include "../stl/iterator.hpp"
#include "fwd.hpp" #include "fwd.hpp"
namespace entt { namespace entt {
/*! @cond ENTT_INTERNAL */ /**
namespace internal { * @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
static constexpr std::size_t dense_map_placeholder_position = (std::numeric_limits<std::size_t>::max)(); namespace internal {
template<typename Key, typename Type> template<typename Key, typename Type>
struct dense_map_node final { struct dense_map_node final {
@@ -39,16 +37,18 @@ struct dense_map_node final {
: next{pos}, : next{pos},
element{std::forward<Args>(args)...} {} element{std::forward<Args>(args)...} {}
template<typename... Args> template<typename Allocator, typename... Args>
dense_map_node(std::allocator_arg_t, const auto &allocator, const std::size_t pos, Args &&...args) dense_map_node(std::allocator_arg_t, const Allocator &allocator, const std::size_t pos, Args &&...args)
: next{pos}, : next{pos},
element{entt::make_obj_using_allocator<value_type>(allocator, std::forward<Args>(args)...)} {} element{entt::make_obj_using_allocator<value_type>(allocator, std::forward<Args>(args)...)} {}
dense_map_node(std::allocator_arg_t, const auto &allocator, const dense_map_node &other) template<typename Allocator>
dense_map_node(std::allocator_arg_t, const Allocator &allocator, const dense_map_node &other)
: next{other.next}, : next{other.next},
element{entt::make_obj_using_allocator<value_type>(allocator, other.element)} {} element{entt::make_obj_using_allocator<value_type>(allocator, other.element)} {}
dense_map_node(std::allocator_arg_t, const auto &allocator, dense_map_node &&other) template<typename Allocator>
dense_map_node(std::allocator_arg_t, const Allocator &allocator, dense_map_node &&other)
: next{other.next}, : next{other.next},
element{entt::make_obj_using_allocator<value_type>(allocator, std::move(other.element))} {} element{entt::make_obj_using_allocator<value_type>(allocator, std::move(other.element))} {}
@@ -61,7 +61,6 @@ class dense_map_iterator final {
template<typename> template<typename>
friend class dense_map_iterator; friend class dense_map_iterator;
static_assert(std::is_pointer_v<It>, "Not a pointer type");
using first_type = decltype(std::as_const(std::declval<It>()->element.first)); using first_type = decltype(std::as_const(std::declval<It>()->element.first));
using second_type = decltype((std::declval<It>()->element.second)); using second_type = decltype((std::declval<It>()->element.second));
@@ -71,92 +70,118 @@ public:
using reference = value_type; using reference = value_type;
using difference_type = std::ptrdiff_t; using difference_type = std::ptrdiff_t;
using iterator_category = std::input_iterator_tag; using iterator_category = std::input_iterator_tag;
using iterator_concept = std::random_access_iterator_tag;
constexpr dense_map_iterator() noexcept dense_map_iterator() ENTT_NOEXCEPT
: it{} {} : it{} {}
constexpr dense_map_iterator(const It iter) noexcept dense_map_iterator(const It iter) ENTT_NOEXCEPT
: it{iter} {} : it{iter} {}
template<typename Other> template<typename Other, typename = std::enable_if_t<!std::is_same_v<It, Other> && std::is_constructible_v<It, Other>>>
requires (!std::same_as<It, Other> && std::constructible_from<It, Other>) dense_map_iterator(const dense_map_iterator<Other> &other) ENTT_NOEXCEPT
constexpr dense_map_iterator(const dense_map_iterator<Other> &other) noexcept
: it{other.it} {} : it{other.it} {}
constexpr dense_map_iterator &operator++() noexcept { dense_map_iterator &operator++() ENTT_NOEXCEPT {
return ++it, *this; return ++it, *this;
} }
constexpr dense_map_iterator operator++(int) noexcept { dense_map_iterator operator++(int) ENTT_NOEXCEPT {
const dense_map_iterator orig = *this; dense_map_iterator orig = *this;
return ++(*this), orig; return ++(*this), orig;
} }
constexpr dense_map_iterator &operator--() noexcept { dense_map_iterator &operator--() ENTT_NOEXCEPT {
return --it, *this; return --it, *this;
} }
constexpr dense_map_iterator operator--(int) noexcept { dense_map_iterator operator--(int) ENTT_NOEXCEPT {
const dense_map_iterator orig = *this; dense_map_iterator orig = *this;
return operator--(), orig; return operator--(), orig;
} }
constexpr dense_map_iterator &operator+=(const difference_type value) noexcept { dense_map_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT {
it += value; it += value;
return *this; return *this;
} }
constexpr dense_map_iterator operator+(const difference_type value) const noexcept { dense_map_iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
dense_map_iterator copy = *this; dense_map_iterator copy = *this;
return (copy += value); return (copy += value);
} }
constexpr dense_map_iterator &operator-=(const difference_type value) noexcept { dense_map_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT {
return (*this += -value); return (*this += -value);
} }
constexpr dense_map_iterator operator-(const difference_type value) const noexcept { dense_map_iterator operator-(const difference_type value) const ENTT_NOEXCEPT {
return (*this + -value); return (*this + -value);
} }
[[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept { [[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT {
return {it[value].element.first, it[value].element.second}; return {it[value].element.first, it[value].element.second};
} }
[[nodiscard]] constexpr pointer operator->() const noexcept { [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT {
return operator*(); return operator*();
} }
[[nodiscard]] constexpr reference operator*() const noexcept { [[nodiscard]] reference operator*() const ENTT_NOEXCEPT {
return operator[](0); return {it->element.first, it->element.second};
} }
template<typename Other> template<typename ILhs, typename IRhs>
[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_map_iterator<Other> &other) const noexcept { friend std::ptrdiff_t operator-(const dense_map_iterator<ILhs> &, const dense_map_iterator<IRhs> &) ENTT_NOEXCEPT;
return it - other.it;
}
template<typename Other> template<typename ILhs, typename IRhs>
[[nodiscard]] constexpr bool operator==(const dense_map_iterator<Other> &other) const noexcept { friend bool operator==(const dense_map_iterator<ILhs> &, const dense_map_iterator<IRhs> &) ENTT_NOEXCEPT;
return it == other.it;
}
template<typename Other> template<typename ILhs, typename IRhs>
[[nodiscard]] constexpr auto operator<=>(const dense_map_iterator<Other> &other) const noexcept { friend bool operator<(const dense_map_iterator<ILhs> &, const dense_map_iterator<IRhs> &) ENTT_NOEXCEPT;
return it <=> other.it;
}
private: private:
It it; It it;
}; };
template<typename ILhs, typename IRhs>
[[nodiscard]] std::ptrdiff_t operator-(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return lhs.it - rhs.it;
}
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator==(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return lhs.it == rhs.it;
}
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator!=(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return !(lhs == rhs);
}
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator<(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return lhs.it < rhs.it;
}
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator>(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return rhs < lhs;
}
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator<=(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return !(lhs > rhs);
}
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator>=(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return !(lhs < rhs);
}
template<typename It> template<typename It>
class dense_map_local_iterator final { class dense_map_local_iterator final {
template<typename> template<typename>
friend class dense_map_local_iterator; friend class dense_map_local_iterator;
static_assert(std::is_pointer_v<It>, "Not a pointer type");
using first_type = decltype(std::as_const(std::declval<It>()->element.first)); using first_type = decltype(std::as_const(std::declval<It>()->element.first));
using second_type = decltype((std::declval<It>()->element.second)); using second_type = decltype((std::declval<It>()->element.second));
@@ -166,54 +191,62 @@ public:
using reference = value_type; using reference = value_type;
using difference_type = std::ptrdiff_t; using difference_type = std::ptrdiff_t;
using iterator_category = std::input_iterator_tag; using iterator_category = std::input_iterator_tag;
using iterator_concept = std::forward_iterator_tag;
constexpr dense_map_local_iterator() noexcept = default; dense_map_local_iterator() ENTT_NOEXCEPT
: it{},
offset{} {}
constexpr dense_map_local_iterator(It iter, const std::size_t pos) noexcept dense_map_local_iterator(It iter, const std::size_t pos) ENTT_NOEXCEPT
: it{iter}, : it{iter},
offset{pos} {} offset{pos} {}
template<typename Other> template<typename Other, typename = std::enable_if_t<!std::is_same_v<It, Other> && std::is_constructible_v<It, Other>>>
requires (!std::same_as<It, Other> && std::constructible_from<It, Other>) dense_map_local_iterator(const dense_map_local_iterator<Other> &other) ENTT_NOEXCEPT
constexpr dense_map_local_iterator(const dense_map_local_iterator<Other> &other) noexcept
: it{other.it}, : it{other.it},
offset{other.offset} {} offset{other.offset} {}
constexpr dense_map_local_iterator &operator++() noexcept { dense_map_local_iterator &operator++() ENTT_NOEXCEPT {
return (offset = it[static_cast<difference_type>(offset)].next), *this; return offset = it[offset].next, *this;
} }
constexpr dense_map_local_iterator operator++(int) noexcept { dense_map_local_iterator operator++(int) ENTT_NOEXCEPT {
const dense_map_local_iterator orig = *this; dense_map_local_iterator orig = *this;
return ++(*this), orig; return ++(*this), orig;
} }
[[nodiscard]] constexpr pointer operator->() const noexcept { [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT {
return operator*(); return operator*();
} }
[[nodiscard]] constexpr reference operator*() const noexcept { [[nodiscard]] reference operator*() const ENTT_NOEXCEPT {
const auto idx = static_cast<difference_type>(offset); return {it[offset].element.first, it[offset].element.second};
return {it[idx].element.first, it[idx].element.second};
} }
template<typename Other> [[nodiscard]] std::size_t index() const ENTT_NOEXCEPT {
[[nodiscard]] constexpr bool operator==(const dense_map_local_iterator<Other> &other) const noexcept {
return offset == other.offset;
}
[[nodiscard]] constexpr std::size_t index() const noexcept {
return offset; return offset;
} }
private: private:
It it{}; It it;
std::size_t offset{dense_map_placeholder_position}; std::size_t offset;
}; };
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator==(const dense_map_local_iterator<ILhs> &lhs, const dense_map_local_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return lhs.index() == rhs.index();
}
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator!=(const dense_map_local_iterator<ILhs> &lhs, const dense_map_local_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return !(lhs == rhs);
}
} // namespace internal } // namespace internal
/*! @endcond */
/**
* Internal details not to be documented.
* @endcond
*/
/** /**
* @brief Associative container for key-value pairs with unique keys. * @brief Associative container for key-value pairs with unique keys.
@@ -232,33 +265,34 @@ template<typename Key, typename Type, typename Hash, typename KeyEqual, typename
class dense_map { class dense_map {
static constexpr float default_threshold = 0.875f; static constexpr float default_threshold = 0.875f;
static constexpr std::size_t minimum_capacity = 8u; static constexpr std::size_t minimum_capacity = 8u;
static constexpr std::size_t placeholder_position = internal::dense_map_placeholder_position;
using node_type = internal::dense_map_node<Key, Type>; using node_type = internal::dense_map_node<Key, Type>;
using alloc_traits = std::allocator_traits<Allocator>; using alloc_traits = typename std::allocator_traits<Allocator>;
static_assert(std::is_same_v<typename alloc_traits::value_type, std::pair<const Key, Type>>, "Invalid value type"); static_assert(std::is_same_v<typename alloc_traits::value_type, std::pair<const Key, Type>>, "Invalid value type");
using sparse_container_type = std::vector<std::size_t, typename alloc_traits::template rebind_alloc<std::size_t>>; using 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>>; using packed_container_type = std::vector<node_type, typename alloc_traits::template rebind_alloc<node_type>>;
[[nodiscard]] std::size_t key_to_bucket(const auto &key) const noexcept { template<typename Other>
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) [[nodiscard]] std::size_t key_to_bucket(const Other &key) const ENTT_NOEXCEPT {
return fast_mod(static_cast<size_type>(sparse.second()(key)), bucket_count()); return fast_mod(sparse.second()(key), bucket_count());
} }
[[nodiscard]] auto constrained_find(const auto &key, const std::size_t bucket) { template<typename Other>
for(auto offset = sparse.first()[bucket]; offset != placeholder_position; offset = packed.first()[offset].next) { [[nodiscard]] auto constrained_find(const Other &key, std::size_t bucket) {
if(packed.second()(packed.first()[offset].element.first, key)) { for(auto it = begin(bucket), last = end(bucket); it != last; ++it) {
return begin() + static_cast<iterator::difference_type>(offset); if(packed.second()(it->first, key)) {
return begin() + static_cast<typename iterator::difference_type>(it.index());
} }
} }
return end(); return end();
} }
[[nodiscard]] auto constrained_find(const auto &key, const std::size_t bucket) const { template<typename Other>
for(auto offset = sparse.first()[bucket]; offset != placeholder_position; offset = packed.first()[offset].next) { [[nodiscard]] auto constrained_find(const Other &key, std::size_t bucket) const {
if(packed.second()(packed.first()[offset].element.first, key)) { for(auto it = cbegin(bucket), last = cend(bucket); it != last; ++it) {
return cbegin() + static_cast<const_iterator::difference_type>(offset); if(packed.second()(it->first, key)) {
return cbegin() + static_cast<typename iterator::difference_type>(it.index());
} }
} }
@@ -298,8 +332,8 @@ class dense_map {
void move_and_pop(const std::size_t pos) { void move_and_pop(const std::size_t pos) {
if(const auto last = size() - 1u; pos != last) { if(const auto last = size() - 1u; pos != last) {
size_type *curr = &sparse.first()[key_to_bucket(packed.first().back().element.first)];
packed.first()[pos] = std::move(packed.first().back()); packed.first()[pos] = std::move(packed.first().back());
size_type *curr = sparse.first().data() + key_to_bucket(packed.first().back().element.first);
for(; *curr != last; curr = &packed.first()[*curr].next) {} for(; *curr != last; curr = &packed.first()[*curr].next) {}
*curr = pos; *curr = pos;
} }
@@ -308,14 +342,12 @@ class dense_map {
} }
void rehash_if_required() { void rehash_if_required() {
if(const auto bc = bucket_count(); size() > static_cast<size_type>(static_cast<float>(bc) * max_load_factor())) { if(size() > (bucket_count() * max_load_factor())) {
rehash(bc * 2u); rehash(bucket_count() * 2u);
} }
} }
public: public:
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Key type of the container. */ /*! @brief Key type of the container. */
using key_type = Key; using key_type = Key;
/*! @brief Mapped type of the container. */ /*! @brief Mapped type of the container. */
@@ -324,24 +356,24 @@ public:
using value_type = std::pair<const Key, Type>; using value_type = std::pair<const Key, Type>;
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
using size_type = std::size_t; using size_type = std::size_t;
/*! @brief Signed integer type. */
using difference_type = std::ptrdiff_t;
/*! @brief Type of function to use to hash the keys. */ /*! @brief Type of function to use to hash the keys. */
using hasher = Hash; using hasher = Hash;
/*! @brief Type of function to use to compare the keys for equality. */ /*! @brief Type of function to use to compare the keys for equality. */
using key_equal = KeyEqual; using key_equal = KeyEqual;
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Input iterator type. */ /*! @brief Input iterator type. */
using iterator = internal::dense_map_iterator<typename packed_container_type::pointer>; using iterator = internal::dense_map_iterator<typename packed_container_type::iterator>;
/*! @brief Constant input iterator type. */ /*! @brief Constant input iterator type. */
using const_iterator = internal::dense_map_iterator<typename packed_container_type::const_pointer>; using const_iterator = internal::dense_map_iterator<typename packed_container_type::const_iterator>;
/*! @brief Input iterator type. */ /*! @brief Input iterator type. */
using local_iterator = internal::dense_map_local_iterator<typename packed_container_type::pointer>; using local_iterator = internal::dense_map_local_iterator<typename packed_container_type::iterator>;
/*! @brief Constant input iterator type. */ /*! @brief Constant input iterator type. */
using const_local_iterator = internal::dense_map_local_iterator<typename packed_container_type::const_pointer>; using const_local_iterator = internal::dense_map_local_iterator<typename packed_container_type::const_iterator>;
/*! @brief Default constructor. */ /*! @brief Default constructor. */
dense_map() dense_map()
: dense_map{minimum_capacity} {} : dense_map(minimum_capacity) {}
/** /**
* @brief Constructs an empty container with a given allocator. * @brief Constructs an empty container with a given allocator.
@@ -353,34 +385,35 @@ public:
/** /**
* @brief Constructs an empty container with a given allocator and user * @brief Constructs an empty container with a given allocator and user
* supplied minimal number of buckets. * supplied minimal number of buckets.
* @param cnt Minimal number of buckets. * @param bucket_count Minimal number of buckets.
* @param allocator The allocator to use. * @param allocator The allocator to use.
*/ */
dense_map(const size_type cnt, const allocator_type &allocator) dense_map(const size_type bucket_count, const allocator_type &allocator)
: dense_map{cnt, hasher{}, key_equal{}, allocator} {} : dense_map{bucket_count, hasher{}, key_equal{}, allocator} {}
/** /**
* @brief Constructs an empty container with a given allocator, hash * @brief Constructs an empty container with a given allocator, hash
* function and user supplied minimal number of buckets. * function and user supplied minimal number of buckets.
* @param cnt Minimal number of buckets. * @param bucket_count Minimal number of buckets.
* @param hash Hash function to use. * @param hash Hash function to use.
* @param allocator The allocator to use. * @param allocator The allocator to use.
*/ */
dense_map(const size_type cnt, const hasher &hash, const allocator_type &allocator) dense_map(const size_type bucket_count, const hasher &hash, const allocator_type &allocator)
: dense_map{cnt, hash, key_equal{}, allocator} {} : dense_map{bucket_count, hash, key_equal{}, allocator} {}
/** /**
* @brief Constructs an empty container with a given allocator, hash * @brief Constructs an empty container with a given allocator, hash
* function, compare function and user supplied minimal number of buckets. * function, compare function and user supplied minimal number of buckets.
* @param cnt Minimal number of buckets. * @param bucket_count Minimal number of buckets.
* @param hash Hash function to use. * @param hash Hash function to use.
* @param equal Compare function to use. * @param equal Compare function to use.
* @param allocator The allocator to use. * @param allocator The allocator to use.
*/ */
explicit dense_map(const size_type cnt, const hasher &hash = hasher{}, const key_equal &equal = key_equal{}, const allocator_type &allocator = allocator_type{}) explicit dense_map(const size_type bucket_count, const hasher &hash = hasher{}, const key_equal &equal = key_equal{}, const allocator_type &allocator = allocator_type{})
: sparse{allocator, hash}, : sparse{allocator, hash},
packed{allocator, equal} { packed{allocator, equal},
rehash(cnt); threshold{default_threshold} {
rehash(bucket_count);
} }
/*! @brief Default copy constructor. */ /*! @brief Default copy constructor. */
@@ -397,7 +430,7 @@ public:
threshold{other.threshold} {} threshold{other.threshold} {}
/*! @brief Default move constructor. */ /*! @brief Default move constructor. */
dense_map(dense_map &&) noexcept = default; dense_map(dense_map &&) = default;
/** /**
* @brief Allocator-extended move constructor. * @brief Allocator-extended move constructor.
@@ -409,9 +442,6 @@ public:
packed{std::piecewise_construct, std::forward_as_tuple(std::move(other.packed.first()), allocator), std::forward_as_tuple(std::move(other.packed.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} {} threshold{other.threshold} {}
/*! @brief Default destructor. */
~dense_map() = default;
/** /**
* @brief Default copy assignment operator. * @brief Default copy assignment operator.
* @return This container. * @return This container.
@@ -422,72 +452,67 @@ public:
* @brief Default move assignment operator. * @brief Default move assignment operator.
* @return This container. * @return This container.
*/ */
dense_map &operator=(dense_map &&) noexcept = default; dense_map &operator=(dense_map &&) = default;
/**
* @brief Exchanges the contents with those of a given container.
* @param other Container to exchange the content with.
*/
void swap(dense_map &other) noexcept {
using std::swap;
swap(sparse, other.sparse);
swap(packed, other.packed);
swap(threshold, other.threshold);
}
/** /**
* @brief Returns the associated allocator. * @brief Returns the associated allocator.
* @return The associated allocator. * @return The associated allocator.
*/ */
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept { [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT {
return sparse.first().get_allocator(); return sparse.first().get_allocator();
} }
/** /**
* @brief Returns an iterator to the beginning. * @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()`. * If the array is empty, the returned iterator will be equal to `end()`.
* *
* @return An iterator to the first instance of the internal array. * @return An iterator to the first instance of the internal array.
*/ */
[[nodiscard]] const_iterator cbegin() const noexcept { [[nodiscard]] const_iterator cbegin() const ENTT_NOEXCEPT {
return packed.first().data(); return packed.first().begin();
} }
/*! @copydoc cbegin */ /*! @copydoc cbegin */
[[nodiscard]] const_iterator begin() const noexcept { [[nodiscard]] const_iterator begin() const ENTT_NOEXCEPT {
return cbegin(); return cbegin();
} }
/*! @copydoc begin */ /*! @copydoc begin */
[[nodiscard]] iterator begin() noexcept { [[nodiscard]] iterator begin() ENTT_NOEXCEPT {
return packed.first().data(); return packed.first().begin();
} }
/** /**
* @brief Returns an iterator to the end. * @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 * @return An iterator to the element following the last instance of the
* internal array. * internal array.
*/ */
[[nodiscard]] const_iterator cend() const noexcept { [[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT {
return packed.first().data() + packed.first().size(); return packed.first().end();
} }
/*! @copydoc cend */ /*! @copydoc cend */
[[nodiscard]] const_iterator end() const noexcept { [[nodiscard]] const_iterator end() const ENTT_NOEXCEPT {
return cend(); return cend();
} }
/*! @copydoc end */ /*! @copydoc end */
[[nodiscard]] iterator end() noexcept { [[nodiscard]] iterator end() ENTT_NOEXCEPT {
return packed.first().data() + packed.first().size(); return packed.first().end();
} }
/** /**
* @brief Checks whether a container is empty. * @brief Checks whether a container is empty.
* @return True if the container is empty, false otherwise. * @return True if the container is empty, false otherwise.
*/ */
[[nodiscard]] bool empty() const noexcept { [[nodiscard]] bool empty() const ENTT_NOEXCEPT {
return packed.first().empty(); return packed.first().empty();
} }
@@ -495,20 +520,12 @@ public:
* @brief Returns the number of elements in a container. * @brief Returns the number of elements in a container.
* @return Number of elements in a container. * @return Number of elements in a container.
*/ */
[[nodiscard]] size_type size() const noexcept { [[nodiscard]] size_type size() const ENTT_NOEXCEPT {
return packed.first().size(); 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. */ /*! @brief Clears the container. */
void clear() noexcept { void clear() ENTT_NOEXCEPT {
sparse.first().clear(); sparse.first().clear();
packed.first().clear(); packed.first().clear();
rehash(0u); rehash(0u);
@@ -535,17 +552,19 @@ public:
* @tparam Arg Type of the key-value pair to insert into the container. * @tparam Arg Type of the key-value pair to insert into the container.
*/ */
template<typename Arg> template<typename Arg>
requires std::constructible_from<value_type, Arg &&> std::enable_if_t<std::is_constructible_v<value_type, Arg &&>, std::pair<iterator, bool>>
std::pair<iterator, bool> insert(Arg &&value) { insert(Arg &&value) {
return insert_or_do_nothing(std::forward<Arg>(value).first, std::forward<Arg>(value).second); return insert_or_do_nothing(std::forward<Arg>(value).first, std::forward<Arg>(value).second);
} }
/** /**
* @brief Inserts elements into the container, if their keys do not exist. * @brief Inserts elements into the container, if their keys do not exist.
* @tparam It Type of input iterator.
* @param first An iterator to the first element of the range of elements. * @param 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. * @param last An iterator past the last element of the range of elements.
*/ */
void insert(stl::input_iterator auto first, stl::input_iterator auto last) { template<typename It>
void insert(It first, It last) {
for(; first != last; ++first) { for(; first != last; ++first) {
insert(*first); insert(*first);
} }
@@ -651,7 +670,7 @@ public:
const auto dist = first - cbegin(); const auto dist = first - cbegin();
for(auto from = last - cbegin(); from != dist; --from) { for(auto from = last - cbegin(); from != dist; --from) {
erase(packed.first()[static_cast<size_type>(from) - 1u].element.first); erase(packed.first()[from - 1u].element.first);
} }
return (begin() + dist); return (begin() + dist);
@@ -663,7 +682,7 @@ public:
* @return Number of elements removed (either 0 or 1). * @return Number of elements removed (either 0 or 1).
*/ */
size_type erase(const key_type &key) { size_type erase(const key_type &key) {
for(size_type *curr = &sparse.first()[key_to_bucket(key)]; *curr != placeholder_position; curr = &packed.first()[*curr].next) { for(size_type *curr = sparse.first().data() + key_to_bucket(key); *curr != (std::numeric_limits<size_type>::max)(); curr = &packed.first()[*curr].next) {
if(packed.second()(packed.first()[*curr].element.first, key)) { if(packed.second()(packed.first()[*curr].element.first, key)) {
const auto index = *curr; const auto index = *curr;
*curr = packed.first()[*curr].next; *curr = packed.first()[*curr].next;
@@ -675,6 +694,17 @@ public:
return 0u; return 0u;
} }
/**
* @brief Exchanges the contents with those of a given container.
* @param other Container to exchange the content with.
*/
void swap(dense_map &other) {
using std::swap;
swap(sparse, other.sparse);
swap(packed, other.packed);
swap(threshold, other.threshold);
}
/** /**
* @brief Accesses a given element with bounds checking. * @brief Accesses a given element with bounds checking.
* @param key A key of an element to find. * @param key A key of an element to find.
@@ -693,26 +723,6 @@ public:
return it->second; return it->second;
} }
/**
* @brief Accesses a given element with bounds checking.
* @param key A key of an element to find.
* @return A reference to the mapped value of the requested element.
*/
[[nodiscard]] mapped_type const &at(const auto &key) const
requires is_transparent_v<hasher> && is_transparent_v<key_equal> {
auto it = find(key);
ENTT_ASSERT(it != cend(), "Invalid key");
return it->second;
}
/*! @copydoc at */
[[nodiscard]] mapped_type &at(const auto &key)
requires is_transparent_v<hasher> && is_transparent_v<key_equal> {
auto it = find(key);
ENTT_ASSERT(it != end(), "Invalid key");
return it->second;
}
/** /**
* @brief Accesses or inserts a given element. * @brief Accesses or inserts a given element.
* @param key A key of an element to find or insert. * @param key A key of an element to find or insert.
@@ -731,25 +741,6 @@ public:
return insert_or_do_nothing(std::move(key)).first->second; return insert_or_do_nothing(std::move(key)).first->second;
} }
/**
* @brief Returns the number of elements matching a key (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 key_type &key) const {
return find(key) != end();
}
/**
* @brief Returns the number of elements matching a key (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 auto &key) const
requires is_transparent_v<hasher> && is_transparent_v<key_equal> {
return find(key) != end();
}
/** /**
* @brief Finds an element with a given key. * @brief Finds an element with a given key.
* @param key Key value of an element to search for. * @param key Key value of an element to search for.
@@ -767,59 +758,25 @@ public:
/** /**
* @brief Finds an element with a key that compares _equivalent_ to a given * @brief Finds an element with a key that compares _equivalent_ to a given
* key. * value.
* @tparam Other Type of the key value of an element to search for.
* @param key Key value of an element to search for. * @param key Key value of an element to search for.
* @return An iterator to an element with the given key. If no such element * @return An iterator to an element with the given key. If no such element
* is found, a past-the-end iterator is returned. * is found, a past-the-end iterator is returned.
*/ */
[[nodiscard]] iterator find(const auto &key) template<typename Other>
requires is_transparent_v<hasher> && is_transparent_v<key_equal> { [[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, iterator>>
find(const Other &key) {
return constrained_find(key, key_to_bucket(key)); return constrained_find(key, key_to_bucket(key));
} }
/*! @copydoc find */ /*! @copydoc find */
[[nodiscard]] const_iterator find(const auto &key) const template<typename Other>
requires is_transparent_v<hasher> && is_transparent_v<key_equal> { [[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, const_iterator>>
find(const Other &key) const {
return constrained_find(key, key_to_bucket(key)); return constrained_find(key, key_to_bucket(key));
} }
/**
* @brief Returns a range containing all elements with a given key.
* @param key Key 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 key_type &key) {
const auto it = find(key);
return {it, it + !(it == end())};
}
/*! @copydoc equal_range */
[[nodiscard]] std::pair<const_iterator, const_iterator> equal_range(const key_type &key) const {
const auto it = find(key);
return {it, it + !(it == cend())};
}
/**
* @brief Returns a range containing all elements that compare _equivalent_
* to a given key.
* @param key Key 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 auto &key)
requires is_transparent_v<hasher> && is_transparent_v<key_equal> {
const auto it = find(key);
return {it, it + !(it == end())};
}
/*! @copydoc equal_range */
[[nodiscard]] std::pair<const_iterator, const_iterator> equal_range(const auto &key) const
requires is_transparent_v<hasher> && is_transparent_v<key_equal> {
const auto it = find(key);
return {it, it + !(it == cend())};
}
/** /**
* @brief Checks if the container contains an element with a given key. * @brief Checks if the container contains an element with a given key.
* @param key Key value of an element to search for. * @param key Key value of an element to search for.
@@ -832,11 +789,13 @@ public:
/** /**
* @brief Checks if the container contains an element with a key that * @brief Checks if the container contains an element with a key that
* compares _equivalent_ to a given value. * compares _equivalent_ to a given value.
* @tparam Other Type of the key value of an element to search for.
* @param key Key value of an element to search for. * @param key Key value of an element to search for.
* @return True if there is such an element, false otherwise. * @return True if there is such an element, false otherwise.
*/ */
[[nodiscard]] bool contains(const auto &key) const template<typename Other>
requires is_transparent_v<hasher> && is_transparent_v<key_equal> { [[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, bool>>
contains(const Other &key) const {
return (find(key) != cend()); return (find(key) != cend());
} }
@@ -846,7 +805,7 @@ public:
* @return An iterator to the beginning of the given bucket. * @return An iterator to the beginning of the given bucket.
*/ */
[[nodiscard]] const_local_iterator cbegin(const size_type index) const { [[nodiscard]] const_local_iterator cbegin(const size_type index) const {
return {packed.first().data(), sparse.first()[index]}; return {packed.first().begin(), sparse.first()[index]};
} }
/** /**
@@ -864,7 +823,7 @@ public:
* @return An iterator to the beginning of the given bucket. * @return An iterator to the beginning of the given bucket.
*/ */
[[nodiscard]] local_iterator begin(const size_type index) { [[nodiscard]] local_iterator begin(const size_type index) {
return {packed.first().data(), sparse.first()[index]}; return {packed.first().begin(), sparse.first()[index]};
} }
/** /**
@@ -873,7 +832,7 @@ public:
* @return An iterator to the end of the given bucket. * @return An iterator to the end of the given bucket.
*/ */
[[nodiscard]] const_local_iterator cend([[maybe_unused]] const size_type index) const { [[nodiscard]] const_local_iterator cend([[maybe_unused]] const size_type index) const {
return {}; return {packed.first().begin(), (std::numeric_limits<size_type>::max)()};
} }
/** /**
@@ -881,7 +840,7 @@ public:
* @param index An index of a bucket to access. * @param index An index of a bucket to access.
* @return An iterator to the end of the given bucket. * @return An iterator to the end of the given bucket.
*/ */
[[nodiscard]] const_local_iterator end(const size_type index) const { [[nodiscard]] const_local_iterator end([[maybe_unused]] const size_type index) const {
return cend(index); return cend(index);
} }
@@ -891,7 +850,7 @@ public:
* @return An iterator to the end of the given bucket. * @return An iterator to the end of the given bucket.
*/ */
[[nodiscard]] local_iterator end([[maybe_unused]] const size_type index) { [[nodiscard]] local_iterator end([[maybe_unused]] const size_type index) {
return {}; return {packed.first().begin(), (std::numeric_limits<size_type>::max)()};
} }
/** /**
@@ -933,7 +892,7 @@ public:
* @return The average number of elements per bucket. * @return The average number of elements per bucket.
*/ */
[[nodiscard]] float load_factor() const { [[nodiscard]] float load_factor() const {
return static_cast<float>(size()) / static_cast<float>(bucket_count()); return size() / static_cast<float>(bucket_count());
} }
/** /**
@@ -957,19 +916,15 @@ public:
/** /**
* @brief Reserves at least the specified number of buckets and regenerates * @brief Reserves at least the specified number of buckets and regenerates
* the hash table. * the hash table.
* @param cnt New number of buckets. * @param count New number of buckets.
*/ */
void rehash(const size_type cnt) { void rehash(const size_type count) {
auto value = cnt > minimum_capacity ? cnt : minimum_capacity; auto value = (std::max)(count, minimum_capacity);
const auto cap = static_cast<size_type>(static_cast<float>(size()) / max_load_factor()); value = (std::max)(value, static_cast<size_type>(size() / max_load_factor()));
value = value > cap ? value : cap;
if(const auto sz = std::bit_ceil(value); sz != bucket_count()) { if(const auto sz = next_power_of_two(value); sz != bucket_count()) {
sparse.first().resize(sz); sparse.first().resize(sz);
std::fill(sparse.first().begin(), sparse.first().end(), (std::numeric_limits<size_type>::max)());
for(auto &&elem: sparse.first()) {
elem = placeholder_position;
}
for(size_type pos{}, last = size(); pos < last; ++pos) { for(size_type pos{}, last = size(); pos < last; ++pos) {
const auto index = key_to_bucket(packed.first()[pos].element.first); const auto index = key_to_bucket(packed.first()[pos].element.first);
@@ -981,11 +936,11 @@ public:
/** /**
* @brief Reserves space for at least the specified number of elements and * @brief Reserves space for at least the specified number of elements and
* regenerates the hash table. * regenerates the hash table.
* @param cnt New number of elements. * @param count New number of elements.
*/ */
void reserve(const size_type cnt) { void reserve(const size_type count) {
packed.first().reserve(cnt); packed.first().reserve(count);
rehash(static_cast<size_type>(std::ceil(static_cast<float>(cnt) / max_load_factor()))); rehash(static_cast<size_type>(std::ceil(count / max_load_factor())));
} }
/** /**
@@ -1007,12 +962,16 @@ public:
private: private:
compressed_pair<sparse_container_type, hasher> sparse; compressed_pair<sparse_container_type, hasher> sparse;
compressed_pair<packed_container_type, key_equal> packed; compressed_pair<packed_container_type, key_equal> packed;
float threshold{default_threshold}; float threshold;
}; };
} // namespace entt } // namespace entt
/*! @cond ENTT_INTERNAL */ /**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
namespace std { namespace std {
template<typename Key, typename Value, typename Allocator> template<typename Key, typename Value, typename Allocator>
@@ -1020,6 +979,10 @@ struct uses_allocator<entt::internal::dense_map_node<Key, Value>, Allocator>
: std::true_type {}; : std::true_type {};
} // namespace std } // namespace std
/*! @endcond */
/**
* Internal details not to be documented.
* @endcond
*/
#endif #endif

View File

@@ -1,10 +1,8 @@
#ifndef ENTT_CONTAINER_DENSE_SET_HPP #ifndef ENTT_CONTAINER_DENSE_SET_HPP
#define ENTT_CONTAINER_DENSE_SET_HPP #define ENTT_CONTAINER_DENSE_SET_HPP
#include <bit> #include <algorithm>
#include <cmath> #include <cmath>
#include <compare>
#include <concepts>
#include <cstddef> #include <cstddef>
#include <functional> #include <functional>
#include <iterator> #include <iterator>
@@ -15,170 +13,205 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "../config/config.h" #include "../config/config.h"
#include "../core/bit.hpp"
#include "../core/compressed_pair.hpp" #include "../core/compressed_pair.hpp"
#include "../core/memory.hpp"
#include "../core/type_traits.hpp" #include "../core/type_traits.hpp"
#include "../stl/iterator.hpp"
#include "fwd.hpp" #include "fwd.hpp"
namespace entt { namespace entt {
/*! @cond ENTT_INTERNAL */ /**
namespace internal { * @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
static constexpr std::size_t dense_set_placeholder_position = (std::numeric_limits<std::size_t>::max)(); namespace internal {
template<typename It> template<typename It>
class dense_set_iterator final { class dense_set_iterator final {
template<typename> template<typename>
friend class dense_set_iterator; friend class dense_set_iterator;
static_assert(std::is_pointer_v<It>, "Not a pointer type");
public: public:
using value_type = std::remove_const_t<std::remove_pointer_t<It>>::second_type; using value_type = typename It::value_type::second_type;
using pointer = const value_type *; using pointer = const value_type *;
using reference = const value_type &; using reference = const value_type &;
using difference_type = std::ptrdiff_t; using difference_type = std::ptrdiff_t;
using iterator_category = std::random_access_iterator_tag; using iterator_category = std::random_access_iterator_tag;
constexpr dense_set_iterator() noexcept dense_set_iterator() ENTT_NOEXCEPT
: it{} {} : it{} {}
constexpr dense_set_iterator(const It iter) noexcept dense_set_iterator(const It iter) ENTT_NOEXCEPT
: it{iter} {} : it{iter} {}
template<typename Other> template<typename Other, typename = std::enable_if_t<!std::is_same_v<It, Other> && std::is_constructible_v<It, Other>>>
requires (!std::same_as<It, Other> && std::constructible_from<It, Other>) dense_set_iterator(const dense_set_iterator<Other> &other) ENTT_NOEXCEPT
constexpr dense_set_iterator(const dense_set_iterator<Other> &other) noexcept
: it{other.it} {} : it{other.it} {}
constexpr dense_set_iterator &operator++() noexcept { dense_set_iterator &operator++() ENTT_NOEXCEPT {
return ++it, *this; return ++it, *this;
} }
constexpr dense_set_iterator operator++(int) noexcept { dense_set_iterator operator++(int) ENTT_NOEXCEPT {
const dense_set_iterator orig = *this; dense_set_iterator orig = *this;
return ++(*this), orig; return ++(*this), orig;
} }
constexpr dense_set_iterator &operator--() noexcept { dense_set_iterator &operator--() ENTT_NOEXCEPT {
return --it, *this; return --it, *this;
} }
constexpr dense_set_iterator operator--(int) noexcept { dense_set_iterator operator--(int) ENTT_NOEXCEPT {
const dense_set_iterator orig = *this; dense_set_iterator orig = *this;
return operator--(), orig; return operator--(), orig;
} }
constexpr dense_set_iterator &operator+=(const difference_type value) noexcept { dense_set_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT {
it += value; it += value;
return *this; return *this;
} }
constexpr dense_set_iterator operator+(const difference_type value) const noexcept { dense_set_iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
dense_set_iterator copy = *this; dense_set_iterator copy = *this;
return (copy += value); return (copy += value);
} }
constexpr dense_set_iterator &operator-=(const difference_type value) noexcept { dense_set_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT {
return (*this += -value); return (*this += -value);
} }
constexpr dense_set_iterator operator-(const difference_type value) const noexcept { dense_set_iterator operator-(const difference_type value) const ENTT_NOEXCEPT {
return (*this + -value); return (*this + -value);
} }
[[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept { [[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT {
return it[value].second; return it[value].second;
} }
[[nodiscard]] constexpr pointer operator->() const noexcept { [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT {
return std::addressof(operator[](0)); return std::addressof(it->second);
} }
[[nodiscard]] constexpr reference operator*() const noexcept { [[nodiscard]] reference operator*() const ENTT_NOEXCEPT {
return operator[](0); return *operator->();
} }
template<typename Other> template<typename ILhs, typename IRhs>
[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_set_iterator<Other> &other) const noexcept { friend std::ptrdiff_t operator-(const dense_set_iterator<ILhs> &, const dense_set_iterator<IRhs> &) ENTT_NOEXCEPT;
return it - other.it;
}
template<typename Other> template<typename ILhs, typename IRhs>
[[nodiscard]] constexpr bool operator==(const dense_set_iterator<Other> &other) const noexcept { friend bool operator==(const dense_set_iterator<ILhs> &, const dense_set_iterator<IRhs> &) ENTT_NOEXCEPT;
return it == other.it;
}
template<typename Other> template<typename ILhs, typename IRhs>
[[nodiscard]] constexpr auto operator<=>(const dense_set_iterator<Other> &other) const noexcept { friend bool operator<(const dense_set_iterator<ILhs> &, const dense_set_iterator<IRhs> &) ENTT_NOEXCEPT;
return it <=> other.it;
}
private: private:
It it; It it;
}; };
template<typename ILhs, typename IRhs>
[[nodiscard]] std::ptrdiff_t operator-(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return lhs.it - rhs.it;
}
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator==(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return lhs.it == rhs.it;
}
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator!=(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return !(lhs == rhs);
}
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator<(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return lhs.it < rhs.it;
}
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator>(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return rhs < lhs;
}
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator<=(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return !(lhs > rhs);
}
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator>=(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return !(lhs < rhs);
}
template<typename It> template<typename It>
class dense_set_local_iterator final { class dense_set_local_iterator final {
template<typename> template<typename>
friend class dense_set_local_iterator; friend class dense_set_local_iterator;
static_assert(std::is_pointer_v<It>, "Not a pointer type");
public: public:
using value_type = std::remove_const_t<std::remove_pointer_t<It>>::second_type; using value_type = typename It::value_type::second_type;
using pointer = const value_type *; using pointer = const value_type *;
using reference = const value_type &; using reference = const value_type &;
using difference_type = std::ptrdiff_t; using difference_type = std::ptrdiff_t;
using iterator_category = std::forward_iterator_tag; using iterator_category = std::forward_iterator_tag;
constexpr dense_set_local_iterator() noexcept = default; dense_set_local_iterator() ENTT_NOEXCEPT
: it{},
offset{} {}
constexpr dense_set_local_iterator(It iter, const std::size_t pos) noexcept dense_set_local_iterator(It iter, const std::size_t pos) ENTT_NOEXCEPT
: it{iter}, : it{iter},
offset{pos} {} offset{pos} {}
template<typename Other> template<typename Other, typename = std::enable_if_t<!std::is_same_v<It, Other> && std::is_constructible_v<It, Other>>>
requires (!std::same_as<It, Other> && std::constructible_from<It, Other>) dense_set_local_iterator(const dense_set_local_iterator<Other> &other) ENTT_NOEXCEPT
constexpr dense_set_local_iterator(const dense_set_local_iterator<Other> &other) noexcept
: it{other.it}, : it{other.it},
offset{other.offset} {} offset{other.offset} {}
constexpr dense_set_local_iterator &operator++() noexcept { dense_set_local_iterator &operator++() ENTT_NOEXCEPT {
return offset = it[static_cast<difference_type>(offset)].first, *this; return offset = it[offset].first, *this;
} }
constexpr dense_set_local_iterator operator++(int) noexcept { dense_set_local_iterator operator++(int) ENTT_NOEXCEPT {
const dense_set_local_iterator orig = *this; dense_set_local_iterator orig = *this;
return ++(*this), orig; return ++(*this), orig;
} }
[[nodiscard]] constexpr pointer operator->() const noexcept { [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT {
return std::addressof(it[static_cast<difference_type>(offset)].second); return std::addressof(it[offset].second);
} }
[[nodiscard]] constexpr reference operator*() const noexcept { [[nodiscard]] reference operator*() const ENTT_NOEXCEPT {
return *operator->(); return *operator->();
} }
template<typename Other> [[nodiscard]] std::size_t index() const ENTT_NOEXCEPT {
[[nodiscard]] constexpr bool operator==(const dense_set_local_iterator<Other> &other) const noexcept {
return offset == other.offset;
}
[[nodiscard]] constexpr std::size_t index() const noexcept {
return offset; return offset;
} }
private: private:
It it{}; It it;
std::size_t offset{dense_set_placeholder_position}; std::size_t offset;
}; };
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator==(const dense_set_local_iterator<ILhs> &lhs, const dense_set_local_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return lhs.index() == rhs.index();
}
template<typename ILhs, typename IRhs>
[[nodiscard]] bool operator!=(const dense_set_local_iterator<ILhs> &lhs, const dense_set_local_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
return !(lhs == rhs);
}
} // namespace internal } // namespace internal
/*! @endcond */
/**
* Internal details not to be documented.
* @endcond
*/
/** /**
* @brief Associative container for unique objects of a given type. * @brief Associative container for unique objects of a given type.
@@ -196,7 +229,6 @@ template<typename Type, typename Hash, typename KeyEqual, typename Allocator>
class dense_set { class dense_set {
static constexpr float default_threshold = 0.875f; static constexpr float default_threshold = 0.875f;
static constexpr std::size_t minimum_capacity = 8u; static constexpr std::size_t minimum_capacity = 8u;
static constexpr std::size_t placeholder_position = internal::dense_set_placeholder_position;
using node_type = std::pair<std::size_t, Type>; using node_type = std::pair<std::size_t, Type>;
using alloc_traits = std::allocator_traits<Allocator>; using alloc_traits = std::allocator_traits<Allocator>;
@@ -204,24 +236,27 @@ class dense_set {
using sparse_container_type = std::vector<std::size_t, typename alloc_traits::template rebind_alloc<std::size_t>>; 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>>; using packed_container_type = std::vector<node_type, typename alloc_traits::template rebind_alloc<node_type>>;
[[nodiscard]] std::size_t value_to_bucket(const auto &value) const noexcept { template<typename Other>
return fast_mod(static_cast<size_type>(sparse.second()(value)), bucket_count()); [[nodiscard]] std::size_t value_to_bucket(const Other &value) const ENTT_NOEXCEPT {
return fast_mod(sparse.second()(value), bucket_count());
} }
[[nodiscard]] auto constrained_find(const auto &value, const std::size_t bucket) { template<typename Other>
for(auto offset = sparse.first()[bucket]; offset != placeholder_position; offset = packed.first()[offset].first) { [[nodiscard]] auto constrained_find(const Other &value, std::size_t bucket) {
if(packed.second()(packed.first()[offset].second, value)) { for(auto it = begin(bucket), last = end(bucket); it != last; ++it) {
return begin() + static_cast<iterator::difference_type>(offset); if(packed.second()(*it, value)) {
return begin() + static_cast<typename iterator::difference_type>(it.index());
} }
} }
return end(); return end();
} }
[[nodiscard]] auto constrained_find(const auto &value, const std::size_t bucket) const { template<typename Other>
for(auto offset = sparse.first()[bucket]; offset != placeholder_position; offset = packed.first()[offset].first) { [[nodiscard]] auto constrained_find(const Other &value, std::size_t bucket) const {
if(packed.second()(packed.first()[offset].second, value)) { for(auto it = cbegin(bucket), last = cend(bucket); it != last; ++it) {
return cbegin() + static_cast<const_iterator::difference_type>(offset); if(packed.second()(*it, value)) {
return cbegin() + static_cast<typename iterator::difference_type>(it.index());
} }
} }
@@ -245,8 +280,8 @@ class dense_set {
void move_and_pop(const std::size_t pos) { void move_and_pop(const std::size_t pos) {
if(const auto last = size() - 1u; pos != last) { if(const auto last = size() - 1u; pos != last) {
size_type *curr = &sparse.first()[value_to_bucket(packed.first().back().second)];
packed.first()[pos] = std::move(packed.first().back()); packed.first()[pos] = std::move(packed.first().back());
size_type *curr = sparse.first().data() + value_to_bucket(packed.first().back().second);
for(; *curr != last; curr = &packed.first()[*curr].first) {} for(; *curr != last; curr = &packed.first()[*curr].first) {}
*curr = pos; *curr = pos;
} }
@@ -255,42 +290,36 @@ class dense_set {
} }
void rehash_if_required() { void rehash_if_required() {
if(const auto bc = bucket_count(); size() > static_cast<size_type>(static_cast<float>(bc) * max_load_factor())) { if(size() > (bucket_count() * max_load_factor())) {
rehash(bc * 2u); rehash(bucket_count() * 2u);
} }
} }
public: public:
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Key type of the container. */ /*! @brief Key type of the container. */
using key_type = Type; using key_type = Type;
/*! @brief Value type of the container. */ /*! @brief Value type of the container. */
using value_type = Type; using value_type = Type;
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
using size_type = std::size_t; using size_type = std::size_t;
/*! @brief Signed integer type. */
using difference_type = std::ptrdiff_t;
/*! @brief Type of function to use to hash the elements. */ /*! @brief Type of function to use to hash the elements. */
using hasher = Hash; using hasher = Hash;
/*! @brief Type of function to use to compare the elements for equality. */ /*! @brief Type of function to use to compare the elements for equality. */
using key_equal = KeyEqual; using key_equal = KeyEqual;
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Random access iterator type. */ /*! @brief Random access iterator type. */
using iterator = internal::dense_set_iterator<typename packed_container_type::pointer>; using iterator = internal::dense_set_iterator<typename packed_container_type::iterator>;
/*! @brief Constant random access iterator type. */ /*! @brief Constant random access iterator type. */
using const_iterator = internal::dense_set_iterator<typename packed_container_type::const_pointer>; using const_iterator = internal::dense_set_iterator<typename packed_container_type::const_iterator>;
/*! @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 Forward iterator type. */ /*! @brief Forward iterator type. */
using local_iterator = internal::dense_set_local_iterator<typename packed_container_type::pointer>; using local_iterator = internal::dense_set_local_iterator<typename packed_container_type::iterator>;
/*! @brief Constant forward iterator type. */ /*! @brief Constant forward iterator type. */
using const_local_iterator = internal::dense_set_local_iterator<typename packed_container_type::const_pointer>; using const_local_iterator = internal::dense_set_local_iterator<typename packed_container_type::const_iterator>;
/*! @brief Default constructor. */ /*! @brief Default constructor. */
dense_set() dense_set()
: dense_set{minimum_capacity} {} : dense_set(minimum_capacity) {}
/** /**
* @brief Constructs an empty container with a given allocator. * @brief Constructs an empty container with a given allocator.
@@ -302,34 +331,35 @@ public:
/** /**
* @brief Constructs an empty container with a given allocator and user * @brief Constructs an empty container with a given allocator and user
* supplied minimal number of buckets. * supplied minimal number of buckets.
* @param cnt Minimal number of buckets. * @param bucket_count Minimal number of buckets.
* @param allocator The allocator to use. * @param allocator The allocator to use.
*/ */
dense_set(const size_type cnt, const allocator_type &allocator) dense_set(const size_type bucket_count, const allocator_type &allocator)
: dense_set{cnt, hasher{}, key_equal{}, allocator} {} : dense_set{bucket_count, hasher{}, key_equal{}, allocator} {}
/** /**
* @brief Constructs an empty container with a given allocator, hash * @brief Constructs an empty container with a given allocator, hash
* function and user supplied minimal number of buckets. * function and user supplied minimal number of buckets.
* @param cnt Minimal number of buckets. * @param bucket_count Minimal number of buckets.
* @param hash Hash function to use. * @param hash Hash function to use.
* @param allocator The allocator to use. * @param allocator The allocator to use.
*/ */
dense_set(const size_type cnt, const hasher &hash, const allocator_type &allocator) dense_set(const size_type bucket_count, const hasher &hash, const allocator_type &allocator)
: dense_set{cnt, hash, key_equal{}, allocator} {} : dense_set{bucket_count, hash, key_equal{}, allocator} {}
/** /**
* @brief Constructs an empty container with a given allocator, hash * @brief Constructs an empty container with a given allocator, hash
* function, compare function and user supplied minimal number of buckets. * function, compare function and user supplied minimal number of buckets.
* @param cnt Minimal number of buckets. * @param bucket_count Minimal number of buckets.
* @param hash Hash function to use. * @param hash Hash function to use.
* @param equal Compare function to use. * @param equal Compare function to use.
* @param allocator The allocator 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{}) explicit dense_set(const size_type bucket_count, const hasher &hash = hasher{}, const key_equal &equal = key_equal{}, const allocator_type &allocator = allocator_type{})
: sparse{allocator, hash}, : sparse{allocator, hash},
packed{allocator, equal} { packed{allocator, equal},
rehash(cnt); threshold{default_threshold} {
rehash(bucket_count);
} }
/*! @brief Default copy constructor. */ /*! @brief Default copy constructor. */
@@ -346,7 +376,7 @@ public:
threshold{other.threshold} {} threshold{other.threshold} {}
/*! @brief Default move constructor. */ /*! @brief Default move constructor. */
dense_set(dense_set &&) noexcept = default; dense_set(dense_set &&) = default;
/** /**
* @brief Allocator-extended move constructor. * @brief Allocator-extended move constructor.
@@ -358,9 +388,6 @@ public:
packed{std::piecewise_construct, std::forward_as_tuple(std::move(other.packed.first()), allocator), std::forward_as_tuple(std::move(other.packed.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} {} threshold{other.threshold} {}
/*! @brief Default destructor. */
~dense_set() = default;
/** /**
* @brief Default copy assignment operator. * @brief Default copy assignment operator.
* @return This container. * @return This container.
@@ -371,112 +398,67 @@ public:
* @brief Default move assignment operator. * @brief Default move assignment operator.
* @return This container. * @return This container.
*/ */
dense_set &operator=(dense_set &&) noexcept = default; dense_set &operator=(dense_set &&) = default;
/**
* @brief Exchanges the contents with those of a given container.
* @param other Container to exchange the content with.
*/
void swap(dense_set &other) noexcept {
using std::swap;
swap(sparse, other.sparse);
swap(packed, other.packed);
swap(threshold, other.threshold);
}
/** /**
* @brief Returns the associated allocator. * @brief Returns the associated allocator.
* @return The associated allocator. * @return The associated allocator.
*/ */
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept { [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT {
return sparse.first().get_allocator(); return sparse.first().get_allocator();
} }
/** /**
* @brief Returns an iterator to the beginning. * @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()`. * If the array is empty, the returned iterator will be equal to `end()`.
* *
* @return An iterator to the first instance of the internal array. * @return An iterator to the first instance of the internal array.
*/ */
[[nodiscard]] const_iterator cbegin() const noexcept { [[nodiscard]] const_iterator cbegin() const ENTT_NOEXCEPT {
return packed.first().data(); return packed.first().begin();
} }
/*! @copydoc cbegin */ /*! @copydoc cbegin */
[[nodiscard]] const_iterator begin() const noexcept { [[nodiscard]] const_iterator begin() const ENTT_NOEXCEPT {
return cbegin(); return cbegin();
} }
/*! @copydoc begin */ /*! @copydoc begin */
[[nodiscard]] iterator begin() noexcept { [[nodiscard]] iterator begin() ENTT_NOEXCEPT {
return packed.first().data(); return packed.first().begin();
} }
/** /**
* @brief Returns an iterator to the end. * @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 * @return An iterator to the element following the last instance of the
* internal array. * internal array.
*/ */
[[nodiscard]] const_iterator cend() const noexcept { [[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT {
return packed.first().data() + packed.first().size(); return packed.first().end();
} }
/*! @copydoc cend */ /*! @copydoc cend */
[[nodiscard]] const_iterator end() const noexcept { [[nodiscard]] const_iterator end() const ENTT_NOEXCEPT {
return cend(); return cend();
} }
/*! @copydoc end */ /*! @copydoc end */
[[nodiscard]] iterator end() noexcept { [[nodiscard]] iterator end() ENTT_NOEXCEPT {
return packed.first().data() + packed.first().size(); return packed.first().end();
}
/**
* @brief Returns a reverse iterator to the beginning.
*
* If the array 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.
* @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 Checks whether a container is empty. * @brief Checks whether a container is empty.
* @return True if the container is empty, false otherwise. * @return True if the container is empty, false otherwise.
*/ */
[[nodiscard]] bool empty() const noexcept { [[nodiscard]] bool empty() const ENTT_NOEXCEPT {
return packed.first().empty(); return packed.first().empty();
} }
@@ -484,20 +466,12 @@ public:
* @brief Returns the number of elements in a container. * @brief Returns the number of elements in a container.
* @return Number of elements in a container. * @return Number of elements in a container.
*/ */
[[nodiscard]] size_type size() const noexcept { [[nodiscard]] size_type size() const ENTT_NOEXCEPT {
return packed.first().size(); 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. */ /*! @brief Clears the container. */
void clear() noexcept { void clear() ENTT_NOEXCEPT {
sparse.first().clear(); sparse.first().clear();
packed.first().clear(); packed.first().clear();
rehash(0u); rehash(0u);
@@ -521,10 +495,12 @@ public:
/** /**
* @brief Inserts elements into the container, if they do not exist. * @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 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. * @param last An iterator past the last element of the range of elements.
*/ */
void insert(stl::input_iterator auto first, stl::input_iterator auto last) { template<typename It>
void insert(It first, It last) {
for(; first != last; ++first) { for(; first != last; ++first) {
insert(*first); insert(*first);
} }
@@ -545,7 +521,7 @@ public:
*/ */
template<typename... Args> template<typename... Args>
std::pair<iterator, bool> emplace(Args &&...args) { std::pair<iterator, bool> emplace(Args &&...args) {
if constexpr(((sizeof...(Args) == 1u) && ... && std::is_same_v<std::decay_t<Args>, value_type>)) { if constexpr(((sizeof...(Args) == 1u) && ... && std::is_same_v<std::remove_cv_t<std::remove_reference_t<Args>>, value_type>)) {
return insert_or_do_nothing(std::forward<Args>(args)...); return insert_or_do_nothing(std::forward<Args>(args)...);
} else { } else {
auto &node = packed.first().emplace_back(std::piecewise_construct, std::make_tuple(packed.first().size()), std::forward_as_tuple(std::forward<Args>(args)...)); auto &node = packed.first().emplace_back(std::piecewise_construct, std::make_tuple(packed.first().size()), std::forward_as_tuple(std::forward<Args>(args)...));
@@ -584,7 +560,7 @@ public:
const auto dist = first - cbegin(); const auto dist = first - cbegin();
for(auto from = last - cbegin(); from != dist; --from) { for(auto from = last - cbegin(); from != dist; --from) {
erase(packed.first()[static_cast<size_type>(from) - 1u].second); erase(packed.first()[from - 1u].second);
} }
return (begin() + dist); return (begin() + dist);
@@ -596,7 +572,7 @@ public:
* @return Number of elements removed (either 0 or 1). * @return Number of elements removed (either 0 or 1).
*/ */
size_type erase(const value_type &value) { size_type erase(const value_type &value) {
for(size_type *curr = &sparse.first()[value_to_bucket(value)]; *curr != placeholder_position; curr = &packed.first()[*curr].first) { 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)) { if(packed.second()(packed.first()[*curr].second, value)) {
const auto index = *curr; const auto index = *curr;
*curr = packed.first()[*curr].first; *curr = packed.first()[*curr].first;
@@ -609,22 +585,14 @@ public:
} }
/** /**
* @brief Returns the number of elements matching a value (either 1 or 0). * @brief Exchanges the contents with those of a given container.
* @param key Key value of an element to search for. * @param other Container to exchange the content with.
* @return Number of elements matching the key (either 1 or 0).
*/ */
[[nodiscard]] size_type count(const value_type &key) const { void swap(dense_set &other) {
return find(key) != end(); using std::swap;
} swap(sparse, other.sparse);
swap(packed, other.packed);
/** swap(threshold, other.threshold);
* @brief Returns the number of elements matching a key (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 auto &key) const
requires is_transparent_v<hasher> && is_transparent_v<key_equal> {
return find(key) != end();
} }
/** /**
@@ -644,58 +612,24 @@ public:
/** /**
* @brief Finds an element that compares _equivalent_ to a given 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. * @param value Value of an element to search for.
* @return An iterator to an element with the given value. If no such * @return An iterator to an element with the given value. If no such
* element is found, a past-the-end iterator is returned. * element is found, a past-the-end iterator is returned.
*/ */
[[nodiscard]] iterator find(const auto &value) template<typename Other>
requires is_transparent_v<hasher> && is_transparent_v<key_equal> { [[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)); return constrained_find(value, value_to_bucket(value));
} }
/*! @copydoc find */ /*! @copydoc find */
[[nodiscard]] const_iterator find(const auto &value) const template<typename Other>
requires is_transparent_v<hasher> && is_transparent_v<key_equal> { [[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)); 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.
* @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 auto &value)
requires is_transparent_v<hasher> && is_transparent_v<key_equal> {
const auto it = find(value);
return {it, it + !(it == end())};
}
/*! @copydoc equal_range */
[[nodiscard]] std::pair<const_iterator, const_iterator> equal_range(const auto &value) const
requires is_transparent_v<hasher> && is_transparent_v<key_equal> {
const auto it = find(value);
return {it, it + !(it == cend())};
}
/** /**
* @brief Checks if the container contains an element with a given value. * @brief Checks if the container contains an element with a given value.
* @param value Value of an element to search for. * @param value Value of an element to search for.
@@ -708,11 +642,13 @@ public:
/** /**
* @brief Checks if the container contains an element that compares * @brief Checks if the container contains an element that compares
* _equivalent_ to a given value. * _equivalent_ to a given value.
* @tparam Other Type of an element to search for.
* @param value Value 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. * @return True if there is such an element, false otherwise.
*/ */
[[nodiscard]] bool contains(const auto &value) const template<typename Other>
requires is_transparent_v<hasher> && is_transparent_v<key_equal> { [[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()); return (find(value) != cend());
} }
@@ -722,7 +658,7 @@ public:
* @return An iterator to the beginning of the given bucket. * @return An iterator to the beginning of the given bucket.
*/ */
[[nodiscard]] const_local_iterator cbegin(const size_type index) const { [[nodiscard]] const_local_iterator cbegin(const size_type index) const {
return {packed.first().data(), sparse.first()[index]}; return {packed.first().begin(), sparse.first()[index]};
} }
/** /**
@@ -740,7 +676,7 @@ public:
* @return An iterator to the beginning of the given bucket. * @return An iterator to the beginning of the given bucket.
*/ */
[[nodiscard]] local_iterator begin(const size_type index) { [[nodiscard]] local_iterator begin(const size_type index) {
return {packed.first().data(), sparse.first()[index]}; return {packed.first().begin(), sparse.first()[index]};
} }
/** /**
@@ -749,7 +685,7 @@ public:
* @return An iterator to the end of the given bucket. * @return An iterator to the end of the given bucket.
*/ */
[[nodiscard]] const_local_iterator cend([[maybe_unused]] const size_type index) const { [[nodiscard]] const_local_iterator cend([[maybe_unused]] const size_type index) const {
return {}; return {packed.first().begin(), (std::numeric_limits<size_type>::max)()};
} }
/** /**
@@ -757,7 +693,7 @@ public:
* @param index An index of a bucket to access. * @param index An index of a bucket to access.
* @return An iterator to the end of the given bucket. * @return An iterator to the end of the given bucket.
*/ */
[[nodiscard]] const_local_iterator end(const size_type index) const { [[nodiscard]] const_local_iterator end([[maybe_unused]] const size_type index) const {
return cend(index); return cend(index);
} }
@@ -767,7 +703,7 @@ public:
* @return An iterator to the end of the given bucket. * @return An iterator to the end of the given bucket.
*/ */
[[nodiscard]] local_iterator end([[maybe_unused]] const size_type index) { [[nodiscard]] local_iterator end([[maybe_unused]] const size_type index) {
return {}; return {packed.first().begin(), (std::numeric_limits<size_type>::max)()};
} }
/** /**
@@ -809,7 +745,7 @@ public:
* @return The average number of elements per bucket. * @return The average number of elements per bucket.
*/ */
[[nodiscard]] float load_factor() const { [[nodiscard]] float load_factor() const {
return static_cast<float>(size()) / static_cast<float>(bucket_count()); return size() / static_cast<float>(bucket_count());
} }
/** /**
@@ -833,19 +769,15 @@ public:
/** /**
* @brief Reserves at least the specified number of buckets and regenerates * @brief Reserves at least the specified number of buckets and regenerates
* the hash table. * the hash table.
* @param cnt New number of buckets. * @param count New number of buckets.
*/ */
void rehash(const size_type cnt) { void rehash(const size_type count) {
auto value = cnt > minimum_capacity ? cnt : minimum_capacity; auto value = (std::max)(count, minimum_capacity);
const auto cap = static_cast<size_type>(static_cast<float>(size()) / max_load_factor()); value = (std::max)(value, static_cast<size_type>(size() / max_load_factor()));
value = value > cap ? value : cap;
if(const auto sz = std::bit_ceil(value); sz != bucket_count()) { if(const auto sz = next_power_of_two(value); sz != bucket_count()) {
sparse.first().resize(sz); sparse.first().resize(sz);
std::fill(sparse.first().begin(), sparse.first().end(), (std::numeric_limits<size_type>::max)());
for(auto &&elem: sparse.first()) {
elem = placeholder_position;
}
for(size_type pos{}, last = size(); pos < last; ++pos) { for(size_type pos{}, last = size(); pos < last; ++pos) {
const auto index = value_to_bucket(packed.first()[pos].second); const auto index = value_to_bucket(packed.first()[pos].second);
@@ -857,11 +789,11 @@ public:
/** /**
* @brief Reserves space for at least the specified number of elements and * @brief Reserves space for at least the specified number of elements and
* regenerates the hash table. * regenerates the hash table.
* @param cnt New number of elements. * @param count New number of elements.
*/ */
void reserve(const size_type cnt) { void reserve(const size_type count) {
packed.first().reserve(cnt); packed.first().reserve(count);
rehash(static_cast<size_type>(std::ceil(static_cast<float>(cnt) / max_load_factor()))); rehash(static_cast<size_type>(std::ceil(count / max_load_factor())));
} }
/** /**
@@ -883,7 +815,7 @@ public:
private: private:
compressed_pair<sparse_container_type, hasher> sparse; compressed_pair<sparse_container_type, hasher> sparse;
compressed_pair<packed_container_type, key_equal> packed; compressed_pair<packed_container_type, key_equal> packed;
float threshold{default_threshold}; float threshold;
}; };
} // namespace entt } // namespace entt

View File

@@ -3,8 +3,6 @@
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <utility>
#include <vector>
namespace entt { namespace entt {
@@ -12,27 +10,17 @@ template<
typename Key, typename Key,
typename Type, typename Type,
typename = std::hash<Key>, typename = std::hash<Key>,
typename = std::equal_to<>, typename = std::equal_to<Key>,
typename = std::allocator<std::pair<const Key, Type>>> typename = std::allocator<std::pair<const Key, Type>>>
class dense_map; class dense_map;
template< template<
typename Type, typename Type,
typename = std::hash<Type>, typename = std::hash<Type>,
typename = std::equal_to<>, typename = std::equal_to<Type>,
typename = std::allocator<Type>> typename = std::allocator<Type>>
class dense_set; class dense_set;
template<typename...>
class basic_table;
/**
* @brief Alias declaration for the most common use case.
* @tparam Type Element types.
*/
template<typename... Type>
using table = basic_table<std::vector<Type>...>;
} // namespace entt } // namespace entt
#endif #endif

View File

@@ -1,432 +0,0 @@
#ifndef ENTT_CONTAINER_TABLE_HPP
#define ENTT_CONTAINER_TABLE_HPP
#include <concepts>
#include <cstddef>
#include <iterator>
#include <tuple>
#include <utility>
#include "../config/config.h"
#include "../core/iterator.hpp"
#include "fwd.hpp"
namespace entt {
/*! @cond ENTT_INTERNAL */
namespace internal {
template<typename... It>
class table_iterator {
template<typename...>
friend class table_iterator;
public:
using value_type = decltype(std::forward_as_tuple(*std::declval<It>()...));
using pointer = input_iterator_pointer<value_type>;
using reference = value_type;
using difference_type = std::ptrdiff_t;
using iterator_category = std::input_iterator_tag;
using iterator_concept = std::random_access_iterator_tag;
constexpr table_iterator() noexcept
: it{} {}
constexpr table_iterator(It... from) noexcept
: it{from...} {}
template<typename... Other>
requires (std::constructible_from<It, Other> && ...)
constexpr table_iterator(const table_iterator<Other...> &other) noexcept
: table_iterator{std::get<Other>(other.it)...} {}
constexpr table_iterator &operator++() noexcept {
return (++std::get<It>(it), ...), *this;
}
constexpr table_iterator operator++(int) noexcept {
const table_iterator orig = *this;
return ++(*this), orig;
}
constexpr table_iterator &operator--() noexcept {
return (--std::get<It>(it), ...), *this;
}
constexpr table_iterator operator--(int) noexcept {
const table_iterator orig = *this;
return operator--(), orig;
}
constexpr table_iterator &operator+=(const difference_type value) noexcept {
return ((std::get<It>(it) += value), ...), *this;
}
constexpr table_iterator operator+(const difference_type value) const noexcept {
table_iterator copy = *this;
return (copy += value);
}
constexpr table_iterator &operator-=(const difference_type value) noexcept {
return (*this += -value);
}
constexpr table_iterator operator-(const difference_type value) const noexcept {
return (*this + -value);
}
[[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept {
return std::forward_as_tuple(std::get<It>(it)[value]...);
}
[[nodiscard]] constexpr pointer operator->() const noexcept {
return {operator[](0)};
}
[[nodiscard]] constexpr reference operator*() const noexcept {
return operator[](0);
}
template<typename... Other>
[[nodiscard]] constexpr std::ptrdiff_t operator-(const table_iterator<Other...> &other) const noexcept {
return std::get<0>(it) - std::get<0>(other.it);
}
template<typename... Other>
[[nodiscard]] constexpr bool operator==(const table_iterator<Other...> &other) const noexcept {
return std::get<0>(it) == std::get<0>(other.it);
}
template<typename... Other>
[[nodiscard]] constexpr auto operator<=>(const table_iterator<Other...> &other) const noexcept {
return std::get<0>(it) <=> std::get<0>(other.it);
}
private:
std::tuple<It...> it;
};
} // namespace internal
/*! @endcond */
/**
* @brief Basic table implementation.
*
* Internal data structures arrange elements to maximize performance. There are
* no guarantees that objects are returned in the insertion order when iterate
* a table. Do not make assumption on the order in any case.
*
* @tparam Container Sequence container row types.
*/
template<typename... Container>
class basic_table {
using container_type = std::tuple<Container...>;
public:
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Signed integer type. */
using difference_type = std::ptrdiff_t;
/*! @brief Input iterator type. */
using iterator = internal::table_iterator<typename Container::iterator...>;
/*! @brief Constant input iterator type. */
using const_iterator = internal::table_iterator<typename Container::const_iterator...>;
/*! @brief Reverse iterator type. */
using reverse_iterator = internal::table_iterator<typename Container::reverse_iterator...>;
/*! @brief Constant reverse iterator type. */
using const_reverse_iterator = internal::table_iterator<typename Container::const_reverse_iterator...>;
/*! @brief Default constructor. */
basic_table()
: payload{} {
}
/**
* @brief Copy constructs the underlying containers.
* @param container The containers to copy from.
*/
explicit basic_table(const Container &...container) noexcept
: payload{container...} {
ENTT_ASSERT((((std::get<Container>(payload).size() * sizeof...(Container)) == (std::get<Container>(payload).size() + ...)) && ...), "Unexpected container size");
}
/**
* @brief Move constructs the underlying containers.
* @param container The containers to move from.
*/
explicit basic_table(Container &&...container) noexcept
: payload{std::move(container)...} {
ENTT_ASSERT((((std::get<Container>(payload).size() * sizeof...(Container)) == (std::get<Container>(payload).size() + ...)) && ...), "Unexpected container size");
}
/*! @brief Default copy constructor, deleted on purpose. */
basic_table(const basic_table &) = delete;
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
basic_table(basic_table &&other) noexcept
: payload{std::move(other.payload)} {}
/**
* @brief Constructs the underlying containers using a given allocator.
* @param allocator A valid allocator.
*/
explicit basic_table(const auto &allocator)
: payload{Container{allocator}...} {}
/**
* @brief Copy constructs the underlying containers using a given allocator.
* @tparam Allocator Type of allocator.
* @param container The containers to copy from.
* @param allocator A valid allocator.
*/
template<class Allocator>
basic_table(const Container &...container, const Allocator &allocator) noexcept
: payload{Container{container, allocator}...} {
ENTT_ASSERT((((std::get<Container>(payload).size() * sizeof...(Container)) == (std::get<Container>(payload).size() + ...)) && ...), "Unexpected container size");
}
/**
* @brief Move constructs the underlying containers using a given allocator.
* @tparam Allocator Type of allocator.
* @param container The containers to move from.
* @param allocator A valid allocator.
*/
template<class Allocator>
basic_table(Container &&...container, const Allocator &allocator) noexcept
: payload{Container{std::move(container), allocator}...} {
ENTT_ASSERT((((std::get<Container>(payload).size() * sizeof...(Container)) == (std::get<Container>(payload).size() + ...)) && ...), "Unexpected container size");
}
/**
* @brief Allocator-extended move constructor.
* @tparam Allocator Type of allocator.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
template<class Allocator>
basic_table(basic_table &&other, const Allocator &allocator)
: payload{Container{std::move(std::get<Container>(other.payload)), allocator}...} {}
/*! @brief Default destructor. */
~basic_table() = default;
/**
* @brief Default copy assignment operator, deleted on purpose.
* @return This container.
*/
basic_table &operator=(const basic_table &) = delete;
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This container.
*/
basic_table &operator=(basic_table &&other) noexcept {
swap(other);
return *this;
}
/**
* @brief Exchanges the contents with those of a given table.
* @param other Table to exchange the content with.
*/
void swap(basic_table &other) noexcept {
using std::swap;
swap(payload, other.payload);
}
/**
* @brief Increases the capacity of a table.
*
* 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) {
(std::get<Container>(payload).reserve(cap), ...);
}
/**
* @brief Returns the number of rows that a table has currently allocated
* space for.
* @return Capacity of the table.
*/
[[nodiscard]] size_type capacity() const noexcept {
return std::get<0>(payload).capacity();
}
/*! @brief Requests the removal of unused capacity. */
void shrink_to_fit() {
(std::get<Container>(payload).shrink_to_fit(), ...);
}
/**
* @brief Returns the number of rows in a table.
* @return Number of rows.
*/
[[nodiscard]] size_type size() const noexcept {
return std::get<0>(payload).size();
}
/**
* @brief Checks whether a table is empty.
* @return True if the table is empty, false otherwise.
*/
[[nodiscard]] bool empty() const noexcept {
return std::get<0>(payload).empty();
}
/**
* @brief Returns an iterator to the beginning.
*
* If the table is empty, the returned iterator will be equal to `end()`.
*
* @return An iterator to the first row of the table.
*/
[[nodiscard]] const_iterator cbegin() const noexcept {
return {std::get<Container>(payload).cbegin()...};
}
/*! @copydoc cbegin */
[[nodiscard]] const_iterator begin() const noexcept {
return cbegin();
}
/*! @copydoc begin */
[[nodiscard]] iterator begin() noexcept {
return {std::get<Container>(payload).begin()...};
}
/**
* @brief Returns an iterator to the end.
* @return An iterator to the element following the last row of the table.
*/
[[nodiscard]] const_iterator cend() const noexcept {
return {std::get<Container>(payload).cend()...};
}
/*! @copydoc cend */
[[nodiscard]] const_iterator end() const noexcept {
return cend();
}
/*! @copydoc end */
[[nodiscard]] iterator end() noexcept {
return {std::get<Container>(payload).end()...};
}
/**
* @brief Returns a reverse iterator to the beginning.
*
* If the table is empty, the returned iterator will be equal to `rend()`.
*
* @return An iterator to the first row of the reversed table.
*/
[[nodiscard]] const_reverse_iterator crbegin() const noexcept {
return {std::get<Container>(payload).crbegin()...};
}
/*! @copydoc crbegin */
[[nodiscard]] const_reverse_iterator rbegin() const noexcept {
return crbegin();
}
/*! @copydoc rbegin */
[[nodiscard]] reverse_iterator rbegin() noexcept {
return {std::get<Container>(payload).rbegin()...};
}
/**
* @brief Returns a reverse iterator to the end.
* @return An iterator to the element following the last row of the reversed
* table.
*/
[[nodiscard]] const_reverse_iterator crend() const noexcept {
return {std::get<Container>(payload).crend()...};
}
/*! @copydoc crend */
[[nodiscard]] const_reverse_iterator rend() const noexcept {
return crend();
}
/*! @copydoc rend */
[[nodiscard]] reverse_iterator rend() noexcept {
return {std::get<Container>(payload).rend()...};
}
/**
* @brief Appends a row to the end of a table.
* @tparam Args Types of arguments to use to construct the row data.
* @param args Parameters to use to construct the row data.
* @return A reference to the newly created row data.
*/
template<typename... Args>
std::tuple<typename Container::value_type &...> emplace(Args &&...args) {
if constexpr(sizeof...(Args) == 0u) {
return std::forward_as_tuple(std::get<Container>(payload).emplace_back()...);
} else {
return std::forward_as_tuple(std::get<Container>(payload).emplace_back(std::forward<Args>(args))...);
}
}
/**
* @brief Removes a row from a table.
* @param pos An iterator to the row to remove.
* @return An iterator following the removed row.
*/
iterator erase(const_iterator pos) {
const auto diff = pos - begin();
return {std::get<Container>(payload).erase(std::get<Container>(payload).begin() + diff)...};
}
/**
* @brief Removes a row from a table.
* @param pos Index of the row to remove.
*/
void erase(const size_type pos) {
ENTT_ASSERT(pos < size(), "Index out of bounds");
erase(begin() + static_cast<difference_type>(pos));
}
/**
* @brief Returns the row data at specified location.
* @param pos The row for which to return the data.
* @return The row data at specified location.
*/
[[nodiscard]] std::tuple<const typename Container::value_type &...> operator[](const size_type pos) const {
ENTT_ASSERT(pos < size(), "Index out of bounds");
return std::forward_as_tuple(std::get<Container>(payload)[pos]...);
}
/*! @copydoc operator[] */
[[nodiscard]] std::tuple<typename Container::value_type &...> operator[](const size_type pos) {
ENTT_ASSERT(pos < size(), "Index out of bounds");
return std::forward_as_tuple(std::get<Container>(payload)[pos]...);
}
/*! @brief Clears a table. */
void clear() {
(std::get<Container>(payload).clear(), ...);
}
private:
container_type payload;
};
} // namespace entt
/*! @cond ENTT_INTERNAL */
namespace std {
template<typename... Container, typename Allocator>
struct uses_allocator<entt::basic_table<Container...>, Allocator>
: std::bool_constant<(std::uses_allocator_v<Container, Allocator> && ...)> {};
} // namespace std
/*! @endcond */
#endif

View File

@@ -2,13 +2,11 @@
#define ENTT_CORE_ALGORITHM_HPP #define ENTT_CORE_ALGORITHM_HPP
#include <algorithm> #include <algorithm>
#include <concepts>
#include <functional> #include <functional>
#include <iterator> #include <iterator>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "../stl/functional.hpp" #include "utility.hpp"
#include "../stl/iterator.hpp"
namespace entt { namespace entt {
@@ -26,6 +24,7 @@ struct std_sort {
* *
* Sorts the elements in a range using the given binary comparison function. * Sorts the elements in a range using the given binary comparison function.
* *
* @tparam It Type of random access iterator.
* @tparam Compare Type of comparison function object. * @tparam Compare Type of comparison function object.
* @tparam Args Types of arguments to forward to the sort function. * @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 first An iterator to the first element of the range to sort.
@@ -33,8 +32,8 @@ struct std_sort {
* @param compare A valid comparison function object. * @param compare A valid comparison function object.
* @param args Arguments to forward to the sort function, if any. * @param args Arguments to forward to the sort function, if any.
*/ */
template<typename Compare = std::less<>, typename... Args> template<typename It, typename Compare = std::less<>, typename... Args>
void operator()(stl::random_access_iterator auto first, stl::random_access_iterator auto last, Compare compare = Compare{}, Args &&...args) const { 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)); std::sort(std::forward<Args>(args)..., std::move(first), std::move(last), std::move(compare));
} }
}; };
@@ -46,23 +45,22 @@ struct insertion_sort {
* *
* Sorts the elements in a range using the given binary comparison function. * Sorts the elements in a range using the given binary comparison function.
* *
* @tparam It Type of random access iterator.
* @tparam Compare Type of comparison function object. * @tparam Compare Type of comparison function object.
* @param first An iterator to the first element of the range to sort. * @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 last An iterator past the last element of the range to sort.
* @param compare A valid comparison function object. * @param compare A valid comparison function object.
*/ */
template<typename Compare = std::less<>> template<typename It, typename Compare = std::less<>>
void operator()(stl::random_access_iterator auto first, stl::random_access_iterator auto last, Compare compare = Compare{}) const { void operator()(It first, It last, Compare compare = Compare{}) const {
if(first < last) { if(first < last) {
for(auto it = first + 1; it < last; ++it) { for(auto it = first + 1; it < last; ++it) {
auto value = std::move(*it); auto value = std::move(*it);
auto pre = it; auto pre = it;
// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
for(; pre > first && compare(value, *(pre - 1)); --pre) { for(; pre > first && compare(value, *(pre - 1)); --pre) {
*pre = std::move(*(pre - 1)); *pre = std::move(*(pre - 1));
} }
// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
*pre = std::move(value); *pre = std::move(value);
} }
@@ -76,8 +74,9 @@ struct insertion_sort {
* @tparam N Maximum number of bits to sort. * @tparam N Maximum number of bits to sort.
*/ */
template<std::size_t Bit, std::size_t N> template<std::size_t Bit, std::size_t N>
requires ((N % Bit) == 0) // The maximum number of bits to sort must be a multiple of the number of bits processed per pass
struct radix_sort { 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. * @brief Sorts the elements in a range.
* *
@@ -93,40 +92,34 @@ struct radix_sort {
* @param last An iterator past the last 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. * @param getter A valid _getter_ function object.
*/ */
template<stl::random_access_iterator It, typename Getter = stl::identity> template<typename It, typename Getter = identity>
void operator()(It first, It last, Getter getter = Getter{}) const { void operator()(It first, It last, Getter getter = Getter{}) const {
if(first < last) { if(first < last) {
constexpr auto passes = N / Bit; static constexpr auto mask = (1 << Bit) - 1;
static constexpr auto buckets = 1 << Bit;
static constexpr auto passes = N / Bit;
using value_type = std::iterator_traits<It>::value_type; using value_type = typename std::iterator_traits<It>::value_type;
using difference_type = std::iterator_traits<It>::difference_type; std::vector<value_type> aux(std::distance(first, last));
std::vector<value_type> aux(static_cast<std::size_t>(std::distance(first, last)));
auto part = [getter = std::move(getter)](auto from, auto to, auto out, auto start) { auto part = [getter = std::move(getter)](auto from, auto to, auto out, auto start) {
constexpr auto mask = (1 << Bit) - 1; std::size_t index[buckets]{};
constexpr auto buckets = 1 << Bit;
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays, misc-const-correctness)
std::size_t count[buckets]{}; std::size_t count[buckets]{};
for(auto it = from; it != to; ++it) { for(auto it = from; it != to; ++it) {
++count[(getter(*it) >> start) & mask]; ++count[(getter(*it) >> start) & mask];
} }
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
std::size_t index[buckets]{};
for(std::size_t pos{}, end = buckets - 1u; pos < end; ++pos) { for(std::size_t pos{}, end = buckets - 1u; pos < end; ++pos) {
index[pos + 1u] = index[pos] + count[pos]; index[pos + 1u] = index[pos] + count[pos];
} }
for(auto it = from; it != to; ++it) { for(auto it = from; it != to; ++it) {
const auto pos = index[(getter(*it) >> start) & mask]++; out[index[(getter(*it) >> start) & mask]++] = std::move(*it);
out[static_cast<difference_type>(pos)] = std::move(*it);
} }
}; };
for(std::size_t pass = 0; pass < (passes & ~1u); pass += 2) { for(std::size_t pass = 0; pass < (passes & ~1); pass += 2) {
part(first, last, aux.begin(), pass * Bit); part(first, last, aux.begin(), pass * Bit);
part(aux.begin(), aux.end(), first, (pass + 1) * Bit); part(aux.begin(), aux.end(), first, (pass + 1) * Bit);
} }

View File

@@ -1,192 +1,150 @@
#ifndef ENTT_CORE_ANY_HPP #ifndef ENTT_CORE_ANY_HPP
#define ENTT_CORE_ANY_HPP #define ENTT_CORE_ANY_HPP
#include <concepts>
#include <cstddef> #include <cstddef>
#include <memory> #include <memory>
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
#include "../config/config.h" #include "../config/config.h"
#include "../core/concepts.hpp" #include "../core/utility.hpp"
#include "fwd.hpp" #include "fwd.hpp"
#include "type_info.hpp" #include "type_info.hpp"
#include "type_traits.hpp" #include "type_traits.hpp"
#include "utility.hpp"
namespace entt { namespace entt {
/*! @cond ENTT_INTERNAL */
namespace internal {
enum class any_request : std::uint8_t {
info,
transfer,
assign,
compare,
copy,
move
};
template<std::size_t Len, std::size_t Align>
struct basic_any_storage {
static constexpr bool has_buffer = true;
union {
const void *instance{};
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
alignas(Align) std::byte buffer[Len];
};
};
template<std::size_t Align>
struct basic_any_storage<0u, Align> {
static constexpr bool has_buffer = false;
const void *instance{};
};
template<typename Type, std::size_t Len, std::size_t Align>
// NOLINTNEXTLINE(bugprone-sizeof-expression)
struct in_situ: std::bool_constant<(Len != 0u) && alignof(Type) <= Align && sizeof(Type) <= Len && std::is_nothrow_move_constructible_v<Type>> {};
template<std::size_t Len, std::size_t Align>
struct in_situ<void, Len, Align>: std::false_type {};
} // namespace internal
/*! @endcond */
/** /**
* @brief A SBO friendly, type-safe container for single values of any type. * @brief A SBO friendly, type-safe container for single values of any type.
* @tparam Len Size of the buffer reserved for the small buffer optimization. * @tparam Len Size of the storage reserved for the small buffer optimization.
* @tparam Align Optional alignment requirement. * @tparam Align Optional alignment requirement.
*/ */
template<std::size_t Len, std::size_t Align> template<std::size_t Len, std::size_t Align>
class basic_any: private internal::basic_any_storage<Len, Align> { class basic_any {
using request = internal::any_request; enum class operation : std::uint8_t {
using base_type = internal::basic_any_storage<Len, Align>; copy,
using vtable_type = const void *(const request, const basic_any &, const void *); move,
using deleter_type = void(const basic_any &); transfer,
assign,
destroy,
compare,
get
};
enum class policy : std::uint8_t {
owner,
ref,
cref
};
using storage_type = std::aligned_storage_t<Len + !Len, Align>;
using vtable_type = const void *(const operation, const basic_any &, const void *);
template<typename Type> template<typename Type>
static constexpr bool in_situ_v = internal::in_situ<Type, Len, Align>::value; static constexpr bool in_situ = Len && alignof(Type) <= alignof(storage_type) && sizeof(Type) <= sizeof(storage_type) && std::is_nothrow_move_constructible_v<Type>;
template<cvref_unqualified Type> template<typename Type>
static const void *basic_vtable(const request req, const basic_any &value, const void *other) { static const void *basic_vtable([[maybe_unused]] const operation op, [[maybe_unused]] const basic_any &value, [[maybe_unused]] const void *other) {
switch(const auto *elem = static_cast<const Type *>(value.data()); req) { static_assert(!std::is_same_v<Type, void> && std::is_same_v<std::remove_cv_t<std::remove_reference_t<Type>>, Type>, "Invalid type");
using enum internal::any_request; const Type *element = nullptr;
case info:
return &type_id<Type>(); if constexpr(in_situ<Type>) {
case transfer: 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>) { if constexpr(std::is_move_assignable_v<Type>) {
// NOLINTNEXTLINE(bugprone-casting-through-void) *const_cast<Type *>(element) = std::move(*static_cast<Type *>(const_cast<void *>(other)));
*const_cast<Type *>(elem) = std::move(*static_cast<Type *>(const_cast<void *>(other)));
return other; return other;
} }
[[fallthrough]]; [[fallthrough]];
case assign: case operation::assign:
if constexpr(std::is_copy_assignable_v<Type>) { if constexpr(std::is_copy_assignable_v<Type>) {
*const_cast<Type *>(elem) = *static_cast<const Type *>(other); *const_cast<Type *>(element) = *static_cast<const Type *>(other);
return other; return other;
} }
break; break;
case compare: case operation::destroy:
if constexpr(!std::is_function_v<Type> && !std::is_array_v<Type> && is_equality_comparable_v<Type>) { if constexpr(in_situ<Type>) {
return (*elem == *static_cast<const Type *>(other)) ? other : nullptr; element->~Type();
} else if constexpr(std::is_array_v<Type>) {
delete[] element;
} else { } else {
return (elem == other) ? other : nullptr; delete element;
}
case copy:
if constexpr(std::is_copy_constructible_v<Type>) {
// NOLINTNEXTLINE(bugprone-casting-through-void)
static_cast<basic_any *>(const_cast<void *>(other))->initialize<Type>(*elem);
} }
break; break;
case move: case operation::compare:
ENTT_ASSERT(value.mode == any_policy::embedded, "Unexpected policy"); if constexpr(!std::is_function_v<Type> && !std::is_array_v<Type> && is_equality_comparable_v<Type>) {
if constexpr(in_situ_v<Type>) { return *static_cast<const Type *>(element) == *static_cast<const Type *>(other) ? other : nullptr;
// NOLINTNEXTLINE(bugprone-casting-through-void, bugprone-multi-level-implicit-pointer-conversion) } else {
return ::new(&static_cast<basic_any *>(const_cast<void *>(other))->buffer) Type{std::move(*const_cast<Type *>(elem))}; return (element == other) ? other : nullptr;
} }
case operation::get:
return element;
} }
return nullptr; return nullptr;
} }
template<cvref_unqualified Type>
static void basic_deleter(const basic_any &value) {
ENTT_ASSERT((value.mode == any_policy::dynamic) || ((value.mode == any_policy::embedded) && !std::is_trivially_destructible_v<Type>), "Unexpected policy");
const auto *elem = static_cast<const Type *>(value.data());
if constexpr(in_situ_v<Type>) {
(value.mode == any_policy::embedded) ? elem->~Type() : (delete elem);
} else if constexpr(std::is_array_v<Type>) {
delete[] elem;
} else {
delete elem;
}
}
template<typename Type, typename... Args> template<typename Type, typename... Args>
void initialize([[maybe_unused]] Args &&...args) { void initialize([[maybe_unused]] Args &&...args) {
using plain_type = std::remove_cvref_t<Type>; if constexpr(!std::is_void_v<Type>) {
info = &type_id<std::remove_cv_t<std::remove_reference_t<Type>>>();
vtable = basic_vtable<std::remove_cv_t<std::remove_reference_t<Type>>>;
vtable = basic_vtable<plain_type>; if constexpr(std::is_lvalue_reference_v<Type>) {
underlying_type = type_hash<plain_type>::value(); 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;
if constexpr(std::is_void_v<Type>) { instance = (std::addressof(args), ...);
deleter = nullptr; } else if constexpr(in_situ<Type>) {
mode = any_policy::empty; if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v<Type>) {
this->instance = nullptr; new(&storage) Type{std::forward<Args>(args)...};
} else if constexpr(std::is_lvalue_reference_v<Type>) { } else {
deleter = nullptr; new(&storage) Type(std::forward<Args>(args)...);
mode = std::is_const_v<std::remove_reference_t<Type>> ? any_policy::cref : any_policy::ref; }
static_assert((std::is_lvalue_reference_v<Args> && ...) && (sizeof...(Args) == 1u), "Invalid arguments");
// NOLINTNEXTLINE(bugprone-multi-level-implicit-pointer-conversion)
this->instance = (std::addressof(args), ...);
} else if constexpr(in_situ_v<plain_type>) {
if constexpr(std::is_trivially_destructible_v<plain_type>) {
deleter = nullptr;
} else { } else {
deleter = &basic_deleter<plain_type>; if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v<Type>) {
} instance = new Type{std::forward<Args>(args)...};
} else {
mode = any_policy::embedded; instance = new Type(std::forward<Args>(args)...);
}
if constexpr(std::is_aggregate_v<plain_type> && (sizeof...(Args) != 0u || !std::is_default_constructible_v<plain_type>)) {
::new(&this->buffer) plain_type{std::forward<Args>(args)...};
} else {
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
::new(&this->buffer) plain_type(std::forward<Args>(args)...);
}
} else {
deleter = &basic_deleter<plain_type>;
mode = any_policy::dynamic;
if constexpr(std::is_aggregate_v<plain_type> && (sizeof...(Args) != 0u || !std::is_default_constructible_v<plain_type>)) {
this->instance = new plain_type{std::forward<Args>(args)...};
} else if constexpr(std::is_array_v<plain_type>) {
static_assert(sizeof...(Args) == 0u, "Invalid arguments");
this->instance = new plain_type[std::extent_v<plain_type>]();
} else {
this->instance = new plain_type(std::forward<Args>(args)...);
} }
} }
} }
void invoke_deleter_if_exists() { basic_any(const basic_any &other, const policy pol) ENTT_NOEXCEPT
if(deleter != nullptr) { : instance{other.data()},
deleter(*this); info{other.info},
} vtable{other.vtable},
} mode{pol} {}
public: public:
/*! @brief Size of the internal buffer. */ /*! @brief Size of the internal storage. */
static constexpr auto length = Len; static constexpr auto length = Len;
/*! @brief Alignment requirement. */ /*! @brief Alignment requirement. */
static constexpr auto alignment = Align; static constexpr auto alignment = Align;
/*! @brief Default constructor. */ /*! @brief Default constructor. */
constexpr basic_any() noexcept constexpr basic_any() ENTT_NOEXCEPT
: basic_any{std::in_place_type<void>} {} : instance{},
info{&type_id<void>()},
vtable{},
mode{policy::owner} {}
/** /**
* @brief Constructs a wrapper by directly initializing the new object. * @brief Constructs a wrapper by directly initializing the new object.
@@ -196,37 +154,20 @@ public:
*/ */
template<typename Type, typename... Args> template<typename Type, typename... Args>
explicit basic_any(std::in_place_type_t<Type>, Args &&...args) explicit basic_any(std::in_place_type_t<Type>, Args &&...args)
: base_type{} { : basic_any{} {
initialize<Type>(std::forward<Args>(args)...); initialize<Type>(std::forward<Args>(args)...);
} }
/**
* @brief Constructs a wrapper taking ownership of the passed object.
* @tparam Type Type of object to use to initialize the wrapper.
* @param value A pointer to an object to take ownership of.
*/
template<typename Type>
requires (!std::is_const_v<Type> && !std::is_void_v<Type>)
explicit basic_any(std::in_place_t, Type *value)
: base_type{} {
if(value == nullptr) {
initialize<void>();
} else {
initialize<Type &>(*value);
deleter = &basic_deleter<Type>;
mode = any_policy::dynamic;
}
}
/** /**
* @brief Constructs a wrapper from a given value. * @brief Constructs a wrapper from a given value.
* @tparam Type Type of object to use to initialize the wrapper. * @tparam Type Type of object to use to initialize the wrapper.
* @param value An instance of an object to use to initialize the wrapper. * @param value An instance of an object to use to initialize the wrapper.
*/ */
template<typename Type> template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::decay_t<Type>, basic_any>>>
requires (!std::same_as<std::remove_cvref_t<Type>, basic_any>)
basic_any(Type &&value) basic_any(Type &&value)
: basic_any{std::in_place_type<std::decay_t<Type>>, std::forward<Type>(value)} {} : basic_any{} {
initialize<std::decay_t<Type>>(std::forward<Type>(value));
}
/** /**
* @brief Copy constructor. * @brief Copy constructor.
@@ -234,29 +175,30 @@ public:
*/ */
basic_any(const basic_any &other) basic_any(const basic_any &other)
: basic_any{} { : basic_any{} {
other.vtable(request::copy, other, this); if(other.vtable) {
other.vtable(operation::copy, other, this);
}
} }
/** /**
* @brief Move constructor. * @brief Move constructor.
* @param other The instance to move from. * @param other The instance to move from.
*/ */
basic_any(basic_any &&other) noexcept basic_any(basic_any &&other) ENTT_NOEXCEPT
: base_type{}, : instance{},
info{other.info},
vtable{other.vtable}, vtable{other.vtable},
deleter{other.deleter},
underlying_type{other.underlying_type},
mode{other.mode} { mode{other.mode} {
if(other.mode == any_policy::embedded) { if(other.vtable) {
other.vtable(request::move, other, this); other.vtable(operation::move, other, this);
} else if(other.mode != any_policy::empty) {
this->instance = std::exchange(other.instance, nullptr);
} }
} }
/*! @brief Frees the internal buffer, whatever it means. */ /*! @brief Frees the internal storage, whatever it means. */
~basic_any() { ~basic_any() {
invoke_deleter_if_exists(); if(vtable && owner()) {
vtable(operation::destroy, *this, nullptr);
}
} }
/** /**
@@ -265,14 +207,10 @@ public:
* @return This any object. * @return This any object.
*/ */
basic_any &operator=(const basic_any &other) { basic_any &operator=(const basic_any &other) {
if(this != &other) { reset();
invoke_deleter_if_exists();
if(other) { if(other.vtable) {
other.vtable(request::copy, other, this); other.vtable(operation::copy, other, this);
} else {
initialize<void>();
}
} }
return *this; return *this;
@@ -283,19 +221,13 @@ public:
* @param other The instance to move from. * @param other The instance to move from.
* @return This any object. * @return This any object.
*/ */
basic_any &operator=(basic_any &&other) noexcept { basic_any &operator=(basic_any &&other) ENTT_NOEXCEPT {
if(this != &other) { reset();
invoke_deleter_if_exists();
if(other.mode == any_policy::embedded) {
other.vtable(request::move, other, this);
} else if(other.mode != any_policy::empty) {
this->instance = std::exchange(other.instance, nullptr);
}
if(other.vtable) {
other.vtable(operation::move, other, this);
info = other.info;
vtable = other.vtable; vtable = other.vtable;
deleter = other.deleter;
underlying_type = other.underlying_type;
mode = other.mode; mode = other.mode;
} }
@@ -309,61 +241,26 @@ public:
* @return This any object. * @return This any object.
*/ */
template<typename Type> template<typename Type>
requires (!std::same_as<std::remove_cvref_t<Type>, basic_any>) std::enable_if_t<!std::is_same_v<std::decay_t<Type>, basic_any>, basic_any &>
basic_any &operator=(Type &&value) { operator=(Type &&value) {
emplace<std::decay_t<Type>>(std::forward<Type>(value)); emplace<std::decay_t<Type>>(std::forward<Type>(value));
return *this; return *this;
} }
/** /**
* @brief Returns false if a wrapper is empty, true otherwise. * @brief Returns the object type if any, `type_id<void>()` otherwise.
* @return False if the wrapper is empty, true otherwise. * @return The object type if any, `type_id<void>()` otherwise.
*/ */
[[nodiscard]] bool has_value() const noexcept { [[nodiscard]] const type_info &type() const ENTT_NOEXCEPT {
return (mode != any_policy::empty); return *info;
}
/**
* @brief Returns false if the wrapper does not contain the expected type,
* true otherwise.
* @param req Expected type.
* @return False if the wrapper does not contain the expected type, true
* otherwise.
*/
[[nodiscard]] bool has_value(const type_info &req) const noexcept {
return (underlying_type == req.hash());
}
/**
* @brief Returns false if the wrapper does not contain the expected type,
* true otherwise.
* @tparam Type Expected type.
* @return False if the wrapper does not contain the expected type, true
* otherwise.
*/
template<cvref_unqualified Type>
[[nodiscard]] bool has_value() const noexcept {
return (underlying_type == type_hash<Type>::value());
}
/**
* @brief Returns the object type info if any, `type_id<void>()` otherwise.
* @return The object type info if any, `type_id<void>()` otherwise.
*/
[[nodiscard]] const type_info &info() const noexcept {
return *static_cast<const type_info *>(vtable(request::info, *this, nullptr));
} }
/** /**
* @brief Returns an opaque pointer to the contained instance. * @brief Returns an opaque pointer to the contained instance.
* @return An opaque pointer the contained instance, if any. * @return An opaque pointer the contained instance, if any.
*/ */
[[nodiscard]] const void *data() const noexcept { [[nodiscard]] const void *data() const ENTT_NOEXCEPT {
if constexpr(base_type::has_buffer) { return vtable ? vtable(operation::get, *this, nullptr) : nullptr;
return (mode == any_policy::embedded) ? &this->buffer : this->instance;
} else {
return this->instance;
}
} }
/** /**
@@ -371,26 +268,16 @@ public:
* @param req Expected type. * @param req Expected type.
* @return An opaque pointer the contained instance, if any. * @return An opaque pointer the contained instance, if any.
*/ */
[[nodiscard]] const void *data(const type_info &req) const noexcept { [[nodiscard]] const void *data(const type_info &req) const ENTT_NOEXCEPT {
return has_value(req) ? data() : nullptr; return *info == req ? data() : nullptr;
}
/**
* @brief Returns an opaque pointer to the contained instance.
* @tparam Type Expected type.
* @return An opaque pointer the contained instance, if any.
*/
template<typename Type>
[[nodiscard]] const Type *data() const noexcept {
return has_value<std::remove_const_t<Type>>() ? static_cast<const Type *>(data()) : nullptr;
} }
/** /**
* @brief Returns an opaque pointer to the contained instance. * @brief Returns an opaque pointer to the contained instance.
* @return An opaque pointer the contained instance, if any. * @return An opaque pointer the contained instance, if any.
*/ */
[[nodiscard]] void *data() noexcept { [[nodiscard]] void *data() ENTT_NOEXCEPT {
return (mode == any_policy::cref) ? nullptr : const_cast<void *>(std::as_const(*this).data()); return (!vtable || mode == policy::cref) ? nullptr : const_cast<void *>(vtable(operation::get, *this, nullptr));
} }
/** /**
@@ -398,22 +285,8 @@ public:
* @param req Expected type. * @param req Expected type.
* @return An opaque pointer the contained instance, if any. * @return An opaque pointer the contained instance, if any.
*/ */
[[nodiscard]] void *data(const type_info &req) noexcept { [[nodiscard]] void *data(const type_info &req) ENTT_NOEXCEPT {
return (mode == any_policy::cref) ? nullptr : const_cast<void *>(std::as_const(*this).data(req)); return *info == req ? data() : nullptr;
}
/**
* @brief Returns an opaque pointer to the contained instance.
* @tparam Type Expected type.
* @return An opaque pointer the contained instance, if any.
*/
template<typename Type>
[[nodiscard]] Type *data() noexcept {
if constexpr(std::is_const_v<Type>) {
return std::as_const(*this).template data<std::remove_const_t<Type>>();
} else {
return (mode == any_policy::cref) ? nullptr : const_cast<Type *>(std::as_const(*this).template data<std::remove_const_t<Type>>());
}
} }
/** /**
@@ -424,7 +297,7 @@ public:
*/ */
template<typename Type, typename... Args> template<typename Type, typename... Args>
void emplace(Args &&...args) { void emplace(Args &&...args) {
invoke_deleter_if_exists(); reset();
initialize<Type>(std::forward<Args>(args)...); initialize<Type>(std::forward<Args>(args)...);
} }
@@ -433,19 +306,22 @@ public:
* @param other The value to assign to the contained object. * @param other The value to assign to the contained object.
* @return True in case of success, false otherwise. * @return True in case of success, false otherwise.
*/ */
bool assign(const basic_any &other) { bool assign(const any &other) {
if(other && (mode != any_policy::cref) && (underlying_type == other.underlying_type)) { if(vtable && mode != policy::cref && *info == *other.info) {
return (vtable(request::assign, *this, other.data()) != nullptr); return (vtable(operation::assign, *this, other.data()) != nullptr);
} }
return false; return false;
} }
/*! @copydoc assign */ /*! @copydoc assign */
// NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved) bool assign(any &&other) {
bool assign(basic_any &&other) { if(vtable && mode != policy::cref && *info == *other.info) {
if(other && (mode != any_policy::cref) && (underlying_type == other.underlying_type)) { if(auto *val = other.data(); val) {
return (other.mode == any_policy::cref) ? (vtable(request::assign, *this, std::as_const(other).data()) != nullptr) : (vtable(request::transfer, *this, other.data()) != nullptr); return (vtable(operation::transfer, *this, val) != nullptr);
} else {
return (vtable(operation::assign, *this, std::as_const(other).data()) != nullptr);
}
} }
return false; return false;
@@ -453,16 +329,21 @@ public:
/*! @brief Destroys contained object */ /*! @brief Destroys contained object */
void reset() { void reset() {
invoke_deleter_if_exists(); if(vtable && owner()) {
initialize<void>(); vtable(operation::destroy, *this, nullptr);
}
info = &type_id<void>();
vtable = nullptr;
mode = policy::owner;
} }
/** /**
* @brief Returns false if a wrapper is empty, true otherwise. * @brief Returns false if a wrapper is empty, true otherwise.
* @return False if the wrapper is empty, true otherwise. * @return False if the wrapper is empty, true otherwise.
*/ */
[[nodiscard]] explicit operator bool() const noexcept { [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT {
return has_value(); return vtable != nullptr;
} }
/** /**
@@ -470,78 +351,68 @@ public:
* @param other Wrapper with which to compare. * @param other Wrapper with which to compare.
* @return False if the two objects differ in their content, true otherwise. * @return False if the two objects differ in their content, true otherwise.
*/ */
[[nodiscard]] bool operator==(const basic_any &other) const noexcept { bool operator==(const basic_any &other) const ENTT_NOEXCEPT {
if(other && (underlying_type == other.underlying_type)) { if(vtable && *info == *other.info) {
return (vtable(request::compare, *this, other.data()) != nullptr); return (vtable(operation::compare, *this, other.data()) != nullptr);
} }
return (!*this && !other); return (!vtable && !other.vtable);
} }
/** /**
* @brief Aliasing constructor. * @brief Aliasing constructor.
* @return A wrapper that shares a reference to an unmanaged object. * @return A wrapper that shares a reference to an unmanaged object.
*/ */
[[nodiscard]] basic_any as_ref() noexcept { [[nodiscard]] basic_any as_ref() ENTT_NOEXCEPT {
basic_any other = std::as_const(*this).as_ref(); return basic_any{*this, (mode == policy::cref ? policy::cref : policy::ref)};
switch(mode) {
using enum any_policy;
case cref:
case empty:
other.mode = mode;
break;
default:
other.mode = any_policy::ref;
break;
}
return other;
} }
/*! @copydoc as_ref */ /*! @copydoc as_ref */
[[nodiscard]] basic_any as_ref() const noexcept { [[nodiscard]] basic_any as_ref() const ENTT_NOEXCEPT {
basic_any other{}; return basic_any{*this, policy::cref};
other.instance = data();
other.vtable = vtable;
other.underlying_type = underlying_type;
other.mode = any_policy::cref;
return other;
} }
/** /**
* @brief Returns true if a wrapper owns its object, false otherwise. * @brief Returns true if a wrapper owns its object, false otherwise.
* @return True if the wrapper owns its object, false otherwise. * @return True if the wrapper owns its object, false otherwise.
*/ */
[[nodiscard]] bool owner() const noexcept { [[nodiscard]] bool owner() const ENTT_NOEXCEPT {
return (mode == any_policy::dynamic || mode == any_policy::embedded); return (mode == policy::owner);
}
/**
* @brief Returns the current mode of an any object.
* @return The current mode of the any object.
*/
[[nodiscard]] any_policy policy() const noexcept {
return mode;
} }
private: private:
vtable_type *vtable{}; union {
deleter_type *deleter{}; const void *instance;
id_type underlying_type{}; storage_type storage;
any_policy mode{}; };
const type_info *info;
vtable_type *vtable;
policy mode;
}; };
/**
* @brief Checks if two wrappers differ in their content.
* @tparam Len Size of the storage reserved for the small buffer optimization.
* @tparam Align Alignment requirement.
* @param lhs A wrapper, either empty or not.
* @param rhs A wrapper, either empty or not.
* @return True if the two wrappers differ in their content, false otherwise.
*/
template<std::size_t Len, std::size_t Align>
[[nodiscard]] inline bool operator!=(const basic_any<Len, Align> &lhs, const basic_any<Len, Align> &rhs) ENTT_NOEXCEPT {
return !(lhs == rhs);
}
/** /**
* @brief Performs type-safe access to the contained object. * @brief Performs type-safe access to the contained object.
* @tparam Type Type to which conversion is required. * @tparam Type Type to which conversion is required.
* @tparam Len Size of the buffer reserved for the small buffer optimization. * @tparam Len Size of the storage reserved for the small buffer optimization.
* @tparam Align Alignment requirement. * @tparam Align Alignment requirement.
* @param data Target any object. * @param data Target any object.
* @return The element converted to the requested type. * @return The element converted to the requested type.
*/ */
template<typename Type, std::size_t Len, std::size_t Align> template<typename Type, std::size_t Len, std::size_t Align>
[[nodiscard]] std::remove_const_t<Type> any_cast(const basic_any<Len, Align> &data) noexcept { Type any_cast(const basic_any<Len, Align> &data) ENTT_NOEXCEPT {
const auto *const instance = any_cast<std::remove_reference_t<Type>>(&data); const auto *const instance = any_cast<std::remove_reference_t<Type>>(&data);
ENTT_ASSERT(instance, "Invalid instance"); ENTT_ASSERT(instance, "Invalid instance");
return static_cast<Type>(*instance); return static_cast<Type>(*instance);
@@ -549,7 +420,7 @@ template<typename Type, std::size_t Len, std::size_t Align>
/*! @copydoc any_cast */ /*! @copydoc any_cast */
template<typename Type, std::size_t Len, std::size_t Align> template<typename Type, std::size_t Len, std::size_t Align>
[[nodiscard]] std::remove_const_t<Type> any_cast(basic_any<Len, Align> &data) noexcept { Type any_cast(basic_any<Len, Align> &data) ENTT_NOEXCEPT {
// forces const on non-reference types to make them work also with wrappers for const references // 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); auto *const instance = any_cast<std::remove_reference_t<const Type>>(&data);
ENTT_ASSERT(instance, "Invalid instance"); ENTT_ASSERT(instance, "Invalid instance");
@@ -558,14 +429,13 @@ template<typename Type, std::size_t Len, std::size_t Align>
/*! @copydoc any_cast */ /*! @copydoc any_cast */
template<typename Type, std::size_t Len, std::size_t Align> template<typename Type, std::size_t Len, std::size_t Align>
// NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved) Type any_cast(basic_any<Len, Align> &&data) ENTT_NOEXCEPT {
[[nodiscard]] std::remove_const_t<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 constexpr(std::is_copy_constructible_v<std::remove_cvref_t<Type>>) {
if(auto *const instance = any_cast<std::remove_reference_t<Type>>(&data); instance) { if(auto *const instance = any_cast<std::remove_reference_t<Type>>(&data); instance) {
return static_cast<Type>(std::move(*instance)); return static_cast<Type>(std::move(*instance));
} else {
return any_cast<Type>(data);
} }
return any_cast<Type>(data);
} else { } else {
auto *const instance = any_cast<std::remove_reference_t<Type>>(&data); auto *const instance = any_cast<std::remove_reference_t<Type>>(&data);
ENTT_ASSERT(instance, "Invalid instance"); ENTT_ASSERT(instance, "Invalid instance");
@@ -575,46 +445,44 @@ template<typename Type, std::size_t Len, std::size_t Align>
/*! @copydoc any_cast */ /*! @copydoc any_cast */
template<typename Type, std::size_t Len, std::size_t Align> template<typename Type, std::size_t Len, std::size_t Align>
[[nodiscard]] const Type *any_cast(const basic_any<Len, Align> *data) noexcept { const Type *any_cast(const basic_any<Len, Align> *data) ENTT_NOEXCEPT {
return data->template data<std::remove_const_t<Type>>(); const auto &info = type_id<std::remove_cv_t<std::remove_reference_t<Type>>>();
return static_cast<const Type *>(data->data(info));
} }
/*! @copydoc any_cast */ /*! @copydoc any_cast */
template<typename Type, std::size_t Len, std::size_t Align> template<typename Type, std::size_t Len, std::size_t Align>
[[nodiscard]] Type *any_cast(basic_any<Len, Align> *data) noexcept { Type *any_cast(basic_any<Len, Align> *data) ENTT_NOEXCEPT {
if constexpr(std::is_const_v<Type>) { const auto &info = type_id<std::remove_cv_t<std::remove_reference_t<Type>>>();
// last attempt to make wrappers for const references return their values // last attempt to make wrappers for const references return their values
return any_cast<Type>(&std::as_const(*data)); return static_cast<Type *>(static_cast<constness_as_t<basic_any<Len, Align>, Type> *>(data)->data(info));
} else {
return data->template data<Type>();
}
} }
/** /**
* @brief Constructs a wrapper from a given type, passing it all arguments. * @brief Constructs a wrapper from a given type, passing it all arguments.
* @tparam Type Type of object to use to initialize the wrapper. * @tparam Type Type of object to use to initialize the wrapper.
* @tparam Len Size of the buffer reserved for the small buffer optimization. * @tparam Len Size of the storage reserved for the small buffer optimization.
* @tparam Align Optional alignment requirement. * @tparam Align Optional alignment requirement.
* @tparam Args Types of arguments to use to construct the new instance. * @tparam Args Types of arguments to use to construct the new instance.
* @param args Parameters to use to construct the instance. * @param args Parameters to use to construct the instance.
* @return A properly initialized wrapper for an object of the given type. * @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> template<typename Type, std::size_t Len = basic_any<>::length, std::size_t Align = basic_any<Len>::alignment, typename... Args>
[[nodiscard]] basic_any<Len, Align> make_any(Args &&...args) { basic_any<Len, Align> make_any(Args &&...args) {
return basic_any<Len, Align>{std::in_place_type<Type>, std::forward<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. * @brief Forwards its argument and avoids copies for lvalue references.
* @tparam Len Size of the buffer reserved for the small buffer optimization. * @tparam Len Size of the storage reserved for the small buffer optimization.
* @tparam Align Optional alignment requirement. * @tparam Align Optional alignment requirement.
* @tparam Type Type of argument to use to construct the new instance. * @tparam Type Type of argument to use to construct the new instance.
* @param value Parameter to use to construct the instance. * @param value Parameter to use to construct the instance.
* @return A properly initialized and not necessarily owning wrapper. * @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> template<std::size_t Len = basic_any<>::length, std::size_t Align = basic_any<Len>::alignment, typename Type>
[[nodiscard]] basic_any<Len, Align> forward_as_any(Type &&value) { basic_any<Len, Align> forward_as_any(Type &&value) {
return basic_any<Len, Align>{std::in_place_type<Type &&>, std::forward<Type>(value)}; return basic_any<Len, Align>{std::in_place_type<std::conditional_t<std::is_rvalue_reference_v<Type>, std::decay_t<Type>, Type>>, std::forward<Type>(value)};
} }
} // namespace entt } // namespace entt

30
src/entt/core/attribute.h Normal file
View 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

View File

@@ -1,26 +0,0 @@
#ifndef ENTT_CORE_BIT_HPP
#define ENTT_CORE_BIT_HPP
#include <bit>
#include <concepts>
#include <cstddef>
#include "../config/config.h"
namespace entt {
/**
* @brief Fast module utility function (powers of two only).
* @tparam Type Unsigned integer type.
* @param value A value of unsigned integer type.
* @param mod _Modulus_, it must be a power of two.
* @return The common remainder.
*/
template<std::unsigned_integral Type>
[[nodiscard]] constexpr Type fast_mod(const Type value, const std::size_t mod) noexcept {
ENTT_ASSERT_CONSTEXPR(std::has_single_bit(mod), "Value must be a power of two");
return static_cast<Type>(value & (mod - 1u));
}
} // namespace entt
#endif

View File

@@ -1,80 +1,84 @@
#ifndef ENTT_CORE_COMPRESSED_PAIR_HPP #ifndef ENTT_CORE_COMPRESSED_PAIR_HPP
#define ENTT_CORE_COMPRESSED_PAIR_HPP #define ENTT_CORE_COMPRESSED_PAIR_HPP
#include <concepts>
#include <cstddef> #include <cstddef>
#include <tuple> #include <tuple>
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
#include "fwd.hpp" #include "../config/config.h"
#include "type_traits.hpp" #include "type_traits.hpp"
namespace entt { namespace entt {
/*! @cond ENTT_INTERNAL */ /**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
namespace internal { namespace internal {
template<typename Type, std::size_t> template<typename Type, std::size_t, typename = void>
struct compressed_pair_element { struct compressed_pair_element {
using reference = Type &; using reference = Type &;
using const_reference = const Type &; using const_reference = const Type &;
// NOLINTNEXTLINE(modernize-use-equals-default) 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>) compressed_pair_element()
requires std::default_initializable<Type> {} : value{} {}
template<typename Arg> template<typename Args, typename = std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_reference_t<Args>>, compressed_pair_element>>>
constexpr compressed_pair_element(Arg &&arg) noexcept(std::is_nothrow_constructible_v<Type, Arg>) compressed_pair_element(Args &&args)
requires (!std::same_as<std::remove_cvref_t<Arg>, compressed_pair_element>) : value{std::forward<Args>(args)} {}
: value{std::forward<Arg>(arg)} {}
template<typename... Args, std::size_t... Index> 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...>) compressed_pair_element(std::tuple<Args...> args, std::index_sequence<Index...>)
: value{std::forward<Args>(std::get<Index>(args))...} {} : value{std::forward<Args>(std::get<Index>(args))...} {}
[[nodiscard]] constexpr reference get() noexcept { [[nodiscard]] reference get() ENTT_NOEXCEPT {
return value; return value;
} }
[[nodiscard]] constexpr const_reference get() const noexcept { [[nodiscard]] const_reference get() const ENTT_NOEXCEPT {
return value; return value;
} }
private: private:
Type value{}; Type value;
}; };
template<typename Type, std::size_t Tag> template<typename Type, std::size_t Tag>
requires is_ebco_eligible_v<Type> struct compressed_pair_element<Type, Tag, std::enable_if_t<is_ebco_eligible_v<Type>>>: Type {
struct compressed_pair_element<Type, Tag>: Type {
using reference = Type &; using reference = Type &;
using const_reference = const Type &; using const_reference = const Type &;
using base_type = Type; using base_type = Type;
constexpr compressed_pair_element() noexcept(std::is_nothrow_default_constructible_v<base_type>) template<bool Dummy = true, typename = std::enable_if_t<Dummy && std::is_default_constructible_v<base_type>>>
requires std::default_initializable<Type> compressed_pair_element()
: base_type{} {} : base_type{} {}
template<typename Arg> template<typename Args, typename = std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_reference_t<Args>>, compressed_pair_element>>>
constexpr compressed_pair_element(Arg &&arg) noexcept(std::is_nothrow_constructible_v<base_type, Arg>) compressed_pair_element(Args &&args)
requires (!std::same_as<std::remove_cvref_t<Arg>, compressed_pair_element>) : base_type{std::forward<Args>(args)} {}
: base_type{std::forward<Arg>(arg)} {}
template<typename... Args, std::size_t... Index> 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...>) compressed_pair_element(std::tuple<Args...> args, std::index_sequence<Index...>)
: base_type{std::forward<Args>(std::get<Index>(args))...} {} : base_type{std::forward<Args>(std::get<Index>(args))...} {}
[[nodiscard]] constexpr reference get() noexcept { [[nodiscard]] reference get() ENTT_NOEXCEPT {
return *this; return *this;
} }
[[nodiscard]] constexpr const_reference get() const noexcept { [[nodiscard]] const_reference get() const ENTT_NOEXCEPT {
return *this; return *this;
} }
}; };
} // namespace internal } // namespace internal
/*! @endcond */
/**
* Internal details not to be documented.
* @endcond
*/
/** /**
* @brief A compressed pair. * @brief A compressed pair.
@@ -103,9 +107,11 @@ public:
* *
* This constructor is only available when the types that the pair stores * This constructor is only available when the types that the pair stores
* are both at least default constructible. * are both at least default constructible.
*
* @tparam Dummy Dummy template parameter used for internal purposes.
*/ */
constexpr compressed_pair() noexcept(std::is_nothrow_default_constructible_v<first_base> && std::is_nothrow_default_constructible_v<second_base>) template<bool Dummy = true, typename = std::enable_if_t<Dummy && std::is_default_constructible_v<first_type> && std::is_default_constructible_v<second_type>>>
requires std::default_initializable<first_type> && std::default_initializable<second_type> constexpr compressed_pair()
: first_base{}, : first_base{},
second_base{} {} second_base{} {}
@@ -119,7 +125,7 @@ public:
* @brief Move constructor. * @brief Move constructor.
* @param other The instance to move from. * @param other The instance to move from.
*/ */
constexpr compressed_pair(compressed_pair &&other) noexcept = default; constexpr compressed_pair(compressed_pair &&other) = default;
/** /**
* @brief Constructs a pair from its values. * @brief Constructs a pair from its values.
@@ -129,7 +135,7 @@ public:
* @param other Value to use to initialize the second element. * @param other Value to use to initialize the second element.
*/ */
template<typename Arg, typename Other> 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>) constexpr compressed_pair(Arg &&arg, Other &&other)
: first_base{std::forward<Arg>(arg)}, : first_base{std::forward<Arg>(arg)},
second_base{std::forward<Other>(other)} {} second_base{std::forward<Other>(other)} {}
@@ -141,13 +147,10 @@ public:
* @param other Arguments to use to initialize the second element. * @param other Arguments to use to initialize the second element.
*/ */
template<typename... Args, typename... Other> 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...>) constexpr compressed_pair(std::piecewise_construct_t, std::tuple<Args...> args, std::tuple<Other...> other)
: first_base{std::move(args), std::index_sequence_for<Args...>{}}, : first_base{std::move(args), std::index_sequence_for<Args...>{}},
second_base{std::move(other), std::index_sequence_for<Other...>{}} {} second_base{std::move(other), std::index_sequence_for<Other...>{}} {}
/*! @brief Default destructor. */
~compressed_pair() = default;
/** /**
* @brief Copy assignment operator. * @brief Copy assignment operator.
* @param other The instance to copy from. * @param other The instance to copy from.
@@ -160,18 +163,18 @@ public:
* @param other The instance to move from. * @param other The instance to move from.
* @return This compressed pair object. * @return This compressed pair object.
*/ */
constexpr compressed_pair &operator=(compressed_pair &&other) noexcept = default; constexpr compressed_pair &operator=(compressed_pair &&other) = default;
/** /**
* @brief Returns the first element that a pair stores. * @brief Returns the first element that a pair stores.
* @return The first element that a pair stores. * @return The first element that a pair stores.
*/ */
[[nodiscard]] constexpr first_type &first() noexcept { [[nodiscard]] first_type &first() ENTT_NOEXCEPT {
return static_cast<first_base &>(*this).get(); return static_cast<first_base &>(*this).get();
} }
/*! @copydoc first */ /*! @copydoc first */
[[nodiscard]] constexpr const first_type &first() const noexcept { [[nodiscard]] const first_type &first() const ENTT_NOEXCEPT {
return static_cast<const first_base &>(*this).get(); return static_cast<const first_base &>(*this).get();
} }
@@ -179,12 +182,12 @@ public:
* @brief Returns the second element that a pair stores. * @brief Returns the second element that a pair stores.
* @return The second element that a pair stores. * @return The second element that a pair stores.
*/ */
[[nodiscard]] constexpr second_type &second() noexcept { [[nodiscard]] second_type &second() ENTT_NOEXCEPT {
return static_cast<second_base &>(*this).get(); return static_cast<second_base &>(*this).get();
} }
/*! @copydoc second */ /*! @copydoc second */
[[nodiscard]] constexpr const second_type &second() const noexcept { [[nodiscard]] const second_type &second() const ENTT_NOEXCEPT {
return static_cast<const second_base &>(*this).get(); return static_cast<const second_base &>(*this).get();
} }
@@ -192,7 +195,7 @@ public:
* @brief Swaps two compressed pair objects. * @brief Swaps two compressed pair objects.
* @param other The compressed pair to swap with. * @param other The compressed pair to swap with.
*/ */
constexpr void swap(compressed_pair &other) noexcept { void swap(compressed_pair &other) {
using std::swap; using std::swap;
swap(first(), other.first()); swap(first(), other.first());
swap(second(), other.second()); swap(second(), other.second());
@@ -205,22 +208,22 @@ public:
* reference to the second element if `Index` is 1. * reference to the second element if `Index` is 1.
*/ */
template<std::size_t Index> template<std::size_t Index>
requires (Index <= 1u) decltype(auto) get() ENTT_NOEXCEPT {
[[nodiscard]] constexpr decltype(auto) get() noexcept {
if constexpr(Index == 0u) { if constexpr(Index == 0u) {
return first(); return first();
} else { } else {
static_assert(Index == 1u, "Index out of bounds");
return second(); return second();
} }
} }
/*! @copydoc get */ /*! @copydoc get */
template<std::size_t Index> template<std::size_t Index>
requires (Index <= 1u) decltype(auto) get() const ENTT_NOEXCEPT {
[[nodiscard]] constexpr decltype(auto) get() const noexcept {
if constexpr(Index == 0u) { if constexpr(Index == 0u) {
return first(); return first();
} else { } else {
static_assert(Index == 1u, "Index out of bounds");
return second(); return second();
} }
} }
@@ -242,12 +245,14 @@ compressed_pair(Type &&, Other &&) -> compressed_pair<std::decay_t<Type>, std::d
* @param rhs A valid compressed pair object. * @param rhs A valid compressed pair object.
*/ */
template<typename First, typename Second> template<typename First, typename Second>
constexpr void swap(compressed_pair<First, Second> &lhs, compressed_pair<First, Second> &rhs) noexcept { inline void swap(compressed_pair<First, Second> &lhs, compressed_pair<First, Second> &rhs) {
lhs.swap(rhs); lhs.swap(rhs);
} }
} // namespace entt } // namespace entt
// disable structured binding support for clang 6, it messes when specializing tuple_size
#if !defined __clang_major__ || __clang_major__ > 6
namespace std { namespace std {
/** /**
@@ -265,9 +270,11 @@ struct tuple_size<entt::compressed_pair<First, Second>>: integral_constant<size_
* @tparam Second The type of the second 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> template<size_t Index, typename First, typename Second>
requires (Index <= 1u) struct tuple_element<Index, entt::compressed_pair<First, Second>>: conditional<Index == 0u, First, 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 } // namespace std
#endif
#endif #endif

View File

@@ -1,17 +0,0 @@
#ifndef ENTT_CORE_CONCEPTS_HPP
#define ENTT_CORE_CONCEPTS_HPP
#include <type_traits>
namespace entt {
/**
* @brief Specifies that a type is not a cv-qualified reference.
* @tparam Type Type to check.
*/
template<typename Type>
concept cvref_unqualified = std::is_same_v<std::remove_cvref_t<Type>, Type>;
} // namespace entt
#endif

View File

@@ -1,8 +1,8 @@
#ifndef ENTT_CORE_ENUM_HPP #ifndef ENTT_CORE_ENUM_HPP
#define ENTT_CORE_ENUM_HPP #define ENTT_CORE_ENUM_HPP
#include <concepts>
#include <type_traits> #include <type_traits>
#include "../config/config.h"
namespace entt { namespace entt {
@@ -10,16 +10,12 @@ namespace entt {
* @brief Enable bitmask support for enum classes. * @brief Enable bitmask support for enum classes.
* @tparam Type The enum type for which to enable bitmask support. * @tparam Type The enum type for which to enable bitmask support.
*/ */
template<typename Type> template<typename Type, typename = void>
struct enum_as_bitmask: std::false_type {}; struct enum_as_bitmask: std::false_type {};
/*! @copydoc enum_as_bitmask */ /*! @copydoc enum_as_bitmask */
template<typename Type> template<typename Type>
requires requires { struct enum_as_bitmask<Type, std::void_t<decltype(Type::_entt_enum_as_bitmask)>>: std::is_enum<Type> {};
requires std::is_enum_v<Type>;
{ Type::_entt_enum_as_bitmask } -> std::same_as<Type>;
}
struct enum_as_bitmask<Type>: std::true_type {};
/** /**
* @brief Helper variable template. * @brief Helper variable template.
@@ -28,14 +24,6 @@ struct enum_as_bitmask<Type>: std::true_type {};
template<typename Type> template<typename Type>
inline constexpr bool enum_as_bitmask_v = enum_as_bitmask<Type>::value; inline constexpr bool enum_as_bitmask_v = enum_as_bitmask<Type>::value;
/**
* @brief Specifies that an enum class supports bitmask operations.
* @tparam Type Enum class type.
*/
template<typename Type>
// check again that it is an enum to deal with incorrect specializations
concept enum_bitmask = std::is_enum_v<Type> && enum_as_bitmask_v<Type>;
} // namespace entt } // namespace entt
/** /**
@@ -46,20 +34,23 @@ concept enum_bitmask = std::is_enum_v<Type> && enum_as_bitmask_v<Type>;
* @return The result of invoking the operator on the underlying types of the * @return The result of invoking the operator on the underlying types of the
* two values provided. * two values provided.
*/ */
template<entt::enum_bitmask Type> template<typename Type>
[[nodiscard]] constexpr Type operator|(const Type lhs, const Type rhs) noexcept { [[nodiscard]] constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type>
operator|(const Type lhs, const Type rhs) ENTT_NOEXCEPT {
return static_cast<Type>(static_cast<std::underlying_type_t<Type>>(lhs) | static_cast<std::underlying_type_t<Type>>(rhs)); return static_cast<Type>(static_cast<std::underlying_type_t<Type>>(lhs) | static_cast<std::underlying_type_t<Type>>(rhs));
} }
/*! @copydoc operator| */ /*! @copydoc operator| */
template<entt::enum_bitmask Type> template<typename Type>
[[nodiscard]] constexpr Type operator&(const Type lhs, const Type rhs) noexcept { [[nodiscard]] constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type>
operator&(const Type lhs, const Type rhs) ENTT_NOEXCEPT {
return static_cast<Type>(static_cast<std::underlying_type_t<Type>>(lhs) & static_cast<std::underlying_type_t<Type>>(rhs)); return static_cast<Type>(static_cast<std::underlying_type_t<Type>>(lhs) & static_cast<std::underlying_type_t<Type>>(rhs));
} }
/*! @copydoc operator| */ /*! @copydoc operator| */
template<entt::enum_bitmask Type> template<typename Type>
[[nodiscard]] constexpr Type operator^(const Type lhs, const Type rhs) noexcept { [[nodiscard]] constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type>
operator^(const Type lhs, const Type rhs) ENTT_NOEXCEPT {
return static_cast<Type>(static_cast<std::underlying_type_t<Type>>(lhs) ^ static_cast<std::underlying_type_t<Type>>(rhs)); return static_cast<Type>(static_cast<std::underlying_type_t<Type>>(lhs) ^ static_cast<std::underlying_type_t<Type>>(rhs));
} }
@@ -70,32 +61,37 @@ template<entt::enum_bitmask Type>
* @return The result of invoking the operator on the underlying types of the * @return The result of invoking the operator on the underlying types of the
* value provided. * value provided.
*/ */
template<entt::enum_bitmask Type> template<typename Type>
[[nodiscard]] constexpr Type operator~(const Type value) noexcept { [[nodiscard]] constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type>
operator~(const Type value) ENTT_NOEXCEPT {
return static_cast<Type>(~static_cast<std::underlying_type_t<Type>>(value)); return static_cast<Type>(~static_cast<std::underlying_type_t<Type>>(value));
} }
/*! @copydoc operator~ */ /*! @copydoc operator~ */
template<entt::enum_bitmask Type> template<typename Type>
[[nodiscard]] constexpr bool operator!(const Type value) noexcept { [[nodiscard]] constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, bool>
operator!(const Type value) ENTT_NOEXCEPT {
return !static_cast<std::underlying_type_t<Type>>(value); return !static_cast<std::underlying_type_t<Type>>(value);
} }
/*! @copydoc operator| */ /*! @copydoc operator| */
template<entt::enum_bitmask Type> template<typename Type>
constexpr Type &operator|=(Type &lhs, const Type rhs) noexcept { constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type &>
operator|=(Type &lhs, const Type rhs) ENTT_NOEXCEPT {
return (lhs = (lhs | rhs)); return (lhs = (lhs | rhs));
} }
/*! @copydoc operator| */ /*! @copydoc operator| */
template<entt::enum_bitmask Type> template<typename Type>
constexpr Type &operator&=(Type &lhs, const Type rhs) noexcept { constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type &>
operator&=(Type &lhs, const Type rhs) ENTT_NOEXCEPT {
return (lhs = (lhs & rhs)); return (lhs = (lhs & rhs));
} }
/*! @copydoc operator| */ /*! @copydoc operator| */
template<entt::enum_bitmask Type> template<typename Type>
constexpr Type &operator^=(Type &lhs, const Type rhs) noexcept { constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type &>
operator^=(Type &lhs, const Type rhs) ENTT_NOEXCEPT {
return (lhs = (lhs ^ rhs)); return (lhs = (lhs ^ rhs));
} }

View File

@@ -15,19 +15,16 @@ namespace entt {
*/ */
template<typename...> template<typename...>
class family { class family {
static auto identifier() noexcept { inline static ENTT_MAYBE_ATOMIC(id_type) identifier{};
static ENTT_MAYBE_ATOMIC(id_type) value{};
return value++;
}
public: public:
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
using value_type = id_type; using family_type = id_type;
/*! @brief Statically generated unique identifier for the given type. */ /*! @brief Statically generated unique identifier for the given type. */
template<typename... Type> template<typename... Type>
// at the time I'm writing, clang crashes during compilation if auto is used instead of value_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(); inline static const family_type type = identifier++;
}; };
} // namespace entt } // namespace entt

View File

@@ -1,28 +1,13 @@
#ifndef ENTT_CORE_FWD_HPP #ifndef ENTT_CORE_FWD_HPP
#define ENTT_CORE_FWD_HPP #define ENTT_CORE_FWD_HPP
#include <cstddef>
#include <cstdint> #include <cstdint>
#include <type_traits>
#include "../config/config.h" #include "../config/config.h"
namespace entt { namespace entt {
/*! @brief Possible modes of an any object. */ template<std::size_t Len = sizeof(double[2]), std::size_t = alignof(typename std::aligned_storage_t<Len + !Len>)>
enum class any_policy : std::uint8_t {
/*! @brief Default mode, no element available. */
empty,
/*! @brief Owning mode, dynamically allocated element. */
dynamic,
/*! @brief Owning mode, embedded element. */
embedded,
/*! @brief Aliasing mode, non-const reference. */
ref,
/*! @brief Const aliasing mode, const reference. */
cref
};
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
template<std::size_t Len = sizeof(double[2]), std::size_t = alignof(double[2])>
class basic_any; class basic_any;
/*! @brief Alias declaration for type identifiers. */ /*! @brief Alias declaration for type identifiers. */
@@ -31,21 +16,6 @@ using id_type = ENTT_ID_TYPE;
/*! @brief Alias declaration for the most common use case. */ /*! @brief Alias declaration for the most common use case. */
using any = basic_any<>; using any = basic_any<>;
template<typename, typename>
class compressed_pair;
template<typename>
class basic_hashed_string;
/*! @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>;
// NOLINTNEXTLINE(bugprone-forward-declaration-namespace)
struct type_info;
} // namespace entt } // namespace entt
#endif #endif

View File

@@ -3,26 +3,33 @@
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include "../config/config.h"
#include "fwd.hpp" #include "fwd.hpp"
namespace entt { namespace entt {
/*! @cond ENTT_INTERNAL */ /**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
namespace internal { namespace internal {
template<typename = id_type> template<typename>
struct fnv_1a_params; struct fnv1a_traits;
template<> template<>
struct fnv_1a_params<std::uint32_t> { struct fnv1a_traits<std::uint32_t> {
static constexpr auto offset = 2166136261; using type = std::uint32_t;
static constexpr auto prime = 16777619; static constexpr std::uint32_t offset = 2166136261;
static constexpr std::uint32_t prime = 16777619;
}; };
template<> template<>
struct fnv_1a_params<std::uint64_t> { struct fnv1a_traits<std::uint64_t> {
static constexpr auto offset = 14695981039346656037ull; using type = std::uint64_t;
static constexpr auto prime = 1099511628211ull; static constexpr std::uint64_t offset = 14695981039346656037ull;
static constexpr std::uint64_t prime = 1099511628211ull;
}; };
template<typename Char> template<typename Char>
@@ -31,13 +38,17 @@ struct basic_hashed_string {
using size_type = std::size_t; using size_type = std::size_t;
using hash_type = id_type; using hash_type = id_type;
const value_type *repr{}; const value_type *repr;
hash_type hash{fnv_1a_params<>::offset}; size_type length;
size_type length{}; hash_type hash;
}; };
} // namespace internal } // namespace internal
/*! @endcond */
/**
* Internal details not to be documented.
* @endcond
*/
/** /**
* @brief Zero overhead unique identifier. * @brief Zero overhead unique identifier.
@@ -57,23 +68,43 @@ struct basic_hashed_string {
template<typename Char> template<typename Char>
class basic_hashed_string: internal::basic_hashed_string<Char> { class basic_hashed_string: internal::basic_hashed_string<Char> {
using base_type = internal::basic_hashed_string<Char>; using base_type = internal::basic_hashed_string<Char>;
using params = internal::fnv_1a_params<>; using hs_traits = internal::fnv1a_traits<id_type>;
struct const_wrapper { struct const_wrapper {
// non-explicit constructor on purpose // non-explicit constructor on purpose
constexpr const_wrapper(const base_type::value_type *str) noexcept constexpr const_wrapper(const Char *str) ENTT_NOEXCEPT: repr{str} {}
: repr{str} {} const Char *repr;
const base_type::value_type *repr;
}; };
// FowlerNollVo hash function v. 1a - the good
[[nodiscard]] static constexpr auto helper(const Char *str) ENTT_NOEXCEPT {
base_type base{str, 0u, hs_traits::offset};
for(; str[base.length]; ++base.length) {
base.hash = (base.hash ^ static_cast<hs_traits::type>(str[base.length])) * hs_traits::prime;
}
return base;
}
// FowlerNollVo hash function v. 1a - the good
[[nodiscard]] static constexpr auto helper(const Char *str, const std::size_t len) ENTT_NOEXCEPT {
base_type base{str, len, hs_traits::offset};
for(size_type pos{}; pos < len; ++pos) {
base.hash = (base.hash ^ static_cast<hs_traits::type>(str[pos])) * hs_traits::prime;
}
return base;
}
public: public:
/*! @brief Character type. */ /*! @brief Character type. */
using value_type = base_type::value_type; using value_type = typename base_type::value_type;
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
using size_type = base_type::size_type; using size_type = typename base_type::size_type;
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
using hash_type = base_type::hash_type; using hash_type = typename base_type::hash_type;
/** /**
* @brief Returns directly the numeric representation of a string view. * @brief Returns directly the numeric representation of a string view.
@@ -81,7 +112,7 @@ public:
* @param len Length of the string to hash. * @param len Length of the string to hash.
* @return The numeric representation of the string. * @return The numeric representation of the string.
*/ */
[[nodiscard]] static constexpr hash_type value(const value_type *str, const size_type len) noexcept { [[nodiscard]] static constexpr hash_type value(const value_type *str, const size_type len) ENTT_NOEXCEPT {
return basic_hashed_string{str, len}; return basic_hashed_string{str, len};
} }
@@ -92,8 +123,7 @@ public:
* @return The numeric representation of the string. * @return The numeric representation of the string.
*/ */
template<std::size_t N> template<std::size_t N>
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays) [[nodiscard]] static constexpr hash_type value(const value_type (&str)[N]) ENTT_NOEXCEPT {
[[nodiscard]] static ENTT_CONSTEVAL hash_type value(const value_type (&str)[N]) noexcept {
return basic_hashed_string{str}; return basic_hashed_string{str};
} }
@@ -102,28 +132,21 @@ public:
* @param wrapper Helps achieving the purpose by relying on overloading. * @param wrapper Helps achieving the purpose by relying on overloading.
* @return The numeric representation of the string. * @return The numeric representation of the string.
*/ */
[[nodiscard]] static constexpr hash_type value(const_wrapper wrapper) noexcept { [[nodiscard]] static constexpr hash_type value(const_wrapper wrapper) ENTT_NOEXCEPT {
return basic_hashed_string{wrapper}; return basic_hashed_string{wrapper};
} }
/*! @brief Constructs an empty hashed string. */ /*! @brief Constructs an empty hashed string. */
constexpr basic_hashed_string() noexcept constexpr basic_hashed_string() ENTT_NOEXCEPT
: basic_hashed_string{nullptr, 0u} {} : base_type{} {}
/** /**
* @brief Constructs a hashed string from a string view. * @brief Constructs a hashed string from a string view.
* @param str Human-readable identifier. * @param str Human-readable identifier.
* @param len Length of the string to hash. * @param len Length of the string to hash.
*/ */
constexpr basic_hashed_string(const value_type *str, const size_type len) noexcept constexpr basic_hashed_string(const value_type *str, const size_type len) ENTT_NOEXCEPT
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) : base_type{helper(str, len)} {}
: base_type{str} {
// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
for(; base_type::length < len; ++base_type::length) {
base_type::hash = (base_type::hash ^ static_cast<id_type>(str[base_type::length])) * params::prime;
}
// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
}
/** /**
* @brief Constructs a hashed string from an array of const characters. * @brief Constructs a hashed string from an array of const characters.
@@ -131,14 +154,8 @@ public:
* @param str Human-readable identifier. * @param str Human-readable identifier.
*/ */
template<std::size_t N> template<std::size_t N>
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays) constexpr basic_hashed_string(const value_type (&str)[N]) ENTT_NOEXCEPT
ENTT_CONSTEVAL basic_hashed_string(const value_type (&str)[N]) noexcept : base_type{helper(str)} {}
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
: base_type{str} {
for(; str[base_type::length]; ++base_type::length) {
base_type::hash = (base_type::hash ^ static_cast<id_type>(str[base_type::length])) * params::prime;
}
}
/** /**
* @brief Explicit constructor on purpose to avoid constructing a hashed * @brief Explicit constructor on purpose to avoid constructing a hashed
@@ -149,20 +166,14 @@ public:
* *
* @param wrapper Helps achieving the purpose by relying on overloading. * @param wrapper Helps achieving the purpose by relying on overloading.
*/ */
explicit constexpr basic_hashed_string(const_wrapper wrapper) noexcept explicit constexpr basic_hashed_string(const_wrapper wrapper) ENTT_NOEXCEPT
: base_type{wrapper.repr} { : base_type{helper(wrapper.repr)} {}
// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
for(; wrapper.repr[base_type::length]; ++base_type::length) {
base_type::hash = (base_type::hash ^ static_cast<id_type>(wrapper.repr[base_type::length])) * params::prime;
}
// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
}
/** /**
* @brief Returns the size of a hashed string. * @brief Returns the size a hashed string.
* @return The size of the hashed string. * @return The size of the hashed string.
*/ */
[[nodiscard]] constexpr size_type size() const noexcept { [[nodiscard]] constexpr size_type size() const ENTT_NOEXCEPT {
return base_type::length; return base_type::length;
} }
@@ -170,7 +181,7 @@ public:
* @brief Returns the human-readable representation of a hashed string. * @brief Returns the human-readable representation of a hashed string.
* @return The string used to initialize the hashed string. * @return The string used to initialize the hashed string.
*/ */
[[nodiscard]] constexpr const value_type *data() const noexcept { [[nodiscard]] constexpr const value_type *data() const ENTT_NOEXCEPT {
return base_type::repr; return base_type::repr;
} }
@@ -178,12 +189,12 @@ public:
* @brief Returns the numeric representation of a hashed string. * @brief Returns the numeric representation of a hashed string.
* @return The numeric representation of the hashed string. * @return The numeric representation of the hashed string.
*/ */
[[nodiscard]] constexpr hash_type value() const noexcept { [[nodiscard]] constexpr hash_type value() const ENTT_NOEXCEPT {
return base_type::hash; return base_type::hash;
} }
/*! @copydoc data */ /*! @copydoc data */
[[nodiscard]] explicit constexpr operator const value_type *() const noexcept { [[nodiscard]] constexpr operator const value_type *() const ENTT_NOEXCEPT {
return data(); return data();
} }
@@ -191,27 +202,9 @@ public:
* @brief Returns the numeric representation of a hashed string. * @brief Returns the numeric representation of a hashed string.
* @return The numeric representation of the hashed string. * @return The numeric representation of the hashed string.
*/ */
[[nodiscard]] constexpr operator hash_type() const noexcept { [[nodiscard]] constexpr operator hash_type() const ENTT_NOEXCEPT {
return value(); return value();
} }
/**
* @brief Compares two hashed strings.
* @param other A valid hashed string.
* @return True if the two hashed strings are identical, false otherwise.
*/
[[nodiscard]] constexpr bool operator==(const basic_hashed_string &other) const noexcept {
return value() == other.value();
}
/**
* @brief Lexicographically compares two hashed strings.
* @param other A valid hashed string.
* @return The relative order between the two hashed strings.
*/
[[nodiscard]] constexpr auto operator<=>(const basic_hashed_string &other) const noexcept {
return value() <=> other.value();
}
}; };
/** /**
@@ -221,7 +214,7 @@ public:
* @param len Length of the string to hash. * @param len Length of the string to hash.
*/ */
template<typename Char> template<typename Char>
basic_hashed_string(const Char *str, std::size_t len) -> basic_hashed_string<Char>; basic_hashed_string(const Char *str, const std::size_t len) -> basic_hashed_string<Char>;
/** /**
* @brief Deduction guide. * @brief Deduction guide.
@@ -230,9 +223,89 @@ basic_hashed_string(const Char *str, std::size_t len) -> basic_hashed_string<Cha
* @param str Human-readable identifier. * @param str Human-readable identifier.
*/ */
template<typename Char, std::size_t N> template<typename Char, std::size_t N>
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
basic_hashed_string(const Char (&str)[N]) -> basic_hashed_string<Char>; 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) ENTT_NOEXCEPT {
return lhs.value() == rhs.value();
}
/**
* @brief Compares two hashed strings.
* @tparam Char Character type.
* @param lhs A valid hashed string.
* @param rhs A valid hashed string.
* @return True if the two hashed strings differ, false otherwise.
*/
template<typename Char>
[[nodiscard]] constexpr bool operator!=(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) ENTT_NOEXCEPT {
return !(lhs == rhs);
}
/**
* @brief Compares two hashed strings.
* @tparam Char Character type.
* @param lhs A valid hashed string.
* @param rhs A valid hashed string.
* @return True if the first element is less than the second, false otherwise.
*/
template<typename Char>
[[nodiscard]] constexpr bool operator<(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) ENTT_NOEXCEPT {
return lhs.value() < rhs.value();
}
/**
* @brief Compares two hashed strings.
* @tparam Char Character type.
* @param lhs A valid hashed string.
* @param rhs A valid hashed string.
* @return True if the first element is less than or equal to the second, false
* otherwise.
*/
template<typename Char>
[[nodiscard]] constexpr bool operator<=(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) ENTT_NOEXCEPT {
return !(rhs < lhs);
}
/**
* @brief Compares two hashed strings.
* @tparam Char Character type.
* @param lhs A valid hashed string.
* @param rhs A valid hashed string.
* @return True if the first element is greater than the second, false
* otherwise.
*/
template<typename Char>
[[nodiscard]] constexpr bool operator>(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) ENTT_NOEXCEPT {
return rhs < lhs;
}
/**
* @brief Compares two hashed strings.
* @tparam Char Character type.
* @param lhs A valid hashed string.
* @param rhs A valid hashed string.
* @return True if the first element is greater than or equal to the second,
* false otherwise.
*/
template<typename Char>
[[nodiscard]] constexpr bool operator>=(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) ENTT_NOEXCEPT {
return !(lhs < rhs);
}
/*! @brief Aliases for common character types. */
using hashed_string = basic_hashed_string<char>;
/*! @brief Aliases for common character types. */
using hashed_wstring = basic_hashed_string<wchar_t>;
inline namespace literals { inline namespace literals {
/** /**
@@ -240,7 +313,7 @@ inline namespace literals {
* @param str The literal without its suffix. * @param str The literal without its suffix.
* @return A properly initialized hashed string. * @return A properly initialized hashed string.
*/ */
[[nodiscard]] ENTT_CONSTEVAL hashed_string operator""_hs(const char *str, std::size_t) noexcept { [[nodiscard]] constexpr hashed_string operator"" _hs(const char *str, std::size_t) ENTT_NOEXCEPT {
return hashed_string{str}; return hashed_string{str};
} }
@@ -249,7 +322,7 @@ inline namespace literals {
* @param str The literal without its suffix. * @param str The literal without its suffix.
* @return A properly initialized hashed wstring. * @return A properly initialized hashed wstring.
*/ */
[[nodiscard]] ENTT_CONSTEVAL hashed_wstring operator""_hws(const wchar_t *str, std::size_t) noexcept { [[nodiscard]] constexpr hashed_wstring operator"" _hws(const wchar_t *str, std::size_t) ENTT_NOEXCEPT {
return hashed_wstring{str}; return hashed_wstring{str};
} }

View File

@@ -4,30 +4,54 @@
#include <cstddef> #include <cstddef>
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
#include "../config/config.h"
#include "fwd.hpp" #include "fwd.hpp"
#include "type_traits.hpp" #include "type_traits.hpp"
namespace entt { namespace entt {
/** /**
* @brief Type integral identifiers. * @brief Types identifiers.
* @tparam Type List of types for which to generate identifiers. *
* Variable template used to generate identifiers at compile-time for the given
* types. Use the `get` member function to know what's the identifier associated
* to the specific type.
*
* @note
* Identifiers are constant expression and can be used in any context where such
* an expression is required. As an example:
* @code{.cpp}
* using id = entt::identifier<a_type, another_type>;
*
* switch(a_type_identifier) {
* case id::type<a_type>:
* // ...
* break;
* case id::type<another_type>:
* // ...
* break;
* default:
* // ...
* }
* @endcode
*
* @tparam Types List of types for which to generate identifiers.
*/ */
template<typename... Type> template<typename... Types>
class ident { class identifier {
template<typename Curr, std::size_t... Index> template<typename Type, std::size_t... Index>
[[nodiscard]] static ENTT_CONSTEVAL id_type get(std::index_sequence<Index...>) noexcept { [[nodiscard]] static constexpr id_type get(std::index_sequence<Index...>) ENTT_NOEXCEPT {
return (0 + ... + (std::is_same_v<Curr, type_list_element_t<Index, type_list<std::decay_t<Type>...>>> ? id_type{Index} : id_type{})); static_assert((std::is_same_v<Type, Types> || ...), "Invalid type");
return (0 + ... + (std::is_same_v<Type, type_list_element_t<Index, type_list<std::decay_t<Types>...>>> ? id_type{Index} : id_type{}));
} }
public: public:
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
using value_type = id_type; using identifier_type = id_type;
/*! @brief Statically generated unique identifier for the given type. */ /*! @brief Statically generated unique identifier for the given type. */
template<typename Curr> template<typename Type>
requires (std::is_same_v<std::remove_cvref_t<Curr>, Type> || ...) static constexpr identifier_type type = get<std::decay_t<Type>>(std::index_sequence_for<Types...>{});
static constexpr value_type value = get<std::remove_cvref_t<Curr>>(std::index_sequence_for<Type...>{});
}; };
} // namespace entt } // namespace entt

View File

@@ -1,12 +1,10 @@
#ifndef ENTT_CORE_ITERATOR_HPP #ifndef ENTT_CORE_ITERATOR_HPP
#define ENTT_CORE_ITERATOR_HPP #define ENTT_CORE_ITERATOR_HPP
#include <concepts>
#include <iterator> #include <iterator>
#include <memory> #include <memory>
#include <type_traits>
#include <utility> #include <utility>
#include "../stl/iterator.hpp" #include "../config/config.h"
namespace entt { namespace entt {
@@ -16,139 +14,77 @@ namespace entt {
*/ */
template<typename Type> template<typename Type>
struct input_iterator_pointer final { struct input_iterator_pointer final {
/*! @brief Value type. */
using value_type = Type;
/*! @brief Pointer type. */ /*! @brief Pointer type. */
using pointer = Type *; using pointer = Type *;
/*! @brief Reference type. */
using reference = Type &; /*! @brief Default copy constructor, deleted on purpose. */
input_iterator_pointer(const input_iterator_pointer &) = delete;
/*! @brief Default move constructor. */
input_iterator_pointer(input_iterator_pointer &&) = default;
/** /**
* @brief Constructs a proxy object by move. * @brief Constructs a proxy object by move.
* @param val Value to use to initialize the proxy object. * @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>) input_iterator_pointer(Type &&val)
: value{std::move(val)} {} : value{std::move(val)} {}
/**
* @brief Default copy assignment operator, deleted on purpose.
* @return This proxy object.
*/
input_iterator_pointer &operator=(const input_iterator_pointer &) = delete;
/**
* @brief Default move assignment operator.
* @return This proxy object.
*/
input_iterator_pointer &operator=(input_iterator_pointer &&) = default;
/** /**
* @brief Access operator for accessing wrapped values. * @brief Access operator for accessing wrapped values.
* @return A pointer to the wrapped value. * @return A pointer to the wrapped value.
*/ */
[[nodiscard]] constexpr pointer operator->() noexcept { [[nodiscard]] pointer operator->() ENTT_NOEXCEPT {
return std::addressof(value); 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: private:
Type value; Type value;
}; };
/**
* @brief Plain iota iterator (waiting for C++20).
* @tparam Type Value type.
*/
template<std::integral Type>
struct iota_iterator final {
/*! @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 {
const iota_iterator orig = *this;
return ++(*this), orig;
}
/**
* @brief Dereference operator.
* @return The underlying value.
*/
[[nodiscard]] constexpr reference operator*() const noexcept {
return current;
}
/**
* @brief Comparison operator.
* @param other A properly initialized iota iterator.
* @return True if the two iterators are identical, false otherwise.
*/
[[nodiscard]] constexpr bool operator==(const iota_iterator &other) const noexcept {
return current == other.current;
}
private:
value_type current;
};
/** /**
* @brief Utility class to create an iterable object from a pair of iterators. * @brief Utility class to create an iterable object from a pair of iterators.
* @tparam It Type of iterator. * @tparam It Type of iterator.
* @tparam Sentinel Type of sentinel. * @tparam Sentinel Type of sentinel.
*/ */
template<stl::input_or_output_iterator It, stl::sentinel_for<It> Sentinel = It> template<typename It, typename Sentinel = It>
struct iterable_adaptor final { struct iterable_adaptor final {
/*! @brief Value type. */ /*! @brief Value type. */
using value_type = std::iterator_traits<It>::value_type; using value_type = typename std::iterator_traits<It>::value_type;
/*! @brief Iterator type. */ /*! @brief Iterator type. */
using iterator = It; using iterator = It;
/*! @brief Sentinel type. */ /*! @brief Sentinel type. */
using sentinel = Sentinel; using sentinel = Sentinel;
/*! @brief Default constructor. */ /*! @brief Default constructor. */
constexpr iterable_adaptor() noexcept(std::is_nothrow_default_constructible_v<iterator> && std::is_nothrow_default_constructible_v<sentinel>) iterable_adaptor() = default;
: first{},
last{} {}
/** /**
* @brief Creates an iterable object from a pair of iterators. * @brief Creates an iterable object from a pair of iterators.
* @param from Begin iterator. * @param from Begin iterator.
* @param to End 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>) iterable_adaptor(iterator from, sentinel to)
: first{std::move(from)}, : first{from},
last{std::move(to)} {} last{to} {}
/** /**
* @brief Returns an iterator to the beginning. * @brief Returns an iterator to the beginning.
* @return An iterator to the first element of the range. * @return An iterator to the first element of the range.
*/ */
[[nodiscard]] constexpr iterator begin() const noexcept { [[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
return first; return first;
} }
@@ -157,17 +93,17 @@ struct iterable_adaptor final {
* @return An iterator to the element following the last element of the * @return An iterator to the element following the last element of the
* range. * range.
*/ */
[[nodiscard]] constexpr sentinel end() const noexcept { [[nodiscard]] sentinel end() const ENTT_NOEXCEPT {
return last; return last;
} }
/*! @copydoc begin */ /*! @copydoc begin */
[[nodiscard]] constexpr iterator cbegin() const noexcept { [[nodiscard]] iterator cbegin() const ENTT_NOEXCEPT {
return begin(); return begin();
} }
/*! @copydoc end */ /*! @copydoc end */
[[nodiscard]] constexpr sentinel cend() const noexcept { [[nodiscard]] sentinel cend() const ENTT_NOEXCEPT {
return end(); return end();
} }

View File

@@ -2,15 +2,30 @@
#define ENTT_CORE_MEMORY_HPP #define ENTT_CORE_MEMORY_HPP
#include <cstddef> #include <cstddef>
#include <limits>
#include <memory> #include <memory>
#include <tuple> #include <tuple>
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
#include "../config/config.h" #include "../config/config.h"
#include "../stl/memory.hpp"
namespace entt { namespace entt {
/**
* @brief Unwraps fancy pointers, does nothing otherwise (waiting for C++20).
* @tparam Type Pointer type.
* @param ptr Fancy or raw pointer.
* @return A raw pointer that represents the address of the original pointer.
*/
template<typename Type>
[[nodiscard]] constexpr auto to_address(Type &&ptr) ENTT_NOEXCEPT {
if constexpr(std::is_pointer_v<std::remove_cv_t<std::remove_reference_t<Type>>>) {
return ptr;
} else {
return to_address(std::forward<Type>(ptr).operator->());
}
}
/** /**
* @brief Utility function to design allocation-aware containers. * @brief Utility function to design allocation-aware containers.
* @tparam Allocator Type of allocator. * @tparam Allocator Type of allocator.
@@ -18,7 +33,7 @@ namespace entt {
* @param rhs Another valid allocator. * @param rhs Another valid allocator.
*/ */
template<typename Allocator> template<typename Allocator>
constexpr void propagate_on_container_copy_assignment([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) noexcept { constexpr void propagate_on_container_copy_assignment([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) ENTT_NOEXCEPT {
if constexpr(std::allocator_traits<Allocator>::propagate_on_container_copy_assignment::value) { if constexpr(std::allocator_traits<Allocator>::propagate_on_container_copy_assignment::value) {
lhs = rhs; lhs = rhs;
} }
@@ -31,7 +46,7 @@ constexpr void propagate_on_container_copy_assignment([[maybe_unused]] Allocator
* @param rhs Another valid allocator. * @param rhs Another valid allocator.
*/ */
template<typename Allocator> template<typename Allocator>
constexpr void propagate_on_container_move_assignment([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) noexcept { constexpr void propagate_on_container_move_assignment([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) ENTT_NOEXCEPT {
if constexpr(std::allocator_traits<Allocator>::propagate_on_container_move_assignment::value) { if constexpr(std::allocator_traits<Allocator>::propagate_on_container_move_assignment::value) {
lhs = std::move(rhs); lhs = std::move(rhs);
} }
@@ -44,40 +59,76 @@ constexpr void propagate_on_container_move_assignment([[maybe_unused]] Allocator
* @param rhs Another valid allocator. * @param rhs Another valid allocator.
*/ */
template<typename Allocator> template<typename Allocator>
constexpr void propagate_on_container_swap([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) noexcept { constexpr void propagate_on_container_swap([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) ENTT_NOEXCEPT {
ENTT_ASSERT(std::allocator_traits<Allocator>::propagate_on_container_swap::value || lhs == rhs, "Cannot swap the containers");
if constexpr(std::allocator_traits<Allocator>::propagate_on_container_swap::value) { if constexpr(std::allocator_traits<Allocator>::propagate_on_container_swap::value) {
using std::swap; using std::swap;
swap(lhs, rhs); swap(lhs, rhs);
} else {
ENTT_ASSERT_CONSTEXPR(lhs == rhs, "Cannot swap the containers");
} }
} }
/**
* @brief Checks whether a value is a power of two or not.
* @param value A value that may or may not be a power of two.
* @return True if the value is a power of two, false otherwise.
*/
[[nodiscard]] inline constexpr bool is_power_of_two(const std::size_t value) ENTT_NOEXCEPT {
return value && ((value & (value - 1)) == 0);
}
/**
* @brief Computes the smallest power of two greater than or equal to a value.
* @param value The value to use.
* @return The smallest power of two greater than or equal to the given value.
*/
[[nodiscard]] inline constexpr std::size_t next_power_of_two(const std::size_t value) ENTT_NOEXCEPT {
ENTT_ASSERT(value < (std::size_t{1u} << (std::numeric_limits<std::size_t>::digits - 1)), "Numeric limits exceeded");
std::size_t curr = value - (value != 0u);
for(int next = 1; next < std::numeric_limits<std::size_t>::digits; next = next * 2) {
curr |= curr >> next;
}
return ++curr;
}
/**
* @brief Fast module utility function (powers of two only).
* @param value A value for which to calculate the modulus.
* @param mod _Modulus_, it must be a power of two.
* @return The common remainder.
*/
[[nodiscard]] inline constexpr std::size_t fast_mod(const std::size_t value, const std::size_t mod) ENTT_NOEXCEPT {
ENTT_ASSERT(is_power_of_two(mod), "Value must be a power of two");
return value & (mod - 1u);
}
/** /**
* @brief Deleter for allocator-aware unique pointers (waiting for C++20). * @brief Deleter for allocator-aware unique pointers (waiting for C++20).
* @tparam Allocator Type of allocator used to manage memory and elements. * @tparam Args Types of arguments to use to construct the object.
*/ */
template<typename Allocator> template<typename Allocator>
struct allocation_deleter: private Allocator { struct allocation_deleter: private Allocator {
/*! @brief Allocator type. */ /*! @brief Allocator type. */
using allocator_type = Allocator; using allocator_type = Allocator;
/*! @brief Pointer type. */ /*! @brief Pointer type. */
using pointer = std::allocator_traits<Allocator>::pointer; using pointer = typename std::allocator_traits<Allocator>::pointer;
/** /**
* @brief Inherited constructors. * @brief Inherited constructors.
* @param alloc The allocator to use. * @param alloc The allocator to use.
*/ */
constexpr allocation_deleter(const allocator_type &alloc) noexcept(std::is_nothrow_copy_constructible_v<allocator_type>) allocation_deleter(const allocator_type &alloc)
: Allocator{alloc} {} : Allocator{alloc} {}
/** /**
* @brief Destroys the pointed object and deallocates its memory. * @brief Destroys the pointed object and deallocates its memory.
* @param ptr A valid pointer to an object of the given type. * @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>) { void operator()(pointer ptr) {
using alloc_traits = std::allocator_traits<Allocator>; using alloc_traits = typename std::allocator_traits<Allocator>;
alloc_traits::destroy(*this, stl::to_address(ptr)); alloc_traits::destroy(*this, to_address(ptr));
alloc_traits::deallocate(*this, ptr, 1u); alloc_traits::deallocate(*this, ptr, 1u);
} }
}; };
@@ -92,17 +143,17 @@ struct allocation_deleter: private Allocator {
* @return A properly initialized unique pointer with a custom deleter. * @return A properly initialized unique pointer with a custom deleter.
*/ */
template<typename Type, typename Allocator, typename... Args> template<typename Type, typename Allocator, typename... Args>
constexpr auto allocate_unique(Allocator &allocator, Args &&...args) { auto allocate_unique(Allocator &allocator, Args &&...args) {
static_assert(!std::is_array_v<Type>, "Array types are not supported"); static_assert(!std::is_array_v<Type>, "Array types are not supported");
using alloc_traits = std::allocator_traits<Allocator>::template rebind_traits<Type>; using alloc_traits = typename std::allocator_traits<Allocator>::template rebind_traits<Type>;
using allocator_type = alloc_traits::allocator_type; using allocator_type = typename alloc_traits::allocator_type;
allocator_type alloc{allocator}; allocator_type alloc{allocator};
auto ptr = alloc_traits::allocate(alloc, 1u); auto ptr = alloc_traits::allocate(alloc, 1u);
ENTT_TRY { ENTT_TRY {
alloc_traits::construct(alloc, stl::to_address(ptr), std::forward<Args>(args)...); alloc_traits::construct(alloc, to_address(ptr), std::forward<Args>(args)...);
} }
ENTT_CATCH { ENTT_CATCH {
alloc_traits::deallocate(alloc, ptr, 1u); alloc_traits::deallocate(alloc, ptr, 1u);
@@ -112,20 +163,24 @@ constexpr auto allocate_unique(Allocator &allocator, Args &&...args) {
return std::unique_ptr<Type, allocation_deleter<allocator_type>>{ptr, alloc}; return std::unique_ptr<Type, allocation_deleter<allocator_type>>{ptr, alloc};
} }
/*! @cond ENTT_INTERNAL */ /**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
namespace internal { namespace internal {
template<typename Type> template<typename Type>
struct uses_allocator_construction { struct uses_allocator_construction {
template<typename Allocator, typename... Params> template<typename Allocator, typename... Params>
static constexpr auto args([[maybe_unused]] const Allocator &allocator, Params &&...params) noexcept { static constexpr auto args([[maybe_unused]] const Allocator &allocator, Params &&...params) ENTT_NOEXCEPT {
if constexpr(!std::uses_allocator_v<Type, Allocator> && std::is_constructible_v<Type, Params...>) { if constexpr(!std::uses_allocator_v<Type, Allocator> && std::is_constructible_v<Type, Params...>) {
return std::forward_as_tuple(std::forward<Params>(params)...); return std::forward_as_tuple(std::forward<Params>(params)...);
} else { } else {
static_assert(std::uses_allocator_v<Type, Allocator>, "Ill-formed request"); 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...>) { 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)...}; return std::tuple<std::allocator_arg_t, const Allocator &, Params &&...>(std::allocator_arg, allocator, std::forward<Params>(params)...);
} else { } else {
static_assert(std::is_constructible_v<Type, Params..., const Allocator &>, "Ill-formed request"); static_assert(std::is_constructible_v<Type, Params..., const Allocator &>, "Ill-formed request");
return std::forward_as_tuple(std::forward<Params>(params)..., allocator); return std::forward_as_tuple(std::forward<Params>(params)..., allocator);
@@ -138,36 +193,41 @@ template<typename Type, typename Other>
struct uses_allocator_construction<std::pair<Type, Other>> { struct uses_allocator_construction<std::pair<Type, Other>> {
using type = std::pair<Type, Other>; using type = std::pair<Type, Other>;
template<typename First, typename Second> template<typename Allocator, typename First, typename Second>
static constexpr auto args(const auto &allocator, std::piecewise_construct_t, First &&first, Second &&second) noexcept { static constexpr auto args(const Allocator &allocator, std::piecewise_construct_t, First &&first, Second &&second) ENTT_NOEXCEPT {
return std::make_tuple( return std::make_tuple(
std::piecewise_construct, 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<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))); std::apply([&allocator](auto &&...curr) { return uses_allocator_construction<Other>::args(allocator, std::forward<decltype(curr)>(curr)...); }, std::forward<Second>(second)));
} }
static constexpr auto args(const auto &allocator) noexcept { template<typename Allocator>
static constexpr auto args(const Allocator &allocator) ENTT_NOEXCEPT {
return uses_allocator_construction<type>::args(allocator, std::piecewise_construct, std::tuple<>{}, std::tuple<>{}); return uses_allocator_construction<type>::args(allocator, std::piecewise_construct, std::tuple<>{}, std::tuple<>{});
} }
template<typename First, typename Second> template<typename Allocator, typename First, typename Second>
static constexpr auto args(const auto &allocator, First &&first, Second &&second) noexcept { static constexpr auto args(const Allocator &allocator, First &&first, Second &&second) ENTT_NOEXCEPT {
return uses_allocator_construction<type>::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::forward<First>(first)), std::forward_as_tuple(std::forward<Second>(second))); 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 First, typename Second> template<typename Allocator, typename First, typename Second>
static constexpr auto args(const auto &allocator, const std::pair<First, Second> &value) noexcept { static constexpr auto args(const Allocator &allocator, const std::pair<First, Second> &value) ENTT_NOEXCEPT {
return uses_allocator_construction<type>::args(allocator, std::piecewise_construct, std::forward_as_tuple(value.first), std::forward_as_tuple(value.second)); return uses_allocator_construction<type>::args(allocator, std::piecewise_construct, std::forward_as_tuple(value.first), std::forward_as_tuple(value.second));
} }
template<typename First, typename Second> template<typename Allocator, typename First, typename Second>
static constexpr auto args(const auto &allocator, std::pair<First, Second> &&value) noexcept { static constexpr auto args(const Allocator &allocator, std::pair<First, Second> &&value) ENTT_NOEXCEPT {
return uses_allocator_construction<type>::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::move(value.first)), std::forward_as_tuple(std::move(value.second))); 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 } // namespace internal
/*! @endcond */
/**
* Internal details not to be documented.
* @endcond
*/
/** /**
* @brief Uses-allocator construction utility (waiting for C++20). * @brief Uses-allocator construction utility (waiting for C++20).
@@ -176,13 +236,14 @@ struct uses_allocator_construction<std::pair<Type, Other>> {
* create an object of a given type by means of uses-allocator construction. * create an object of a given type by means of uses-allocator construction.
* *
* @tparam Type Type to return arguments for. * @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. * @tparam Args Types of arguments to use to construct the object.
* @param allocator The allocator to use. * @param allocator The allocator to use.
* @param args Parameters to use to construct the object. * @param args Parameters to use to construct the object.
* @return The arguments needed to create an object of the given type. * @return The arguments needed to create an object of the given type.
*/ */
template<typename Type, typename... Args> template<typename Type, typename Allocator, typename... Args>
constexpr auto uses_allocator_construction_args(const auto &allocator, Args &&...args) noexcept { constexpr auto uses_allocator_construction_args(const Allocator &allocator, Args &&...args) ENTT_NOEXCEPT {
return internal::uses_allocator_construction<Type>::args(allocator, std::forward<Args>(args)...); return internal::uses_allocator_construction<Type>::args(allocator, std::forward<Args>(args)...);
} }
@@ -193,13 +254,14 @@ constexpr auto uses_allocator_construction_args(const auto &allocator, Args &&..
* means of uses-allocator construction. * means of uses-allocator construction.
* *
* @tparam Type Type of object to create. * @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. * @tparam Args Types of arguments to use to construct the object.
* @param allocator The allocator to use. * @param allocator The allocator to use.
* @param args Parameters to use to construct the object. * @param args Parameters to use to construct the object.
* @return A newly created object of the given type. * @return A newly created object of the given type.
*/ */
template<typename Type, typename... Args> template<typename Type, typename Allocator, typename... Args>
constexpr Type make_obj_using_allocator(const auto &allocator, Args &&...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)...)); return std::make_from_tuple<Type>(internal::uses_allocator_construction<Type>::args(allocator, std::forward<Args>(args)...));
} }
@@ -210,15 +272,16 @@ constexpr Type make_obj_using_allocator(const auto &allocator, Args &&...args) {
* means of uses-allocator construction at an uninitialized memory location. * means of uses-allocator construction at an uninitialized memory location.
* *
* @tparam Type Type of object to create. * @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. * @tparam Args Types of arguments to use to construct the object.
* @param value Memory location in which to place the object. * @param value Memory location in which to place the object.
* @param allocator The allocator to use. * @param allocator The allocator to use.
* @param args Parameters to use to construct the object. * @param args Parameters to use to construct the object.
* @return A pointer to the newly created object of the given type. * @return A pointer to the newly created object of the given type.
*/ */
template<typename Type, typename... Args> template<typename Type, typename Allocator, typename... Args>
constexpr Type *uninitialized_construct_using_allocator(Type *value, const auto &allocator, Args &&...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)...)); return std::apply([&](auto &&...curr) { return new(value) Type(std::forward<decltype(curr)>(curr)...); }, internal::uses_allocator_construction<Type>::args(allocator, std::forward<Args>(args)...));
} }
} // namespace entt } // namespace entt

View File

@@ -23,12 +23,10 @@ struct monostate {
* @brief Assigns a value of a specific type to a given key. * @brief Assigns a value of a specific type to a given key.
* @tparam Type Type of the value to assign. * @tparam Type Type of the value to assign.
* @param val User data to assign to the given key. * @param val User data to assign to the given key.
* @return This monostate object.
*/ */
template<typename Type> template<typename Type>
monostate &operator=(Type val) noexcept { void operator=(Type val) const ENTT_NOEXCEPT {
value<Type> = val; value<Type> = val;
return *this;
} }
/** /**
@@ -37,13 +35,12 @@ struct monostate {
* @return Stored value, if any. * @return Stored value, if any.
*/ */
template<typename Type> template<typename Type>
operator Type() const noexcept { operator Type() const ENTT_NOEXCEPT {
return value<Type>; return value<Type>;
} }
private: private:
template<typename Type> template<typename Type>
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
inline static ENTT_MAYBE_ATOMIC(Type) value{}; inline static ENTT_MAYBE_ATOMIC(Type) value{};
}; };
@@ -52,8 +49,7 @@ private:
* @tparam Value Value used to differentiate between different variables. * @tparam Value Value used to differentiate between different variables.
*/ */
template<id_type Value> template<id_type Value>
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) inline monostate<Value> monostate_v = {};
inline monostate<Value> monostate_v{};
} // namespace entt } // namespace entt

View File

@@ -1,20 +0,0 @@
#ifndef ENTT_CORE_RANGES_HPP
#define ENTT_CORE_RANGES_HPP
#if __has_include(<version>)
# include <version>
#
# if defined(__cpp_lib_ranges)
# include <ranges>
# include "iterator.hpp"
template<class... Args>
inline constexpr bool std::ranges::enable_borrowed_range<entt::iterable_adaptor<Args...>>{true};
template<class... Args>
inline constexpr bool std::ranges::enable_view<entt::iterable_adaptor<Args...>>{true};
# endif
#endif
#endif

View File

@@ -4,31 +4,10 @@
#include <tuple> #include <tuple>
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
#include "../config/config.h"
namespace entt { namespace entt {
/**
* @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: std::false_type {};
/**
* @copybrief is_tuple
* @tparam Args Tuple template arguments.
*/
template<typename... Args>
struct is_tuple<std::tuple<Args...>>: std::true_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. * @brief Utility function to unwrap tuples of a single element.
* @tparam Type Tuple type of any sizes. * @tparam Type Tuple type of any sizes.
@@ -37,7 +16,7 @@ inline constexpr bool is_tuple_v = is_tuple<Type>::value;
* element otherwise. * element otherwise.
*/ */
template<typename Type> template<typename Type>
constexpr decltype(auto) unwrap_tuple(Type &&value) noexcept { constexpr decltype(auto) unwrap_tuple(Type &&value) ENTT_NOEXCEPT {
if constexpr(std::tuple_size_v<std::remove_reference_t<Type>> == 1u) { if constexpr(std::tuple_size_v<std::remove_reference_t<Type>> == 1u) {
return std::get<0>(std::forward<Type>(value)); return std::get<0>(std::forward<Type>(value));
} else { } else {
@@ -45,46 +24,6 @@ constexpr decltype(auto) unwrap_tuple(Type &&value) noexcept {
} }
} }
/**
* @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<typename... 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<typename 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<typename 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_cvref_t<Func>>;
} // namespace entt } // namespace entt
#endif #endif

View File

@@ -1,68 +1,63 @@
#ifndef ENTT_CORE_TYPE_INFO_HPP #ifndef ENTT_CORE_TYPE_INFO_HPP
#define ENTT_CORE_TYPE_INFO_HPP #define ENTT_CORE_TYPE_INFO_HPP
#include <compare>
#include <string_view> #include <string_view>
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
#include "../config/config.h" #include "../config/config.h"
#include "../core/attribute.h"
#include "fwd.hpp" #include "fwd.hpp"
#include "hashed_string.hpp" #include "hashed_string.hpp"
namespace entt { namespace entt {
/*! @cond ENTT_INTERNAL */ /**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
namespace internal { namespace internal {
struct ENTT_API type_index final { struct ENTT_API type_index final {
[[nodiscard]] static id_type next() noexcept { [[nodiscard]] static id_type next() ENTT_NOEXCEPT {
static ENTT_MAYBE_ATOMIC(id_type) value{}; static ENTT_MAYBE_ATOMIC(id_type) value{};
return value++; return value++;
} }
}; };
template<typename Type> template<typename Type>
[[nodiscard]] constexpr const char *pretty_function() noexcept { [[nodiscard]] constexpr auto stripped_type_name() ENTT_NOEXCEPT {
#if defined ENTT_PRETTY_FUNCTION #if defined ENTT_PRETTY_FUNCTION
return static_cast<const char *>(ENTT_PRETTY_FUNCTION); std::string_view pretty_function{ENTT_PRETTY_FUNCTION};
#else auto first = pretty_function.find_first_not_of(' ', pretty_function.find_first_of(ENTT_PRETTY_FUNCTION_PREFIX) + 1);
return ""; auto value = pretty_function.substr(first, pretty_function.find_last_of(ENTT_PRETTY_FUNCTION_SUFFIX) - first);
#endif
}
template<typename Type>
[[nodiscard]] constexpr auto stripped_type_name() noexcept {
#if defined ENTT_PRETTY_FUNCTION
const std::string_view full_name{pretty_function<Type>()};
auto first = full_name.find_first_not_of(' ', full_name.find_first_of(ENTT_PRETTY_FUNCTION_PREFIX) + 1);
auto value = full_name.substr(first, full_name.find_last_of(ENTT_PRETTY_FUNCTION_SUFFIX) - first);
return value; return value;
#else #else
return std::string_view{}; return std::string_view{""};
#endif #endif
} }
template<typename Type, auto = stripped_type_name<Type>().find_first_of('.')> template<typename Type, auto = stripped_type_name<Type>().find_first_of('.')>
[[nodiscard]] ENTT_CONSTEVAL std::string_view type_name(int) noexcept { [[nodiscard]] static constexpr std::string_view type_name(int) ENTT_NOEXCEPT {
constexpr auto value = stripped_type_name<Type>(); constexpr auto value = stripped_type_name<Type>();
return value; return value;
} }
template<typename Type> template<typename Type>
[[nodiscard]] std::string_view type_name(char) noexcept { [[nodiscard]] static std::string_view type_name(char) ENTT_NOEXCEPT {
static const auto value = stripped_type_name<Type>(); static const auto value = stripped_type_name<Type>();
return value; return value;
} }
template<typename Type, auto = stripped_type_name<Type>().find_first_of('.')> template<typename Type, auto = stripped_type_name<Type>().find_first_of('.')>
[[nodiscard]] ENTT_CONSTEVAL id_type type_hash(int) noexcept { [[nodiscard]] static constexpr id_type type_hash(int) ENTT_NOEXCEPT {
constexpr auto stripped = stripped_type_name<Type>(); constexpr auto stripped = stripped_type_name<Type>();
constexpr auto value = hashed_string::value(stripped.data(), stripped.size()); constexpr auto value = hashed_string::value(stripped.data(), stripped.size());
return value; return value;
} }
template<typename Type> template<typename Type>
[[nodiscard]] id_type type_hash(char) noexcept { [[nodiscard]] static id_type type_hash(char) ENTT_NOEXCEPT {
static const auto value = [](const auto stripped) { static const auto value = [](const auto stripped) {
return hashed_string::value(stripped.data(), stripped.size()); return hashed_string::value(stripped.data(), stripped.size());
}(stripped_type_name<Type>()); }(stripped_type_name<Type>());
@@ -70,25 +65,29 @@ template<typename Type>
} }
} // namespace internal } // namespace internal
/*! @endcond */
/**
* Internal details not to be documented.
* @endcond
*/
/** /**
* @brief Type sequential identifier. * @brief Type sequential identifier.
* @tparam Type Type for which to generate a sequential identifier. * @tparam Type Type for which to generate a sequential identifier.
*/ */
template<typename Type> template<typename Type, typename = void>
struct ENTT_API type_index final { struct ENTT_API type_index final {
/** /**
* @brief Returns the sequential identifier of a given type. * @brief Returns the sequential identifier of a given type.
* @return The sequential identifier of a given type. * @return The sequential identifier of a given type.
*/ */
[[nodiscard]] static id_type value() noexcept { [[nodiscard]] static id_type value() ENTT_NOEXCEPT {
static const id_type value = internal::type_index::next(); static const id_type value = internal::type_index::next();
return value; return value;
} }
/*! @copydoc value */ /*! @copydoc value */
[[nodiscard]] constexpr operator id_type() const noexcept { [[nodiscard]] constexpr operator id_type() const ENTT_NOEXCEPT {
return value(); return value();
} }
}; };
@@ -97,23 +96,23 @@ struct ENTT_API type_index final {
* @brief Type hash. * @brief Type hash.
* @tparam Type Type for which to generate a hash value. * @tparam Type Type for which to generate a hash value.
*/ */
template<typename Type> template<typename Type, typename = void>
struct type_hash final { struct type_hash final {
/** /**
* @brief Returns the numeric representation of a given type. * @brief Returns the numeric representation of a given type.
* @return The numeric representation of the given type. * @return The numeric representation of the given type.
*/ */
#if defined ENTT_PRETTY_FUNCTION #if defined ENTT_PRETTY_FUNCTION
[[nodiscard]] static constexpr id_type value() noexcept { [[nodiscard]] static constexpr id_type value() ENTT_NOEXCEPT {
return internal::type_hash<Type>(0); return internal::type_hash<Type>(0);
#else #else
[[nodiscard]] static constexpr id_type value() noexcept { [[nodiscard]] static constexpr id_type value() ENTT_NOEXCEPT {
return type_index<Type>::value(); return type_index<Type>::value();
#endif #endif
} }
/*! @copydoc value */ /*! @copydoc value */
[[nodiscard]] constexpr operator id_type() const noexcept { [[nodiscard]] constexpr operator id_type() const ENTT_NOEXCEPT {
return value(); return value();
} }
}; };
@@ -122,18 +121,18 @@ struct type_hash final {
* @brief Type name. * @brief Type name.
* @tparam Type Type for which to generate a name. * @tparam Type Type for which to generate a name.
*/ */
template<typename Type> template<typename Type, typename = void>
struct type_name final { struct type_name final {
/** /**
* @brief Returns the name of a given type. * @brief Returns the name of a given type.
* @return The name of the given type. * @return The name of the given type.
*/ */
[[nodiscard]] static constexpr std::string_view value() noexcept { [[nodiscard]] static constexpr std::string_view value() ENTT_NOEXCEPT {
return internal::type_name<Type>(0); return internal::type_name<Type>(0);
} }
/*! @copydoc value */ /*! @copydoc value */
[[nodiscard]] constexpr operator std::string_view() const noexcept { [[nodiscard]] constexpr operator std::string_view() const ENTT_NOEXCEPT {
return value(); return value();
} }
}; };
@@ -145,18 +144,16 @@ struct type_info final {
* @tparam Type Type for which to construct a type info object. * @tparam Type Type for which to construct a type info object.
*/ */
template<typename Type> template<typename Type>
// NOLINTBEGIN(modernize-use-transparent-functors) constexpr type_info(std::in_place_type_t<Type>) ENTT_NOEXCEPT
constexpr type_info(std::in_place_type_t<Type>) noexcept : seq{type_index<std::remove_cv_t<std::remove_reference_t<Type>>>::value()},
: seq{type_index<std::remove_cvref_t<Type>>::value()}, identifier{type_hash<std::remove_cv_t<std::remove_reference_t<Type>>>::value()},
identifier{type_hash<std::remove_cvref_t<Type>>::value()}, alias{type_name<std::remove_cv_t<std::remove_reference_t<Type>>>::value()} {}
alias{type_name<std::remove_cvref_t<Type>>::value()} {}
// NOLINTEND(modernize-use-transparent-functors)
/** /**
* @brief Type index. * @brief Type index.
* @return Type index. * @return Type index.
*/ */
[[nodiscard]] constexpr id_type index() const noexcept { [[nodiscard]] constexpr id_type index() const ENTT_NOEXCEPT {
return seq; return seq;
} }
@@ -164,7 +161,7 @@ struct type_info final {
* @brief Type hash. * @brief Type hash.
* @return Type hash. * @return Type hash.
*/ */
[[nodiscard]] constexpr id_type hash() const noexcept { [[nodiscard]] constexpr id_type hash() const ENTT_NOEXCEPT {
return identifier; return identifier;
} }
@@ -172,34 +169,79 @@ struct type_info final {
* @brief Type name. * @brief Type name.
* @return Type name. * @return Type name.
*/ */
[[nodiscard]] constexpr std::string_view name() const noexcept { [[nodiscard]] constexpr std::string_view name() const ENTT_NOEXCEPT {
return alias; return alias;
} }
/**
* @brief Compares two type info objects.
* @param other A type info object.
* @return True if the two type info objects are identical, false otherwise.
*/
[[nodiscard]] constexpr bool operator==(const type_info &other) const noexcept {
return identifier == other.identifier;
}
/**
* @brief Lexicographically compares two type info objects.
* @param other A type info object.
* @return The relative order between the two type info objects.
*/
[[nodiscard]] constexpr auto operator<=>(const type_info &other) const noexcept {
return seq <=> other.seq;
}
private: private:
id_type seq; id_type seq;
id_type identifier; id_type identifier;
std::string_view alias; std::string_view alias;
}; };
/**
* @brief Compares the contents of two type info objects.
* @param lhs A type info object.
* @param rhs A type info object.
* @return True if the two type info objects are identical, false otherwise.
*/
[[nodiscard]] inline constexpr bool operator==(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT {
return lhs.hash() == rhs.hash();
}
/**
* @brief Compares the contents of two type info objects.
* @param lhs A type info object.
* @param rhs A type info object.
* @return True if the two type info objects differ, false otherwise.
*/
[[nodiscard]] inline constexpr bool operator!=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT {
return !(lhs == rhs);
}
/**
* @brief Compares two type info objects.
* @param lhs A valid type info object.
* @param rhs A valid type info object.
* @return True if the first element is less than the second, false otherwise.
*/
[[nodiscard]] constexpr bool operator<(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT {
return lhs.index() < rhs.index();
}
/**
* @brief Compares two type info objects.
* @param lhs A valid type info object.
* @param rhs A valid type info object.
* @return True if the first element is less than or equal to the second, false
* otherwise.
*/
[[nodiscard]] constexpr bool operator<=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT {
return !(rhs < lhs);
}
/**
* @brief Compares two type info objects.
* @param lhs A valid type info object.
* @param rhs A valid type info object.
* @return True if the first element is greater than the second, false
* otherwise.
*/
[[nodiscard]] constexpr bool operator>(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT {
return rhs < lhs;
}
/**
* @brief Compares two type info objects.
* @param lhs A valid type info object.
* @param rhs A valid type info object.
* @return True if the first element is greater than or equal to the second,
* false otherwise.
*/
[[nodiscard]] constexpr bool operator>=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT {
return !(lhs < rhs);
}
/** /**
* @brief Returns the type info object associated to a given type. * @brief Returns the type info object associated to a given type.
* *
@@ -212,19 +254,19 @@ private:
* @return A reference to a properly initialized type info object. * @return A reference to a properly initialized type info object.
*/ */
template<typename Type> template<typename Type>
[[nodiscard]] const type_info &type_id() noexcept { [[nodiscard]] const type_info &type_id() ENTT_NOEXCEPT {
if constexpr(std::is_same_v<Type, std::remove_cvref_t<Type>>) { if constexpr(std::is_same_v<Type, std::remove_cv_t<std::remove_reference_t<Type>>>) {
static const type_info instance{std::in_place_type<Type>}; static type_info instance{std::in_place_type<Type>};
return instance; return instance;
} else { } else {
return type_id<std::remove_cvref_t<Type>>(); return type_id<std::remove_cv_t<std::remove_reference_t<Type>>>();
} }
} }
/*! @copydoc type_id */ /*! @copydoc type_id */
template<typename Type> template<typename Type>
[[nodiscard]] const type_info &type_id(const Type &) noexcept { [[nodiscard]] const type_info &type_id(Type &&) ENTT_NOEXCEPT {
return type_id<std::remove_cvref_t<Type>>(); return type_id<std::remove_cv_t<std::remove_reference_t<Type>>>();
} }
} // namespace entt } // namespace entt

View File

@@ -1,10 +1,8 @@
#ifndef ENTT_CORE_TYPE_TRAITS_HPP #ifndef ENTT_CORE_TYPE_TRAITS_HPP
#define ENTT_CORE_TYPE_TRAITS_HPP #define ENTT_CORE_TYPE_TRAITS_HPP
#include <concepts>
#include <cstddef> #include <cstddef>
#include <iterator> #include <iterator>
#include <tuple>
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
#include "../config/config.h" #include "../config/config.h"
@@ -18,8 +16,8 @@ namespace entt {
*/ */
template<std::size_t N> template<std::size_t N>
struct choice_t struct choice_t
// unfortunately, doxygen cannot parse such a construct // Unfortunately, doxygen cannot parse such a construct.
: /*! @cond ENTT_INTERNAL */ choice_t<N - 1> /*! @endcond */ : /*! @cond TURN_OFF_DOXYGEN */ choice_t<N - 1> /*! @endcond */
{}; {};
/*! @copybrief choice_t */ /*! @copybrief choice_t */
@@ -34,17 +32,37 @@ template<std::size_t N>
inline constexpr choice_t<N> choice{}; inline constexpr choice_t<N> choice{};
/** /**
* @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains. * @brief Identity type trait.
* @tparam Type The type of which to return the size. *
* 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> 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> {}; struct size_of: std::integral_constant<std::size_t, 0u> {};
/*! @copydoc size_of */ /*! @copydoc size_of */
template<typename Type> template<typename Type>
requires requires { sizeof(Type); } struct size_of<Type, std::void_t<decltype(sizeof(Type))>>
struct size_of<Type>
// NOLINTNEXTLINE(bugprone-sizeof-expression)
: std::integral_constant<std::size_t, sizeof(Type)> {}; : std::integral_constant<std::size_t, sizeof(Type)> {};
/** /**
@@ -103,22 +121,22 @@ struct type_list_element;
/** /**
* @brief Provides compile-time indexed access to the types of a type list. * @brief Provides compile-time indexed access to the types of a type list.
* @tparam Index Index of the type to return. * @tparam Index Index of the type to return.
* @tparam First First type provided by the type list. * @tparam Type First type provided by the type list.
* @tparam Other Other types provided by the type list. * @tparam Other Other types provided by the type list.
*/ */
template<std::size_t Index, typename First, typename... Other> template<std::size_t Index, typename Type, typename... Other>
struct type_list_element<Index, type_list<First, Other...>> struct type_list_element<Index, type_list<Type, Other...>>
: type_list_element<Index - 1u, type_list<Other...>> {}; : type_list_element<Index - 1u, type_list<Other...>> {};
/** /**
* @brief Provides compile-time indexed access to the types of a type list. * @brief Provides compile-time indexed access to the types of a type list.
* @tparam First First type provided by the type list. * @tparam Type First type provided by the type list.
* @tparam Other Other types provided by the type list. * @tparam Other Other types provided by the type list.
*/ */
template<typename First, typename... Other> template<typename Type, typename... Other>
struct type_list_element<0u, type_list<First, Other...>> { struct type_list_element<0u, type_list<Type, Other...>> {
/*! @brief Searched type. */ /*! @brief Searched type. */
using type = First; using type = Type;
}; };
/** /**
@@ -127,59 +145,7 @@ struct type_list_element<0u, type_list<First, Other...>> {
* @tparam List Type list to search into. * @tparam List Type list to search into.
*/ */
template<std::size_t Index, typename List> template<std::size_t Index, typename List>
using type_list_element_t = type_list_element<Index, List>::type; 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. * @brief Concatenates multiple type lists.
@@ -188,7 +154,7 @@ inline constexpr std::size_t type_list_index_v = type_list_index<Type, List>::va
* @return A type list composed by the types of both the type lists. * @return A type list composed by the types of both the type lists.
*/ */
template<typename... Type, typename... Other> template<typename... Type, typename... Other>
ENTT_CONSTEVAL type_list<Type..., Other...> operator+(type_list<Type...>, type_list<Other...>) { constexpr type_list<Type..., Other...> operator+(type_list<Type...>, type_list<Other...>) {
return {}; return {};
} }
@@ -212,7 +178,7 @@ struct type_list_cat<> {
template<typename... Type, typename... Other, typename... List> template<typename... Type, typename... Other, typename... List>
struct type_list_cat<type_list<Type...>, type_list<Other...>, 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. */ /*! @brief A type list composed by the types of all the type lists. */
using type = type_list_cat<type_list<Type..., Other...>, List...>::type; using type = typename type_list_cat<type_list<Type..., Other...>, List...>::type;
}; };
/** /**
@@ -230,42 +196,39 @@ struct type_list_cat<type_list<Type...>> {
* @tparam List Type lists to concatenate. * @tparam List Type lists to concatenate.
*/ */
template<typename... List> template<typename... List>
using type_list_cat_t = type_list_cat<List...>::type; using type_list_cat_t = typename type_list_cat<List...>::type;
/*! @cond ENTT_INTERNAL */ /*! @brief Primary template isn't defined on purpose. */
namespace internal { template<typename>
template<typename...>
struct type_list_unique; struct type_list_unique;
template<typename First, typename... Other, typename... Type>
struct type_list_unique<type_list<First, Other...>, Type...>
: std::conditional_t<(std::is_same_v<First, Type> || ...), type_list_unique<type_list<Other...>, Type...>, type_list_unique<type_list<Other...>, Type..., First>> {};
template<typename... Type>
struct type_list_unique<type_list<>, Type...> {
using type = type_list<Type...>;
};
} // namespace internal
/*! @endcond */
/** /**
* @brief Removes duplicates types from a type list. * @brief Removes duplicates types from a type list.
* @tparam List 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 List> template<typename Type, typename... Other>
struct type_list_unique { struct type_list_unique<type_list<Type, Other...>> {
/*! @brief A type list without duplicate types. */ /*! @brief A type list without duplicate types. */
using type = internal::type_list_unique<List>::type; 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. * @brief Helper type.
* @tparam List Type list. * @tparam Type A type list.
*/ */
template<typename List> template<typename Type>
using type_list_unique_t = type_list_unique<List>::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 * @brief Provides the member constant `value` to true if a type list contains a
@@ -282,8 +245,7 @@ struct type_list_contains;
* @tparam Other Type to look for. * @tparam Other Type to look for.
*/ */
template<typename... Type, typename Other> template<typename... Type, typename Other>
struct type_list_contains<type_list<Type...>, Other> struct type_list_contains<type_list<Type...>, Other>: std::disjunction<std::is_same<Type, Other>...> {};
: std::bool_constant<(std::is_same_v<Type, Other> || ...)> {};
/** /**
* @brief Helper variable template. * @brief Helper variable template.
@@ -313,31 +275,7 @@ struct type_list_diff<type_list<Type...>, type_list<Other...>> {
* @tparam List Type lists between which to compute the difference. * @tparam List Type lists between which to compute the difference.
*/ */
template<typename... List> template<typename... List>
using type_list_diff_t = type_list_diff<List...>::type; 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. */
// NOLINTNEXTLINE(modernize-type-traits)
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 = type_list_transform<List, Op>::type;
/** /**
* @brief A class to use to push around lists of constant values, nothing more. * @brief A class to use to push around lists of constant values, nothing more.
@@ -372,20 +310,10 @@ struct value_list_element<Index, value_list<Value, Other...>>
*/ */
template<auto Value, auto... Other> template<auto Value, auto... Other>
struct value_list_element<0u, value_list<Value, Other...>> { struct value_list_element<0u, value_list<Value, Other...>> {
/*! @brief Searched type. */
using type = decltype(Value);
/*! @brief Searched value. */ /*! @brief Searched value. */
static constexpr auto value = Value; static constexpr auto value = Value;
}; };
/**
* @brief Helper type.
* @tparam Index Index of the type to return.
* @tparam List Value list to search into.
*/
template<std::size_t Index, typename List>
using value_list_element_t = value_list_element<Index, List>::type;
/** /**
* @brief Helper type. * @brief Helper type.
* @tparam Index Index of the value to return. * @tparam Index Index of the value to return.
@@ -394,58 +322,6 @@ using value_list_element_t = value_list_element<Index, List>::type;
template<std::size_t Index, typename List> template<std::size_t Index, typename List>
inline constexpr auto value_list_element_v = value_list_element<Index, List>::value; inline constexpr auto value_list_element_v = value_list_element<Index, List>::value;
/*! @brief Primary template isn't defined on purpose. */
template<auto, typename>
struct value_list_index;
/**
* @brief Provides compile-time type access to the values of a value list.
* @tparam Value Value to look for and for which to return the index.
* @tparam First First value provided by the value list.
* @tparam Other Other values provided by the value list.
*/
template<auto Value, auto First, auto... Other>
struct value_list_index<Value, value_list<First, Other...>> {
/*! @brief Unsigned integer type. */
using value_type = std::size_t;
/*! @brief Compile-time position of the given value in the sublist. */
static constexpr value_type value = 1u + value_list_index<Value, value_list<Other...>>::value;
};
/**
* @brief Provides compile-time type access to the values of a value list.
* @tparam Value Value to look for and for which to return the index.
* @tparam Other Other values provided by the value list.
*/
template<auto Value, auto... Other>
struct value_list_index<Value, value_list<Value, Other...>> {
static_assert(value_list_index<Value, value_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 value in the sublist. */
static constexpr value_type value = 0u;
};
/**
* @brief Provides compile-time type access to the values of a value list.
* @tparam Value Value to look for and for which to return the index.
*/
template<auto Value>
struct value_list_index<Value, value_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 Value list.
* @tparam Value Value to look for and for which to return the index.
*/
template<auto Value, typename List>
inline constexpr std::size_t value_list_index_v = value_list_index<Value, List>::value;
/** /**
* @brief Concatenates multiple value lists. * @brief Concatenates multiple value lists.
* @tparam Value Values provided by the first value list. * @tparam Value Values provided by the first value list.
@@ -453,7 +329,7 @@ inline constexpr std::size_t value_list_index_v = value_list_index<Value, List>:
* @return A value list composed by the values of both the value lists. * @return A value list composed by the values of both the value lists.
*/ */
template<auto... Value, auto... Other> template<auto... Value, auto... Other>
ENTT_CONSTEVAL value_list<Value..., Other...> operator+(value_list<Value...>, value_list<Other...>) { constexpr value_list<Value..., Other...> operator+(value_list<Value...>, value_list<Other...>) {
return {}; return {};
} }
@@ -477,7 +353,7 @@ struct value_list_cat<> {
template<auto... Value, auto... Other, typename... List> template<auto... Value, auto... Other, typename... List>
struct value_list_cat<value_list<Value...>, value_list<Other...>, 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. */ /*! @brief A value list composed by the values of all the value lists. */
using type = value_list_cat<value_list<Value..., Other...>, List...>::type; using type = typename value_list_cat<value_list<Value..., Other...>, List...>::type;
}; };
/** /**
@@ -495,87 +371,7 @@ struct value_list_cat<value_list<Value...>> {
* @tparam List Value lists to concatenate. * @tparam List Value lists to concatenate.
*/ */
template<typename... List> template<typename... List>
using value_list_cat_t = value_list_cat<List...>::type; using value_list_cat_t = typename value_list_cat<List...>::type;
/*! @brief Primary template isn't defined on purpose. */
template<typename>
struct value_list_unique;
/**
* @brief Removes duplicates values from a value list.
* @tparam Value One of the values provided by the given value list.
* @tparam Other The other values provided by the given value list.
*/
template<auto Value, auto... Other>
struct value_list_unique<value_list<Value, Other...>> {
/*! @brief A value list without duplicate types. */
using type = std::conditional_t<
((Value == Other) || ...),
typename value_list_unique<value_list<Other...>>::type,
value_list_cat_t<value_list<Value>, typename value_list_unique<value_list<Other...>>::type>>;
};
/*! @brief Removes duplicates values from a value list. */
template<>
struct value_list_unique<value_list<>> {
/*! @brief A value list without duplicate types. */
using type = value_list<>;
};
/**
* @brief Helper type.
* @tparam Type A value list.
*/
template<typename Type>
using value_list_unique_t = value_list_unique<Type>::type;
/**
* @brief Provides the member constant `value` to true if a value list contains
* a given value, false otherwise.
* @tparam List Value list.
* @tparam Value Value to look for.
*/
template<typename List, auto Value>
struct value_list_contains;
/**
* @copybrief value_list_contains
* @tparam Value Values provided by the value list.
* @tparam Other Value to look for.
*/
template<auto... Value, auto Other>
struct value_list_contains<value_list<Value...>, Other>
: std::bool_constant<((Value == Other) || ...)> {};
/**
* @brief Helper variable template.
* @tparam List Value list.
* @tparam Value Value to look for.
*/
template<typename List, auto Value>
inline constexpr bool value_list_contains_v = value_list_contains<List, Value>::value;
/*! @brief Primary template isn't defined on purpose. */
template<typename...>
struct value_list_diff;
/**
* @brief Computes the difference between two value lists.
* @tparam Value Values provided by the first value list.
* @tparam Other Values provided by the second value list.
*/
template<auto... Value, auto... Other>
struct value_list_diff<value_list<Value...>, value_list<Other...>> {
/*! @brief A value list that is the difference between the two value lists. */
using type = value_list_cat_t<std::conditional_t<value_list_contains_v<value_list<Other...>, Value>, value_list<>, value_list<Value>>...>;
};
/**
* @brief Helper type.
* @tparam List Value lists between which to compute the difference.
*/
template<typename... List>
using value_list_diff_t = value_list_diff<List...>::type;
/*! @brief Same as std::is_invocable, but with tuples. */ /*! @brief Same as std::is_invocable, but with tuples. */
template<typename, typename> template<typename, typename>
@@ -636,13 +432,12 @@ inline constexpr bool is_applicable_r_v = is_applicable_r<Ret, Func, Args>::valu
* complete, false otherwise. * complete, false otherwise.
* @tparam Type The type to test. * @tparam Type The type to test.
*/ */
template<typename Type> template<typename Type, typename = void>
struct is_complete: std::false_type {}; struct is_complete: std::false_type {};
/*! @copydoc is_complete */ /*! @copydoc is_complete */
template<typename Type> template<typename Type>
requires requires { sizeof(Type); } struct is_complete<Type, std::void_t<decltype(sizeof(Type))>>: std::true_type {};
struct is_complete<Type>: std::true_type {};
/** /**
* @brief Helper variable template. * @brief Helper variable template.
@@ -656,26 +451,33 @@ inline constexpr bool is_complete_v = is_complete<Type>::value;
* iterator, false otherwise. * iterator, false otherwise.
* @tparam Type The type to test. * @tparam Type The type to test.
*/ */
template<typename Type> template<typename Type, typename = void>
struct is_iterator: std::false_type {}; struct is_iterator: std::false_type {};
/*! @cond ENTT_INTERNAL */ /**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
namespace internal { namespace internal {
template<typename> template<typename, typename = void>
struct has_iterator_category: std::false_type {}; struct has_iterator_category: std::false_type {};
template<typename Type> template<typename Type>
requires requires { typename std::iterator_traits<Type>::iterator_category; } struct has_iterator_category<Type, std::void_t<typename std::iterator_traits<Type>::iterator_category>>: std::true_type {};
struct has_iterator_category<Type>: std::true_type {};
} // namespace internal } // namespace internal
/*! @endcond */
/**
* Internal details not to be documented.
* @endcond
*/
/*! @copydoc is_iterator */ /*! @copydoc is_iterator */
template<typename Type> template<typename Type>
requires (!std::is_void_v<std::remove_const_t<std::remove_pointer_t<Type>>>) struct is_iterator<Type, std::enable_if_t<!std::is_same_v<std::remove_const_t<std::remove_pointer_t<Type>>, void>>>
struct is_iterator<Type>: internal::has_iterator_category<Type> {}; : internal::has_iterator_category<Type> {};
/** /**
* @brief Helper variable template. * @brief Helper variable template.
@@ -690,7 +492,8 @@ inline constexpr bool is_iterator_v = is_iterator<Type>::value;
* @tparam Type The type to test * @tparam Type The type to test
*/ */
template<typename Type> template<typename Type>
struct is_ebco_eligible: std::bool_constant<std::is_empty_v<Type> && !std::is_final_v<Type>> {}; struct is_ebco_eligible
: std::conjunction<std::is_empty<Type>, std::negation<std::is_final<Type>>> {};
/** /**
* @brief Helper variable template. * @brief Helper variable template.
@@ -704,13 +507,12 @@ inline constexpr bool is_ebco_eligible_v = is_ebco_eligible<Type>::value;
* is valid and denotes a type, false otherwise. * is valid and denotes a type, false otherwise.
* @tparam Type The type to test. * @tparam Type The type to test.
*/ */
template<typename Type> template<typename Type, typename = void>
struct is_transparent: std::false_type {}; struct is_transparent: std::false_type {};
/*! @copydoc is_transparent */ /*! @copydoc is_transparent */
template<typename Type> template<typename Type>
requires requires { typename Type::is_transparent; } struct is_transparent<Type, std::void_t<typename Type::is_transparent>>: std::true_type {};
struct is_transparent<Type>: std::true_type {};
/** /**
* @brief Helper variable template. * @brief Helper variable template.
@@ -719,78 +521,68 @@ struct is_transparent<Type>: std::true_type {};
template<typename Type> template<typename Type>
inline constexpr bool is_transparent_v = is_transparent<Type>::value; inline constexpr bool is_transparent_v = is_transparent<Type>::value;
/*! @cond ENTT_INTERNAL */
namespace internal {
template<typename>
struct has_tuple_size_value: std::false_type {};
template<typename Type>
requires is_complete_v<std::tuple_size<const Type>>
struct has_tuple_size_value<Type>: std::true_type {};
template<typename>
struct has_value_type: std::false_type {};
template<typename Type>
requires requires { typename Type::value_type; }
struct has_value_type<Type>: std::true_type {};
template<typename>
[[nodiscard]] ENTT_CONSTEVAL bool dispatch_is_equality_comparable();
template<typename Type, std::size_t... Index>
[[nodiscard]] ENTT_CONSTEVAL bool unpack_maybe_equality_comparable(std::index_sequence<Index...>) {
return (dispatch_is_equality_comparable<std::tuple_element_t<Index, Type>>() && ...);
}
template<typename>
[[nodiscard]] ENTT_CONSTEVAL bool maybe_equality_comparable(char) {
return false;
}
template<typename Type>
[[nodiscard]] ENTT_CONSTEVAL auto maybe_equality_comparable(int) -> decltype(std::declval<Type>() == std::declval<Type>()) {
return true;
}
template<typename Type>
[[nodiscard]] ENTT_CONSTEVAL bool dispatch_is_equality_comparable() {
// NOLINTBEGIN(modernize-use-transparent-functors)
if constexpr(std::is_array_v<Type>) {
return false;
} else if constexpr(is_complete_v<std::tuple_size<std::remove_const_t<Type>>>) {
if constexpr(has_tuple_size_value<Type>::value) {
return maybe_equality_comparable<Type>(0) && unpack_maybe_equality_comparable<Type>(std::make_index_sequence<std::tuple_size<Type>::value>{});
} else {
return maybe_equality_comparable<Type>(0);
}
} else if constexpr(has_value_type<Type>::value) {
if constexpr(is_iterator_v<Type> || std::is_same_v<typename Type::value_type, Type> || dispatch_is_equality_comparable<typename Type::value_type>()) {
return maybe_equality_comparable<Type>(0);
} else {
return false;
}
} else {
return maybe_equality_comparable<Type>(0);
}
// NOLINTEND(modernize-use-transparent-functors)
}
} // namespace internal
/*! @endcond */
/** /**
* @brief Provides the member constant `value` to true if a given type is * @brief Provides the member constant `value` to true if a given type is
* equality comparable, false otherwise. * equality comparable, false otherwise.
* @tparam Type The type to test. * @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> template<typename Type>
struct is_equality_comparable: std::bool_constant<internal::dispatch_is_equality_comparable<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_const_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 */ /*! @copydoc is_equality_comparable */
template<typename Type> template<typename Type>
struct is_equality_comparable<const Type>: is_equality_comparable<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. * @brief Helper variable template.
@@ -814,7 +606,7 @@ struct constness_as {
template<typename To, typename From> template<typename To, typename From>
struct constness_as<To, const From> { struct constness_as<To, const From> {
/*! @brief The type resulting from the transcription of the constness. */ /*! @brief The type resulting from the transcription of the constness. */
using type = const To; using type = std::add_const_t<To>;
}; };
/** /**
@@ -823,7 +615,7 @@ struct constness_as<To, const From> {
* @tparam From The type from which to transcribe the constness. * @tparam From The type from which to transcribe the constness.
*/ */
template<typename To, typename From> template<typename To, typename From>
using constness_as_t = constness_as<To, From>::type; using constness_as_t = typename constness_as<To, From>::type;
/** /**
* @brief Extracts the class of a non-static member object or function. * @brief Extracts the class of a non-static member object or function.
@@ -852,55 +644,8 @@ public:
* @tparam Member A pointer to a non-static member object or function. * @tparam Member A pointer to a non-static member object or function.
*/ */
template<typename Member> template<typename Member>
using member_class_t = member_class<Member>::type; using member_class_t = typename member_class<Member>::type;
/**
* @brief Extracts the n-th argument of a _callable_ type.
* @tparam Index The index of the argument to extract.
* @tparam Candidate A valid _callable_ type.
*/
template<std::size_t Index, typename Candidate>
class nth_argument {
template<typename Ret, typename... Args>
static ENTT_CONSTEVAL type_list<Args...> pick_up(Ret (*)(Args...));
template<typename Ret, typename Class, typename... Args>
static ENTT_CONSTEVAL type_list<Args...> pick_up(Ret (Class ::*)(Args...));
template<typename Ret, typename Class, typename... Args>
static ENTT_CONSTEVAL type_list<Args...> pick_up(Ret (Class ::*)(Args...) const);
template<typename Type, typename Class>
static ENTT_CONSTEVAL type_list<Type> pick_up(Type Class ::*);
template<typename Type>
static ENTT_CONSTEVAL decltype(pick_up(&Type::operator())) pick_up(Type &&);
public:
/*! @brief N-th argument of the _callable_ type. */
using type = type_list_element_t<Index, decltype(pick_up(std::declval<Candidate>()))>;
};
/**
* @brief Helper type.
* @tparam Index The index of the argument to extract.
* @tparam Candidate A valid function, member function or data member type.
*/
template<std::size_t Index, typename Candidate>
using nth_argument_t = nth_argument<Index, Candidate>::type;
} // namespace entt } // namespace entt
template<typename... Type>
struct std::tuple_size<entt::type_list<Type...>>: std::integral_constant<std::size_t, entt::type_list<Type...>::size> {};
template<std::size_t Index, typename... Type>
struct std::tuple_element<Index, entt::type_list<Type...>>: entt::type_list_element<Index, entt::type_list<Type...>> {};
template<auto... Value>
struct std::tuple_size<entt::value_list<Value...>>: std::integral_constant<std::size_t, entt::value_list<Value...>::size> {};
template<std::size_t Index, auto... Value>
struct std::tuple_element<Index, entt::value_list<Value...>>: entt::value_list_element<Index, entt::value_list<Value...>> {};
#endif #endif

View File

@@ -1,11 +1,28 @@
#ifndef ENTT_CORE_UTILITY_HPP #ifndef ENTT_CORE_UTILITY_HPP
#define ENTT_CORE_UTILITY_HPP #define ENTT_CORE_UTILITY_HPP
#include <type_traits>
#include <utility> #include <utility>
#include "../config/config.h"
namespace entt { namespace entt {
/*! @brief Identity function object (waiting for C++20). */
struct identity {
/*! @brief Indicates that this is a transparent function object. */
using is_transparent = void;
/**
* @brief Returns its argument unchanged.
* @tparam Type Type of the argument.
* @param value The actual argument.
* @return The submitted value as-is.
*/
template<class Type>
[[nodiscard]] constexpr Type &&operator()(Type &&value) const ENTT_NOEXCEPT {
return std::forward<Type>(value);
}
};
/** /**
* @brief Constant utility to disambiguate overloaded members of a class. * @brief Constant utility to disambiguate overloaded members of a class.
* @tparam Type Type of the desired overload. * @tparam Type Type of the desired overload.
@@ -14,7 +31,7 @@ namespace entt {
* @return Pointer to the member. * @return Pointer to the member.
*/ */
template<typename Type, typename Class> template<typename Type, typename Class>
[[nodiscard]] constexpr auto overload(Type Class::*member) noexcept { [[nodiscard]] constexpr auto overload(Type Class::*member) ENTT_NOEXCEPT {
return member; return member;
} }
@@ -25,7 +42,7 @@ template<typename Type, typename Class>
* @return Pointer to the function. * @return Pointer to the function.
*/ */
template<typename Func> template<typename Func>
[[nodiscard]] constexpr auto overload(Func *func) noexcept { [[nodiscard]] constexpr auto overload(Func *func) ENTT_NOEXCEPT {
return func; return func;
} }
@@ -33,7 +50,7 @@ template<typename Func>
* @brief Helper type for visitors. * @brief Helper type for visitors.
* @tparam Func Types of function objects. * @tparam Func Types of function objects.
*/ */
template<typename... Func> template<class... Func>
struct overloaded: Func... { struct overloaded: Func... {
using Func::operator()...; using Func::operator()...;
}; };
@@ -42,20 +59,20 @@ struct overloaded: Func... {
* @brief Deduction guide. * @brief Deduction guide.
* @tparam Func Types of function objects. * @tparam Func Types of function objects.
*/ */
template<typename... Func> template<class... Func>
overloaded(Func...) -> overloaded<Func...>; overloaded(Func...) -> overloaded<Func...>;
/** /**
* @brief Basic implementation of a y-combinator. * @brief Basic implementation of a y-combinator.
* @tparam Func Type of a potentially recursive function. * @tparam Func Type of a potentially recursive function.
*/ */
template<typename Func> template<class Func>
struct y_combinator { struct y_combinator {
/** /**
* @brief Constructs a y-combinator from a given function. * @brief Constructs a y-combinator from a given function.
* @param recursive A potentially recursive function. * @param recursive A potentially recursive function.
*/ */
constexpr y_combinator(Func recursive) noexcept(std::is_nothrow_move_constructible_v<Func>) y_combinator(Func recursive)
: func{std::move(recursive)} {} : func{std::move(recursive)} {}
/** /**
@@ -64,14 +81,14 @@ struct y_combinator {
* @param args Parameters 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. * @return Return value of the underlying function, if any.
*/ */
template<typename... Args> template<class... Args>
constexpr decltype(auto) operator()(Args &&...args) const noexcept(std::is_nothrow_invocable_v<Func, const y_combinator &, Args...>) { decltype(auto) operator()(Args &&...args) const {
return func(*this, std::forward<Args>(args)...); return func(*this, std::forward<Args>(args)...);
} }
/*! @copydoc operator()() */ /*! @copydoc operator()() */
template<typename... Args> template<class... Args>
constexpr decltype(auto) operator()(Args &&...args) noexcept(std::is_nothrow_invocable_v<Func, y_combinator &, Args...>) { decltype(auto) operator()(Args &&...args) {
return func(*this, std::forward<Args>(args)...); return func(*this, std::forward<Args>(args)...);
} }

View File

@@ -1,52 +1,47 @@
#ifndef ENTT_ENTITY_COMPONENT_HPP #ifndef ENTT_ENTITY_COMPONENT_HPP
#define ENTT_ENTITY_COMPONENT_HPP #define ENTT_ENTITY_COMPONENT_HPP
#include <concepts>
#include <cstddef> #include <cstddef>
#include <type_traits> #include <type_traits>
#include "../config/config.h" #include "../config/config.h"
#include "../core/concepts.hpp"
#include "fwd.hpp"
namespace entt { namespace entt {
/*! @cond ENTT_INTERNAL */ /**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
namespace internal { namespace internal {
template<typename Type> template<typename, typename = void>
struct in_place_delete: std::bool_constant<!(std::is_move_constructible_v<Type> && std::is_move_assignable_v<Type>)> {}; struct in_place_delete: std::false_type {};
template<>
struct in_place_delete<void>: std::false_type {};
template<typename Type> template<typename Type>
requires Type::in_place_delete struct in_place_delete<Type, std::enable_if_t<Type::in_place_delete>>
struct in_place_delete<Type>: std::true_type {}; : std::true_type {};
template<typename Type, typename = void>
struct page_size: std::integral_constant<std::size_t, (ENTT_IGNORE_IF_EMPTY && std::is_empty_v<Type>) ? 0u : ENTT_PACKED_PAGE> {};
template<typename Type> template<typename Type>
struct page_size: std::integral_constant<std::size_t, !std::is_empty_v<ENTT_ETO_TYPE(Type)> * ENTT_PACKED_PAGE> {}; 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> {};
template<>
struct page_size<void>: std::integral_constant<std::size_t, 0u> {};
template<typename Type>
requires std::is_convertible_v<decltype(Type::page_size), std::size_t>
struct page_size<Type>: std::integral_constant<std::size_t, Type::page_size> {};
} // namespace internal } // namespace internal
/*! @endcond */
/**
* Internal details not to be documented.
* @endcond
*/
/** /**
* @brief Common way to access various properties of components. * @brief Common way to access various properties of components.
* @tparam Type Element type. * @tparam Type Type of component.
* @tparam Entity A valid entity type.
*/ */
template<cvref_unqualified Type, typename Entity> template<typename Type, typename = void>
struct component_traits { struct component_traits {
/*! @brief Element type. */ static_assert(std::is_same_v<std::decay_t<Type>, Type>, "Unsupported type");
using element_type = Type;
/*! @brief Underlying entity identifier. */
using entity_type = Entity;
/*! @brief Pointer stability, default is `false`. */ /*! @brief Pointer stability, default is `false`. */
static constexpr bool in_place_delete = internal::in_place_delete<Type>::value; static constexpr bool in_place_delete = internal::in_place_delete<Type>::value;
@@ -54,6 +49,13 @@ struct component_traits {
static constexpr std::size_t page_size = internal::page_size<Type>::value; static constexpr std::size_t page_size = internal::page_size<Type>::value;
}; };
/**
* @brief Helper variable template.
* @tparam Type Type of component.
*/
template<class Type>
inline constexpr bool ignore_as_empty_v = (component_traits<Type>::page_size == 0u);
} // namespace entt } // namespace entt
#endif #endif

View File

@@ -1,103 +1,85 @@
#ifndef ENTT_ENTITY_ENTITY_HPP #ifndef ENTT_ENTITY_ENTITY_HPP
#define ENTT_ENTITY_ENTITY_HPP #define ENTT_ENTITY_ENTITY_HPP
#include <bit>
#include <concepts>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <type_traits> #include <type_traits>
#include "../config/config.h" #include "../config/config.h"
#include "../core/bit.hpp"
#include "fwd.hpp" #include "fwd.hpp"
namespace entt { namespace entt {
/*! @cond ENTT_INTERNAL */ /**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
namespace internal { namespace internal {
template<typename> template<typename, typename = void>
struct entt_traits; struct entt_traits;
template<typename Type> template<typename Type>
requires requires { struct entt_traits<Type, std::enable_if_t<std::is_enum_v<Type>>>
requires std::is_enum_v<Type>; : entt_traits<std::underlying_type_t<Type>> {};
typename internal::entt_traits<std::underlying_type_t<Type>>::value_type;
}
struct entt_traits<Type>: entt_traits<std::underlying_type_t<Type>> {
using value_type = Type;
};
template<typename Type> template<typename Type>
requires requires { typename Type::entity_type; } struct entt_traits<Type, std::enable_if_t<std::is_class_v<Type>>>
struct entt_traits<Type> : entt_traits<typename Type::entity_type> {};
: entt_traits<typename Type::entity_type> {
using value_type = Type;
};
template<> template<>
struct entt_traits<std::uint32_t> { struct entt_traits<std::uint32_t> {
using value_type = std::uint32_t;
using entity_type = std::uint32_t; using entity_type = std::uint32_t;
using version_type = std::uint16_t; using version_type = std::uint16_t;
static constexpr entity_type entity_mask = 0xFFFFF; static constexpr entity_type entity_mask = 0xFFFFF;
static constexpr entity_type version_mask = 0xFFF; static constexpr entity_type version_mask = 0xFFF;
static constexpr std::size_t entity_shift = 20u;
}; };
template<> template<>
struct entt_traits<std::uint64_t> { struct entt_traits<std::uint64_t> {
using value_type = std::uint64_t;
using entity_type = std::uint64_t; using entity_type = std::uint64_t;
using version_type = std::uint32_t; using version_type = std::uint32_t;
static constexpr entity_type entity_mask = 0xFFFFFFFF; static constexpr entity_type entity_mask = 0xFFFFFFFF;
static constexpr entity_type version_mask = 0xFFFFFFFF; static constexpr entity_type version_mask = 0xFFFFFFFF;
static constexpr std::size_t entity_shift = 32u;
}; };
} // namespace internal } // namespace internal
/*! @endcond */
/** /**
* @brief Specifies that a type is an entity-like type. * Internal details not to be documented.
* @tparam Type Type to check. * @endcond
*/
/**
* @brief Entity traits.
* @tparam Type Type of identifier.
*/ */
template<typename Type> template<typename Type>
concept entity_like = requires { class entt_traits: internal::entt_traits<Type> {
typename internal::entt_traits<Type>::value_type; using base_type = internal::entt_traits<Type>;
};
/**
* @brief Common basic entity traits implementation.
* @tparam Traits Actual entity traits to use.
*/
template<typename Traits>
class basic_entt_traits {
static constexpr auto length = std::popcount(Traits::entity_mask);
static_assert(Traits::entity_mask && ((Traits::entity_mask & (Traits::entity_mask + 1)) == 0), "Invalid entity mask");
static_assert((Traits::version_mask & (Traits::version_mask + 1)) == 0, "Invalid version mask");
public: public:
/*! @brief Value type. */ /*! @brief Value type. */
using value_type = Traits::value_type; using value_type = Type;
/*! @brief Underlying entity type. */ /*! @brief Underlying entity type. */
using entity_type = Traits::entity_type; using entity_type = typename base_type::entity_type;
/*! @brief Underlying version type. */ /*! @brief Underlying version type. */
using version_type = Traits::version_type; using version_type = typename base_type::version_type;
/*! @brief Reserved identifier. */
/*! @brief Entity mask size. */ static constexpr entity_type reserved = base_type::entity_mask | (base_type::version_mask << base_type::entity_shift);
static constexpr entity_type entity_mask = Traits::entity_mask; /*! @brief Page size, default is `ENTT_SPARSE_PAGE`. */
/*! @brief Version mask size */ static constexpr auto page_size = ENTT_SPARSE_PAGE;
static constexpr entity_type version_mask = Traits::version_mask;
/** /**
* @brief Converts an entity to its underlying type. * @brief Converts an entity to its underlying type.
* @param value The value to convert. * @param value The value to convert.
* @return The integral representation of the given value. * @return The integral representation of the given value.
*/ */
[[nodiscard]] static constexpr entity_type to_integral(const value_type value) noexcept { [[nodiscard]] static constexpr entity_type to_integral(const value_type value) ENTT_NOEXCEPT {
return static_cast<entity_type>(value); return static_cast<entity_type>(value);
} }
@@ -106,8 +88,8 @@ public:
* @param value The value to convert. * @param value The value to convert.
* @return The integral representation of the entity part. * @return The integral representation of the entity part.
*/ */
[[nodiscard]] static constexpr entity_type to_entity(const value_type value) noexcept { [[nodiscard]] static constexpr entity_type to_entity(const value_type value) ENTT_NOEXCEPT {
return (to_integral(value) & entity_mask); return (to_integral(value) & base_type::entity_mask);
} }
/** /**
@@ -115,22 +97,8 @@ public:
* @param value The value to convert. * @param value The value to convert.
* @return The integral representation of the version part. * @return The integral representation of the version part.
*/ */
[[nodiscard]] static constexpr version_type to_version(const value_type value) noexcept { [[nodiscard]] static constexpr version_type to_version(const value_type value) ENTT_NOEXCEPT {
if constexpr(Traits::version_mask == 0u) { return (to_integral(value) >> base_type::entity_shift);
return version_type{};
} else {
return (static_cast<version_type>(to_integral(value) >> length) & version_mask);
}
}
/**
* @brief Returns the successor of a given identifier.
* @param value The identifier of which to return the successor.
* @return The successor of the given identifier.
*/
[[nodiscard]] static constexpr value_type next(const value_type value) noexcept {
const auto vers = to_version(value) + 1;
return construct(to_integral(value), static_cast<version_type>(vers + (vers == version_mask)));
} }
/** /**
@@ -143,12 +111,8 @@ public:
* @param version The version part of the identifier. * @param version The version part of the identifier.
* @return A properly constructed identifier. * @return A properly constructed identifier.
*/ */
[[nodiscard]] static constexpr value_type construct(const entity_type entity, const version_type version) noexcept { [[nodiscard]] static constexpr value_type construct(const entity_type entity, const version_type version) ENTT_NOEXCEPT {
if constexpr(Traits::version_mask == 0u) { return value_type{(entity & base_type::entity_mask) | (static_cast<entity_type>(version) << base_type::entity_shift)};
return value_type{entity & entity_mask};
} else {
return value_type{(entity & entity_mask) | (static_cast<entity_type>(version & version_mask) << length)};
}
} }
/** /**
@@ -161,57 +125,36 @@ public:
* @param rhs The identifier from which to take the version part. * @param rhs The identifier from which to take the version part.
* @return A properly constructed identifier. * @return A properly constructed identifier.
*/ */
[[nodiscard]] static constexpr value_type combine(const entity_type lhs, const entity_type rhs) noexcept { [[nodiscard]] static constexpr value_type combine(const entity_type lhs, const entity_type rhs) ENTT_NOEXCEPT {
if constexpr(Traits::version_mask == 0u) { constexpr auto mask = (base_type::version_mask << base_type::entity_shift);
return value_type{lhs & entity_mask}; return value_type{(lhs & base_type::entity_mask) | (rhs & mask)};
} else {
return value_type{(lhs & entity_mask) | (rhs & (version_mask << length))};
}
} }
}; };
/** /**
* @brief Entity traits. * @copydoc entt_traits<Entity>::to_integral
* @tparam Type Type of identifier.
*/
template<entity_like Type>
struct entt_traits: basic_entt_traits<internal::entt_traits<Type>> {
/*! @brief Base type. */
using base_type = basic_entt_traits<internal::entt_traits<Type>>;
/*! @brief Page size, default is `ENTT_SPARSE_PAGE`. */
static constexpr std::size_t page_size = ENTT_SPARSE_PAGE;
};
/**
* @brief Converts an entity to its underlying type.
* @tparam Entity The value type. * @tparam Entity The value type.
* @param value The value to convert.
* @return The integral representation of the given value.
*/ */
template<typename Entity> template<typename Entity>
[[nodiscard]] constexpr entt_traits<Entity>::entity_type to_integral(const Entity value) noexcept { [[nodiscard]] constexpr typename entt_traits<Entity>::entity_type to_integral(const Entity value) ENTT_NOEXCEPT {
return entt_traits<Entity>::to_integral(value); return entt_traits<Entity>::to_integral(value);
} }
/** /**
* @brief Returns the entity part once converted to the underlying type. * @copydoc entt_traits<Entity>::to_entity
* @tparam Entity The value type. * @tparam Entity The value type.
* @param value The value to convert.
* @return The integral representation of the entity part.
*/ */
template<typename Entity> template<typename Entity>
[[nodiscard]] constexpr entt_traits<Entity>::entity_type to_entity(const Entity value) noexcept { [[nodiscard]] constexpr typename entt_traits<Entity>::entity_type to_entity(const Entity value) ENTT_NOEXCEPT {
return entt_traits<Entity>::to_entity(value); return entt_traits<Entity>::to_entity(value);
} }
/** /**
* @brief Returns the version part once converted to the underlying type. * @copydoc entt_traits<Entity>::to_version
* @tparam Entity The value type. * @tparam Entity The value type.
* @param value The value to convert.
* @return The integral representation of the version part.
*/ */
template<typename Entity> template<typename Entity>
[[nodiscard]] constexpr entt_traits<Entity>::version_type to_version(const Entity value) noexcept { [[nodiscard]] constexpr typename entt_traits<Entity>::version_type to_version(const Entity value) ENTT_NOEXCEPT {
return entt_traits<Entity>::to_version(value); return entt_traits<Entity>::to_version(value);
} }
@@ -222,10 +165,10 @@ struct null_t {
* @tparam Entity Type of identifier. * @tparam Entity Type of identifier.
* @return The null representation for the given type. * @return The null representation for the given type.
*/ */
template<entity_like Entity> template<typename Entity>
[[nodiscard]] constexpr operator Entity() const noexcept { [[nodiscard]] constexpr operator Entity() const ENTT_NOEXCEPT {
using traits_type = entt_traits<Entity>; using entity_traits = entt_traits<Entity>;
return traits_type::construct(traits_type::entity_mask, traits_type::version_mask); return entity_traits::combine(entity_traits::reserved, entity_traits::reserved);
} }
/** /**
@@ -233,23 +176,67 @@ struct null_t {
* @param other A null object. * @param other A null object.
* @return True in all cases. * @return True in all cases.
*/ */
[[nodiscard]] constexpr bool operator==([[maybe_unused]] const null_t other) const noexcept { [[nodiscard]] constexpr bool operator==([[maybe_unused]] const null_t other) const ENTT_NOEXCEPT {
return true; 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 ENTT_NOEXCEPT {
return false;
}
/** /**
* @brief Compares a null object and an identifier of any type. * @brief Compares a null object and an identifier of any type.
* @tparam Entity Type of identifier. * @tparam Entity Type of identifier.
* @param entity Identifier with which to compare. * @param entity Identifier with which to compare.
* @return False if the two elements differ, true otherwise. * @return False if the two elements differ, true otherwise.
*/ */
template<entity_like Entity> template<typename Entity>
[[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept { [[nodiscard]] constexpr bool operator==(const Entity entity) const ENTT_NOEXCEPT {
using traits_type = entt_traits<Entity>; using entity_traits = entt_traits<Entity>;
return traits_type::to_entity(entity) == traits_type::to_entity(*this); 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 ENTT_NOEXCEPT {
return !(entity == *this);
} }
}; };
/**
* @brief Compares a null object and an identifier of any type.
* @tparam Entity Type of identifier.
* @param entity Identifier with which to compare.
* @param other A null object yet to be converted.
* @return False if the two elements differ, true otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator==(const Entity entity, const null_t other) ENTT_NOEXCEPT {
return other.operator==(entity);
}
/**
* @brief Compares a null object and an identifier of any type.
* @tparam Entity Type of identifier.
* @param entity Identifier with which to compare.
* @param other A null object yet to be converted.
* @return True if the two elements differ, false otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator!=(const Entity entity, const null_t other) ENTT_NOEXCEPT {
return !(other == entity);
}
/*! @brief Tombstone object for all identifiers. */ /*! @brief Tombstone object for all identifiers. */
struct tombstone_t { struct tombstone_t {
/** /**
@@ -257,10 +244,10 @@ struct tombstone_t {
* @tparam Entity Type of identifier. * @tparam Entity Type of identifier.
* @return The tombstone representation for the given type. * @return The tombstone representation for the given type.
*/ */
template<entity_like Entity> template<typename Entity>
[[nodiscard]] constexpr operator Entity() const noexcept { [[nodiscard]] constexpr operator Entity() const ENTT_NOEXCEPT {
using traits_type = entt_traits<Entity>; using entity_traits = entt_traits<Entity>;
return traits_type::construct(traits_type::entity_mask, traits_type::version_mask); return entity_traits::combine(entity_traits::reserved, entity_traits::reserved);
} }
/** /**
@@ -268,28 +255,67 @@ struct tombstone_t {
* @param other A tombstone object. * @param other A tombstone object.
* @return True in all cases. * @return True in all cases.
*/ */
[[nodiscard]] constexpr bool operator==([[maybe_unused]] const tombstone_t other) const noexcept { [[nodiscard]] constexpr bool operator==([[maybe_unused]] const tombstone_t other) const ENTT_NOEXCEPT {
return true; return true;
} }
/**
* @brief Compares two tombstone objects.
* @param other A tombstone object.
* @return False in all cases.
*/
[[nodiscard]] constexpr bool operator!=([[maybe_unused]] const tombstone_t other) const ENTT_NOEXCEPT {
return false;
}
/** /**
* @brief Compares a tombstone object and an identifier of any type. * @brief Compares a tombstone object and an identifier of any type.
* @tparam Entity Type of identifier. * @tparam Entity Type of identifier.
* @param entity Identifier with which to compare. * @param entity Identifier with which to compare.
* @return False if the two elements differ, true otherwise. * @return False if the two elements differ, true otherwise.
*/ */
template<entity_like Entity> template<typename Entity>
[[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept { [[nodiscard]] constexpr bool operator==(const Entity entity) const ENTT_NOEXCEPT {
using traits_type = entt_traits<Entity>; using entity_traits = entt_traits<Entity>;
return entity_traits::to_version(entity) == entity_traits::to_version(*this);
}
if constexpr(traits_type::version_mask == 0u) { /**
return false; * @brief Compares a tombstone object and an identifier of any type.
} else { * @tparam Entity Type of identifier.
return (traits_type::to_version(entity) == traits_type::to_version(*this)); * @param entity Identifier with which to compare.
} * @return True if the two elements differ, false otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator!=(const Entity entity) const ENTT_NOEXCEPT {
return !(entity == *this);
} }
}; };
/**
* @brief Compares a tombstone object and an identifier of any type.
* @tparam Entity Type of identifier.
* @param entity Identifier with which to compare.
* @param other A tombstone object yet to be converted.
* @return False if the two elements differ, true otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator==(const Entity entity, const tombstone_t other) ENTT_NOEXCEPT {
return other.operator==(entity);
}
/**
* @brief Compares a tombstone object and an identifier of any type.
* @tparam Entity Type of identifier.
* @param entity Identifier with which to compare.
* @param other A tombstone object yet to be converted.
* @return True if the two elements differ, false otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator!=(const Entity entity, const tombstone_t other) ENTT_NOEXCEPT {
return !(other == entity);
}
/** /**
* @brief Compile-time constant for null entities. * @brief Compile-time constant for null entities.
* *

View File

@@ -1,63 +1,38 @@
#ifndef ENTT_ENTITY_FWD_HPP #ifndef ENTT_ENTITY_FWD_HPP
#define ENTT_ENTITY_FWD_HPP #define ENTT_ENTITY_FWD_HPP
#include <cstdint>
#include <memory> #include <memory>
#include <type_traits>
#include "../config/config.h"
#include "../core/concepts.hpp"
#include "../core/fwd.hpp" #include "../core/fwd.hpp"
#include "../core/type_traits.hpp" #include "utility.hpp"
namespace entt { namespace entt {
/*! @brief Default entity identifier. */ template<typename Entity, typename = std::allocator<Entity>>
enum class entity : id_type {};
/*! @brief Storage 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 Swap-only deletion policy. */
swap_only = 2u,
/*! @brief Unspecified deletion policy. */
unspecified = swap_and_pop
};
template<cvref_unqualified Type, typename Entity = entity>
struct component_traits;
template<typename Entity = entity, typename = std::allocator<Entity>>
class basic_sparse_set; class basic_sparse_set;
template<typename Type, typename = entity, typename = std::allocator<Type>> template<typename, typename Type, typename = std::allocator<Type>, typename = void>
class basic_storage; class basic_storage;
template<typename, typename> template<typename>
class basic_sigh_mixin;
template<typename, typename>
class basic_reactive_mixin;
template<typename Entity = entity, typename = std::allocator<Entity>>
class basic_registry; class basic_registry;
template<typename, typename> template<typename, typename, typename, typename = void>
class basic_view; class basic_view;
template<typename Type, typename = std::allocator<Type *>> template<typename>
class basic_runtime_view; struct basic_runtime_view;
template<typename, typename, typename> template<typename, typename, typename, typename>
class basic_group; class basic_group;
template<typename>
class basic_observer;
template<typename> template<typename>
class basic_organizer; class basic_organizer;
template<typename, typename...> template<typename, typename...>
class basic_handle; struct basic_handle;
template<typename> template<typename>
class basic_snapshot; class basic_snapshot;
@@ -68,223 +43,74 @@ class basic_snapshot_loader;
template<typename> template<typename>
class basic_continuous_loader; class basic_continuous_loader;
/*! @brief Alias declaration for the most common use case. */ /*! @brief Default entity identifier. */
using sparse_set = basic_sparse_set<>; enum class entity : id_type {};
/**
* @brief Alias declaration for the most common use case.
* @tparam Type Element type.
*/
template<typename Type>
using storage = basic_storage<Type>;
/**
* @brief Alias declaration for the most common use case.
* @tparam Type Underlying storage type.
*/
template<typename Type>
using sigh_mixin = basic_sigh_mixin<Type, basic_registry<typename Type::entity_type, typename Type::base_type::allocator_type>>;
/**
* @brief Alias declaration for the most common use case.
* @tparam Type Underlying storage type.
*/
template<typename Type>
using reactive_mixin = basic_reactive_mixin<Type, basic_registry<typename Type::entity_type, typename Type::base_type::allocator_type>>;
/*! @brief Alias declaration for the most common use case. */ /*! @brief Alias declaration for the most common use case. */
using registry = basic_registry<>; using sparse_set = basic_sparse_set<entity>;
/*! @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. * @brief Alias declaration for the most common use case.
* @tparam Args Other template parameters. * @tparam Args Other template parameters.
*/ */
template<typename... Args> template<typename... Args>
using handle_view = basic_handle<registry, Args...>; using storage = basic_storage<entity, Args...>;
/*! @brief Alias declaration for the most common use case. */
using registry = basic_registry<entity>;
/*! @brief Alias declaration for the most common use case. */
using observer = basic_observer<entity>;
/*! @brief Alias declaration for the most common use case. */
using organizer = basic_organizer<entity>;
/*! @brief Alias declaration for the most common use case. */
using handle = basic_handle<entity>;
/*! @brief Alias declaration for the most common use case. */
using const_handle = basic_handle<const entity>;
/** /**
* @brief Alias declaration for the most common use case. * @brief Alias declaration for the most common use case.
* @tparam Args Other template parameters. * @tparam Args Other template parameters.
*/ */
template<typename... Args> template<typename... Args>
using const_handle_view = basic_handle<const registry, Args...>; using handle_view = basic_handle<entity, 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 entity, Args...>;
/*! @brief Alias declaration for the most common use case. */ /*! @brief Alias declaration for the most common use case. */
using snapshot = basic_snapshot<registry>; using snapshot = basic_snapshot<entity>;
/*! @brief Alias declaration for the most common use case. */ /*! @brief Alias declaration for the most common use case. */
using snapshot_loader = basic_snapshot_loader<registry>; using snapshot_loader = basic_snapshot_loader<entity>;
/*! @brief Alias declaration for the most common use case. */ /*! @brief Alias declaration for the most common use case. */
using continuous_loader = basic_continuous_loader<registry>; using continuous_loader = basic_continuous_loader<entity>;
/**
* @brief Alias declaration for the most common use case.
* @tparam Get Types of components iterated by the view.
* @tparam Exclude Types of components used to filter the view.
*/
template<typename Get, typename Exclude = exclude_t<>>
using view = basic_view<entity, Get, Exclude>;
/*! @brief Alias declaration for the most common use case. */ /*! @brief Alias declaration for the most common use case. */
using runtime_view = basic_runtime_view<sparse_set>; 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 for exclusion lists.
* @tparam Type List of types.
*/
template<typename... Type>
struct exclude_t final: type_list<Type...> {
/*! @brief Default constructor. */
explicit ENTT_CONSTEVAL exclude_t() = default;
};
/**
* @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 elements.
* @tparam Type List of types.
*/
template<typename... Type>
struct get_t final: type_list<Type...> {
/*! @brief Default constructor. */
explicit ENTT_CONSTEVAL get_t() = default;
};
/**
* @brief Variable template for lists of observed elements.
* @tparam Type List of types.
*/
template<typename... Type>
inline constexpr get_t<Type...> get{};
/**
* @brief Alias for lists of owned elements.
* @tparam Type List of types.
*/
template<typename... Type>
struct owned_t final: type_list<Type...> {
/*! @brief Default constructor. */
explicit ENTT_CONSTEVAL owned_t() = default;
};
/**
* @brief Variable template for lists of owned elements.
* @tparam Type List of types.
*/
template<typename... Type>
inline constexpr owned_t<Type...> owned{};
/**
* @brief Applies a given _function_ to a get list and generate a new list.
* @tparam Type Types provided by the get 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<get_t<Type...>, Op> {
/*! @brief Resulting get list after applying the transform function. */
using type = get_t<typename Op<Type>::type...>;
};
/**
* @brief Applies a given _function_ to an exclude list and generate a new list.
* @tparam Type Types provided by the exclude 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<exclude_t<Type...>, Op> {
/*! @brief Resulting exclude list after applying the transform function. */
using type = exclude_t<typename Op<Type>::type...>;
};
/**
* @brief Applies a given _function_ to an owned list and generate a new list.
* @tparam Type Types provided by the owned 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<owned_t<Type...>, Op> {
/*! @brief Resulting owned list after applying the transform function. */
using type = owned_t<typename Op<Type>::type...>;
};
/**
* @brief Provides a common way to define storage types.
* @tparam Type Storage value type.
* @tparam Entity A valid entity type.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Type, typename Entity = entity, typename Allocator = std::allocator<Type>>
struct storage_type {
/*! @brief Type-to-storage conversion result. */
using type = ENTT_STORAGE(sigh_mixin, basic_storage<Type, Entity, Allocator>);
};
/*! @brief Empty value type for reactive storage types. */
struct reactive final {};
/**
* @ brief Partial specialization for reactive storage types.
* @tparam Entity A valid entity type.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Entity, typename Allocator>
struct storage_type<reactive, Entity, Allocator> {
/*! @brief Type-to-storage conversion result. */
using type = ENTT_STORAGE(reactive_mixin, basic_storage<reactive, Entity, Allocator>);
};
/**
* @brief Helper type.
* @tparam Args Arguments to forward.
*/
template<typename... Args>
using storage_type_t = 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.
* @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 = storage_for<Args...>::type;
/** /**
* @brief Alias declaration for the most common use case. * @brief Alias declaration for the most common use case.
* @tparam Get Types of storage iterated by the view. * @tparam Args Other template parameters.
* @tparam Exclude Types of storage used to filter the view.
*/ */
template<typename Get, typename Exclude = exclude_t<>> template<typename... Args>
using view = basic_view<type_list_transform_t<Get, storage_for>, type_list_transform_t<Exclude, storage_for>>; using group = basic_group<entity, Args...>;
/**
* @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 = get_t<>, typename Exclude = exclude_t<>>
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 } // namespace entt

File diff suppressed because it is too large Load Diff

View File

@@ -1,115 +1,38 @@
#ifndef ENTT_ENTITY_HANDLE_HPP #ifndef ENTT_ENTITY_HANDLE_HPP
#define ENTT_ENTITY_HANDLE_HPP #define ENTT_ENTITY_HANDLE_HPP
#include <iterator>
#include <tuple> #include <tuple>
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
#include "../config/config.h" #include "../config/config.h"
#include "../core/iterator.hpp"
#include "../core/type_traits.hpp" #include "../core/type_traits.hpp"
#include "entity.hpp"
#include "fwd.hpp" #include "fwd.hpp"
#include "registry.hpp"
namespace entt { namespace entt {
/*! @cond ENTT_INTERNAL */
namespace internal {
template<typename It>
class handle_storage_iterator final {
template<typename>
friend class handle_storage_iterator;
using underlying_type = std::remove_reference_t<typename It::value_type::second_type>;
using entity_type = underlying_type::entity_type;
public:
using value_type = 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;
using iterator_concept = std::forward_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 {
for(++it; it != last && !it->second.contains(entt); ++it) {}
return *this;
}
constexpr handle_storage_iterator operator++(int) noexcept {
const 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 Other>
[[nodiscard]] constexpr bool operator==(const handle_storage_iterator<Other> &other) const noexcept {
return it == other.it;
}
private:
entity_type entt;
It it;
It last;
};
} // namespace internal
/*! @endcond */
/** /**
* @brief Non-owning handle to an entity. * @brief Non-owning handle to an entity.
* *
* Tiny wrapper around a registry and an entity. * Tiny wrapper around a registry and an entity.
* *
* @tparam Registry Basic registry type. * @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Scope Types to which to restrict the scope of a handle. * @tparam Type Types to which to restrict the scope of a handle.
*/ */
template<typename Registry, typename... Scope> template<typename Entity, typename... Type>
class basic_handle { struct basic_handle {
using traits_type = entt_traits<typename Registry::entity_type>;
[[nodiscard]] auto &owner_or_assert() const noexcept {
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
return static_cast<Registry &>(*owner);
}
public:
/*! @brief Type of registry accepted by the handle. */ /*! @brief Type of registry accepted by the handle. */
using registry_type = Registry; using registry_type = constness_as_t<basic_registry<std::remove_const_t<Entity>>, Entity>;
/*! @brief Underlying entity identifier. */ /*! @brief Underlying entity identifier. */
using entity_type = traits_type::value_type; using entity_type = typename registry_type::entity_type;
/*! @brief Underlying version type. */ /*! @brief Underlying version type. */
using version_type = traits_type::version_type; using version_type = typename registry_type::version_type;
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
using size_type = std::size_t; using size_type = typename registry_type::size_type;
/*! @brief Iterable handle type. */
using iterable = iterable_adaptor<internal::handle_storage_iterator<typename decltype(std::declval<registry_type>().storage())::iterator>>;
/*! @brief Constructs an invalid handle. */ /*! @brief Constructs an invalid handle. */
basic_handle() noexcept basic_handle() ENTT_NOEXCEPT
: owner{}, : reg{},
entt{null} {} entt{null} {}
/** /**
@@ -117,252 +40,301 @@ public:
* @param ref An instance of the registry class. * @param ref An instance of the registry class.
* @param value A valid identifier. * @param value A valid identifier.
*/ */
basic_handle(registry_type &ref, entity_type value) noexcept basic_handle(registry_type &ref, entity_type value) ENTT_NOEXCEPT
: owner{&ref}, : reg{&ref},
entt{value} {} entt{value} {}
/** /**
* @brief Returns an iterable object to use to _visit_ a handle. * @brief Constructs a const handle from a non-const one.
* * @tparam Other A valid entity type (see entt_traits for more details).
* The iterable object returns a pair that contains the name and a reference * @tparam Args Scope of the handle to construct.
* to the current storage.<br/> * @return A const handle referring to the same registry and the same
* Returned storage are those that contain the entity associated with the * entity.
* handle.
*
* @return An iterable object to use to _visit_ the handle.
*/ */
[[nodiscard]] iterable storage() const noexcept { template<typename Other, typename... Args>
auto underlying = owner_or_assert().storage(); operator basic_handle<Other, Args...>() const ENTT_NOEXCEPT {
return iterable{{entt, underlying.begin(), underlying.end()}, {entt, underlying.end(), underlying.end()}}; static_assert(std::is_same_v<Other, Entity> || std::is_same_v<std::remove_const_t<Other>, Entity>, "Invalid conversion between different handles");
} static_assert((sizeof...(Type) == 0 || ((sizeof...(Args) != 0 && sizeof...(Args) <= sizeof...(Type)) && ... && (type_list_contains_v<type_list<Type...>, Args>))), "Invalid conversion between different handles");
/*! @copydoc valid */ return reg ? basic_handle<Other, Args...>{*reg, entt} : basic_handle<Other, Args...>{};
[[nodiscard]] explicit operator bool() const noexcept {
return owner && owner->valid(entt);
} }
/** /**
* @brief Checks if a handle refers to a valid registry and entity. * @brief Converts a handle to its underlying entity.
* @return True if the handle refers to a valid registry and entity, false * @return The contained identifier.
* otherwise. */
[[nodiscard]] operator entity_type() const ENTT_NOEXCEPT {
return entity();
}
/**
* @brief Checks if a handle refers to non-null registry pointer and entity.
* @return True if the handle refers to non-null registry and entity, false otherwise.
*/
[[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT {
return reg && reg->valid(entt);
}
/**
* @brief Checks if a handle refers to a valid entity or not.
* @return True if the handle refers to a valid entity, false otherwise.
*/ */
[[nodiscard]] bool valid() const { [[nodiscard]] bool valid() const {
return static_cast<bool>(*this); return reg->valid(entt);
} }
/** /**
* @brief Returns a pointer to the underlying registry, if any. * @brief Returns a pointer to the underlying registry, if any.
* @return A pointer to the underlying registry, if any. * @return A pointer to the underlying registry, if any.
*/ */
[[nodiscard]] registry_type *registry() const noexcept { [[nodiscard]] registry_type *registry() const ENTT_NOEXCEPT {
return owner; return reg;
} }
/** /**
* @brief Returns the entity associated with a handle. * @brief Returns the entity associated with a handle.
* @return The entity associated with the handle. * @return The entity associated with the handle.
*/ */
[[nodiscard]] entity_type entity() const noexcept { [[nodiscard]] entity_type entity() const ENTT_NOEXCEPT {
return entt; return entt;
} }
/*! @copydoc entity */
[[nodiscard]] operator entity_type() const noexcept {
return entity();
}
/*! @brief Destroys the entity associated with a handle. */
void destroy() {
owner_or_assert().destroy(std::exchange(entt, null));
}
/** /**
* @brief Destroys the entity associated with a handle. * @brief Destroys the entity associated with a handle.
* @sa basic_registry::destroy
*/
void destroy() {
reg->destroy(entt);
}
/**
* @brief Destroys the entity associated with a handle.
* @sa basic_registry::destroy
* @param version A desired version upon destruction. * @param version A desired version upon destruction.
*/ */
void destroy(const version_type version) { void destroy(const version_type version) {
owner_or_assert().destroy(std::exchange(entt, null), version); reg->destroy(entt, version);
} }
/** /**
* @brief Assigns the given element to a handle. * @brief Assigns the given component to a handle.
* @tparam Type Type of element to create. * @sa basic_registry::emplace
* @tparam Args Types of arguments to use to construct the element. * @tparam Component Type of component to create.
* @param args Parameters to use to initialize the element. * @tparam Args Types of arguments to use to construct the component.
* @return A reference to the newly created element. * @param args Parameters to use to initialize the component.
* @return A reference to the newly created component.
*/ */
template<typename Type, typename... Args> template<typename Component, typename... Args>
// NOLINTNEXTLINE(modernize-use-nodiscard)
decltype(auto) emplace(Args &&...args) const { decltype(auto) emplace(Args &&...args) const {
static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v<Type, Scope>), "Invalid type"); static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v<Component, Type>), "Invalid type");
return owner_or_assert().template emplace<Type>(entt, std::forward<Args>(args)...); return reg->template emplace<Component>(entt, std::forward<Args>(args)...);
} }
/** /**
* @brief Assigns or replaces the given element for a handle. * @brief Assigns or replaces the given component for a handle.
* @tparam Type Type of element to assign or replace. * @sa basic_registry::emplace_or_replace
* @tparam Args Types of arguments to use to construct the element. * @tparam Component Type of component to assign or replace.
* @param args Parameters to use to initialize the element. * @tparam Args Types of arguments to use to construct the component.
* @return A reference to the newly created element. * @param args Parameters to use to initialize the component.
* @return A reference to the newly created component.
*/ */
template<typename Type, typename... Args> template<typename Component, typename... Args>
decltype(auto) emplace_or_replace(Args &&...args) const { decltype(auto) emplace_or_replace(Args &&...args) const {
static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v<Type, Scope>), "Invalid type"); static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v<Component, Type>), "Invalid type");
return owner_or_assert().template emplace_or_replace<Type>(entt, std::forward<Args>(args)...); return reg->template emplace_or_replace<Component>(entt, std::forward<Args>(args)...);
} }
/** /**
* @brief Patches the given element for a handle. * @brief Patches the given component for a handle.
* @tparam Type Type of element to patch. * @sa basic_registry::patch
* @tparam Component Type of component to patch.
* @tparam Func Types of the function objects to invoke. * @tparam Func Types of the function objects to invoke.
* @param func Valid function objects. * @param func Valid function objects.
* @return A reference to the patched element. * @return A reference to the patched component.
*/ */
template<typename Type, typename... Func> template<typename Component, typename... Func>
decltype(auto) patch(Func &&...func) const { decltype(auto) patch(Func &&...func) const {
static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v<Type, Scope>), "Invalid type"); static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v<Component, Type>), "Invalid type");
return owner_or_assert().template patch<Type>(entt, std::forward<Func>(func)...); return reg->template patch<Component>(entt, std::forward<Func>(func)...);
} }
/** /**
* @brief Replaces the given element for a handle. * @brief Replaces the given component for a handle.
* @tparam Type Type of element to replace. * @sa basic_registry::replace
* @tparam Args Types of arguments to use to construct the element. * @tparam Component Type of component to replace.
* @param args Parameters to use to initialize the element. * @tparam Args Types of arguments to use to construct the component.
* @return A reference to the element being replaced. * @param args Parameters to use to initialize the component.
* @return A reference to the component being replaced.
*/ */
template<typename Type, typename... Args> template<typename Component, typename... Args>
decltype(auto) replace(Args &&...args) const { decltype(auto) replace(Args &&...args) const {
static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v<Type, Scope>), "Invalid type"); static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v<Component, Type>), "Invalid type");
return owner_or_assert().template replace<Type>(entt, std::forward<Args>(args)...); return reg->template replace<Component>(entt, std::forward<Args>(args)...);
} }
/** /**
* @brief Removes the given elements from a handle. * @brief Removes the given components from a handle.
* @tparam Type Types of elements to remove. * @sa basic_registry::remove
* @return The number of elements actually removed. * @tparam Component Types of components to remove.
* @return The number of components actually removed.
*/ */
template<typename... Type> template<typename... Component>
// NOLINTNEXTLINE(modernize-use-nodiscard)
size_type remove() const { size_type remove() const {
static_assert(sizeof...(Scope) == 0 || (type_list_contains_v<type_list<Scope...>, Type> && ...), "Invalid type"); static_assert(sizeof...(Type) == 0 || (type_list_contains_v<type_list<Type...>, Component> && ...), "Invalid type");
return owner_or_assert().template remove<Type...>(entt); return reg->template remove<Component...>(entt);
} }
/** /**
* @brief Erases the given elements from a handle. * @brief Erases the given components from a handle.
* @tparam Type Types of elements to erase. * @sa basic_registry::erase
* @tparam Component Types of components to erase.
*/ */
template<typename... Type> template<typename... Component>
void erase() const { void erase() const {
static_assert(sizeof...(Scope) == 0 || (type_list_contains_v<type_list<Scope...>, Type> && ...), "Invalid type"); static_assert(sizeof...(Type) == 0 || (type_list_contains_v<type_list<Type...>, Component> && ...), "Invalid type");
owner_or_assert().template erase<Type...>(entt); reg->template erase<Component...>(entt);
} }
/** /**
* @brief Checks if a handle has all the given elements. * @brief Checks if a handle has all the given components.
* @tparam Type Elements for which to perform the check. * @sa basic_registry::all_of
* @return True if the handle has all the elements, false otherwise. * @tparam Component Components for which to perform the check.
* @return True if the handle has all the components, false otherwise.
*/ */
template<typename... Type> template<typename... Component>
[[nodiscard]] decltype(auto) all_of() const { [[nodiscard]] decltype(auto) all_of() const {
return owner_or_assert().template all_of<Type...>(entt); return reg->template all_of<Component...>(entt);
} }
/** /**
* @brief Checks if a handle has at least one of the given elements. * @brief Checks if a handle has at least one of the given components.
* @tparam Type Elements for which to perform the check. * @sa basic_registry::any_of
* @return True if the handle has at least one of the given elements, * @tparam Component Components for which to perform the check.
* @return True if the handle has at least one of the given components,
* false otherwise. * false otherwise.
*/ */
template<typename... Type> template<typename... Component>
[[nodiscard]] decltype(auto) any_of() const { [[nodiscard]] decltype(auto) any_of() const {
return owner_or_assert().template any_of<Type...>(entt); return reg->template any_of<Component...>(entt);
} }
/** /**
* @brief Returns references to the given elements for a handle. * @brief Returns references to the given components for a handle.
* @tparam Type Types of elements to get. * @sa basic_registry::get
* @return References to the elements owned by the handle. * @tparam Component Types of components to get.
* @return References to the components owned by the handle.
*/ */
template<typename... Type> template<typename... Component>
[[nodiscard]] decltype(auto) get() const { [[nodiscard]] decltype(auto) get() const {
static_assert(sizeof...(Scope) == 0 || (type_list_contains_v<type_list<Scope...>, Type> && ...), "Invalid type"); static_assert(sizeof...(Type) == 0 || (type_list_contains_v<type_list<Type...>, Component> && ...), "Invalid type");
return owner_or_assert().template get<Type...>(entt); return reg->template get<Component...>(entt);
} }
/** /**
* @brief Returns a reference to the given element for a handle. * @brief Returns a reference to the given component for a handle.
* @tparam Type Type of element to get. * @sa basic_registry::get_or_emplace
* @tparam Args Types of arguments to use to construct the element. * @tparam Component Type of component to get.
* @param args Parameters to use to initialize the element. * @tparam Args Types of arguments to use to construct the component.
* @return Reference to the element owned by the handle. * @param args Parameters to use to initialize the component.
* @return Reference to the component owned by the handle.
*/ */
template<typename Type, typename... Args> template<typename Component, typename... Args>
[[nodiscard]] decltype(auto) get_or_emplace(Args &&...args) const { [[nodiscard]] decltype(auto) get_or_emplace(Args &&...args) const {
static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v<Type, Scope>), "Invalid type"); static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v<Component, Type>), "Invalid type");
return owner_or_assert().template get_or_emplace<Type>(entt, std::forward<Args>(args)...); return reg->template get_or_emplace<Component>(entt, std::forward<Args>(args)...);
} }
/** /**
* @brief Returns pointers to the given elements for a handle. * @brief Returns pointers to the given components for a handle.
* @tparam Type Types of elements to get. * @sa basic_registry::try_get
* @return Pointers to the elements owned by the handle. * @tparam Component Types of components to get.
* @return Pointers to the components owned by the handle.
*/ */
template<typename... Type> template<typename... Component>
[[nodiscard]] auto try_get() const { [[nodiscard]] auto try_get() const {
static_assert(sizeof...(Scope) == 0 || (type_list_contains_v<type_list<Scope...>, Type> && ...), "Invalid type"); static_assert(sizeof...(Type) == 0 || (type_list_contains_v<type_list<Type...>, Component> && ...), "Invalid type");
return owner_or_assert().template try_get<Type...>(entt); return reg->template try_get<Component...>(entt);
} }
/** /**
* @brief Checks if a handle has elements assigned. * @brief Checks if a handle has components assigned.
* @return True if the handle has no elements assigned, false otherwise. * @return True if the handle has no components assigned, false otherwise.
*/ */
[[nodiscard]] bool orphan() const { [[nodiscard]] bool orphan() const {
return owner_or_assert().orphan(entt); return reg->orphan(entt);
} }
/** /**
* @brief Compares two handles. * @brief Visits a handle and returns the pools for its components.
* @tparam Other Scope of the other handle. *
* @param other A valid handle. * The signature of the function should be equivalent to the following:
* @return True if both handles refer to the same registry and the same *
* entity, false otherwise. * @code{.cpp}
* void(id_type, const basic_sparse_set<entity_type> &);
* @endcode
*
* Returned pools are those that contain the entity associated with the
* handle.
*
* @tparam Func Type of the function object to invoke.
* @param func A valid function object.
*/ */
template<typename... Other> template<typename Func>
[[nodiscard]] bool operator==(const basic_handle<Other...> &other) const noexcept { void visit(Func &&func) const {
return owner == other.registry() && entt == other.entity(); for(auto [id, storage]: reg->storage()) {
} if(storage.contains(entt)) {
func(id, storage);
/** }
* @brief Compares a handle with the null object. }
* @param other A null object yet to be converted.
* @return False if the two elements differ, true otherwise.
*/
[[nodiscard]] constexpr bool operator==(const null_t other) const noexcept {
return (entt == other);
}
/**
* @brief Returns a const handle from a non-const one.
* @tparam Other A valid entity type.
* @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 owner ? basic_handle<Other, Args...>{*owner, entt} : basic_handle<Other, Args...>{};
} }
private: private:
registry_type *owner; registry_type *reg;
entity_type entt; entity_type entt;
}; };
/**
* @brief Compares two handles.
* @tparam Args Scope of the first handle.
* @tparam Other Scope of the second handle.
* @param lhs A valid handle.
* @param rhs A valid handle.
* @return True if both handles refer to the same registry and the same
* entity, false otherwise.
*/
template<typename... Args, typename... Other>
[[nodiscard]] bool operator==(const basic_handle<Args...> &lhs, const basic_handle<Other...> &rhs) ENTT_NOEXCEPT {
return lhs.registry() == rhs.registry() && lhs.entity() == rhs.entity();
}
/**
* @brief Compares two handles.
* @tparam Args Scope of the first handle.
* @tparam Other Scope of the second handle.
* @param lhs A valid handle.
* @param rhs A valid handle.
* @return False if both handles refer to the same registry and the same
* entity, true otherwise.
*/
template<typename... Args, typename... Other>
[[nodiscard]] bool operator!=(const basic_handle<Args...> &lhs, const basic_handle<Other...> &rhs) ENTT_NOEXCEPT {
return !(lhs == rhs);
}
/**
* @brief Deduction guide.
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
basic_handle(basic_registry<Entity> &, Entity) -> basic_handle<Entity>;
/**
* @brief Deduction guide.
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
basic_handle(const basic_registry<Entity> &, Entity) -> basic_handle<const Entity>;
} // namespace entt } // namespace entt
#endif #endif

View File

@@ -1,255 +1,155 @@
#ifndef ENTT_ENTITY_HELPER_HPP #ifndef ENTT_ENTITY_HELPER_HPP
#define ENTT_ENTITY_HELPER_HPP #define ENTT_ENTITY_HELPER_HPP
#include <memory>
#include <type_traits> #include <type_traits>
#include <utility> #include "../config/config.h"
#include "../core/fwd.hpp" #include "../core/fwd.hpp"
#include "../core/type_traits.hpp" #include "../core/type_traits.hpp"
#include "component.hpp" #include "../signal/delegate.hpp"
#include "fwd.hpp" #include "fwd.hpp"
#include "group.hpp" #include "registry.hpp"
#include "storage.hpp"
#include "view.hpp"
namespace entt { namespace entt {
/** /**
* @brief Converts a registry to a view. * @brief Converts a registry to a view.
* @tparam Registry Basic registry type. * @tparam Entity A valid entity type (see entt_traits for more details).
*/ */
template<typename Registry> template<typename Entity>
class as_view { struct as_view {
template<typename... Get, typename... Exclude>
[[nodiscard]] auto dispatch(get_t<Get...>, exclude_t<Exclude...>) const {
return reg->template view<constness_as_t<typename Get::element_type, Get>...>(exclude_t<constness_as_t<typename Exclude::element_type, Exclude>...>{});
}
public:
/*! @brief Type of registry to convert. */
using registry_type = Registry;
/*! @brief Underlying entity identifier. */ /*! @brief Underlying entity identifier. */
using entity_type = registry_type::entity_type; using entity_type = std::remove_const_t<Entity>;
/*! @brief Type of registry to convert. */
using registry_type = constness_as_t<basic_registry<entity_type>, Entity>;
/** /**
* @brief Constructs a converter for a given registry. * @brief Constructs a converter for a given registry.
* @param source A valid reference to a registry. * @param source A valid reference to a registry.
*/ */
as_view(registry_type &source) noexcept as_view(registry_type &source) ENTT_NOEXCEPT: reg{source} {}
: reg{&source} {}
/** /**
* @brief Conversion function from a registry to a view. * @brief Conversion function from a registry to a view.
* @tparam Get Type of storage used to construct the view. * @tparam Exclude Types of components used to filter the view.
* @tparam Exclude Types of storage used to filter the view. * @tparam Component Type of components used to construct the view.
* @return A newly created view. * @return A newly created view.
*/ */
template<typename Get, typename Exclude> template<typename Exclude, typename... Component>
operator basic_view<Get, Exclude>() const { operator basic_view<entity_type, get_t<Component...>, Exclude>() const {
return dispatch(Get{}, Exclude{}); return reg.template view<Component...>(Exclude{});
} }
private: private:
registry_type *reg; registry_type &reg;
};
/**
* @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>
[[nodiscard]] 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::element_type...>(get_t<typename Get::element_type...>{}, exclude_t<typename Exclude::element_type...>{});
} else {
return reg->template group<constness_as_t<typename Owned::element_type, Owned>...>(get_t<constness_as_t<typename Get::element_type, Get>...>{}, exclude_t<constness_as_t<typename Exclude::element_type, Exclude>...>{});
}
}
public:
/*! @brief Type of registry to convert. */
using registry_type = Registry;
/*! @brief Underlying entity identifier. */
using entity_type = 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 *reg;
};
/**
* @brief Helper to create a listener that directly invokes a member function.
* @tparam Member Member function to invoke on an element of the given type.
* @tparam Registry Basic registry type.
* @param reg A registry that contains the given entity and its elements.
* @param entt Entity from which to get the element.
*/
template<auto Member, typename Registry = std::decay_t<nth_argument_t<0u, decltype(Member)>>>
void invoke(Registry &reg, const typename Registry::entity_type entt) {
static_assert(std::is_member_function_pointer_v<decltype(Member)>, "Invalid pointer to non-static member function");
(reg.template get<member_class_t<decltype(Member)>>(entt).*Member)(reg, entt);
}
/**
* @brief Returns the entity associated with a given element.
*
* @warning
* Currently, this function only works correctly with the default storage as it
* makes assumptions about how the elements are laid out.
*
* @tparam Args Storage type template parameters.
* @param storage A storage that contains the given element.
* @param instance A valid element instance.
* @return The entity associated with the given element.
*/
template<typename... Args>
basic_storage<Args...>::entity_type to_entity(const basic_storage<Args...> &storage, const typename basic_storage<Args...>::value_type &instance) {
using traits_type = component_traits<typename basic_storage<Args...>::value_type, typename basic_storage<Args...>::entity_type>;
static_assert(traits_type::page_size != 0u, "Unexpected page size");
const auto *page = storage.raw();
// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
for(std::size_t pos{}, count = storage.size(); pos < count; pos += traits_type::page_size, ++page) {
if(const auto dist = (std::addressof(instance) - *page); dist >= 0 && dist < static_cast<decltype(dist)>(traits_type::page_size)) {
return *(static_cast<const basic_storage<Args...>::base_type &>(storage).rbegin() + static_cast<decltype(dist)>(pos) + dist);
}
}
// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
return null;
}
/*! @brief Primary template isn't defined on purpose. */
template<typename...>
struct sigh_helper;
/**
* @brief Signal connection helper for registries.
* @tparam Registry Basic registry type.
*/
template<typename Registry>
struct sigh_helper<Registry> {
/*! @brief Registry type. */
using registry_type = Registry;
/**
* @brief Constructs a helper for a given registry.
* @param ref A valid reference to a registry.
*/
sigh_helper(registry_type &ref)
: bucket{&ref} {}
/**
* @brief Binds a properly initialized helper to a given signal type.
* @tparam Type Type of signal to bind the helper to.
* @param id Optional name for the underlying storage to use.
* @return A helper for a given registry and signal type.
*/
template<typename Type>
auto with(const id_type id = type_hash<Type>::value()) noexcept {
return sigh_helper<registry_type, Type>{*bucket, id};
}
/**
* @brief Returns a reference to the underlying registry.
* @return A reference to the underlying registry.
*/
[[nodiscard]] registry_type &registry() noexcept {
return *bucket;
}
private:
registry_type *bucket;
};
/**
* @brief Signal connection helper for registries.
* @tparam Registry Basic registry type.
* @tparam Type Type of signal to connect listeners to.
*/
template<typename Registry, typename Type>
struct sigh_helper<Registry, Type> final: sigh_helper<Registry> {
/*! @brief Registry type. */
using registry_type = Registry;
/**
* @brief Constructs a helper for a given registry.
* @param ref A valid reference to a registry.
* @param id Optional name for the underlying storage to use.
*/
sigh_helper(registry_type &ref, const id_type id = type_hash<Type>::value())
: sigh_helper<Registry>{ref},
name{id} {}
/**
* @brief Forwards the call to `on_construct` on the underlying storage.
* @tparam Candidate Function or member to connect.
* @tparam Args Type of class or type of payload, if any.
* @param args A valid object that fits the purpose, if any.
* @return This helper.
*/
template<auto Candidate, typename... Args>
auto on_construct(Args &&...args) {
this->registry().template on_construct<Type>(name).template connect<Candidate>(std::forward<Args>(args)...);
return *this;
}
/**
* @brief Forwards the call to `on_update` on the underlying storage.
* @tparam Candidate Function or member to connect.
* @tparam Args Type of class or type of payload, if any.
* @param args A valid object that fits the purpose, if any.
* @return This helper.
*/
template<auto Candidate, typename... Args>
auto on_update(Args &&...args) {
this->registry().template on_update<Type>(name).template connect<Candidate>(std::forward<Args>(args)...);
return *this;
}
/**
* @brief Forwards the call to `on_destroy` on the underlying storage.
* @tparam Candidate Function or member to connect.
* @tparam Args Type of class or type of payload, if any.
* @param args A valid object that fits the purpose, if any.
* @return This helper.
*/
template<auto Candidate, typename... Args>
auto on_destroy(Args &&...args) {
this->registry().template on_destroy<Type>(name).template connect<Candidate>(std::forward<Args>(args)...);
return *this;
}
private:
id_type name;
}; };
/** /**
* @brief Deduction guide. * @brief Deduction guide.
* @tparam Registry Basic registry type. * @tparam Entity A valid entity type (see entt_traits for more details).
*/ */
template<typename Registry> template<typename Entity>
sigh_helper(Registry &) -> sigh_helper<Registry>; as_view(basic_registry<Entity> &) -> as_view<Entity>;
/**
* @brief Deduction guide.
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
as_view(const basic_registry<Entity> &) -> as_view<const Entity>;
/**
* @brief Converts a registry to a group.
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
struct as_group {
/*! @brief Underlying entity identifier. */
using entity_type = std::remove_const_t<Entity>;
/*! @brief Type of registry to convert. */
using registry_type = constness_as_t<basic_registry<entity_type>, Entity>;
/**
* @brief Constructs a converter for a given registry.
* @param source A valid reference to a registry.
*/
as_group(registry_type &source) ENTT_NOEXCEPT: reg{source} {}
/**
* @brief Conversion function from a registry to a group.
* @tparam Get Types of components observed by the group.
* @tparam Exclude Types of components used to filter the group.
* @tparam Owned Types of components owned by the group.
* @return A newly created group.
*/
template<typename Get, typename Exclude, typename... Owned>
operator basic_group<entity_type, owned_t<Owned...>, Get, Exclude>() const {
if constexpr(std::is_const_v<registry_type>) {
return reg.template group_if_exists<Owned...>(Get{}, Exclude{});
} else {
return reg.template group<Owned...>(Get{}, Exclude{});
}
}
private:
registry_type &reg;
};
/**
* @brief Deduction guide.
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
as_group(basic_registry<Entity> &) -> as_group<Entity>;
/**
* @brief Deduction guide.
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
as_group(const basic_registry<Entity> &) -> as_group<const Entity>;
/**
* @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 Entity A valid entity type (see entt_traits for more details).
* @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 Entity = entity>
void invoke(basic_registry<Entity> &reg, const Entity entt) {
static_assert(std::is_member_function_pointer_v<decltype(Member)>, "Invalid pointer to non-static member function");
delegate<void(basic_registry<Entity> &, const Entity)> 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 Entity A valid entity type (see entt_traits for more details).
* @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 Entity, typename Component>
Entity to_entity(const basic_registry<Entity> &reg, const Component &instance) {
const auto &storage = reg.template storage<Component>();
const typename basic_registry<Entity>::base_type &base = storage;
const auto *addr = std::addressof(instance);
for(auto it = base.rbegin(), last = base.rend(); it < last; it += ENTT_PACKED_PAGE) {
if(const auto dist = (addr - std::addressof(storage.get(*it))); dist >= 0 && dist < ENTT_PACKED_PAGE) {
return *(it + dist);
}
}
return null;
}
} // namespace entt } // namespace entt

View File

@@ -1,592 +0,0 @@
#ifndef ENTT_ENTITY_MIXIN_HPP
#define ENTT_ENTITY_MIXIN_HPP
#include <concepts>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "../core/any.hpp"
#include "../core/type_info.hpp"
#include "../signal/sigh.hpp"
#include "../stl/iterator.hpp"
#include "entity.hpp"
#include "fwd.hpp"
namespace entt {
/*! @cond ENTT_INTERNAL */
namespace internal {
template<typename, typename>
struct has_on_construct final: std::false_type {};
template<typename Type, typename Registry>
requires std::invocable<decltype(&Type::on_construct), Registry &, typename Registry::entity_type>
struct has_on_construct<Type, Registry>: std::true_type {};
template<typename, typename>
struct has_on_update final: std::false_type {};
template<typename Type, typename Registry>
requires std::invocable<decltype(&Type::on_update), Registry &, typename Registry::entity_type>
struct has_on_update<Type, Registry>: std::true_type {};
template<typename, typename>
struct has_on_destroy final: std::false_type {};
template<typename Type, typename Registry>
requires std::invocable<decltype(&Type::on_destroy), Registry &, typename Registry::entity_type>
struct has_on_destroy<Type, Registry>: std::true_type {};
} // namespace internal
/*! @endcond */
/**
* @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 Underlying storage type.
* @tparam Registry Basic registry type.
*/
template<typename Type, typename Registry>
class basic_sigh_mixin final: public Type {
using underlying_type = Type;
using owner_type = Registry;
using basic_registry_type = basic_registry<typename owner_type::entity_type, typename owner_type::allocator_type>;
using sigh_type = sigh<void(owner_type &, const typename underlying_type::entity_type), typename underlying_type::allocator_type>;
using underlying_iterator = underlying_type::base_type::basic_iterator;
static_assert(std::is_base_of_v<basic_registry_type, owner_type>, "Invalid registry type");
[[nodiscard]] auto &owner_or_assert() const noexcept {
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
return static_cast<owner_type &>(*owner);
}
private:
void pop(underlying_iterator first, underlying_iterator last) final {
if(auto &reg = owner_or_assert(); destruction.empty()) {
underlying_type::pop(first, last);
} else {
for(; first != last; ++first) {
const auto entt = *first;
destruction.publish(reg, entt);
const auto it = underlying_type::find(entt);
underlying_type::pop(it, it + 1u);
}
}
}
void pop_all() final {
if(auto &reg = owner_or_assert(); !destruction.empty()) {
if constexpr(std::is_same_v<typename underlying_type::element_type, entity_type>) {
for(typename underlying_type::size_type pos{}, last = underlying_type::free_list(); pos < last; ++pos) {
destruction.publish(reg, underlying_type::base_type::operator[](pos));
}
} else {
for(auto entt: static_cast<underlying_type::base_type &>(*this)) {
if constexpr(underlying_type::storage_policy == deletion_policy::in_place) {
if(entt != tombstone) {
destruction.publish(reg, entt);
}
} else {
destruction.publish(reg, entt);
}
}
}
}
underlying_type::pop_all();
}
underlying_iterator try_emplace(const underlying_type::entity_type entt, const bool force_back, const void *value) final {
const auto it = underlying_type::try_emplace(entt, force_back, value);
if(auto &reg = owner_or_assert(); it != underlying_type::base_type::end()) {
construction.publish(reg, *it);
}
return it;
}
void bind_any(any value) noexcept final {
owner = any_cast<basic_registry_type>(&value);
if constexpr(!std::is_same_v<registry_type, basic_registry_type>) {
if(owner == nullptr) {
owner = any_cast<registry_type>(&value);
}
}
underlying_type::bind_any(std::move(value));
}
public:
/*! @brief Allocator type. */
using allocator_type = underlying_type::allocator_type;
/*! @brief Underlying entity identifier. */
using entity_type = underlying_type::entity_type;
/*! @brief Expected registry type. */
using registry_type = owner_type;
/*! @brief Default constructor. */
basic_sigh_mixin()
: basic_sigh_mixin{allocator_type{}} {}
/**
* @brief Constructs an empty storage with a given allocator.
* @param allocator The allocator to use.
*/
explicit basic_sigh_mixin(const allocator_type &allocator)
: underlying_type{allocator},
owner{},
construction{allocator},
destruction{allocator},
update{allocator} {
if constexpr(internal::has_on_construct<typename underlying_type::element_type, Registry>::value) {
sink{construction}.template connect<&underlying_type::element_type::on_construct>();
}
if constexpr(internal::has_on_update<typename underlying_type::element_type, Registry>::value) {
sink{update}.template connect<&underlying_type::element_type::on_update>();
}
if constexpr(internal::has_on_destroy<typename underlying_type::element_type, Registry>::value) {
sink{destruction}.template connect<&underlying_type::element_type::on_destroy>();
}
}
/*! @brief Default copy constructor, deleted on purpose. */
basic_sigh_mixin(const basic_sigh_mixin &) = delete;
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
basic_sigh_mixin(basic_sigh_mixin &&other) noexcept
: underlying_type{static_cast<underlying_type &&>(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.
*/
basic_sigh_mixin(basic_sigh_mixin &&other, const allocator_type &allocator)
: underlying_type{static_cast<underlying_type &&>(other), allocator},
owner{other.owner},
construction{std::move(other.construction), allocator},
destruction{std::move(other.destruction), allocator},
update{std::move(other.update), allocator} {}
/*! @brief Default destructor. */
~basic_sigh_mixin() override = default;
/**
* @brief Default copy assignment operator, deleted on purpose.
* @return This mixin.
*/
basic_sigh_mixin &operator=(const basic_sigh_mixin &) = delete;
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This mixin.
*/
basic_sigh_mixin &operator=(basic_sigh_mixin &&other) noexcept {
swap(other);
return *this;
}
/**
* @brief Exchanges the contents with those of a given storage.
* @param other Storage to exchange the content with.
*/
void swap(basic_sigh_mixin &other) noexcept {
using std::swap;
swap(owner, other.owner);
swap(construction, other.construction);
swap(destruction, other.destruction);
swap(update, other.update);
underlying_type::swap(other);
}
/**
* @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 Checks if a mixin refers to a valid registry.
* @return True if the mixin refers to a valid registry, false otherwise.
*/
[[nodiscard]] explicit operator bool() const noexcept {
return (owner != nullptr);
}
/**
* @brief Returns a pointer to the underlying registry, if any.
* @return A pointer to the underlying registry, if any.
*/
[[nodiscard]] const registry_type &registry() const noexcept {
return owner_or_assert();
}
/*! @copydoc registry */
[[nodiscard]] registry_type &registry() noexcept {
return owner_or_assert();
}
/**
* @brief Creates a new identifier or recycles a destroyed one.
* @return A valid identifier.
*/
auto generate() {
const auto entt = underlying_type::generate();
construction.publish(owner_or_assert(), entt);
return entt;
}
/**
* @brief Creates a new identifier or recycles a destroyed one.
* @param hint Required identifier.
* @return A valid identifier.
*/
entity_type generate(const entity_type hint) {
const auto entt = underlying_type::generate(hint);
construction.publish(owner_or_assert(), entt);
return entt;
}
/**
* @brief Assigns each element in a range an identifier.
* @tparam It Type of output iterator.
* @param first An iterator to the first element of the range to generate.
* @param last An iterator past the last element of the range to generate.
*/
template<stl::output_iterator<entity_type> It>
void generate(It first, It last) {
underlying_type::generate(first, last);
if(auto &reg = owner_or_assert(); !construction.empty()) {
for(; first != last; ++first) {
construction.publish(reg, *first);
}
}
}
/**
* @brief Assigns an entity to a storage and constructs its object.
* @tparam Args Types of arguments to forward to the underlying storage.
* @param entt A valid identifier.
* @param args Parameters to forward to the underlying storage.
* @return A reference to the newly created object.
*/
template<typename... Args>
decltype(auto) emplace(const entity_type entt, Args &&...args) {
underlying_type::emplace(entt, std::forward<Args>(args)...);
construction.publish(owner_or_assert(), entt);
return this->get(entt);
}
/**
* @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 patched instance.
*/
template<typename... Func>
decltype(auto) patch(const entity_type entt, Func &&...func) {
underlying_type::patch(entt, std::forward<Func>(func)...);
update.publish(owner_or_assert(), entt);
return this->get(entt);
}
/**
* @brief Assigns one or more entities to a storage and constructs their
* objects from a given instance.
* @tparam Args Types of arguments to forward to the underlying storage.
* @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 forward to the underlying storage.
*/
template<typename... Args>
void insert(stl::input_iterator auto first, stl::input_iterator auto last, Args &&...args) {
auto from = underlying_type::size();
underlying_type::insert(first, last, std::forward<Args>(args)...);
if(auto &reg = owner_or_assert(); !construction.empty()) {
// fine as long as insert passes force_back true to try_emplace
for(const auto to = underlying_type::size(); from != to; ++from) {
construction.publish(reg, underlying_type::operator[](from));
}
}
}
private:
basic_registry_type *owner;
sigh_type construction;
sigh_type destruction;
sigh_type update;
};
/**
* @brief Mixin type used to add _reactive_ support to storage types.
* @tparam Type Underlying storage type.
* @tparam Registry Basic registry type.
*/
template<typename Type, typename Registry>
class basic_reactive_mixin final: public Type {
using underlying_type = Type;
using owner_type = Registry;
using alloc_traits = std::allocator_traits<typename underlying_type::allocator_type>;
using basic_registry_type = basic_registry<typename owner_type::entity_type, typename owner_type::allocator_type>;
using container_type = std::vector<connection, typename alloc_traits::template rebind_alloc<connection>>;
static_assert(std::is_base_of_v<basic_registry_type, owner_type>, "Invalid registry type");
[[nodiscard]] auto &owner_or_assert() const noexcept {
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
return static_cast<owner_type &>(*owner);
}
void emplace_element(const Registry &, underlying_type::entity_type entity) {
if(!underlying_type::contains(entity)) {
underlying_type::emplace(entity);
}
}
private:
void bind_any(any value) noexcept final {
owner = any_cast<basic_registry_type>(&value);
if constexpr(!std::is_same_v<registry_type, basic_registry_type>) {
if(owner == nullptr) {
owner = any_cast<registry_type>(&value);
}
}
underlying_type::bind_any(std::move(value));
}
public:
/*! @brief Allocator type. */
using allocator_type = underlying_type::allocator_type;
/*! @brief Underlying entity identifier. */
using entity_type = underlying_type::entity_type;
/*! @brief Expected registry type. */
using registry_type = owner_type;
/*! @brief Default constructor. */
basic_reactive_mixin()
: basic_reactive_mixin{allocator_type{}} {}
/**
* @brief Constructs an empty storage with a given allocator.
* @param allocator The allocator to use.
*/
explicit basic_reactive_mixin(const allocator_type &allocator)
: underlying_type{allocator},
owner{},
conn{allocator} {
}
/*! @brief Default copy constructor, deleted on purpose. */
basic_reactive_mixin(const basic_reactive_mixin &) = delete;
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
basic_reactive_mixin(basic_reactive_mixin &&other) noexcept
: underlying_type{static_cast<underlying_type &&>(other)},
owner{other.owner},
conn{std::move(other.conn)} {
}
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
basic_reactive_mixin(basic_reactive_mixin &&other, const allocator_type &allocator)
: underlying_type{static_cast<underlying_type &&>(other), allocator},
owner{other.owner},
conn{std::move(other.conn), allocator} {
}
/*! @brief Default destructor. */
~basic_reactive_mixin() override = default;
/**
* @brief Default copy assignment operator, deleted on purpose.
* @return This mixin.
*/
basic_reactive_mixin &operator=(const basic_reactive_mixin &) = delete;
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This mixin.
*/
basic_reactive_mixin &operator=(basic_reactive_mixin &&other) noexcept {
underlying_type::swap(other);
return *this;
}
/**
* @brief Makes storage _react_ to creation of objects of the given type.
* @tparam Clazz Type of element to _react_ to.
* @tparam Candidate Function to use to _react_ to the event.
* @param id Optional name used to map the storage within the registry.
* @return This mixin.
*/
template<typename Clazz, auto Candidate = &basic_reactive_mixin::emplace_element>
basic_reactive_mixin &on_construct(const id_type id = type_hash<Clazz>::value()) {
auto curr = owner_or_assert().template storage<Clazz>(id).on_construct().template connect<Candidate>(*this);
conn.push_back(std::move(curr));
return *this;
}
/**
* @brief Makes storage _react_ to update of objects of the given type.
* @tparam Clazz Type of element to _react_ to.
* @tparam Candidate Function to use to _react_ to the event.
* @param id Optional name used to map the storage within the registry.
* @return This mixin.
*/
template<typename Clazz, auto Candidate = &basic_reactive_mixin::emplace_element>
basic_reactive_mixin &on_update(const id_type id = type_hash<Clazz>::value()) {
auto curr = owner_or_assert().template storage<Clazz>(id).on_update().template connect<Candidate>(*this);
conn.push_back(std::move(curr));
return *this;
}
/**
* @brief Makes storage _react_ to destruction of objects of the given type.
* @tparam Clazz Type of element to _react_ to.
* @tparam Candidate Function to use to _react_ to the event.
* @param id Optional name used to map the storage within the registry.
* @return This mixin.
*/
template<typename Clazz, auto Candidate = &basic_reactive_mixin::emplace_element>
basic_reactive_mixin &on_destroy(const id_type id = type_hash<Clazz>::value()) {
auto curr = owner_or_assert().template storage<Clazz>(id).on_destroy().template connect<Candidate>(*this);
conn.push_back(std::move(curr));
return *this;
}
/**
* @brief Checks if a mixin refers to a valid registry.
* @return True if the mixin refers to a valid registry, false otherwise.
*/
[[nodiscard]] explicit operator bool() const noexcept {
return (owner != nullptr);
}
/**
* @brief Returns a pointer to the underlying registry, if any.
* @return A pointer to the underlying registry, if any.
*/
[[nodiscard]] const registry_type &registry() const noexcept {
return owner_or_assert();
}
/*! @copydoc registry */
[[nodiscard]] registry_type &registry() noexcept {
return owner_or_assert();
}
/**
* @brief Returns a view that is filtered by the underlying storage.
* @tparam Get Types of elements used to construct the view.
* @tparam Exclude Types of elements used to filter the view.
* @return A newly created view.
*/
template<typename... Get, typename... Exclude>
[[nodiscard]] basic_view<get_t<const basic_reactive_mixin, typename basic_registry_type::template storage_for_type<const Get>...>, exclude_t<typename basic_registry_type::template storage_for_type<const Exclude>...>>
view(exclude_t<Exclude...> = exclude_t{}) const {
const owner_type &parent = owner_or_assert();
basic_view<get_t<const basic_reactive_mixin, typename basic_registry_type::template storage_for_type<const Get>...>, exclude_t<typename basic_registry_type::template storage_for_type<const Exclude>...>> elem{};
[&elem](const auto *...curr) { ((curr ? elem.storage(*curr) : void()), ...); }(parent.template storage<std::remove_const_t<Exclude>>()..., parent.template storage<std::remove_const_t<Get>>()..., this);
return elem;
}
/*! @copydoc view */
template<typename... Get, typename... Exclude>
[[nodiscard]] basic_view<get_t<const basic_reactive_mixin, typename basic_registry_type::template storage_for_type<Get>...>, exclude_t<typename basic_registry_type::template storage_for_type<Exclude>...>>
view(exclude_t<Exclude...> = exclude_t{}) {
std::conditional_t<((std::is_const_v<Get> && ...) && (std::is_const_v<Exclude> && ...)), const owner_type, owner_type> &parent = owner_or_assert();
return {*this, parent.template storage<std::remove_const_t<Get>>()..., parent.template storage<std::remove_const_t<Exclude>>()...};
}
/*! @brief Releases all connections to the underlying registry, if any. */
void reset() {
for(auto &&curr: conn) {
curr.release();
}
conn.clear();
}
private:
basic_registry_type *owner;
container_type conn;
};
} // namespace entt
#endif

View File

@@ -0,0 +1,436 @@
#ifndef ENTT_ENTITY_OBSERVER_HPP
#define ENTT_ENTITY_OBSERVER_HPP
#include <cstddef>
#include <cstdint>
#include <limits>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "../core/type_traits.hpp"
#include "../signal/delegate.hpp"
#include "entity.hpp"
#include "fwd.hpp"
#include "registry.hpp"
#include "storage.hpp"
#include "utility.hpp"
namespace entt {
/*! @brief Grouping matcher. */
template<typename...>
struct matcher {};
/**
* @brief Collector.
*
* Primary template isn't defined on purpose. All the specializations give a
* compile-time error, but for a few reasonable cases.
*/
template<typename...>
struct basic_collector;
/**
* @brief Collector.
*
* A collector contains a set of rules (literally, matchers) to use to track
* entities.<br/>
* Its main purpose is to generate a descriptor that allows an observer to know
* how to connect to a registry.
*/
template<>
struct basic_collector<> {
/**
* @brief Adds a grouping matcher to the collector.
* @tparam AllOf Types of components tracked by the matcher.
* @tparam NoneOf Types of components used to filter out entities.
* @return The updated collector.
*/
template<typename... AllOf, typename... NoneOf>
static constexpr auto group(exclude_t<NoneOf...> = {}) ENTT_NOEXCEPT {
return basic_collector<matcher<type_list<>, type_list<>, type_list<NoneOf...>, AllOf...>>{};
}
/**
* @brief Adds an observing matcher to the collector.
* @tparam AnyOf Type of component for which changes should be detected.
* @return The updated collector.
*/
template<typename AnyOf>
static constexpr auto update() ENTT_NOEXCEPT {
return basic_collector<matcher<type_list<>, type_list<>, AnyOf>>{};
}
};
/**
* @brief Collector.
* @copydetails basic_collector<>
* @tparam Reject Untracked types used to filter out entities.
* @tparam Require Untracked types required by the matcher.
* @tparam Rule Specific details of the current matcher.
* @tparam Other Other matchers.
*/
template<typename... Reject, typename... Require, typename... Rule, typename... Other>
struct basic_collector<matcher<type_list<Reject...>, type_list<Require...>, Rule...>, Other...> {
/*! @brief Current matcher. */
using current_type = matcher<type_list<Reject...>, type_list<Require...>, Rule...>;
/**
* @brief Adds a grouping matcher to the collector.
* @tparam AllOf Types of components tracked by the matcher.
* @tparam NoneOf Types of components used to filter out entities.
* @return The updated collector.
*/
template<typename... AllOf, typename... NoneOf>
static constexpr auto group(exclude_t<NoneOf...> = {}) ENTT_NOEXCEPT {
return basic_collector<matcher<type_list<>, type_list<>, type_list<NoneOf...>, AllOf...>, current_type, Other...>{};
}
/**
* @brief Adds an observing matcher to the collector.
* @tparam AnyOf Type of component for which changes should be detected.
* @return The updated collector.
*/
template<typename AnyOf>
static constexpr auto update() ENTT_NOEXCEPT {
return basic_collector<matcher<type_list<>, type_list<>, AnyOf>, current_type, Other...>{};
}
/**
* @brief Updates the filter of the last added matcher.
* @tparam AllOf Types of components required by the matcher.
* @tparam NoneOf Types of components used to filter out entities.
* @return The updated collector.
*/
template<typename... AllOf, typename... NoneOf>
static constexpr auto where(exclude_t<NoneOf...> = {}) ENTT_NOEXCEPT {
using extended_type = matcher<type_list<Reject..., NoneOf...>, type_list<Require..., AllOf...>, Rule...>;
return basic_collector<extended_type, Other...>{};
}
};
/*! @brief Variable template used to ease the definition of collectors. */
inline constexpr basic_collector<> collector{};
/**
* @brief Observer.
*
* An observer returns all the entities and only the entities that fit the
* requirements of at least one matcher. Moreover, it's guaranteed that the
* entity list is tightly packed in memory for fast iterations.<br/>
* In general, observers don't stay true to the order of any set of components.
*
* Observers work mainly with two types of matchers, provided through a
* collector:
*
* * Observing matcher: an observer will return at least all the living entities
* for which one or more of the given components have been updated and not yet
* destroyed.
* * Grouping matcher: an observer will return at least all the living entities
* that would have entered the given group if it existed and that would have
* not yet left it.
*
* If an entity respects the requirements of multiple matchers, it will be
* returned once and only once by the observer in any case.
*
* Matchers support also filtering by means of a _where_ clause that accepts
* both a list of types and an exclusion list.<br/>
* Whenever a matcher finds that an entity matches its requirements, the
* condition of the filter is verified before to register the entity itself.
* Moreover, a registered entity isn't returned by the observer if the condition
* set by the filter is broken in the meantime.
*
* @b Important
*
* Iterators aren't invalidated if:
*
* * New instances of the given components are created and assigned to entities.
* * The entity currently pointed is modified (as an example, if one of the
* given components is removed from the entity to which the iterator points).
* * The entity currently pointed is destroyed.
*
* In all the other cases, modifying the pools of the given components in any
* way invalidates all the iterators and using them results in undefined
* behavior.
*
* @warning
* Lifetime of an observer doesn't necessarily have to overcome that of the
* registry to which it is connected. However, the observer must be disconnected
* from the registry before being destroyed to avoid crashes due to dangling
* pointers.
*
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
class basic_observer {
using payload_type = std::uint32_t;
template<typename>
struct matcher_handler;
template<typename... Reject, typename... Require, typename AnyOf>
struct matcher_handler<matcher<type_list<Reject...>, type_list<Require...>, AnyOf>> {
template<std::size_t Index>
static void maybe_valid_if(basic_observer &obs, basic_registry<Entity> &reg, const Entity entt) {
if(reg.template all_of<Require...>(entt) && !reg.template any_of<Reject...>(entt)) {
if(!obs.storage.contains(entt)) {
obs.storage.emplace(entt);
}
obs.storage.get(entt) |= (1 << Index);
}
}
template<std::size_t Index>
static void discard_if(basic_observer &obs, basic_registry<Entity> &, const Entity entt) {
if(obs.storage.contains(entt) && !(obs.storage.get(entt) &= (~(1 << Index)))) {
obs.storage.erase(entt);
}
}
template<std::size_t Index>
static void connect(basic_observer &obs, basic_registry<Entity> &reg) {
(reg.template on_destroy<Require>().template connect<&discard_if<Index>>(obs), ...);
(reg.template on_construct<Reject>().template connect<&discard_if<Index>>(obs), ...);
reg.template on_update<AnyOf>().template connect<&maybe_valid_if<Index>>(obs);
reg.template on_destroy<AnyOf>().template connect<&discard_if<Index>>(obs);
}
static void disconnect(basic_observer &obs, basic_registry<Entity> &reg) {
(reg.template on_destroy<Require>().disconnect(obs), ...);
(reg.template on_construct<Reject>().disconnect(obs), ...);
reg.template on_update<AnyOf>().disconnect(obs);
reg.template on_destroy<AnyOf>().disconnect(obs);
}
};
template<typename... Reject, typename... Require, typename... NoneOf, typename... AllOf>
struct matcher_handler<matcher<type_list<Reject...>, type_list<Require...>, type_list<NoneOf...>, AllOf...>> {
template<std::size_t Index, typename... Ignore>
static void maybe_valid_if(basic_observer &obs, basic_registry<Entity> &reg, const Entity entt) {
auto condition = [&reg, entt]() {
if constexpr(sizeof...(Ignore) == 0) {
return reg.template all_of<AllOf..., Require...>(entt) && !reg.template any_of<NoneOf..., Reject...>(entt);
} else {
return reg.template all_of<AllOf..., Require...>(entt) && ((std::is_same_v<Ignore..., NoneOf> || !reg.template any_of<NoneOf>(entt)) && ...) && !reg.template any_of<Reject...>(entt);
}
};
if(condition()) {
if(!obs.storage.contains(entt)) {
obs.storage.emplace(entt);
}
obs.storage.get(entt) |= (1 << Index);
}
}
template<std::size_t Index>
static void discard_if(basic_observer &obs, basic_registry<Entity> &, const Entity entt) {
if(obs.storage.contains(entt) && !(obs.storage.get(entt) &= (~(1 << Index)))) {
obs.storage.erase(entt);
}
}
template<std::size_t Index>
static void connect(basic_observer &obs, basic_registry<Entity> &reg) {
(reg.template on_destroy<Require>().template connect<&discard_if<Index>>(obs), ...);
(reg.template on_construct<Reject>().template connect<&discard_if<Index>>(obs), ...);
(reg.template on_construct<AllOf>().template connect<&maybe_valid_if<Index>>(obs), ...);
(reg.template on_destroy<NoneOf>().template connect<&maybe_valid_if<Index, NoneOf>>(obs), ...);
(reg.template on_destroy<AllOf>().template connect<&discard_if<Index>>(obs), ...);
(reg.template on_construct<NoneOf>().template connect<&discard_if<Index>>(obs), ...);
}
static void disconnect(basic_observer &obs, basic_registry<Entity> &reg) {
(reg.template on_destroy<Require>().disconnect(obs), ...);
(reg.template on_construct<Reject>().disconnect(obs), ...);
(reg.template on_construct<AllOf>().disconnect(obs), ...);
(reg.template on_destroy<NoneOf>().disconnect(obs), ...);
(reg.template on_destroy<AllOf>().disconnect(obs), ...);
(reg.template on_construct<NoneOf>().disconnect(obs), ...);
}
};
template<typename... Matcher>
static void disconnect(basic_registry<Entity> &reg, basic_observer &obs) {
(matcher_handler<Matcher>::disconnect(obs, reg), ...);
}
template<typename... Matcher, std::size_t... Index>
void connect(basic_registry<Entity> &reg, std::index_sequence<Index...>) {
static_assert(sizeof...(Matcher) < std::numeric_limits<payload_type>::digits, "Too many matchers");
(matcher_handler<Matcher>::template connect<Index>(*this, reg), ...);
release.template connect<&basic_observer::disconnect<Matcher...>>(reg);
}
public:
/*! @brief Underlying entity identifier. */
using entity_type = Entity;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Random access iterator type. */
using iterator = typename basic_sparse_set<Entity>::iterator;
/*! @brief Default constructor. */
basic_observer()
: release{},
storage{} {}
/*! @brief Default copy constructor, deleted on purpose. */
basic_observer(const basic_observer &) = delete;
/*! @brief Default move constructor, deleted on purpose. */
basic_observer(basic_observer &&) = delete;
/**
* @brief Creates an observer and connects it to a given registry.
* @tparam Matcher Types of matchers to use to initialize the observer.
* @param reg A valid reference to a registry.
*/
template<typename... Matcher>
basic_observer(basic_registry<entity_type> &reg, basic_collector<Matcher...>)
: basic_observer{} {
connect<Matcher...>(reg, std::index_sequence_for<Matcher...>{});
}
/*! @brief Default destructor. */
~basic_observer() = default;
/**
* @brief Default copy assignment operator, deleted on purpose.
* @return This observer.
*/
basic_observer &operator=(const basic_observer &) = delete;
/**
* @brief Default move assignment operator, deleted on purpose.
* @return This observer.
*/
basic_observer &operator=(basic_observer &&) = delete;
/**
* @brief Connects an observer to a given registry.
* @tparam Matcher Types of matchers to use to initialize the observer.
* @param reg A valid reference to a registry.
*/
template<typename... Matcher>
void connect(basic_registry<entity_type> &reg, basic_collector<Matcher...>) {
disconnect();
connect<Matcher...>(reg, std::index_sequence_for<Matcher...>{});
storage.clear();
}
/*! @brief Disconnects an observer from the registry it keeps track of. */
void disconnect() {
if(release) {
release(*this);
release.reset();
}
}
/**
* @brief Returns the number of elements in an observer.
* @return Number of elements.
*/
[[nodiscard]] size_type size() const ENTT_NOEXCEPT {
return storage.size();
}
/**
* @brief Checks whether an observer is empty.
* @return True if the observer is empty, false otherwise.
*/
[[nodiscard]] bool empty() const ENTT_NOEXCEPT {
return storage.empty();
}
/**
* @brief Direct access to the list of entities of the observer.
*
* The returned pointer is such that range `[data(), data() + size())` is
* always a valid range, even if the container is empty.
*
* @note
* Entities are in the reverse order as returned by the `begin`/`end`
* iterators.
*
* @return A pointer to the array of entities.
*/
[[nodiscard]] const entity_type *data() const ENTT_NOEXCEPT {
return storage.data();
}
/**
* @brief Returns an iterator to the first entity of the observer.
*
* The returned iterator points to the first entity of the observer. If the
* container is empty, the returned iterator will be equal to `end()`.
*
* @return An iterator to the first entity of the observer.
*/
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
return storage.basic_sparse_set<entity_type>::begin();
}
/**
* @brief Returns an iterator that is past the last entity of the observer.
*
* The returned iterator points to the entity following the last entity of
* the observer. Attempting to dereference the returned iterator results in
* undefined behavior.
*
* @return An iterator to the entity following the last entity of the
* observer.
*/
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
return storage.basic_sparse_set<entity_type>::end();
}
/*! @brief Clears the underlying container. */
void clear() ENTT_NOEXCEPT {
storage.clear();
}
/**
* @brief Iterates entities and applies the given function object to them.
*
* The function object is invoked for each entity.<br/>
* The signature of the function must be equivalent to the following form:
*
* @code{.cpp}
* void(const entity_type);
* @endcode
*
* @tparam Func Type of the function object to invoke.
* @param func A valid function object.
*/
template<typename Func>
void each(Func func) const {
for(const auto entity: *this) {
func(entity);
}
}
/**
* @brief Iterates entities and applies the given function object to them,
* then clears the observer.
*
* @sa each
*
* @tparam Func Type of the function object to invoke.
* @param func A valid function object.
*/
template<typename Func>
void each(Func func) {
std::as_const(*this).each(std::move(func));
clear();
}
private:
delegate<void(basic_observer &)> release;
basic_storage<entity_type, payload_type> storage;
};
} // namespace entt
#endif

View File

@@ -1,109 +1,97 @@
#ifndef ENTT_ENTITY_ORGANIZER_HPP #ifndef ENTT_ENTITY_ORGANIZER_HPP
#define ENTT_ENTITY_ORGANIZER_HPP #define ENTT_ENTITY_ORGANIZER_HPP
#include <algorithm>
#include <cstddef> #include <cstddef>
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "../container/dense_map.hpp"
#include "../core/type_info.hpp" #include "../core/type_info.hpp"
#include "../core/type_traits.hpp" #include "../core/type_traits.hpp"
#include "../core/utility.hpp" #include "../core/utility.hpp"
#include "../graph/adjacency_matrix.hpp"
#include "../graph/flow.hpp"
#include "fwd.hpp" #include "fwd.hpp"
#include "helper.hpp" #include "helper.hpp"
namespace entt { namespace entt {
/*! @cond ENTT_INTERNAL */ /**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
namespace internal { namespace internal {
template<typename> template<typename>
struct is_view: std::false_type {}; struct is_view: std::false_type {};
template<typename... Args> template<typename Entity, typename... Component, typename... Exclude>
struct is_view<basic_view<Args...>>: std::true_type {}; struct is_view<basic_view<Entity, get_t<Component...>, exclude_t<Exclude...>>>: std::true_type {};
template<typename Type> template<typename Type>
inline constexpr bool is_view_v = is_view<Type>::value; inline constexpr bool is_view_v = is_view<Type>::value;
template<typename>
struct is_group: std::false_type {};
template<typename... Args>
struct is_group<basic_group<Args...>>: std::true_type {};
template<typename Type>
inline constexpr bool is_group_v = is_group<Type>::value;
template<typename Type, typename Override> template<typename Type, typename Override>
struct unpack_type { struct unpack_type {
using ro = std::conditional_t< 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_contains_v<Override, std::add_const_t<Type>> || (std::is_const_v<Type> && !type_list_contains_v<Override, std::remove_const_t<Type>>),
type_list<std::remove_const_t<Type>>, type_list<std::remove_const_t<Type>>,
type_list<>>; type_list<>>;
using rw = std::conditional_t< 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_contains_v<Override, std::remove_const_t<Type>> || (!std::is_const_v<Type> && !type_list_contains_v<Override, std::add_const_t<Type>>),
type_list<Type>, type_list<Type>,
type_list<>>; type_list<>>;
}; };
template<typename... Args, typename... Override> template<typename Entity, typename... Override>
struct unpack_type<basic_registry<Args...>, type_list<Override...>> { struct unpack_type<basic_registry<Entity>, type_list<Override...>> {
using ro = type_list<>; using ro = type_list<>;
using rw = type_list<>; using rw = type_list<>;
}; };
template<typename... Args, typename... Override> template<typename Entity, typename... Override>
struct unpack_type<const basic_registry<Args...>, type_list<Override...>> struct unpack_type<const basic_registry<Entity>, type_list<Override...>>
: unpack_type<basic_registry<Args...>, type_list<Override...>> {}; : unpack_type<basic_registry<Entity>, type_list<Override...>> {};
template<typename... Get, typename... Exclude, typename... Override> template<typename Entity, typename... Component, typename... Exclude, typename... Override>
struct unpack_type<basic_view<get_t<Get...>, exclude_t<Exclude...>>, type_list<Override...>> { struct unpack_type<basic_view<Entity, get_t<Component...>, exclude_t<Exclude...>>, type_list<Override...>> {
using ro = type_list_cat_t<type_list<typename Exclude::element_type...>, typename unpack_type<constness_as_t<typename Get::element_type, Get>, type_list<Override...>>::ro...>; using ro = type_list_cat_t<type_list<Exclude...>, typename unpack_type<Component, type_list<Override...>>::ro...>;
using rw = type_list_cat_t<typename unpack_type<constness_as_t<typename Get::element_type, Get>, type_list<Override...>>::rw...>; using rw = type_list_cat_t<typename unpack_type<Component, type_list<Override...>>::rw...>;
}; };
template<typename... Get, typename... Exclude, typename... Override> template<typename Entity, typename... Component, typename... Exclude, typename... Override>
struct unpack_type<const basic_view<get_t<Get...>, exclude_t<Exclude...>>, type_list<Override...>> struct unpack_type<const basic_view<Entity, get_t<Component...>, exclude_t<Exclude...>>, type_list<Override...>>
: unpack_type<basic_view<get_t<Get...>, exclude_t<Exclude...>>, type_list<Override...>> {}; : unpack_type<basic_view<Entity, get_t<Component...>, exclude_t<Exclude...>>, type_list<Override...>> {};
template<typename... Owned, typename... Get, typename... Exclude, typename... Override> template<typename, typename>
struct unpack_type<basic_group<owned_t<Owned...>, get_t<Get...>, exclude_t<Exclude...>>, type_list<Override...>> {
using ro = type_list_cat_t<type_list<typename Exclude::element_type...>, typename unpack_type<constness_as_t<typename Get::element_type, Get>, type_list<Override...>>::ro..., typename unpack_type<constness_as_t<typename Owned::element_type, Owned>, type_list<Override...>>::ro...>;
using rw = type_list_cat_t<typename unpack_type<constness_as_t<typename Get::element_type, Get>, type_list<Override...>>::rw..., typename unpack_type<constness_as_t<typename Owned::element_type, Owned>, type_list<Override...>>::rw...>;
};
template<typename... Owned, typename... Get, typename... Exclude, typename... Override>
struct unpack_type<const basic_group<owned_t<Owned...>, get_t<Get...>, exclude_t<Exclude...>>, type_list<Override...>>
: unpack_type<basic_group<owned_t<Owned...>, get_t<Get...>, exclude_t<Exclude...>>, type_list<Override...>> {};
template<typename, typename, typename>
struct resource_traits; struct resource_traits;
template<typename Registry, typename... Args, typename... Req> template<typename... Args, typename... Req>
struct resource_traits<Registry, type_list<Args...>, type_list<Req...>> { struct resource_traits<type_list<Args...>, type_list<Req...>> {
using args = type_list<std::remove_const_t<Args>...>; 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 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...>; using rw = type_list_cat_t<typename unpack_type<Args, type_list<Req...>>::rw..., typename unpack_type<Req, type_list<>>::rw...>;
static constexpr auto sync_point = (std::is_same_v<Args, Registry> || ...);
}; };
template<typename Registry, typename... Req, typename Ret, typename... Args> template<typename... Req, typename Ret, typename... Args>
resource_traits<Registry, type_list<std::remove_reference_t<Args>...>, type_list<Req...>> free_function_to_resource_traits(Ret (*)(Args...)); resource_traits<type_list<std::remove_reference_t<Args>...>, type_list<Req...>> free_function_to_resource_traits(Ret (*)(Args...));
template<typename Registry, typename... Req, typename Ret, typename Type, typename... Args> template<typename... Req, typename Ret, typename Type, typename... Args>
resource_traits<Registry, type_list<std::remove_reference_t<Args>...>, type_list<Req...>> constrained_function_to_resource_traits(Ret (*)(Type &, Args...)); resource_traits<type_list<std::remove_reference_t<Args>...>, type_list<Req...>> constrained_function_to_resource_traits(Ret (*)(Type &, Args...));
template<typename Registry, typename... Req, typename Ret, typename Class, typename... Args> template<typename... Req, typename Ret, typename Class, typename... Args>
resource_traits<Registry, type_list<std::remove_reference_t<Args>...>, type_list<Req...>> constrained_function_to_resource_traits(Ret (Class::*)(Args...)); resource_traits<type_list<std::remove_reference_t<Args>...>, type_list<Req...>> constrained_function_to_resource_traits(Ret (Class::*)(Args...));
template<typename Registry, typename... Req, typename Ret, typename Class, typename... Args> template<typename... Req, typename Ret, typename Class, typename... Args>
resource_traits<Registry, type_list<std::remove_reference_t<Args>...>, type_list<Req...>> constrained_function_to_resource_traits(Ret (Class::*)(Args...) const); resource_traits<type_list<std::remove_reference_t<Args>...>, type_list<Req...>> constrained_function_to_resource_traits(Ret (Class::*)(Args...) const);
} // namespace internal } // namespace internal
/*! @endcond */
/**
* Internal details not to be documented.
* @endcond
*/
/** /**
* @brief Utility class for creating a static task graph. * @brief Utility class for creating a static task graph.
@@ -114,12 +102,12 @@ resource_traits<Registry, type_list<std::remove_reference_t<Args>...>, type_list
* goal of the tool. Instead, they are returned to the user in the form of a * goal of the tool. Instead, they are returned to the user in the form of a
* graph that allows for safe execution. * graph that allows for safe execution.
* *
* @tparam Registry Basic registry type. * @tparam Entity A valid entity type (see entt_traits for more details).
*/ */
template<typename Registry> template<typename Entity>
class basic_organizer final { class basic_organizer final {
using callback_type = void(const void *, Registry &); using callback_type = void(const void *, basic_registry<Entity> &);
using prepare_type = void(Registry &); using prepare_type = void(basic_registry<Entity> &);
using dependency_type = std::size_t(const bool, const type_info **, const std::size_t); using dependency_type = std::size_t(const bool, const type_info **, const std::size_t);
struct vertex_data final { struct vertex_data final {
@@ -128,60 +116,119 @@ class basic_organizer final {
const char *name{}; const char *name{};
const void *payload{}; const void *payload{};
callback_type *callback{}; callback_type *callback{};
dependency_type *dependency{}; dependency_type *dependency;
prepare_type *prepare{}; prepare_type *prepare{};
const type_info *info{}; const type_info *info{};
}; };
template<typename Type> template<typename Type>
[[nodiscard]] static decltype(auto) extract(Registry &reg) { [[nodiscard]] static decltype(auto) extract(basic_registry<Entity> &reg) {
if constexpr(std::is_same_v<Type, Registry>) { if constexpr(std::is_same_v<Type, basic_registry<Entity>>) {
return reg; return reg;
} else if constexpr(internal::is_view_v<Type>) { } else if constexpr(internal::is_view_v<Type>) {
return static_cast<Type>(as_view{reg}); return as_view{reg};
} else if constexpr(internal::is_group_v<Type>) {
return static_cast<Type>(as_group{reg});
} else { } else {
return reg.ctx().template emplace<std::remove_reference_t<Type>>(); return reg.ctx().template emplace<std::remove_reference_t<Type>>();
} }
} }
template<typename... Args> template<typename... Args>
[[nodiscard]] static auto to_args(Registry &reg, type_list<Args...>) { [[nodiscard]] static auto to_args(basic_registry<Entity> &reg, type_list<Args...>) {
return std::tuple<decltype(extract<Args>(reg))...>(extract<Args>(reg)...); return std::tuple<decltype(extract<Args>(reg))...>(extract<Args>(reg)...);
} }
template<typename... Type> template<typename... Type>
[[nodiscard]] static std::size_t fill_dependencies(type_list<Type...>, [[maybe_unused]] const type_info **buffer, [[maybe_unused]] const std::size_t count) { 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) { if constexpr(sizeof...(Type) == 0u) {
return {}; return {};
} else { } else {
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays) const type_info *info[sizeof...(Type)]{&type_id<Type>()...};
const type_info *info[]{&type_id<Type>()...}; const auto length = (std::min)(count, sizeof...(Type));
const auto length = count < sizeof...(Type) ? count : sizeof...(Type); std::copy_n(info, length, buffer);
for(std::size_t pos{}; pos < length; ++pos) {
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
buffer[pos] = info[pos];
}
return length; return length;
} }
} }
template<typename... RO, typename... RW> template<typename... RO, typename... RW>
void track_dependencies(std::size_t index, const bool sync_point, type_list<RO...>, type_list<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)); dependencies[type_hash<basic_registry<Entity>>::value()].emplace_back(index, requires_registry || (sizeof...(RO) + sizeof...(RW) == 0u));
builder.set(type_hash<Registry>::value(), sync_point || (sizeof...(RO) + sizeof...(RW) == 0u)); (dependencies[type_hash<RO>::value()].emplace_back(index, false), ...);
(builder.ro(type_hash<RO>::value()), ...); (dependencies[type_hash<RW>::value()].emplace_back(index, true), ...);
(builder.rw(type_hash<RW>::value()), ...); }
[[nodiscard]] std::vector<bool> adjacency_matrix() {
const auto length = vertices.size();
std::vector<bool> edges(length * length, false);
// creates the adjacency matrix
for(const auto &deps: dependencies) {
const auto last = deps.second.cend();
auto it = deps.second.cbegin();
while(it != last) {
if(it->second) {
// rw item
if(auto curr = it++; it != last) {
if(it->second) {
edges[curr->first * length + it->first] = true;
} else {
if(const auto next = std::find_if(it, last, [](const auto &elem) { return elem.second; }); next != last) {
for(; it != next; ++it) {
edges[curr->first * length + it->first] = true;
edges[it->first * length + next->first] = true;
}
} else {
for(; it != next; ++it) {
edges[curr->first * length + it->first] = true;
}
}
}
}
} else {
// ro item, possibly only on first iteration
if(const auto next = std::find_if(it, last, [](const auto &elem) { return elem.second; }); next != last) {
for(; it != next; ++it) {
edges[it->first * length + next->first] = true;
}
} else {
it = last;
}
}
}
}
// computes the transitive closure
for(std::size_t vk{}; vk < length; ++vk) {
for(std::size_t vi{}; vi < length; ++vi) {
for(std::size_t vj{}; vj < length; ++vj) {
edges[vi * length + vj] = edges[vi * length + vj] || (edges[vi * length + vk] && edges[vk * length + vj]);
}
}
}
// applies the transitive reduction
for(std::size_t vert{}; vert < length; ++vert) {
edges[vert * length + vert] = false;
}
for(std::size_t vj{}; vj < length; ++vj) {
for(std::size_t vi{}; vi < length; ++vi) {
if(edges[vi * length + vj]) {
for(std::size_t vk{}; vk < length; ++vk) {
if(edges[vj * length + vk]) {
edges[vi * length + vk] = false;
}
}
}
}
}
return edges;
} }
public: public:
/*! Basic registry type. */
using registry_type = Registry;
/*! @brief Underlying entity identifier. */ /*! @brief Underlying entity identifier. */
using entity_type = registry_type::entity_type; using entity_type = Entity;
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
using size_type = std::size_t; using size_type = std::size_t;
/*! @brief Raw task function type. */ /*! @brief Raw task function type. */
@@ -191,14 +238,14 @@ public:
struct vertex { struct vertex {
/** /**
* @brief Constructs a vertex of the task graph. * @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 data The data associated with the vertex.
* @param from List of in-edges of the vertex. * @param edges The indices of the children in the adjacency list.
* @param to List of out-edges of the vertex.
*/ */
vertex(vertex_data data, std::vector<std::size_t> from, std::vector<std::size_t> to) vertex(const bool vtype, vertex_data data, std::vector<std::size_t> edges)
: node{std::move(data)}, : is_top_level{vtype},
in{std::move(from)}, node{std::move(data)},
out{std::move(to)} {} reachable{std::move(edges)} {}
/** /**
* @brief Fills a buffer with the type info objects for the writable * @brief Fills a buffer with the type info objects for the writable
@@ -207,7 +254,7 @@ public:
* @param length The length of the user-supplied buffer. * @param length The length of the user-supplied buffer.
* @return The number of type info objects written to the buffer. * @return The number of type info objects written to the buffer.
*/ */
[[nodiscard]] size_type ro_dependency(const type_info **buffer, const std::size_t length) const noexcept { size_type ro_dependency(const type_info **buffer, const std::size_t length) const ENTT_NOEXCEPT {
return node.dependency(false, buffer, length); return node.dependency(false, buffer, length);
} }
@@ -218,7 +265,7 @@ public:
* @param length The length of the user-supplied buffer. * @param length The length of the user-supplied buffer.
* @return The number of type info objects written to the buffer. * @return The number of type info objects written to the buffer.
*/ */
[[nodiscard]] size_type rw_dependency(const type_info **buffer, const std::size_t length) const noexcept { size_type rw_dependency(const type_info **buffer, const std::size_t length) const ENTT_NOEXCEPT {
return node.dependency(true, buffer, length); return node.dependency(true, buffer, length);
} }
@@ -226,7 +273,7 @@ public:
* @brief Returns the number of read-only resources of a vertex. * @brief Returns the number of read-only resources of a vertex.
* @return The number of read-only resources of the vertex. * @return The number of read-only resources of the vertex.
*/ */
[[nodiscard]] size_type ro_count() const noexcept { size_type ro_count() const ENTT_NOEXCEPT {
return node.ro_count; return node.ro_count;
} }
@@ -234,7 +281,7 @@ public:
* @brief Returns the number of writable resources of a vertex. * @brief Returns the number of writable resources of a vertex.
* @return The number of writable resources of the vertex. * @return The number of writable resources of the vertex.
*/ */
[[nodiscard]] size_type rw_count() const noexcept { size_type rw_count() const ENTT_NOEXCEPT {
return node.rw_count; return node.rw_count;
} }
@@ -242,15 +289,15 @@ public:
* @brief Checks if a vertex is also a top-level one. * @brief Checks if a vertex is also a top-level one.
* @return True if the vertex is a top-level one, false otherwise. * @return True if the vertex is a top-level one, false otherwise.
*/ */
[[nodiscard]] bool top_level() const noexcept { bool top_level() const ENTT_NOEXCEPT {
return in.empty(); return is_top_level;
} }
/** /**
* @brief Returns a type info object associated with a vertex. * @brief Returns a type info object associated with a vertex.
* @return A properly initialized type info object. * @return A properly initialized type info object.
*/ */
[[nodiscard]] const type_info &info() const noexcept { const type_info &info() const ENTT_NOEXCEPT {
return *node.info; return *node.info;
} }
@@ -258,7 +305,7 @@ public:
* @brief Returns a user defined name associated with a vertex, if any. * @brief Returns a user defined name associated with a vertex, if any.
* @return The user defined name associated with the vertex, if any. * @return The user defined name associated with the vertex, if any.
*/ */
[[nodiscard]] const char *name() const noexcept { const char *name() const ENTT_NOEXCEPT {
return node.name; return node.name;
} }
@@ -266,7 +313,7 @@ public:
* @brief Returns the function associated with a vertex. * @brief Returns the function associated with a vertex.
* @return The function associated with the vertex. * @return The function associated with the vertex.
*/ */
[[nodiscard]] function_type *callback() const noexcept { function_type *callback() const ENTT_NOEXCEPT {
return node.callback; return node.callback;
} }
@@ -274,24 +321,16 @@ public:
* @brief Returns the payload associated with a vertex, if any. * @brief Returns the payload associated with a vertex, if any.
* @return The payload associated with the vertex, if any. * @return The payload associated with the vertex, if any.
*/ */
[[nodiscard]] const void *data() const noexcept { const void *data() const ENTT_NOEXCEPT {
return node.payload; return node.payload;
} }
/** /**
* @brief Returns the list of in-edges of a vertex. * @brief Returns the list of nodes reachable from a given vertex.
* @return The list of in-edges of a vertex. * @return The list of nodes reachable from the vertex.
*/ */
[[nodiscard]] const std::vector<std::size_t> &in_edges() const noexcept { const std::vector<std::size_t> &children() const ENTT_NOEXCEPT {
return in; return reachable;
}
/**
* @brief Returns the list of out-edges of a vertex.
* @return The list of out-edges of a vertex.
*/
[[nodiscard]] const std::vector<std::size_t> &out_edges() const noexcept {
return out;
} }
/** /**
@@ -299,14 +338,14 @@ public:
* are properly instantiated before using them. * are properly instantiated before using them.
* @param reg A valid registry. * @param reg A valid registry.
*/ */
void prepare(registry_type &reg) const { void prepare(basic_registry<entity_type> &reg) const {
node.prepare ? node.prepare(reg) : void(); node.prepare ? node.prepare(reg) : void();
} }
private: private:
bool is_top_level;
vertex_data node; vertex_data node;
std::vector<std::size_t> in; std::vector<std::size_t> reachable;
std::vector<std::size_t> out;
}; };
/** /**
@@ -317,9 +356,10 @@ public:
*/ */
template<auto Candidate, typename... Req> template<auto Candidate, typename... Req>
void emplace(const char *name = nullptr) { void emplace(const char *name = nullptr) {
using resource_type = decltype(internal::free_function_to_resource_traits<registry_type, Req...>(Candidate)); using resource_type = decltype(internal::free_function_to_resource_traits<Req...>(Candidate));
constexpr auto requires_registry = type_list_contains_v<typename resource_type::args, basic_registry<entity_type>>;
callback_type *callback = +[](const void *, registry_type &reg) { callback_type *callback = +[](const void *, basic_registry<entity_type> &reg) {
std::apply(Candidate, to_args(reg, typename resource_type::args{})); std::apply(Candidate, to_args(reg, typename resource_type::args{}));
}; };
@@ -330,10 +370,10 @@ public:
nullptr, nullptr,
callback, 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); }, +[](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 &reg) { void(to_args(reg, typename resource_type::args{})); }, +[](basic_registry<entity_type> &reg) { void(to_args(reg, typename resource_type::args{})); },
&type_id<std::integral_constant<decltype(Candidate), Candidate>>()}; &type_id<std::integral_constant<decltype(Candidate), Candidate>>()};
track_dependencies(vertices.size(), resource_type::sync_point, typename resource_type::ro{}, typename resource_type::rw{}); track_dependencies(vertices.size(), requires_registry, typename resource_type::ro{}, typename resource_type::rw{});
vertices.push_back(std::move(vdata)); vertices.push_back(std::move(vdata));
} }
@@ -348,9 +388,10 @@ public:
*/ */
template<auto Candidate, typename... Req, typename Type> template<auto Candidate, typename... Req, typename Type>
void emplace(Type &value_or_instance, const char *name = nullptr) { void emplace(Type &value_or_instance, const char *name = nullptr) {
using resource_type = decltype(internal::constrained_function_to_resource_traits<registry_type, Req...>(Candidate)); using resource_type = decltype(internal::constrained_function_to_resource_traits<Req...>(Candidate));
constexpr auto requires_registry = type_list_contains_v<typename resource_type::args, basic_registry<entity_type>>;
callback_type *callback = +[](const void *payload, registry_type &reg) { callback_type *callback = +[](const void *payload, basic_registry<entity_type> &reg) {
Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload)); 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{}))); std::apply(Candidate, std::tuple_cat(std::forward_as_tuple(*curr), to_args(reg, typename resource_type::args{})));
}; };
@@ -362,10 +403,10 @@ public:
&value_or_instance, &value_or_instance,
callback, 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); }, +[](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 &reg) { void(to_args(reg, typename resource_type::args{})); }, +[](basic_registry<entity_type> &reg) { void(to_args(reg, typename resource_type::args{})); },
&type_id<std::integral_constant<decltype(Candidate), Candidate>>()}; &type_id<std::integral_constant<decltype(Candidate), Candidate>>()};
track_dependencies(vertices.size(), resource_type::sync_point, typename resource_type::ro{}, typename resource_type::rw{}); track_dependencies(vertices.size(), requires_registry, typename resource_type::ro{}, typename resource_type::rw{});
vertices.push_back(std::move(vdata)); vertices.push_back(std::move(vdata));
} }
@@ -379,7 +420,7 @@ public:
*/ */
template<typename... Req> template<typename... Req>
void emplace(function_type *func, const void *payload = nullptr, const char *name = nullptr) { void emplace(function_type *func, const void *payload = nullptr, const char *name = nullptr) {
using resource_type = internal::resource_traits<registry_type, type_list<>, type_list<Req...>>; using resource_type = internal::resource_traits<type_list<>, type_list<Req...>>;
track_dependencies(vertices.size(), true, typename resource_type::ro{}, typename resource_type::rw{}); track_dependencies(vertices.size(), true, typename resource_type::ro{}, typename resource_type::rw{});
vertex_data vdata{ vertex_data vdata{
@@ -399,23 +440,29 @@ public:
* @brief Generates a task graph for the current content. * @brief Generates a task graph for the current content.
* @return The adjacency list of the task graph. * @return The adjacency list of the task graph.
*/ */
[[nodiscard]] std::vector<vertex> graph() const { std::vector<vertex> graph() {
const auto edges = adjacency_matrix();
// creates the adjacency list
std::vector<vertex> adjacency_list{}; std::vector<vertex> adjacency_list{};
adjacency_list.reserve(vertices.size()); adjacency_list.reserve(vertices.size());
for(auto adjacency_matrix = builder.graph(); auto curr: adjacency_matrix.vertices()) { for(std::size_t col{}, length = vertices.size(); col < length; ++col) {
std::vector<std::size_t> in{}; std::vector<std::size_t> reachable{};
std::vector<std::size_t> out{}; const auto row = col * length;
bool is_top_level = true;
for(auto &&edge: adjacency_matrix.in_edges(curr)) { for(std::size_t next{}; next < length; ++next) {
in.push_back(edge.first); if(edges[row + next]) {
reachable.push_back(next);
}
} }
for(auto &&edge: adjacency_matrix.out_edges(curr)) { for(std::size_t next{}; next < length && is_top_level; ++next) {
out.push_back(edge.second); is_top_level = !edges[next * length + col];
} }
adjacency_list.emplace_back(vertices[curr], std::move(in), std::move(out)); adjacency_list.emplace_back(is_top_level, vertices[col], std::move(reachable));
} }
return adjacency_list; return adjacency_list;
@@ -423,13 +470,13 @@ public:
/*! @brief Erases all elements from a container. */ /*! @brief Erases all elements from a container. */
void clear() { void clear() {
builder.clear(); dependencies.clear();
vertices.clear(); vertices.clear();
} }
private: private:
dense_map<id_type, std::vector<std::pair<std::size_t, bool>>, identity> dependencies;
std::vector<vertex_data> vertices; std::vector<vertex_data> vertices;
flow builder;
}; };
} // namespace entt } // namespace entt

View File

@@ -1,26 +0,0 @@
#ifndef ENTT_ENTITY_RANGES_HPP
#define ENTT_ENTITY_RANGES_HPP
#if __has_include(<version>)
# include <version>
#
# if defined(__cpp_lib_ranges)
# include <ranges>
# include "fwd.hpp"
template<class... Args>
inline constexpr bool std::ranges::enable_borrowed_range<entt::basic_view<Args...>>{true};
template<class... Args>
inline constexpr bool std::ranges::enable_borrowed_range<entt::basic_group<Args...>>{true};
template<class... Args>
inline constexpr bool std::ranges::enable_view<entt::basic_view<Args...>>{true};
template<class... Args>
inline constexpr bool std::ranges::enable_view<entt::basic_group<Args...>>{true};
# endif
#endif
#endif

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More