Compare commits

..

13 Commits

Author SHA1 Message Date
Michele Caini
dd6863f71d update single include file 2021-07-28 09:58:14 +02:00
Michele Caini
dc7c976518 Ready to cut v3.8.1 2021-07-28 09:56:40 +02:00
Michele Caini
3408556eea sparse_set: fix an issue with assuring pages properly on emplace (close #746) 2021-07-27 18:53:30 +02:00
Michele Caini
f94b9773da build system: test id type std::uint64_t on the CI, all platforms 2021-07-26 23:37:55 +02:00
Michele Caini
e0b3786d97 test: make tests for entity traits work when id_type is std::uint64_t 2021-07-26 23:37:50 +02:00
Michele Caini
4047cb01a8 test: make tests for hashed string work when id_type is std::uint64_t 2021-07-26 23:37:46 +02:00
Michele Caini
8cfd08b137 sparse set: make vs2017 work (more or less) fine when id_type is std::uint64_t 2021-07-26 23:37:42 +02:00
Michele Caini
311011672c entity: avoid UBs when id type is std::uint64_t (close #745) 2021-07-26 23:37:39 +02:00
Hussein Taher
6df19c833d snapshot: fix warning for discarding a nodiscard (#728) 2021-07-26 23:37:36 +02:00
Michele Caini
78d9e71888 test: get rid of inconsistent line (close #741) 2021-07-26 23:37:32 +02:00
Michele Caini
94131648dd type_traits: try to also please gcc :) 2021-07-26 23:37:12 +02:00
Michele Caini
151f180199 core: make is_equality_comparable[_v] work with iterators (close #739) 2021-07-26 23:37:02 +02:00
Michele Caini
bb8bfaf262 meta_any: avoid risky fallthrough in the vtable (close #736) 2021-07-26 23:36:51 +02:00
345 changed files with 61259 additions and 134719 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++17
build:macos --cxxopt=-std=c++17
common:ci --announce_rc
common:ci --verbose_failures
common:ci --keep_going
test:ci --test_output=errors
try-import %workspace%/user.bazelrc

View File

@@ -1,43 +0,0 @@
BasedOnStyle: llvm
---
AccessModifierOffset: -4
AlignEscapedNewlines: DontAlign
AllowShortBlocksOnASingleLine: Always
AllowShortEnumsOnASingleLine: true
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: WithoutElse
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: true
AlwaysBreakTemplateDeclarations: Yes
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeTernaryOperators: true
ColumnLimit: 0
DerivePointerAlignment: false
IncludeCategories:
- Regex: '<[[:alnum:]_]+>'
Priority: 1
- Regex: '<(gtest|gmock)/'
Priority: 2
- Regex: '<[[:alnum:]_./]+>'
Priority: 3
- Regex: '<entt/'
Priority: 4
- Regex: '.*'
Priority: 5
IncludeIsMainRegex: "^$"
IndentPPDirectives: AfterHash
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: false
Language: Cpp
PointerAlignment: Right
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: false
SpaceAroundPointerQualifiers: After
SpaceBeforeCaseColon: false
SpaceBeforeCtorInitializerColon: false
SpaceBeforeInheritanceColon: false
SpaceBeforeParens: Never
SpaceBeforeRangeBasedForLoopColon: false
Standard: Latest
TabWidth: 4
UseTab: Never

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

8
.github/FUNDING.yml vendored
View File

@@ -1,4 +1,12 @@
# These are supported funding model platforms
github: skypjack
patreon:
open_collective:
ko_fi:
tidelift:
community_bridge:
liberapay:
issuehunt:
otechie:
custom: https://www.paypal.me/skypjack

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,102 +5,110 @@ on: [push, pull_request]
jobs:
linux:
timeout-minutes: 10
strategy:
matrix:
compiler:
- { pkg: g++, exe: 'g++', version: 12 }
- { pkg: g++, exe: 'g++', version: 13 }
- { pkg: g++, exe: 'g++', version: 14 }
- { pkg: clang, exe: 'clang++', version: 16 }
- { pkg: clang, exe: 'clang++', version: 17 }
- { pkg: clang, exe: 'clang++', version: 18 }
- pkg: g++-7
exe: g++-7
- pkg: g++-8
exe: g++-8
- pkg: g++-9
exe: g++-9
- pkg: g++
exe: g++
- pkg: clang-8
exe: clang++-8
- pkg: clang-9
exe: clang++-9
- pkg: clang-10
exe: clang++-10
- pkg: clang
exe: clang++
id_type: [uint32, uint64]
include:
- id_type: uint64
id_type_option: -DENTT_BUILD_UINT64=ON
timeout-minutes: 15
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
- name: Install compiler
run: |
sudo apt update
sudo apt install -y ${{ matrix.compiler.pkg }}-${{ matrix.compiler.version }}
sudo apt-get update
sudo apt-get install ${{ matrix.compiler.pkg }} -y
- name: Compile tests
working-directory: build
env:
CXX: ${{ matrix.compiler.exe }}-${{ matrix.compiler.version }}
CXX: ${{ matrix.compiler.exe }}
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 ${{ matrix.id_type_option }} ..
make -j4
- name: Run tests
working-directory: build
env:
CTEST_OUTPUT_ON_FAILURE: 1
run: ctest -C Debug -j4
run: ctest --timeout 10 -C Debug -j4
windows:
timeout-minutes: 10
strategy:
matrix:
toolset: [default, v142, v143, clang-cl]
os: [windows-latest, windows-2016]
toolset: [clang-cl, default, v141]
id_type: [uint32, uint64]
include:
- toolset: v142
toolset_option: -T"v142"
- toolset: v143
toolset_option: -T"v143"
- toolset: clang-cl
toolset_option: -T"ClangCl"
- toolset: v141
toolset_option: -T"v141"
- id_type: uint64
id_type_option: -DENTT_BUILD_UINT64=ON
exclude:
- os: windows-2016
toolset: clang-cl
- os: windows-2016
toolset: v141
timeout-minutes: 15
runs-on: windows-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 ${{ matrix.toolset_option }} ..
cmake --build . -j 4
- name: Run tests
working-directory: build
env:
CTEST_OUTPUT_ON_FAILURE: 1
run: ctest -C Debug -j4
macos:
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:
matrix:
os: [windows-latest, macOS-latest, ubuntu-latest]
id_type: ["std::uint32_t", "std::uint64_t"]
cxx_std: [cxx_std_17, cxx_std_20]
timeout-minutes: 15
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
- name: Compile tests
working-directory: build
run: |
cmake -DENTT_BUILD_TESTING=ON -DENTT_CXX_STD=${{ matrix.cxx_std }} -DENTT_ID_TYPE=${{ matrix.id_type }} ..
cmake -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON ${{ matrix.id_type_option }} ${{ matrix.toolset_option }} ..
cmake --build . -j 4
- name: Run tests
working-directory: build
env:
CTEST_OUTPUT_ON_FAILURE: 1
run: ctest -C Debug -j4
run: ctest --timeout 10 -C Debug -j4
macos:
timeout-minutes: 10
strategy:
matrix:
id_type: [uint32, uint64]
include:
- id_type: uint64
id_type_option: -DENTT_BUILD_UINT64=ON
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 ${{ matrix.id_type_option }} ..
make -j4
- name: Run tests
working-directory: build
env:
CTEST_OUTPUT_ON_FAILURE: 1
run: ctest --timeout 10 -C Debug -j4

View File

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

View File

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

View File

@@ -4,28 +4,30 @@ on: [push, pull_request]
jobs:
clang:
timeout-minutes: 15
linux:
timeout-minutes: 10
strategy:
matrix:
compiler: [clang++]
id_type: ["std::uint32_t", "std::uint64_t"]
cxx_std: [cxx_std_17, cxx_std_20]
id_type: [uint32, uint64]
include:
- id_type: uint64
id_type_option: -DENTT_BUILD_UINT64=ON
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
- name: Compile tests
working-directory: build
env:
CXX: ${{ matrix.compiler }}
run: |
cmake -DENTT_USE_SANITIZER=ON -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON -DENTT_CXX_STD=${{ matrix.cxx_std }} -DENTT_ID_TYPE=${{ matrix.id_type }} ..
cmake -DENTT_USE_SANITIZER=ON -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON ${{ matrix.id_type_option }} ..
make -j4
- name: Run tests
working-directory: build
env:
CTEST_OUTPUT_ON_FAILURE: 1
run: ctest -C Debug -j4
run: ctest --timeout 10 -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-*
/test/bazel-*
/user.bazelrc
*.bazel.lock

View File

@@ -11,12 +11,10 @@ ceeac
ColinH
corystegel
Croydon
cschreib
cugone
dbacchet
dBagrat
djarek
DNKpp
DonKult
drglove
eliasdaler
@@ -33,7 +31,6 @@ Lawrencemm
markand
mhammerc
Milerius
Minimonium
morbo84
m-waka
netpoetica
@@ -42,7 +39,6 @@ Oortonaut
Paolo-Oliverio
pgruenbacher
prowolf
Qix-
stefanofiorentino
suVrik
szunhammer
@@ -50,7 +46,6 @@ The5-1
vblanco20-1
willtunnels
WizardIke
WoLfulus
w1th0utnam3
xissburg
zaucy

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",
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,85 +1,77 @@
#
# EnTT
#
cmake_minimum_required(VERSION 3.15.7)
cmake_minimum_required(VERSION 3.12.4)
#
# Building in-tree is not allowed (we take care of your craziness).
#
if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR})
message(FATAL_ERROR "Prevented in-tree built. Please create a build directory outside of the source code and call cmake from there. Thank you.")
endif()
#
# Read project version
#
set(ENTT_VERSION_REGEX "#define ENTT_VERSION_.*[ \t]+(.+)")
file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/src/entt/config/version.h" ENTT_VERSION REGEX ${ENTT_VERSION_REGEX})
list(TRANSFORM ENTT_VERSION REPLACE ${ENTT_VERSION_REGEX} "\\1")
string(JOIN "." ENTT_VERSION ${ENTT_VERSION})
#
# Project configuration
#
project(
EnTT
VERSION ${ENTT_VERSION}
DESCRIPTION "Gaming meets modern C++ - a fast and reliable entity-component system (ECS) and much more"
HOMEPAGE_URL "https://github.com/skypjack/entt"
LANGUAGES C CXX
LANGUAGES CXX
)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
endif()
message(VERBOSE "*")
message(VERBOSE "* ${PROJECT_NAME} v${PROJECT_VERSION} (${CMAKE_BUILD_TYPE})")
message(VERBOSE "* Copyright (c) 2017-2025 Michele Caini <michele.caini@gmail.com>")
message(VERBOSE "* Copyright (c) 2017-2021 Michele Caini <michele.caini@gmail.com>")
message(VERBOSE "*")
# CMake stuff
list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules)
option(ENTT_USE_LIBCPP "Use libc++ by adding -stdlib=libc++ flag if availbale." ON)
option(ENTT_USE_SANITIZER "Enable sanitizers by adding -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined flags" OFF)
#
# Compiler stuff
#
option(ENTT_USE_LIBCPP "Use libc++ by adding -stdlib=libc++ flag if available." OFF)
option(ENTT_USE_SANITIZER "Enable sanitizers by adding -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined flags if available." OFF)
option(ENTT_USE_CLANG_TIDY "Enable static analysis with clang-tidy" OFF)
if(NOT WIN32 AND ENTT_USE_LIBCPP)
include(CheckCXXSourceCompiles)
include(CMakePushCheckState)
if(ENTT_USE_LIBCPP)
if(NOT WIN32)
include(CheckCXXSourceCompiles)
include(CMakePushCheckState)
cmake_push_check_state()
cmake_push_check_state()
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -stdlib=libc++")
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -stdlib=libc++")
check_cxx_source_compiles("
#include<type_traits>
int main() { return std::is_same_v<int, char>; }
" ENTT_HAS_LIBCPP)
cmake_pop_check_state()
endif()
check_cxx_source_compiles("
#include<type_traits>
int main() { return std::is_same_v<int, char>; }
" ENTT_HAS_LIBCPP)
if(NOT ENTT_HAS_LIBCPP)
message(VERBOSE "The option ENTT_USE_LIBCPP is set but libc++ is not available.")
endif()
endif()
if(ENTT_USE_SANITIZER)
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
set(ENTT_HAS_SANITIZER TRUE CACHE BOOL "" FORCE)
mark_as_advanced(ENTT_HAS_SANITIZER)
endif()
if(NOT ENTT_HAS_SANITIZER)
message(VERBOSE "The option ENTT_USE_SANITIZER is set but sanitizer support is not available.")
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.")
message(VERBOSE "The option ENTT_USE_LIBCPP is set (by default) but libc++ is not available. The flag will not be added to the target.")
endif()
cmake_pop_check_state()
endif()
#
# Add EnTT target
#
include(GNUInstallDirs)
@@ -93,266 +85,130 @@ target_include_directories(
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)
target_compile_features(EnTT INTERFACE cxx_std_17)
if(ENTT_USE_SANITIZER)
target_compile_options(EnTT INTERFACE $<$<CONFIG:Debug>:-fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined>)
target_link_libraries(EnTT INTERFACE $<$<CONFIG:Debug>:-fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined>)
endif()
if(ENTT_HAS_LIBCPP)
target_compile_options(EnTT BEFORE INTERFACE -stdlib=libc++)
endif()
if(ENTT_HAS_SANITIZER)
target_compile_options(EnTT INTERFACE $<$<CONFIG:Debug>:-fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined>)
target_link_libraries(EnTT INTERFACE $<$<CONFIG:Debug>:-fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined>)
endif()
target_compile_features(EnTT INTERFACE cxx_std_17)
if(ENTT_CLANG_TIDY_EXECUTABLE)
set(ENTT_CLANG_TIDY_OPTIONS ";--config-file=${EnTT_SOURCE_DIR}/.clang-tidy;--header-filter=${EnTT_SOURCE_DIR}/src/entt/.*")
#
# Install pkg-config file
#
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(EnTT_PKGCONFIG ${CMAKE_CURRENT_BINARY_DIR}/entt.pc)
set(CMAKE_CXX_CLANG_TIDY "${ENTT_CLANG_TIDY_EXECUTABLE}${ENTT_CLANG_TIDY_OPTIONS}")
endif()
configure_file(
${EnTT_SOURCE_DIR}/cmake/in/entt.pc.in
${EnTT_PKGCONFIG}
@ONLY
)
# Add EnTT goodies
install(
FILES ${EnTT_PKGCONFIG}
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig
)
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)
#
# Install EnTT
#
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/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
tools/davey.hpp
entt.hpp
fwd.hpp
tools.hpp
)
include(CMakePackageConfigHelpers)
list(TRANSFORM HEADERS_FILES APPEND ">" OUTPUT_VARIABLE HEADERS_BUILD_INTERFACE)
list(TRANSFORM HEADERS_BUILD_INTERFACE PREPEND "$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/")
install(
TARGETS EnTT
EXPORT EnTTTargets
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
list(TRANSFORM HEADERS_FILES APPEND ">" OUTPUT_VARIABLE HEADERS_INSTALL_INTERFACE)
list(TRANSFORM HEADERS_INSTALL_INTERFACE PREPEND "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/entt/")
write_basic_package_version_file(
EnTTConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY AnyNewerVersion
)
target_sources(EnTT INTERFACE ${HEADERS_BUILD_INTERFACE} ${HEADERS_INSTALL_INTERFACE})
endif()
configure_package_config_file(
${EnTT_SOURCE_DIR}/cmake/in/EnTTConfig.cmake.in
EnTTConfig.cmake
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/EnTT/cmake
)
if(ENTT_INCLUDE_NATVIS)
if(MSVC)
set(ENTT_HAS_NATVIS TRUE CACHE BOOL "" FORCE)
mark_as_advanced(ENTT_HAS_NATVIS)
endif()
export(
EXPORT EnTTTargets
FILE ${CMAKE_CURRENT_BINARY_DIR}/EnTTTargets.cmake
NAMESPACE EnTT::
)
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()
install(
EXPORT EnTTTargets
FILE EnTTTargets.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/EnTT/cmake
NAMESPACE EnTT::
)
if(ENTT_HAS_NATVIS)
set(
NATVIS_FILES
config.natvis
container.natvis
core.natvis
entity.natvis
graph.natvis
locator.natvis
meta.natvis
poly.natvis
process.natvis
resource.natvis
signal.natvis
)
install(
FILES
${PROJECT_BINARY_DIR}/EnTTConfig.cmake
${PROJECT_BINARY_DIR}/EnTTConfigVersion.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/EnTT/cmake
)
list(TRANSFORM NATVIS_FILES APPEND ">" OUTPUT_VARIABLE NATVIS_BUILD_INTERFACE)
list(TRANSFORM NATVIS_BUILD_INTERFACE PREPEND "$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/natvis/")
install(DIRECTORY src/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
list(TRANSFORM NATVIS_FILES APPEND ">" OUTPUT_VARIABLE NATVIS_INSTALL_INTERFACE)
list(TRANSFORM NATVIS_INSTALL_INTERFACE PREPEND "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/entt/natvis/")
export(PACKAGE EnTT)
target_sources(EnTT INTERFACE ${NATVIS_BUILD_INTERFACE} ${NATVIS_INSTALL_INTERFACE})
endif()
# Install EnTT and all related files
option(ENTT_INSTALL "Install EnTT and all related files." OFF)
if(ENTT_INSTALL)
# Install pkg-config file
include(JoinPaths)
set(EnTT_PKGCONFIG ${CMAKE_CURRENT_BINARY_DIR}/entt.pc)
join_paths(EnTT_PKGCONFIG_INCLUDEDIR "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}")
configure_file(
${EnTT_SOURCE_DIR}/cmake/in/entt.pc.in
${EnTT_PKGCONFIG}
@ONLY
)
install(
FILES ${EnTT_PKGCONFIG}
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig
)
# Install EnTT
include(CMakePackageConfigHelpers)
install(
TARGETS EnTT
EXPORT EnTTTargets
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
write_basic_package_version_file(
EnTTConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY AnyNewerVersion
)
configure_package_config_file(
${EnTT_SOURCE_DIR}/cmake/in/EnTTConfig.cmake.in
EnTTConfig.cmake
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/EnTT/cmake
)
export(
EXPORT EnTTTargets
FILE ${CMAKE_CURRENT_BINARY_DIR}/EnTTTargets.cmake
NAMESPACE EnTT::
)
install(
EXPORT EnTTTargets
FILE EnTTTargets.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/EnTT/cmake
NAMESPACE EnTT::
)
install(
FILES
${PROJECT_BINARY_DIR}/EnTTConfig.cmake
${PROJECT_BINARY_DIR}/EnTTConfigVersion.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/EnTT/cmake
)
install(
DIRECTORY src/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
FILES_MATCHING
PATTERN "*.h"
PATTERN "*.hpp"
PATTERN "*.natvis"
)
export(PACKAGE EnTT)
endif()
# Tests and testbed
#
# Tests
#
option(ENTT_BUILD_TESTING "Enable building tests." OFF)
option(ENTT_BUILD_TESTBED "Enable building testbed." OFF)
if(ENTT_BUILD_TESTING OR ENTT_BUILD_TESTBED)
set(ENTT_ID_TYPE std::uint32_t CACHE STRING "Type of identifiers to use for tests and testbed")
set(ENTT_CXX_STD cxx_std_17 CACHE STRING "C++ standard revision to use for tests and testbed")
if(ENTT_BUILD_TESTING)
option(ENTT_FIND_GTEST_PACKAGE "Enable finding gtest package." OFF)
option(ENTT_BUILD_BENCHMARK "Build benchmark." OFF)
option(ENTT_BUILD_EXAMPLE "Build examples." OFF)
option(ENTT_BUILD_LIB "Build lib tests." OFF)
option(ENTT_BUILD_SNAPSHOT "Build snapshot test with Cereal." OFF)
option(ENTT_BUILD_UINT64 "Build using 64b entity identifiers" OFF)
# Tests and tesetbed do not work together because SDL gets confused with EnTT tests
if(ENTT_BUILD_TESTING)
option(ENTT_FIND_GTEST_PACKAGE "Enable finding gtest package." OFF)
option(ENTT_BUILD_BENCHMARK "Build benchmark." OFF)
option(ENTT_BUILD_EXAMPLE "Build examples." OFF)
option(ENTT_BUILD_LIB "Build lib tests." OFF)
option(ENTT_BUILD_SNAPSHOT "Build snapshot test with Cereal." OFF)
include(CTest)
enable_testing()
add_subdirectory(test)
elseif(ENTT_BUILD_TESTBED)
add_subdirectory(testbed)
endif()
include(CTest)
enable_testing()
add_subdirectory(test)
endif()
#
# Documentation
#
option(ENTT_BUILD_DOCS "Enable building with documentation." OFF)
if(ENTT_BUILD_DOCS)
add_subdirectory(docs)
find_package(Doxygen 1.8)
if(DOXYGEN_FOUND)
add_subdirectory(docs)
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,7 +1,7 @@
# Contributing
First of all, thank you very much for taking the time to contribute to the
`EnTT` library.<br/>
`EnTT` framework.<br/>
How to do it mostly depends on the type of contribution:
* If you have a question, **please** ensure there isn't already an answer for
@@ -28,7 +28,7 @@ How to do it mostly depends on the type of contribution:
* If you found a bug and you wrote a patch to fix it, open a new
[pull request](https://github.com/skypjack/entt/pulls) with your code.
**Please**, add some tests to avoid regressions in future if possible, it
would be really appreciated. Note that the `EnTT` library has a
would be really appreciated. Note that the `EnTT` framework has a
[coverage at 100%](https://coveralls.io/github/skypjack/entt?branch=master)
(at least it was at 100% at the time I wrote this file) and this is the reason
for which you can be confident with using it in a production environment.

View File

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

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")

158
README.md
View File

@@ -1,18 +1,15 @@
![EnTT: Gaming meets modern C++](https://user-images.githubusercontent.com/1812216/103550016-90752280-4ea8-11eb-8667-12ed2219e137.png)
<!--
@cond TURN_OFF_DOXYGEN
-->
[![Build Status](https://github.com/skypjack/entt/workflows/build/badge.svg)](https://github.com/skypjack/entt/actions)
[![Coverage](https://codecov.io/gh/skypjack/entt/branch/master/graph/badge.svg)](https://codecov.io/gh/skypjack/entt)
[![Try online](https://img.shields.io/badge/try-online-brightgreen)](https://godbolt.org/z/zxW73f)
[![Documentation](https://img.shields.io/badge/docs-doxygen-blue)](https://skypjack.github.io/entt/)
[![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)
[![Documentation](https://img.shields.io/badge/docs-docsforge-blue)](http://entt.docsforge.com/)
[![Gitter chat](https://badges.gitter.im/skypjack/entt.png)](https://gitter.im/skypjack/entt)
[![Discord channel](https://img.shields.io/discord/707607951396962417?logo=discord)](https://discord.gg/5BjPWBd)
> `EnTT` has been a dream so far, we haven't found a single bug to date and it's
> super easy to work with
>
> -- Every EnTT User Ever
[![Donate](https://img.shields.io/badge/donate-paypal-blue.svg)](https://www.paypal.me/skypjack)
`EnTT` is a header-only, tiny and easy to use library for game programming and
much more written in **modern C++**.<br/>
@@ -21,7 +18,7 @@ in [**Minecraft**](https://minecraft.net/en-us/attribution/) by Mojang, the
[**ArcGIS Runtime SDKs**](https://developers.arcgis.com/arcgis-runtime/) by Esri
and the amazing [**Ragdoll**](https://ragdolldynamics.com/).<br/>
If you don't see your project in the list, please open an issue, submit a PR or
add the [\#entt](https://github.com/topics/entt) tag to your _topics_! :+1:
add the [#entt](https://github.com/topics/entt) tag to your _topics_! :+1:
---
@@ -36,8 +33,7 @@ Don't forget to check the
there.
Do you want to support `EnTT`? Consider becoming a
[**sponsor**](https://github.com/users/skypjack/sponsorship) or making a
donation via [**PayPal**](https://www.paypal.me/skypjack).<br/>
[**sponsor**](https://github.com/users/skypjack/sponsorship).
Many thanks to [these people](https://skypjack.github.io/sponsorship/) and
**special** thanks to:
@@ -49,11 +45,10 @@ Many thanks to [these people](https://skypjack.github.io/sponsorship/) and
* [Introduction](#introduction)
* [Code Example](#code-example)
* [Motivation](#motivation)
* [Benchmark](#benchmark)
* [Performance](#performance)
* [Integration](#integration)
* [Requirements](#requirements)
* [CMake](#cmake)
* [Natvis support](#natvis-support)
* [Packaging Tools](#packaging-tools)
* [pkg-config](#pkg-config)
* [Documentation](#documentation)
@@ -61,6 +56,9 @@ Many thanks to [these people](https://skypjack.github.io/sponsorship/) and
* [EnTT in Action](#entt-in-action)
* [Contributors](#contributors)
* [License](#license)
<!--
@endcond TURN_OFF_DOXYGEN
-->
# Introduction
@@ -75,34 +73,33 @@ This project started off as a pure entity-component system. Over time the
codebase has grown as more and more classes and functionalities were added.<br/>
Here is a brief, yet incomplete list of what it offers today:
* Built-in **RTTI system** mostly similar to the standard one.
* A `constexpr` utility for human-readable **resource names**.
* Minimal **configuration system** built using the monostate pattern.
* Incredibly fast **entity-component system** with its own _pay for what you
use_ policy, unconstrained component types with optional pointer stability and
hooks for storage customization.
* Statically generated integer **identifiers** for types (assigned either at
compile-time or at runtime).
* A `constexpr` utility for human readable **resource names**.
* A minimal **configuration system** built using the monostate pattern.
* An incredibly fast **entity-component system** based on sparse sets, with its
own _pay for what you use_ policy to adjust performance and memory usage
according to the users' requirements.
* Views and groups to iterate entities and components and allow different access
patterns, from **perfect SoA** to fully random.
* A lot of **facilities** built on top of the entity-component system to help
the users and avoid reinventing the wheel.
* General purpose **execution graph builder** for optimal scheduling.
the users and avoid reinventing the wheel (dependencies, snapshot, handles,
support for **reactive systems** and so on).
* The smallest and most basic implementation of a **service locator** ever seen.
* A built-in, non-intrusive and macro-free runtime **reflection system**.
* **Static polymorphism** made simple and within everyone's reach.
* A few homemade containers, like a sparse set based **hash map**.
* A **cooperative scheduler** for processes of any type.
* All that is needed for **resource management** (cache, loaders, handles).
* Delegates, **signal handlers** and a tiny event dispatcher.
* Delegates, **signal handlers** (with built-in support for collectors) and a
tiny event dispatcher for immediate and delayed events to integrate in loops.
* A general purpose **event emitter** as a CRTP idiom based class template.
* And **much more**! Check out the
[**wiki**](https://github.com/skypjack/entt/wiki).
Consider this list a work in progress as well as the project. The whole API is
fully documented in-code for those who are brave enough to read it.<br/>
Please, do note that all tools are also DLL-friendly now and run smoothly across
boundaries.
fully documented in-code for those who are brave enough to read it.
One thing known to most is that `EnTT` is also used in **Minecraft**.<br/>
It is also known that `EnTT` is used in **Minecraft**.<br/>
Given that the game is available literally everywhere, I can confidently say
that the library has been sufficiently tested on every platform that can come to
mind.
@@ -159,7 +156,7 @@ int main() {
## Motivation
I started developing `EnTT` for the _wrong_ reason: my goal was to design an
entity-component system to beat another well known open source library both in
entity-component system to beat another well known open source solution both in
terms of performance and possibly memory usage.<br/>
In the end, I did it, but it wasn't very satisfying. Actually it wasn't
satisfying at all. The fastest and nothing more, fairly little indeed. When I
@@ -170,28 +167,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
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
bad or offer dubious comparisons just to make this library seem cooler.<br/>
I leave this activity to others, if they enjoy it (and it seems that some people
actually like it). I prefer to make better use of my time.
The proposed entity-component system is incredibly fast to iterate entities and
components, this is a fact. Some compilers make a lot of optimizations because
of how `EnTT` works, some others aren't that good. In general, if we consider
real world cases, `EnTT` is somewhere between a bit and much faster than many of
the other solutions around, although I couldn't check them all for obvious
reasons.
If you are interested, you can compile the `benchmark` test in release mode (to
enable compiler optimizations, otherwise it would make little sense) by setting
the `ENTT_BUILD_BENCHMARK` option of `CMake` to `ON`, then evaluate yourself
whether you're satisfied with the results or not.
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
completely wrong, many others are simply incomplete, good at omitting some
information and using the wrong function to compare a given feature. Certainly
there are also good ones but they age quickly if nobody updates them, especially
when the library they are dealing with is actively developed.<br/>
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
can't say exactly whether `EnTT` is used correctly or not. However, even if used
poorly, it should still give the reader an idea of where it's going to operate.
when the library they are dealing with is actively developed.
The choice to use `EnTT` should be based on its carefully designed API, its
set of features and the general performance, **not** because some single
benchmark shows it to be the fastest tool available.
In the future I'll likely try to get even better performance while still adding
new features, mainly for fun.<br/>
If you want to contribute and/or have suggestions, feel free to make a PR or
open an issue to discuss your idea.
# Integration
@@ -232,27 +238,13 @@ build system of the library.
## CMake
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`),
embedding (as in `add_subdirectory`), fetching (as in `FetchContent`) or using
it in many of the ways that you can think of and that involve `CMake`.<br/>
Covering all possible cases would require a treatise and not a simple README
file, but I'm confident that anyone reading this section also knows what it's
about and can use `EnTT` from a `CMake` project without problems.
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
When using `CMake`, just enable the option `ENTT_INCLUDE_NATVIS` and enjoy
it.<br/>
Otherwise, most of the tools are covered via Natvis and all files can be found
in the `natvis` subdirectory, divided by module.<br/>
If you spot errors or have suggestions, any contribution is welcome!
Covering all possible cases would require a treaty and not a simple README file,
but I'm confident that anyone reading this section also knows what it's about
and can use `EnTT` from a `CMake` project without problems.
## Packaging Tools
@@ -273,12 +265,6 @@ If you spot errors or have suggestions, any contribution is welcome!
$ vcpkg install entt
```
Or you can use the `experimental` feature to test the latest changes:
```
vcpkg install entt[experimental] --head
```
The `EnTT` port in `vcpkg` is kept up to date by Microsoft team members and
community contributors.<br/>
If the version is out of date, please
@@ -319,18 +305,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)
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.12.2")
```
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.
## pkg-config
@@ -348,22 +322,32 @@ The documentation is based on [doxygen](http://www.doxygen.nl/). To build it:
$ cmake .. -DENTT_BUILD_DOCS=ON
$ make
The API reference is created in HTML format in the `build/docs/html` directory.
To navigate it with your favorite browser:
The API reference will be created in HTML format within the directory
`build/docs/html`. To navigate it with your favorite browser:
$ cd build
$ your_favorite_browser docs/html/index.html
<!--
@cond TURN_OFF_DOXYGEN
-->
The same version is also available [online](https://skypjack.github.io/entt/)
for the latest release, that is the last stable tag.<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
to the project where users can find all related documentation pages.
<!--
@endcond TURN_OFF_DOXYGEN
-->
# Tests
To compile and run the tests, `EnTT` requires *googletest*.<br/>
`cmake` downloads and compiles the library before compiling anything else. In
order to build the tests, set the `CMake` option `ENTT_BUILD_TESTING` to `ON`.
`cmake` will download and compile the library before compiling anything else.
In order to build the tests, set the `CMake` option `ENTT_BUILD_TESTING` to
`ON`.
To build the most basic set of tests:
@@ -374,6 +358,9 @@ To build the most basic set of tests:
Note that benchmarks are not part of this set.
<!--
@cond TURN_OFF_DOXYGEN
-->
# EnTT in Action
`EnTT` is widely used in private and commercial applications. I cannot even
@@ -391,7 +378,7 @@ open an issue or a PR and I'll be glad to add them to the list.
# 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
experience or you do want to get part of the project for some other reason, feel
@@ -401,15 +388,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.
If you decide to participate, please see the guidelines for
[contributing](https://github.com/skypjack/entt/blob/master/CONTRIBUTING.md)
before to create issues or pull requests.<br/>
[contributing](CONTRIBUTING.md) before to create issues or pull
requests.<br/>
Take also a look at the
[contributors list](https://github.com/skypjack/entt/blob/master/AUTHORS) to
know who has participated so far.
<!--
@endcond TURN_OFF_DOXYGEN
-->
# License
Code and documentation Copyright (c) 2017-2025 Michele Caini.<br/>
Code and documentation Copyright (c) 2017-2021 Michele Caini.<br/>
Colorful logo Copyright (c) 2018-2021 Richard Caseres.
Code released under

71
TODO
View File

@@ -1,37 +1,36 @@
EXAMPLES
* filter on runtime values/variables (not only types)
* support to polymorphic types (see #859)
DOC:
* custom storage/view
* update entity doc when the storage based model is in place
* in-place O(1) release/destroy for non-orphaned entities, out-of-sync model
* view: single vs multi type views are no longer a thing actually
* bump entities, reserved bits on identifiers
TODO:
* review all NOLINT
* bring nested groups back in place (see bd34e7f)
* long term feature: shared_ptr less locator and resource cache
* 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
* combine version-mask-vs-version-bits tricks with reserved bits to allow things like enabling/disabling
* self contained entity traits to avoid explicit specializations (ie enum constants)
* 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
* review cmake warning about FetchContent_Populate (need .28 and EXCLUDE_FROM_ALL for FetchContent)
* 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)
* FetchContent_Populate -> FetchContent_MakeAvailable warnings
* doc: IMPLICIT_DIR_DOCS for dir docs or \dir
* 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)
* meta: conversion_helper machinery has lot of room for improvements
* organizer: view/storage only based model, no registry
* allow to replace std:: with custom implementations
* add examples (and credits) from @alanjfs :)
* custom pools example (multi instance, tables, enable/disable, and so on...)
WIP:
* remove view/storage dispatcher, add support to relax policy constraints on user request (eg view.use<T>())
* improve perf for sparse_set/storage::insert/emplace/destroy/remove/...
* custom allocators all over
WIP:
* make value_type available from meta container types, otherwise we have to default construct a container to get it
* make it possible to register externally managed pools with the registry (allow for system centric mode)
* registry: switch to the udata/mixin model and get rid of poly storage, use pointer to sparse set only for pools, discard pool_data type.
* it's now possible to have 0 as null entity/version, so we can finally switch to it
* make pools available (registry/view/group), review operator| for views
* page size: add per-pool size, allow for 0 sizes (old fully packed array)
* compressed pair to exploit ebo in sparse set and the others
* isolate view iterator, unwrap iterators in registry ::remove/::erase/::destroy to use the faster solution for non-view iterators
* remove view each<T>(F), each<T>(), make view::use return a view and remove the mutable data member
* resource, forward the id to the loader from the cache and if constexpr the call to load, update doc and describe customization points
* make it possible to create views of the type `view<T, T>`, add get by index and such, allow to register custom pools by name with the registry
* add user data to type_info
* any_vector for context variables
* make const registry::view thread safe, switch to a view<T...>{registry} model (long term goal)
* weak reference wrapper example with custom storage
* headless (sparse set only) view
* write documentation for custom storages and views!!
* make runtime views use opaque storage and therefore return also elements.
* add exclude-only views to combine with packs
* entity-aware observer, add observer functions aside observer class
* deprecate non-owning groups in favor of owning views and view packs, introduce lazy owning views
* snapshot: support for range-based archives
* add example: 64 bit ids with 32 bits reserved for users' purposes

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++17",
"-w",
],
("@rules_cc//cc/compiler:msvc-cl", "@rules_cc//cc/compiler:clang-cl"): [
"/std:c++17",
"/permissive-",
"/w",
],
})

View File

@@ -1,5 +1,5 @@
prefix=@CMAKE_INSTALL_PREFIX@
includedir=@EnTT_PKGCONFIG_INCLUDEDIR@
includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
Name: EnTT
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

@@ -15,7 +15,7 @@ void update(entt::registry &registry) {
auto view = registry.view<position, velocity>();
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);
@@ -28,7 +28,7 @@ void update(entt::registry &registry) {
void update(std::uint64_t dt, entt::registry &registry) {
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.y += vel.dy * dt;

View File

@@ -1,59 +1,39 @@
#
# Doxygen configuration (documentation)
#
find_package(Doxygen 1.13)
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)
include(FetchContent)
configure_file(doxy.in doxy.cfg @ONLY)
FetchContent_Declare(
doxygen-awesome-css
GIT_REPOSITORY https://github.com/jothepro/doxygen-awesome-css
GIT_TAG main
GIT_SHALLOW 1
)
add_custom_target(
docs ALL
COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxy.cfg
WORKING_DIRECTORY ${EnTT_SOURCE_DIR}
VERBATIM
SOURCES
dox/extra.dox
md/config.md
md/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_GetProperties(doxygen-awesome-css)
if(NOT doxygen-awesome-css_POPULATED)
FetchContent_Populate(doxygen-awesome-css)
set(doxygen-awesome-css_INCLUDE_DIR ${doxygen-awesome-css_SOURCE_DIR})
endif()
set(DOXY_SOURCE_DIRECTORY ${EnTT_SOURCE_DIR}/src)
set(DOXY_CSS_DIRECTORY ${doxygen-awesome-css_INCLUDE_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/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
)
if(ENTT_INSTALL)
install(
DIRECTORY ${DOXY_OUTPUT_DIRECTORY}/html
DESTINATION share/${PROJECT_NAME}-${PROJECT_VERSION}/
)
endif()
endif()
install(
DIRECTORY ${DOXY_OUTPUT_DIRECTORY}/html
DESTINATION share/${PROJECT_NAME}-${PROJECT_VERSION}/
)

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,53 +1,57 @@
# Crash Course: configuration
<!--
@cond TURN_OFF_DOXYGEN
-->
# Table of Contents
* [Introduction](#introduction)
* [Definitions](#definitions)
* [ENTT_NOEXCEPTION](#entt_noexception)
* [ENTT_NOEXCEPTION](#entt_noexcept)
* [ENTT_USE_ATOMIC](#entt_use_atomic)
* [ENTT_ID_TYPE](#entt_id_type)
* [ENTT_SPARSE_PAGE](#entt_sparse_page)
* [ENTT_PACKED_PAGE](#entt_packed_page)
* [ENTT_ASSERT](#entt_assert)
* [ENTT_ASSERT_CONSTEXPR](#entt_assert_constexpr)
* [ENTT_DISABLE_ASSERT](#entt_disable_assert)
* [ENTT_NO_ETO](#entt_no_eto)
* [ENTT_STANDARD_CPP](#entt_standard_cpp)
<!--
@endcond TURN_OFF_DOXYGEN
-->
# Introduction
`EnTT` has become almost completely customizable over time, in many
respects. These variables are just one of the many ways to customize how it
works.<br/>
`EnTT` doesn't offer many hooks for customization but it certainly offers
some.<br/>
In the vast majority of cases, users will have no interest in changing the
default parameters. For all other cases, the list of possible configurations
with which it is possible to adjust the behavior of the library at runtime can
be found below.
with which it's possible to adjust the behavior of the library at runtime can be
found below.
# Definitions
All options are intended as parameters to the compiler (or user-defined macros
within the compilation units, if preferred).<br/>
Each parameter can result in internal library definitions. It 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
will remain stable over time unlike the options below.
## ENTT_NOEXCEPTION
Define this variable without assigning any value to it to turn off exception
handling in `EnTT`.<br/>
This is roughly equivalent to setting the compiler flag `-fno-exceptions` but is
also limited to this library only.
This parameter can be used to switch off exception handling in `EnTT`.<br/>
To do this, simply define the variable without assigning any value to it. This
is roughly equivalent to setting the compiler flag `-ff-noexceptions`.
## ENTT_USE_ATOMIC
In general, `EnTT` 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 user is the one who knows if a synchronization point is required.<br/>
However, some internal static data shared between threads should be atomic when
using `EnTT` from multiple threads, even when dealing with local storage. Define
this macro without assigning any value to it to get the job done.
the user is the only one who knows if and when a synchronization point is
required.<br/>
However, some features aren't easily accessible to users and can be made
thread-safe by means of this definition.
## ENTT_ID_TYPE
@@ -58,51 +62,42 @@ default type if necessary.
## ENTT_SPARSE_PAGE
It is known that the ECS module of `EnTT` is based on _sparse sets_. What is
less known perhaps is that the sparse arrays are paged to reduce memory
usage.<br/>
It's known that the ECS module of `EnTT` is based on _sparse sets_. What is less
known perhaps is that the sparse arrays are paged to reduce memory usage.<br/>
Default size of pages (that is, the number of elements they contain) is 4096 but
users can adjust it if appropriate. In all 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.
## ENTT_PACKED_PAGE
As it happens with sparse arrays, packed arrays are also paginated. However, in
this case the aim is not to reduce memory usage but to have pointer stability
upon component creation.<br/>
Similar to sparse arrays, packed arrays of components are paginated as well. In
However, int this case the aim isn't to reduce memory usage but to have pointer
stability upon component creation.<br/>
Default size of pages (that is, the number of elements they contain) is 1024 but
users can adjust it if appropriate. In all 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.
## 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
if not used correctly.<br/>
To get around this, the library relies on a lot of asserts for the purpose of
detecting errors in debug builds. By default, it uses `assert` internally. Users
are allowed to overwrite its behavior by setting this variable.
### ENTT_ASSERT_CONSTEXPR
Usually, an assert within a `constexpr` function 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`.
detecting errors in debug builds. By default, it uses `assert` internally, but
users are allowed to overwrite its behavior by setting this variable.
### ENTT_DISABLE_ASSERT
Assertions may in turn affect performance to an extent when enabled. Whether
`ENTT_ASSERT` and `ENTT_ASSERT_CONSTEXPR` are redefined or not, all asserts can
be disabled at once by means of this definition.<br/>
Note that `ENTT_DISABLE_ASSERT` takes precedence over the redefinition of the
other variables and is therefore meant to disable all controls no matter what.
`ENTT_ASSERT` is redefined or not, all asserts can be disabled at once by means
of this definition.<br/>
Note that `ENTT_DISABLE_ASSERT` takes precedence over the redefinition of
`ENTT_ASSERT` and is therefore meant to disable all controls no matter what.
## ENTT_NO_ETO
In order to reduce memory consumption and increase performance, empty types are
never 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
dedicated storage for them.
@@ -110,7 +105,7 @@ dedicated storage for them.
`EnTT` mixes non-standard language features with others that are perfectly
compliant to offer some of its functionalities.<br/>
This definition prevents the library from using non-standard techniques, that
is, functionalities that are not fully compliant with the standard C++.<br/>
This definition will prevent the library from using non-standard techniques,
that is, functionalities that aren't fully compliant with the standard C++.<br/>
While there are no known portability issues at the time of this writing, this
should make the library fully portable anyway if needed.

View File

@@ -1,85 +0,0 @@
# Crash Course: containers
# Table of Contents
* [Introduction](#introduction)
* [Containers](#containers)
* [Dense map](#dense-map)
* [Dense set](#dense-set)
* [Adaptors](#adaptors)
* [Table](#table)
# Introduction
The standard C++ library offers a wide range of containers and adaptors already.
It is really difficult to do better (although it is very easy to do worse, as
many examples available online demonstrate).<br/>
`EnTT` does not try in any way to replace what is offered by the standard. Quite
the opposite, given the widespread use that is made of standard containers.<br/>
However, the library also tries to fill a gap in features and functionalities by
making available some containers and adaptors initially developed for internal
use.
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
needs.<br/>
For all containers and adaptors made available, full test coverage and stability
over time is guaranteed as usual.
# Containers
## Dense map
The dense map made available in `EnTT` is a hash map that aims to return a
packed array of elements, so as to reduce the number of jumps in memory during
iterations.<br/>
The implementation is based on _sparse sets_ and each bucket is identified by an
implicit list within the packed array itself.
The interface is very close to its counterpart in the standard library, that is,
the `std::unordered_map` class.<br/>
However, both local and non-local iterators returned by a dense map belong to
the input iterator category although they respectively model the concepts of a
_forward iterator_ type and a _random access iterator_ type.<br/>
This is because they return a pair of references rather than a reference to a
pair. In other words, dense maps return a so called _proxy iterator_ the value
type of which is:
* `std::pair<const Key &, Type &>` for non-const iterator types.
* `std::pair<const Key &, const Type &>` for const iterator types.
This is quite different from what any standard library map returns and should be
taken into account when looking for a drop-in replacement.
## Dense set
The dense set made available in `EnTT` is a hash set that aims to return a
packed array of elements, so as to reduce the number of jumps in memory during
iterations.<br/>
The implementation is based on _sparse sets_ and each bucket is identified by an
implicit list within the packed array itself.
The interface is in all respects similar to its counterpart in the standard
library, that is, the `std::unordered_set` class.<br/>
However, this type of set also supports reverse iteration and therefore offers
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.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,8 @@
# Frequently Asked Questions
<!--
@cond TURN_OFF_DOXYGEN
-->
# Table of Contents
* [Introduction](#introduction)
@@ -7,20 +10,22 @@
* [Why is my debug build on Windows so slow?](#why-is-my-debug-build-on-windows-so-slow)
* [How can I represent hierarchies with my components?](#how-can-i-represent-hierarchies-with-my-components)
* [Custom entity identifiers: yay or nay?](#custom-entity-identifiers-yay-or-nay)
* [Warning 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)
* [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
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/>
If you do not find your answer here, there are two cases: nobody has done it
yet, or this section needs updating. In both cases, you can
[open a new issue](https://github.com/skypjack/entt/issues/new) or enter either
the [gitter channel](https://gitter.im/skypjack/entt) or the
[discord server](https://discord.gg/5BjPWBd) to ask for help.<br/>
If you don't find your answer here, there are two cases: nobody has done it yet
or this section needs updating. In both cases, try to
[open a new issue](https://github.com/skypjack/entt/issues/new) or enter the
[gitter channel](https://gitter.im/skypjack/entt) and ask your question.
Probably someone already has an answer for you and we can then integrate this
part of the documentation.
@@ -29,50 +34,50 @@ part of the documentation.
## Why is my debug build on Windows so slow?
`EnTT` is an experimental project that I also use to keep me up-to-date with the
latest revision of the language and the standard library. For this reason, it is
likely that some classes you are working with are using standard containers
under the hood.<br/>
Unfortunately, it is known that the standard containers are not particularly
latest revision of the language and the standard library. For this reason, it's
likely that some classes you're working with are using standard containers under
the hood.<br/>
Unfortunately, it's known that the standard containers aren't particularly
performing in debugging (the reasons for this go beyond this document) and are
even less so on Windows, apparently. Fortunately, this can also be mitigated a
even less so on Windows apparently. Fortunately this can also be mitigated a
lot, achieving good results in many cases.
First of all, there are two things to do in a Windows project:
* Disable the [`/JMC`](https://docs.microsoft.com/cpp/build/reference/jmc)
option (_Just My Code_ debugging), available starting with Visual Studio 2017
option (_Just My Code_ debugging), available starting in Visual Studio 2017
version 15.8.
* Set the [`_ITERATOR_DEBUG_LEVEL`](https://docs.microsoft.com/cpp/standard-library/iterator-debug-level)
macro to 0. This will disable checked iterators and iterator debugging.
Moreover, set the `ENTT_DISABLE_ASSERT` variable or redefine the `ENTT_ASSERT`
macro to disable internal debug checks in `EnTT`:
Moreover, the macro `ENTT_ASSERT` should be redefined to disable internal checks
made by `EnTT` in debug:
```cpp
#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.
With these changes, debug performance should increase enough in most cases. If
you want something more, you can also switch to an optimization level `O0` or
preferably `O1`.
With these changes, debug performance should increase enough for most cases. If
you want something more, you can can also switch to an optimization level `O0`
or preferably `O1`.
## How can I represent hierarchies with my components?
This is one of the first questions that anyone makes when starting to work with
the entity-component-system architectural pattern.<br/>
There are several approaches to the problem, and the best one depends mainly on
the real problem one is facing. In all cases, how to do it does not strictly
depend on the library in use, but the latter certainly allows or not different
techniques depending on how the data are laid out.
There are several approaches to the problem and whats the best one depends
mainly on the real problem one is facing. In all cases, how to do it doesn't
strictly depend on the library in use, but the latter can certainly allow or
not different techniques depending on how the data are laid out.
I tried to describe some of the approaches that fit well with the model of
`EnTT`. [This](https://skypjack.github.io/2019-06-25-ecs-baf-part-4/) is the
first post of a series that tries to _explore_ the problem. More will probably
come in the future.<br/>
I tried to describe some of the techniques that fit well with the model of
`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
come in future.<br/>
In addition, `EnTT` also offers the possibility to create stable storage types
and therefore have pointer stability for one, all or some components. This is by
far the most convenient solution when it comes to creating hierarchies and
@@ -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:
* 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`.
* If you want to avoid conflicts when using multiple registries.
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.
## 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
On Windows, a header file defines two macros `min` and `max` which may result in
conflicts with their counterparts in the standard library and therefore in
errors during compilation.
It is a pretty big problem. However, fortunately it is not a problem of `EnTT`
and there is a fairly simple solution to it.<br/>
It consists in defining the `NOMINMAX` macro before including any other header
It's a pretty big problem but fortunately it's not a problem of `EnTT` and there
is a fairly simple solution to it.<br/>
It consists in defining the `NOMINMAX` macro before to include any other header
so as to get rid of the extra definitions:
```cpp
@@ -119,14 +149,15 @@ more details.
## The standard and the non-copyable types
`EnTT` uses internally the trait `std::is_copy_constructible_v` to check if a
component is actually copyable. However, this trait does not really check
whether a type is actually copyable. Instead, it just checks that a suitable
copy constructor and copy operator exist.<br/>
This can lead to surprising results due to some idiosyncrasies of the standard.
component is actually copyable. This trait doesn't check if an object can
actually be copied but only verifies if there is a copy constructor
available.<br/>
This can lead to surprising results due to some idiosyncrasies of the standard
mainly related to the need to guarantee backward compatibility.
For example, `std::vector` defines a copy constructor that is conditionally
enabled depending on whether the value type is copyable or not. As a result,
`std::is_copy_constructible_v` returns true for the following specialization:
For example, `std::vector` defines a copy constructor no matter if its value
type is copyable or not. As a result, `std::is_copy_constructible_v` is true
for the following specialization:
```cpp
struct type {
@@ -134,75 +165,39 @@ struct type {
};
```
However, the copy constructor is effectively disabled upon specialization.
Therefore, trying to assign an instance of this type to an entity may trigger a
compilation error.<br/>
As a workaround, users can mark the type explicitly as non-copyable. This also
suppresses the implicit generation of the move constructor and operator, which
will therefore have to be defaulted accordingly:
When trying to assign an instance of this type to an entity in the ECS part,
this may trigger a compilation error because we cannot really make a copy of
it.<br/>
As a workaround, users can mark the type explicitly as non-copyable:
```cpp
struct type {
type(const type &) = delete;
type(type &&) = default;
type & operator=(const type &) = delete;
type & operator=(type &&) = default;
std::vector<std::unique_ptr<action>> vec;
};
```
Note that aggregate initialization is also disabled as a consequence.<br/>
Fortunately, this type of trick is quite rare. The bad news is that there is no
way to deal with it at the library level, this being due to the design of the
language. On the other hand, the fact that the language itself also offers a way
to mitigate the problem makes it manageable.
Unfortunately, this will also disable aggregate initialization.
## 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/>
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
replaced) to an entity.
* `on_update` is called whenever an existing component is modified or replaced.
* `on_destroyed` is called when a component is explicitly or implicitly removed
from an entity.
Among the most controversial functions can be found `emplace_or_replace` and
`destroy`. However, following the above rules, it is quite simple to know what
`destroy`. However, following the above rules, it's quite simple to know what
will happen.<br/>
In the first case, `on_created` is invoked if the entity has not the component,
otherwise the latter is replaced and therefore `on_update` is triggered. As for
the second case, components are removed from their entities and thus freed when
they are recycled. It means that `on_destroyed` is triggered for every component
owned by the entity that is destroyed.
## 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 constexpr id_type value() noexcept {
return hashed_string::value("Type");
}
[[nodiscard]] constexpr 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
<!--
@cond TURN_OFF_DOXYGEN
-->
# Table of Contents
* [Working across boundaries](#working-across-boundaries)
* [Smooth until proven otherwise](#smooth-until-proven-otherwise)
* [The EnTT way](#the-entt-way)
* [Meta context](#meta-context)
* [Memory management](#memory-management)
<!--
@endcond TURN_OFF_DOXYGEN
-->
# Working across boundaries
@@ -13,58 +19,88 @@
general and on GNU/Linux when default visibility was set to hidden. The
limitation was mainly due to a custom utility used to assign unique, sequential
identifiers with different types.<br/>
Fortunately, nowadays `EnTT` works smoothly across boundaries.
Fortunately, nowadays using `EnTT` across boundaries is easier. However, use in
standalone applications is favored and user intervention is otherwise required.
## Smooth until proven otherwise
## The EnTT way
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 in itself (in fact, it's the basis of an API so convenient
to use). However, a way is needed to recognize the objects whose type has been
erased on the other side of a boundary.<br/>
The `type_hash` class template is how identifiers are generated and thus made
available to the rest of the library. In general, this class arouses little
interest. The only exception is when a conflict between identifiers occurs
(definitely uncommon though) or when the default solution proposed by `EnTT` is
not suitable for the user's purposes.<br/>
The section dedicated to `type_info` contains all the details to get around the
issue in a concise and elegant way. Please refer to the specific documentation.
available to the rest of the library. The `type_seq` class template makes all
types _indexable_ instead, so as to speed up the lookup.
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
everything work nicely across boundaries.<br/>
On the other hand, everything should run smoothly when working with plugins or
shared libraries that do not export any symbols.
In general, these classes don't arouse much interest. The only exceptions are:
For those who need more details, the test suite contains many examples 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.
* When a conflict between identifiers occurs (definitely uncommon though) or
when the default solution proposed by `EnTT` isn't suitable for the user's
purposes.<br/>
The section dedicated to `type_info` contains all the details to get around
the problem in a concise and elegant way. Please refer to the specific
documentation.
* When working with linked libraries that also export all required symbols.<br/>
Compile definitions `ENTT_API_EXPORT` and `ENTT_API_IMPORT` should be passed
respectively where there is a need to import or export the symbols defined by
`EnTT`, so as to make everything work nicely across boundaries.
* When working with plugins or shared libraries that don't export any symbol. In
this case, `type_seq` confuses the other classes by giving potentially wrong
information to them.<br/>
To avoid problems, it's required to provide a custom generator. Briefly, it's
necessary to specialize the `type_seq` class and make it point to a context
that is also shared between the main application and the dynamically loaded
libraries or plugins.<br/>
This will make the type system available to the whole application, not just to
a particular tool such as the registry or the dispatcher. It means that a call
to `type_seq::value()` will return the same identifier for the same type from
both sides of a boundary and can be used reliably for any purpose.
For anyone who needs more details, the test suite contains multiple examples
covering the most common cases (see the `lib` directory for all details).<br/>
It goes without saying that it's impossible to cover all the possible cases.
However, what is offered should hopefully serve as a basis for all of them.
## Meta context
The runtime reflection system deserves a special mention when it comes to using
it across boundaries.<br/>
Since it is linked already to a static context to which the elements are
attached and different contexts do not relate to each other, they must be
Since it's linked already to a static context to which the visible components
are attached and different contexts don't relate to each other, they must be
_shared_ to allow the use of meta types across boundaries.
Fortunately, sharing a context is also trivial to do. First of all, the local
one is acquired in the main space:
Sharing a context is trivial though. First of all, the local one must be
acquired in the main space:
```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,
thus discarding or storing aside the local one:
Then, it must passed to the receiving space that will set it as its global
context, thus releasing the local one that remains available but is no longer
referred to by the runtime reflection system:
```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
types attached, no matter where they are created.<br/>
Note that _replacing_ the main context does not also propagate changes across
boundaries. In other words, replacing a context results in the decoupling of the
two sides and therefore a divergence in the contents.
From now on, both spaces will refer to the same context and on it will be
attached the new visible meta types, no matter where they are created.<br/>
A context can also be reset and then associated again locally as:
```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
@@ -81,10 +117,9 @@ is unknown to the former, a dedicated pool is created within the registry on
first use.<br/>
As one can guess, this pool is instantiated on a different side of the boundary
from the `registry`. Therefore, the instance is now managing memory from
different spaces, and this can quickly lead to crashes if not properly
addressed.
different spaces and this can quickly lead to crashes if not properly 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
`EnTT` classes from time to time and as appropriate.<br/>
Refer to the test suite for some examples, read the documentation available

View File

@@ -1,62 +1,33 @@
# 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
mention most of them because of some signatures I put on some documents time
ago. Fortunately, there are also people who took the time to implement open
source projects based on `EnTT` and did not hold back when it came to
documenting them.
source projects based on `EnTT` and didn't hold back when it came to documenting
them.
Below an incomplete list of games, applications and articles that can be used as
a reference.<br/>
Where I put the word _apparently_ means that the use of `EnTT` is documented but
the authors did not make explicit announcements or contacted me directly.
a reference. Where I put the word _apparently_ means that the use of `EnTT` is
documented but the authors didn't make explicit announcements or contacted me
directly.
If you know of other resources out there that are about `EnTT`, feel free to
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
I hope this list can grow much more in the future:
* Games:
* [Minecraft](https://minecraft.net/en-us/attribution/) by
[Mojang](https://mojang.com/): of course, **that** Minecraft, see the
open source attributions page for more details.
* [Minecraft 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
[Mojang](https://mojang.com/): an augmented reality game for mobile, that
lets users bring Minecraft into the real world.
* [Ember Sword](https://embersword.com/): a modern Free-to-Play MMORPG with a
player-driven economy, a classless combat system, and scarce, tradable
cosmetic collectibles.
* Apparently [Diablo II: Resurrected](https://diablo2.blizzard.com/) by
[Blizzard](https://www.blizzard.com/): monsters, heroes, items, spells, all
resurrected. Thanks unknown insider.
* [Apparently](https://www.youtube.com/watch?v=P8xvOA3ikrQ&t=1105s)
[Call of Duty: Vanguard](https://www.callofduty.com/vanguard) by
[Sledgehammer Games](https://www.sledgehammergames.com/): I can neither
confirm nor deny, but there is a license I know in the credits.
* Apparently [D&D Dark Alliance](https://darkalliance.wizards.com) by
[Wizards of the Coast](https://company.wizards.com): your party, their
funeral.
* [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
to play online.
* [Antkeeper](https://github.com/antkeeper/antkeeper-source): an ant colony
simulation [game](https://antkeeper.com/).
* [War of Rights](https://store.steampowered.com/app/424030/War_of_Rights/): a
multiplayer game set during the perilous days of the American Civil War, by
Campfire Games.
* [Openblack](https://github.com/openblack/openblack): open source
reimplementation of the game _Black & White_ (2001).
* [Land of the Rair](https://github.com/LandOfTheRair/core2): the new backend
@@ -73,7 +44,7 @@ I hope the following lists can grow much more in the future.
puzzler with logic gates and other cool stuff.
[Check it out](https://indi-kernick.itch.io/the-machine-web-version).
* [EnTTPong](https://github.com/DomRe/EnttPong): a basic game made to showcase
different parts of `EnTT` and C++17.
different parts of EnTT and C++17.
* [Randballs](https://github.com/gale93/randballs): simple `SFML` and `EnTT`
playground.
* [EnTT Tower Defense](https://github.com/Daivuk/tddod): a data oriented tower
@@ -97,60 +68,12 @@ I hope the following lists can grow much more in the future.
by Quake.
* [Destroid](https://github.com/tyrannicaltoucan/destroid): _one-bazillionth_
arcade game about shooting dirty rocks in space, inspired by Asteroids.
* [Wanderer](https://github.com/albin-johansson/wanderer): a 2D
exploration-based indie game.
* [Spelunky® Classic remake](https://github.com/dbeef/spelunky-psp): a truly
* [Wanderer](https://github.com/albin-johansson/wanderer): a 2D exploration
based indie game.
* [Spelunky® Classic remake](https://github.com/dbeef/spelunky-psp): A truly
multiplatform experience with a rewrite from scratch.
* [CubbyTower](https://github.com/utilForever/CubbyTower): a simple tower
defense game using C++ with Entity Component System (ECS).
* [Runeterra](https://github.com/utilForever/Runeterra): Legends of Runeterra
simulator using C++ with some reinforcement learning.
* [Black Sun](https://store.steampowered.com/app/1670930/Black_Sun/): fly your
spaceship through a large 2D open world.
* [PokeMaster](https://github.com/utilForever/PokeMaster): Pokémon Battle
simulator using C++ with some reinforcement learning.
* [HomeHearth](https://youtu.be/GrEWl8npL9Y): choose your hero, protect the
town, before it is too late.
* [City Builder Game](https://github.com/PhiGei2000/CityBuilderGame): a simple
city-building game using C++ and OpenGL.
* [BattleSub](https://github.com/bfeldpw/battlesub): two player 2D submarine
game with some fluid dynamics.
* [Crimson Rush](https://github.com/WilKam01/LuaCGame): a dungeon-crawler and
rougelike inspired game about exploring and surviving as long as possible.
* [Space Fight](https://github.com/cholushkin/SpaceFight): one screen
multi-player arcade shooter game prototype.
* [Confetti Party](https://github.com/hexerei/entt-confetti): C++ sample
application as a starting point using `EnTT` and `SDL2`.
* [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`.
## 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.
* Engines and the like:
* [Aether Engine](https://hadean.com/spatial-simulation/)
[v1.1+](https://docs.hadean.com/v1.1/Licenses/) by
[Hadean](https://hadean.com/): a library designed for spatially partitioning
@@ -169,9 +92,6 @@ I hope the following lists can grow much more in the future.
vrooooommm.
* [Antara Gaming SDK](https://github.com/KomodoPlatform/antara-gaming-sdk):
the Komodo Gaming Software Development Kit.
* [XVP](https://ravingbots.com/xvp-expansive-vehicle-physics-for-unreal-engine/):
[_eXpansive Vehicle Physics_](https://github.com/raving-bots/xvp/wiki/Plugin-integration-guide)
plugin for Unreal Engine.
* [Apparently](https://teamwisp.github.io/credits/)
[Wisp](https://teamwisp.github.io/product/) by
[Team Wisp](https://teamwisp.github.io/): an advanced real-time ray tracing
@@ -196,60 +116,8 @@ I hope the following lists can grow much more in the future.
framework in C++17 for backend development.
* [Unity/EnTT](https://github.com/TongTungGiang/unity-entt): tech demo of a
native simulation layer using `EnTT` and `Unity` as a rendering engine.
* [OverEngine](https://github.com/OverShifted/OverEngine): an overengineered
game engine.
* [Electro](https://github.com/Electro-Technologies/Electro): high performance
3D game engine with a high emphasis on rendering.
* [Kawaii](https://github.com/Mathieu-Lala/Kawaii_Engine): a modern data
oriented game engine.
* [Becketron](https://github.com/Doctor-Foxling/Becketron): a game engine
written mostly in C++.
* [Spatial Engine](https://github.com/luizgabriel/Spatial.Engine): a
cross-platform engine created on top of Google's filament rendering engine.
* [Kaguya](https://github.com/KaiH0717/Kaguya): D3D12 Rendering Engine.
* [OpenAWE](https://github.com/OpenAWE-Project/OpenAWE): open implementation
of the Alan Wake Engine.
* [Nazara Engine](https://github.com/DigitalPulseSoftware/NazaraEngine): fast,
cross-platform, object-oriented API to help in daily developer life.
* [Billy Engine](https://github.com/billy4479/BillyEngine): some kind of 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`.
## Articles, videos and blog posts:
* Articles, videos and blog posts:
* [Some posts](https://skypjack.github.io/tags/#entt) on my personal
[blog](https://skypjack.github.io/) are about `EnTT`, for those who want to
know **more** on this project.
@@ -273,23 +141,6 @@ I hope the following lists can grow much more in the future.
- ... And so on.
[Check out](https://www.youtube.com/channel/UCQ-W1KE9EYfdxhL6S4twUNw) the
_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):
huge space battle built entirely from scratch.
* [Space Battle](https://github.com/vblanco20-1/ECS_SpaceBattle): huge space
@@ -312,34 +163,25 @@ I hope the following lists can grow much more in the future.
project retrospect by [Eric Hildebrand](https://www.erichildebrand.net/).
* [EnTT Entity Component System Gaming Library](https://gamefromscratch.com/entt-entity-component-system-gaming-library/):
`EnTT` on GameFromScratch.com.
* [Custom C++ server for UE5](https://youtu.be/fbXZVNCOvjM) optimized for
MMO(RPG)s and its [follow-up](https://youtu.be/yGlZeopx2hU) episode about
player bots and full external ECS: a series definitely worth looking at.
## Any Other Business:
* Any Other Business:
* [ArcGIS Runtime SDKs](https://developers.arcgis.com/arcgis-runtime/) by
[Esri](https://www.esri.com/): they use `EnTT` for the internal ECS and the
cross-platform C++ rendering engine. The SDKs are 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
such as
[Explorer](https://play.google.com/store/apps/details?id=com.esri.explorer),
[Collector](https://play.google.com/store/apps/details?id=com.esri.arcgis.collector)
and
[Navigator](https://play.google.com/store/apps/details?id=com.esri.navigator).
* [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)
by [Cenit](http://www.cenit.com/en_EN/about-us/overview.html): they use
`EnTT` to drive their simulation, that is, the communication between robot
controller emulator and renderer.
* [Ragdoll](https://ragdolldynamics.com/): real-time physics for Autodesk Maya
2020.
* [Project Lagrange](https://github.com/adobe/lagrange): a robust geometry
processing library by [Adobe](https://github.com/adobe).
* [AtomicDEX](https://github.com/KomodoPlatform/atomicDEX-Desktop): a secure
wallet and noncustodial decentralized exchange rolled into one application.
wallet and non-custodial decentralized exchange rolled into one application.
* [Apparently](https://www.linkedin.com/in/skypjack/)
[NIO](https://www.nio.io/): there was a collaboration to make some changes
to `EnTT`, at the time used for internal projects.
@@ -362,3 +204,6 @@ I hope the following lists can grow much more in the future.
* GitHub contains also
[many other examples](https://github.com/search?o=desc&q=%22skypjack%2Fentt%22&s=indexed&type=Code)
of use of `EnTT` from which to take inspiration if interested.
If you know of other resources out there that are about `EnTT`, feel free to
open an issue or a PR and I'll be glad to add them to this page.

View File

@@ -1,82 +1,75 @@
# Crash Course: service locator
<!--
@cond TURN_OFF_DOXYGEN
-->
# Table of Contents
* [Introduction](#introduction)
* [Service locator](#service-locator)
* [Opaque handles](#opaque-handles)
<!--
@endcond TURN_OFF_DOXYGEN
-->
# Introduction
Usually, service locators are tightly bound to the services they expose. It is
hard to define a general purpose solution.<br/>
This tiny class tries to fill the gap and gets rid of the burden of defining a
different specific locator for each application.
Usually service locators are tightly bound to the services they expose and it's
hard to define a general purpose solution. This template based implementation
tries to fill the gap and to get rid of the burden of defining a different
specific locator for each application.<br/>
This class is tiny, partially unsafe and thus risky to use. Moreover it doesn't
fit probably most of the scenarios in which a service locator is required. Look
at it as a small tool that can sometimes be useful if users know how to handle
it.
# Service locator
The service locator API tries to mimic that of `std::optional` and adds some
extra functionalities on top of it such as allocator support.<br/>
There are a couple of functions to set up a service, namely `emplace` and
`allocate_emplace`:
The API is straightforward. The basic idea is that services are implemented by
means of interfaces and rely on polymorphism.<br/>
The locator is instantiated with the base type of the service if any and a
concrete implementation is provided along with all the parameters required to
initialize it. As an example:
```cpp
entt::locator<interface>::emplace<service>(argument);
entt::locator<interface>::allocate_emplace<service>(allocator, argument);
// the service has no base type, a locator is used to treat it as a kind of singleton
entt::service_locator<my_service>::set(params...);
// sets up an opaque service
entt::service_locator<audio_interface>::set<audio_implementation>(params...);
// resets (destroys) the service
entt::service_locator<audio_interface>::reset();
```
The difference is that the latter expects an allocator as the first argument and
uses it to allocate the service itself.<br/>
Once a service is set up, it is retrieved using the `value` function:
The locator can also be queried to know if an active service is currently set
and to retrieve it if necessary (either as a pointer or as a reference):
```cpp
interface &service = entt::locator<interface>::value();
// no service currently set
auto empty = entt::service_locator<audio_interface>::empty();
// gets a (possibly empty) shared pointer to the service ...
std::shared_ptr<audio_interface> ptr = entt::service_locator<audio_interface>::get();
// ... or a reference, but it's undefined behaviour if the service isn't set yet
audio_interface &ref = entt::service_locator<audio_interface>::ref();
```
Since the service may not be set (and therefore this function may result in an
undefined behavior), the `has_value` and `value_or` functions are also available
to test a service locator and to get a fallback service in case there is none:
A common use is to wrap the different locators in a container class, creating
aliases for the various services:
```cpp
if(entt::locator<interface>::has_value()) {
struct locator {
using camera = entt::service_locator<camera_interface>;
using audio = entt::service_locator<audio_interface>;
// ...
};
// ...
void init() {
locator::camera::set<camera_null>();
locator::audio::set<audio_implementation>(params...);
// ...
}
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
exist and therefore the fallback service is constructed and returned. In all
other cases, they are discarded.<br/>
Finally, to reset a service, use the `reset` function.
## Opaque handles
Sometimes it 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
<!--
@cond TURN_OFF_DOXYGEN
-->
# Table of Contents
* [Introduction](#introduction)
@@ -7,10 +10,13 @@
* [Concept and implementation](#concept-and-implementation)
* [Deduced interface](#deduced-interface)
* [Defined interface](#defined-interface)
* [Fulfill a concept](#fulfill-a-concept)
* [Fullfill a concept](#fullfill-a-concept)
* [Inheritance](#inheritance)
* [Static polymorphism in the wild](#static-polymorphism-in-the-wild)
* [Storage size and alignment requirement](#storage-size-and-alignment-requirement)
<!--
@endcond TURN_OFF_DOXYGEN
-->
# Introduction
@@ -18,18 +24,19 @@ Static polymorphism is a very powerful tool in C++, albeit sometimes cumbersome
to obtain.<br/>
This module aims to make it simple and easy to use.
The library allows to define _concepts_ as interfaces to fulfill with concrete
classes without having to inherit from a common base.<br/>
Among others, this is one of the advantages of static polymorphism in general
The library allows to define _concepts_ as interfaces to fullfill with concrete
classes withouth having to inherit from a common base.<br/>
This is, among others, one of the advantages of static polymorphism in general
and of a generic wrapper like that offered by the `poly` class template in
particular.<br/>
The result is an object to pass around as such and not through a reference or a
pointer, as it happens when it comes to working with dynamic polymorphism.
What users get is an object that can be passed around as such and not through a
reference or a pointer, as happens when it comes to working with dynamic
polymorphism.
Since the `poly` class template makes use of `entt::any` internally, it also
supports most of its feature. For example, the possibility to create aliases to
existing and thus unmanaged objects. This allows users to exploit the static
polymorphism while maintaining ownership of objects.<br/>
supports most of its feature. Among the most important, the possibility to
create aliases to existing and thus unmanaged objects. This allows users to
exploit the static polymorphism while maintaining ownership of objects.<br/>
Likewise, the `poly` class template also benefits from the small buffer
optimization offered by the `entt::any` class and therefore minimizes the number
of allocations, avoiding them altogether where possible.
@@ -37,7 +44,7 @@ of allocations, avoiding them altogether where possible.
## Other libraries
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.
* [`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.
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
flaw is the API that I find slightly more cumbersome than other solutions.<br/>
The latter was undoubtedly a source of inspiration for this module. Although I
flaw is the API which I find slightly more cumbersome than other solutions.<br/>
The latter was undoubtedly a source of inspiration for this module, although I
opted for different choices in the implementation of both the final API and some
features.
feature.
Either way, the authors are gurus of the C++ community, people I only have to
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/>
For this purpose, the library offers a single class that supports both deduced
and fully defined interfaces. Although having interfaces deduced automatically
is convenient and allows users to write less code in most cases, it has some
limitations. It is therefore useful to be able to get around the deduction by
is convenient and allows users to write less code in most cases, this has some
limitations and it's therefore useful to be able to get around the deduction by
providing a custom definition for the static virtual table.
Once the interface is defined, a generic implementation is needed to fulfill the
concept itself.<br/>
Once the interface is defined, it will be sufficient to provide a generic
implementation to fullfill the concept.<br/>
Also in this case, the library allows customizations based on types or families
of types, so as to be able to go beyond the generic case where necessary.
## Deduced interface
This is how a concept with a deduced interface is defined:
This is how a concept with a deduced interface is introduced:
```cpp
struct Drawable: entt::type_list<> {
@@ -86,8 +93,8 @@ struct Drawable: entt::type_list<> {
};
```
It is 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
It's recognizable by the fact that it inherits from an empty type list.<br/>
Functions can also be const, accept any number of paramters and return a type
other than `void`:
```cpp
@@ -101,19 +108,19 @@ 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/>
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
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:
```cpp
struct Drawable: entt::type_list<> {
template<typename Base>
struct type: Base {
void draw() const { entt::poly_call<0>(*this); }
bool draw() const { entt::poly_call<0>(*this); }
};
// ...
@@ -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
the deduced ones?<br>
In fact, this is the limitation that can be worked around by manually defining
the static virtual table.
Because, in fact, this is exactly the limitation that can be worked around by
manually defining the static virtual table.
When things are deduced, there is an implicit constraint.<br/>
If the concept exposes a member function called `draw` with function type
`void()`, a concept is satisfied:
`void()`, a concept can be satisfied:
* Either by a class that exposes a member function with the same name and the
same signature.
@@ -171,18 +178,18 @@ 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
interface itself.
In other words, it is 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/>
Similarly, it is not possible to deduce a function in the static virtual table
In other words, it's not possible to make use of functions not belonging to the
interface, even if they are present in the types that fulfill the concept.<br/>
Similarly, it's not possible to deduce a function in the static virtual table
with a function type different from that of the associated member function in
the interface itself.
Explicitly defining a static virtual table suppresses the deduction step and
allows maximum flexibility when providing the implementation for a concept.
## Fulfill a concept
## Fullfill 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
struct Drawable: entt::type_list<> {
@@ -193,9 +200,9 @@ struct Drawable: entt::type_list<> {
};
```
In this case, it is stated that the `draw` method of a generic type is enough to
satisfy the requirements of the `Drawable` concept.<br/>
Both member functions and free functions are supported to fulfill concepts:
In this case, it's stated that the `draw` method of a generic type will be
enough to satisfy the requirements of the `Drawable` concept.<br/>
Both member functions and free functions are supported to fullfill concepts:
```cpp
template<typename Type>
@@ -211,9 +218,9 @@ struct Drawable: entt::type_list<void()> {
Likewise, as long as the parameter types and return type support conversions to
and from those of the function type referenced in the static virtual table, the
actual implementation may differ in its function type since it is erased
actual implementation may differ in its function type since it's erased
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.
Refer to the inline documentation for more details.
@@ -221,7 +228,7 @@ Refer to the inline documentation for more details.
# Inheritance
_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
_family_, that is, they must be either all deduced or all defined.
@@ -230,8 +237,8 @@ For a deduced concept, inheritance is achieved in a few steps:
```cpp
struct DrawableAndErasable: entt::type_list<> {
template<typename Base>
struct type: typename Drawable::type<Base> {
static constexpr auto base = Drawable::impl<Drawable::type<entt::poly_inspector>>::size;
struct type: typename Drawable::template type<Base> {
static constexpr auto base = std::tuple_size_v<typename entt::poly_vtable<Drawable>::type>;
void erase() { entt::poly_call<base + 0>(*this); }
};
@@ -244,30 +251,31 @@ struct DrawableAndErasable: entt::type_list<> {
```
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
_size_ of the static virtual table of the base class is used as an offset for
the local indexes.<br/>
size of the static virtual table of the base class is used as an offset for the
local indexes.<br/>
Finally, by means of the `value_list_cat_t` utility, the implementation consists
in appending the new functions to the previous list.
As for a defined concept instead, the list of types is _extended_ in a 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
_concept_ into its underlying `type_list` object:
As for a defined concept instead, also the list of types must be extended, in a
similar way to what is shown for the implementation of the above concept.<br/>
To do this, it's useful to declare a function that allows to convert a _concept_
into its underlying `type_list` object:
```cpp
template<typename... Type>
entt::type_list<Type...> as_type_list(const entt::type_list<Type...> &);
```
The definition is not strictly required, since the function is only used through
a `decltype` as it follows:
The definition isn't strictly required, since the function will only be used
through a `decltype` as it follows:
```cpp
struct DrawableAndErasable: entt::type_list_cat_t<
decltype(as_type_list(std::declval<Drawable>())),
entt::type_list<void()>> {
entt::type_list<void()>
> {
// ...
};
```
@@ -278,8 +286,9 @@ Everything else is the same as already shown instead.
# Static polymorphism in the wild
Once the _concept_ and implementation are defined, it is possible to use the
`poly` class template to _wrap_ instances that meet the requirements:
Once the _concept_ and implementation have been introduced, it will be possible
to use the `poly` class template to contain instances that meet the
requirements:
```cpp
using drawable = entt::poly<Drawable>;
@@ -301,9 +310,9 @@ instance = square{};
instance->draw();
```
This class offers a wide range of constructors, from the default one (which
returns an uninitialized `poly` object) to the copy and move constructors, as
well as the ability to create objects in-place.<br/>
The `poly` class template offers a wide range of constructors, from the default
one (which will return an uninitialized `poly` object) to the copy and move
constructors, as well as the ability to create objects in-place.<br/>
Among others, there is also a constructor that allows users to wrap unmanaged
objects in a `poly` instance (either const or non-const ones):
@@ -312,23 +321,22 @@ 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:
```cpp
drawable other = instance.as_ref();
```
In both cases, although the interface of the `poly` object does not change, it
does not construct any element or take care of destroying the referenced
objects.
In both cases, although the interface of the `poly` object doesn't change, it
won't construct any element or take care of destroying the referenced objects.
Note also how the underlying concept is accessed via a call to `operator->` and
not directly as `instance.draw()`.<br/>
This allows users to decouple the API of the wrapper from that of the concept.
Therefore, where `instance.data()` invokes the `data` member function of the
poly object, `instance->data()` maps directly to the functionality exposed by
the underlying concept.
Therefore, where `instance.data()` will invoke the `data` member function of the
poly object, `instance->data()` will map directly to the functionality exposed
by the underlying concept.
# Storage size and alignment requirement
@@ -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
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
is the most stringent (the largest) for any object whose size is at most equal
to the one provided.<br/>
It is worth noting that providing a size of 0 (which is an accepted value in all
an integer. The alignment requirement is optional instead and by default such
that it's the most stringent (the largest) for any object whose size is at most
equal to the one provided.<br/>
It's worth noting that providing a size of 0 (which is an accepted value in all
respects) will force the system to dynamically allocate the contained objects in
all cases.

View File

@@ -1,68 +1,81 @@
# Crash Course: cooperative scheduler
<!--
@cond TURN_OFF_DOXYGEN
-->
# Table of Contents
* [Introduction](#introduction)
* [The process](#the-process)
* [Continuation](#continuation)
* [Shared process](#shared-process)
* [Adaptor](#adaptor)
* [The scheduler](#the-scheduler)
<!--
@endcond TURN_OFF_DOXYGEN
-->
# Introduction
Processes are a useful tool to work around the strict definition of a system and
introduce logic in a different way, usually without resorting to other component
types.<br/>
`EnTT` offers minimal support to this paradigm by introducing a few classes used
to define and execute cooperative processes.
Sometimes processes are a useful tool to work around the strict definition of a
system and introduce logic in a different way, usually without resorting to the
introduction of other components.
`EnTT` offers a minimal support to this paradigm by introducing a few classes
that users can use to define and execute cooperative processes.
# The process
A typical task inherits from the `process` class template. Derived classes also
specify what the intended type for elapsed times is.
A typical process must inherit from the `process` class template that stays true
to the CRTP idiom. Moreover, derived classes must specify what's the intended
type for elapsed times.
A process should implement the following member functions whether needed (note
that it is not required to define a function unless the derived class wants to
_override_ the default behavior):
A process should expose publicly the following member functions whether needed
(note that it isn't required to define a function unless the derived class wants
to _override_ the default behavior):
* `void update(Delta, void *);`
This is invoked once per tick until a process is explicitly aborted or ends
either with or without errors. Each process should at least define it to work
_properly_. The `void *` parameter is an opaque pointer to user data (if any)
forwarded directly to the process during an update.
It's invoked once per tick until a process is explicitly aborted or it
terminates either with or without errors. Even though it's not mandatory to
declare this member function, as a rule of thumb each process should at
least define it to work properly. The `void *` parameter is an opaque pointer
to user data (if any) forwarded directly to the process during an update.
* `void init();`
It's invoked when the process joins the running queue of a scheduler. This
happens as soon as it's attached to the scheduler if the process is a top
level one, otherwise when it replaces its parent if the process is a
continuation.
* `void succeeded();`
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.
* `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.
* `void aborted();`
This is 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
aborted immediately or not.
It's invoked only if a process is explicitly aborted. There is no guarantee
that it executes in the same tick, this depends solely on whether the
process is aborted immediately or not.
A class can also change the state of a process by invoking `succeed` and `fail`,
as well as `pause` and `unpause` the process itself.<br/>
All these are public member functions made available to manage the life cycle of
a process easily.
Derived classes can also change the internal state of a process by invoking
`succeed` and `fail`, as well as `pause` and `unpause` the process itself. All
these are protected member functions made available to be able to manage the
life cycle of a process from a derived class.
Here is a minimal example for the sake of curiosity:
```cpp
struct my_process: entt::process {
using allocator_type = typename entt::process::allocator_type;
using delta_type = typename entt::process::delta_type;
struct my_process: entt::process<my_process, std::uint32_t> {
using delta_type = std::uint32_t;
my_process(const allocator_type &allocator, delta_type delay)
: entt::process{allocator},
remaining{delay}
my_process(delta_type delay)
: remaining{delay}
{}
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/>
This pairing can be set up at creation time, keeping the processes conceptually
separate from each other while still combining them at runtime:
Lambdas and functors can't be used directly with a scheduler for they are not
properly defined processes with managed life cycles.<br/>
This class helps in filling the gap and turning lambdas and functors into
full featured processes usable by a scheduler.
The function call operator has a signature similar to the one of the `update`
function of a process but for the fact that it receives two extra arguments to
call whenever a process is terminated with success or with an error:
```cpp
my_process process{};
process.then<my_other_process>();
void(Delta delta, void *data, auto succeed, auto fail);
```
This approach allows processes to be developed in isolation and combined to
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.
Parameters have the following meaning:
The `then` function also accepts lambdas, which are associated with a dedicated
process internally:
* `delta` is the elapsed time.
* `data` is an opaque pointer to user data if any, `nullptr` otherwise.
* `succeed` is a function to call when a process terminates with success.
* `fail` is a function to call when a process terminates with errors.
```cpp
process.then([](entt::process &proc, std::uint32_t delta, void *data) {
// ...
})
```
Both `succeed` and `fail` accept no parameters at all.
The lambda function is such that it accepts a reference to the process that
manages it (to be able to terminate it, pause it and so on), plus the usual
values also passed to the `update` function.
## 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.
Note that usually users shouldn't worry about creating adaptors at all. A
scheduler creates them internally each and every time a lambda or a functor is
used as a process.
# The scheduler
A cooperative scheduler runs different processes and helps manage their life
A cooperative scheduler runs different processes and helps managing their life
cycles.
Each process is invoked once per tick. If it terminates, it is removed
automatically from the scheduler, and it is never invoked again. Otherwise,
it is 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
Each process is invoked once per tick. If it terminates, it's removed
automatically from the scheduler and it's never invoked again. Otherwise it's
a good candidate to run one more time the next tick.<br/>
A process can also have a child. In this case, the parent process is replaced
with its child when it terminates and only if it returns with success. In case
of errors, both the parent process and its child are discarded. This way, it is
easy to create a _chain of processes_ to run sequentially.
of errors, both the parent process and its child are discarded. This way, it's
easy to create chain of processes to run sequentially.
Using a scheduler is straightforward. To create it, users must provide only the
type for the elapsed times and no arguments at all:
```cpp
entt::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
uses `std::uint32_t` as a default type:
```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:
It has member functions to query its internal data structures, like `empty` or
`size`, as well as a `clear` utility to reset it to a clean state:
```cpp
// checks if there are processes still running
const auto empty = scheduler.empty();
// gets the number of processes still running
entt::scheduler::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
scheduler.clear();
```
To attach a process to a scheduler, invoke the `attach` function with a process
type and the arguments to use to construct it:
To attach a process to a scheduler there are mainly two ways:
```cpp
scheduler.attach<my_process>(_1000u);
```
* If the process inherits from the `process` class template, it's enough to
indicate its type and submit all the parameters required to construct it to
the `attach` member function:
The scheduler will also provide the process with its allocator as the first
argument.<br>
In case of lambdas or functors, the required signature is the one already seen
for the `then` function of a process:
```cpp
scheduler.attach<my_process>(1000u);
```
```cpp
scheduler.attach([](entt::process &, std::uint32_t, void *){ /* ... */ });
```
* Otherwise, in case of a lambda or a functor, it's enough to provide an
instance of the class to the `attach` member function:
In both cases, the newly created process is returned by reference and its `then`
member function is used to create chains of processes to run sequentially.<br/>
```cpp
scheduler.attach([](auto...){ /* ... */ });
```
In both cases, the return value is an opaque object that offers a `then` member
function to use to create chains of processes to run sequentially.<br/>
As a minimal example of use:
```cpp
// schedules a task in the form of a lambda function
scheduler.attach([](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
.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
@@ -210,7 +201,7 @@ scheduler.update(delta, &data);
```
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
// aborts all the processes abruptly ...
@@ -219,6 +210,3 @@ scheduler.abort(true);
// ... or gracefully during the next tick
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
# Table of Contents
* [Introduction](#introduction)
* [Similar projects](#similar-projects)
# Introduction
There are many projects similar to `EnTT`, both open source and not.<br/>
Some even borrowed some ideas from this library and expressed them in different
languages.<br/>
Others developed different architectures from scratch and therefore offer
alternative solutions with their pros and cons.
If you know of other similar projects out there, feel free to open an issue or a
PR, and I will be glad to add them to this page.<br/>
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
Below an incomplete list of those that I've come across so far.<br/>
If some terms or designs aren't clear, I recommend referring to the
[_ECS Back and Forth_](https://skypjack.github.io/tags/#ecs) series for all the
details.
I hope this list can grow much more in the future:
* C:
* [destral_ecs](https://github.com/roig/destral_ecs): a single-file ECS based
on sparse sets.
@@ -35,57 +23,41 @@ details.
* [lent](https://github.com/nem0/lent): the Donald Trump of the ECS libraries.
* C++:
* [decs](https://github.com/vblanco20-1/decs): a chunk-based archetype ECS.
* [decs](https://github.com/vblanco20-1/decs): a chunk based archetype ECS.
* [ecst](https://github.com/SuperV1234/ecst): a multithreaded compile-time
ECS that uses sparse sets to keep track of entities in systems.
* [EntityX](https://github.com/alecthomas/entityx): a bitset based ECS that
uses a single large matrix of components indexed with entities.
* [Gaia-ECS](https://github.com/richardbiely/gaia-ecs): a chunk-based
archetype ECS.
* [Polypropylene](https://github.com/pmbittner/Polypropylene): a hybrid
solution between an ECS and dynamic mixins.
* C#
* [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
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
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
platform-agnostic and table-based ECS framework.
platform agnostic and table based ECS framework.
* Go:
* [gecs](https://github.com/tutumagi/gecs): a sparse sets based ECS inspired
by `EnTT`.
* Javascript:
* [\@javelin/ecs](https://github.com/3mcd/javelin/tree/master/packages/ecs):
an archetype ECS in TypeScript.
* [ecsy](https://github.com/MozillaReality/ecsy): I have not had the time to
* [\@javelin/ecs](https://github.com/3mcd/javelin/tree/master/packages/ecs): an
archetype ECS in TypeScript.
* [ecsy](https://github.com/MozillaReality/ecsy): I haven't had the time to
investigate the underlying design of `ecsy` but it looks cool anyway.
* Perl:
* [Game::Entities](https://gitlab.com/jjatria/perl-game-entities): a simple
entity registry for ECS designs inspired by `EnTT`.
* Raku:
* [Game::Entities](https://gitlab.com/jjatria/raku-game-entities): a simple
entity registry for ECS designs inspired by `EnTT`.
* Rust:
* [Legion](https://github.com/TomGillen/legion): a chunk based archetype ECS.
* [Shipyard](https://github.com/leudz/shipyard): it borrows some ideas from
`EnTT` and offers a sparse sets based ECS with grouping functionalities.
* [Sparsey](https://github.com/LechintanTudor/sparsey): sparse set based ECS
written in Rust.
* [Specs](https://github.com/amethyst/specs): a parallel ECS based mainly on
hierarchical bitsets that allows different types of storage as needed.
* Zig
* [zig-ecs](https://github.com/prime31/zig-ecs): a _zig-ification_ of `EnTT`.
If you know of other resources out there that can be of interest for the reader,
feel free to open an issue or a PR and I'll be glad to add them to this page.

View File

@@ -1,186 +1,231 @@
# Crash Course: resource management
<!--
@cond TURN_OFF_DOXYGEN
-->
# Table of Contents
* [Introduction](#introduction)
* [The resource, the loader and the cache](#the-resource-the-loader-and-the-cache)
* [Resource handle](#resource-handle)
* [Loaders](#loaders)
* [The cache class](#the-cache-class)
<!--
@endcond TURN_OFF_DOXYGEN
-->
# Introduction
Resource management is usually one of the most critical parts of a game.
Solutions are often tuned to the particular application. There exist several
approaches and all of them are perfectly fine as long as they fit the
Resource management is usually one of the most critical part of a software like
a game. Solutions are often tuned to the particular application. There exist
several approaches and all of them are perfectly fine as long as they fit the
requirements of the piece of software in which they are used.<br/>
Examples are loading everything on start, loading on request, predictive
loading, and so on.
`EnTT` does not pretend to offer a _one-fits-all_ solution for the different
cases.<br/>
Instead, the library comes with a minimal, general purpose resource cache that
might be useful in many cases.
`EnTT` doesn't pretend to offer a _one-fits-all_ solution for the different
cases. Instead, it offers a minimal and perhaps trivial cache that can be useful
most of the time during prototyping and sometimes even in a production
environments.<br/>
For those interested in the subject, the plan is to improve it considerably over
time in terms of performance, memory usage and functionalities. Hoping to make
it, of course, one step at a time.
# The resource, the loader and the cache
Resource, loader and cache are the three main actors for the purpose.<br/>
The _resource_ is an image, an audio, a video or any other type:
There are three main actors in the model: the resource, the loader and the
cache.
The _resource_ is whatever users want it to be. An image, a video, an audio,
whatever. There are no limits.<br/>
As a minimal example:
```cpp
struct my_resource { const int value; };
```
The _loader_ is a callable type, the aim of which is to load a specific
resource:
A _loader_ is a class the aim of which is to load a specific resource. It has to
inherit directly from a dedicated base class as in the following example:
```cpp
struct my_loader final {
using result_type = std::shared_ptr<my_resource>;
struct my_loader final: entt::resource_loader<my_loader, my_resource> {
// ...
};
```
result_type operator()(int value) const {
Where `my_resource` is the type of resources it creates.<br/>
A resource loader must also expose a public const member function named `load`
that accepts a variable number of arguments and returns a shared pointer to a
resource.<br/>
As an example:
```cpp
struct my_loader: entt::resource_loader<my_loader, my_resource> {
std::shared_ptr<my_resource> load(int value) const {
// ...
return std::make_shared<my_resource>(value);
return std::shared_ptr<my_resource>(new my_resource{ value });
}
};
```
Its function operator can accept any arguments and should return a value of the
declared result type (`std::shared_ptr<my_resource>` in the example).<br/>
A loader can also overload its function call operator to make it possible to
construct the same or another resource from different lists of arguments.
In general, resource loaders should not have a state or retain data of any type.
They should let the cache manage their resources instead.<br/>
As a side note, base class and CRTP idiom aren't strictly required with the
current implementation. One could argue that a cache can easily work with
loaders of any type. However, future changes won't be breaking ones by forcing
the use of a base class today and that's why the model is already in its place.
Finally, a cache is a specialization of a class template tailored to a specific
resource and (optionally) a loader:
resource:
```cpp
using my_cache = entt::resource_cache<my_resource, my_loader>;
using my_cache = entt::resource_cache<my_resource>;
// ...
my_cache cache{};
```
The class is designed to create different caches for different resource types
and to manage each one independently in the most appropriate way.<br/>
The idea is to create different caches for different types of resources and to
manage each one independently in the most appropriate way.<br/>
As a (very) trivial example, audio tracks can survive in most of the scenes of
an application while meshes can be associated with a single scene only, then
discarded when a player leaves it.
an application while meshes can be associated with a single scene and then
discarded when users leave it.
## Resource handle
Resources are not returned directly to the caller. Instead, they are wrapped in
a _resource handle_, an instance of the `entt::resource` class template.<br/>
For those who know the _flyweight design pattern_ already, that is exactly what
it is. To all others, this is the time to brush up on some notions instead.
A shared pointer could have been used as a resource handle. In fact, the default
implementation mostly maps the interface of its standard counterpart and only
adds a few things on top of it.<br/>
However, the handle in `EnTT` is designed as a standalone class template. This
is due to the fact that specializing a class in the standard library is often
undefined behavior while having the ability to specialize the handle for one,
more or all resource types could help over time.
## Loaders
A loader is responsible for _loading_ resources (quite obviously).<br/>
By default, it is just a callable object that forwards its arguments to the
resource itself. That is, a _passthrough type_. All the work is demanded to the
constructor(s) of the resource itself.<br/>
Loaders also are fully customizable as expected.
A custom loader is a class with at least one function call operator and a member
type named `result_type`.<br/>
The loader is not required to return a resource handle. As long as `return_type`
is suitable for constructing a handle, that is fine.
When using the default handle, it expects a resource type which is convertible
to or suitable for constructing an `std::shared_ptr<Type>` (where `Type` is the
actual resource type).<br/>
In other terms, the loader should return shared pointers to the given resource
type. However, this is not mandatory. Users can easily get around this
constraint by specializing both the handle and the loader.
A cache forwards all its arguments to the loader if required. This means that
loaders can also support tag dispatching to offer different loading policies:
A cache offers a set of basic functionalities to query its internal state and to
_organize_ it:
```cpp
struct my_loader {
using result_type = std::shared_ptr<my_resource>;
// gets the number of resources managed by a cache
const auto size = cache.size();
struct from_disk_tag{};
struct from_network_tag{};
// checks if a cache contains at least a valid resource
const auto empty = cache.empty();
template<typename... Args>
result_type operator()(from_disk_tag, Args&&... args) {
// ...
return std::make_shared<my_resource>(std::forward<Args>(args)...);
}
template<typename... Args>
result_type operator()(from_network_tag, Args&&... args) {
// ...
return std::make_shared<my_resource>(std::forward<Args>(args)...);
}
}
// clears a cache and discards its content
cache.clear();
```
This makes the whole loading logic quite flexible and easy to extend over time.
## The cache class
The cache is the class that is asked to _connect the dots_.<br/>
It loads the resources, stores them aside and returns handles as needed:
Besides these member functions, a cache contains what is needed to load, use and
discard resources of the given type.<br/>
Before exploring this part of the interface, it makes sense to mention how
resources are identified. They have type `id_type` and therefore they can be
created explicitly as in the following example:
```cpp
entt::resource_cache<my_resource, my_loader> cache{};
constexpr auto identifier = "my/resource/identifier"_hs;
// this is equivalent to the following
constexpr entt::id_type hs = entt::hashed_string{"my/resource/identifier"};
```
Under the hood, a cache is nothing more than a map where the key value has type
`entt::id_type` while the mapped value is whatever type its loader returns.<br/>
For this reason, it offers most of the functionalities a user would expect from
a map, such as `empty` or `size` and so on. Similarly, it is an iterable type
that also supports indexing by resource id:
The class `hashed_string` is described in a dedicated section, so I won't go in
details here.
Resources are loaded and thus stored in a cache through the `load` member
function. It accepts the loader to use as a template parameter, the resource
identifier and the parameters used to construct the resource as arguments:
```cpp
for(auto [id, res]: cache) {
// ...
}
// uses the identifier declared above
cache.load<my_loader>(identifier, 0);
if(entt::resource<my_resource> res = cache["resource/id"_hs]; res) {
// uses a hashed string directly
cache.load<my_loader>("another/identifier"_hs, 42);
```
The function returns a handle to the resource, whether it already exists or is
loaded. In case the loader returns an invalid pointer, the handle is invalid as
well and therefore it can be easily used with an `if` statement:
```cpp
if(entt::resource_handle handle = cache.load<my_loader>("another/identifier"_hs, 42); handle) {
// ...
}
```
Please, refer to the inline documentation for all the details about the other
functions (such as `contains` or `erase`).
Set aside the part of the API that this class _shares_ with a map, it also adds
something on top of it in order to address the most common requirements of a
resource cache.<br/>
In particular, it does not have an `emplace` member function which is replaced
by `load` and `force_load` instead (where the former loads a new resource only
if not present while the second triggers a forced loading in any case):
Before trying to load a resource, the `contains` member function can be used to
know if a cache already contains a specific resource:
```cpp
auto ret = cache.load("resource/id"_hs);
// true only if the resource was not already present
const bool loaded = ret.second;
// takes the resource handle pointed to by the returned iterator
entt::resource<my_resource> res = ret.first->second;
auto exists = cache.contains("my/identifier"_hs);
```
Note that the hashed string is used for convenience in the example above.<br/>
Resource identifiers are nothing more than integral values. Therefore, plain
numbers as well as non-class enum value are accepted.
There exists also a member function to use to force a reload of an already
existing resource if needed:
It is worth mentioning that the iterators of a cache as well as its indexing
operators return resource handles rather than instances of the mapped type.<br/>
Since the cache has no control over the loader and a resource is not required to
also be convertible to bool, these handles can be invalid. This usually means an
error in the user logic, but it may also be an _expected_ event.<br/>
It is therefore recommended to verify handles validity with a check in debug
(for example, when loading) or an appropriate logic in retail.
```cpp
auto handle = cache.reload<my_loader>("another/identifier"_hs, 42);
```
As above, the function returns a handle to the resource that is invalid in case
of errors. The `reload` member function is a kind of alias of the following
snippet:
```cpp
cache.discard(identifier);
cache.load<my_loader>(identifier, 42);
```
Where the `discard` member function is used to get rid of a resource if loaded.
In case the cache doesn't contain a resource for the given identifier, `discard`
does nothing and returns immediately.
So far, so good. Resources are finally loaded and stored within the cache.<br/>
They are returned to users in the form of handles. To get one of them later on:
```cpp
auto handle = cache.handle("my/identifier"_hs);
```
The idea behind a handle is the same of the flyweight pattern. In other terms,
resources aren't copied around. Instead, instances are shared between handles.
Users of a resource own a handle that guarantees that a resource isn't destroyed
until all the handles are destroyed, even if the resource itself is removed from
the cache.<br/>
Handles are tiny objects both movable and copyable. They return the contained
resource as a (possibly const) reference on request:
* By means of the `get` member function:
```cpp
auto &resource = handle.get();
```
* Using the proper cast operator:
```cpp
auto &resource = handle;
```
* Through the dereference operator:
```cpp
auto &resource = *handle;
```
The resource can also be accessed directly using the arrow operator if required:
```cpp
auto value = handle->value;
```
To test if a handle is still valid, the cast operator to `bool` allows users to
use it in a guard:
```cpp
if(handle) {
// ...
}
```
Finally, in case there is the need to load a resource and thus to get a handle
without storing the resource itself in the cache, users can rely on the `temp`
member function template.<br/>
The declaration is similar to that of `load`, a (possibly invalid) handle for
the resource is returned also in this case:
```cpp
if(auto handle = cache.temp<my_loader>(42); handle) {
// ...
}
```
Do not forget to test the handle for validity. Otherwise, getting a reference to
the resource it points may result in undefined behavior.

View File

@@ -1,42 +1,46 @@
# Crash Course: events, signals and everything in between
<!--
@cond TURN_OFF_DOXYGEN
-->
# Table of Contents
* [Introduction](#introduction)
* [Delegate](#delegate)
* [Runtime arguments](#runtime-arguments)
* [Lambda support](#lambda-support)
* [Raw access](#raw-access)
* [Signals](#signals)
* [Event dispatcher](#event-dispatcher)
* [Named queues](#named-queues)
* [Event emitter](#event-emitter)
<!--
@endcond TURN_OFF_DOXYGEN
-->
# Introduction
Signals are more often than not a core part of games and software architectures
in general.<br/>
They help to decouple the various parts of a system while allowing them to
communicate with each other somehow.
Signals are usually a core part of games and software architectures in
general.<br/>
Roughly speaking, they help to decouple the various parts of a system while
allowing them to communicate with each other somehow.
The so-called _modern C++_ comes with a tool that can be useful in 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/>
However, there is no guarantee that an `std::function` does not perform
allocations under the hood and this could be problematic sometimes. Furthermore,
it solves a problem but may not adapt well to other requirements that may arise
from time to time.
In case that the flexibility and power of an `std::function` is not required or
if the price to pay for them is too high, `EnTT` offers a complete set of
In case that the flexibility and power of an `std::function` isn't required or
if the price to pay for them is too high,` EnTT` offers a complete set of
lightweight classes to solve the same and many other problems.
# Delegate
A delegate can be used as a general purpose invoker with no memory overhead for
free functions, lambdas and members provided along with an instance on which to
invoke them.<br/>
It does not claim to be a drop-in replacement for an `std::function`, so do not
expect to use it whenever an `std::function` fits well. That said, it is most
free functions and members provided along with an instance on which to invoke
them.<br/>
It doesn't claim to be a drop-in replacement for an `std::function`, so don't
expect to use it whenever an `std::function` fits well. That said, it's most
likely even a better fit than an `std::function` in a lot of cases, so expect to
use it quite a lot anyway.
@@ -47,13 +51,15 @@ delegates:
entt::delegate<int(int)> delegate{};
```
What is needed to create an instance is to specify the type of the function the
delegate _accepts_, that is the signature of the functions it models.<br/>
However, attempting to use an empty delegate by invoking its function call
operator results in undefined behavior or most likely a crash.
All what is needed to create an instance is to specify the type of the function
the delegate will _contain_, that is the signature of the free function or the
member one wants to assign to it.
There exist a few overloads of the `connect` member function to initialize a
delegate:
Attempting to use an empty delegate by invoking its function call operator
results in undefined behavior or most likely a crash. Before to use a delegate,
it must be initialized.<br/>
There exists a bunch of overloads of the `connect` member function to do that.
As an example of use:
```cpp
int f(int i) { return i; }
@@ -70,13 +76,13 @@ my_struct 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
value of the data member is at least convertible to the return type.
Free functions having type equivalent to `void(T &, args...)` are accepted as
well. The first argument `T &` is considered a payload, and the function will
receive it back every time it is invoked. In other terms, this works just fine
well. The first argument `T &` is considered a payload and the function will
receive it back every time it's invoked. In other terms, this works just fine
with the above definition:
```cpp
@@ -87,11 +93,14 @@ delegate.connect<&g>(c);
delegate(42);
```
Function `g` is invoked with a reference to `c` and `42`. However, the function
type of the delegate is still `void(int)`. This is also the signature of its
function call operator.<br/>
Another interesting aspect of the delegate class is that it accepts functions
with a list of parameters that is shorter than that of its function type:
The function `g` will be invoked with a reference to `c` and `42`. However, the
function type of the delegate is still `void(int)`. This is also the signature
of its function call operator.
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
void g() { /* ... */ }
@@ -100,15 +109,9 @@ delegate(42);
```
Where the function type of the delegate is `void(int)` as above. It goes without
saying that the extra arguments are silently discarded internally. This is a
nice-to-have feature in a lot of cases, as an example when the `delegate` class
is used as a building block of a signal-slot system.<br/>
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.
saying that the extra arguments are silently discarded internally.<br/>
This is a nice-to-have feature in a lot of cases, as an example when the
`delegate` class is used as a building block of a signal-slot system.
To create and initialize a delegate at once, there are a few specialized
constructors. Because of the rules of the language, the listener is provided by
@@ -118,7 +121,7 @@ means of the `entt::connect_arg` variable template:
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/>
To know if a delegate is empty, it can be used explicitly in every conditional
statement:
@@ -136,14 +139,14 @@ already shown in the examples above:
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
a result that is convertible to the given result type, everything works just
fine.
As a side note, members of classes may or may not be associated with instances.
If they are not, the first argument of the function type must be that of the
class on which the members operate, and an instance of this class must obviously
class on which the members operate and an instance of this class must obviously
be passed when invoking the delegate:
```cpp
@@ -154,23 +157,23 @@ my_struct instance;
delegate(instance, 42);
```
In this case, it is 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
In this case, it's not possible to deduce the function type since the first
argument doesn't necessarily have to be a reference (for example, it can be a
pointer, as well as a const reference).<br/>
Therefore, the function type must be declared explicitly for unbound members.
## Runtime arguments
The `delegate` class is meant to be used primarily with template arguments.
However, as a consequence of its design, it also offers minimal support for
However, as a consequence of its design, it can also offer minimal support for
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.
* Functions with an argument list that differs from that of the delegate are not
* Curried functions aren't accepted.
* Functions with an argument list that differs from that of the delegate aren't
supported.
* Return type and types of arguments **must** coincide with those of the
delegate and _being at least convertible_ 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
functions connected at runtime must necessarily be `Ret(const void *, Args...)`.
@@ -200,12 +203,12 @@ explained.
## Lambda support
In general, the `delegate` class does not fully support lambda functions in all
their nuances. The reason is pretty simple: a `delegate` is not a drop-in
In general, the `delegate` class doesn't fully support lambda functions in all
their nuances. The reason is pretty simple: a `delegate` isn't a drop-in
replacement for an `std::function`. Instead, it tries to overcome the problems
with the latter.<br/>
That being said, non-capturing lambda functions are supported, even though some
features 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
runtime. Therefore, lambda functions undergo the same rules and
@@ -228,48 +231,29 @@ delegate.connect([](const void *ptr, int value) {
}, &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
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
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
Signal handlers work with references to classes, function pointers and pointers
to members. Listeners can be any kind of objects and users are in charge of
connecting and disconnecting them from a signal to avoid crashes due to
different lifetimes. On the other side, performance 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/>
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
documentation of the `delegate` class for further information.
A signal handler is can be used as a private data member without exposing any
_publish_ functionality to the clients of a class.<br/>
The basic idea is to impose a clear separation between the signal itself and the
`sink` class, that is a tool to be used to connect and disconnect listeners on
the fly.
A signal handler can be used as a private data member without exposing any
_publish_ functionality to the clients of a class. The basic idea is to impose a
clear separation between the signal itself and the `sink` class, that is a tool
to be used to connect and disconnect listeners on the fly.
The API of a signal handler is straightforward. If a collector is supplied to
the signal when something is published, all the values returned by its listeners
are literally _collected_ and used later by the caller. Otherwise, the class
the signal when something is published, all the values returned by the listeners
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/>
To create instances of signal handlers it is sufficient to provide the type of
function to which they refer:
@@ -309,31 +293,41 @@ sink.disconnect<&foo>();
sink.disconnect<&listener::bar>(instance);
// disconnect all member functions of an instance, if any
sink.disconnect(&instance);
sink.disconnect(instance);
// discards all listeners at once
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
result that is convertible to the given return type, everything works just
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`
object to be used as an alternative to break a connection by means of its
`release` member function.<br/>
A `scoped_connection` can also be created from a connection. In this case, the
link is broken automatically as soon as the object goes out of scope.
`release` member function. A `scoped_connection` can also be created from a
connection. In this case, the link is broken automatically as soon as the object
goes out of scope.
Once listeners are attached (or even if there are no listeners at all), events
and data in general are published through a signal by means of the `publish`
and data in general can be published through a signal by means of the `publish`
member function:
```cpp
signal.publish(42, 'c');
```
To collect data, the `collect` member function 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
int f() { return 0; }
@@ -357,7 +351,7 @@ assert(vec[1] == 1);
A collector must expose a function operator that accepts as an argument a type
to which the return type of the listeners can be converted. Moreover, it can
optionally return a boolean value that is true to stop collecting data, false
otherwise. This way one can avoid calling all the listeners in case it is not
otherwise. This way one can avoid calling all the listeners in case it isn't
necessary.<br/>
Functors can also be used in place of a lambda. Since the collector is copied
when invoking the `collect` member function, `std::ref` is the way to go in this
@@ -367,7 +361,7 @@ case:
struct my_collector {
std::vector<int> vec{};
bool operator()(int v) {
bool operator()(int v) noexcept {
vec.push_back(v);
return true;
}
@@ -381,20 +375,23 @@ signal.collect(std::ref(collector));
# Event dispatcher
The event dispatcher class allows users to trigger immediate events or to queue
and publish them all together later.<br/>
This class lazily instantiates its queues. Therefore, it is not necessary to
_announce_ the event types in advance:
The event dispatcher class is designed so as to be used in a loop. It allows
users both to trigger immediate events or to queue events to be published all
together once per tick.<br/>
This class shares part of its API with the one of the signal handler, but it
doesn't require that all the types of events are specified when declared:
```cpp
// define a general purpose dispatcher
entt::dispatcher dispatcher{};
```
A listener registered with a dispatcher is such that its type offers one or more
member functions that take arguments of type `Event &` for any type of event,
regardless of the return value.<br/>
These functions are linked directly via `connect` to a _sink_:
In order to register an instance of a class to a dispatcher, its type must
expose one or more member functions the arguments of which are such that `E &`
can be converted to them for each type of event `E`, no matter what the return
value is.<br/>
The name of the member function aimed to receive the event must be provided to
the `connect` member function of the sink in charge for the specific event:
```cpp
struct an_event { int value; };
@@ -412,37 +409,41 @@ dispatcher.sink<an_event>().connect<&listener::receive>(listener);
dispatcher.sink<another_event>().connect<&listener::method>(listener);
```
Note that connecting listeners within event handlers can result in undefined
behavior.<br/>
The `disconnect` member function is used to remove one listener at a time or all
of them at once:
The `disconnect` member function follows the same pattern and can be used to
remove one listener at a time or all of them at once:
```cpp
dispatcher.sink<an_event>().disconnect<&listener::receive>(listener);
dispatcher.sink<another_event>().disconnect(&listener);
dispatcher.sink<another_event>().disconnect(listener);
```
The `trigger` member function serves the purpose of sending an immediate event
to all the listeners registered so far:
to all the listeners registered so far. It offers a convenient approach that
relieves users from having to create the event itself. Instead, it's enough to
specify the type of event and provide all the parameters required to construct
it.<br/>
As an example:
```cpp
dispatcher.trigger(an_event{42});
dispatcher.trigger<an_event>(42);
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_
notification on a mobile app.
On the other hand, the `enqueue` member function queues messages together and
helps to maintain control over the moment they are sent to listeners:
allows to maintain control over the moment they are sent to listeners. The
signature of this method is more or less the same of `trigger`:
```cpp
dispatcher.enqueue<an_event>(42);
dispatcher.enqueue(another_event{});
dispatcher.enqueue<another_event>();
```
Events are stored aside until the `update` member function is invoked:
Events are stored aside until the `update` member function is invoked, then all
the messages that are still pending are sent to the listeners at once:
```cpp
// emits all the events of the given type at once
@@ -455,30 +456,6 @@ dispatcher.update();
This way users can embed the dispatcher in a loop and literally dispatch events
once per tick to their systems.
## Named queues
All queues within a dispatcher are associated by default with an event type and
then retrieved from it.<br/>
However, it is possible to create queues with different _names_ (and therefore
also multiple queues for a single type). In fact, more or less all functions
also take an additional parameter. As an example:
```cpp
dispatcher.sink<an_event>("custom"_hs).connect<&listener::receive>(listener);
```
In this case, the term _name_ is misused as these are actual numeric identifiers
of type `id_type`.<br/>
An exception to this rule is the `enqueue` function. There is no additional
parameter for it but rather a different function:
```cpp
dispatcher.enqueue_hint<an_event>("custom"_hs, 42);
```
This is mainly due to the template argument deduction rules, and there is no
real (elegant) way to avoid it.
# Event emitter
A general purpose event emitter thought mainly for those cases where it comes to
@@ -487,7 +464,8 @@ Originally designed to fit the requirements of
[`uvw`](https://github.com/skypjack/uvw) (a wrapper for `libuv` written in
modern C++), it was adapted later to be included in this library.
To create an emitter type, derived classes must inherit from the base as:
To create a custom emitter type, derived classes must inherit directly from the
base class as:
```cpp
struct my_emitter: emitter<my_emitter> {
@@ -495,10 +473,18 @@ struct my_emitter: emitter<my_emitter> {
}
```
Handlers for the different events are created internally on the fly. It is not
required to specify in advance the full list of accepted events.<br/>
Moreover, whenever an event is published, an emitter also passes a reference
to itself to its listeners.
The full list of accepted types of events isn't required. Handlers are created
internally on the fly and thus each type of event is accepted by default.
Whenever an event is published, an emitter provides the listeners with a
reference to itself along with a 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:
@@ -506,54 +492,90 @@ To create new instances of an emitter, no arguments are required:
my_emitter emitter{};
```
Listeners are movable and callable objects (free functions, lambdas, functors,
`std::function`s, whatever) whose function type is compatible with:
Listeners must be movable and callable objects (free functions, lambdas,
functors, `std::function`s, whatever) whose function type is compatible with:
```cpp
void(Type &, my_emitter &)
void(Event &, my_emitter &)
```
Where `Type` is the type of event they want to receive.<br/>
To attach a listener to an emitter, there exists the `on` member function:
Where `Event` is the type of event they want to listen.<br/>
There are two ways to attach a listener to an event emitter that differ
slightly from each other:
* To register a long-lived listener, use the `on` member function. It is meant
to register a listener designed to be invoked more than once for the given
event type.<br/>
As an example:
```cpp
auto conn = emitter.on<my_event>([](const my_event &event, my_emitter &emitter) {
// ...
});
```
The connection object can be freely discarded. Otherwise, it can be used later
to disconnect the listener if required.
* To register a short-lived listener, use the `once` member function. It is
meant to register a listener designed to be invoked only once for the given
event type. The listener is automatically disconnected after the first
invocation.<br/>
As an example:
```cpp
auto conn = emitter.once<my_event>([](const my_event &event, my_emitter &emitter) {
// ...
});
```
The connection object can be freely discarded. Otherwise, it can be used later
to disconnect the listener if required.
In both cases, the connection object can be used with the `erase` member
function:
```cpp
emitter.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
type while `clear` is used to disconnect all listeners at once:
There are also two member functions to use either to disconnect all the
listeners for a given type of event or to clear the emitter:
```cpp
// resets the listener for my_event
emitter.erase<my_event>();
// removes all the listener for the specific event
emitter.clear<my_event>();
// resets all listeners
emitter.clear()
// removes all the listeners registered so far
emitter.clear();
```
To send an event to the listener registered on a given type, the `publish`
function is the way to go:
To send an event to all the listeners that are interested in it, the `publish`
member function offers a convenient approach that relieves users from having to
create the event:
```cpp
struct my_event { int i; };
// ...
emitter.publish(my_event{42});
emitter.publish<my_event>(42);
```
Finally, the `empty` member function tests if there exists at least a listener
registered with the event emitter while `contains` is used to check if a given
event type is associated with a valid listener:
Finally, the `empty` member function tests if there exists at least either a
listener registered with the event emitter or to a given type of event:
```cpp
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/>
More in general, it is a handy tool when the derived classes _wrap_ asynchronous
operations, but it is not limited to such uses.
In general, the event emitter is a handy tool when the derived classes _wrap_
asynchronous operations, because it introduces a _nice-to-have_ model based on
events and listeners that kindly hides the complexity behind the scenes. However
it is not limited to such uses.

View File

@@ -1,15 +1,19 @@
# 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
> Skip this part if you are working with UE5, Since UE5 uses cpp17 by default.
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
@@ -24,7 +28,7 @@ CppStandard = CppStandardVersion.Cpp17;
Replace `<PCH filename>.h` with the name of the already existing PCH header
file, if any.<br/>
In case the project does not already contain a file of this type, it is possible
In case the project doesn't already contain a file of this type, it's possible
to create one with the following content:
```cpp
@@ -37,8 +41,8 @@ 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 does not necessarily mean that the IDE
in use will also start to recognize its syntax.<br/>
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.
@@ -62,8 +66,8 @@ Source
\---entt (GitHub repository content inside)
```
To make this happen, create the folder `ThirdParty` under `Source` if it does
not exist already. Then, add an `EnTT` folder under `ThirdParty`.<br/>
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

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

@@ -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,133 +1,85 @@
#ifndef ENTT_CONFIG_CONFIG_H
#define ENTT_CONFIG_CONFIG_H
#include "version.h"
// NOLINTBEGIN(cppcoreguidelines-macro-usage)
#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION)
# define ENTT_CONSTEXPR
# define ENTT_THROW throw
# define ENTT_TRY try
# define ENTT_CATCH catch(...)
# define ENTT_NOEXCEPT noexcept
# define ENTT_THROW throw
# define ENTT_TRY try
# define ENTT_CATCH catch(...)
#else
# define ENTT_CONSTEXPR constexpr // use only with throwing functions (waiting for C++20)
# define ENTT_THROW
# define ENTT_TRY if(true)
# define ENTT_CATCH if(false)
# define ENTT_NOEXCEPT
# define ENTT_THROW
# define ENTT_TRY if(true)
# define ENTT_CATCH if(false)
#endif
#if __has_include(<version>)
# include <version>
#
# if defined(__cpp_consteval)
# define ENTT_CONSTEVAL consteval
# endif
#endif
#ifndef ENTT_CONSTEVAL
# define ENTT_CONSTEVAL constexpr
#endif
#ifdef ENTT_USE_ATOMIC
# include <atomic>
# define ENTT_MAYBE_ATOMIC(Type) std::atomic<Type>
#if defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606L
# include <new>
# define ENTT_LAUNDER(expr) std::launder(expr)
#else
# define ENTT_MAYBE_ATOMIC(Type) Type
# define ENTT_LAUNDER(expr) expr
#endif
#ifndef ENTT_USE_ATOMIC
# define ENTT_MAYBE_ATOMIC(Type) Type
#else
# include <atomic>
# define ENTT_MAYBE_ATOMIC(Type) std::atomic<Type>
#endif
#ifndef ENTT_ID_TYPE
# include <cstdint>
# define ENTT_ID_TYPE std::uint32_t
# include <cstdint>
# define ENTT_ID_TYPE std::uint32_t
#endif
#ifdef ENTT_SPARSE_PAGE
static_assert(ENTT_SPARSE_PAGE && ((ENTT_SPARSE_PAGE & (ENTT_SPARSE_PAGE - 1)) == 0), "ENTT_SPARSE_PAGE must be a power of two");
#else
# include <cstdint> // provides coverage for types in the std namespace
# define ENTT_SPARSE_PAGE 4096
#endif
#ifndef ENTT_SPARSE_PAGE
# define ENTT_SPARSE_PAGE 4096
#ifdef ENTT_PACKED_PAGE
static_assert(ENTT_PACKED_PAGE && ((ENTT_PACKED_PAGE & (ENTT_PACKED_PAGE - 1)) == 0), "ENTT_PACKED_PAGE must be a power of two");
#else
# define ENTT_PACKED_PAGE 1024
#endif
#ifndef ENTT_PACKED_PAGE
# define ENTT_PACKED_PAGE 1024
#endif
#ifdef ENTT_DISABLE_ASSERT
# undef ENTT_ASSERT
# define ENTT_ASSERT(condition, msg) (void(0))
# undef ENTT_ASSERT
# define ENTT_ASSERT(...) (void(0))
#elif !defined ENTT_ASSERT
# include <cassert>
# define ENTT_ASSERT(condition, msg) assert(((condition) && (msg)))
# include <cassert>
# define ENTT_ASSERT(condition, ...) assert(condition)
#endif
#ifdef ENTT_DISABLE_ASSERT
# undef ENTT_ASSERT_CONSTEXPR
# define ENTT_ASSERT_CONSTEXPR(condition, msg) (void(0))
#elif !defined ENTT_ASSERT_CONSTEXPR
# define ENTT_ASSERT_CONSTEXPR(condition, msg) ENTT_ASSERT(condition, msg)
#endif
#define ENTT_FAIL(msg) ENTT_ASSERT(false, msg);
#ifdef ENTT_NO_ETO
# define ENTT_ETO_TYPE(Type) void
# include <type_traits>
# define ENTT_IGNORE_IF_EMPTY std::false_type
#else
# define ENTT_ETO_TYPE(Type) Type
# include <type_traits>
# define ENTT_IGNORE_IF_EMPTY std::true_type
#endif
#ifdef ENTT_NO_MIXIN
# define ENTT_STORAGE(Mixin, ...) __VA_ARGS__
#else
# define ENTT_STORAGE(Mixin, ...) Mixin<__VA_ARGS__>
#endif
#ifdef ENTT_STANDARD_CPP
# define ENTT_NONSTD false
#else
# define ENTT_NONSTD true
#ifndef ENTT_STANDARD_CPP
# if defined __clang__ || defined __GNUC__
# define ENTT_PRETTY_FUNCTION __PRETTY_FUNCTION__
# define ENTT_PRETTY_FUNCTION_PREFIX '='
# define ENTT_PRETTY_FUNCTION_SUFFIX ']'
# define ENTT_PRETTY_FUNCTION __PRETTY_FUNCTION__
# define ENTT_PRETTY_FUNCTION_PREFIX '='
# define ENTT_PRETTY_FUNCTION_SUFFIX ']'
# elif defined _MSC_VER
# define ENTT_PRETTY_FUNCTION __FUNCSIG__
# define ENTT_PRETTY_FUNCTION_PREFIX '<'
# define ENTT_PRETTY_FUNCTION_SUFFIX '>'
# endif
# define ENTT_PRETTY_FUNCTION __FUNCSIG__
# define ENTT_PRETTY_FUNCTION_PREFIX '<'
# define ENTT_PRETTY_FUNCTION_SUFFIX '>'
# 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
# pragma detect_mismatch("entt.version", ENTT_VERSION)
# pragma detect_mismatch("entt.noexcept", ENTT_XSTR(ENTT_TRY))
# pragma detect_mismatch("entt.id", ENTT_XSTR(ENTT_ID_TYPE))
# pragma detect_mismatch("entt.nonstd", ENTT_XSTR(ENTT_NONSTD))
#endif
// NOLINTEND(cppcoreguidelines-macro-usage)
#endif

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,930 +0,0 @@
#ifndef ENTT_CONTAINER_DENSE_SET_HPP
#define ENTT_CONTAINER_DENSE_SET_HPP
#include <cmath>
#include <cstddef>
#include <functional>
#include <iterator>
#include <limits>
#include <memory>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
#include "../config/config.h"
#include "../core/bit.hpp"
#include "../core/compressed_pair.hpp"
#include "../core/type_traits.hpp"
#include "fwd.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
static constexpr std::size_t dense_set_placeholder_position = (std::numeric_limits<std::size_t>::max)();
template<typename It>
class dense_set_iterator final {
template<typename>
friend class dense_set_iterator;
public:
using value_type = typename It::value_type::second_type;
using pointer = const value_type *;
using reference = const value_type &;
using difference_type = std::ptrdiff_t;
using iterator_category = std::random_access_iterator_tag;
constexpr dense_set_iterator() noexcept
: it{} {}
constexpr dense_set_iterator(const It iter) noexcept
: it{iter} {}
template<typename Other, typename = std::enable_if_t<!std::is_same_v<It, Other> && std::is_constructible_v<It, Other>>>
constexpr dense_set_iterator(const dense_set_iterator<Other> &other) noexcept
: it{other.it} {}
constexpr dense_set_iterator &operator++() noexcept {
return ++it, *this;
}
constexpr dense_set_iterator operator++(int) noexcept {
const dense_set_iterator orig = *this;
return ++(*this), orig;
}
constexpr dense_set_iterator &operator--() noexcept {
return --it, *this;
}
constexpr dense_set_iterator operator--(int) noexcept {
const dense_set_iterator orig = *this;
return operator--(), orig;
}
constexpr dense_set_iterator &operator+=(const difference_type value) noexcept {
it += value;
return *this;
}
constexpr dense_set_iterator operator+(const difference_type value) const noexcept {
dense_set_iterator copy = *this;
return (copy += value);
}
constexpr dense_set_iterator &operator-=(const difference_type value) noexcept {
return (*this += -value);
}
constexpr dense_set_iterator operator-(const difference_type value) const noexcept {
return (*this + -value);
}
[[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept {
return it[value].second;
}
[[nodiscard]] constexpr pointer operator->() const noexcept {
return std::addressof(operator[](0));
}
[[nodiscard]] constexpr reference operator*() const noexcept {
return operator[](0);
}
template<typename Lhs, typename Rhs>
friend constexpr std::ptrdiff_t operator-(const dense_set_iterator<Lhs> &, const dense_set_iterator<Rhs> &) noexcept;
template<typename Lhs, typename Rhs>
friend constexpr bool operator==(const dense_set_iterator<Lhs> &, const dense_set_iterator<Rhs> &) noexcept;
template<typename Lhs, typename Rhs>
friend constexpr bool operator<(const dense_set_iterator<Lhs> &, const dense_set_iterator<Rhs> &) noexcept;
private:
It it;
};
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
return lhs.it - rhs.it;
}
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator==(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
return lhs.it == rhs.it;
}
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator!=(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
return !(lhs == rhs);
}
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator<(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
return lhs.it < rhs.it;
}
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator>(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
return rhs < lhs;
}
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator<=(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
return !(lhs > rhs);
}
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator>=(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept {
return !(lhs < rhs);
}
template<typename It>
class dense_set_local_iterator final {
template<typename>
friend class dense_set_local_iterator;
public:
using value_type = typename It::value_type::second_type;
using pointer = const value_type *;
using reference = const value_type &;
using difference_type = std::ptrdiff_t;
using iterator_category = std::forward_iterator_tag;
constexpr dense_set_local_iterator() noexcept = default;
constexpr dense_set_local_iterator(It iter, const std::size_t pos) noexcept
: it{iter},
offset{pos} {}
template<typename Other, typename = std::enable_if_t<!std::is_same_v<It, Other> && std::is_constructible_v<It, Other>>>
constexpr dense_set_local_iterator(const dense_set_local_iterator<Other> &other) noexcept
: it{other.it},
offset{other.offset} {}
constexpr dense_set_local_iterator &operator++() noexcept {
return offset = it[static_cast<typename It::difference_type>(offset)].first, *this;
}
constexpr dense_set_local_iterator operator++(int) noexcept {
const dense_set_local_iterator orig = *this;
return ++(*this), orig;
}
[[nodiscard]] constexpr pointer operator->() const noexcept {
return std::addressof(it[static_cast<typename It::difference_type>(offset)].second);
}
[[nodiscard]] constexpr reference operator*() const noexcept {
return *operator->();
}
[[nodiscard]] constexpr std::size_t index() const noexcept {
return offset;
}
private:
It it{};
std::size_t offset{dense_set_placeholder_position};
};
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator==(const dense_set_local_iterator<Lhs> &lhs, const dense_set_local_iterator<Rhs> &rhs) noexcept {
return lhs.index() == rhs.index();
}
template<typename Lhs, typename Rhs>
[[nodiscard]] constexpr bool operator!=(const dense_set_local_iterator<Lhs> &lhs, const dense_set_local_iterator<Rhs> &rhs) noexcept {
return !(lhs == rhs);
}
} // namespace internal
/*! @endcond */
/**
* @brief Associative container for unique objects of a given type.
*
* Internally, elements are organized into buckets. Which bucket an element is
* placed into depends entirely on its hash. Elements with the same hash code
* appear in the same bucket.
*
* @tparam Type Value type of the associative container.
* @tparam Hash Type of function to use to hash the values.
* @tparam KeyEqual Type of function to use to compare the values for equality.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Type, typename Hash, typename KeyEqual, typename Allocator>
class dense_set {
static constexpr float default_threshold = 0.875f;
static constexpr std::size_t minimum_capacity = 8u;
static constexpr std::size_t placeholder_position = internal::dense_set_placeholder_position;
using node_type = std::pair<std::size_t, Type>;
using alloc_traits = std::allocator_traits<Allocator>;
static_assert(std::is_same_v<typename alloc_traits::value_type, Type>, "Invalid value type");
using sparse_container_type = std::vector<std::size_t, typename alloc_traits::template rebind_alloc<std::size_t>>;
using packed_container_type = std::vector<node_type, typename alloc_traits::template rebind_alloc<node_type>>;
template<typename Other>
[[nodiscard]] std::size_t value_to_bucket(const Other &value) const noexcept {
return fast_mod(static_cast<size_type>(sparse.second()(value)), bucket_count());
}
template<typename Other>
[[nodiscard]] auto constrained_find(const Other &value, const std::size_t bucket) {
for(auto offset = sparse.first()[bucket]; offset != placeholder_position; offset = packed.first()[offset].first) {
if(packed.second()(packed.first()[offset].second, value)) {
return begin() + static_cast<typename iterator::difference_type>(offset);
}
}
return end();
}
template<typename Other>
[[nodiscard]] auto constrained_find(const Other &value, const std::size_t bucket) const {
for(auto offset = sparse.first()[bucket]; offset != placeholder_position; offset = packed.first()[offset].first) {
if(packed.second()(packed.first()[offset].second, value)) {
return cbegin() + static_cast<typename const_iterator::difference_type>(offset);
}
}
return cend();
}
template<typename Other>
[[nodiscard]] auto insert_or_do_nothing(Other &&value) {
const auto index = value_to_bucket(value);
if(auto it = constrained_find(value, index); it != end()) {
return std::make_pair(it, false);
}
packed.first().emplace_back(sparse.first()[index], std::forward<Other>(value));
sparse.first()[index] = packed.first().size() - 1u;
rehash_if_required();
return std::make_pair(--end(), true);
}
void move_and_pop(const std::size_t pos) {
if(const auto last = size() - 1u; pos != last) {
size_type *curr = &sparse.first()[value_to_bucket(packed.first().back().second)];
packed.first()[pos] = std::move(packed.first().back());
for(; *curr != last; curr = &packed.first()[*curr].first) {}
*curr = pos;
}
packed.first().pop_back();
}
void rehash_if_required() {
if(const auto bc = bucket_count(); size() > static_cast<size_type>(static_cast<float>(bc) * max_load_factor())) {
rehash(bc * 2u);
}
}
public:
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Key type of the container. */
using key_type = Type;
/*! @brief Value type of the container. */
using value_type = Type;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Signed integer type. */
using difference_type = std::ptrdiff_t;
/*! @brief Type of function to use to hash the elements. */
using hasher = Hash;
/*! @brief Type of function to use to compare the elements for equality. */
using key_equal = KeyEqual;
/*! @brief Random access iterator type. */
using iterator = internal::dense_set_iterator<typename packed_container_type::iterator>;
/*! @brief Constant random access iterator type. */
using const_iterator = internal::dense_set_iterator<typename packed_container_type::const_iterator>;
/*! @brief 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. */
using local_iterator = internal::dense_set_local_iterator<typename packed_container_type::iterator>;
/*! @brief Constant forward iterator type. */
using const_local_iterator = internal::dense_set_local_iterator<typename packed_container_type::const_iterator>;
/*! @brief Default constructor. */
dense_set()
: dense_set{minimum_capacity} {}
/**
* @brief Constructs an empty container with a given allocator.
* @param allocator The allocator to use.
*/
explicit dense_set(const allocator_type &allocator)
: dense_set{minimum_capacity, hasher{}, key_equal{}, allocator} {}
/**
* @brief Constructs an empty container with a given allocator and user
* supplied minimal number of buckets.
* @param cnt Minimal number of buckets.
* @param allocator The allocator to use.
*/
dense_set(const size_type cnt, const allocator_type &allocator)
: dense_set{cnt, hasher{}, key_equal{}, allocator} {}
/**
* @brief Constructs an empty container with a given allocator, hash
* function and user supplied minimal number of buckets.
* @param cnt Minimal number of buckets.
* @param hash Hash function to use.
* @param allocator The allocator to use.
*/
dense_set(const size_type cnt, const hasher &hash, const allocator_type &allocator)
: dense_set{cnt, hash, key_equal{}, allocator} {}
/**
* @brief Constructs an empty container with a given allocator, hash
* function, compare function and user supplied minimal number of buckets.
* @param cnt Minimal number of buckets.
* @param hash Hash function to use.
* @param equal Compare function to use.
* @param allocator The allocator to use.
*/
explicit dense_set(const size_type cnt, const hasher &hash = hasher{}, const key_equal &equal = key_equal{}, const allocator_type &allocator = allocator_type{})
: sparse{allocator, hash},
packed{allocator, equal} {
rehash(cnt);
}
/*! @brief Default copy constructor. */
dense_set(const dense_set &) = default;
/**
* @brief Allocator-extended copy constructor.
* @param other The instance to copy from.
* @param allocator The allocator to use.
*/
dense_set(const dense_set &other, const allocator_type &allocator)
: sparse{std::piecewise_construct, std::forward_as_tuple(other.sparse.first(), allocator), std::forward_as_tuple(other.sparse.second())},
packed{std::piecewise_construct, std::forward_as_tuple(other.packed.first(), allocator), std::forward_as_tuple(other.packed.second())},
threshold{other.threshold} {}
/*! @brief Default move constructor. */
dense_set(dense_set &&) noexcept = default;
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
dense_set(dense_set &&other, const allocator_type &allocator)
: sparse{std::piecewise_construct, std::forward_as_tuple(std::move(other.sparse.first()), allocator), std::forward_as_tuple(std::move(other.sparse.second()))},
packed{std::piecewise_construct, std::forward_as_tuple(std::move(other.packed.first()), allocator), std::forward_as_tuple(std::move(other.packed.second()))},
threshold{other.threshold} {}
/*! @brief Default destructor. */
~dense_set() = default;
/**
* @brief Default copy assignment operator.
* @return This container.
*/
dense_set &operator=(const dense_set &) = default;
/**
* @brief Default move assignment operator.
* @return This container.
*/
dense_set &operator=(dense_set &&) noexcept = 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.
* @return The associated allocator.
*/
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
return sparse.first().get_allocator();
}
/**
* @brief Returns an iterator to the beginning.
*
* If the array is empty, the returned iterator will be equal to `end()`.
*
* @return An iterator to the first instance of the internal array.
*/
[[nodiscard]] const_iterator cbegin() const noexcept {
return packed.first().begin();
}
/*! @copydoc cbegin */
[[nodiscard]] const_iterator begin() const noexcept {
return cbegin();
}
/*! @copydoc begin */
[[nodiscard]] iterator begin() noexcept {
return packed.first().begin();
}
/**
* @brief Returns an iterator to the end.
* @return An iterator to the element following the last instance of the
* internal array.
*/
[[nodiscard]] const_iterator cend() const noexcept {
return packed.first().end();
}
/*! @copydoc cend */
[[nodiscard]] const_iterator end() const noexcept {
return cend();
}
/*! @copydoc end */
[[nodiscard]] iterator end() noexcept {
return packed.first().end();
}
/**
* @brief 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.
* @return True if the container is empty, false otherwise.
*/
[[nodiscard]] bool empty() const noexcept {
return packed.first().empty();
}
/**
* @brief Returns the number of elements in a container.
* @return Number of elements in a container.
*/
[[nodiscard]] size_type size() const noexcept {
return packed.first().size();
}
/**
* @brief Returns the maximum possible number of elements.
* @return Maximum possible number of elements.
*/
[[nodiscard]] size_type max_size() const noexcept {
return packed.first().max_size();
}
/*! @brief Clears the container. */
void clear() noexcept {
sparse.first().clear();
packed.first().clear();
rehash(0u);
}
/**
* @brief Inserts an element into the container, if it does not exist.
* @param value An element to insert into the container.
* @return A pair consisting of an iterator to the inserted element (or to
* the element that prevented the insertion) and a bool denoting whether the
* insertion took place.
*/
std::pair<iterator, bool> insert(const value_type &value) {
return insert_or_do_nothing(value);
}
/*! @copydoc insert */
std::pair<iterator, bool> insert(value_type &&value) {
return insert_or_do_nothing(std::move(value));
}
/**
* @brief Inserts elements into the container, if they do not exist.
* @tparam It Type of input iterator.
* @param first An iterator to the first element of the range of elements.
* @param last An iterator past the last element of the range of elements.
*/
template<typename It>
void insert(It first, It last) {
for(; first != last; ++first) {
insert(*first);
}
}
/**
* @brief Constructs an element in-place, if it does not exist.
*
* The element is also constructed when the container already has the key,
* in which case the newly constructed object is destroyed immediately.
*
* @tparam Args Types of arguments to forward to the constructor of the
* element.
* @param args Arguments to forward to the constructor of the element.
* @return A pair consisting of an iterator to the inserted element (or to
* the element that prevented the insertion) and a bool denoting whether the
* insertion took place.
*/
template<typename... Args>
std::pair<iterator, bool> emplace(Args &&...args) {
if constexpr(((sizeof...(Args) == 1u) && ... && std::is_same_v<std::decay_t<Args>, value_type>)) {
return insert_or_do_nothing(std::forward<Args>(args)...);
} else {
auto &node = packed.first().emplace_back(std::piecewise_construct, std::make_tuple(packed.first().size()), std::forward_as_tuple(std::forward<Args>(args)...));
const auto index = value_to_bucket(node.second);
if(auto it = constrained_find(node.second, index); it != end()) {
packed.first().pop_back();
return std::make_pair(it, false);
}
std::swap(node.first, sparse.first()[index]);
rehash_if_required();
return std::make_pair(--end(), true);
}
}
/**
* @brief Removes an element from a given position.
* @param pos An iterator to the element to remove.
* @return An iterator following the removed element.
*/
iterator erase(const_iterator pos) {
const auto diff = pos - cbegin();
erase(*pos);
return begin() + diff;
}
/**
* @brief Removes the given elements from a container.
* @param first An iterator to the first element of the range of elements.
* @param last An iterator past the last element of the range of elements.
* @return An iterator following the last removed element.
*/
iterator erase(const_iterator first, const_iterator last) {
const auto dist = first - cbegin();
for(auto from = last - cbegin(); from != dist; --from) {
erase(packed.first()[static_cast<size_type>(from) - 1u].second);
}
return (begin() + dist);
}
/**
* @brief Removes the element associated with a given value.
* @param value Value of an element to remove.
* @return Number of elements removed (either 0 or 1).
*/
size_type erase(const value_type &value) {
for(size_type *curr = &sparse.first()[value_to_bucket(value)]; *curr != placeholder_position; curr = &packed.first()[*curr].first) {
if(packed.second()(packed.first()[*curr].second, value)) {
const auto index = *curr;
*curr = packed.first()[*curr].first;
move_and_pop(index);
return 1u;
}
}
return 0u;
}
/**
* @brief Returns the number of elements matching a value (either 1 or 0).
* @param key Key value of an element to search for.
* @return Number of elements matching the key (either 1 or 0).
*/
[[nodiscard]] size_type count(const value_type &key) const {
return find(key) != end();
}
/**
* @brief Returns the number of elements matching a key (either 1 or 0).
* @tparam Other Type of the key value of an element to search for.
* @param key Key value of an element to search for.
* @return Number of elements matching the key (either 1 or 0).
*/
template<typename Other>
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, size_type>>
count(const Other &key) const {
return find(key) != end();
}
/**
* @brief Finds an element with a given value.
* @param value Value of an element to search for.
* @return An iterator to an element with the given value. If no such
* element is found, a past-the-end iterator is returned.
*/
[[nodiscard]] iterator find(const value_type &value) {
return constrained_find(value, value_to_bucket(value));
}
/*! @copydoc find */
[[nodiscard]] const_iterator find(const value_type &value) const {
return constrained_find(value, value_to_bucket(value));
}
/**
* @brief Finds an element that compares _equivalent_ to a given value.
* @tparam Other Type of an element to search for.
* @param value Value of an element to search for.
* @return An iterator to an element with the given value. If no such
* element is found, a past-the-end iterator is returned.
*/
template<typename Other>
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, iterator>>
find(const Other &value) {
return constrained_find(value, value_to_bucket(value));
}
/*! @copydoc find */
template<typename Other>
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, const_iterator>>
find(const Other &value) const {
return constrained_find(value, value_to_bucket(value));
}
/**
* @brief Returns a range containing all elements with a given value.
* @param value Value of an element to search for.
* @return A pair of iterators pointing to the first element and past the
* last element of the range.
*/
[[nodiscard]] std::pair<iterator, iterator> equal_range(const value_type &value) {
const auto it = find(value);
return {it, it + !(it == end())};
}
/*! @copydoc equal_range */
[[nodiscard]] std::pair<const_iterator, const_iterator> equal_range(const value_type &value) const {
const auto it = find(value);
return {it, it + !(it == cend())};
}
/**
* @brief Returns a range containing all elements that compare _equivalent_
* to a given value.
* @tparam Other Type of an element to search for.
* @param value Value of an element to search for.
* @return A pair of iterators pointing to the first element and past the
* last element of the range.
*/
template<typename Other>
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, std::pair<iterator, iterator>>>
equal_range(const Other &value) {
const auto it = find(value);
return {it, it + !(it == end())};
}
/*! @copydoc equal_range */
template<typename Other>
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, std::pair<const_iterator, const_iterator>>>
equal_range(const Other &value) const {
const auto it = find(value);
return {it, it + !(it == cend())};
}
/**
* @brief Checks if the container contains an element with a given value.
* @param value Value of an element to search for.
* @return True if there is such an element, false otherwise.
*/
[[nodiscard]] bool contains(const value_type &value) const {
return (find(value) != cend());
}
/**
* @brief Checks if the container contains an element that compares
* _equivalent_ to a given value.
* @tparam Other Type of an element to search for.
* @param value Value of an element to search for.
* @return True if there is such an element, false otherwise.
*/
template<typename Other>
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, bool>>
contains(const Other &value) const {
return (find(value) != cend());
}
/**
* @brief Returns an iterator to the beginning of a given bucket.
* @param index An index of a bucket to access.
* @return An iterator to the beginning of the given bucket.
*/
[[nodiscard]] const_local_iterator cbegin(const size_type index) const {
return {packed.first().begin(), sparse.first()[index]};
}
/**
* @brief Returns an iterator to the beginning of a given bucket.
* @param index An index of a bucket to access.
* @return An iterator to the beginning of the given bucket.
*/
[[nodiscard]] const_local_iterator begin(const size_type index) const {
return cbegin(index);
}
/**
* @brief Returns an iterator to the beginning of a given bucket.
* @param index An index of a bucket to access.
* @return An iterator to the beginning of the given bucket.
*/
[[nodiscard]] local_iterator begin(const size_type index) {
return {packed.first().begin(), sparse.first()[index]};
}
/**
* @brief Returns an iterator to the end of a given bucket.
* @param index An index of a bucket to access.
* @return An iterator to the end of the given bucket.
*/
[[nodiscard]] const_local_iterator cend([[maybe_unused]] const size_type index) const {
return {};
}
/**
* @brief Returns an iterator to the end of a given bucket.
* @param index An index of a bucket to access.
* @return An iterator to the end of the given bucket.
*/
[[nodiscard]] const_local_iterator end(const size_type index) const {
return cend(index);
}
/**
* @brief Returns an iterator to the end of a given bucket.
* @param index An index of a bucket to access.
* @return An iterator to the end of the given bucket.
*/
[[nodiscard]] local_iterator end([[maybe_unused]] const size_type index) {
return {};
}
/**
* @brief Returns the number of buckets.
* @return The number of buckets.
*/
[[nodiscard]] size_type bucket_count() const {
return sparse.first().size();
}
/**
* @brief Returns the maximum number of buckets.
* @return The maximum number of buckets.
*/
[[nodiscard]] size_type max_bucket_count() const {
return sparse.first().max_size();
}
/**
* @brief Returns the number of elements in a given bucket.
* @param index The index of the bucket to examine.
* @return The number of elements in the given bucket.
*/
[[nodiscard]] size_type bucket_size(const size_type index) const {
return static_cast<size_type>(std::distance(begin(index), end(index)));
}
/**
* @brief Returns the bucket for a given element.
* @param value The value of the element to examine.
* @return The bucket for the given element.
*/
[[nodiscard]] size_type bucket(const value_type &value) const {
return value_to_bucket(value);
}
/**
* @brief Returns the average number of elements per bucket.
* @return The average number of elements per bucket.
*/
[[nodiscard]] float load_factor() const {
return static_cast<float>(size()) / static_cast<float>(bucket_count());
}
/**
* @brief Returns the maximum average number of elements per bucket.
* @return The maximum average number of elements per bucket.
*/
[[nodiscard]] float max_load_factor() const {
return threshold;
}
/**
* @brief Sets the desired maximum average number of elements per bucket.
* @param value A desired maximum average number of elements per bucket.
*/
void max_load_factor(const float value) {
ENTT_ASSERT(value > 0.f, "Invalid load factor");
threshold = value;
rehash(0u);
}
/**
* @brief Reserves at least the specified number of buckets and regenerates
* the hash table.
* @param cnt New number of buckets.
*/
void rehash(const size_type cnt) {
auto value = cnt > minimum_capacity ? cnt : minimum_capacity;
const auto cap = static_cast<size_type>(static_cast<float>(size()) / max_load_factor());
value = value > cap ? value : cap;
if(const auto sz = next_power_of_two(value); sz != bucket_count()) {
sparse.first().resize(sz);
for(auto &&elem: sparse.first()) {
elem = placeholder_position;
}
for(size_type pos{}, last = size(); pos < last; ++pos) {
const auto index = value_to_bucket(packed.first()[pos].second);
packed.first()[pos].first = std::exchange(sparse.first()[index], pos);
}
}
}
/**
* @brief Reserves space for at least the specified number of elements and
* regenerates the hash table.
* @param cnt New number of elements.
*/
void reserve(const size_type cnt) {
packed.first().reserve(cnt);
rehash(static_cast<size_type>(std::ceil(static_cast<float>(cnt) / max_load_factor())));
}
/**
* @brief Returns the function used to hash the elements.
* @return The function used to hash the elements.
*/
[[nodiscard]] hasher hash_function() const {
return sparse.second();
}
/**
* @brief Returns the function used to compare elements for equality.
* @return The function used to compare elements for equality.
*/
[[nodiscard]] key_equal key_eq() const {
return packed.second();
}
private:
compressed_pair<sparse_container_type, hasher> sparse;
compressed_pair<packed_container_type, key_equal> packed;
float threshold{default_threshold};
};
} // namespace entt
#endif

View File

@@ -1,38 +0,0 @@
#ifndef ENTT_CONTAINER_FWD_HPP
#define ENTT_CONTAINER_FWD_HPP
#include <functional>
#include <memory>
#include <utility>
#include <vector>
namespace entt {
template<
typename Key,
typename Type,
typename = std::hash<Key>,
typename = std::equal_to<>,
typename = std::allocator<std::pair<const Key, Type>>>
class dense_map;
template<
typename Type,
typename = std::hash<Type>,
typename = std::equal_to<>,
typename = std::allocator<Type>>
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
#endif

View File

@@ -1,462 +0,0 @@
#ifndef ENTT_CONTAINER_TABLE_HPP
#define ENTT_CONTAINER_TABLE_HPP
#include <cstddef>
#include <iterator>
#include <tuple>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "../core/iterator.hpp"
#include "fwd.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
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, typename = std::enable_if_t<(std::is_constructible_v<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... Lhs, typename... Rhs>
friend constexpr std::ptrdiff_t operator-(const table_iterator<Lhs...> &, const table_iterator<Rhs...> &) noexcept;
template<typename... Lhs, typename... Rhs>
friend constexpr bool operator==(const table_iterator<Lhs...> &, const table_iterator<Rhs...> &) noexcept;
template<typename... Lhs, typename... Rhs>
friend constexpr bool operator<(const table_iterator<Lhs...> &, const table_iterator<Rhs...> &) noexcept;
private:
std::tuple<It...> it;
};
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr std::ptrdiff_t operator-(const table_iterator<Lhs...> &lhs, const table_iterator<Rhs...> &rhs) noexcept {
return std::get<0>(lhs.it) - std::get<0>(rhs.it);
}
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator==(const table_iterator<Lhs...> &lhs, const table_iterator<Rhs...> &rhs) noexcept {
return std::get<0>(lhs.it) == std::get<0>(rhs.it);
}
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator!=(const table_iterator<Lhs...> &lhs, const table_iterator<Rhs...> &rhs) noexcept {
return !(lhs == rhs);
}
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator<(const table_iterator<Lhs...> &lhs, const table_iterator<Rhs...> &rhs) noexcept {
return std::get<0>(lhs.it) < std::get<0>(rhs.it);
}
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator>(const table_iterator<Lhs...> &lhs, const table_iterator<Rhs...> &rhs) noexcept {
return rhs < lhs;
}
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator<=(const table_iterator<Lhs...> &lhs, const table_iterator<Rhs...> &rhs) noexcept {
return !(lhs > rhs);
}
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator>=(const table_iterator<Lhs...> &lhs, const table_iterator<Rhs...> &rhs) noexcept {
return !(lhs < rhs);
}
} // 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.
* @tparam Allocator Type of allocator.
* @param allocator A valid allocator.
*/
template<typename Allocator>
explicit basic_table(const Allocator &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 TURN_OFF_DOXYGEN */
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

@@ -1,15 +1,18 @@
#ifndef ENTT_CORE_ALGORITHM_HPP
#define ENTT_CORE_ALGORITHM_HPP
#include <vector>
#include <utility>
#include <iterator>
#include <algorithm>
#include <functional>
#include <iterator>
#include <utility>
#include <vector>
#include "utility.hpp"
namespace entt {
/**
* @brief Function object to wrap `std::sort` in a class type.
*
@@ -33,11 +36,12 @@ struct std_sort {
* @param args Arguments to forward to the sort function, if any.
*/
template<typename It, typename Compare = std::less<>, typename... Args>
void operator()(It first, It last, Compare compare = Compare{}, Args &&...args) const {
void operator()(It first, It last, Compare compare = Compare{}, Args &&... args) const {
std::sort(std::forward<Args>(args)..., std::move(first), std::move(last), std::move(compare));
}
};
/*! @brief Function object for performing insertion sort. */
struct insertion_sort {
/**
@@ -58,11 +62,9 @@ struct insertion_sort {
auto value = std::move(*it);
auto pre = it;
// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
for(; pre > first && compare(value, *(pre - 1)); --pre) {
*pre = std::move(*(pre - 1));
for(; pre > first && compare(value, *(pre-1)); --pre) {
*pre = std::move(*(pre-1));
}
// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
*pre = std::move(value);
}
@@ -70,6 +72,7 @@ struct insertion_sort {
}
};
/**
* @brief Function object for performing LSD radix sort.
* @tparam Bit Number of bits processed per pass.
@@ -97,37 +100,31 @@ struct radix_sort {
template<typename It, typename Getter = identity>
void operator()(It first, It last, Getter getter = Getter{}) const {
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 = typename std::iterator_traits<It>::value_type;
using difference_type = typename std::iterator_traits<It>::difference_type;
std::vector<value_type> aux(static_cast<std::size_t>(std::distance(first, last)));
std::vector<value_type> aux(std::distance(first, last));
auto part = [getter = std::move(getter)](auto from, auto to, auto out, auto start) {
constexpr auto mask = (1 << Bit) - 1;
constexpr auto buckets = 1 << Bit;
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays, misc-const-correctness)
std::size_t index[buckets]{};
std::size_t count[buckets]{};
for(auto it = from; it != to; ++it) {
++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) {
index[pos + 1u] = index[pos] + count[pos];
}
for(auto it = from; it != to; ++it) {
const auto pos = index[(getter(*it) >> start) & mask]++;
out[static_cast<difference_type>(pos)] = std::move(*it);
out[index[(getter(*it) >> start) & mask]++] = 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(aux.begin(), aux.end(), first, (pass + 1) * Bit);
}
@@ -140,6 +137,8 @@ struct radix_sort {
}
};
} // namespace entt
}
#endif

View File

@@ -1,192 +1,152 @@
#ifndef ENTT_CORE_ANY_HPP
#define ENTT_CORE_ANY_HPP
#include <cstddef>
#include <functional>
#include <memory>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "../core/utility.hpp"
#include "fwd.hpp"
#include "type_info.hpp"
#include "type_traits.hpp"
#include "utility.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
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.
* @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.
*/
template<std::size_t Len, std::size_t Align>
class basic_any: private internal::basic_any_storage<Len, Align> {
using request = internal::any_request;
using base_type = internal::basic_any_storage<Len, Align>;
using vtable_type = const void *(const request, const basic_any &, const void *);
using deleter_type = void(const basic_any &);
class basic_any {
enum class operation: std::uint8_t { COPY, MOVE, DTOR, COMP, ADDR, CADDR, TYPE };
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 &, void *);
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<typename Type>
static const void *basic_vtable(const request req, const basic_any &value, const void *other) {
static_assert(std::is_same_v<std::remove_const_t<std::remove_reference_t<Type>>, Type>, "Invalid type");
switch(const auto *elem = static_cast<const Type *>(value.data()); req) {
case request::info:
return &type_id<Type>();
case request::transfer:
if constexpr(std::is_move_assignable_v<Type>) {
// NOLINTNEXTLINE(bugprone-casting-through-void)
*const_cast<Type *>(elem) = std::move(*static_cast<Type *>(const_cast<void *>(other)));
return other;
}
[[fallthrough]];
case request::assign:
if constexpr(std::is_copy_assignable_v<Type>) {
*const_cast<Type *>(elem) = *static_cast<const Type *>(other);
return other;
}
break;
case request::compare:
if constexpr(!std::is_function_v<Type> && !std::is_array_v<Type> && is_equality_comparable_v<Type>) {
return (*elem == *static_cast<const Type *>(other)) ? other : nullptr;
[[nodiscard]] static constexpr policy type_to_policy() {
if constexpr(std::is_lvalue_reference_v<Type>) {
if constexpr(std::is_const_v<std::remove_reference_t<Type>>) {
return policy::CREF;
} else {
return (elem == other) ? other : nullptr;
return policy::REF;
}
case request::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;
case request::move:
ENTT_ASSERT(value.mode == any_policy::embedded, "Unexpected policy");
if constexpr(in_situ_v<Type>) {
// NOLINTNEXTLINE(bugprone-casting-through-void, bugprone-multi-level-implicit-pointer-conversion)
return ::new(&static_cast<basic_any *>(const_cast<void *>(other))->buffer) Type{std::move(*const_cast<Type *>(elem))};
} else {
return policy::OWNER;
}
}
template<typename Type>
[[nodiscard]] static bool compare(const void *lhs, const void *rhs) {
if constexpr(!std::is_function_v<Type> && is_equality_comparable_v<Type>) {
return *static_cast<const Type *>(lhs) == *static_cast<const Type *>(rhs);
} else {
return lhs == rhs;
}
}
template<typename Type>
static const void * basic_vtable([[maybe_unused]] const operation op, [[maybe_unused]] const basic_any &from, [[maybe_unused]] void *to) {
static_assert(std::is_same_v<std::remove_reference_t<std::remove_const_t<Type>>, Type>, "Invalid type");
if constexpr(!std::is_void_v<Type>) {
const Type *instance = (in_situ<Type> && from.mode == policy::OWNER)
? ENTT_LAUNDER(reinterpret_cast<const Type *>(&from.storage))
: static_cast<const Type *>(from.instance);
switch(op) {
case operation::COPY:
if constexpr(std::is_copy_constructible_v<Type>) {
static_cast<basic_any *>(to)->emplace<Type>(*instance);
}
break;
case operation::MOVE:
if constexpr(in_situ<Type>) {
if(from.mode == policy::OWNER) {
return new (&static_cast<basic_any *>(to)->storage) Type{std::move(*const_cast<Type *>(instance))};
}
}
return (static_cast<basic_any *>(to)->instance = std::exchange(const_cast<basic_any &>(from).instance, nullptr));
case operation::DTOR:
if(from.mode == policy::OWNER) {
if constexpr(in_situ<Type>) {
instance->~Type();
} else if constexpr(std::is_array_v<Type>) {
delete[] instance;
} else {
delete instance;
}
}
break;
case operation::COMP:
return compare<Type>(instance, (*static_cast<const basic_any **>(to))->data()) ? to : nullptr;
case operation::ADDR:
if(from.mode == policy::CREF) {
return nullptr;
}
[[fallthrough]];
case operation::CADDR:
return instance;
case operation::TYPE:
*static_cast<type_info *>(to) = type_id<Type>();
break;
}
}
return nullptr;
}
template<typename Type>
static void basic_deleter(const basic_any &value) {
static_assert(std::is_same_v<std::remove_const_t<std::remove_reference_t<Type>>, Type>, "Invalid type");
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>
void initialize([[maybe_unused]] Args &&...args) {
using plain_type = std::remove_const_t<std::remove_reference_t<Type>>;
vtable = basic_vtable<plain_type>;
underlying_type = type_hash<plain_type>::value();
if constexpr(std::is_void_v<Type>) {
deleter = nullptr;
mode = any_policy::empty;
this->instance = nullptr;
} else if constexpr(std::is_lvalue_reference_v<Type>) {
deleter = nullptr;
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;
void initialize([[maybe_unused]] Args &&... args) {
if constexpr(!std::is_void_v<Type>) {
if constexpr(std::is_lvalue_reference_v<Type>) {
static_assert(sizeof...(Args) == 1u && (std::is_lvalue_reference_v<Args> && ...), "Invalid arguments");
instance = (std::addressof(args), ...);
} else if constexpr(in_situ<Type>) {
if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v<Type>) {
new (&storage) Type{std::forward<Args>(args)...};
} else {
new (&storage) Type(std::forward<Args>(args)...);
}
} else {
deleter = &basic_deleter<plain_type>;
}
mode = any_policy::embedded;
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)...);
if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v<Type>) {
instance = new Type{std::forward<Args>(args)...};
} else {
instance = new Type(std::forward<Args>(args)...);
}
}
}
}
void invoke_deleter_if_exists() {
if(deleter != nullptr) {
deleter(*this);
}
}
basic_any(const basic_any &other, const policy pol) ENTT_NOEXCEPT
: instance{other.data()},
vtable{other.vtable},
mode{pol}
{}
public:
/*! @brief Size of the internal buffer. */
/*! @brief Size of the internal storage. */
static constexpr auto length = Len;
/*! @brief Alignment requirement. */
static constexpr auto alignment = Align;
/*! @brief Default constructor. */
constexpr basic_any() noexcept
: basic_any{std::in_place_type<void>} {}
basic_any() ENTT_NOEXCEPT
: instance{},
vtable{&basic_vtable<void>},
mode{policy::OWNER}
{}
/**
* @brief Constructs a wrapper by directly initializing the new object.
@@ -195,28 +155,25 @@ public:
* @param args Parameters to use to construct the instance.
*/
template<typename Type, typename... Args>
explicit basic_any(std::in_place_type_t<Type>, Args &&...args)
: base_type{} {
explicit basic_any(std::in_place_type_t<Type>, Args &&... args)
: instance{},
vtable{&basic_vtable<std::remove_const_t<std::remove_reference_t<Type>>>},
mode{type_to_policy<Type>()}
{
initialize<Type>(std::forward<Args>(args)...);
}
/**
* @brief Constructs a wrapper taking ownership of the passed object.
* @brief Constructs a wrapper that holds an unmanaged object.
* @tparam Type Type of object to use to initialize the wrapper.
* @param value A pointer to an object to take ownership of.
* @param value An instance of an object to use to initialize the wrapper.
*/
template<typename Type>
explicit basic_any(std::in_place_t, Type *value)
: base_type{} {
static_assert(!std::is_const_v<Type> && !std::is_void_v<Type>, "Non-const non-void pointer required");
if(value == nullptr) {
initialize<void>();
} else {
initialize<Type &>(*value);
deleter = &basic_deleter<Type>;
mode = any_policy::dynamic;
}
basic_any(std::reference_wrapper<Type> value) ENTT_NOEXCEPT
: basic_any{}
{
// invokes deprecated assignment operator (and avoids issues with vs2017)
*this = value;
}
/**
@@ -226,37 +183,40 @@ public:
*/
template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::decay_t<Type>, basic_any>>>
basic_any(Type &&value)
: basic_any{std::in_place_type<std::decay_t<Type>>, std::forward<Type>(value)} {}
: instance{},
vtable{&basic_vtable<std::decay_t<Type>>},
mode{policy::OWNER}
{
initialize<std::decay_t<Type>>(std::forward<Type>(value));
}
/**
* @brief Copy constructor.
* @param other The instance to copy from.
*/
basic_any(const basic_any &other)
: basic_any{} {
other.vtable(request::copy, other, this);
: instance{},
vtable{&basic_vtable<void>},
mode{policy::OWNER}
{
other.vtable(operation::COPY, other, this);
}
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
basic_any(basic_any &&other) noexcept
: base_type{},
basic_any(basic_any &&other) ENTT_NOEXCEPT
: instance{},
vtable{other.vtable},
deleter{other.deleter},
underlying_type{other.underlying_type},
mode{other.mode} {
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);
}
mode{other.mode}
{
vtable(operation::MOVE, other, this);
}
/*! @brief Frees the internal buffer, whatever it means. */
/*! @brief Frees the internal storage, whatever it means. */
~basic_any() {
invoke_deleter_if_exists();
vtable(operation::DTOR, *this, nullptr);
}
/**
@@ -264,17 +224,9 @@ public:
* @param other The instance to copy from.
* @return This any object.
*/
basic_any &operator=(const basic_any &other) {
if(this != &other) {
invoke_deleter_if_exists();
if(other) {
other.vtable(request::copy, other, this);
} else {
initialize<void>();
}
}
basic_any & operator=(const basic_any &other) {
reset();
other.vtable(operation::COPY, other, this);
return *this;
}
@@ -283,22 +235,10 @@ public:
* @param other The instance to move from.
* @return This any object.
*/
basic_any &operator=(basic_any &&other) noexcept {
if(this != &other) {
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);
}
vtable = other.vtable;
deleter = other.deleter;
underlying_type = other.underlying_type;
mode = other.mode;
}
basic_any & operator=(basic_any &&other) ENTT_NOEXCEPT {
std::exchange(vtable, other.vtable)(operation::DTOR, *this, nullptr);
other.vtable(operation::MOVE, other, this);
mode = other.mode;
return *this;
}
@@ -308,117 +248,47 @@ public:
* @param value An instance of an object to use to initialize the wrapper.
* @return This any object.
*/
template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::decay_t<Type>, basic_any>>>
basic_any &operator=(Type &&value) {
template<typename Type>
[[deprecated("Use std::in_place_type<T &>, entt::make_any<T &>, emplace<Type &> or forward_as_any instead")]]
basic_any & operator=(std::reference_wrapper<Type> value) ENTT_NOEXCEPT {
emplace<Type &>(value.get());
return *this;
}
/**
* @brief Value assignment operator.
* @tparam Type Type of object to use to initialize the wrapper.
* @param value An instance of an object to use to initialize the wrapper.
* @return This any object.
*/
template<typename Type>
std::enable_if_t<!std::is_same_v<std::decay_t<Type>, basic_any>, basic_any &>
operator=(Type &&value) {
emplace<std::decay_t<Type>>(std::forward<Type>(value));
return *this;
}
/**
* @brief Returns false if a wrapper is empty, true otherwise.
* @return False if the wrapper is empty, true otherwise.
* @brief Returns the type of the contained object.
* @return The type of the contained object, if any.
*/
[[nodiscard]] bool has_value() const noexcept {
return (mode != any_policy::empty);
}
/**
* @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<typename Type>
[[nodiscard]] bool has_value() const noexcept {
static_assert(std::is_same_v<std::remove_const_t<Type>, Type>, "Invalid type");
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));
}
/*! @copydoc info */
[[deprecated("use ::info instead")]] [[nodiscard]] const type_info &type() const noexcept {
return info();
[[nodiscard]] type_info type() const ENTT_NOEXCEPT {
type_info info{};
vtable(operation::TYPE, *this, &info);
return info;
}
/**
* @brief Returns an opaque pointer to the contained instance.
* @return An opaque pointer the contained instance, if any.
*/
[[nodiscard]] const void *data() const noexcept {
if constexpr(base_type::has_buffer) {
return (mode == any_policy::embedded) ? &this->buffer : this->instance;
} else {
return this->instance;
}
[[nodiscard]] const void * data() const ENTT_NOEXCEPT {
return vtable(operation::CADDR, *this, nullptr);
}
/**
* @brief Returns an opaque pointer to the contained instance.
* @param req Expected type.
* @return An opaque pointer the contained instance, if any.
*/
[[nodiscard]] const void *data(const type_info &req) const noexcept {
return has_value(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.
* @return An opaque pointer the contained instance, if any.
*/
[[nodiscard]] void *data() noexcept {
return (mode == any_policy::cref) ? nullptr : const_cast<void *>(std::as_const(*this).data());
}
/**
* @brief Returns an opaque pointer to the contained instance.
* @param req Expected type.
* @return An opaque pointer the contained instance, if any.
*/
[[nodiscard]] void *data(const type_info &req) noexcept {
return (mode == any_policy::cref) ? nullptr : const_cast<void *>(std::as_const(*this).data(req));
}
/**
* @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>>());
}
/*! @copydoc data */
[[nodiscard]] void * data() ENTT_NOEXCEPT {
return const_cast<void *>(vtable(operation::ADDR, *this, nullptr));
}
/**
@@ -428,46 +298,24 @@ public:
* @param args Parameters to use to construct the instance.
*/
template<typename Type, typename... Args>
void emplace(Args &&...args) {
invoke_deleter_if_exists();
void emplace(Args &&... args) {
std::exchange(vtable, &basic_vtable<std::remove_const_t<std::remove_reference_t<Type>>>)(operation::DTOR, *this, nullptr);
mode = type_to_policy<Type>();
initialize<Type>(std::forward<Args>(args)...);
}
/**
* @brief Assigns a value to the contained object without replacing it.
* @param other The value to assign to the contained object.
* @return True in case of success, false otherwise.
*/
bool assign(const basic_any &other) {
if(other && (mode != any_policy::cref) && (underlying_type == other.underlying_type)) {
return (vtable(request::assign, *this, other.data()) != nullptr);
}
return false;
}
/*! @copydoc assign */
// NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
bool assign(basic_any &&other) {
if(other && (mode != any_policy::cref) && (underlying_type == other.underlying_type)) {
return (other.mode == any_policy::cref) ? (vtable(request::assign, *this, std::as_const(other).data()) != nullptr) : (vtable(request::transfer, *this, other.data()) != nullptr);
}
return false;
}
/*! @brief Destroys contained object */
void reset() {
invoke_deleter_if_exists();
initialize<void>();
std::exchange(vtable, &basic_vtable<void>)(operation::DTOR, *this, nullptr);
mode = policy::OWNER;
}
/**
* @brief Returns false if a wrapper is empty, true otherwise.
* @return False if the wrapper is empty, true otherwise.
*/
[[nodiscard]] explicit operator bool() const noexcept {
return has_value();
[[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT {
return !(vtable(operation::CADDR, *this, nullptr) == nullptr);
}
/**
@@ -475,151 +323,134 @@ public:
* @param other Wrapper with which to compare.
* @return False if the two objects differ in their content, true otherwise.
*/
[[nodiscard]] bool operator==(const basic_any &other) const noexcept {
if(other && (underlying_type == other.underlying_type)) {
return (vtable(request::compare, *this, other.data()) != nullptr);
}
return (!*this && !other);
}
/**
* @brief Checks if two wrappers differ in their content.
* @param other Wrapper with which to compare.
* @return True if the two objects differ in their content, false otherwise.
*/
[[nodiscard]] bool operator!=(const basic_any &other) const noexcept {
return !(*this == other);
bool operator==(const basic_any &other) const ENTT_NOEXCEPT {
const basic_any *trampoline = &other;
return type() == other.type() && (vtable(operation::COMP, *this, &trampoline) || !other.data());
}
/**
* @brief Aliasing constructor.
* @return A wrapper that shares a reference to an unmanaged object.
*/
[[nodiscard]] basic_any as_ref() noexcept {
basic_any other = std::as_const(*this).as_ref();
other.mode = (mode == any_policy::cref ? any_policy::cref : any_policy::ref);
return other;
[[nodiscard]] basic_any as_ref() ENTT_NOEXCEPT {
return basic_any{*this, (mode == policy::CREF ? policy::CREF : policy::REF)};
}
/*! @copydoc as_ref */
[[nodiscard]] basic_any as_ref() const noexcept {
basic_any other{};
other.instance = data();
other.vtable = vtable;
other.underlying_type = underlying_type;
other.mode = any_policy::cref;
return other;
[[nodiscard]] basic_any as_ref() const ENTT_NOEXCEPT {
return basic_any{*this, policy::CREF};
}
/**
* @brief Returns true if a wrapper owns its object, false otherwise.
* @return True if the wrapper owns its object, false otherwise.
*/
[[nodiscard]] bool owner() const noexcept {
return (mode == any_policy::dynamic || mode == any_policy::embedded);
}
/**
* @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;
[[nodiscard]] bool owner() const ENTT_NOEXCEPT {
return (mode == policy::OWNER);
}
private:
vtable_type *vtable{};
deleter_type *deleter{};
id_type underlying_type{};
any_policy mode{};
union { const void *instance; storage_type storage; };
vtable_type *vtable;
policy mode;
};
/**
* @brief Checks if two wrappers differ in their content.
* @tparam Len Size of the storage reserved for the small buffer optimization.
* @tparam Align Alignment requirement.
* @param lhs A wrapper, either empty or not.
* @param rhs A wrapper, either empty or not.
* @return True if the two wrappers differ in their content, false otherwise.
*/
template<std::size_t Len, std::size_t Align>
[[nodiscard]] inline bool operator!=(const basic_any<Len, Align> &lhs, const basic_any<Len, Align> &rhs) ENTT_NOEXCEPT {
return !(lhs == rhs);
}
/**
* @brief Performs type-safe access to the contained object.
* @tparam Type Type to which conversion is required.
* @tparam Len Size of the buffer reserved for the small buffer optimization.
* @tparam Len Size of the storage reserved for the small buffer optimization.
* @tparam Align Alignment requirement.
* @param data Target any object.
* @return The element converted to the requested type.
*/
template<typename Type, std::size_t Len, std::size_t Align>
[[nodiscard]] std::remove_const_t<Type> any_cast(const basic_any<Len, Align> &data) noexcept {
const auto *const instance = any_cast<std::remove_reference_t<Type>>(&data);
Type any_cast(const basic_any<Len, Align> &data) ENTT_NOEXCEPT {
const auto * const instance = any_cast<std::remove_reference_t<Type>>(&data);
ENTT_ASSERT(instance, "Invalid instance");
return static_cast<Type>(*instance);
}
/*! @copydoc any_cast */
template<typename Type, std::size_t Len, std::size_t Align>
[[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
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");
return static_cast<Type>(*instance);
}
/*! @copydoc any_cast */
template<typename Type, std::size_t Len, std::size_t Align>
// NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
[[nodiscard]] std::remove_const_t<Type> any_cast(basic_any<Len, Align> &&data) noexcept {
if constexpr(std::is_copy_constructible_v<std::remove_const_t<std::remove_reference_t<Type>>>) {
if(auto *const instance = any_cast<std::remove_reference_t<Type>>(&data); instance) {
return static_cast<Type>(std::move(*instance));
}
return any_cast<Type>(data);
} else {
auto *const instance = any_cast<std::remove_reference_t<Type>>(&data);
ENTT_ASSERT(instance, "Invalid instance");
return static_cast<Type>(std::move(*instance));
}
}
/*! @copydoc any_cast */
template<typename Type, std::size_t Len, std::size_t Align>
[[nodiscard]] const Type *any_cast(const basic_any<Len, Align> *data) noexcept {
return data->template data<std::remove_const_t<Type>>();
Type any_cast(basic_any<Len, Align> &&data) ENTT_NOEXCEPT {
// forces const on non-reference types to make them work also with wrappers for const references
auto * const instance = any_cast<std::remove_reference_t<const Type>>(&data);
ENTT_ASSERT(instance, "Invalid instance");
return static_cast<Type>(std::move(*instance));
}
/*! @copydoc any_cast */
template<typename Type, std::size_t Len, std::size_t Align>
[[nodiscard]] Type *any_cast(basic_any<Len, Align> *data) noexcept {
if constexpr(std::is_const_v<Type>) {
// last attempt to make wrappers for const references return their values
return any_cast<Type>(&std::as_const(*data));
} else {
return data->template data<Type>();
}
const Type * any_cast(const basic_any<Len, Align> *data) ENTT_NOEXCEPT {
return (data->type() == type_id<Type>() ? static_cast<const Type *>(data->data()) : nullptr);
}
/*! @copydoc any_cast */
template<typename Type, std::size_t Len, std::size_t Align>
Type * any_cast(basic_any<Len, Align> *data) ENTT_NOEXCEPT {
// last attempt to make wrappers for const references return their values
return (data->type() == type_id<Type>() ? static_cast<Type *>(static_cast<constness_as_t<basic_any<Len, Align>, Type> *>(data)->data()) : nullptr);
}
/**
* @brief Constructs a wrapper from a given type, passing it all arguments.
* @tparam Type Type of object to use to initialize the wrapper.
* @tparam Len Size of the 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 Args Types of arguments to use to construct the new instance.
* @param args Parameters to use to construct the instance.
* @return A properly initialized wrapper for an object of the given type.
*/
template<typename Type, std::size_t Len = basic_any<>::length, std::size_t Align = basic_any<Len>::alignment, typename... Args>
[[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)...};
}
/**
* @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 Type Type of argument to use to construct the new instance.
* @param value Parameter to use to construct the instance.
* @return A properly initialized and not necessarily owning wrapper.
*/
template<std::size_t Len = basic_any<>::length, std::size_t Align = basic_any<Len>::alignment, typename Type>
[[nodiscard]] basic_any<Len, Align> forward_as_any(Type &&value) {
return basic_any<Len, Align>{std::in_place_type<Type &&>, std::forward<Type>(value)};
basic_any<Len, Align> forward_as_any(Type &&value) {
return basic_any<Len, Align>{std::in_place_type<std::conditional_t<std::is_rvalue_reference_v<Type>, std::decay_t<Type>, Type>>, std::forward<Type>(value)};
}
}
} // namespace entt
#endif

33
src/entt/core/attribute.h Normal file
View File

@@ -0,0 +1,33 @@
#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,70 +0,0 @@
#ifndef ENTT_CORE_BIT_HPP
#define ENTT_CORE_BIT_HPP
#include <cstddef>
#include <limits>
#include <type_traits>
#include "../config/config.h"
namespace entt {
/**
* @brief Returns the number of set bits in a value (waiting for C++20 and
* `std::popcount`).
* @tparam Type Unsigned integer type.
* @param value A value of unsigned integer type.
* @return The number of set bits in the value.
*/
template<typename Type>
[[nodiscard]] constexpr std::enable_if_t<std::is_unsigned_v<Type>, int> popcount(const Type value) noexcept {
return value ? (int(value & 1) + popcount(static_cast<Type>(value >> 1))) : 0;
}
/**
* @brief Checks whether a value is a power of two or not (waiting for C++20 and
* `std::has_single_bit`).
* @tparam Type Unsigned integer type.
* @param value A value of unsigned integer type.
* @return True if the value is a power of two, false otherwise.
*/
template<typename Type>
[[nodiscard]] constexpr std::enable_if_t<std::is_unsigned_v<Type>, bool> has_single_bit(const Type value) noexcept {
return value && ((value & (value - 1)) == 0);
}
/**
* @brief Computes the smallest power of two greater than or equal to a value
* (waiting for C++20 and `std::bit_ceil`).
* @tparam Type Unsigned integer type.
* @param value A value of unsigned integer type.
* @return The smallest power of two greater than or equal to the given value.
*/
template<typename Type>
[[nodiscard]] constexpr std::enable_if_t<std::is_unsigned_v<Type>, Type> next_power_of_two(const Type value) noexcept {
// NOLINTNEXTLINE(bugprone-assert-side-effect)
ENTT_ASSERT_CONSTEXPR(value < (Type{1u} << (std::numeric_limits<Type>::digits - 1)), "Numeric limits exceeded");
Type curr = value - (value != 0u);
for(int next = 1; next < std::numeric_limits<Type>::digits; next = next * 2) {
curr |= (curr >> next);
}
return ++curr;
}
/**
* @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<typename Type>
[[nodiscard]] constexpr std::enable_if_t<std::is_unsigned_v<Type>, Type> fast_mod(const Type value, const std::size_t mod) noexcept {
ENTT_ASSERT_CONSTEXPR(has_single_bit(mod), "Value must be a power of two");
return static_cast<Type>(value & (mod - 1u));
}
} // namespace entt
#endif

View File

@@ -1,272 +0,0 @@
#ifndef ENTT_CORE_COMPRESSED_PAIR_HPP
#define ENTT_CORE_COMPRESSED_PAIR_HPP
#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>
#include "fwd.hpp"
#include "type_traits.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename Type, std::size_t, typename = void>
struct compressed_pair_element {
using reference = Type &;
using const_reference = const Type &;
template<typename Dummy = Type, typename = std::enable_if_t<std::is_default_constructible_v<Dummy>>>
// NOLINTNEXTLINE(modernize-use-equals-default)
constexpr compressed_pair_element() noexcept(std::is_nothrow_default_constructible_v<Type>) {}
template<typename Arg, typename = std::enable_if_t<!std::is_same_v<std::remove_const_t<std::remove_reference_t<Arg>>, compressed_pair_element>>>
constexpr compressed_pair_element(Arg &&arg) noexcept(std::is_nothrow_constructible_v<Type, Arg>)
: value{std::forward<Arg>(arg)} {}
template<typename... Args, std::size_t... Index>
constexpr compressed_pair_element(std::tuple<Args...> args, std::index_sequence<Index...>) noexcept(std::is_nothrow_constructible_v<Type, Args...>)
: value{std::forward<Args>(std::get<Index>(args))...} {}
[[nodiscard]] constexpr reference get() noexcept {
return value;
}
[[nodiscard]] constexpr const_reference get() const noexcept {
return value;
}
private:
Type value{};
};
template<typename Type, std::size_t Tag>
struct compressed_pair_element<Type, Tag, std::enable_if_t<is_ebco_eligible_v<Type>>>: Type {
using reference = Type &;
using const_reference = const Type &;
using base_type = Type;
template<typename Dummy = Type, typename = std::enable_if_t<std::is_default_constructible_v<Dummy>>>
constexpr compressed_pair_element() noexcept(std::is_nothrow_default_constructible_v<base_type>)
: base_type{} {}
template<typename Arg, typename = std::enable_if_t<!std::is_same_v<std::remove_const_t<std::remove_reference_t<Arg>>, compressed_pair_element>>>
constexpr compressed_pair_element(Arg &&arg) noexcept(std::is_nothrow_constructible_v<base_type, Arg>)
: base_type{std::forward<Arg>(arg)} {}
template<typename... Args, std::size_t... Index>
constexpr compressed_pair_element(std::tuple<Args...> args, std::index_sequence<Index...>) noexcept(std::is_nothrow_constructible_v<base_type, Args...>)
: base_type{std::forward<Args>(std::get<Index>(args))...} {}
[[nodiscard]] constexpr reference get() noexcept {
return *this;
}
[[nodiscard]] constexpr const_reference get() const noexcept {
return *this;
}
};
} // namespace internal
/*! @endcond */
/**
* @brief A compressed pair.
*
* A pair that exploits the _Empty Base Class Optimization_ (or _EBCO_) to
* reduce its final size to a minimum.
*
* @tparam First The type of the first element that the pair stores.
* @tparam Second The type of the second element that the pair stores.
*/
template<typename First, typename Second>
class compressed_pair final
: internal::compressed_pair_element<First, 0u>,
internal::compressed_pair_element<Second, 1u> {
using first_base = internal::compressed_pair_element<First, 0u>;
using second_base = internal::compressed_pair_element<Second, 1u>;
public:
/*! @brief The type of the first element that the pair stores. */
using first_type = First;
/*! @brief The type of the second element that the pair stores. */
using second_type = Second;
/**
* @brief Default constructor, conditionally enabled.
*
* This constructor is only available when the types that the pair stores
* are both at least default constructible.
*
* @tparam Dummy Dummy template parameter used for internal purposes.
*/
template<bool Dummy = true, typename = std::enable_if_t<Dummy && std::is_default_constructible_v<first_type> && std::is_default_constructible_v<second_type>>>
constexpr compressed_pair() noexcept(std::is_nothrow_default_constructible_v<first_base> && std::is_nothrow_default_constructible_v<second_base>)
: first_base{},
second_base{} {}
/**
* @brief Copy constructor.
* @param other The instance to copy from.
*/
constexpr compressed_pair(const compressed_pair &other) = default;
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
constexpr compressed_pair(compressed_pair &&other) noexcept = default;
/**
* @brief Constructs a pair from its values.
* @tparam Arg Type of value to use to initialize the first element.
* @tparam Other Type of value to use to initialize the second element.
* @param arg Value to use to initialize the first element.
* @param other Value to use to initialize the second element.
*/
template<typename Arg, typename Other>
constexpr compressed_pair(Arg &&arg, Other &&other) noexcept(std::is_nothrow_constructible_v<first_base, Arg> && std::is_nothrow_constructible_v<second_base, Other>)
: first_base{std::forward<Arg>(arg)},
second_base{std::forward<Other>(other)} {}
/**
* @brief Constructs a pair by forwarding the arguments to its parts.
* @tparam Args Types of arguments to use to initialize the first element.
* @tparam Other Types of arguments to use to initialize the second element.
* @param args Arguments to use to initialize the first element.
* @param other Arguments to use to initialize the second element.
*/
template<typename... Args, typename... Other>
constexpr compressed_pair(std::piecewise_construct_t, std::tuple<Args...> args, std::tuple<Other...> other) noexcept(std::is_nothrow_constructible_v<first_base, Args...> && std::is_nothrow_constructible_v<second_base, Other...>)
: first_base{std::move(args), std::index_sequence_for<Args...>{}},
second_base{std::move(other), std::index_sequence_for<Other...>{}} {}
/*! @brief Default destructor. */
~compressed_pair() = default;
/**
* @brief Copy assignment operator.
* @param other The instance to copy from.
* @return This compressed pair object.
*/
constexpr compressed_pair &operator=(const compressed_pair &other) = default;
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This compressed pair object.
*/
constexpr compressed_pair &operator=(compressed_pair &&other) noexcept = default;
/**
* @brief Returns the first element that a pair stores.
* @return The first element that a pair stores.
*/
[[nodiscard]] constexpr first_type &first() noexcept {
return static_cast<first_base &>(*this).get();
}
/*! @copydoc first */
[[nodiscard]] constexpr const first_type &first() const noexcept {
return static_cast<const first_base &>(*this).get();
}
/**
* @brief Returns the second element that a pair stores.
* @return The second element that a pair stores.
*/
[[nodiscard]] constexpr second_type &second() noexcept {
return static_cast<second_base &>(*this).get();
}
/*! @copydoc second */
[[nodiscard]] constexpr const second_type &second() const noexcept {
return static_cast<const second_base &>(*this).get();
}
/**
* @brief Swaps two compressed pair objects.
* @param other The compressed pair to swap with.
*/
constexpr void swap(compressed_pair &other) noexcept {
using std::swap;
swap(first(), other.first());
swap(second(), other.second());
}
/**
* @brief Extracts an element from the compressed pair.
* @tparam Index An integer value that is either 0 or 1.
* @return Returns a reference to the first element if `Index` is 0 and a
* reference to the second element if `Index` is 1.
*/
template<std::size_t Index>
[[nodiscard]] constexpr decltype(auto) get() noexcept {
if constexpr(Index == 0u) {
return first();
} else {
static_assert(Index == 1u, "Index out of bounds");
return second();
}
}
/*! @copydoc get */
template<std::size_t Index>
[[nodiscard]] constexpr decltype(auto) get() const noexcept {
if constexpr(Index == 0u) {
return first();
} else {
static_assert(Index == 1u, "Index out of bounds");
return second();
}
}
};
/**
* @brief Deduction guide.
* @tparam Type Type of value to use to initialize the first element.
* @tparam Other Type of value to use to initialize the second element.
*/
template<typename Type, typename Other>
compressed_pair(Type &&, Other &&) -> compressed_pair<std::decay_t<Type>, std::decay_t<Other>>;
/**
* @brief Swaps two compressed pair objects.
* @tparam First The type of the first element that the pairs store.
* @tparam Second The type of the second element that the pairs store.
* @param lhs A valid compressed pair object.
* @param rhs A valid compressed pair object.
*/
template<typename First, typename Second>
constexpr void swap(compressed_pair<First, Second> &lhs, compressed_pair<First, Second> &rhs) noexcept {
lhs.swap(rhs);
}
} // namespace entt
namespace std {
/**
* @brief `std::tuple_size` specialization for `compressed_pair`s.
* @tparam First The type of the first element that the pair stores.
* @tparam Second The type of the second element that the pair stores.
*/
template<typename First, typename Second>
struct tuple_size<entt::compressed_pair<First, Second>>: integral_constant<size_t, 2u> {};
/**
* @brief `std::tuple_element` specialization for `compressed_pair`s.
* @tparam Index The index of the type to return.
* @tparam First The type of the first element that the pair stores.
* @tparam Second The type of the second element that the pair stores.
*/
template<size_t Index, typename First, typename Second>
struct tuple_element<Index, entt::compressed_pair<First, Second>>: conditional<Index == 0u, First, Second> {
static_assert(Index < 2u, "Index out of bounds");
};
} // namespace std
#endif

View File

@@ -1,97 +0,0 @@
#ifndef ENTT_CORE_ENUM_HPP
#define ENTT_CORE_ENUM_HPP
#include <type_traits>
namespace entt {
/**
* @brief Enable bitmask support for enum classes.
* @tparam Type The enum type for which to enable bitmask support.
*/
template<typename Type, typename = void>
struct enum_as_bitmask: std::false_type {};
/*! @copydoc enum_as_bitmask */
template<typename Type>
struct enum_as_bitmask<Type, std::void_t<decltype(Type::_entt_enum_as_bitmask)>>: std::is_enum<Type> {};
/**
* @brief Helper variable template.
* @tparam Type The enum class type for which to enable bitmask support.
*/
template<typename Type>
inline constexpr bool enum_as_bitmask_v = enum_as_bitmask<Type>::value;
} // namespace entt
/**
* @brief Operator available for enums for which bitmask support is enabled.
* @tparam Type Enum class type.
* @param lhs The first value to use.
* @param rhs The second value to use.
* @return The result of invoking the operator on the underlying types of the
* two values provided.
*/
template<typename Type>
[[nodiscard]] constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type>
operator|(const Type lhs, const Type rhs) noexcept {
return static_cast<Type>(static_cast<std::underlying_type_t<Type>>(lhs) | static_cast<std::underlying_type_t<Type>>(rhs));
}
/*! @copydoc operator| */
template<typename Type>
[[nodiscard]] constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type>
operator&(const Type lhs, const Type rhs) noexcept {
return static_cast<Type>(static_cast<std::underlying_type_t<Type>>(lhs) & static_cast<std::underlying_type_t<Type>>(rhs));
}
/*! @copydoc operator| */
template<typename Type>
[[nodiscard]] constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type>
operator^(const Type lhs, const Type rhs) noexcept {
return static_cast<Type>(static_cast<std::underlying_type_t<Type>>(lhs) ^ static_cast<std::underlying_type_t<Type>>(rhs));
}
/**
* @brief Operator available for enums for which bitmask support is enabled.
* @tparam Type Enum class type.
* @param value The value to use.
* @return The result of invoking the operator on the underlying types of the
* value provided.
*/
template<typename Type>
[[nodiscard]] constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type>
operator~(const Type value) noexcept {
return static_cast<Type>(~static_cast<std::underlying_type_t<Type>>(value));
}
/*! @copydoc operator~ */
template<typename Type>
[[nodiscard]] constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, bool>
operator!(const Type value) noexcept {
return !static_cast<std::underlying_type_t<Type>>(value);
}
/*! @copydoc operator| */
template<typename Type>
constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type &>
operator|=(Type &lhs, const Type rhs) noexcept {
return (lhs = (lhs | rhs));
}
/*! @copydoc operator| */
template<typename Type>
constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type &>
operator&=(Type &lhs, const Type rhs) noexcept {
return (lhs = (lhs & rhs));
}
/*! @copydoc operator| */
template<typename Type>
constexpr std::enable_if_t<entt::enum_as_bitmask_v<Type>, Type &>
operator^=(Type &lhs, const Type rhs) noexcept {
return (lhs = (lhs ^ rhs));
}
#endif

View File

@@ -1,11 +1,14 @@
#ifndef ENTT_CORE_FAMILY_HPP
#define ENTT_CORE_FAMILY_HPP
#include "../config/config.h"
#include "fwd.hpp"
namespace entt {
/**
* @brief Dynamic identifier generator.
*
@@ -15,21 +18,20 @@ namespace entt {
*/
template<typename...>
class family {
static auto identifier() noexcept {
static ENTT_MAYBE_ATOMIC(id_type) value{};
return value++;
}
inline static ENTT_MAYBE_ATOMIC(id_type) identifier{};
public:
/*! @brief Unsigned integer type. */
using value_type = id_type;
using family_type = id_type;
/*! @brief Statically generated unique identifier for the given type. */
template<typename... Type>
// at the time I'm writing, clang crashes during compilation if auto is used instead of family_type
inline static const value_type value = identifier();
inline static const family_type type = identifier++;
};
} // namespace entt
}
#endif

View File

@@ -1,51 +1,27 @@
#ifndef ENTT_CORE_FWD_HPP
#define ENTT_CORE_FWD_HPP
#include <cstddef>
#include <cstdint>
#include <type_traits>
#include "../config/config.h"
namespace entt {
/*! @brief Possible modes of an any object. */
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])>
template<std::size_t Len = sizeof(double[2]), std::size_t = alignof(typename std::aligned_storage_t<Len + !Len>)>
class basic_any;
/*! @brief Alias declaration for type identifiers. */
using id_type = ENTT_ID_TYPE;
/*! @brief Alias declaration for the most common use case. */
using any = basic_any<>;
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
#endif

View File

@@ -1,100 +1,122 @@
#ifndef ENTT_CORE_HASHED_STRING_HPP
#define ENTT_CORE_HASHED_STRING_HPP
#include <cstddef>
#include <cstdint>
#include "../config/config.h"
#include "fwd.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
namespace internal {
template<typename = id_type>
struct fnv_1a_params;
template<typename>
struct fnv1a_traits;
template<>
struct fnv_1a_params<std::uint32_t> {
static constexpr auto offset = 2166136261;
static constexpr auto prime = 16777619;
struct fnv1a_traits<std::uint32_t> {
using type = std::uint32_t;
static constexpr std::uint32_t offset = 2166136261;
static constexpr std::uint32_t prime = 16777619;
};
template<>
struct fnv_1a_params<std::uint64_t> {
static constexpr auto offset = 14695981039346656037ull;
static constexpr auto prime = 1099511628211ull;
struct fnv1a_traits<std::uint64_t> {
using type = std::uint64_t;
static constexpr std::uint64_t offset = 14695981039346656037ull;
static constexpr std::uint64_t prime = 1099511628211ull;
};
template<typename Char>
struct basic_hashed_string {
using value_type = Char;
using size_type = std::size_t;
using hash_type = id_type;
const value_type *repr{};
hash_type hash{fnv_1a_params<>::offset};
size_type length{};
};
}
/**
* Internal details not to be documented.
* @endcond
*/
} // namespace internal
/*! @endcond */
/**
* @brief Zero overhead unique identifier.
*
* A hashed string is a compile-time tool that allows users to use
* human-readable identifiers in the codebase while using their numeric
* human-readable identifers in the codebase while using their numeric
* counterparts at runtime.<br/>
* Because of that, a hashed string can also be used in constant expressions if
* required.
*
* @warning
* This class doesn't take ownership of user-supplied strings nor does it make a
* copy of them.
*
* @tparam Char Character type.
*/
template<typename Char>
class basic_hashed_string: internal::basic_hashed_string<Char> {
using base_type = internal::basic_hashed_string<Char>;
using params = internal::fnv_1a_params<>;
class basic_hashed_string {
using traits_type = internal::fnv1a_traits<id_type>;
struct const_wrapper {
// non-explicit constructor on purpose
constexpr const_wrapper(const typename base_type::value_type *str) noexcept
: repr{str} {}
const typename base_type::value_type *repr;
constexpr const_wrapper(const Char *curr) ENTT_NOEXCEPT: str{curr} {}
const Char *str;
};
// FowlerNollVo hash function v. 1a - the good
[[nodiscard]] static constexpr id_type helper(const Char *curr) ENTT_NOEXCEPT {
auto value = traits_type::offset;
while(*curr != 0) {
value = (value ^ static_cast<traits_type::type>(*(curr++))) * traits_type::prime;
}
return value;
}
public:
/*! @brief Character type. */
using value_type = typename base_type::value_type;
using value_type = Char;
/*! @brief Unsigned integer type. */
using size_type = typename base_type::size_type;
/*! @brief Unsigned integer type. */
using hash_type = typename base_type::hash_type;
using hash_type = id_type;
/**
* @brief Returns directly the numeric representation of a string view.
* @param str Human-readable identifier.
* @param len Length of the string to hash.
* @param str Human-readable identifer.
* @param size Length of the string to hash.
* @return The numeric representation of the string.
*/
[[nodiscard]] static constexpr hash_type value(const value_type *str, const size_type len) noexcept {
return basic_hashed_string{str, len};
[[nodiscard]] static constexpr hash_type value(const value_type *str, std::size_t size) ENTT_NOEXCEPT {
id_type partial{traits_type::offset};
while(size--) { partial = (partial^(str++)[0])*traits_type::prime; }
return partial;
}
/**
* @brief Returns directly the numeric representation of a string.
*
* Forcing template resolution avoids implicit conversions. An
* human-readable identifier can be anything but a plain, old bunch of
* characters.<br/>
* Example of use:
* @code{.cpp}
* const auto value = basic_hashed_string<char>::to_value("my.png");
* @endcode
*
* @tparam N Number of characters of the identifier.
* @param str Human-readable identifier.
* @param str Human-readable identifer.
* @return The numeric representation of the string.
*/
template<std::size_t N>
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
[[nodiscard]] static ENTT_CONSTEVAL hash_type value(const value_type (&str)[N]) noexcept {
return basic_hashed_string{str};
[[nodiscard]] static constexpr hash_type value(const value_type (&str)[N]) ENTT_NOEXCEPT {
return helper(str);
}
/**
@@ -102,118 +124,97 @@ public:
* @param wrapper Helps achieving the purpose by relying on overloading.
* @return The numeric representation of the string.
*/
[[nodiscard]] static constexpr hash_type value(const_wrapper wrapper) noexcept {
return basic_hashed_string{wrapper};
[[nodiscard]] static hash_type value(const_wrapper wrapper) ENTT_NOEXCEPT {
return helper(wrapper.str);
}
/*! @brief Constructs an empty hashed string. */
constexpr basic_hashed_string() noexcept
: basic_hashed_string{nullptr, 0u} {}
/**
* @brief Constructs a hashed string from a string view.
* @param str Human-readable identifier.
* @param len Length of the string to hash.
*/
constexpr basic_hashed_string(const value_type *str, const size_type len) noexcept
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
: 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)
}
constexpr basic_hashed_string() ENTT_NOEXCEPT
: str{nullptr}, hash{}
{}
/**
* @brief Constructs a hashed string from an array of const characters.
*
* Forcing template resolution avoids implicit conversions. An
* human-readable identifier can be anything but a plain, old bunch of
* characters.<br/>
* Example of use:
* @code{.cpp}
* basic_hashed_string<char> hs{"my.png"};
* @endcode
*
* @tparam N Number of characters of the identifier.
* @param str Human-readable identifier.
* @param curr Human-readable identifer.
*/
template<std::size_t N>
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
ENTT_CONSTEVAL basic_hashed_string(const value_type (&str)[N]) noexcept
// 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;
}
}
constexpr basic_hashed_string(const value_type (&curr)[N]) ENTT_NOEXCEPT
: str{curr}, hash{helper(curr)}
{}
/**
* @brief Explicit constructor on purpose to avoid constructing a hashed
* string directly from a `const value_type *`.
*
* @warning
* The lifetime of the string is not extended nor is it copied.
*
* @param wrapper Helps achieving the purpose by relying on overloading.
*/
explicit constexpr basic_hashed_string(const_wrapper wrapper) noexcept
: base_type{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.
* @return The size of the hashed string.
*/
[[nodiscard]] constexpr size_type size() const noexcept {
return base_type::length;
}
explicit constexpr basic_hashed_string(const_wrapper wrapper) ENTT_NOEXCEPT
: str{wrapper.str}, hash{helper(wrapper.str)}
{}
/**
* @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 instance.
*/
[[nodiscard]] constexpr const value_type *data() const noexcept {
return base_type::repr;
[[nodiscard]] constexpr const value_type * data() const ENTT_NOEXCEPT {
return str;
}
/**
* @brief Returns the numeric representation of a hashed string.
* @return The numeric representation of the hashed string.
* @return The numeric representation of the instance.
*/
[[nodiscard]] constexpr hash_type value() const noexcept {
return base_type::hash;
[[nodiscard]] constexpr hash_type value() const ENTT_NOEXCEPT {
return hash;
}
/*! @copydoc data */
[[nodiscard]] explicit constexpr operator const value_type *() const noexcept {
return data();
}
[[nodiscard]] constexpr operator const value_type *() const ENTT_NOEXCEPT { return data(); }
/**
* @brief Returns the numeric representation of a hashed string.
* @return The numeric representation of the hashed string.
* @return The numeric representation of the instance.
*/
[[nodiscard]] constexpr operator hash_type() const noexcept {
return value();
[[nodiscard]] constexpr operator hash_type() const ENTT_NOEXCEPT { return value(); }
/**
* @brief Compares two hashed strings.
* @param other Hashed string with which to compare.
* @return True if the two hashed strings are identical, false otherwise.
*/
[[nodiscard]] constexpr bool operator==(const basic_hashed_string &other) const ENTT_NOEXCEPT {
return hash == other.hash;
}
private:
const value_type *str;
hash_type hash;
};
/**
* @brief Deduction guide.
* @tparam Char Character type.
* @param str Human-readable identifier.
* @param len Length of the string to hash.
*/
template<typename Char>
basic_hashed_string(const Char *str, std::size_t len) -> basic_hashed_string<Char>;
/**
* @brief Deduction guide.
*
* It allows to deduce the character type of the hashed string directly from a
* human-readable identifer provided to the constructor.
*
* @tparam Char Character type.
* @tparam N Number of characters of the identifier.
* @param str Human-readable identifier.
* @param str Human-readable identifer.
*/
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.
@@ -223,95 +224,46 @@ basic_hashed_string(const Char (&str)[N]) -> basic_hashed_string<Char>;
* @return True if the two hashed strings are identical, false otherwise.
*/
template<typename Char>
[[nodiscard]] constexpr bool operator==(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) noexcept {
return lhs.value() == rhs.value();
}
/**
* @brief Compares two hashed strings.
* @tparam Char Character type.
* @param lhs A valid hashed string.
* @param rhs A valid hashed string.
* @return True if the two hashed strings differ, false otherwise.
*/
template<typename Char>
[[nodiscard]] constexpr bool operator!=(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) noexcept {
[[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) noexcept {
return lhs.value() < rhs.value();
}
/**
* @brief Compares two hashed strings.
* @tparam Char Character type.
* @param lhs A valid hashed string.
* @param rhs A valid hashed string.
* @return True if the first element is less than or equal to the second, false
* otherwise.
*/
template<typename Char>
[[nodiscard]] constexpr bool operator<=(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) noexcept {
return !(rhs < lhs);
}
/*! @brief Aliases for common character types. */
using hashed_string = 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 first element is greater than the second, false
* otherwise.
*/
template<typename Char>
[[nodiscard]] constexpr bool operator>(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) noexcept {
return rhs < lhs;
}
/**
* @brief Compares two hashed strings.
* @tparam Char Character type.
* @param lhs A valid hashed string.
* @param rhs A valid hashed string.
* @return True if the first element is greater than or equal to the second,
* false otherwise.
*/
template<typename Char>
[[nodiscard]] constexpr bool operator>=(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) noexcept {
return !(lhs < rhs);
}
/*! @brief Aliases for common character types. */
using hashed_wstring = basic_hashed_string<wchar_t>;
inline namespace literals {
/**
* @brief User defined literal for hashed strings.
* @param str The literal without its suffix.
* @return A properly initialized hashed string.
*/
[[nodiscard]] ENTT_CONSTEVAL hashed_string operator""_hs(const char *str, std::size_t) noexcept {
return hashed_string{str};
[[nodiscard]] constexpr entt::hashed_string operator"" _hs(const char *str, std::size_t) ENTT_NOEXCEPT {
return entt::hashed_string{str};
}
/**
* @brief User defined literal for hashed wstrings.
* @param str The literal without its suffix.
* @return A properly initialized hashed wstring.
*/
[[nodiscard]] ENTT_CONSTEVAL hashed_wstring operator""_hws(const wchar_t *str, std::size_t) noexcept {
return hashed_wstring{str};
[[nodiscard]] constexpr entt::hashed_wstring operator"" _hws(const wchar_t *str, std::size_t) ENTT_NOEXCEPT {
return entt::hashed_wstring{str};
}
} // namespace literals
} // namespace entt
}
}
#endif

View File

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

View File

@@ -1,197 +0,0 @@
#ifndef ENTT_CORE_ITERATOR_HPP
#define ENTT_CORE_ITERATOR_HPP
#include <iterator>
#include <memory>
#include <type_traits>
#include <utility>
namespace entt {
/**
* @brief Helper type to use as pointer with input iterators.
* @tparam Type of wrapped value.
*/
template<typename Type>
struct input_iterator_pointer final {
/*! @brief Value type. */
using value_type = Type;
/*! @brief Pointer type. */
using pointer = Type *;
/*! @brief Reference type. */
using reference = Type &;
/**
* @brief Constructs a proxy object by move.
* @param val Value to use to initialize the proxy object.
*/
constexpr input_iterator_pointer(value_type &&val) noexcept(std::is_nothrow_move_constructible_v<value_type>)
: value{std::move(val)} {}
/**
* @brief Access operator for accessing wrapped values.
* @return A pointer to the wrapped value.
*/
[[nodiscard]] constexpr pointer operator->() noexcept {
return std::addressof(value);
}
/**
* @brief Dereference operator for accessing wrapped values.
* @return A reference to the wrapped value.
*/
[[nodiscard]] constexpr reference operator*() noexcept {
return value;
}
private:
Type value;
};
/**
* @brief Plain iota iterator (waiting for C++20).
* @tparam Type Value type.
*/
template<typename Type>
class iota_iterator final {
static_assert(std::is_integral_v<Type>, "Not an integral type");
public:
/*! @brief Value type, likely an integral one. */
using value_type = Type;
/*! @brief Invalid pointer type. */
using pointer = void;
/*! @brief Non-reference type, same as value type. */
using reference = value_type;
/*! @brief Difference type. */
using difference_type = std::ptrdiff_t;
/*! @brief Iterator category. */
using iterator_category = std::input_iterator_tag;
/*! @brief Default constructor. */
constexpr iota_iterator() noexcept
: current{} {}
/**
* @brief Constructs an iota iterator from a given value.
* @param init The initial value assigned to the iota iterator.
*/
constexpr iota_iterator(const value_type init) noexcept
: current{init} {}
/**
* @brief Pre-increment operator.
* @return This iota iterator.
*/
constexpr iota_iterator &operator++() noexcept {
return ++current, *this;
}
/**
* @brief Post-increment operator.
* @return This iota iterator.
*/
constexpr iota_iterator operator++(int) noexcept {
const iota_iterator orig = *this;
return ++(*this), orig;
}
/**
* @brief Dereference operator.
* @return The underlying value.
*/
[[nodiscard]] constexpr reference operator*() const noexcept {
return current;
}
private:
value_type current;
};
/**
* @brief Comparison operator.
* @tparam Type Value type of the iota iterator.
* @param lhs A properly initialized iota iterator.
* @param rhs A properly initialized iota iterator.
* @return True if the two iterators are identical, false otherwise.
*/
template<typename Type>
[[nodiscard]] constexpr bool operator==(const iota_iterator<Type> &lhs, const iota_iterator<Type> &rhs) noexcept {
return *lhs == *rhs;
}
/**
* @brief Comparison operator.
* @tparam Type Value type of the iota iterator.
* @param lhs A properly initialized iota iterator.
* @param rhs A properly initialized iota iterator.
* @return True if the two iterators differ, false otherwise.
*/
template<typename Type>
[[nodiscard]] constexpr bool operator!=(const iota_iterator<Type> &lhs, const iota_iterator<Type> &rhs) noexcept {
return !(lhs == rhs);
}
/**
* @brief Utility class to create an iterable object from a pair of iterators.
* @tparam It Type of iterator.
* @tparam Sentinel Type of sentinel.
*/
template<typename It, typename Sentinel = It>
struct iterable_adaptor final {
/*! @brief Value type. */
using value_type = typename std::iterator_traits<It>::value_type;
/*! @brief Iterator type. */
using iterator = It;
/*! @brief Sentinel type. */
using sentinel = Sentinel;
/*! @brief Default constructor. */
constexpr iterable_adaptor() noexcept(std::is_nothrow_default_constructible_v<iterator> && std::is_nothrow_default_constructible_v<sentinel>)
: first{},
last{} {}
/**
* @brief Creates an iterable object from a pair of iterators.
* @param from Begin iterator.
* @param to End iterator.
*/
constexpr iterable_adaptor(iterator from, sentinel to) noexcept(std::is_nothrow_move_constructible_v<iterator> && std::is_nothrow_move_constructible_v<sentinel>)
: first{std::move(from)},
last{std::move(to)} {}
/**
* @brief Returns an iterator to the beginning.
* @return An iterator to the first element of the range.
*/
[[nodiscard]] constexpr iterator begin() const noexcept {
return first;
}
/**
* @brief Returns an iterator to the end.
* @return An iterator to the element following the last element of the
* range.
*/
[[nodiscard]] constexpr sentinel end() const noexcept {
return last;
}
/*! @copydoc begin */
[[nodiscard]] constexpr iterator cbegin() const noexcept {
return begin();
}
/*! @copydoc end */
[[nodiscard]] constexpr sentinel cend() const noexcept {
return end();
}
private:
It first;
Sentinel last;
};
} // namespace entt
#endif

View File

@@ -1,244 +0,0 @@
#ifndef ENTT_CORE_MEMORY_HPP
#define ENTT_CORE_MEMORY_HPP
#include <cstddef>
#include <memory>
#include <tuple>
#include <type_traits>
#include <utility>
#include "../config/config.h"
namespace entt {
/**
* @brief Unwraps fancy pointers, does nothing otherwise (waiting for C++20).
* @tparam Type Pointer type.
* @param ptr Fancy or raw pointer.
* @return A raw pointer that represents the address of the original pointer.
*/
template<typename Type>
[[nodiscard]] constexpr auto to_address(Type &&ptr) noexcept {
if constexpr(std::is_pointer_v<std::decay_t<Type>>) {
return ptr;
} else {
return to_address(std::forward<Type>(ptr).operator->());
}
}
/**
* @brief Utility function to design allocation-aware containers.
* @tparam Allocator Type of allocator.
* @param lhs A valid allocator.
* @param rhs Another valid allocator.
*/
template<typename Allocator>
constexpr void propagate_on_container_copy_assignment([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) noexcept {
if constexpr(std::allocator_traits<Allocator>::propagate_on_container_copy_assignment::value) {
lhs = rhs;
}
}
/**
* @brief Utility function to design allocation-aware containers.
* @tparam Allocator Type of allocator.
* @param lhs A valid allocator.
* @param rhs Another valid allocator.
*/
template<typename Allocator>
constexpr void propagate_on_container_move_assignment([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) noexcept {
if constexpr(std::allocator_traits<Allocator>::propagate_on_container_move_assignment::value) {
lhs = std::move(rhs);
}
}
/**
* @brief Utility function to design allocation-aware containers.
* @tparam Allocator Type of allocator.
* @param lhs A valid allocator.
* @param rhs Another valid allocator.
*/
template<typename Allocator>
constexpr void propagate_on_container_swap([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) noexcept {
if constexpr(std::allocator_traits<Allocator>::propagate_on_container_swap::value) {
using std::swap;
swap(lhs, rhs);
} else {
ENTT_ASSERT_CONSTEXPR(lhs == rhs, "Cannot swap the containers");
}
}
/**
* @brief Deleter for allocator-aware unique pointers (waiting for C++20).
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Allocator>
struct allocation_deleter: private Allocator {
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Pointer type. */
using pointer = typename std::allocator_traits<Allocator>::pointer;
/**
* @brief Inherited constructors.
* @param alloc The allocator to use.
*/
constexpr allocation_deleter(const allocator_type &alloc) noexcept(std::is_nothrow_copy_constructible_v<allocator_type>)
: Allocator{alloc} {}
/**
* @brief Destroys the pointed object and deallocates its memory.
* @param ptr A valid pointer to an object of the given type.
*/
constexpr void operator()(pointer ptr) noexcept(std::is_nothrow_destructible_v<typename allocator_type::value_type>) {
using alloc_traits = std::allocator_traits<Allocator>;
alloc_traits::destroy(*this, to_address(ptr));
alloc_traits::deallocate(*this, ptr, 1u);
}
};
/**
* @brief Allows `std::unique_ptr` to use allocators (waiting for C++20).
* @tparam Type Type of object to allocate for and to construct.
* @tparam Allocator Type of allocator used to manage memory and elements.
* @tparam Args Types of arguments to use to construct the object.
* @param allocator The allocator to use.
* @param args Parameters to use to construct the object.
* @return A properly initialized unique pointer with a custom deleter.
*/
template<typename Type, typename Allocator, typename... Args>
ENTT_CONSTEXPR auto allocate_unique(Allocator &allocator, Args &&...args) {
static_assert(!std::is_array_v<Type>, "Array types are not supported");
using alloc_traits = typename std::allocator_traits<Allocator>::template rebind_traits<Type>;
using allocator_type = typename alloc_traits::allocator_type;
allocator_type alloc{allocator};
auto ptr = alloc_traits::allocate(alloc, 1u);
ENTT_TRY {
alloc_traits::construct(alloc, to_address(ptr), std::forward<Args>(args)...);
}
ENTT_CATCH {
alloc_traits::deallocate(alloc, ptr, 1u);
ENTT_THROW;
}
return std::unique_ptr<Type, allocation_deleter<allocator_type>>{ptr, alloc};
}
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename Type>
struct uses_allocator_construction {
template<typename Allocator, typename... Params>
static constexpr auto args([[maybe_unused]] const Allocator &allocator, Params &&...params) noexcept {
if constexpr(!std::uses_allocator_v<Type, Allocator> && std::is_constructible_v<Type, Params...>) {
return std::forward_as_tuple(std::forward<Params>(params)...);
} else {
static_assert(std::uses_allocator_v<Type, Allocator>, "Ill-formed request");
if constexpr(std::is_constructible_v<Type, std::allocator_arg_t, const Allocator &, Params...>) {
return std::tuple<std::allocator_arg_t, const Allocator &, Params &&...>{std::allocator_arg, allocator, std::forward<Params>(params)...};
} else {
static_assert(std::is_constructible_v<Type, Params..., const Allocator &>, "Ill-formed request");
return std::forward_as_tuple(std::forward<Params>(params)..., allocator);
}
}
}
};
template<typename Type, typename Other>
struct uses_allocator_construction<std::pair<Type, Other>> {
using type = std::pair<Type, Other>;
template<typename Allocator, typename First, typename Second>
static constexpr auto args(const Allocator &allocator, std::piecewise_construct_t, First &&first, Second &&second) noexcept {
return std::make_tuple(
std::piecewise_construct,
std::apply([&allocator](auto &&...curr) { return uses_allocator_construction<Type>::args(allocator, std::forward<decltype(curr)>(curr)...); }, std::forward<First>(first)),
std::apply([&allocator](auto &&...curr) { return uses_allocator_construction<Other>::args(allocator, std::forward<decltype(curr)>(curr)...); }, std::forward<Second>(second)));
}
template<typename Allocator>
static constexpr auto args(const Allocator &allocator) noexcept {
return uses_allocator_construction<type>::args(allocator, std::piecewise_construct, std::tuple<>{}, std::tuple<>{});
}
template<typename Allocator, typename First, typename Second>
static constexpr auto args(const Allocator &allocator, First &&first, Second &&second) noexcept {
return uses_allocator_construction<type>::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::forward<First>(first)), std::forward_as_tuple(std::forward<Second>(second)));
}
template<typename Allocator, typename First, typename Second>
static constexpr auto args(const Allocator &allocator, const std::pair<First, Second> &value) noexcept {
return uses_allocator_construction<type>::args(allocator, std::piecewise_construct, std::forward_as_tuple(value.first), std::forward_as_tuple(value.second));
}
template<typename Allocator, typename First, typename Second>
static constexpr auto args(const Allocator &allocator, std::pair<First, Second> &&value) noexcept {
return uses_allocator_construction<type>::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::move(value.first)), std::forward_as_tuple(std::move(value.second)));
}
};
} // namespace internal
/*! @endcond */
/**
* @brief Uses-allocator construction utility (waiting for C++20).
*
* Primarily intended for internal use. Prepares the argument list needed to
* create an object of a given type by means of uses-allocator construction.
*
* @tparam Type Type to return arguments for.
* @tparam Allocator Type of allocator used to manage memory and elements.
* @tparam Args Types of arguments to use to construct the object.
* @param allocator The allocator to use.
* @param args Parameters to use to construct the object.
* @return The arguments needed to create an object of the given type.
*/
template<typename Type, typename Allocator, typename... Args>
constexpr auto uses_allocator_construction_args(const Allocator &allocator, Args &&...args) noexcept {
return internal::uses_allocator_construction<Type>::args(allocator, std::forward<Args>(args)...);
}
/**
* @brief Uses-allocator construction utility (waiting for C++20).
*
* Primarily intended for internal use. Creates an object of a given type by
* means of uses-allocator construction.
*
* @tparam Type Type of object to create.
* @tparam Allocator Type of allocator used to manage memory and elements.
* @tparam Args Types of arguments to use to construct the object.
* @param allocator The allocator to use.
* @param args Parameters to use to construct the object.
* @return A newly created object of the given type.
*/
template<typename Type, typename Allocator, typename... Args>
constexpr Type make_obj_using_allocator(const Allocator &allocator, Args &&...args) {
return std::make_from_tuple<Type>(internal::uses_allocator_construction<Type>::args(allocator, std::forward<Args>(args)...));
}
/**
* @brief Uses-allocator construction utility (waiting for C++20).
*
* Primarily intended for internal use. Creates an object of a given type by
* means of uses-allocator construction at an uninitialized memory location.
*
* @tparam Type Type of object to create.
* @tparam Allocator Type of allocator used to manage memory and elements.
* @tparam Args Types of arguments to use to construct the object.
* @param value Memory location in which to place the object.
* @param allocator The allocator to use.
* @param args Parameters to use to construct the object.
* @return A pointer to the newly created object of the given type.
*/
template<typename Type, typename Allocator, typename... Args>
constexpr Type *uninitialized_construct_using_allocator(Type *value, const Allocator &allocator, Args &&...args) {
return std::apply([value](auto &&...curr) { return ::new(value) Type(std::forward<decltype(curr)>(curr)...); }, internal::uses_allocator_construction<Type>::args(allocator, std::forward<Args>(args)...));
}
} // namespace entt
#endif

View File

@@ -1,11 +1,14 @@
#ifndef ENTT_CORE_MONOSTATE_HPP
#define ENTT_CORE_MONOSTATE_HPP
#include "../config/config.h"
#include "fwd.hpp"
namespace entt {
/**
* @brief Minimal implementation of the monostate pattern.
*
@@ -23,12 +26,10 @@ struct monostate {
* @brief Assigns a value of a specific type to a given key.
* @tparam Type Type of the value to assign.
* @param val User data to assign to the given key.
* @return This monostate object.
*/
template<typename Type>
monostate &operator=(Type val) noexcept {
void operator=(Type val) const ENTT_NOEXCEPT {
value<Type> = val;
return *this;
}
/**
@@ -37,24 +38,25 @@ struct monostate {
* @return Stored value, if any.
*/
template<typename Type>
operator Type() const noexcept {
operator Type() const ENTT_NOEXCEPT {
return value<Type>;
}
private:
template<typename Type>
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
inline static ENTT_MAYBE_ATOMIC(Type) value{};
};
/**
* @brief Helper variable template.
* @tparam Value Value used to differentiate between different variables.
*/
template<id_type Value>
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
inline monostate<Value> monostate_v{};
inline monostate<Value> monostate_v = {};
}
} // namespace entt
#endif

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

@@ -1,90 +0,0 @@
#ifndef ENTT_CORE_TUPLE_HPP
#define ENTT_CORE_TUPLE_HPP
#include <tuple>
#include <type_traits>
#include <utility>
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.
* @tparam Type Tuple type of any sizes.
* @param value A tuple object of the given type.
* @return The tuple itself if it contains more than one element, the first
* element otherwise.
*/
template<typename Type>
constexpr decltype(auto) unwrap_tuple(Type &&value) noexcept {
if constexpr(std::tuple_size_v<std::remove_reference_t<Type>> == 1u) {
return std::get<0>(std::forward<Type>(value));
} else {
return std::forward<Type>(value);
}
}
/**
* @brief Utility class to forward-and-apply tuple objects.
* @tparam Func Type of underlying invocable object.
*/
template<typename Func>
struct forward_apply: private Func {
/**
* @brief Constructs a forward-and-apply object.
* @tparam Args Types of arguments to use to construct the new instance.
* @param args Parameters to use to construct the instance.
*/
template<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_reference_t<std::remove_const_t<Func>>>;
} // namespace entt
#endif

View File

@@ -1,97 +1,108 @@
#ifndef ENTT_CORE_TYPE_INFO_HPP
#define ENTT_CORE_TYPE_INFO_HPP
#include <string_view>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "fwd.hpp"
#include "../core/attribute.h"
#include "hashed_string.hpp"
#include "fwd.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
namespace internal {
struct ENTT_API type_index final {
[[nodiscard]] static id_type next() noexcept {
struct ENTT_API type_seq final {
[[nodiscard]] static id_type next() ENTT_NOEXCEPT {
static ENTT_MAYBE_ATOMIC(id_type) value{};
return value++;
}
};
template<typename Type>
[[nodiscard]] constexpr const char *pretty_function() noexcept {
#if defined ENTT_PRETTY_FUNCTION
return static_cast<const char *>(ENTT_PRETTY_FUNCTION);
#else
return "";
#endif
}
template<typename Type>
[[nodiscard]] constexpr auto stripped_type_name() noexcept {
[[nodiscard]] constexpr auto stripped_type_name() ENTT_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);
std::string_view pretty_function{ENTT_PRETTY_FUNCTION};
auto first = pretty_function.find_first_not_of(' ', pretty_function.find_first_of(ENTT_PRETTY_FUNCTION_PREFIX)+1);
auto value = pretty_function.substr(first, pretty_function.find_last_of(ENTT_PRETTY_FUNCTION_SUFFIX) - first);
return value;
#else
return std::string_view{};
return std::string_view{""};
#endif
}
template<typename Type, auto = stripped_type_name<Type>().find_first_of('.')>
[[nodiscard]] constexpr 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>();
return value;
}
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>();
return value;
}
template<typename Type, auto = stripped_type_name<Type>().find_first_of('.')>
[[nodiscard]] constexpr 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 value = hashed_string::value(stripped.data(), stripped.size());
return value;
}
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) {
return hashed_string::value(stripped.data(), stripped.size());
}(stripped_type_name<Type>());
return value;
}
} // namespace internal
/*! @endcond */
}
/**
* Internal details not to be documented.
* @endcond
*/
/**
* @brief Type sequential identifier.
* @tparam Type Type for which to generate a sequential identifier.
*/
template<typename Type, typename = void>
struct ENTT_API type_index final {
struct ENTT_API type_seq final {
/**
* @brief Returns the sequential identifier of a given type.
* @return The sequential identifier of a given type.
*/
[[nodiscard]] static id_type value() noexcept {
static const id_type value = internal::type_index::next();
[[nodiscard]] static id_type value() ENTT_NOEXCEPT {
static const id_type value = internal::type_seq::next();
return value;
}
/*! @copydoc value */
[[nodiscard]] constexpr operator id_type() const noexcept {
return value();
}
[[nodiscard]] constexpr operator id_type() const ENTT_NOEXCEPT { return value(); }
};
/**
* @brief Type hash.
* @tparam Type Type for which to generate a hash value.
@@ -103,20 +114,19 @@ struct type_hash final {
* @return The numeric representation of the given type.
*/
#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);
#else
[[nodiscard]] static constexpr id_type value() noexcept {
return type_index<Type>::value();
[[nodiscard]] static constexpr id_type value() ENTT_NOEXCEPT {
return type_seq<Type>::value();
#endif
}
/*! @copydoc value */
[[nodiscard]] constexpr operator id_type() const noexcept {
return value();
}
[[nodiscard]] constexpr operator id_type() const ENTT_NOEXCEPT { return value(); }
};
/**
* @brief Type name.
* @tparam Type Type for which to generate a name.
@@ -127,151 +137,124 @@ struct type_name final {
* @brief Returns the name of a given type.
* @return The name of the given type.
*/
[[nodiscard]] static constexpr std::string_view value() noexcept {
[[nodiscard]] static constexpr std::string_view value() ENTT_NOEXCEPT {
return internal::type_name<Type>(0);
}
/*! @copydoc value */
[[nodiscard]] constexpr operator std::string_view() const noexcept {
return value();
}
[[nodiscard]] constexpr operator std::string_view() const ENTT_NOEXCEPT { return value(); }
};
/*! @brief Implementation specific information about a type. */
struct type_info final {
/**
* @brief Constructs a type info object for a given type.
* @tparam Type Type for which to construct a type info object.
*/
template<typename Type>
// NOLINTBEGIN(modernize-use-transparent-functors)
constexpr type_info(std::in_place_type_t<Type>) noexcept
: seq{type_index<std::remove_const_t<std::remove_reference_t<Type>>>::value()},
identifier{type_hash<std::remove_const_t<std::remove_reference_t<Type>>>::value()},
alias{type_name<std::remove_const_t<std::remove_reference_t<Type>>>::value()} {}
// NOLINTEND(modernize-use-transparent-functors)
class type_info final {
template<typename>
friend type_info type_id() ENTT_NOEXCEPT;
type_info(id_type seq_v, id_type hash_v, std::string_view name_v) ENTT_NOEXCEPT
: seq_value{seq_v},
hash_value{hash_v},
name_value{name_v}
{}
public:
/*! @brief Default constructor. */
type_info() ENTT_NOEXCEPT
: type_info({}, {}, {})
{}
/*! @brief Default copy constructor. */
type_info(const type_info &) ENTT_NOEXCEPT = default;
/*! @brief Default move constructor. */
type_info(type_info &&) ENTT_NOEXCEPT = default;
/**
* @brief Type index.
* @return Type index.
* @brief Default copy assignment operator.
* @return This type info object.
*/
[[nodiscard]] constexpr id_type index() const noexcept {
return seq;
type_info & operator=(const type_info &) ENTT_NOEXCEPT = default;
/**
* @brief Default move assignment operator.
* @return This type info object.
*/
type_info & operator=(type_info &&) ENTT_NOEXCEPT = default;
/**
* @brief Checks if a type info object is properly initialized.
* @return True if the object is properly initialized, false otherwise.
*/
[[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT {
return name_value.data() != nullptr;
}
/**
* @brief Type sequential identifier.
* @return Type sequential identifier.
*/
[[nodiscard]] id_type seq() const ENTT_NOEXCEPT {
return seq_value;
}
/**
* @brief Type hash.
* @return Type hash.
*/
[[nodiscard]] constexpr id_type hash() const noexcept {
return identifier;
[[nodiscard]] id_type hash() const ENTT_NOEXCEPT {
return hash_value;
}
/**
* @brief Type name.
* @return Type name.
*/
[[nodiscard]] constexpr std::string_view name() const noexcept {
return alias;
[[nodiscard]] std::string_view name() const ENTT_NOEXCEPT {
return name_value;
}
/**
* @brief Compares the contents of two type info objects.
* @param other Object with which to compare.
* @return False if the two contents differ, true otherwise.
*/
[[nodiscard]] bool operator==(const type_info &other) const ENTT_NOEXCEPT {
return hash_value == other.hash_value;
}
private:
id_type seq;
id_type identifier;
std::string_view alias;
id_type seq_value;
id_type hash_value;
std::string_view name_value;
};
/**
* @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]] constexpr bool operator==(const type_info &lhs, const type_info &rhs) noexcept {
return lhs.hash() == rhs.hash();
}
/**
* @brief Compares the contents of two type info objects.
* @param lhs A type info object.
* @param rhs A type info object.
* @return True if the two type info objects differ, false otherwise.
* @return True if the two contents differ, false otherwise.
*/
[[nodiscard]] constexpr bool operator!=(const type_info &lhs, const type_info &rhs) noexcept {
[[nodiscard]] inline 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) noexcept {
return lhs.index() < rhs.index();
}
/**
* @brief Compares two type info objects.
* @param lhs A valid type info object.
* @param rhs A valid type info object.
* @return True if the first element is less than or equal to the second, false
* otherwise.
*/
[[nodiscard]] constexpr bool operator<=(const type_info &lhs, const type_info &rhs) noexcept {
return !(rhs < lhs);
}
/**
* @brief Compares two type info objects.
* @param lhs A valid type info object.
* @param rhs A valid type info object.
* @return True if the first element is greater than the second, false
* otherwise.
*/
[[nodiscard]] constexpr bool operator>(const type_info &lhs, const type_info &rhs) noexcept {
return rhs < lhs;
}
/**
* @brief Compares two type info objects.
* @param lhs A valid type info object.
* @param rhs A valid type info object.
* @return True if the first element is greater than or equal to the second,
* false otherwise.
*/
[[nodiscard]] constexpr bool operator>=(const type_info &lhs, const type_info &rhs) noexcept {
return !(lhs < rhs);
}
/**
* @brief Returns the type info object associated to a given type.
*
* The returned element refers to an object with static storage duration.<br/>
* The type doesn't need to be a complete type. If the type is a reference, the
* result refers to the referenced type. In all cases, top-level cv-qualifiers
* are ignored.
*
* @brief Returns the type info object for a given type.
* @tparam Type Type for which to generate a type info object.
* @return A reference to a properly initialized type info object.
* @return The type info object for the given type.
*/
template<typename Type>
[[nodiscard]] const type_info &type_id() noexcept {
if constexpr(std::is_same_v<Type, std::remove_const_t<std::remove_reference_t<Type>>>) {
static const type_info instance{std::in_place_type<Type>};
return instance;
} else {
return type_id<std::remove_const_t<std::remove_reference_t<Type>>>();
}
[[nodiscard]] type_info type_id() ENTT_NOEXCEPT {
return type_info{
type_seq<std::remove_cv_t<std::remove_reference_t<Type>>>::value(),
type_hash<std::remove_cv_t<std::remove_reference_t<Type>>>::value(),
type_name<std::remove_cv_t<std::remove_reference_t<Type>>>::value()
};
}
/*! @copydoc type_id */
template<typename Type>
// NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward)
[[nodiscard]] const type_info &type_id(Type &&) noexcept {
return type_id<std::remove_const_t<std::remove_reference_t<Type>>>();
}
} // namespace entt
#endif

View File

@@ -1,30 +1,36 @@
#ifndef ENTT_CORE_TYPE_TRAITS_HPP
#define ENTT_CORE_TYPE_TRAITS_HPP
#include <cstddef>
#include <iterator>
#include <tuple>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "fwd.hpp"
namespace entt {
/**
* @brief Utility class to disambiguate overloaded functions.
* @tparam N Number of choices available.
*/
template<std::size_t N>
struct choice_t
// unfortunately, doxygen cannot parse such a construct
: /*! @cond TURN_OFF_DOXYGEN */ choice_t<N - 1> /*! @endcond */
// Unfortunately, doxygen cannot parse such a construct.
/*! @cond TURN_OFF_DOXYGEN */
: choice_t<N-1>
/*! @endcond */
{};
/*! @copybrief choice_t */
template<>
struct choice_t<0> {};
/**
* @brief Variable template for the choice trick.
* @tparam N Number of choices available.
@@ -32,6 +38,7 @@ struct choice_t<0> {};
template<std::size_t N>
inline constexpr choice_t<N> choice{};
/**
* @brief Identity type trait.
*
@@ -46,6 +53,7 @@ struct type_identity {
using type = Type;
};
/**
* @brief Helper type.
* @tparam Type A type.
@@ -53,33 +61,39 @@ struct type_identity {
template<typename Type>
using type_identity_t = typename type_identity<Type>::type;
/**
* @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains.
* @tparam Type The type of which to return the size.
* @tparam The size of the type if `sizeof` accepts it, 0 otherwise.
*/
template<typename Type, typename = void>
struct size_of: std::integral_constant<std::size_t, 0u> {};
/*! @copydoc size_of */
template<typename Type>
struct size_of<Type, std::void_t<decltype(sizeof(Type))>>
// NOLINTNEXTLINE(bugprone-sizeof-expression)
: std::integral_constant<std::size_t, sizeof(Type)> {};
: std::integral_constant<std::size_t, sizeof(Type)>
{};
/**
* @brief Helper variable template.
* @tparam Type The type of which to return the size.
*/
template<typename Type>
template<class Type>
inline constexpr std::size_t size_of_v = size_of<Type>::value;
/**
* @brief Using declaration to be used to _repeat_ the same type a number of
* times equal to the size of a given parameter pack.
* @tparam Type A type to repeat.
*/
template<typename Type, typename>
using unpack_as_type = Type;
using unpack_as_t = Type;
/**
* @brief Helper variable template to be used to _repeat_ the same value a
@@ -87,7 +101,8 @@ using unpack_as_type = Type;
* @tparam Value A value to repeat.
*/
template<auto Value, typename>
inline constexpr auto unpack_as_value = Value;
inline constexpr auto unpack_as_v = Value;
/**
* @brief Wraps a static constant.
@@ -96,6 +111,7 @@ inline constexpr auto unpack_as_value = Value;
template<auto Value>
using integral_constant = std::integral_constant<decltype(Value), Value>;
/**
* @brief Alias template to facilitate the creation of named values.
* @tparam Value A constant value at least convertible to `id_type`.
@@ -103,6 +119,7 @@ using integral_constant = std::integral_constant<decltype(Value), Value>;
template<id_type Value>
using tag = integral_constant<Value>;
/**
* @brief A class to use to push around lists of types, nothing more.
* @tparam Type Types provided by the type list.
@@ -115,31 +132,36 @@ struct type_list {
static constexpr auto size = sizeof...(Type);
};
/*! @brief Primary template isn't defined on purpose. */
template<std::size_t, typename>
struct type_list_element;
/**
* @brief Provides compile-time indexed access to the types of a type list.
* @tparam Index Index of the type to return.
* @tparam First First type provided by the type list.
* @tparam Other Other types provided by the type list.
*/
template<std::size_t Index, typename First, typename... Other>
struct type_list_element<Index, type_list<First, Other...>>
: type_list_element<Index - 1u, type_list<Other...>> {};
/**
* @brief Provides compile-time indexed access to the types of a type list.
* @tparam First First type provided by the type list.
* @tparam Index Index of the type to return.
* @tparam Type First type provided by the type list.
* @tparam Other Other types provided by the type list.
*/
template<typename First, typename... Other>
struct type_list_element<0u, type_list<First, Other...>> {
template<std::size_t Index, typename Type, typename... Other>
struct type_list_element<Index, type_list<Type, Other...>>
: type_list_element<Index - 1u, type_list<Other...>>
{};
/**
* @brief Provides compile-time indexed access to the types of a type list.
* @tparam Type First type provided by the type list.
* @tparam Other Other types provided by the type list.
*/
template<typename Type, typename... Other>
struct type_list_element<0u, type_list<Type, Other...>> {
/*! @brief Searched type. */
using type = First;
using type = Type;
};
/**
* @brief Helper type.
* @tparam Index Index of the type to return.
@@ -148,57 +170,6 @@ struct type_list_element<0u, type_list<First, Other...>> {
template<std::size_t Index, typename List>
using type_list_element_t = typename type_list_element<Index, List>::type;
/*! @brief Primary template isn't defined on purpose. */
template<typename, typename>
struct type_list_index;
/**
* @brief Provides compile-time type access to the types of a type list.
* @tparam Type Type to look for and for which to return the index.
* @tparam First First type provided by the type list.
* @tparam Other Other types provided by the type list.
*/
template<typename Type, typename First, typename... Other>
struct type_list_index<Type, type_list<First, Other...>> {
/*! @brief Unsigned integer type. */
using value_type = std::size_t;
/*! @brief Compile-time position of the given type in the sublist. */
static constexpr value_type value = 1u + type_list_index<Type, type_list<Other...>>::value;
};
/**
* @brief Provides compile-time type access to the types of a type list.
* @tparam Type Type to look for and for which to return the index.
* @tparam Other Other types provided by the type list.
*/
template<typename Type, typename... Other>
struct type_list_index<Type, type_list<Type, Other...>> {
static_assert(type_list_index<Type, type_list<Other...>>::value == sizeof...(Other), "Non-unique type");
/*! @brief Unsigned integer type. */
using value_type = std::size_t;
/*! @brief Compile-time position of the given type in the sublist. */
static constexpr value_type value = 0u;
};
/**
* @brief Provides compile-time type access to the types of a type list.
* @tparam Type Type to look for and for which to return the index.
*/
template<typename Type>
struct type_list_index<Type, type_list<>> {
/*! @brief Unsigned integer type. */
using value_type = std::size_t;
/*! @brief Compile-time position of the given type in the sublist. */
static constexpr value_type value = 0u;
};
/**
* @brief Helper variable template.
* @tparam List Type list.
* @tparam Type Type to look for and for which to return the index.
*/
template<typename Type, typename List>
inline constexpr std::size_t type_list_index_v = type_list_index<Type, List>::value;
/**
* @brief Concatenates multiple type lists.
@@ -207,14 +178,14 @@ 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.
*/
template<typename... Type, typename... Other>
constexpr type_list<Type..., Other...> operator+(type_list<Type...>, type_list<Other...>) {
return {};
}
constexpr type_list<Type..., Other...> operator+(type_list<Type...>, type_list<Other...>) { return {}; }
/*! @brief Primary template isn't defined on purpose. */
template<typename...>
struct type_list_cat;
/*! @brief Concatenates multiple type lists. */
template<>
struct type_list_cat<> {
@@ -222,6 +193,7 @@ struct type_list_cat<> {
using type = type_list<>;
};
/**
* @brief Concatenates multiple type lists.
* @tparam Type Types provided by the first type list.
@@ -234,6 +206,7 @@ struct type_list_cat<type_list<Type...>, type_list<Other...>, List...> {
using type = typename type_list_cat<type_list<Type..., Other...>, List...>::type;
};
/**
* @brief Concatenates multiple type lists.
* @tparam Type Types provided by the type list.
@@ -244,6 +217,7 @@ struct type_list_cat<type_list<Type...>> {
using type = type_list<Type...>;
};
/**
* @brief Helper type.
* @tparam List Type lists to concatenate.
@@ -251,40 +225,43 @@ struct type_list_cat<type_list<Type...>> {
template<typename... List>
using type_list_cat_t = typename type_list_cat<List...>::type;
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename...>
/*! @brief Primary template isn't defined on purpose. */
template<typename>
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.
* @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>
struct type_list_unique {
template<typename Type, typename... Other>
struct type_list_unique<type_list<Type, Other...>> {
/*! @brief A type list without duplicate types. */
using type = typename internal::type_list_unique<List>::type;
using type = std::conditional_t<
std::disjunction_v<std::is_same<Type, Other>...>,
typename type_list_unique<type_list<Other...>>::type,
type_list_cat_t<type_list<Type>, typename type_list_unique<type_list<Other...>>::type>
>;
};
/*! @brief Removes duplicates types from a type list. */
template<>
struct type_list_unique<type_list<>> {
/*! @brief A type list without duplicate types. */
using type = type_list<>;
};
/**
* @brief Helper type.
* @tparam List Type list.
* @tparam Type A type list.
*/
template<typename List>
using type_list_unique_t = typename type_list_unique<List>::type;
template<typename Type>
using type_list_unique_t = typename type_list_unique<Type>::type;
/**
* @brief Provides the member constant `value` to true if a type list contains a
@@ -295,27 +272,30 @@ using type_list_unique_t = typename type_list_unique<List>::type;
template<typename List, typename Type>
struct type_list_contains;
/**
* @copybrief type_list_contains
* @tparam Type Types provided by the type list.
* @tparam Other Type to look for.
*/
template<typename... Type, typename Other>
struct type_list_contains<type_list<Type...>, Other>
: std::bool_constant<(std::is_same_v<Type, Other> || ...)> {};
struct type_list_contains<type_list<Type...>, Other>: std::disjunction<std::is_same<Type, Other>...> {};
/**
* @brief Helper variable template.
* @tparam List Type list.
* @tparam Type Type to look for.
*/
template<typename List, typename Type>
template<class List, typename Type>
inline constexpr bool type_list_contains_v = type_list_contains<List, Type>::value;
/*! @brief Primary template isn't defined on purpose. */
template<typename...>
struct type_list_diff;
/**
* @brief Computes the difference between two type lists.
* @tparam Type Types provided by the first type list.
@@ -327,6 +307,7 @@ struct type_list_diff<type_list<Type...>, type_list<Other...>> {
using type = type_list_cat_t<std::conditional_t<type_list_contains_v<type_list<Other...>, Type>, type_list<>, type_list<Type>>...>;
};
/**
* @brief Helper type.
* @tparam List Type lists between which to compute the difference.
@@ -334,29 +315,6 @@ struct type_list_diff<type_list<Type...>, type_list<Other...>> {
template<typename... List>
using type_list_diff_t = typename type_list_diff<List...>::type;
/*! @brief Primary template isn't defined on purpose. */
template<typename, template<typename...> class>
struct type_list_transform;
/**
* @brief Applies a given _function_ to a type list and generate a new list.
* @tparam Type Types provided by the type list.
* @tparam Op Unary operation as template class with a type member named `type`.
*/
template<typename... Type, template<typename...> class Op>
struct type_list_transform<type_list<Type...>, Op> {
/*! @brief Resulting type list after applying the transform function. */
// 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 = typename type_list_transform<List, Op>::type;
/**
* @brief A class to use to push around lists of constant values, nothing more.
@@ -370,10 +328,12 @@ struct value_list {
static constexpr auto size = sizeof...(Value);
};
/*! @brief Primary template isn't defined on purpose. */
template<std::size_t, typename>
struct value_list_element;
/**
* @brief Provides compile-time indexed access to the values of a value list.
* @tparam Index Index of the value to return.
@@ -382,7 +342,9 @@ struct value_list_element;
*/
template<std::size_t Index, auto Value, auto... Other>
struct value_list_element<Index, value_list<Value, Other...>>
: value_list_element<Index - 1u, value_list<Other...>> {};
: value_list_element<Index - 1u, value_list<Other...>>
{};
/**
* @brief Provides compile-time indexed access to the types of a type list.
@@ -391,19 +353,10 @@ struct value_list_element<Index, value_list<Value, Other...>>
*/
template<auto Value, auto... Other>
struct value_list_element<0u, value_list<Value, Other...>> {
/*! @brief Searched type. */
using type = decltype(Value);
/*! @brief Searched 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 = typename value_list_element<Index, List>::type;
/**
* @brief Helper type.
@@ -413,57 +366,6 @@ using value_list_element_t = typename value_list_element<Index, List>::type;
template<std::size_t Index, typename List>
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.
@@ -472,14 +374,14 @@ 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.
*/
template<auto... Value, auto... Other>
constexpr value_list<Value..., Other...> operator+(value_list<Value...>, value_list<Other...>) {
return {};
}
constexpr value_list<Value..., Other...> operator+(value_list<Value...>, value_list<Other...>) { return {}; }
/*! @brief Primary template isn't defined on purpose. */
template<typename...>
struct value_list_cat;
/*! @brief Concatenates multiple value lists. */
template<>
struct value_list_cat<> {
@@ -487,6 +389,7 @@ struct value_list_cat<> {
using type = value_list<>;
};
/**
* @brief Concatenates multiple value lists.
* @tparam Value Values provided by the first value list.
@@ -499,6 +402,7 @@ struct value_list_cat<value_list<Value...>, value_list<Other...>, List...> {
using type = typename value_list_cat<value_list<Value..., Other...>, List...>::type;
};
/**
* @brief Concatenates multiple value lists.
* @tparam Value Values provided by the value list.
@@ -509,6 +413,7 @@ struct value_list_cat<value_list<Value...>> {
using type = value_list<Value...>;
};
/**
* @brief Helper type.
* @tparam List Value lists to concatenate.
@@ -516,90 +421,12 @@ struct value_list_cat<value_list<Value...>> {
template<typename... List>
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 = typename 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 = typename value_list_diff<List...>::type;
/*! @brief Same as std::is_invocable, but with tuples. */
template<typename, typename>
struct is_applicable: std::false_type {};
/**
* @copybrief is_applicable
* @tparam Func A valid function type.
@@ -609,6 +436,7 @@ struct is_applicable: std::false_type {};
template<typename Func, template<typename...> class Tuple, typename... Args>
struct is_applicable<Func, Tuple<Args...>>: std::is_invocable<Func, Args...> {};
/**
* @copybrief is_applicable
* @tparam Func A valid function type.
@@ -618,6 +446,7 @@ struct is_applicable<Func, Tuple<Args...>>: std::is_invocable<Func, Args...> {};
template<typename Func, template<typename...> class Tuple, typename... Args>
struct is_applicable<Func, const Tuple<Args...>>: std::is_invocable<Func, Args...> {};
/**
* @brief Helper variable template.
* @tparam Func A valid function type.
@@ -626,10 +455,12 @@ struct is_applicable<Func, const Tuple<Args...>>: std::is_invocable<Func, Args..
template<typename Func, typename Args>
inline constexpr bool is_applicable_v = is_applicable<Func, Args>::value;
/*! @brief Same as std::is_invocable_r, but with tuples for arguments. */
template<typename, typename, typename>
struct is_applicable_r: std::false_type {};
/**
* @copybrief is_applicable_r
* @tparam Ret The type to which the return type of the function should be
@@ -640,6 +471,7 @@ struct is_applicable_r: std::false_type {};
template<typename Ret, typename Func, typename... Args>
struct is_applicable_r<Ret, Func, std::tuple<Args...>>: std::is_invocable_r<Ret, Func, Args...> {};
/**
* @brief Helper variable template.
* @tparam Ret The type to which the return type of the function should be
@@ -650,6 +482,7 @@ struct is_applicable_r<Ret, Func, std::tuple<Args...>>: std::is_invocable_r<Ret,
template<typename Ret, typename Func, typename Args>
inline constexpr bool is_applicable_r_v = is_applicable_r<Ret, Func, Args>::value;
/**
* @brief Provides the member constant `value` to true if a given type is
* complete, false otherwise.
@@ -658,10 +491,12 @@ inline constexpr bool is_applicable_r_v = is_applicable_r<Ret, Func, Args>::valu
template<typename Type, typename = void>
struct is_complete: std::false_type {};
/*! @copydoc is_complete */
template<typename Type>
struct is_complete<Type, std::void_t<decltype(sizeof(Type))>>: std::true_type {};
/**
* @brief Helper variable template.
* @tparam Type The type to test.
@@ -669,6 +504,7 @@ struct is_complete<Type, std::void_t<decltype(sizeof(Type))>>: std::true_type {}
template<typename Type>
inline constexpr bool is_complete_v = is_complete<Type>::value;
/**
* @brief Provides the member constant `value` to true if a given type is an
* iterator, false otherwise.
@@ -677,22 +513,13 @@ inline constexpr bool is_complete_v = is_complete<Type>::value;
template<typename Type, typename = void>
struct is_iterator: std::false_type {};
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename, typename = void>
struct has_iterator_category: std::false_type {};
template<typename Type>
struct has_iterator_category<Type, std::void_t<typename std::iterator_traits<Type>::iterator_category>>: std::true_type {};
} // namespace internal
/*! @endcond */
/*! @copydoc is_iterator */
template<typename Type>
struct is_iterator<Type, std::enable_if_t<!std::is_void_v<std::remove_const_t<std::remove_pointer_t<Type>>>>>
: internal::has_iterator_category<Type> {};
struct is_iterator<Type, std::void_t<typename std::iterator_traits<Type>::iterator_category>>
: std::true_type
{};
/**
* @brief Helper variable template.
@@ -701,119 +528,104 @@ struct is_iterator<Type, std::enable_if_t<!std::is_void_v<std::remove_const_t<st
template<typename Type>
inline constexpr bool is_iterator_v = is_iterator<Type>::value;
/**
* @brief Provides the member constant `value` to true if a given type is both
* an empty and non-final class, false otherwise.
* @tparam Type The type to test
* @brief Provides the member constant `value` to true if a given type is of the
* required iterator type, false otherwise.
* @tparam Type The type to test.
* @tparam It Required iterator type.
*/
template<typename Type>
struct is_ebco_eligible
: std::bool_constant<std::is_empty_v<Type> && !std::is_final_v<Type>> {};
template<typename Type, typename It, typename = void>
struct is_iterator_type: std::false_type {};
/*! @copydoc is_iterator_type */
template<typename Type, typename It>
struct is_iterator_type<Type, It, std::enable_if_t<is_iterator_v<Type> && std::is_same_v<Type, It>>>
: std::true_type
{};
/*! @copydoc is_iterator_type */
template<typename Type, typename It>
struct is_iterator_type<Type, It, std::enable_if_t<!std::is_same_v<Type, It>, std::void_t<typename It::iterator_type>>>
: is_iterator_type<Type, typename It::iterator_type>
{};
/**
* @brief Helper variable template.
* @tparam Type The type to test.
* @tparam It Required iterator type.
*/
template<typename Type>
inline constexpr bool is_ebco_eligible_v = is_ebco_eligible<Type>::value;
template<typename Type, typename It>
inline constexpr bool is_iterator_type_v = is_iterator_type<Type, It>::value;
/**
* @brief Provides the member constant `value` to true if `Type::is_transparent`
* is valid and denotes a type, false otherwise.
* @tparam Type The type to test.
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
template<typename Type, typename = void>
struct is_transparent: std::false_type {};
/*! @copydoc is_transparent */
template<typename Type>
struct is_transparent<Type, std::void_t<typename Type::is_transparent>>: std::true_type {};
/**
* @brief Helper variable template.
* @tparam Type The type to test.
*/
template<typename Type>
inline constexpr bool is_transparent_v = is_transparent<Type>::value;
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename, typename = void>
struct has_tuple_size_value: std::false_type {};
template<typename Type>
struct has_tuple_size_value<Type, std::void_t<decltype(std::tuple_size<const Type>::value)>>: std::true_type {};
template<typename, typename = void>
struct has_value_type: std::false_type {};
template<typename Type>
struct has_value_type<Type, std::void_t<typename Type::value_type>>: std::true_type {};
template<typename>
[[nodiscard]] constexpr bool dispatch_is_equality_comparable();
[[nodiscard]] constexpr bool is_equality_comparable(...) { return false; }
template<typename Type, std::size_t... Index>
[[nodiscard]] constexpr bool unpack_maybe_equality_comparable(std::index_sequence<Index...>) {
return (dispatch_is_equality_comparable<std::tuple_element_t<Index, Type>>() && ...);
}
template<typename>
[[nodiscard]] constexpr bool maybe_equality_comparable(char) {
return false;
}
template<typename Type>
[[nodiscard]] constexpr auto maybe_equality_comparable(int) -> decltype(std::declval<Type>() == std::declval<Type>()) {
return true;
}
[[nodiscard]] constexpr auto is_equality_comparable(choice_t<0>)
-> decltype(std::declval<Type>() == std::declval<Type>()) { return true; }
template<typename Type>
[[nodiscard]] constexpr 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;
}
[[nodiscard]] constexpr auto is_equality_comparable(choice_t<1>)
-> decltype(std::declval<typename Type::value_type>(), std::declval<Type>() == std::declval<Type>()) {
if constexpr(is_iterator_v<Type>) {
return true;
} else if constexpr(std::is_same_v<typename Type::value_type, Type>) {
return is_equality_comparable<Type>(choice<0>);
} else {
return maybe_equality_comparable<Type>(0);
return is_equality_comparable<typename Type::value_type>(choice<2>);
}
// NOLINTEND(modernize-use-transparent-functors)
}
} // namespace internal
/*! @endcond */
template<typename Type>
[[nodiscard]] constexpr auto is_equality_comparable(choice_t<2>)
-> decltype(std::declval<typename Type::mapped_type>(), std::declval<Type>() == std::declval<Type>()) {
return is_equality_comparable<typename Type::key_type>(choice<2>) && is_equality_comparable<typename Type::mapped_type>(choice<2>);
}
}
/**
* Internal details not to be documented.
* @endcond
*/
/**
* @brief Provides the member constant `value` to true if a given type is
* equality comparable, false otherwise.
* @tparam Type The type to test.
*/
template<typename Type>
struct is_equality_comparable: std::bool_constant<internal::dispatch_is_equality_comparable<Type>()> {};
template<typename Type, typename = void>
struct is_equality_comparable: std::bool_constant<internal::is_equality_comparable<Type>(choice<2>)> {};
/*! @copydoc is_equality_comparable */
template<typename Type>
struct is_equality_comparable<const Type>: is_equality_comparable<Type> {};
/**
* @brief Helper variable template.
* @tparam Type The type to test.
*/
template<typename Type>
template<class Type>
inline constexpr bool is_equality_comparable_v = is_equality_comparable<Type>::value;
/**
* @brief Transcribes the constness of a type to another type.
* @tparam To The type to which to transcribe the constness.
@@ -825,13 +637,15 @@ struct constness_as {
using type = std::remove_const_t<To>;
};
/*! @copydoc constness_as */
template<typename To, typename From>
struct constness_as<To, const From> {
/*! @brief The type resulting from the transcription of the constness. */
using type = const To;
using type = std::add_const_t<To>;
};
/**
* @brief Alias template to facilitate the transcription of the constness.
* @tparam To The type to which to transcribe the constness.
@@ -840,6 +654,7 @@ struct constness_as<To, const From> {
template<typename To, typename From>
using constness_as_t = typename constness_as<To, From>::type;
/**
* @brief Extracts the class of a non-static member object or function.
* @tparam Member A pointer to a non-static member object or function.
@@ -849,19 +664,20 @@ class member_class {
static_assert(std::is_member_pointer_v<Member>, "Invalid pointer type to non-static member object or function");
template<typename Class, typename Ret, typename... Args>
static Class *clazz(Ret (Class::*)(Args...));
static Class * clazz(Ret(Class:: *)(Args...));
template<typename Class, typename Ret, typename... Args>
static Class *clazz(Ret (Class::*)(Args...) const);
static Class * clazz(Ret(Class:: *)(Args...) const);
template<typename Class, typename Type>
static Class *clazz(Type Class::*);
static Class * clazz(Type Class:: *);
public:
/*! @brief The class of the given non-static member object or function. */
using type = std::remove_pointer_t<decltype(clazz(std::declval<Member>()))>;
};
/**
* @brief Helper type.
* @tparam Member A pointer to a non-static member object or function.
@@ -869,53 +685,8 @@ public:
template<typename Member>
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 constexpr type_list<Args...> pick_up(Ret (*)(Args...));
template<typename Ret, typename Class, typename... Args>
static constexpr type_list<Args...> pick_up(Ret (Class ::*)(Args...));
}
template<typename Ret, typename Class, typename... Args>
static constexpr type_list<Args...> pick_up(Ret (Class ::*)(Args...) const);
template<typename Type, typename Class>
static constexpr type_list<Type> pick_up(Type Class ::*);
template<typename Type>
static constexpr 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 = typename nth_argument<Index, Candidate>::type;
} // 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

View File

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

View File

@@ -1,59 +1,34 @@
#ifndef ENTT_ENTITY_COMPONENT_HPP
#define ENTT_ENTITY_COMPONENT_HPP
#include <cstddef>
#include <type_traits>
#include "../config/config.h"
#include "fwd.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename Type, typename = void>
struct in_place_delete: std::bool_constant<!(std::is_move_constructible_v<Type> && std::is_move_assignable_v<Type>)> {};
/*! @brief Commonly used default traits for all types. */
struct basic_component_traits {
/*! @brief Pointer stability, default is `std::false_type`. */
using in_place_delete = std::false_type;
/*! @brief Empty type optimization, default is `ENTT_IGNORE_IF_EMPTY`. */
using ignore_if_empty = ENTT_IGNORE_IF_EMPTY;
};
template<>
struct in_place_delete<void>: std::false_type {};
template<typename Type>
struct in_place_delete<Type, std::enable_if_t<Type::in_place_delete>>
: std::true_type {};
template<typename Type, typename = void>
struct page_size: std::integral_constant<std::size_t, !std::is_empty_v<ENTT_ETO_TYPE(Type)> * ENTT_PACKED_PAGE> {};
template<>
struct page_size<void>: std::integral_constant<std::size_t, 0u> {};
template<typename Type>
struct page_size<Type, std::void_t<decltype(Type::page_size)>>
: std::integral_constant<std::size_t, Type::page_size> {};
} // namespace internal
/*! @endcond */
/**
* @brief Common way to access various properties of components.
* @tparam Type Element type.
* @tparam Entity A valid entity type.
* @tparam Type Type of component.
*/
template<typename Type, typename Entity, typename>
struct component_traits {
template<typename Type, typename = void>
struct component_traits: basic_component_traits {
static_assert(std::is_same_v<std::decay_t<Type>, Type>, "Unsupported type");
/*! @brief Element type. */
using element_type = Type;
/*! @brief Underlying entity identifier. */
using entity_type = Entity;
/*! @brief Pointer stability, default is `false`. */
static constexpr bool in_place_delete = internal::in_place_delete<Type>::value;
/*! @brief Page size, default is `ENTT_PACKED_PAGE` for non-empty types. */
static constexpr std::size_t page_size = internal::page_size<Type>::value;
};
} // namespace entt
}
#endif

View File

@@ -1,88 +1,98 @@
#ifndef ENTT_ENTITY_ENTITY_HPP
#define ENTT_ENTITY_ENTITY_HPP
#include <cstddef>
#include <cstdint>
#include <type_traits>
#include "../config/config.h"
#include "../core/bit.hpp"
#include "fwd.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
namespace internal {
template<typename, typename = void>
struct entt_traits;
template<typename Type>
struct entt_traits<Type, std::enable_if_t<std::is_enum_v<Type>>>
: entt_traits<std::underlying_type_t<Type>> {
using value_type = Type;
};
: entt_traits<std::underlying_type_t<Type>>
{};
template<typename Type>
struct entt_traits<Type, std::enable_if_t<std::is_class_v<Type>>>
: entt_traits<typename Type::entity_type> {
using value_type = Type;
};
: entt_traits<typename Type::entity_type>
{};
template<>
struct entt_traits<std::uint32_t> {
using value_type = std::uint32_t;
using entity_type = std::uint32_t;
using version_type = std::uint16_t;
using difference_type = std::int64_t;
static constexpr entity_type entity_mask = 0xFFFFF;
static constexpr entity_type version_mask = 0xFFF;
static constexpr std::size_t entity_shift = 20u;
};
template<>
struct entt_traits<std::uint64_t> {
using value_type = std::uint64_t;
using entity_type = std::uint64_t;
using version_type = std::uint32_t;
using difference_type = std::int64_t;
static constexpr entity_type entity_mask = 0xFFFFFFFF;
static constexpr entity_type version_mask = 0xFFFFFFFF;
static constexpr std::size_t entity_shift = 32u;
};
} // namespace internal
/*! @endcond */
}
/**
* @brief Common basic entity traits implementation.
* @tparam Traits Actual entity traits to use.
* Internal details not to be documented.
* @endcond
*/
template<typename Traits>
class basic_entt_traits {
static constexpr auto length = 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");
/**
* @brief Entity traits.
* @tparam Type Type of identifier.
*/
template<typename Type>
class entt_traits: private internal::entt_traits<Type> {
using traits_type = internal::entt_traits<Type>;
public:
/*! @brief Value type. */
using value_type = typename Traits::value_type;
using value_type = Type;
/*! @brief Underlying entity type. */
using entity_type = typename Traits::entity_type;
using entity_type = typename traits_type::entity_type;
/*! @brief Underlying version type. */
using version_type = typename Traits::version_type;
/*! @brief Entity mask size. */
static constexpr entity_type entity_mask = Traits::entity_mask;
/*! @brief Version mask size */
static constexpr entity_type version_mask = Traits::version_mask;
using version_type = typename traits_type::version_type;
/*! @brief Difference type. */
using difference_type = typename traits_type::difference_type;
/**
* @brief Converts an entity to its underlying type.
* @param value The value to convert.
* @return The integral representation of the given value.
*/
[[nodiscard]] static constexpr entity_type to_integral(const value_type value) noexcept {
[[nodiscard]] static constexpr entity_type to_integral(const value_type value) ENTT_NOEXCEPT {
return static_cast<entity_type>(value);
}
@@ -91,8 +101,8 @@ public:
* @param value The value to convert.
* @return The integral representation of the entity part.
*/
[[nodiscard]] static constexpr entity_type to_entity(const value_type value) noexcept {
return (to_integral(value) & entity_mask);
[[nodiscard]] static constexpr entity_type to_entity(const value_type value) ENTT_NOEXCEPT {
return (to_integral(value) & traits_type::entity_mask);
}
/**
@@ -100,22 +110,9 @@ public:
* @param value The value to convert.
* @return The integral representation of the version part.
*/
[[nodiscard]] static constexpr version_type to_version(const value_type value) noexcept {
if constexpr(Traits::version_mask == 0u) {
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)));
[[nodiscard]] static constexpr version_type to_version(const value_type value) ENTT_NOEXCEPT {
constexpr auto mask = (traits_type::version_mask << traits_type::entity_shift);
return ((to_integral(value) & mask) >> traits_type::entity_shift);
}
/**
@@ -128,90 +125,34 @@ public:
* @param version The version part of the identifier.
* @return A properly constructed identifier.
*/
[[nodiscard]] static constexpr value_type construct(const entity_type entity, const version_type version) noexcept {
if constexpr(Traits::version_mask == 0u) {
return value_type{entity & entity_mask};
} else {
return value_type{(entity & entity_mask) | (static_cast<entity_type>(version & version_mask) << length)};
}
}
/**
* @brief Combines two identifiers in a single one.
*
* The returned identifier is a copy of the first element except for its
* version, which is taken from the second element.
*
* @param lhs The identifier from which to take the entity part.
* @param rhs The identifier from which to take the version part.
* @return A properly constructed identifier.
*/
[[nodiscard]] static constexpr value_type combine(const entity_type lhs, const entity_type rhs) noexcept {
if constexpr(Traits::version_mask == 0u) {
return value_type{lhs & entity_mask};
} else {
return value_type{(lhs & entity_mask) | (rhs & (version_mask << length))};
}
[[nodiscard]] static constexpr value_type construct(const entity_type entity = traits_type::entity_mask, const version_type version = traits_type::version_mask) ENTT_NOEXCEPT {
return value_type{(entity & traits_type::entity_mask) | (static_cast<entity_type>(version) << traits_type::entity_shift)};
}
};
/**
* @brief Entity traits.
* @tparam Type Type of identifier.
*/
template<typename 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.
* @param value The value to convert.
* @param entity The value to convert.
* @return The integral representation of the given value.
*/
template<typename Entity>
[[nodiscard]] constexpr typename entt_traits<Entity>::entity_type to_integral(const Entity value) noexcept {
return entt_traits<Entity>::to_integral(value);
[[nodiscard]] constexpr auto to_integral(const Entity entity) ENTT_NOEXCEPT {
return entt_traits<Entity>::to_integral(entity);
}
/**
* @brief Returns the entity part once converted to the underlying type.
* @tparam Entity The value type.
* @param value The value to convert.
* @return The integral representation of the entity part.
*/
template<typename Entity>
[[nodiscard]] constexpr typename entt_traits<Entity>::entity_type to_entity(const Entity value) noexcept {
return entt_traits<Entity>::to_entity(value);
}
/**
* @brief Returns the version part once converted to the underlying type.
* @tparam Entity The value type.
* @param value The value to convert.
* @return The integral representation of the version part.
*/
template<typename Entity>
[[nodiscard]] constexpr typename entt_traits<Entity>::version_type to_version(const Entity value) noexcept {
return entt_traits<Entity>::to_version(value);
}
/*! @brief Null object for all identifiers. */
/*! @brief Null object for all entity identifiers. */
struct null_t {
/**
* @brief Converts the null object to identifiers of any type.
* @tparam Entity Type of identifier.
* @tparam Entity Type of entity identifier.
* @return The null representation for the given type.
*/
template<typename Entity>
[[nodiscard]] constexpr operator Entity() const noexcept {
using traits_type = entt_traits<Entity>;
constexpr auto value = traits_type::construct(traits_type::entity_mask, traits_type::version_mask);
return value;
[[nodiscard]] constexpr operator Entity() const ENTT_NOEXCEPT {
return entt_traits<Entity>::construct();
}
/**
@@ -219,7 +160,7 @@ struct null_t {
* @param other A null object.
* @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;
}
@@ -228,70 +169,81 @@ struct null_t {
* @param other A null object.
* @return False 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 false;
}
/**
* @brief Compares a null object and an identifier of any type.
* @tparam Entity Type of identifier.
* @param entity Identifier with which to compare.
* @brief Compares a null object and an entity identifier of any type.
* @tparam Entity Type of entity identifier.
* @param entity Entity identifier with which to compare.
* @return False if the two elements differ, true otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept {
using traits_type = entt_traits<Entity>;
return traits_type::to_entity(entity) == traits_type::to_entity(*this);
[[nodiscard]] constexpr bool operator==(const Entity entity) const ENTT_NOEXCEPT {
return entt_traits<Entity>::to_entity(entity) == entt_traits<Entity>::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.
* @brief Compares a null object and an entity identifier of any type.
* @tparam Entity Type of entity identifier.
* @param entity Entity identifier with which to compare.
* @return True if the two elements differ, false otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator!=(const Entity entity) const noexcept {
[[nodiscard]] constexpr bool operator!=(const Entity entity) const ENTT_NOEXCEPT {
return !(entity == *this);
}
/**
* @brief Creates a null object from an entity identifier of any type.
* @tparam Entity Type of entity identifier.
* @param entity Entity identifier to turn into a null object.
* @return The null representation for the given identifier.
*/
template<typename Entity>
[[nodiscard]] constexpr Entity operator|(const Entity entity) const ENTT_NOEXCEPT {
return entt_traits<Entity>::construct(entt_traits<Entity>::to_entity(*this), entt_traits<Entity>::to_version(entity));
}
};
/**
* @brief Compares a null object and an identifier of any type.
* @tparam Entity Type of identifier.
* @param lhs Identifier with which to compare.
* @param rhs A null object yet to be converted.
* @brief Compares a null object and an entity identifier of any type.
* @tparam Entity Type of entity identifier.
* @param entity 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 lhs, const null_t rhs) noexcept {
return rhs.operator==(lhs);
[[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 lhs Identifier with which to compare.
* @param rhs A null object yet to be converted.
* @brief Compares a null object and an entity identifier of any type.
* @tparam Entity Type of entity identifier.
* @param entity 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 lhs, const null_t rhs) noexcept {
return !(rhs == lhs);
[[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 entity identifiers. */
struct tombstone_t {
/**
* @brief Converts the tombstone object to identifiers of any type.
* @tparam Entity Type of identifier.
* @tparam Entity Type of entity identifier.
* @return The tombstone representation for the given type.
*/
template<typename Entity>
[[nodiscard]] constexpr operator Entity() const noexcept {
using traits_type = entt_traits<Entity>;
constexpr auto value = traits_type::construct(traits_type::entity_mask, traits_type::version_mask);
return value;
[[nodiscard]] constexpr operator Entity() const ENTT_NOEXCEPT {
return entt_traits<Entity>::construct();
}
/**
@@ -299,7 +251,7 @@ struct tombstone_t {
* @param other A tombstone object.
* @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;
}
@@ -308,81 +260,92 @@ struct tombstone_t {
* @param other A tombstone object.
* @return False 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 false;
}
/**
* @brief Compares a tombstone object and an identifier of any type.
* @tparam Entity Type of identifier.
* @param entity Identifier with which to compare.
* @brief Compares a tombstone object and an entity identifier of any type.
* @tparam Entity Type of entity identifier.
* @param entity Entity identifier with which to compare.
* @return False if the two elements differ, true otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept {
using traits_type = entt_traits<Entity>;
if constexpr(traits_type::version_mask == 0u) {
return false;
} else {
return (traits_type::to_version(entity) == traits_type::to_version(*this));
}
[[nodiscard]] constexpr bool operator==(const Entity entity) const ENTT_NOEXCEPT {
return entt_traits<Entity>::to_version(entity) == entt_traits<Entity>::to_version(*this);
}
/**
* @brief Compares a tombstone object and an identifier of any type.
* @tparam Entity Type of identifier.
* @param entity Identifier with which to compare.
* @brief Compares a tombstone object and an entity identifier of any type.
* @tparam Entity Type of entity identifier.
* @param entity Entity identifier with which to compare.
* @return True if the two elements differ, false otherwise.
*/
template<typename Entity>
[[nodiscard]] constexpr bool operator!=(const Entity entity) const noexcept {
[[nodiscard]] constexpr bool operator!=(const Entity entity) const ENTT_NOEXCEPT {
return !(entity == *this);
}
/**
* @brief Creates a tombstone object from an entity identifier of any type.
* @tparam Entity Type of entity identifier.
* @param entity Entity identifier to turn into a tombstone object.
* @return The tombstone representation for the given identifier.
*/
template<typename Entity>
[[nodiscard]] constexpr Entity operator|(const Entity entity) const ENTT_NOEXCEPT {
return entt_traits<Entity>::construct(entt_traits<Entity>::to_entity(entity));
}
};
/**
* @brief Compares a tombstone object and an identifier of any type.
* @tparam Entity Type of identifier.
* @param lhs Identifier with which to compare.
* @param rhs A tombstone object yet to be converted.
* @brief Compares a tombstone object and an entity identifier of any type.
* @tparam Entity Type of entity identifier.
* @param entity 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 lhs, const tombstone_t rhs) noexcept {
return rhs.operator==(lhs);
[[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 lhs Identifier with which to compare.
* @param rhs A tombstone object yet to be converted.
* @brief Compares a tombstone object and an entity identifier of any type.
* @tparam Entity Type of entity identifier.
* @param entity 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 lhs, const tombstone_t rhs) noexcept {
return !(rhs == lhs);
[[nodiscard]] constexpr bool operator!=(const Entity entity, const tombstone_t other) ENTT_NOEXCEPT {
return !(other == entity);
}
/**
* @brief Compile-time constant for null entities.
*
* There exist implicit conversions from this variable to identifiers of any
* allowed type. Similarly, there exist comparison operators between the null
* entity and any other identifier.
* There exist implicit conversions from this variable to entity identifiers of
* any allowed type. Similarly, there exist comparision operators between the
* null entity and any other entity identifier.
*/
inline constexpr null_t null{};
/**
* @brief Compile-time constant for tombstone entities.
*
* There exist implicit conversions from this variable to identifiers of any
* allowed type. Similarly, there exist comparison operators between the
* tombstone entity and any other identifier.
* There exist implicit conversions from this variable to entity identifiers of
* any allowed type. Similarly, there exist comparision operators between the
* tombstone entity and any other entity identifier.
*/
inline constexpr tombstone_t tombstone{};
} // namespace entt
}
#endif

View File

@@ -1,290 +1,147 @@
#ifndef ENTT_ENTITY_FWD_HPP
#define ENTT_ENTITY_FWD_HPP
#include <cstdint>
#include <memory>
#include <type_traits>
#include "../config/config.h"
#include "../core/fwd.hpp"
#include "../core/type_traits.hpp"
namespace entt {
/*! @brief Default entity identifier. */
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<typename Type, typename Entity = entity, typename = void>
struct component_traits;
template<typename Entity = entity, typename = std::allocator<Entity>>
template<typename Entity, typename = std::allocator<Entity>>
class basic_sparse_set;
template<typename Type, typename = entity, typename = std::allocator<Type>, typename = void>
class basic_storage;
template<typename, typename>
class basic_sigh_mixin;
template<typename, typename Type, typename = std::allocator<Type>>
struct basic_storage;
template<typename, typename>
class basic_reactive_mixin;
template<typename Entity = entity, typename = std::allocator<Entity>>
template<typename>
class basic_registry;
template<typename, typename, typename = void>
class basic_view;
template<typename Type, typename = std::allocator<Type *>>
template<typename...>
struct basic_view;
template<typename>
class basic_runtime_view;
template<typename, typename, typename>
template<typename...>
class basic_group;
template<typename>
class basic_observer;
template<typename>
class basic_organizer;
template<typename, typename...>
class basic_handle;
struct basic_handle;
template<typename>
class basic_snapshot;
template<typename>
class basic_snapshot_loader;
template<typename>
class basic_continuous_loader;
/*! @brief Alias declaration for the most common use case. */
using sparse_set = basic_sparse_set<>;
/**
* @brief Alias declaration for the most common use case.
* @tparam Type Element type.
*/
template<typename Type>
using storage = basic_storage<Type>;
/*! @brief Default entity identifier. */
enum class entity: id_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. */
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.
* @tparam Args Other template parameters.
*/
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.
* @tparam Args Other template parameters.
*/
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. */
using snapshot = basic_snapshot<registry>;
/*! @brief Alias declaration for the most common use case. */
using snapshot_loader = basic_snapshot_loader<registry>;
/*! @brief Alias declaration for the most common use case. */
using continuous_loader = basic_continuous_loader<registry>;
/*! @brief Alias declaration for the most common use case. */
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 constexpr 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 constexpr 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 constexpr 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>, typename = void>
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 = typename storage_type<Args...>::type;
/**
* Type-to-storage conversion utility that preserves constness.
* @tparam Type Storage value type, eventually const.
* @tparam Entity A valid entity type.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Type, typename Entity = entity, typename Allocator = std::allocator<std::remove_const_t<Type>>>
struct storage_for {
/*! @brief Type-to-storage conversion result. */
using type = constness_as_t<storage_type_t<std::remove_const_t<Type>, Entity, Allocator>, Type>;
};
/**
* @brief Helper type.
* @tparam Args Arguments to forward.
*/
template<typename... Args>
using storage_for_t = typename storage_for<Args...>::type;
/**
* @brief Alias declaration for the most common use case.
* @tparam Get Types of storage iterated by the view.
* @tparam Exclude Types of storage used to filter the view.
* @tparam Args Other template parameters.
*/
template<typename Get, typename Exclude = exclude_t<>>
using view = basic_view<type_list_transform_t<Get, storage_for>, type_list_transform_t<Exclude, storage_for>>;
template<typename... Args>
using const_handle_view = basic_handle<const entity, Args...>;
/*! @brief Alias declaration for the most common use case. */
using snapshot = basic_snapshot<entity>;
/*! @brief Alias declaration for the most common use case. */
using snapshot_loader = basic_snapshot_loader<entity>;
/*! @brief Alias declaration for the most common use case. */
using continuous_loader = basic_continuous_loader<entity>;
/**
* @brief Alias declaration for the most common use case.
* @tparam 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.
* @tparam Args Other template parameters.
*/
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>>;
template<typename... Args>
using view = basic_view<entity, Args...>;
/*! @brief Alias declaration for the most common use case. */
using runtime_view = basic_runtime_view<entity>;
/**
* @brief Alias declaration for the most common use case.
* @tparam Args Other template parameters.
*/
template<typename... Args>
using group = basic_group<entity, Args...>;
}
} // namespace entt
#endif

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,256 +1,168 @@
#ifndef ENTT_ENTITY_HELPER_HPP
#define ENTT_ENTITY_HELPER_HPP
#include <memory>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "../core/fwd.hpp"
#include "../core/type_traits.hpp"
#include "component.hpp"
#include "../signal/delegate.hpp"
#include "registry.hpp"
#include "fwd.hpp"
#include "group.hpp"
#include "storage.hpp"
#include "view.hpp"
namespace entt {
/**
* @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>
class 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;
template<typename Entity>
struct as_view {
/*! @brief Underlying entity identifier. */
using entity_type = typename 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.
* @param source A valid reference to a registry.
*/
as_view(registry_type &source) noexcept
: reg{&source} {}
as_view(registry_type &source) ENTT_NOEXCEPT: reg{source} {}
/**
* @brief Conversion function from a registry to a view.
* @tparam Get Type of storage used to construct the view.
* @tparam Exclude Types of storage used to filter the view.
* @tparam Exclude Types of components used to filter the view.
* @tparam Component Type of components used to construct the view.
* @return A newly created view.
*/
template<typename Get, typename Exclude>
operator basic_view<Get, Exclude>() const {
return dispatch(Get{}, Exclude{});
template<typename Exclude, typename... Component>
operator basic_view<entity_type, Exclude, Component...>() const {
return reg.template view<Component...>(Exclude{});
}
private:
registry_type *reg;
registry_type &reg;
};
/**
* @brief Deduction guide.
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
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 Registry Basic registry type.
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
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;
template<typename Entity>
struct as_group {
/*! @brief Underlying entity identifier. */
using entity_type = typename 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.
* @param source A valid reference to a registry.
*/
as_group(registry_type &source) noexcept
: reg{&source} {}
as_group(registry_type &source) ENTT_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.
* @tparam Exclude Types of components used to filter the group.
* @tparam Get Types of components observed by the group.
* @tparam Owned Types of components owned by 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>
typename 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 typename basic_storage<Args...>::base_type &>(storage).rbegin() + static_cast<decltype(dist)>(pos) + dist);
template<typename Exclude, typename Get, typename... Owned>
operator basic_group<entity_type, Exclude, Get, Owned...>() 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{});
}
}
// 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;
registry_type &reg;
};
/**
* @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.
* @tparam Registry Basic registry type.
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Registry>
sigh_helper(Registry &) -> sigh_helper<Registry>;
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 view = reg.template view<const Component>();
const auto *addr = std::addressof(instance);
for(auto it = view.rbegin(), last = view.rend(); it < last; it += ENTT_PACKED_PAGE) {
if(const auto dist = (addr - std::addressof(view.template get<const Component>(*it))); dist >= 0 && dist < ENTT_PACKED_PAGE) {
return *(it + dist);
}
}
return entt::null;
}
}
} // namespace entt
#endif

View File

@@ -1,591 +0,0 @@
#ifndef ENTT_ENTITY_MIXIN_HPP
#define ENTT_ENTITY_MIXIN_HPP
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "../core/any.hpp"
#include "../core/type_info.hpp"
#include "../signal/sigh.hpp"
#include "entity.hpp"
#include "fwd.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename, typename, typename = void>
struct has_on_construct final: std::false_type {};
template<typename Type, typename Registry>
struct has_on_construct<Type, Registry, std::void_t<decltype(Type::on_construct(std::declval<Registry &>(), std::declval<Registry>().create()))>>
: std::true_type {};
template<typename, typename, typename = void>
struct has_on_update final: std::false_type {};
template<typename Type, typename Registry>
struct has_on_update<Type, Registry, std::void_t<decltype(Type::on_update(std::declval<Registry &>(), std::declval<Registry>().create()))>>
: std::true_type {};
template<typename, typename, typename = void>
struct has_on_destroy final: std::false_type {};
template<typename Type, typename Registry>
struct has_on_destroy<Type, Registry, std::void_t<decltype(Type::on_destroy(std::declval<Registry &>(), std::declval<Registry>().create()))>>
: 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 = typename 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<typename 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 typename 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 = typename underlying_type::allocator_type;
/*! @brief Underlying entity identifier. */
using entity_type = typename 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 mutable forward 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<typename 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 It Type of input iterator.
* @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 It, typename... Args>
void insert(It first, It 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 &, typename 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 = typename underlying_type::allocator_type;
/*! @brief Underlying entity identifier. */
using entity_type = typename 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,446 @@
#ifndef ENTT_ENTITY_OBSERVER_HPP
#define ENTT_ENTITY_OBSERVER_HPP
#include <limits>
#include <cstddef>
#include <cstdint>
#include <utility>
#include <type_traits>
#include "../config/config.h"
#include "../core/type_traits.hpp"
#include "../signal/delegate.hpp"
#include "registry.hpp"
#include "storage.hpp"
#include "utility.hpp"
#include "entity.hpp"
#include "fwd.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) {
if([&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(!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;
};
}
#endif

View File

@@ -1,109 +1,114 @@
#ifndef ENTT_ENTITY_ORGANIZER_HPP
#define ENTT_ENTITY_ORGANIZER_HPP
#include <cstddef>
#include <algorithm>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <vector>
#include "../core/type_info.hpp"
#include "../core/type_traits.hpp"
#include "../core/utility.hpp"
#include "../graph/adjacency_matrix.hpp"
#include "../graph/flow.hpp"
#include "fwd.hpp"
#include "helper.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
namespace internal {
template<typename>
struct is_view: std::false_type {};
template<typename... Args>
struct is_view<basic_view<Args...>>: std::true_type {};
template<typename Entity, typename... Exclude, typename... Component>
struct is_view<basic_view<Entity, exclude_t<Exclude...>, Component...>>: std::true_type {};
template<typename Type>
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>
struct unpack_type {
using ro = std::conditional_t<
type_list_contains_v<Override, const Type> || (std::is_const_v<Type> && !type_list_contains_v<Override, std::remove_const_t<Type>>),
type_list_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<>>;
type_list<>
>;
using rw = std::conditional_t<
type_list_contains_v<Override, std::remove_const_t<Type>> || (!std::is_const_v<Type> && !type_list_contains_v<Override, const Type>),
type_list_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_list<>
>;
};
template<typename... Args, typename... Override>
struct unpack_type<basic_registry<Args...>, type_list<Override...>> {
template<typename Entity, typename... Override>
struct unpack_type<basic_registry<Entity>, type_list<Override...>> {
using ro = type_list<>;
using rw = type_list<>;
};
template<typename... Args, typename... Override>
struct unpack_type<const basic_registry<Args...>, type_list<Override...>>
: unpack_type<basic_registry<Args...>, type_list<Override...>> {};
template<typename Entity, typename... Override>
struct unpack_type<const basic_registry<Entity>, type_list<Override...>>
: unpack_type<basic_registry<Entity>, type_list<Override...>>
{};
template<typename... Get, typename... Exclude, typename... Override>
struct unpack_type<basic_view<get_t<Get...>, exclude_t<Exclude...>>, type_list<Override...>> {
using ro = type_list_cat_t<type_list<typename Exclude::element_type...>, typename unpack_type<constness_as_t<typename Get::element_type, Get>, 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...>;
template<typename Entity, typename... Exclude, typename... Component, typename... Override>
struct unpack_type<basic_view<Entity, exclude_t<Exclude...>, Component...>, type_list<Override...>> {
using ro = type_list_cat_t<type_list<Exclude...>, typename unpack_type<Component, type_list<Override...>>::ro...>;
using rw = type_list_cat_t<typename unpack_type<Component, type_list<Override...>>::rw...>;
};
template<typename... Get, typename... Exclude, typename... Override>
struct unpack_type<const basic_view<get_t<Get...>, exclude_t<Exclude...>>, type_list<Override...>>
: unpack_type<basic_view<get_t<Get...>, exclude_t<Exclude...>>, type_list<Override...>> {};
template<typename Entity, typename... Exclude, typename... Component, typename... Override>
struct unpack_type<const basic_view<Entity, exclude_t<Exclude...>, Component...>, type_list<Override...>>
: unpack_type<basic_view<Entity, exclude_t<Exclude...>, Component...>, type_list<Override...>>
{};
template<typename... Owned, typename... Get, typename... Exclude, typename... Override>
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>
struct resource;
template<typename, typename, typename>
struct resource_traits;
template<typename Registry, typename... Args, typename... Req>
struct resource_traits<Registry, type_list<Args...>, type_list<Req...>> {
template<typename... Args, typename... Req>
struct resource<type_list<Args...>, type_list<Req...>> {
using args = type_list<std::remove_const_t<Args>...>;
using ro = type_list_cat_t<typename unpack_type<Args, type_list<Req...>>::ro..., typename unpack_type<Req, type_list<>>::ro...>;
using rw = type_list_cat_t<typename unpack_type<Args, type_list<Req...>>::rw..., typename unpack_type<Req, type_list<>>::rw...>;
static constexpr auto sync_point = (std::is_same_v<Args, Registry> || ...);
};
template<typename Registry, 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...));
template<typename Registry, 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...));
template<typename... Req, typename Ret, typename... Args>
resource<type_list<std::remove_reference_t<Args>...>, type_list<Req...>> free_function_to_resource(Ret(*)(Args...));
template<typename Registry, 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...));
template<typename... Req, typename Ret, typename Type, typename... Args>
resource<type_list<std::remove_reference_t<Args>...>, type_list<Req...>> constrained_function_to_resource(Ret(*)(Type &, Args...));
template<typename Registry, 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);
template<typename... Req, typename Ret, typename Class, typename... Args>
resource<type_list<std::remove_reference_t<Args>...>, type_list<Req...>> constrained_function_to_resource(Ret(Class:: *)(Args...));
template<typename... Req, typename Ret, typename Class, typename... Args>
resource<type_list<std::remove_reference_t<Args>...>, type_list<Req...>> constrained_function_to_resource(Ret(Class:: *)(Args...) const);
template<typename... Req>
resource<type_list<>, type_list<Req...>> to_resource();
}
/**
* Internal details not to be documented.
* @endcond
*/
} // namespace internal
/*! @endcond */
/**
* @brief Utility class for creating a static task graph.
@@ -114,13 +119,13 @@ 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
* 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 {
using callback_type = void(const void *, Registry &);
using prepare_type = void(Registry &);
using dependency_type = std::size_t(const bool, const type_info **, const std::size_t);
using callback_type = void(const void *, entt::basic_registry<Entity> &);
using prepare_type = void(entt::basic_registry<Entity> &);
using dependency_type = std::size_t(const bool, type_info *, const std::size_t);
struct vertex_data final {
std::size_t ro_count{};
@@ -128,60 +133,119 @@ class basic_organizer final {
const char *name{};
const void *payload{};
callback_type *callback{};
dependency_type *dependency{};
dependency_type *dependency;
prepare_type *prepare{};
const type_info *info{};
type_info info{};
};
template<typename Type>
[[nodiscard]] static decltype(auto) extract(Registry &reg) {
if constexpr(std::is_same_v<Type, Registry>) {
[[nodiscard]] static decltype(auto) extract(basic_registry<Entity> &reg) {
if constexpr(std::is_same_v<Type, basic_registry<Entity>>) {
return reg;
} else if constexpr(internal::is_view_v<Type>) {
return static_cast<Type>(as_view{reg});
} else if constexpr(internal::is_group_v<Type>) {
return static_cast<Type>(as_group{reg});
return as_view{reg};
} else {
return reg.ctx().template emplace<std::remove_reference_t<Type>>();
return reg.template ctx_or_set<std::remove_reference_t<Type>>();
}
}
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)...);
}
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]] type_info *buffer, [[maybe_unused]] const std::size_t count) {
if constexpr(sizeof...(Type) == 0u) {
return {};
} else {
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
const type_info *info[]{&type_id<Type>()...};
const auto length = count < sizeof...(Type) ? count : sizeof...(Type);
for(std::size_t pos{}; pos < length; ++pos) {
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
buffer[pos] = info[pos];
}
type_info info[sizeof...(Type)]{type_id<Type>()...};
const auto length = (std::min)(count, sizeof...(Type));
std::copy_n(info, length, buffer);
return length;
}
}
template<typename... RO, typename... RW>
void track_dependencies(std::size_t index, const bool sync_point, type_list<RO...>, type_list<RW...>) {
builder.bind(static_cast<id_type>(index));
builder.set(type_hash<Registry>::value(), sync_point || (sizeof...(RO) + sizeof...(RW) == 0u));
(builder.ro(type_hash<RO>::value()), ...);
(builder.rw(type_hash<RW>::value()), ...);
void track_dependencies(std::size_t index, const bool requires_registry, type_list<RO...>, type_list<RW...>) {
dependencies[type_hash<basic_registry<Entity>>::value()].emplace_back(index, requires_registry || (sizeof...(RO) + sizeof...(RW) == 0u));
(dependencies[type_hash<RO>::value()].emplace_back(index, false), ...);
(dependencies[type_hash<RW>::value()].emplace_back(index, true), ...);
}
[[nodiscard]] std::vector<bool> adjacency_matrix() {
const auto length = vertices.size();
std::vector<bool> edges(length * length, false);
// creates the ajacency 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:
/*! Basic registry type. */
using registry_type = Registry;
/*! @brief Underlying entity identifier. */
using entity_type = typename registry_type::entity_type;
using entity_type = Entity;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Raw task function type. */
@@ -191,14 +255,15 @@ public:
struct vertex {
/**
* @brief Constructs a vertex of the task graph.
* @param vtype True if the vertex is a top-level one, false otherwise.
* @param data The data associated with the vertex.
* @param from List of in-edges of the vertex.
* @param to List of out-edges of the vertex.
* @param edges The indices of the children in the adjacency list.
*/
vertex(vertex_data data, std::vector<std::size_t> from, std::vector<std::size_t> to)
: node{std::move(data)},
in{std::move(from)},
out{std::move(to)} {}
vertex(const bool vtype, vertex_data data, std::vector<std::size_t> edges)
: is_top_level{vtype},
node{std::move(data)},
reachable{std::move(edges)}
{}
/**
* @brief Fills a buffer with the type info objects for the writable
@@ -207,7 +272,7 @@ public:
* @param length The length of the user-supplied 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(type_info *buffer, const std::size_t length) const ENTT_NOEXCEPT {
return node.dependency(false, buffer, length);
}
@@ -218,7 +283,7 @@ public:
* @param length The length of the user-supplied 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(type_info *buffer, const std::size_t length) const ENTT_NOEXCEPT {
return node.dependency(true, buffer, length);
}
@@ -226,7 +291,7 @@ public:
* @brief Returns the number of read-only resources of a 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;
}
@@ -234,7 +299,7 @@ public:
* @brief Returns the number of writable resources of a 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;
}
@@ -242,23 +307,23 @@ public:
* @brief Checks if a vertex is also a top-level one.
* @return True if the vertex is a top-level one, false otherwise.
*/
[[nodiscard]] bool top_level() const noexcept {
return in.empty();
bool top_level() const ENTT_NOEXCEPT {
return is_top_level;
}
/**
* @brief Returns a type info object associated with a vertex.
* @return A properly initialized type info object.
*/
[[nodiscard]] const type_info &info() const noexcept {
return *node.info;
type_info info() const ENTT_NOEXCEPT {
return node.info;
}
/**
* @brief Returns a user defined name associated with a vertex, if any.
* @return The user defined name associated with the vertex, if any.
*/
[[nodiscard]] const char *name() const noexcept {
const char * name() const ENTT_NOEXCEPT {
return node.name;
}
@@ -266,7 +331,7 @@ public:
* @brief Returns the function associated with a vertex.
* @return The function associated with the vertex.
*/
[[nodiscard]] function_type *callback() const noexcept {
function_type * callback() const ENTT_NOEXCEPT {
return node.callback;
}
@@ -274,24 +339,16 @@ public:
* @brief Returns the payload associated with a 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;
}
/**
* @brief Returns the list of in-edges of a vertex.
* @return The list of in-edges of a vertex.
* @brief Returns the list of nodes reachable from a given vertex.
* @return The list of nodes reachable from the vertex.
*/
[[nodiscard]] const std::vector<std::size_t> &in_edges() const noexcept {
return in;
}
/**
* @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;
const std::vector<std::size_t> & children() const ENTT_NOEXCEPT {
return reachable;
}
/**
@@ -299,14 +356,14 @@ public:
* are properly instantiated before using them.
* @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();
}
private:
bool is_top_level;
vertex_data node;
std::vector<std::size_t> in;
std::vector<std::size_t> out;
std::vector<std::size_t> reachable;
};
/**
@@ -317,24 +374,25 @@ public:
*/
template<auto Candidate, typename... Req>
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<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{}));
};
vertex_data vdata{
track_dependencies(vertices.size(), requires_registry, typename resource_type::ro{}, typename resource_type::rw{});
vertices.push_back({
resource_type::ro::size,
resource_type::rw::size,
name,
nullptr,
callback,
+[](const bool rw, const type_info **buffer, const std::size_t length) { return rw ? fill_dependencies(typename resource_type::rw{}, buffer, length) : fill_dependencies(typename resource_type::ro{}, buffer, length); },
+[](registry_type &reg) { void(to_args(reg, typename resource_type::args{})); },
&type_id<std::integral_constant<decltype(Candidate), Candidate>>()};
track_dependencies(vertices.size(), resource_type::sync_point, typename resource_type::ro{}, typename resource_type::rw{});
vertices.push_back(std::move(vdata));
+[](const bool rw, type_info *buffer, const std::size_t length) { return rw ? fill_dependencies(typename resource_type::rw{}, buffer, length) : fill_dependencies(typename resource_type::ro{}, buffer, length); },
+[](basic_registry<entity_type> &reg) { void(to_args(reg, typename resource_type::args{})); },
type_id<std::integral_constant<decltype(Candidate), Candidate>>()
});
}
/**
@@ -348,25 +406,30 @@ public:
*/
template<auto Candidate, typename... Req, typename Type>
void emplace(Type &value_or_instance, const char *name = nullptr) {
using resource_type = decltype(internal::constrained_function_to_resource_traits<registry_type, Req...>(Candidate));
using resource_type = decltype(internal::constrained_function_to_resource<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));
std::apply(Candidate, std::tuple_cat(std::forward_as_tuple(*curr), to_args(reg, typename resource_type::args{})));
};
vertex_data vdata{
track_dependencies(vertices.size(), requires_registry, typename resource_type::ro{}, typename resource_type::rw{});
vertices.push_back({
resource_type::ro::size,
resource_type::rw::size,
name,
&value_or_instance,
callback,
+[](const bool rw, const type_info **buffer, const std::size_t length) { return rw ? fill_dependencies(typename resource_type::rw{}, buffer, length) : fill_dependencies(typename resource_type::ro{}, buffer, length); },
+[](registry_type &reg) { void(to_args(reg, typename resource_type::args{})); },
&type_id<std::integral_constant<decltype(Candidate), Candidate>>()};
track_dependencies(vertices.size(), resource_type::sync_point, typename resource_type::ro{}, typename resource_type::rw{});
vertices.push_back(std::move(vdata));
+[](const bool rw, type_info *buffer, const std::size_t length) {
return rw ? fill_dependencies(typename resource_type::rw{}, buffer, length) : fill_dependencies(typename resource_type::ro{}, buffer, length);
},
+[](basic_registry<entity_type> &reg) {
void(to_args(reg, typename resource_type::args{}));
},
type_id<std::integral_constant<decltype(Candidate), Candidate>>()
});
}
/**
@@ -379,44 +442,50 @@ public:
*/
template<typename... Req>
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<type_list<>, type_list<Req...>>;
track_dependencies(vertices.size(), true, typename resource_type::ro{}, typename resource_type::rw{});
vertex_data vdata{
vertices.push_back({
resource_type::ro::size,
resource_type::rw::size,
name,
payload,
func,
+[](const bool rw, const type_info **buffer, const std::size_t length) { return rw ? fill_dependencies(typename resource_type::rw{}, buffer, length) : fill_dependencies(typename resource_type::ro{}, buffer, length); },
+[](const bool rw, type_info *buffer, const std::size_t length) {
return rw ? fill_dependencies(typename resource_type::rw{}, buffer, length) : fill_dependencies(typename resource_type::ro{}, buffer, length);
},
nullptr,
&type_id<void>()};
vertices.push_back(std::move(vdata));
type_info{}
});
}
/**
* @brief Generates a task graph for the current content.
* @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{};
adjacency_list.reserve(vertices.size());
auto adjacency_matrix = builder.graph();
for(auto curr: adjacency_matrix.vertices()) {
std::vector<std::size_t> in{};
std::vector<std::size_t> out{};
for(std::size_t col{}, length = vertices.size(); col < length; ++col) {
std::vector<std::size_t> reachable{};
const auto row = col * length;
bool is_top_level = true;
for(auto &&edge: adjacency_matrix.in_edges(curr)) {
in.push_back(edge.first);
for(std::size_t next{}; next < length; ++next) {
if(edges[row + next]) {
reachable.push_back(next);
}
}
for(auto &&edge: adjacency_matrix.out_edges(curr)) {
out.push_back(edge.second);
for(std::size_t next{}; next < length && is_top_level; ++next) {
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;
@@ -424,15 +493,17 @@ public:
/*! @brief Erases all elements from a container. */
void clear() {
builder.clear();
dependencies.clear();
vertices.clear();
}
private:
std::unordered_map<entt::id_type, std::vector<std::pair<std::size_t, bool>>> dependencies;
std::vector<vertex_data> vertices;
flow builder;
};
} // namespace entt
}
#endif

View File

@@ -0,0 +1,60 @@
#ifndef ENTT_ENTITY_POLY_STORAGE_HPP
#define ENTT_ENTITY_POLY_STORAGE_HPP
#include <cstddef>
#include <tuple>
#include "../core/type_info.hpp"
#include "../core/type_traits.hpp"
#include "../poly/poly.hpp"
#include "fwd.hpp"
namespace entt {
/**
* @brief Basic poly storage implementation.
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
struct Storage: type_list<type_info() const ENTT_NOEXCEPT> {
/**
* @brief Concept definition.
* @tparam Base Opaque base class from which to inherit.
*/
template<typename Base>
struct type: Base {
/**
* @brief Returns a type info for the contained objects.
* @return The type info for the contained objects.
*/
type_info value_type() const ENTT_NOEXCEPT {
return poly_call<0>(*this);
}
};
/**
* @brief Concept implementation.
* @tparam Type Type for which to generate an implementation.
*/
template<typename Type>
using impl = value_list<&type_id<typename Type::value_type>>;
};
/**
* @brief Defines the poly storage type associate with a given entity type.
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity, typename = void>
struct poly_storage_traits {
/*! @brief Poly storage type for the given entity type. */
using storage_type = poly<Storage<Entity>>;
};
}
#endif

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

View File

@@ -1,248 +1,167 @@
#ifndef ENTT_ENTITY_RUNTIME_VIEW_HPP
#define ENTT_ENTITY_RUNTIME_VIEW_HPP
#include <algorithm>
#include <cstddef>
#include <iterator>
#include <utility>
#include <vector>
#include <utility>
#include <algorithm>
#include <type_traits>
#include "../config/config.h"
#include "entity.hpp"
#include "sparse_set.hpp"
#include "fwd.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename Set>
class runtime_view_iterator final {
using iterator_type = typename Set::iterator;
using iterator_traits = std::iterator_traits<iterator_type>;
[[nodiscard]] bool valid() const {
return (!tombstone_check || *it != tombstone)
&& std::all_of(++pools->begin(), pools->end(), [entt = *it](const auto *curr) { return curr->contains(entt); })
&& std::none_of(filter->cbegin(), filter->cend(), [entt = *it](const auto *curr) { return curr && curr->contains(entt); });
}
public:
using value_type = typename iterator_traits::value_type;
using pointer = typename iterator_traits::pointer;
using reference = typename iterator_traits::reference;
using difference_type = typename iterator_traits::difference_type;
using iterator_category = std::bidirectional_iterator_tag;
constexpr runtime_view_iterator() noexcept
: pools{},
filter{},
it{},
tombstone_check{} {}
runtime_view_iterator(const std::vector<Set *> &cpools, iterator_type curr, const std::vector<Set *> &ignore) noexcept
: pools{&cpools},
filter{&ignore},
it{curr},
tombstone_check{pools->size() == 1u && (*pools)[0u]->policy() == deletion_policy::in_place} {
if(it != (*pools)[0]->end() && !valid()) {
++(*this);
}
}
runtime_view_iterator &operator++() {
++it;
for(const auto last = (*pools)[0]->end(); it != last && !valid(); ++it) {}
return *this;
}
runtime_view_iterator operator++(int) {
const runtime_view_iterator orig = *this;
return ++(*this), orig;
}
runtime_view_iterator &operator--() {
--it;
for(const auto first = (*pools)[0]->begin(); it != first && !valid(); --it) {}
return *this;
}
runtime_view_iterator operator--(int) {
const runtime_view_iterator orig = *this;
return operator--(), orig;
}
[[nodiscard]] pointer operator->() const noexcept {
return it.operator->();
}
[[nodiscard]] reference operator*() const noexcept {
return *operator->();
}
[[nodiscard]] constexpr bool operator==(const runtime_view_iterator &other) const noexcept {
return it == other.it;
}
[[nodiscard]] constexpr bool operator!=(const runtime_view_iterator &other) const noexcept {
return !(*this == other);
}
private:
const std::vector<Set *> *pools;
const std::vector<Set *> *filter;
iterator_type it;
bool tombstone_check;
};
} // namespace internal
/*! @endcond */
/**
* @brief Generic runtime view.
* @brief Runtime view.
*
* Runtime views iterate over those entities that are at least in the given
* storage. During initialization, a runtime view looks at the number of
* entities available for each element and uses the smallest set in order to get
* a performance boost when iterating.
* Runtime views iterate over those entities that have at least all the given
* components in their bags. During initialization, a runtime view looks at the
* number of entities available for each component and picks up a reference to
* the smallest set of candidate entities in order to get a performance boost
* when iterate.<br/>
* Order of elements during iterations are highly dependent on the order of the
* underlying data structures. See sparse_set and its specializations for more
* details.
*
* @b Important
*
* Iterators aren't invalidated if:
*
* * New elements are added to the storage.
* * The entity currently pointed is modified (for example, elements are added
* or removed from it).
* * 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 other cases, modifying the storage iterated by the view in any way
* invalidates all the iterators.
* 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.
*
* @tparam Type Common base type.
* @tparam Allocator Type of allocator used to manage memory and elements.
* @note
* Views share references to the underlying data structures of the registry that
* generated them. Therefore any change to the entities and to the components
* made by means of the registry are immediately reflected by the views, unless
* a pool was missing when the view was built (in this case, the view won't
* have a valid reference and won't be updated accordingly).
*
* @warning
* Lifetime of a view must not overcome that of the registry that generated it.
* In any other case, attempting to use a view results in undefined behavior.
*
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Type, typename Allocator>
class basic_runtime_view {
using alloc_traits = std::allocator_traits<Allocator>;
static_assert(std::is_same_v<typename alloc_traits::value_type, Type *>, "Invalid value type");
using container_type = std::vector<Type *, Allocator>;
template<typename Entity>
class basic_runtime_view final {
using basic_common_type = basic_sparse_set<Entity>;
using underlying_iterator = typename basic_common_type::iterator;
[[nodiscard]] auto offset() const noexcept {
ENTT_ASSERT(!pools.empty(), "Invalid view");
const auto &leading = *pools.front();
return (leading.policy() == deletion_policy::swap_only) ? leading.free_list() : leading.size();
class view_iterator final {
[[nodiscard]] bool valid() const {
const auto entt = *it;
return (!stable_storage || (entt != tombstone))
&& std::all_of(pools->begin()++, pools->end(), [entt](const auto *curr) { return curr->contains(entt); })
&& std::none_of(filter->cbegin(), filter->cend(), [entt](const auto *curr) { return curr && curr->contains(entt); });
}
public:
using difference_type = typename underlying_iterator::difference_type;
using value_type = typename underlying_iterator::value_type;
using pointer = typename underlying_iterator::pointer;
using reference = typename underlying_iterator::reference;
using iterator_category = std::bidirectional_iterator_tag;
view_iterator() ENTT_NOEXCEPT = default;
view_iterator(const std::vector<const basic_common_type *> &cpools, const std::vector<const basic_common_type *> &ignore, underlying_iterator curr) ENTT_NOEXCEPT
: pools{&cpools},
filter{&ignore},
it{curr},
stable_storage{std::any_of(pools->cbegin(), pools->cend(), [](const basic_common_type *cpool) { return (cpool->policy() == deletion_policy::in_place); })}
{
if(it != (*pools)[0]->end() && !valid()) {
++(*this);
}
}
view_iterator & operator++() {
while(++it != (*pools)[0]->end() && !valid());
return *this;
}
view_iterator operator++(int) {
view_iterator orig = *this;
return ++(*this), orig;
}
view_iterator & operator--() ENTT_NOEXCEPT {
while(--it != (*pools)[0]->begin() && !valid());
return *this;
}
view_iterator operator--(int) ENTT_NOEXCEPT {
view_iterator orig = *this;
return operator--(), orig;
}
[[nodiscard]] bool operator==(const view_iterator &other) const ENTT_NOEXCEPT {
return other.it == it;
}
[[nodiscard]] bool operator!=(const view_iterator &other) const ENTT_NOEXCEPT {
return !(*this == other);
}
[[nodiscard]] pointer operator->() const {
return it.operator->();
}
[[nodiscard]] reference operator*() const {
return *operator->();
}
private:
const std::vector<const basic_common_type *> *pools;
const std::vector<const basic_common_type *> *filter;
underlying_iterator it;
bool stable_storage;
};
[[nodiscard]] bool valid() const {
return !pools.empty() && pools.front();
}
public:
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Underlying entity identifier. */
using entity_type = typename Type::entity_type;
using entity_type = Entity;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Signed integer type. */
using difference_type = std::ptrdiff_t;
/*! @brief Common type among all storage types. */
using common_type = Type;
/*! @brief Bidirectional iterator type. */
using iterator = internal::runtime_view_iterator<common_type>;
using iterator = view_iterator;
/*! @brief Default constructor to use to create empty, invalid views. */
basic_runtime_view() noexcept
: basic_runtime_view{allocator_type{}} {}
basic_runtime_view() ENTT_NOEXCEPT
: pools{},
filter{}
{}
/**
* @brief Constructs an empty, invalid view with a given allocator.
* @param allocator The allocator to use.
* @brief Constructs a runtime view from a set of storage classes.
* @param cpools The storage for the types to iterate.
* @param epools The storage for the types used to filter the view.
*/
explicit basic_runtime_view(const allocator_type &allocator)
: pools{allocator},
filter{allocator} {}
/*! @brief Default copy constructor. */
basic_runtime_view(const basic_runtime_view &) = default;
/**
* @brief Allocator-extended copy constructor.
* @param other The instance to copy from.
* @param allocator The allocator to use.
*/
basic_runtime_view(const basic_runtime_view &other, const allocator_type &allocator)
: pools{other.pools, allocator},
filter{other.filter, allocator} {}
/*! @brief Default move constructor. */
basic_runtime_view(basic_runtime_view &&) noexcept = default;
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
basic_runtime_view(basic_runtime_view &&other, const allocator_type &allocator)
: pools{std::move(other.pools), allocator},
filter{std::move(other.filter), allocator} {}
/*! @brief Default destructor. */
~basic_runtime_view() = default;
/**
* @brief Default copy assignment operator.
* @return This runtime view.
*/
basic_runtime_view &operator=(const basic_runtime_view &) = default;
/**
* @brief Default move assignment operator.
* @return This runtime view.
*/
basic_runtime_view &operator=(basic_runtime_view &&) noexcept = default;
/**
* @brief Exchanges the contents with those of a given view.
* @param other View to exchange the content with.
*/
void swap(basic_runtime_view &other) noexcept {
using std::swap;
swap(pools, other.pools);
swap(filter, other.filter);
}
/**
* @brief Returns the associated allocator.
* @return The associated allocator.
*/
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
return pools.get_allocator();
}
/*! @brief Clears the view. */
void clear() {
pools.clear();
filter.clear();
}
/**
* @brief Appends an opaque storage object to a runtime view.
* @param base An opaque reference to a storage object.
* @return This runtime view.
*/
basic_runtime_view &iterate(common_type &base) {
if(pools.empty() || !(base.size() < pools.front()->size())) {
pools.push_back(&base);
} else {
pools.push_back(std::exchange(pools.front(), &base));
}
return *this;
}
/**
* @brief Adds an opaque storage object as a filter of a runtime view.
* @param base An opaque reference to a storage object.
* @return This runtime view.
*/
basic_runtime_view &exclude(common_type &base) {
filter.push_back(&base);
return *this;
basic_runtime_view(std::vector<const basic_common_type *> cpools, std::vector<const basic_common_type *> epools) ENTT_NOEXCEPT
: pools{std::move(cpools)},
filter{std::move(epools)}
{
// brings the best candidate (if any) on front of the vector
std::rotate(pools.begin(), std::min_element(pools.begin(), pools.end(), [](const auto *lhs, const auto *rhs) {
return (!lhs && rhs) || (lhs && rhs && lhs->size() < rhs->size());
}), pools.end());
}
/**
@@ -250,56 +169,54 @@ public:
* @return Estimated number of entities iterated by the view.
*/
[[nodiscard]] size_type size_hint() const {
return pools.empty() ? size_type{} : offset();
return valid() ? pools.front()->size() : size_type{};
}
/**
* @brief Returns an iterator to the first entity that has the given
* elements.
* components.
*
* If the view is empty, the returned iterator will be equal to `end()`.
* The returned iterator points to the first entity that has the given
* components. If the view is empty, the returned iterator will be equal to
* `end()`.
*
* @return An iterator to the first entity that has the given elements.
* @return An iterator to the first entity that has the given components.
*/
[[nodiscard]] iterator begin() const {
return pools.empty() ? iterator{} : iterator{pools, pools.front()->end() - static_cast<difference_type>(offset()), filter};
return valid() ? iterator{pools, filter, pools[0]->begin()} : iterator{};
}
/**
* @brief Returns an iterator that is past the last entity that has the
* given elements.
* given components.
*
* The returned iterator points to the entity following the last entity that
* has the given components. Attempting to dereference the returned iterator
* results in undefined behavior.
*
* @return An iterator to the entity following the last entity that has the
* given elements.
* given components.
*/
[[nodiscard]] iterator end() const {
return pools.empty() ? iterator{} : iterator{pools, pools.front()->end(), filter};
}
/**
* @brief Checks whether a view is initialized or not.
* @return True if the view is initialized, false otherwise.
*/
[[nodiscard]] explicit operator bool() const noexcept {
return !(pools.empty() && filter.empty());
return valid() ? iterator{pools, filter, pools[0]->end()} : iterator{};
}
/**
* @brief Checks if a view contains an entity.
* @param entt A valid identifier.
* @param entt A valid entity identifier.
* @return True if the view contains the given entity, false otherwise.
*/
[[nodiscard]] bool contains(const entity_type entt) const {
return !pools.empty()
&& std::all_of(pools.cbegin(), pools.cend(), [entt](const auto *curr) { return curr->contains(entt); })
&& std::none_of(filter.cbegin(), filter.cend(), [entt](const auto *curr) { return curr && curr->contains(entt); })
&& pools.front()->index(entt) < offset();
return valid() && std::all_of(pools.cbegin(), pools.cend(), [entt](const auto *curr) { return curr->contains(entt); })
&& std::none_of(filter.cbegin(), filter.cend(), [entt](const auto *curr) { return curr && curr->contains(entt); });
}
/**
* @brief Iterates entities and applies the given function object to them.
*
* The function object is invoked for each entity. It is provided only with
* the entity itself.<br/>
* the entity itself. To get the components, users can use the registry with
* which the view was built.<br/>
* The signature of the function should be equivalent to the following:
*
* @code{.cpp}
@@ -317,10 +234,12 @@ public:
}
private:
container_type pools;
container_type filter;
std::vector<const basic_common_type *> pools;
std::vector<const basic_common_type *> filter;
};
} // namespace entt
}
#endif

View File

@@ -1,167 +1,157 @@
#ifndef ENTT_ENTITY_SNAPSHOT_HPP
#define ENTT_ENTITY_SNAPSHOT_HPP
#include <array>
#include <cstddef>
#include <iterator>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <vector>
#include "../config/config.h"
#include "../container/dense_map.hpp"
#include "../core/type_traits.hpp"
#include "entity.hpp"
#include "fwd.hpp"
#include "view.hpp"
#include "registry.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename Registry>
void orphans(Registry &registry) {
auto &storage = registry.template storage<typename Registry::entity_type>();
for(auto entt: storage) {
if(registry.orphan(entt)) {
storage.erase(entt);
}
}
}
} // namespace internal
/*! @endcond */
/**
* @brief Utility class to create snapshots from a registry.
*
* A _snapshot_ can be either a dump of the entire registry or a narrower
* selection of elements of interest.<br/>
* selection of components of interest.<br/>
* This type can be used in both cases if provided with a correctly configured
* output archive.
*
* @tparam Registry Basic registry type.
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Registry>
template<typename Entity>
class basic_snapshot {
static_assert(!std::is_const_v<Registry>, "Non-const registry type required");
using traits_type = entt_traits<typename Registry::entity_type>;
using traits_type = entt_traits<Entity>;
template<typename Component, typename Archive, typename It>
void get(Archive &archive, std::size_t sz, It first, It last) const {
const auto view = reg->template view<std::add_const_t<Component>>();
archive(typename traits_type::entity_type(sz));
while(first != last) {
const auto entt = *(first++);
if(reg->template all_of<Component>(entt)) {
std::apply(archive, std::tuple_cat(std::make_tuple(entt), view.get(entt)));
}
}
}
template<typename... Component, typename Archive, typename It, std::size_t... Index>
void component(Archive &archive, It first, It last, std::index_sequence<Index...>) const {
std::array<std::size_t, sizeof...(Index)> size{};
auto begin = first;
while(begin != last) {
const auto entt = *(begin++);
((reg->template all_of<Component>(entt) ? ++size[Index] : 0u), ...);
}
(get<Component>(archive, size[Index], first, last), ...);
}
public:
/*! Basic registry type. */
using registry_type = Registry;
/*! @brief Underlying entity identifier. */
using entity_type = typename registry_type::entity_type;
using entity_type = Entity;
/**
* @brief Constructs an instance that is bound to a given registry.
* @param source A valid reference to a registry.
*/
basic_snapshot(const registry_type &source) noexcept
: reg{&source} {}
/*! @brief Default copy constructor, deleted on purpose. */
basic_snapshot(const basic_snapshot &) = delete;
basic_snapshot(const basic_registry<entity_type> &source) ENTT_NOEXCEPT
: reg{&source}
{}
/*! @brief Default move constructor. */
basic_snapshot(basic_snapshot &&) noexcept = default;
basic_snapshot(basic_snapshot &&) = default;
/*! @brief Default destructor. */
~basic_snapshot() = default;
/*! @brief Default move assignment operator. @return This snapshot. */
basic_snapshot & operator=(basic_snapshot &&) = default;
/**
* @brief Default copy assignment operator, deleted on purpose.
* @return This snapshot.
*/
basic_snapshot &operator=(const basic_snapshot &) = delete;
/**
* @brief Default move assignment operator.
* @return This snapshot.
*/
basic_snapshot &operator=(basic_snapshot &&) noexcept = default;
/**
* @brief Serializes all elements of a type with associated identifiers.
* @tparam Type Type of elements to serialize.
* @brief Puts aside all the entities from the underlying registry.
*
* Entities are serialized along with their versions. Destroyed entities are
* taken in consideration as well by this function.
*
* @tparam Archive Type of output archive.
* @param archive A valid reference to an output archive.
* @param id Optional name used to map the storage within the registry.
* @return An object of this type to continue creating the snapshot.
*/
template<typename Type, typename Archive>
const basic_snapshot &get(Archive &archive, const id_type id = type_hash<Type>::value()) const {
if(const auto *storage = reg->template storage<Type>(id); storage) {
const typename registry_type::common_type &base = *storage;
template<typename Archive>
const basic_snapshot & entities(Archive &archive) const {
const auto sz = reg->size();
archive(static_cast<typename traits_type::entity_type>(storage->size()));
archive(typename traits_type::entity_type(sz));
if constexpr(std::is_same_v<Type, entity_type>) {
archive(static_cast<typename traits_type::entity_type>(storage->free_list()));
for(auto first = base.rbegin(), last = base.rend(); first != last; ++first) {
archive(*first);
}
} else if constexpr(registry_type::template storage_for_type<Type>::storage_policy == deletion_policy::in_place) {
for(auto it = base.rbegin(), last = base.rend(); it != last; ++it) {
if(const auto entt = *it; entt == tombstone) {
archive(static_cast<entity_type>(null));
} else {
archive(entt);
std::apply([&archive](auto &&...args) { (archive(std::forward<decltype(args)>(args)), ...); }, storage->get_as_tuple(entt));
}
}
} else {
for(auto elem: storage->reach()) {
std::apply([&archive](auto &&...args) { (archive(std::forward<decltype(args)>(args)), ...); }, elem);
}
}
} else {
archive(typename traits_type::entity_type{});
for(auto first = reg->data(), last = first + sz; first != last; ++first) {
archive(*first);
}
archive(reg->released());
return *this;
}
/**
* @brief Serializes all elements of a type with associated identifiers for
* the entities in a range.
* @tparam Type Type of elements to serialize.
* @brief Puts aside the given components.
*
* Each instance is serialized together with the entity to which it belongs.
* Entities are serialized along with their versions.
*
* @tparam Component Types of components to serialize.
* @tparam Archive Type of output archive.
* @param archive A valid reference to an output archive.
* @return An object of this type to continue creating the snapshot.
*/
template<typename... Component, typename Archive>
const basic_snapshot & component(Archive &archive) const {
if constexpr(sizeof...(Component) == 1u) {
const auto view = reg->template view<const Component...>();
(component<Component>(archive, view.data(), view.data() + view.size()), ...);
return *this;
} else {
(component<Component>(archive), ...);
return *this;
}
}
/**
* @brief Puts aside the given components for the entities in a range.
*
* Each instance is serialized together with the entity to which it belongs.
* Entities are serialized along with their versions.
*
* @tparam Component Types of components to serialize.
* @tparam Archive Type of output archive.
* @tparam It Type of input iterator.
* @param archive A valid reference to an output archive.
* @param first An iterator to the first element of the range to serialize.
* @param last An iterator past the last element of the range to serialize.
* @param id Optional name used to map the storage within the registry.
* @return An object of this type to continue creating the snapshot.
*/
template<typename Type, typename Archive, typename It>
const basic_snapshot &get(Archive &archive, It first, It last, const id_type id = type_hash<Type>::value()) const {
static_assert(!std::is_same_v<Type, entity_type>, "Entity types not supported");
if(const auto *storage = reg->template storage<Type>(id); storage && !storage->empty()) {
archive(static_cast<typename traits_type::entity_type>(std::distance(first, last)));
for(; first != last; ++first) {
if(const auto entt = *first; storage->contains(entt)) {
archive(entt);
std::apply([&archive](auto &&...args) { (archive(std::forward<decltype(args)>(args)), ...); }, storage->get_as_tuple(entt));
} else {
archive(static_cast<entity_type>(null));
}
}
} else {
archive(typename traits_type::entity_type{});
}
template<typename... Component, typename Archive, typename It>
const basic_snapshot & component(Archive &archive, It first, It last) const {
component<Component...>(archive, first, last, std::index_sequence_for<Component...>{});
return *this;
}
private:
const registry_type *reg;
const basic_registry<entity_type> *reg;
};
/**
* @brief Utility class to restore a snapshot as a whole.
*
@@ -170,122 +160,130 @@ private:
* originally had.<br/>
* An example of use is the implementation of a save/restore utility.
*
* @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_snapshot_loader {
static_assert(!std::is_const_v<Registry>, "Non-const registry type required");
using traits_type = entt_traits<typename Registry::entity_type>;
using traits_type = entt_traits<Entity>;
template<typename Type, typename Archive>
void assign(Archive &archive) const {
typename traits_type::entity_type length{};
archive(length);
entity_type entt{};
if constexpr(std::tuple_size_v<decltype(reg->template view<Type>().get({}))> == 0) {
while(length--) {
archive(entt);
const auto entity = reg->valid(entt) ? entt : reg->create(entt);
ENTT_ASSERT(entity == entt, "Entity not available for use");
reg->template emplace<Type>(entity);
}
} else {
Type instance{};
while(length--) {
archive(entt, instance);
const auto entity = reg->valid(entt) ? entt : reg->create(entt);
ENTT_ASSERT(entity == entt, "Entity not available for use");
reg->template emplace<Type>(entity, std::move(instance));
}
}
}
public:
/*! Basic registry type. */
using registry_type = Registry;
/*! @brief Underlying entity identifier. */
using entity_type = typename registry_type::entity_type;
using entity_type = Entity;
/**
* @brief Constructs an instance that is bound to a given registry.
* @param source A valid reference to a registry.
*/
basic_snapshot_loader(registry_type &source) noexcept
: reg{&source} {
basic_snapshot_loader(basic_registry<entity_type> &source) ENTT_NOEXCEPT
: reg{&source}
{
// restoring a snapshot as a whole requires a clean registry
ENTT_ASSERT(reg->template storage<entity_type>().free_list() == 0u, "Registry must be empty");
ENTT_ASSERT(reg->empty(), "Registry must be empty");
}
/*! @brief Default copy constructor, deleted on purpose. */
basic_snapshot_loader(const basic_snapshot_loader &) = delete;
/*! @brief Default move constructor. */
basic_snapshot_loader(basic_snapshot_loader &&) noexcept = default;
basic_snapshot_loader(basic_snapshot_loader &&) = default;
/*! @brief Default destructor. */
~basic_snapshot_loader() = default;
/*! @brief Default move assignment operator. @return This loader. */
basic_snapshot_loader & operator=(basic_snapshot_loader &&) = default;
/**
* @brief Default copy assignment operator, deleted on purpose.
* @return This loader.
*/
basic_snapshot_loader &operator=(const basic_snapshot_loader &) = delete;
/**
* @brief Default move assignment operator.
* @return This loader.
*/
basic_snapshot_loader &operator=(basic_snapshot_loader &&) noexcept = default;
/**
* @brief Restores all elements of a type with associated identifiers.
* @tparam Type Type of elements to restore.
* @brief Restores entities that were in use during serialization.
*
* This function restores the entities that were in use during serialization
* and gives them the versions they originally had.
*
* @tparam Archive Type of input archive.
* @param archive A valid reference to an input archive.
* @param id Optional name used to map the storage within the registry.
* @return A valid loader to continue restoring data.
*/
template<typename Type, typename Archive>
basic_snapshot_loader &get(Archive &archive, const id_type id = type_hash<Type>::value()) {
auto &storage = reg->template storage<Type>(id);
template<typename Archive>
const basic_snapshot_loader & entities(Archive &archive) const {
typename traits_type::entity_type length{};
archive(length);
std::vector<entity_type> all(length);
if constexpr(std::is_same_v<Type, entity_type>) {
typename traits_type::entity_type count{};
entity_type placeholder{};
storage.reserve(length);
archive(count);
for(entity_type entity = null; length; --length) {
archive(entity);
storage.generate(entity);
placeholder = (entity > placeholder) ? entity : placeholder;
}
storage.start_from(traits_type::next(placeholder));
storage.free_list(count);
} else {
auto &other = reg->template storage<entity_type>();
entity_type entt{null};
while(length--) {
if(archive(entt); entt != null) {
const auto entity = other.contains(entt) ? entt : other.generate(entt);
ENTT_ASSERT(entity == entt, "Entity not available for use");
if constexpr(std::tuple_size_v<decltype(storage.get_as_tuple({}))> == 0u) {
storage.emplace(entity);
} else {
Type elem{};
archive(elem);
storage.emplace(entity, std::move(elem));
}
}
}
for(decltype(length) pos{}; pos < length; ++pos) {
archive(all[pos]);
}
entity_type destroyed;
archive(destroyed);
reg->assign(all.cbegin(), all.cend(), destroyed);
return *this;
}
/**
* @brief Destroys those entities that have no elements.
* @brief Restores components and assigns them to the right entities.
*
* In case all the entities were serialized but only part of the elements
* was saved, it could happen that some of the entities have no elements
* The template parameter list must be exactly the same used during
* serialization. In the event that the entity to which the component is
* assigned doesn't exist yet, the loader will take care to create it with
* the version it originally had.
*
* @tparam Component Types of components to restore.
* @tparam Archive Type of input archive.
* @param archive A valid reference to an input archive.
* @return A valid loader to continue restoring data.
*/
template<typename... Component, typename Archive>
const basic_snapshot_loader & component(Archive &archive) const {
(assign<Component>(archive), ...);
return *this;
}
/**
* @brief Destroys those entities that have no components.
*
* In case all the entities were serialized but only part of the components
* was saved, it could happen that some of the entities have no components
* once restored.<br/>
* This function helps to identify and destroy those entities.
* This functions helps to identify and destroy those entities.
*
* @return A valid loader to continue restoring data.
*/
basic_snapshot_loader &orphans() {
internal::orphans(*reg);
const basic_snapshot_loader & orphans() const {
reg->orphans([this](const auto entt) {
reg->release(entt);
});
return *this;
}
private:
registry_type *reg;
basic_registry<entity_type> *reg;
};
/**
* @brief Utility class for _continuous loading_.
*
@@ -296,29 +294,43 @@ private:
* Identifiers that entities originally had are not transferred to the target.
* Instead, the loader maps remote identifiers to local ones while restoring a
* snapshot.<br/>
* An example of use is the implementation of a client-server application with
* An example of use is the implementation of a client-server applications with
* the requirement of transferring somehow parts of the representation side to
* side.
*
* @tparam Registry Basic registry type.
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Registry>
template<typename Entity>
class basic_continuous_loader {
static_assert(!std::is_const_v<Registry>, "Non-const registry type required");
using traits_type = entt_traits<typename Registry::entity_type>;
using traits_type = entt_traits<Entity>;
void restore(typename Registry::entity_type entt) {
if(const auto entity = to_entity(entt); remloc.contains(entity) && remloc[entity].first == entt) {
if(!reg->valid(remloc[entity].second)) {
remloc[entity].second = reg->create();
}
void destroy(Entity entt) {
if(const auto it = remloc.find(entt); it == remloc.cend()) {
const auto local = reg->create();
remloc.emplace(entt, std::make_pair(local, true));
reg->destroy(local);
}
}
void restore(Entity entt) {
const auto it = remloc.find(entt);
if(it == remloc.cend()) {
const auto local = reg->create();
remloc.emplace(entt, std::make_pair(local, true));
} else {
remloc.insert_or_assign(entity, std::make_pair(entt, reg->create()));
if(!reg->valid(remloc[entt].first)) {
remloc[entt].first = reg->create();
}
// set the dirty flag
remloc[entt].second = true;
}
}
template<typename Container>
auto update(int, Container &container) -> decltype(typename Container::mapped_type{}, void()) {
auto update(int, Container &container)
-> decltype(typename Container::mapped_type{}, void()) {
// map like container
Container other;
@@ -336,12 +348,12 @@ class basic_continuous_loader {
}
}
using std::swap;
swap(container, other);
std::swap(container, other);
}
template<typename Container>
auto update(char, Container &container) -> decltype(typename Container::value_type{}, void()) {
auto update(char, Container &container)
-> decltype(typename Container::value_type{}, void()) {
// vector like container
static_assert(std::is_same_v<typename Container::value_type, entity_type>, "Invalid value type");
@@ -350,9 +362,9 @@ class basic_continuous_loader {
}
}
template<typename Component, typename Other, typename Member>
void update([[maybe_unused]] Component &instance, [[maybe_unused]] Member Other::*member) {
if constexpr(!std::is_same_v<Component, Other>) {
template<typename Other, typename Type, typename Member>
void update([[maybe_unused]] Other &instance, [[maybe_unused]] Member Type:: *member) {
if constexpr(!std::is_same_v<Other, Type>) {
return;
} else if constexpr(std::is_same_v<Member, entity_type>) {
instance.*member = map(instance.*member);
@@ -362,102 +374,143 @@ class basic_continuous_loader {
}
}
template<typename Component>
void remove_if_exists() {
for(auto &&ref: remloc) {
const auto local = ref.second.first;
if(reg->valid(local)) {
reg->template remove<Component>(local);
}
}
}
template<typename Other, typename Archive, typename... Type, typename... Member>
void assign(Archive &archive, [[maybe_unused]] Member Type:: *... member) {
typename traits_type::entity_type length{};
archive(length);
entity_type entt{};
if constexpr(std::tuple_size_v<decltype(reg->template view<Other>().get({}))> == 0) {
while(length--) {
archive(entt);
restore(entt);
reg->template emplace_or_replace<Other>(map(entt));
}
} else {
Other instance{};
while(length--) {
archive(entt, instance);
(update(instance, member), ...);
restore(entt);
reg->template emplace_or_replace<Other>(map(entt), std::move(instance));
}
}
}
public:
/*! Basic registry type. */
using registry_type = Registry;
/*! @brief Underlying entity identifier. */
using entity_type = typename registry_type::entity_type;
using entity_type = Entity;
/**
* @brief Constructs an instance that is bound to a given registry.
* @param source A valid reference to a registry.
*/
basic_continuous_loader(registry_type &source) noexcept
: remloc{source.get_allocator()},
reg{&source} {}
/*! @brief Default copy constructor, deleted on purpose. */
basic_continuous_loader(const basic_continuous_loader &) = delete;
basic_continuous_loader(basic_registry<entity_type> &source) ENTT_NOEXCEPT
: reg{&source}
{}
/*! @brief Default move constructor. */
basic_continuous_loader(basic_continuous_loader &&) noexcept = default;
basic_continuous_loader(basic_continuous_loader &&) = default;
/*! @brief Default destructor. */
~basic_continuous_loader() = default;
/*! @brief Default move assignment operator. @return This loader. */
basic_continuous_loader & operator=(basic_continuous_loader &&) = default;
/**
* @brief Default copy assignment operator, deleted on purpose.
* @return This loader.
*/
basic_continuous_loader &operator=(const basic_continuous_loader &) = delete;
/**
* @brief Default move assignment operator.
* @return This loader.
*/
basic_continuous_loader &operator=(basic_continuous_loader &&) noexcept = default;
/**
* @brief Restores all elements of a type with associated identifiers.
* @brief Restores entities that were in use during serialization.
*
* It creates local counterparts for remote elements as needed.<br/>
* Members are either data members of type entity_type or containers of
* entities. In both cases, a loader visits them and replaces entities with
* their local counterpart.
* This function restores the entities that were in use during serialization
* and creates local counterparts for them if required.
*
* @tparam Type Type of elements to restore.
* @tparam Archive Type of input archive.
* @param archive A valid reference to an input archive.
* @param id Optional name used to map the storage within the registry.
* @return A valid loader to continue restoring data.
* @return A non-const reference to this loader.
*/
template<typename Type, typename Archive>
basic_continuous_loader &get(Archive &archive, const id_type id = type_hash<Type>::value()) {
auto &storage = reg->template storage<Type>(id);
template<typename Archive>
basic_continuous_loader & entities(Archive &archive) {
typename traits_type::entity_type length{};
entity_type entt{null};
entity_type entt{};
archive(length);
if constexpr(std::is_same_v<Type, entity_type>) {
typename traits_type::entity_type in_use{};
for(decltype(length) pos{}; pos < length; ++pos) {
archive(entt);
storage.reserve(length);
archive(in_use);
for(std::size_t pos{}; pos < in_use; ++pos) {
archive(entt);
if(const auto entity = traits_type::to_entity(entt); entity == pos) {
restore(entt);
} else {
destroy(entt);
}
}
for(std::size_t pos = in_use; pos < length; ++pos) {
archive(entt);
// discards the head of the list of destroyed entities
archive(entt);
if(const auto entity = to_entity(entt); remloc.contains(entity)) {
if(reg->valid(remloc[entity].second)) {
reg->destroy(remloc[entity].second);
}
return *this;
}
remloc.erase(entity);
/**
* @brief Restores components and assigns them to the right entities.
*
* The template parameter list must be exactly the same used during
* serialization. In the event that the entity to which the component is
* assigned doesn't exist yet, the loader will take care to create a local
* counterpart for it.<br/>
* Members can be either data members of type entity_type or containers of
* entities. In both cases, the loader will visit them and update the
* entities by replacing each one with its local counterpart.
*
* @tparam Component Type of component to restore.
* @tparam Archive Type of input archive.
* @tparam Type Types of components to update with local counterparts.
* @tparam Member Types of members to update with their local counterparts.
* @param archive A valid reference to an input archive.
* @param member Members to update with their local counterparts.
* @return A non-const reference to this loader.
*/
template<typename... Component, typename Archive, typename... Type, typename... Member>
basic_continuous_loader & component(Archive &archive, Member Type:: *... member) {
(remove_if_exists<Component>(), ...);
(assign<Component>(archive, member...), ...);
return *this;
}
/**
* @brief Helps to purge entities that no longer have a conterpart.
*
* Users should invoke this member function after restoring each snapshot,
* unless they know exactly what they are doing.
*
* @return A non-const reference to this loader.
*/
basic_continuous_loader & shrink() {
auto it = remloc.begin();
while(it != remloc.cend()) {
const auto local = it->second.first;
bool &dirty = it->second.second;
if(dirty) {
dirty = false;
++it;
} else {
if(reg->valid(local)) {
reg->destroy(local);
}
}
} else {
for(auto &&ref: remloc) {
storage.remove(ref.second.second);
}
while(length--) {
if(archive(entt); entt != null) {
restore(entt);
if constexpr(std::tuple_size_v<decltype(storage.get_as_tuple({}))> == 0u) {
storage.emplace(map(entt));
} else {
Type elem{};
archive(elem);
storage.emplace(map(entt), std::move(elem));
}
}
it = remloc.erase(it);
}
}
@@ -465,48 +518,55 @@ public:
}
/**
* @brief Destroys those entities that have no elements.
* @brief Destroys those entities that have no components.
*
* In case all the entities were serialized but only part of the elements
* was saved, it could happen that some of the entities have no elements
* In case all the entities were serialized but only part of the components
* was saved, it could happen that some of the entities have no components
* once restored.<br/>
* This function helps to identify and destroy those entities.
* This functions helps to identify and destroy those entities.
*
* @return A non-const reference to this loader.
*/
basic_continuous_loader &orphans() {
internal::orphans(*reg);
basic_continuous_loader & orphans() {
reg->orphans([this](const auto entt) {
reg->release(entt);
});
return *this;
}
/**
* @brief Tests if a loader knows about a given entity.
* @param entt A valid identifier.
* @param entt An entity identifier.
* @return True if `entity` is managed by the loader, false otherwise.
*/
[[nodiscard]] bool contains(entity_type entt) const noexcept {
const auto it = remloc.find(to_entity(entt));
return it != remloc.cend() && it->second.first == entt;
[[nodiscard]] bool contains(entity_type entt) const ENTT_NOEXCEPT {
return (remloc.find(entt) != remloc.cend());
}
/**
* @brief Returns the identifier to which an entity refers.
* @param entt A valid identifier.
* @param entt An entity identifier.
* @return The local identifier if any, the null entity otherwise.
*/
[[nodiscard]] entity_type map(entity_type entt) const noexcept {
if(const auto it = remloc.find(to_entity(entt)); it != remloc.cend() && it->second.first == entt) {
return it->second.second;
[[nodiscard]] entity_type map(entity_type entt) const ENTT_NOEXCEPT {
const auto it = remloc.find(entt);
entity_type other = null;
if(it != remloc.cend()) {
other = it->second.first;
}
return null;
return other;
}
private:
dense_map<typename traits_type::entity_type, std::pair<entity_type, entity_type>> remloc;
registry_type *reg;
std::unordered_map<entity_type, std::pair<entity_type, bool>> remloc;
basic_registry<entity_type> *reg;
};
} // namespace entt
}
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,46 @@
#ifndef ENTT_ENTITY_UTILITY_HPP
#define ENTT_ENTITY_UTILITY_HPP
#include "../core/type_traits.hpp"
namespace entt {
/**
* @brief Alias for exclusion lists.
* @tparam Type List of types.
*/
template<typename... Type>
struct exclude_t: type_list<Type...> {};
/**
* @brief Variable template for exclusion lists.
* @tparam Type List of types.
*/
template<typename... Type>
inline constexpr exclude_t<Type...> exclude{};
/**
* @brief Alias for lists of observed components.
* @tparam Type List of types.
*/
template<typename... Type>
struct get_t: type_list<Type...>{};
/**
* @brief Variable template for lists of observed components.
* @tparam Type List of types.
*/
template<typename... Type>
inline constexpr get_t<Type...> get{};
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -1,26 +1,11 @@
/*! @brief `EnTT` default namespace. */
namespace entt {}
// IWYU pragma: begin_exports
#include "config/config.h"
#include "config/macro.h"
#include "config/version.h"
#include "container/dense_map.hpp"
#include "container/dense_set.hpp"
#include "container/table.hpp"
#include "core/algorithm.hpp"
#include "core/any.hpp"
#include "core/bit.hpp"
#include "core/compressed_pair.hpp"
#include "core/enum.hpp"
#include "core/attribute.h"
#include "core/family.hpp"
#include "core/hashed_string.hpp"
#include "core/ident.hpp"
#include "core/iterator.hpp"
#include "core/memory.hpp"
#include "core/monostate.hpp"
#include "core/ranges.hpp"
#include "core/tuple.hpp"
#include "core/type_info.hpp"
#include "core/type_traits.hpp"
#include "core/utility.hpp"
@@ -29,22 +14,20 @@ namespace entt {}
#include "entity/group.hpp"
#include "entity/handle.hpp"
#include "entity/helper.hpp"
#include "entity/mixin.hpp"
#include "entity/observer.hpp"
#include "entity/organizer.hpp"
#include "entity/ranges.hpp"
#include "entity/poly_storage.hpp"
#include "entity/registry.hpp"
#include "entity/runtime_view.hpp"
#include "entity/snapshot.hpp"
#include "entity/sparse_set.hpp"
#include "entity/storage.hpp"
#include "entity/utility.hpp"
#include "entity/view.hpp"
#include "graph/adjacency_matrix.hpp"
#include "graph/dot.hpp"
#include "graph/flow.hpp"
#include "locator/locator.hpp"
#include "meta/adl_pointer.hpp"
#include "meta/container.hpp"
#include "meta/context.hpp"
#include "meta/ctx.hpp"
#include "meta/factory.hpp"
#include "meta/meta.hpp"
#include "meta/node.hpp"
@@ -55,14 +38,14 @@ namespace entt {}
#include "meta/template.hpp"
#include "meta/type_traits.hpp"
#include "meta/utility.hpp"
#include "platform/android-ndk-r17.hpp"
#include "poly/poly.hpp"
#include "process/process.hpp"
#include "process/scheduler.hpp"
#include "resource/cache.hpp"
#include "resource/handle.hpp"
#include "resource/loader.hpp"
#include "resource/resource.hpp"
#include "signal/delegate.hpp"
#include "signal/dispatcher.hpp"
#include "signal/emitter.hpp"
#include "signal/sigh.hpp"
// IWYU pragma: end_exports

View File

@@ -1,11 +1,5 @@
// IWYU pragma: begin_exports
#include "container/fwd.hpp"
#include "core/fwd.hpp"
#include "entity/fwd.hpp"
#include "graph/fwd.hpp"
#include "meta/fwd.hpp"
#include "poly/fwd.hpp"
#include "process/fwd.hpp"
#include "resource/fwd.hpp"
#include "signal/fwd.hpp"
// IWYU pragma: end_exports

View File

@@ -1,341 +0,0 @@
#ifndef ENTT_GRAPH_ADJACENCY_MATRIX_HPP
#define ENTT_GRAPH_ADJACENCY_MATRIX_HPP
#include <cstddef>
#include <iterator>
#include <memory>
#include <type_traits>
#include <utility>
#include <vector>
#include "../config/config.h"
#include "../core/iterator.hpp"
#include "fwd.hpp"
namespace entt {
/*! @cond TURN_OFF_DOXYGEN */
namespace internal {
template<typename It>
class edge_iterator {
using size_type = std::size_t;
void find_next() noexcept {
for(; pos != last && !it[static_cast<typename It::difference_type>(pos)]; pos += offset) {}
}
public:
using value_type = std::pair<size_type, size_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 edge_iterator() noexcept = default;
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
constexpr edge_iterator(It base, const size_type vertices, const size_type from, const size_type to, const size_type step) noexcept
: it{std::move(base)},
vert{vertices},
pos{from},
last{to},
offset{step} {
find_next();
}
constexpr edge_iterator &operator++() noexcept {
pos += offset;
find_next();
return *this;
}
constexpr edge_iterator operator++(int) noexcept {
const edge_iterator orig = *this;
return ++(*this), orig;
}
[[nodiscard]] constexpr reference operator*() const noexcept {
return *operator->();
}
[[nodiscard]] constexpr pointer operator->() const noexcept {
return std::make_pair<size_type>(pos / vert, pos % vert);
}
template<typename Type>
friend constexpr bool operator==(const edge_iterator<Type> &, const edge_iterator<Type> &) noexcept;
private:
It it{};
size_type vert{};
size_type pos{};
size_type last{};
size_type offset{};
};
template<typename Container>
[[nodiscard]] constexpr bool operator==(const edge_iterator<Container> &lhs, const edge_iterator<Container> &rhs) noexcept {
return lhs.pos == rhs.pos;
}
template<typename Container>
[[nodiscard]] constexpr bool operator!=(const edge_iterator<Container> &lhs, const edge_iterator<Container> &rhs) noexcept {
return !(lhs == rhs);
}
} // namespace internal
/*! @endcond */
/**
* @brief Basic implementation of a directed adjacency matrix.
* @tparam Category Either a directed or undirected category tag.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Category, typename Allocator>
class adjacency_matrix {
using alloc_traits = std::allocator_traits<Allocator>;
static_assert(std::is_base_of_v<directed_tag, Category>, "Invalid graph category");
static_assert(std::is_same_v<typename alloc_traits::value_type, std::size_t>, "Invalid value type");
using container_type = std::vector<std::size_t, typename alloc_traits::template rebind_alloc<std::size_t>>;
public:
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Vertex type. */
using vertex_type = size_type;
/*! @brief Edge type. */
using edge_type = std::pair<vertex_type, vertex_type>;
/*! @brief Vertex iterator type. */
using vertex_iterator = iota_iterator<vertex_type>;
/*! @brief Edge iterator type. */
using edge_iterator = internal::edge_iterator<typename container_type::const_iterator>;
/*! @brief Out-edge iterator type. */
using out_edge_iterator = edge_iterator;
/*! @brief In-edge iterator type. */
using in_edge_iterator = edge_iterator;
/*! @brief Graph category tag. */
using graph_category = Category;
/*! @brief Default constructor. */
adjacency_matrix() noexcept(noexcept(allocator_type{}))
: adjacency_matrix{0u} {
}
/**
* @brief Constructs an empty container with a given allocator.
* @param allocator The allocator to use.
*/
explicit adjacency_matrix(const allocator_type &allocator) noexcept
: adjacency_matrix{0u, allocator} {}
/**
* @brief Constructs an empty container with a given allocator and user
* supplied number of vertices.
* @param vertices Number of vertices.
* @param allocator The allocator to use.
*/
adjacency_matrix(const size_type vertices, const allocator_type &allocator = allocator_type{})
: matrix{vertices * vertices, allocator},
vert{vertices} {}
/*! @brief Default copy constructor. */
adjacency_matrix(const adjacency_matrix &) = default;
/**
* @brief Allocator-extended copy constructor.
* @param other The instance to copy from.
* @param allocator The allocator to use.
*/
adjacency_matrix(const adjacency_matrix &other, const allocator_type &allocator)
: matrix{other.matrix, allocator},
vert{other.vert} {}
/*! @brief Default move constructor. */
adjacency_matrix(adjacency_matrix &&) noexcept = default;
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
adjacency_matrix(adjacency_matrix &&other, const allocator_type &allocator)
: matrix{std::move(other.matrix), allocator},
vert{other.vert} {}
/*! @brief Default destructor. */
~adjacency_matrix() = default;
/**
* @brief Default copy assignment operator.
* @return This container.
*/
adjacency_matrix &operator=(const adjacency_matrix &) = default;
/**
* @brief Default move assignment operator.
* @return This container.
*/
adjacency_matrix &operator=(adjacency_matrix &&) noexcept = default;
/**
* @brief Exchanges the contents with those of a given adjacency matrix.
* @param other Adjacency matrix to exchange the content with.
*/
void swap(adjacency_matrix &other) noexcept {
using std::swap;
swap(matrix, other.matrix);
swap(vert, other.vert);
}
/**
* @brief Returns the associated allocator.
* @return The associated allocator.
*/
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
return matrix.get_allocator();
}
/*! @brief Clears the adjacency matrix. */
void clear() noexcept {
matrix.clear();
vert = {};
}
/**
* @brief Returns true if an adjacency matrix is empty, false otherwise.
*
* @warning
* Potentially expensive, try to avoid it on hot paths.
*
* @return True if the adjacency matrix is empty, false otherwise.
*/
[[nodiscard]] bool empty() const noexcept {
const auto iterable = edges();
return (iterable.begin() == iterable.end());
}
/**
* @brief Returns the number of vertices.
* @return The number of vertices.
*/
[[nodiscard]] size_type size() const noexcept {
return vert;
}
/**
* @brief Returns an iterable object to visit all vertices of a matrix.
* @return An iterable object to visit all vertices of a matrix.
*/
[[nodiscard]] iterable_adaptor<vertex_iterator> vertices() const noexcept {
return {0u, vert};
}
/**
* @brief Returns an iterable object to visit all edges of a matrix.
* @return An iterable object to visit all edges of a matrix.
*/
[[nodiscard]] iterable_adaptor<edge_iterator> edges() const noexcept {
const auto it = matrix.cbegin();
const auto sz = matrix.size();
return {{it, vert, 0u, sz, 1u}, {it, vert, sz, sz, 1u}};
}
/**
* @brief Returns an iterable object to visit all out-edges of a vertex.
* @param vertex The vertex of which to return all out-edges.
* @return An iterable object to visit all out-edges of a vertex.
*/
[[nodiscard]] iterable_adaptor<out_edge_iterator> out_edges(const vertex_type vertex) const noexcept {
const auto it = matrix.cbegin();
const auto from = vertex * vert;
const auto to = from + vert;
return {{it, vert, from, to, 1u}, {it, vert, to, to, 1u}};
}
/**
* @brief Returns an iterable object to visit all in-edges of a vertex.
* @param vertex The vertex of which to return all in-edges.
* @return An iterable object to visit all in-edges of a vertex.
*/
[[nodiscard]] iterable_adaptor<in_edge_iterator> in_edges(const vertex_type vertex) const noexcept {
const auto it = matrix.cbegin();
const auto from = vertex;
const auto to = vert * vert + from;
return {{it, vert, from, to, vert}, {it, vert, to, to, vert}};
}
/**
* @brief Resizes an adjacency matrix.
* @param vertices The new number of vertices.
*/
void resize(const size_type vertices) {
adjacency_matrix other{vertices, get_allocator()};
for(auto [lhs, rhs]: edges()) {
other.insert(lhs, rhs);
}
other.swap(*this);
}
/**
* @brief Inserts an edge into the adjacency matrix, if it does not exist.
* @param lhs The left hand vertex of the edge.
* @param rhs The right hand vertex of the edge.
* @return A pair consisting of an iterator to the inserted element (or to
* the element that prevented the insertion) and a bool denoting whether the
* insertion took place.
*/
std::pair<edge_iterator, bool> insert(const vertex_type lhs, const vertex_type rhs) {
const auto pos = lhs * vert + rhs;
if constexpr(std::is_same_v<graph_category, undirected_tag>) {
const auto rev = rhs * vert + lhs;
ENTT_ASSERT(matrix[pos] == matrix[rev], "Something went really wrong");
matrix[rev] = 1u;
}
const auto inserted = !std::exchange(matrix[pos], 1u);
return {edge_iterator{matrix.cbegin(), vert, pos, matrix.size(), 1u}, inserted};
}
/**
* @brief Removes the edge associated with a pair of given vertices.
* @param lhs The left hand vertex of the edge.
* @param rhs The right hand vertex of the edge.
* @return Number of elements removed (either 0 or 1).
*/
size_type erase(const vertex_type lhs, const vertex_type rhs) {
const auto pos = lhs * vert + rhs;
if constexpr(std::is_same_v<graph_category, undirected_tag>) {
const auto rev = rhs * vert + lhs;
ENTT_ASSERT(matrix[pos] == matrix[rev], "Something went really wrong");
matrix[rev] = 0u;
}
return std::exchange(matrix[pos], 0u);
}
/**
* @brief Checks if an adjacency matrix contains a given edge.
* @param lhs The left hand vertex of the edge.
* @param rhs The right hand vertex of the edge.
* @return True if there is such an edge, false otherwise.
*/
[[nodiscard]] bool contains(const vertex_type lhs, const vertex_type rhs) const {
const auto pos = lhs * vert + rhs;
return pos < matrix.size() && matrix[pos];
}
private:
container_type matrix;
size_type vert;
};
} // namespace entt
#endif

View File

@@ -1,58 +0,0 @@
#ifndef ENTT_GRAPH_DOT_HPP
#define ENTT_GRAPH_DOT_HPP
#include <ostream>
#include <type_traits>
#include "fwd.hpp"
namespace entt {
/**
* @brief Outputs a graph in dot format.
* @tparam Graph Graph type, valid as long as it exposes edges and vertices.
* @tparam Writer Vertex decorator type.
* @param out A standard output stream.
* @param graph The graph to output.
* @param writer Vertex decorator object.
*/
template<typename Graph, typename Writer>
void dot(std::ostream &out, const Graph &graph, Writer writer) {
static_assert(std::is_base_of_v<directed_tag, typename Graph::graph_category>, "Invalid graph category");
if constexpr(std::is_same_v<typename Graph::graph_category, undirected_tag>) {
out << "graph{";
} else {
out << "digraph{";
}
for(auto &&vertex: graph.vertices()) {
out << vertex << "[";
writer(out, vertex);
out << "];";
}
for(auto [lhs, rhs]: graph.edges()) {
if constexpr(std::is_same_v<typename Graph::graph_category, undirected_tag>) {
out << lhs << "--" << rhs << ";";
} else {
out << lhs << "->" << rhs << ";";
}
}
out << "}";
}
/**
* @brief Outputs a graph in dot format.
* @tparam Graph Graph type, valid as long as it exposes edges and vertices.
* @param out A standard output stream.
* @param graph The graph to output.
*/
template<typename Graph>
void dot(std::ostream &out, const Graph &graph) {
return dot(out, graph, [](auto &&...) {});
}
} // namespace entt
#endif

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