Compare commits

..

No commits in common. "da774e10e8deca9ca159b02a34ae6c8ef0dd386f" and "4afe39daccfd1b89e45377a1c384209156038068" have entirely different histories.

154 changed files with 14285 additions and 24330 deletions

View File

@ -12,14 +12,14 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
env: env:
IWYU: 0.19 IWYU: 0.18
LLVM: 15 LLVM: 14
runs-on: ubuntu-latest runs-on: ubuntu-latest
continue-on-error: true continue-on-error: true
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Install llvm/clang - name: Install llvm/clang
# see: https://apt.llvm.org/ # see: https://apt.llvm.org/
run: | run: |
@ -39,23 +39,17 @@ jobs:
git clone https://github.com/include-what-you-use/include-what-you-use.git --branch $IWYU --depth 1 git clone https://github.com/include-what-you-use/include-what-you-use.git --branch $IWYU --depth 1
mkdir include-what-you-use/build mkdir include-what-you-use/build
cd include-what-you-use/build cd include-what-you-use/build
cmake -DCMAKE_C_COMPILER=clang-$LLVM \ cmake -DCMAKE_C_COMPILER=clang-$LLVM -DCMAKE_CXX_COMPILER=clang++-$LLVM -DCMAKE_INSTALL_PREFIX=./ ..
-DCMAKE_CXX_COMPILER=clang++-$LLVM \
-DCMAKE_INSTALL_PREFIX=./ \
..
make -j4 make -j4
bin/include-what-you-use --version bin/include-what-you-use --version
- name: Compile tests - name: Compile tests
working-directory: build working-directory: build
run: | run: |
export PATH=$PATH:${GITHUB_WORKSPACE}/build/include-what-you-use/build/bin export PATH=$PATH:${GITHUB_WORKSPACE}/build/include-what-you-use/build/bin
cmake -DCMAKE_C_COMPILER=clang-$LLVM \ cmake -DENTT_BUILD_TESTING=ON \
-DCMAKE_CXX_COMPILER=clang++-$LLVM \
-DENTT_BUILD_TESTING=ON \
-DENTT_BUILD_BENCHMARK=ON \ -DENTT_BUILD_BENCHMARK=ON \
-DENTT_BUILD_EXAMPLE=ON \ -DENTT_BUILD_EXAMPLE=ON \
-DENTT_BUILD_LIB=ON \ -DENTT_BUILD_LIB=ON \
-DENTT_BUILD_SNAPSHOT=ON \ -DENTT_BUILD_SNAPSHOT=ON \
-DCMAKE_CXX_INCLUDE_WHAT_YOU_USE="include-what-you-use;-Xiwyu;--mapping_file=${GITHUB_WORKSPACE}/entt.imp;-Xiwyu;--no_fwd_decls;-Xiwyu;--verbose=1" \ -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 make -j4

View File

@ -9,61 +9,38 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest, ubuntu-20.04]
compiler: compiler:
- { pkg: g++, exe: 'g++', version: 7 } - pkg: g++-7
- { pkg: g++, exe: 'g++', version: 8 } exe: g++-7
- { pkg: g++, exe: 'g++', version: 9 } - pkg: g++-8
- { pkg: g++, exe: 'g++', version: 10 } exe: g++-8
- { pkg: g++, exe: 'g++', version: 11 } - pkg: g++-9
- { pkg: g++, exe: 'g++', version: 12 } exe: g++-9
- { pkg: clang, exe: 'clang++', version: 8 } - pkg: g++-10
- { pkg: clang, exe: 'clang++', version: 9 } exe: g++-10
- { pkg: clang, exe: 'clang++', version: 10 } - pkg: clang-8
- { pkg: clang, exe: 'clang++', version: 11 } exe: clang++-8
- { pkg: clang, exe: 'clang++', version: 12 } - pkg: clang-9
- { pkg: clang, exe: 'clang++', version: 13 } exe: clang++-9
- { pkg: clang, exe: 'clang++', version: 14 } - pkg: clang-10
exclude: exe: clang++-10
- os: ubuntu-latest - pkg: clang-11
compiler: { pkg: g++, exe: 'g++', version: 7 } exe: clang++-11
- os: ubuntu-latest - pkg: clang-12
compiler: { pkg: g++, exe: 'g++', version: 8 } exe: clang++-12
- os: ubuntu-latest
compiler: { pkg: g++, exe: 'g++', version: 9 }
- os: ubuntu-latest
compiler: { pkg: clang, exe: 'clang++', version: 8 }
- os: ubuntu-latest
compiler: { pkg: clang, exe: 'clang++', version: 9 }
- os: ubuntu-latest
compiler: { pkg: clang, exe: 'clang++', version: 10 }
- os: ubuntu-latest
compiler: { pkg: clang, exe: 'clang++', version: 11 }
- os: ubuntu-20.04
compiler: { pkg: g++, exe: 'g++', version: 10 }
- os: ubuntu-20.04
compiler: { pkg: g++, exe: 'g++', version: 11 }
- os: ubuntu-20.04
compiler: { pkg: g++, exe: 'g++', version: 12 }
- os: ubuntu-20.04
compiler: { pkg: clang, exe: 'clang++', version: 12 }
- os: ubuntu-20.04
compiler: { pkg: clang, exe: 'clang++', version: 13 }
- os: ubuntu-20.04
compiler: { pkg: clang, exe: 'clang++', version: 14 }
runs-on: ${{ matrix.os }} runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Install compiler - name: Install compiler
run: | run: |
sudo apt update sudo apt update
sudo apt install -y ${{ matrix.compiler.pkg }}-${{ matrix.compiler.version }} sudo apt install -y ${{ matrix.compiler.pkg }}
- name: Compile tests - name: Compile tests
working-directory: build working-directory: build
env: env:
CXX: ${{ matrix.compiler.exe }}-${{ matrix.compiler.version }} CXX: ${{ matrix.compiler.exe }}
run: | run: |
cmake -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON .. cmake -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON ..
make -j4 make -j4
@ -73,6 +50,32 @@ jobs:
CTEST_OUTPUT_ON_FAILURE: 1 CTEST_OUTPUT_ON_FAILURE: 1
run: ctest --timeout 30 -C Debug -j4 run: ctest --timeout 30 -C Debug -j4
linux-extra:
timeout-minutes: 15
strategy:
matrix:
compiler: [g++, clang++]
id_type: ["std::uint32_t", "std::uint64_t"]
cxx_std: [cxx_std_17, cxx_std_20]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Compile tests
working-directory: build
env:
CXX: ${{ matrix.compiler }}
run: |
cmake -DENTT_BUILD_TESTING=ON -DENTT_CXX_STD=${{ matrix.cxx_std }} -DENTT_ID_TYPE=${{ matrix.id_type }} ..
make -j4
- name: Run tests
working-directory: build
env:
CTEST_OUTPUT_ON_FAILURE: 1
run: ctest --timeout 30 -C Debug -j4
windows: windows:
timeout-minutes: 15 timeout-minutes: 15
@ -90,7 +93,7 @@ jobs:
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Compile tests - name: Compile tests
working-directory: build working-directory: build
run: | run: |
@ -102,12 +105,35 @@ jobs:
CTEST_OUTPUT_ON_FAILURE: 1 CTEST_OUTPUT_ON_FAILURE: 1
run: ctest --timeout 30 -C Debug -j4 run: ctest --timeout 30 -C Debug -j4
windows-extra:
timeout-minutes: 15
strategy:
matrix:
id_type: ["std::uint32_t", "std::uint64_t"]
cxx_std: [cxx_std_17, cxx_std_20]
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Compile tests
working-directory: build
run: |
cmake -DENTT_BUILD_TESTING=ON -DENTT_CXX_STD=${{ matrix.cxx_std }} -DENTT_ID_TYPE=${{ matrix.id_type }} ..
cmake --build . -j 4
- name: Run tests
working-directory: build
env:
CTEST_OUTPUT_ON_FAILURE: 1
run: ctest --timeout 30 -C Debug -j4
macos: macos:
timeout-minutes: 15 timeout-minutes: 15
runs-on: macOS-latest runs-on: macOS-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Compile tests - name: Compile tests
working-directory: build working-directory: build
run: | run: |
@ -119,24 +145,23 @@ jobs:
CTEST_OUTPUT_ON_FAILURE: 1 CTEST_OUTPUT_ON_FAILURE: 1
run: ctest --timeout 30 -C Debug -j4 run: ctest --timeout 30 -C Debug -j4
extra: macos-extra:
timeout-minutes: 15 timeout-minutes: 15
strategy: strategy:
matrix: matrix:
os: [windows-latest, macOS-latest, ubuntu-latest]
id_type: ["std::uint32_t", "std::uint64_t"] id_type: ["std::uint32_t", "std::uint64_t"]
cxx_std: [cxx_std_17, cxx_std_20] cxx_std: [cxx_std_17, cxx_std_20]
runs-on: ${{ matrix.os }} runs-on: macOS-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Compile tests - name: Compile tests
working-directory: build working-directory: build
run: | run: |
cmake -DENTT_BUILD_TESTING=ON -DENTT_CXX_STD=${{ matrix.cxx_std }} -DENTT_ID_TYPE=${{ matrix.id_type }} .. cmake -DENTT_BUILD_TESTING=ON -DENTT_CXX_STD=${{ matrix.cxx_std }} -DENTT_ID_TYPE=${{ matrix.id_type }} ..
cmake --build . -j 4 make -j4
- name: Run tests - name: Run tests
working-directory: build working-directory: build
env: env:

View File

@ -9,11 +9,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Compile tests - name: Compile tests
working-directory: build working-directory: build
env: env:
CXXFLAGS: "--coverage -fno-elide-constructors -fno-inline -fno-default-inline" CXXFLAGS: "--coverage -fno-inline"
CXX: g++ CXX: g++
run: | run: |
cmake -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON .. cmake -DENTT_BUILD_TESTING=ON -DENTT_BUILD_LIB=ON -DENTT_BUILD_EXAMPLE=ON ..
@ -30,7 +30,7 @@ jobs:
lcov -c -d . -o coverage.info lcov -c -d . -o coverage.info
lcov -l coverage.info lcov -l coverage.info
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v2
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
files: build/coverage.info files: build/coverage.info

View File

@ -15,7 +15,7 @@ jobs:
FORMULA: entt.rb FORMULA: entt.rb
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Clone repository - name: Clone repository
working-directory: build working-directory: build
env: env:

View File

@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Compile tests - name: Compile tests
working-directory: build working-directory: build
env: env:

View File

@ -2,7 +2,7 @@
# EnTT # EnTT
# #
cmake_minimum_required(VERSION 3.15.7) cmake_minimum_required(VERSION 3.12.4)
# #
# Read project version # Read project version
@ -31,7 +31,7 @@ endif()
message(VERBOSE "*") message(VERBOSE "*")
message(VERBOSE "* ${PROJECT_NAME} v${PROJECT_VERSION} (${CMAKE_BUILD_TYPE})") message(VERBOSE "* ${PROJECT_NAME} v${PROJECT_VERSION} (${CMAKE_BUILD_TYPE})")
message(VERBOSE "* Copyright (c) 2017-2023 Michele Caini <michele.caini@gmail.com>") message(VERBOSE "* Copyright (c) 2017-2022 Michele Caini <michele.caini@gmail.com>")
message(VERBOSE "*") message(VERBOSE "*")
# #
@ -143,7 +143,6 @@ if(ENTT_INCLUDE_HEADERS)
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/fwd.hpp> $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/fwd.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/group.hpp> $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/group.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/handle.hpp> $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/handle.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/mixin.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/helper.hpp> $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/helper.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/observer.hpp> $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/observer.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/organizer.hpp> $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/organizer.hpp>
@ -152,6 +151,7 @@ if(ENTT_INCLUDE_HEADERS)
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/snapshot.hpp> $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/snapshot.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/sparse_set.hpp> $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/sparse_set.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/storage.hpp> $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/storage.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/storage_mixin.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/view.hpp> $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/entity/view.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/graph/adjacency_matrix.hpp> $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/graph/adjacency_matrix.hpp>
$<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/graph/dot.hpp> $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/graph/dot.hpp>

View File

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

View File

@ -6,8 +6,7 @@
[![Build Status](https://github.com/skypjack/entt/workflows/build/badge.svg)](https://github.com/skypjack/entt/actions) [![Build Status](https://github.com/skypjack/entt/workflows/build/badge.svg)](https://github.com/skypjack/entt/actions)
[![Coverage](https://codecov.io/gh/skypjack/entt/branch/master/graph/badge.svg)](https://codecov.io/gh/skypjack/entt) [![Coverage](https://codecov.io/gh/skypjack/entt/branch/master/graph/badge.svg)](https://codecov.io/gh/skypjack/entt)
[![Try online](https://img.shields.io/badge/try-online-brightgreen)](https://godbolt.org/z/zxW73f) [![Try online](https://img.shields.io/badge/try-online-brightgreen)](https://godbolt.org/z/zxW73f)
[![Vcpkg port](https://img.shields.io/vcpkg/v/entt)](https://vcpkg.link/ports/entt) [![Documentation](https://img.shields.io/badge/docs-docsforge-blue)](http://entt.docsforge.com/)
[![Documentation](https://img.shields.io/badge/docs-doxygen-blue)](https://skypjack.github.io/entt/)
[![Gitter chat](https://badges.gitter.im/skypjack/entt.png)](https://gitter.im/skypjack/entt) [![Gitter chat](https://badges.gitter.im/skypjack/entt.png)](https://gitter.im/skypjack/entt)
[![Discord channel](https://img.shields.io/discord/707607951396962417?logo=discord)](https://discord.gg/5BjPWBd) [![Discord channel](https://img.shields.io/discord/707607951396962417?logo=discord)](https://discord.gg/5BjPWBd)
[![Donate](https://img.shields.io/badge/donate-paypal-blue.svg)](https://www.paypal.me/skypjack) [![Donate](https://img.shields.io/badge/donate-paypal-blue.svg)](https://www.paypal.me/skypjack)
@ -344,8 +343,8 @@ The documentation is based on [doxygen](http://www.doxygen.nl/). To build it:
$ cmake .. -DENTT_BUILD_DOCS=ON $ cmake .. -DENTT_BUILD_DOCS=ON
$ make $ make
The API reference is created in HTML format in the `build/docs/html` directory. The API reference will be created in HTML format within the directory
To navigate it with your favorite browser: `build/docs/html`. To navigate it with your favorite browser:
$ cd build $ cd build
$ your_favorite_browser docs/html/index.html $ your_favorite_browser docs/html/index.html
@ -354,7 +353,10 @@ To navigate it with your favorite browser:
@cond TURN_OFF_DOXYGEN @cond TURN_OFF_DOXYGEN
--> -->
The same version is also available [online](https://skypjack.github.io/entt/) The same version is also available [online](https://skypjack.github.io/entt/)
for the latest release, that is the last stable tag.<br/> for the latest release, that is the last stable tag. If you are looking for
something more pleasing to the eye, consider reading the nice-looking version
available on [docsforge](https://entt.docsforge.com/): same documentation, much
more pleasant to read.<br/>
Moreover, there exists a [wiki](https://github.com/skypjack/entt/wiki) dedicated Moreover, there exists a [wiki](https://github.com/skypjack/entt/wiki) dedicated
to the project where users can find all related documentation pages. to the project where users can find all related documentation pages.
<!-- <!--
@ -364,8 +366,9 @@ to the project where users can find all related documentation pages.
# Tests # Tests
To compile and run the tests, `EnTT` requires *googletest*.<br/> To compile and run the tests, `EnTT` requires *googletest*.<br/>
`cmake` downloads and compiles the library before compiling anything else. In `cmake` will download and compile the library before compiling anything else.
order to build the tests, set the `CMake` option `ENTT_BUILD_TESTING` to `ON`. In order to build the tests, set the `CMake` option `ENTT_BUILD_TESTING` to
`ON`.
To build the most basic set of tests: To build the most basic set of tests:
@ -417,7 +420,7 @@ know who has participated so far.
# License # License
Code and documentation Copyright (c) 2017-2023 Michele Caini.<br/> Code and documentation Copyright (c) 2017-2022 Michele Caini.<br/>
Colorful logo Copyright (c) 2018-2021 Richard Caseres. Colorful logo Copyright (c) 2018-2021 Richard Caseres.
Code released under Code released under

View File

@ -1,26 +1,27 @@
* debugging tools (#60): the issue online already contains interesting tips on this, look at it
* work stealing job system (see #100) + mt scheduler based on const awareness for types
EXAMPLES EXAMPLES
* filter on runtime values/variables (not only types) * filter on runtime values/variables (not only types)
* support to polymorphic types (see #859) * support to polymorphic types (see #859)
DOC: DOC:
* storage<void>
* custom storage/view * custom storage/view
* examples (and credits) from @alanjfs :) * examples (and credits) from @alanjfs :)
* update entity doc when the storage based model is in place * 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
TODO (high prio): TODO (high prio):
* check natvis files (periodically :) * remove the static storage from the const assure in the registry
* resource cache: avoid using shared ptr with loader and the others
* further optimize exclusion lists in multi type views (no existence check)
* further improve the snapshot stuff, ie component functions
* use fixture for storage tests to reduce loc number and duplication as much as possible
* basic_view<...>::reach(...)
* doc: bump entities
WIP: WIP:
* get rid of observers, storage based views made them pointless - document alternatives * get rid of observers, storage based views made them pointless - document alternatives
* add storage getter for filters to views and groups
* exploit the tombstone mechanism to allow enabling/disabling entities (see bump, compact and clear for further details) * exploit the tombstone mechanism to allow enabling/disabling entities (see bump, compact and clear for further details)
* basic_storage::bind for cross-registry setups (see and remove todo from entity_copy.cpp)
* process scheduler: reviews, use free lists internally * process scheduler: reviews, use free lists internally
* dedicated entity storage, in-place O(1) release/destroy for non-orphaned entities, out-of-sync model
* entity-only and exclude-only views (both solved with entity storage and storage<void>)
* custom allocators all over (registry, ...)
* add test for maximum number of entities reached
* deprecate non-owning groups in favor of owning views and view packs, introduce lazy owning views * deprecate non-owning groups in favor of owning views and view packs, introduce lazy owning views
* bring nested groups back in place (see bd34e7f)
* work stealing job system (see #100) + mt scheduler based on const awareness for types

View File

@ -2,22 +2,8 @@
# Doxygen configuration (documentation) # Doxygen configuration (documentation)
# #
FetchContent_Declare( set(DOXY_DEPS_DIRECTORY ${EnTT_SOURCE_DIR}/deps)
doxygen-awesome-css
GIT_REPOSITORY https://github.com/jothepro/doxygen-awesome-css
GIT_TAG main
GIT_SHALLOW 1
)
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_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_DOCS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
set(DOXY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) set(DOXY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})

View File

@ -1,4 +1,4 @@
# Doxyfile 1.9.6 # Doxyfile 1.9.4
# This file describes the settings to be used by the documentation system # This file describes the settings to be used by the documentation system
# doxygen (www.doxygen.org) for a project. # doxygen (www.doxygen.org) for a project.
@ -19,8 +19,7 @@
# configuration file: # configuration file:
# doxygen -x [configFile] # doxygen -x [configFile]
# Use doxygen to compare the used configuration file with the template # Use doxygen to compare the used configuration file with the template
# configuration file without replacing the environment variables or CMake type # configuration file without replacing the environment variables:
# replacement variables:
# doxygen -x_noenv [configFile] # doxygen -x_noenv [configFile]
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
@ -86,7 +85,7 @@ CREATE_SUBDIRS = NO
# level increment doubles the number of directories, resulting in 4096 # level increment doubles the number of directories, resulting in 4096
# directories at level 8 which is the default and also the maximum value. The # directories at level 8 which is the default and also the maximum value. The
# sub-directories are organized in 2 levels, the first level always has a fixed # sub-directories are organized in 2 levels, the first level always has a fixed
# number of 16 directories. # numer of 16 directories.
# Minimum value: 0, maximum value: 8, default value: 8. # Minimum value: 0, maximum value: 8, default value: 8.
# This tag requires that the tag CREATE_SUBDIRS is set to YES. # This tag requires that the tag CREATE_SUBDIRS is set to YES.
@ -558,8 +557,7 @@ HIDE_UNDOC_MEMBERS = NO
# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all # If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
# undocumented classes that are normally visible in the class hierarchy. If set # undocumented classes that are normally visible in the class hierarchy. If set
# to NO, these classes will be included in the various overviews. This option # to NO, these classes will be included in the various overviews. This option
# will also hide undocumented C++ concepts if enabled. This option has no effect # has no effect if EXTRACT_ALL is enabled.
# if EXTRACT_ALL is enabled.
# The default value is: NO. # The default value is: NO.
HIDE_UNDOC_CLASSES = NO HIDE_UNDOC_CLASSES = NO
@ -597,8 +595,7 @@ INTERNAL_DOCS = NO
# Windows (including Cygwin) and MacOS, users should typically set this option # Windows (including Cygwin) and MacOS, users should typically set this option
# to NO, whereas on Linux or other Unix flavors it should typically be set to # to NO, whereas on Linux or other Unix flavors it should typically be set to
# YES. # YES.
# Possible values are: SYSTEM, NO and YES. # The default value is: system dependent.
# The default value is: SYSTEM.
CASE_SENSE_NAMES = YES CASE_SENSE_NAMES = YES
@ -850,14 +847,6 @@ WARN_IF_INCOMPLETE_DOC = YES
WARN_NO_PARAMDOC = YES WARN_NO_PARAMDOC = YES
# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about
# undocumented enumeration values. If set to NO, doxygen will accept
# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag
# will automatically be disabled.
# The default value is: NO.
WARN_IF_UNDOC_ENUM_VAL = NO
# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS # a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS
# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but # then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but
@ -916,21 +905,10 @@ INPUT = @DOXY_SOURCE_DIRECTORY@ \
# libiconv (or the iconv built into libc) for the transcoding. See the libiconv # libiconv (or the iconv built into libc) for the transcoding. See the libiconv
# documentation (see: # documentation (see:
# https://www.gnu.org/software/libiconv/) for the list of possible encodings. # https://www.gnu.org/software/libiconv/) for the list of possible encodings.
# See also: INPUT_FILE_ENCODING
# The default value is: UTF-8. # The default value is: UTF-8.
INPUT_ENCODING = UTF-8 INPUT_ENCODING = UTF-8
# This tag can be used to specify the character encoding of the source files
# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify
# character encoding on a per file pattern basis. Doxygen will compare the file
# name with each pattern and apply the encoding instead of the default
# INPUT_ENCODING) if there is a match. The character encodings are a list of the
# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding
# "INPUT_ENCODING" for further information on supported encodings.
INPUT_FILE_ENCODING =
# If the value of the INPUT tag contains directories, you can use the # If the value of the INPUT tag contains directories, you can use the
# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
# *.h) to filter out the source-files in the directories. # *.h) to filter out the source-files in the directories.
@ -967,7 +945,7 @@ RECURSIVE = YES
# Note that relative paths are relative to the directory from which doxygen is # Note that relative paths are relative to the directory from which doxygen is
# run. # run.
EXCLUDE = EXCLUDE = @DOXY_DEPS_DIRECTORY@
# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
# directories that are symbolic links (a Unix file system feature) are excluded # directories that are symbolic links (a Unix file system feature) are excluded
@ -1037,11 +1015,6 @@ IMAGE_PATH =
# code is scanned, but not when the output code is generated. If lines are added # code is scanned, but not when the output code is generated. If lines are added
# or removed, the anchors will not be placed correctly. # or removed, the anchors will not be placed correctly.
# #
# Note that doxygen will use the data processed and written to standard output
# for further processing, therefore nothing else, like debug statements or used
# commands (so in case of a Windows batch file always use @echo OFF), should be
# written to standard output.
#
# Note that for custom extensions or not directly supported extensions you also # Note that for custom extensions or not directly supported extensions you also
# need to set EXTENSION_MAPPING for the extension otherwise the files are not # need to set EXTENSION_MAPPING for the extension otherwise the files are not
# properly processed by doxygen. # properly processed by doxygen.
@ -1083,15 +1056,6 @@ FILTER_SOURCE_PATTERNS =
USE_MDFILE_AS_MAINPAGE = @PROJECT_SOURCE_DIR@/README.md USE_MDFILE_AS_MAINPAGE = @PROJECT_SOURCE_DIR@/README.md
# The Fortran standard specifies that for fixed formatted Fortran code all
# characters from position 72 are to be considered as comment. A common
# extension is to allow longer lines before the automatic comment starts. The
# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can
# be processed before the automatic comment starts.
# Minimum value: 7, maximum value: 10000, default value: 72.
FORTRAN_COMMENT_AFTER = 72
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
# Configuration options related to source browsing # Configuration options related to source browsing
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
@ -1229,11 +1193,10 @@ CLANG_DATABASE_PATH =
ALPHABETICAL_INDEX = YES ALPHABETICAL_INDEX = YES
# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) # In case all classes in a project start with a common prefix, all classes will
# that should be ignored while generating the index headers. The IGNORE_PREFIX # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
# tag works for classes, function and member names. The entity will be placed in # can be used to specify a prefix (or a list of prefixes) that should be ignored
# the alphabetical list under the first letter of the entity name that remains # while generating the index headers.
# after removing the prefix.
# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
IGNORE_PREFIX = IGNORE_PREFIX =
@ -1302,7 +1265,7 @@ HTML_FOOTER =
# obsolete. # obsolete.
# This tag requires that the tag GENERATE_HTML is set to YES. # This tag requires that the tag GENERATE_HTML is set to YES.
HTML_STYLESHEET = @DOXY_CSS_DIRECTORY@/doxygen-awesome.css HTML_STYLESHEET =
# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
# cascading style sheets that are included after the standard style sheets # cascading style sheets that are included after the standard style sheets
@ -1312,12 +1275,7 @@ HTML_STYLESHEET = @DOXY_CSS_DIRECTORY@/doxygen-awesome.css
# Doxygen will copy the style sheet files to the output directory. # Doxygen will copy the style sheet files to the output directory.
# Note: The order of the extra style sheet files is of importance (e.g. the last # Note: The order of the extra style sheet files is of importance (e.g. the last
# style sheet in the list overrules the setting of the previous ones in the # style sheet in the list overrules the setting of the previous ones in the
# list). # list). For an example see the documentation.
# Note: Since the styling of scrollbars can currently not be overruled in
# Webkit/Chromium, the styling will be left out of the default doxygen.css if
# one or more extra stylesheets have been specified. So if scrollbar
# customization is desired it has to be added explicitly. For an example see the
# documentation.
# This tag requires that the tag GENERATE_HTML is set to YES. # This tag requires that the tag GENERATE_HTML is set to YES.
HTML_EXTRA_STYLESHEET = HTML_EXTRA_STYLESHEET =
@ -1332,19 +1290,6 @@ HTML_EXTRA_STYLESHEET =
HTML_EXTRA_FILES = HTML_EXTRA_FILES =
# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output
# should be rendered with a dark or light theme.
# Possible values are: LIGHT always generate light mode output, DARK always
# generate dark mode output, AUTO_LIGHT automatically set the mode according to
# the user preference, use light mode if no preference is set (the default),
# AUTO_DARK automatically set the mode according to the user preference, use
# dark mode if no preference is set and TOGGLE allow to user to switch between
# light and dark mode via a button.
# The default value is: AUTO_LIGHT.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_COLORSTYLE = LIGHT
# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
# will adjust the colors in the style sheet and background images according to # will adjust the colors in the style sheet and background images according to
# this color. Hue is specified as an angle on a color-wheel, see # this color. Hue is specified as an angle on a color-wheel, see
@ -1708,6 +1653,17 @@ HTML_FORMULA_FORMAT = png
FORMULA_FONTSIZE = 10 FORMULA_FONTSIZE = 10
# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
# generated for formulas are transparent PNGs. Transparent PNGs are not
# supported properly for IE 6.0, but are supported on all modern browsers.
#
# Note that when changing this option you need to delete any form_*.png files in
# the HTML output directory before the changes have effect.
# The default value is: YES.
# This tag requires that the tag GENERATE_HTML is set to YES.
FORMULA_TRANSPARENT = YES
# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands # The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
# to create new LaTeX commands to be used in formulas as building blocks. See # to create new LaTeX commands to be used in formulas as building blocks. See
# the section "Including formulas" for details. # the section "Including formulas" for details.
@ -2423,38 +2379,26 @@ HAVE_DOT = YES
DOT_NUM_THREADS = 0 DOT_NUM_THREADS = 0
# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of # When you want a differently looking font in the dot files that doxygen
# subgraphs. When you want a differently looking font in the dot files that # generates you can specify the font name using DOT_FONTNAME. You need to make
# doxygen generates you can specify fontname, fontcolor and fontsize attributes. # sure dot is able to find the font, which can be done by putting it in a
# For details please see <a href=https://graphviz.org/doc/info/attrs.html>Node, # standard location or by setting the DOTFONTPATH environment variable or by
# Edge and Graph Attributes specification</a> You need to make sure dot is able # setting DOT_FONTPATH to the directory containing the font.
# to find the font, which can be done by putting it in a standard location or by # The default value is: Helvetica.
# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the
# directory containing the font. Default graphviz fontsize is 14.
# The default value is: fontname=Helvetica,fontsize=10.
# This tag requires that the tag HAVE_DOT is set to YES. # This tag requires that the tag HAVE_DOT is set to YES.
DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10" DOT_FONTNAME = Helvetica
# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can # The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. <a # dot graphs.
# href=https://graphviz.org/doc/info/arrows.html>Complete documentation about # Minimum value: 4, maximum value: 24, default value: 10.
# arrows shapes.</a>
# The default value is: labelfontname=Helvetica,labelfontsize=10.
# This tag requires that the tag HAVE_DOT is set to YES. # This tag requires that the tag HAVE_DOT is set to YES.
DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10" DOT_FONTSIZE = 10
# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes # By default doxygen will tell dot to use the default font as specified with
# around nodes set 'shape=plain' or 'shape=plaintext' <a # DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
# href=https://www.graphviz.org/doc/info/shapes.html>Shapes specification</a> # the path where dot can find it using this tag.
# The default value is: shape=box,height=0.2,width=0.4.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4"
# You can set the path where dot can find font specified with fontname in
# DOT_COMMON_ATTR and others dot attributes.
# This tag requires that the tag HAVE_DOT is set to YES. # This tag requires that the tag HAVE_DOT is set to YES.
DOT_FONTPATH = DOT_FONTPATH =
@ -2697,6 +2641,18 @@ DOT_GRAPH_MAX_NODES = 50
MAX_DOT_GRAPH_DEPTH = 0 MAX_DOT_GRAPH_DEPTH = 0
# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
# background. This is disabled by default, because dot on Windows does not seem
# to support this out of the box.
#
# Warning: Depending on the platform used, enabling this option may lead to
# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
# read).
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_TRANSPARENT = NO
# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output # Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
# files in one run (i.e. multiple -o and -T options on the command line). This # files in one run (i.e. multiple -o and -T options on the command line). This
# makes dot run faster, but since only newer versions of dot (>1.8.10) support # makes dot run faster, but since only newer versions of dot (>1.8.10) support

View File

@ -17,6 +17,7 @@
* [ENTT_DISABLE_ASSERT](#entt_disable_assert) * [ENTT_DISABLE_ASSERT](#entt_disable_assert)
* [ENTT_NO_ETO](#entt_no_eto) * [ENTT_NO_ETO](#entt_no_eto)
* [ENTT_STANDARD_CPP](#entt_standard_cpp) * [ENTT_STANDARD_CPP](#entt_standard_cpp)
<!-- <!--
@endcond TURN_OFF_DOXYGEN @endcond TURN_OFF_DOXYGEN
--> -->

View File

@ -9,6 +9,7 @@
* [Containers](#containers) * [Containers](#containers)
* [Dense map](#dense-map) * [Dense map](#dense-map)
* [Dense set](#dense-set) * [Dense set](#dense-set)
<!-- <!--
@endcond TURN_OFF_DOXYGEN @endcond TURN_OFF_DOXYGEN
--> -->
@ -20,7 +21,7 @@ difficult to do better (although it's very easy to do worse, as many examples
available online demonstrate).<br/> available online demonstrate).<br/>
`EnTT` doesn't try in any way to replace what is offered by the standard. Quite `EnTT` doesn't try in any way to replace what is offered by the standard. Quite
the opposite, given the widespread use that is made of standard containers.<br/> the opposite, given the widespread use that is made of standard containers.<br/>
However, the library also tries to fill a gap in features and functionalities by However, the library also tries to fill a gap in features and functionality by
making available some containers initially developed for internal use. making available some containers initially developed for internal use.
This section of the library is likely to grow larger over time. However, for the This section of the library is likely to grow larger over time. However, for the
@ -39,7 +40,7 @@ The implementation is based on _sparse sets_ and each bucket is identified by an
implicit list within the packed array itself. implicit list within the packed array itself.
The interface is very close to its counterpart in the standard library, that is, The interface is very close to its counterpart in the standard library, that is,
the `std::unordered_map` class.<br/> `std::unordered_map`.<br/>
However, both local and non-local iterators returned by a dense map belong to However, both local and non-local iterators returned by a dense map belong to
the input iterator category although they respectively model the concepts of a the input iterator category although they respectively model the concepts of a
_forward iterator_ type and a _random access iterator_ type.<br/> _forward iterator_ type and a _random access iterator_ type.<br/>
@ -62,5 +63,5 @@ The implementation is based on _sparse sets_ and each bucket is identified by an
implicit list within the packed array itself. implicit list within the packed array itself.
The interface is in all respects similar to its counterpart in the standard The interface is in all respects similar to its counterpart in the standard
library, that is, the `std::unordered_set` class.<br/> library, that is, `std::unordered_set`.<br/>
Therefore, there is no need to go into the API description. Therefore, there is no need to go into the API description.

View File

@ -46,24 +46,25 @@
# Introduction # Introduction
`EnTT` comes with a bunch of core functionalities mostly used by the other parts `EnTT` comes with a bunch of core functionalities mostly used by the other parts
of the library.<br/> of the library itself.<br/>
Many of these tools are also useful in everyday work. Therefore, it's worth Hardly users will include these features in their code, but it's worth
describing them so as not to reinvent the wheel in case of need. describing what `EnTT` offers so as not to reinvent the wheel in case of need.
# Any as in any type # Any as in any type
`EnTT` offers its own `any` type. It may seem redundant considering that C++17 `EnTT` comes with its own `any` type. It may seem redundant considering that
introduced `std::any`, but it is not (hopefully).<br/> C++17 introduced `std::any`, but it is not (hopefully).<br/>
First of all, the _type_ returned by an `std::any` is a const reference to an First of all, the _type_ returned by an `std::any` is a const reference to an
`std::type_info`, an implementation defined class that's not something everyone `std::type_info`, an implementation defined class that's not something everyone
wants to see in a software. Furthermore, there is no way to bind it to the type wants to see in a software. Furthermore, there is no way to connect it with the
system of the library and therefore with its integrated RTTI support. type system of the library and therefore with its integrated RTTI support.<br/>
Note that this class is largely used internally by the library itself.
The `any` API is very similar to that of its most famous counterpart, mainly The API is very similar to that of its most famous counterpart, mainly because
because this class serves the same purpose of being an opaque container for any this class serves the same purpose of being an opaque container for any type of
type of value.<br/> value.<br/>
Instances also minimize the number of allocations by relying on a well known Instances of `any` also minimize the number of allocations by relying on a well
technique called _small buffer optimization_ and a fake vtable. known technique called _small buffer optimization_ and a fake vtable.
Creating an object of the `any` type, whether empty or not, is trivial: Creating an object of the `any` type, whether empty or not, is trivial:
@ -92,8 +93,8 @@ Furthermore, an instance of `any` isn't tied to an actual type. Therefore, the
wrapper is reconfigured when it's assigned a new object of a type other than wrapper is reconfigured when it's assigned a new object of a type other than
the one it contains. the one it contains.
There is also a way to directly assign a value to the variable contained by an There exists also a way to directly assign a value to the variable contained by
`entt::any`, without necessarily replacing it. This is especially useful when an `entt::any`, without necessarily replacing it. This is especially useful when
the object is used in _aliasing mode_, as described below: the object is used in _aliasing mode_, as described below:
```cpp ```cpp
@ -107,15 +108,15 @@ any.assign(value);
any.assign(std::move(value)); any.assign(std::move(value));
``` ```
The `any` class performs a check on the type information and whether or not the The `any` class will also perform a check on the type information and whether or
original type was copy or move assignable, as appropriate.<br/> not the original type was copy or move assignable, as appropriate.<br/>
In all cases, the `assign` function returns a boolean value that is true in case In all cases, the `assign` function returns a boolean value to indicate the
of success and false otherwise. success or failure of the operation.
When in doubt about the type of object contained, the `type` member function When in doubt about the type of object contained, the `type` member function of
returns a const reference to the `type_info` associated with its element, or `any` returns a const reference to the `type_info` associated with its element,
`type_id<void>()` if the container is empty.<br/> or `type_id<void>()` if the container is empty. The type is also used internally
The type is also used internally when comparing two `any` objects: when comparing two `any` objects:
```cpp ```cpp
if(any == empty) { /* ... */ } if(any == empty) { /* ... */ }
@ -124,7 +125,7 @@ if(any == empty) { /* ... */ }
In this case, before proceeding with a comparison, it's verified that the _type_ In this case, before proceeding with a comparison, it's verified that the _type_
of the two objects is actually the same.<br/> of the two objects is actually the same.<br/>
Refer to the `EnTT` type system documentation for more details about how Refer to the `EnTT` type system documentation for more details about how
`type_info` works and the possible risks of a comparison. `type_info` works and on possible risks of a comparison.
A particularly interesting feature of this class is that it can also be used as A particularly interesting feature of this class is that it can also be used as
an opaque container for const and non-const references: an opaque container for const and non-const references:
@ -152,19 +153,22 @@ entt::any ref = other.as_ref();
``` ```
In this case, it doesn't matter if the original container actually holds an In this case, it doesn't matter if the original container actually holds an
object or is as a reference for unmanaged elements already. The new instance object or acts already as a reference for unmanaged elements, the new instance
thus created doesn't create copies and only serves as a reference for the thus created won't create copies and will only serve as a reference for the
original item. original item.<br/>
This means that, starting from the example above, both `ref` and `other` will
point to the same object, whether it's initially contained in `other` or already
an unmanaged element.
It's worth mentioning that, while everything works transparently when it comes As a side note, it's worth mentioning that, while everything works transparently
to non-const references, there are some exceptions when it comes to const when it comes to non-const references, there are some exceptions when it comes
references.<br/> to const references.<br/>
In particular, the `data` member function invoked on a non-const instance of In particular, the `data` member function invoked on a non-const instance of
`any` that wraps a const reference returns a null pointer in all cases. `any` that wraps a const reference will return a null pointer in all cases.
To cast an instance of `any` to a type, the library offers a set of `any_cast` To cast an instance of `any` to a type, the library offers a set of `any_cast`
functions in all respects similar to their most famous counterparts.<br/> functions in all respects similar to their most famous counterparts.<br/>
The only difference is that, in the case of `EnTT`, they won't raise exceptions The only difference is that, in the case of `EnTT`, these won't raise exceptions
but will only trigger an assert in debug mode, otherwise resulting in undefined but will only trigger an assert in debug mode, otherwise resulting in undefined
behavior in case of misuse in release mode. behavior in case of misuse in release mode.
@ -184,23 +188,31 @@ using my_any = entt::basic_any<sizeof(double[4])>;
This feature, in addition to allowing the choice of a size that best suits the This feature, in addition to allowing the choice of a size that best suits the
needs of an application, also offers the possibility of forcing dynamic creation needs of an application, also offers the possibility of forcing dynamic creation
of objects during construction.<br/> of objects during construction.<br/>
In other terms, if the size is 0, `any` suppresses the small buffer optimization In other terms, if the size is 0, `any` avoids the use of any optimization and
and always dynamically allocates objects (except for aliasing cases). always dynamically allocates objects (except for aliasing cases).
Note that the size of the internal storage as well as the alignment requirements
are directly part of the type and therefore contribute to define different types
that won't be able to interoperate with each other.
## Alignment requirement ## Alignment requirement
The alignment requirement is optional and by default the most stringent (the The alignment requirement is optional and by default the most stringent (the
largest) for any object whose size is at most equal to the one provided.<br/> largest) for any object whose size is at most equal to the one provided.<br/>
It's provided as an optional second parameter following the desired size for the The `basic_any` class template inspects the alignment requirements in each case,
internal storage: even when not provided and may decide not to use the small buffer optimization
in order to meet them.
The alignment requirement is provided as an optional second parameter following
the desired size for the internal storage:
```cpp ```cpp
using my_any = entt::basic_any<sizeof(double[4]), alignof(double[4])>; using my_any = entt::basic_any<sizeof(double[4]), alignof(double[4])>;
``` ```
The `basic_any` class template inspects the alignment requirements in each case, Note that the alignment requirements as well as the size of the internal storage
even when not provided and may decide not to use the small buffer optimization are directly part of the type and therefore contribute to define different types
in order to meet them. that won't be able to interoperate with each other.
# Compressed pair # Compressed pair
@ -214,7 +226,7 @@ is more important than having some cool and probably useless feature.
Although the API is very close to that of `std::pair` (apart from the fact that Although the API is very close to that of `std::pair` (apart from the fact that
the template parameters are inferred from the constructor and therefore there is the template parameters are inferred from the constructor and therefore there is
no` entt::make_compressed_pair`), the major difference is that `first` and no` entt::make_compressed_pair`), the major difference is that `first` and
`second` are functions for implementation requirements: `second` are functions for implementation needs:
```cpp ```cpp
entt::compressed_pair pair{0, 3.}; entt::compressed_pair pair{0, 3.};
@ -227,15 +239,16 @@ intuition. At the end of the day, it's just a pair and nothing more.
# Enum as bitmask # Enum as bitmask
Sometimes it's useful to be able to use enums as bitmasks. However, enum classes Sometimes it's useful to be able to use enums as bitmasks. However, enum classes
aren't really suitable for the purpose. Main problem is that they don't convert aren't really suitable for the purpose out of the box. Main problem is that they
implicitly to their underlying type.<br/> don't convert implicitly to their underlying type.<br/>
The choice is then between using old-fashioned enums (with all their problems All that remains is to make a choice between using old-fashioned enums (with all
that I don't want to discuss here) or writing _ugly_ code. their problems that I don't want to discuss here) or writing _ugly_ code.
Fortunately, there is also a third way: adding enough operators in the global Fortunately, there is also a third way: adding enough operators in the global
scope to treat enum classes as bitmasks transparently.<br/> scope to treat enum classes as bitmask transparently.<br/>
The ultimate goal is to write code like the following (or maybe something more The ultimate goal is to be able to write code like the following (or maybe
meaningful, but this should give a grasp and remain simple at the same time): something more meaningful, but this should give a grasp and remain simple at the
same time):
```cpp ```cpp
enum class my_flag { enum class my_flag {
@ -248,11 +261,11 @@ const my_flag flags = my_flag::enabled;
const bool is_enabled = !!(flags & my_flag::enabled); const bool is_enabled = !!(flags & my_flag::enabled);
``` ```
The problem with adding all operators to the global scope is that these come The problem with adding all operators to the global scope is that these will
into play even when not required, with the risk of introducing errors that are come into play even when not required, with the risk of introducing errors that
difficult to deal with.<br/> are difficult to deal with.<br/>
However, C++ offers enough tools to get around this problem. In particular, the However, C++ offers enough tools to get around this problem. In particular, the
library requires users to register the enum classes for which bitmask support library requires users to register all enum classes for which bitmask support
should be enabled: should be enabled:
```cpp ```cpp
@ -263,7 +276,7 @@ struct entt::enum_as_bitmask<my_flag>
``` ```
This is handy when dealing with enum classes defined by third party libraries This is handy when dealing with enum classes defined by third party libraries
and over which the user has no control. However, it's also verbose and can be and over which the users have no control. However, it's also verbose and can be
avoided by adding a specific value to the enum class itself: avoided by adding a specific value to the enum class itself:
```cpp ```cpp
@ -276,21 +289,23 @@ enum class my_flag {
``` ```
In this case, there is no need to specialize the `enum_as_bitmask` traits, since In this case, there is no need to specialize the `enum_as_bitmask` traits, since
`EnTT` automatically detects the flag and enables the bitmask support.<br/> `EnTT` will automatically detect the flag and enable the bitmask support.<br/>
Once the enum class is registered (in one way or the other), the most common Once the enum class has been registered (in one way or the other) all the most
operators such as `&`, `|` but also `&=` and `|=` are available for use. common operators will be available, such as `&`, `|` but also `&=` and `|=`.
Refer to the official documentation for the full list of operators. Refer to the official documentation for the full list of operators.
# Hashed strings # Hashed strings
Hashed strings are human-readable identifiers in the codebase that turn into A hashed string is a zero overhead unique identifier. Users can use
numeric values at runtime, thus without affecting performance.<br/> human-readable identifiers in the codebase while using their numeric
counterparts at runtime, thus without affecting performance.<br/>
The class has an implicit `constexpr` constructor that chews a bunch of The class has an implicit `constexpr` constructor that chews a bunch of
characters. Once created, one can get the original string by means of the `data` characters. Once created, all what one can do with it is getting back the
member function or convert the instance into a number.<br/> original string through the `data` member function or converting the instance
A hashed string is well suited wherever a constant expression is required. No into a number.<br/>
_string-to-number_ conversion will take place at runtime if used carefully. The good part is that a hashed string can be used wherever a constant expression
is required and no _string-to-number_ conversion will take place at runtime if
used carefully.
Example of use: Example of use:
@ -303,18 +318,19 @@ auto resource = load(entt::hashed_string{"gui/background"});
``` ```
There is also a _user defined literal_ dedicated to hashed strings to make them There is also a _user defined literal_ dedicated to hashed strings to make them
more _user-friendly_: more user-friendly:
```cpp ```cpp
using namespace entt::literals; using namespace entt::literals;
constexpr auto str = "text"_hs; constexpr auto str = "text"_hs;
``` ```
User defined literals in `EnTT` are enclosed in the `entt::literals` namespace. To use it, remember that all user defined literals in `EnTT` are enclosed in the
Therefore, the entire namespace or selectively the literal of interest must be `entt::literals` namespace. Therefore, the entire namespace or selectively the
explicitly included before each use, a bit like `std::literals`.<br/> literal of interest must be explicitly included before each use, a bit like
The class also offers the necessary functionalities to create hashed strings at `std::literals`.<br/>
runtime: Finally, in case users need to create hashed strings at runtime, this class also
offers the necessary functionalities:
```cpp ```cpp
std::string orig{"text"}; std::string orig{"text"};
@ -327,14 +343,16 @@ const auto hash = entt::hashed_string::value(orig.c_str());
``` ```
This possibility shouldn't be exploited in tight loops, since the computation This possibility shouldn't be exploited in tight loops, since the computation
takes place at runtime and no longer at compile-time. It could therefore affect takes place at runtime and no longer at compile-time and could therefore impact
performance to some degrees. performance to some degrees.
## Wide characters ## Wide characters
The `hashed_string` class is an alias for `basic_hashed_string<char>`. To use The hashed string has a design that is close to that of an `std::basic_string`.
the C++ type for wide character representations, there exists also the alias It means that `hashed_string` is nothing more than an alias for
`hashed_wstring` for `basic_hashed_string<wchar_t>`.<br/> `basic_hashed_string<char>`. For those who want to use the C++ type for wide
character representation, there exists also the alias `hashed_wstring` for
`basic_hashed_string<wchar_t>`.<br/>
In this case, the user defined literal to use to create hashed strings on the In this case, the user defined literal to use to create hashed strings on the
fly is `_hws`: fly is `_hws`:
@ -342,15 +360,16 @@ fly is `_hws`:
constexpr auto str = L"text"_hws; constexpr auto str = L"text"_hws;
``` ```
The hash type of `hashed_wstring` is the same as its counterpart. Note that the hash type of the `hashed_wstring` is the same of its counterpart.
## Conflicts ## Conflicts
The hashed string class uses FNV-1a internally to hash strings. Because of the The hashed string class uses internally FNV-1a to compute the numeric
_pigeonhole principle_, conflicts are possible. This is a fact.<br/> counterpart of a string. Because of the _pigeonhole principle_, conflicts are
possible. This is a fact.<br/>
There is no silver bullet to solve the problem of conflicts when dealing with There is no silver bullet to solve the problem of conflicts when dealing with
hashing functions. In this case, the best solution is likely to give up. That's hashing functions. In this case, the best solution seemed to be to give up.
all.<br/> That's all.<br/>
After all, human-readable unique identifiers aren't something strictly defined After all, human-readable unique identifiers aren't something strictly defined
and over which users have not the control. Choosing a slightly different and over which users have not the control. Choosing a slightly different
identifier is probably the best solution to make the conflict disappear in this identifier is probably the best solution to make the conflict disappear in this
@ -358,8 +377,8 @@ case.
# Iterators # Iterators
Writing and working with iterators isn't always easy. More often than not it Writing and working with iterators isn't always easy and more often than not
also leads to duplicated code.<br/> leads to duplicated code.<br/>
`EnTT` tries to overcome this problem by offering some utilities designed to `EnTT` tries to overcome this problem by offering some utilities designed to
make this hard work easier. make this hard work easier.
@ -412,7 +431,7 @@ user.
## Iterable adaptor ## Iterable adaptor
Typically, a container class provides `begin` and `end` member functions (with Typically, a container class provides `begin` and `end` member functions (with
their const counterparts) for iteration.<br/> their const counterparts) to be iterated by the user.<br/>
However, it can happen that a class offers multiple iteration methods or allows However, it can happen that a class offers multiple iteration methods or allows
users to iterate different sets of _elements_. users to iterate different sets of _elements_.
@ -433,8 +452,8 @@ by returning an iterable object for the purpose.
There are a handful of tools within `EnTT` to interact with memory in one way or There are a handful of tools within `EnTT` to interact with memory in one way or
another.<br/> another.<br/>
Some are geared towards simplifying the implementation of (internal or external) Some are geared towards simplifying the implementation of (internal or external)
allocator aware containers. Others are designed to help the developer with allocator aware containers. Others, on the other hand, are designed to help the
everyday problems. developer with everyday problems.
The former are very specific and for niche problems. These are tools designed to The former are very specific and for niche problems. These are tools designed to
unwrap fancy or plain pointers (`to_address`) or to help forget the meaning of unwrap fancy or plain pointers (`to_address`) or to help forget the meaning of
@ -462,7 +481,7 @@ modulus and for this reason preferred in many areas.
A nasty thing in C++ (at least up to C++20) is the fact that shared pointers A nasty thing in C++ (at least up to C++20) is the fact that shared pointers
support allocators while unique pointers don't.<br/> support allocators while unique pointers don't.<br/>
There is a proposal at the moment that also shows (among the other things) how There is a proposal at the moment that also shows among the other things how
this can be implemented without any compiler support. this can be implemented without any compiler support.
The `allocate_unique` function follows this proposal, making a virtue out of The `allocate_unique` function follows this proposal, making a virtue out of
@ -479,16 +498,13 @@ the same feature.
# Monostate # Monostate
The monostate pattern is often presented as an alternative to a singleton based The monostate pattern is often presented as an alternative to a singleton based
configuration system.<br/> configuration system. This is exactly its purpose in `EnTT`. Moreover, this
This is exactly its purpose in `EnTT`. Moreover, this implementation is thread implementation is thread safe by design (hopefully).<br/>
safe by design (hopefully). Keys are represented by hashed strings, values are basic types like `int`s or
`bool`s. Values of different types can be associated to each key, even more than
Keys are integral values (easily obtained by hashed strings), values are basic one at a time. Because of this, users must pay attention to use the same type
types like `int`s or `bool`s. Values of different types can be associated with both during an assignment and when they try to read back their data. Otherwise,
each key, even more than one at a time.<br/> they will probably incur in unexpected results.
Because of this, one should pay attention to use the same type both during an
assignment and when trying to read back the data. Otherwise, there is the risk
to incur in unexpected results.
Example of use: Example of use:
@ -526,10 +542,17 @@ Basically, the whole system relies on a handful of classes. In particular:
auto index = entt::type_index<a_type>::value(); auto index = entt::type_index<a_type>::value();
``` ```
The returned value isn't guaranteed to be stable across different runs.<br/> The returned value isn't guaranteed to be stable across different runs.
However, it can be very useful as index in associative and unordered However, it can be very useful as index in associative and unordered
associative containers or for positional accesses in a vector or an array. associative containers or for positional accesses in a vector or an array.
So as not to conflict with the other tools available, the `family` class isn't
used to generate these indexes. Therefore, the numeric identifiers returned by
the two tools may differ.<br/>
On the other hand, this leaves users with full powers over the `family` class
and therefore the generation of custom runtime sequences of indices for their
own purposes, if necessary.
An external generator can also be used if needed. In fact, `type_index` can be An external generator can also be used if needed. In fact, `type_index` can be
specialized by type and is also _sfinae-friendly_ in order to allow more specialized by type and is also _sfinae-friendly_ in order to allow more
refined specializations such as: refined specializations such as:
@ -543,7 +566,7 @@ Basically, the whole system relies on a handful of classes. In particular:
}; };
``` ```
Indexes **must** be sequentially generated in this case.<br/> Note that indexes **must** still be generated sequentially in this case.<br/>
The tool is widely used within `EnTT`. Generating indices not sequentially The tool is widely used within `EnTT`. Generating indices not sequentially
would break an assumption and would likely lead to undesired behaviors. would break an assumption and would likely lead to undesired behaviors.
@ -560,14 +583,14 @@ Basically, the whole system relies on a handful of classes. In particular:
This function **can** use non-standard features of the language for its own This function **can** use non-standard features of the language for its own
purposes. This makes it possible to provide compile-time identifiers that purposes. This makes it possible to provide compile-time identifiers that
remain stable across different runs.<br/> remain stable across different runs.<br/>
Users can prevent the library from using these features by means of the In all cases, users can prevent the library from using these features by means
`ENTT_STANDARD_CPP` definition. In this case, there is no guarantee that of the `ENTT_STANDARD_CPP` definition. In this case, there is no guarantee
identifiers remain stable across executions. Moreover, they are generated that identifiers remain stable across executions. Moreover, they are generated
at runtime and are no longer a compile-time thing. at runtime and are no longer a compile-time thing.
As it happens with `type_index`, also `type_hash` is a _sfinae-friendly_ class As for `type_index`, also `type_hash` is a _sfinae-friendly_ class that can be
that can be specialized in order to customize its behavior globally or on a specialized in order to customize its behavior globally or on a per-type or
per-type or per-traits basis. per-traits basis.
* The name associated with a given type: * The name associated with a given type:
@ -575,9 +598,10 @@ Basically, the whole system relies on a handful of classes. In particular:
auto name = entt::type_name<a_type>::value(); auto name = entt::type_name<a_type>::value();
``` ```
This value is extracted from some information generally made available by the The name associated with a type is extracted from some information generally
compiler in use. Therefore, it may differ depending on the compiler and may be made available by the compiler in use. Therefore, it may differ depending on
empty in the event that this information isn't available.<br/> the compiler and may be empty in the event that this information isn't
available.<br/>
For example, given the following class: For example, given the following class:
```cpp ```cpp
@ -588,20 +612,21 @@ Basically, the whole system relies on a handful of classes. In particular:
when MSVC is in use.<br/> when MSVC is in use.<br/>
Most of the time the name is also retrieved at compile-time and is therefore Most of the time the name is also retrieved at compile-time and is therefore
always returned through an `std::string_view`. Users can easily access it and always returned through an `std::string_view`. Users can easily access it and
modify it as needed, for example by removing the word `struct` to normalize modify it as needed, for example by removing the word `struct` to standardize
the result. `EnTT` doesn't do this for obvious reasons, since it would be the result. `EnTT` won't do this for obvious reasons, since it requires
creating a new string at runtime otherwise. copying and creating a new string potentially at runtime.
This function **can** use non-standard features of the language for its own This function **can** use non-standard features of the language for its own
purposes. Users can prevent the library from using these features by means of purposes. Users can prevent the library from using non-standard features by
the `ENTT_STANDARD_CPP` definition. In this case, the name is just empty. means of the `ENTT_STANDARD_CPP` definition. In this case, the name will be
empty by default.
As it happens with `type_index`, also `type_name` is a _sfinae-friendly_ class As for `type_index`, also `type_name` is a _sfinae-friendly_ class that can be
that can be specialized in order to customize its behavior globally or on a specialized in order to customize its behavior globally or on a per-type or
per-type or per-traits basis. per-traits basis.
These are then combined into utilities that aim to offer an API that is somewhat These are then combined into utilities that aim to offer an API that is somewhat
similar to that made available by the standard library. similar to that offered by the language.
### Type info ### Type info
@ -683,8 +708,8 @@ In fact, although this is quite rare, it's not entirely excluded.
Another case where two types are assigned the same identifier is when classes Another case where two types are assigned the same identifier is when classes
from different contexts (for example two or more libraries loaded at runtime) from different contexts (for example two or more libraries loaded at runtime)
have the same fully qualified name. In this case, `type_name` returns the same have the same fully qualified name. In this case, also `type_name` will return
value for the two types.<br/> the same value for the two types.<br/>
Fortunately, there are several easy ways to deal with this: Fortunately, there are several easy ways to deal with this:
* The most trivial one is to define the `ENTT_STANDARD_CPP` macro. Runtime * The most trivial one is to define the `ENTT_STANDARD_CPP` macro. Runtime
@ -714,9 +739,9 @@ offered by this module.
### Size of ### Size of
The standard operator `sizeof` complains if users provide it with functions or The standard operator `sizeof` complains when users provide it for example with
incomplete types. On the other hand, it's guaranteed that its result is always function or incomplete types. On the other hand, it's guaranteed that its result
non-zero, even if applied to an empty class type.<br/> is always nonzero, even if applied to an empty class type.<br/>
This small class combines the two and offers an alternative to `sizeof` that This small class combines the two and offers an alternative to `sizeof` that
works under all circumstances, returning zero if the type isn't supported: works under all circumstances, returning zero if the type isn't supported:
@ -752,8 +777,8 @@ A utility to easily transfer the constness of a type to another type:
using type = entt::constness_as_t<dst_type, const src_type>; using type = entt::constness_as_t<dst_type, const src_type>;
``` ```
The trait is subject to the rules of the language. For example, _transferring_ The trait is subject to the rules of the language. Therefore, for example,
constness between references won't give the desired effect. transferring constness between references won't give the desired effect.
### Member class type ### Member class type
@ -773,7 +798,7 @@ A utility to quickly find the n-th argument of a function, member function or
data member (for blind operations on opaque types): data member (for blind operations on opaque types):
```cpp ```cpp
using type = entt::nth_argument_t<1u, &clazz::member>; using type = entt::nt_argument_t<1u, &clazz::member>;
``` ```
Disambiguation of overloaded functions is the responsibility of the user, should Disambiguation of overloaded functions is the responsibility of the user, should
@ -800,8 +825,8 @@ registry.emplace<enemy_tag>(entity);
### Tag ### Tag
Type `id_type` is very important and widely used in `EnTT`. Therefore, there is Since `id_type` is very important and widely used in `EnTT`, there is a more
a more user-friendly shortcut for the creation of constants based on it.<br/> user-friendly shortcut for the creation of integral constants based on it.<br/>
This shortcut is the alias template `entt::tag`. This shortcut is the alias template `entt::tag`.
If used in combination with hashed strings, it helps to use human-readable names If used in combination with hashed strings, it helps to use human-readable names
@ -893,8 +918,8 @@ Perhaps a bit ugly to see in a codebase but it gets the job done at least.
## Runtime generator ## Runtime generator
The `family` class template helps to generate sequential numeric identifiers for To generate sequential numeric identifiers at runtime, `EnTT` offers the
types at runtime: `family` class template:
```cpp ```cpp
// defines a custom generator // defines a custom generator
@ -911,8 +936,8 @@ numeric identifier for the given type.<br/>
The generator is customizable, so as to get different _sequences_ for different The generator is customizable, so as to get different _sequences_ for different
purposes if needed. purposes if needed.
Identifiers aren't guaranteed to be stable across different runs. Indeed it Please, note that identifiers aren't guaranteed to be stable across different
mostly depends on the flow of execution. runs. Indeed it mostly depends on the flow of execution.
# Utilities # Utilities

File diff suppressed because it is too large Load Diff

View File

@ -193,7 +193,7 @@ to mitigate the problem makes it manageable.
## Which functions trigger which signals ## Which functions trigger which signals
Storage classes offer three _signals_ that are emitted following specific The `registry` class offers three signals that are emitted following specific
operations. Maybe not everyone knows what these operations are, though.<br/> operations. Maybe not everyone knows what these operations are, though.<br/>
If this isn't clear, below you can find a _vademecum_ for this purpose: If this isn't clear, below you can find a _vademecum_ for this purpose:

View File

@ -14,6 +14,7 @@
* [Fake resources and order of execution](#fake-resources-and-order-of-execution) * [Fake resources and order of execution](#fake-resources-and-order-of-execution)
* [Sync points](#sync-points) * [Sync points](#sync-points)
* [Execution graph](#execution-graph) * [Execution graph](#execution-graph)
<!-- <!--
@endcond TURN_OFF_DOXYGEN @endcond TURN_OFF_DOXYGEN
--> -->
@ -22,15 +23,15 @@
`EnTT` doesn't aim to offer everything one needs to work with graphs. Therefore, `EnTT` doesn't aim to offer everything one needs to work with graphs. Therefore,
anyone looking for this in the _graph_ submodule will be disappointed.<br/> 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 Quite the opposite is true. This submodule is minimal and contains only the data
the data structures and algorithms strictly necessary for the development of structures and algorithms strictly necessary for the development of some tools
some tools such as the _flow builder_. such as the _flow builder_.
# Data structures # Data structures
As anticipated in the introduction, the aim isn't to offer all possible data As anticipated in the introduction, the aim isn't to offer all possible data
structures suitable for representing and working with graphs. Many will likely structures suitable for representing and working with graphs. Many will likely
be added or refined over time. However I want to discourage anyone expecting be added or refined over time, however I want to discourage anyone expecting
tight scheduling on the subject.<br/> tight scheduling on the subject.<br/>
The data structures presented in this section are mainly useful for the The data structures presented in this section are mainly useful for the
development and support of some tools which are also part of the same submodule. development and support of some tools which are also part of the same submodule.
@ -48,7 +49,7 @@ The `directed_tag` type _creates_ the graph as directed. There is also an
`undirected_tag` counterpart which creates it as undirected.<br/> `undirected_tag` counterpart which creates it as undirected.<br/>
The interface deviates slightly from the typical double indexing of C and offers 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 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 and modification of an element will take place via the `contains`, `insert` and
`erase` functions rather than a double call to an `operator[]`: `erase` functions rather than a double call to an `operator[]`:
```cpp ```cpp
@ -59,14 +60,14 @@ if(adjacency_matrix.contains(0u, 1u)) {
} }
``` ```
Both `insert` and` erase` are _idempotent_ functions which have no effect if the Both `insert` and` erase` are idempotent functions which have no effect if the
element already exists or has already been deleted.<br/> element already exists or has already been deleted.<br/>
The first one returns an `std::pair` containing the iterator to the element and 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 a boolean value indicating whether the element has been inserted or was already
second one returns the number of deleted elements (0 or 1). present. The second one instead returns the number of deleted elements (0 or 1).
An adjacency matrix is initialized with the number of elements (vertices) when An adjacency matrix must be initialized with the number of elements (vertices)
constructing it but can also be resized later using the `resize` function: when constructing it but can also be resized later using the `resize` function:
```cpp ```cpp
entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{3u}; entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{3u};
@ -81,8 +82,8 @@ for(auto &&vertex: adjacency_matrix.vertices()) {
} }
``` ```
The same result is obtained with the following snippet, since the vertices are Note that the same result can be obtained with the following snippet, since the
plain unsigned integral values: vertices are unsigned integral values:
```cpp ```cpp
for(auto last = adjacency_matrix.size(), pos = {}; pos < last; ++pos) { for(auto last = adjacency_matrix.size(), pos = {}; pos < last; ++pos) {
@ -92,8 +93,8 @@ for(auto last = adjacency_matrix.size(), pos = {}; pos < last; ++pos) {
As for visiting the edges, a few functions are available.<br/> 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 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 `edges` function returns an iterable object that can be used to get them as
vertices: pairs of vertices:
```cpp ```cpp
for(auto [lhs, rhs]: adjacency_matrix.edges()) { for(auto [lhs, rhs]: adjacency_matrix.edges()) {
@ -101,8 +102,8 @@ 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 On the other hand, if the goal is to visit all the in- or out-edges of a given
`in_edges` and `out_edges` functions are meant for that: vertex, the `in_edges` and `out_edges` functions are meant for that:
```cpp ```cpp
for(auto [lhs, rhs]: adjacency_matrix.out_edges(3u)) { for(auto [lhs, rhs]: adjacency_matrix.out_edges(3u)) {
@ -110,11 +111,11 @@ for(auto [lhs, rhs]: adjacency_matrix.out_edges(3u)) {
} }
``` ```
Both the functions expect the vertex to visit (that is, to return the in- or As might be expected, these functions expect the vertex to visit (that is, to
out-edges for) as an argument.<br/> return the in- or out-edges for) as an argument.<br/>
Finally, the adjacency matrix is an allocator-aware container and offers most of 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 the functionality one would expect from this type of containers, such as `clear`
`clear` or 'get_allocator` and so on. or 'get_allocator` and so on.
## Graphviz dot language ## Graphviz dot language
@ -128,19 +129,19 @@ std::ostringstream output{};
entt::dot(output, adjacency_matrix); entt::dot(output, adjacency_matrix);
``` ```
It's also possible to provide a callback to which the vertices are passed and However, there is also the option of providing a callback to which the vertices
which can be used to add (`dot`) properties to the output as needed: are passed and which can be used to add (`dot`) properties to the output from
time to time:
```cpp ```cpp
std::ostringstream output{}; std::ostringstream output{};
entt::dot(output, adjacency_matrix, [](auto &output, auto vertex) { entt::dot(output, adjacency_matrix, [](auto &output, auto vertex) {
out << "label=\"v\"" << vertex << ",shape=\"box\""; out << "label=\"v\"" << vertex << ",shape=\"box\"";
}); });
``` ```
This second mode is particularly convenient when the user wants to associate This second mode is particularly convenient when the user wants to associate
externally managed data to the graph being converted. data managed externally to the graph being converted.
# Flow builder # Flow builder
@ -154,42 +155,42 @@ specified.<br/>
Most of the functions in the API also return the flow builder itself, according 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. 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 Once all tasks have been registered and resources assigned to them, an execution
in the form of an adjacency matrix is returned to the user.<br/> 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 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 _vertices_. The _vertex_ itself can be used as an index to get the identifier
during registration. passed during registration.
## Tasks and resources ## Tasks and resources
Although these terms are used extensively in the documentation, the flow builder Although these terms are used extensively in the documentation, the flow builder
has no real concept of tasks and resources.<br/> has no real concept of tasks and resources.<br/>
This class works mainly with _identifiers_, that is, values of type `id_type`. 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/> That is, both tasks and resources are identified by integral values.<br/>
This allows not to couple the class itself to the rest of the library or to any 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 particular data structure. On the other hand, it requires the user to keep track
of the association between identifiers and operations or actual data. of the association between identifiers and operations or actual data.
Once a flow builder is created (which requires no constructor arguments), the Once a flow builder has been created (which requires no constructor arguments),
first thing to do is to bind a task. This tells to the builder _who_ intends to the first thing to do is to bind a task. This will indicate to the builder who
consume the resources that are specified immediately after: intends to consume the resources that will be specified immediately after:
```cpp ```cpp
entt::flow builder{}; entt::flow builder{};
builder.bind("task_1"_hs); builder.bind("task_1"_hs);
``` ```
The example uses the `EnTT` hashed string to generate an identifier for the Note that the example uses the `EnTT` hashed string to generate an identifier
task.<br/> for the task.<br/>
Indeed, the use of `id_type` as an identifier type isn't by accident. In fact, Indeed, the use of `id_type` as an identifier type is not by accident. In fact,
it matches well with the internal hashed string class. Moreover, it's also the it matches well with the internal hashed string class. Moreover, it's also the
same type returned by the hash function of the internal RTTI system, in case the same type returned by the hash function of the internal RTTI system, in case the
user wants to rely on that.<br/> user wants to rely on that.<br/>
However, being an integral value, it leaves the user full freedom to rely on his However, being an integral value, it leaves the user full freedom to rely on his
own tools if necessary. own tools if he deems it necessary.
Once a task is associated with the flow builder, it's also assigned read-only or Once a task has been associated with the flow builder, it can be assigned
read-write resources as appropriate: read-only or read-write resources, as appropriate:
```cpp ```cpp
builder builder
@ -202,87 +203,13 @@ builder
As mentioned, many functions return the builder itself and it's therefore easy As mentioned, many functions return the builder itself and it's therefore easy
to concatenate the different calls.<br/> to concatenate the different calls.<br/>
Also in the case of resources, they are identified by numeric values of type Also in the case of resources, these are identified by numeric values of type
`id_type`. As above, the choice is not entirely random. This goes well with the `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. 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 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. 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's 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's 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 doesn't 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-write request from the second task and finally
a new read-only request from the first task.<br/>
Although this definition would probably be counted as an error, the resulting
graph may be unexpected. This in fact consists of a single edge outgoing from
the second task and directed to the first task.<br/>
To intuitively understand what happens, it's 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 don't know what they're doing.<br/>
However, once the mechanisms of resource-based graph generation are understood,
it can offer to the expert user a flexibility and a range of possibilities
otherwise inaccessible.
## Fake resources and order of execution ## Fake resources and order of execution
The flow builder doesn't offer the ability to specify when a task should execute The flow builder doesn't offer the ability to specify when a task should execute
@ -290,10 +217,10 @@ before or after another task.<br/>
In fact, the order of _registration_ on the resources also determines the order 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. 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/> 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 Briefly, since accessing a resource in opposite modes requires sequential rather
than parallel scheduling, it's possible to make use of fake resources to rule on than parallel scheduling, it's possible to make use of fake resources to force
the execution order: the order execution:
```cpp ```cpp
builder builder
@ -308,10 +235,10 @@ builder
.ro("fake"_hs) .ro("fake"_hs)
``` ```
This snippet forces the execution of `task_1` **before** `task_2` and `task_3`. This snippet forces the execution of `task_2` and `task_3` **after** `task_1`.
This is due to the fact that the former sets a read-write requirement on a fake This is due to the fact that the latter sets a read-write requirement on a fake
resource that the other tasks also want to access in read-only mode.<br/> resource that the other tasks also want to access in read-only mode.<br/>
Similarly, it's possible to force a task to run **after** a certain group: Similarly, it's possible to force a task to run after a certain group:
```cpp ```cpp
builder builder
@ -334,7 +261,7 @@ others tasks.
Sometimes it's useful to assign the role of _sync point_ to a node.<br/> Sometimes it's useful to assign the role of _sync point_ to a node.<br/>
Whether it accesses new resources or is simply a watershed, the procedure for Whether it accesses new resources or is simply a watershed, the procedure for
assigning this role to a vertex is always the same. First it's tied to the flow assigning this role to a vertex is always the same: first it's tied to the flow
builder, then the `sync` function is invoked: builder, then the `sync` function is invoked:
```cpp ```cpp
@ -356,7 +283,7 @@ all specified constraints to return the best scheduling for the vertices:
entt::adjacency_matrix<entt::directed_tag> graph = builder.graph(); entt::adjacency_matrix<entt::directed_tag> graph = builder.graph();
``` ```
Searching for the main vertices (that is, those without in-edges) is usually the The search for the main vertices, that is those without in-edges, is usually the
first thing required: first thing required:
```cpp ```cpp
@ -367,6 +294,6 @@ for(auto &&vertex: graph) {
} }
``` ```
Then it's possible to instantiate an execution graph by means of other functions Starting from them, using the other functions appropriately (such as `out_edges`
such as `out_edges` to retrieve the children of a given task or `edges` to get to retrieve the children of a given task or `edges` to access their identifiers)
the identifiers. it will be possible to instantiate an execution graph.

View File

@ -19,12 +19,14 @@
general and on GNU/Linux when default visibility was set to hidden. The general and on GNU/Linux when default visibility was set to hidden. The
limitation was mainly due to a custom utility used to assign unique, sequential limitation was mainly due to a custom utility used to assign unique, sequential
identifiers with different types.<br/> identifiers with different types.<br/>
Fortunately, nowadays `EnTT` works smoothly across boundaries. Fortunately, nowadays using `EnTT` across boundaries is much easier.
## Smooth until proven otherwise ## Smooth until proven otherwise
Many classes in `EnTT` make extensive use of type erasure for their purposes. Many classes in `EnTT` make extensive use of type erasure for their purposes.
This raises the need to identify objects whose type has been erased.<br/> This isn't a problem on itself (in fact, it's the basis of an API so convenient
to use). However, a way is needed to recognize the objects whose type has been
erased on the other side of a boundary.<br/>
The `type_hash` class template is how identifiers are generated and thus made The `type_hash` class template is how identifiers are generated and thus made
available to the rest of the library. In general, this class doesn't arouse much available to the rest of the library. In general, this class doesn't arouse much
interest. The only exception is when a conflict between identifiers occurs interest. The only exception is when a conflict between identifiers occurs
@ -34,13 +36,13 @@ The section dedicated to `type_info` contains all the details to get around the
issue in a concise and elegant way. Please refer to the specific documentation. issue in a concise and elegant way. Please refer to the specific documentation.
When working with linked libraries, compile definitions `ENTT_API_EXPORT` and When working with linked libraries, compile definitions `ENTT_API_EXPORT` and
`ENTT_API_IMPORT` are to import or export symbols, so as to make everything work `ENTT_API_IMPORT` can be used where there is a need to import or export symbols,
nicely across boundaries.<br/> so as to make everything work nicely across boundaries.<br/>
On the other hand, everything should run smoothly when working with plugins or On the other hand, everything should run smoothly when working with plugins or
shared libraries that don't export any symbols. shared libraries that don't export any symbols.
For those who need more details, the test suite contains many examples covering For anyone who needs more details, the test suite contains multiple examples
the most common cases (see the `lib` directory for all details).<br/> covering the most common cases (see the `lib` directory for all details).<br/>
It goes without saying that it's impossible to cover **all** possible cases. It goes without saying that it's impossible to cover **all** possible cases.
However, what is offered should hopefully serve as a basis for all of them. However, what is offered should hopefully serve as a basis for all of them.
@ -68,8 +70,8 @@ entt::locator<entt::meta_ctx>::reset(handle);
From now on, both spaces refer to the same context and on it are attached all From now on, both spaces refer to the same context and on it are attached all
new meta types, no matter where they are created.<br/> new meta types, no matter where they are created.<br/>
Note that _replacing_ the main context doesn't also propagate changes across Note that resetting the main context doesn't also propagate changes across
boundaries. In other words, replacing a context results in the decoupling of the boundaries. In other words, resetting a context results in the decoupling of the
two sides and therefore a divergence in the contents. two sides and therefore a divergence in the contents.
## Memory Management ## Memory Management

View File

@ -1,22 +1,5 @@
# EnTT in Action # EnTT in Action
<!--
@cond TURN_OFF_DOXYGEN
-->
# 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)
<!--
@endcond TURN_OFF_DOXYGEN
-->
# Introduction
`EnTT` is widely used in private and commercial applications. I cannot even `EnTT` is widely used in private and commercial applications. I cannot even
mention most of them because of some signatures I put on some documents time mention most of them because of some signatures I put on some documents time
ago. Fortunately, there are also people who took the time to implement open ago. Fortunately, there are also people who took the time to implement open
@ -24,18 +7,13 @@ source projects based on `EnTT` and didn't hold back when it came to documenting
them. them.
Below an incomplete list of games, applications and articles that can be used as Below an incomplete list of games, applications and articles that can be used as
a reference.<br/> a reference. Where I put the word _apparently_ means that the use of `EnTT` is
Where I put the word _apparently_ means that the use of `EnTT` is documented but documented but the authors didn't make explicit announcements or contacted me
the authors didn't make explicit announcements or contacted me directly. directly.
If you know of other resources out there that are about `EnTT`, feel free to I hope this list can grow much more in the future:
open an issue or a PR and I'll be glad to add them to this page.<br/>
I hope the following lists can grow much more in the future.
# EnTT in Action
## Games
* Games:
* [Minecraft](https://minecraft.net/en-us/attribution/) by * [Minecraft](https://minecraft.net/en-us/attribution/) by
[Mojang](https://mojang.com/): of course, **that** Minecraft, see the [Mojang](https://mojang.com/): of course, **that** Minecraft, see the
open source attributions page for more details. open source attributions page for more details.
@ -125,8 +103,7 @@ I hope the following lists can grow much more in the future.
* [Confetti Party](https://github.com/hexerei/entt-confetti): C++ sample * [Confetti Party](https://github.com/hexerei/entt-confetti): C++ sample
application as a starting point using `EnTT` and `SDL2`. application as a starting point using `EnTT` and `SDL2`.
## Engines and the like: * Engines and the like:
* [Aether Engine](https://hadean.com/spatial-simulation/) * [Aether Engine](https://hadean.com/spatial-simulation/)
[v1.1+](https://docs.hadean.com/v1.1/Licenses/) by [v1.1+](https://docs.hadean.com/v1.1/Licenses/) by
[Hadean](https://hadean.com/): a library designed for spatially partitioning [Hadean](https://hadean.com/): a library designed for spatially partitioning
@ -191,19 +168,8 @@ I hope the following lists can grow much more in the future.
engine based on `SDL2` and `EnTT`. engine based on `SDL2` and `EnTT`.
* [Ducktape](https://github.com/DucktapeEngine/Ducktape): an open source C++ * [Ducktape](https://github.com/DucktapeEngine/Ducktape): an open source C++
2D & 3D game engine that focuses on being fast and powerful. 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-ystem.
## Articles, videos and blog posts:
* Articles, videos and blog posts:
* [Some posts](https://skypjack.github.io/tags/#entt) on my personal * [Some posts](https://skypjack.github.io/tags/#entt) on my personal
[blog](https://skypjack.github.io/) are about `EnTT`, for those who want to [blog](https://skypjack.github.io/) are about `EnTT`, for those who want to
know **more** on this project. know **more** on this project.
@ -227,12 +193,6 @@ I hope the following lists can grow much more in the future.
- ... And so on. - ... And so on.
[Check out](https://www.youtube.com/channel/UCQ-W1KE9EYfdxhL6S4twUNw) the [Check out](https://www.youtube.com/channel/UCQ-W1KE9EYfdxhL6S4twUNw) the
_Game Engine Series_ by The Cherno for more videos. _Game Engine Series_ by The Cherno for more videos.
* [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 couldn't have said it
better.
* [Space Battle: Huge edition](http://victor.madtriangles.com/code%20experiment/2018/06/11/post-ecs-battle-huge.html): * [Space Battle: Huge edition](http://victor.madtriangles.com/code%20experiment/2018/06/11/post-ecs-battle-huge.html):
huge space battle built entirely from scratch. huge space battle built entirely from scratch.
* [Space Battle](https://github.com/vblanco20-1/ECS_SpaceBattle): huge space * [Space Battle](https://github.com/vblanco20-1/ECS_SpaceBattle): huge space
@ -259,8 +219,7 @@ I hope the following lists can grow much more in the future.
MMO(RPG)s and its [follow-up](https://youtu.be/yGlZeopx2hU) episode about 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. player bots and full external ECS: a series definitely worth looking at.
## Any Other Business: * Any Other Business:
* [ArcGIS Runtime SDKs](https://developers.arcgis.com/arcgis-runtime/) by * [ArcGIS Runtime SDKs](https://developers.arcgis.com/arcgis-runtime/) by
[Esri](https://www.esri.com/): they use `EnTT` for the internal ECS and the [Esri](https://www.esri.com/): they use `EnTT` for the internal ECS and the
cross platform C++ rendering engine. The SDKs are utilized by a lot of cross platform C++ rendering engine. The SDKs are utilized by a lot of
@ -302,3 +261,6 @@ I hope the following lists can grow much more in the future.
* GitHub contains also * GitHub contains also
[many other examples](https://github.com/search?o=desc&q=%22skypjack%2Fentt%22&s=indexed&type=Code) [many other examples](https://github.com/search?o=desc&q=%22skypjack%2Fentt%22&s=indexed&type=Code)
of use of `EnTT` from which to take inspiration if interested. of use of `EnTT` from which to take inspiration if interested.
If you know of other resources out there that are about `EnTT`, feel free to
open an issue or a PR and I'll be glad to add them to this page.

View File

@ -67,15 +67,17 @@ recommended.
# Reflection in a nutshell # Reflection in a nutshell
Reflection always starts from actual C++ types. Users cannot reflect _imaginary_ Reflection always starts from real types (users cannot reflect imaginary types
types.<br/> and it would not make much sense, we wouldn't be talking about reflection
The `meta` function is where it all starts: anymore).<br/>
To create a meta node, the library provides the `meta` function that accepts a
type to reflect as a template parameter:
```cpp ```cpp
auto factory = entt::meta<my_type>(); auto factory = entt::meta<my_type>();
``` ```
The returned value is a _factory object_ to use to continue building the meta The returned value is a factory object to use to continue building the meta
type. type.
By default, a meta type is associated with the identifier returned by the By default, a meta type is associated with the identifier returned by the
@ -86,42 +88,45 @@ However, it's also possible to assign custom identifiers to meta types:
auto factory = entt::meta<my_type>().type("reflected_type"_hs); auto factory = entt::meta<my_type>().type("reflected_type"_hs);
``` ```
Identifiers are used to _retrieve_ meta types at runtime by _name_ other than by Identifiers are important because users can retrieve meta types at runtime by
type.<br/> searching for them by _name_ other than by type.<br/>
However, users can be interested in adding features to a reflected type so that On the other hand, there are cases in which users can be interested in adding
the reflection system can use it correctly under the hood, while they don't want features to a reflected type so that the reflection system can use it correctly
to also make the type _searchable_. In this case, it's sufficient not to invoke under the hood, but they don't want to also make the type _searchable_. In this
`type`. case, it's sufficient not to invoke `type`.
A factory is such that all its member functions return the factory itself. It's A factory is such that all its member functions return the factory itself or a
generally used to create the following: decorated version of it. This object can be used to add the following:
* _Constructors_. A constructors is assigned to a reflected type by specifying * _Constructors_. Actual constructors can be assigned to a reflected type by
its _list of arguments_. Free functions are also accepted if the return type specifying their list of arguments. Free functions (namely, factories) can be
is the expected one. From a client perspective, nothing changes between a free used as well, as long as the return type is the expected one. From a client's
function or an actual constructor: point of view, nothing changes if a constructor is a free function or an
actual constructor.<br/>
Use the `ctor` member function for this purpose:
```cpp ```cpp
entt::meta<my_type>().ctor<int, char>().ctor<&factory>(); entt::meta<my_type>().ctor<int, char>().ctor<&factory>();
``` ```
Meta default constructors are implicitly generated, if possible. * _Destructors_. Free functions and member functions can be used as destructors
of reflected types. The purpose is to give users the ability to free up
* _Destructors_. Both free functions and member functions are valid destructors: resources that require special treatment before an object is actually
destroyed.<br/>
Use the `dtor` member function for this purpose:
```cpp ```cpp
entt::meta<my_type>().dtor<&destroy>(); entt::meta<my_type>().dtor<&destroy>();
``` ```
The purpose is to offer the possibility to free up resources that require
_special treatment_ before an object is actually destroyed.<br/>
A function should neither delete nor explicitly invoke the destructor of a A function should neither delete nor explicitly invoke the destructor of a
given instance. given instance.
* _Data members_. Meta data members are actual data members of the underlying * _Data members_. Both real data members of the underlying type and static and
type but also static and global variables or constants of any kind. From the global variables, as well as constants of any kind, can be attached to a meta
point of view of the client, all the variables associated with the reflected type. From the point of view of the client, all the variables associated with
type appear as if they were part of the type itself: the reflected type will appear as if they were part of the type itself.<br/>
Use the `data` member function for this purpose:
```cpp ```cpp
entt::meta<my_type>() entt::meta<my_type>()
@ -130,11 +135,13 @@ generally used to create the following:
.data<&global_variable>("global"_hs); .data<&global_variable>("global"_hs);
``` ```
The `data` function requires the identifier to use for the meta data member. The function requires as an argument the identifier to give to the meta data
Users can then access it by _name_ at runtime.<br/> once created. Users can then access meta data at runtime by searching for them
Data members are also defined by means of a setter and getter pair. These are by _name_.<br/>
either free functions, class members or a mix of them. This approach is also Data members can also be defined by means of a setter and getter pair. Setters
convenient to create read-only properties from a non-const data member: and getters can be either free functions, class members or a mix of them, as
long as they respect the required signatures. This approach is also convenient
to create a read-only variable from a non-const data member:
```cpp ```cpp
entt::meta<my_type>().data<nullptr, &my_type::data_member>("member"_hs); entt::meta<my_type>().data<nullptr, &my_type::data_member>("member"_hs);
@ -146,10 +153,13 @@ generally used to create the following:
entt::meta<my_type>().data<entt::value_list<&from_int, &from_string>, &my_type::data_member>("member"_hs); entt::meta<my_type>().data<entt::value_list<&from_int, &from_string>, &my_type::data_member>("member"_hs);
``` ```
* _Member functions_. Meta member functions are actual member functions of the Refer to the inline documentation for all the details.
underlying type but also plain free functions. From the point of view of the
client, all the functions associated with the reflected type appear as if they * _Member functions_. Both real member functions of the underlying type and free
were part of the type itself: functions can be attached to a meta type. From the point of view of the
client, all the functions associated with the reflected type will appear as if
they were part of the type itself.<br/>
Use the `func` member function for this purpose:
```cpp ```cpp
entt::meta<my_type>() entt::meta<my_type>()
@ -158,31 +168,40 @@ generally used to create the following:
.func<&free_function>("free"_hs); .func<&free_function>("free"_hs);
``` ```
The `func` function requires the identifier to use for the meta data function. The function requires as an argument the identifier to give to the meta
Users can then access it by _name_ at runtime.<br/> function once created. Users can then access meta functions at runtime by
searching for them by _name_.<br/>
Overloading of meta functions is supported. Overloaded functions are resolved Overloading of meta functions is supported. Overloaded functions are resolved
at runtime by the reflection system according to the types of the arguments. at runtime by the reflection system according to the types of the arguments.
* _Base classes_. A base class is such that the underlying type is actually * _Base classes_. A base class is such that the underlying type is actually
derived from it: derived from it. In this case, the reflection system tracks the relationship
and allows for implicit casts at runtime when required.<br/>
Use the `base` member function for this purpose:
```cpp ```cpp
entt::meta<derived_type>().base<base_type>(); entt::meta<derived_type>().base<base_type>();
``` ```
The reflection system tracks the relationship and allows for implicit casts at From now on, wherever a `base_type` is required, an instance of `derived_type`
runtime when required. In other terms, wherever a `base_type` is required, an will also be accepted.
instance of `derived_type` is also accepted.
* _Conversion functions_. Conversion functions allow users to define conversions * _Conversion functions_. Actual types can be converted, this is a fact. Just
that are implicitly performed by the reflection system when required: think of the relationship between a `double` and an `int` to see it. Similar
to bases, conversion functions allow users to define conversions that will be
implicitly performed by the reflection system when required.<br/>
Use the `conv` member function for this purpose:
```cpp ```cpp
entt::meta<double>().conv<int>(); entt::meta<double>().conv<int>();
``` ```
This is everything users need to create meta types. Refer to the inline That's all, everything users need to create meta types and enjoy the reflection
documentation for further details. system. At first glance it may not seem that much, but users usually learn to
appreciate it over time.<br/>
Also, do not forget what these few lines hide under the hood: a built-in,
non-intrusive and macro-free system for reflection in C++. Features that are
definitely worth the price, at least for me.
## Any to the rescue ## Any to the rescue
@ -195,13 +214,13 @@ The API is very similar to that of the `any` type. The class `meta_any` _wraps_
many of the feature to infer a meta node, before forwarding some or all of the many of the feature to infer a meta node, before forwarding some or all of the
arguments to the underlying storage.<br/> arguments to the underlying storage.<br/>
Among the few relevant differences, `meta_any` adds support for containers and Among the few relevant differences, `meta_any` adds support for containers and
pointer-like types, while `any` doesn't.<br/> pointer-like types (see the following sections for more details), while `any`
Similar to `any`, this class is also used to create _aliases_ for unmanaged does not.<br/>
Similar to `any`, this class can also be used to create _aliases_ for unmanaged
objects either with `forward_as_meta` or using the `std::in_place_type<T &>` objects either with `forward_as_meta` or using the `std::in_place_type<T &>`
disambiguation tag, as well as from an existing object by means of the `as_ref` disambiguation tag, as well as from an existing object by means of the `as_ref`
member function.<br/> member function. However, unlike `any`, `meta_any` treats an empty instance and
Unlike `any` instead, `meta_any` treats an empty instance and one initialized one initialized with `void` differently:
with `void` differently:
```cpp ```cpp
entt::meta_any empty{}; entt::meta_any empty{};
@ -210,19 +229,21 @@ entt::meta_any other{std::in_place_type<void>};
While `any` considers both as empty, `meta_any` treats objects initialized with While `any` considers both as empty, `meta_any` treats objects initialized with
`void` as if they were _valid_ ones. This allows to differentiate between failed `void` as if they were _valid_ ones. This allows to differentiate between failed
function calls and function calls that are successful but return nothing. function calls and function calls that are successful but return nothing.<br/>
Finally, the member functions `try_cast`, `cast` and `allow_cast` are used to Finally, the member functions `try_cast`, `cast` and `allow_cast` are used to
cast the underlying object to a given type (either a reference or a value type) cast the underlying object to a given type (either a reference or a value type)
or to _convert_ a `meta_any` in such a way that a cast becomes viable for the or to _convert_ a `meta_any` in such a way that a cast becomes viable for the
resulting object.<br/> resulting object. There is in fact no `any_cast` equivalent for `meta_any`.
There is in fact no `any_cast` equivalent for `meta_any`.
## Enjoy the runtime ## Enjoy the runtime
Once the web of reflected types is constructed, it's a matter of using it at Once the web of reflected types has been constructed, it's a matter of using it
runtime where required.<br/> at runtime where required.<br/>
There are a few options to search for a reflected type: All this has the great merit that the reflection system stands in fact as a
non-intrusive tool for the runtime, unlike the vast majority of the things
offered by this library and closely linked to the compile-time.
To search for a reflected type there are a few options:
```cpp ```cpp
// direct access to a reflected type // direct access to a reflected type
@ -236,8 +257,8 @@ auto by_type_id = entt::resolve(entt::type_id<my_type>());
``` ```
There exists also an overload of the `resolve` function to use to iterate all There exists also an overload of the `resolve` function to use to iterate all
reflected types at once. It returns an iterable object to be used in a range-for the reflected types at once. It returns an iterable object that can be used in a
loop: range-for loop:
```cpp ```cpp
for(auto &&[id, type]: entt::resolve()) { for(auto &&[id, type]: entt::resolve()) {
@ -249,7 +270,9 @@ In all cases, the returned value is an instance of `meta_type` (possibly with
its id). This kind of objects offer an API to know their _runtime identifiers_, its id). This kind of objects offer an API to know their _runtime identifiers_,
to iterate all the meta objects associated with them and even to build instances to iterate all the meta objects associated with them and even to build instances
of the underlying type.<br/> of the underlying type.<br/>
Meta data members and functions are accessed by name: Refer to the inline documentation for all the details.
Meta data members and functions are accessed by name among the other things:
* Meta data members: * Meta data members:
@ -274,11 +297,11 @@ Meta data members and functions are accessed by name:
A meta function object offers an API to query the underlying type (for A meta function object offers an API to query the underlying type (for
example, to know if it's a const or a static function), to know the number of example, to know if it's a const or a static function), to know the number of
arguments, the meta return type and the meta types of the parameters. In arguments, the meta return type and the meta types of the parameters. In
addition, a meta function object is used to invoke the underlying function and addition, a meta function object can be used to invoke the underlying function
then get the return value in the form of a `meta_any` object. and then get the return value in the form of a `meta_any` object.
All the meta objects thus obtained as well as the meta types explicitly convert All the meta objects thus obtained as well as the meta types can be explicitly
to a boolean value to check for validity: converted to a boolean value to check if they are valid:
```cpp ```cpp
if(auto func = entt::resolve<my_type>().func("member"_hs); func) { if(auto func = entt::resolve<my_type>().func("member"_hs); func) {
@ -296,23 +319,26 @@ for(auto &&[id, type]: entt::resolve<my_type>().base()) {
} }
``` ```
Meta type are also used to `construct` actual instances of the underlying A meta type can also be used to `construct` actual instances of the underlying
type.<br/> type.<br/>
In particular, the `construct` member function accepts a variable number of In particular, the `construct` member function accepts a variable number of
arguments and searches for a match. It then returns a `meta_any` object that may arguments and searches for a match. It then returns a `meta_any` object that may
or may not be initialized, depending on whether a suitable constructor was found or may not be initialized, depending on whether a suitable constructor has been
or not. found or not.
There is no object that wraps the destructor of a meta type nor a `destroy` There is no object that wraps the destructor of a meta type nor a `destroy`
member function in its API. Destructors are invoked implicitly by `meta_any` member function in its API. Destructors are invoked implicitly by `meta_any`
behind the scenes and users have not to deal with them explicitly. Furthermore, behind the scenes and users have not to deal with them explicitly. Furthermore,
they've no name, cannot be searched and wouldn't have member functions to expose they have no name, cannot be searched and wouldn't have member functions to
anyway.<br/> expose anyway.<br/>
Similarly, conversion functions aren't directly accessible. They're used Similarly, conversion functions aren't directly accessible. They are used
internally by `meta_any` and the meta objects when needed. internally by `meta_any` and the meta objects when needed.
Meta types and meta objects in general contain much more than what was said. Meta types and meta objects in general contain much more than what is said: a
Refer to the inline documentation for further details. plethora of functions in addition to those listed whose purposes and uses go
unfortunately beyond the scope of this document.<br/>
I invite anyone interested in the subject to look at the code, experiment and
read the inline documentation to get the best out of this powerful tool.
## Container support ## Container support
@ -323,7 +349,7 @@ meta system in many cases.
To make a container be recognized as such by the meta system, users are required To make a container be recognized as such by the meta system, users are required
to provide specializations for either the `meta_sequence_container_traits` class to provide specializations for either the `meta_sequence_container_traits` class
or the `meta_associative_container_traits` class, according to the actual _type_ or the `meta_associative_container_traits` class, according to the actual type
of the container.<br/> of the container.<br/>
`EnTT` already exports the specializations for some common classes. In `EnTT` already exports the specializations for some common classes. In
particular: particular:
@ -360,10 +386,11 @@ if(any.type().is_sequence_container()) {
The method to use to get a proxy object for associative containers is The method to use to get a proxy object for associative containers is
`as_associative_container` instead.<br/> `as_associative_container` instead.<br/>
It's not necessary to perform a double check actually. Instead, it's enough to It goes without saying that it's not necessary to perform a double check.
query the meta type or verify that the proxy object is valid. In fact, proxies Instead, it's sufficient to query the meta type or verify that the proxy object
are contextually convertible to bool to check for validity. For example, invalid is valid. In fact, proxies are contextually convertible to bool to know if they
proxies are returned when the wrapped object isn't a container.<br/> are valid. For example, invalid proxies are returned when the wrapped object
isn't a container.<br/>
In all cases, users aren't expected to _reflect_ containers explicitly. It's In all cases, users aren't expected to _reflect_ containers explicitly. It's
sufficient to assign a container for which a specialization of the traits sufficient to assign a container for which a specialization of the traits
classes exists to a `meta_any` object to be able to get its proxy object. classes exists to a `meta_any` object to be able to get its proxy object.
@ -375,18 +402,32 @@ to case. In particular:
* The `value_type` member function returns the meta type of the elements. * The `value_type` member function returns the meta type of the elements.
* The `size` member function returns the number of elements in the container as * The `size` member function returns the number of elements in the container as
an unsigned integer value. an unsigned integer value:
```cpp
const auto size = view.size();
```
* The `resize` member function allows to resize the wrapped container and * The `resize` member function allows to resize the wrapped container and
returns true in case of success.<br/> returns true in case of success:
```cpp
const bool ok = view.resize(3u);
```
For example, it's not possible to resize fixed size containers. For example, it's not possible to resize fixed size containers.
* The `clear` member function allows to clear the wrapped container and returns * The `clear` member function allows to clear the wrapped container and returns
true in case of success.<br/> true in case of success:
```cpp
const bool ok = view.clear();
```
For example, it's not possible to clear fixed size containers. For example, it's not possible to clear fixed size containers.
* The `begin` and `end` member functions return opaque iterators that is used to * The `begin` and `end` member functions return opaque iterators that can be
iterate the container directly: used to iterate the container directly:
```cpp ```cpp
for(entt::meta_any element: view) { for(entt::meta_any element: view) {
@ -400,7 +441,7 @@ to case. In particular:
All meta iterators are input iterators and don't offer an indirection operator All meta iterators are input iterators and don't offer an indirection operator
on purpose. on purpose.
* The `insert` member function is used to add elements to the container. It * The `insert` member function can be used to add elements to the container. It
accepts a meta iterator and the element to insert: accepts a meta iterator and the element to insert:
```cpp ```cpp
@ -410,15 +451,15 @@ to case. In particular:
``` ```
This function returns a meta iterator pointing to the inserted element and a This function returns a meta iterator pointing to the inserted element and a
boolean value to indicate whether the operation was successful or not. A call boolean value to indicate whether the operation was successful or not. Note
to `insert` may silently fail in case of fixed size containers or whether the that a call to `insert` may silently fail in case of fixed size containers or
arguments aren't at least convertible to the required types.<br/> whether the arguments aren't at least convertible to the required types.<br/>
Since meta iterators are contextually convertible to bool, users can rely on Since the meta iterators are contextually convertible to bool, users can rely
them to know if the operation failed on the actual container or upstream, for on them to know if the operation has failed on the actual container or
example due to an argument conversion problem. upstream, for example for an argument conversion problem.
* The `erase` member function is used to remove elements from the container. It * The `erase` member function can be used to remove elements from the container.
accepts a meta iterator to the element to remove: It accepts a meta iterator to the element to remove:
```cpp ```cpp
auto first = view.begin(); auto first = view.begin();
@ -427,11 +468,11 @@ to case. In particular:
``` ```
This function returns a meta iterator following the last removed element and a This function returns a meta iterator following the last removed element and a
boolean value to indicate whether the operation was successful or not. A call boolean value to indicate whether the operation was successful or not. Note
to `erase` may silently fail in case of fixed size containers. that a call to `erase` may silently fail in case of fixed size containers.
* The `operator[]` is used to access container elements. It accepts a single * The `operator[]` can be used to access elements in a container. It accepts a
argument, the position of the element to return: single argument, that is the position of the element to return:
```cpp ```cpp
for(std::size_t pos{}, last = view.size(); pos < last; ++pos) { for(std::size_t pos{}, last = view.size(); pos < last; ++pos) {
@ -441,8 +482,8 @@ to case. In particular:
``` ```
The function returns instances of `meta_any` that directly refer to the actual The function returns instances of `meta_any` that directly refer to the actual
elements. Modifying the returned object directly modifies the element inside elements. Modifying the returned object will then directly modify the element
the container.<br/> inside the container.<br/>
Depending on the underlying sequence container, this operation may not be as Depending on the underlying sequence container, this operation may not be as
efficient. For example, in the case of an `std::list`, a positional access efficient. For example, in the case of an `std::list`, a positional access
translates to a linear visit of the list itself (probably not what the user translates to a linear visit of the list itself (probably not what the user
@ -467,13 +508,21 @@ differences in behavior in the case of key-only containers. In particular:
`std::map<int, char>`. `std::map<int, char>`.
* The `size` member function returns the number of elements in the container as * The `size` member function returns the number of elements in the container as
an unsigned integer value. an unsigned integer value:
```cpp
const auto size = view.size();
```
* The `clear` member function allows to clear the wrapped container and returns * The `clear` member function allows to clear the wrapped container and returns
true in case of success. true in case of success:
* The `begin` and `end` member functions return opaque iterators that are used ```cpp
to iterate the container directly: const bool ok = view.clear();
```
* The `begin` and `end` member functions return opaque iterators that can be
used to iterate the container directly:
```cpp ```cpp
for(std::pair<entt::meta_any, entt::meta_any> element: view) { for(std::pair<entt::meta_any, entt::meta_any> element: view) {
@ -490,11 +539,11 @@ differences in behavior in the case of key-only containers. In particular:
While the accessed key is usually constant in the associative containers and While the accessed key is usually constant in the associative containers and
is therefore returned by copy, the value (if any) is wrapped by an instance of is therefore returned by copy, the value (if any) is wrapped by an instance of
`meta_any` that directly refers to the actual element. Modifying it directly `meta_any` that directly refers to the actual element. Modifying it will then
modifies the element inside the container. directly modify the element inside the container.
* The `insert` member function is used to add elements to a container. It gets * The `insert` member function can be used to add elements to the container. It
two arguments, respectively the key and the value to insert: accepts two arguments, respectively the key and the value to be inserted:
```cpp ```cpp
auto last = view.end(); auto last = view.end();
@ -503,39 +552,39 @@ differences in behavior in the case of key-only containers. In particular:
``` ```
This function returns a boolean value to indicate whether the operation was This function returns a boolean value to indicate whether the operation was
successful or not. A call to `insert` may fail when the arguments aren't at successful or not. Note that a call to `insert` may fail when the arguments
least convertible to the required types. aren't at least convertible to the required types.
* The `erase` member function is used to remove elements from a container. It * The `erase` member function can be used to remove elements from the container.
gets a single argument, the key to remove: It accepts a single argument, that is the key to be removed:
```cpp ```cpp
view.erase(42); view.erase(42);
``` ```
This function returns a boolean value to indicate whether the operation was This function returns a boolean value to indicate whether the operation was
successful or not. A call to `erase` may fail when the argument isn't at least successful or not. Note that a call to `erase` may fail when the argument
convertible to the required type. isn't at least convertible to the required type.
* The `operator[]` is used to access elements in a container. It gets a single * The `operator[]` can be used to access elements in a container. It accepts a
argument, the key of the element to return: single argument, that is the key of the element to return:
```cpp ```cpp
entt::meta_any value = view[42]; entt::meta_any value = view[42];
``` ```
The function returns instances of `meta_any` that directly refer to the actual The function returns instances of `meta_any` that directly refer to the actual
elements. Modifying the returned object directly modifies the element inside elements. Modifying the returned object will then directly modify the element
the container. inside the container.
Container support is minimal but likely sufficient to satisfy all needs. Container support is minimal but likely sufficient to satisfy all needs.
## Pointer-like types ## Pointer-like types
As with containers, it's also possible to _tell_ to the meta system which types As with containers, it's also possible to communicate to the meta system which
are _pointers_. This makes it possible to dereference instances of `meta_any`, types to consider _pointers_. This will allow to dereference instances of
thus obtaining light _references_ to pointed objects that are also correctly `meta_any`, thus obtaining light _references_ to the pointed objects that are
associated with their meta types.<br/> also correctly associated with their meta types.<br/>
To make the meta system recognize a type as _pointer-like_, users can specialize To make the meta system recognize a type as _pointer-like_, users can specialize
the `is_meta_pointer_like` class. `EnTT` already exports the specializations for the `is_meta_pointer_like` class. `EnTT` already exports the specializations for
some common classes. In particular: some common classes. In particular:
@ -565,12 +614,13 @@ if(any.type().is_pointer_like()) {
} }
``` ```
It's not necessary to perform a double check. Instead, it's enough to query the Of course, it's not necessary to perform a double check. Instead, it's enough to
meta type or verify that the returned object is valid. For example, invalid query the meta type or verify that the returned object is valid. For example,
instances are returned when the wrapped object isn't a pointer-like type.<br/> invalid instances are returned when the wrapped object isn't a pointer-like
Dereferencing a pointer-like object returns an instance of `meta_any` which type.<br/>
_refers_ to the pointed object. Modifying it means modifying the pointed object Note that dereferencing a pointer-like object returns an instance of `meta_any`
directly (unless the returned element is const). which refers to the pointed object and allows users to modify it directly
(unless the returned element is const, of course).
In general, _dereferencing_ a pointer-like type boils down to a `*ptr`. However, In general, _dereferencing_ a pointer-like type boils down to a `*ptr`. However,
`EnTT` also supports classes that don't offer an `operator*`. In particular: `EnTT` also supports classes that don't offer an `operator*`. In particular:
@ -598,12 +648,12 @@ In general, _dereferencing_ a pointer-like type boils down to a `*ptr`. However,
}; };
``` ```
In all other cases and when dereferencing a pointer works as expected regardless In all other cases, that is, when dereferencing a pointer works as expected and
of the pointed type, no user intervention is required. regardless of the pointed type, no user intervention is required.
## Template information ## Template information
Meta types also provide a minimal set of information about the _nature_ of the Meta types also provide a minimal set of information about the nature of the
original type in case it's a class template.<br/> original type in case it's a class template.<br/>
By default, this works out of the box and requires no user action. However, it's By default, this works out of the box and requires no user action. However, it's
important to include the header file `template.hpp` to make this information important to include the header file `template.hpp` to make this information
@ -638,9 +688,9 @@ template<typename Ret, typename... Args>
struct function_type<Ret(Args...)> {}; struct function_type<Ret(Args...)> {};
``` ```
In this case, rather than the function type, it might be useful to provide the In this case, rather than the function type, the user might want the return type
return type and unpacked arguments as if they were different template parameters and unpacked arguments as if they were different template parameters for the
for the original class template.<br/> original class template.<br/>
To achieve this, users must enter the library internals and provide their own To achieve this, users must enter the library internals and provide their own
specialization for the class template `entt::meta_template_traits`, such as: specialization for the class template `entt::meta_template_traits`, such as:
@ -654,8 +704,8 @@ struct entt::meta_template_traits<function_type<Ret(Args...)>> {
The reflection system doesn't verify the accuracy of the information nor infer a The reflection system doesn't verify the accuracy of the information nor infer a
correspondence between real types and meta types.<br/> correspondence between real types and meta types.<br/>
Therefore, the specialization is used as is and the information it contains is Therefore, the specialization will be used as is and the information it contains
associated with the appropriate type when required. will be associated with the appropriate type when required.
## Automatic conversions ## Automatic conversions
@ -702,29 +752,29 @@ any.allow_cast(type);
int value = any.cast<int>(); int value = any.cast<int>();
``` ```
This makes working with arithmetic types and scoped or unscoped enums as easy as This should make working with arithmetic types and scoped or unscoped enums as
it is in C++.<br/> easy as it is in C++.<br/>
It's still possible to set up conversion functions manually and these are always It's also worth noting that it's still possible to set up conversion functions
preferred over the automatic ones. manually and these will always be preferred over the automatic ones.
## Implicitly generated default constructor ## Implicitly generated default constructor
Creating objects of default constructible types through the reflection system In many cases, it's useful to be able to create objects of default constructible
while not having to explicitly register the meta type or its default constructor types through the reflection system, while not having to explicitly register the
is also possible.<br/> meta type or the default constructor.<br/>
For example, in the case of primitive types like `int` or `char`, but not just For example, in the case of primitive types like `int` or `char`, but not just
them. them.
For default constructible types only, default constructors are automatically For this reason and only for default constructible types, default constructors
defined and associated with their meta types, whether they are explicitly or are automatically defined and associated with their meta types, whether they are
implicitly generated.<br/> explicitly or implicitly generated.<br/>
Therefore, this is all is needed to construct an integer from its meta type: Therefore, this is all is needed to construct an integer from its meta type:
```cpp ```cpp
entt::resolve<int>().construct(); entt::resolve<int>().construct();
``` ```
Where the meta type is for example the one returned from a meta container, Where the meta type can be for example the one returned from a meta container,
useful for building keys without knowing or having to register the actual types. useful for building keys without knowing or having to register the actual types.
In all cases, when users register default constructors, they are preferred both In all cases, when users register default constructors, they are preferred both
@ -733,8 +783,8 @@ during searches and when the `construct` member function is invoked.
## From void to any ## From void to any
Sometimes all a user has is an opaque pointer to an object of a known meta type. Sometimes all a user has is an opaque pointer to an object of a known meta type.
It would be handy in this case to be able to construct a `meta_any` element from It would be handy in this case to be able to construct a `meta_any` object from
it.<br/> them.<br/>
For this purpose, the `meta_type` class offers a `from_void` member function For this purpose, the `meta_type` class offers a `from_void` member function
designed to convert an opaque pointer into a `meta_any`: designed to convert an opaque pointer into a `meta_any`:
@ -742,8 +792,9 @@ designed to convert an opaque pointer into a `meta_any`:
entt::meta_any any = entt::resolve(id).from_void(element); entt::meta_any any = entt::resolve(id).from_void(element);
``` ```
Unfortunately, it's not possible to do a check on the actual type. Therefore, It goes without saying that it's not possible to do a check on the actual type.
this call can be considered as a _static cast_ with all its _problems_.<br/> Therefore, this call can be considered as a _static cast_ with all the problems
and undefined behaviors of the case following errors.<br/>
On the other hand, the ability to construct a `meta_any` from an opaque pointer On the other hand, the ability to construct a `meta_any` from an opaque pointer
opens the door to some pretty interesting uses that are worth exploring. opens the door to some pretty interesting uses that are worth exploring.
@ -775,17 +826,17 @@ There are a few alternatives available at the moment:
entt::meta<my_type>().func<&my_type::member_function, entt::as_void_t>("member"_hs); entt::meta<my_type>().func<&my_type::member_function, entt::as_void_t>("member"_hs);
``` ```
If the use with functions is obvious, perhaps less so is use with constructors If the use with functions is obvious, it must be said that it's also possible
and data members. In the first case, the returned wrapper is always empty even to use this policy with constructors and data members. In the first case, the
though the constructor is still invoked. In the second case, the property constructor will be invoked but the returned wrapper will actually be empty.
isn't accessible for reading instead. In the second case, instead, the property will not be accessible for reading.
* The _as-ref_ and _as-cref_ policies, associated with the types * The _as-ref_ and _as-cref_ policies, associated with the types
`entt::as_ref_t` and `entt::as_cref_t`.<br/> `entt::as_ref_t` and `entt::as_cref_t`.<br/>
They allow to build wrappers that act as references to unmanaged objects. They allow to build wrappers that act as references to unmanaged objects.
Accessing the object contained in the wrapper for which the _reference_ was Accessing the object contained in the wrapper for which the _reference_ was
requested makes it possible to directly access the instance used to initialize requested will make it possible to directly access the instance used to
the wrapper itself: initialize the wrapper itself:
```cpp ```cpp
entt::meta<my_type>().data<&my_type::data_member, entt::as_ref_t>("member"_hs); entt::meta<my_type>().data<&my_type::data_member, entt::as_ref_t>("member"_hs);
@ -803,16 +854,21 @@ obvious corner cases that can in turn be solved with the use of policies.
## Named constants and enums ## Named constants and enums
As mentioned, the `data` member function is used to reflect constants of any A special mention should be made for constant values and enums. It wouldn't be
type.<br/> necessary, but it will help distracted readers.
This allows users to create meta types for enums that work exactly like any
other meta type built from a class. Similarly, arithmetic types are _enriched_
with constants of special meaning where required.<br/>
All values thus exported appear to users as if they were constant data members
of the reflected types. This avoids the need to _export_ what is the difference
between enums and classes in C++ directly in the space of the reflected types.
Exposing constant values or elements from an enum is quite simple: As mentioned, the `data` member function can be used to reflect constants of any
type among the other things.<br/>
This allows users to create meta types for enums that will work exactly like any
other meta type built from a class. Similarly, arithmetic types can be enriched
with constants of special meaning where required.<br/>
Personally, I find it very useful not to export what is the difference between
enums and classes in C++ directly in the space of the reflected types.
All the values thus exported will appear to users as if they were constant data
members of the reflected types.
Exporting constant values or elements from an enum is as simple as ever:
```cpp ```cpp
entt::meta<my_enum>() entt::meta<my_enum>()
@ -822,22 +878,28 @@ entt::meta<my_enum>()
entt::meta<int>().data<2048>("max_int"_hs); entt::meta<int>().data<2048>("max_int"_hs);
``` ```
Accessing them is trivial as well. It's a matter of doing the following, as with It goes without saying that accessing them is trivial as well. It's a matter of
any other data member of a meta type: doing the following, as with any other data member of a meta type:
```cpp ```cpp
auto value = entt::resolve<my_enum>().data("a_value"_hs).get({}).cast<my_enum>(); auto value = entt::resolve<my_enum>().data("a_value"_hs).get({}).cast<my_enum>();
auto max = entt::resolve<int>().data("max_int"_hs).get({}).cast<int>(); auto max = entt::resolve<int>().data("max_int"_hs).get({}).cast<int>();
``` ```
All this happens behind the scenes without any allocation because of the small As a side note, remember that all this happens behind the scenes without any
object optimization performed by the `meta_any` class. allocation because of the small object optimization performed by the `meta_any`
class.
## Properties and meta objects ## Properties and meta objects
Sometimes (for example, when it comes to creating an editor) it might be useful Sometimes (for example, when it comes to creating an editor) it might be useful
to attach properties to the meta objects created. Fortunately, this is possible to attach properties to the meta objects created. Fortunately, this is possible
for most of them: for most of them.<br/>
For the meta objects that support properties, the member functions of the
factory used for registering them will return an extended version of the factory
itself. The latter can be used to attach properties to the last created meta
object.<br/>
Apparently, it's more difficult to say than to do:
```cpp ```cpp
entt::meta<my_type>().type("reflected_type"_hs).prop("tooltip"_hs, "message"); entt::meta<my_type>().type("reflected_type"_hs).prop("tooltip"_hs, "message");
@ -852,10 +914,10 @@ Key only properties are also supported out of the box:
entt::meta<my_type>().type("reflected_type"_hs).prop(my_enum::key_only); entt::meta<my_type>().type("reflected_type"_hs).prop(my_enum::key_only);
``` ```
To attach multiple properties to a meta object, just invoke `prop` more than To attach multiple properties to a meta object, it's possible to invoke `prop`
once.<br/> more than once.<br/>
It's also possible to call `prop` at different times, as long as the factory is It's also possible to invoke `prop` at different times, as long as the factory
reset to the meta object of interest. is reset to the meta object of interest.
The meta objects for which properties are supported are currently meta types, The meta objects for which properties are supported are currently meta types,
meta data and meta functions.<br/> meta data and meta functions.<br/>
@ -878,7 +940,7 @@ form of a `meta_any` object.
## Unregister types ## Unregister types
A type registered with the reflection system can also be _unregistered_. This A type registered with the reflection system can also be unregistered. This
means unregistering all its data members, member functions, conversion functions means unregistering all its data members, member functions, conversion functions
and so on. However, base classes aren't unregistered as well, since they don't and so on. However, base classes aren't unregistered as well, since they don't
necessarily depend on it.<br/> necessarily depend on it.<br/>
@ -907,7 +969,7 @@ A type can be re-registered later with a completely different name and form.
## Meta context ## Meta context
All meta types and their parts are created at runtime and stored in a default All meta types and their parts are created at runtime and stored in a default
_context_. This is obtained via a service locator as: _context_. This can be reached via a service locator as:
```cpp ```cpp
auto &&context = entt::locator<entt::meta_context>::value_or(); auto &&context = entt::locator<entt::meta_context>::value_or();
@ -922,8 +984,8 @@ auto &&context = entt::locator<entt::meta_context>::value_or();
std::swap(context, other); std::swap(context, other);
``` ```
This is useful for testing purposes or to define multiple context objects with This can be useful for testing purposes or to define multiple contexts with
different meta type to use as appropriate. different meta objects to be used as appropriate.
If _replacing_ the default context isn't enough, `EnTT` also offers the ability If _replacing_ the default context isn't enough, `EnTT` also offers the ability
to use multiple and externally managed contexts with the runtime reflection to use multiple and externally managed contexts with the runtime reflection
@ -936,16 +998,16 @@ entt::meta_ctx context{};
auto factory = entt::meta<my_type>(context).type("reflected_type"_hs); auto factory = entt::meta<my_type>(context).type("reflected_type"_hs);
``` ```
By doing so, the new meta type isn't available in the default context but is By doing so, the new meta type won't be available in the default context but
usable by passing around the new context when needed, such as when creating a will be usable by passing around the new context when needed, such as when
new `meta_any` object: creating a new `meta_any` object:
```cpp ```cpp
entt::meta_any any{context, std::in_place_type<my_type>}; entt::meta_any any{context, std::in_place_type<my_type>};
``` ```
Similarly, to search for meta types in a context other than the default one, Similarly, to search for meta types in a context other than the default one, it
it's necessary to pass it to the `resolve` function: will be necessary to pass it to the `resolve` function:
```cpp ```cpp
entt::meta_type type = entt::resolve(context, "reflected_type"_hs) entt::meta_type type = entt::resolve(context, "reflected_type"_hs)

View File

@ -26,16 +26,17 @@ This module aims to make it simple and easy to use.
The library allows to define _concepts_ as interfaces to fulfill with concrete The library allows to define _concepts_ as interfaces to fulfill with concrete
classes without having to inherit from a common base.<br/> classes without having to inherit from a common base.<br/>
Among others, this is one of the advantages of static polymorphism in general This is, among others, one of the advantages of static polymorphism in general
and of a generic wrapper like that offered by the `poly` class template in and of a generic wrapper like that offered by the `poly` class template in
particular.<br/> particular.<br/>
The result is an object to pass around as such and not through a reference or a What users get is an object that can be passed around as such and not through a
pointer, as it happens when it comes to working with dynamic polymorphism. reference or a pointer, as happens when it comes to working with dynamic
polymorphism.
Since the `poly` class template makes use of `entt::any` internally, it also Since the `poly` class template makes use of `entt::any` internally, it also
supports most of its feature. For example, the possibility to create aliases to supports most of its feature. Among the most important, the possibility to
existing and thus unmanaged objects. This allows users to exploit the static create aliases to existing and thus unmanaged objects. This allows users to
polymorphism while maintaining ownership of objects.<br/> exploit the static polymorphism while maintaining ownership of objects.<br/>
Likewise, the `poly` class template also benefits from the small buffer Likewise, the `poly` class template also benefits from the small buffer
optimization offered by the `entt::any` class and therefore minimizes the number optimization offered by the `entt::any` class and therefore minimizes the number
of allocations, avoiding them altogether where possible. of allocations, avoiding them altogether where possible.
@ -43,7 +44,7 @@ of allocations, avoiding them altogether where possible.
## Other libraries ## Other libraries
There are some very interesting libraries regarding static polymorphism.<br/> There are some very interesting libraries regarding static polymorphism.<br/>
The ones that I like more are: Among all, the two that I prefer are:
* [`dyno`](https://github.com/ldionne/dyno): runtime polymorphism done right. * [`dyno`](https://github.com/ldionne/dyno): runtime polymorphism done right.
* [`Poly`](https://github.com/facebook/folly/blob/master/folly/docs/Poly.md): * [`Poly`](https://github.com/facebook/folly/blob/master/folly/docs/Poly.md):
@ -68,18 +69,18 @@ use the terminology introduced by Eric Niebler) is to define a _concept_ that
types will have to adhere to.<br/> types will have to adhere to.<br/>
For this purpose, the library offers a single class that supports both deduced For this purpose, the library offers a single class that supports both deduced
and fully defined interfaces. Although having interfaces deduced automatically and fully defined interfaces. Although having interfaces deduced automatically
is convenient and allows users to write less code in most cases, it has some is convenient and allows users to write less code in most cases, this has some
limitations and it's therefore useful to be able to get around the deduction by limitations and it's therefore useful to be able to get around the deduction by
providing a custom definition for the static virtual table. providing a custom definition for the static virtual table.
Once the interface is defined, a generic implementation is needed to fulfill the Once the interface is defined, it will be sufficient to provide a generic
concept itself.<br/> implementation to fulfill the concept.<br/>
Also in this case, the library allows customizations based on types or families Also in this case, the library allows customizations based on types or families
of types, so as to be able to go beyond the generic case where necessary. of types, so as to be able to go beyond the generic case where necessary.
## Deduced interface ## Deduced interface
This is how a concept with a deduced interface is defined: This is how a concept with a deduced interface is introduced:
```cpp ```cpp
struct Drawable: entt::type_list<> { struct Drawable: entt::type_list<> {
@ -107,12 +108,12 @@ struct Drawable: entt::type_list<> {
}; };
``` ```
In this case, all parameters are passed to `invoke` after the reference to In this case, all parameters must be passed to `invoke` after the reference to
`this` and the return value is whatever the internal call returns.<br/> `this` and the return value is whatever the internal call returns.<br/>
As for `invoke`, this is a name that is injected into the _concept_ through As for `invoke`, this is a name that is injected into the _concept_ through
`Base`, from which one must necessarily inherit. Since it's also a dependent `Base`, from which one must necessarily inherit. Since it's also a dependent
name, the `this-> template` form is unfortunately necessary due to the rules of name, the `this-> template` form is unfortunately necessary due to the rules of
the language. However, there also exists an alternative that goes through an the language. However, there exists also an alternative that goes through an
external call: external call:
```cpp ```cpp
@ -164,12 +165,12 @@ struct Drawable: entt::type_list<bool(int) const> {
Why should a user fully define a concept if the function types are the same as Why should a user fully define a concept if the function types are the same as
the deduced ones?<br> the deduced ones?<br>
In fact, this is the limitation that can be worked around by manually defining Because, in fact, this is exactly the limitation that can be worked around by
the static virtual table. manually defining the static virtual table.
When things are deduced, there is an implicit constraint.<br/> When things are deduced, there is an implicit constraint.<br/>
If the concept exposes a member function called `draw` with function type If the concept exposes a member function called `draw` with function type
`void()`, a concept is satisfied: `void()`, a concept can be satisfied:
* Either by a class that exposes a member function with the same name and the * Either by a class that exposes a member function with the same name and the
same signature. same signature.
@ -178,7 +179,7 @@ If the concept exposes a member function called `draw` with function type
interface itself. interface itself.
In other words, it's not possible to make use of functions not belonging to the In other words, it's not possible to make use of functions not belonging to the
interface, even if they're part of the types that fulfill the concept.<br/> interface, even if they are present in the types that fulfill the concept.<br/>
Similarly, it's not possible to deduce a function in the static virtual table Similarly, it's not possible to deduce a function in the static virtual table
with a function type different from that of the associated member function in with a function type different from that of the associated member function in
the interface itself. the interface itself.
@ -199,8 +200,8 @@ struct Drawable: entt::type_list<> {
}; };
``` ```
In this case, it's stated that the `draw` method of a generic type is enough to In this case, it's stated that the `draw` method of a generic type will be
satisfy the requirements of the `Drawable` concept.<br/> enough to satisfy the requirements of the `Drawable` concept.<br/>
Both member functions and free functions are supported to fulfill concepts: Both member functions and free functions are supported to fulfill concepts:
```cpp ```cpp
@ -250,15 +251,15 @@ struct DrawableAndErasable: entt::type_list<> {
``` ```
The static virtual table is empty and must remain so.<br/> The static virtual table is empty and must remain so.<br/>
On the other hand, `type` no longer inherits from `Base`. Instead, it forwards On the other hand, `type` no longer inherits from `Base` and instead forwards
its template parameter to the type exposed by the _base class_. Internally, the its template parameter to the type exposed by the _base class_. Internally, the
_size_ of the static virtual table of the base class is used as an offset for size of the static virtual table of the base class is used as an offset for the
the local indexes.<br/> local indexes.<br/>
Finally, by means of the `value_list_cat_t` utility, the implementation consists Finally, by means of the `value_list_cat_t` utility, the implementation consists
in appending the new functions to the previous list. in appending the new functions to the previous list.
As for a defined concept instead, the list of types is _extended_ in a similar As for a defined concept instead, also the list of types must be extended, in a
way to what is shown for the implementation of the above concept.<br/> similar way to what is shown for the implementation of the above concept.<br/>
To do this, it's useful to declare a function that allows to convert a _concept_ To do this, it's useful to declare a function that allows to convert a _concept_
into its underlying `type_list` object: into its underlying `type_list` object:
@ -267,8 +268,8 @@ template<typename... Type>
entt::type_list<Type...> as_type_list(const entt::type_list<Type...> &); entt::type_list<Type...> as_type_list(const entt::type_list<Type...> &);
``` ```
The definition isn't strictly required, since the function is only used through The definition isn't strictly required, since the function will only be used
a `decltype` as it follows: through a `decltype` as it follows:
```cpp ```cpp
struct DrawableAndErasable: entt::type_list_cat_t< struct DrawableAndErasable: entt::type_list_cat_t<
@ -285,8 +286,9 @@ Everything else is the same as already shown instead.
# Static polymorphism in the wild # Static polymorphism in the wild
Once the _concept_ and implementation are defined, it's possible to use the Once the _concept_ and implementation have been introduced, it will be possible
`poly` class template to _wrap_ instances that meet the requirements: to use the `poly` class template to contain instances that meet the
requirements:
```cpp ```cpp
using drawable = entt::poly<Drawable>; using drawable = entt::poly<Drawable>;
@ -308,9 +310,9 @@ instance = square{};
instance->draw(); instance->draw();
``` ```
This class offers a wide range of constructors, from the default one (which The `poly` class template offers a wide range of constructors, from the default
returns an uninitialized `poly` object) to the copy and move constructors, as one (which will return an uninitialized `poly` object) to the copy and move
well as the ability to create objects in-place.<br/> constructors, as well as the ability to create objects in-place.<br/>
Among others, there is also a constructor that allows users to wrap unmanaged Among others, there is also a constructor that allows users to wrap unmanaged
objects in a `poly` instance (either const or non-const ones): objects in a `poly` instance (either const or non-const ones):
@ -327,14 +329,14 @@ drawable other = instance.as_ref();
``` ```
In both cases, although the interface of the `poly` object doesn't change, it In both cases, although the interface of the `poly` object doesn't change, it
doesn't construct any element or take care of destroying the referenced objects. 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 Note also how the underlying concept is accessed via a call to `operator->` and
not directly as `instance.draw()`.<br/> not directly as `instance.draw()`.<br/>
This allows users to decouple the API of the wrapper from that of the concept. This allows users to decouple the API of the wrapper from that of the concept.
Therefore, where `instance.data()` invokes the `data` member function of the Therefore, where `instance.data()` will invoke the `data` member function of the
poly object, `instance->data()` maps directly to the functionality exposed by poly object, `instance->data()` will map directly to the functionality exposed
the underlying concept. by the underlying concept.
# Storage size and alignment requirement # Storage size and alignment requirement
@ -349,9 +351,9 @@ entt::basic_poly<Drawable, sizeof(double[4]), alignof(double[4])>
The default size is `sizeof(double[2])`, which seems like a good compromise The default size is `sizeof(double[2])`, which seems like a good compromise
between a buffer that is too large and one unable to hold anything larger than between a buffer that is too large and one unable to hold anything larger than
an integer. The alignment requirement is optional and by default such that it's an integer. The alignment requirement is optional instead and by default such
the most stringent (the largest) for any object whose size is at most equal to that it's the most stringent (the largest) for any object whose size is at most
the one provided.<br/> equal to the one provided.<br/>
It's worth noting that providing a size of 0 (which is an accepted value in all It's worth noting that providing a size of 0 (which is an accepted value in all
respects) will force the system to dynamically allocate the contained objects in respects) will force the system to dynamically allocate the contained objects in
all cases. all cases.

View File

@ -15,17 +15,18 @@
# Introduction # Introduction
Processes are a useful tool to work around the strict definition of a system and Sometimes processes are a useful tool to work around the strict definition of a
introduce logic in a different way, usually without resorting to other component system and introduce logic in a different way, usually without resorting to the
types.<br/> introduction of other components.
`EnTT` offers minimal support to this paradigm by introducing a few classes used
to define and execute cooperative processes. `EnTT` offers a minimal support to this paradigm by introducing a few classes
that users can use to define and execute cooperative processes.
# The process # The process
A typical task inherits from the `process` class template that stays true to the A typical process must inherit from the `process` class template that stays true
CRTP idiom. Moreover, derived classes specify what the intended type for elapsed to the CRTP idiom. Moreover, derived classes must specify what's the intended
times is. type for elapsed times.
A process should expose publicly the following member functions whether needed 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 (note that it isn't required to define a function unless the derived class wants
@ -33,38 +34,39 @@ to _override_ the default behavior):
* `void update(Delta, void *);` * `void update(Delta, void *);`
This is invoked once per tick until a process is explicitly aborted or ends It's invoked once per tick until a process is explicitly aborted or it
either with or without errors. Even though it's not mandatory to declare this terminates either with or without errors. Even though it's not mandatory to
member function, as a rule of thumb each process should at least define it to declare this member function, as a rule of thumb each process should at
work _properly_. The `void *` parameter is an opaque pointer to user data (if least define it to work properly. The `void *` parameter is an opaque pointer
any) forwarded directly to the process during an update. to user data (if any) forwarded directly to the process during an update.
* `void init();` * `void init();`
This is invoked when the process joins the running queue of a scheduler. It It's invoked when the process joins the running queue of a scheduler. This
happens usually as soon as the process is attached to the scheduler if it's a happens as soon as it's attached to the scheduler if the process is a top
top level one, otherwise when it replaces its parent if it's a _continuation_. level one, otherwise when it replaces its parent if the process is a
continuation.
* `void succeeded();` * `void succeeded();`
This is invoked in case of success, immediately after an update and during the It's invoked in case of success, immediately after an update and during the
same tick. same tick.
* `void failed();` * `void failed();`
This is invoked in case of errors, immediately after an update and during the It's invoked in case of errors, immediately after an update and during the
same tick. same tick.
* `void aborted();` * `void aborted();`
This is invoked only if a process is explicitly aborted. There is no guarantee It's invoked only if a process is explicitly aborted. There is no guarantee
that it executes in the same tick, it depends solely on whether the process is that it executes in the same tick, this depends solely on whether the
aborted immediately or not. process is aborted immediately or not.
Derived classes can also change the internal state of a process by invoking 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.<br/> `succeed` and `fail`, as well as `pause` and `unpause` the process itself. All
All these are protected member functions made available to manage the life cycle these are protected member functions made available to be able to manage the
of a process from a derived class. life cycle of a process from a derived class.
Here is a minimal example for the sake of curiosity: Here is a minimal example for the sake of curiosity:
@ -93,14 +95,14 @@ private:
## Adaptor ## Adaptor
Lambdas and functors can't be used directly with a scheduler because they aren't Lambdas and functors can't be used directly with a scheduler for they are not
properly defined processes with managed life cycles.<br/> properly defined processes with managed life cycles.<br/>
This class helps in filling the gap and turning lambdas and functors into This class helps in filling the gap and turning lambdas and functors into
full-featured processes usable by a scheduler. full-featured processes usable by a scheduler.
The function call operator has a signature similar to the one of the `update` 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 callbacks to function of a process but for the fact that it receives two extra arguments to
invoke whenever a process terminates with success or with an error: call whenever a process is terminated with success or with an error:
```cpp ```cpp
void(Delta delta, void *data, auto succeed, auto fail); void(Delta delta, void *data, auto succeed, auto fail);
@ -125,9 +127,9 @@ A cooperative scheduler runs different processes and helps managing their life
cycles. cycles.
Each process is invoked once per tick. If it terminates, it's removed 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 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 good candidate to run one more time the next tick.<br/>
A process can also have a _child_. In this case, the parent process is replaced A process can also have a child. In this case, the parent process is replaced
with its child when it terminates and only if it returns with success. In case with its child when it terminates and only if it returns with success. In case
of errors, both the parent process and its child are discarded. This way, it's of errors, both the parent process and its child are discarded. This way, it's
easy to create chain of processes to run sequentially. easy to create chain of processes to run sequentially.
@ -136,25 +138,18 @@ Using a scheduler is straightforward. To create it, users must provide only the
type for the elapsed times and no arguments at all: type for the elapsed times and no arguments at all:
```cpp ```cpp
entt::basic_scheduler<std::uint64_t> scheduler; entt::scheduler<std::uint32_t> scheduler;
``` ```
Otherwise, the `scheduler` alias is also available for the most common cases. It It has member functions to query its internal data structures, like `empty` or
uses `std::uint32_t` as a default type: `size`, as well as a `clear` utility to reset it to a clean state:
```cpp
entt::scheduler scheduler;
```
The class has member functions to query its internal data structures, like
`empty` or `size`, as well as a `clear` utility to reset it to a clean state:
```cpp ```cpp
// checks if there are processes still running // checks if there are processes still running
const auto empty = scheduler.empty(); const auto empty = scheduler.empty();
// gets the number of processes still running // gets the number of processes still running
entt::scheduler::size_type size = scheduler.size(); entt::scheduler<std::uint32_t>::size_type size = scheduler.size();
// resets the scheduler to its initial state and discards all the processes // resets the scheduler to its initial state and discards all the processes
scheduler.clear(); scheduler.clear();
@ -178,7 +173,7 @@ To attach a process to a scheduler there are mainly two ways:
``` ```
In both cases, the return value is an opaque object that offers a `then` member In both cases, the return value is an opaque object that offers a `then` member
function used to create chains of processes to run sequentially.<br/> function to use to create chains of processes to run sequentially.<br/>
As a minimal example of use: As a minimal example of use:
```cpp ```cpp
@ -206,7 +201,7 @@ scheduler.update(delta, &data);
``` ```
In addition to these functions, the scheduler offers an `abort` member function In addition to these functions, the scheduler offers an `abort` member function
that is used to discard all the running processes at once: that can be used to discard all the running processes at once:
```cpp ```cpp
// aborts all the processes abruptly ... // aborts all the processes abruptly ...

View File

@ -1,35 +1,18 @@
# Similar projects # Similar projects
<!--
@cond TURN_OFF_DOXYGEN
-->
# Table of Contents
* [Introduction](#introduction)
* [Similar projects](#similar-projects)
<!--
@endcond TURN_OFF_DOXYGEN
-->
# Introduction
There are many projects similar to `EnTT`, both open source and not.<br/> There are many projects similar to `EnTT`, both open source and not.<br/>
Some even borrowed some ideas from this library and expressed them in different Some even borrowed some ideas from this library and expressed them in different
languages.<br/> languages.<br/>
Others developed different architectures from scratch and therefore offer Others developed different architectures from scratch and therefore offer
alternative solutions with their pros and cons. alternative solutions with their pros and cons.
If you know of other similar projects out there, feel free to open an issue or a Below an incomplete list of those that I've come across so far.<br/>
PR and I'll 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've come across so far.<br/>
If some terms or designs aren't clear, I recommend referring to the 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 [_ECS Back and Forth_](https://skypjack.github.io/tags/#ecs) series for all the
details. details.
I hope this list can grow much more in the future:
* C: * C:
* [destral_ecs](https://github.com/roig/destral_ecs): a single-file ECS based * [destral_ecs](https://github.com/roig/destral_ecs): a single-file ECS based
on sparse sets. on sparse sets.
@ -51,8 +34,6 @@ details.
solution between an ECS and dynamic mixins. solution between an ECS and dynamic mixins.
* C# * C#
* [Arch](https://github.com/genaray/Arch): a simple, fast and _unity entities_
inspired archetype ECS with optional multithreading.
* [Entitas](https://github.com/sschmid/Entitas-CSharp): the ECS framework for * [Entitas](https://github.com/sschmid/Entitas-CSharp): the ECS framework for
C# and Unity, where _reactive systems_ were invented. C# and Unity, where _reactive systems_ were invented.
* [LeoECS](https://github.com/Leopotam/ecs): simple lightweight C# Entity * [LeoECS](https://github.com/Leopotam/ecs): simple lightweight C# Entity
@ -89,3 +70,6 @@ details.
* Zig * Zig
* [zig-ecs](https://github.com/prime31/zig-ecs): a _zig-ification_ of `EnTT`. * [zig-ecs](https://github.com/prime31/zig-ecs): a _zig-ification_ of `EnTT`.
If you know of other resources out there that can be of interest for the reader,
feel free to open an issue or a PR and I'll be glad to add them to this page.

View File

@ -9,7 +9,6 @@
* [Delegate](#delegate) * [Delegate](#delegate)
* [Runtime arguments](#runtime-arguments) * [Runtime arguments](#runtime-arguments)
* [Lambda support](#lambda-support) * [Lambda support](#lambda-support)
* [Raw access](#raw-access)
* [Signals](#signals) * [Signals](#signals)
* [Event dispatcher](#event-dispatcher) * [Event dispatcher](#event-dispatcher)
* [Named queues](#named-queues) * [Named queues](#named-queues)
@ -39,7 +38,7 @@ lightweight classes to solve the same and many other problems.
# Delegate # Delegate
A delegate can be used as a general purpose invoker with no memory overhead for A delegate can be used as a general purpose invoker with no memory overhead for
free functions, lambdas and members provided along with an instance on which to free functions and member functions provided along with an instance on which to
invoke them.<br/> invoke them.<br/>
It doesn't claim to be a drop-in replacement for an `std::function`, so don't 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 expect to use it whenever an `std::function` fits well. That said, it's most
@ -93,9 +92,9 @@ delegate.connect<&g>(c);
delegate(42); delegate(42);
``` ```
Function `g` is invoked with a reference to `c` and `42`. However, the function The function `g` is invoked with a reference to `c` and `42`. However, the
type of the delegate is still `void(int)`. This is also the signature of its function type of the delegate is still `void(int)`. This is also the signature
function call operator.<br/> of its function call operator.<br/>
Another interesting aspect of the delegate class is that it accepts functions 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: with a list of parameters that is shorter than that of its function type:
@ -106,15 +105,9 @@ delegate(42);
``` ```
Where the function type of the delegate is `void(int)` as above. It goes without Where the function type of the delegate is `void(int)` as above. It goes without
saying that the extra arguments are silently discarded internally. This is a saying that the extra arguments are silently discarded internally.<br/>
nice-to-have feature in a lot of cases, as an example when the `delegate` class This is a nice-to-have feature in a lot of cases, as an example when the
is used as a building block of a signal-slot system.<br/> `delegate` class is used as a building block of a signal-slot system.
In fact, this filtering works both ways. The class tries to pass its first
_count_ arguments **first**, then the last _count_. Watch out for conversion
rules if in doubt when connecting a listener!<br/>
Arbitrary functions that pull random arguments from the delegate list aren't
supported instead. Other feature were preferred, such as support for functions
with compatible argument lists although not equal to those of the delegate.
To create and initialize a delegate at once, there are a few specialized To create and initialize a delegate at once, there are a few specialized
constructors. Because of the rules of the language, the listener is provided by constructors. Because of the rules of the language, the listener is provided by
@ -238,24 +231,6 @@ As above, the first parameter (`const void *`) isn't part of the function type
of the delegate and is used to dispatch arbitrary user data back and forth. In of the delegate and is used to dispatch arbitrary user data back and forth. In
other terms, the function type of the delegate above is `int(int)`. other terms, the function type of the delegate above is `int(int)`.
## Raw access
While not recommended, a delegate also allows direct access to the stored
callable function target and underlying data, if any.<br/>
This makes it possible to bypass the behavior of the delegate itself and force
calls on different instances:
```cpp
my_struct other;
delegate.target(&other, 42);
```
It goes without saying that this type of approach is **very** risky, especially
since there is no way of knowing whether the contained function was originally a
member function of some class, a free function or a lambda.<br/>
Another possible (and meaningful) use of this feature is that of identifying a
particular delegate through its descriptive _traits_ instead.
# Signals # Signals
Signal handlers work with references to classes, function pointers and pointers Signal handlers work with references to classes, function pointers and pointers
@ -315,7 +290,7 @@ sink.disconnect<&foo>();
sink.disconnect<&listener::bar>(instance); sink.disconnect<&listener::bar>(instance);
// disconnect all member functions of an instance, if any // disconnect all member functions of an instance, if any
sink.disconnect(&instance); sink.disconnect(instance);
// discards all listeners at once // discards all listeners at once
sink.disconnect(); sink.disconnect();
@ -325,6 +300,15 @@ As shown above, listeners don't have to strictly follow the signature of the
signal. As long as a listener can be invoked with the given arguments to yield a signal. As long as a listener can be invoked with the given arguments to yield a
result that is convertible to the given return type, everything works just result that is convertible to the given return type, everything works just
fine.<br/> fine.<br/>
It's also possible to connect a listener before other elements already contained
by the signal. The `before` function returns a `sink` object that is correctly
initialized for the purpose and can be used to connect one or more listeners in
order and in the desired position:
```cpp
sink.before<&foo>().connect<&listener::bar>(instance);
```
In all cases, the `connect` member function returns by default a `connection` In all cases, the `connect` member function returns by default a `connection`
object to be used as an alternative to break a connection by means of its object to be used as an alternative to break a connection by means of its
`release` member function.<br/> `release` member function.<br/>
@ -425,7 +409,7 @@ of them at once:
```cpp ```cpp
dispatcher.sink<an_event>().disconnect<&listener::receive>(listener); 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 The `trigger` member function serves the purpose of sending an immediate event

View File

@ -1,31 +1,62 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> <AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<Type Name="entt::basic_registry&lt;*&gt;"> <Type Name="entt::basic_registry&lt;*&gt;">
<Intrinsic Name="to_entity" Expression="*((traits_type::entity_type *)&amp;entity) &amp; traits_type::entity_mask"> <Intrinsic Name="pools_size" Expression="pools.packed.first_base::value.size()"/>
<Parameter Name="entity" Type="traits_type::value_type &amp;"/> <Intrinsic Name="vars_size" Expression="vars.ctx.packed.first_base::value.size()"/>
<Intrinsic Name="to_entity" Expression="*((entity_traits::entity_type *)&amp;entity) &amp; entity_traits::entity_mask">
<Parameter Name="entity" Type="entity_traits::value_type &amp;"/>
</Intrinsic> </Intrinsic>
<DisplayString>{{ pools={ pools.size() } }}</DisplayString> <DisplayString>{{ size={ epool.size() } }}</DisplayString>
<Expand> <Expand>
<Item Name="[entities]">entities</Item> <Item IncludeView="simple" Name="[epool]">epool,view(simple)nr</Item>
<Synthetic Name="[epool]" ExcludeView="simple">
<DisplayString>{ epool.size() }</DisplayString>
<Expand>
<CustomListItems>
<Variable Name="pos" InitialValue="0" />
<Variable Name="last" InitialValue="epool.size()"/>
<Loop>
<Break Condition="pos == last"/>
<If Condition="to_entity(epool[pos]) == pos">
<Item Name="[{ pos }]">epool[pos]</Item>
</If>
<Exec>++pos</Exec>
</Loop>
</CustomListItems>
</Expand>
</Synthetic>
<Synthetic Name="[destroyed]" ExcludeView="simple">
<DisplayString>{ to_entity(free_list) != entity_traits::entity_mask }</DisplayString>
<Expand>
<CustomListItems>
<Variable Name="it" InitialValue="to_entity(free_list)" />
<Loop>
<Break Condition="it == entity_traits::entity_mask"/>
<Item Name="[{ it }]">epool[it]</Item>
<Exec>it = to_entity(epool[it])</Exec>
</Loop>
</CustomListItems>
</Expand>
</Synthetic>
<Synthetic Name="[pools]"> <Synthetic Name="[pools]">
<DisplayString>{ pools.size() }</DisplayString> <DisplayString>{ pools_size() }</DisplayString>
<Expand> <Expand>
<IndexListItems ExcludeView="simple"> <IndexListItems ExcludeView="simple">
<Size>pools.size()</Size> <Size>pools_size()</Size>
<ValueNode>*pools.packed.first_base::value[$i].element.second</ValueNode> <ValueNode>*pools.packed.first_base::value[$i].element.second</ValueNode>
</IndexListItems> </IndexListItems>
<IndexListItems IncludeView="simple"> <IndexListItems IncludeView="simple">
<Size>pools.size()</Size> <Size>pools_size()</Size>
<ValueNode>*pools.packed.first_base::value[$i].element.second,view(simple)</ValueNode> <ValueNode>*pools.packed.first_base::value[$i].element.second,view(simple)</ValueNode>
</IndexListItems> </IndexListItems>
</Expand> </Expand>
</Synthetic> </Synthetic>
<Item Name="[groups]" ExcludeView="simple">groups.size()</Item> <Item Name="[groups]" ExcludeView="simple">groups.size()</Item>
<Synthetic Name="[vars]"> <Synthetic Name="[vars]">
<DisplayString>{ vars.ctx.size() }</DisplayString> <DisplayString>{ vars_size() }</DisplayString>
<Expand> <Expand>
<IndexListItems> <IndexListItems>
<Size>vars.ctx.size()</Size> <Size>vars_size()</Size>
<ValueNode>vars.ctx.packed.first_base::value[$i].element.second</ValueNode> <ValueNode>vars.ctx.packed.first_base::value[$i].element.second</ValueNode>
</IndexListItems> </IndexListItems>
</Expand> </Expand>
@ -38,20 +69,20 @@
<Item Name="[capacity]" ExcludeView="simple">packed.capacity()</Item> <Item Name="[capacity]" ExcludeView="simple">packed.capacity()</Item>
<Item Name="[policy]">mode,en</Item> <Item Name="[policy]">mode,en</Item>
<Synthetic Name="[sparse]"> <Synthetic Name="[sparse]">
<DisplayString>{ sparse.size() * traits_type::page_size }</DisplayString> <DisplayString>{ sparse.size() * entity_traits::page_size }</DisplayString>
<Expand> <Expand>
<ExpandedItem IncludeView="simple">sparse,view(simple)</ExpandedItem> <ExpandedItem IncludeView="simple">sparse,view(simple)</ExpandedItem>
<CustomListItems ExcludeView="simple"> <CustomListItems ExcludeView="simple">
<Variable Name="pos" InitialValue="0"/> <Variable Name="pos" InitialValue="0"/>
<Variable Name="page" InitialValue="0"/> <Variable Name="page" InitialValue="0"/>
<Variable Name="offset" InitialValue="0"/> <Variable Name="offset" InitialValue="0"/>
<Variable Name="last" InitialValue="sparse.size() * traits_type::page_size"/> <Variable Name="last" InitialValue="sparse.size() * entity_traits::page_size"/>
<Loop> <Loop>
<Break Condition="pos == last"/> <Break Condition="pos == last"/>
<Exec>page = pos / traits_type::page_size</Exec> <Exec>page = pos / entity_traits::page_size</Exec>
<Exec>offset = pos &amp; (traits_type::page_size - 1)</Exec> <Exec>offset = pos &amp; (entity_traits::page_size - 1)</Exec>
<If Condition="sparse[page] &amp;&amp; (*((traits_type::entity_type *)&amp;sparse[page][offset]) &lt; ~traits_type::entity_mask)"> <If Condition="sparse[page] &amp;&amp; (*((entity_traits::entity_type *)&amp;sparse[page][offset]) &lt; ~entity_traits::entity_mask)">
<Item Name="[{ pos }]">*((traits_type::entity_type *)&amp;sparse[page][offset]) &amp; traits_type::entity_mask</Item> <Item Name="[{ pos }]">*((entity_traits::entity_type *)&amp;sparse[page][offset]) &amp; entity_traits::entity_mask</Item>
</If> </If>
<Exec>++pos</Exec> <Exec>++pos</Exec>
</Loop> </Loop>
@ -67,7 +98,7 @@
<Variable Name="last" InitialValue="packed.size()"/> <Variable Name="last" InitialValue="packed.size()"/>
<Loop> <Loop>
<Break Condition="pos == last"/> <Break Condition="pos == last"/>
<If Condition="*((traits_type::entity_type *)&amp;packed[pos]) &lt; ~traits_type::entity_mask"> <If Condition="*((entity_traits::entity_type *)&amp;packed[pos]) &lt; ~entity_traits::entity_mask">
<Item Name="[{ pos }]">packed[pos]</Item> <Item Name="[{ pos }]">packed[pos]</Item>
</If> </If>
<Exec>++pos</Exec> <Exec>++pos</Exec>
@ -80,19 +111,18 @@
<Type Name="entt::basic_storage&lt;*&gt;"> <Type Name="entt::basic_storage&lt;*&gt;">
<DisplayString>{{ size={ base_type::packed.size() }, type={ base_type::info->alias,na } }}</DisplayString> <DisplayString>{{ size={ base_type::packed.size() }, type={ base_type::info->alias,na } }}</DisplayString>
<Expand> <Expand>
<Item Name="[capacity]" Optional="true" ExcludeView="simple">payload.capacity() * traits_type::page_size</Item> <Item Name="[capacity]" Optional="true" ExcludeView="simple">packed.first_base::value.capacity() * comp_traits::page_size</Item>
<Item Name="[page size]" Optional="true" ExcludeView="simple">traits_type::page_size</Item> <Item Name="[page size]" Optional="true" ExcludeView="simple">comp_traits::page_size</Item>
<Item Name="[length]" Optional="true" ExcludeView="simple">length</Item>
<Item Name="[base]" ExcludeView="simple">(base_type*)this,nand</Item> <Item Name="[base]" ExcludeView="simple">(base_type*)this,nand</Item>
<Item Name="[base]" IncludeView="simple">(base_type*)this,view(simple)nand</Item> <Item Name="[base]" IncludeView="simple">(base_type*)this,view(simple)nand</Item>
<!-- having SFINAE-like techniques in natvis is priceless :) --> <!-- having SFINAE-like techniques in natvis is priceless :) -->
<CustomListItems Condition="payload.size() != 0" Optional="true"> <CustomListItems Condition="packed.first_base::value.size() != 0" Optional="true">
<Variable Name="pos" InitialValue="0" /> <Variable Name="pos" InitialValue="0" />
<Variable Name="last" InitialValue="base_type::packed.size()"/> <Variable Name="last" InitialValue="base_type::packed.size()"/>
<Loop> <Loop>
<Break Condition="pos == last"/> <Break Condition="pos == last"/>
<If Condition="*((base_type::traits_type::entity_type *)&amp;base_type::packed[pos]) &lt; ~base_type::traits_type::entity_mask"> <If Condition="*((base_type::entity_traits::entity_type *)&amp;base_type::packed[pos]) &lt; ~base_type::entity_traits::entity_mask">
<Item Name="[{ pos }:{ base_type::packed[pos] }]">payload[pos / traits_type::page_size][pos &amp; (traits_type::page_size - 1)]</Item> <Item Name="[{ pos }:{ base_type::packed[pos] }]">packed.first_base::value[pos / comp_traits::page_size][pos &amp; (comp_traits::page_size - 1)]</Item>
</If> </If>
<Exec>++pos</Exec> <Exec>++pos</Exec>
</Loop> </Loop>

View File

@ -8,7 +8,7 @@
</Expand> </Expand>
</Type> </Type>
<Type Name="entt::basic_dispatcher&lt;*&gt;"> <Type Name="entt::basic_dispatcher&lt;*&gt;">
<Intrinsic Name="size" Expression="pools.first_base::value.size()"/> <Intrinsic Name="size" Expression="pools.first_base::value.packed.first_base::value.size()"/>
<DisplayString>{{ size={ size() } }}</DisplayString> <DisplayString>{{ size={ size() } }}</DisplayString>
<Expand> <Expand>
<Synthetic Name="[pools]"> <Synthetic Name="[pools]">
@ -50,6 +50,7 @@
<DisplayString>{{ type={ "$T1" } }}</DisplayString> <DisplayString>{{ type={ "$T1" } }}</DisplayString>
<Expand> <Expand>
<Item Name="[signal]">signal,na</Item> <Item Name="[signal]">signal,na</Item>
<Item Name="[offset]">offset</Item>
</Expand> </Expand>
</Type> </Type>
</AutoVisualizer> </AutoVisualizer>

File diff suppressed because it is too large Load Diff

View File

@ -50,8 +50,6 @@
# define ENTT_ASSERT_CONSTEXPR(condition, msg) ENTT_ASSERT(condition, msg) # define ENTT_ASSERT_CONSTEXPR(condition, msg) ENTT_ASSERT(condition, msg)
#endif #endif
#define ENTT_FAIL(msg) ENTT_ASSERT(false, msg);
#ifdef ENTT_NO_ETO #ifdef ENTT_NO_ETO
# define ENTT_ETO_TYPE(Type) void # define ENTT_ETO_TYPE(Type) void
#else #else

View File

@ -4,8 +4,8 @@
#include "macro.h" #include "macro.h"
#define ENTT_VERSION_MAJOR 3 #define ENTT_VERSION_MAJOR 3
#define ENTT_VERSION_MINOR 12 #define ENTT_VERSION_MINOR 11
#define ENTT_VERSION_PATCH 2 #define ENTT_VERSION_PATCH 1
#define ENTT_VERSION \ #define ENTT_VERSION \
ENTT_XSTR(ENTT_VERSION_MAJOR) \ ENTT_XSTR(ENTT_VERSION_MAJOR) \

View File

@ -128,51 +128,51 @@ public:
return {it->element.first, it->element.second}; return {it->element.first, it->element.second};
} }
template<typename Lhs, typename Rhs> template<typename ILhs, typename IRhs>
friend constexpr std::ptrdiff_t operator-(const dense_map_iterator<Lhs> &, const dense_map_iterator<Rhs> &) noexcept; friend constexpr std::ptrdiff_t operator-(const dense_map_iterator<ILhs> &, const dense_map_iterator<IRhs> &) noexcept;
template<typename Lhs, typename Rhs> template<typename ILhs, typename IRhs>
friend constexpr bool operator==(const dense_map_iterator<Lhs> &, const dense_map_iterator<Rhs> &) noexcept; friend constexpr bool operator==(const dense_map_iterator<ILhs> &, const dense_map_iterator<IRhs> &) noexcept;
template<typename Lhs, typename Rhs> template<typename ILhs, typename IRhs>
friend constexpr bool operator<(const dense_map_iterator<Lhs> &, const dense_map_iterator<Rhs> &) noexcept; friend constexpr bool operator<(const dense_map_iterator<ILhs> &, const dense_map_iterator<IRhs> &) noexcept;
private: private:
It it; It it;
}; };
template<typename Lhs, typename Rhs> template<typename ILhs, typename IRhs>
[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_map_iterator<Lhs> &lhs, const dense_map_iterator<Rhs> &rhs) noexcept { [[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) noexcept {
return lhs.it - rhs.it; return lhs.it - rhs.it;
} }
template<typename Lhs, typename Rhs> template<typename ILhs, typename IRhs>
[[nodiscard]] constexpr bool operator==(const dense_map_iterator<Lhs> &lhs, const dense_map_iterator<Rhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator==(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) noexcept {
return lhs.it == rhs.it; return lhs.it == rhs.it;
} }
template<typename Lhs, typename Rhs> template<typename ILhs, typename IRhs>
[[nodiscard]] constexpr bool operator!=(const dense_map_iterator<Lhs> &lhs, const dense_map_iterator<Rhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator!=(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) noexcept {
return !(lhs == rhs); return !(lhs == rhs);
} }
template<typename Lhs, typename Rhs> template<typename ILhs, typename IRhs>
[[nodiscard]] constexpr bool operator<(const dense_map_iterator<Lhs> &lhs, const dense_map_iterator<Rhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator<(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) noexcept {
return lhs.it < rhs.it; return lhs.it < rhs.it;
} }
template<typename Lhs, typename Rhs> template<typename ILhs, typename IRhs>
[[nodiscard]] constexpr bool operator>(const dense_map_iterator<Lhs> &lhs, const dense_map_iterator<Rhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator>(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) noexcept {
return rhs < lhs; return rhs < lhs;
} }
template<typename Lhs, typename Rhs> template<typename ILhs, typename IRhs>
[[nodiscard]] constexpr bool operator<=(const dense_map_iterator<Lhs> &lhs, const dense_map_iterator<Rhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator<=(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) noexcept {
return !(lhs > rhs); return !(lhs > rhs);
} }
template<typename Lhs, typename Rhs> template<typename ILhs, typename IRhs>
[[nodiscard]] constexpr bool operator>=(const dense_map_iterator<Lhs> &lhs, const dense_map_iterator<Rhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator>=(const dense_map_iterator<ILhs> &lhs, const dense_map_iterator<IRhs> &rhs) noexcept {
return !(lhs < rhs); return !(lhs < rhs);
} }
@ -230,13 +230,13 @@ private:
std::size_t offset; std::size_t offset;
}; };
template<typename Lhs, typename Rhs> template<typename ILhs, typename IRhs>
[[nodiscard]] constexpr bool operator==(const dense_map_local_iterator<Lhs> &lhs, const dense_map_local_iterator<Rhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator==(const dense_map_local_iterator<ILhs> &lhs, const dense_map_local_iterator<IRhs> &rhs) noexcept {
return lhs.index() == rhs.index(); return lhs.index() == rhs.index();
} }
template<typename Lhs, typename Rhs> template<typename ILhs, typename IRhs>
[[nodiscard]] constexpr bool operator!=(const dense_map_local_iterator<Lhs> &lhs, const dense_map_local_iterator<Rhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator!=(const dense_map_local_iterator<ILhs> &lhs, const dense_map_local_iterator<IRhs> &rhs) noexcept {
return !(lhs == rhs); return !(lhs == rhs);
} }
@ -266,7 +266,7 @@ class dense_map {
static constexpr std::size_t minimum_capacity = 8u; static constexpr std::size_t minimum_capacity = 8u;
using node_type = internal::dense_map_node<Key, Type>; using node_type = internal::dense_map_node<Key, Type>;
using alloc_traits = std::allocator_traits<Allocator>; using alloc_traits = typename std::allocator_traits<Allocator>;
static_assert(std::is_same_v<typename alloc_traits::value_type, std::pair<const Key, Type>>, "Invalid value type"); static_assert(std::is_same_v<typename alloc_traits::value_type, std::pair<const Key, Type>>, "Invalid value type");
using sparse_container_type = std::vector<std::size_t, typename alloc_traits::template rebind_alloc<std::size_t>>; using sparse_container_type = std::vector<std::size_t, typename alloc_traits::template rebind_alloc<std::size_t>>;
using packed_container_type = std::vector<node_type, typename alloc_traits::template rebind_alloc<node_type>>; using packed_container_type = std::vector<node_type, typename alloc_traits::template rebind_alloc<node_type>>;
@ -464,6 +464,7 @@ public:
/** /**
* @brief Returns an iterator to the beginning. * @brief Returns an iterator to the beginning.
* *
* The returned iterator points to the first instance of the internal array.
* If the array is empty, the returned iterator will be equal to `end()`. * If the array is empty, the returned iterator will be equal to `end()`.
* *
* @return An iterator to the first instance of the internal array. * @return An iterator to the first instance of the internal array.
@ -484,6 +485,11 @@ public:
/** /**
* @brief Returns an iterator to the end. * @brief Returns an iterator to the end.
*
* The returned iterator points to the element following the last instance
* of the internal array. Attempting to dereference the returned iterator
* results in undefined behavior.
*
* @return An iterator to the element following the last instance of the * @return An iterator to the element following the last instance of the
* internal array. * internal array.
*/ */
@ -832,7 +838,7 @@ public:
} }
/*! @copydoc equal_range */ /*! @copydoc equal_range */
template<typename Other> template<class Other>
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, std::pair<const_iterator, const_iterator>>> [[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 &key) const { equal_range(const Other &key) const {
const auto it = find(key); const auto it = find(key);

View File

@ -96,51 +96,51 @@ public:
return *operator->(); return *operator->();
} }
template<typename Lhs, typename Rhs> template<typename ILhs, typename IRhs>
friend constexpr std::ptrdiff_t operator-(const dense_set_iterator<Lhs> &, const dense_set_iterator<Rhs> &) noexcept; friend constexpr std::ptrdiff_t operator-(const dense_set_iterator<ILhs> &, const dense_set_iterator<IRhs> &) noexcept;
template<typename Lhs, typename Rhs> template<typename ILhs, typename IRhs>
friend constexpr bool operator==(const dense_set_iterator<Lhs> &, const dense_set_iterator<Rhs> &) noexcept; friend constexpr bool operator==(const dense_set_iterator<ILhs> &, const dense_set_iterator<IRhs> &) noexcept;
template<typename Lhs, typename Rhs> template<typename ILhs, typename IRhs>
friend constexpr bool operator<(const dense_set_iterator<Lhs> &, const dense_set_iterator<Rhs> &) noexcept; friend constexpr bool operator<(const dense_set_iterator<ILhs> &, const dense_set_iterator<IRhs> &) noexcept;
private: private:
It it; It it;
}; };
template<typename Lhs, typename Rhs> template<typename ILhs, typename IRhs>
[[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept { [[nodiscard]] constexpr std::ptrdiff_t operator-(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) noexcept {
return lhs.it - rhs.it; return lhs.it - rhs.it;
} }
template<typename Lhs, typename Rhs> template<typename ILhs, typename IRhs>
[[nodiscard]] constexpr bool operator==(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator==(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) noexcept {
return lhs.it == rhs.it; return lhs.it == rhs.it;
} }
template<typename Lhs, typename Rhs> template<typename ILhs, typename IRhs>
[[nodiscard]] constexpr bool operator!=(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator!=(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) noexcept {
return !(lhs == rhs); return !(lhs == rhs);
} }
template<typename Lhs, typename Rhs> template<typename ILhs, typename IRhs>
[[nodiscard]] constexpr bool operator<(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator<(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) noexcept {
return lhs.it < rhs.it; return lhs.it < rhs.it;
} }
template<typename Lhs, typename Rhs> template<typename ILhs, typename IRhs>
[[nodiscard]] constexpr bool operator>(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator>(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) noexcept {
return rhs < lhs; return rhs < lhs;
} }
template<typename Lhs, typename Rhs> template<typename ILhs, typename IRhs>
[[nodiscard]] constexpr bool operator<=(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator<=(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) noexcept {
return !(lhs > rhs); return !(lhs > rhs);
} }
template<typename Lhs, typename Rhs> template<typename ILhs, typename IRhs>
[[nodiscard]] constexpr bool operator>=(const dense_set_iterator<Lhs> &lhs, const dense_set_iterator<Rhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator>=(const dense_set_iterator<ILhs> &lhs, const dense_set_iterator<IRhs> &rhs) noexcept {
return !(lhs < rhs); return !(lhs < rhs);
} }
@ -195,13 +195,13 @@ private:
std::size_t offset; std::size_t offset;
}; };
template<typename Lhs, typename Rhs> template<typename ILhs, typename IRhs>
[[nodiscard]] constexpr bool operator==(const dense_set_local_iterator<Lhs> &lhs, const dense_set_local_iterator<Rhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator==(const dense_set_local_iterator<ILhs> &lhs, const dense_set_local_iterator<IRhs> &rhs) noexcept {
return lhs.index() == rhs.index(); return lhs.index() == rhs.index();
} }
template<typename Lhs, typename Rhs> template<typename ILhs, typename IRhs>
[[nodiscard]] constexpr bool operator!=(const dense_set_local_iterator<Lhs> &lhs, const dense_set_local_iterator<Rhs> &rhs) noexcept { [[nodiscard]] constexpr bool operator!=(const dense_set_local_iterator<ILhs> &lhs, const dense_set_local_iterator<IRhs> &rhs) noexcept {
return !(lhs == rhs); return !(lhs == rhs);
} }
@ -410,6 +410,7 @@ public:
/** /**
* @brief Returns an iterator to the beginning. * @brief Returns an iterator to the beginning.
* *
* The returned iterator points to the first instance of the internal array.
* If the array is empty, the returned iterator will be equal to `end()`. * If the array is empty, the returned iterator will be equal to `end()`.
* *
* @return An iterator to the first instance of the internal array. * @return An iterator to the first instance of the internal array.
@ -430,6 +431,11 @@ public:
/** /**
* @brief Returns an iterator to the end. * @brief Returns an iterator to the end.
*
* The returned iterator points to the element following the last instance
* of the internal array. Attempting to dereference the returned iterator
* results in undefined behavior.
*
* @return An iterator to the element following the last instance of the * @return An iterator to the element following the last instance of the
* internal array. * internal array.
*/ */
@ -685,7 +691,7 @@ public:
} }
/*! @copydoc equal_range */ /*! @copydoc equal_range */
template<typename Other> template<class Other>
[[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, std::pair<const_iterator, const_iterator>>> [[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 { equal_range(const Other &value) const {
const auto it = find(value); const auto it = find(value);

View File

@ -95,15 +95,14 @@ struct radix_sort {
template<typename It, typename Getter = identity> template<typename It, typename Getter = identity>
void operator()(It first, It last, Getter getter = Getter{}) const { void operator()(It first, It last, Getter getter = Getter{}) const {
if(first < last) { if(first < last) {
constexpr auto passes = N / Bit; static constexpr auto mask = (1 << Bit) - 1;
static constexpr auto buckets = 1 << Bit;
static constexpr auto passes = N / Bit;
using value_type = typename std::iterator_traits<It>::value_type; using value_type = typename std::iterator_traits<It>::value_type;
std::vector<value_type> aux(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) { 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;
std::size_t index[buckets]{}; std::size_t index[buckets]{};
std::size_t count[buckets]{}; std::size_t count[buckets]{};

View File

@ -128,17 +128,17 @@ class basic_any {
vtable = basic_vtable<std::remove_cv_t<std::remove_reference_t<Type>>>; vtable = basic_vtable<std::remove_cv_t<std::remove_reference_t<Type>>>;
if constexpr(std::is_lvalue_reference_v<Type>) { if constexpr(std::is_lvalue_reference_v<Type>) {
static_assert((std::is_lvalue_reference_v<Args> && ...) && (sizeof...(Args) == 1u), "Invalid arguments"); static_assert(sizeof...(Args) == 1u && (std::is_lvalue_reference_v<Args> && ...), "Invalid arguments");
mode = std::is_const_v<std::remove_reference_t<Type>> ? policy::cref : policy::ref; mode = std::is_const_v<std::remove_reference_t<Type>> ? policy::cref : policy::ref;
instance = (std::addressof(args), ...); instance = (std::addressof(args), ...);
} else if constexpr(in_situ<std::remove_cv_t<std::remove_reference_t<Type>>>) { } else if constexpr(in_situ<std::remove_cv_t<std::remove_reference_t<Type>>>) {
if constexpr(std::is_aggregate_v<std::remove_cv_t<std::remove_reference_t<Type>>> && (sizeof...(Args) != 0u || !std::is_default_constructible_v<std::remove_cv_t<std::remove_reference_t<Type>>>)) { if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v<std::remove_cv_t<std::remove_reference_t<Type>>>) {
new(&storage) std::remove_cv_t<std::remove_reference_t<Type>>{std::forward<Args>(args)...}; new(&storage) std::remove_cv_t<std::remove_reference_t<Type>>{std::forward<Args>(args)...};
} else { } else {
new(&storage) std::remove_cv_t<std::remove_reference_t<Type>>(std::forward<Args>(args)...); new(&storage) std::remove_cv_t<std::remove_reference_t<Type>>(std::forward<Args>(args)...);
} }
} else { } else {
if constexpr(std::is_aggregate_v<std::remove_cv_t<std::remove_reference_t<Type>>> && (sizeof...(Args) != 0u || !std::is_default_constructible_v<std::remove_cv_t<std::remove_reference_t<Type>>>)) { if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v<std::remove_cv_t<std::remove_reference_t<Type>>>) {
instance = new std::remove_cv_t<std::remove_reference_t<Type>>{std::forward<Args>(args)...}; instance = new std::remove_cv_t<std::remove_reference_t<Type>>{std::forward<Args>(args)...};
} else { } else {
instance = new std::remove_cv_t<std::remove_reference_t<Type>>(std::forward<Args>(args)...); instance = new std::remove_cv_t<std::remove_reference_t<Type>>(std::forward<Args>(args)...);
@ -428,7 +428,7 @@ private:
* @return The element converted to the requested type. * @return The element converted to the requested type.
*/ */
template<typename Type, std::size_t Len, std::size_t Align> template<typename Type, std::size_t Len, std::size_t Align>
[[nodiscard]] Type any_cast(const basic_any<Len, Align> &data) noexcept { Type any_cast(const basic_any<Len, Align> &data) noexcept {
const auto *const instance = any_cast<std::remove_reference_t<Type>>(&data); const auto *const instance = any_cast<std::remove_reference_t<Type>>(&data);
ENTT_ASSERT(instance, "Invalid instance"); ENTT_ASSERT(instance, "Invalid instance");
return static_cast<Type>(*instance); return static_cast<Type>(*instance);
@ -436,7 +436,7 @@ template<typename Type, std::size_t Len, std::size_t Align>
/*! @copydoc any_cast */ /*! @copydoc any_cast */
template<typename Type, std::size_t Len, std::size_t Align> template<typename Type, std::size_t Len, std::size_t Align>
[[nodiscard]] Type any_cast(basic_any<Len, Align> &data) noexcept { Type any_cast(basic_any<Len, Align> &data) noexcept {
// forces const on non-reference types to make them work also with wrappers for const references // forces const on non-reference types to make them work also with wrappers for const references
auto *const instance = any_cast<std::remove_reference_t<const Type>>(&data); auto *const instance = any_cast<std::remove_reference_t<const Type>>(&data);
ENTT_ASSERT(instance, "Invalid instance"); ENTT_ASSERT(instance, "Invalid instance");
@ -445,7 +445,7 @@ template<typename Type, std::size_t Len, std::size_t Align>
/*! @copydoc any_cast */ /*! @copydoc any_cast */
template<typename Type, std::size_t Len, std::size_t Align> template<typename Type, std::size_t Len, std::size_t Align>
[[nodiscard]] Type any_cast(basic_any<Len, Align> &&data) noexcept { Type any_cast(basic_any<Len, Align> &&data) noexcept {
if constexpr(std::is_copy_constructible_v<std::remove_cv_t<std::remove_reference_t<Type>>>) { if constexpr(std::is_copy_constructible_v<std::remove_cv_t<std::remove_reference_t<Type>>>) {
if(auto *const instance = any_cast<std::remove_reference_t<Type>>(&data); instance) { if(auto *const instance = any_cast<std::remove_reference_t<Type>>(&data); instance) {
return static_cast<Type>(std::move(*instance)); return static_cast<Type>(std::move(*instance));
@ -461,14 +461,14 @@ template<typename Type, std::size_t Len, std::size_t Align>
/*! @copydoc any_cast */ /*! @copydoc any_cast */
template<typename Type, std::size_t Len, std::size_t Align> template<typename Type, std::size_t Len, std::size_t Align>
[[nodiscard]] const Type *any_cast(const basic_any<Len, Align> *data) noexcept { const Type *any_cast(const basic_any<Len, Align> *data) noexcept {
const auto &info = type_id<std::remove_cv_t<Type>>(); const auto &info = type_id<std::remove_cv_t<Type>>();
return static_cast<const Type *>(data->data(info)); return static_cast<const Type *>(data->data(info));
} }
/*! @copydoc any_cast */ /*! @copydoc any_cast */
template<typename Type, std::size_t Len, std::size_t Align> template<typename Type, std::size_t Len, std::size_t Align>
[[nodiscard]] Type *any_cast(basic_any<Len, Align> *data) noexcept { Type *any_cast(basic_any<Len, Align> *data) noexcept {
if constexpr(std::is_const_v<Type>) { if constexpr(std::is_const_v<Type>) {
// last attempt to make wrappers for const references return their values // last attempt to make wrappers for const references return their values
return any_cast<Type>(&std::as_const(*data)); return any_cast<Type>(&std::as_const(*data));
@ -488,7 +488,7 @@ template<typename Type, std::size_t Len, std::size_t Align>
* @return A properly initialized wrapper for an object of the given type. * @return A properly initialized wrapper for an object of the given type.
*/ */
template<typename Type, std::size_t Len = basic_any<>::length, std::size_t Align = basic_any<Len>::alignment, typename... Args> template<typename Type, std::size_t Len = basic_any<>::length, std::size_t Align = basic_any<Len>::alignment, typename... Args>
[[nodiscard]] basic_any<Len, Align> make_any(Args &&...args) { basic_any<Len, Align> make_any(Args &&...args) {
return basic_any<Len, Align>{std::in_place_type<Type>, std::forward<Args>(args)...}; return basic_any<Len, Align>{std::in_place_type<Type>, std::forward<Args>(args)...};
} }
@ -501,7 +501,7 @@ template<typename Type, std::size_t Len = basic_any<>::length, std::size_t Align
* @return A properly initialized and not necessarily owning wrapper. * @return A properly initialized and not necessarily owning wrapper.
*/ */
template<std::size_t Len = basic_any<>::length, std::size_t Align = basic_any<Len>::alignment, typename Type> template<std::size_t Len = basic_any<>::length, std::size_t Align = basic_any<Len>::alignment, typename Type>
[[nodiscard]] basic_any<Len, Align> forward_as_any(Type &&value) { basic_any<Len, Align> forward_as_any(Type &&value) {
return basic_any<Len, Align>{std::in_place_type<Type &&>, std::forward<Type>(value)}; return basic_any<Len, Align>{std::in_place_type<Type &&>, std::forward<Type>(value)};
} }

View File

@ -67,7 +67,7 @@ struct basic_hashed_string {
template<typename Char> template<typename Char>
class basic_hashed_string: internal::basic_hashed_string<Char> { class basic_hashed_string: internal::basic_hashed_string<Char> {
using base_type = internal::basic_hashed_string<Char>; using base_type = internal::basic_hashed_string<Char>;
using traits_type = internal::fnv1a_traits<id_type>; using hs_traits = internal::fnv1a_traits<id_type>;
struct const_wrapper { struct const_wrapper {
// non-explicit constructor on purpose // non-explicit constructor on purpose
@ -79,10 +79,10 @@ class basic_hashed_string: internal::basic_hashed_string<Char> {
// FowlerNollVo hash function v. 1a - the good // FowlerNollVo hash function v. 1a - the good
[[nodiscard]] static constexpr auto helper(const Char *str) noexcept { [[nodiscard]] static constexpr auto helper(const Char *str) noexcept {
base_type base{str, 0u, traits_type::offset}; base_type base{str, 0u, hs_traits::offset};
for(; str[base.length]; ++base.length) { for(; str[base.length]; ++base.length) {
base.hash = (base.hash ^ static_cast<traits_type::type>(str[base.length])) * traits_type::prime; base.hash = (base.hash ^ static_cast<hs_traits::type>(str[base.length])) * hs_traits::prime;
} }
return base; return base;
@ -90,10 +90,10 @@ class basic_hashed_string: internal::basic_hashed_string<Char> {
// FowlerNollVo hash function v. 1a - the good // FowlerNollVo hash function v. 1a - the good
[[nodiscard]] static constexpr auto helper(const Char *str, const std::size_t len) noexcept { [[nodiscard]] static constexpr auto helper(const Char *str, const std::size_t len) noexcept {
base_type base{str, len, traits_type::offset}; base_type base{str, len, hs_traits::offset};
for(size_type pos{}; pos < len; ++pos) { for(size_type pos{}; pos < len; ++pos) {
base.hash = (base.hash ^ static_cast<traits_type::type>(str[pos])) * traits_type::prime; base.hash = (base.hash ^ static_cast<hs_traits::type>(str[pos])) * hs_traits::prime;
} }
return base; return base;

View File

@ -106,7 +106,7 @@ constexpr void propagate_on_container_swap([[maybe_unused]] Allocator &lhs, [[ma
/** /**
* @brief Deleter for allocator-aware unique pointers (waiting for C++20). * @brief Deleter for allocator-aware unique pointers (waiting for C++20).
* @tparam Allocator Type of allocator used to manage memory and elements. * @tparam Args Types of arguments to use to construct the object.
*/ */
template<typename Allocator> template<typename Allocator>
struct allocation_deleter: private Allocator { struct allocation_deleter: private Allocator {
@ -127,7 +127,7 @@ struct allocation_deleter: private Allocator {
* @param ptr A valid pointer to an object of the given type. * @param ptr A valid pointer to an object of the given type.
*/ */
constexpr void operator()(pointer ptr) noexcept(std::is_nothrow_destructible_v<typename allocator_type::value_type>) { constexpr void operator()(pointer ptr) noexcept(std::is_nothrow_destructible_v<typename allocator_type::value_type>) {
using alloc_traits = std::allocator_traits<Allocator>; using alloc_traits = typename std::allocator_traits<Allocator>;
alloc_traits::destroy(*this, to_address(ptr)); alloc_traits::destroy(*this, to_address(ptr));
alloc_traits::deallocate(*this, ptr, 1u); alloc_traits::deallocate(*this, ptr, 1u);
} }

View File

@ -69,7 +69,7 @@ struct forward_apply: private Func {
* @tparam Args Types of arguments to use to construct the new instance. * @tparam Args Types of arguments to use to construct the new instance.
* @param args Parameters to use to construct the instance. * @param args Parameters to use to construct the instance.
*/ */
template<typename... Args> template<class... Args>
constexpr forward_apply(Args &&...args) noexcept(std::is_nothrow_constructible_v<Func, Args...>) constexpr forward_apply(Args &&...args) noexcept(std::is_nothrow_constructible_v<Func, Args...>)
: Func{std::forward<Args>(args)...} {} : Func{std::forward<Args>(args)...} {}
@ -79,13 +79,13 @@ struct forward_apply: private Func {
* @param args Parameters to forward to the underlying function. * @param args Parameters to forward to the underlying function.
* @return Return value of the underlying function, if any. * @return Return value of the underlying function, if any.
*/ */
template<typename Type> template<class Type>
constexpr decltype(auto) operator()(Type &&args) noexcept(noexcept(std::apply(std::declval<Func &>(), args))) { 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)); return std::apply(static_cast<Func &>(*this), std::forward<Type>(args));
} }
/*! @copydoc operator()() */ /*! @copydoc operator()() */
template<typename Type> template<class Type>
constexpr decltype(auto) operator()(Type &&args) const noexcept(noexcept(std::apply(std::declval<const Func &>(), args))) { 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)); return std::apply(static_cast<const Func &>(*this), std::forward<Type>(args));
} }

View File

@ -3,7 +3,6 @@
#include <cstddef> #include <cstddef>
#include <iterator> #include <iterator>
#include <tuple>
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
#include "../config/config.h" #include "../config/config.h"
@ -56,6 +55,7 @@ using type_identity_t = typename type_identity<Type>::type;
/** /**
* @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains. * @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains.
* @tparam Type The type of which to return the size. * @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> template<typename Type, typename = void>
struct size_of: std::integral_constant<std::size_t, 0u> {}; struct size_of: std::integral_constant<std::size_t, 0u> {};
@ -297,8 +297,7 @@ struct type_list_contains;
* @tparam Other Type to look for. * @tparam Other Type to look for.
*/ */
template<typename... Type, typename Other> template<typename... Type, typename Other>
struct type_list_contains<type_list<Type...>, Other> struct type_list_contains<type_list<Type...>, Other>: std::disjunction<std::is_same<Type, Other>...> {};
: std::bool_constant<(std::is_same_v<Type, Other> || ...)> {};
/** /**
* @brief Helper variable template. * @brief Helper variable template.
@ -386,20 +385,10 @@ struct value_list_element<Index, value_list<Value, Other...>>
*/ */
template<auto Value, auto... Other> template<auto Value, auto... Other>
struct value_list_element<0u, value_list<Value, Other...>> { struct value_list_element<0u, value_list<Value, Other...>> {
/*! @brief Searched type. */
using type = decltype(Value);
/*! @brief Searched value. */ /*! @brief Searched value. */
static constexpr auto value = Value; static constexpr auto value = Value;
}; };
/**
* @brief Helper type.
* @tparam Index Index of the type to return.
* @tparam List Value list to search into.
*/
template<std::size_t Index, typename List>
using value_list_element_t = typename value_list_element<Index, List>::type;
/** /**
* @brief Helper type. * @brief Helper type.
* @tparam Index Index of the value to return. * @tparam Index Index of the value to return.
@ -408,58 +397,6 @@ using value_list_element_t = typename value_list_element<Index, List>::type;
template<std::size_t Index, typename List> template<std::size_t Index, typename List>
inline constexpr auto value_list_element_v = value_list_element<Index, List>::value; inline constexpr auto value_list_element_v = value_list_element<Index, List>::value;
/*! @brief Primary template isn't defined on purpose. */
template<auto, typename>
struct value_list_index;
/**
* @brief Provides compile-time type access to the values of a value list.
* @tparam Value Value to look for and for which to return the index.
* @tparam First First value provided by the value list.
* @tparam Other Other values provided by the value list.
*/
template<auto Value, auto First, auto... Other>
struct value_list_index<Value, value_list<First, Other...>> {
/*! @brief Unsigned integer type. */
using value_type = std::size_t;
/*! @brief Compile-time position of the given value in the sublist. */
static constexpr value_type value = 1u + value_list_index<Value, value_list<Other...>>::value;
};
/**
* @brief Provides compile-time type access to the values of a value list.
* @tparam Value Value to look for and for which to return the index.
* @tparam Other Other values provided by the value list.
*/
template<auto Value, auto... Other>
struct value_list_index<Value, value_list<Value, Other...>> {
static_assert(value_list_index<Value, value_list<Other...>>::value == sizeof...(Other), "Non-unique type");
/*! @brief Unsigned integer type. */
using value_type = std::size_t;
/*! @brief Compile-time position of the given value in the sublist. */
static constexpr value_type value = 0u;
};
/**
* @brief Provides compile-time type access to the values of a value list.
* @tparam Value Value to look for and for which to return the index.
*/
template<auto Value>
struct value_list_index<Value, value_list<>> {
/*! @brief Unsigned integer type. */
using value_type = std::size_t;
/*! @brief Compile-time position of the given type in the sublist. */
static constexpr value_type value = 0u;
};
/**
* @brief Helper variable template.
* @tparam List Value list.
* @tparam Value Value to look for and for which to return the index.
*/
template<auto Value, typename List>
inline constexpr std::size_t value_list_index_v = value_list_index<Value, List>::value;
/** /**
* @brief Concatenates multiple value lists. * @brief Concatenates multiple value lists.
* @tparam Value Values provided by the first value list. * @tparam Value Values provided by the first value list.
@ -511,89 +448,6 @@ struct value_list_cat<value_list<Value...>> {
template<typename... List> template<typename... List>
using value_list_cat_t = typename value_list_cat<List...>::type; using value_list_cat_t = typename value_list_cat<List...>::type;
/*! @brief Primary template isn't defined on purpose. */
template<typename>
struct value_list_unique;
/**
* @brief Removes duplicates values from a value list.
* @tparam Value One of the values provided by the given value list.
* @tparam Other The other values provided by the given value list.
*/
template<auto Value, auto... Other>
struct value_list_unique<value_list<Value, Other...>> {
/*! @brief A value list without duplicate types. */
using type = std::conditional_t<
((Value == Other) || ...),
typename value_list_unique<value_list<Other...>>::type,
value_list_cat_t<value_list<Value>, typename value_list_unique<value_list<Other...>>::type>>;
};
/*! @brief Removes duplicates values from a value list. */
template<>
struct value_list_unique<value_list<>> {
/*! @brief A value list without duplicate types. */
using type = value_list<>;
};
/**
* @brief Helper type.
* @tparam Type A value list.
*/
template<typename Type>
using value_list_unique_t = 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...>
class 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>
class value_list_diff<value_list<Value...>, value_list<Other...>> {
using v141_toolset_workaround = value_list<Other...>;
public:
/*! @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<v141_toolset_workaround, 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. */ /*! @brief Same as std::is_invocable, but with tuples. */
template<typename, typename> template<typename, typename>
struct is_applicable: std::false_type {}; struct is_applicable: std::false_type {};
@ -714,7 +568,7 @@ inline constexpr bool is_iterator_v = is_iterator<Type>::value;
*/ */
template<typename Type> template<typename Type>
struct is_ebco_eligible struct is_ebco_eligible
: std::bool_constant<std::is_empty_v<Type> && !std::is_final_v<Type>> {}; : std::conjunction<std::is_empty<Type>, std::negation<std::is_final<Type>>> {};
/** /**
* @brief Helper variable template. * @brief Helper variable template.
@ -805,10 +659,6 @@ template<typename Type>
struct is_equality_comparable<Type, std::void_t<decltype(std::declval<Type>() == std::declval<Type>())>> struct is_equality_comparable<Type, std::void_t<decltype(std::declval<Type>() == std::declval<Type>())>>
: std::bool_constant<internal::maybe_equality_comparable<Type>(choice<2>)> {}; : std::bool_constant<internal::maybe_equality_comparable<Type>(choice<2>)> {};
/*! @copydoc is_equality_comparable */
template<typename Type, auto N>
struct is_equality_comparable<Type[N]>: std::false_type {};
/** /**
* @brief Helper variable template. * @brief Helper variable template.
* @tparam Type The type to test. * @tparam Type The type to test.
@ -905,16 +755,4 @@ using nth_argument_t = typename nth_argument<Index, Candidate>::type;
} // namespace entt } // namespace entt
template<typename... Type>
struct std::tuple_size<entt::type_list<Type...>>: std::integral_constant<std::size_t, entt::type_list<Type...>::size> {};
template<std::size_t Index, typename... Type>
struct std::tuple_element<Index, entt::type_list<Type...>>: entt::type_list_element<Index, entt::type_list<Type...>> {};
template<auto... Value>
struct std::tuple_size<entt::value_list<Value...>>: std::integral_constant<std::size_t, entt::value_list<Value...>::size> {};
template<std::size_t Index, auto... Value>
struct std::tuple_element<Index, entt::value_list<Value...>>: entt::value_list_element<Index, entt::value_list<Value...>> {};
#endif #endif

View File

@ -17,7 +17,7 @@ struct identity {
* @param value The actual argument. * @param value The actual argument.
* @return The submitted value as-is. * @return The submitted value as-is.
*/ */
template<typename Type> template<class Type>
[[nodiscard]] constexpr Type &&operator()(Type &&value) const noexcept { [[nodiscard]] constexpr Type &&operator()(Type &&value) const noexcept {
return std::forward<Type>(value); return std::forward<Type>(value);
} }
@ -50,7 +50,7 @@ template<typename Func>
* @brief Helper type for visitors. * @brief Helper type for visitors.
* @tparam Func Types of function objects. * @tparam Func Types of function objects.
*/ */
template<typename... Func> template<class... Func>
struct overloaded: Func... { struct overloaded: Func... {
using Func::operator()...; using Func::operator()...;
}; };
@ -59,14 +59,14 @@ struct overloaded: Func... {
* @brief Deduction guide. * @brief Deduction guide.
* @tparam Func Types of function objects. * @tparam Func Types of function objects.
*/ */
template<typename... Func> template<class... Func>
overloaded(Func...) -> overloaded<Func...>; overloaded(Func...) -> overloaded<Func...>;
/** /**
* @brief Basic implementation of a y-combinator. * @brief Basic implementation of a y-combinator.
* @tparam Func Type of a potentially recursive function. * @tparam Func Type of a potentially recursive function.
*/ */
template<typename Func> template<class Func>
struct y_combinator { struct y_combinator {
/** /**
* @brief Constructs a y-combinator from a given function. * @brief Constructs a y-combinator from a given function.
@ -81,13 +81,13 @@ struct y_combinator {
* @param args Parameters to use to invoke the underlying function. * @param args Parameters to use to invoke the underlying function.
* @return Return value of the underlying function, if any. * @return Return value of the underlying function, if any.
*/ */
template<typename... Args> template<class... Args>
constexpr decltype(auto) operator()(Args &&...args) const noexcept(std::is_nothrow_invocable_v<Func, const y_combinator &, Args...>) { constexpr decltype(auto) operator()(Args &&...args) const noexcept(std::is_nothrow_invocable_v<Func, const y_combinator &, Args...>) {
return func(*this, std::forward<Args>(args)...); return func(*this, std::forward<Args>(args)...);
} }
/*! @copydoc operator()() */ /*! @copydoc operator()() */
template<typename... Args> template<class... Args>
constexpr decltype(auto) operator()(Args &&...args) noexcept(std::is_nothrow_invocable_v<Func, y_combinator &, Args...>) { constexpr decltype(auto) operator()(Args &&...args) noexcept(std::is_nothrow_invocable_v<Func, y_combinator &, Args...>) {
return func(*this, std::forward<Args>(args)...); return func(*this, std::forward<Args>(args)...);
} }

View File

@ -4,7 +4,6 @@
#include <cstddef> #include <cstddef>
#include <type_traits> #include <type_traits>
#include "../config/config.h" #include "../config/config.h"
#include "fwd.hpp"
namespace entt { namespace entt {
@ -18,9 +17,6 @@ namespace internal {
template<typename Type, typename = void> template<typename Type, typename = void>
struct in_place_delete: std::bool_constant<!(std::is_move_constructible_v<Type> && std::is_move_assignable_v<Type>)> {}; struct in_place_delete: std::bool_constant<!(std::is_move_constructible_v<Type> && std::is_move_assignable_v<Type>)> {};
template<>
struct in_place_delete<void>: std::false_type {};
template<typename Type> template<typename Type>
struct in_place_delete<Type, std::enable_if_t<Type::in_place_delete>> struct in_place_delete<Type, std::enable_if_t<Type::in_place_delete>>
: std::true_type {}; : std::true_type {};
@ -28,9 +24,6 @@ struct in_place_delete<Type, std::enable_if_t<Type::in_place_delete>>
template<typename Type, typename = void> 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> {}; 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> template<typename Type>
struct page_size<Type, std::enable_if_t<std::is_convertible_v<decltype(Type::page_size), std::size_t>>> struct page_size<Type, std::enable_if_t<std::is_convertible_v<decltype(Type::page_size), std::size_t>>>
: std::integral_constant<std::size_t, Type::page_size> {}; : std::integral_constant<std::size_t, Type::page_size> {};
@ -59,6 +52,13 @@ struct component_traits {
static constexpr std::size_t page_size = internal::page_size<Type>::value; static constexpr std::size_t page_size = internal::page_size<Type>::value;
}; };
/**
* @brief Helper variable template.
* @tparam Type Type of component.
*/
template<class Type>
inline constexpr bool ignore_as_empty_v = (std::is_void_v<Type> || component_traits<Type>::page_size == 0u);
} // namespace entt } // namespace entt
#endif #endif

View File

@ -16,47 +16,35 @@ namespace entt {
namespace internal { namespace internal {
// waiting for C++20 (and std::popcount)
template<typename Type>
static constexpr int popcount(Type value) noexcept {
return value ? (int(value & 1) + popcount(value >> 1)) : 0;
}
template<typename, typename = void> template<typename, typename = void>
struct entt_traits; struct entt_traits;
template<typename Type> template<typename Type>
struct entt_traits<Type, std::enable_if_t<std::is_enum_v<Type>>> struct entt_traits<Type, std::enable_if_t<std::is_enum_v<Type>>>
: entt_traits<std::underlying_type_t<Type>> { : entt_traits<std::underlying_type_t<Type>> {};
using value_type = Type;
};
template<typename Type> template<typename Type>
struct entt_traits<Type, std::enable_if_t<std::is_class_v<Type>>> struct entt_traits<Type, std::enable_if_t<std::is_class_v<Type>>>
: entt_traits<typename Type::entity_type> { : entt_traits<typename Type::entity_type> {};
using value_type = Type;
};
template<> template<>
struct entt_traits<std::uint32_t> { struct entt_traits<std::uint32_t> {
using value_type = std::uint32_t;
using entity_type = std::uint32_t; using entity_type = std::uint32_t;
using version_type = std::uint16_t; using version_type = std::uint16_t;
static constexpr entity_type entity_mask = 0xFFFFF; static constexpr entity_type entity_mask = 0xFFFFF;
static constexpr entity_type version_mask = 0xFFF; static constexpr entity_type version_mask = 0xFFF;
static constexpr std::size_t entity_shift = 20u;
}; };
template<> template<>
struct entt_traits<std::uint64_t> { struct entt_traits<std::uint64_t> {
using value_type = std::uint64_t;
using entity_type = std::uint64_t; using entity_type = std::uint64_t;
using version_type = std::uint32_t; using version_type = std::uint32_t;
static constexpr entity_type entity_mask = 0xFFFFFFFF; static constexpr entity_type entity_mask = 0xFFFFFFFF;
static constexpr entity_type version_mask = 0xFFFFFFFF; static constexpr entity_type version_mask = 0xFFFFFFFF;
static constexpr std::size_t entity_shift = 32u;
}; };
} // namespace internal } // namespace internal
@ -67,28 +55,24 @@ struct entt_traits<std::uint64_t> {
*/ */
/** /**
* @brief Common basic entity traits implementation. * @brief Entity traits.
* @tparam Traits Actual entity traits to use. * @tparam Type Type of identifier.
*/ */
template<typename Traits> template<typename Type>
class basic_entt_traits { class entt_traits: internal::entt_traits<Type> {
static constexpr auto length = internal::popcount(Traits::entity_mask); using base_type = internal::entt_traits<Type>;
static_assert(Traits::entity_mask && ((typename Traits::entity_type{1} << length) == (Traits::entity_mask + 1)), "Invalid entity mask");
static_assert((typename Traits::entity_type{1} << internal::popcount(Traits::version_mask)) == (Traits::version_mask + 1), "Invalid version mask");
public: public:
/*! @brief Value type. */ /*! @brief Value type. */
using value_type = typename Traits::value_type; using value_type = Type;
/*! @brief Underlying entity type. */ /*! @brief Underlying entity type. */
using entity_type = typename Traits::entity_type; using entity_type = typename base_type::entity_type;
/*! @brief Underlying version type. */ /*! @brief Underlying version type. */
using version_type = typename Traits::version_type; using version_type = typename base_type::version_type;
/*! @brief Reserved identifier. */
/*! @brief Entity mask size. */ static constexpr entity_type reserved = base_type::entity_mask | (base_type::version_mask << base_type::entity_shift);
static constexpr entity_type entity_mask = Traits::entity_mask; /*! @brief Page size, default is `ENTT_SPARSE_PAGE`. */
/*! @brief Version mask size */ static constexpr auto page_size = ENTT_SPARSE_PAGE;
static constexpr entity_type version_mask = Traits::version_mask;
/** /**
* @brief Converts an entity to its underlying type. * @brief Converts an entity to its underlying type.
@ -105,7 +89,7 @@ public:
* @return The integral representation of the entity part. * @return The integral representation of the entity part.
*/ */
[[nodiscard]] static constexpr entity_type to_entity(const value_type value) noexcept { [[nodiscard]] static constexpr entity_type to_entity(const value_type value) noexcept {
return (to_integral(value) & entity_mask); return (to_integral(value) & base_type::entity_mask);
} }
/** /**
@ -114,17 +98,7 @@ public:
* @return The integral representation of the version part. * @return The integral representation of the version part.
*/ */
[[nodiscard]] static constexpr version_type to_version(const value_type value) noexcept { [[nodiscard]] static constexpr version_type to_version(const value_type value) noexcept {
return static_cast<version_type>(to_integral(value) >> length); return (to_integral(value) >> base_type::entity_shift);
}
/**
* @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_entity(value), static_cast<version_type>(vers + (vers == version_mask)));
} }
/** /**
@ -138,7 +112,7 @@ public:
* @return A properly constructed identifier. * @return A properly constructed identifier.
*/ */
[[nodiscard]] static constexpr value_type construct(const entity_type entity, const version_type version) noexcept { [[nodiscard]] static constexpr value_type construct(const entity_type entity, const version_type version) noexcept {
return value_type{(entity & entity_mask) | (static_cast<entity_type>(version) << length)}; return value_type{(entity & base_type::entity_mask) | (static_cast<entity_type>(version) << base_type::entity_shift)};
} }
/** /**
@ -152,23 +126,11 @@ public:
* @return A properly constructed identifier. * @return A properly constructed identifier.
*/ */
[[nodiscard]] static constexpr value_type combine(const entity_type lhs, const entity_type rhs) noexcept { [[nodiscard]] static constexpr value_type combine(const entity_type lhs, const entity_type rhs) noexcept {
constexpr auto mask = (version_mask << length); constexpr auto mask = (base_type::version_mask << base_type::entity_shift);
return value_type{(lhs & entity_mask) | (rhs & mask)}; return value_type{(lhs & base_type::entity_mask) | (rhs & mask)};
} }
}; };
/**
* @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;
};
/** /**
* @copydoc entt_traits<Entity>::to_integral * @copydoc entt_traits<Entity>::to_integral
* @tparam Entity The value type. * @tparam Entity The value type.
@ -205,9 +167,8 @@ struct null_t {
*/ */
template<typename Entity> template<typename Entity>
[[nodiscard]] constexpr operator Entity() const noexcept { [[nodiscard]] constexpr operator Entity() const noexcept {
using traits_type = entt_traits<Entity>; using entity_traits = entt_traits<Entity>;
constexpr auto value = traits_type::construct(traits_type::entity_mask, traits_type::version_mask); return entity_traits::combine(entity_traits::reserved, entity_traits::reserved);
return value;
} }
/** /**
@ -236,8 +197,8 @@ struct null_t {
*/ */
template<typename Entity> template<typename Entity>
[[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept { [[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept {
using traits_type = entt_traits<Entity>; using entity_traits = entt_traits<Entity>;
return traits_type::to_entity(entity) == traits_type::to_entity(*this); return entity_traits::to_entity(entity) == entity_traits::to_entity(*this);
} }
/** /**
@ -285,9 +246,8 @@ struct tombstone_t {
*/ */
template<typename Entity> template<typename Entity>
[[nodiscard]] constexpr operator Entity() const noexcept { [[nodiscard]] constexpr operator Entity() const noexcept {
using traits_type = entt_traits<Entity>; using entity_traits = entt_traits<Entity>;
constexpr auto value = traits_type::construct(traits_type::entity_mask, traits_type::version_mask); return entity_traits::combine(entity_traits::reserved, entity_traits::reserved);
return value;
} }
/** /**
@ -316,8 +276,8 @@ struct tombstone_t {
*/ */
template<typename Entity> template<typename Entity>
[[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept { [[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept {
using traits_type = entt_traits<Entity>; using entity_traits = entt_traits<Entity>;
return traits_type::to_version(entity) == traits_type::to_version(*this); return entity_traits::to_version(entity) == entity_traits::to_version(*this);
} }
/** /**

View File

@ -1,7 +1,6 @@
#ifndef ENTT_ENTITY_FWD_HPP #ifndef ENTT_ENTITY_FWD_HPP
#define ENTT_ENTITY_FWD_HPP #define ENTT_ENTITY_FWD_HPP
#include <cstdint>
#include <memory> #include <memory>
#include <type_traits> #include <type_traits>
#include "../core/fwd.hpp" #include "../core/fwd.hpp"
@ -12,14 +11,6 @@ namespace entt {
/*! @brief Default entity identifier. */ /*! @brief Default entity identifier. */
enum class entity : id_type {}; 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
};
template<typename Entity = entity, typename = std::allocator<Entity>> template<typename Entity = entity, typename = std::allocator<Entity>>
class basic_sparse_set; class basic_sparse_set;
@ -27,18 +18,18 @@ template<typename Type, typename = entity, typename = std::allocator<Type>, type
class basic_storage; class basic_storage;
template<typename Type> template<typename Type>
class sigh_mixin; class sigh_storage_mixin;
/** /**
* @brief Provides a common way to define storage types. * @brief Provides a common way to define storage types.
* @tparam Type Storage value type. * @tparam Type Storage value type.
* @tparam Entity A valid entity type. * @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Allocator Type of allocator used to manage memory and elements. * @tparam Allocator Type of allocator used to manage memory and elements.
*/ */
template<typename Type, typename Entity = entity, typename Allocator = std::allocator<Type>, typename = void> template<typename Type, typename Entity = entity, typename Allocator = std::allocator<Type>, typename = void>
struct storage_type { struct storage_type {
/*! @brief Type-to-storage conversion result. */ /*! @brief Type-to-storage conversion result. */
using type = sigh_mixin<basic_storage<Type, Entity, Allocator>>; using type = sigh_storage_mixin<basic_storage<Type, Entity, Allocator>>;
}; };
/** /**
@ -51,7 +42,7 @@ using storage_type_t = typename storage_type<Args...>::type;
/** /**
* Type-to-storage conversion utility that preserves constness. * Type-to-storage conversion utility that preserves constness.
* @tparam Type Storage value type, eventually const. * @tparam Type Storage value type, eventually const.
* @tparam Entity A valid entity type. * @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Allocator Type of allocator used to manage memory and elements. * @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>>> template<typename Type, typename Entity = entity, typename Allocator = std::allocator<std::remove_const_t<Type>>>
@ -79,7 +70,7 @@ class basic_runtime_view;
template<typename, typename, typename> template<typename, typename, typename>
class basic_group; class basic_group;
template<typename, typename Mask = std::uint32_t, typename = std::allocator<Mask>> template<typename>
class basic_observer; class basic_observer;
template<typename> template<typename>
@ -102,10 +93,7 @@ class basic_continuous_loader;
* @tparam Type List of types. * @tparam Type List of types.
*/ */
template<typename... Type> template<typename... Type>
struct exclude_t final: type_list<Type...> { using exclude_t = type_list<Type...>;
/*! @brief Default constructor. */
explicit constexpr exclude_t() {}
};
/** /**
* @brief Variable template for exclusion lists. * @brief Variable template for exclusion lists.
@ -119,10 +107,7 @@ inline constexpr exclude_t<Type...> exclude{};
* @tparam Type List of types. * @tparam Type List of types.
*/ */
template<typename... Type> template<typename... Type>
struct get_t final: type_list<Type...> { using get_t = type_list<Type...>;
/*! @brief Default constructor. */
explicit constexpr get_t() {}
};
/** /**
* @brief Variable template for lists of observed components. * @brief Variable template for lists of observed components.
@ -136,10 +121,7 @@ inline constexpr get_t<Type...> get{};
* @tparam Type List of types. * @tparam Type List of types.
*/ */
template<typename... Type> template<typename... Type>
struct owned_t final: type_list<Type...> { using owned_t = type_list<Type...>;
/*! @brief Default constructor. */
explicit constexpr owned_t() {}
};
/** /**
* @brief Variable template for lists of owned components. * @brief Variable template for lists of owned components.
@ -148,39 +130,6 @@ struct owned_t final: type_list<Type...> {
template<typename... Type> template<typename... Type>
inline constexpr owned_t<Type...> owned{}; 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 Alias declaration for the most common use case. */ /*! @brief Alias declaration for the most common use case. */
using sparse_set = basic_sparse_set<>; using sparse_set = basic_sparse_set<>;

View File

@ -5,10 +5,9 @@
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
#include "../config/config.h" #include "../config/config.h"
#include "../core/fwd.hpp"
#include "../core/iterator.hpp" #include "../core/iterator.hpp"
#include "../core/type_info.hpp"
#include "../core/type_traits.hpp" #include "../core/type_traits.hpp"
#include "component.hpp"
#include "entity.hpp" #include "entity.hpp"
#include "fwd.hpp" #include "fwd.hpp"
#include "sparse_set.hpp" #include "sparse_set.hpp"
@ -30,7 +29,7 @@ template<typename It, typename... Owned, typename... Get>
class extended_group_iterator<It, owned_t<Owned...>, get_t<Get...>> { class extended_group_iterator<It, owned_t<Owned...>, get_t<Get...>> {
template<typename Type> template<typename Type>
auto index_to_element([[maybe_unused]] Type &cpool) const { auto index_to_element([[maybe_unused]] Type &cpool) const {
if constexpr(Type::traits_type::page_size == 0u) { if constexpr(ignore_as_empty_v<typename Type::value_type>) {
return std::make_tuple(); return std::make_tuple();
} else { } else {
return std::forward_as_tuple(cpool.rbegin()[it.index()]); return std::forward_as_tuple(cpool.rbegin()[it.index()]);
@ -38,7 +37,6 @@ class extended_group_iterator<It, owned_t<Owned...>, get_t<Get...>> {
} }
public: public:
using iterator_type = It;
using difference_type = std::ptrdiff_t; using difference_type = std::ptrdiff_t;
using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval<It>()), std::declval<Owned>().get_as_tuple({})..., std::declval<Get>().get_as_tuple({})...)); using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval<It>()), std::declval<Owned>().get_as_tuple({})..., std::declval<Get>().get_as_tuple({})...));
using pointer = input_iterator_pointer<value_type>; using pointer = input_iterator_pointer<value_type>;
@ -70,10 +68,6 @@ public:
return operator*(); return operator*();
} }
[[nodiscard]] constexpr iterator_type base() const noexcept {
return it;
}
template<typename... Lhs, typename... Rhs> template<typename... Lhs, typename... Rhs>
friend constexpr bool operator==(const extended_group_iterator<Lhs...> &, const extended_group_iterator<Rhs...> &) noexcept; friend constexpr bool operator==(const extended_group_iterator<Lhs...> &, const extended_group_iterator<Rhs...> &) noexcept;
@ -92,164 +86,6 @@ template<typename... Lhs, typename... Rhs>
return !(lhs == rhs); return !(lhs == rhs);
} }
struct group_descriptor {
using size_type = std::size_t;
virtual ~group_descriptor() = default;
virtual size_type owned(const id_type *, const size_type) const noexcept {
return 0u;
}
};
template<typename, typename, typename>
class group_handler;
template<typename... Owned, typename... Get, typename... Exclude>
class group_handler<owned_t<Owned...>, get_t<Get...>, exclude_t<Exclude...>> final: public group_descriptor {
// nasty workaround for an issue with the toolset v141 that doesn't accept a fold expression here
static_assert(!std::disjunction_v<std::bool_constant<Owned::traits_type::in_place_delete>...>, "Groups do not support in-place delete");
static_assert(!std::disjunction_v<std::is_const<Owned>..., std::is_const<Get>..., std::is_const<Exclude>...>, "Const storage type not allowed");
using base_type = std::common_type_t<typename Owned::base_type..., typename Get::base_type..., typename Exclude::base_type...>;
using entity_type = typename base_type::entity_type;
void swap_elements(const std::size_t pos, const entity_type entt) {
std::apply([pos, entt](auto *...cpool) { (cpool->swap_elements(cpool->data()[pos], entt), ...); }, pools);
}
void push_on_construct(const entity_type entt) {
if(std::apply([entt, len = len](auto *cpool, auto *...other) { return cpool->contains(entt) && !(cpool->index(entt) < len) && (other->contains(entt) && ...); }, pools)
&& std::apply([entt](auto *...cpool) { return (!cpool->contains(entt) && ...); }, filter)) {
swap_elements(len++, entt);
}
}
void push_on_destroy(const entity_type entt) {
if(std::apply([entt, len = len](auto *cpool, auto *...other) { return cpool->contains(entt) && !(cpool->index(entt) < len) && (other->contains(entt) && ...); }, pools)
&& std::apply([entt](auto *...cpool) { return (0u + ... + cpool->contains(entt)) == 1u; }, filter)) {
swap_elements(len++, entt);
}
}
void remove_if(const entity_type entt) {
if(std::get<0>(pools)->contains(entt) && (std::get<0>(pools)->index(entt) < len)) {
swap_elements(--len, entt);
}
}
public:
using size_type = typename base_type::size_type;
group_handler(Owned &...opool, Get &...gpool, Exclude &...epool)
: pools{&opool..., &gpool...},
filter{&epool...},
len{} {
std::apply([this](auto *...cpool) { ((cpool->on_construct().template connect<&group_handler::push_on_construct>(*this), cpool->on_destroy().template connect<&group_handler::remove_if>(*this)), ...); }, pools);
std::apply([this](auto *...cpool) { ((cpool->on_construct().template connect<&group_handler::remove_if>(*this), cpool->on_destroy().template connect<&group_handler::push_on_destroy>(*this)), ...); }, filter);
// we cannot iterate backwards because we want to leave behind valid entities in case of owned types
for(auto *first = std::get<0>(pools)->data(), *last = first + std::get<0>(pools)->size(); first != last; ++first) {
push_on_construct(*first);
}
}
size_type owned(const id_type *elem, const size_type length) const noexcept final {
size_type cnt = 0u;
for(auto pos = 0u; pos < length; ++pos) {
cnt += ((elem[pos] == entt::type_hash<typename Owned::value_type>::value()) || ...);
}
return cnt;
}
[[nodiscard]] size_type length() const noexcept {
return len;
}
template<typename Type>
Type pools_as() const noexcept {
return pools;
}
template<typename Type>
Type filter_as() const noexcept {
return filter;
}
private:
std::tuple<Owned *..., Get *...> pools;
std::tuple<Exclude *...> filter;
std::size_t len;
};
template<typename... Get, typename... Exclude>
class group_handler<owned_t<>, get_t<Get...>, exclude_t<Exclude...>> final: public group_descriptor {
// nasty workaround for an issue with the toolset v141 that doesn't accept a fold expression here
static_assert(!std::disjunction_v<std::is_const<Get>..., std::is_const<Exclude>...>, "Const storage type not allowed");
using base_type = std::common_type_t<typename Get::base_type..., typename Exclude::base_type...>;
using entity_type = typename base_type::entity_type;
void push_on_construct(const entity_type entt) {
if(!elem.contains(entt)
&& std::apply([entt](auto *...cpool) { return (cpool->contains(entt) && ...); }, pools)
&& std::apply([entt](auto *...cpool) { return (!cpool->contains(entt) && ...); }, filter)) {
elem.push(entt);
}
}
void push_on_destroy(const entity_type entt) {
if(!elem.contains(entt)
&& std::apply([entt](auto *...cpool) { return (cpool->contains(entt) && ...); }, pools)
&& std::apply([entt](auto *...cpool) { return (0u + ... + cpool->contains(entt)) == 1u; }, filter)) {
elem.push(entt);
}
}
void remove_if(const entity_type entt) {
elem.remove(entt);
}
public:
using common_type = base_type;
template<typename Alloc>
group_handler(const Alloc &alloc, Get &...gpool, Exclude &...epool)
: pools{&gpool...},
filter{&epool...},
elem{alloc} {
std::apply([this](auto *...cpool) { ((cpool->on_construct().template connect<&group_handler::push_on_construct>(*this), cpool->on_destroy().template connect<&group_handler::remove_if>(*this)), ...); }, pools);
std::apply([this](auto *...cpool) { ((cpool->on_construct().template connect<&group_handler::remove_if>(*this), cpool->on_destroy().template connect<&group_handler::push_on_destroy>(*this)), ...); }, filter);
for(const auto entity: static_cast<base_type &>(*std::get<0>(pools))) {
push_on_construct(entity);
}
}
common_type &handle() noexcept {
return elem;
}
const common_type &handle() const noexcept {
return elem;
}
template<typename Type>
Type pools_as() const noexcept {
return pools;
}
template<typename Type>
Type filter_as() const noexcept {
return filter;
}
private:
std::tuple<Get *...> pools;
std::tuple<Exclude *...> filter;
base_type elem;
};
} // namespace internal } // namespace internal
/** /**
@ -283,28 +119,18 @@ class basic_group;
* * The entity currently pointed is destroyed. * * The entity currently pointed is destroyed.
* *
* In all other cases, modifying the pools iterated by the group in any way * In all other cases, modifying the pools iterated by the group in any way
* invalidates all the iterators. * invalidates all the iterators and using them results in undefined behavior.
* *
* @tparam Get Types of storage _observed_ 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 storage used to filter the group.
*/ */
template<typename... Get, typename... Exclude> template<typename... Get, typename... Exclude>
class basic_group<owned_t<>, get_t<Get...>, exclude_t<Exclude...>> { class basic_group<owned_t<>, get_t<Get...>, exclude_t<Exclude...>> {
using base_type = std::common_type_t<typename Get::base_type..., typename Exclude::base_type...>; using underlying_type = std::common_type_t<typename Get::entity_type..., typename Exclude::entity_type...>;
using underlying_type = typename base_type::entity_type; using basic_common_type = std::common_type_t<typename Get::base_type..., typename Exclude::base_type...>;
template<typename Type> template<typename Type>
static constexpr std::size_t index_of = type_list_index_v<std::remove_const_t<Type>, type_list<typename Get::value_type..., typename Exclude::value_type...>>; static constexpr std::size_t index_of = type_list_index_v<std::remove_const_t<Type>, type_list<typename Get::value_type...>>;
auto pools() const noexcept {
using return_type = std::tuple<Get *...>;
return descriptor ? descriptor->template pools_as<return_type>() : return_type{};
}
auto filter() const noexcept {
using return_type = std::tuple<Exclude *...>;
return descriptor ? descriptor->template filter_as<return_type>() : return_type{};
}
public: public:
/*! @brief Underlying entity identifier. */ /*! @brief Underlying entity identifier. */
@ -312,59 +138,53 @@ public:
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
using size_type = std::size_t; using size_type = std::size_t;
/*! @brief Common type among all storage types. */ /*! @brief Common type among all storage types. */
using common_type = base_type; using base_type = basic_common_type;
/*! @brief Random access iterator type. */ /*! @brief Random access iterator type. */
using iterator = typename common_type::iterator; using iterator = typename base_type::iterator;
/*! @brief Reversed iterator type. */ /*! @brief Reversed iterator type. */
using reverse_iterator = typename common_type::reverse_iterator; using reverse_iterator = typename base_type::reverse_iterator;
/*! @brief Iterable group type. */ /*! @brief Iterable group type. */
using iterable = iterable_adaptor<internal::extended_group_iterator<iterator, owned_t<>, get_t<Get...>>>; using iterable = iterable_adaptor<internal::extended_group_iterator<iterator, owned_t<>, get_t<Get...>>>;
/*! @brief Group handler type. */
using handler = internal::group_handler<owned_t<>, get_t<std::remove_const_t<Get>...>, exclude_t<std::remove_const_t<Exclude>...>>;
/*! @brief Default constructor to use to create empty, invalid groups. */ /*! @brief Default constructor to use to create empty, invalid groups. */
basic_group() noexcept basic_group() noexcept
: descriptor{} {} : handler{} {}
/** /**
* @brief Constructs a group from a set of storage classes. * @brief Constructs a group from a set of storage classes.
* @param ref A reference to a group handler. * @param ref The actual entities to iterate.
* @param gpool Storage types to iterate _observed_ by the group.
*/ */
basic_group(handler &ref) noexcept basic_group(basic_common_type &ref, Get &...gpool) noexcept
: descriptor{&ref} {} : handler{&ref},
pools{&gpool...} {}
/** /**
* @brief Returns the leading storage of a group. * @brief Returns a const reference to the underlying handler.
* @return The leading storage of the group. * @return A const reference to the underlying handler.
*/ */
[[nodiscard]] const common_type &handle() const noexcept { [[nodiscard]] const base_type &handle() const noexcept {
return descriptor->handle(); return *handler;
} }
/** /**
* @brief Returns the storage for a given component type, if any. * @brief Returns the storage for a given component type.
* @tparam Type Type of component of which to return the storage. * @tparam Type Type of component of which to return the storage.
* @return The storage for the given component type. * @return The storage for the given component type.
*/ */
template<typename Type> template<typename Type>
[[nodiscard]] auto *storage() const noexcept { [[nodiscard]] decltype(auto) storage() const noexcept {
return storage<index_of<Type>>(); return storage<index_of<Type>>();
} }
/** /**
* @brief Returns the storage for a given index, if any. * @brief Returns the storage for a given index.
* @tparam Index Index of the storage to return. * @tparam Index Index of the storage to return.
* @return The storage for the given index. * @return The storage for the given index.
*/ */
template<std::size_t Index> template<std::size_t Index>
[[nodiscard]] auto *storage() const noexcept { [[nodiscard]] decltype(auto) storage() const noexcept {
constexpr auto offset = sizeof...(Get); return *std::get<Index>(pools);
if constexpr(Index < offset) {
return std::get<Index>(pools());
} else {
return std::get<Index - offset>(filter());
}
} }
/** /**
@ -372,7 +192,7 @@ public:
* @return Number of entities that are part of the group. * @return Number of entities that are part of the group.
*/ */
[[nodiscard]] size_type size() const noexcept { [[nodiscard]] size_type size() const noexcept {
return *this ? handle().size() : size_type{}; return *this ? handler->size() : size_type{};
} }
/** /**
@ -381,13 +201,13 @@ public:
* @return Capacity of the group. * @return Capacity of the group.
*/ */
[[nodiscard]] size_type capacity() const noexcept { [[nodiscard]] size_type capacity() const noexcept {
return *this ? handle().capacity() : size_type{}; return *this ? handler->capacity() : size_type{};
} }
/*! @brief Requests the removal of unused capacity. */ /*! @brief Requests the removal of unused capacity. */
void shrink_to_fit() { void shrink_to_fit() {
if(*this) { if(*this) {
descriptor->handle().shrink_to_fit(); handler->shrink_to_fit();
} }
} }
@ -396,48 +216,60 @@ public:
* @return True if the group is empty, false otherwise. * @return True if the group is empty, false otherwise.
*/ */
[[nodiscard]] bool empty() const noexcept { [[nodiscard]] bool empty() const noexcept {
return !*this || handle().empty(); return !*this || handler->empty();
} }
/** /**
* @brief Returns an iterator to the first entity of the group. * @brief Returns an iterator to the first entity of the group.
* *
* If the group is empty, the returned iterator will be equal to `end()`. * The returned iterator points to the first entity of the group. If the
* group is empty, the returned iterator will be equal to `end()`.
* *
* @return An iterator to the first entity of the group. * @return An iterator to the first entity of the group.
*/ */
[[nodiscard]] iterator begin() const noexcept { [[nodiscard]] iterator begin() const noexcept {
return *this ? handle().begin() : iterator{}; return *this ? handler->begin() : iterator{};
} }
/** /**
* @brief Returns an iterator that is past the last entity of the group. * @brief Returns an iterator that is past the last entity of the group.
*
* The returned iterator points to the entity following the last entity of
* the group. Attempting to dereference the returned iterator results in
* undefined behavior.
*
* @return An iterator to the entity following the last entity of the * @return An iterator to the entity following the last entity of the
* group. * group.
*/ */
[[nodiscard]] iterator end() const noexcept { [[nodiscard]] iterator end() const noexcept {
return *this ? handle().end() : iterator{}; return *this ? handler->end() : iterator{};
} }
/** /**
* @brief Returns an iterator to the first entity of the reversed group. * @brief Returns an iterator to the first entity of the reversed group.
* *
* The returned iterator points to the first entity of the reversed group.
* If the group is empty, the returned iterator will be equal to `rend()`. * If the group is empty, the returned iterator will be equal to `rend()`.
* *
* @return An iterator to the first entity of the reversed group. * @return An iterator to the first entity of the reversed group.
*/ */
[[nodiscard]] reverse_iterator rbegin() const noexcept { [[nodiscard]] reverse_iterator rbegin() const noexcept {
return *this ? handle().rbegin() : reverse_iterator{}; return *this ? handler->rbegin() : reverse_iterator{};
} }
/** /**
* @brief Returns an iterator that is past the last entity of the reversed * @brief Returns an iterator that is past the last entity of the reversed
* group. * group.
*
* The returned iterator points to the entity following the last entity of
* the reversed group. Attempting to dereference the returned iterator
* results in undefined behavior.
*
* @return An iterator to the entity following the last entity of the * @return An iterator to the entity following the last entity of the
* reversed group. * reversed group.
*/ */
[[nodiscard]] reverse_iterator rend() const noexcept { [[nodiscard]] reverse_iterator rend() const noexcept {
return *this ? handle().rend() : reverse_iterator{}; return *this ? handler->rend() : reverse_iterator{};
} }
/** /**
@ -467,7 +299,8 @@ public:
* iterator otherwise. * iterator otherwise.
*/ */
[[nodiscard]] iterator find(const entity_type entt) const noexcept { [[nodiscard]] iterator find(const entity_type entt) const noexcept {
return *this ? handle().find(entt) : iterator{}; const auto it = *this ? handler->find(entt) : iterator{};
return it != end() && *it == entt ? it : end();
} }
/** /**
@ -484,7 +317,7 @@ public:
* @return True if the group is properly initialized, false otherwise. * @return True if the group is properly initialized, false otherwise.
*/ */
[[nodiscard]] explicit operator bool() const noexcept { [[nodiscard]] explicit operator bool() const noexcept {
return descriptor != nullptr; return handler != nullptr;
} }
/** /**
@ -493,47 +326,32 @@ public:
* @return True if the group contains the given entity, false otherwise. * @return True if the group contains the given entity, false otherwise.
*/ */
[[nodiscard]] bool contains(const entity_type entt) const noexcept { [[nodiscard]] bool contains(const entity_type entt) const noexcept {
return *this && handle().contains(entt); return *this && handler->contains(entt);
} }
/** /**
* @brief Returns the components assigned to the given entity. * @brief Returns the components assigned to the given entity.
* *
* @warning * Prefer this function instead of `registry::get` during iterations. It has
* Attempting to use an entity that doesn't belong to the group results in * far better performance than its counterpart.
* undefined behavior.
* *
* @tparam Type Type of the component to get. * @warning
* @tparam Other Other types of components to get. * Attempting to use an invalid component type results in a compilation
* error. Attempting to use an entity that doesn't belong to the group
* results in undefined behavior.
*
* @tparam Type Types of components to get.
* @param entt A valid identifier. * @param entt A valid identifier.
* @return The components assigned to the entity. * @return The components assigned to the entity.
*/ */
template<typename Type, typename... Other> template<typename... Type>
[[nodiscard]] decltype(auto) get(const entity_type entt) const { [[nodiscard]] decltype(auto) get(const entity_type entt) const {
return get<index_of<Type>, index_of<Other>...>(entt); if constexpr(sizeof...(Type) == 0) {
} return std::apply([entt](auto *...curr) { return std::tuple_cat(curr->get_as_tuple(entt)...); }, pools);
} else if constexpr(sizeof...(Type) == 1) {
/** return (std::get<index_of<Type>>(pools)->get(entt), ...);
* @brief Returns the components assigned to the given entity.
*
* @warning
* Attempting to use an entity that doesn't belong to the groups results in
* undefined behavior.
*
* @tparam Index Indexes of the components to get.
* @param entt A valid identifier.
* @return The components assigned to the entity.
*/
template<std::size_t... Index>
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
const auto cpools = pools();
if constexpr(sizeof...(Index) == 0) {
return std::apply([entt](auto *...curr) { return std::tuple_cat(curr->get_as_tuple(entt)...); }, cpools);
} else if constexpr(sizeof...(Index) == 1) {
return (std::get<Index>(cpools)->get(entt), ...);
} else { } else {
return std::tuple_cat(std::get<Index>(cpools)->get_as_tuple(entt)...); return std::tuple_cat(std::get<index_of<Type>>(pools)->get_as_tuple(entt)...);
} }
} }
@ -584,13 +402,16 @@ public:
* @return An iterable object to use to _visit_ the group. * @return An iterable object to use to _visit_ the group.
*/ */
[[nodiscard]] iterable each() const noexcept { [[nodiscard]] iterable each() const noexcept {
const auto cpools = pools(); return iterable{{begin(), pools}, {end(), pools}};
return iterable{{begin(), cpools}, {end(), cpools}};
} }
/** /**
* @brief Sort a group according to the given comparison function. * @brief Sort a group according to the given comparison function.
* *
* Sort the group so that iterating it with a couple of iterators returns
* entities and components in the expected order. See `begin` and `end` for
* more details.
*
* The comparison function object must return `true` if the first element * The comparison function object must return `true` if the first element
* is _less_ than the second one, `false` otherwise. The signature of the * is _less_ than the second one, `false` otherwise. The signature of the
* comparison function should be equivalent to one of the following: * comparison function should be equivalent to one of the following:
@ -612,8 +433,7 @@ public:
* * An iterator past the last element of the range to sort. * * An iterator past the last element of the range to sort.
* * A comparison function to use to compare the elements. * * A comparison function to use to compare the elements.
* *
* @tparam Type Optional type of component to compare. * @tparam Type Optional types of components to compare.
* @tparam Other Other optional types of components to compare.
* @tparam Compare Type of comparison function object. * @tparam Compare Type of comparison function object.
* @tparam Sort Type of sort function object. * @tparam Sort Type of sort function object.
* @tparam Args Types of arguments to forward to the sort function object. * @tparam Args Types of arguments to forward to the sort function object.
@ -621,60 +441,52 @@ public:
* @param algo A valid sort function object. * @param algo A valid sort function object.
* @param args Arguments to forward to the sort function object, if any. * @param args Arguments to forward to the sort function object, if any.
*/ */
template<typename Type, typename... Other, typename Compare, typename Sort = std_sort, typename... Args> template<typename... Type, typename Compare, typename Sort = std_sort, typename... Args>
void sort(Compare compare, Sort algo = Sort{}, Args &&...args) {
sort<index_of<Type>, index_of<Other>...>(std::move(compare), std::move(algo), std::forward<Args>(args)...);
}
/**
* @brief Sort a group according to the given comparison function.
*
* @sa sort
*
* @tparam Index Optional indexes of components to compare.
* @tparam Compare Type of comparison function object.
* @tparam Sort Type of sort function object.
* @tparam Args Types of arguments to forward to the sort function object.
* @param compare A valid comparison function object.
* @param algo A valid sort function object.
* @param args Arguments to forward to the sort function object, if any.
*/
template<std::size_t... Index, typename Compare, typename Sort = std_sort, typename... Args>
void sort(Compare compare, Sort algo = Sort{}, Args &&...args) { void sort(Compare compare, Sort algo = Sort{}, Args &&...args) {
if(*this) { if(*this) {
if constexpr(sizeof...(Index) == 0) { if constexpr(sizeof...(Type) == 0) {
static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>, "Invalid comparison function"); static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>, "Invalid comparison function");
descriptor->handle().sort(std::move(compare), std::move(algo), std::forward<Args>(args)...); handler->sort(std::move(compare), std::move(algo), std::forward<Args>(args)...);
} else { } else {
auto comp = [&compare, cpools = pools()](const entity_type lhs, const entity_type rhs) { auto comp = [this, &compare](const entity_type lhs, const entity_type rhs) {
if constexpr(sizeof...(Index) == 1) { if constexpr(sizeof...(Type) == 1) {
return compare((std::get<Index>(cpools)->get(lhs), ...), (std::get<Index>(cpools)->get(rhs), ...)); return compare((std::get<index_of<Type>>(pools)->get(lhs), ...), (std::get<index_of<Type>>(pools)->get(rhs), ...));
} else { } else {
return compare(std::forward_as_tuple(std::get<Index>(cpools)->get(lhs)...), std::forward_as_tuple(std::get<Index>(cpools)->get(rhs)...)); return compare(std::forward_as_tuple(std::get<index_of<Type>>(pools)->get(lhs)...), std::forward_as_tuple(std::get<index_of<Type>>(pools)->get(rhs)...));
} }
}; };
descriptor->handle().sort(std::move(comp), std::move(algo), std::forward<Args>(args)...); handler->sort(std::move(comp), std::move(algo), std::forward<Args>(args)...);
} }
} }
} }
/** /**
* @brief Sort the shared pool of entities according to a given storage. * @brief Sort the shared pool of entities according to the given component.
* *
* Non-owning groups of the same type share with the registry a pool of
* entities with its own order that doesn't depend on the order of any pool
* of components. Users can order the underlying data structure so that it
* respects the order of the pool of the given component.
*
* @note
* The shared pool of entities and thus its order is affected by the changes * The shared pool of entities and thus its order is affected by the changes
* to each and every pool that it tracks. * to each and every pool that it tracks. Therefore changes to those pools
* can quickly ruin the order imposed to the pool of entities shared between
* the non-owning groups.
* *
* @param other The storage to use to impose the order. * @tparam Type Type of component to use to impose the order.
*/ */
void sort_as(const common_type &other) const { template<typename Type>
void sort() const {
if(*this) { if(*this) {
descriptor->handle().sort_as(other); handler->respect(*std::get<index_of<Type>>(pools));
} }
} }
private: private:
handler *descriptor; base_type *const handler;
const std::tuple<Get *...> pools;
}; };
/** /**
@ -702,7 +514,7 @@ private:
* * The entity currently pointed is destroyed. * * The entity currently pointed is destroyed.
* *
* In all other cases, modifying the pools iterated by the group in any way * In all other cases, modifying the pools iterated by the group in any way
* invalidates all the iterators. * invalidates all the iterators and using them results in undefined behavior.
* *
* @tparam Owned Types of storage _owned_ by the group. * @tparam Owned Types of storage _owned_ by the group.
* @tparam Get Types of storage _observed_ by the group. * @tparam Get Types of storage _observed_ by the group.
@ -710,21 +522,11 @@ private:
*/ */
template<typename... Owned, typename... Get, typename... Exclude> template<typename... Owned, typename... Get, typename... Exclude>
class basic_group<owned_t<Owned...>, get_t<Get...>, exclude_t<Exclude...>> { class basic_group<owned_t<Owned...>, get_t<Get...>, exclude_t<Exclude...>> {
using base_type = std::common_type_t<typename Owned::base_type..., typename Get::base_type..., typename Exclude::base_type...>; using underlying_type = std::common_type_t<typename Owned::entity_type..., typename Get::entity_type..., typename Exclude::entity_type...>;
using underlying_type = typename base_type::entity_type; using basic_common_type = std::common_type_t<typename Owned::base_type..., typename Get::base_type..., typename Exclude::base_type...>;
template<typename Type> template<typename Type>
static constexpr std::size_t index_of = type_list_index_v<std::remove_const_t<Type>, type_list<typename Owned::value_type..., typename Get::value_type..., typename Exclude::value_type...>>; static constexpr std::size_t index_of = type_list_index_v<std::remove_const_t<Type>, type_list<typename Owned::value_type..., typename Get::value_type...>>;
auto pools() const noexcept {
using return_type = std::tuple<Owned *..., Get *...>;
return descriptor ? descriptor->template pools_as<return_type>() : return_type{};
}
auto filter() const noexcept {
using return_type = std::tuple<Exclude *...>;
return descriptor ? descriptor->template filter_as<return_type>() : return_type{};
}
public: public:
/*! @brief Underlying entity identifier. */ /*! @brief Underlying entity identifier. */
@ -732,59 +534,46 @@ public:
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
using size_type = std::size_t; using size_type = std::size_t;
/*! @brief Common type among all storage types. */ /*! @brief Common type among all storage types. */
using common_type = base_type; using base_type = basic_common_type;
/*! @brief Random access iterator type. */ /*! @brief Random access iterator type. */
using iterator = typename common_type::iterator; using iterator = typename base_type::iterator;
/*! @brief Reversed iterator type. */ /*! @brief Reversed iterator type. */
using reverse_iterator = typename common_type::reverse_iterator; using reverse_iterator = typename base_type::reverse_iterator;
/*! @brief Iterable group type. */ /*! @brief Iterable group type. */
using iterable = iterable_adaptor<internal::extended_group_iterator<iterator, owned_t<Owned...>, get_t<Get...>>>; using iterable = iterable_adaptor<internal::extended_group_iterator<iterator, owned_t<Owned...>, get_t<Get...>>>;
/*! @brief Group handler type. */
using handler = internal::group_handler<owned_t<std::remove_const_t<Owned>...>, get_t<std::remove_const_t<Get>...>, exclude_t<std::remove_const_t<Exclude>...>>;
/*! @brief Default constructor to use to create empty, invalid groups. */ /*! @brief Default constructor to use to create empty, invalid groups. */
basic_group() noexcept basic_group() noexcept
: descriptor{} {} : length{} {}
/** /**
* @brief Constructs a group from a set of storage classes. * @brief Constructs a group from a set of storage classes.
* @param ref A reference to a group handler. * @param extent The actual number of entities to iterate.
* @param opool Storage types to iterate _owned_ by the group.
* @param gpool Storage types to iterate _observed_ by the group.
*/ */
basic_group(handler &ref) noexcept basic_group(const std::size_t &extent, Owned &...opool, Get &...gpool) noexcept
: descriptor{&ref} {} : pools{&opool..., &gpool...},
length{&extent} {}
/** /**
* @brief Returns the leading storage of a group. * @brief Returns the storage for a given component type.
* @return The leading storage of the group.
*/
[[nodiscard]] const common_type &handle() const noexcept {
return *storage<0>();
}
/**
* @brief Returns the storage for a given component type, if any.
* @tparam Type Type of component of which to return the storage. * @tparam Type Type of component of which to return the storage.
* @return The storage for the given component type. * @return The storage for the given component type.
*/ */
template<typename Type> template<typename Type>
[[nodiscard]] auto *storage() const noexcept { [[nodiscard]] decltype(auto) storage() const noexcept {
return storage<index_of<Type>>(); return storage<index_of<Type>>();
} }
/** /**
* @brief Returns the storage for a given index, if any. * @brief Returns the storage for a given index.
* @tparam Index Index of the storage to return. * @tparam Index Index of the storage to return.
* @return The storage for the given index. * @return The storage for the given index.
*/ */
template<std::size_t Index> template<std::size_t Index>
[[nodiscard]] auto *storage() const noexcept { [[nodiscard]] decltype(auto) storage() const noexcept {
constexpr auto offset = sizeof...(Owned) + sizeof...(Get); return *std::get<Index>(pools);
if constexpr(Index < offset) {
return std::get<Index>(pools());
} else {
return std::get<Index - offset>(filter());
}
} }
/** /**
@ -792,7 +581,7 @@ public:
* @return Number of entities that that are part of the group. * @return Number of entities that that are part of the group.
*/ */
[[nodiscard]] size_type size() const noexcept { [[nodiscard]] size_type size() const noexcept {
return *this ? descriptor->length() : size_type{}; return *this ? *length : size_type{};
} }
/** /**
@ -800,48 +589,60 @@ public:
* @return True if the group is empty, false otherwise. * @return True if the group is empty, false otherwise.
*/ */
[[nodiscard]] bool empty() const noexcept { [[nodiscard]] bool empty() const noexcept {
return !*this || !descriptor->length(); return !*this || !*length;
} }
/** /**
* @brief Returns an iterator to the first entity of the group. * @brief Returns an iterator to the first entity of the group.
* *
* If the group is empty, the returned iterator will be equal to `end()`. * The returned iterator points to the first entity of the group. If the
* group is empty, the returned iterator will be equal to `end()`.
* *
* @return An iterator to the first entity of the group. * @return An iterator to the first entity of the group.
*/ */
[[nodiscard]] iterator begin() const noexcept { [[nodiscard]] iterator begin() const noexcept {
return *this ? (handle().end() - descriptor->length()) : iterator{}; return *this ? (std::get<0>(pools)->base_type::end() - *length) : iterator{};
} }
/** /**
* @brief Returns an iterator that is past the last entity of the group. * @brief Returns an iterator that is past the last entity of the group.
*
* The returned iterator points to the entity following the last entity of
* the group. Attempting to dereference the returned iterator results in
* undefined behavior.
*
* @return An iterator to the entity following the last entity of the * @return An iterator to the entity following the last entity of the
* group. * group.
*/ */
[[nodiscard]] iterator end() const noexcept { [[nodiscard]] iterator end() const noexcept {
return *this ? handle().end() : iterator{}; return *this ? std::get<0>(pools)->base_type::end() : iterator{};
} }
/** /**
* @brief Returns an iterator to the first entity of the reversed group. * @brief Returns an iterator to the first entity of the reversed group.
* *
* The returned iterator points to the first entity of the reversed group.
* If the group is empty, the returned iterator will be equal to `rend()`. * If the group is empty, the returned iterator will be equal to `rend()`.
* *
* @return An iterator to the first entity of the reversed group. * @return An iterator to the first entity of the reversed group.
*/ */
[[nodiscard]] reverse_iterator rbegin() const noexcept { [[nodiscard]] reverse_iterator rbegin() const noexcept {
return *this ? handle().rbegin() : reverse_iterator{}; return *this ? std::get<0>(pools)->base_type::rbegin() : reverse_iterator{};
} }
/** /**
* @brief Returns an iterator that is past the last entity of the reversed * @brief Returns an iterator that is past the last entity of the reversed
* group. * group.
*
* The returned iterator points to the entity following the last entity of
* the reversed group. Attempting to dereference the returned iterator
* results in undefined behavior.
*
* @return An iterator to the entity following the last entity of the * @return An iterator to the entity following the last entity of the
* reversed group. * reversed group.
*/ */
[[nodiscard]] reverse_iterator rend() const noexcept { [[nodiscard]] reverse_iterator rend() const noexcept {
return *this ? (handle().rbegin() + descriptor->length()) : reverse_iterator{}; return *this ? (std::get<0>(pools)->base_type::rbegin() + *length) : reverse_iterator{};
} }
/** /**
@ -871,8 +672,8 @@ public:
* iterator otherwise. * iterator otherwise.
*/ */
[[nodiscard]] iterator find(const entity_type entt) const noexcept { [[nodiscard]] iterator find(const entity_type entt) const noexcept {
const auto it = *this ? handle().find(entt) : iterator{}; const auto it = *this ? std::get<0>(pools)->find(entt) : iterator{};
return it >= begin() ? it : iterator{}; return it != end() && it >= begin() && *it == entt ? it : end();
} }
/** /**
@ -889,7 +690,7 @@ public:
* @return True if the group is properly initialized, false otherwise. * @return True if the group is properly initialized, false otherwise.
*/ */
[[nodiscard]] explicit operator bool() const noexcept { [[nodiscard]] explicit operator bool() const noexcept {
return descriptor != nullptr; return length != nullptr;
} }
/** /**
@ -898,47 +699,32 @@ public:
* @return True if the group contains the given entity, false otherwise. * @return True if the group contains the given entity, false otherwise.
*/ */
[[nodiscard]] bool contains(const entity_type entt) const noexcept { [[nodiscard]] bool contains(const entity_type entt) const noexcept {
return *this && handle().contains(entt) && (handle().index(entt) < (descriptor->length())); return *this && std::get<0>(pools)->contains(entt) && (std::get<0>(pools)->index(entt) < (*length));
} }
/** /**
* @brief Returns the components assigned to the given entity. * @brief Returns the components assigned to the given entity.
* *
* @warning * Prefer this function instead of `registry::get` during iterations. It has
* Attempting to use an entity that doesn't belong to the group results in * far better performance than its counterpart.
* undefined behavior.
* *
* @tparam Type Type of the component to get. * @warning
* @tparam Other Other types of components to get. * Attempting to use an invalid component type results in a compilation
* error. Attempting to use an entity that doesn't belong to the group
* results in undefined behavior.
*
* @tparam Type Types of components to get.
* @param entt A valid identifier. * @param entt A valid identifier.
* @return The components assigned to the entity. * @return The components assigned to the entity.
*/ */
template<typename Type, typename... Other> template<typename... Type>
[[nodiscard]] decltype(auto) get(const entity_type entt) const { [[nodiscard]] decltype(auto) get(const entity_type entt) const {
return get<index_of<Type>, index_of<Other>...>(entt); if constexpr(sizeof...(Type) == 0) {
} return std::apply([entt](auto *...curr) { return std::tuple_cat(curr->get_as_tuple(entt)...); }, pools);
} else if constexpr(sizeof...(Type) == 1) {
/** return (std::get<index_of<Type>>(pools)->get(entt), ...);
* @brief Returns the components assigned to the given entity.
*
* @warning
* Attempting to use an entity that doesn't belong to the groups results in
* undefined behavior.
*
* @tparam Index Indexes of the components to get.
* @param entt A valid identifier.
* @return The components assigned to the entity.
*/
template<std::size_t... Index>
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
const auto cpools = pools();
if constexpr(sizeof...(Index) == 0) {
return std::apply([entt](auto *...curr) { return std::tuple_cat(curr->get_as_tuple(entt)...); }, cpools);
} else if constexpr(sizeof...(Index) == 1) {
return (std::get<Index>(cpools)->get(entt), ...);
} else { } else {
return std::tuple_cat(std::get<Index>(cpools)->get_as_tuple(entt)...); return std::tuple_cat(std::get<index_of<Type>>(pools)->get_as_tuple(entt)...);
} }
} }
@ -989,13 +775,16 @@ public:
* @return An iterable object to use to _visit_ the group. * @return An iterable object to use to _visit_ the group.
*/ */
[[nodiscard]] iterable each() const noexcept { [[nodiscard]] iterable each() const noexcept {
const auto cpools = pools(); return {{begin(), pools}, {end(), pools}};
return {{begin(), cpools}, {end(), cpools}};
} }
/** /**
* @brief Sort a group according to the given comparison function. * @brief Sort a group according to the given comparison function.
* *
* Sort the group so that iterating it with a couple of iterators returns
* entities and components in the expected order. See `begin` and `end` for
* more details.
*
* The comparison function object must return `true` if the first element * The comparison function object must return `true` if the first element
* is _less_ than the second one, `false` otherwise. The signature of the * is _less_ than the second one, `false` otherwise. The signature of the
* comparison function should be equivalent to one of the following: * comparison function should be equivalent to one of the following:
@ -1018,8 +807,7 @@ public:
* * An iterator past the last element of the range to sort. * * An iterator past the last element of the range to sort.
* * A comparison function to use to compare the elements. * * A comparison function to use to compare the elements.
* *
* @tparam Type Optional type of component to compare. * @tparam Type Optional types of components to compare.
* @tparam Other Other optional types of components to compare.
* @tparam Compare Type of comparison function object. * @tparam Compare Type of comparison function object.
* @tparam Sort Type of sort function object. * @tparam Sort Type of sort function object.
* @tparam Args Types of arguments to forward to the sort function object. * @tparam Args Types of arguments to forward to the sort function object.
@ -1027,56 +815,36 @@ public:
* @param algo A valid sort function object. * @param algo A valid sort function object.
* @param args Arguments to forward to the sort function object, if any. * @param args Arguments to forward to the sort function object, if any.
*/ */
template<typename Type, typename... Other, typename Compare, typename Sort = std_sort, typename... Args> template<typename... Type, typename Compare, typename Sort = std_sort, typename... Args>
void sort(Compare compare, Sort algo = Sort{}, Args &&...args) const { void sort(Compare compare, Sort algo = Sort{}, Args &&...args) const {
sort<index_of<Type>, index_of<Other>...>(std::move(compare), std::move(algo), std::forward<Args>(args)...); if constexpr(sizeof...(Type) == 0) {
}
/**
* @brief Sort a group according to the given comparison function.
*
* @sa sort
*
* @tparam Index Optional indexes of components to compare.
* @tparam Compare Type of comparison function object.
* @tparam Sort Type of sort function object.
* @tparam Args Types of arguments to forward to the sort function object.
* @param compare A valid comparison function object.
* @param algo A valid sort function object.
* @param args Arguments to forward to the sort function object, if any.
*/
template<std::size_t... Index, typename Compare, typename Sort = std_sort, typename... Args>
void sort(Compare compare, Sort algo = Sort{}, Args &&...args) const {
const auto cpools = pools();
if constexpr(sizeof...(Index) == 0) {
static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>, "Invalid comparison function"); static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>, "Invalid comparison function");
storage<0>()->sort_n(descriptor->length(), std::move(compare), std::move(algo), std::forward<Args>(args)...); std::get<0>(pools)->sort_n(*length, std::move(compare), std::move(algo), std::forward<Args>(args)...);
} else { } else {
auto comp = [&compare, &cpools](const entity_type lhs, const entity_type rhs) { auto comp = [this, &compare](const entity_type lhs, const entity_type rhs) {
if constexpr(sizeof...(Index) == 1) { if constexpr(sizeof...(Type) == 1) {
return compare((std::get<Index>(cpools)->get(lhs), ...), (std::get<Index>(cpools)->get(rhs), ...)); return compare((std::get<index_of<Type>>(pools)->get(lhs), ...), (std::get<index_of<Type>>(pools)->get(rhs), ...));
} else { } else {
return compare(std::forward_as_tuple(std::get<Index>(cpools)->get(lhs)...), std::forward_as_tuple(std::get<Index>(cpools)->get(rhs)...)); return compare(std::forward_as_tuple(std::get<index_of<Type>>(pools)->get(lhs)...), std::forward_as_tuple(std::get<index_of<Type>>(pools)->get(rhs)...));
} }
}; };
storage<0>()->sort_n(descriptor->length(), std::move(comp), std::move(algo), std::forward<Args>(args)...); std::get<0>(pools)->sort_n(*length, std::move(comp), std::move(algo), std::forward<Args>(args)...);
} }
auto cb = [this](auto *head, auto *...other) { std::apply([this](auto *head, auto *...other) {
for(auto next = descriptor->length(); next; --next) { for(auto next = *length; next; --next) {
const auto pos = next - 1; const auto pos = next - 1;
[[maybe_unused]] const auto entt = head->data()[pos]; [[maybe_unused]] const auto entt = head->data()[pos];
(other->swap_elements(other->data()[pos], entt), ...); (other->swap_elements(other->data()[pos], entt), ...);
} }
}; },
pools);
std::apply(cb, cpools);
} }
private: private:
handler *descriptor; const std::tuple<Owned *..., Get *...> pools;
const size_type *const length;
}; };
} // namespace entt } // namespace entt

View File

@ -43,9 +43,7 @@ public:
: entt{value}, : entt{value},
it{from}, it{from},
last{to} { last{to} {
while(it != last && !it->second.contains(entt)) { while(it != last && !it->second.contains(entt)) { ++it; }
++it;
}
} }
constexpr handle_storage_iterator &operator++() noexcept { constexpr handle_storage_iterator &operator++() noexcept {
@ -143,7 +141,7 @@ struct basic_handle {
/** /**
* @brief Constructs a const handle from a non-const one. * @brief Constructs a const handle from a non-const one.
* @tparam Other A valid entity type. * @tparam Other A valid entity type (see entt_traits for more details).
* @tparam Args Scope of the handle to construct. * @tparam Args Scope of the handle to construct.
* @return A const handle referring to the same registry and the same * @return A const handle referring to the same registry and the same
* entity. * entity.
@ -198,7 +196,7 @@ struct basic_handle {
/*! @brief Destroys the entity associated with a handle. */ /*! @brief Destroys the entity associated with a handle. */
void destroy() { void destroy() {
reg->destroy(std::exchange(entt, null)); reg->destroy(entt);
} }
/** /**
@ -206,7 +204,7 @@ struct basic_handle {
* @param version A desired version upon destruction. * @param version A desired version upon destruction.
*/ */
void destroy(const version_type version) { void destroy(const version_type version) {
reg->destroy(std::exchange(entt, null), version); reg->destroy(entt, version);
} }
/** /**

View File

@ -3,10 +3,10 @@
#include <memory> #include <memory>
#include <type_traits> #include <type_traits>
#include <utility>
#include "../core/fwd.hpp" #include "../core/fwd.hpp"
#include "../core/type_traits.hpp" #include "../core/type_traits.hpp"
#include "../signal/delegate.hpp" #include "../signal/delegate.hpp"
#include "component.hpp"
#include "fwd.hpp" #include "fwd.hpp"
#include "group.hpp" #include "group.hpp"
#include "view.hpp" #include "view.hpp"
@ -28,7 +28,7 @@ public:
/*! @brief Type of registry to convert. */ /*! @brief Type of registry to convert. */
using registry_type = Registry; using registry_type = Registry;
/*! @brief Underlying entity identifier. */ /*! @brief Underlying entity identifier. */
using entity_type = typename registry_type::entity_type; using entity_type = std::remove_const_t<typename registry_type::entity_type>;
/** /**
* @brief Constructs a converter for a given registry. * @brief Constructs a converter for a given registry.
@ -71,7 +71,7 @@ public:
/*! @brief Type of registry to convert. */ /*! @brief Type of registry to convert. */
using registry_type = Registry; using registry_type = Registry;
/*! @brief Underlying entity identifier. */ /*! @brief Underlying entity identifier. */
using entity_type = typename registry_type::entity_type; using entity_type = std::remove_const_t<typename registry_type::entity_type>;
/** /**
* @brief Constructs a converter for a given registry. * @brief Constructs a converter for a given registry.
@ -126,133 +126,19 @@ void invoke(Registry &reg, const typename Registry::entity_type entt) {
*/ */
template<typename Registry, typename Component> template<typename Registry, typename Component>
typename Registry::entity_type to_entity(const Registry &reg, const Component &instance) { typename Registry::entity_type to_entity(const Registry &reg, const Component &instance) {
if(const auto *storage = reg.template storage<Component>(); storage) { const auto &storage = reg.template storage<Component>();
constexpr auto page_size = std::remove_const_t<std::remove_pointer_t<decltype(storage)>>::traits_type::page_size; const typename Registry::base_type &base = storage;
const typename Registry::common_type &base = *storage;
const auto *addr = std::addressof(instance); const auto *addr = std::addressof(instance);
for(auto it = base.rbegin(), last = base.rend(); it < last; it += page_size) { for(auto it = base.rbegin(), last = base.rend(); it < last; it += component_traits<Component>::page_size) {
if(const auto dist = (addr - std::addressof(storage->get(*it))); dist >= 0 && dist < static_cast<decltype(dist)>(page_size)) { if(const auto dist = (addr - std::addressof(storage.get(*it))); dist >= 0 && dist < static_cast<decltype(dist)>(component_traits<Component>::page_size)) {
return *(it + dist); return *(it + dist);
} }
} }
}
return null; return null;
} }
/*! @brief Primary template isn't defined on purpose. */
template<typename...>
struct sigh_helper;
/**
* @brief Signal connection helper for registries.
* @tparam Registry Basic registry type.
*/
template<typename Registry>
struct sigh_helper<Registry> {
/*! @brief Registry type. */
using registry_type = Registry;
/**
* @brief Constructs a helper for a given registry.
* @param ref A valid reference to a registry.
*/
sigh_helper(registry_type &ref)
: bucket{&ref} {}
/**
* @brief Binds a properly initialized helper to a given signal type.
* @tparam Type Type of signal to bind the helper to.
* @param id Optional name for the underlying storage to use.
* @return A helper for a given registry and signal type.
*/
template<typename Type>
auto with(const id_type id = type_hash<Type>::value()) noexcept {
return sigh_helper<registry_type, Type>{*bucket, id};
}
/**
* @brief Returns a reference to the underlying registry.
* @return A reference to the underlying registry.
*/
[[nodiscard]] registry_type &registry() noexcept {
return *bucket;
}
private:
registry_type *bucket;
};
/**
* @brief Signal connection helper for registries.
* @tparam Registry Basic registry type.
* @tparam Type Type of signal to connect listeners to.
*/
template<typename Registry, typename Type>
struct sigh_helper<Registry, Type> final: sigh_helper<Registry> {
/*! @brief Registry type. */
using registry_type = Registry;
/**
* @brief Constructs a helper for a given registry.
* @param ref A valid reference to a registry.
* @param id Optional name for the underlying storage to use.
*/
sigh_helper(registry_type &ref, const id_type id = type_hash<Type>::value())
: sigh_helper<Registry>{ref},
name{id} {}
/**
* @brief Forwards the call to `on_construct` on the underlying storage.
* @tparam Candidate Function or member to connect.
* @tparam Args Type of class or type of payload, if any.
* @param args A valid object that fits the purpose, if any.
* @return This helper.
*/
template<auto Candidate, typename... Args>
auto on_construct(Args &&...args) {
this->registry().template on_construct<Type>(name).template connect<Candidate>(std::forward<Args>(args)...);
return *this;
}
/**
* @brief Forwards the call to `on_update` on the underlying storage.
* @tparam Candidate Function or member to connect.
* @tparam Args Type of class or type of payload, if any.
* @param args A valid object that fits the purpose, if any.
* @return This helper.
*/
template<auto Candidate, typename... Args>
auto on_update(Args &&...args) {
this->registry().template on_update<Type>(name).template connect<Candidate>(std::forward<Args>(args)...);
return *this;
}
/**
* @brief Forwards the call to `on_destroy` on the underlying storage.
* @tparam Candidate Function or member to connect.
* @tparam Args Type of class or type of payload, if any.
* @param args A valid object that fits the purpose, if any.
* @return This helper.
*/
template<auto Candidate, typename... Args>
auto on_destroy(Args &&...args) {
this->registry().template on_destroy<Type>(name).template connect<Candidate>(std::forward<Args>(args)...);
return *this;
}
private:
id_type name;
};
/**
* @brief Deduction guide.
* @tparam Registry Basic registry type.
*/
template<typename Registry>
sigh_helper(Registry &) -> sigh_helper<Registry>;
} // namespace entt } // namespace entt
#endif #endif

View File

@ -1,293 +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 "../signal/sigh.hpp"
#include "entity.hpp"
#include "fwd.hpp"
namespace entt {
/**
* @brief Mixin type used to add signal support to storage types.
*
* The function type of a listener is equivalent to:
*
* @code{.cpp}
* void(basic_registry<entity_type> &, entity_type);
* @endcode
*
* This applies to all signals made available.
*
* @tparam Type The type of the underlying storage.
*/
template<typename Type>
class sigh_mixin final: public Type {
using underlying_type = Type;
using basic_registry_type = basic_registry<typename underlying_type::entity_type, typename underlying_type::base_type::allocator_type>;
using sigh_type = sigh<void(basic_registry_type &, const typename underlying_type::entity_type), typename underlying_type::allocator_type>;
using underlying_iterator = typename underlying_type::base_type::basic_iterator;
basic_registry_type &owner_or_assert() const noexcept {
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
return *owner;
}
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()) {
for(auto pos = underlying_type::each().begin().base().index(); !(pos < 0); --pos) {
if constexpr(underlying_type::traits_type::in_place_delete) {
if(const auto entt = underlying_type::operator[](static_cast<typename underlying_type::size_type>(pos)); entt != tombstone) {
destruction.publish(reg, entt);
}
} else {
destruction.publish(reg, underlying_type::operator[](static_cast<typename underlying_type::size_type>(pos)));
}
}
}
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;
}
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 = basic_registry_type;
/*! @brief Default constructor. */
sigh_mixin()
: sigh_mixin{allocator_type{}} {}
/**
* @brief Constructs an empty storage with a given allocator.
* @param allocator The allocator to use.
*/
explicit sigh_mixin(const allocator_type &allocator)
: underlying_type{allocator},
owner{},
construction{allocator},
destruction{allocator},
update{allocator} {}
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
sigh_mixin(sigh_mixin &&other) noexcept
: underlying_type{std::move(other)},
owner{other.owner},
construction{std::move(other.construction)},
destruction{std::move(other.destruction)},
update{std::move(other.update)} {}
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
sigh_mixin(sigh_mixin &&other, const allocator_type &allocator) noexcept
: underlying_type{std::move(other), allocator},
owner{other.owner},
construction{std::move(other.construction), allocator},
destruction{std::move(other.destruction), allocator},
update{std::move(other.update), allocator} {}
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This storage.
*/
sigh_mixin &operator=(sigh_mixin &&other) noexcept {
underlying_type::operator=(std::move(other));
owner = other.owner;
construction = std::move(other.construction);
destruction = std::move(other.destruction);
update = std::move(other.update);
return *this;
}
/**
* @brief Exchanges the contents with those of a given storage.
* @param other Storage to exchange the content with.
*/
void swap(sigh_mixin &other) {
using std::swap;
underlying_type::swap(other);
swap(owner, other.owner);
swap(construction, other.construction);
swap(destruction, other.destruction);
swap(update, other.update);
}
/**
* @brief Returns a sink object.
*
* The sink returned by this function can be used to receive notifications
* whenever a new instance is created and assigned to an entity.<br/>
* Listeners are invoked after the object has been assigned to the entity.
*
* @sa sink
*
* @return A temporary sink object.
*/
[[nodiscard]] auto on_construct() noexcept {
return sink{construction};
}
/**
* @brief Returns a sink object.
*
* The sink returned by this function can be used to receive notifications
* whenever an instance is explicitly updated.<br/>
* Listeners are invoked after the object has been updated.
*
* @sa sink
*
* @return A temporary sink object.
*/
[[nodiscard]] auto on_update() noexcept {
return sink{update};
}
/**
* @brief Returns a sink object.
*
* The sink returned by this function can be used to receive notifications
* whenever an instance is removed from an entity and thus destroyed.<br/>
* Listeners are invoked before the object has been removed from the entity.
*
* @sa sink
*
* @return A temporary sink object.
*/
[[nodiscard]] auto on_destroy() noexcept {
return sink{destruction};
}
/**
* @brief Emplace elements into a storage.
*
* The behavior of this operation depends on the underlying storage type
* (for example, components vs entities).<br/>
* Refer to the specific documentation for more details.
*
* @return A return value as returned by the underlying storage.
*/
auto emplace() {
const auto entt = underlying_type::emplace();
construction.publish(owner_or_assert(), entt);
return entt;
}
/**
* @brief Emplace elements into a storage.
*
* The behavior of this operation depends on the underlying storage type
* (for example, components vs entities).<br/>
* Refer to the specific documentation for more details.
*
* @tparam Args Types of arguments to forward to the underlying storage.
* @param hint A valid identifier.
* @param args Parameters to forward to the underlying storage.
* @return A return value as returned by the underlying storage.
*/
template<typename... Args>
decltype(auto) emplace(const entity_type hint, Args &&...args) {
if constexpr(std::is_same_v<typename underlying_type::value_type, typename underlying_type::entity_type>) {
const auto entt = underlying_type::emplace(hint, std::forward<Args>(args)...);
construction.publish(owner_or_assert(), entt);
return entt;
} else {
underlying_type::emplace(hint, std::forward<Args>(args)...);
construction.publish(owner_or_assert(), hint);
return this->get(hint);
}
}
/**
* @brief Patches the given instance for an entity.
* @tparam Func Types of the function objects to invoke.
* @param entt A valid identifier.
* @param func Valid function objects.
* @return A reference to the patched instance.
*/
template<typename... Func>
decltype(auto) patch(const entity_type entt, Func &&...func) {
underlying_type::patch(entt, std::forward<Func>(func)...);
update.publish(owner_or_assert(), entt);
return this->get(entt);
}
/**
* @brief Emplace elements into a storage.
*
* The behavior of this operation depends on the underlying storage type
* (for example, components vs entities).<br/>
* Refer to the specific documentation for more details.
*
* @tparam It Iterator type (as required by the underlying storage type).
* @tparam Args Types of arguments to forward to the underlying storage.
* @param first An iterator to the first element of the range.
* @param last An iterator past the last element of the range.
* @param args Parameters to use to forward to the underlying storage.
*/
template<typename It, typename... Args>
void insert(It first, It last, Args &&...args) {
underlying_type::insert(first, last, std::forward<Args>(args)...);
if(auto &reg = owner_or_assert(); !construction.empty()) {
for(; first != last; ++first) {
construction.publish(reg, *first);
}
}
}
/**
* @brief Forwards variables to derived classes, if any.
* @param value A variable wrapped in an opaque container.
*/
void bind(any value) noexcept final {
auto *reg = any_cast<basic_registry_type>(&value);
owner = reg ? reg : owner;
underlying_type::bind(std::move(value));
}
private:
basic_registry_type *owner;
sigh_type construction;
sigh_type destruction;
sigh_type update;
};
} // namespace entt
#endif

View File

@ -43,7 +43,7 @@ struct basic_collector<> {
* @return The updated collector. * @return The updated collector.
*/ */
template<typename... AllOf, typename... NoneOf> template<typename... AllOf, typename... NoneOf>
static constexpr auto group(exclude_t<NoneOf...> = exclude_t{}) noexcept { static constexpr auto group(exclude_t<NoneOf...> = {}) noexcept {
return basic_collector<matcher<type_list<>, type_list<>, type_list<NoneOf...>, AllOf...>>{}; return basic_collector<matcher<type_list<>, type_list<>, type_list<NoneOf...>, AllOf...>>{};
} }
@ -78,7 +78,7 @@ struct basic_collector<matcher<type_list<Reject...>, type_list<Require...>, Rule
* @return The updated collector. * @return The updated collector.
*/ */
template<typename... AllOf, typename... NoneOf> template<typename... AllOf, typename... NoneOf>
static constexpr auto group(exclude_t<NoneOf...> = exclude_t{}) noexcept { static constexpr auto group(exclude_t<NoneOf...> = {}) noexcept {
return basic_collector<matcher<type_list<>, type_list<>, type_list<NoneOf...>, AllOf...>, current_type, Other...>{}; return basic_collector<matcher<type_list<>, type_list<>, type_list<NoneOf...>, AllOf...>, current_type, Other...>{};
} }
@ -99,7 +99,7 @@ struct basic_collector<matcher<type_list<Reject...>, type_list<Require...>, Rule
* @return The updated collector. * @return The updated collector.
*/ */
template<typename... AllOf, typename... NoneOf> template<typename... AllOf, typename... NoneOf>
static constexpr auto where(exclude_t<NoneOf...> = exclude_t{}) noexcept { static constexpr auto where(exclude_t<NoneOf...> = {}) noexcept {
using extended_type = matcher<type_list<Reject..., NoneOf...>, type_list<Require..., AllOf...>, Rule...>; using extended_type = matcher<type_list<Reject..., NoneOf...>, type_list<Require..., AllOf...>, Rule...>;
return basic_collector<extended_type, Other...>{}; return basic_collector<extended_type, Other...>{};
} }
@ -146,7 +146,8 @@ inline constexpr basic_collector<> collector{};
* * The entity currently pointed is destroyed. * * The entity currently pointed is destroyed.
* *
* In all the other cases, modifying the pools of the given components in any * In all the other cases, modifying the pools of the given components in any
* way invalidates all the iterators. * way invalidates all the iterators and using them results in undefined
* behavior.
* *
* @warning * @warning
* Lifetime of an observer doesn't necessarily have to overcome that of the * Lifetime of an observer doesn't necessarily have to overcome that of the
@ -155,12 +156,10 @@ inline constexpr basic_collector<> collector{};
* pointers. * pointers.
* *
* @tparam Registry Basic registry type. * @tparam Registry Basic registry type.
* @tparam Mask Mask type.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/ */
template<typename Registry, typename Mask, typename Allocator> template<typename Registry>
class basic_observer: private basic_storage<Mask, typename Registry::entity_type, Allocator> { class basic_observer: private basic_storage<std::uint32_t, typename Registry::entity_type> {
using base_type = basic_storage<Mask, typename Registry::entity_type, Allocator>; using base_type = basic_storage<std::uint32_t, typename Registry::entity_type>;
template<typename> template<typename>
struct matcher_handler; struct matcher_handler;
@ -194,10 +193,10 @@ class basic_observer: private basic_storage<Mask, typename Registry::entity_type
} }
static void disconnect(basic_observer &obs, Registry &reg) { static void disconnect(basic_observer &obs, Registry &reg) {
(reg.template on_destroy<Require>().disconnect(&obs), ...); (reg.template on_destroy<Require>().disconnect(obs), ...);
(reg.template on_construct<Reject>().disconnect(&obs), ...); (reg.template on_construct<Reject>().disconnect(obs), ...);
reg.template on_update<AnyOf>().disconnect(&obs); reg.template on_update<AnyOf>().disconnect(obs);
reg.template on_destroy<AnyOf>().disconnect(&obs); reg.template on_destroy<AnyOf>().disconnect(obs);
} }
}; };
@ -240,12 +239,12 @@ class basic_observer: private basic_storage<Mask, typename Registry::entity_type
} }
static void disconnect(basic_observer &obs, Registry &reg) { static void disconnect(basic_observer &obs, Registry &reg) {
(reg.template on_destroy<Require>().disconnect(&obs), ...); (reg.template on_destroy<Require>().disconnect(obs), ...);
(reg.template on_construct<Reject>().disconnect(&obs), ...); (reg.template on_construct<Reject>().disconnect(obs), ...);
(reg.template on_construct<AllOf>().disconnect(&obs), ...); (reg.template on_construct<AllOf>().disconnect(obs), ...);
(reg.template on_destroy<NoneOf>().disconnect(&obs), ...); (reg.template on_destroy<NoneOf>().disconnect(obs), ...);
(reg.template on_destroy<AllOf>().disconnect(&obs), ...); (reg.template on_destroy<AllOf>().disconnect(obs), ...);
(reg.template on_construct<NoneOf>().disconnect(&obs), ...); (reg.template on_construct<NoneOf>().disconnect(obs), ...);
} }
}; };
@ -268,26 +267,15 @@ public:
using entity_type = typename registry_type::entity_type; using entity_type = typename registry_type::entity_type;
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
using size_type = std::size_t; using size_type = std::size_t;
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Random access iterator type. */ /*! @brief Random access iterator type. */
using iterator = typename registry_type::common_type::iterator; using iterator = typename registry_type::base_type::iterator;
/*! @brief Default constructor. */ /*! @brief Default constructor. */
basic_observer() basic_observer()
: basic_observer{allocator_type{}} {} : release{} {}
/**
* @brief Constructs an empty storage with a given allocator.
* @param allocator The allocator to use.
*/
explicit basic_observer(const allocator_type &allocator)
: base_type{allocator},
release{} {}
/*! @brief Default copy constructor, deleted on purpose. */ /*! @brief Default copy constructor, deleted on purpose. */
basic_observer(const basic_observer &) = delete; basic_observer(const basic_observer &) = delete;
/*! @brief Default move constructor, deleted on purpose. */ /*! @brief Default move constructor, deleted on purpose. */
basic_observer(basic_observer &&) = delete; basic_observer(basic_observer &&) = delete;
@ -295,14 +283,16 @@ public:
* @brief Creates an observer and connects it to a given registry. * @brief Creates an observer and connects it to a given registry.
* @tparam Matcher Types of matchers to use to initialize the observer. * @tparam Matcher Types of matchers to use to initialize the observer.
* @param reg A valid reference to a registry. * @param reg A valid reference to a registry.
* @param allocator The allocator to use.
*/ */
template<typename... Matcher> template<typename... Matcher>
basic_observer(registry_type &reg, basic_collector<Matcher...>, const allocator_type &allocator = allocator_type{}) basic_observer(registry_type &reg, basic_collector<Matcher...>)
: basic_observer{allocator} { : basic_observer{} {
connect<Matcher...>(reg, std::index_sequence_for<Matcher...>{}); connect<Matcher...>(reg, std::index_sequence_for<Matcher...>{});
} }
/*! @brief Default destructor. */
~basic_observer() = default;
/** /**
* @brief Default copy assignment operator, deleted on purpose. * @brief Default copy assignment operator, deleted on purpose.
* @return This observer. * @return This observer.
@ -370,7 +360,8 @@ public:
/** /**
* @brief Returns an iterator to the first entity of the observer. * @brief Returns an iterator to the first entity of the observer.
* *
* If the observer is empty, the returned iterator will be equal to `end()`. * 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. * @return An iterator to the first entity of the observer.
*/ */
@ -380,6 +371,11 @@ public:
/** /**
* @brief Returns an iterator that is past the last entity of the observer. * @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 * @return An iterator to the entity following the last entity of the
* observer. * observer.
*/ */

View File

@ -126,7 +126,7 @@ class basic_organizer final {
if constexpr(std::is_same_v<Type, Registry>) { if constexpr(std::is_same_v<Type, Registry>) {
return reg; return reg;
} else if constexpr(internal::is_view_v<Type>) { } else if constexpr(internal::is_view_v<Type>) {
return static_cast<Type>(as_view{reg}); return as_view{reg};
} else { } else {
return reg.ctx().template emplace<std::remove_reference_t<Type>>(); return reg.ctx().template emplace<std::remove_reference_t<Type>>();
} }

File diff suppressed because it is too large Load Diff

View File

@ -104,22 +104,38 @@ private:
/** /**
* @brief Generic runtime view. * @brief Generic runtime view.
* *
* Runtime views iterate over those entities that are at least in the given * Runtime views iterate over those entities that have at least all the given
* storage. During initialization, a runtime view looks at the number of * components in their bags. During initialization, a runtime view looks at the
* entities available for each component and uses the smallest set in order to * number of entities available for each component and picks up a reference to
* get a performance boost when iterating. * 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 * @b Important
* *
* Iterators aren't invalidated if: * Iterators aren't invalidated if:
* *
* * New elements are added to the storage. * * New instances of the given components are created and assigned to entities.
* * The entity currently pointed is modified (for example, components are added * * The entity currently pointed is modified (as an example, if one of the
* or removed from it). * given components is removed from the entity to which the iterator points).
* * The entity currently pointed is destroyed. * * The entity currently pointed is destroyed.
* *
* In all other cases, modifying the storage iterated by the view in any way * In all the other cases, modifying the pools of the given components in any
* invalidates all the iterators. * way invalidates all the iterators and using them results in undefined
* behavior.
*
* @note
* Views share references to the underlying data structures of the registry that
* generated them. Therefore any change to the entities and to the components
* made by means of the registry are immediately reflected by the views, unless
* a pool was missing when the view was built (in this case, the view won't
* have a valid reference and won't be updated accordingly).
*
* @warning
* Lifetime of a view must not overcome that of the registry that generated it.
* In any other case, attempting to use a view results in undefined behavior.
* *
* @tparam Type Common base type. * @tparam Type Common base type.
* @tparam Allocator Type of allocator used to manage memory and elements. * @tparam Allocator Type of allocator used to manage memory and elements.
@ -138,9 +154,9 @@ public:
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
using size_type = std::size_t; using size_type = std::size_t;
/*! @brief Common type among all storage types. */ /*! @brief Common type among all storage types. */
using common_type = Type; using base_type = Type;
/*! @brief Bidirectional iterator type. */ /*! @brief Bidirectional iterator type. */
using iterator = internal::runtime_view_iterator<common_type>; using iterator = internal::runtime_view_iterator<base_type>;
/*! @brief Default constructor to use to create empty, invalid views. */ /*! @brief Default constructor to use to create empty, invalid views. */
basic_runtime_view() noexcept basic_runtime_view() noexcept
@ -219,7 +235,7 @@ public:
* @param base An opaque reference to a storage object. * @param base An opaque reference to a storage object.
* @return This runtime view. * @return This runtime view.
*/ */
basic_runtime_view &iterate(common_type &base) { basic_runtime_view &iterate(base_type &base) {
if(pools.empty() || !(base.size() < pools[0u]->size())) { if(pools.empty() || !(base.size() < pools[0u]->size())) {
pools.push_back(&base); pools.push_back(&base);
} else { } else {
@ -234,7 +250,7 @@ public:
* @param base An opaque reference to a storage object. * @param base An opaque reference to a storage object.
* @return This runtime view. * @return This runtime view.
*/ */
basic_runtime_view &exclude(common_type &base) { basic_runtime_view &exclude(base_type &base) {
filter.push_back(&base); filter.push_back(&base);
return *this; return *this;
} }
@ -251,7 +267,9 @@ public:
* @brief Returns an iterator to the first entity that has the given * @brief Returns an iterator to the first entity that has the given
* components. * 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 components. * @return An iterator to the first entity that has the given components.
*/ */
@ -262,6 +280,11 @@ public:
/** /**
* @brief Returns an iterator that is past the last entity that has the * @brief Returns an iterator that is past the last entity that has the
* given components. * 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 * @return An iterator to the entity following the last entity that has the
* given components. * given components.
*/ */
@ -284,7 +307,8 @@ public:
* @brief Iterates entities and applies the given function object to them. * @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 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: * The signature of the function should be equivalent to the following:
* *
* @code{.cpp} * @code{.cpp}

View File

@ -1,6 +1,7 @@
#ifndef ENTT_ENTITY_SNAPSHOT_HPP #ifndef ENTT_ENTITY_SNAPSHOT_HPP
#define ENTT_ENTITY_SNAPSHOT_HPP #define ENTT_ENTITY_SNAPSHOT_HPP
#include <array>
#include <cstddef> #include <cstddef>
#include <iterator> #include <iterator>
#include <tuple> #include <tuple>
@ -10,37 +11,13 @@
#include "../config/config.h" #include "../config/config.h"
#include "../container/dense_map.hpp" #include "../container/dense_map.hpp"
#include "../core/type_traits.hpp" #include "../core/type_traits.hpp"
#include "component.hpp"
#include "entity.hpp" #include "entity.hpp"
#include "fwd.hpp" #include "fwd.hpp"
#include "view.hpp" #include "view.hpp"
namespace entt { namespace entt {
/**
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
namespace internal {
template<typename Registry>
void orphans(Registry &registry) {
auto view = registry.template view<typename Registry::entity_type>();
for(auto entt: view) {
if(registry.orphan(entt)) {
view.storage()->erase(entt);
}
}
}
} // namespace internal
/**
* Internal details not to be documented.
* @endcond
*/
/** /**
* @brief Utility class to create snapshots from a registry. * @brief Utility class to create snapshots from a registry.
* *
@ -53,8 +30,34 @@ void orphans(Registry &registry) {
*/ */
template<typename Registry> template<typename Registry>
class basic_snapshot { class basic_snapshot {
static_assert(!std::is_const_v<Registry>, "Non-const registry type required"); using entity_traits = entt_traits<typename Registry::entity_type>;
using traits_type = typename Registry::traits_type;
template<typename Component, typename Archive, typename It>
void get(Archive &archive, std::size_t sz, It first, It last) const {
const auto view = reg->template view<const Component>();
archive(typename entity_traits::entity_type(sz));
while(first != last) {
const auto entt = *(first++);
if(reg->template all_of<Component>(entt)) {
std::apply(archive, std::tuple_cat(std::make_tuple(entt), view.get(entt)));
}
}
}
template<typename... Component, typename Archive, typename It, std::size_t... Index>
void component(Archive &archive, It first, It last, std::index_sequence<Index...>) const {
std::array<std::size_t, sizeof...(Index)> size{};
auto begin = first;
while(begin != last) {
const auto entt = *(begin++);
((reg->template all_of<Component>(entt) ? ++size[Index] : 0u), ...);
}
(get<Component>(archive, size[Index], first, last), ...);
}
public: public:
/*! Basic registry type. */ /*! Basic registry type. */
@ -76,96 +79,58 @@ public:
basic_snapshot &operator=(basic_snapshot &&) noexcept = default; basic_snapshot &operator=(basic_snapshot &&) noexcept = default;
/** /**
* @brief Serializes all elements of a type with associated identifiers. * @brief Puts aside all the entities from the underlying registry.
* @tparam Type Type of elements to serialize. *
* @tparam Archive Type of output archive. * Entities are serialized along with their versions. Destroyed entities are
* @param archive A valid reference to an output archive. * taken in consideration as well by this function.
* @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) {
archive(static_cast<typename traits_type::entity_type>(storage->size()));
if constexpr(std::is_same_v<Type, entity_type>) {
archive(static_cast<typename traits_type::entity_type>(storage->in_use()));
for(auto first = storage->data(), last = first + storage->size(); first != last; ++first) {
archive(*first);
}
} 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{});
}
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.
* @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{});
}
return *this;
}
/**
* @brief Serializes all identifiers, including those to be recycled.
* @tparam Archive Type of output archive. * @tparam Archive Type of output archive.
* @param archive A valid reference to an output archive. * @param archive A valid reference to an output archive.
* @return An object of this type to continue creating the snapshot. * @return An object of this type to continue creating the snapshot.
*/ */
template<typename Archive> template<typename Archive>
[[deprecated("use .get<Entity>(archive) instead")]] const basic_snapshot &entities(Archive &archive) const { const basic_snapshot &entities(Archive &archive) const {
return get<entity_type>(archive); const auto sz = reg->size();
archive(typename entity_traits::entity_type(sz + 1u));
archive(reg->released());
for(auto first = reg->data(), last = first + sz; first != last; ++first) {
archive(*first);
}
return *this;
} }
/** /**
* @brief Serializes all elements of a type with associated identifiers. * @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 Component Types of components to serialize.
* @tparam Archive Type of output archive. * @tparam Archive Type of output archive.
* @param archive A valid reference to an output archive. * @param archive A valid reference to an output archive.
* @return An object of this type to continue creating the snapshot. * @return An object of this type to continue creating the snapshot.
*/ */
template<typename... Component, typename Archive> template<typename... Component, typename Archive>
[[deprecated("use .get<Type>(archive) instead")]] const basic_snapshot &component(Archive &archive) const { const basic_snapshot &component(Archive &archive) const {
return (get<Component>(archive), ...); if constexpr(sizeof...(Component) == 1u) {
const auto view = reg->template view<const Component...>();
(component<Component>(archive, view.rbegin(), view.rend()), ...);
return *this;
} else {
(component<Component>(archive), ...);
return *this;
}
} }
/** /**
* @brief Serializes all elements of a type with associated identifiers for * @brief Puts aside the given components for the entities in a range.
* 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 Component Types of components to serialize.
* @tparam Archive Type of output archive. * @tparam Archive Type of output archive.
* @tparam It Type of input iterator. * @tparam It Type of input iterator.
@ -175,8 +140,9 @@ public:
* @return An object of this type to continue creating the snapshot. * @return An object of this type to continue creating the snapshot.
*/ */
template<typename... Component, typename Archive, typename It> template<typename... Component, typename Archive, typename It>
[[deprecated("use .get<Type>(archive, first, last) instead")]] const basic_snapshot &component(Archive &archive, It first, It last) const { const basic_snapshot &component(Archive &archive, It first, It last) const {
return (get<Component>(archive, first, last), ...); component<Component...>(archive, first, last, std::index_sequence_for<Component...>{});
return *this;
} }
private: private:
@ -195,8 +161,33 @@ private:
*/ */
template<typename Registry> template<typename Registry>
class basic_snapshot_loader { class basic_snapshot_loader {
static_assert(!std::is_const_v<Registry>, "Non-const registry type required"); using entity_traits = entt_traits<typename Registry::entity_type>;
using traits_type = typename Registry::traits_type;
template<typename Component, typename Archive>
void assign(Archive &archive) const {
typename entity_traits::entity_type length{};
entity_type entt;
archive(length);
if constexpr(ignore_as_empty_v<Component>) {
while(length--) {
archive(entt);
const auto entity = reg->valid(entt) ? entt : reg->create(entt);
ENTT_ASSERT(entity == entt, "Entity not available for use");
reg->template emplace<Component>(entt);
}
} else {
Component instance;
while(length--) {
archive(entt, instance);
const auto entity = reg->valid(entt) ? entt : reg->create(entt);
ENTT_ASSERT(entity == entt, "Entity not available for use");
reg->template emplace<Component>(entt, std::move(instance));
}
}
}
public: public:
/*! Basic registry type. */ /*! Basic registry type. */
@ -211,9 +202,7 @@ public:
basic_snapshot_loader(registry_type &source) noexcept basic_snapshot_loader(registry_type &source) noexcept
: reg{&source} { : reg{&source} {
// restoring a snapshot as a whole requires a clean registry // restoring a snapshot as a whole requires a clean registry
for([[maybe_unused]] auto elem: source.storage()) { ENTT_ASSERT(reg->empty(), "Registry must be empty");
ENTT_ASSERT(elem.second.empty(), "Registry must be empty");
}
} }
/*! @brief Default move constructor. */ /*! @brief Default move constructor. */
@ -223,80 +212,48 @@ public:
basic_snapshot_loader &operator=(basic_snapshot_loader &&) noexcept = default; basic_snapshot_loader &operator=(basic_snapshot_loader &&) noexcept = default;
/** /**
* @brief Restores all elements of a type with associated identifiers. * @brief Restores entities that were in use during serialization.
* @tparam Type Type of elements to restore. *
* @tparam Archive Type of input archive. * This function restores the entities that were in use during serialization
* @param archive A valid reference to an input archive. * and gives them the versions they originally had.
* @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([[maybe_unused]] Archive &archive, const id_type id = type_hash<Type>::value()) {
auto &storage = reg->template storage<Type>(id);
typename traits_type::entity_type length{};
archive(length);
if constexpr(std::is_same_v<Type, entity_type>) {
typename traits_type::entity_type in_use{};
storage.reserve(length);
archive(in_use);
for(entity_type entity = null; length; --length) {
archive(entity);
storage.emplace(entity);
}
storage.in_use(in_use);
} 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.emplace(entt);
ENTT_ASSERT(entity == entt, "Entity not available for use");
if constexpr(Registry::template storage_for_type<Type>::traits_type::page_size == 0u) {
storage.emplace(entity);
} else {
Type elem{};
archive(elem);
storage.emplace(entity, std::move(elem));
}
}
}
}
return *this;
}
/**
* @brief Restores all identifiers, including those to be recycled.
* @tparam Archive Type of input archive. * @tparam Archive Type of input archive.
* @param archive A valid reference to an input archive. * @param archive A valid reference to an input archive.
* @return A valid loader to continue restoring data. * @return A valid loader to continue restoring data.
*/ */
template<typename Archive> template<typename Archive>
[[deprecated("use .get<Entity>(archive) instead")]] basic_snapshot_loader &entities(Archive &archive) { const basic_snapshot_loader &entities(Archive &archive) const {
return get<entity_type>(archive); typename entity_traits::entity_type length{};
archive(length);
std::vector<entity_type> all(length);
for(std::size_t pos{}; pos < length; ++pos) {
archive(all[pos]);
}
reg->assign(++all.cbegin(), all.cend(), all[0u]);
return *this;
} }
/** /**
* @brief Restores all elements of a type with associated identifiers. * @brief Restores components and assigns them to the right entities.
* *
* The template parameter list must be exactly the same used during * The template parameter list must be exactly the same used during
* serialization. * 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 Type of component to restore. * @tparam Component Types of components to restore.
* @tparam Archive Type of input archive. * @tparam Archive Type of input archive.
* @param archive A valid reference to an input archive. * @param archive A valid reference to an input archive.
* @return A valid loader to continue restoring data. * @return A valid loader to continue restoring data.
*/ */
template<typename... Component, typename Archive> template<typename... Component, typename Archive>
[[deprecated("use .get<Type>(archive) instead")]] basic_snapshot_loader &component(Archive &archive) { const basic_snapshot_loader &component(Archive &archive) const {
return (get<Component>(archive), ...); (assign<Component>(archive), ...);
return *this;
} }
/** /**
@ -305,12 +262,17 @@ public:
* In case all the entities were serialized but only part of the 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 * was saved, it could happen that some of the entities have no components
* once restored.<br/> * 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. * @return A valid loader to continue restoring data.
*/ */
basic_snapshot_loader &orphans() { const basic_snapshot_loader &orphans() const {
internal::orphans(*reg); reg->each([this](const auto entt) {
if(reg->orphan(entt)) {
reg->release(entt);
}
});
return *this; return *this;
} }
@ -328,7 +290,7 @@ private:
* Identifiers that entities originally had are not transferred to the target. * Identifiers that entities originally had are not transferred to the target.
* Instead, the loader maps remote identifiers to local ones while restoring a * Instead, the loader maps remote identifiers to local ones while restoring a
* snapshot.<br/> * 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 * the requirement of transferring somehow parts of the representation side to
* side. * side.
* *
@ -336,16 +298,29 @@ private:
*/ */
template<typename Registry> template<typename Registry>
class basic_continuous_loader { class basic_continuous_loader {
static_assert(!std::is_const_v<Registry>, "Non-const registry type required"); using entity_traits = entt_traits<typename Registry::entity_type>;
using traits_type = typename Registry::traits_type;
void destroy(typename Registry::entity_type entt) {
if(const auto it = remloc.find(entt); it == remloc.cend()) {
const auto local = reg->create();
remloc.emplace(entt, std::make_pair(local, true));
reg->destroy(local);
}
}
void restore(typename Registry::entity_type entt) { void restore(typename Registry::entity_type entt) {
if(const auto entity = to_entity(entt); remloc.contains(entity) && remloc[entity].first == entt) { const auto it = remloc.find(entt);
if(!reg->valid(remloc[entity].second)) {
remloc[entity].second = reg->create(); if(it == remloc.cend()) {
} const auto local = reg->create();
remloc.emplace(entt, std::make_pair(local, true));
} else { } 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;
} }
} }
@ -394,6 +369,42 @@ 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 Component, typename Archive, typename... Other, typename... Member>
void assign(Archive &archive, [[maybe_unused]] Member Other::*...member) {
typename entity_traits::entity_type length{};
entity_type entt;
archive(length);
if constexpr(ignore_as_empty_v<Component>) {
while(length--) {
archive(entt);
restore(entt);
reg->template emplace_or_replace<Component>(map(entt));
}
} else {
Component instance;
while(length--) {
archive(entt, instance);
(update(instance, member), ...);
restore(entt);
reg->template emplace_or_replace<Component>(map(entt), std::move(instance));
}
}
}
public: public:
/*! Basic registry type. */ /*! Basic registry type. */
using registry_type = Registry; using registry_type = Registry;
@ -405,8 +416,7 @@ public:
* @param source A valid reference to a registry. * @param source A valid reference to a registry.
*/ */
basic_continuous_loader(registry_type &source) noexcept basic_continuous_loader(registry_type &source) noexcept
: remloc{source.get_allocator()}, : reg{&source} {}
reg{&source} {}
/*! @brief Default move constructor. */ /*! @brief Default move constructor. */
basic_continuous_loader(basic_continuous_loader &&) = default; basic_continuous_loader(basic_continuous_loader &&) = default;
@ -415,142 +425,90 @@ public:
basic_continuous_loader &operator=(basic_continuous_loader &&) = default; basic_continuous_loader &operator=(basic_continuous_loader &&) = 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/> * This function restores the entities that were in use during serialization
* Members are either data members of type entity_type or containers of * and creates local counterparts for them if required.
* entities. In both cases, a loader visits them and replaces entities with
* their local counterpart.
*
* @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.
*/
template<typename Type, typename Archive>
basic_continuous_loader &get([[maybe_unused]] Archive &archive, const id_type id = type_hash<Type>::value()) {
auto &storage = reg->template storage<Type>(id);
typename traits_type::entity_type length{};
entity_type entt{null};
archive(length);
if constexpr(std::is_same_v<Type, entity_type>) {
typename traits_type::entity_type in_use{};
storage.reserve(length);
archive(in_use);
for(std::size_t pos{}; pos < in_use; ++pos) {
archive(entt);
restore(entt);
}
for(std::size_t pos = in_use; pos < length; ++pos) {
archive(entt);
if(const auto entity = to_entity(entt); remloc.contains(entity)) {
if(reg->valid(remloc[entity].second)) {
reg->destroy(remloc[entity].second);
}
remloc.erase(entity);
}
}
} else {
for(auto &&ref: remloc) {
storage.remove(ref.second.second);
}
while(length--) {
if(archive(entt); entt != null) {
restore(entt);
if constexpr(Registry::template storage_for_type<Type>::traits_type::page_size == 0u) {
storage.emplace(map(entt));
} else {
Type elem{};
archive(elem);
storage.emplace(map(entt), std::move(elem));
}
}
}
}
return *this;
}
/**
* @brief Restores all identifiers, including those to be recycled.
*
* It creates local counterparts for remote elements as needed.
* *
* @tparam Archive Type of input archive. * @tparam Archive Type of input archive.
* @param archive A valid reference to an input archive. * @param archive A valid reference to an input archive.
* @return A non-const reference to this loader. * @return A non-const reference to this loader.
*/ */
template<typename Archive> template<typename Archive>
[[deprecated("use .get<Entity>(archive) instead")]] basic_continuous_loader &entities(Archive &archive) { basic_continuous_loader &entities(Archive &archive) {
return get<entity_type>(archive); typename entity_traits::entity_type length{};
} entity_type entt{};
/**
* @brief Serializes all elements of a type with associated identifiers.
*
* 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.
*
* @tparam Component Type of component to restore.
* @tparam Archive Type of input archive.
* @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... Member, typename... Clazz>
[[deprecated("use .component<Type>(archive, members...) instead")]] basic_continuous_loader &component(Archive &archive, Member Clazz::*...member) {
([&](auto &storage) {
for(auto &&ref: remloc) {
storage.remove(ref.second.second);
}
typename traits_type::entity_type length{};
entity_type entt{null};
archive(length); archive(length);
// discards the head of the list of destroyed entities
archive(entt);
while(length--) { for(std::size_t pos{}, last = length - 1u; pos < last; ++pos) {
if(archive(entt); entt != null) { archive(entt);
if(const auto entity = entity_traits::to_entity(entt); entity == pos) {
restore(entt); restore(entt);
if constexpr(std::remove_reference_t<decltype(storage)>::traits_type::page_size == 0u) {
storage.emplace(map(entt));
} else { } else {
typename std::remove_reference_t<decltype(storage)>::value_type elem{}; destroy(entt);
archive(elem);
(update(elem, member), ...);
storage.emplace(map(entt), std::move(elem));
} }
} }
}
}(reg->template storage<Component>()),
...);
return *this; return *this;
} }
/** /**
* @brief Helps to purge entities that no longer have a counterpart. * @brief Restores components and assigns them to the right entities.
*
* The template parameter list must be exactly the same used during
* serialization. In the event that the entity to which the component is
* assigned doesn't exist yet, the loader will take care to create a local
* counterpart for it.<br/>
* Members can be either data members of type entity_type or containers of
* entities. In both cases, the loader will visit them and update the
* entities by replacing each one with its local counterpart.
*
* @tparam Component Type of component to restore.
* @tparam Archive Type of input archive.
* @tparam Other Types of components to update with local counterparts.
* @tparam Member Types of members to update with their local counterparts.
* @param archive A valid reference to an input archive.
* @param member Members to update with their local counterparts.
* @return A non-const reference to this loader.
*/
template<typename... Component, typename Archive, typename... Other, typename... Member>
basic_continuous_loader &component(Archive &archive, Member Other::*...member) {
(remove_if_exists<Component>(), ...);
(assign<Component>(archive, member...), ...);
return *this;
}
/**
* @brief Helps to purge entities that no longer have a conterpart.
* *
* Users should invoke this member function after restoring each snapshot, * Users should invoke this member function after restoring each snapshot,
* unless they know exactly what they are doing. * unless they know exactly what they are doing.
* *
* @return A non-const reference to this loader. * @return A non-const reference to this loader.
*/ */
[[deprecated("use .get<Entity>(archive) instead")]] basic_continuous_loader &shrink() { basic_continuous_loader &shrink() {
auto it = remloc.begin();
while(it != remloc.cend()) {
const auto local = it->second.first;
bool &dirty = it->second.second;
if(dirty) {
dirty = false;
++it;
} else {
if(reg->valid(local)) {
reg->destroy(local);
}
it = remloc.erase(it);
}
}
return *this; return *this;
} }
@ -560,12 +518,17 @@ public:
* In case all the entities were serialized but only part of the 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 * was saved, it could happen that some of the entities have no components
* once restored.<br/> * 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. * @return A non-const reference to this loader.
*/ */
basic_continuous_loader &orphans() { basic_continuous_loader &orphans() {
internal::orphans(*reg); reg->each([this](const auto entt) {
if(reg->orphan(entt)) {
reg->release(entt);
}
});
return *this; return *this;
} }
@ -575,8 +538,7 @@ public:
* @return True if `entity` is managed by the loader, false otherwise. * @return True if `entity` is managed by the loader, false otherwise.
*/ */
[[nodiscard]] bool contains(entity_type entt) const noexcept { [[nodiscard]] bool contains(entity_type entt) const noexcept {
const auto it = remloc.find(to_entity(entt)); return (remloc.find(entt) != remloc.cend());
return it != remloc.cend() && it->second.first == entt;
} }
/** /**
@ -585,15 +547,18 @@ public:
* @return The local identifier if any, the null entity otherwise. * @return The local identifier if any, the null entity otherwise.
*/ */
[[nodiscard]] entity_type map(entity_type entt) const noexcept { [[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) { const auto it = remloc.find(entt);
return it->second.second; entity_type other = null;
if(it != remloc.cend()) {
other = it->second.first;
} }
return null; return other;
} }
private: private:
dense_map<typename traits_type::entity_type, std::pair<entity_type, entity_type>> remloc; dense_map<entity_type, std::pair<entity_type, bool>> remloc;
registry_type *reg; registry_type *reg;
}; };

View File

@ -88,10 +88,6 @@ struct sparse_set_iterator final {
return *operator->(); return *operator->();
} }
[[nodiscard]] constexpr pointer data() const noexcept {
return packed ? packed->data() : nullptr;
}
[[nodiscard]] constexpr difference_type index() const noexcept { [[nodiscard]] constexpr difference_type index() const noexcept {
return offset - 1; return offset - 1;
} }
@ -101,38 +97,38 @@ private:
difference_type offset; difference_type offset;
}; };
template<typename Container> template<typename Type, typename Other>
[[nodiscard]] constexpr std::ptrdiff_t operator-(const sparse_set_iterator<Container> &lhs, const sparse_set_iterator<Container> &rhs) noexcept { [[nodiscard]] constexpr std::ptrdiff_t operator-(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) noexcept {
return rhs.index() - lhs.index(); return rhs.index() - lhs.index();
} }
template<typename Container> template<typename Type, typename Other>
[[nodiscard]] constexpr bool operator==(const sparse_set_iterator<Container> &lhs, const sparse_set_iterator<Container> &rhs) noexcept { [[nodiscard]] constexpr bool operator==(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) noexcept {
return lhs.index() == rhs.index(); return lhs.index() == rhs.index();
} }
template<typename Container> template<typename Type, typename Other>
[[nodiscard]] constexpr bool operator!=(const sparse_set_iterator<Container> &lhs, const sparse_set_iterator<Container> &rhs) noexcept { [[nodiscard]] constexpr bool operator!=(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) noexcept {
return !(lhs == rhs); return !(lhs == rhs);
} }
template<typename Container> template<typename Type, typename Other>
[[nodiscard]] constexpr bool operator<(const sparse_set_iterator<Container> &lhs, const sparse_set_iterator<Container> &rhs) noexcept { [[nodiscard]] constexpr bool operator<(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) noexcept {
return lhs.index() > rhs.index(); return lhs.index() > rhs.index();
} }
template<typename Container> template<typename Type, typename Other>
[[nodiscard]] constexpr bool operator>(const sparse_set_iterator<Container> &lhs, const sparse_set_iterator<Container> &rhs) noexcept { [[nodiscard]] constexpr bool operator>(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) noexcept {
return rhs < lhs; return lhs.index() < rhs.index();
} }
template<typename Container> template<typename Type, typename Other>
[[nodiscard]] constexpr bool operator<=(const sparse_set_iterator<Container> &lhs, const sparse_set_iterator<Container> &rhs) noexcept { [[nodiscard]] constexpr bool operator<=(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) noexcept {
return !(lhs > rhs); return !(lhs > rhs);
} }
template<typename Container> template<typename Type, typename Other>
[[nodiscard]] constexpr bool operator>=(const sparse_set_iterator<Container> &lhs, const sparse_set_iterator<Container> &rhs) noexcept { [[nodiscard]] constexpr bool operator>=(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) noexcept {
return !(lhs < rhs); return !(lhs < rhs);
} }
@ -143,6 +139,14 @@ template<typename Container>
* @endcond * @endcond
*/ */
/*! @brief Sparse set deletion policy. */
enum class deletion_policy : std::uint8_t {
/*! @brief Swap-and-pop deletion policy. */
swap_and_pop = 0u,
/*! @brief In-place deletion policy. */
in_place = 1u
};
/** /**
* @brief Basic sparse set implementation. * @brief Basic sparse set implementation.
* *
@ -150,6 +154,10 @@ template<typename Container>
* Two arrays: an _external_ one and an _internal_ one; a _sparse_ one and a * Two arrays: an _external_ one and an _internal_ one; a _sparse_ one and a
* _packed_ one; one used for direct access through contiguous memory, the other * _packed_ one; one used for direct access through contiguous memory, the other
* one used to get the data through an extra level of indirection.<br/> * one used to get the data through an extra level of indirection.<br/>
* This is largely used by the registry to offer users the fastest access ever
* to the components. Views and groups in general are almost entirely designed
* around sparse sets.
*
* This type of data structure is widely documented in the literature and on the * This type of data structure is widely documented in the literature and on the
* web. This is nothing more than a customized implementation suitable for the * web. This is nothing more than a customized implementation suitable for the
* purpose of the framework. * purpose of the framework.
@ -159,7 +167,7 @@ template<typename Container>
* no guarantees that entities are returned in the insertion order when iterate * no guarantees that entities are returned in the insertion order when iterate
* a sparse set. Do not make assumption on the order in any case. * a sparse set. Do not make assumption on the order in any case.
* *
* @tparam Entity A valid entity type. * @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Allocator Type of allocator used to manage memory and elements. * @tparam Allocator Type of allocator used to manage memory and elements.
*/ */
template<typename Entity, typename Allocator> template<typename Entity, typename Allocator>
@ -168,26 +176,23 @@ class basic_sparse_set {
static_assert(std::is_same_v<typename alloc_traits::value_type, Entity>, "Invalid value type"); static_assert(std::is_same_v<typename alloc_traits::value_type, Entity>, "Invalid value type");
using sparse_container_type = std::vector<typename alloc_traits::pointer, typename alloc_traits::template rebind_alloc<typename alloc_traits::pointer>>; using sparse_container_type = std::vector<typename alloc_traits::pointer, typename alloc_traits::template rebind_alloc<typename alloc_traits::pointer>>;
using packed_container_type = std::vector<Entity, Allocator>; using packed_container_type = std::vector<Entity, Allocator>;
using entity_traits = entt_traits<Entity>;
[[nodiscard]] auto sparse_ptr(const Entity entt) const { [[nodiscard]] auto sparse_ptr(const Entity entt) const {
const auto pos = static_cast<size_type>(traits_type::to_entity(entt)); const auto pos = static_cast<size_type>(entity_traits::to_entity(entt));
const auto page = pos / traits_type::page_size; const auto page = pos / entity_traits::page_size;
return (page < sparse.size() && sparse[page]) ? (sparse[page] + fast_mod(pos, traits_type::page_size)) : nullptr; return (page < sparse.size() && sparse[page]) ? (sparse[page] + fast_mod(pos, entity_traits::page_size)) : nullptr;
} }
[[nodiscard]] auto &sparse_ref(const Entity entt) const { [[nodiscard]] auto &sparse_ref(const Entity entt) const {
ENTT_ASSERT(sparse_ptr(entt), "Invalid element"); ENTT_ASSERT(sparse_ptr(entt), "Invalid element");
const auto pos = static_cast<size_type>(traits_type::to_entity(entt)); const auto pos = static_cast<size_type>(entity_traits::to_entity(entt));
return sparse[pos / traits_type::page_size][fast_mod(pos, traits_type::page_size)]; return sparse[pos / entity_traits::page_size][fast_mod(pos, entity_traits::page_size)];
}
[[nodiscard]] auto to_iterator(const Entity entt) const {
return --(end() - index(entt));
} }
[[nodiscard]] auto &assure_at_least(const Entity entt) { [[nodiscard]] auto &assure_at_least(const Entity entt) {
const auto pos = static_cast<size_type>(traits_type::to_entity(entt)); const auto pos = static_cast<size_type>(entity_traits::to_entity(entt));
const auto page = pos / traits_type::page_size; const auto page = pos / entity_traits::page_size;
if(!(page < sparse.size())) { if(!(page < sparse.size())) {
sparse.resize(page + 1u, nullptr); sparse.resize(page + 1u, nullptr);
@ -195,11 +200,11 @@ class basic_sparse_set {
if(!sparse[page]) { if(!sparse[page]) {
auto page_allocator{packed.get_allocator()}; auto page_allocator{packed.get_allocator()};
sparse[page] = alloc_traits::allocate(page_allocator, traits_type::page_size); sparse[page] = alloc_traits::allocate(page_allocator, entity_traits::page_size);
std::uninitialized_fill(sparse[page], sparse[page] + traits_type::page_size, null); std::uninitialized_fill(sparse[page], sparse[page] + entity_traits::page_size, null);
} }
auto &elem = sparse[page][fast_mod(pos, traits_type::page_size)]; auto &elem = sparse[page][fast_mod(pos, entity_traits::page_size)];
ENTT_ASSERT(elem == null, "Slot not available"); ENTT_ASSERT(elem == null, "Slot not available");
return elem; return elem;
} }
@ -209,8 +214,8 @@ class basic_sparse_set {
for(auto &&page: sparse) { for(auto &&page: sparse) {
if(page != nullptr) { if(page != nullptr) {
std::destroy(page, page + traits_type::page_size); std::destroy(page, page + entity_traits::page_size);
alloc_traits::deallocate(page_allocator, page, traits_type::page_size); alloc_traits::deallocate(page_allocator, page, entity_traits::page_size);
page = nullptr; page = nullptr;
} }
} }
@ -221,28 +226,13 @@ private:
return nullptr; return nullptr;
} }
virtual void swap_or_move(const std::size_t, const std::size_t) {} virtual void swap_at(const std::size_t, const std::size_t) {}
virtual void move_element(const std::size_t, const std::size_t) {}
protected: protected:
/*! @brief Random access iterator type. */ /*! @brief Random access iterator type. */
using basic_iterator = internal::sparse_set_iterator<packed_container_type>; using basic_iterator = internal::sparse_set_iterator<packed_container_type>;
/**
* @brief Swaps two items at specific locations.
* @param lhs A position to move from.
* @param rhs The other position to move from.
*/
void swap_at(const std::size_t lhs, const std::size_t rhs) {
const auto entity = static_cast<typename traits_type::entity_type>(lhs);
const auto other = static_cast<typename traits_type::entity_type>(rhs);
sparse_ref(packed[lhs]) = traits_type::combine(other, traits_type::to_integral(packed[lhs]));
sparse_ref(packed[rhs]) = traits_type::combine(entity, traits_type::to_integral(packed[rhs]));
using std::swap;
swap(packed[lhs], packed[rhs]);
}
/** /**
* @brief Erases an entity from a sparse set. * @brief Erases an entity from a sparse set.
* @param it An iterator to the element to pop. * @param it An iterator to the element to pop.
@ -250,8 +240,8 @@ protected:
void swap_and_pop(const basic_iterator it) { void swap_and_pop(const basic_iterator it) {
ENTT_ASSERT(mode == deletion_policy::swap_and_pop, "Deletion policy mismatched"); ENTT_ASSERT(mode == deletion_policy::swap_and_pop, "Deletion policy mismatched");
auto &self = sparse_ref(*it); auto &self = sparse_ref(*it);
const auto entt = traits_type::to_entity(self); const auto entt = entity_traits::to_entity(self);
sparse_ref(packed.back()) = traits_type::combine(entt, traits_type::to_integral(packed.back())); sparse_ref(packed.back()) = entity_traits::combine(entt, entity_traits::to_integral(packed.back()));
packed[static_cast<size_type>(entt)] = packed.back(); packed[static_cast<size_type>(entt)] = packed.back();
// unnecessary but it helps to detect nasty bugs // unnecessary but it helps to detect nasty bugs
ENTT_ASSERT((packed.back() = null, true), ""); ENTT_ASSERT((packed.back() = null, true), "");
@ -266,8 +256,8 @@ protected:
*/ */
void in_place_pop(const basic_iterator it) { void in_place_pop(const basic_iterator it) {
ENTT_ASSERT(mode == deletion_policy::in_place, "Deletion policy mismatched"); ENTT_ASSERT(mode == deletion_policy::in_place, "Deletion policy mismatched");
const auto entt = traits_type::to_entity(std::exchange(sparse_ref(*it), null)); const auto entt = entity_traits::to_entity(std::exchange(sparse_ref(*it), null));
packed[static_cast<size_type>(entt)] = std::exchange(free_list, traits_type::combine(entt, tombstone)); packed[static_cast<size_type>(entt)] = std::exchange(free_list, entity_traits::combine(entt, entity_traits::reserved));
} }
protected: protected:
@ -288,23 +278,6 @@ protected:
} }
} }
/*! @brief Erases all entities of a sparse set. */
virtual void pop_all() {
if(const auto prev = std::exchange(free_list, tombstone); prev == null) {
for(auto first = begin(); !(first.index() < 0); ++first) {
sparse_ref(*first) = null;
}
} else {
for(auto first = begin(); !(first.index() < 0); ++first) {
if(*first != tombstone) {
sparse_ref(*first) = null;
}
}
}
packed.clear();
}
/** /**
* @brief Assigns an entity to a sparse set. * @brief Assigns an entity to a sparse set.
* @param entt A valid identifier. * @param entt A valid identifier.
@ -316,27 +289,25 @@ protected:
if(auto &elem = assure_at_least(entt); free_list == null || force_back) { if(auto &elem = assure_at_least(entt); free_list == null || force_back) {
packed.push_back(entt); packed.push_back(entt);
elem = traits_type::combine(static_cast<typename traits_type::entity_type>(packed.size() - 1u), traits_type::to_integral(entt)); elem = entity_traits::combine(static_cast<typename entity_traits::entity_type>(packed.size() - 1u), entity_traits::to_integral(entt));
return begin(); return begin();
} else { } else {
const auto pos = static_cast<size_type>(traits_type::to_entity(free_list)); const auto pos = static_cast<size_type>(entity_traits::to_entity(free_list));
elem = traits_type::combine(traits_type::to_integral(free_list), traits_type::to_integral(entt)); elem = entity_traits::combine(entity_traits::to_integral(free_list), entity_traits::to_integral(entt));
free_list = std::exchange(packed[pos], entt); free_list = std::exchange(packed[pos], entt);
return --(end() - pos); return --(end() - pos);
} }
} }
public: public:
/*! @brief Entity traits. */
using traits_type = entt_traits<Entity>;
/*! @brief Underlying entity identifier. */
using entity_type = typename traits_type::value_type;
/*! @brief Underlying version type. */
using version_type = typename traits_type::version_type;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Allocator type. */ /*! @brief Allocator type. */
using allocator_type = Allocator; using allocator_type = Allocator;
/*! @brief Underlying entity identifier. */
using entity_type = typename entity_traits::value_type;
/*! @brief Underlying version type. */
using version_type = typename entity_traits::version_type;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Pointer type to contained entities. */ /*! @brief Pointer type to contained entities. */
using pointer = typename packed_container_type::const_pointer; using pointer = typename packed_container_type::const_pointer;
/*! @brief Random access iterator type. */ /*! @brief Random access iterator type. */
@ -346,7 +317,7 @@ public:
/*! @brief Reverse iterator type. */ /*! @brief Reverse iterator type. */
using reverse_iterator = std::reverse_iterator<iterator>; using reverse_iterator = std::reverse_iterator<iterator>;
/*! @brief Constant reverse iterator type. */ /*! @brief Constant reverse iterator type. */
using const_reverse_iterator = std::reverse_iterator<const_iterator>; using const_reverse_iterator = reverse_iterator;
/*! @brief Default constructor. */ /*! @brief Default constructor. */
basic_sparse_set() basic_sparse_set()
@ -370,14 +341,14 @@ public:
/** /**
* @brief Constructs an empty container with the given value type, policy * @brief Constructs an empty container with the given value type, policy
* and allocator. * and allocator.
* @param elem Returned value type, if any. * @param value Returned value type, if any.
* @param pol Type of deletion policy. * @param pol Type of deletion policy.
* @param allocator The allocator to use (possibly default-constructed). * @param allocator The allocator to use (possibly default-constructed).
*/ */
explicit basic_sparse_set(const type_info &elem, deletion_policy pol = deletion_policy::swap_and_pop, const allocator_type &allocator = {}) explicit basic_sparse_set(const type_info &value, deletion_policy pol = deletion_policy::swap_and_pop, const allocator_type &allocator = {})
: sparse{allocator}, : sparse{allocator},
packed{allocator}, packed{allocator},
info{&elem}, info{&value},
free_list{tombstone}, free_list{tombstone},
mode{pol} {} mode{pol} {}
@ -494,7 +465,7 @@ public:
* @return Extent of the sparse set. * @return Extent of the sparse set.
*/ */
[[nodiscard]] size_type extent() const noexcept { [[nodiscard]] size_type extent() const noexcept {
return sparse.size() * traits_type::page_size; return sparse.size() * entity_traits::page_size;
} }
/** /**
@ -519,14 +490,6 @@ public:
return packed.empty(); return packed.empty();
} }
/**
* @brief Checks whether a sparse set is fully packed.
* @return True if the sparse set is fully packed, false otherwise.
*/
[[nodiscard]] bool contiguous() const noexcept {
return (free_list == null);
}
/** /**
* @brief Direct access to the internal packed array. * @brief Direct access to the internal packed array.
* @return A pointer to the internal packed array. * @return A pointer to the internal packed array.
@ -538,7 +501,8 @@ public:
/** /**
* @brief Returns an iterator to the beginning. * @brief Returns an iterator to the beginning.
* *
* If the sparse set is empty, the returned iterator will be equal to * The returned iterator points to the first entity of the internal packed
* array. If the sparse set is empty, the returned iterator will be equal to
* `end()`. * `end()`.
* *
* @return An iterator to the first entity of the sparse set. * @return An iterator to the first entity of the sparse set.
@ -555,6 +519,11 @@ public:
/** /**
* @brief Returns an iterator to the end. * @brief Returns an iterator to the end.
*
* The returned iterator points to the element following the last entity in
* a sparse set. Attempting to dereference the returned iterator results in
* undefined behavior.
*
* @return An iterator to the element following the last entity of a sparse * @return An iterator to the element following the last entity of a sparse
* set. * set.
*/ */
@ -570,8 +539,9 @@ public:
/** /**
* @brief Returns a reverse iterator to the beginning. * @brief Returns a reverse iterator to the beginning.
* *
* If the sparse set is empty, the returned iterator will be equal to * The returned iterator points to the first entity of the reversed internal
* `rend()`. * packed array. If the sparse set is empty, the returned iterator will be
* equal to `rend()`.
* *
* @return An iterator to the first entity of the reversed internal packed * @return An iterator to the first entity of the reversed internal packed
* array. * array.
@ -587,6 +557,11 @@ public:
/** /**
* @brief Returns a reverse iterator to the end. * @brief Returns a reverse iterator to the end.
*
* The returned iterator points to the element following the last entity in
* the reversed sparse set. Attempting to dereference the returned iterator
* results in undefined behavior.
*
* @return An iterator to the element following the last entity of the * @return An iterator to the element following the last entity of the
* reversed sparse set. * reversed sparse set.
*/ */
@ -606,7 +581,7 @@ public:
* iterator otherwise. * iterator otherwise.
*/ */
[[nodiscard]] iterator find(const entity_type entt) const noexcept { [[nodiscard]] iterator find(const entity_type entt) const noexcept {
return contains(entt) ? to_iterator(entt) : end(); return contains(entt) ? --(end() - index(entt)) : end();
} }
/** /**
@ -616,9 +591,9 @@ public:
*/ */
[[nodiscard]] bool contains(const entity_type entt) const noexcept { [[nodiscard]] bool contains(const entity_type entt) const noexcept {
const auto elem = sparse_ptr(entt); const auto elem = sparse_ptr(entt);
constexpr auto cap = traits_type::to_entity(null); constexpr auto cap = entity_traits::to_entity(null);
// testing versions permits to avoid accessing the packed array // testing versions permits to avoid accessing the packed array
return elem && (((~cap & traits_type::to_integral(entt)) ^ traits_type::to_integral(*elem)) < cap); return elem && (((~cap & entity_traits::to_integral(entt)) ^ entity_traits::to_integral(*elem)) < cap);
} }
/** /**
@ -629,8 +604,8 @@ public:
*/ */
[[nodiscard]] version_type current(const entity_type entt) const noexcept { [[nodiscard]] version_type current(const entity_type entt) const noexcept {
const auto elem = sparse_ptr(entt); const auto elem = sparse_ptr(entt);
constexpr auto fallback = traits_type::to_version(tombstone); constexpr auto fallback = entity_traits::to_version(tombstone);
return elem ? traits_type::to_version(*elem) : fallback; return elem ? entity_traits::to_version(*elem) : fallback;
} }
/** /**
@ -645,7 +620,7 @@ public:
*/ */
[[nodiscard]] size_type index(const entity_type entt) const noexcept { [[nodiscard]] size_type index(const entity_type entt) const noexcept {
ENTT_ASSERT(contains(entt), "Set does not contain entity"); ENTT_ASSERT(contains(entt), "Set does not contain entity");
return static_cast<size_type>(traits_type::to_entity(sparse_ref(entt))); return static_cast<size_type>(entity_traits::to_entity(sparse_ref(entt)));
} }
/** /**
@ -677,13 +652,13 @@ public:
* @param entt A valid identifier. * @param entt A valid identifier.
* @return An opaque pointer to the element assigned to the entity, if any. * @return An opaque pointer to the element assigned to the entity, if any.
*/ */
[[nodiscard]] const void *value(const entity_type entt) const noexcept { [[nodiscard]] const void *get(const entity_type entt) const noexcept {
return get_at(index(entt)); return get_at(index(entt));
} }
/*! @copydoc value */ /*! @copydoc get */
[[nodiscard]] void *value(const entity_type entt) noexcept { [[nodiscard]] void *get(const entity_type entt) noexcept {
return const_cast<void *>(std::as_const(*this).value(entt)); return const_cast<void *>(std::as_const(*this).get(entt));
} }
/** /**
@ -694,12 +669,28 @@ public:
* results in undefined behavior. * results in undefined behavior.
* *
* @param entt A valid identifier. * @param entt A valid identifier.
* @param elem Optional opaque element to forward to mixins, if any. * @param value Optional opaque value to forward to mixins, if any.
* @return Iterator pointing to the emplaced element in case of success, the * @return Iterator pointing to the emplaced element in case of success, the
* `end()` iterator otherwise. * `end()` iterator otherwise.
*/ */
iterator push(const entity_type entt, const void *elem = nullptr) { iterator emplace(const entity_type entt, const void *value = nullptr) {
return try_emplace(entt, false, elem); return try_emplace(entt, false, value);
}
/**
* @brief Bump the version number of an entity.
*
* @warning
* Attempting to bump the version of an entity that doesn't belong to the
* sparse set results in undefined behavior.
*
* @param entt A valid identifier.
*/
void bump(const entity_type entt) {
auto &entity = sparse_ref(entt);
ENTT_ASSERT(entt != tombstone && entity != null, "Cannot set the required version");
entity = entity_traits::combine(entity_traits::to_integral(entity), entity_traits::to_integral(entt));
packed[static_cast<size_type>(entity_traits::to_entity(entity))] = entt;
} }
/** /**
@ -716,7 +707,7 @@ public:
* success, the `end()` iterator otherwise. * success, the `end()` iterator otherwise.
*/ */
template<typename It> template<typename It>
iterator push(It first, It last) { iterator insert(It first, It last) {
for(auto it = first; it != last; ++it) { for(auto it = first; it != last; ++it) {
try_emplace(*it, true); try_emplace(*it, true);
} }
@ -724,24 +715,6 @@ public:
return first == last ? end() : find(*first); return first == last ? end() : find(*first);
} }
/**
* @brief Bump the version number of an entity.
*
* @warning
* Attempting to bump the version of an entity that doesn't belong to the
* sparse set results in undefined behavior.
*
* @param entt A valid identifier.
* @return The version of the given identifier.
*/
version_type bump(const entity_type entt) {
auto &entity = sparse_ref(entt);
ENTT_ASSERT(entt != tombstone && entity != null, "Cannot set the required version");
entity = traits_type::combine(traits_type::to_integral(entity), traits_type::to_integral(entt));
packed[static_cast<size_type>(traits_type::to_entity(entity))] = entt;
return traits_type::to_version(entt);
}
/** /**
* @brief Erases an entity from a sparse set. * @brief Erases an entity from a sparse set.
* *
@ -752,7 +725,7 @@ public:
* @param entt A valid identifier. * @param entt A valid identifier.
*/ */
void erase(const entity_type entt) { void erase(const entity_type entt) {
const auto it = to_iterator(entt); const auto it = --(end() - index(entt));
pop(it, it + 1u); pop(it, it + 1u);
} }
@ -796,45 +769,29 @@ public:
size_type remove(It first, It last) { size_type remove(It first, It last) {
size_type count{}; size_type count{};
if constexpr(std::is_same_v<It, basic_iterator>) {
while(first != last) {
while(first != last && !contains(*first)) {
++first;
}
const auto it = first;
while(first != last && contains(*first)) {
++first;
}
count += std::distance(it, first);
erase(it, first);
}
} else {
for(; first != last; ++first) { for(; first != last; ++first) {
count += remove(*first); count += remove(*first);
} }
}
return count; return count;
} }
/*! @brief Removes all tombstones from a sparse set. */ /*! @brief Removes all tombstones from the packed array of a sparse set. */
void compact() { void compact() {
size_type from = packed.size(); size_type from = packed.size();
for(; from && packed[from - 1u] == tombstone; --from) {} for(; from && packed[from - 1u] == tombstone; --from) {}
for(auto *it = &free_list; *it != null && from; it = std::addressof(packed[traits_type::to_entity(*it)])) { for(auto *it = &free_list; *it != null && from; it = std::addressof(packed[entity_traits::to_entity(*it)])) {
if(const size_type to = traits_type::to_entity(*it); to < from) { if(const size_type to = entity_traits::to_entity(*it); to < from) {
--from; --from;
swap_or_move(from, to); move_element(from, to);
packed[to] = std::exchange(packed[from], tombstone); using std::swap;
const auto entity = static_cast<typename traits_type::entity_type>(to); swap(packed[from], packed[to]);
sparse_ref(packed[to]) = traits_type::combine(entity, traits_type::to_integral(packed[to]));
*it = traits_type::combine(static_cast<typename traits_type::entity_type>(from), tombstone); const auto entity = static_cast<typename entity_traits::entity_type>(to);
sparse_ref(packed[to]) = entity_traits::combine(entity, entity_traits::to_integral(packed[to]));
*it = entity_traits::combine(static_cast<typename entity_traits::entity_type>(from), entity_traits::reserved);
for(; from && packed[from - 1u] == tombstone; --from) {} for(; from && packed[from - 1u] == tombstone; --from) {}
} }
} }
@ -857,12 +814,21 @@ public:
* @param rhs A valid identifier. * @param rhs A valid identifier.
*/ */
void swap_elements(const entity_type lhs, const entity_type rhs) { void swap_elements(const entity_type lhs, const entity_type rhs) {
const auto from = index(lhs); ENTT_ASSERT(contains(lhs) && contains(rhs), "Set does not contain entities");
const auto to = index(rhs);
// basic no-leak guarantee if swapping throws auto &entt = sparse_ref(lhs);
swap_or_move(from, to); auto &other = sparse_ref(rhs);
swap_at(from, to);
const auto from = entity_traits::to_entity(entt);
const auto to = entity_traits::to_entity(other);
// basic no-leak guarantee (with invalid state) if swapping throws
swap_at(static_cast<size_type>(from), static_cast<size_type>(to));
entt = entity_traits::combine(to, entity_traits::to_integral(packed[from]));
other = entity_traits::combine(from, entity_traits::to_integral(packed[to]));
using std::swap;
swap(packed[from], packed[to]);
} }
/** /**
@ -910,9 +876,9 @@ public:
const auto idx = index(packed[next]); const auto idx = index(packed[next]);
const auto entt = packed[curr]; const auto entt = packed[curr];
swap_or_move(next, idx); swap_at(next, idx);
const auto entity = static_cast<typename traits_type::entity_type>(curr); const auto entity = static_cast<typename entity_traits::entity_type>(curr);
sparse_ref(entt) = traits_type::combine(entity, traits_type::to_integral(packed[curr])); sparse_ref(entt) = entity_traits::combine(entity, entity_traits::to_integral(packed[curr]));
curr = std::exchange(next, idx); curr = std::exchange(next, idx);
} }
} }
@ -940,35 +906,48 @@ public:
* @brief Sort entities according to their order in another sparse set. * @brief Sort entities according to their order in another sparse set.
* *
* Entities that are part of both the sparse sets are ordered internally * Entities that are part of both the sparse sets are ordered internally
* according to the order they have in `other`.<br/> * according to the order they have in `other`. All the other entities goes
* All the other entities goes to the end of the list and there are no * to the end of the list and there are no guarantees on their order.<br/>
* guarantees on their order. * In other terms, this function can be used to impose the same order on two
* sets by using one of them as a master and the other one as a slave.
*
* Iterating the sparse set with a couple of iterators returns elements in
* the expected order after a call to `respect`. See `begin` and `end` for
* more details.
* *
* @param other The sparse sets that imposes the order of the entities. * @param other The sparse sets that imposes the order of the entities.
*/ */
void sort_as(const basic_sparse_set &other) { void respect(const basic_sparse_set &other) {
compact(); compact();
const auto to = other.end(); const auto to = other.end();
auto from = other.begin(); auto from = other.begin();
for(auto it = begin(); it.index() && from != to; ++from) { for(size_type pos = packed.size() - 1; pos && from != to; ++from) {
if(const auto curr = *from; contains(curr)) { if(contains(*from)) {
if(const auto entt = *it; entt != curr) { if(*from != packed[pos]) {
// basic no-leak guarantee (with invalid state) if swapping throws // basic no-leak guarantee (with invalid state) if swapping throws
swap_elements(entt, curr); swap_elements(packed[pos], *from);
} }
++it; --pos;
} }
} }
} }
/*! @brief Clears a sparse set. */ /*! @brief Clears a sparse set. */
void clear() { void clear() {
pop_all(); if(const auto last = end(); free_list == null) {
// sanity check to avoid subtle issues due to storage classes pop(begin(), last);
ENTT_ASSERT((compact(), size()) == 0u, "Non-empty set"); } else {
for(auto &&entity: *this) {
// tombstone filter on itself
if(const auto it = find(entity); it != last) {
pop(it, it + 1u);
}
}
}
free_list = tombstone; free_list = tombstone;
packed.clear(); packed.clear();
} }

View File

@ -9,6 +9,7 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "../config/config.h" #include "../config/config.h"
#include "../core/compressed_pair.hpp"
#include "../core/iterator.hpp" #include "../core/iterator.hpp"
#include "../core/memory.hpp" #include "../core/memory.hpp"
#include "../core/type_info.hpp" #include "../core/type_info.hpp"
@ -16,6 +17,7 @@
#include "entity.hpp" #include "entity.hpp"
#include "fwd.hpp" #include "fwd.hpp"
#include "sparse_set.hpp" #include "sparse_set.hpp"
#include "storage_mixin.hpp"
namespace entt { namespace entt {
@ -26,12 +28,13 @@ namespace entt {
namespace internal { namespace internal {
template<typename Container, std::size_t Size> template<typename Container>
class storage_iterator final { class storage_iterator final {
friend storage_iterator<const Container, Size>; friend storage_iterator<const Container>;
using container_type = std::remove_const_t<Container>; using container_type = std::remove_const_t<Container>;
using alloc_traits = std::allocator_traits<typename container_type::allocator_type>; using alloc_traits = std::allocator_traits<typename container_type::allocator_type>;
using comp_traits = component_traits<std::remove_pointer_t<typename container_type::value_type>>;
using iterator_traits = std::iterator_traits<std::conditional_t< using iterator_traits = std::iterator_traits<std::conditional_t<
std::is_const_v<Container>, std::is_const_v<Container>,
@ -48,12 +51,12 @@ public:
constexpr storage_iterator() noexcept = default; constexpr storage_iterator() noexcept = default;
constexpr storage_iterator(Container *ref, const difference_type idx) noexcept constexpr storage_iterator(Container *ref, const difference_type idx) noexcept
: payload{ref}, : packed{ref},
offset{idx} {} offset{idx} {}
template<bool Const = std::is_const_v<Container>, typename = std::enable_if_t<Const>> template<bool Const = std::is_const_v<Container>, typename = std::enable_if_t<Const>>
constexpr storage_iterator(const storage_iterator<std::remove_const_t<Container>, Size> &other) noexcept constexpr storage_iterator(const storage_iterator<std::remove_const_t<Container>> &other) noexcept
: storage_iterator{other.payload, other.offset} {} : storage_iterator{other.packed, other.offset} {}
constexpr storage_iterator &operator++() noexcept { constexpr storage_iterator &operator++() noexcept {
return --offset, *this; return --offset, *this;
@ -93,12 +96,12 @@ public:
[[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept { [[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept {
const auto pos = index() - value; const auto pos = index() - value;
return (*payload)[pos / Size][fast_mod(pos, Size)]; return (*packed)[pos / comp_traits::page_size][fast_mod(pos, comp_traits::page_size)];
} }
[[nodiscard]] constexpr pointer operator->() const noexcept { [[nodiscard]] constexpr pointer operator->() const noexcept {
const auto pos = index(); const auto pos = index();
return (*payload)[pos / Size] + fast_mod(pos, Size); return (*packed)[pos / comp_traits::page_size] + fast_mod(pos, comp_traits::page_size);
} }
[[nodiscard]] constexpr reference operator*() const noexcept { [[nodiscard]] constexpr reference operator*() const noexcept {
@ -110,42 +113,42 @@ public:
} }
private: private:
Container *payload; Container *packed;
difference_type offset; difference_type offset;
}; };
template<typename Lhs, typename Rhs, std::size_t Size> template<typename CLhs, typename CRhs>
[[nodiscard]] constexpr std::ptrdiff_t operator-(const storage_iterator<Lhs, Size> &lhs, const storage_iterator<Rhs, Size> &rhs) noexcept { [[nodiscard]] constexpr std::ptrdiff_t operator-(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) noexcept {
return rhs.index() - lhs.index(); return rhs.index() - lhs.index();
} }
template<typename Lhs, typename Rhs, std::size_t Size> template<typename CLhs, typename CRhs>
[[nodiscard]] constexpr bool operator==(const storage_iterator<Lhs, Size> &lhs, const storage_iterator<Rhs, Size> &rhs) noexcept { [[nodiscard]] constexpr bool operator==(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) noexcept {
return lhs.index() == rhs.index(); return lhs.index() == rhs.index();
} }
template<typename Lhs, typename Rhs, std::size_t Size> template<typename CLhs, typename CRhs>
[[nodiscard]] constexpr bool operator!=(const storage_iterator<Lhs, Size> &lhs, const storage_iterator<Rhs, Size> &rhs) noexcept { [[nodiscard]] constexpr bool operator!=(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) noexcept {
return !(lhs == rhs); return !(lhs == rhs);
} }
template<typename Lhs, typename Rhs, std::size_t Size> template<typename CLhs, typename CRhs>
[[nodiscard]] constexpr bool operator<(const storage_iterator<Lhs, Size> &lhs, const storage_iterator<Rhs, Size> &rhs) noexcept { [[nodiscard]] constexpr bool operator<(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) noexcept {
return lhs.index() > rhs.index(); return lhs.index() > rhs.index();
} }
template<typename Lhs, typename Rhs, std::size_t Size> template<typename CLhs, typename CRhs>
[[nodiscard]] constexpr bool operator>(const storage_iterator<Lhs, Size> &lhs, const storage_iterator<Rhs, Size> &rhs) noexcept { [[nodiscard]] constexpr bool operator>(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) noexcept {
return rhs < lhs; return lhs.index() < rhs.index();
} }
template<typename Lhs, typename Rhs, std::size_t Size> template<typename CLhs, typename CRhs>
[[nodiscard]] constexpr bool operator<=(const storage_iterator<Lhs, Size> &lhs, const storage_iterator<Rhs, Size> &rhs) noexcept { [[nodiscard]] constexpr bool operator<=(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) noexcept {
return !(lhs > rhs); return !(lhs > rhs);
} }
template<typename Lhs, typename Rhs, std::size_t Size> template<typename CLhs, typename CRhs>
[[nodiscard]] constexpr bool operator>=(const storage_iterator<Lhs, Size> &lhs, const storage_iterator<Rhs, Size> &rhs) noexcept { [[nodiscard]] constexpr bool operator>=(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) noexcept {
return !(lhs < rhs); return !(lhs < rhs);
} }
@ -155,11 +158,10 @@ class extended_storage_iterator final {
friend class extended_storage_iterator; friend class extended_storage_iterator;
public: public:
using iterator_type = It;
using difference_type = std::ptrdiff_t;
using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval<It>()), std::forward_as_tuple(*std::declval<Other>()...))); using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval<It>()), std::forward_as_tuple(*std::declval<Other>()...)));
using pointer = input_iterator_pointer<value_type>; using pointer = input_iterator_pointer<value_type>;
using reference = value_type; using reference = value_type;
using difference_type = std::ptrdiff_t;
using iterator_category = std::input_iterator_tag; using iterator_category = std::input_iterator_tag;
constexpr extended_storage_iterator() constexpr extended_storage_iterator()
@ -189,24 +191,20 @@ public:
return {*std::get<It>(it), *std::get<Other>(it)...}; return {*std::get<It>(it), *std::get<Other>(it)...};
} }
[[nodiscard]] constexpr iterator_type base() const noexcept { template<typename... CLhs, typename... CRhs>
return std::get<It>(it); friend constexpr bool operator==(const extended_storage_iterator<CLhs...> &, const extended_storage_iterator<CRhs...> &) noexcept;
}
template<typename... Lhs, typename... Rhs>
friend constexpr bool operator==(const extended_storage_iterator<Lhs...> &, const extended_storage_iterator<Rhs...> &) noexcept;
private: private:
std::tuple<It, Other...> it; std::tuple<It, Other...> it;
}; };
template<typename... Lhs, typename... Rhs> template<typename... CLhs, typename... CRhs>
[[nodiscard]] constexpr bool operator==(const extended_storage_iterator<Lhs...> &lhs, const extended_storage_iterator<Rhs...> &rhs) noexcept { [[nodiscard]] constexpr bool operator==(const extended_storage_iterator<CLhs...> &lhs, const extended_storage_iterator<CRhs...> &rhs) noexcept {
return std::get<0>(lhs.it) == std::get<0>(rhs.it); return std::get<0>(lhs.it) == std::get<0>(rhs.it);
} }
template<typename... Lhs, typename... Rhs> template<typename... CLhs, typename... CRhs>
[[nodiscard]] constexpr bool operator!=(const extended_storage_iterator<Lhs...> &lhs, const extended_storage_iterator<Rhs...> &rhs) noexcept { [[nodiscard]] constexpr bool operator!=(const extended_storage_iterator<CLhs...> &lhs, const extended_storage_iterator<CRhs...> &rhs) noexcept {
return !(lhs == rhs); return !(lhs == rhs);
} }
@ -229,43 +227,43 @@ template<typename... Lhs, typename... Rhs>
* normally available for non-empty types will not be available for empty ones. * normally available for non-empty types will not be available for empty ones.
* *
* @tparam Type Type of objects assigned to the entities. * @tparam Type Type of objects assigned to the entities.
* @tparam Entity A valid entity type. * @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Allocator Type of allocator used to manage memory and elements. * @tparam Allocator Type of allocator used to manage memory and elements.
*/ */
template<typename Type, typename Entity, typename Allocator, typename> template<typename Type, typename Entity, typename Allocator, typename>
class basic_storage: public basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>> { class basic_storage: public basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>> {
using alloc_traits = std::allocator_traits<Allocator>; using alloc_traits = std::allocator_traits<Allocator>;
static_assert(std::is_same_v<typename alloc_traits::value_type, Type>, "Invalid value type"); static_assert(std::is_same_v<typename alloc_traits::value_type, Type>, "Invalid value type");
using container_type = std::vector<typename alloc_traits::pointer, typename alloc_traits::template rebind_alloc<typename alloc_traits::pointer>>;
using underlying_type = basic_sparse_set<Entity, typename alloc_traits::template rebind_alloc<Entity>>; using underlying_type = basic_sparse_set<Entity, typename alloc_traits::template rebind_alloc<Entity>>;
using underlying_iterator = typename underlying_type::basic_iterator; using container_type = std::vector<typename alloc_traits::pointer, typename alloc_traits::template rebind_alloc<typename alloc_traits::pointer>>;
using comp_traits = component_traits<Type>;
static constexpr bool is_pinned_type_v = !(std::is_move_constructible_v<Type> && std::is_move_assignable_v<Type>); static constexpr bool is_pinned_type_v = !(std::is_move_constructible_v<Type> && std::is_move_assignable_v<Type>);
[[nodiscard]] auto &element_at(const std::size_t pos) const { [[nodiscard]] auto &element_at(const std::size_t pos) const {
return payload[pos / traits_type::page_size][fast_mod(pos, traits_type::page_size)]; return packed.first()[pos / comp_traits::page_size][fast_mod(pos, comp_traits::page_size)];
} }
auto assure_at_least(const std::size_t pos) { auto assure_at_least(const std::size_t pos) {
const auto idx = pos / traits_type::page_size; auto &&container = packed.first();
const auto idx = pos / comp_traits::page_size;
if(!(idx < payload.size())) { if(!(idx < container.size())) {
auto curr = payload.size(); auto curr = container.size();
allocator_type allocator{get_allocator()}; container.resize(idx + 1u, nullptr);
payload.resize(idx + 1u, nullptr);
ENTT_TRY { ENTT_TRY {
for(const auto last = payload.size(); curr < last; ++curr) { for(const auto last = container.size(); curr < last; ++curr) {
payload[curr] = alloc_traits::allocate(allocator, traits_type::page_size); container[curr] = alloc_traits::allocate(packed.second(), comp_traits::page_size);
} }
} }
ENTT_CATCH { ENTT_CATCH {
payload.resize(curr); container.resize(curr);
ENTT_THROW; ENTT_THROW;
} }
} }
return payload[idx] + fast_mod(pos, traits_type::page_size); return container[idx] + fast_mod(pos, comp_traits::page_size);
} }
template<typename... Args> template<typename... Args>
@ -274,7 +272,7 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
ENTT_TRY { ENTT_TRY {
auto elem = assure_at_least(static_cast<size_type>(it.index())); auto elem = assure_at_least(static_cast<size_type>(it.index()));
entt::uninitialized_construct_using_allocator(to_address(elem), get_allocator(), std::forward<Args>(args)...); entt::uninitialized_construct_using_allocator(to_address(elem), packed.second(), std::forward<Args>(args)...);
} }
ENTT_CATCH { ENTT_CATCH {
base_type::pop(it, it + 1u); base_type::pop(it, it + 1u);
@ -285,24 +283,25 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
} }
void shrink_to_size(const std::size_t sz) { void shrink_to_size(const std::size_t sz) {
const auto from = (sz + traits_type::page_size - 1u) / traits_type::page_size;
allocator_type allocator{get_allocator()};
for(auto pos = sz, length = base_type::size(); pos < length; ++pos) { for(auto pos = sz, length = base_type::size(); pos < length; ++pos) {
if constexpr(traits_type::in_place_delete) { if constexpr(comp_traits::in_place_delete) {
if(base_type::at(pos) != tombstone) { if(base_type::at(pos) != tombstone) {
alloc_traits::destroy(allocator, std::addressof(element_at(pos))); std::destroy_at(std::addressof(element_at(pos)));
} }
} else { } else {
alloc_traits::destroy(allocator, std::addressof(element_at(pos))); std::destroy_at(std::addressof(element_at(pos)));
} }
} }
for(auto pos = from, last = payload.size(); pos < last; ++pos) { auto &&container = packed.first();
alloc_traits::deallocate(allocator, payload[pos], traits_type::page_size); auto page_allocator{packed.second()};
const auto from = (sz + comp_traits::page_size - 1u) / comp_traits::page_size;
for(auto pos = from, last = container.size(); pos < last; ++pos) {
alloc_traits::deallocate(page_allocator, container[pos], comp_traits::page_size);
} }
payload.resize(from); container.resize(from);
} }
private: private:
@ -310,68 +309,54 @@ private:
return std::addressof(element_at(pos)); return std::addressof(element_at(pos));
} }
void swap_or_move([[maybe_unused]] const std::size_t from, [[maybe_unused]] const std::size_t to) override { void swap_at([[maybe_unused]] const std::size_t lhs, [[maybe_unused]] const std::size_t rhs) final {
// use a runtime value to avoid compile-time suppression that drives the code coverage tool crazy
ENTT_ASSERT((lhs + 1u) && !is_pinned_type_v, "Pinned type");
if constexpr(!is_pinned_type_v) {
using std::swap;
swap(element_at(lhs), element_at(rhs));
}
}
void move_element([[maybe_unused]] const std::size_t from, [[maybe_unused]] const std::size_t to) final {
// use a runtime value to avoid compile-time suppression that drives the code coverage tool crazy // use a runtime value to avoid compile-time suppression that drives the code coverage tool crazy
ENTT_ASSERT((from + 1u) && !is_pinned_type_v, "Pinned type"); ENTT_ASSERT((from + 1u) && !is_pinned_type_v, "Pinned type");
if constexpr(!is_pinned_type_v) { if constexpr(!is_pinned_type_v) {
auto &elem = element_at(from); auto &elem = element_at(from);
entt::uninitialized_construct_using_allocator(to_address(assure_at_least(to)), packed.second(), std::move(elem));
if constexpr(traits_type::in_place_delete) { std::destroy_at(std::addressof(elem));
if(base_type::operator[](to) == tombstone) {
allocator_type allocator{get_allocator()};
entt::uninitialized_construct_using_allocator(to_address(assure_at_least(to)), allocator, std::move(elem));
alloc_traits::destroy(allocator, std::addressof(elem));
return;
}
}
using std::swap;
swap(elem, element_at(to));
} }
} }
protected: protected:
/*! @brief Random access iterator type. */
using basic_iterator = typename underlying_type::basic_iterator;
/** /**
* @brief Erases entities from a storage. * @brief Erases entities from a sparse set.
* @param first An iterator to the first element of the range of entities. * @param first An iterator to the first element of the range of entities.
* @param last An iterator past the last element of the range of entities. * @param last An iterator past the last element of the range of entities.
*/ */
void pop(underlying_iterator first, underlying_iterator last) override { void pop(basic_iterator first, basic_iterator last) override {
for(allocator_type allocator{get_allocator()}; first != last; ++first) { for(; first != last; ++first) {
// cannot use first.index() because it would break with cross iterators // cannot use first.index() because it would break with cross iterators
auto &elem = element_at(base_type::index(*first)); auto &elem = element_at(base_type::index(*first));
if constexpr(traits_type::in_place_delete) { if constexpr(comp_traits::in_place_delete) {
base_type::in_place_pop(first); base_type::in_place_pop(first);
alloc_traits::destroy(allocator, std::addressof(elem)); std::destroy_at(std::addressof(elem));
} else { } else {
auto &other = element_at(base_type::size() - 1u); auto &other = element_at(base_type::size() - 1u);
// destroying on exit allows reentrant destructors // destroying on exit allows reentrant destructors
[[maybe_unused]] auto unused = std::exchange(elem, std::move(other)); [[maybe_unused]] auto unused = std::exchange(elem, std::move(other));
alloc_traits::destroy(allocator, std::addressof(other)); std::destroy_at(std::addressof(other));
base_type::swap_and_pop(first); base_type::swap_and_pop(first);
} }
} }
} }
/*! @brief Erases all entities of a storage. */
void pop_all() override {
allocator_type allocator{get_allocator()};
for(auto first = base_type::begin(); !(first.index() < 0); ++first) {
if constexpr(traits_type::in_place_delete) {
if(*first != tombstone) {
base_type::in_place_pop(first);
alloc_traits::destroy(allocator, std::addressof(element_at(static_cast<size_type>(first.index()))));
}
} else {
base_type::swap_and_pop(first);
alloc_traits::destroy(allocator, std::addressof(element_at(static_cast<size_type>(first.index()))));
}
}
}
/** /**
* @brief Assigns an entity to a storage. * @brief Assigns an entity to a storage.
* @param entt A valid identifier. * @param entt A valid identifier.
@ -379,7 +364,7 @@ protected:
* @param force_back Force back insertion. * @param force_back Force back insertion.
* @return Iterator pointing to the emplaced element. * @return Iterator pointing to the emplaced element.
*/ */
underlying_iterator try_emplace([[maybe_unused]] const Entity entt, [[maybe_unused]] const bool force_back, const void *value) override { basic_iterator try_emplace([[maybe_unused]] const Entity entt, [[maybe_unused]] const bool force_back, const void *value) override {
if(value) { if(value) {
if constexpr(std::is_copy_constructible_v<value_type>) { if constexpr(std::is_copy_constructible_v<value_type>) {
return emplace_element(entt, force_back, *static_cast<const value_type *>(value)); return emplace_element(entt, force_back, *static_cast<const value_type *>(value));
@ -398,24 +383,22 @@ protected:
public: public:
/*! @brief Base type. */ /*! @brief Base type. */
using base_type = underlying_type; using base_type = underlying_type;
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Type of the objects assigned to entities. */ /*! @brief Type of the objects assigned to entities. */
using value_type = Type; using value_type = Type;
/*! @brief Component traits. */
using traits_type = component_traits<value_type>;
/*! @brief Underlying entity identifier. */ /*! @brief Underlying entity identifier. */
using entity_type = Entity; using entity_type = Entity;
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
using size_type = std::size_t; using size_type = std::size_t;
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Pointer type to contained elements. */ /*! @brief Pointer type to contained elements. */
using pointer = typename container_type::pointer; using pointer = typename container_type::pointer;
/*! @brief Constant pointer type to contained elements. */ /*! @brief Constant pointer type to contained elements. */
using const_pointer = typename alloc_traits::template rebind_traits<typename alloc_traits::const_pointer>::const_pointer; using const_pointer = typename alloc_traits::template rebind_traits<typename alloc_traits::const_pointer>::const_pointer;
/*! @brief Random access iterator type. */ /*! @brief Random access iterator type. */
using iterator = internal::storage_iterator<container_type, traits_type::page_size>; using iterator = internal::storage_iterator<container_type>;
/*! @brief Constant random access iterator type. */ /*! @brief Constant random access iterator type. */
using const_iterator = internal::storage_iterator<const container_type, traits_type::page_size>; using const_iterator = internal::storage_iterator<const container_type>;
/*! @brief Reverse iterator type. */ /*! @brief Reverse iterator type. */
using reverse_iterator = std::reverse_iterator<iterator>; using reverse_iterator = std::reverse_iterator<iterator>;
/*! @brief Constant reverse iterator type. */ /*! @brief Constant reverse iterator type. */
@ -424,10 +407,6 @@ public:
using iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::iterator, iterator>>; using iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::iterator, iterator>>;
/*! @brief Constant extended iterable storage proxy. */ /*! @brief Constant extended iterable storage proxy. */
using const_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_iterator, const_iterator>>; using const_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_iterator, const_iterator>>;
/*! @brief Extended reverse iterable storage proxy. */
using reverse_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::reverse_iterator, reverse_iterator>>;
/*! @brief Constant extended reverse iterable storage proxy. */
using const_reverse_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_reverse_iterator, const_reverse_iterator>>;
/*! @brief Default constructor. */ /*! @brief Default constructor. */
basic_storage() basic_storage()
@ -438,8 +417,8 @@ public:
* @param allocator The allocator to use. * @param allocator The allocator to use.
*/ */
explicit basic_storage(const allocator_type &allocator) explicit basic_storage(const allocator_type &allocator)
: base_type{type_id<value_type>(), deletion_policy{traits_type::in_place_delete}, allocator}, : base_type{type_id<value_type>(), deletion_policy{comp_traits::in_place_delete}, allocator},
payload{allocator} {} packed{container_type{allocator}, allocator} {}
/** /**
* @brief Move constructor. * @brief Move constructor.
@ -447,7 +426,7 @@ public:
*/ */
basic_storage(basic_storage &&other) noexcept basic_storage(basic_storage &&other) noexcept
: base_type{std::move(other)}, : base_type{std::move(other)},
payload{std::move(other.payload)} {} packed{std::move(other.packed)} {}
/** /**
* @brief Allocator-extended move constructor. * @brief Allocator-extended move constructor.
@ -456,8 +435,8 @@ public:
*/ */
basic_storage(basic_storage &&other, const allocator_type &allocator) noexcept basic_storage(basic_storage &&other, const allocator_type &allocator) noexcept
: base_type{std::move(other), allocator}, : base_type{std::move(other), allocator},
payload{std::move(other.payload), allocator} { packed{container_type{std::move(other.packed.first()), allocator}, allocator} {
ENTT_ASSERT(alloc_traits::is_always_equal::value || payload.get_allocator() == other.payload.get_allocator(), "Copying a storage is not allowed"); ENTT_ASSERT(alloc_traits::is_always_equal::value || packed.second() == other.packed.second(), "Copying a storage is not allowed");
} }
/*! @brief Default destructor. */ /*! @brief Default destructor. */
@ -471,11 +450,12 @@ public:
* @return This storage. * @return This storage.
*/ */
basic_storage &operator=(basic_storage &&other) noexcept { basic_storage &operator=(basic_storage &&other) noexcept {
ENTT_ASSERT(alloc_traits::is_always_equal::value || payload.get_allocator() == other.payload.get_allocator(), "Copying a storage is not allowed"); ENTT_ASSERT(alloc_traits::is_always_equal::value || packed.second() == other.packed.second(), "Copying a storage is not allowed");
shrink_to_size(0u); shrink_to_size(0u);
base_type::operator=(std::move(other)); base_type::operator=(std::move(other));
payload = std::move(other.payload); packed.first() = std::move(other.packed.first());
propagate_on_container_move_assignment(packed.second(), other.packed.second());
return *this; return *this;
} }
@ -485,8 +465,9 @@ public:
*/ */
void swap(basic_storage &other) { void swap(basic_storage &other) {
using std::swap; using std::swap;
base_type::swap(other); underlying_type::swap(other);
swap(payload, other.payload); propagate_on_container_swap(packed.second(), other.packed.second());
swap(packed.first(), other.packed.first());
} }
/** /**
@ -494,7 +475,7 @@ public:
* @return The associated allocator. * @return The associated allocator.
*/ */
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept { [[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
return payload.get_allocator(); return allocator_type{packed.second()};
} }
/** /**
@ -518,7 +499,7 @@ public:
* @return Capacity of the storage. * @return Capacity of the storage.
*/ */
[[nodiscard]] size_type capacity() const noexcept override { [[nodiscard]] size_type capacity() const noexcept override {
return payload.size() * traits_type::page_size; return packed.first().size() * comp_traits::page_size;
} }
/*! @brief Requests the removal of unused capacity. */ /*! @brief Requests the removal of unused capacity. */
@ -532,24 +513,25 @@ public:
* @return A pointer to the array of objects. * @return A pointer to the array of objects.
*/ */
[[nodiscard]] const_pointer raw() const noexcept { [[nodiscard]] const_pointer raw() const noexcept {
return payload.data(); return packed.first().data();
} }
/*! @copydoc raw */ /*! @copydoc raw */
[[nodiscard]] pointer raw() noexcept { [[nodiscard]] pointer raw() noexcept {
return payload.data(); return packed.first().data();
} }
/** /**
* @brief Returns an iterator to the beginning. * @brief Returns an iterator to the beginning.
* *
* The returned iterator points to the first instance of the internal array.
* If the storage is empty, the returned iterator will be equal to `end()`. * If the storage is empty, the returned iterator will be equal to `end()`.
* *
* @return An iterator to the first instance of the internal array. * @return An iterator to the first instance of the internal array.
*/ */
[[nodiscard]] const_iterator cbegin() const noexcept { [[nodiscard]] const_iterator cbegin() const noexcept {
const auto pos = static_cast<typename iterator::difference_type>(base_type::size()); const auto pos = static_cast<typename iterator::difference_type>(base_type::size());
return const_iterator{&payload, pos}; return const_iterator{&packed.first(), pos};
} }
/*! @copydoc cbegin */ /*! @copydoc cbegin */
@ -560,16 +542,21 @@ public:
/*! @copydoc begin */ /*! @copydoc begin */
[[nodiscard]] iterator begin() noexcept { [[nodiscard]] iterator begin() noexcept {
const auto pos = static_cast<typename iterator::difference_type>(base_type::size()); const auto pos = static_cast<typename iterator::difference_type>(base_type::size());
return iterator{&payload, pos}; return iterator{&packed.first(), pos};
} }
/** /**
* @brief Returns an iterator to the end. * @brief Returns an iterator to the end.
*
* The returned iterator points to the element following the last instance
* of the internal array. Attempting to dereference the returned iterator
* results in undefined behavior.
*
* @return An iterator to the element following the last instance of the * @return An iterator to the element following the last instance of the
* internal array. * internal array.
*/ */
[[nodiscard]] const_iterator cend() const noexcept { [[nodiscard]] const_iterator cend() const noexcept {
return const_iterator{&payload, {}}; return const_iterator{&packed.first(), {}};
} }
/*! @copydoc cend */ /*! @copydoc cend */
@ -579,13 +566,15 @@ public:
/*! @copydoc end */ /*! @copydoc end */
[[nodiscard]] iterator end() noexcept { [[nodiscard]] iterator end() noexcept {
return iterator{&payload, {}}; return iterator{&packed.first(), {}};
} }
/** /**
* @brief Returns a reverse iterator to the beginning. * @brief Returns a reverse iterator to the beginning.
* *
* If the storage is empty, the returned iterator will be equal to `rend()`. * The returned iterator points to the first instance of the reversed
* internal array. If the storage is empty, the returned iterator will be
* equal to `rend()`.
* *
* @return An iterator to the first instance of the reversed internal array. * @return An iterator to the first instance of the reversed internal array.
*/ */
@ -605,6 +594,11 @@ public:
/** /**
* @brief Returns a reverse iterator to the end. * @brief Returns a reverse iterator to the end.
*
* The returned iterator points to the element following the last instance
* of the reversed internal array. Attempting to dereference the returned
* iterator results in undefined behavior.
*
* @return An iterator to the element following the last instance of the * @return An iterator to the element following the last instance of the
* reversed internal array. * reversed internal array.
*/ */
@ -669,7 +663,7 @@ public:
*/ */
template<typename... Args> template<typename... Args>
value_type &emplace(const entity_type entt, Args &&...args) { value_type &emplace(const entity_type entt, Args &&...args) {
if constexpr(std::is_aggregate_v<value_type> && (sizeof...(Args) != 0u || !std::is_default_constructible_v<value_type>)) { if constexpr(std::is_aggregate_v<value_type>) {
const auto it = emplace_element(entt, false, Type{std::forward<Args>(args)...}); const auto it = emplace_element(entt, false, Type{std::forward<Args>(args)...});
return element_at(static_cast<size_type>(it.index())); return element_at(static_cast<size_type>(it.index()));
} else { } else {
@ -705,15 +699,12 @@ public:
* @param first An iterator to the first element of the range of entities. * @param first An iterator to the first element of the range of entities.
* @param last An iterator past the last element of the range of entities. * @param last An iterator past the last element of the range of entities.
* @param value An instance of the object to construct. * @param value An instance of the object to construct.
* @return Iterator pointing to the last element inserted, if any.
*/ */
template<typename It> template<typename It>
iterator insert(It first, It last, const value_type &value = {}) { void insert(It first, It last, const value_type &value = {}) {
for(; first != last; ++first) { for(; first != last; ++first) {
emplace_element(*first, true, value); emplace_element(*first, true, value);
} }
return begin();
} }
/** /**
@ -727,15 +718,12 @@ public:
* @param first An iterator to the first element of the range of entities. * @param first An iterator to the first element of the range of entities.
* @param last An iterator past the last element of the range of entities. * @param last An iterator past the last element of the range of entities.
* @param from An iterator to the first element of the range of objects. * @param from An iterator to the first element of the range of objects.
* @return Iterator pointing to the first element inserted, if any.
*/ */
template<typename EIt, typename CIt, typename = std::enable_if_t<std::is_same_v<typename std::iterator_traits<CIt>::value_type, value_type>>> template<typename EIt, typename CIt, typename = std::enable_if_t<std::is_same_v<typename std::iterator_traits<CIt>::value_type, value_type>>>
iterator insert(EIt first, EIt last, CIt from) { void insert(EIt first, EIt last, CIt from) {
for(; first != last; ++first, ++from) { for(; first != last; ++first, ++from) {
emplace_element(*first, true, *from); emplace_element(*first, true, *from);
} }
return begin();
} }
/** /**
@ -755,54 +743,34 @@ public:
return {internal::extended_storage_iterator{base_type::cbegin(), cbegin()}, internal::extended_storage_iterator{base_type::cend(), cend()}}; return {internal::extended_storage_iterator{base_type::cbegin(), cbegin()}, internal::extended_storage_iterator{base_type::cend(), cend()}};
} }
/**
* @brief Returns a reverse iterable object to use to _visit_ a storage.
*
* @sa each
*
* @return A reverse iterable object to use to _visit_ the storage.
*/
[[nodiscard]] reverse_iterable reach() noexcept {
return {internal::extended_storage_iterator{base_type::rbegin(), rbegin()}, internal::extended_storage_iterator{base_type::rend(), rend()}};
}
/*! @copydoc reach */
[[nodiscard]] const_reverse_iterable reach() const noexcept {
return {internal::extended_storage_iterator{base_type::crbegin(), crbegin()}, internal::extended_storage_iterator{base_type::crend(), crend()}};
}
private: private:
container_type payload; compressed_pair<container_type, allocator_type> packed;
}; };
/*! @copydoc basic_storage */ /*! @copydoc basic_storage */
template<typename Type, typename Entity, typename Allocator> template<typename Type, typename Entity, typename Allocator>
class basic_storage<Type, Entity, Allocator, std::enable_if_t<component_traits<Type>::page_size == 0u>> class basic_storage<Type, Entity, Allocator, std::enable_if_t<ignore_as_empty_v<Type>>>
: public basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>> { : public basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>> {
using alloc_traits = std::allocator_traits<Allocator>; using alloc_traits = std::allocator_traits<Allocator>;
static_assert(std::is_same_v<typename alloc_traits::value_type, Type>, "Invalid value type"); static_assert(std::is_same_v<typename alloc_traits::value_type, Type>, "Invalid value type");
using underlying_type = basic_sparse_set<Entity, typename alloc_traits::template rebind_alloc<Entity>>;
using comp_traits = component_traits<Type>;
public: public:
/*! @brief Base type. */ /*! @brief Base type. */
using base_type = basic_sparse_set<Entity, typename alloc_traits::template rebind_alloc<Entity>>; using base_type = underlying_type;
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Type of the objects assigned to entities. */ /*! @brief Type of the objects assigned to entities. */
using value_type = Type; using value_type = Type;
/*! @brief Component traits. */
using traits_type = component_traits<value_type>;
/*! @brief Underlying entity identifier. */ /*! @brief Underlying entity identifier. */
using entity_type = Entity; using entity_type = Entity;
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
using size_type = std::size_t; using size_type = std::size_t;
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Extended iterable storage proxy. */ /*! @brief Extended iterable storage proxy. */
using iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::iterator>>; using iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::iterator>>;
/*! @brief Constant extended iterable storage proxy. */ /*! @brief Constant extended iterable storage proxy. */
using const_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_iterator>>; using const_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_iterator>>;
/*! @brief Extended reverse iterable storage proxy. */
using reverse_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::reverse_iterator>>;
/*! @brief Constant extended reverse iterable storage proxy. */
using const_reverse_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_reverse_iterator>>;
/*! @brief Default constructor. */ /*! @brief Default constructor. */
basic_storage() basic_storage()
@ -813,7 +781,7 @@ public:
* @param allocator The allocator to use. * @param allocator The allocator to use.
*/ */
explicit basic_storage(const allocator_type &allocator) explicit basic_storage(const allocator_type &allocator)
: base_type{type_id<value_type>(), deletion_policy{traits_type::in_place_delete}, allocator} {} : base_type{type_id<value_type>(), deletion_policy{comp_traits::in_place_delete}, allocator} {}
/** /**
* @brief Move constructor. * @brief Move constructor.
@ -928,325 +896,6 @@ public:
[[nodiscard]] const_iterable each() const noexcept { [[nodiscard]] const_iterable each() const noexcept {
return {internal::extended_storage_iterator{base_type::cbegin()}, internal::extended_storage_iterator{base_type::cend()}}; return {internal::extended_storage_iterator{base_type::cbegin()}, internal::extended_storage_iterator{base_type::cend()}};
} }
/**
* @brief Returns a reverse iterable object to use to _visit_ a storage.
*
* @sa each
*
* @return A reverse iterable object to use to _visit_ the storage.
*/
[[nodiscard]] reverse_iterable reach() noexcept {
return {internal::extended_storage_iterator{base_type::rbegin()}, internal::extended_storage_iterator{base_type::rend()}};
}
/*! @copydoc reach */
[[nodiscard]] const_reverse_iterable reach() const noexcept {
return {internal::extended_storage_iterator{base_type::crbegin()}, internal::extended_storage_iterator{base_type::crend()}};
}
};
/**
* @brief Swap-only entity storage specialization.
* @tparam Entity A valid entity type.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Entity, typename Allocator>
class basic_storage<Entity, Entity, Allocator>
: public basic_sparse_set<Entity, Allocator> {
using alloc_traits = std::allocator_traits<Allocator>;
static_assert(std::is_same_v<typename alloc_traits::value_type, Entity>, "Invalid value type");
using underlying_type = basic_sparse_set<Entity, typename alloc_traits::template rebind_alloc<Entity>>;
using underlying_iterator = typename underlying_type::basic_iterator;
using local_traits_type = entt_traits<Entity>;
auto entity_at(const std::size_t pos) const noexcept {
ENTT_ASSERT(pos < local_traits_type::to_entity(null), "Invalid element");
return local_traits_type::combine(static_cast<typename local_traits_type::entity_type>(pos), {});
}
private:
void swap_or_move([[maybe_unused]] const std::size_t lhs, [[maybe_unused]] const std::size_t rhs) override {
ENTT_ASSERT(((lhs < length) + (rhs < length)) != 1u, "Cross swapping is not supported");
}
protected:
/**
* @brief Erases entities from a 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.
*/
void pop(underlying_iterator first, underlying_iterator last) override {
for(; first != last; ++first) {
if(const auto pos = base_type::index(*first); pos < length) {
base_type::bump(local_traits_type::next(*first));
if(pos != --length) {
base_type::swap_at(pos, length);
}
}
}
}
/*! @brief Erases all entities of a sparse set. */
void pop_all() override {
length = 0u;
base_type::pop_all();
}
/**
* @brief Assigns an entity to a storage.
* @param hint A valid identifier.
* @return Iterator pointing to the emplaced element.
*/
underlying_iterator try_emplace(const Entity hint, const bool, const void *) override {
return base_type::find(emplace(hint));
}
public:
/*! @brief Base type. */
using base_type = basic_sparse_set<Entity, Allocator>;
/*! @brief Type of the objects assigned to entities. */
using value_type = Entity;
/*! @brief Component traits. */
using traits_type = component_traits<value_type>;
/*! @brief Underlying entity identifier. */
using entity_type = Entity;
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Extended iterable storage proxy. */
using iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::iterator>>;
/*! @brief Constant extended iterable storage proxy. */
using const_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_iterator>>;
/*! @brief Extended reverse iterable storage proxy. */
using reverse_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::reverse_iterator>>;
/*! @brief Constant extended reverse iterable storage proxy. */
using const_reverse_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_reverse_iterator>>;
/*! @brief Default constructor. */
basic_storage()
: basic_storage{allocator_type{}} {
}
/**
* @brief Constructs an empty container with a given allocator.
* @param allocator The allocator to use.
*/
explicit basic_storage(const allocator_type &allocator)
: base_type{type_id<value_type>(), deletion_policy::swap_and_pop, allocator},
length{} {}
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
basic_storage(basic_storage &&other) noexcept
: base_type{std::move(other)},
length{std::exchange(other.length, size_type{})} {}
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
basic_storage(basic_storage &&other, const allocator_type &allocator) noexcept
: base_type{std::move(other), allocator},
length{std::exchange(other.length, size_type{})} {}
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This storage.
*/
basic_storage &operator=(basic_storage &&other) noexcept {
base_type::operator=(std::move(other));
length = std::exchange(other.length, size_type{});
return *this;
}
/**
* @brief Returns the object assigned to an entity, that is `void`.
*
* @warning
* Attempting to use an entity that doesn't belong to the storage results in
* undefined behavior.
*
* @param entt A valid identifier.
*/
void get([[maybe_unused]] const entity_type entt) const noexcept {
ENTT_ASSERT(base_type::index(entt) < length, "The requested entity is not a live one");
}
/**
* @brief Returns an empty tuple.
*
* @warning
* Attempting to use an entity that doesn't belong to the storage results in
* undefined behavior.
*
* @param entt A valid identifier.
* @return Returns an empty tuple.
*/
[[nodiscard]] std::tuple<> get_as_tuple([[maybe_unused]] const entity_type entt) const noexcept {
ENTT_ASSERT(base_type::index(entt) < length, "The requested entity is not a live one");
return std::tuple{};
}
/**
* @brief Exchanges the contents with those of a given storage.
* @param other Storage to exchange the content with.
*/
void swap(basic_storage &other) {
using std::swap;
base_type::swap(other);
swap(length, other.length);
}
/**
* @brief Creates a new identifier or recycles a destroyed one.
* @return A valid identifier.
*/
entity_type emplace() {
if(length == base_type::size()) {
return *base_type::try_emplace(entity_at(length++), true);
}
return base_type::operator[](length++);
}
/**
* @brief Creates a new identifier or recycles a destroyed one.
*
* If the requested identifier isn't in use, the suggested one is used.
* Otherwise, a new identifier is returned.
*
* @param hint Required identifier.
* @return A valid identifier.
*/
entity_type emplace(const entity_type hint) {
if(hint == null || hint == tombstone) {
return emplace();
} else if(const auto curr = local_traits_type::construct(local_traits_type::to_entity(hint), base_type::current(hint)); curr == tombstone) {
const auto pos = static_cast<size_type>(local_traits_type::to_entity(hint));
while(!(pos < base_type::size())) {
base_type::try_emplace(entity_at(base_type::size()), true);
}
base_type::swap_at(pos, length++);
} else if(const auto idx = base_type::index(curr); idx < length) {
return emplace();
} else {
base_type::swap_at(idx, length++);
}
base_type::bump(hint);
return hint;
}
/**
* @brief Updates a given identifier.
* @tparam Func Types of the function objects to invoke.
* @param entt A valid identifier.
* @param func Valid function objects.
*/
template<typename... Func>
void patch([[maybe_unused]] const entity_type entt, Func &&...func) {
ENTT_ASSERT(base_type::contains(entt), "Storage does not contain entity");
(std::forward<Func>(func)(), ...);
}
/**
* @brief Assigns 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 insert(It first, It last) {
for(const auto sz = base_type::size(); first != last && length != sz; ++first, ++length) {
*first = base_type::operator[](length);
}
for(; first != last; ++first) {
*first = *base_type::try_emplace(entity_at(length++), true);
}
}
/**
* @brief Makes all elements in a range contiguous.
* @tparam It Type of forward iterator.
* @param first An iterator to the first element of the range to pack.
* @param last An iterator past the last element of the range to pack.
* @return The number of elements within the newly created range.
*/
template<typename It>
size_type pack(It first, It last) {
size_type len = length;
for(; first != last; ++first, --len) {
const auto pos = base_type::index(*first);
ENTT_ASSERT(pos < length, "Invalid element");
base_type::swap_at(pos, static_cast<size_type>(len - 1u));
}
return (length - len);
}
/**
* @brief Returns the number of elements considered still in use.
* @return The number of elements considered still in use.
*/
[[nodiscard]] size_type in_use() const noexcept {
return length;
}
/**
* @brief Sets the number of elements considered still in use.
* @param len The number of elements considered still in use.
*/
void in_use(const size_type len) noexcept {
ENTT_ASSERT(!(len > base_type::size()), "Invalid length");
length = len;
}
/**
* @brief Returns an iterable object to use to _visit_ a storage.
*
* The iterable object returns a tuple that contains the current entity.
*
* @return An iterable object to use to _visit_ the storage.
*/
[[nodiscard]] iterable each() noexcept {
return {internal::extended_storage_iterator{base_type::end() - length}, internal::extended_storage_iterator{base_type::end()}};
}
/*! @copydoc each */
[[nodiscard]] const_iterable each() const noexcept {
return {internal::extended_storage_iterator{base_type::cend() - length}, internal::extended_storage_iterator{base_type::cend()}};
}
/**
* @brief Returns a reverse iterable object to use to _visit_ a storage.
*
* @sa each
*
* @return A reverse iterable object to use to _visit_ the storage.
*/
[[nodiscard]] reverse_iterable reach() noexcept {
return {internal::extended_storage_iterator{base_type::rbegin()}, internal::extended_storage_iterator{base_type::rbegin() + length}};
}
/*! @copydoc reach */
[[nodiscard]] const_reverse_iterable reach() const noexcept {
return {internal::extended_storage_iterator{base_type::crbegin()}, internal::extended_storage_iterator{base_type::crbegin() + length}};
}
private:
size_type length;
}; };
} // namespace entt } // namespace entt

View File

@ -0,0 +1,236 @@
#ifndef ENTT_ENTITY_SIGH_STORAGE_MIXIN_HPP
#define ENTT_ENTITY_SIGH_STORAGE_MIXIN_HPP
#include <utility>
#include "../config/config.h"
#include "../core/any.hpp"
#include "../signal/sigh.hpp"
#include "fwd.hpp"
namespace entt {
/**
* @brief Mixin type used to add signal support to storage types.
*
* The function type of a listener is equivalent to:
*
* @code{.cpp}
* void(basic_registry<entity_type> &, entity_type);
* @endcode
*
* This applies to all signals made available.
*
* @tparam Type The type of the underlying storage.
*/
template<typename Type>
class sigh_storage_mixin final: public Type {
using basic_registry_type = basic_registry<typename Type::entity_type, typename Type::base_type::allocator_type>;
using sigh_type = sigh<void(basic_registry_type &, const typename Type::entity_type), typename Type::allocator_type>;
using basic_iterator = typename Type::basic_iterator;
void pop(basic_iterator first, basic_iterator last) override {
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
for(; first != last; ++first) {
const auto entt = *first;
destruction.publish(*owner, entt);
const auto it = Type::find(entt);
Type::pop(it, it + 1u);
}
}
basic_iterator try_emplace(const typename basic_registry_type::entity_type entt, const bool force_back, const void *value) final {
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
Type::try_emplace(entt, force_back, value);
construction.publish(*owner, entt);
return Type::find(entt);
}
public:
/*! @brief Allocator type. */
using allocator_type = typename Type::allocator_type;
/*! @brief Underlying entity identifier. */
using entity_type = typename Type::entity_type;
/*! @brief Expected registry type. */
using registry_type = basic_registry_type;
/*! @brief Default constructor. */
sigh_storage_mixin()
: sigh_storage_mixin{allocator_type{}} {}
/**
* @brief Constructs an empty storage with a given allocator.
* @param allocator The allocator to use.
*/
explicit sigh_storage_mixin(const allocator_type &allocator)
: Type{allocator},
owner{},
construction{allocator},
destruction{allocator},
update{allocator} {}
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
sigh_storage_mixin(sigh_storage_mixin &&other) noexcept
: Type{std::move(other)},
owner{other.owner},
construction{std::move(other.construction)},
destruction{std::move(other.destruction)},
update{std::move(other.update)} {}
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
sigh_storage_mixin(sigh_storage_mixin &&other, const allocator_type &allocator) noexcept
: Type{std::move(other), allocator},
owner{other.owner},
construction{std::move(other.construction), allocator},
destruction{std::move(other.destruction), allocator},
update{std::move(other.update), allocator} {}
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This storage.
*/
sigh_storage_mixin &operator=(sigh_storage_mixin &&other) noexcept {
Type::operator=(std::move(other));
owner = other.owner;
construction = std::move(other.construction);
destruction = std::move(other.destruction);
update = std::move(other.update);
return *this;
}
/**
* @brief Exchanges the contents with those of a given storage.
* @param other Storage to exchange the content with.
*/
void swap(sigh_storage_mixin &other) {
using std::swap;
Type::swap(other);
swap(owner, other.owner);
swap(construction, other.construction);
swap(destruction, other.destruction);
swap(update, other.update);
}
/**
* @brief Returns a sink object.
*
* The sink returned by this function can be used to receive notifications
* whenever a new instance is created and assigned to an entity.<br/>
* Listeners are invoked after the object has been assigned to the entity.
*
* @sa sink
*
* @return A temporary sink object.
*/
[[nodiscard]] auto on_construct() noexcept {
return sink{construction};
}
/**
* @brief Returns a sink object.
*
* The sink returned by this function can be used to receive notifications
* whenever an instance is explicitly updated.<br/>
* Listeners are invoked after the object has been updated.
*
* @sa sink
*
* @return A temporary sink object.
*/
[[nodiscard]] auto on_update() noexcept {
return sink{update};
}
/**
* @brief Returns a sink object.
*
* The sink returned by this function can be used to receive notifications
* whenever an instance is removed from an entity and thus destroyed.<br/>
* Listeners are invoked before the object has been removed from the entity.
*
* @sa sink
*
* @return A temporary sink object.
*/
[[nodiscard]] auto on_destroy() noexcept {
return sink{destruction};
}
/**
* @brief Assigns entities to a storage.
* @tparam Args Types of arguments to use to construct the object.
* @param entt A valid identifier.
* @param args Parameters to use to initialize the object.
* @return A reference to the newly created object.
*/
template<typename... Args>
decltype(auto) emplace(const entity_type entt, Args &&...args) {
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
Type::emplace(entt, std::forward<Args>(args)...);
construction.publish(*owner, entt);
return this->get(entt);
}
/**
* @brief Patches the given instance for an entity.
* @tparam Func Types of the function objects to invoke.
* @param entt A valid identifier.
* @param func Valid function objects.
* @return A reference to the patched instance.
*/
template<typename... Func>
decltype(auto) patch(const entity_type entt, Func &&...func) {
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
Type::patch(entt, std::forward<Func>(func)...);
update.publish(*owner, entt);
return this->get(entt);
}
/**
* @brief Assigns entities to a storage.
* @tparam It Type of input iterator.
* @tparam Args Types of arguments to use to construct the objects assigned
* to the entities.
* @param first An iterator to the first element of the range of entities.
* @param last An iterator past the last element of the range of entities.
* @param args Parameters to use to initialize the objects assigned to the
* entities.
*/
template<typename It, typename... Args>
void insert(It first, It last, Args &&...args) {
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
Type::insert(first, last, std::forward<Args>(args)...);
for(auto it = construction.empty() ? last : first; it != last; ++it) {
construction.publish(*owner, *it);
}
}
/**
* @brief Forwards variables to derived classes, if any.
* @param value A variable wrapped in an opaque container.
*/
void bind(any value) noexcept final {
auto *reg = any_cast<basic_registry_type>(&value);
owner = reg ? reg : owner;
Type::bind(std::move(value));
}
private:
basic_registry_type *owner;
sigh_type construction;
sigh_type destruction;
sigh_type update;
};
} // namespace entt
#endif

View File

@ -9,8 +9,11 @@
#include "../config/config.h" #include "../config/config.h"
#include "../core/iterator.hpp" #include "../core/iterator.hpp"
#include "../core/type_traits.hpp" #include "../core/type_traits.hpp"
#include "component.hpp"
#include "entity.hpp" #include "entity.hpp"
#include "fwd.hpp" #include "fwd.hpp"
#include "sparse_set.hpp"
#include "storage.hpp"
namespace entt { namespace entt {
@ -21,24 +24,6 @@ namespace entt {
namespace internal { namespace internal {
template<typename... Args, typename Type, std::size_t N>
[[nodiscard]] auto filter_as_tuple(const std::array<const Type *, N> &filter) noexcept {
return std::apply([](const auto *...curr) { return std::make_tuple(static_cast<Args *>(const_cast<constness_as_t<Type, Args> *>(curr))...); }, filter);
}
template<typename Type, std::size_t N>
[[nodiscard]] auto none_of(const std::array<const Type *, N> &filter, const typename Type::entity_type entt) noexcept {
return std::apply([entt](const auto *...curr) { return (!(curr && curr->contains(entt)) && ...); }, filter);
}
template<typename... Get, typename... Exclude, std::size_t... Index>
[[nodiscard]] auto view_pack(const std::tuple<Get *...> value, const std::tuple<Exclude *...> excl, std::index_sequence<Index...>) {
const auto pools = std::tuple_cat(value, excl);
basic_view<get_t<Get...>, exclude_t<Exclude...>> elem{};
(((std::get<Index>(pools) != nullptr) ? elem.template storage<Index>(*std::get<Index>(pools)) : void()), ...);
return elem;
}
template<typename Type, std::size_t Get, std::size_t Exclude> template<typename Type, std::size_t Get, std::size_t Exclude>
class view_iterator final { class view_iterator final {
using iterator_type = typename Type::const_iterator; using iterator_type = typename Type::const_iterator;
@ -46,7 +31,7 @@ class view_iterator final {
[[nodiscard]] bool valid() const noexcept { [[nodiscard]] bool valid() const noexcept {
return ((Get != 0u) || (*it != tombstone)) return ((Get != 0u) || (*it != tombstone))
&& std::apply([entt = *it](const auto *...curr) { return (curr->contains(entt) && ...); }, pools) && std::apply([entt = *it](const auto *...curr) { return (curr->contains(entt) && ...); }, pools)
&& none_of(filter, *it); && std::apply([entt = *it](const auto *...curr) { return (!curr->contains(entt) && ...); }, filter);
} }
public: public:
@ -62,11 +47,11 @@ public:
pools{}, pools{},
filter{} {} filter{} {}
view_iterator(iterator_type curr, iterator_type to, std::array<const Type *, Get> value, std::array<const Type *, Exclude> excl) noexcept view_iterator(iterator_type curr, iterator_type to, std::array<const Type *, Get> all_of, std::array<const Type *, Exclude> none_of) noexcept
: it{curr}, : it{curr},
last{to}, last{to},
pools{value}, pools{all_of},
filter{excl} { filter{none_of} {
while(it != last && !valid()) { while(it != last && !valid()) {
++it; ++it;
} }
@ -112,7 +97,6 @@ template<typename LhsType, auto... LhsArgs, typename RhsType, auto... RhsArgs>
template<typename It, typename... Type> template<typename It, typename... Type>
struct extended_view_iterator final { struct extended_view_iterator final {
using iterator_type = It;
using difference_type = std::ptrdiff_t; using difference_type = std::ptrdiff_t;
using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval<It>()), std::declval<Type>().get_as_tuple({})...)); using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval<It>()), std::declval<Type>().get_as_tuple({})...));
using pointer = input_iterator_pointer<value_type>; using pointer = input_iterator_pointer<value_type>;
@ -123,9 +107,9 @@ struct extended_view_iterator final {
: it{}, : it{},
pools{} {} pools{} {}
extended_view_iterator(It from, std::tuple<Type *...> value) extended_view_iterator(It from, std::tuple<Type *...> storage)
: it{from}, : it{from},
pools{value} {} pools{storage} {}
extended_view_iterator &operator++() noexcept { extended_view_iterator &operator++() noexcept {
return ++it, *this; return ++it, *this;
@ -144,10 +128,6 @@ struct extended_view_iterator final {
return operator*(); return operator*();
} }
[[nodiscard]] constexpr iterator_type base() const noexcept {
return it;
}
template<typename... Lhs, typename... Rhs> template<typename... Lhs, typename... Rhs>
friend bool constexpr operator==(const extended_view_iterator<Lhs...> &, const extended_view_iterator<Rhs...> &) noexcept; friend bool constexpr operator==(const extended_view_iterator<Lhs...> &, const extended_view_iterator<Rhs...> &) noexcept;
@ -199,33 +179,31 @@ class basic_view;
* or removed from it). * or removed from it).
* * The entity currently pointed is destroyed. * * The entity currently pointed is destroyed.
* *
* In all other cases, modifying the storage iterated by the view in any way * In all other cases, modifying the pools iterated by the view in any way
* invalidates all the iterators. * invalidates all the iterators and using them results in undefined behavior.
* *
* @tparam Get Types of storage iterated by the view. * @tparam Get Types of storage iterated by the view.
* @tparam Exclude Types of storage used to filter the view. * @tparam Exclude Types of storage used to filter the view.
*/ */
template<typename... Get, typename... Exclude> template<typename... Get, typename... Exclude>
class basic_view<get_t<Get...>, exclude_t<Exclude...>> { class basic_view<get_t<Get...>, exclude_t<Exclude...>> {
static constexpr auto offset = sizeof...(Get); using underlying_type = std::common_type_t<typename Get::entity_type..., typename Exclude::entity_type...>;
using base_type = std::common_type_t<typename Get::base_type..., typename Exclude::base_type...>; using basic_common_type = std::common_type_t<typename Get::base_type..., typename Exclude::base_type...>;
using underlying_type = typename base_type::entity_type;
template<typename, typename, typename> template<typename, typename, typename>
friend class basic_view; friend class basic_view;
template<typename Type> template<typename Type>
static constexpr std::size_t index_of = type_list_index_v<std::remove_const_t<Type>, type_list<typename Get::value_type..., typename Exclude::value_type...>>; static constexpr std::size_t index_of = type_list_index_v<std::remove_const_t<Type>, type_list<typename Get::value_type...>>;
[[nodiscard]] auto opaque_check_set() const noexcept { [[nodiscard]] auto opaque_check_set() const noexcept {
std::array<const common_type *, sizeof...(Get) - 1u> other{}; std::array<const base_type *, sizeof...(Get) - 1u> other{};
std::apply([&other, pos = 0u, view = view](const auto *...curr) mutable { ((curr == view ? void() : void(other[pos++] = curr)), ...); }, pools); std::apply([&other, pos = 0u, view = view](const auto *...curr) mutable { ((curr == view ? void() : void(other[pos++] = curr)), ...); }, pools);
return other; return other;
} }
void unchecked_refresh() noexcept { [[nodiscard]] auto filter_as_array() const noexcept {
view = std::get<0>(pools); return std::apply([](const auto *...curr) { return std::array<const base_type *, sizeof...(Exclude)>{curr...}; }, filter);
std::apply([this](auto *, auto *...other) { ((this->view = other->size() < this->view->size() ? other : this->view), ...); }, pools);
} }
template<std::size_t Curr, std::size_t Other, typename... Args> template<std::size_t Curr, std::size_t Other, typename... Args>
@ -233,14 +211,18 @@ class basic_view<get_t<Get...>, exclude_t<Exclude...>> {
if constexpr(Curr == Other) { if constexpr(Curr == Other) {
return std::forward_as_tuple(std::get<Args>(curr)...); return std::forward_as_tuple(std::get<Args>(curr)...);
} else { } else {
return std::get<Other>(pools)->get_as_tuple(std::get<0>(curr)); return storage<Other>().get_as_tuple(std::get<0>(curr));
} }
} }
[[nodiscard]] auto reject(const underlying_type entt) const noexcept {
return std::apply([entt](const auto *...curr) { return (curr->contains(entt) || ...); }, filter);
}
template<std::size_t Curr, typename Func, std::size_t... Index> template<std::size_t Curr, typename Func, std::size_t... Index>
void each(Func &func, std::index_sequence<Index...>) const { void each(Func &func, std::index_sequence<Index...>) const {
for(const auto curr: std::get<Curr>(pools)->each()) { for(const auto curr: storage<Curr>().each()) {
if(const auto entt = std::get<0>(curr); ((sizeof...(Get) != 1u) || (entt != tombstone)) && ((Curr == Index || std::get<Index>(pools)->contains(entt)) && ...) && internal::none_of(filter, entt)) { if(const auto entt = std::get<0>(curr); ((sizeof...(Get) != 1u) || (entt != tombstone)) && ((Curr == Index || storage<Index>().contains(entt)) && ...) && !reject(entt)) {
if constexpr(is_applicable_v<Func, decltype(std::tuple_cat(std::tuple<entity_type>{}, std::declval<basic_view>().get({})))>) { if constexpr(is_applicable_v<Func, decltype(std::tuple_cat(std::tuple<entity_type>{}, std::declval<basic_view>().get({})))>) {
std::apply(func, std::tuple_cat(std::make_tuple(entt), dispatch_get<Curr, Index>(curr)...)); std::apply(func, std::tuple_cat(std::make_tuple(entt), dispatch_get<Curr, Index>(curr)...));
} else { } else {
@ -252,7 +234,7 @@ class basic_view<get_t<Get...>, exclude_t<Exclude...>> {
template<typename Func, std::size_t... Index> template<typename Func, std::size_t... Index>
void pick_and_each(Func &func, std::index_sequence<Index...> seq) const { void pick_and_each(Func &func, std::index_sequence<Index...> seq) const {
((std::get<Index>(pools) == view ? each<Index>(func, seq) : void()), ...); ((&storage<Index>() == view ? each<Index>(func, seq) : void()), ...);
} }
public: public:
@ -261,9 +243,9 @@ public:
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
using size_type = std::size_t; using size_type = std::size_t;
/*! @brief Common type among all storage types. */ /*! @brief Common type among all storage types. */
using common_type = base_type; using base_type = basic_common_type;
/*! @brief Bidirectional iterator type. */ /*! @brief Bidirectional iterator type. */
using iterator = internal::view_iterator<common_type, sizeof...(Get) - 1u, sizeof...(Exclude)>; using iterator = internal::view_iterator<base_type, sizeof...(Get) - 1u, sizeof...(Exclude)>;
/*! @brief Iterable view type. */ /*! @brief Iterable view type. */
using iterable = iterable_adaptor<internal::extended_view_iterator<iterator, Get...>>; using iterable = iterable_adaptor<internal::extended_view_iterator<iterator, Get...>>;
@ -276,14 +258,12 @@ public:
/** /**
* @brief Constructs a multi-type view from a set of storage classes. * @brief Constructs a multi-type view from a set of storage classes.
* @param value The storage for the types to iterate. * @param value The storage for the types to iterate.
* @param excl The storage for the types used to filter the view. * @param exclude The storage for the types used to filter the view.
*/ */
basic_view(Get &...value, Exclude &...excl) noexcept basic_view(Get &...value, Exclude &...exclude) noexcept
: pools{&value...}, : pools{&value...},
filter{&excl...}, filter{&exclude...},
view{} { view{[](const base_type *first, const auto *...other) { ((first = other->size() < first->size() ? other : first), ...); return first; }(&value...)} {}
unchecked_refresh();
}
/** /**
* @brief Constructs a multi-type view from a set of storage classes. * @brief Constructs a multi-type view from a set of storage classes.
@ -291,91 +271,66 @@ public:
* @param excl The storage for the types used to filter the view. * @param excl The storage for the types used to filter the view.
*/ */
basic_view(std::tuple<Get &...> value, std::tuple<Exclude &...> excl = {}) noexcept basic_view(std::tuple<Get &...> value, std::tuple<Exclude &...> excl = {}) noexcept
: basic_view{std::make_from_tuple<basic_view>(std::tuple_cat(value, excl))} {} : pools{std::apply([](auto &...curr) { return std::make_tuple(&curr...); }, value)},
filter{std::apply([](auto &...curr) { return std::make_tuple(&curr...); }, excl)},
view{std::apply([](const base_type *first, const auto *...other) { ((first = other->size() < first->size() ? other : first), ...); return first; }, pools)} {}
/** /**
* @brief Forces a view to use a given component to drive iterations * @brief Creates a new view driven by a given component in its iterations.
* @tparam Type Type of component to use to drive iterations. * @tparam Type Type of component used to drive the iteration.
* @return A new view driven by the given component in its iterations.
*/ */
template<typename Type> template<typename Type>
void use() noexcept { [[nodiscard]] basic_view use() const noexcept {
use<index_of<Type>>(); return use<index_of<Type>>();
} }
/** /**
* @brief Forces a view to use a given component to drive iterations * @brief Creates a new view driven by a given component in its iterations.
* @tparam Index Index of the component to use to drive iterations. * @tparam Index Index of the component used to drive the iteration.
* @return A new view driven by the given component in its iterations.
*/ */
template<std::size_t Index> template<std::size_t Index>
void use() noexcept { [[nodiscard]] basic_view use() const noexcept {
if(view) { basic_view other{*this};
view = std::get<Index>(pools); other.view = &storage<Index>();
} return other;
}
/*! @brief Updates the internal leading view if required. */
void refresh() noexcept {
if(view || std::apply([](const auto *...curr) { return ((curr != nullptr) && ...); }, pools)) {
unchecked_refresh();
}
} }
/** /**
* @brief Returns the leading storage of a view, if any. * @brief Updates the internal leading view if required.
* @return A newly created and internally optimized view.
*/
[[nodiscard]] basic_view refresh() const noexcept {
return std::apply([](auto *...elem) { return basic_view{*elem...}; }, std::tuple_cat(pools, filter));
}
/**
* @brief Returns the leading storage of a view.
* @return The leading storage of the view. * @return The leading storage of the view.
*/ */
[[nodiscard]] const common_type *handle() const noexcept { [[nodiscard]] const base_type &handle() const noexcept {
return view; return *view;
} }
/** /**
* @brief Returns the storage for a given component type, if any. * @brief Returns the storage for a given component type.
* @tparam Type Type of component of which to return the storage. * @tparam Comp Type of component of which to return the storage.
* @return The storage for the given component type. * @return The storage for the given component type.
*/ */
template<typename Type> template<typename Type>
[[nodiscard]] auto *storage() const noexcept { [[nodiscard]] decltype(auto) storage() const noexcept {
return storage<index_of<Type>>(); return storage<index_of<Type>>();
} }
/** /**
* @brief Returns the storage for a given index, if any. * @brief Returns the storage for a given index.
* @tparam Index Index of the storage to return. * @tparam Index Index of the storage to return.
* @return The storage for the given index. * @return The storage for the given index.
*/ */
template<std::size_t Index> template<std::size_t Index>
[[nodiscard]] auto *storage() const noexcept { [[nodiscard]] decltype(auto) storage() const noexcept {
if constexpr(Index < offset) { return *std::get<Index>(pools);
return std::get<Index>(pools);
} else {
return std::get<Index - offset>(internal::filter_as_tuple<Exclude...>(filter));
}
}
/**
* @brief Assigns a storage to a view.
* @tparam Type Type of storage to assign to the view.
* @param elem A storage to assign to the view.
*/
template<typename Type>
void storage(Type &elem) noexcept {
storage<index_of<typename Type::value_type>>(elem);
}
/**
* @brief Assigns a storage to a view.
* @tparam Index Index of the storage to assign to the view.
* @tparam Type Type of storage to assign to the view.
* @param elem A storage to assign to the view.
*/
template<std::size_t Index, typename Type>
void storage(Type &elem) noexcept {
if constexpr(Index < offset) {
std::get<Index>(pools) = &elem;
refresh();
} else {
std::get<Index - offset>(filter) = &elem;
}
} }
/** /**
@ -383,26 +338,32 @@ public:
* @return Estimated number of entities iterated by the view. * @return Estimated number of entities iterated by the view.
*/ */
[[nodiscard]] size_type size_hint() const noexcept { [[nodiscard]] size_type size_hint() const noexcept {
return view ? view->size() : size_type{}; return view->size();
} }
/** /**
* @brief Returns an iterator to the first entity of the view. * @brief Returns an iterator to the first entity of the view.
* *
* If the view is empty, the returned iterator will be equal to `end()`. * The returned iterator points to the first entity of the view. If the view
* is empty, the returned iterator will be equal to `end()`.
* *
* @return An iterator to the first entity of the view. * @return An iterator to the first entity of the view.
*/ */
[[nodiscard]] iterator begin() const noexcept { [[nodiscard]] iterator begin() const noexcept {
return view ? iterator{view->begin(), view->end(), opaque_check_set(), filter} : iterator{}; return iterator{view->begin(), view->end(), opaque_check_set(), filter_as_array()};
} }
/** /**
* @brief Returns an iterator that is past the last entity of the view. * @brief Returns an iterator that is past the last entity of the view.
*
* The returned iterator points to the entity following the last entity of
* the view. Attempting to dereference the returned iterator results in
* undefined behavior.
*
* @return An iterator to the entity following the last entity of the view. * @return An iterator to the entity following the last entity of the view.
*/ */
[[nodiscard]] iterator end() const noexcept { [[nodiscard]] iterator end() const noexcept {
return view ? iterator{view->end(), view->end(), opaque_check_set(), filter} : iterator{}; return iterator{view->end(), view->end(), opaque_check_set(), filter_as_array()};
} }
/** /**
@ -421,15 +382,11 @@ public:
* otherwise. * otherwise.
*/ */
[[nodiscard]] entity_type back() const noexcept { [[nodiscard]] entity_type back() const noexcept {
if(view) {
auto it = view->rbegin(); auto it = view->rbegin();
for(const auto last = view->rend(); it != last && !contains(*it); ++it) {} for(const auto last = view->rend(); it != last && !contains(*it); ++it) {}
return it == view->rend() ? null : *it; return it == view->rend() ? null : *it;
} }
return null;
}
/** /**
* @brief Finds an entity. * @brief Finds an entity.
* @param entt A valid identifier. * @param entt A valid identifier.
@ -437,7 +394,7 @@ public:
* iterator otherwise. * iterator otherwise.
*/ */
[[nodiscard]] iterator find(const entity_type entt) const noexcept { [[nodiscard]] iterator find(const entity_type entt) const noexcept {
return contains(entt) ? iterator{view->find(entt), view->end(), opaque_check_set(), filter} : end(); return contains(entt) ? iterator{view->find(entt), view->end(), opaque_check_set(), filter_as_array()} : end();
} }
/** /**
@ -450,12 +407,11 @@ public:
} }
/** /**
* @brief Checks if a view is fully initialized. * @brief Checks if a view is properly initialized.
* @return True if the view is fully initialized, false otherwise. * @return True if the view is properly initialized, false otherwise.
*/ */
[[nodiscard]] explicit operator bool() const noexcept { [[nodiscard]] explicit operator bool() const noexcept {
return std::apply([](const auto *...curr) { return ((curr != nullptr) && ...); }, pools) return view != nullptr;
&& std::apply([](const auto *...curr) { return ((curr != nullptr) && ...); }, filter);
} }
/** /**
@ -464,7 +420,8 @@ public:
* @return True if the view contains the given entity, false otherwise. * @return True if the view contains the given entity, false otherwise.
*/ */
[[nodiscard]] bool contains(const entity_type entt) const noexcept { [[nodiscard]] bool contains(const entity_type entt) const noexcept {
return view && std::apply([entt](const auto *...curr) { return (curr->contains(entt) && ...); }, pools) && internal::none_of(filter, entt); return std::apply([entt](const auto *...curr) { return (curr->contains(entt) && ...); }, pools)
&& std::apply([entt](const auto *...curr) { return (!curr->contains(entt) && ...); }, filter);
} }
/** /**
@ -474,33 +431,39 @@ public:
* Attempting to use an entity that doesn't belong to the view results in * Attempting to use an entity that doesn't belong to the view results in
* undefined behavior. * undefined behavior.
* *
* @tparam Type Type of the component to get. * @tparam Type Types of components to get.
* @tparam Other Other types of components to get.
* @param entt A valid identifier. * @param entt A valid identifier.
* @return The components assigned to the entity. * @return The components assigned to the entity.
*/ */
template<typename Type, typename... Other> template<typename... Type>
[[nodiscard]] decltype(auto) get(const entity_type entt) const { [[nodiscard]] decltype(auto) get(const entity_type entt) const {
return get<index_of<Type>, index_of<Other>...>(entt); if constexpr(sizeof...(Type) == 0) {
return std::apply([entt](auto *...curr) { return std::tuple_cat(curr->get_as_tuple(entt)...); }, pools);
} else if constexpr(sizeof...(Type) == 1) {
return (storage<index_of<Type>>().get(entt), ...);
} else {
return std::tuple_cat(storage<index_of<Type>>().get_as_tuple(entt)...);
}
} }
/** /**
* @brief Returns the components assigned to the given entity. * @brief Returns the components assigned to the given entity.
* *
* @sa get * @warning
* Attempting to use an entity that doesn't belong to the view results in
* undefined behavior.
* *
* @tparam Index Indexes of the components to get. * @tparam First Index of a component to get.
* @tparam Other Indexes of other components to get.
* @param entt A valid identifier. * @param entt A valid identifier.
* @return The components assigned to the entity. * @return The components assigned to the entity.
*/ */
template<std::size_t... Index> template<std::size_t First, std::size_t... Other>
[[nodiscard]] decltype(auto) get(const entity_type entt) const { [[nodiscard]] decltype(auto) get(const entity_type entt) const {
if constexpr(sizeof...(Index) == 0) { if constexpr(sizeof...(Other) == 0) {
return std::apply([entt](auto *...curr) { return std::tuple_cat(curr->get_as_tuple(entt)...); }, pools); return storage<First>().get(entt);
} else if constexpr(sizeof...(Index) == 1) {
return (std::get<Index>(pools)->get(entt), ...);
} else { } else {
return std::tuple_cat(std::get<Index>(pools)->get_as_tuple(entt)...); return std::tuple_cat(storage<First>().get_as_tuple(entt), storage<Other>().get_as_tuple(entt)...);
} }
} }
@ -524,7 +487,7 @@ public:
*/ */
template<typename Func> template<typename Func>
void each(Func func) const { void each(Func func) const {
view ? pick_and_each(func, std::index_sequence_for<Get...>{}) : void(); pick_and_each(func, std::index_sequence_for<Get...>{});
} }
/** /**
@ -549,16 +512,14 @@ public:
*/ */
template<typename... OGet, typename... OExclude> template<typename... OGet, typename... OExclude>
[[nodiscard]] auto operator|(const basic_view<get_t<OGet...>, exclude_t<OExclude...>> &other) const noexcept { [[nodiscard]] auto operator|(const basic_view<get_t<OGet...>, exclude_t<OExclude...>> &other) const noexcept {
return internal::view_pack( return std::make_from_tuple<basic_view<get_t<Get..., OGet...>, exclude_t<Exclude..., OExclude...>>>(
std::tuple_cat(pools, other.pools), std::apply([](auto *...curr) { return std::forward_as_tuple(*curr...); }, std::tuple_cat(pools, other.pools, filter, other.filter)));
std::tuple_cat(internal::filter_as_tuple<Exclude...>(filter), internal::filter_as_tuple<OExclude...>(other.filter)),
std::index_sequence_for<Get..., OGet..., Exclude..., OExclude...>{});
} }
private: private:
std::tuple<Get *...> pools; std::tuple<Get *...> pools;
std::array<const common_type *, sizeof...(Exclude)> filter; std::tuple<Exclude *...> filter;
const common_type *view; const base_type *view;
}; };
/** /**
@ -577,13 +538,13 @@ private:
* or removed from it). * or removed from it).
* * The entity currently pointed is destroyed. * * The entity currently pointed is destroyed.
* *
* In all other cases, modifying the storage iterated by the view in any way * In all other cases, modifying the pool iterated by the view in any way
* invalidates all the iterators. * invalidates all the iterators and using them results in undefined behavior.
* *
* @tparam Get Type of storage iterated by the view. * @tparam Get Type of storage iterated by the view.
*/ */
template<typename Get> template<typename Get>
class basic_view<get_t<Get>, exclude_t<>, std::void_t<std::enable_if_t<!Get::traits_type::in_place_delete>>> { class basic_view<get_t<Get>, exclude_t<>, std::void_t<std::enable_if_t<!component_traits<typename Get::value_type>::in_place_delete>>> {
template<typename, typename, typename> template<typename, typename, typename>
friend class basic_view; friend class basic_view;
@ -593,81 +554,62 @@ public:
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
using size_type = std::size_t; using size_type = std::size_t;
/*! @brief Common type among all storage types. */ /*! @brief Common type among all storage types. */
using common_type = typename Get::base_type; using base_type = typename Get::base_type;
/*! @brief Random access iterator type. */ /*! @brief Random access iterator type. */
using iterator = typename common_type::iterator; using iterator = typename base_type::iterator;
/*! @brief Reversed iterator type. */ /*! @brief Reversed iterator type. */
using reverse_iterator = typename common_type::reverse_iterator; using reverse_iterator = typename base_type::reverse_iterator;
/*! @brief Iterable view type. */ /*! @brief Iterable view type. */
using iterable = decltype(std::declval<Get>().each()); using iterable = decltype(std::declval<Get>().each());
/*! @brief Default constructor to use to create empty, invalid views. */ /*! @brief Default constructor to use to create empty, invalid views. */
basic_view() noexcept basic_view() noexcept
: pools{}, : pools{},
filter{}, filter{} {}
view{} {}
/** /**
* @brief Constructs a single-type view from a storage class. * @brief Constructs a single-type view from a storage class.
* @param value The storage for the type to iterate. * @param ref The storage for the type to iterate.
*/ */
basic_view(Get &value) noexcept basic_view(Get &ref) noexcept
: pools{&value}, : pools{&ref},
filter{}, filter{} {}
view{&value} {}
/** /**
* @brief Constructs a single-type view from a storage class. * @brief Constructs a single-type view from a storage class.
* @param value The storage for the type to iterate. * @param ref The storage for the type to iterate.
*/ */
basic_view(std::tuple<Get &> value, std::tuple<> = {}) noexcept basic_view(std::tuple<Get &> ref, std::tuple<> = {}) noexcept
: basic_view{std::get<0>(value)} {} : pools{&std::get<0>(ref)},
filter{} {}
/** /**
* @brief Returns the leading storage of a view, if any. * @brief Returns the leading storage of a view.
* @return The leading storage of the view. * @return The leading storage of the view.
*/ */
[[nodiscard]] const common_type *handle() const noexcept { [[nodiscard]] const base_type &handle() const noexcept {
return view; return storage();
} }
/** /**
* @brief Returns the storage for a given component type, if any. * @brief Returns the storage for a given component type.
* @tparam Type Type of component of which to return the storage. * @tparam Type Type of component of which to return the storage.
* @return The storage for the given component type. * @return The storage for the given component type.
*/ */
template<typename Type = typename Get::value_type> template<typename Type = typename Get::value_type>
[[nodiscard]] auto *storage() const noexcept { [[nodiscard]] decltype(auto) storage() const noexcept {
static_assert(std::is_same_v<std::remove_const_t<Type>, typename Get::value_type>, "Invalid component type"); static_assert(std::is_same_v<std::remove_const_t<Type>, typename Get::value_type>, "Invalid component type");
return storage<0>(); return storage<0>();
} }
/** /**
* @brief Returns the storage for a given index, if any. * @brief Returns the storage for a given index.
* @tparam Index Index of the storage to return. * @tparam Index Index of the storage to return.
* @return The storage for the given index. * @return The storage for the given index.
*/ */
template<std::size_t Index> template<std::size_t Index>
[[nodiscard]] auto *storage() const noexcept { [[nodiscard]] decltype(auto) storage() const noexcept {
return std::get<Index>(pools); return *std::get<Index>(pools);
}
/**
* @brief Assigns a storage to a view.
* @param elem A storage to assign to the view.
*/
void storage(Get &elem) noexcept {
storage<0>(elem);
}
/**
* @brief Assigns a storage to a view.
* @tparam Index Index of the storage to assign to the view.
* @param elem A storage to assign to the view.
*/
template<std::size_t Index>
void storage(Get &elem) noexcept {
view = std::get<Index>(pools) = &elem;
} }
/** /**
@ -675,7 +617,7 @@ public:
* @return Number of entities that have the given component. * @return Number of entities that have the given component.
*/ */
[[nodiscard]] size_type size() const noexcept { [[nodiscard]] size_type size() const noexcept {
return view ? view->size() : size_type{}; return handle().size();
} }
/** /**
@ -683,47 +625,59 @@ public:
* @return True if the view is empty, false otherwise. * @return True if the view is empty, false otherwise.
*/ */
[[nodiscard]] bool empty() const noexcept { [[nodiscard]] bool empty() const noexcept {
return !view || view->empty(); return handle().empty();
} }
/** /**
* @brief Returns an iterator to the first entity of the view. * @brief Returns an iterator to the first entity of the view.
* *
* If the view is empty, the returned iterator will be equal to `end()`. * The returned iterator points to the first entity of the view. If the view
* is empty, the returned iterator will be equal to `end()`.
* *
* @return An iterator to the first entity of the view. * @return An iterator to the first entity of the view.
*/ */
[[nodiscard]] iterator begin() const noexcept { [[nodiscard]] iterator begin() const noexcept {
return view ? view->begin() : iterator{}; return handle().begin();
} }
/** /**
* @brief Returns an iterator that is past the last entity of the view. * @brief Returns an iterator that is past the last entity of the view.
*
* The returned iterator points to the entity following the last entity of
* the view. Attempting to dereference the returned iterator results in
* undefined behavior.
*
* @return An iterator to the entity following the last entity of the view. * @return An iterator to the entity following the last entity of the view.
*/ */
[[nodiscard]] iterator end() const noexcept { [[nodiscard]] iterator end() const noexcept {
return view ? view->end() : iterator{}; return handle().end();
} }
/** /**
* @brief Returns an iterator to the first entity of the reversed view. * @brief Returns an iterator to the first entity of the reversed view.
* *
* If the view is empty, the returned iterator will be equal to `rend()`. * The returned iterator points to the first entity of the reversed view. If
* the view is empty, the returned iterator will be equal to `rend()`.
* *
* @return An iterator to the first entity of the reversed view. * @return An iterator to the first entity of the reversed view.
*/ */
[[nodiscard]] reverse_iterator rbegin() const noexcept { [[nodiscard]] reverse_iterator rbegin() const noexcept {
return view ? view->rbegin() : reverse_iterator{}; return handle().rbegin();
} }
/** /**
* @brief Returns an iterator that is past the last entity of the reversed * @brief Returns an iterator that is past the last entity of the reversed
* view. * view.
*
* The returned iterator points to the entity following the last entity of
* the reversed view. Attempting to dereference the returned iterator
* results in undefined behavior.
*
* @return An iterator to the entity following the last entity of the * @return An iterator to the entity following the last entity of the
* reversed view. * reversed view.
*/ */
[[nodiscard]] reverse_iterator rend() const noexcept { [[nodiscard]] reverse_iterator rend() const noexcept {
return view ? view->rend() : reverse_iterator{}; return handle().rend();
} }
/** /**
@ -732,7 +686,7 @@ public:
* otherwise. * otherwise.
*/ */
[[nodiscard]] entity_type front() const noexcept { [[nodiscard]] entity_type front() const noexcept {
return (!view || view->empty()) ? null : *view->begin(); return empty() ? null : *begin();
} }
/** /**
@ -741,7 +695,7 @@ public:
* otherwise. * otherwise.
*/ */
[[nodiscard]] entity_type back() const noexcept { [[nodiscard]] entity_type back() const noexcept {
return (!view || view->empty()) ? null : *view->rbegin(); return empty() ? null : *rbegin();
} }
/** /**
@ -751,7 +705,7 @@ public:
* iterator otherwise. * iterator otherwise.
*/ */
[[nodiscard]] iterator find(const entity_type entt) const noexcept { [[nodiscard]] iterator find(const entity_type entt) const noexcept {
return view ? view->find(entt) : iterator{}; return contains(entt) ? handle().find(entt) : end();
} }
/** /**
@ -769,15 +723,15 @@ public:
* @return The component assigned to the given entity. * @return The component assigned to the given entity.
*/ */
[[nodiscard]] decltype(auto) operator[](const entity_type entt) const { [[nodiscard]] decltype(auto) operator[](const entity_type entt) const {
return std::get<0>(pools)->get(entt); return storage().get(entt);
} }
/** /**
* @brief Checks if a view is fully initialized. * @brief Checks if a view is properly initialized.
* @return True if the view is fully initialized, false otherwise. * @return True if the view is properly initialized, false otherwise.
*/ */
[[nodiscard]] explicit operator bool() const noexcept { [[nodiscard]] explicit operator bool() const noexcept {
return (std::get<0>(pools) != nullptr); return std::get<0>(pools) != nullptr;
} }
/** /**
@ -786,7 +740,7 @@ public:
* @return True if the view contains the given entity, false otherwise. * @return True if the view contains the given entity, false otherwise.
*/ */
[[nodiscard]] bool contains(const entity_type entt) const noexcept { [[nodiscard]] bool contains(const entity_type entt) const noexcept {
return view && view->contains(entt); return handle().contains(entt);
} }
/** /**
@ -796,24 +750,24 @@ public:
* Attempting to use an entity that doesn't belong to the view results in * Attempting to use an entity that doesn't belong to the view results in
* undefined behavior. * undefined behavior.
* *
* @tparam Elem Type or index of the component to get. * @tparam Type Type or index of the component to get.
* @param entt A valid identifier. * @param entt A valid identifier.
* @return The component assigned to the entity. * @return The component assigned to the entity.
*/ */
template<typename Elem> template<typename... Type>
[[nodiscard]] decltype(auto) get(const entity_type entt) const { [[nodiscard]] decltype(auto) get(const entity_type entt) const {
static_assert(std::is_same_v<std::remove_const_t<Elem>, typename Get::value_type>, "Invalid component type"); if constexpr(sizeof...(Type) == 0) {
return get<0>(entt); return storage().get_as_tuple(entt);
} else {
static_assert((std::is_same_v<std::remove_const_t<Type>, typename Get::value_type> && ...), "Invalid component type");
return storage().get(entt);
}
} }
/*! @copydoc get */ /*! @copydoc get */
template<std::size_t... Elem> template<std::size_t Index>
[[nodiscard]] decltype(auto) get(const entity_type entt) const { [[nodiscard]] decltype(auto) get(const entity_type entt) const {
if constexpr(sizeof...(Elem) == 0) { return storage().get(entt);
return std::get<0>(pools)->get_as_tuple(entt);
} else {
return std::get<Elem...>(pools)->get(entt);
}
} }
/** /**
@ -840,22 +794,20 @@ public:
*/ */
template<typename Func> template<typename Func>
void each(Func func) const { void each(Func func) const {
if(view) {
if constexpr(is_applicable_v<Func, decltype(*each().begin())>) { if constexpr(is_applicable_v<Func, decltype(*each().begin())>) {
for(const auto pack: each()) { for(const auto pack: each()) {
std::apply(func, pack); std::apply(func, pack);
} }
} else if constexpr(Get::traits_type::page_size == 0u) { } else if constexpr(ignore_as_empty_v<typename Get::value_type>) {
for(size_type pos{}, last = size(); pos < last; ++pos) { for(size_type pos{}, last = size(); pos < last; ++pos) {
func(); func();
} }
} else { } else {
for(auto &&component: *std::get<0>(pools)) { for(auto &&component: storage()) {
func(component); func(component);
} }
} }
} }
}
/** /**
* @brief Returns an iterable object to use to _visit_ a view. * @brief Returns an iterable object to use to _visit_ a view.
@ -867,7 +819,7 @@ public:
* @return An iterable object to use to _visit_ the view. * @return An iterable object to use to _visit_ the view.
*/ */
[[nodiscard]] iterable each() const noexcept { [[nodiscard]] iterable each() const noexcept {
return view ? std::get<0>(pools)->each() : iterable{}; return storage().each();
} }
/** /**
@ -879,16 +831,13 @@ public:
*/ */
template<typename... OGet, typename... OExclude> template<typename... OGet, typename... OExclude>
[[nodiscard]] auto operator|(const basic_view<get_t<OGet...>, exclude_t<OExclude...>> &other) const noexcept { [[nodiscard]] auto operator|(const basic_view<get_t<OGet...>, exclude_t<OExclude...>> &other) const noexcept {
return internal::view_pack( return std::make_from_tuple<basic_view<get_t<Get, OGet...>, exclude_t<OExclude...>>>(
std::tuple_cat(pools, other.pools), std::apply([](auto *...curr) { return std::forward_as_tuple(*curr...); }, std::tuple_cat(pools, other.pools, other.filter)));
internal::filter_as_tuple<OExclude...>(other.filter),
std::index_sequence_for<Get, OGet..., OExclude...>{});
} }
private: private:
std::tuple<Get *> pools; std::tuple<Get *> pools;
std::array<const common_type *, 0u> filter; std::tuple<> filter;
const common_type *view;
}; };
/** /**

View File

@ -24,7 +24,6 @@
#include "entity/group.hpp" #include "entity/group.hpp"
#include "entity/handle.hpp" #include "entity/handle.hpp"
#include "entity/helper.hpp" #include "entity/helper.hpp"
#include "entity/mixin.hpp"
#include "entity/observer.hpp" #include "entity/observer.hpp"
#include "entity/organizer.hpp" #include "entity/organizer.hpp"
#include "entity/registry.hpp" #include "entity/registry.hpp"
@ -32,6 +31,7 @@
#include "entity/snapshot.hpp" #include "entity/snapshot.hpp"
#include "entity/sparse_set.hpp" #include "entity/sparse_set.hpp"
#include "entity/storage.hpp" #include "entity/storage.hpp"
#include "entity/storage_mixin.hpp"
#include "entity/view.hpp" #include "entity/view.hpp"
#include "graph/adjacency_matrix.hpp" #include "graph/adjacency_matrix.hpp"
#include "graph/dot.hpp" #include "graph/dot.hpp"

View File

@ -258,7 +258,7 @@ public:
[[nodiscard]] iterable_adaptor<out_edge_iterator> out_edges(const vertex_type vertex) const noexcept { [[nodiscard]] iterable_adaptor<out_edge_iterator> out_edges(const vertex_type vertex) const noexcept {
const auto it = matrix.cbegin(); const auto it = matrix.cbegin();
const auto from = vertex * vert; const auto from = vertex * vert;
const auto to = from + vert; const auto to = vertex * vert + vert;
return {{it, vert, from, to, 1u}, {it, vert, to, to, 1u}}; return {{it, vert, from, to, 1u}, {it, vert, to, to, 1u}};
} }
@ -270,7 +270,7 @@ public:
[[nodiscard]] iterable_adaptor<in_edge_iterator> in_edges(const vertex_type vertex) const noexcept { [[nodiscard]] iterable_adaptor<in_edge_iterator> in_edges(const vertex_type vertex) const noexcept {
const auto it = matrix.cbegin(); const auto it = matrix.cbegin();
const auto from = vertex; const auto from = vertex;
const auto to = vert * vert + from; const auto to = vert * (vert - 1u) + vertex;
return {{it, vert, from, to, vert}, {it, vert, to, to, vert}}; return {{it, vert, from, to, vert}, {it, vert, to, to, vert}};
} }

View File

@ -32,7 +32,6 @@ class basic_flow {
using task_container_type = dense_set<id_type, identity, std::equal_to<id_type>, typename alloc_traits::template rebind_alloc<id_type>>; using task_container_type = dense_set<id_type, identity, std::equal_to<id_type>, typename alloc_traits::template rebind_alloc<id_type>>;
using ro_rw_container_type = std::vector<std::pair<std::size_t, bool>, typename alloc_traits::template rebind_alloc<std::pair<std::size_t, bool>>>; using ro_rw_container_type = std::vector<std::pair<std::size_t, bool>, typename alloc_traits::template rebind_alloc<std::pair<std::size_t, bool>>>;
using deps_container_type = dense_map<id_type, ro_rw_container_type, identity, std::equal_to<id_type>, typename alloc_traits::template rebind_alloc<std::pair<const id_type, ro_rw_container_type>>>; using deps_container_type = dense_map<id_type, ro_rw_container_type, identity, std::equal_to<id_type>, typename alloc_traits::template rebind_alloc<std::pair<const id_type, ro_rw_container_type>>>;
using adjacency_matrix_type = adjacency_matrix<directed_tag, typename alloc_traits::template rebind_alloc<std::size_t>>;
void emplace(const id_type res, const bool is_rw) { void emplace(const id_type res, const bool is_rw) {
ENTT_ASSERT(index.first() < vertices.size(), "Invalid node"); ENTT_ASSERT(index.first() < vertices.size(), "Invalid node");
@ -44,76 +43,6 @@ class basic_flow {
deps[res].emplace_back(index.first(), is_rw); deps[res].emplace_back(index.first(), is_rw);
} }
void setup_graph(adjacency_matrix_type &matrix) const {
for(const auto &elem: deps) {
const auto last = elem.second.cend();
auto it = elem.second.cbegin();
while(it != last) {
if(it->second) {
// rw item
if(auto curr = it++; it != last) {
if(it->second) {
matrix.insert(curr->first, it->first);
} else if(const auto next = std::find_if(it, last, [](const auto &value) { return value.second; }); next != last) {
for(; it != next; ++it) {
matrix.insert(curr->first, it->first);
matrix.insert(it->first, next->first);
}
} else {
for(; it != next; ++it) {
matrix.insert(curr->first, it->first);
}
}
}
} else {
// ro item (first iteration only)
if(const auto next = std::find_if(it, last, [](const auto &value) { return value.second; }); next != last) {
for(; it != next; ++it) {
matrix.insert(it->first, next->first);
}
} else {
it = last;
}
}
}
}
}
void transitive_closure(adjacency_matrix_type &matrix) const {
const auto length = matrix.size();
for(std::size_t vk{}; vk < length; ++vk) {
for(std::size_t vi{}; vi < length; ++vi) {
for(std::size_t vj{}; vj < length; ++vj) {
if(matrix.contains(vi, vk) && matrix.contains(vk, vj)) {
matrix.insert(vi, vj);
}
}
}
}
}
void transitive_reduction(adjacency_matrix_type &matrix) const {
const auto length = matrix.size();
for(std::size_t vert{}; vert < length; ++vert) {
matrix.erase(vert, vert);
}
for(std::size_t vj{}; vj < length; ++vj) {
for(std::size_t vi{}; vi < length; ++vi) {
if(matrix.contains(vi, vj)) {
for(std::size_t vk{}; vk < length; ++vk) {
if(matrix.contains(vj, vk)) {
matrix.erase(vi, vk);
}
}
}
}
}
}
public: public:
/*! @brief Allocator type. */ /*! @brief Allocator type. */
using allocator_type = Allocator; using allocator_type = Allocator;
@ -121,8 +50,6 @@ public:
using size_type = std::size_t; using size_type = std::size_t;
/*! @brief Iterable task list. */ /*! @brief Iterable task list. */
using iterable = iterable_adaptor<typename task_container_type::const_iterator>; using iterable = iterable_adaptor<typename task_container_type::const_iterator>;
/*! @brief Adjacency matrix type. */
using graph_type = adjacency_matrix_type;
/*! @brief Default constructor. */ /*! @brief Default constructor. */
basic_flow() basic_flow()
@ -197,10 +124,9 @@ public:
/*! @brief Clears the flow builder. */ /*! @brief Clears the flow builder. */
void clear() noexcept { void clear() noexcept {
index.first() = {}; index.first() = sync_on = {};
vertices.clear(); vertices.clear();
deps.clear(); deps.clear();
sync_on = {};
} }
/** /**
@ -319,12 +245,72 @@ public:
* @brief Generates a task graph for the current content. * @brief Generates a task graph for the current content.
* @return The adjacency matrix of the task graph. * @return The adjacency matrix of the task graph.
*/ */
[[nodiscard]] graph_type graph() const { [[nodiscard]] adjacency_matrix<directed_tag> graph() const {
graph_type matrix{vertices.size(), get_allocator()}; const auto length = vertices.size();
adjacency_matrix<directed_tag> matrix{length};
setup_graph(matrix); // creates the adjacency matrix
transitive_closure(matrix); for(const auto &elem: deps) {
transitive_reduction(matrix); const auto last = elem.second.cend();
auto it = elem.second.cbegin();
while(it != last) {
if(it->second) {
// rw item
if(auto curr = it++; it != last) {
if(it->second) {
matrix.insert(curr->first, it->first);
} else if(const auto next = std::find_if(it, last, [](const auto &value) { return value.second; }); next != last) {
for(; it != next; ++it) {
matrix.insert(curr->first, it->first);
matrix.insert(it->first, next->first);
}
} else {
for(; it != next; ++it) {
matrix.insert(curr->first, it->first);
}
}
}
} else {
// ro item (first iteration only)
if(const auto next = std::find_if(it, last, [](const auto &value) { return value.second; }); next != last) {
for(; it != next; ++it) {
matrix.insert(it->first, next->first);
}
} 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) {
if(matrix.contains(vi, vk) && matrix.contains(vk, vj)) {
matrix.insert(vi, vj);
}
}
}
}
// applies the transitive reduction
for(std::size_t vert{}; vert < length; ++vert) {
matrix.erase(vert, vert);
}
for(std::size_t vj{}; vj < length; ++vj) {
for(std::size_t vi{}; vi < length; ++vi) {
if(matrix.contains(vi, vj)) {
for(std::size_t vk{}; vk < length; ++vk) {
if(matrix.contains(vj, vk)) {
matrix.erase(vi, vk);
}
}
}
}
}
return matrix; return matrix;
} }

View File

@ -70,40 +70,40 @@ public:
* cases, they are discarded. * cases, they are discarded.
* *
* @tparam Args Types of arguments to use to construct the fallback service. * @tparam Args Types of arguments to use to construct the fallback service.
* @tparam Type Fallback service type. * @tparam Impl Fallback service type.
* @param args Parameters to use to construct the fallback service. * @param args Parameters to use to construct the fallback service.
* @return A reference to a valid service. * @return A reference to a valid service.
*/ */
template<typename Type = Service, typename... Args> template<typename Impl = Service, typename... Args>
[[nodiscard]] static Service &value_or(Args &&...args) { [[nodiscard]] static Service &value_or(Args &&...args) {
return service ? *service : emplace<Type>(std::forward<Args>(args)...); return service ? *service : emplace<Impl>(std::forward<Args>(args)...);
} }
/** /**
* @brief Sets or replaces a service. * @brief Sets or replaces a service.
* @tparam Type Service type. * @tparam Impl Service type.
* @tparam Args Types of arguments to use to construct the service. * @tparam Args Types of arguments to use to construct the service.
* @param args Parameters to use to construct the service. * @param args Parameters to use to construct the service.
* @return A reference to a valid service. * @return A reference to a valid service.
*/ */
template<typename Type = Service, typename... Args> template<typename Impl = Service, typename... Args>
static Service &emplace(Args &&...args) { static Service &emplace(Args &&...args) {
service = std::make_shared<Type>(std::forward<Args>(args)...); service = std::make_shared<Impl>(std::forward<Args>(args)...);
return *service; return *service;
} }
/** /**
* @brief Sets or replaces a service using a given allocator. * @brief Sets or replaces a service using a given allocator.
* @tparam Type Service type. * @tparam Impl Service type.
* @tparam Allocator Type of allocator used to manage memory and elements. * @tparam Allocator Type of allocator used to manage memory and elements.
* @tparam Args Types of arguments to use to construct the service. * @tparam Args Types of arguments to use to construct the service.
* @param alloc The allocator to use. * @param alloc The allocator to use.
* @param args Parameters to use to construct the service. * @param args Parameters to use to construct the service.
* @return A reference to a valid service. * @return A reference to a valid service.
*/ */
template<typename Type = Service, typename Allocator, typename... Args> template<typename Impl = Service, typename Allocator, typename... Args>
static Service &emplace(std::allocator_arg_t, Allocator alloc, Args &&...args) { static Service &allocate_emplace(Allocator alloc, Args &&...args) {
service = std::allocate_shared<Type>(alloc, std::forward<Args>(args)...); service = std::allocate_shared<Impl>(alloc, std::forward<Args>(args)...);
return *service; return *service;
} }
@ -125,18 +125,6 @@ public:
service = other.value; service = other.value;
} }
/**
* @brief Resets or replaces a service.
* @tparam Type Service type.
* @tparam Deleter Deleter type.
* @param elem A pointer to a service to manage.
* @param deleter A deleter to use to destroy the service.
*/
template<typename Type, typename Deleter = std::default_delete<Type>>
static void reset(Type *elem, Deleter deleter = {}) {
service = std::shared_ptr<Service>{elem, std::move(deleter)};
}
private: private:
// std::shared_ptr because of its type erased allocator which is useful here // std::shared_ptr because of its type erased allocator which is useful here
inline static std::shared_ptr<Service> service{}; inline static std::shared_ptr<Service> service{};

View File

@ -21,8 +21,8 @@ struct meta_type_node;
struct meta_context { struct meta_context {
dense_map<id_type, meta_type_node, identity> value{}; dense_map<id_type, meta_type_node, identity> value{};
[[nodiscard]] static inline meta_context &from(meta_ctx &ctx); static inline meta_context &from(meta_ctx &ctx);
[[nodiscard]] static inline const meta_context &from(const meta_ctx &ctx); static inline const meta_context &from(const meta_ctx &ctx);
}; };
} // namespace internal } // namespace internal
@ -49,11 +49,11 @@ class meta_ctx: private internal::meta_context {
* Internal details not to be documented. * Internal details not to be documented.
*/ */
[[nodiscard]] inline internal::meta_context &internal::meta_context::from(meta_ctx &ctx) { inline internal::meta_context &internal::meta_context::from(meta_ctx &ctx) {
return ctx; return ctx;
} }
[[nodiscard]] inline const internal::meta_context &internal::meta_context::from(const meta_ctx &ctx) { inline const internal::meta_context &internal::meta_context::from(const meta_ctx &ctx) {
return ctx; return ctx;
} }

View File

@ -29,12 +29,28 @@ namespace entt {
namespace internal { namespace internal {
[[nodiscard]] inline decltype(auto) owner(meta_ctx &ctx, const type_info &info) { inline decltype(auto) owner(meta_ctx &ctx, const type_info &info) {
auto &&context = internal::meta_context::from(ctx); auto &&context = internal::meta_context::from(ctx);
ENTT_ASSERT(context.value.contains(info.hash()), "Type not available"); ENTT_ASSERT(context.value.contains(info.hash()), "Type not available");
return context.value[info.hash()]; return context.value[info.hash()];
} }
inline meta_base_node &meta_extend(internal::meta_type_node &parent, const id_type id, meta_base_node node) {
return parent.details->base.insert_or_assign(id, std::move(node)).first->second;
}
inline meta_conv_node &meta_extend(internal::meta_type_node &parent, const id_type id, meta_conv_node node) {
return parent.details->conv.insert_or_assign(id, std::move(node)).first->second;
}
inline meta_ctor_node &meta_extend(internal::meta_type_node &parent, const id_type id, meta_ctor_node node) {
return parent.details->ctor.insert_or_assign(id, std::move(node)).first->second;
}
inline meta_dtor_node &meta_extend(internal::meta_type_node &parent, meta_dtor_node node) {
return (parent.dtor = std::move(node));
}
inline meta_data_node &meta_extend(internal::meta_type_node &parent, const id_type id, meta_data_node node) { inline meta_data_node &meta_extend(internal::meta_type_node &parent, const id_type id, meta_data_node node) {
return parent.details->data.insert_or_assign(id, std::move(node)).first->second; return parent.details->data.insert_or_assign(id, std::move(node)).first->second;
} }
@ -56,6 +72,10 @@ inline meta_func_node &meta_extend(internal::meta_type_node &parent, const id_ty
return parent.details->func.insert_or_assign(id, std::move(node)).first->second; return parent.details->func.insert_or_assign(id, std::move(node)).first->second;
} }
inline meta_prop_node &meta_extend(dense_map<id_type, meta_prop_node, identity> &prop, const id_type id, meta_prop_node node) {
return (prop[id] = std::move(node));
}
} // namespace internal } // namespace internal
/** /**
@ -136,8 +156,16 @@ public:
template<typename Base> template<typename Base>
auto base() noexcept { auto base() noexcept {
static_assert(!std::is_same_v<Type, Base> && std::is_base_of_v<Base, Type>, "Invalid base type"); static_assert(!std::is_same_v<Type, Base> && std::is_base_of_v<Base, Type>, "Invalid base type");
auto *const op = +[](const void *instance) noexcept { return static_cast<const void *>(static_cast<const Base *>(static_cast<const Type *>(instance))); };
internal::owner(*ctx, *info).details->base.insert_or_assign(type_id<Base>().hash(), internal::meta_base_node{&internal::resolve<Base>, op}); internal::meta_extend(
internal::owner(*ctx, *info),
type_id<Base>().hash(),
internal::meta_base_node{
&internal::resolve<Base>,
+[](const void *instance) noexcept {
return static_cast<const void *>(static_cast<const Base *>(static_cast<const Type *>(instance)));
}});
bucket = nullptr; bucket = nullptr;
return *this; return *this;
} }
@ -157,8 +185,15 @@ public:
template<auto Candidate> template<auto Candidate>
auto conv() noexcept { auto conv() noexcept {
using conv_type = std::remove_cv_t<std::remove_reference_t<std::invoke_result_t<decltype(Candidate), Type &>>>; using conv_type = std::remove_cv_t<std::remove_reference_t<std::invoke_result_t<decltype(Candidate), Type &>>>;
auto *const op = +[](const meta_ctx &area, const void *instance) { return forward_as_meta(area, std::invoke(Candidate, *static_cast<const Type *>(instance))); };
internal::owner(*ctx, *info).details->conv.insert_or_assign(type_id<conv_type>().hash(), internal::meta_conv_node{op}); internal::meta_extend(
internal::owner(*ctx, *info),
type_id<conv_type>().hash(),
internal::meta_conv_node{
+[](const meta_ctx &area, const void *instance) {
return forward_as_meta(area, std::invoke(Candidate, *static_cast<const Type *>(instance)));
}});
bucket = nullptr; bucket = nullptr;
return *this; return *this;
} }
@ -175,8 +210,15 @@ public:
template<typename To> template<typename To>
auto conv() noexcept { auto conv() noexcept {
using conv_type = std::remove_cv_t<std::remove_reference_t<To>>; using conv_type = std::remove_cv_t<std::remove_reference_t<To>>;
auto *const op = +[](const meta_ctx &area, const void *instance) { return forward_as_meta(area, static_cast<To>(*static_cast<const Type *>(instance))); };
internal::owner(*ctx, *info).details->conv.insert_or_assign(type_id<conv_type>().hash(), internal::meta_conv_node{op}); internal::meta_extend(
internal::owner(*ctx, *info),
type_id<conv_type>().hash(),
internal::meta_conv_node{
+[](const meta_ctx &area, const void *instance) {
return forward_as_meta(area, static_cast<To>(*static_cast<const Type *>(instance)));
}});
bucket = nullptr; bucket = nullptr;
return *this; return *this;
} }
@ -199,7 +241,15 @@ public:
using descriptor = meta_function_helper_t<Type, decltype(Candidate)>; using descriptor = meta_function_helper_t<Type, decltype(Candidate)>;
static_assert(Policy::template value<typename descriptor::return_type>, "Invalid return type for the given policy"); static_assert(Policy::template value<typename descriptor::return_type>, "Invalid return type for the given policy");
static_assert(std::is_same_v<std::remove_cv_t<std::remove_reference_t<typename descriptor::return_type>>, Type>, "The function doesn't return an object of the required type"); static_assert(std::is_same_v<std::remove_cv_t<std::remove_reference_t<typename descriptor::return_type>>, Type>, "The function doesn't return an object of the required type");
internal::owner(*ctx, *info).details->ctor.insert_or_assign(type_id<typename descriptor::args_type>().hash(), internal::meta_ctor_node{descriptor::args_type::size, &meta_arg<typename descriptor::args_type>, &meta_construct<Type, Candidate, Policy>});
internal::meta_extend(
internal::owner(*ctx, *info),
type_id<typename descriptor::args_type>().hash(),
internal::meta_ctor_node{
descriptor::args_type::size,
&meta_arg<typename descriptor::args_type>,
&meta_construct<Type, Candidate, Policy>});
bucket = nullptr; bucket = nullptr;
return *this; return *this;
} }
@ -219,7 +269,14 @@ public:
// default constructor is already implicitly generated, no need for redundancy // default constructor is already implicitly generated, no need for redundancy
if constexpr(sizeof...(Args) != 0u) { if constexpr(sizeof...(Args) != 0u) {
using descriptor = meta_function_helper_t<Type, Type (*)(Args...)>; using descriptor = meta_function_helper_t<Type, Type (*)(Args...)>;
internal::owner(*ctx, *info).details->ctor.insert_or_assign(type_id<typename descriptor::args_type>().hash(), internal::meta_ctor_node{descriptor::args_type::size, &meta_arg<typename descriptor::args_type>, &meta_construct<Type, Args...>});
internal::meta_extend(
internal::owner(*ctx, *info),
type_id<typename descriptor::args_type>().hash(),
internal::meta_ctor_node{
descriptor::args_type::size,
&meta_arg<typename descriptor::args_type>,
&meta_construct<Type, Args...>});
} }
bucket = nullptr; bucket = nullptr;
@ -247,8 +304,12 @@ public:
template<auto Func> template<auto Func>
auto dtor() noexcept { auto dtor() noexcept {
static_assert(std::is_invocable_v<decltype(Func), Type &>, "The function doesn't accept an object of the type provided"); static_assert(std::is_invocable_v<decltype(Func), Type &>, "The function doesn't accept an object of the type provided");
auto *const op = +[](void *instance) { std::invoke(Func, *static_cast<Type *>(instance)); };
internal::owner(*ctx, *info).dtor = internal::meta_dtor_node{op}; internal::meta_extend(
internal::owner(*ctx, *info),
internal::meta_dtor_node{
+[](void *instance) { std::invoke(Func, *static_cast<Type *>(instance)); }});
bucket = nullptr; bucket = nullptr;
return *this; return *this;
} }
@ -269,39 +330,32 @@ public:
template<auto Data, typename Policy = as_is_t> template<auto Data, typename Policy = as_is_t>
auto data(const id_type id) noexcept { auto data(const id_type id) noexcept {
if constexpr(std::is_member_object_pointer_v<decltype(Data)>) { if constexpr(std::is_member_object_pointer_v<decltype(Data)>) {
using data_type = std::invoke_result_t<decltype(Data), Type &>; using data_type = std::remove_reference_t<std::invoke_result_t<decltype(Data), Type &>>;
static_assert(Policy::template value<data_type>, "Invalid return type for the given policy");
auto &&elem = internal::meta_extend( auto &&elem = internal::meta_extend(
internal::owner(*ctx, *info), internal::owner(*ctx, *info),
id, id,
internal::meta_data_node{ internal::meta_data_node{
/* this is never static */ /* this is never static */
std::is_const_v<std::remove_reference_t<data_type>> ? internal::meta_traits::is_const : internal::meta_traits::is_none, std::is_const_v<data_type> ? internal::meta_traits::is_const : internal::meta_traits::is_none,
1u, 1u,
&internal::resolve<std::remove_cv_t<std::remove_reference_t<data_type>>>, &internal::resolve<std::remove_cv_t<data_type>>,
&meta_arg<type_list<std::remove_cv_t<std::remove_reference_t<data_type>>>>, &meta_arg<type_list<std::remove_cv_t<data_type>>>,
&meta_setter<Type, Data>, &meta_setter<Type, Data>,
&meta_getter<Type, Data, Policy>}); &meta_getter<Type, Data, Policy>});
bucket = &elem.prop; bucket = &elem.prop;
} else { } else {
using data_type = std::remove_pointer_t<decltype(Data)>; using data_type = std::remove_reference_t<std::remove_pointer_t<decltype(Data)>>;
if constexpr(std::is_pointer_v<decltype(Data)>) {
static_assert(Policy::template value<decltype(*Data)>, "Invalid return type for the given policy");
} else {
static_assert(Policy::template value<data_type>, "Invalid return type for the given policy");
}
auto &&elem = internal::meta_extend( auto &&elem = internal::meta_extend(
internal::owner(*ctx, *info), internal::owner(*ctx, *info),
id, id,
internal::meta_data_node{ internal::meta_data_node{
((std::is_same_v<Type, std::remove_cv_t<std::remove_reference_t<data_type>>> || std::is_const_v<std::remove_reference_t<data_type>>) ? internal::meta_traits::is_const : internal::meta_traits::is_none) | internal::meta_traits::is_static, ((std::is_same_v<Type, std::remove_cv_t<data_type>> || std::is_const_v<data_type>) ? internal::meta_traits::is_const : internal::meta_traits::is_none) | internal::meta_traits::is_static,
1u, 1u,
&internal::resolve<std::remove_cv_t<std::remove_reference_t<data_type>>>, &internal::resolve<std::remove_cv_t<data_type>>,
&meta_arg<type_list<std::remove_cv_t<std::remove_reference_t<data_type>>>>, &meta_arg<type_list<std::remove_cv_t<data_type>>>,
&meta_setter<Type, Data>, &meta_setter<Type, Data>,
&meta_getter<Type, Data, Policy>}); &meta_getter<Type, Data, Policy>});
@ -441,11 +495,18 @@ public:
ENTT_ASSERT(bucket != nullptr, "Meta object does not support properties"); ENTT_ASSERT(bucket != nullptr, "Meta object does not support properties");
if constexpr(sizeof...(Value) == 0u) { if constexpr(sizeof...(Value) == 0u) {
(*bucket)[id] = internal::meta_prop_node{&internal::resolve<void>}; internal::meta_extend(
*bucket,
id,
internal::meta_prop_node{
&internal::resolve<void>});
} else { } else {
(*bucket)[id] = internal::meta_prop_node{ internal::meta_extend(
*bucket,
id,
internal::meta_prop_node{
&internal::resolve<std::decay_t<Value>>..., &internal::resolve<std::decay_t<Value>>...,
std::make_shared<std::decay_t<Value>>(std::forward<Value>(value))...}; std::make_shared<std::decay_t<Value>>(std::forward<Value>(value))...});
} }
return *this; return *this;

View File

@ -613,7 +613,7 @@ private:
* @return A properly initialized and not necessarily owning wrapper. * @return A properly initialized and not necessarily owning wrapper.
*/ */
template<typename Type> template<typename Type>
[[nodiscard]] meta_any forward_as_meta(const meta_ctx &ctx, Type &&value) { meta_any forward_as_meta(const meta_ctx &ctx, Type &&value) {
return meta_any{ctx, std::in_place_type<Type &&>, std::forward<Type>(value)}; return meta_any{ctx, std::in_place_type<Type &&>, std::forward<Type>(value)};
} }
@ -624,7 +624,7 @@ template<typename Type>
* @return A properly initialized and not necessarily owning wrapper. * @return A properly initialized and not necessarily owning wrapper.
*/ */
template<typename Type> template<typename Type>
[[nodiscard]] meta_any forward_as_meta(Type &&value) { meta_any forward_as_meta(Type &&value) {
return forward_as_meta(locator<meta_ctx>::value_or(), std::forward<Type>(value)); return forward_as_meta(locator<meta_ctx>::value_or(), std::forward<Type>(value));
} }
@ -722,16 +722,6 @@ struct meta_handle {
return static_cast<bool>(any); return static_cast<bool>(any);
} }
/*! @copydoc meta_any::operator== */
[[nodiscard]] bool operator==(const meta_handle &other) const noexcept {
return (any == other.any);
}
/*! @copydoc meta_any::operator!= */
[[nodiscard]] bool operator!=(const meta_handle &other) const noexcept {
return !(*this == other);
}
/** /**
* @brief Access operator for accessing the contained opaque object. * @brief Access operator for accessing the contained opaque object.
* @return A wrapper that shares a reference to an unmanaged object. * @return A wrapper that shares a reference to an unmanaged object.
@ -766,21 +756,13 @@ struct meta_prop {
ctx{&area} {} ctx{&area} {}
/** /**
* @brief Returns the stored value by const reference. * @brief Returns the stored value by copy.
* @return A wrapper containing the value stored with the property. * @return A wrapper containing the value stored with the property.
*/ */
[[nodiscard]] meta_any value() const { [[nodiscard]] meta_any value() const {
return node->value ? node->type(internal::meta_context::from(*ctx)).from_void(*ctx, nullptr, node->value.get()) : meta_any{meta_ctx_arg, *ctx}; return node->value ? node->type(internal::meta_context::from(*ctx)).from_void(*ctx, nullptr, node->value.get()) : meta_any{meta_ctx_arg, *ctx};
} }
/**
* @brief Returns the stored value by reference.
* @return A wrapper containing the value stored with the property.
*/
[[nodiscard]] meta_any value() {
return node->value ? node->type(internal::meta_context::from(*ctx)).from_void(*ctx, node->value.get(), nullptr) : meta_any{meta_ctx_arg, *ctx};
}
/** /**
* @brief Returns true if an object is valid, false otherwise. * @brief Returns true if an object is valid, false otherwise.
* @return True if the object is valid, false otherwise. * @return True if the object is valid, false otherwise.
@ -789,30 +771,11 @@ struct meta_prop {
return (node != nullptr); return (node != nullptr);
} }
/**
* @brief Checks if two objects refer to the same type.
* @param other The object with which to compare.
* @return True if the objects refer to the same type, false otherwise.
*/
[[nodiscard]] bool operator==(const meta_prop &other) const noexcept {
return (ctx == other.ctx && node == other.node);
}
private: private:
const internal::meta_prop_node *node; const internal::meta_prop_node *node;
const meta_ctx *ctx; const meta_ctx *ctx;
}; };
/**
* @brief Checks if two objects refer to the same type.
* @param lhs An object, either valid or not.
* @param rhs An object, either valid or not.
* @return False if the objects refer to the same node, true otherwise.
*/
[[nodiscard]] inline bool operator!=(const meta_prop &lhs, const meta_prop &rhs) noexcept {
return !(lhs == rhs);
}
/*! @brief Opaque wrapper for data members. */ /*! @brief Opaque wrapper for data members. */
struct meta_data { struct meta_data {
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
@ -913,26 +876,11 @@ struct meta_data {
return (node != nullptr); return (node != nullptr);
} }
/*! @copydoc meta_prop::operator== */
[[nodiscard]] bool operator==(const meta_data &other) const noexcept {
return (ctx == other.ctx && node == other.node);
}
private: private:
const internal::meta_data_node *node; const internal::meta_data_node *node;
const meta_ctx *ctx; const meta_ctx *ctx;
}; };
/**
* @brief Checks if two objects refer to the same type.
* @param lhs An object, either valid or not.
* @param rhs An object, either valid or not.
* @return False if the objects refer to the same node, true otherwise.
*/
[[nodiscard]] inline bool operator!=(const meta_data &lhs, const meta_data &rhs) noexcept {
return !(lhs == rhs);
}
/*! @brief Opaque wrapper for member functions. */ /*! @brief Opaque wrapper for member functions. */
struct meta_func { struct meta_func {
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
@ -1014,12 +962,8 @@ struct meta_func {
*/ */
template<typename... Args> template<typename... Args>
meta_any invoke(meta_handle instance, Args &&...args) const { meta_any invoke(meta_handle instance, Args &&...args) const {
if constexpr(sizeof...(Args) == 0u) { meta_any arguments[sizeof...(Args) + 1u]{{*ctx, std::forward<Args>(args)}...};
return invoke(std::move(instance), static_cast<meta_any *>(nullptr), size_type{}); return invoke(meta_handle{*ctx, std::move(instance)}, arguments, sizeof...(Args));
} else {
meta_any arguments[sizeof...(Args)]{{*ctx, std::forward<Args>(args)}...};
return invoke(std::move(instance), arguments, sizeof...(Args));
}
} }
/*! @copydoc meta_data::prop */ /*! @copydoc meta_data::prop */
@ -1053,26 +997,11 @@ struct meta_func {
return (node != nullptr); return (node != nullptr);
} }
/*! @copydoc meta_prop::operator== */
[[nodiscard]] bool operator==(const meta_func &other) const noexcept {
return (ctx == other.ctx && node == other.node);
}
private: private:
const internal::meta_func_node *node; const internal::meta_func_node *node;
const meta_ctx *ctx; const meta_ctx *ctx;
}; };
/**
* @brief Checks if two objects refer to the same type.
* @param lhs An object, either valid or not.
* @param rhs An object, either valid or not.
* @return False if the objects refer to the same node, true otherwise.
*/
[[nodiscard]] inline bool operator!=(const meta_func &lhs, const meta_func &rhs) noexcept {
return !(lhs == rhs);
}
/*! @brief Opaque wrapper for types. */ /*! @brief Opaque wrapper for types. */
class meta_type { class meta_type {
template<typename Func> template<typename Func>
@ -1410,25 +1339,21 @@ public:
*/ */
template<typename... Args> template<typename... Args>
[[nodiscard]] meta_any construct(Args &&...args) const { [[nodiscard]] meta_any construct(Args &&...args) const {
if constexpr(sizeof...(Args) == 0u) { meta_any arguments[sizeof...(Args) + 1u]{{*ctx, std::forward<Args>(args)}...};
return construct(static_cast<meta_any *>(nullptr), size_type{});
} else {
meta_any arguments[sizeof...(Args)]{{*ctx, std::forward<Args>(args)}...};
return construct(arguments, sizeof...(Args)); return construct(arguments, sizeof...(Args));
} }
}
/** /**
* @brief Wraps an opaque element of the underlying type. * @brief Wraps an opaque element of the underlying type.
* @param element A valid pointer to an element of the underlying type. * @param element A valid pointer to an element of the underlying type.
* @return A wrapper that references the given instance. * @return A wrapper that references the given instance.
*/ */
[[nodiscard]] meta_any from_void(void *element) const { meta_any from_void(void *element) const {
return (element && node.from_void) ? node.from_void(*ctx, element, nullptr) : meta_any{meta_ctx_arg, *ctx}; return (element && node.from_void) ? node.from_void(*ctx, element, nullptr) : meta_any{meta_ctx_arg, *ctx};
} }
/*! @copydoc from_void */ /*! @copydoc from_void */
[[nodiscard]] meta_any from_void(const void *element) const { meta_any from_void(const void *element) const {
return (element && node.from_void) ? node.from_void(*ctx, nullptr, element) : meta_any{meta_ctx_arg, *ctx}; return (element && node.from_void) ? node.from_void(*ctx, nullptr, element) : meta_any{meta_ctx_arg, *ctx};
} }
@ -1448,7 +1373,7 @@ public:
meta_any invoke(const id_type id, meta_handle instance, meta_any *const args, const size_type sz) const { meta_any invoke(const id_type id, meta_handle instance, meta_any *const args, const size_type sz) const {
if(node.details) { if(node.details) {
if(auto it = node.details->func.find(id); it != node.details->func.cend()) { if(auto it = node.details->func.find(id); it != node.details->func.cend()) {
if(const auto *candidate = lookup(args, sz, instance && (instance->data() == nullptr), [curr = &it->second]() mutable { return curr ? std::exchange(curr, curr->next.get()) : nullptr; }); candidate) { if(const auto *candidate = lookup(args, sz, (instance->data() == nullptr), [curr = &it->second]() mutable { return curr ? std::exchange(curr, curr->next.get()) : nullptr; }); candidate) {
return candidate->invoke(*ctx, meta_handle{*ctx, std::move(instance)}, args); return candidate->invoke(*ctx, meta_handle{*ctx, std::move(instance)}, args);
} }
} }
@ -1474,12 +1399,8 @@ public:
*/ */
template<typename... Args> template<typename... Args>
meta_any invoke(const id_type id, meta_handle instance, Args &&...args) const { meta_any invoke(const id_type id, meta_handle instance, Args &&...args) const {
if constexpr(sizeof...(Args) == 0u) { meta_any arguments[sizeof...(Args) + 1u]{{*ctx, std::forward<Args>(args)}...};
return invoke(id, std::move(instance), static_cast<meta_any *>(nullptr), size_type{}); return invoke(id, meta_handle{*ctx, std::move(instance)}, arguments, sizeof...(Args));
} else {
meta_any arguments[sizeof...(Args)]{{*ctx, std::forward<Args>(args)}...};
return invoke(id, std::move(instance), arguments, sizeof...(Args));
}
} }
/** /**
@ -1545,7 +1466,11 @@ public:
return !(ctx == nullptr); return !(ctx == nullptr);
} }
/*! @copydoc meta_prop::operator== */ /**
* @brief Checks if two objects refer to the same type.
* @param other The object with which to compare.
* @return True if the objects refer to the same type, false otherwise.
*/
[[nodiscard]] bool operator==(const meta_type &other) const noexcept { [[nodiscard]] bool operator==(const meta_type &other) const noexcept {
return (ctx == other.ctx) && ((!node.info && !other.node.info) || (node.info && other.node.info && *node.info == *other.node.info)); return (ctx == other.ctx) && ((!node.info && !other.node.info) || (node.info && other.node.info && *node.info == *other.node.info));
} }
@ -1697,7 +1622,7 @@ public:
explicit meta_iterator(const meta_ctx &area, It iter) noexcept explicit meta_iterator(const meta_ctx &area, It iter) noexcept
: ctx{&area}, : ctx{&area},
vtable{&basic_vtable<It>}, vtable{&basic_vtable<It>},
handle{iter} {} handle{std::move(iter)} {}
meta_iterator &operator++() noexcept { meta_iterator &operator++() noexcept {
vtable(operation::incr, handle, 1, nullptr); vtable(operation::incr, handle, 1, nullptr);
@ -1791,7 +1716,7 @@ public:
meta_iterator(const meta_ctx &area, std::integral_constant<bool, KeyOnly>, It iter) noexcept meta_iterator(const meta_ctx &area, std::integral_constant<bool, KeyOnly>, It iter) noexcept
: ctx{&area}, : ctx{&area},
vtable{&basic_vtable<KeyOnly, It>}, vtable{&basic_vtable<KeyOnly, It>},
handle{iter} {} handle{std::move(iter)} {}
meta_iterator &operator++() noexcept { meta_iterator &operator++() noexcept {
vtable(operation::incr, handle, nullptr); vtable(operation::incr, handle, nullptr);
@ -1901,7 +1826,7 @@ inline meta_sequence_container::iterator meta_sequence_container::insert(iterato
* @return A possibly invalid iterator following the last removed element. * @return A possibly invalid iterator following the last removed element.
*/ */
inline meta_sequence_container::iterator meta_sequence_container::erase(iterator it) { inline meta_sequence_container::iterator meta_sequence_container::erase(iterator it) {
return insert(it, {}); return insert(std::move(it), {});
} }
/** /**

View File

@ -68,7 +68,11 @@ struct as_void_t final {
*/ */
template<typename Type> template<typename Type>
struct is_meta_policy struct is_meta_policy
: std::bool_constant<std::is_same_v<Type, as_ref_t> || std::is_same_v<Type, as_cref_t> || std::is_same_v<Type, as_is_t> || std::is_same_v<Type, as_void_t>> {}; : std::disjunction<
std::is_same<Type, as_ref_t>,
std::is_same<Type, as_cref_t>,
std::is_same<Type, as_is_t>,
std::is_same<Type, as_void_t>> {};
/** /**
* @brief Helper variable template. * @brief Helper variable template.

View File

@ -25,19 +25,19 @@ struct meta_range_iterator final {
using reference = value_type; using reference = value_type;
using iterator_category = std::input_iterator_tag; using iterator_category = std::input_iterator_tag;
constexpr meta_range_iterator() noexcept meta_range_iterator() noexcept
: it{}, : it{},
ctx{} {} ctx{} {}
constexpr meta_range_iterator(const meta_ctx &area, const It iter) noexcept meta_range_iterator(const meta_ctx &area, const It iter) noexcept
: it{iter}, : it{iter},
ctx{&area} {} ctx{&area} {}
constexpr meta_range_iterator &operator++() noexcept { meta_range_iterator &operator++() noexcept {
return ++it, *this; return ++it, *this;
} }
constexpr meta_range_iterator operator++(int) noexcept { meta_range_iterator operator++(int) noexcept {
meta_range_iterator orig = *this; meta_range_iterator orig = *this;
return ++(*this), orig; return ++(*this), orig;
} }

View File

@ -30,6 +30,7 @@ struct meta_associative_container_traits;
/** /**
* @brief Provides the member constant `value` to true if a given type is a * @brief Provides the member constant `value` to true if a given type is a
* pointer-like type from the point of view of the meta system, false otherwise. * pointer-like type from the point of view of the meta system, false otherwise.
* @tparam Type Potentially pointer-like type.
*/ */
template<typename> template<typename>
struct is_meta_pointer_like: std::false_type {}; struct is_meta_pointer_like: std::false_type {};

View File

@ -92,12 +92,9 @@ template<typename Type, typename Ret, typename MaybeType, typename... Args>
struct meta_function_descriptor<Type, Ret (*)(MaybeType, Args...)> struct meta_function_descriptor<Type, Ret (*)(MaybeType, Args...)>
: meta_function_descriptor_traits< : meta_function_descriptor_traits<
Ret, Ret,
std::conditional_t< std::conditional_t<std::is_base_of_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type>, type_list<Args...>, type_list<MaybeType, Args...>>,
std::is_same_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type> || std::is_base_of_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type>, !std::is_base_of_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type>,
type_list<Args...>, std::is_base_of_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type> && std::is_const_v<std::remove_reference_t<MaybeType>>> {};
type_list<MaybeType, Args...>>,
!(std::is_same_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type> || std::is_base_of_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type>),
std::is_const_v<std::remove_reference_t<MaybeType>> && (std::is_same_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type> || std::is_base_of_v<std::remove_cv_t<std::remove_reference_t<MaybeType>>, Type>)> {};
/** /**
* @brief Meta function descriptor. * @brief Meta function descriptor.
@ -165,7 +162,7 @@ using meta_function_helper_t = typename meta_function_helper<Type, Candidate>::t
* @return A meta any containing the returned value, if any. * @return A meta any containing the returned value, if any.
*/ */
template<typename Policy = as_is_t, typename Type> template<typename Policy = as_is_t, typename Type>
[[nodiscard]] std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_dispatch(const meta_ctx &ctx, [[maybe_unused]] Type &&value) { std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_dispatch(const meta_ctx &ctx, [[maybe_unused]] Type &&value) {
if constexpr(std::is_same_v<Policy, as_void_t>) { if constexpr(std::is_same_v<Policy, as_void_t>) {
return meta_any{ctx, std::in_place_type<void>}; return meta_any{ctx, std::in_place_type<void>};
} else if constexpr(std::is_same_v<Policy, as_ref_t>) { } else if constexpr(std::is_same_v<Policy, as_ref_t>) {
@ -186,7 +183,7 @@ template<typename Policy = as_is_t, typename Type>
* @return A meta any containing the returned value, if any. * @return A meta any containing the returned value, if any.
*/ */
template<typename Policy = as_is_t, typename Type> template<typename Policy = as_is_t, typename Type>
[[nodiscard]] std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_dispatch(Type &&value) { std::enable_if_t<is_meta_policy_v<Policy>, meta_any> meta_dispatch(Type &&value) {
return meta_dispatch<Policy, Type>(locator<meta_ctx>::value_or(), std::forward<Type>(value)); return meta_dispatch<Policy, Type>(locator<meta_ctx>::value_or(), std::forward<Type>(value));
} }

View File

@ -19,7 +19,7 @@ struct poly_inspector {
* @brief Generic conversion operator (definition only). * @brief Generic conversion operator (definition only).
* @tparam Type Type to which conversion is requested. * @tparam Type Type to which conversion is requested.
*/ */
template<typename Type> template<class Type>
operator Type &&() const; operator Type &&() const;
/** /**

View File

@ -1,18 +1,13 @@
#ifndef ENTT_PROCESS_FWD_HPP #ifndef ENTT_PROCESS_FWD_HPP
#define ENTT_PROCESS_FWD_HPP #define ENTT_PROCESS_FWD_HPP
#include <cstdint>
namespace entt { namespace entt {
template<typename, typename> template<typename, typename>
class process; class process;
template<typename = std::uint32_t> template<typename>
class basic_scheduler; class scheduler;
/*! @brief Alias declaration for the most common use case. */
using scheduler = basic_scheduler<>;
} // namespace entt } // namespace entt

View File

@ -7,7 +7,6 @@
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "fwd.hpp"
#include "process.hpp" #include "process.hpp"
namespace entt { namespace entt {
@ -39,11 +38,11 @@ namespace entt {
* @tparam Delta Type to use to provide elapsed time. * @tparam Delta Type to use to provide elapsed time.
*/ */
template<typename Delta> template<typename Delta>
class basic_scheduler { class scheduler {
struct process_handler { struct process_handler {
using instance_type = std::unique_ptr<void, void (*)(void *)>; using instance_type = std::unique_ptr<void, void (*)(void *)>;
using update_fn_type = bool(basic_scheduler &, std::size_t, Delta, void *); using update_fn_type = bool(scheduler &, std::size_t, Delta, void *);
using abort_fn_type = void(basic_scheduler &, std::size_t, bool); using abort_fn_type = void(scheduler &, std::size_t, bool);
using next_type = std::unique_ptr<process_handler>; using next_type = std::unique_ptr<process_handler>;
instance_type instance; instance_type instance;
@ -59,8 +58,8 @@ class basic_scheduler {
template<typename Proc, typename... Args> template<typename Proc, typename... Args>
continuation then(Args &&...args) { continuation then(Args &&...args) {
static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>, "Invalid process type"); static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>, "Invalid process type");
auto proc = typename process_handler::instance_type{new Proc{std::forward<Args>(args)...}, &basic_scheduler::deleter<Proc>}; auto proc = typename process_handler::instance_type{new Proc{std::forward<Args>(args)...}, &scheduler::deleter<Proc>};
handler->next.reset(new process_handler{std::move(proc), &basic_scheduler::update<Proc>, &basic_scheduler::abort<Proc>, nullptr}); handler->next.reset(new process_handler{std::move(proc), &scheduler::update<Proc>, &scheduler::abort<Proc>, nullptr});
handler = handler->next.get(); handler = handler->next.get();
return *this; return *this;
} }
@ -75,7 +74,7 @@ class basic_scheduler {
}; };
template<typename Proc> template<typename Proc>
[[nodiscard]] static bool update(basic_scheduler &owner, std::size_t pos, const Delta delta, void *data) { [[nodiscard]] static bool update(scheduler &owner, std::size_t pos, const Delta delta, void *data) {
auto *process = static_cast<Proc *>(owner.handlers[pos].instance.get()); auto *process = static_cast<Proc *>(owner.handlers[pos].instance.get());
process->tick(delta, data); process->tick(delta, data);
@ -95,7 +94,7 @@ class basic_scheduler {
} }
template<typename Proc> template<typename Proc>
static void abort(basic_scheduler &owner, std::size_t pos, const bool immediately) { static void abort(scheduler &owner, std::size_t pos, const bool immediately) {
static_cast<Proc *>(owner.handlers[pos].instance.get())->abort(immediately); static_cast<Proc *>(owner.handlers[pos].instance.get())->abort(immediately);
} }
@ -105,20 +104,18 @@ class basic_scheduler {
} }
public: public:
/*! @brief Unsigned integer type. */
using delta_type = Delta;
/*! @brief Unsigned integer type. */ /*! @brief Unsigned integer type. */
using size_type = std::size_t; using size_type = std::size_t;
/*! @brief Default constructor. */ /*! @brief Default constructor. */
basic_scheduler() scheduler()
: handlers{} {} : handlers{} {}
/*! @brief Default move constructor. */ /*! @brief Default move constructor. */
basic_scheduler(basic_scheduler &&) = default; scheduler(scheduler &&) = default;
/*! @brief Default move assignment operator. @return This scheduler. */ /*! @brief Default move assignment operator. @return This scheduler. */
basic_scheduler &operator=(basic_scheduler &&) = default; scheduler &operator=(scheduler &&) = default;
/** /**
* @brief Number of processes currently scheduled. * @brief Number of processes currently scheduled.
@ -174,8 +171,8 @@ public:
template<typename Proc, typename... Args> template<typename Proc, typename... Args>
auto attach(Args &&...args) { auto attach(Args &&...args) {
static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>, "Invalid process type"); static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>, "Invalid process type");
auto proc = typename process_handler::instance_type{new Proc{std::forward<Args>(args)...}, &basic_scheduler::deleter<Proc>}; auto proc = typename process_handler::instance_type{new Proc{std::forward<Args>(args)...}, &scheduler::deleter<Proc>};
auto &&ref = handlers.emplace_back(process_handler{std::move(proc), &basic_scheduler::update<Proc>, &basic_scheduler::abort<Proc>, nullptr}); auto &&ref = handlers.emplace_back(process_handler{std::move(proc), &scheduler::update<Proc>, &scheduler::abort<Proc>, nullptr});
// forces the process to exit the uninitialized state // forces the process to exit the uninitialized state
ref.update(*this, handlers.size() - 1u, {}, nullptr); ref.update(*this, handlers.size() - 1u, {}, nullptr);
return continuation{&handlers.back()}; return continuation{&handlers.back()};
@ -249,7 +246,7 @@ public:
* @param delta Elapsed time. * @param delta Elapsed time.
* @param data Optional data. * @param data Optional data.
*/ */
void update(const delta_type delta, void *data = nullptr) { void update(const Delta delta, void *data = nullptr) {
for(auto pos = handlers.size(); pos; --pos) { for(auto pos = handlers.size(); pos; --pos) {
const auto curr = pos - 1u; const auto curr = pos - 1u;

View File

@ -95,51 +95,51 @@ public:
return operator*(); return operator*();
} }
template<typename... Lhs, typename... Rhs> template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
friend constexpr std::ptrdiff_t operator-(const resource_cache_iterator<Lhs...> &, const resource_cache_iterator<Rhs...> &) noexcept; friend constexpr std::ptrdiff_t operator-(const resource_cache_iterator<TLhs, ILhs> &, const resource_cache_iterator<TRhs, IRhs> &) noexcept;
template<typename... Lhs, typename... Rhs> template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
friend constexpr bool operator==(const resource_cache_iterator<Lhs...> &, const resource_cache_iterator<Rhs...> &) noexcept; friend constexpr bool operator==(const resource_cache_iterator<TLhs, ILhs> &, const resource_cache_iterator<TRhs, IRhs> &) noexcept;
template<typename... Lhs, typename... Rhs> template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
friend constexpr bool operator<(const resource_cache_iterator<Lhs...> &, const resource_cache_iterator<Rhs...> &) noexcept; friend constexpr bool operator<(const resource_cache_iterator<TLhs, ILhs> &, const resource_cache_iterator<TRhs, IRhs> &) noexcept;
private: private:
It it; It it;
}; };
template<typename... Lhs, typename... Rhs> template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
[[nodiscard]] constexpr std::ptrdiff_t operator-(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept { [[nodiscard]] constexpr std::ptrdiff_t operator-(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) noexcept {
return lhs.it - rhs.it; return lhs.it - rhs.it;
} }
template<typename... Lhs, typename... Rhs> template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
[[nodiscard]] constexpr bool operator==(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept { [[nodiscard]] constexpr bool operator==(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) noexcept {
return lhs.it == rhs.it; return lhs.it == rhs.it;
} }
template<typename... Lhs, typename... Rhs> template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
[[nodiscard]] constexpr bool operator!=(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept { [[nodiscard]] constexpr bool operator!=(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) noexcept {
return !(lhs == rhs); return !(lhs == rhs);
} }
template<typename... Lhs, typename... Rhs> template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
[[nodiscard]] constexpr bool operator<(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept { [[nodiscard]] constexpr bool operator<(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) noexcept {
return lhs.it < rhs.it; return lhs.it < rhs.it;
} }
template<typename... Lhs, typename... Rhs> template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
[[nodiscard]] constexpr bool operator>(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept { [[nodiscard]] constexpr bool operator>(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) noexcept {
return rhs < lhs; return rhs < lhs;
} }
template<typename... Lhs, typename... Rhs> template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
[[nodiscard]] constexpr bool operator<=(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept { [[nodiscard]] constexpr bool operator<=(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) noexcept {
return !(lhs > rhs); return !(lhs > rhs);
} }
template<typename... Lhs, typename... Rhs> template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
[[nodiscard]] constexpr bool operator>=(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept { [[nodiscard]] constexpr bool operator>=(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) noexcept {
return !(lhs < rhs); return !(lhs < rhs);
} }
@ -158,7 +158,7 @@ template<typename... Lhs, typename... Rhs>
*/ */
template<typename Type, typename Loader, typename Allocator> template<typename Type, typename Loader, typename Allocator>
class resource_cache { class resource_cache {
using alloc_traits = std::allocator_traits<Allocator>; using alloc_traits = typename std::allocator_traits<Allocator>;
static_assert(std::is_same_v<typename alloc_traits::value_type, Type>, "Invalid value type"); static_assert(std::is_same_v<typename alloc_traits::value_type, Type>, "Invalid value type");
using container_allocator = typename alloc_traits::template rebind_alloc<std::pair<const id_type, typename Loader::result_type>>; using container_allocator = typename alloc_traits::template rebind_alloc<std::pair<const id_type, typename Loader::result_type>>;
using container_type = dense_map<id_type, typename Loader::result_type, identity, std::equal_to<id_type>, container_allocator>; using container_type = dense_map<id_type, typename Loader::result_type, identity, std::equal_to<id_type>, container_allocator>;
@ -241,7 +241,8 @@ public:
/** /**
* @brief Returns an iterator to the beginning. * @brief Returns an iterator to the beginning.
* *
* If the cache is empty, the returned iterator will be equal to `end()`. * The returned iterator points to the first instance of the cache. If the
* cache is empty, the returned iterator will be equal to `end()`.
* *
* @return An iterator to the first instance of the internal cache. * @return An iterator to the first instance of the internal cache.
*/ */
@ -261,6 +262,11 @@ public:
/** /**
* @brief Returns an iterator to the end. * @brief Returns an iterator to the end.
*
* The returned iterator points to the element following the last instance
* of the cache. Attempting to dereference the returned iterator results in
* undefined behavior.
*
* @return An iterator to the element following the last instance of the * @return An iterator to the element following the last instance of the
* internal cache. * internal cache.
*/ */

View File

@ -163,81 +163,81 @@ private:
/** /**
* @brief Compares two handles. * @brief Compares two handles.
* @tparam Lhs Type of resource managed by the first handle. * @tparam Res Type of resource managed by the first handle.
* @tparam Rhs Type of resource managed by the second handle. * @tparam Other Type of resource managed by the second handle.
* @param lhs A valid handle. * @param lhs A valid handle.
* @param rhs A valid handle. * @param rhs A valid handle.
* @return True if both handles refer to the same resource, false otherwise. * @return True if both handles refer to the same resource, false otherwise.
*/ */
template<typename Lhs, typename Rhs> template<typename Res, typename Other>
[[nodiscard]] bool operator==(const resource<Lhs> &lhs, const resource<Rhs> &rhs) noexcept { [[nodiscard]] bool operator==(const resource<Res> &lhs, const resource<Other> &rhs) noexcept {
return (std::addressof(*lhs) == std::addressof(*rhs)); return (std::addressof(*lhs) == std::addressof(*rhs));
} }
/** /**
* @brief Compares two handles. * @brief Compares two handles.
* @tparam Lhs Type of resource managed by the first handle. * @tparam Res Type of resource managed by the first handle.
* @tparam Rhs Type of resource managed by the second handle. * @tparam Other Type of resource managed by the second handle.
* @param lhs A valid handle. * @param lhs A valid handle.
* @param rhs A valid handle. * @param rhs A valid handle.
* @return False if both handles refer to the same resource, true otherwise. * @return False if both handles refer to the same registry, true otherwise.
*/ */
template<typename Lhs, typename Rhs> template<typename Res, typename Other>
[[nodiscard]] bool operator!=(const resource<Lhs> &lhs, const resource<Rhs> &rhs) noexcept { [[nodiscard]] bool operator!=(const resource<Res> &lhs, const resource<Other> &rhs) noexcept {
return !(lhs == rhs); return !(lhs == rhs);
} }
/** /**
* @brief Compares two handles. * @brief Compares two handles.
* @tparam Lhs Type of resource managed by the first handle. * @tparam Res Type of resource managed by the first handle.
* @tparam Rhs Type of resource managed by the second handle. * @tparam Other Type of resource managed by the second handle.
* @param lhs A valid handle. * @param lhs A valid handle.
* @param rhs A valid handle. * @param rhs A valid handle.
* @return True if the first handle is less than the second, false otherwise. * @return True if the first handle is less than the second, false otherwise.
*/ */
template<typename Lhs, typename Rhs> template<typename Res, typename Other>
[[nodiscard]] bool operator<(const resource<Lhs> &lhs, const resource<Rhs> &rhs) noexcept { [[nodiscard]] bool operator<(const resource<Res> &lhs, const resource<Other> &rhs) noexcept {
return (std::addressof(*lhs) < std::addressof(*rhs)); return (std::addressof(*lhs) < std::addressof(*rhs));
} }
/** /**
* @brief Compares two handles. * @brief Compares two handles.
* @tparam Lhs Type of resource managed by the first handle. * @tparam Res Type of resource managed by the first handle.
* @tparam Rhs Type of resource managed by the second handle. * @tparam Other Type of resource managed by the second handle.
* @param lhs A valid handle. * @param lhs A valid handle.
* @param rhs A valid handle. * @param rhs A valid handle.
* @return True if the first handle is greater than the second, false otherwise. * @return True if the first handle is greater than the second, false otherwise.
*/ */
template<typename Lhs, typename Rhs> template<typename Res, typename Other>
[[nodiscard]] bool operator>(const resource<Lhs> &lhs, const resource<Rhs> &rhs) noexcept { [[nodiscard]] bool operator>(const resource<Res> &lhs, const resource<Other> &rhs) noexcept {
return rhs < lhs; return (std::addressof(*lhs) > std::addressof(*rhs));
} }
/** /**
* @brief Compares two handles. * @brief Compares two handles.
* @tparam Lhs Type of resource managed by the first handle. * @tparam Res Type of resource managed by the first handle.
* @tparam Rhs Type of resource managed by the second handle. * @tparam Other Type of resource managed by the second handle.
* @param lhs A valid handle. * @param lhs A valid handle.
* @param rhs A valid handle. * @param rhs A valid handle.
* @return True if the first handle is less than or equal to the second, false * @return True if the first handle is less than or equal to the second, false
* otherwise. * otherwise.
*/ */
template<typename Lhs, typename Rhs> template<typename Res, typename Other>
[[nodiscard]] bool operator<=(const resource<Lhs> &lhs, const resource<Rhs> &rhs) noexcept { [[nodiscard]] bool operator<=(const resource<Res> &lhs, const resource<Other> &rhs) noexcept {
return !(lhs > rhs); return !(lhs > rhs);
} }
/** /**
* @brief Compares two handles. * @brief Compares two handles.
* @tparam Lhs Type of resource managed by the first handle. * @tparam Res Type of resource managed by the first handle.
* @tparam Rhs Type of resource managed by the second handle. * @tparam Other Type of resource managed by the second handle.
* @param lhs A valid handle. * @param lhs A valid handle.
* @param rhs A valid handle. * @param rhs A valid handle.
* @return True if the first handle is greater than or equal to the second, * @return True if the first handle is greater than or equal to the second,
* false otherwise. * false otherwise.
*/ */
template<typename Lhs, typename Rhs> template<typename Res, typename Other>
[[nodiscard]] bool operator>=(const resource<Lhs> &lhs, const resource<Rhs> &rhs) noexcept { [[nodiscard]] bool operator>=(const resource<Res> &lhs, const resource<Other> &rhs) noexcept {
return !(lhs < rhs); return !(lhs < rhs);
} }

View File

@ -76,13 +76,7 @@ class delegate<Ret(Args...)> {
[[nodiscard]] auto wrap(std::index_sequence<Index...>) noexcept { [[nodiscard]] auto wrap(std::index_sequence<Index...>) noexcept {
return [](const void *, Args... args) -> Ret { return [](const void *, Args... args) -> Ret {
[[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward<Args>(args)...); [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward<Args>(args)...);
if constexpr(std::is_invocable_r_v<Ret, decltype(Candidate), type_list_element_t<Index, type_list<Args...>>...>) {
return static_cast<Ret>(std::invoke(Candidate, std::forward<type_list_element_t<Index, type_list<Args...>>>(std::get<Index>(arguments))...)); return static_cast<Ret>(std::invoke(Candidate, std::forward<type_list_element_t<Index, type_list<Args...>>>(std::get<Index>(arguments))...));
} else {
constexpr auto offset = sizeof...(Args) - sizeof...(Index);
return static_cast<Ret>(std::invoke(Candidate, std::forward<type_list_element_t<Index + offset, type_list<Args...>>>(std::get<Index + offset>(arguments))...));
}
}; };
} }
@ -91,13 +85,7 @@ class delegate<Ret(Args...)> {
return [](const void *payload, Args... args) -> Ret { return [](const void *payload, Args... args) -> Ret {
[[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward<Args>(args)...); [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward<Args>(args)...);
Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload)); Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload));
if constexpr(std::is_invocable_r_v<Ret, decltype(Candidate), Type &, type_list_element_t<Index, type_list<Args...>>...>) {
return static_cast<Ret>(std::invoke(Candidate, *curr, std::forward<type_list_element_t<Index, type_list<Args...>>>(std::get<Index>(arguments))...)); return static_cast<Ret>(std::invoke(Candidate, *curr, std::forward<type_list_element_t<Index, type_list<Args...>>>(std::get<Index>(arguments))...));
} else {
constexpr auto offset = sizeof...(Args) - sizeof...(Index);
return static_cast<Ret>(std::invoke(Candidate, *curr, std::forward<type_list_element_t<Index + offset, type_list<Args...>>>(std::get<Index + offset>(arguments))...));
}
}; };
} }
@ -106,13 +94,7 @@ class delegate<Ret(Args...)> {
return [](const void *payload, Args... args) -> Ret { return [](const void *payload, Args... args) -> Ret {
[[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward<Args>(args)...); [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward<Args>(args)...);
Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload)); Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload));
if constexpr(std::is_invocable_r_v<Ret, decltype(Candidate), Type *, type_list_element_t<Index, type_list<Args...>>...>) {
return static_cast<Ret>(std::invoke(Candidate, curr, std::forward<type_list_element_t<Index, type_list<Args...>>>(std::get<Index>(arguments))...)); return static_cast<Ret>(std::invoke(Candidate, curr, std::forward<type_list_element_t<Index, type_list<Args...>>>(std::get<Index>(arguments))...));
} else {
constexpr auto offset = sizeof...(Args) - sizeof...(Index);
return static_cast<Ret>(std::invoke(Candidate, curr, std::forward<type_list_element_t<Index + offset, type_list<Args...>>>(std::get<Index + offset>(arguments))...));
}
}; };
} }
@ -251,14 +233,6 @@ public:
fn = nullptr; fn = nullptr;
} }
/**
* @brief Returns a pointer to the stored callable function target, if any.
* @return An opaque pointer to the stored callable function target.
*/
[[nodiscard]] function_type *target() const noexcept {
return fn;
}
/** /**
* @brief Returns the instance or the payload linked to a delegate, if any. * @brief Returns the instance or the payload linked to a delegate, if any.
* @return An opaque pointer to the underlying data. * @return An opaque pointer to the underlying data.

View File

@ -75,7 +75,7 @@ public:
template<typename... Args> template<typename... Args>
void enqueue(Args &&...args) { void enqueue(Args &&...args) {
if constexpr(std::is_aggregate_v<Type> && (sizeof...(Args) != 0u || !std::is_default_constructible_v<Type>)) { if constexpr(std::is_aggregate_v<Type>) {
events.push_back(Type{std::forward<Args>(args)...}); events.push_back(Type{std::forward<Args>(args)...});
} else { } else {
events.emplace_back(std::forward<Args>(args)...); events.emplace_back(std::forward<Args>(args)...);
@ -179,9 +179,7 @@ public:
* @param allocator The allocator to use. * @param allocator The allocator to use.
*/ */
basic_dispatcher(basic_dispatcher &&other, const allocator_type &allocator) noexcept basic_dispatcher(basic_dispatcher &&other, const allocator_type &allocator) noexcept
: pools{container_type{std::move(other.pools.first()), allocator}, allocator} { : pools{container_type{std::move(other.pools.first()), allocator}, allocator} {}
ENTT_ASSERT(alloc_traits::is_always_equal::value || pools.second() == other.pools.second(), "Copying a dispatcher is not allowed");
}
/** /**
* @brief Move assignment operator. * @brief Move assignment operator.
@ -189,8 +187,6 @@ public:
* @return This dispatcher. * @return This dispatcher.
*/ */
basic_dispatcher &operator=(basic_dispatcher &&other) noexcept { basic_dispatcher &operator=(basic_dispatcher &&other) noexcept {
ENTT_ASSERT(alloc_traits::is_always_equal::value || pools.second() == other.pools.second(), "Copying a dispatcher is not allowed");
pools = std::move(other.pools); pools = std::move(other.pools);
return *this; return *this;
} }

View File

@ -76,9 +76,7 @@ public:
* @param allocator The allocator to use. * @param allocator The allocator to use.
*/ */
emitter(emitter &&other, const allocator_type &allocator) noexcept emitter(emitter &&other, const allocator_type &allocator) noexcept
: handlers{container_type{std::move(other.handlers.first()), allocator}, allocator} { : handlers{container_type{std::move(other.handlers.first()), allocator}, allocator} {}
ENTT_ASSERT(alloc_traits::is_always_equal::value || handlers.second() == other.handlers.second(), "Copying an emitter is not allowed");
}
/** /**
* @brief Move assignment operator. * @brief Move assignment operator.
@ -86,8 +84,6 @@ public:
* @return This dispatcher. * @return This dispatcher.
*/ */
emitter &operator=(emitter &&other) noexcept { emitter &operator=(emitter &&other) noexcept {
ENTT_ASSERT(alloc_traits::is_always_equal::value || handlers.second() == other.handlers.second(), "Copying an emitter is not allowed");
handlers = std::move(other.handlers); handlers = std::move(other.handlers);
return *this; return *this;
} }

View File

@ -1,8 +1,8 @@
#ifndef ENTT_SIGNAL_SIGH_HPP #ifndef ENTT_SIGNAL_SIGH_HPP
#define ENTT_SIGNAL_SIGH_HPP #define ENTT_SIGNAL_SIGH_HPP
#include <cstddef> #include <algorithm>
#include <memory> #include <functional>
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
#include <vector> #include <vector>
@ -56,8 +56,7 @@ class sigh<Ret(Args...), Allocator> {
friend class sink<sigh<Ret(Args...), Allocator>>; friend class sink<sigh<Ret(Args...), Allocator>>;
using alloc_traits = std::allocator_traits<Allocator>; using alloc_traits = std::allocator_traits<Allocator>;
using delegate_type = delegate<Ret(Args...)>; using container_type = std::vector<delegate<Ret(Args...)>, typename alloc_traits::template rebind_alloc<delegate<Ret(Args...)>>>;
using container_type = std::vector<delegate_type, typename alloc_traits::template rebind_alloc<delegate_type>>;
public: public:
/*! @brief Allocator type. */ /*! @brief Allocator type. */
@ -169,8 +168,8 @@ public:
* @param args Arguments to use to invoke listeners. * @param args Arguments to use to invoke listeners.
*/ */
void publish(Args... args) const { void publish(Args... args) const {
for(auto pos = calls.size(); pos; --pos) { for(auto &&call: std::as_const(calls)) {
calls[pos - 1u](args...); call(args...);
} }
} }
@ -190,24 +189,24 @@ public:
*/ */
template<typename Func> template<typename Func>
void collect(Func func, Args... args) const { void collect(Func func, Args... args) const {
for(auto pos = calls.size(); pos; --pos) { for(auto &&call: calls) {
if constexpr(std::is_void_v<Ret> || !std::is_invocable_v<Func, Ret>) { if constexpr(std::is_void_v<Ret>) {
calls[pos - 1u](args...);
if constexpr(std::is_invocable_r_v<bool, Func>) { if constexpr(std::is_invocable_r_v<bool, Func>) {
call(args...);
if(func()) { if(func()) {
break; break;
} }
} else { } else {
call(args...);
func(); func();
} }
} else { } else {
if constexpr(std::is_invocable_r_v<bool, Func, Ret>) { if constexpr(std::is_invocable_r_v<bool, Func, Ret>) {
if(func(calls[pos - 1u](args...))) { if(func(call(args...))) {
break; break;
} }
} else { } else {
func(calls[pos - 1u](args...)); func(call(args...));
} }
} }
} }
@ -359,7 +358,6 @@ private:
template<typename Ret, typename... Args, typename Allocator> template<typename Ret, typename... Args, typename Allocator>
class sink<sigh<Ret(Args...), Allocator>> { class sink<sigh<Ret(Args...), Allocator>> {
using signal_type = sigh<Ret(Args...), Allocator>; using signal_type = sigh<Ret(Args...), Allocator>;
using delegate_type = typename signal_type::delegate_type;
using difference_type = typename signal_type::container_type::difference_type; using difference_type = typename signal_type::container_type::difference_type;
template<auto Candidate, typename Type> template<auto Candidate, typename Type>
@ -372,14 +370,13 @@ class sink<sigh<Ret(Args...), Allocator>> {
sink{*static_cast<signal_type *>(signal)}.disconnect<Candidate>(); sink{*static_cast<signal_type *>(signal)}.disconnect<Candidate>();
} }
template<typename Func> auto before(delegate<Ret(Args...)> call) {
void disconnect_if(Func callback) { const auto &calls = signal->calls;
for(auto pos = signal->calls.size(); pos; --pos) { const auto it = std::find(calls.cbegin(), calls.cend(), std::move(call));
if(auto &elem = signal->calls[pos - 1u]; callback(elem)) {
elem = std::move(signal->calls.back()); sink other{*this};
signal->calls.pop_back(); other.offset = calls.cend() - it;
} return other;
}
} }
public: public:
@ -388,7 +385,8 @@ public:
* @param ref A valid reference to a signal object. * @param ref A valid reference to a signal object.
*/ */
sink(sigh<Ret(Args...), Allocator> &ref) noexcept sink(sigh<Ret(Args...), Allocator> &ref) noexcept
: signal{&ref} {} : offset{},
signal{&ref} {}
/** /**
* @brief Returns false if at least a listener is connected to the sink. * @brief Returns false if at least a listener is connected to the sink.
@ -398,9 +396,89 @@ public:
return signal->calls.empty(); return signal->calls.empty();
} }
/**
* @brief Returns a sink that connects before a given free function or an
* unbound member.
* @tparam Function A valid free function pointer.
* @return A properly initialized sink object.
*/
template<auto Function>
[[nodiscard]] sink before() {
delegate<Ret(Args...)> call{};
call.template connect<Function>();
return before(std::move(call));
}
/**
* @brief Returns a sink that connects before a free function with payload
* or a bound member.
* @tparam Candidate Member or free function to look for.
* @tparam Type Type of class or type of payload.
* @param value_or_instance A valid object that fits the purpose.
* @return A properly initialized sink object.
*/
template<auto Candidate, typename Type>
[[nodiscard]] sink before(Type &&value_or_instance) {
delegate<Ret(Args...)> call{};
call.template connect<Candidate>(value_or_instance);
return before(std::move(call));
}
/**
* @brief Returns a sink that connects before a given instance or specific
* payload.
* @tparam Type Type of class or type of payload.
* @param value_or_instance A valid object that fits the purpose.
* @return A properly initialized sink object.
*/
template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::decay_t<std::remove_pointer_t<Type>>, void>, sink>>
[[nodiscard]] sink before(Type &value_or_instance) {
return before(&value_or_instance);
}
/**
* @brief Returns a sink that connects before a given instance or specific
* payload.
* @param value_or_instance A valid pointer that fits the purpose.
* @return A properly initialized sink object.
*/
[[nodiscard]] sink before(const void *value_or_instance) {
sink other{*this};
if(value_or_instance) {
const auto &calls = signal->calls;
const auto it = std::find_if(calls.cbegin(), calls.cend(), [value_or_instance](const auto &delegate) {
return delegate.data() == value_or_instance;
});
other.offset = calls.cend() - it;
}
return other;
}
/**
* @brief Returns a sink that connects before anything else.
* @return A properly initialized sink object.
*/
[[nodiscard]] sink before() {
sink other{*this};
other.offset = signal->calls.size();
return other;
}
/** /**
* @brief Connects a free function (with or without payload), a bound or an * @brief Connects a free function (with or without payload), a bound or an
* unbound member to a signal. * unbound member to a signal.
*
* The signal isn't responsible for the connected object or the payload, if
* any. Users must guarantee that the lifetime of the instance overcomes the
* one of the signal. On the other side, the signal handler performs
* checks to avoid multiple connections for the same function.<br/>
* When used to connect a free function with payload, its signature must be
* such that the instance is the first argument before the ones used to
* define the signal itself.
*
* @tparam Candidate Function or member to connect to the signal. * @tparam Candidate Function or member to connect to the signal.
* @tparam Type Type of class or type of payload, if any. * @tparam Type Type of class or type of payload, if any.
* @param value_or_instance A valid object that fits the purpose, if any. * @param value_or_instance A valid object that fits the purpose, if any.
@ -410,9 +488,9 @@ public:
connection connect(Type &&...value_or_instance) { connection connect(Type &&...value_or_instance) {
disconnect<Candidate>(value_or_instance...); disconnect<Candidate>(value_or_instance...);
delegate_type call{}; delegate<Ret(Args...)> call{};
call.template connect<Candidate>(value_or_instance...); call.template connect<Candidate>(value_or_instance...);
signal->calls.push_back(std::move(call)); signal->calls.insert(signal->calls.end() - offset, std::move(call));
delegate<void(void *)> conn{}; delegate<void(void *)> conn{};
conn.template connect<&release<Candidate, Type...>>(value_or_instance...); conn.template connect<&release<Candidate, Type...>>(value_or_instance...);
@ -428,9 +506,21 @@ public:
*/ */
template<auto Candidate, typename... Type> template<auto Candidate, typename... Type>
void disconnect(Type &&...value_or_instance) { void disconnect(Type &&...value_or_instance) {
delegate_type call{}; auto &calls = signal->calls;
delegate<Ret(Args...)> call{};
call.template connect<Candidate>(value_or_instance...); call.template connect<Candidate>(value_or_instance...);
disconnect_if([&call](const auto &elem) { return elem == call; }); calls.erase(std::remove(calls.begin(), calls.end(), std::move(call)), calls.end());
}
/**
* @brief Disconnects free functions with payload or bound members from a
* signal.
* @tparam Type Type of class or type of payload.
* @param value_or_instance A valid object that fits the purpose.
*/
template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::decay_t<std::remove_pointer_t<Type>>, void>>>
void disconnect(Type &value_or_instance) {
disconnect(&value_or_instance);
} }
/** /**
@ -440,7 +530,9 @@ public:
*/ */
void disconnect(const void *value_or_instance) { void disconnect(const void *value_or_instance) {
if(value_or_instance) { if(value_or_instance) {
disconnect_if([value_or_instance](const auto &elem) { return elem.data() == value_or_instance; }); auto &calls = signal->calls;
auto predicate = [value_or_instance](const auto &delegate) { return delegate.data() == value_or_instance; };
calls.erase(std::remove_if(calls.begin(), calls.end(), std::move(predicate)), calls.end());
} }
} }
@ -450,6 +542,7 @@ public:
} }
private: private:
difference_type offset;
signal_type *signal; signal_type *signal;
}; };

View File

@ -51,20 +51,7 @@ function(SETUP_TARGET TARGET_NAME)
target_compile_options( target_compile_options(
${TARGET_NAME} ${TARGET_NAME}
PRIVATE PRIVATE
# vs2017 emits too many false positives for my tastes /EHsc /W1 /wd4996 /w14800
$<IF:$<EQUAL:${MSVC_TOOLSET_VERSION},141>, /W1, /W4>
# clang-cl goes a little wrong with some warnings instead
$<$<STREQUAL:"${CMAKE_CXX_COMPILER_ID}","Clang">:
-Wno-deprecated-declarations
-Wno-ignored-qualifiers
-Wno-unknown-warning-option
-Wno-exceptions
-Wno-unused-local-typedef
-Wno-unused-private-field
>
# documentation diagnostic turned on for clang-cl only
$<$<STREQUAL:"${CMAKE_CXX_COMPILER_ID}","Clang">:-Wdocumentation>
/EHsc /wd4324 /wd4996
$<$<CONFIG:Debug>:/Od> $<$<CONFIG:Debug>:/Od>
$<$<CONFIG:Release>:/O2> $<$<CONFIG:Release>:/O2>
) )
@ -107,23 +94,21 @@ function(SETUP_BASIC_TEST TEST_NAME TEST_SOURCES)
add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}) add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME})
endfunction() endfunction()
function(SETUP_LIB_SHARED_TEST TEST_NAME SUB_PATH) function(SETUP_LIB_TEST TEST_NAME)
set(TARGET_NAME ${TEST_NAME}_${SUB_PATH}) add_library(_${TEST_NAME} SHARED $<TARGET_OBJECTS:odr> lib/${TEST_NAME}/lib.cpp)
add_library(_${TARGET_NAME} SHARED $<TARGET_OBJECTS:odr> lib/${TEST_NAME}/${SUB_PATH}/lib.cpp) SETUP_TARGET(_${TEST_NAME} ENTT_API_EXPORT)
SETUP_TARGET(_${TARGET_NAME} ENTT_API_EXPORT) SETUP_BASIC_TEST(lib_${TEST_NAME} lib/${TEST_NAME}/main.cpp ENTT_API_IMPORT)
SETUP_BASIC_TEST(lib_${TARGET_NAME} lib/${TEST_NAME}/${SUB_PATH}/main.cpp ENTT_API_IMPORT) target_link_libraries(lib_${TEST_NAME} PRIVATE _${TEST_NAME})
target_link_libraries(lib_${TARGET_NAME} PRIVATE _${TARGET_NAME})
endfunction() endfunction()
function(SETUP_LIB_PLUGIN_TEST TEST_NAME SUB_PATH) function(SETUP_PLUGIN_TEST TEST_NAME)
set(TARGET_NAME ${TEST_NAME}_${SUB_PATH}) add_library(_${TEST_NAME} MODULE $<TARGET_OBJECTS:odr> lib/${TEST_NAME}/plugin.cpp)
add_library(_${TARGET_NAME} MODULE $<TARGET_OBJECTS:odr> lib/${TEST_NAME}/${SUB_PATH}/plugin.cpp) SETUP_TARGET(_${TEST_NAME} ${ARGVN})
SETUP_TARGET(_${TARGET_NAME} ${ARGVN}) SETUP_BASIC_TEST(lib_${TEST_NAME} lib/${TEST_NAME}/main.cpp PLUGIN="$<TARGET_FILE:_${TEST_NAME}>" ${ARGVN})
SETUP_BASIC_TEST(lib_${TARGET_NAME} lib/${TEST_NAME}/${SUB_PATH}/main.cpp PLUGIN="$<TARGET_FILE:_${TARGET_NAME}>" ${ARGVN}) target_include_directories(_${TEST_NAME} PRIVATE ${cr_INCLUDE_DIR})
target_include_directories(_${TARGET_NAME} PRIVATE ${cr_INCLUDE_DIR}) target_include_directories(lib_${TEST_NAME} PRIVATE ${cr_INCLUDE_DIR})
target_include_directories(lib_${TARGET_NAME} PRIVATE ${cr_INCLUDE_DIR}) target_link_libraries(lib_${TEST_NAME} PRIVATE ${CMAKE_DL_LIBS})
target_link_libraries(lib_${TARGET_NAME} PRIVATE ${CMAKE_DL_LIBS}) add_dependencies(lib_${TEST_NAME} _${TEST_NAME})
add_dependencies(lib_${TARGET_NAME} _${TARGET_NAME})
endfunction() endfunction()
# Test benchmark # Test benchmark
@ -157,21 +142,19 @@ if(ENTT_BUILD_LIB)
set(cr_INCLUDE_DIR ${cr_SOURCE_DIR}) set(cr_INCLUDE_DIR ${cr_SOURCE_DIR})
endif() endif()
SETUP_LIB_SHARED_TEST(dispatcher shared) SETUP_LIB_TEST(dispatcher)
SETUP_LIB_PLUGIN_TEST(dispatcher plugin) SETUP_LIB_TEST(emitter)
SETUP_LIB_TEST(locator)
SETUP_LIB_TEST(meta)
SETUP_LIB_TEST(registry)
SETUP_LIB_SHARED_TEST(emitter shared) SETUP_PLUGIN_TEST(dispatcher_plugin)
SETUP_LIB_PLUGIN_TEST(emitter plugin) SETUP_PLUGIN_TEST(emitter_plugin)
SETUP_PLUGIN_TEST(locator_plugin)
SETUP_PLUGIN_TEST(meta_plugin)
SETUP_PLUGIN_TEST(registry_plugin)
SETUP_LIB_SHARED_TEST(locator shared) SETUP_PLUGIN_TEST(meta_plugin_std ENTT_STANDARD_CPP)
SETUP_LIB_PLUGIN_TEST(locator plugin)
SETUP_LIB_SHARED_TEST(meta shared)
SETUP_LIB_PLUGIN_TEST(meta plugin)
SETUP_LIB_PLUGIN_TEST(meta plugin_std ENTT_STANDARD_CPP)
SETUP_LIB_SHARED_TEST(registry shared)
SETUP_LIB_PLUGIN_TEST(registry plugin)
endif() endif()
# Test snapshot # Test snapshot
@ -232,11 +215,10 @@ SETUP_BASIC_TEST(observer entt/entity/observer.cpp)
SETUP_BASIC_TEST(organizer entt/entity/organizer.cpp) SETUP_BASIC_TEST(organizer entt/entity/organizer.cpp)
SETUP_BASIC_TEST(registry entt/entity/registry.cpp) SETUP_BASIC_TEST(registry entt/entity/registry.cpp)
SETUP_BASIC_TEST(runtime_view entt/entity/runtime_view.cpp) SETUP_BASIC_TEST(runtime_view entt/entity/runtime_view.cpp)
SETUP_BASIC_TEST(sigh_mixin entt/entity/sigh_mixin.cpp)
SETUP_BASIC_TEST(snapshot entt/entity/snapshot.cpp) SETUP_BASIC_TEST(snapshot entt/entity/snapshot.cpp)
SETUP_BASIC_TEST(sparse_set entt/entity/sparse_set.cpp) SETUP_BASIC_TEST(sparse_set entt/entity/sparse_set.cpp)
SETUP_BASIC_TEST(storage entt/entity/storage.cpp) SETUP_BASIC_TEST(storage entt/entity/storage.cpp)
SETUP_BASIC_TEST(storage_entity entt/entity/storage_entity.cpp) SETUP_BASIC_TEST(storage_mixin entt/entity/storage_mixin.cpp)
SETUP_BASIC_TEST(view entt/entity/view.cpp) SETUP_BASIC_TEST(view entt/entity/view.cpp)
# Test graph # Test graph

View File

@ -19,9 +19,7 @@ struct stable_position: position {
}; };
template<auto> template<auto>
struct comp { struct comp { int x; };
int x;
};
struct timer final { struct timer final {
timer() timer()
@ -36,24 +34,16 @@ private:
std::chrono::time_point<std::chrono::system_clock> start; std::chrono::time_point<std::chrono::system_clock> start;
}; };
template<typename Func, typename... Args>
void generic_with(Func func) {
timer timer;
func();
timer.elapsed();
}
template<typename Iterable, typename Func> template<typename Iterable, typename Func>
void iterate_with(Iterable &&iterable, Func func) { void generic(Iterable &&iterable, Func func) {
timer timer; timer timer;
std::forward<Iterable>(iterable).each(func); std::forward<Iterable>(iterable).each(func);
timer.elapsed(); timer.elapsed();
} }
template<typename Func> template<typename Func>
void pathological_with(Func func) { void pathological(Func func) {
entt::registry registry; entt::registry registry;
auto view = func(registry);
for(std::uint64_t i = 0; i < 500000L; i++) { for(std::uint64_t i = 0; i < 500000L; i++) {
const auto entity = registry.create(); const auto entity = registry.create();
@ -64,21 +54,10 @@ void pathological_with(Func func) {
for(auto i = 0; i < 10; ++i) { for(auto i = 0; i < 10; ++i) {
registry.each([i = 0, &registry](const auto entity) mutable { registry.each([i = 0, &registry](const auto entity) mutable {
if(!(++i % 7)) { if(!(++i % 7)) { registry.remove<position>(entity); }
registry.remove<position>(entity); if(!(++i % 11)) { registry.remove<velocity>(entity); }
} if(!(++i % 13)) { registry.remove<comp<0>>(entity); }
if(!(++i % 17)) { registry.destroy(entity); }
if(!(++i % 11)) {
registry.remove<velocity>(entity);
}
if(!(++i % 13)) {
registry.remove<comp<0>>(entity);
}
if(!(++i % 17)) {
registry.destroy(entity);
}
}); });
for(std::uint64_t j = 0; j < 50000L; j++) { for(std::uint64_t j = 0; j < 50000L; j++) {
@ -90,7 +69,7 @@ void pathological_with(Func func) {
} }
timer timer; timer timer;
view.each([](auto &...comp) { ((comp.x = {}), ...); }); func(registry).each([](auto &...comp) { ((comp.x = {}), ...); });
timer.elapsed(); timer.elapsed();
} }
@ -99,11 +78,13 @@ TEST(Benchmark, Create) {
std::cout << "Creating 1000000 entities" << std::endl; std::cout << "Creating 1000000 entities" << std::endl;
generic_with([&]() { timer timer;
for(std::uint64_t i = 0; i < 1000000L; i++) { for(std::uint64_t i = 0; i < 1000000L; i++) {
static_cast<void>(registry.create()); static_cast<void>(registry.create());
} }
});
timer.elapsed();
} }
TEST(Benchmark, CreateMany) { TEST(Benchmark, CreateMany) {
@ -112,9 +93,9 @@ TEST(Benchmark, CreateMany) {
std::cout << "Creating 1000000 entities at once" << std::endl; std::cout << "Creating 1000000 entities at once" << std::endl;
generic_with([&]() { timer timer;
registry.create(entities.begin(), entities.end()); registry.create(entities.begin(), entities.end());
}); timer.elapsed();
} }
TEST(Benchmark, CreateManyAndEmplaceComponents) { TEST(Benchmark, CreateManyAndEmplaceComponents) {
@ -123,14 +104,15 @@ TEST(Benchmark, CreateManyAndEmplaceComponents) {
std::cout << "Creating 1000000 entities at once and emplace components" << std::endl; std::cout << "Creating 1000000 entities at once and emplace components" << std::endl;
generic_with([&]() { timer timer;
registry.create(entities.begin(), entities.end()); registry.create(entities.begin(), entities.end());
for(const auto entity: entities) { for(const auto entity: entities) {
registry.emplace<position>(entity); registry.emplace<position>(entity);
registry.emplace<velocity>(entity); registry.emplace<velocity>(entity);
} }
});
timer.elapsed();
} }
TEST(Benchmark, CreateManyWithComponents) { TEST(Benchmark, CreateManyWithComponents) {
@ -139,107 +121,79 @@ TEST(Benchmark, CreateManyWithComponents) {
std::cout << "Creating 1000000 entities at once with components" << std::endl; std::cout << "Creating 1000000 entities at once with components" << std::endl;
generic_with([&]() { timer timer;
registry.create(entities.begin(), entities.end()); registry.create(entities.begin(), entities.end());
registry.insert<position>(entities.begin(), entities.end()); registry.insert<position>(entities.begin(), entities.end());
registry.insert<velocity>(entities.begin(), entities.end()); registry.insert<velocity>(entities.begin(), entities.end());
}); timer.elapsed();
} }
TEST(Benchmark, Erase) { TEST(Benchmark, Erase) {
entt::registry registry; entt::registry registry;
std::vector<entt::entity> entities(1000000); std::vector<entt::entity> entities(1000000);
auto view = registry.view<position>(); auto view = registry.view<int>();
std::cout << "Erasing 1000000 components from their entities" << std::endl; std::cout << "Erasing 1000000 components from their entities" << std::endl;
registry.create(entities.begin(), entities.end()); registry.create(entities.begin(), entities.end());
registry.insert<position>(entities.begin(), entities.end()); registry.insert<int>(entities.begin(), entities.end());
timer timer;
generic_with([&]() {
for(auto entity: view) { for(auto entity: view) {
registry.erase<position>(entity); registry.erase<int>(entity);
} }
});
timer.elapsed();
} }
TEST(Benchmark, EraseMany) { TEST(Benchmark, EraseMany) {
entt::registry registry; entt::registry registry;
std::vector<entt::entity> entities(1000000); std::vector<entt::entity> entities(1000000);
auto view = registry.view<position>(); auto view = registry.view<int>();
std::cout << "Erasing 1000000 components from their entities at once" << std::endl; std::cout << "Erasing 1000000 components from their entities at once" << std::endl;
registry.create(entities.begin(), entities.end()); registry.create(entities.begin(), entities.end());
registry.insert<position>(entities.begin(), entities.end()); registry.insert<int>(entities.begin(), entities.end());
generic_with([&]() { timer timer;
registry.erase<position>(view.begin(), view.end()); registry.erase<int>(view.begin(), view.end());
}); timer.elapsed();
}
TEST(Benchmark, EraseManyMulti) {
entt::registry registry;
std::vector<entt::entity> entities(1000000);
auto view = registry.view<position>();
std::cout << "Erasing 1000000 components per type from their entities at once" << std::endl;
registry.create(entities.begin(), entities.end());
registry.insert<position>(entities.begin(), entities.end());
registry.insert<velocity>(entities.begin(), entities.end());
generic_with([&]() {
registry.erase<position, velocity>(view.begin(), view.end());
});
} }
TEST(Benchmark, Remove) { TEST(Benchmark, Remove) {
entt::registry registry; entt::registry registry;
std::vector<entt::entity> entities(1000000); std::vector<entt::entity> entities(1000000);
auto view = registry.view<position>(); auto view = registry.view<int>();
std::cout << "Removing 1000000 components from their entities" << std::endl; std::cout << "Removing 1000000 components from their entities" << std::endl;
registry.create(entities.begin(), entities.end()); registry.create(entities.begin(), entities.end());
registry.insert<position>(entities.begin(), entities.end()); registry.insert<int>(entities.begin(), entities.end());
timer timer;
generic_with([&]() {
for(auto entity: view) { for(auto entity: view) {
registry.remove<position>(entity); registry.remove<int>(entity);
} }
});
timer.elapsed();
} }
TEST(Benchmark, RemoveMany) { TEST(Benchmark, RemoveMany) {
entt::registry registry; entt::registry registry;
std::vector<entt::entity> entities(1000000); std::vector<entt::entity> entities(1000000);
auto view = registry.view<position>(); auto view = registry.view<int>();
std::cout << "Removing 1000000 components from their entities at once" << std::endl; std::cout << "Removing 1000000 components from their entities at once" << std::endl;
registry.create(entities.begin(), entities.end()); registry.create(entities.begin(), entities.end());
registry.insert<position>(entities.begin(), entities.end()); registry.insert<int>(entities.begin(), entities.end());
generic_with([&]() { timer timer;
registry.remove<position>(view.begin(), view.end()); registry.remove<int>(view.begin(), view.end());
}); timer.elapsed();
}
TEST(Benchmark, RemoveManyMulti) {
entt::registry registry;
std::vector<entt::entity> entities(1000000);
auto view = registry.view<position>();
std::cout << "Removing 1000000 components per type from their entities at once" << std::endl;
registry.create(entities.begin(), entities.end());
registry.insert<position>(entities.begin(), entities.end());
registry.insert<velocity>(entities.begin(), entities.end());
generic_with([&]() {
registry.remove<position, velocity>(view.begin(), view.end());
});
} }
TEST(Benchmark, Clear) { TEST(Benchmark, Clear) {
@ -249,40 +203,11 @@ TEST(Benchmark, Clear) {
std::cout << "Clearing 1000000 components from their entities" << std::endl; std::cout << "Clearing 1000000 components from their entities" << std::endl;
registry.create(entities.begin(), entities.end()); registry.create(entities.begin(), entities.end());
registry.insert<position>(entities.begin(), entities.end()); registry.insert<int>(entities.begin(), entities.end());
generic_with([&]() { timer timer;
registry.clear<position>(); registry.clear<int>();
}); timer.elapsed();
}
TEST(Benchmark, ClearMulti) {
entt::registry registry;
std::vector<entt::entity> entities(1000000);
std::cout << "Clearing 1000000 components per type from their entities" << std::endl;
registry.create(entities.begin(), entities.end());
registry.insert<position>(entities.begin(), entities.end());
registry.insert<velocity>(entities.begin(), entities.end());
generic_with([&]() {
registry.clear<position, velocity>();
});
}
TEST(Benchmark, ClearStable) {
entt::registry registry;
std::vector<entt::entity> entities(1000000);
std::cout << "Clearing 1000000 stable components from their entities" << std::endl;
registry.create(entities.begin(), entities.end());
registry.insert<stable_position>(entities.begin(), entities.end());
generic_with([&]() {
registry.clear<stable_position>();
});
} }
TEST(Benchmark, Recycle) { TEST(Benchmark, Recycle) {
@ -292,13 +217,18 @@ TEST(Benchmark, Recycle) {
std::cout << "Recycling 1000000 entities" << std::endl; std::cout << "Recycling 1000000 entities" << std::endl;
registry.create(entities.begin(), entities.end()); registry.create(entities.begin(), entities.end());
registry.destroy(entities.begin(), entities.end());
generic_with([&]() { registry.each([&registry](auto entity) {
for(auto next = entities.size(); next; --next) { registry.destroy(entity);
entities[next] = registry.create();
}
}); });
timer timer;
for(auto next = entities.size(); next; --next) {
static_cast<void>(registry.create());
}
timer.elapsed();
} }
TEST(Benchmark, RecycleMany) { TEST(Benchmark, RecycleMany) {
@ -308,121 +238,62 @@ TEST(Benchmark, RecycleMany) {
std::cout << "Recycling 1000000 entities" << std::endl; std::cout << "Recycling 1000000 entities" << std::endl;
registry.create(entities.begin(), entities.end()); registry.create(entities.begin(), entities.end());
registry.destroy(entities.begin(), entities.end());
generic_with([&]() { registry.each([&registry](auto entity) {
registry.create(entities.begin(), entities.end()); registry.destroy(entity);
}); });
timer timer;
registry.create(entities.begin(), entities.end());
timer.elapsed();
} }
TEST(Benchmark, Destroy) { TEST(Benchmark, Destroy) {
entt::registry registry; entt::registry registry;
std::vector<entt::entity> entities(1000000); std::vector<entt::entity> entities(1000000);
auto view = registry.view<position>(); auto view = registry.view<int>();
std::cout << "Destroying 1000000 entities" << std::endl; std::cout << "Destroying 1000000 entities" << std::endl;
registry.create(entities.begin(), entities.end()); registry.create(entities.begin(), entities.end());
registry.insert<position>(entities.begin(), entities.end()); registry.insert<int>(entities.begin(), entities.end());
timer timer;
generic_with([&]() {
for(auto entity: view) { for(auto entity: view) {
registry.destroy(entity); registry.destroy(entity);
} }
});
timer.elapsed();
} }
TEST(Benchmark, DestroyMany) { TEST(Benchmark, DestroyMany) {
entt::registry registry; entt::registry registry;
std::vector<entt::entity> entities(1000000); std::vector<entt::entity> entities(1000000);
auto view = registry.view<position>(); auto view = registry.view<int>();
std::cout << "Destroying 1000000 entities at once" << std::endl; std::cout << "Destroying 1000000 entities at once" << std::endl;
registry.create(entities.begin(), entities.end()); registry.create(entities.begin(), entities.end());
registry.insert<position>(entities.begin(), entities.end()); registry.insert<int>(entities.begin(), entities.end());
generic_with([&]() { timer timer;
registry.destroy(view.begin(), view.end()); registry.destroy(view.begin(), view.end());
}); timer.elapsed();
} }
TEST(Benchmark, DestroyManyMulti) { TEST(Benchmark, DestroyManyFastPath) {
entt::registry registry;
std::vector<entt::entity> entities(1000000);
auto view = registry.view<position>();
std::cout << "Destroying 1000000 entities at once, multiple components" << std::endl;
registry.create(entities.begin(), entities.end());
registry.insert<position>(entities.begin(), entities.end());
registry.insert<velocity>(entities.begin(), entities.end());
generic_with([&]() {
registry.destroy(view.begin(), view.end());
});
}
TEST(Benchmark, GetFromRegistry) {
entt::registry registry; entt::registry registry;
std::vector<entt::entity> entities(1000000); std::vector<entt::entity> entities(1000000);
registry.create(entities.begin(), entities.end()); std::cout << "Destroying 1000000 entities at once, fast path" << std::endl;
registry.insert<position>(entities.begin(), entities.end());
generic_with([&]() {
for(auto entity: entities) {
registry.get<position>(entity).x = 0u;
}
});
}
TEST(Benchmark, GetFromRegistryMulti) {
entt::registry registry;
std::vector<entt::entity> entities(1000000);
registry.create(entities.begin(), entities.end()); registry.create(entities.begin(), entities.end());
registry.insert<position>(entities.begin(), entities.end()); registry.insert<int>(entities.begin(), entities.end());
registry.insert<velocity>(entities.begin(), entities.end());
generic_with([&]() { timer timer;
for(auto entity: entities) { registry.destroy(entities.begin(), entities.end());
registry.get<position>(entity).x = 0u; timer.elapsed();
registry.get<velocity>(entity).y = 0u;
}
});
}
TEST(Benchmark, GetFromView) {
entt::registry registry;
std::vector<entt::entity> entities(1000000);
auto view = registry.view<position>();
registry.create(entities.begin(), entities.end());
registry.insert<position>(entities.begin(), entities.end());
generic_with([&]() {
for(auto entity: entities) {
view.get<position>(entity).x = 0u;
}
});
}
TEST(Benchmark, GetFromViewMulti) {
entt::registry registry;
std::vector<entt::entity> entities(1000000);
auto view = registry.view<position, velocity>();
registry.create(entities.begin(), entities.end());
registry.insert<position>(entities.begin(), entities.end());
registry.insert<velocity>(entities.begin(), entities.end());
generic_with([&]() {
for(auto entity: entities) {
view.get<position>(entity).x = 0u;
view.get<velocity>(entity).y = 0u;
}
});
} }
TEST(Benchmark, IterateSingleComponent1M) { TEST(Benchmark, IterateSingleComponent1M) {
@ -435,22 +306,22 @@ TEST(Benchmark, IterateSingleComponent1M) {
registry.emplace<position>(entity); registry.emplace<position>(entity);
} }
iterate_with(registry.view<position>(), [](auto &...comp) { generic(registry.view<position>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
TEST(Benchmark, IterateSingleStableComponent1M) { TEST(Benchmark, IterateSingleComponentTombstonePolicy1M) {
entt::registry registry; entt::registry registry;
std::cout << "Iterating over 1000000 entities, one stable component" << std::endl; std::cout << "Iterating over 1000000 entities, one component, tombstone policy" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) { for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create(); const auto entity = registry.create();
registry.emplace<stable_position>(entity); registry.emplace<stable_position>(entity);
} }
iterate_with(registry.view<stable_position>(), [](auto &...comp) { generic(registry.view<stable_position>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -468,7 +339,7 @@ TEST(Benchmark, IterateSingleComponentRuntime1M) {
entt::runtime_view view{}; entt::runtime_view view{};
view.iterate(registry.storage<position>()); view.iterate(registry.storage<position>());
iterate_with(view, [&](auto entity) { generic(view, [&registry](auto entity) {
registry.get<position>(entity).x = {}; registry.get<position>(entity).x = {};
}); });
} }
@ -484,15 +355,15 @@ TEST(Benchmark, IterateTwoComponents1M) {
registry.emplace<velocity>(entity); registry.emplace<velocity>(entity);
} }
iterate_with(registry.view<position, velocity>(), [](auto &...comp) { generic(registry.view<position, velocity>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
TEST(Benchmark, IterateTwoStableComponents1M) { TEST(Benchmark, IterateTombstonePolicyTwoComponentsTombstonePolicy1M) {
entt::registry registry; entt::registry registry;
std::cout << "Iterating over 1000000 entities, two stable components" << std::endl; std::cout << "Iterating over 1000000 entities, two components, tombstone policy" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) { for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create(); const auto entity = registry.create();
@ -500,7 +371,7 @@ TEST(Benchmark, IterateTwoStableComponents1M) {
registry.emplace<velocity>(entity); registry.emplace<velocity>(entity);
} }
iterate_with(registry.view<stable_position, velocity>(), [](auto &...comp) { generic(registry.view<stable_position, velocity>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -519,7 +390,7 @@ TEST(Benchmark, IterateTwoComponents1MHalf) {
} }
} }
iterate_with(registry.view<position, velocity>(), [](auto &...comp) { generic(registry.view<position, velocity>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -538,7 +409,7 @@ TEST(Benchmark, IterateTwoComponents1MOne) {
} }
} }
iterate_with(registry.view<position, velocity>(), [](auto &...comp) { generic(registry.view<position, velocity>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -554,7 +425,7 @@ TEST(Benchmark, IterateTwoComponentsNonOwningGroup1M) {
registry.emplace<velocity>(entity); registry.emplace<velocity>(entity);
} }
iterate_with(registry.group<>(entt::get<position, velocity>), [](auto &...comp) { generic(registry.group<>(entt::get<position, velocity>), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -570,7 +441,7 @@ TEST(Benchmark, IterateTwoComponentsFullOwningGroup1M) {
registry.emplace<velocity>(entity); registry.emplace<velocity>(entity);
} }
iterate_with(registry.group<position, velocity>(), [](auto &...comp) { generic(registry.group<position, velocity>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -586,7 +457,7 @@ TEST(Benchmark, IterateTwoComponentsPartialOwningGroup1M) {
registry.emplace<velocity>(entity); registry.emplace<velocity>(entity);
} }
iterate_with(registry.group<position>(entt::get<velocity>), [](auto &...comp) { generic(registry.group<position>(entt::get<velocity>), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -606,7 +477,7 @@ TEST(Benchmark, IterateTwoComponentsRuntime1M) {
view.iterate(registry.storage<position>()) view.iterate(registry.storage<position>())
.iterate(registry.storage<velocity>()); .iterate(registry.storage<velocity>());
iterate_with(view, [&](auto entity) { generic(view, [&registry](auto entity) {
registry.get<position>(entity).x = {}; registry.get<position>(entity).x = {};
registry.get<velocity>(entity).x = {}; registry.get<velocity>(entity).x = {};
}); });
@ -630,7 +501,7 @@ TEST(Benchmark, IterateTwoComponentsRuntime1MHalf) {
view.iterate(registry.storage<position>()) view.iterate(registry.storage<position>())
.iterate(registry.storage<velocity>()); .iterate(registry.storage<velocity>());
iterate_with(view, [&](auto entity) { generic(view, [&registry](auto entity) {
registry.get<position>(entity).x = {}; registry.get<position>(entity).x = {};
registry.get<velocity>(entity).x = {}; registry.get<velocity>(entity).x = {};
}); });
@ -654,7 +525,7 @@ TEST(Benchmark, IterateTwoComponentsRuntime1MOne) {
view.iterate(registry.storage<position>()) view.iterate(registry.storage<position>())
.iterate(registry.storage<velocity>()); .iterate(registry.storage<velocity>());
iterate_with(view, [&](auto entity) { generic(view, [&registry](auto entity) {
registry.get<position>(entity).x = {}; registry.get<position>(entity).x = {};
registry.get<velocity>(entity).x = {}; registry.get<velocity>(entity).x = {};
}); });
@ -672,15 +543,15 @@ TEST(Benchmark, IterateThreeComponents1M) {
registry.emplace<comp<0>>(entity); registry.emplace<comp<0>>(entity);
} }
iterate_with(registry.view<position, velocity, comp<0>>(), [](auto &...comp) { generic(registry.view<position, velocity, comp<0>>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
TEST(Benchmark, IterateThreeStableComponents1M) { TEST(Benchmark, IterateThreeComponentsTombstonePolicy1M) {
entt::registry registry; entt::registry registry;
std::cout << "Iterating over 1000000 entities, three stable components" << std::endl; std::cout << "Iterating over 1000000 entities, three components, tombstone policy" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) { for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create(); const auto entity = registry.create();
@ -689,7 +560,7 @@ TEST(Benchmark, IterateThreeStableComponents1M) {
registry.emplace<comp<0>>(entity); registry.emplace<comp<0>>(entity);
} }
iterate_with(registry.view<stable_position, velocity, comp<0>>(), [](auto &...comp) { generic(registry.view<stable_position, velocity, comp<0>>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -709,7 +580,7 @@ TEST(Benchmark, IterateThreeComponents1MHalf) {
} }
} }
iterate_with(registry.view<position, velocity, comp<0>>(), [](auto &...comp) { generic(registry.view<position, velocity, comp<0>>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -729,7 +600,7 @@ TEST(Benchmark, IterateThreeComponents1MOne) {
} }
} }
iterate_with(registry.view<position, velocity, comp<0>>(), [](auto &...comp) { generic(registry.view<position, velocity, comp<0>>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -746,7 +617,7 @@ TEST(Benchmark, IterateThreeComponentsNonOwningGroup1M) {
registry.emplace<comp<0>>(entity); registry.emplace<comp<0>>(entity);
} }
iterate_with(registry.group<>(entt::get<position, velocity, comp<0>>), [](auto &...comp) { generic(registry.group<>(entt::get<position, velocity, comp<0>>), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -763,7 +634,7 @@ TEST(Benchmark, IterateThreeComponentsFullOwningGroup1M) {
registry.emplace<comp<0>>(entity); registry.emplace<comp<0>>(entity);
} }
iterate_with(registry.group<position, velocity, comp<0>>(), [](auto &...comp) { generic(registry.group<position, velocity, comp<0>>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -780,7 +651,7 @@ TEST(Benchmark, IterateThreeComponentsPartialOwningGroup1M) {
registry.emplace<comp<0>>(entity); registry.emplace<comp<0>>(entity);
} }
iterate_with(registry.group<position, velocity>(entt::get<comp<0>>), [](auto &...comp) { generic(registry.group<position, velocity>(entt::get<comp<0>>), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -802,7 +673,7 @@ TEST(Benchmark, IterateThreeComponentsRuntime1M) {
.iterate(registry.storage<velocity>()) .iterate(registry.storage<velocity>())
.iterate(registry.storage<comp<0>>()); .iterate(registry.storage<comp<0>>());
iterate_with(view, [&](auto entity) { generic(view, [&registry](auto entity) {
registry.get<position>(entity).x = {}; registry.get<position>(entity).x = {};
registry.get<velocity>(entity).x = {}; registry.get<velocity>(entity).x = {};
registry.get<comp<0>>(entity).x = {}; registry.get<comp<0>>(entity).x = {};
@ -829,7 +700,7 @@ TEST(Benchmark, IterateThreeComponentsRuntime1MHalf) {
.iterate(registry.storage<velocity>()) .iterate(registry.storage<velocity>())
.iterate(registry.storage<comp<0>>()); .iterate(registry.storage<comp<0>>());
iterate_with(view, [&](auto entity) { generic(view, [&registry](auto entity) {
registry.get<position>(entity).x = {}; registry.get<position>(entity).x = {};
registry.get<velocity>(entity).x = {}; registry.get<velocity>(entity).x = {};
registry.get<comp<0>>(entity).x = {}; registry.get<comp<0>>(entity).x = {};
@ -856,7 +727,7 @@ TEST(Benchmark, IterateThreeComponentsRuntime1MOne) {
.iterate(registry.storage<velocity>()) .iterate(registry.storage<velocity>())
.iterate(registry.storage<comp<0>>()); .iterate(registry.storage<comp<0>>());
iterate_with(view, [&](auto entity) { generic(view, [&registry](auto entity) {
registry.get<position>(entity).x = {}; registry.get<position>(entity).x = {};
registry.get<velocity>(entity).x = {}; registry.get<velocity>(entity).x = {};
registry.get<comp<0>>(entity).x = {}; registry.get<comp<0>>(entity).x = {};
@ -877,15 +748,15 @@ TEST(Benchmark, IterateFiveComponents1M) {
registry.emplace<comp<2>>(entity); registry.emplace<comp<2>>(entity);
} }
iterate_with(registry.view<position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) { generic(registry.view<position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
TEST(Benchmark, IterateFiveStableComponents1M) { TEST(Benchmark, IterateFiveComponentsTombstonePolicy1M) {
entt::registry registry; entt::registry registry;
std::cout << "Iterating over 1000000 entities, five stable components" << std::endl; std::cout << "Iterating over 1000000 entities, five components, tombstone policy" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) { for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create(); const auto entity = registry.create();
@ -896,7 +767,7 @@ TEST(Benchmark, IterateFiveStableComponents1M) {
registry.emplace<comp<2>>(entity); registry.emplace<comp<2>>(entity);
} }
iterate_with(registry.view<stable_position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) { generic(registry.view<stable_position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -918,7 +789,7 @@ TEST(Benchmark, IterateFiveComponents1MHalf) {
} }
} }
iterate_with(registry.view<position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) { generic(registry.view<position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -940,7 +811,7 @@ TEST(Benchmark, IterateFiveComponents1MOne) {
} }
} }
iterate_with(registry.view<position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) { generic(registry.view<position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -959,7 +830,7 @@ TEST(Benchmark, IterateFiveComponentsNonOwningGroup1M) {
registry.emplace<comp<2>>(entity); registry.emplace<comp<2>>(entity);
} }
iterate_with(registry.group<>(entt::get<position, velocity, comp<0>, comp<1>, comp<2>>), [](auto &...comp) { generic(registry.group<>(entt::get<position, velocity, comp<0>, comp<1>, comp<2>>), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -978,7 +849,7 @@ TEST(Benchmark, IterateFiveComponentsFullOwningGroup1M) {
registry.emplace<comp<2>>(entity); registry.emplace<comp<2>>(entity);
} }
iterate_with(registry.group<position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) { generic(registry.group<position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -997,7 +868,7 @@ TEST(Benchmark, IterateFiveComponentsPartialFourOfFiveOwningGroup1M) {
registry.emplace<comp<2>>(entity); registry.emplace<comp<2>>(entity);
} }
iterate_with(registry.group<position, velocity, comp<0>, comp<1>>(entt::get<comp<2>>), [](auto &...comp) { generic(registry.group<position, velocity, comp<0>, comp<1>>(entt::get<comp<2>>), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -1016,7 +887,7 @@ TEST(Benchmark, IterateFiveComponentsPartialThreeOfFiveOwningGroup1M) {
registry.emplace<comp<2>>(entity); registry.emplace<comp<2>>(entity);
} }
iterate_with(registry.group<position, velocity, comp<0>>(entt::get<comp<1>, comp<2>>), [](auto &...comp) { generic(registry.group<position, velocity, comp<0>>(entt::get<comp<1>, comp<2>>), [](auto &...comp) {
((comp.x = {}), ...); ((comp.x = {}), ...);
}); });
} }
@ -1042,7 +913,7 @@ TEST(Benchmark, IterateFiveComponentsRuntime1M) {
.iterate(registry.storage<comp<1>>()) .iterate(registry.storage<comp<1>>())
.iterate(registry.storage<comp<2>>()); .iterate(registry.storage<comp<2>>());
iterate_with(view, [&](auto entity) { generic(view, [&registry](auto entity) {
registry.get<position>(entity).x = {}; registry.get<position>(entity).x = {};
registry.get<velocity>(entity).x = {}; registry.get<velocity>(entity).x = {};
registry.get<comp<0>>(entity).x = {}; registry.get<comp<0>>(entity).x = {};
@ -1075,7 +946,7 @@ TEST(Benchmark, IterateFiveComponentsRuntime1MHalf) {
.iterate(registry.storage<comp<1>>()) .iterate(registry.storage<comp<1>>())
.iterate(registry.storage<comp<2>>()); .iterate(registry.storage<comp<2>>());
iterate_with(view, [&](auto entity) { generic(view, [&registry](auto entity) {
registry.get<position>(entity).x = {}; registry.get<position>(entity).x = {};
registry.get<velocity>(entity).x = {}; registry.get<velocity>(entity).x = {};
registry.get<comp<0>>(entity).x = {}; registry.get<comp<0>>(entity).x = {};
@ -1108,7 +979,7 @@ TEST(Benchmark, IterateFiveComponentsRuntime1MOne) {
.iterate(registry.storage<comp<1>>()) .iterate(registry.storage<comp<1>>())
.iterate(registry.storage<comp<2>>()); .iterate(registry.storage<comp<2>>());
iterate_with(view, [&](auto entity) { generic(view, [&registry](auto entity) {
registry.get<position>(entity).x = {}; registry.get<position>(entity).x = {};
registry.get<velocity>(entity).x = {}; registry.get<velocity>(entity).x = {};
registry.get<comp<0>>(entity).x = {}; registry.get<comp<0>>(entity).x = {};
@ -1119,22 +990,22 @@ TEST(Benchmark, IterateFiveComponentsRuntime1MOne) {
TEST(Benchmark, IteratePathological) { TEST(Benchmark, IteratePathological) {
std::cout << "Pathological case" << std::endl; std::cout << "Pathological case" << std::endl;
pathological_with([](auto &registry) { return registry.template view<position, velocity, comp<0>>(); }); pathological([](auto &registry) { return registry.template view<position, velocity, comp<0>>(); });
} }
TEST(Benchmark, IteratePathologicalNonOwningGroup) { TEST(Benchmark, IteratePathologicalNonOwningGroup) {
std::cout << "Pathological case (non-owning group)" << std::endl; std::cout << "Pathological case (non-owning group)" << std::endl;
pathological_with([](auto &registry) { return registry.template group<>(entt::get<position, velocity, comp<0>>); }); pathological([](auto &registry) { return registry.template group<>(entt::get<position, velocity, comp<0>>); });
} }
TEST(Benchmark, IteratePathologicalFullOwningGroup) { TEST(Benchmark, IteratePathologicalFullOwningGroup) {
std::cout << "Pathological case (full-owning group)" << std::endl; std::cout << "Pathological case (full-owning group)" << std::endl;
pathological_with([](auto &registry) { return registry.template group<position, velocity, comp<0>>(); }); pathological([](auto &registry) { return registry.template group<position, velocity, comp<0>>(); });
} }
TEST(Benchmark, IteratePathologicalPartialOwningGroup) { TEST(Benchmark, IteratePathologicalPartialOwningGroup) {
std::cout << "Pathological case (partial-owning group)" << std::endl; std::cout << "Pathological case (partial-owning group)" << std::endl;
pathological_with([](auto &registry) { return registry.template group<position, velocity>(entt::get<comp<0>>); }); pathological([](auto &registry) { return registry.template group<position, velocity>(entt::get<comp<0>>); });
} }
TEST(Benchmark, SortSingle) { TEST(Benchmark, SortSingle) {
@ -1147,9 +1018,9 @@ TEST(Benchmark, SortSingle) {
registry.emplace<position>(entity, i, i); registry.emplace<position>(entity, i, i);
} }
generic_with([&]() { timer timer;
registry.sort<position>([](const auto &lhs, const auto &rhs) { return lhs.x < rhs.x && lhs.y < rhs.y; }); registry.sort<position>([](const auto &lhs, const auto &rhs) { return lhs.x < rhs.x && lhs.y < rhs.y; });
}); timer.elapsed();
} }
TEST(Benchmark, SortMulti) { TEST(Benchmark, SortMulti) {
@ -1165,9 +1036,9 @@ TEST(Benchmark, SortMulti) {
registry.sort<position>([](const auto &lhs, const auto &rhs) { return lhs.x < rhs.x && lhs.y < rhs.y; }); registry.sort<position>([](const auto &lhs, const auto &rhs) { return lhs.x < rhs.x && lhs.y < rhs.y; });
generic_with([&]() { timer timer;
registry.sort<velocity, position>(); registry.sort<velocity, position>();
}); timer.elapsed();
} }
TEST(Benchmark, AlmostSortedStdSort) { TEST(Benchmark, AlmostSortedStdSort) {
@ -1191,9 +1062,9 @@ TEST(Benchmark, AlmostSortedStdSort) {
registry.emplace<position>(entity, 50000 * i, 50000 * i); registry.emplace<position>(entity, 50000 * i, 50000 * i);
} }
generic_with([&]() { timer timer;
registry.sort<position>([](const auto &lhs, const auto &rhs) { return lhs.x > rhs.x && lhs.y > rhs.y; }); registry.sort<position>([](const auto &lhs, const auto &rhs) { return lhs.x > rhs.x && lhs.y > rhs.y; });
}); timer.elapsed();
} }
TEST(Benchmark, AlmostSortedInsertionSort) { TEST(Benchmark, AlmostSortedInsertionSort) {
@ -1217,7 +1088,7 @@ TEST(Benchmark, AlmostSortedInsertionSort) {
registry.emplace<position>(entity, 50000 * i, 50000 * i); registry.emplace<position>(entity, 50000 * i, 50000 * i);
} }
generic_with([&]() { timer timer;
registry.sort<position>([](const auto &lhs, const auto &rhs) { return lhs.x > rhs.x && lhs.y > rhs.y; }, entt::insertion_sort{}); registry.sort<position>([](const auto &lhs, const auto &rhs) { return lhs.x > rhs.x && lhs.y > rhs.y; }, entt::insertion_sort{});
}); timer.elapsed();
} }

View File

@ -22,7 +22,7 @@ struct basic_test_allocator: std::allocator<Type> {
return *this; return *this;
} }
bool operator==(const basic_test_allocator &other) const { bool operator==(const basic_test_allocator &other) {
return (this == &other); return (this == &other);
} }
}; };

View File

@ -25,14 +25,14 @@ public:
using propagate_on_container_swap = std::true_type; using propagate_on_container_swap = std::true_type;
using exception_type = test_exception; using exception_type = test_exception;
template<typename Other> template<class Other>
struct rebind { struct rebind {
using other = throwing_allocator<Other>; using other = throwing_allocator<Other>;
}; };
throwing_allocator() = default; throwing_allocator() = default;
template<typename Other> template<class Other>
throwing_allocator(const throwing_allocator<Other> &other) throwing_allocator(const throwing_allocator<Other> &other)
: base{other} {} : base{other} {}

View File

@ -100,7 +100,7 @@ TEST(DenseMap, Functionalities) {
} }
TEST(DenseMap, Constructors) { TEST(DenseMap, Constructors) {
constexpr std::size_t minimum_bucket_count = 8u; static constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<int, int> map; entt::dense_map<int, int> map;
ASSERT_EQ(map.bucket_count(), minimum_bucket_count); ASSERT_EQ(map.bucket_count(), minimum_bucket_count);
@ -395,7 +395,7 @@ TEST(DenseMap, Insert) {
} }
TEST(DenseMap, InsertRehash) { TEST(DenseMap, InsertRehash) {
constexpr std::size_t minimum_bucket_count = 8u; static constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map; entt::dense_map<std::size_t, std::size_t, entt::identity> map;
ASSERT_EQ(map.size(), 0u); ASSERT_EQ(map.size(), 0u);
@ -429,7 +429,7 @@ TEST(DenseMap, InsertRehash) {
} }
TEST(DenseMap, InsertSameBucket) { TEST(DenseMap, InsertSameBucket) {
constexpr std::size_t minimum_bucket_count = 8u; static constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map; entt::dense_map<std::size_t, std::size_t, entt::identity> map;
for(std::size_t next{}; next < minimum_bucket_count; ++next) { for(std::size_t next{}; next < minimum_bucket_count; ++next) {
@ -598,7 +598,7 @@ TEST(DenseMap, Emplace) {
} }
TEST(DenseMap, EmplaceRehash) { TEST(DenseMap, EmplaceRehash) {
constexpr std::size_t minimum_bucket_count = 8u; static constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map; entt::dense_map<std::size_t, std::size_t, entt::identity> map;
ASSERT_EQ(map.size(), 0u); ASSERT_EQ(map.size(), 0u);
@ -633,7 +633,7 @@ TEST(DenseMap, EmplaceRehash) {
} }
TEST(DenseMap, EmplaceSameBucket) { TEST(DenseMap, EmplaceSameBucket) {
constexpr std::size_t minimum_bucket_count = 8u; static constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map; entt::dense_map<std::size_t, std::size_t, entt::identity> map;
for(std::size_t next{}; next < minimum_bucket_count; ++next) { for(std::size_t next{}; next < minimum_bucket_count; ++next) {
@ -681,7 +681,7 @@ TEST(DenseMap, TryEmplace) {
} }
TEST(DenseMap, TryEmplaceRehash) { TEST(DenseMap, TryEmplaceRehash) {
constexpr std::size_t minimum_bucket_count = 8u; static constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map; entt::dense_map<std::size_t, std::size_t, entt::identity> map;
ASSERT_EQ(map.size(), 0u); ASSERT_EQ(map.size(), 0u);
@ -715,7 +715,7 @@ TEST(DenseMap, TryEmplaceRehash) {
} }
TEST(DenseMap, TryEmplaceSameBucket) { TEST(DenseMap, TryEmplaceSameBucket) {
constexpr std::size_t minimum_bucket_count = 8u; static constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map; entt::dense_map<std::size_t, std::size_t, entt::identity> map;
for(std::size_t next{}; next < minimum_bucket_count; ++next) { for(std::size_t next{}; next < minimum_bucket_count; ++next) {
@ -749,7 +749,7 @@ TEST(DenseMap, TryEmplaceMovableType) {
} }
TEST(DenseMap, Erase) { TEST(DenseMap, Erase) {
constexpr std::size_t minimum_bucket_count = 8u; static constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map; entt::dense_map<std::size_t, std::size_t, entt::identity> map;
for(std::size_t next{}, last = minimum_bucket_count + 1u; next < last; ++next) { for(std::size_t next{}, last = minimum_bucket_count + 1u; next < last; ++next) {
@ -799,7 +799,7 @@ TEST(DenseMap, Erase) {
} }
TEST(DenseMap, EraseWithMovableKeyValue) { TEST(DenseMap, EraseWithMovableKeyValue) {
constexpr std::size_t minimum_bucket_count = 8u; static constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::string, std::size_t> map; entt::dense_map<std::string, std::size_t> map;
map.emplace("0", 0u); map.emplace("0", 0u);
@ -817,7 +817,7 @@ TEST(DenseMap, EraseWithMovableKeyValue) {
} }
TEST(DenseMap, EraseFromBucket) { TEST(DenseMap, EraseFromBucket) {
constexpr std::size_t minimum_bucket_count = 8u; static constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map; entt::dense_map<std::size_t, std::size_t, entt::identity> map;
ASSERT_EQ(map.bucket_count(), minimum_bucket_count); ASSERT_EQ(map.bucket_count(), minimum_bucket_count);
@ -995,7 +995,7 @@ TEST(DenseMap, LocalIterator) {
static_assert(std::is_same_v<iterator::pointer, entt::input_iterator_pointer<std::pair<const std::size_t &, std::size_t &>>>); static_assert(std::is_same_v<iterator::pointer, entt::input_iterator_pointer<std::pair<const std::size_t &, std::size_t &>>>);
static_assert(std::is_same_v<iterator::reference, std::pair<const std::size_t &, std::size_t &>>); static_assert(std::is_same_v<iterator::reference, std::pair<const std::size_t &, std::size_t &>>);
constexpr std::size_t minimum_bucket_count = 8u; static constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map; entt::dense_map<std::size_t, std::size_t, entt::identity> map;
map.emplace(3u, 42u); map.emplace(3u, 42u);
map.emplace(3u + minimum_bucket_count, 99u); map.emplace(3u + minimum_bucket_count, 99u);
@ -1023,7 +1023,7 @@ TEST(DenseMap, ConstLocalIterator) {
static_assert(std::is_same_v<iterator::pointer, entt::input_iterator_pointer<std::pair<const std::size_t &, const std::size_t &>>>); static_assert(std::is_same_v<iterator::pointer, entt::input_iterator_pointer<std::pair<const std::size_t &, const std::size_t &>>>);
static_assert(std::is_same_v<iterator::reference, std::pair<const std::size_t &, const std::size_t &>>); static_assert(std::is_same_v<iterator::reference, std::pair<const std::size_t &, const std::size_t &>>);
constexpr std::size_t minimum_bucket_count = 8u; static constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map; entt::dense_map<std::size_t, std::size_t, entt::identity> map;
map.emplace(3u, 42u); map.emplace(3u, 42u);
map.emplace(3u + minimum_bucket_count, 99u); map.emplace(3u + minimum_bucket_count, 99u);
@ -1064,7 +1064,7 @@ TEST(DenseMap, LocalIteratorConversion) {
} }
TEST(DenseMap, Rehash) { TEST(DenseMap, Rehash) {
constexpr std::size_t minimum_bucket_count = 8u; static constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map; entt::dense_map<std::size_t, std::size_t, entt::identity> map;
map[32u] = 99u; map[32u] = 99u;
@ -1147,7 +1147,7 @@ TEST(DenseMap, Rehash) {
} }
TEST(DenseMap, Reserve) { TEST(DenseMap, Reserve) {
constexpr std::size_t minimum_bucket_count = 8u; static constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<int, int> map; entt::dense_map<int, int> map;
ASSERT_EQ(map.bucket_count(), minimum_bucket_count); ASSERT_EQ(map.bucket_count(), minimum_bucket_count);
@ -1159,7 +1159,7 @@ TEST(DenseMap, Reserve) {
map.reserve(minimum_bucket_count); map.reserve(minimum_bucket_count);
ASSERT_EQ(map.bucket_count(), 2 * minimum_bucket_count); ASSERT_EQ(map.bucket_count(), 2 * minimum_bucket_count);
ASSERT_EQ(map.bucket_count(), entt::next_power_of_two(static_cast<std::size_t>(std::ceil(minimum_bucket_count / map.max_load_factor())))); ASSERT_EQ(map.bucket_count(), entt::next_power_of_two(std::ceil(minimum_bucket_count / map.max_load_factor())));
} }
TEST(DenseMap, ThrowingAllocator) { TEST(DenseMap, ThrowingAllocator) {
@ -1167,7 +1167,7 @@ TEST(DenseMap, ThrowingAllocator) {
using packed_allocator = test::throwing_allocator<entt::internal::dense_map_node<std::size_t, std::size_t>>; using packed_allocator = test::throwing_allocator<entt::internal::dense_map_node<std::size_t, std::size_t>>;
using packed_exception = typename packed_allocator::exception_type; using packed_exception = typename packed_allocator::exception_type;
constexpr std::size_t minimum_bucket_count = 8u; static constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, std::hash<std::size_t>, std::equal_to<std::size_t>, allocator> map{}; entt::dense_map<std::size_t, std::size_t, std::hash<std::size_t>, std::equal_to<std::size_t>, allocator> map{};
packed_allocator::trigger_on_allocate = true; packed_allocator::trigger_on_allocate = true;

View File

@ -98,7 +98,7 @@ TEST(DenseSet, Functionalities) {
} }
TEST(DenseSet, Constructors) { TEST(DenseSet, Constructors) {
constexpr std::size_t minimum_bucket_count = 8u; static constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<int> set; entt::dense_set<int> set;
ASSERT_EQ(set.bucket_count(), minimum_bucket_count); ASSERT_EQ(set.bucket_count(), minimum_bucket_count);
@ -357,7 +357,7 @@ TEST(DenseSet, Insert) {
} }
TEST(DenseSet, InsertRehash) { TEST(DenseSet, InsertRehash) {
constexpr std::size_t minimum_bucket_count = 8u; static constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::size_t, entt::identity> set; entt::dense_set<std::size_t, entt::identity> set;
ASSERT_EQ(set.size(), 0u); ASSERT_EQ(set.size(), 0u);
@ -388,7 +388,7 @@ TEST(DenseSet, InsertRehash) {
} }
TEST(DenseSet, InsertSameBucket) { TEST(DenseSet, InsertSameBucket) {
constexpr std::size_t minimum_bucket_count = 8u; static constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::size_t, entt::identity> set; entt::dense_set<std::size_t, entt::identity> set;
for(std::size_t next{}; next < minimum_bucket_count; ++next) { for(std::size_t next{}; next < minimum_bucket_count; ++next) {
@ -451,7 +451,7 @@ TEST(DenseSet, Emplace) {
} }
TEST(DenseSet, EmplaceRehash) { TEST(DenseSet, EmplaceRehash) {
constexpr std::size_t minimum_bucket_count = 8u; static constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::size_t, entt::identity> set; entt::dense_set<std::size_t, entt::identity> set;
ASSERT_EQ(set.size(), 0u); ASSERT_EQ(set.size(), 0u);
@ -483,7 +483,7 @@ TEST(DenseSet, EmplaceRehash) {
} }
TEST(DenseSet, EmplaceSameBucket) { TEST(DenseSet, EmplaceSameBucket) {
constexpr std::size_t minimum_bucket_count = 8u; static constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::size_t, entt::identity> set; entt::dense_set<std::size_t, entt::identity> set;
for(std::size_t next{}; next < minimum_bucket_count; ++next) { for(std::size_t next{}; next < minimum_bucket_count; ++next) {
@ -503,7 +503,7 @@ TEST(DenseSet, EmplaceSameBucket) {
} }
TEST(DenseSet, Erase) { TEST(DenseSet, Erase) {
constexpr std::size_t minimum_bucket_count = 8u; static constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::size_t, entt::identity> set; entt::dense_set<std::size_t, entt::identity> set;
for(std::size_t next{}, last = minimum_bucket_count + 1u; next < last; ++next) { for(std::size_t next{}, last = minimum_bucket_count + 1u; next < last; ++next) {
@ -553,7 +553,7 @@ TEST(DenseSet, Erase) {
} }
TEST(DenseSet, EraseWithMovableKeyValue) { TEST(DenseSet, EraseWithMovableKeyValue) {
constexpr std::size_t minimum_bucket_count = 8u; static constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::string> set; entt::dense_set<std::string> set;
set.emplace("0"); set.emplace("0");
@ -570,7 +570,7 @@ TEST(DenseSet, EraseWithMovableKeyValue) {
} }
TEST(DenseSet, EraseFromBucket) { TEST(DenseSet, EraseFromBucket) {
constexpr std::size_t minimum_bucket_count = 8u; static constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::size_t, entt::identity> set; entt::dense_set<std::size_t, entt::identity> set;
ASSERT_EQ(set.bucket_count(), minimum_bucket_count); ASSERT_EQ(set.bucket_count(), minimum_bucket_count);
@ -721,7 +721,7 @@ TEST(DenseSet, LocalIterator) {
static_assert(std::is_same_v<iterator::pointer, const std::size_t *>); static_assert(std::is_same_v<iterator::pointer, const std::size_t *>);
static_assert(std::is_same_v<iterator::reference, const std::size_t &>); static_assert(std::is_same_v<iterator::reference, const std::size_t &>);
constexpr std::size_t minimum_bucket_count = 8u; static constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::size_t, entt::identity> set; entt::dense_set<std::size_t, entt::identity> set;
set.emplace(3u); set.emplace(3u);
set.emplace(3u + minimum_bucket_count); set.emplace(3u + minimum_bucket_count);
@ -749,7 +749,7 @@ TEST(DenseSet, ConstLocalIterator) {
static_assert(std::is_same_v<iterator::pointer, const std::size_t *>); static_assert(std::is_same_v<iterator::pointer, const std::size_t *>);
static_assert(std::is_same_v<iterator::reference, const std::size_t &>); static_assert(std::is_same_v<iterator::reference, const std::size_t &>);
constexpr std::size_t minimum_bucket_count = 8u; static constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::size_t, entt::identity> set; entt::dense_set<std::size_t, entt::identity> set;
set.emplace(3u); set.emplace(3u);
set.emplace(3u + minimum_bucket_count); set.emplace(3u + minimum_bucket_count);
@ -790,7 +790,7 @@ TEST(DenseSet, LocalIteratorConversion) {
} }
TEST(DenseSet, Rehash) { TEST(DenseSet, Rehash) {
constexpr std::size_t minimum_bucket_count = 8u; static constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::size_t, entt::identity> set; entt::dense_set<std::size_t, entt::identity> set;
set.emplace(32u); set.emplace(32u);
@ -865,7 +865,7 @@ TEST(DenseSet, Rehash) {
} }
TEST(DenseSet, Reserve) { TEST(DenseSet, Reserve) {
constexpr std::size_t minimum_bucket_count = 8u; static constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<int> set; entt::dense_set<int> set;
ASSERT_EQ(set.bucket_count(), minimum_bucket_count); ASSERT_EQ(set.bucket_count(), minimum_bucket_count);
@ -877,7 +877,7 @@ TEST(DenseSet, Reserve) {
set.reserve(minimum_bucket_count); set.reserve(minimum_bucket_count);
ASSERT_EQ(set.bucket_count(), 2 * minimum_bucket_count); ASSERT_EQ(set.bucket_count(), 2 * minimum_bucket_count);
ASSERT_EQ(set.bucket_count(), entt::next_power_of_two(static_cast<std::size_t>(std::ceil(minimum_bucket_count / set.max_load_factor())))); ASSERT_EQ(set.bucket_count(), entt::next_power_of_two(std::ceil(minimum_bucket_count / set.max_load_factor())));
} }
TEST(DenseSet, ThrowingAllocator) { TEST(DenseSet, ThrowingAllocator) {
@ -885,7 +885,7 @@ TEST(DenseSet, ThrowingAllocator) {
using packed_allocator = test::throwing_allocator<std::pair<std::size_t, std::size_t>>; using packed_allocator = test::throwing_allocator<std::pair<std::size_t, std::size_t>>;
using packed_exception = typename packed_allocator::exception_type; using packed_exception = typename packed_allocator::exception_type;
constexpr std::size_t minimum_bucket_count = 8u; static constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::size_t, std::hash<std::size_t>, std::equal_to<std::size_t>, allocator> set{}; entt::dense_set<std::size_t, std::hash<std::size_t>, std::equal_to<std::size_t>, allocator> set{};
packed_allocator::trigger_on_allocate = true; packed_allocator::trigger_on_allocate = true;

View File

@ -2,7 +2,6 @@
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include <iterator> #include <iterator>
#include <memory>
#include <type_traits> #include <type_traits>
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>
@ -40,9 +39,21 @@ struct not_comparable {
bool operator==(const not_comparable &) const = delete; bool operator==(const not_comparable &) const = delete;
}; };
struct not_copyable {
not_copyable()
: payload{} {}
not_copyable(const not_copyable &) = delete;
not_copyable(not_copyable &&) = default;
not_copyable &operator=(const not_copyable &) = delete;
not_copyable &operator=(not_copyable &&) = default;
double payload;
};
struct not_movable { struct not_movable {
not_movable() = default; not_movable() = default;
not_movable(const not_movable &) = default; not_movable(const not_movable &) = default;
not_movable(not_movable &&) = delete; not_movable(not_movable &&) = delete;
@ -1172,12 +1183,13 @@ TEST_F(Any, AnyCast) {
ASSERT_EQ(entt::any_cast<int &>(any), 42); ASSERT_EQ(entt::any_cast<int &>(any), 42);
ASSERT_EQ(entt::any_cast<const int &>(cany), 42); ASSERT_EQ(entt::any_cast<const int &>(cany), 42);
auto instance = std::make_unique<double>(42.); not_copyable instance{};
instance.payload = 42.;
entt::any ref{entt::forward_as_any(instance)}; entt::any ref{entt::forward_as_any(instance)};
entt::any cref{entt::forward_as_any(std::as_const(*instance))}; entt::any cref{entt::forward_as_any(std::as_const(instance).payload)};
ASSERT_EQ(entt::any_cast<not_copyable>(std::move(ref)).payload, 42.);
ASSERT_EQ(entt::any_cast<double>(std::move(cref)), 42.); ASSERT_EQ(entt::any_cast<double>(std::move(cref)), 42.);
ASSERT_EQ(*entt::any_cast<std::unique_ptr<double>>(std::move(ref)), 42.);
ASSERT_EQ(entt::any_cast<int>(entt::any{42}), 42); ASSERT_EQ(entt::any_cast<int>(entt::any{42}), 42);
} }
@ -1185,15 +1197,16 @@ ENTT_DEBUG_TEST_F(AnyDeathTest, AnyCast) {
entt::any any{42}; entt::any any{42};
const auto &cany = any; const auto &cany = any;
ASSERT_DEATH([[maybe_unused]] auto &elem = entt::any_cast<double &>(any), ""); ASSERT_DEATH(entt::any_cast<double &>(any), "");
ASSERT_DEATH([[maybe_unused]] const auto &elem = entt::any_cast<const double &>(cany), ""); ASSERT_DEATH(entt::any_cast<const double &>(cany), "");
auto instance = std::make_unique<double>(42.); not_copyable instance{};
instance.payload = 42.;
entt::any ref{entt::forward_as_any(instance)}; entt::any ref{entt::forward_as_any(instance)};
entt::any cref{entt::forward_as_any(std::as_const(*instance))}; entt::any cref{entt::forward_as_any(std::as_const(instance).payload)};
ASSERT_DEATH([[maybe_unused]] auto elem = entt::any_cast<std::unique_ptr<double>>(std::as_const(ref).as_ref()), ""); ASSERT_DEATH(entt::any_cast<not_copyable>(std::as_const(ref).as_ref()), "");
ASSERT_DEATH([[maybe_unused]] auto elem = entt::any_cast<double>(entt::any{42}), ""); ASSERT_DEATH(entt::any_cast<double>(entt::any{42}), "");
} }
TEST_F(Any, MakeAny) { TEST_F(Any, MakeAny) {
@ -1250,8 +1263,8 @@ TEST_F(Any, ForwardAsAny) {
} }
TEST_F(Any, NotCopyableType) { TEST_F(Any, NotCopyableType) {
const std::unique_ptr<int> value{}; const not_copyable value{};
entt::any any{std::in_place_type<std::unique_ptr<int>>}; entt::any any{std::in_place_type<not_copyable>};
entt::any other = entt::forward_as_any(value); entt::any other = entt::forward_as_any(value);
ASSERT_TRUE(any); ASSERT_TRUE(any);
@ -1283,7 +1296,7 @@ TEST_F(Any, NotCopyableType) {
TEST_F(Any, NotCopyableValueType) { TEST_F(Any, NotCopyableValueType) {
std::vector<entt::any> vec{}; std::vector<entt::any> vec{};
vec.emplace_back(std::in_place_type<std::unique_ptr<int>>); vec.emplace_back(std::in_place_type<not_copyable>);
vec.shrink_to_fit(); vec.shrink_to_fit();
ASSERT_EQ(vec.size(), 1u); ASSERT_EQ(vec.size(), 1u);
@ -1291,7 +1304,7 @@ TEST_F(Any, NotCopyableValueType) {
ASSERT_TRUE(vec[0u]); ASSERT_TRUE(vec[0u]);
// strong exception guarantee due to noexcept move ctor // strong exception guarantee due to noexcept move ctor
vec.emplace_back(std::in_place_type<std::unique_ptr<int>>); vec.emplace_back(std::in_place_type<not_copyable>);
ASSERT_EQ(vec.size(), 2u); ASSERT_EQ(vec.size(), 2u);
ASSERT_TRUE(vec[0u]); ASSERT_TRUE(vec[0u]);
@ -1398,7 +1411,7 @@ TEST_F(Any, SBOVsZeroedSBOSize) {
} }
TEST_F(Any, SboAlignment) { TEST_F(Any, SboAlignment) {
constexpr auto alignment = alignof(over_aligned); static constexpr auto alignment = alignof(over_aligned);
entt::basic_any<alignment, alignment> sbo[2] = {over_aligned{}, over_aligned{}}; entt::basic_any<alignment, alignment> sbo[2] = {over_aligned{}, over_aligned{}};
const auto *data = sbo[0].data(); const auto *data = sbo[0].data();
@ -1414,7 +1427,7 @@ TEST_F(Any, SboAlignment) {
} }
TEST_F(Any, NoSboAlignment) { TEST_F(Any, NoSboAlignment) {
constexpr auto alignment = alignof(over_aligned); static constexpr auto alignment = alignof(over_aligned);
entt::basic_any<alignment> nosbo[2] = {over_aligned{}, over_aligned{}}; entt::basic_any<alignment> nosbo[2] = {over_aligned{}, over_aligned{}};
const auto *data = nosbo[0].data(); const auto *data = nosbo[0].data();

View File

@ -6,19 +6,19 @@
#include <entt/core/hashed_string.hpp> #include <entt/core/hashed_string.hpp>
template<typename> template<typename>
struct expected; struct foobar_t;
template<> template<>
struct expected<std::uint32_t> { struct foobar_t<std::uint32_t> {
static constexpr auto value = 0xbf9cf968; static constexpr auto value = 0xbf9cf968;
}; };
template<> template<>
struct expected<std::uint64_t> { struct foobar_t<std::uint64_t> {
static constexpr auto value = 0x85944171f73967e8; static constexpr auto value = 0x85944171f73967e8;
}; };
inline constexpr auto expected_v = expected<entt::id_type>::value; inline constexpr auto foobar_v = foobar_t<entt::id_type>::value;
TEST(BasicHashedString, DeductionGuide) { TEST(BasicHashedString, DeductionGuide) {
static_assert(std::is_same_v<decltype(entt::basic_hashed_string{"foo"}), entt::hashed_string>); static_assert(std::is_same_v<decltype(entt::basic_hashed_string{"foo"}), entt::hashed_string>);
@ -47,8 +47,8 @@ TEST(HashedString, Functionalities) {
entt::hashed_string hs{"foobar"}; entt::hashed_string hs{"foobar"};
ASSERT_EQ(static_cast<hash_type>(hs), expected_v); ASSERT_EQ(static_cast<hash_type>(hs), foobar_v);
ASSERT_EQ(hs.value(), expected_v); ASSERT_EQ(hs.value(), foobar_v);
ASSERT_EQ(foo_hs, "foo"_hs); ASSERT_EQ(foo_hs, "foo"_hs);
ASSERT_NE(bar_hs, "foo"_hs); ASSERT_NE(bar_hs, "foo"_hs);
@ -78,13 +78,13 @@ TEST(HashedString, Correctness) {
const char *foobar = "foobar"; const char *foobar = "foobar";
std::string_view view{"foobar__", 6}; std::string_view view{"foobar__", 6};
ASSERT_EQ(entt::hashed_string{foobar}, expected_v); ASSERT_EQ(entt::hashed_string{foobar}, foobar_v);
ASSERT_EQ((entt::hashed_string{view.data(), view.size()}), expected_v); ASSERT_EQ((entt::hashed_string{view.data(), view.size()}), foobar_v);
ASSERT_EQ(entt::hashed_string{"foobar"}, expected_v); ASSERT_EQ(entt::hashed_string{"foobar"}, foobar_v);
ASSERT_EQ(entt::hashed_string::value(foobar), expected_v); ASSERT_EQ(entt::hashed_string::value(foobar), foobar_v);
ASSERT_EQ(entt::hashed_string::value(view.data(), view.size()), expected_v); ASSERT_EQ(entt::hashed_string::value(view.data(), view.size()), foobar_v);
ASSERT_EQ(entt::hashed_string::value("foobar"), expected_v); ASSERT_EQ(entt::hashed_string::value("foobar"), foobar_v);
ASSERT_EQ(entt::hashed_string{foobar}.size(), 6u); ASSERT_EQ(entt::hashed_string{foobar}.size(), 6u);
ASSERT_EQ((entt::hashed_string{view.data(), view.size()}).size(), 6u); ASSERT_EQ((entt::hashed_string{view.data(), view.size()}).size(), 6u);
@ -111,16 +111,16 @@ TEST(HashedString, Constexprness) {
constexpr std::string_view view{"foobar__", 6}; constexpr std::string_view view{"foobar__", 6};
static_assert(entt::hashed_string{"quux"} == "quux"_hs); static_assert(entt::hashed_string{"quux"} == "quux"_hs);
static_assert(entt::hashed_string{"foobar"} == expected_v); static_assert(entt::hashed_string{"foobar"} == foobar_v);
static_assert(entt::hashed_string::value("quux") == "quux"_hs); static_assert(entt::hashed_string::value("quux") == "quux"_hs);
static_assert(entt::hashed_string::value("foobar") == expected_v); static_assert(entt::hashed_string::value("foobar") == foobar_v);
static_assert(entt::hashed_string{"quux", 4} == "quux"_hs); static_assert(entt::hashed_string{"quux", 4} == "quux"_hs);
static_assert(entt::hashed_string{view.data(), view.size()} == expected_v); static_assert(entt::hashed_string{view.data(), view.size()} == foobar_v);
static_assert(entt::hashed_string::value("quux", 4) == "quux"_hs); static_assert(entt::hashed_string::value("quux", 4) == "quux"_hs);
static_assert(entt::hashed_string::value(view.data(), view.size()) == expected_v); static_assert(entt::hashed_string::value(view.data(), view.size()) == foobar_v);
static_assert(entt::hashed_string{"bar"} < "foo"_hs); static_assert(entt::hashed_string{"bar"} < "foo"_hs);
static_assert(entt::hashed_string{"bar"} <= "bar"_hs); static_assert(entt::hashed_string{"bar"} <= "bar"_hs);
@ -151,8 +151,8 @@ TEST(HashedWString, Functionalities) {
entt::hashed_wstring hws{L"foobar"}; entt::hashed_wstring hws{L"foobar"};
ASSERT_EQ(static_cast<hash_type>(hws), expected_v); ASSERT_EQ(static_cast<hash_type>(hws), foobar_v);
ASSERT_EQ(hws.value(), expected_v); ASSERT_EQ(hws.value(), foobar_v);
ASSERT_EQ(foo_hws, L"foo"_hws); ASSERT_EQ(foo_hws, L"foo"_hws);
ASSERT_NE(bar_hws, L"foo"_hws); ASSERT_NE(bar_hws, L"foo"_hws);
@ -172,13 +172,13 @@ TEST(HashedWString, Correctness) {
const wchar_t *foobar = L"foobar"; const wchar_t *foobar = L"foobar";
std::wstring_view view{L"foobar__", 6}; std::wstring_view view{L"foobar__", 6};
ASSERT_EQ(entt::hashed_wstring{foobar}, expected_v); ASSERT_EQ(entt::hashed_wstring{foobar}, foobar_v);
ASSERT_EQ((entt::hashed_wstring{view.data(), view.size()}), expected_v); ASSERT_EQ((entt::hashed_wstring{view.data(), view.size()}), foobar_v);
ASSERT_EQ(entt::hashed_wstring{L"foobar"}, expected_v); ASSERT_EQ(entt::hashed_wstring{L"foobar"}, foobar_v);
ASSERT_EQ(entt::hashed_wstring::value(foobar), expected_v); ASSERT_EQ(entt::hashed_wstring::value(foobar), foobar_v);
ASSERT_EQ(entt::hashed_wstring::value(view.data(), view.size()), expected_v); ASSERT_EQ(entt::hashed_wstring::value(view.data(), view.size()), foobar_v);
ASSERT_EQ(entt::hashed_wstring::value(L"foobar"), expected_v); ASSERT_EQ(entt::hashed_wstring::value(L"foobar"), foobar_v);
ASSERT_EQ(entt::hashed_wstring{foobar}.size(), 6u); ASSERT_EQ(entt::hashed_wstring{foobar}.size(), 6u);
ASSERT_EQ((entt::hashed_wstring{view.data(), view.size()}).size(), 6u); ASSERT_EQ((entt::hashed_wstring{view.data(), view.size()}).size(), 6u);
@ -205,16 +205,16 @@ TEST(HashedWString, Constexprness) {
constexpr std::wstring_view view{L"foobar__", 6}; constexpr std::wstring_view view{L"foobar__", 6};
static_assert(entt::hashed_wstring{L"quux"} == L"quux"_hws); static_assert(entt::hashed_wstring{L"quux"} == L"quux"_hws);
static_assert(entt::hashed_wstring{L"foobar"} == expected_v); static_assert(entt::hashed_wstring{L"foobar"} == foobar_v);
static_assert(entt::hashed_wstring::value(L"quux") == L"quux"_hws); static_assert(entt::hashed_wstring::value(L"quux") == L"quux"_hws);
static_assert(entt::hashed_wstring::value(L"foobar") == expected_v); static_assert(entt::hashed_wstring::value(L"foobar") == foobar_v);
static_assert(entt::hashed_wstring{L"quux", 4} == L"quux"_hws); static_assert(entt::hashed_wstring{L"quux", 4} == L"quux"_hws);
static_assert(entt::hashed_wstring{view.data(), view.size()} == expected_v); static_assert(entt::hashed_wstring{view.data(), view.size()} == foobar_v);
static_assert(entt::hashed_wstring::value(L"quux", 4) == L"quux"_hws); static_assert(entt::hashed_wstring::value(L"quux", 4) == L"quux"_hws);
static_assert(entt::hashed_wstring::value(view.data(), view.size()) == expected_v); static_assert(entt::hashed_wstring::value(view.data(), view.size()) == foobar_v);
static_assert(entt::hashed_wstring{L"bar"} < L"foo"_hws); static_assert(entt::hashed_wstring{L"bar"} < L"foo"_hws);
static_assert(entt::hashed_wstring{L"bar"} <= L"bar"_hws); static_assert(entt::hashed_wstring{L"bar"} <= L"bar"_hws);

View File

@ -24,12 +24,6 @@ TEST(ToAddress, Functionalities) {
TEST(PoccaPocmaAndPocs, Functionalities) { TEST(PoccaPocmaAndPocs, Functionalities) {
test::basic_test_allocator<int> lhs, rhs; test::basic_test_allocator<int> lhs, rhs;
test::basic_test_allocator<int, std::false_type> no_pocs;
// code coverage purposes
ASSERT_FALSE(lhs == rhs);
ASSERT_NO_FATAL_FAILURE(entt::propagate_on_container_swap(no_pocs, no_pocs));
// honestly, I don't even know how one is supposed to test such a thing :) // honestly, I don't even know how one is supposed to test such a thing :)
entt::propagate_on_container_copy_assignment(lhs, rhs); entt::propagate_on_container_copy_assignment(lhs, rhs);
entt::propagate_on_container_move_assignment(lhs, rhs); entt::propagate_on_container_move_assignment(lhs, rhs);
@ -37,8 +31,8 @@ TEST(PoccaPocmaAndPocs, Functionalities) {
} }
ENTT_DEBUG_TEST(PoccaPocmaAndPocsDeathTest, Functionalities) { ENTT_DEBUG_TEST(PoccaPocmaAndPocsDeathTest, Functionalities) {
test::basic_test_allocator<int, std::false_type> lhs, rhs; using pocs = std::false_type;
test::basic_test_allocator<int, pocs> lhs, rhs;
ASSERT_DEATH(entt::propagate_on_container_swap(lhs, rhs), ""); ASSERT_DEATH(entt::propagate_on_container_swap(lhs, rhs), "");
} }
@ -66,8 +60,8 @@ TEST(NextPowerOfTwo, Functionalities) {
ASSERT_EQ(entt::next_power_of_two(17u), 32u); ASSERT_EQ(entt::next_power_of_two(17u), 32u);
ASSERT_EQ(entt::next_power_of_two(32u), 32u); ASSERT_EQ(entt::next_power_of_two(32u), 32u);
ASSERT_EQ(entt::next_power_of_two(33u), 64u); ASSERT_EQ(entt::next_power_of_two(33u), 64u);
ASSERT_EQ(entt::next_power_of_two(static_cast<std::size_t>(std::pow(2, 16))), static_cast<std::size_t>(std::pow(2, 16))); ASSERT_EQ(entt::next_power_of_two(std::pow(2, 16)), std::pow(2, 16));
ASSERT_EQ(entt::next_power_of_two(static_cast<std::size_t>(std::pow(2, 16) + 1u)), static_cast<std::size_t>(std::pow(2, 17))); ASSERT_EQ(entt::next_power_of_two(std::pow(2, 16) + 1u), std::pow(2, 17));
} }
ENTT_DEBUG_TEST(NextPowerOfTwoDeathTest, Functionalities) { ENTT_DEBUG_TEST(NextPowerOfTwoDeathTest, Functionalities) {

View File

@ -34,5 +34,5 @@ TEST(Tuple, UnwrapTuple) {
TEST(Tuple, ForwardApply) { TEST(Tuple, ForwardApply) {
ASSERT_EQ(entt::forward_apply{[](auto &&...args) { return sizeof...(args); }}(std::make_tuple()), 0u); ASSERT_EQ(entt::forward_apply{[](auto &&...args) { return sizeof...(args); }}(std::make_tuple()), 0u);
ASSERT_EQ(entt::forward_apply{[](int i) { return i; }}(std::make_tuple(42)), 42); ASSERT_EQ(entt::forward_apply{[](int i) { return i; }}(std::make_tuple(42)), 42);
ASSERT_EQ(entt::forward_apply{[](auto... args) { return (args + ...); }}(std::make_tuple('a', 1)), 'b'); ASSERT_EQ(entt::forward_apply{[](auto... args) { return (args + ...); }}(std::make_tuple('a', 1u)), 'b');
} }

View File

@ -89,7 +89,7 @@ TEST(TypeList, Functionalities) {
static_assert(std::is_same_v<entt::type_list_cat_t<type, other, type, other>, entt::type_list<int, char, double, int, char, double>>); static_assert(std::is_same_v<entt::type_list_cat_t<type, other, type, other>, entt::type_list<int, char, double, int, char, double>>);
static_assert(std::is_same_v<entt::type_list_cat_t<type, other>, entt::type_list<int, char, double>>); static_assert(std::is_same_v<entt::type_list_cat_t<type, other>, entt::type_list<int, char, double>>);
static_assert(std::is_same_v<entt::type_list_cat_t<type, type>, entt::type_list<int, char, int, char>>); static_assert(std::is_same_v<entt::type_list_cat_t<type, type>, entt::type_list<int, char, int, char>>);
static_assert(std::is_same_v<entt::type_list_unique_t<entt::type_list_cat_t<type, type>>, type>); static_assert(std::is_same_v<entt::type_list_unique_t<entt::type_list_cat_t<type, type>>, entt::type_list<int, char>>);
static_assert(entt::type_list_contains_v<type, int>); static_assert(entt::type_list_contains_v<type, int>);
static_assert(entt::type_list_contains_v<type, char>); static_assert(entt::type_list_contains_v<type, char>);
@ -112,14 +112,6 @@ TEST(TypeList, Functionalities) {
static_assert(std::is_same_v<entt::type_list_transform_t<entt::type_list<int, char>, entt::type_identity>, entt::type_list<int, char>>); static_assert(std::is_same_v<entt::type_list_transform_t<entt::type_list<int, char>, entt::type_identity>, entt::type_list<int, char>>);
static_assert(std::is_same_v<entt::type_list_transform_t<entt::type_list<int, char>, std::add_const>, entt::type_list<const int, const char>>); static_assert(std::is_same_v<entt::type_list_transform_t<entt::type_list<int, char>, std::add_const>, entt::type_list<const int, const char>>);
static_assert(std::is_same_v<entt::type_list_transform_t<entt::type_list<int, char>, multi_argument_operation>, entt::type_list<void, void>>); static_assert(std::is_same_v<entt::type_list_transform_t<entt::type_list<int, char>, multi_argument_operation>, entt::type_list<void, void>>);
static_assert(std::tuple_size_v<entt::type_list<>> == 0u);
static_assert(std::tuple_size_v<entt::type_list<int>> == 1u);
static_assert(std::tuple_size_v<entt::type_list<int, float>> == 2u);
static_assert(std::is_same_v<int, std::tuple_element_t<0, entt::type_list<int>>>);
static_assert(std::is_same_v<int, std::tuple_element_t<0, entt::type_list<int, float>>>);
static_assert(std::is_same_v<float, std::tuple_element_t<1, entt::type_list<int, float>>>);
} }
TEST(ValueList, Functionalities) { TEST(ValueList, Functionalities) {
@ -133,33 +125,10 @@ TEST(ValueList, Functionalities) {
static_assert(std::is_same_v<entt::value_list_cat_t<value, other, value, other>, entt::value_list<0, 2, 1, 0, 2, 1>>); static_assert(std::is_same_v<entt::value_list_cat_t<value, other, value, other>, entt::value_list<0, 2, 1, 0, 2, 1>>);
static_assert(std::is_same_v<entt::value_list_cat_t<value, other>, entt::value_list<0, 2, 1>>); static_assert(std::is_same_v<entt::value_list_cat_t<value, other>, entt::value_list<0, 2, 1>>);
static_assert(std::is_same_v<entt::value_list_cat_t<value, value>, entt::value_list<0, 2, 0, 2>>); static_assert(std::is_same_v<entt::value_list_cat_t<value, value>, entt::value_list<0, 2, 0, 2>>);
static_assert(std::is_same_v<entt::value_list_unique_t<entt::value_list_cat_t<value, value>>, value>);
static_assert(entt::value_list_contains_v<value, 0>);
static_assert(entt::value_list_contains_v<value, 2>);
static_assert(!entt::value_list_contains_v<value, 1>);
static_assert(entt::value_list_element_v<0u, value> == 0); static_assert(entt::value_list_element_v<0u, value> == 0);
static_assert(entt::value_list_element_v<1u, value> == 2); static_assert(entt::value_list_element_v<1u, value> == 2);
static_assert(entt::value_list_element_v<0u, other> == 1); static_assert(entt::value_list_element_v<0u, other> == 1);
static_assert(entt::value_list_index_v<0, value> == 0u);
static_assert(entt::value_list_index_v<2, value> == 1u);
static_assert(entt::value_list_index_v<1, other> == 0u);
static_assert(std::is_same_v<entt::value_list_diff_t<entt::value_list<0, 1, 2>, entt::value_list<3, 4>>, entt::value_list<0, 1, 2>>);
static_assert(std::is_same_v<entt::value_list_diff_t<entt::value_list<0, 1, 2>, entt::value_list<0, 1, 2>>, entt::value_list<>>);
static_assert(std::is_same_v<entt::value_list_diff_t<entt::value_list<0, 1, 2>, entt::value_list<0, 1>>, entt::value_list<2>>);
static_assert(std::is_same_v<entt::value_list_diff_t<entt::value_list<0, 1, 2>, entt::value_list<1, 2>>, entt::value_list<0>>);
static_assert(std::is_same_v<entt::value_list_diff_t<entt::value_list<0, 1, 2>, entt::value_list<1>>, entt::value_list<0, 2>>);
static_assert(std::tuple_size_v<entt::value_list<>> == 0u);
static_assert(std::tuple_size_v<entt::value_list<42>> == 1u);
static_assert(std::tuple_size_v<entt::value_list<42, 'a'>> == 2u);
static_assert(std::is_same_v<int, std::tuple_element_t<0, entt::value_list<42>>>);
static_assert(std::is_same_v<int, std::tuple_element_t<0, entt::value_list<42, 'a'>>>);
static_assert(std::is_same_v<char, std::tuple_element_t<1, entt::value_list<42, 'a'>>>);
} }
TEST(IsApplicable, Functionalities) { TEST(IsApplicable, Functionalities) {
@ -214,7 +183,6 @@ TEST(IsEqualityComparable, Functionalities) {
static_assert(entt::is_equality_comparable_v<std::vector<not_comparable>::iterator>); static_assert(entt::is_equality_comparable_v<std::vector<not_comparable>::iterator>);
static_assert(entt::is_equality_comparable_v<nlohmann_json_like>); static_assert(entt::is_equality_comparable_v<nlohmann_json_like>);
static_assert(!entt::is_equality_comparable_v<int[3u]>);
static_assert(!entt::is_equality_comparable_v<not_comparable>); static_assert(!entt::is_equality_comparable_v<not_comparable>);
static_assert(!entt::is_equality_comparable_v<const not_comparable>); static_assert(!entt::is_equality_comparable_v<const not_comparable>);
static_assert(!entt::is_equality_comparable_v<std::vector<not_comparable>>); static_assert(!entt::is_equality_comparable_v<std::vector<not_comparable>>);

View File

@ -30,43 +30,50 @@ struct entt::component_traits<traits_based> {
}; };
TEST(Component, VoidType) { TEST(Component, VoidType) {
using traits_type = entt::component_traits<void>; using traits = entt::component_traits<void>;
static_assert(!traits_type::in_place_delete); static_assert(traits::in_place_delete);
static_assert(traits_type::page_size == 0u); static_assert(entt::ignore_as_empty_v<typename traits::type>);
// we don't really care about this thanks to ignore_as_empty_v
static_assert(traits::page_size != 0u);
} }
TEST(Component, Empty) { TEST(Component, Empty) {
using traits_type = entt::component_traits<empty>; using traits = entt::component_traits<empty>;
static_assert(!traits_type::in_place_delete); static_assert(!traits::in_place_delete);
static_assert(traits_type::page_size == 0u); static_assert(entt::ignore_as_empty_v<typename traits::type>);
static_assert(traits::page_size == 0u);
} }
TEST(Component, NonEmpty) { TEST(Component, NonEmpty) {
using traits_type = entt::component_traits<non_empty>; using traits = entt::component_traits<non_empty>;
static_assert(!traits_type::in_place_delete); static_assert(!traits::in_place_delete);
static_assert(traits_type::page_size == ENTT_PACKED_PAGE); static_assert(!entt::ignore_as_empty_v<typename traits::type>);
static_assert(traits::page_size == ENTT_PACKED_PAGE);
} }
TEST(Component, NonMovable) { TEST(Component, NonMovable) {
using traits_type = entt::component_traits<non_movable>; using traits = entt::component_traits<non_movable>;
static_assert(traits_type::in_place_delete); static_assert(traits::in_place_delete);
static_assert(traits_type::page_size == ENTT_PACKED_PAGE); static_assert(!entt::ignore_as_empty_v<typename traits::type>);
static_assert(traits::page_size == ENTT_PACKED_PAGE);
} }
TEST(Component, SelfContained) { TEST(Component, SelfContained) {
using traits_type = entt::component_traits<self_contained>; using traits = entt::component_traits<self_contained>;
static_assert(traits_type::in_place_delete); static_assert(traits::in_place_delete);
static_assert(traits_type::page_size == 4u); static_assert(!entt::ignore_as_empty_v<typename traits::type>);
static_assert(traits::page_size == 4u);
} }
TEST(Component, TraitsBased) { TEST(Component, TraitsBased) {
using traits_type = entt::component_traits<traits_based>; using traits = entt::component_traits<traits_based>;
static_assert(!traits_type::in_place_delete); static_assert(!traits::in_place_delete);
static_assert(traits_type::page_size == 8u); static_assert(!entt::ignore_as_empty_v<typename traits::type>);
static_assert(traits::page_size == 8u);
} }

View File

@ -29,12 +29,6 @@ TEST(Entity, Traits) {
ASSERT_EQ(traits_type::combine(entt::tombstone, entt::null), tombstone); ASSERT_EQ(traits_type::combine(entt::tombstone, entt::null), tombstone);
ASSERT_EQ(traits_type::combine(entt::null, entt::tombstone), null); ASSERT_EQ(traits_type::combine(entt::null, entt::tombstone), null);
ASSERT_EQ(traits_type::next(entity), traits_type::construct(entt::to_integral(entity), entt::to_version(entity) + 1u));
ASSERT_EQ(traits_type::next(other), traits_type::construct(entt::to_integral(other), entt::to_version(other) + 1u));
ASSERT_EQ(traits_type::next(entt::tombstone), traits_type::construct(entt::null, {}));
ASSERT_EQ(traits_type::next(entt::null), traits_type::construct(entt::null, {}));
} }
TEST(Entity, Null) { TEST(Entity, Null) {

View File

@ -8,7 +8,6 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <entt/entity/group.hpp> #include <entt/entity/group.hpp>
#include <entt/entity/registry.hpp> #include <entt/entity/registry.hpp>
#include "../common/config.h"
struct empty_type {}; struct empty_type {};
@ -57,7 +56,7 @@ TEST(NonOwningGroup, Functionalities) {
for(auto entity: group) { for(auto entity: group) {
ASSERT_EQ(std::get<0>(cgroup.get<const int, const char>(entity)), 42); ASSERT_EQ(std::get<0>(cgroup.get<const int, const char>(entity)), 42);
ASSERT_EQ(std::get<1>(group.get<int, char>(entity)), '2'); ASSERT_EQ(std::get<1>(group.get<int, char>(entity)), '2');
ASSERT_EQ(cgroup.get<1>(entity), '2'); ASSERT_EQ(cgroup.get<const char>(entity), '2');
} }
ASSERT_EQ(group.handle().data()[0u], e1); ASSERT_EQ(group.handle().data()[0u], e1);
@ -94,7 +93,6 @@ TEST(NonOwningGroup, Handle) {
ASSERT_TRUE(handle.empty()); ASSERT_TRUE(handle.empty());
ASSERT_FALSE(handle.contains(entity)); ASSERT_FALSE(handle.contains(entity));
ASSERT_EQ(&handle, &group.handle()); ASSERT_EQ(&handle, &group.handle());
ASSERT_NE(&handle, group.storage<int>());
registry.emplace<int>(entity); registry.emplace<int>(entity);
registry.emplace<char>(entity); registry.emplace<char>(entity);
@ -189,10 +187,10 @@ TEST(NonOwningGroup, Each) {
auto cgroup = std::as_const(registry).group_if_exists(entt::get<const int, const char>); auto cgroup = std::as_const(registry).group_if_exists(entt::get<const int, const char>);
registry.emplace<int>(entity[0u], 0); registry.emplace<int>(entity[0u], 0);
registry.emplace<char>(entity[0u], static_cast<char>(0)); registry.emplace<char>(entity[0u], 0);
registry.emplace<int>(entity[1u], 1); registry.emplace<int>(entity[1u], 1);
registry.emplace<char>(entity[1u], static_cast<char>(1)); registry.emplace<char>(entity[1u], 1);
auto iterable = group.each(); auto iterable = group.each();
auto citerable = cgroup.each(); auto citerable = cgroup.each();
@ -203,18 +201,16 @@ TEST(NonOwningGroup, Each) {
auto it = iterable.begin(); auto it = iterable.begin();
ASSERT_EQ(it.base(), group.begin());
ASSERT_EQ((it++, ++it), iterable.end()); ASSERT_EQ((it++, ++it), iterable.end());
ASSERT_EQ(it.base(), group.end());
group.each([expected = 1](auto entt, int &ivalue, char &cvalue) mutable { group.each([expected = 1u](auto entt, int &ivalue, char &cvalue) mutable {
ASSERT_EQ(static_cast<int>(entt::to_integral(entt)), expected); ASSERT_EQ(entt::to_integral(entt), expected);
ASSERT_EQ(ivalue, expected); ASSERT_EQ(ivalue, expected);
ASSERT_EQ(cvalue, expected); ASSERT_EQ(cvalue, expected);
--expected; --expected;
}); });
cgroup.each([expected = 1](const int &ivalue, const char &cvalue) mutable { cgroup.each([expected = 1u](const int &ivalue, const char &cvalue) mutable {
ASSERT_EQ(ivalue, expected); ASSERT_EQ(ivalue, expected);
ASSERT_EQ(cvalue, expected); ASSERT_EQ(cvalue, expected);
--expected; --expected;
@ -228,8 +224,8 @@ TEST(NonOwningGroup, Each) {
// do not use iterable, make sure an iterable group works when created from a temporary // do not use iterable, make sure an iterable group works when created from a temporary
for(auto [entt, ivalue, cvalue]: registry.group(entt::get<int, char>).each()) { for(auto [entt, ivalue, cvalue]: registry.group(entt::get<int, char>).each()) {
ASSERT_EQ(static_cast<int>(entt::to_integral(entt)), ivalue); ASSERT_EQ(entt::to_integral(entt), ivalue);
ASSERT_EQ(static_cast<char>(entt::to_integral(entt)), cvalue); ASSERT_EQ(entt::to_integral(entt), cvalue);
} }
} }
@ -277,9 +273,9 @@ TEST(NonOwningGroup, Sort) {
ASSERT_EQ(group.handle().data()[1u], e1); ASSERT_EQ(group.handle().data()[1u], e1);
ASSERT_EQ(group.handle().data()[2u], e2); ASSERT_EQ(group.handle().data()[2u], e2);
ASSERT_EQ((group.get<0, 1>(e0)), (std::make_tuple(0, 0u))); ASSERT_EQ((group.get<const int, unsigned int>(e0)), (std::make_tuple(0, 0u)));
ASSERT_EQ((group.get<0, 1>(e1)), (std::make_tuple(1, 1u))); ASSERT_EQ((group.get<const int, unsigned int>(e1)), (std::make_tuple(1, 1u)));
ASSERT_EQ((group.get<0, 1>(e2)), (std::make_tuple(2, 2u))); ASSERT_EQ((group.get<const int, unsigned int>(e2)), (std::make_tuple(2, 2u)));
ASSERT_FALSE(group.contains(e3)); ASSERT_FALSE(group.contains(e3));
@ -327,10 +323,10 @@ TEST(NonOwningGroup, SortAsAPool) {
} }
registry.sort<unsigned int>(std::less<unsigned int>{}); registry.sort<unsigned int>(std::less<unsigned int>{});
group.sort_as(*group.storage<unsigned int>()); group.sort<unsigned int>();
ASSERT_EQ((group.get<const int, unsigned int>(e0)), (std::make_tuple(0, 0u))); ASSERT_EQ((group.get<const int, unsigned int>(e0)), (std::make_tuple(0, 0u)));
ASSERT_EQ((group.get<0, 1>(e1)), (std::make_tuple(1, 1u))); ASSERT_EQ((group.get<const int, unsigned int>(e1)), (std::make_tuple(1, 1u)));
ASSERT_EQ((group.get<const int, unsigned int>(e2)), (std::make_tuple(2, 2u))); ASSERT_EQ((group.get<const int, unsigned int>(e2)), (std::make_tuple(2, 2u)));
ASSERT_FALSE(group.contains(e3)); ASSERT_FALSE(group.contains(e3));
@ -388,18 +384,9 @@ TEST(NonOwningGroup, ConstNonConstAndAllInBetween) {
ASSERT_EQ(group.size(), 1u); ASSERT_EQ(group.size(), 1u);
static_assert(std::is_same_v<decltype(group.get<0>({})), int &>);
static_assert(std::is_same_v<decltype(group.get<int>({})), int &>); static_assert(std::is_same_v<decltype(group.get<int>({})), int &>);
static_assert(std::is_same_v<decltype(group.get<1>({})), void>);
static_assert(std::is_same_v<decltype(group.get<empty_type>({})), void>);
static_assert(std::is_same_v<decltype(group.get<2>({})), const char &>);
static_assert(std::is_same_v<decltype(group.get<const char>({})), const char &>); static_assert(std::is_same_v<decltype(group.get<const char>({})), const char &>);
static_assert(std::is_same_v<decltype(group.get<int, const char>({})), std::tuple<int &, const char &>>);
static_assert(std::is_same_v<decltype(group.get<int, empty_type, const char>({})), std::tuple<int &, const char &>>);
static_assert(std::is_same_v<decltype(group.get<0, 1, 2>({})), std::tuple<int &, const char &>>);
static_assert(std::is_same_v<decltype(group.get({})), std::tuple<int &, const char &>>); static_assert(std::is_same_v<decltype(group.get({})), std::tuple<int &, const char &>>);
static_assert(std::is_same_v<decltype(std::as_const(registry).group_if_exists(entt::get<int, char>)), decltype(std::as_const(registry).group_if_exists(entt::get<const int, const char>))>); static_assert(std::is_same_v<decltype(std::as_const(registry).group_if_exists(entt::get<int, char>)), decltype(std::as_const(registry).group_if_exists(entt::get<const int, const char>))>);
@ -488,7 +475,7 @@ TEST(NonOwningGroup, ExcludedComponents) {
if(entity == e0) { if(entity == e0) {
ASSERT_EQ(group.get<int>(e0), 0); ASSERT_EQ(group.get<int>(e0), 0);
} else if(entity == e2) { } else if(entity == e2) {
ASSERT_EQ(group.get<0>(e2), 2); ASSERT_EQ(group.get<int>(e2), 2);
} }
} }
@ -506,7 +493,7 @@ TEST(NonOwningGroup, ExcludedComponents) {
if(entity == e1) { if(entity == e1) {
ASSERT_EQ(group.get<int>(e1), 1); ASSERT_EQ(group.get<int>(e1), 1);
} else if(entity == e3) { } else if(entity == e3) {
ASSERT_EQ(group.get<0>(e3), 3); ASSERT_EQ(group.get<int>(e3), 3);
} }
} }
} }
@ -676,100 +663,28 @@ TEST(NonOwningGroup, IterableGroupAlgorithmCompatibility) {
TEST(NonOwningGroup, Storage) { TEST(NonOwningGroup, Storage) {
entt::registry registry; entt::registry registry;
const auto entity = registry.create(); const auto entity = registry.create();
auto group = registry.group(entt::get<int, const char>, entt::exclude<double, const float>); const auto group = registry.group(entt::get<int, const char>);
static_assert(std::is_same_v<decltype(group.storage<0u>()), entt::storage_type_t<int> *>); static_assert(std::is_same_v<decltype(group.storage<0u>()), entt::storage_type_t<int> &>);
static_assert(std::is_same_v<decltype(group.storage<int>()), entt::storage_type_t<int> *>); static_assert(std::is_same_v<decltype(group.storage<int>()), entt::storage_type_t<int> &>);
static_assert(std::is_same_v<decltype(group.storage<const int>()), entt::storage_type_t<int> *>); static_assert(std::is_same_v<decltype(group.storage<1u>()), const entt::storage_type_t<char> &>);
static_assert(std::is_same_v<decltype(group.storage<1u>()), const entt::storage_type_t<char> *>); static_assert(std::is_same_v<decltype(group.storage<const char>()), const entt::storage_type_t<char> &>);
static_assert(std::is_same_v<decltype(group.storage<char>()), const entt::storage_type_t<char> *>);
static_assert(std::is_same_v<decltype(group.storage<const char>()), const entt::storage_type_t<char> *>);
static_assert(std::is_same_v<decltype(group.storage<2u>()), entt::storage_type_t<double> *>);
static_assert(std::is_same_v<decltype(group.storage<double>()), entt::storage_type_t<double> *>);
static_assert(std::is_same_v<decltype(group.storage<const double>()), entt::storage_type_t<double> *>);
static_assert(std::is_same_v<decltype(group.storage<3u>()), const entt::storage_type_t<float> *>);
static_assert(std::is_same_v<decltype(group.storage<float>()), const entt::storage_type_t<float> *>);
static_assert(std::is_same_v<decltype(group.storage<const float>()), const entt::storage_type_t<float> *>);
ASSERT_TRUE(group);
ASSERT_NE(group.storage<int>(), nullptr);
ASSERT_NE(group.storage<1u>(), nullptr);
ASSERT_NE(group.storage<double>(), nullptr);
ASSERT_NE(group.storage<3u>(), nullptr);
ASSERT_EQ(group.size(), 0u); ASSERT_EQ(group.size(), 0u);
group.storage<int>()->emplace(entity); group.storage<int>().emplace(entity);
group.storage<double>()->emplace(entity);
registry.emplace<char>(entity); registry.emplace<char>(entity);
registry.emplace<float>(entity);
ASSERT_EQ(group.size(), 0u);
ASSERT_EQ(group.begin(), group.end());
ASSERT_TRUE(group.storage<int>()->contains(entity));
ASSERT_TRUE(group.storage<const char>()->contains(entity));
ASSERT_TRUE(group.storage<double>()->contains(entity));
ASSERT_TRUE(group.storage<const float>()->contains(entity));
ASSERT_TRUE((registry.all_of<int, char, double, float>(entity)));
group.storage<double>()->erase(entity);
registry.erase<float>(entity);
ASSERT_EQ(group.size(), 1u); ASSERT_EQ(group.size(), 1u);
ASSERT_NE(group.begin(), group.end()); ASSERT_TRUE(group.storage<int>().contains(entity));
ASSERT_TRUE(group.storage<const int>()->contains(entity)); ASSERT_TRUE(group.storage<const char>().contains(entity));
ASSERT_TRUE(group.storage<char>()->contains(entity));
ASSERT_FALSE(group.storage<const double>()->contains(entity));
ASSERT_FALSE(group.storage<float>()->contains(entity));
ASSERT_TRUE((registry.all_of<int, char>(entity))); ASSERT_TRUE((registry.all_of<int, char>(entity)));
ASSERT_FALSE((registry.any_of<double, float>(entity)));
group.storage<0u>()->erase(entity); group.storage<0u>().erase(entity);
ASSERT_EQ(group.size(), 0u); ASSERT_EQ(group.size(), 0u);
ASSERT_EQ(group.begin(), group.end()); ASSERT_TRUE(group.storage<1u>().contains(entity));
ASSERT_FALSE(group.storage<0u>()->contains(entity)); ASSERT_FALSE((registry.all_of<int, char>(entity)));
ASSERT_TRUE(group.storage<1u>()->contains(entity));
ASSERT_FALSE(group.storage<2u>()->contains(entity));
ASSERT_FALSE(group.storage<3u>()->contains(entity));
ASSERT_TRUE((registry.all_of<char>(entity)));
ASSERT_FALSE((registry.any_of<int, double, float>(entity)));
group = {};
ASSERT_FALSE(group);
ASSERT_EQ(group.storage<0u>(), nullptr);
ASSERT_EQ(group.storage<const char>(), nullptr);
ASSERT_EQ(group.storage<2u>(), nullptr);
ASSERT_EQ(group.storage<const float>(), nullptr);
}
TEST(NonOwningGroup, Overlapping) {
entt::registry registry;
auto group = registry.group(entt::get<char>, entt::exclude<double>);
auto other = registry.group<int>(entt::get<char>, entt::exclude<double>);
ASSERT_TRUE(group.empty());
ASSERT_TRUE(other.empty());
const auto entity = registry.create();
registry.emplace<char>(entity, '1');
ASSERT_FALSE(group.empty());
ASSERT_TRUE(other.empty());
registry.emplace<int>(entity, 42);
ASSERT_FALSE(group.empty());
ASSERT_FALSE(other.empty());
registry.emplace<double>(entity, 3.);
ASSERT_TRUE(group.empty());
ASSERT_TRUE(other.empty());
} }
TEST(OwningGroup, Functionalities) { TEST(OwningGroup, Functionalities) {
@ -806,17 +721,16 @@ TEST(OwningGroup, Functionalities) {
ASSERT_EQ(group.size(), 1u); ASSERT_EQ(group.size(), 1u);
ASSERT_EQ(cgroup.storage<const int>()->raw()[0u][0u], 42); ASSERT_EQ(cgroup.storage<const int>().raw()[0u][0u], 42);
ASSERT_EQ(group.storage<int>()->raw()[0u][0u], 42); ASSERT_EQ(group.storage<int>().raw()[0u][0u], 42);
for(auto entity: group) { for(auto entity: group) {
ASSERT_EQ(std::get<0>(cgroup.get<const int, const char>(entity)), 42); ASSERT_EQ(std::get<0>(cgroup.get<const int, const char>(entity)), 42);
ASSERT_EQ(std::get<1>(group.get<int, char>(entity)), '2'); ASSERT_EQ(std::get<1>(group.get<int, char>(entity)), '2');
ASSERT_EQ(cgroup.get<1>(entity), '2'); ASSERT_EQ(cgroup.get<const char>(entity), '2');
} }
ASSERT_EQ(group.handle().data()[0u], e1); ASSERT_EQ(group.storage<int>().raw()[0u][0u], 42);
ASSERT_EQ(group.storage<int>()->raw()[0u][0u], 42);
registry.erase<char>(e0); registry.erase<char>(e0);
registry.erase<char>(e1); registry.erase<char>(e1);
@ -834,26 +748,6 @@ TEST(OwningGroup, Functionalities) {
ASSERT_FALSE(invalid); ASSERT_FALSE(invalid);
} }
TEST(OwningGroup, Handle) {
entt::registry registry;
const auto entity = registry.create();
auto group = registry.group<int>(entt::get<char>);
auto &&handle = group.handle();
ASSERT_TRUE(handle.empty());
ASSERT_FALSE(handle.contains(entity));
ASSERT_EQ(&handle, &group.handle());
ASSERT_EQ(&handle, group.storage<int>());
registry.emplace<int>(entity);
registry.emplace<char>(entity);
ASSERT_FALSE(handle.empty());
ASSERT_TRUE(handle.contains(entity));
ASSERT_EQ(&handle, &group.handle());
}
TEST(OwningGroup, Invalid) { TEST(OwningGroup, Invalid) {
entt::registry registry{}; entt::registry registry{};
auto group = std::as_const(registry).group_if_exists<const int>(entt::get<const empty_type>); auto group = std::as_const(registry).group_if_exists<const int>(entt::get<const empty_type>);
@ -937,10 +831,10 @@ TEST(OwningGroup, Each) {
auto cgroup = std::as_const(registry).group_if_exists<const int>(entt::get<const char>); auto cgroup = std::as_const(registry).group_if_exists<const int>(entt::get<const char>);
registry.emplace<int>(entity[0u], 0); registry.emplace<int>(entity[0u], 0);
registry.emplace<char>(entity[0u], static_cast<char>(0)); registry.emplace<char>(entity[0u], 0);
registry.emplace<int>(entity[1u], 1); registry.emplace<int>(entity[1u], 1);
registry.emplace<char>(entity[1u], static_cast<char>(1)); registry.emplace<char>(entity[1u], 1);
auto iterable = group.each(); auto iterable = group.each();
auto citerable = cgroup.each(); auto citerable = cgroup.each();
@ -951,18 +845,16 @@ TEST(OwningGroup, Each) {
auto it = iterable.begin(); auto it = iterable.begin();
ASSERT_EQ(it.base(), group.begin());
ASSERT_EQ((it++, ++it), iterable.end()); ASSERT_EQ((it++, ++it), iterable.end());
ASSERT_EQ(it.base(), group.end());
group.each([expected = 1](auto entt, int &ivalue, char &cvalue) mutable { group.each([expected = 1u](auto entt, int &ivalue, char &cvalue) mutable {
ASSERT_EQ(static_cast<int>(entt::to_integral(entt)), expected); ASSERT_EQ(entt::to_integral(entt), expected);
ASSERT_EQ(ivalue, expected); ASSERT_EQ(ivalue, expected);
ASSERT_EQ(cvalue, expected); ASSERT_EQ(cvalue, expected);
--expected; --expected;
}); });
cgroup.each([expected = 1](const int &ivalue, const char &cvalue) mutable { cgroup.each([expected = 1u](const int &ivalue, const char &cvalue) mutable {
ASSERT_EQ(ivalue, expected); ASSERT_EQ(ivalue, expected);
ASSERT_EQ(cvalue, expected); ASSERT_EQ(cvalue, expected);
--expected; --expected;
@ -976,8 +868,8 @@ TEST(OwningGroup, Each) {
// do not use iterable, make sure an iterable group works when created from a temporary // do not use iterable, make sure an iterable group works when created from a temporary
for(auto [entt, ivalue, cvalue]: registry.group<int>(entt::get<char>).each()) { for(auto [entt, ivalue, cvalue]: registry.group<int>(entt::get<char>).each()) {
ASSERT_EQ(static_cast<int>(entt::to_integral(entt)), ivalue); ASSERT_EQ(entt::to_integral(entt), ivalue);
ASSERT_EQ(static_cast<char>(entt::to_integral(entt)), cvalue); ASSERT_EQ(entt::to_integral(entt), cvalue);
} }
} }
@ -1001,27 +893,27 @@ TEST(OwningGroup, SortOrdered) {
registry.emplace<boxed_int>(entities[4], 2); registry.emplace<boxed_int>(entities[4], 2);
group.sort([&group](const entt::entity lhs, const entt::entity rhs) { group.sort([&group](const entt::entity lhs, const entt::entity rhs) {
return group.get<boxed_int>(lhs).value < group.get<0>(rhs).value; return group.get<boxed_int>(lhs).value < group.get<boxed_int>(rhs).value;
}); });
ASSERT_EQ(group.handle().data()[0u], entities[0]); ASSERT_EQ(group.storage<boxed_int>().data()[0u], entities[0]);
ASSERT_EQ(group.handle().data()[1u], entities[1]); ASSERT_EQ(group.storage<boxed_int>().data()[1u], entities[1]);
ASSERT_EQ(group.handle().data()[2u], entities[2]); ASSERT_EQ(group.storage<boxed_int>().data()[2u], entities[2]);
ASSERT_EQ(group.handle().data()[3u], entities[3]); ASSERT_EQ(group.storage<boxed_int>().data()[3u], entities[3]);
ASSERT_EQ(group.handle().data()[4u], entities[4]); ASSERT_EQ(group.storage<boxed_int>().data()[4u], entities[4]);
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][0u].value, 12); ASSERT_EQ(group.storage<boxed_int>().raw()[0u][0u].value, 12);
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][1u].value, 9); ASSERT_EQ(group.storage<boxed_int>().raw()[0u][1u].value, 9);
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][2u].value, 6); ASSERT_EQ(group.storage<boxed_int>().raw()[0u][2u].value, 6);
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][3u].value, 1); ASSERT_EQ(group.storage<boxed_int>().raw()[0u][3u].value, 1);
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][4u].value, 2); ASSERT_EQ(group.storage<boxed_int>().raw()[0u][4u].value, 2);
ASSERT_EQ(group.storage<char>()->raw()[0u][0u], 'a'); ASSERT_EQ(group.storage<char>().raw()[0u][0u], 'a');
ASSERT_EQ(group.storage<char>()->raw()[0u][1u], 'b'); ASSERT_EQ(group.storage<char>().raw()[0u][1u], 'b');
ASSERT_EQ(group.storage<char>()->raw()[0u][2u], 'c'); ASSERT_EQ(group.storage<char>().raw()[0u][2u], 'c');
ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{12}, 'a'))); ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{12}, 'a')));
ASSERT_EQ((group.get<0, 1>(entities[1])), (std::make_tuple(boxed_int{9}, 'b'))); ASSERT_EQ((group.get<boxed_int, char>(entities[1])), (std::make_tuple(boxed_int{9}, 'b')));
ASSERT_EQ((group.get<boxed_int, char>(entities[2])), (std::make_tuple(boxed_int{6}, 'c'))); ASSERT_EQ((group.get<boxed_int, char>(entities[2])), (std::make_tuple(boxed_int{6}, 'c')));
ASSERT_FALSE(group.contains(entities[3])); ASSERT_FALSE(group.contains(entities[3]));
@ -1051,24 +943,24 @@ TEST(OwningGroup, SortReverse) {
return lhs.value < rhs.value; return lhs.value < rhs.value;
}); });
ASSERT_EQ(group.handle().data()[0u], entities[2]); ASSERT_EQ(group.storage<boxed_int>().data()[0u], entities[2]);
ASSERT_EQ(group.handle().data()[1u], entities[1]); ASSERT_EQ(group.storage<boxed_int>().data()[1u], entities[1]);
ASSERT_EQ(group.handle().data()[2u], entities[0]); ASSERT_EQ(group.storage<boxed_int>().data()[2u], entities[0]);
ASSERT_EQ(group.handle().data()[3u], entities[3]); ASSERT_EQ(group.storage<boxed_int>().data()[3u], entities[3]);
ASSERT_EQ(group.handle().data()[4u], entities[4]); ASSERT_EQ(group.storage<boxed_int>().data()[4u], entities[4]);
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][0u].value, 12); ASSERT_EQ(group.storage<boxed_int>().raw()[0u][0u].value, 12);
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][1u].value, 9); ASSERT_EQ(group.storage<boxed_int>().raw()[0u][1u].value, 9);
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][2u].value, 6); ASSERT_EQ(group.storage<boxed_int>().raw()[0u][2u].value, 6);
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][3u].value, 1); ASSERT_EQ(group.storage<boxed_int>().raw()[0u][3u].value, 1);
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][4u].value, 2); ASSERT_EQ(group.storage<boxed_int>().raw()[0u][4u].value, 2);
ASSERT_EQ(group.storage<char>()->raw()[0u][0u], 'c'); ASSERT_EQ(group.storage<char>().raw()[0u][0u], 'c');
ASSERT_EQ(group.storage<char>()->raw()[0u][1u], 'b'); ASSERT_EQ(group.storage<char>().raw()[0u][1u], 'b');
ASSERT_EQ(group.storage<char>()->raw()[0u][2u], 'a'); ASSERT_EQ(group.storage<char>().raw()[0u][2u], 'a');
ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{6}, 'a'))); ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{6}, 'a')));
ASSERT_EQ((group.get<0, 1>(entities[1])), (std::make_tuple(boxed_int{9}, 'b'))); ASSERT_EQ((group.get<boxed_int, char>(entities[1])), (std::make_tuple(boxed_int{9}, 'b')));
ASSERT_EQ((group.get<boxed_int, char>(entities[2])), (std::make_tuple(boxed_int{12}, 'c'))); ASSERT_EQ((group.get<boxed_int, char>(entities[2])), (std::make_tuple(boxed_int{12}, 'c')));
ASSERT_FALSE(group.contains(entities[3])); ASSERT_FALSE(group.contains(entities[3]));
@ -1106,27 +998,27 @@ TEST(OwningGroup, SortUnordered) {
return std::get<1>(lhs) < std::get<1>(rhs); return std::get<1>(lhs) < std::get<1>(rhs);
}); });
ASSERT_EQ(group.handle().data()[0u], entities[4]); ASSERT_EQ(group.storage<boxed_int>().data()[0u], entities[4]);
ASSERT_EQ(group.handle().data()[1u], entities[3]); ASSERT_EQ(group.storage<boxed_int>().data()[1u], entities[3]);
ASSERT_EQ(group.handle().data()[2u], entities[0]); ASSERT_EQ(group.storage<boxed_int>().data()[2u], entities[0]);
ASSERT_EQ(group.handle().data()[3u], entities[1]); ASSERT_EQ(group.storage<boxed_int>().data()[3u], entities[1]);
ASSERT_EQ(group.handle().data()[4u], entities[2]); ASSERT_EQ(group.storage<boxed_int>().data()[4u], entities[2]);
ASSERT_EQ(group.handle().data()[5u], entities[5]); ASSERT_EQ(group.storage<boxed_int>().data()[5u], entities[5]);
ASSERT_EQ(group.handle().data()[6u], entities[6]); ASSERT_EQ(group.storage<boxed_int>().data()[6u], entities[6]);
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][0u].value, 12); ASSERT_EQ(group.storage<boxed_int>().raw()[0u][0u].value, 12);
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][1u].value, 9); ASSERT_EQ(group.storage<boxed_int>().raw()[0u][1u].value, 9);
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][2u].value, 6); ASSERT_EQ(group.storage<boxed_int>().raw()[0u][2u].value, 6);
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][3u].value, 3); ASSERT_EQ(group.storage<boxed_int>().raw()[0u][3u].value, 3);
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][4u].value, 1); ASSERT_EQ(group.storage<boxed_int>().raw()[0u][4u].value, 1);
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][5u].value, 4); ASSERT_EQ(group.storage<boxed_int>().raw()[0u][5u].value, 4);
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][6u].value, 5); ASSERT_EQ(group.storage<boxed_int>().raw()[0u][6u].value, 5);
ASSERT_EQ(group.get<char>(group.handle().data()[0u]), 'e'); ASSERT_EQ(group.get<char>(group.storage<boxed_int>().data()[0u]), 'e');
ASSERT_EQ(group.get<1>(group.handle().data()[1u]), 'd'); ASSERT_EQ(group.get<char>(group.storage<boxed_int>().data()[1u]), 'd');
ASSERT_EQ(group.get<char>(group.handle().data()[2u]), 'c'); ASSERT_EQ(group.get<char>(group.storage<boxed_int>().data()[2u]), 'c');
ASSERT_EQ(group.get<1>(group.handle().data()[3u]), 'b'); ASSERT_EQ(group.get<char>(group.storage<boxed_int>().data()[3u]), 'b');
ASSERT_EQ(group.get<char>(group.handle().data()[4u]), 'a'); ASSERT_EQ(group.get<char>(group.storage<boxed_int>().data()[4u]), 'a');
ASSERT_FALSE(group.contains(entities[5])); ASSERT_FALSE(group.contains(entities[5]));
ASSERT_FALSE(group.contains(entities[6])); ASSERT_FALSE(group.contains(entities[6]));
@ -1134,7 +1026,7 @@ TEST(OwningGroup, SortUnordered) {
TEST(OwningGroup, SortWithExclusionList) { TEST(OwningGroup, SortWithExclusionList) {
entt::registry registry; entt::registry registry;
auto group = registry.group<boxed_int>(entt::get<>, entt::exclude<char>); auto group = registry.group<boxed_int>({}, entt::exclude<char>);
entt::entity entities[5]{}; entt::entity entities[5]{};
registry.create(std::begin(entities), std::end(entities)); registry.create(std::begin(entities), std::end(entities));
@ -1151,20 +1043,20 @@ TEST(OwningGroup, SortWithExclusionList) {
return lhs < rhs; return lhs < rhs;
}); });
ASSERT_EQ(group.handle().data()[0u], entities[4]); ASSERT_EQ(group.storage<boxed_int>().data()[0u], entities[4]);
ASSERT_EQ(group.handle().data()[1u], entities[3]); ASSERT_EQ(group.storage<boxed_int>().data()[1u], entities[3]);
ASSERT_EQ(group.handle().data()[2u], entities[1]); ASSERT_EQ(group.storage<boxed_int>().data()[2u], entities[1]);
ASSERT_EQ(group.handle().data()[3u], entities[0]); ASSERT_EQ(group.storage<boxed_int>().data()[3u], entities[0]);
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][0u].value, 4); ASSERT_EQ(group.storage<boxed_int>().raw()[0u][0u].value, 4);
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][1u].value, 3); ASSERT_EQ(group.storage<boxed_int>().raw()[0u][1u].value, 3);
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][2u].value, 1); ASSERT_EQ(group.storage<boxed_int>().raw()[0u][2u].value, 1);
ASSERT_EQ(group.storage<boxed_int>()->raw()[0u][3u].value, 0); ASSERT_EQ(group.storage<boxed_int>().raw()[0u][3u].value, 0);
ASSERT_EQ(group.get<boxed_int>(entities[0]).value, 0); ASSERT_EQ(group.get<boxed_int>(entities[0]).value, 0);
ASSERT_EQ(group.get<0>(entities[1]).value, 1); ASSERT_EQ(group.get<boxed_int>(entities[1]).value, 1);
ASSERT_EQ(group.get<boxed_int>(entities[3]).value, 3); ASSERT_EQ(group.get<boxed_int>(entities[3]).value, 3);
ASSERT_EQ(group.get<0>(entities[4]).value, 4); ASSERT_EQ(group.get<boxed_int>(entities[4]).value, 4);
ASSERT_FALSE(group.contains(entities[2])); ASSERT_FALSE(group.contains(entities[2]));
} }
@ -1218,24 +1110,11 @@ TEST(OwningGroup, ConstNonConstAndAllInBetween) {
ASSERT_EQ(group.size(), 1u); ASSERT_EQ(group.size(), 1u);
static_assert(std::is_same_v<decltype(group.get<0>({})), int &>);
static_assert(std::is_same_v<decltype(group.get<int>({})), int &>); static_assert(std::is_same_v<decltype(group.get<int>({})), int &>);
static_assert(std::is_same_v<decltype(group.get<1>({})), const char &>);
static_assert(std::is_same_v<decltype(group.get<const char>({})), const char &>); static_assert(std::is_same_v<decltype(group.get<const char>({})), const char &>);
static_assert(std::is_same_v<decltype(group.get<2>({})), void>);
static_assert(std::is_same_v<decltype(group.get<empty_type>({})), void>);
static_assert(std::is_same_v<decltype(group.get<3>({})), double &>);
static_assert(std::is_same_v<decltype(group.get<double>({})), double &>); static_assert(std::is_same_v<decltype(group.get<double>({})), double &>);
static_assert(std::is_same_v<decltype(group.get<4>({})), const float &>);
static_assert(std::is_same_v<decltype(group.get<const float>({})), const float &>); static_assert(std::is_same_v<decltype(group.get<const float>({})), const float &>);
static_assert(std::is_same_v<decltype(group.get<int, const char, double, const float>({})), std::tuple<int &, const char &, double &, const float &>>);
static_assert(std::is_same_v<decltype(group.get<int, const char, empty_type, double, const float>({})), std::tuple<int &, const char &, double &, const float &>>);
static_assert(std::is_same_v<decltype(group.get<0, 1, 2, 3, 4>({})), std::tuple<int &, const char &, double &, const float &>>);
static_assert(std::is_same_v<decltype(group.get({})), std::tuple<int &, const char &, double &, const float &>>); static_assert(std::is_same_v<decltype(group.get({})), std::tuple<int &, const char &, double &, const float &>>);
static_assert(std::is_same_v<decltype(std::as_const(registry).group_if_exists<int>(entt::get<char>)), decltype(std::as_const(registry).group_if_exists<const int>(entt::get<const char>))>); static_assert(std::is_same_v<decltype(std::as_const(registry).group_if_exists<int>(entt::get<char>)), decltype(std::as_const(registry).group_if_exists<const int>(entt::get<const char>))>);
@ -1313,7 +1192,7 @@ TEST(OwningGroup, ExcludedComponents) {
registry.emplace<int>(e1, 1); registry.emplace<int>(e1, 1);
registry.emplace<char>(e1); registry.emplace<char>(e1);
const auto group = registry.group<int>(entt::get<>, entt::exclude<char, double>); const auto group = registry.group<int>({}, entt::exclude<char, double>);
const auto e2 = registry.create(); const auto e2 = registry.create();
registry.emplace<int>(e2, 2); registry.emplace<int>(e2, 2);
@ -1328,7 +1207,7 @@ TEST(OwningGroup, ExcludedComponents) {
if(entity == e0) { if(entity == e0) {
ASSERT_EQ(group.get<int>(e0), 0); ASSERT_EQ(group.get<int>(e0), 0);
} else if(entity == e2) { } else if(entity == e2) {
ASSERT_EQ(group.get<0>(e2), 2); ASSERT_EQ(group.get<int>(e2), 2);
} }
} }
@ -1346,7 +1225,7 @@ TEST(OwningGroup, ExcludedComponents) {
if(entity == e1) { if(entity == e1) {
ASSERT_EQ(group.get<int>(e1), 1); ASSERT_EQ(group.get<int>(e1), 1);
} else if(entity == e3) { } else if(entity == e3) {
ASSERT_EQ(group.get<0>(e3), 3); ASSERT_EQ(group.get<int>(e3), 3);
} }
} }
} }
@ -1384,8 +1263,8 @@ TEST(OwningGroup, EmptyAndNonEmptyTypes) {
TEST(OwningGroup, TrackEntitiesOnComponentDestruction) { TEST(OwningGroup, TrackEntitiesOnComponentDestruction) {
entt::registry registry; entt::registry registry;
const auto group = registry.group<int>(entt::get<>, entt::exclude<char>); const auto group = registry.group<int>({}, entt::exclude<char>);
const auto cgroup = std::as_const(registry).group_if_exists<const int>(entt::get<>, entt::exclude<char>); const auto cgroup = std::as_const(registry).group_if_exists<const int>({}, entt::exclude<char>);
const auto entity = registry.create(); const auto entity = registry.create();
registry.emplace<int>(entity); registry.emplace<int>(entity);
@ -1524,7 +1403,7 @@ TEST(OwningGroup, SwappingValuesIsAllowed) {
// thanks to @andranik3949 for pointing out this missing test // thanks to @andranik3949 for pointing out this missing test
registry.view<const boxed_int>().each([](const auto entity, const auto &value) { registry.view<const boxed_int>().each([](const auto entity, const auto &value) {
ASSERT_EQ(static_cast<int>(entt::to_integral(entity)), value.value); ASSERT_EQ(entt::to_integral(entity), value.value);
}); });
} }
@ -1564,81 +1443,26 @@ TEST(OwningGroup, IterableGroupAlgorithmCompatibility) {
TEST(OwningGroup, Storage) { TEST(OwningGroup, Storage) {
entt::registry registry; entt::registry registry;
const auto entity = registry.create(); const auto entity = registry.create();
auto group = registry.group<int>(entt::get<const char>, entt::exclude<double, const float>); const auto group = registry.group<int>(entt::get<const char>);
static_assert(std::is_same_v<decltype(group.storage<0u>()), entt::storage_type_t<int> *>); static_assert(std::is_same_v<decltype(group.storage<0u>()), entt::storage_type_t<int> &>);
static_assert(std::is_same_v<decltype(group.storage<int>()), entt::storage_type_t<int> *>); static_assert(std::is_same_v<decltype(group.storage<int>()), entt::storage_type_t<int> &>);
static_assert(std::is_same_v<decltype(group.storage<const int>()), entt::storage_type_t<int> *>); static_assert(std::is_same_v<decltype(group.storage<1u>()), const entt::storage_type_t<char> &>);
static_assert(std::is_same_v<decltype(group.storage<1u>()), const entt::storage_type_t<char> *>); static_assert(std::is_same_v<decltype(group.storage<const char>()), const entt::storage_type_t<char> &>);
static_assert(std::is_same_v<decltype(group.storage<char>()), const entt::storage_type_t<char> *>);
static_assert(std::is_same_v<decltype(group.storage<const char>()), const entt::storage_type_t<char> *>);
static_assert(std::is_same_v<decltype(group.storage<2u>()), entt::storage_type_t<double> *>);
static_assert(std::is_same_v<decltype(group.storage<double>()), entt::storage_type_t<double> *>);
static_assert(std::is_same_v<decltype(group.storage<const double>()), entt::storage_type_t<double> *>);
static_assert(std::is_same_v<decltype(group.storage<3u>()), const entt::storage_type_t<float> *>);
static_assert(std::is_same_v<decltype(group.storage<float>()), const entt::storage_type_t<float> *>);
static_assert(std::is_same_v<decltype(group.storage<const float>()), const entt::storage_type_t<float> *>);
ASSERT_TRUE(group);
ASSERT_NE(group.storage<int>(), nullptr);
ASSERT_NE(group.storage<1u>(), nullptr);
ASSERT_NE(group.storage<double>(), nullptr);
ASSERT_NE(group.storage<3u>(), nullptr);
ASSERT_EQ(group.size(), 0u); ASSERT_EQ(group.size(), 0u);
group.storage<int>()->emplace(entity); group.storage<int>().emplace(entity);
group.storage<double>()->emplace(entity);
registry.emplace<char>(entity); registry.emplace<char>(entity);
registry.emplace<float>(entity);
ASSERT_EQ(group.size(), 0u);
ASSERT_EQ(group.begin(), group.end());
ASSERT_TRUE(group.storage<int>()->contains(entity));
ASSERT_TRUE(group.storage<const char>()->contains(entity));
ASSERT_TRUE(group.storage<double>()->contains(entity));
ASSERT_TRUE(group.storage<const float>()->contains(entity));
ASSERT_TRUE((registry.all_of<int, char, double, float>(entity)));
group.storage<double>()->erase(entity);
registry.erase<float>(entity);
ASSERT_EQ(group.size(), 1u); ASSERT_EQ(group.size(), 1u);
ASSERT_NE(group.begin(), group.end()); ASSERT_TRUE(group.storage<int>().contains(entity));
ASSERT_TRUE(group.storage<const int>()->contains(entity)); ASSERT_TRUE(group.storage<const char>().contains(entity));
ASSERT_TRUE(group.storage<char>()->contains(entity));
ASSERT_FALSE(group.storage<const double>()->contains(entity));
ASSERT_FALSE(group.storage<float>()->contains(entity));
ASSERT_TRUE((registry.all_of<int, char>(entity))); ASSERT_TRUE((registry.all_of<int, char>(entity)));
ASSERT_FALSE((registry.any_of<double, float>(entity)));
group.storage<0u>()->erase(entity); group.storage<0u>().erase(entity);
ASSERT_EQ(group.size(), 0u); ASSERT_EQ(group.size(), 0u);
ASSERT_EQ(group.begin(), group.end()); ASSERT_TRUE(group.storage<1u>().contains(entity));
ASSERT_FALSE(group.storage<0u>()->contains(entity)); ASSERT_FALSE((registry.all_of<int, char>(entity)));
ASSERT_TRUE(group.storage<1u>()->contains(entity));
ASSERT_FALSE(group.storage<2u>()->contains(entity));
ASSERT_FALSE(group.storage<3u>()->contains(entity));
ASSERT_TRUE((registry.all_of<char>(entity)));
ASSERT_FALSE((registry.any_of<int, double, float>(entity)));
group = {};
ASSERT_FALSE(group);
ASSERT_EQ(group.storage<0u>(), nullptr);
ASSERT_EQ(group.storage<const char>(), nullptr);
ASSERT_EQ(group.storage<2u>(), nullptr);
ASSERT_EQ(group.storage<const float>(), nullptr);
}
ENTT_DEBUG_TEST(OwningGroupDeathTest, Overlapping) {
entt::registry registry;
registry.group<char>(entt::get<int>, entt::exclude<double>);
ASSERT_DEATH((registry.group<char, float>(entt::get<float>, entt::exclude<double>)), "");
ASSERT_DEATH(registry.group<char>(entt::get<int, float>, entt::exclude<double>), "");
ASSERT_DEATH(registry.group<char>(entt::get<int>, entt::exclude<double, float>), "");
} }

View File

@ -83,8 +83,8 @@ TEST(BasicHandle, Destruction) {
ASSERT_FALSE(handle); ASSERT_FALSE(handle);
ASSERT_FALSE(handle.valid()); ASSERT_FALSE(handle.valid());
ASSERT_NE(handle.registry(), nullptr); ASSERT_NE(handle.registry(), nullptr);
ASSERT_EQ(handle.entity(), entity);
ASSERT_EQ(registry.current(entity), typename entt::registry::version_type{}); ASSERT_EQ(registry.current(entity), typename entt::registry::version_type{});
ASSERT_EQ(handle.entity(), entt::entity{entt::null});
handle = entt::handle{registry, registry.create()}; handle = entt::handle{registry, registry.create()};
@ -98,8 +98,8 @@ TEST(BasicHandle, Destruction) {
ASSERT_FALSE(handle); ASSERT_FALSE(handle);
ASSERT_FALSE(handle.valid()); ASSERT_FALSE(handle.valid());
ASSERT_NE(handle.registry(), nullptr); ASSERT_NE(handle.registry(), nullptr);
ASSERT_EQ(handle.entity(), entity);
ASSERT_NE(registry.current(entity), typename entt::registry::version_type{}); ASSERT_NE(registry.current(entity), typename entt::registry::version_type{});
ASSERT_EQ(handle.entity(), entt::entity{entt::null});
} }
TEST(BasicHandle, Comparison) { TEST(BasicHandle, Comparison) {
@ -275,8 +275,6 @@ TEST(BasicHandle, HandleStorageIterator) {
registry.emplace<int>(entity); registry.emplace<int>(entity);
registry.emplace<double>(entity); registry.emplace<double>(entity);
// required to test the find-first initialization step
registry.storage<entt::entity>().erase(entity);
auto test = [](auto iterable) { auto test = [](auto iterable) {
auto end{iterable.begin()}; auto end{iterable.begin()};
@ -292,13 +290,6 @@ TEST(BasicHandle, HandleStorageIterator) {
ASSERT_EQ(++begin, iterable.end()); ASSERT_EQ(++begin, iterable.end());
}; };
const auto handle = entt::handle{registry, entity}; test(entt::handle{registry, entity}.storage());
const auto chandle = entt::const_handle{std::as_const(registry), entity}; test(entt::const_handle{std::as_const(registry), entity}.storage());
ASSERT_FALSE(registry.valid(entity));
ASSERT_FALSE(handle);
ASSERT_FALSE(chandle);
test(handle.storage());
test(chandle.storage());
} }

View File

@ -16,10 +16,6 @@ struct stable_type {
int value; int value;
}; };
void sigh_callback(int &value) {
++value;
}
TEST(Helper, AsView) { TEST(Helper, AsView) {
entt::registry registry; entt::registry registry;
const entt::registry cregistry; const entt::registry cregistry;
@ -52,7 +48,7 @@ TEST(Helper, Invoke) {
TEST(Helper, ToEntity) { TEST(Helper, ToEntity) {
entt::registry registry; entt::registry registry;
const entt::entity null = entt::null; const entt::entity null = entt::null;
constexpr auto page_size = entt::storage_type_t<int>::traits_type::page_size; constexpr auto page_size = entt::component_traits<int>::page_size;
const int value = 42; const int value = 42;
ASSERT_EQ(entt::to_entity(registry, 42), null); ASSERT_EQ(entt::to_entity(registry, 42), null);
@ -92,7 +88,7 @@ TEST(Helper, ToEntity) {
TEST(Helper, ToEntityStableType) { TEST(Helper, ToEntityStableType) {
entt::registry registry; entt::registry registry;
const entt::entity null = entt::null; const entt::entity null = entt::null;
constexpr auto page_size = entt::storage_type_t<stable_type>::traits_type::page_size; constexpr auto page_size = entt::component_traits<stable_type>::page_size;
const stable_type value{42}; const stable_type value{42};
ASSERT_EQ(entt::to_entity(registry, stable_type{42}), null); ASSERT_EQ(entt::to_entity(registry, stable_type{42}), null);
@ -128,44 +124,3 @@ TEST(Helper, ToEntityStableType) {
ASSERT_EQ(entt::to_entity(registry, stable_type{42}), null); ASSERT_EQ(entt::to_entity(registry, stable_type{42}), null);
ASSERT_EQ(entt::to_entity(registry, value), null); ASSERT_EQ(entt::to_entity(registry, value), null);
} }
TEST(Helper, SighHelper) {
using namespace entt::literals;
entt::registry registry{};
const auto entt = registry.create();
entt::sigh_helper helper{registry};
int counter{};
ASSERT_EQ(&helper.registry(), &registry);
helper.with<int>()
.on_construct<&sigh_callback>(counter)
.on_update<&sigh_callback>(counter)
.on_destroy<&sigh_callback>(counter);
ASSERT_EQ(counter, 0);
registry.emplace<int>(entt);
registry.replace<int>(entt);
registry.erase<int>(entt);
ASSERT_EQ(counter, 3);
helper.with<double>("other"_hs)
.on_construct<&sigh_callback>(counter)
.on_update<&sigh_callback>(counter)
.on_destroy<&sigh_callback>(counter);
registry.emplace<double>(entt);
registry.replace<double>(entt);
registry.erase<double>(entt);
ASSERT_EQ(counter, 3);
registry.storage<double>("other"_hs).emplace(entt);
registry.storage<double>("other"_hs).patch(entt);
registry.storage<double>("other"_hs).erase(entt);
ASSERT_EQ(counter, 6);
}

View File

@ -1,7 +1,5 @@
#include <cstddef> #include <cstddef>
#include <utility>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <entt/core/type_info.hpp>
#include <entt/entity/organizer.hpp> #include <entt/entity/organizer.hpp>
#include <entt/entity/registry.hpp> #include <entt/entity/registry.hpp>
@ -22,7 +20,7 @@ struct clazz {
static void ro_int_char_with_payload(clazz &, entt::view<entt::get_t<const int, const char>>) {} static void ro_int_char_with_payload(clazz &, entt::view<entt::get_t<const int, const char>>) {}
}; };
void to_args_integrity(entt::view<entt::get_t<int>> view, std::size_t &value, entt::registry &) { void to_args_integrity(entt::view<entt::get_t<int>> view, std::size_t &value, entt::registry &registry) {
value = view.size(); value = view.size();
} }
@ -365,10 +363,6 @@ TEST(Organizer, Prepare) {
ASSERT_FALSE(registry.ctx().contains<char>()); ASSERT_FALSE(registry.ctx().contains<char>());
ASSERT_FALSE(registry.ctx().contains<double>()); ASSERT_FALSE(registry.ctx().contains<double>());
ASSERT_EQ(std::as_const(registry).storage<int>(), nullptr);
ASSERT_EQ(std::as_const(registry).storage<char>(), nullptr);
ASSERT_EQ(std::as_const(registry).storage<double>(), nullptr);
for(auto &&vertex: graph) { for(auto &&vertex: graph) {
vertex.prepare(registry); vertex.prepare(registry);
} }
@ -376,10 +370,6 @@ TEST(Organizer, Prepare) {
ASSERT_FALSE(registry.ctx().contains<int>()); ASSERT_FALSE(registry.ctx().contains<int>());
ASSERT_FALSE(registry.ctx().contains<char>()); ASSERT_FALSE(registry.ctx().contains<char>());
ASSERT_TRUE(registry.ctx().contains<double>()); ASSERT_TRUE(registry.ctx().contains<double>());
ASSERT_NE(std::as_const(registry).storage<int>(), nullptr);
ASSERT_NE(std::as_const(registry).storage<char>(), nullptr);
ASSERT_EQ(std::as_const(registry).storage<double>(), nullptr);
} }
TEST(Organizer, Dependencies) { TEST(Organizer, Dependencies) {
@ -438,5 +428,5 @@ TEST(Organizer, ToArgsIntegrity) {
auto graph = organizer.graph(); auto graph = organizer.graph();
graph[0u].callback()(graph[0u].data(), registry); graph[0u].callback()(graph[0u].data(), registry);
ASSERT_EQ(registry.ctx().get<std::size_t>(), 0u); ASSERT_EQ(registry.ctx().at<std::size_t>(), 0u);
} }

View File

@ -40,16 +40,18 @@ struct aggregate {
}; };
struct listener { struct listener {
template<typename Type> template<typename Component>
static void sort(entt::registry &registry) { static void sort(entt::registry &registry) {
registry.sort<Type>([](auto lhs, auto rhs) { return lhs < rhs; }); registry.sort<Component>([](auto lhs, auto rhs) { return lhs < rhs; });
} }
template<typename Component>
void incr(const entt::registry &, entt::entity entity) { void incr(const entt::registry &, entt::entity entity) {
last = entity; last = entity;
++counter; ++counter;
} }
template<typename Component>
void decr(const entt::registry &, entt::entity entity) { void decr(const entt::registry &, entt::entity entity) {
last = entity; last = entity;
--counter; --counter;
@ -73,11 +75,11 @@ struct destruction_order {
destruction_order(const entt::registry &ref, bool &ctx) destruction_order(const entt::registry &ref, bool &ctx)
: registry{&ref}, : registry{&ref},
ctx_check{&ctx} { ctx_check{&ctx} {
*ctx_check = (registry->ctx().find<ctx_check_type>() != nullptr); *ctx_check = (registry->ctx().find<int>() != nullptr);
} }
~destruction_order() { ~destruction_order() {
*ctx_check = *ctx_check && (registry->ctx().find<ctx_check_type>() != nullptr); *ctx_check = *ctx_check && (registry->ctx().find<int>() != nullptr);
} }
private: private:
@ -85,22 +87,6 @@ private:
bool *ctx_check{}; bool *ctx_check{};
}; };
enum class small_entity : std::uint32_t {};
struct small_entity_traits {
using value_type = small_entity;
using entity_type = uint32_t;
using version_type = uint16_t;
static constexpr entity_type entity_mask = 0xFF;
static constexpr entity_type version_mask = 0x00;
};
template<>
struct entt::entt_traits<small_entity>: entt::basic_entt_traits<small_entity_traits> {
using base_type = entt::basic_entt_traits<small_entity_traits>;
static constexpr auto page_size = ENTT_SPARSE_PAGE;
};
TEST(Registry, Context) { TEST(Registry, Context) {
entt::registry registry; entt::registry registry;
auto &ctx = registry.ctx(); auto &ctx = registry.ctx();
@ -140,8 +126,8 @@ TEST(Registry, Context) {
ASSERT_EQ(ctx.emplace<const int>(0), 42); ASSERT_EQ(ctx.emplace<const int>(0), 42);
ASSERT_EQ(ctx.find<const int>(), cctx.find<int>()); ASSERT_EQ(ctx.find<const int>(), cctx.find<int>());
ASSERT_EQ(ctx.get<int>(), cctx.get<const int>()); ASSERT_EQ(ctx.at<int>(), cctx.at<const int>());
ASSERT_EQ(ctx.get<int>(), 42); ASSERT_EQ(ctx.at<int>(), 42);
ASSERT_EQ(ctx.find<double>(), nullptr); ASSERT_EQ(ctx.find<double>(), nullptr);
ASSERT_EQ(cctx.find<double>(), nullptr); ASSERT_EQ(cctx.find<double>(), nullptr);
@ -153,8 +139,8 @@ TEST(Registry, Context) {
ASSERT_EQ(ctx.insert_or_assign<const int>(0), 0); ASSERT_EQ(ctx.insert_or_assign<const int>(0), 0);
ASSERT_EQ(ctx.find<const int>(), cctx.find<int>()); ASSERT_EQ(ctx.find<const int>(), cctx.find<int>());
ASSERT_EQ(ctx.get<int>(), cctx.get<const int>()); ASSERT_EQ(ctx.at<int>(), cctx.at<const int>());
ASSERT_EQ(ctx.get<int>(), 0); ASSERT_EQ(ctx.at<int>(), 0);
} }
TEST(Registry, ContextHint) { TEST(Registry, ContextHint) {
@ -390,12 +376,13 @@ TEST(Registry, Functionalities) {
TEST(Registry, Constructors) { TEST(Registry, Constructors) {
entt::registry registry; entt::registry registry;
entt::registry other{42}; entt::registry other{42};
const entt::entity entity = entt::tombstone;
ASSERT_TRUE(registry.empty()); ASSERT_TRUE(registry.empty());
ASSERT_TRUE(other.empty()); ASSERT_TRUE(other.empty());
ASSERT_EQ(registry.released(), 0u); ASSERT_EQ(registry.released(), entity);
ASSERT_EQ(other.released(), 0u); ASSERT_EQ(other.released(), entity);
} }
TEST(Registry, Move) { TEST(Registry, Move) {
@ -499,8 +486,6 @@ TEST(Registry, Identifiers) {
} }
TEST(Registry, Data) { TEST(Registry, Data) {
using traits_type = entt::entt_traits<entt::entity>;
entt::registry registry; entt::registry registry;
ASSERT_EQ(std::as_const(registry).data(), nullptr); ASSERT_EQ(std::as_const(registry).data(), nullptr);
@ -512,8 +497,8 @@ TEST(Registry, Data) {
const auto other = registry.create(); const auto other = registry.create();
registry.release(entity); registry.release(entity);
ASSERT_EQ(*std::as_const(registry).data(), other); ASSERT_NE(*std::as_const(registry).data(), entity);
ASSERT_EQ(*(std::as_const(registry).data() + 1u), traits_type::next(entity)); ASSERT_EQ(*(std::as_const(registry).data() + 1u), other);
} }
TEST(Registry, CreateManyEntitiesAtOnce) { TEST(Registry, CreateManyEntitiesAtOnce) {
@ -548,7 +533,7 @@ TEST(Registry, CreateManyEntitiesAtOnceWithListener) {
entt::entity entities[3]; entt::entity entities[3];
listener listener; listener listener;
registry.on_construct<int>().connect<&listener::incr>(listener); registry.on_construct<int>().connect<&listener::incr<int>>(listener);
registry.create(std::begin(entities), std::end(entities)); registry.create(std::begin(entities), std::end(entities));
registry.insert(std::begin(entities), std::end(entities), 42); registry.insert(std::begin(entities), std::end(entities), 42);
registry.insert(std::begin(entities), std::end(entities), 'c'); registry.insert(std::begin(entities), std::end(entities), 'c');
@ -557,8 +542,8 @@ TEST(Registry, CreateManyEntitiesAtOnceWithListener) {
ASSERT_EQ(registry.get<char>(entities[1]), 'c'); ASSERT_EQ(registry.get<char>(entities[1]), 'c');
ASSERT_EQ(listener.counter, 3); ASSERT_EQ(listener.counter, 3);
registry.on_construct<int>().disconnect<&listener::incr>(listener); registry.on_construct<int>().disconnect<&listener::incr<int>>(listener);
registry.on_construct<empty_type>().connect<&listener::incr>(listener); registry.on_construct<empty_type>().connect<&listener::incr<empty_type>>(listener);
registry.create(std::begin(entities), std::end(entities)); registry.create(std::begin(entities), std::end(entities));
registry.insert(std::begin(entities), std::end(entities), 'a'); registry.insert(std::begin(entities), std::end(entities), 'a');
registry.insert<empty_type>(std::begin(entities), std::end(entities)); registry.insert<empty_type>(std::begin(entities), std::end(entities));
@ -575,9 +560,8 @@ TEST(Registry, CreateWithHint) {
auto e3 = registry.create(entt::entity{3}); auto e3 = registry.create(entt::entity{3});
auto e2 = registry.create(entt::entity{3}); auto e2 = registry.create(entt::entity{3});
ASSERT_EQ(e2, entt::entity{1}); ASSERT_EQ(e2, entt::entity{2});
ASSERT_FALSE(registry.valid(entt::entity{0})); ASSERT_FALSE(registry.valid(entt::entity{1}));
ASSERT_FALSE(registry.valid(entt::entity{2}));
ASSERT_EQ(e3, entt::entity{3}); ASSERT_EQ(e3, entt::entity{3});
registry.release(e2); registry.release(e2);
@ -588,10 +572,10 @@ TEST(Registry, CreateWithHint) {
e2 = registry.create(); e2 = registry.create();
auto e1 = registry.create(entt::entity{2}); auto e1 = registry.create(entt::entity{2});
ASSERT_EQ(traits_type::to_entity(e2), 1u); ASSERT_EQ(traits_type::to_entity(e2), 2u);
ASSERT_EQ(traits_type::to_version(e2), 1u); ASSERT_EQ(traits_type::to_version(e2), 1u);
ASSERT_EQ(traits_type::to_entity(e1), 2u); ASSERT_EQ(traits_type::to_entity(e1), 1u);
ASSERT_EQ(traits_type::to_version(e1), 0u); ASSERT_EQ(traits_type::to_version(e1), 0u);
registry.release(e1); registry.release(e1);
@ -656,14 +640,6 @@ TEST(Registry, CreateDestroyReleaseCornerCase) {
ASSERT_EQ(registry.current(e1), 1u); ASSERT_EQ(registry.current(e1), 1u);
} }
ENTT_DEBUG_TEST(RegistryDeathTest, CreateTooManyEntities) {
entt::basic_registry<small_entity> registry;
std::vector<small_entity> entities(entt::entt_traits<small_entity>::to_entity(entt::null));
registry.create(entities.begin(), entities.end());
ASSERT_DEATH([[maybe_unused]] const auto entity = registry.create(), "");
}
TEST(Registry, DestroyVersion) { TEST(Registry, DestroyVersion) {
entt::registry registry; entt::registry registry;
@ -690,7 +666,7 @@ ENTT_DEBUG_TEST(RegistryDeathTest, DestroyVersion) {
ASSERT_DEATH(registry.destroy(entity, 3), ""); ASSERT_DEATH(registry.destroy(entity, 3), "");
} }
TEST(Registry, DestroyRange) { TEST(Registry, RangeDestroy) {
entt::registry registry; entt::registry registry;
const auto iview = registry.view<int>(); const auto iview = registry.view<int>();
const auto icview = registry.view<int, char>(); const auto icview = registry.view<int, char>();
@ -746,24 +722,6 @@ TEST(Registry, DestroyRange) {
ASSERT_FALSE(registry.valid(entities[1u])); ASSERT_FALSE(registry.valid(entities[1u]));
ASSERT_FALSE(registry.valid(entities[2u])); ASSERT_FALSE(registry.valid(entities[2u]));
ASSERT_EQ(registry.storage<int>().size(), 0u); ASSERT_EQ(registry.storage<int>().size(), 0u);
entt::sparse_set managed{};
registry.create(std::begin(entities), std::end(entities));
managed.push(std::begin(entities), std::end(entities));
registry.insert<int>(managed.begin(), managed.end());
ASSERT_TRUE(registry.valid(managed[0u]));
ASSERT_TRUE(registry.valid(managed[1u]));
ASSERT_TRUE(registry.valid(managed[2u]));
ASSERT_EQ(registry.storage<int>().size(), 3u);
registry.destroy(managed.begin(), managed.end());
ASSERT_FALSE(registry.valid(managed[0u]));
ASSERT_FALSE(registry.valid(managed[1u]));
ASSERT_FALSE(registry.valid(managed[2u]));
ASSERT_EQ(registry.storage<int>().size(), 0u);
} }
TEST(Registry, StableDestroy) { TEST(Registry, StableDestroy) {
@ -834,7 +792,7 @@ ENTT_DEBUG_TEST(RegistryDeathTest, ReleaseVersion) {
ASSERT_DEATH(registry.release(entity, 3), ""); ASSERT_DEATH(registry.release(entity, 3), "");
} }
TEST(Registry, ReleaseRange) { TEST(Registry, RangeRelease) {
entt::registry registry; entt::registry registry;
entt::entity entities[3u]; entt::entity entities[3u];
@ -972,12 +930,10 @@ TEST(Registry, Orphans) {
TEST(Registry, View) { TEST(Registry, View) {
entt::registry registry; entt::registry registry;
entt::entity entities[3u]; auto mview = registry.view<int, char>();
auto iview = registry.view<int>(); auto iview = registry.view<int>();
auto cview = registry.view<char>(); auto cview = registry.view<char>();
auto mview = registry.view<int, char>(); entt::entity entities[3u];
auto fview = registry.view<int>(entt::exclude<char>);
registry.create(std::begin(entities), std::end(entities)); registry.create(std::begin(entities), std::end(entities));
@ -992,55 +948,10 @@ TEST(Registry, View) {
ASSERT_EQ(iview.size(), 3u); ASSERT_EQ(iview.size(), 3u);
ASSERT_EQ(cview.size(), 2u); ASSERT_EQ(cview.size(), 2u);
ASSERT_EQ(mview.size_hint(), 3u); std::size_t cnt{};
ASSERT_EQ(fview.size_hint(), 3u); mview.each([&cnt](auto...) { ++cnt; });
mview.refresh(); ASSERT_EQ(cnt, 2u);
fview.refresh();
ASSERT_EQ(mview.size_hint(), 2u);
ASSERT_EQ(fview.size_hint(), 3u);
ASSERT_NE(mview.begin(), mview.end());
ASSERT_NE(fview.begin(), fview.end());
ASSERT_EQ(std::distance(mview.begin(), mview.end()), 2);
ASSERT_EQ(std::distance(fview.begin(), fview.end()), 1);
mview.each([&entities, first = true](auto entity, auto &&...) mutable {
ASSERT_EQ(entity, entities[2u * first]);
first = false;
});
fview.each([&entities](auto entity, auto &&...) {
ASSERT_EQ(entity, entities[1u]);
});
}
TEST(Registry, ExcludeOnlyView) {
entt::registry registry;
entt::entity entities[4u];
auto view = registry.view<entt::entity>(entt::exclude<int>);
registry.create(std::begin(entities), std::end(entities));
registry.emplace<int>(entities[0u], 0);
registry.emplace<int>(entities[2u], 0);
registry.emplace<int>(entities[3u], 0);
registry.destroy(entities[3u]);
ASSERT_EQ(view.size_hint(), 4u);
ASSERT_NE(view.begin(), view.end());
// returns all matching identifiers, both in-use and available ones
ASSERT_EQ(std::distance(view.begin(), view.end()), 2);
// skips available identifiers automatically, only returns in-use elements
view.each([&entities](auto entity, auto &&...) {
ASSERT_EQ(entity, entities[1u]);
});
} }
TEST(Registry, NonOwningGroupInitOnFirstUse) { TEST(Registry, NonOwningGroupInitOnFirstUse) {
@ -1053,7 +964,7 @@ TEST(Registry, NonOwningGroupInitOnFirstUse) {
registry.emplace<char>(entities[2u], 'c'); registry.emplace<char>(entities[2u], 'c');
std::size_t cnt{}; std::size_t cnt{};
auto group = registry.group(entt::get<int, char>); auto group = registry.group<>(entt::get<int, char>);
group.each([&cnt](auto...) { ++cnt; }); group.each([&cnt](auto...) { ++cnt; });
ASSERT_FALSE((registry.owned<int, char>())); ASSERT_FALSE((registry.owned<int, char>()));
@ -1063,7 +974,7 @@ TEST(Registry, NonOwningGroupInitOnFirstUse) {
TEST(Registry, NonOwningGroupInitOnEmplace) { TEST(Registry, NonOwningGroupInitOnEmplace) {
entt::registry registry; entt::registry registry;
entt::entity entities[3u]; entt::entity entities[3u];
auto group = registry.group(entt::get<int, char>); auto group = registry.group<>(entt::get<int, char>);
registry.create(std::begin(entities), std::end(entities)); registry.create(std::begin(entities), std::end(entities));
registry.insert<int>(std::begin(entities), std::end(entities), 0); registry.insert<int>(std::begin(entities), std::end(entities), 0);
@ -1186,7 +1097,7 @@ TEST(Registry, CleanViewAfterRemoveAndClear) {
TEST(Registry, CleanNonOwningGroupViewAfterRemoveAndClear) { TEST(Registry, CleanNonOwningGroupViewAfterRemoveAndClear) {
entt::registry registry; entt::registry registry;
auto group = registry.group(entt::get<int, char>); auto group = registry.group<>(entt::get<int, char>);
const auto entity = registry.create(); const auto entity = registry.create();
registry.emplace<int>(entity, 0); registry.emplace<int>(entity, 0);
@ -1277,21 +1188,107 @@ TEST(Registry, CleanPartialOwningGroupViewAfterRemoveAndClear) {
ASSERT_EQ(group.size(), 0u); ASSERT_EQ(group.size(), 0u);
} }
ENTT_DEBUG_TEST(RegistryDeathTest, NestedGroups) { TEST(Registry, NestedGroups) {
entt::registry registry; entt::registry registry;
registry.group<int, double>(entt::get<char>); entt::entity entities[10];
ASSERT_DEATH(registry.group<int>(entt::get<char>), ""); registry.create(std::begin(entities), std::end(entities));
ASSERT_DEATH(registry.group<int>(entt::get<char, double>), ""); registry.insert<int>(std::begin(entities), std::end(entities));
ASSERT_DEATH(registry.group<int>(entt::get<char>, entt::exclude<double>), ""); registry.insert<char>(std::begin(entities), std::end(entities));
ASSERT_DEATH((registry.group<int, double>()), ""); const auto g1 = registry.group<int>(entt::get<char>, entt::exclude<double>);
ASSERT_TRUE(registry.sortable(g1));
ASSERT_EQ(g1.size(), 10u);
const auto g2 = registry.group<int>(entt::get<char>);
ASSERT_TRUE(registry.sortable(g1));
ASSERT_FALSE(registry.sortable(g2));
ASSERT_EQ(g1.size(), 10u);
ASSERT_EQ(g2.size(), 10u);
for(auto i = 0u; i < 5u; ++i) {
ASSERT_TRUE(g1.contains(entities[i * 2 + 1]));
ASSERT_TRUE(g1.contains(entities[i * 2]));
ASSERT_TRUE(g2.contains(entities[i * 2 + 1]));
ASSERT_TRUE(g2.contains(entities[i * 2]));
registry.emplace<double>(entities[i * 2]);
} }
ENTT_DEBUG_TEST(RegistryDeathTest, ConflictingGroups) { ASSERT_EQ(g1.size(), 5u);
entt::registry registry; ASSERT_EQ(g2.size(), 10u);
registry.group<char>(entt::get<int>, entt::exclude<double>); for(auto i = 0u; i < 5u; ++i) {
ASSERT_DEATH(registry.group<char>(entt::get<float>, entt::exclude<double>), ""); ASSERT_TRUE(g1.contains(entities[i * 2 + 1]));
ASSERT_FALSE(g1.contains(entities[i * 2]));
ASSERT_TRUE(g2.contains(entities[i * 2 + 1]));
ASSERT_TRUE(g2.contains(entities[i * 2]));
registry.erase<int>(entities[i * 2 + 1]);
}
ASSERT_EQ(g1.size(), 0u);
ASSERT_EQ(g2.size(), 5u);
const auto g3 = registry.group<int, float>(entt::get<char>, entt::exclude<double>);
ASSERT_FALSE(registry.sortable(g1));
ASSERT_FALSE(registry.sortable(g2));
ASSERT_TRUE(registry.sortable(g3));
ASSERT_EQ(g1.size(), 0u);
ASSERT_EQ(g2.size(), 5u);
ASSERT_EQ(g3.size(), 0u);
for(auto i = 0u; i < 5u; ++i) {
ASSERT_FALSE(g1.contains(entities[i * 2 + 1]));
ASSERT_FALSE(g1.contains(entities[i * 2]));
ASSERT_FALSE(g2.contains(entities[i * 2 + 1]));
ASSERT_TRUE(g2.contains(entities[i * 2]));
ASSERT_FALSE(g3.contains(entities[i * 2 + 1]));
ASSERT_FALSE(g3.contains(entities[i * 2]));
registry.emplace<int>(entities[i * 2 + 1]);
}
ASSERT_EQ(g1.size(), 5u);
ASSERT_EQ(g2.size(), 10u);
ASSERT_EQ(g3.size(), 0u);
for(auto i = 0u; i < 5u; ++i) {
ASSERT_TRUE(g1.contains(entities[i * 2 + 1]));
ASSERT_FALSE(g1.contains(entities[i * 2]));
ASSERT_TRUE(g2.contains(entities[i * 2 + 1]));
ASSERT_TRUE(g2.contains(entities[i * 2]));
ASSERT_FALSE(g3.contains(entities[i * 2 + 1]));
ASSERT_FALSE(g3.contains(entities[i * 2]));
registry.emplace<float>(entities[i * 2]);
}
ASSERT_EQ(g1.size(), 5u);
ASSERT_EQ(g2.size(), 10u);
ASSERT_EQ(g3.size(), 0u);
for(auto i = 0u; i < 5u; ++i) {
registry.erase<double>(entities[i * 2]);
}
ASSERT_EQ(g1.size(), 10u);
ASSERT_EQ(g2.size(), 10u);
ASSERT_EQ(g3.size(), 5u);
for(auto i = 0u; i < 5u; ++i) {
ASSERT_TRUE(g1.contains(entities[i * 2 + 1]));
ASSERT_TRUE(g1.contains(entities[i * 2]));
ASSERT_TRUE(g2.contains(entities[i * 2 + 1]));
ASSERT_TRUE(g2.contains(entities[i * 2]));
ASSERT_FALSE(g3.contains(entities[i * 2 + 1]));
ASSERT_TRUE(g3.contains(entities[i * 2]));
registry.erase<int>(entities[i * 2 + 1]);
registry.erase<int>(entities[i * 2]);
}
ASSERT_EQ(g1.size(), 0u);
ASSERT_EQ(g2.size(), 0u);
ASSERT_EQ(g3.size(), 0u);
} }
TEST(Registry, SortSingle) { TEST(Registry, SortSingle) {
@ -1382,10 +1379,10 @@ TEST(Registry, Signals) {
entt::entity entities[2u]; entt::entity entities[2u];
listener listener; listener listener;
registry.on_construct<empty_type>().connect<&listener::incr>(listener); registry.on_construct<empty_type>().connect<&listener::incr<empty_type>>(listener);
registry.on_destroy<empty_type>().connect<&listener::decr>(listener); registry.on_destroy<empty_type>().connect<&listener::decr<empty_type>>(listener);
registry.on_construct<int>().connect<&listener::incr>(listener); registry.on_construct<int>().connect<&listener::incr<int>>(listener);
registry.on_destroy<int>().connect<&listener::decr>(listener); registry.on_destroy<int>().connect<&listener::decr<int>>(listener);
registry.create(std::begin(entities), std::end(entities)); registry.create(std::begin(entities), std::end(entities));
registry.insert<empty_type>(std::begin(entities), std::end(entities)); registry.insert<empty_type>(std::begin(entities), std::end(entities));
@ -1403,16 +1400,16 @@ TEST(Registry, Signals) {
ASSERT_EQ(listener.counter, 2); ASSERT_EQ(listener.counter, 2);
ASSERT_EQ(listener.last, entities[0u]); ASSERT_EQ(listener.last, entities[0u]);
registry.on_destroy<empty_type>().disconnect<&listener::decr>(listener); registry.on_destroy<empty_type>().disconnect<&listener::decr<empty_type>>(listener);
registry.on_destroy<int>().disconnect<&listener::decr>(listener); registry.on_destroy<int>().disconnect<&listener::decr<int>>(listener);
registry.erase<empty_type, int>(entities[1u]); registry.erase<empty_type, int>(entities[1u]);
ASSERT_EQ(listener.counter, 2); ASSERT_EQ(listener.counter, 2);
ASSERT_EQ(listener.last, entities[0u]); ASSERT_EQ(listener.last, entities[0u]);
registry.on_construct<empty_type>().disconnect<&listener::incr>(listener); registry.on_construct<empty_type>().disconnect<&listener::incr<empty_type>>(listener);
registry.on_construct<int>().disconnect<&listener::incr>(listener); registry.on_construct<int>().disconnect<&listener::incr<int>>(listener);
registry.emplace<empty_type>(entities[1u]); registry.emplace<empty_type>(entities[1u]);
registry.emplace<int>(entities[1u]); registry.emplace<int>(entities[1u]);
@ -1420,8 +1417,8 @@ TEST(Registry, Signals) {
ASSERT_EQ(listener.counter, 2); ASSERT_EQ(listener.counter, 2);
ASSERT_EQ(listener.last, entities[0u]); ASSERT_EQ(listener.last, entities[0u]);
registry.on_construct<int>().connect<&listener::incr>(listener); registry.on_construct<int>().connect<&listener::incr<int>>(listener);
registry.on_destroy<int>().connect<&listener::decr>(listener); registry.on_destroy<int>().connect<&listener::decr<int>>(listener);
registry.emplace<int>(entities[0u]); registry.emplace<int>(entities[0u]);
registry.erase<int>(entities[1u]); registry.erase<int>(entities[1u]);
@ -1429,8 +1426,8 @@ TEST(Registry, Signals) {
ASSERT_EQ(listener.counter, 2); ASSERT_EQ(listener.counter, 2);
ASSERT_EQ(listener.last, entities[1u]); ASSERT_EQ(listener.last, entities[1u]);
registry.on_construct<empty_type>().connect<&listener::incr>(listener); registry.on_construct<empty_type>().connect<&listener::incr<empty_type>>(listener);
registry.on_destroy<empty_type>().connect<&listener::decr>(listener); registry.on_destroy<empty_type>().connect<&listener::decr<empty_type>>(listener);
registry.erase<empty_type>(entities[1u]); registry.erase<empty_type>(entities[1u]);
registry.emplace<empty_type>(entities[0u]); registry.emplace<empty_type>(entities[0u]);
@ -1457,8 +1454,8 @@ TEST(Registry, Signals) {
ASSERT_EQ(listener.counter, 2); ASSERT_EQ(listener.counter, 2);
ASSERT_EQ(listener.last, entities[0u]); ASSERT_EQ(listener.last, entities[0u]);
registry.on_destroy<empty_type>().disconnect<&listener::decr>(listener); registry.on_destroy<empty_type>().disconnect<&listener::decr<empty_type>>(listener);
registry.on_destroy<int>().disconnect<&listener::decr>(listener); registry.on_destroy<int>().disconnect<&listener::decr<int>>(listener);
registry.emplace_or_replace<empty_type>(entities[0u]); registry.emplace_or_replace<empty_type>(entities[0u]);
registry.emplace_or_replace<int>(entities[0u]); registry.emplace_or_replace<int>(entities[0u]);
@ -1466,8 +1463,8 @@ TEST(Registry, Signals) {
ASSERT_EQ(listener.counter, 2); ASSERT_EQ(listener.counter, 2);
ASSERT_EQ(listener.last, entities[0u]); ASSERT_EQ(listener.last, entities[0u]);
registry.on_update<empty_type>().connect<&listener::incr>(listener); registry.on_update<empty_type>().connect<&listener::incr<empty_type>>(listener);
registry.on_update<int>().connect<&listener::incr>(listener); registry.on_update<int>().connect<&listener::incr<int>>(listener);
registry.emplace_or_replace<empty_type>(entities[0u]); registry.emplace_or_replace<empty_type>(entities[0u]);
registry.emplace_or_replace<int>(entities[0u]); registry.emplace_or_replace<int>(entities[0u]);
@ -1482,84 +1479,6 @@ TEST(Registry, Signals) {
ASSERT_EQ(listener.last, entities[0u]); ASSERT_EQ(listener.last, entities[0u]);
} }
TEST(Registry, SignalsOnRuntimePool) {
using namespace entt::literals;
entt::registry registry;
const auto entity = registry.create();
listener listener;
registry.on_construct<int>("custom"_hs).connect<&listener::incr>(listener);
registry.on_update<int>("custom"_hs).connect<&listener::incr>(listener);
registry.on_destroy<int>("custom"_hs).connect<&listener::incr>(listener);
ASSERT_EQ(listener.counter, 0);
registry.emplace<int>(entity);
registry.patch<int>(entity);
registry.erase<int>(entity);
ASSERT_EQ(listener.counter, 0);
registry.storage<int>("custom"_hs).emplace(entity);
registry.storage<int>("custom"_hs).patch(entity);
registry.storage<int>("custom"_hs).erase(entity);
ASSERT_EQ(listener.counter, 3);
}
TEST(Registry, SignalsOnEntity) {
entt::registry registry;
listener listener;
registry.on_construct<entt::entity>().connect<&listener::incr>(listener);
entt::entity entity = registry.create();
entt::entity other = registry.create();
ASSERT_EQ(listener.counter, 2);
ASSERT_EQ(listener.last, other);
registry.destroy(other);
registry.destroy(entity);
ASSERT_EQ(listener.counter, 2);
ASSERT_EQ(listener.last, other);
registry.on_construct<entt::entity>().disconnect(&listener);
other = registry.create();
entity = registry.create();
ASSERT_EQ(listener.counter, 2);
ASSERT_NE(listener.last, entity);
ASSERT_NE(listener.last, other);
registry.on_update<entt::entity>().connect<&listener::decr>(listener);
registry.patch<entt::entity>(entity);
ASSERT_EQ(listener.counter, 1);
ASSERT_EQ(listener.last, entity);
registry.on_update<entt::entity>().disconnect(&listener);
registry.patch<entt::entity>(other);
ASSERT_EQ(listener.counter, 1);
ASSERT_NE(listener.last, other);
registry.on_destroy<entt::entity>().connect<&listener::decr>(listener);
registry.destroy(entity);
ASSERT_EQ(listener.counter, 0);
ASSERT_EQ(listener.last, entity);
registry.on_destroy<entt::entity>().disconnect(&listener);
registry.destroy(other);
ASSERT_EQ(listener.counter, 0);
ASSERT_NE(listener.last, other);
}
TEST(Registry, SignalWhenDestroying) { TEST(Registry, SignalWhenDestroying) {
entt::registry registry; entt::registry registry;
const auto entity = registry.create(); const auto entity = registry.create();
@ -1657,17 +1576,6 @@ TEST(Registry, Erase) {
ASSERT_EQ(registry.storage<char>().size(), 0u); ASSERT_EQ(registry.storage<char>().size(), 0u);
ASSERT_EQ(registry.storage<double>().size(), 1u); ASSERT_EQ(registry.storage<double>().size(), 1u);
registry.insert<int>(std::begin(entities) + 1, std::end(entities) - 1u);
registry.insert<char>(std::begin(entities) + 1, std::end(entities) - 1u);
ASSERT_EQ(registry.storage<int>().size(), 1u);
ASSERT_EQ(registry.storage<char>().size(), 1u);
registry.erase<int, char>(iview.begin(), iview.end());
ASSERT_EQ(registry.storage<int>().size(), 0u);
ASSERT_EQ(registry.storage<char>().size(), 0u);
registry.insert<int>(std::begin(entities), std::end(entities)); registry.insert<int>(std::begin(entities), std::end(entities));
registry.insert<char>(std::begin(entities), std::end(entities)); registry.insert<char>(std::begin(entities), std::end(entities));
@ -1735,39 +1643,6 @@ TEST(Registry, StableErase) {
ASSERT_EQ(registry.storage<double>().size(), 1u); ASSERT_EQ(registry.storage<double>().size(), 1u);
} }
TEST(Registry, EraseIf) {
using namespace entt::literals;
entt::registry registry;
const auto entity = registry.create();
registry.emplace<int>(entity);
registry.storage<int>("other"_hs).emplace(entity);
registry.emplace<char>(entity);
ASSERT_TRUE(registry.storage<int>().contains(entity));
ASSERT_TRUE(registry.storage<int>("other"_hs).contains(entity));
ASSERT_TRUE(registry.storage<char>().contains(entity));
registry.erase_if(entity, [](auto &&...) { return false; });
ASSERT_TRUE(registry.storage<int>().contains(entity));
ASSERT_TRUE(registry.storage<int>("other"_hs).contains(entity));
ASSERT_TRUE(registry.storage<char>().contains(entity));
registry.erase_if(entity, [](entt::id_type id, auto &&...) { return id == "other"_hs; });
ASSERT_TRUE(registry.storage<int>().contains(entity));
ASSERT_FALSE(registry.storage<int>("other"_hs).contains(entity));
ASSERT_TRUE(registry.storage<char>().contains(entity));
registry.erase_if(entity, [](auto, const auto &storage) { return storage.type() == entt::type_id<char>(); });
ASSERT_TRUE(registry.storage<int>().contains(entity));
ASSERT_FALSE(registry.storage<int>("other"_hs).contains(entity));
ASSERT_FALSE(registry.storage<char>().contains(entity));
}
TEST(Registry, Remove) { TEST(Registry, Remove) {
entt::registry registry; entt::registry registry;
const auto iview = registry.view<int>(); const auto iview = registry.view<int>();
@ -1814,18 +1689,6 @@ TEST(Registry, Remove) {
ASSERT_EQ(registry.storage<char>().size(), 0u); ASSERT_EQ(registry.storage<char>().size(), 0u);
ASSERT_EQ(registry.storage<double>().size(), 1u); ASSERT_EQ(registry.storage<double>().size(), 1u);
registry.insert<int>(std::begin(entities) + 1, std::end(entities) - 1u);
registry.insert<char>(std::begin(entities) + 1, std::end(entities) - 1u);
ASSERT_EQ(registry.storage<int>().size(), 1u);
ASSERT_EQ(registry.storage<char>().size(), 1u);
registry.remove<int, char>(iview.begin(), iview.end());
registry.remove<int, char>(iview.begin(), iview.end());
ASSERT_EQ(registry.storage<int>().size(), 0u);
ASSERT_EQ(registry.storage<char>().size(), 0u);
registry.insert<int>(std::begin(entities), std::end(entities)); registry.insert<int>(std::begin(entities), std::end(entities));
registry.insert<char>(std::begin(entities), std::end(entities)); registry.insert<char>(std::begin(entities), std::end(entities));
@ -1928,7 +1791,7 @@ TEST(Registry, NonOwningGroupInterleaved) {
registry.emplace<int>(entity); registry.emplace<int>(entity);
registry.emplace<char>(entity); registry.emplace<char>(entity);
const auto group = registry.group(entt::get<int, char>); const auto group = registry.group<>(entt::get<int, char>);
entity = registry.create(); entity = registry.create();
registry.emplace<int>(entity); registry.emplace<int>(entity);
@ -1982,7 +1845,7 @@ TEST(Registry, PartialOwningGroupInterleaved) {
TEST(Registry, NonOwningGroupSortInterleaved) { TEST(Registry, NonOwningGroupSortInterleaved) {
entt::registry registry; entt::registry registry;
const auto group = registry.group(entt::get<int, char>); const auto group = registry.group<>(entt::get<int, char>);
const auto e0 = registry.create(); const auto e0 = registry.create();
registry.emplace<int>(e0, 0); registry.emplace<int>(e0, 0);
@ -2146,7 +2009,7 @@ TEST(Registry, ScramblingPoolsIsAllowed) {
// thanks to @andranik3949 for pointing out this missing test // thanks to @andranik3949 for pointing out this missing test
registry.view<const int>().each([](const auto entity, const auto &value) { registry.view<const int>().each([](const auto entity, const auto &value) {
ASSERT_EQ(static_cast<int>(entt::to_integral(entity)), value); ASSERT_EQ(entt::to_integral(entity), value);
}); });
} }
@ -2158,7 +2021,7 @@ TEST(Registry, RuntimePools) {
const auto entity = registry.create(); const auto entity = registry.create();
static_assert(std::is_same_v<decltype(registry.storage<empty_type>()), entt::storage_type_t<empty_type> &>); static_assert(std::is_same_v<decltype(registry.storage<empty_type>()), entt::storage_type_t<empty_type> &>);
static_assert(std::is_same_v<decltype(std::as_const(registry).storage<empty_type>()), const entt::storage_type_t<empty_type> *>); static_assert(std::is_same_v<decltype(std::as_const(registry).storage<empty_type>()), const entt::storage_type_t<empty_type> &>);
static_assert(std::is_same_v<decltype(registry.storage("other"_hs)), entt::storage_type_t<empty_type>::base_type *>); static_assert(std::is_same_v<decltype(registry.storage("other"_hs)), entt::storage_type_t<empty_type>::base_type *>);
static_assert(std::is_same_v<decltype(std::as_const(registry).storage("other"_hs)), const entt::storage_type_t<empty_type>::base_type *>); static_assert(std::is_same_v<decltype(std::as_const(registry).storage("other"_hs)), const entt::storage_type_t<empty_type>::base_type *>);
@ -2167,7 +2030,7 @@ TEST(Registry, RuntimePools) {
ASSERT_EQ(std::as_const(registry).storage("rehto"_hs), nullptr); ASSERT_EQ(std::as_const(registry).storage("rehto"_hs), nullptr);
ASSERT_EQ(&registry.storage<empty_type>("other"_hs), &storage); ASSERT_EQ(&registry.storage<empty_type>("other"_hs), &storage);
ASSERT_NE(std::as_const(registry).storage<empty_type>(), &storage); ASSERT_NE(&std::as_const(registry).storage<empty_type>(), &storage);
ASSERT_FALSE(registry.any_of<empty_type>(entity)); ASSERT_FALSE(registry.any_of<empty_type>(entity));
ASSERT_FALSE(storage.contains(entity)); ASSERT_FALSE(storage.contains(entity));
@ -2207,7 +2070,6 @@ TEST(Registry, Storage) {
entt::registry registry; entt::registry registry;
const auto entity = registry.create(); const auto entity = registry.create();
auto &storage = registry.storage<int>("int"_hs); auto &storage = registry.storage<int>("int"_hs);
storage.emplace(entity); storage.emplace(entity);
@ -2287,7 +2149,8 @@ TEST(Registry, RegistryStorageIterator) {
TEST(Registry, RegistryStorageIteratorConversion) { TEST(Registry, RegistryStorageIteratorConversion) {
entt::registry registry; entt::registry registry;
registry.storage<int>(); const auto entity = registry.create();
registry.emplace<int>(entity);
auto proxy = registry.storage(); auto proxy = registry.storage();
auto cproxy = std::as_const(registry).storage(); auto cproxy = std::as_const(registry).storage();
@ -2313,22 +2176,6 @@ TEST(Registry, RegistryStorageIteratorConversion) {
ASSERT_NE(++cit, it); ASSERT_NE(++cit, it);
} }
TEST(Registry, VoidType) {
using namespace entt::literals;
entt::registry registry;
const auto entity = registry.create();
auto &storage = registry.storage<void>("custom"_hs);
storage.emplace(entity);
ASSERT_TRUE(registry.storage<void>().empty());
ASSERT_FALSE(registry.storage<void>("custom"_hs).empty());
ASSERT_TRUE(registry.storage<void>("custom"_hs).contains(entity));
ASSERT_EQ(registry.storage<void>().type(), entt::type_id<void>());
ASSERT_EQ(registry.storage<void>("custom"_hs).type(), entt::type_id<void>());
}
TEST(Registry, NoEtoType) { TEST(Registry, NoEtoType) {
entt::registry registry; entt::registry registry;
const auto entity = registry.create(); const auto entity = registry.create();

File diff suppressed because it is too large Load Diff

View File

@ -26,7 +26,6 @@ TEST(SparseSet, Functionalities) {
ASSERT_EQ(set.capacity(), 42u); ASSERT_EQ(set.capacity(), 42u);
ASSERT_TRUE(set.empty()); ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u); ASSERT_EQ(set.size(), 0u);
ASSERT_TRUE(set.contiguous());
ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end()); ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end());
ASSERT_EQ(set.begin(), set.end()); ASSERT_EQ(set.begin(), set.end());
ASSERT_FALSE(set.contains(entt::entity{0})); ASSERT_FALSE(set.contains(entt::entity{0}));
@ -37,11 +36,10 @@ TEST(SparseSet, Functionalities) {
ASSERT_EQ(set.capacity(), 42u); ASSERT_EQ(set.capacity(), 42u);
ASSERT_TRUE(set.empty()); ASSERT_TRUE(set.empty());
set.push(entt::entity{42}); set.emplace(entt::entity{42});
ASSERT_FALSE(set.empty()); ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 1u); ASSERT_EQ(set.size(), 1u);
ASSERT_TRUE(set.contiguous());
ASSERT_NE(std::as_const(set).begin(), std::as_const(set).end()); ASSERT_NE(std::as_const(set).begin(), std::as_const(set).end());
ASSERT_NE(set.begin(), set.end()); ASSERT_NE(set.begin(), set.end());
ASSERT_FALSE(set.contains(entt::entity{0})); ASSERT_FALSE(set.contains(entt::entity{0}));
@ -50,13 +48,12 @@ TEST(SparseSet, Functionalities) {
ASSERT_EQ(set.at(0u), entt::entity{42}); ASSERT_EQ(set.at(0u), entt::entity{42});
ASSERT_EQ(set.at(1u), static_cast<entt::entity>(entt::null)); ASSERT_EQ(set.at(1u), static_cast<entt::entity>(entt::null));
ASSERT_EQ(set[0u], entt::entity{42}); ASSERT_EQ(set[0u], entt::entity{42});
ASSERT_EQ(set.value(entt::entity{42}), nullptr); ASSERT_EQ(set.get(entt::entity{42}), nullptr);
set.erase(entt::entity{42}); set.erase(entt::entity{42});
ASSERT_TRUE(set.empty()); ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u); ASSERT_EQ(set.size(), 0u);
ASSERT_TRUE(set.contiguous());
ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end()); ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end());
ASSERT_EQ(set.begin(), set.end()); ASSERT_EQ(set.begin(), set.end());
ASSERT_FALSE(set.contains(entt::entity{0})); ASSERT_FALSE(set.contains(entt::entity{0}));
@ -64,7 +61,7 @@ TEST(SparseSet, Functionalities) {
ASSERT_EQ(set.at(0u), static_cast<entt::entity>(entt::null)); ASSERT_EQ(set.at(0u), static_cast<entt::entity>(entt::null));
ASSERT_EQ(set.at(1u), static_cast<entt::entity>(entt::null)); ASSERT_EQ(set.at(1u), static_cast<entt::entity>(entt::null));
set.push(entt::entity{42}); set.emplace(entt::entity{42});
ASSERT_FALSE(set.empty()); ASSERT_FALSE(set.empty());
ASSERT_EQ(set.index(entt::entity{42}), 0u); ASSERT_EQ(set.index(entt::entity{42}), 0u);
@ -76,7 +73,6 @@ TEST(SparseSet, Functionalities) {
ASSERT_TRUE(set.empty()); ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u); ASSERT_EQ(set.size(), 0u);
ASSERT_TRUE(set.contiguous());
ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end()); ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end());
ASSERT_EQ(set.begin(), set.end()); ASSERT_EQ(set.begin(), set.end());
ASSERT_FALSE(set.contains(entt::entity{0})); ASSERT_FALSE(set.contains(entt::entity{0}));
@ -90,11 +86,11 @@ TEST(SparseSet, Contains) {
entt::sparse_set set{entt::deletion_policy::in_place}; entt::sparse_set set{entt::deletion_policy::in_place};
set.push(entt::entity{0}); set.emplace(entt::entity{0});
set.push(entt::entity{3}); set.emplace(entt::entity{3});
set.push(entt::entity{42}); set.emplace(entt::entity{42});
set.push(entt::entity{99}); set.emplace(entt::entity{99});
set.push(traits_type::construct(1, 5)); set.emplace(traits_type::construct(1, 5));
ASSERT_FALSE(set.contains(entt::null)); ASSERT_FALSE(set.contains(entt::null));
ASSERT_FALSE(set.contains(entt::tombstone)); ASSERT_FALSE(set.contains(entt::tombstone));
@ -137,8 +133,8 @@ TEST(SparseSet, Current) {
ASSERT_EQ(set.current(traits_type::construct(0, 0)), traits_type::to_version(entt::tombstone)); ASSERT_EQ(set.current(traits_type::construct(0, 0)), traits_type::to_version(entt::tombstone));
ASSERT_EQ(set.current(traits_type::construct(3, 3)), traits_type::to_version(entt::tombstone)); ASSERT_EQ(set.current(traits_type::construct(3, 3)), traits_type::to_version(entt::tombstone));
set.push(traits_type::construct(0, 0)); set.emplace(traits_type::construct(0, 0));
set.push(traits_type::construct(3, 3)); set.emplace(traits_type::construct(3, 3));
ASSERT_NE(set.current(traits_type::construct(0, 0)), traits_type::to_version(entt::tombstone)); ASSERT_NE(set.current(traits_type::construct(0, 0)), traits_type::to_version(entt::tombstone));
ASSERT_NE(set.current(traits_type::construct(3, 3)), traits_type::to_version(entt::tombstone)); ASSERT_NE(set.current(traits_type::construct(3, 3)), traits_type::to_version(entt::tombstone));
@ -157,8 +153,8 @@ TEST(SparseSet, Index) {
entt::sparse_set set{}; entt::sparse_set set{};
set.push(traits_type::construct(0, 0)); set.emplace(traits_type::construct(0, 0));
set.push(traits_type::construct(3, 3)); set.emplace(traits_type::construct(3, 3));
ASSERT_EQ(set.index(traits_type::construct(0, 0)), 0u); ASSERT_EQ(set.index(traits_type::construct(0, 0)), 0u);
ASSERT_EQ(set.index(traits_type::construct(3, 3)), 1u); ASSERT_EQ(set.index(traits_type::construct(3, 3)), 1u);
@ -179,7 +175,7 @@ ENTT_DEBUG_TEST(SparseSetDeathTest, Index) {
TEST(SparseSet, Move) { TEST(SparseSet, Move) {
entt::sparse_set set; entt::sparse_set set;
set.push(entt::entity{42}); set.emplace(entt::entity{42});
ASSERT_TRUE(std::is_move_constructible_v<decltype(set)>); ASSERT_TRUE(std::is_move_constructible_v<decltype(set)>);
ASSERT_TRUE(std::is_move_assignable_v<decltype(set)>); ASSERT_TRUE(std::is_move_assignable_v<decltype(set)>);
@ -199,7 +195,7 @@ TEST(SparseSet, Move) {
ASSERT_EQ(other.at(0u), static_cast<entt::entity>(entt::null)); ASSERT_EQ(other.at(0u), static_cast<entt::entity>(entt::null));
other = entt::sparse_set{}; other = entt::sparse_set{};
other.push(entt::entity{3}); other.emplace(entt::entity{3});
other = std::move(set); other = std::move(set);
ASSERT_TRUE(set.empty()); ASSERT_TRUE(set.empty());
@ -212,10 +208,10 @@ TEST(SparseSet, Swap) {
entt::sparse_set set; entt::sparse_set set;
entt::sparse_set other{entt::deletion_policy::in_place}; entt::sparse_set other{entt::deletion_policy::in_place};
set.push(entt::entity{42}); set.emplace(entt::entity{42});
other.push(entt::entity{9}); other.emplace(entt::entity{9});
other.push(entt::entity{3}); other.emplace(entt::entity{3});
other.erase(entt::entity{9}); other.erase(entt::entity{9});
ASSERT_EQ(set.size(), 1u); ASSERT_EQ(set.size(), 1u);
@ -237,12 +233,12 @@ TEST(SparseSet, Pagination) {
ASSERT_EQ(set.extent(), 0u); ASSERT_EQ(set.extent(), 0u);
set.push(entt::entity{traits_type::page_size - 1u}); set.emplace(entt::entity{traits_type::page_size - 1u});
ASSERT_EQ(set.extent(), traits_type::page_size); ASSERT_EQ(set.extent(), traits_type::page_size);
ASSERT_TRUE(set.contains(entt::entity{traits_type::page_size - 1u})); ASSERT_TRUE(set.contains(entt::entity{traits_type::page_size - 1u}));
set.push(entt::entity{traits_type::page_size}); set.emplace(entt::entity{traits_type::page_size});
ASSERT_EQ(set.extent(), 2 * traits_type::page_size); ASSERT_EQ(set.extent(), 2 * traits_type::page_size);
ASSERT_TRUE(set.contains(entt::entity{traits_type::page_size - 1u})); ASSERT_TRUE(set.contains(entt::entity{traits_type::page_size - 1u}));
@ -267,17 +263,17 @@ TEST(SparseSet, Pagination) {
ASSERT_EQ(set.extent(), 2 * traits_type::page_size); ASSERT_EQ(set.extent(), 2 * traits_type::page_size);
} }
TEST(SparseSet, Push) { TEST(SparseSet, Emplace) {
entt::sparse_set set{entt::deletion_policy::in_place}; entt::sparse_set set{entt::deletion_policy::in_place};
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
ASSERT_TRUE(set.empty()); ASSERT_TRUE(set.empty());
ASSERT_NE(set.push(entities[0u]), set.end()); ASSERT_NE(set.emplace(entities[0u]), set.end());
set.erase(entities[0u]); set.erase(entities[0u]);
ASSERT_NE(set.push(entities[1u]), set.end()); ASSERT_NE(set.emplace(entities[1u]), set.end());
ASSERT_NE(set.push(entities[0u]), set.end()); ASSERT_NE(set.emplace(entities[0u]), set.end());
ASSERT_EQ(set.at(0u), entities[1u]); ASSERT_EQ(set.at(0u), entities[1u]);
ASSERT_EQ(set.at(1u), entities[0u]); ASSERT_EQ(set.at(1u), entities[0u]);
@ -286,8 +282,8 @@ TEST(SparseSet, Push) {
set.erase(std::begin(entities), std::end(entities)); set.erase(std::begin(entities), std::end(entities));
ASSERT_NE(set.push(entities[1u]), set.end()); ASSERT_NE(set.emplace(entities[1u]), set.end());
ASSERT_NE(set.push(entities[0u]), set.end()); ASSERT_NE(set.emplace(entities[0u]), set.end());
ASSERT_EQ(set.at(0u), entities[1u]); ASSERT_EQ(set.at(0u), entities[1u]);
ASSERT_EQ(set.at(1u), entities[0u]); ASSERT_EQ(set.at(1u), entities[0u]);
@ -295,16 +291,72 @@ TEST(SparseSet, Push) {
ASSERT_EQ(set.index(entities[1u]), 0u); ASSERT_EQ(set.index(entities[1u]), 0u);
} }
TEST(SparseSet, PushRange) { ENTT_DEBUG_TEST(SparseSetDeathTest, Emplace) {
entt::sparse_set set{entt::deletion_policy::in_place};
set.emplace(entt::entity{42});
ASSERT_DEATH(set.emplace(entt::entity{42}), "");
}
TEST(SparseSet, EmplaceOutOfBounds) {
using traits_type = entt::entt_traits<entt::entity>;
entt::sparse_set set{entt::deletion_policy::in_place};
entt::entity entities[2u]{entt::entity{0}, entt::entity{traits_type::page_size}};
ASSERT_NE(set.emplace(entities[0u]), set.end());
ASSERT_EQ(set.extent(), traits_type::page_size);
ASSERT_EQ(set.index(entities[0u]), 0u);
set.erase(entities[0u]);
ASSERT_NE(set.emplace(entities[1u]), set.end());
ASSERT_EQ(set.extent(), 2u * traits_type::page_size);
ASSERT_EQ(set.index(entities[1u]), 0u);
}
TEST(SparseSet, Bump) {
using traits_type = entt::entt_traits<entt::entity>;
entt::sparse_set set;
entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, traits_type::construct(9, 3)};
set.insert(std::begin(entities), std::end(entities));
ASSERT_EQ(set.current(entities[0u]), 0u);
ASSERT_EQ(set.current(entities[1u]), 0u);
ASSERT_EQ(set.current(entities[2u]), 3u);
set.bump(entities[0u]);
set.bump(traits_type::construct(traits_type::to_entity(entities[1u]), 1));
set.bump(traits_type::construct(traits_type::to_entity(entities[2u]), 0));
ASSERT_EQ(set.current(entities[0u]), 0u);
ASSERT_EQ(set.current(entities[1u]), 1u);
ASSERT_EQ(set.current(entities[2u]), 0u);
}
ENTT_DEBUG_TEST(SparseSetDeathTest, Bump) {
using traits_type = entt::entt_traits<entt::entity>;
entt::sparse_set set{entt::deletion_policy::in_place};
set.emplace(entt::entity{3});
ASSERT_DEATH(set.bump(entt::null), "");
ASSERT_DEATH(set.bump(entt::tombstone), "");
ASSERT_DEATH(set.bump(entt::entity{42}), "");
ASSERT_DEATH(set.bump(traits_type::construct(traits_type::to_entity(entt::entity{3}), traits_type::to_version(entt::tombstone))), "");
}
TEST(SparseSet, Insert) {
entt::sparse_set set{entt::deletion_policy::in_place}; entt::sparse_set set{entt::deletion_policy::in_place};
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
set.push(entt::entity{12}); set.emplace(entt::entity{12});
ASSERT_EQ(set.push(std::end(entities), std::end(entities)), set.end()); ASSERT_EQ(set.insert(std::end(entities), std::end(entities)), set.end());
ASSERT_NE(set.push(std::begin(entities), std::end(entities)), set.end()); ASSERT_NE(set.insert(std::begin(entities), std::end(entities)), set.end());
set.push(entt::entity{24}); set.emplace(entt::entity{24});
ASSERT_TRUE(set.contains(entities[0u])); ASSERT_TRUE(set.contains(entities[0u]));
ASSERT_TRUE(set.contains(entities[1u])); ASSERT_TRUE(set.contains(entities[1u]));
@ -326,7 +378,7 @@ TEST(SparseSet, PushRange) {
set.erase(std::begin(entities), std::end(entities)); set.erase(std::begin(entities), std::end(entities));
ASSERT_NE(set.push(std::rbegin(entities), std::rend(entities)), set.end()); ASSERT_NE(set.insert(std::rbegin(entities), std::rend(entities)), set.end());
ASSERT_EQ(set.size(), 6u); ASSERT_EQ(set.size(), 6u);
ASSERT_EQ(set.at(4u), entities[1u]); ASSERT_EQ(set.at(4u), entities[1u]);
@ -335,64 +387,6 @@ TEST(SparseSet, PushRange) {
ASSERT_EQ(set.index(entities[1u]), 4u); ASSERT_EQ(set.index(entities[1u]), 4u);
} }
ENTT_DEBUG_TEST(SparseSetDeathTest, Push) {
entt::sparse_set set{entt::deletion_policy::in_place};
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
set.push(entt::entity{42});
ASSERT_DEATH(set.push(entt::entity{42}), "");
ASSERT_DEATH(set.push(std::begin(entities), std::end(entities)), "");
}
TEST(SparseSet, PushOutOfBounds) {
using traits_type = entt::entt_traits<entt::entity>;
entt::sparse_set set{entt::deletion_policy::in_place};
entt::entity entities[2u]{entt::entity{0}, entt::entity{traits_type::page_size}};
ASSERT_NE(set.push(entities[0u]), set.end());
ASSERT_EQ(set.extent(), traits_type::page_size);
ASSERT_EQ(set.index(entities[0u]), 0u);
set.erase(entities[0u]);
ASSERT_NE(set.push(entities[1u]), set.end());
ASSERT_EQ(set.extent(), 2u * traits_type::page_size);
ASSERT_EQ(set.index(entities[1u]), 0u);
}
TEST(SparseSet, Bump) {
using traits_type = entt::entt_traits<entt::entity>;
entt::sparse_set set;
entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, traits_type::construct(9, 3)};
set.push(std::begin(entities), std::end(entities));
ASSERT_EQ(set.current(entities[0u]), 0u);
ASSERT_EQ(set.current(entities[1u]), 0u);
ASSERT_EQ(set.current(entities[2u]), 3u);
ASSERT_EQ(set.bump(entities[0u]), 0u);
ASSERT_EQ(set.bump(traits_type::construct(traits_type::to_entity(entities[1u]), 1)), 1u);
ASSERT_EQ(set.bump(traits_type::construct(traits_type::to_entity(entities[2u]), 0)), 0u);
ASSERT_EQ(set.current(entities[0u]), 0u);
ASSERT_EQ(set.current(entities[1u]), 1u);
ASSERT_EQ(set.current(entities[2u]), 0u);
}
ENTT_DEBUG_TEST(SparseSetDeathTest, Bump) {
using traits_type = entt::entt_traits<entt::entity>;
entt::sparse_set set{entt::deletion_policy::in_place};
set.push(entt::entity{3});
ASSERT_DEATH(set.bump(entt::null), "");
ASSERT_DEATH(set.bump(entt::tombstone), "");
ASSERT_DEATH(set.bump(entt::entity{42}), "");
ASSERT_DEATH(set.bump(traits_type::construct(traits_type::to_entity(entt::entity{3}), traits_type::to_version(entt::tombstone))), "");
}
TEST(SparseSet, Erase) { TEST(SparseSet, Erase) {
using traits_type = entt::entt_traits<entt::entity>; using traits_type = entt::entt_traits<entt::entity>;
@ -402,7 +396,7 @@ TEST(SparseSet, Erase) {
ASSERT_EQ(set.policy(), entt::deletion_policy::swap_and_pop); ASSERT_EQ(set.policy(), entt::deletion_policy::swap_and_pop);
ASSERT_TRUE(set.empty()); ASSERT_TRUE(set.empty());
set.push(std::begin(entities), std::end(entities)); set.insert(std::begin(entities), std::end(entities));
set.erase(set.begin(), set.end()); set.erase(set.begin(), set.end());
ASSERT_TRUE(set.empty()); ASSERT_TRUE(set.empty());
@ -410,7 +404,7 @@ TEST(SparseSet, Erase) {
ASSERT_EQ(set.current(entities[1u]), traits_type::to_version(entt::tombstone)); ASSERT_EQ(set.current(entities[1u]), traits_type::to_version(entt::tombstone));
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
set.push(std::begin(entities), std::end(entities)); set.insert(std::begin(entities), std::end(entities));
set.erase(entities, entities + 2u); set.erase(entities, entities + 2u);
ASSERT_FALSE(set.empty()); ASSERT_FALSE(set.empty());
@ -424,7 +418,7 @@ TEST(SparseSet, Erase) {
ASSERT_TRUE(set.empty()); ASSERT_TRUE(set.empty());
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
set.push(std::begin(entities), std::end(entities)); set.insert(std::begin(entities), std::end(entities));
std::swap(entities[1u], entities[2u]); std::swap(entities[1u], entities[2u]);
set.erase(entities, entities + 2u); set.erase(entities, entities + 2u);
@ -448,8 +442,8 @@ TEST(SparseSet, CrossErase) {
entt::sparse_set other; entt::sparse_set other;
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
set.push(std::begin(entities), std::end(entities)); set.insert(std::begin(entities), std::end(entities));
other.push(entities[1u]); other.emplace(entities[1u]);
set.erase(other.begin(), other.end()); set.erase(other.begin(), other.end());
ASSERT_TRUE(set.contains(entities[0u])); ASSERT_TRUE(set.contains(entities[0u]));
@ -467,9 +461,9 @@ TEST(SparseSet, StableErase) {
ASSERT_TRUE(set.empty()); ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u); ASSERT_EQ(set.size(), 0u);
set.push(entities[0u]); set.emplace(entities[0u]);
set.push(entities[1u]); set.emplace(entities[1u]);
set.push(entities[2u]); set.emplace(entities[2u]);
set.erase(set.begin(), set.end()); set.erase(set.begin(), set.end());
@ -482,9 +476,9 @@ TEST(SparseSet, StableErase) {
ASSERT_TRUE(set.at(1u) == entt::tombstone); ASSERT_TRUE(set.at(1u) == entt::tombstone);
ASSERT_TRUE(set.at(2u) == entt::tombstone); ASSERT_TRUE(set.at(2u) == entt::tombstone);
set.push(entities[0u]); set.emplace(entities[0u]);
set.push(entities[1u]); set.emplace(entities[1u]);
set.push(entities[2u]); set.emplace(entities[2u]);
set.erase(entities, entities + 2u); set.erase(entities, entities + 2u);
@ -503,9 +497,9 @@ TEST(SparseSet, StableErase) {
ASSERT_EQ(set.size(), 3u); ASSERT_EQ(set.size(), 3u);
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
set.push(entities[0u]); set.emplace(entities[0u]);
set.push(entities[1u]); set.emplace(entities[1u]);
set.push(entities[2u]); set.emplace(entities[2u]);
std::swap(entities[1u], entities[2u]); std::swap(entities[1u], entities[2u]);
set.erase(entities, entities + 2u); set.erase(entities, entities + 2u);
@ -533,9 +527,9 @@ TEST(SparseSet, StableErase) {
ASSERT_EQ(set.size(), 0u); ASSERT_EQ(set.size(), 0u);
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
set.push(entities[0u]); set.emplace(entities[0u]);
set.push(entities[1u]); set.emplace(entities[1u]);
set.push(entities[2u]); set.emplace(entities[2u]);
set.erase(entities[2u]); set.erase(entities[2u]);
@ -552,13 +546,13 @@ TEST(SparseSet, StableErase) {
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
ASSERT_TRUE(*set.begin() == entt::tombstone); ASSERT_TRUE(*set.begin() == entt::tombstone);
set.push(entities[0u]); set.emplace(entities[0u]);
ASSERT_EQ(*++set.begin(), entities[0u]); ASSERT_EQ(*++set.begin(), entities[0u]);
set.push(entities[1u]); set.emplace(entities[1u]);
set.push(entities[2u]); set.emplace(entities[2u]);
set.push(entt::entity{0}); set.emplace(entt::entity{0});
ASSERT_EQ(set.size(), 4u); ASSERT_EQ(set.size(), 4u);
ASSERT_EQ(*set.begin(), entt::entity{0}); ASSERT_EQ(*set.begin(), entt::entity{0});
@ -586,8 +580,8 @@ TEST(SparseSet, CrossStableErase) {
entt::sparse_set other{entt::deletion_policy::in_place}; entt::sparse_set other{entt::deletion_policy::in_place};
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
set.push(std::begin(entities), std::end(entities)); set.insert(std::begin(entities), std::end(entities));
other.push(entities[1u]); other.emplace(entities[1u]);
set.erase(other.begin(), other.end()); set.erase(other.begin(), other.end());
ASSERT_TRUE(set.contains(entities[0u])); ASSERT_TRUE(set.contains(entities[0u]));
@ -605,11 +599,11 @@ TEST(SparseSet, Remove) {
ASSERT_TRUE(set.empty()); ASSERT_TRUE(set.empty());
ASSERT_EQ(set.remove(std::begin(entities), std::end(entities)), 0u); ASSERT_EQ(set.remove(std::begin(entities), std::end(entities)), 0u);
ASSERT_FALSE(set.remove(entities[1u])); ASSERT_EQ(set.remove(entities[1u]), 0u);
ASSERT_TRUE(set.empty()); ASSERT_TRUE(set.empty());
set.push(std::begin(entities), std::end(entities)); set.insert(std::begin(entities), std::end(entities));
ASSERT_EQ(set.remove(set.begin(), set.end()), 3u); ASSERT_EQ(set.remove(set.begin(), set.end()), 3u);
ASSERT_TRUE(set.empty()); ASSERT_TRUE(set.empty());
@ -617,7 +611,7 @@ TEST(SparseSet, Remove) {
ASSERT_EQ(set.current(entities[1u]), traits_type::to_version(entt::tombstone)); ASSERT_EQ(set.current(entities[1u]), traits_type::to_version(entt::tombstone));
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
set.push(std::begin(entities), std::end(entities)); set.insert(std::begin(entities), std::end(entities));
ASSERT_EQ(set.remove(entities, entities + 2u), 2u); ASSERT_EQ(set.remove(entities, entities + 2u), 2u);
ASSERT_FALSE(set.empty()); ASSERT_FALSE(set.empty());
@ -626,12 +620,12 @@ TEST(SparseSet, Remove) {
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entities[2u])); ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entities[2u]));
ASSERT_EQ(*set.begin(), entities[2u]); ASSERT_EQ(*set.begin(), entities[2u]);
ASSERT_TRUE(set.remove(entities[2u])); ASSERT_EQ(set.remove(entities[2u]), 1u);
ASSERT_FALSE(set.remove(entities[2u])); ASSERT_EQ(set.remove(entities[2u]), 0u);
ASSERT_TRUE(set.empty()); ASSERT_TRUE(set.empty());
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
set.push(entities, entities + 2u); set.insert(entities, entities + 2u);
ASSERT_EQ(set.remove(std::begin(entities), std::end(entities)), 2u); ASSERT_EQ(set.remove(std::begin(entities), std::end(entities)), 2u);
ASSERT_EQ(set.current(entities[0u]), traits_type::to_version(entt::tombstone)); ASSERT_EQ(set.current(entities[0u]), traits_type::to_version(entt::tombstone));
@ -639,7 +633,7 @@ TEST(SparseSet, Remove) {
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
ASSERT_TRUE(set.empty()); ASSERT_TRUE(set.empty());
set.push(std::begin(entities), std::end(entities)); set.insert(std::begin(entities), std::end(entities));
std::swap(entities[1u], entities[2u]); std::swap(entities[1u], entities[2u]);
ASSERT_EQ(set.remove(entities, entities + 2u), 2u); ASSERT_EQ(set.remove(entities, entities + 2u), 2u);
@ -647,9 +641,9 @@ TEST(SparseSet, Remove) {
ASSERT_FALSE(set.empty()); ASSERT_FALSE(set.empty());
ASSERT_EQ(*set.begin(), entities[2u]); ASSERT_EQ(*set.begin(), entities[2u]);
ASSERT_FALSE(set.remove(traits_type::construct(9, 0))); ASSERT_EQ(set.remove(traits_type::construct(9, 0)), 0u);
ASSERT_FALSE(set.remove(entt::tombstone)); ASSERT_EQ(set.remove(entt::tombstone), 0u);
ASSERT_FALSE(set.remove(entt::null)); ASSERT_EQ(set.remove(entt::null), 0u);
} }
TEST(SparseSet, CrossRemove) { TEST(SparseSet, CrossRemove) {
@ -657,8 +651,8 @@ TEST(SparseSet, CrossRemove) {
entt::sparse_set other; entt::sparse_set other;
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
set.push(std::begin(entities), std::end(entities)); set.insert(std::begin(entities), std::end(entities));
other.push(entities[1u]); other.emplace(entities[1u]);
set.remove(other.begin(), other.end()); set.remove(other.begin(), other.end());
ASSERT_TRUE(set.contains(entities[0u])); ASSERT_TRUE(set.contains(entities[0u]));
@ -677,14 +671,14 @@ TEST(SparseSet, StableRemove) {
ASSERT_EQ(set.size(), 0u); ASSERT_EQ(set.size(), 0u);
ASSERT_EQ(set.remove(std::begin(entities), std::end(entities)), 0u); ASSERT_EQ(set.remove(std::begin(entities), std::end(entities)), 0u);
ASSERT_FALSE(set.remove(entities[1u])); ASSERT_EQ(set.remove(entities[1u]), 0u);
ASSERT_TRUE(set.empty()); ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u); ASSERT_EQ(set.size(), 0u);
set.push(entities[0u]); set.emplace(entities[0u]);
set.push(entities[1u]); set.emplace(entities[1u]);
set.push(entities[2u]); set.emplace(entities[2u]);
ASSERT_EQ(set.remove(set.begin(), set.end()), 3u); ASSERT_EQ(set.remove(set.begin(), set.end()), 3u);
ASSERT_EQ(set.remove(set.begin(), set.end()), 0u); ASSERT_EQ(set.remove(set.begin(), set.end()), 0u);
@ -698,9 +692,9 @@ TEST(SparseSet, StableRemove) {
ASSERT_TRUE(set.at(1u) == entt::tombstone); ASSERT_TRUE(set.at(1u) == entt::tombstone);
ASSERT_TRUE(set.at(2u) == entt::tombstone); ASSERT_TRUE(set.at(2u) == entt::tombstone);
set.push(entities[0u]); set.emplace(entities[0u]);
set.push(entities[1u]); set.emplace(entities[1u]);
set.push(entities[2u]); set.emplace(entities[2u]);
ASSERT_EQ(set.remove(entities, entities + 2u), 2u); ASSERT_EQ(set.remove(entities, entities + 2u), 2u);
ASSERT_EQ(set.remove(entities, entities + 2u), 0u); ASSERT_EQ(set.remove(entities, entities + 2u), 0u);
@ -714,16 +708,16 @@ TEST(SparseSet, StableRemove) {
ASSERT_TRUE(set.at(0u) == entt::tombstone); ASSERT_TRUE(set.at(0u) == entt::tombstone);
ASSERT_TRUE(set.at(1u) == entt::tombstone); ASSERT_TRUE(set.at(1u) == entt::tombstone);
ASSERT_TRUE(set.remove(entities[2u])); ASSERT_EQ(set.remove(entities[2u]), 1u);
ASSERT_FALSE(set.remove(entities[2u])); ASSERT_EQ(set.remove(entities[2u]), 0u);
ASSERT_FALSE(set.empty()); ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 3u); ASSERT_EQ(set.size(), 3u);
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
set.push(entities[0u]); set.emplace(entities[0u]);
set.push(entities[1u]); set.emplace(entities[1u]);
set.push(entities[2u]); set.emplace(entities[2u]);
std::swap(entities[1u], entities[2u]); std::swap(entities[1u], entities[2u]);
@ -753,19 +747,19 @@ TEST(SparseSet, StableRemove) {
ASSERT_EQ(set.size(), 0u); ASSERT_EQ(set.size(), 0u);
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
set.push(entities[0u]); set.emplace(entities[0u]);
set.push(entities[1u]); set.emplace(entities[1u]);
set.push(entities[2u]); set.emplace(entities[2u]);
ASSERT_TRUE(set.remove(entities[2u])); ASSERT_EQ(set.remove(entities[2u]), 1u);
ASSERT_FALSE(set.remove(entities[2u])); ASSERT_EQ(set.remove(entities[2u]), 0u);
ASSERT_NE(set.current(entities[0u]), traits_type::to_version(entt::tombstone)); ASSERT_NE(set.current(entities[0u]), traits_type::to_version(entt::tombstone));
ASSERT_NE(set.current(entities[1u]), traits_type::to_version(entt::tombstone)); ASSERT_NE(set.current(entities[1u]), traits_type::to_version(entt::tombstone));
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
ASSERT_TRUE(set.remove(entities[0u])); ASSERT_EQ(set.remove(entities[0u]), 1u);
ASSERT_TRUE(set.remove(entities[1u])); ASSERT_EQ(set.remove(entities[1u]), 1u);
ASSERT_EQ(set.remove(entities, entities + 2u), 0u); ASSERT_EQ(set.remove(entities, entities + 2u), 0u);
ASSERT_EQ(set.size(), 3u); ASSERT_EQ(set.size(), 3u);
@ -774,13 +768,13 @@ TEST(SparseSet, StableRemove) {
ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
ASSERT_TRUE(*set.begin() == entt::tombstone); ASSERT_TRUE(*set.begin() == entt::tombstone);
set.push(entities[0u]); set.emplace(entities[0u]);
ASSERT_EQ(*++set.begin(), entities[0u]); ASSERT_EQ(*++set.begin(), entities[0u]);
set.push(entities[1u]); set.emplace(entities[1u]);
set.push(entities[2u]); set.emplace(entities[2u]);
set.push(entt::entity{0}); set.emplace(entt::entity{0});
ASSERT_EQ(set.size(), 4u); ASSERT_EQ(set.size(), 4u);
ASSERT_EQ(*set.begin(), entt::entity{0}); ASSERT_EQ(*set.begin(), entt::entity{0});
@ -792,8 +786,8 @@ TEST(SparseSet, StableRemove) {
ASSERT_NE(set.current(entities[1u]), traits_type::to_version(entt::tombstone)); ASSERT_NE(set.current(entities[1u]), traits_type::to_version(entt::tombstone));
ASSERT_NE(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); ASSERT_NE(set.current(entities[2u]), traits_type::to_version(entt::tombstone));
ASSERT_FALSE(set.remove(traits_type::construct(9, 0))); ASSERT_EQ(set.remove(traits_type::construct(9, 0)), 0u);
ASSERT_FALSE(set.remove(entt::null)); ASSERT_EQ(set.remove(entt::null), 0u);
} }
TEST(SparseSet, CrossStableRemove) { TEST(SparseSet, CrossStableRemove) {
@ -801,8 +795,8 @@ TEST(SparseSet, CrossStableRemove) {
entt::sparse_set other{entt::deletion_policy::in_place}; entt::sparse_set other{entt::deletion_policy::in_place};
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
set.push(std::begin(entities), std::end(entities)); set.insert(std::begin(entities), std::end(entities));
other.push(entities[1u]); other.emplace(entities[1u]);
set.remove(other.begin(), other.end()); set.remove(other.begin(), other.end());
ASSERT_TRUE(set.contains(entities[0u])); ASSERT_TRUE(set.contains(entities[0u]));
@ -821,13 +815,13 @@ TEST(SparseSet, Compact) {
ASSERT_TRUE(set.empty()); ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u); ASSERT_EQ(set.size(), 0u);
set.push(entt::entity{0}); set.emplace(entt::entity{0});
set.compact(); set.compact();
ASSERT_FALSE(set.empty()); ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 1u); ASSERT_EQ(set.size(), 1u);
set.push(entt::entity{42}); set.emplace(entt::entity{42});
set.erase(entt::entity{0}); set.erase(entt::entity{0});
ASSERT_EQ(set.size(), 2u); ASSERT_EQ(set.size(), 2u);
@ -838,7 +832,7 @@ TEST(SparseSet, Compact) {
ASSERT_EQ(set.size(), 1u); ASSERT_EQ(set.size(), 1u);
ASSERT_EQ(set.index(entt::entity{42}), 0u); ASSERT_EQ(set.index(entt::entity{42}), 0u);
set.push(entt::entity{0}); set.emplace(entt::entity{0});
set.compact(); set.compact();
ASSERT_EQ(set.size(), 2u); ASSERT_EQ(set.size(), 2u);
@ -857,8 +851,8 @@ TEST(SparseSet, SwapEntity) {
entt::sparse_set set; entt::sparse_set set;
set.push(traits_type::construct(3, 5)); set.emplace(traits_type::construct(3, 5));
set.push(traits_type::construct(42, 99)); set.emplace(traits_type::construct(42, 99));
ASSERT_EQ(set.index(traits_type::construct(3, 5)), 0u); ASSERT_EQ(set.index(traits_type::construct(3, 5)), 0u);
ASSERT_EQ(set.index(traits_type::construct(42, 99)), 1u); ASSERT_EQ(set.index(traits_type::construct(42, 99)), 1u);
@ -884,9 +878,9 @@ ENTT_DEBUG_TEST(SparseSetDeathTest, SwapEntity) {
TEST(SparseSet, Clear) { TEST(SparseSet, Clear) {
entt::sparse_set set{entt::deletion_policy::in_place}; entt::sparse_set set{entt::deletion_policy::in_place};
set.push(entt::entity{3}); set.emplace(entt::entity{3});
set.push(entt::entity{42}); set.emplace(entt::entity{42});
set.push(entt::entity{9}); set.emplace(entt::entity{9});
set.erase(entt::entity{42}); set.erase(entt::entity{42});
ASSERT_FALSE(set.empty()); ASSERT_FALSE(set.empty());
@ -901,9 +895,9 @@ TEST(SparseSet, Clear) {
ASSERT_EQ(set.find(entt::entity{3}), set.end()); ASSERT_EQ(set.find(entt::entity{3}), set.end());
ASSERT_EQ(set.find(entt::entity{9}), set.end()); ASSERT_EQ(set.find(entt::entity{9}), set.end());
set.push(entt::entity{3}); set.emplace(entt::entity{3});
set.push(entt::entity{42}); set.emplace(entt::entity{42});
set.push(entt::entity{9}); set.emplace(entt::entity{9});
ASSERT_FALSE(set.empty()); ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 3u); ASSERT_EQ(set.size(), 3u);
@ -919,64 +913,6 @@ TEST(SparseSet, Clear) {
ASSERT_EQ(set.find(entt::entity{9}), set.end()); ASSERT_EQ(set.find(entt::entity{9}), set.end());
} }
TEST(SparseSet, Contiguous) {
entt::sparse_set swap_and_pop{entt::deletion_policy::swap_and_pop};
entt::sparse_set in_place{entt::deletion_policy::in_place};
const entt::entity entity{42};
const entt::entity other{3};
ASSERT_TRUE(swap_and_pop.contiguous());
ASSERT_TRUE(in_place.contiguous());
swap_and_pop.push(entity);
in_place.push(entity);
swap_and_pop.push(other);
in_place.push(other);
ASSERT_TRUE(swap_and_pop.contiguous());
ASSERT_TRUE(in_place.contiguous());
swap_and_pop.erase(entity);
in_place.erase(entity);
ASSERT_TRUE(swap_and_pop.contiguous());
ASSERT_FALSE(in_place.contiguous());
swap_and_pop.push(entity);
in_place.push(entity);
ASSERT_TRUE(swap_and_pop.contiguous());
ASSERT_TRUE(in_place.contiguous());
swap_and_pop.erase(other);
in_place.erase(other);
ASSERT_TRUE(swap_and_pop.contiguous());
ASSERT_FALSE(in_place.contiguous());
in_place.compact();
ASSERT_TRUE(swap_and_pop.contiguous());
ASSERT_TRUE(in_place.contiguous());
swap_and_pop.push(other);
in_place.push(other);
swap_and_pop.erase(entity);
in_place.erase(entity);
ASSERT_TRUE(swap_and_pop.contiguous());
ASSERT_FALSE(in_place.contiguous());
swap_and_pop.clear();
in_place.clear();
ASSERT_TRUE(swap_and_pop.contiguous());
ASSERT_TRUE(in_place.contiguous());
}
TEST(SparseSet, Iterator) { TEST(SparseSet, Iterator) {
using iterator = typename entt::sparse_set::iterator; using iterator = typename entt::sparse_set::iterator;
@ -985,20 +921,13 @@ TEST(SparseSet, Iterator) {
static_assert(std::is_same_v<iterator::reference, const entt::entity &>); static_assert(std::is_same_v<iterator::reference, const entt::entity &>);
entt::sparse_set set; entt::sparse_set set;
set.push(entt::entity{3}); set.emplace(entt::entity{3});
iterator end{set.begin()}; iterator end{set.begin()};
iterator begin{}; iterator begin{};
ASSERT_EQ(end.data(), set.data());
ASSERT_EQ(begin.data(), nullptr);
begin = set.end(); begin = set.end();
std::swap(begin, end); std::swap(begin, end);
ASSERT_EQ(end.data(), set.data());
ASSERT_EQ(begin.data(), set.data());
ASSERT_EQ(begin, set.cbegin()); ASSERT_EQ(begin, set.cbegin());
ASSERT_EQ(end, set.cend()); ASSERT_EQ(end, set.cend());
ASSERT_NE(begin, end); ASSERT_NE(begin, end);
@ -1038,7 +967,7 @@ TEST(SparseSet, Iterator) {
ASSERT_EQ(begin.index(), 0); ASSERT_EQ(begin.index(), 0);
ASSERT_EQ(end.index(), -1); ASSERT_EQ(end.index(), -1);
set.push(entt::entity{42}); set.emplace(entt::entity{42});
begin = set.begin(); begin = set.begin();
ASSERT_EQ(begin.index(), 1); ASSERT_EQ(begin.index(), 1);
@ -1056,7 +985,7 @@ TEST(SparseSet, ReverseIterator) {
static_assert(std::is_same_v<reverse_iterator::reference, const entt::entity &>); static_assert(std::is_same_v<reverse_iterator::reference, const entt::entity &>);
entt::sparse_set set; entt::sparse_set set;
set.push(entt::entity{3}); set.emplace(entt::entity{3});
reverse_iterator end{set.rbegin()}; reverse_iterator end{set.rbegin()};
reverse_iterator begin{}; reverse_iterator begin{};
@ -1102,7 +1031,7 @@ TEST(SparseSet, ReverseIterator) {
ASSERT_EQ(begin.base().index(), -1); ASSERT_EQ(begin.base().index(), -1);
ASSERT_EQ(end.base().index(), 0); ASSERT_EQ(end.base().index(), 0);
set.push(entt::entity{42}); set.emplace(entt::entity{42});
end = set.rend(); end = set.rend();
ASSERT_EQ(begin.base().index(), -1); ASSERT_EQ(begin.base().index(), -1);
@ -1116,9 +1045,9 @@ TEST(SparseSet, Find) {
using traits_type = entt::entt_traits<entt::entity>; using traits_type = entt::entt_traits<entt::entity>;
entt::sparse_set set; entt::sparse_set set;
set.push(entt::entity{3}); set.emplace(entt::entity{3});
set.push(entt::entity{42}); set.emplace(entt::entity{42});
set.push(traits_type::construct(99, 1)); set.emplace(traits_type::construct(99, 1));
ASSERT_NE(set.find(entt::entity{3}), set.end()); ASSERT_NE(set.find(entt::entity{3}), set.end());
ASSERT_NE(set.find(entt::entity{42}), set.end()); ASSERT_NE(set.find(entt::entity{42}), set.end());
@ -1140,9 +1069,9 @@ TEST(SparseSet, Find) {
TEST(SparseSet, Data) { TEST(SparseSet, Data) {
entt::sparse_set set; entt::sparse_set set;
set.push(entt::entity{3}); set.emplace(entt::entity{3});
set.push(entt::entity{12}); set.emplace(entt::entity{12});
set.push(entt::entity{42}); set.emplace(entt::entity{42});
ASSERT_EQ(set.index(entt::entity{3}), 0u); ASSERT_EQ(set.index(entt::entity{3}), 0u);
ASSERT_EQ(set.index(entt::entity{12}), 1u); ASSERT_EQ(set.index(entt::entity{12}), 1u);
@ -1157,7 +1086,7 @@ TEST(SparseSet, SortOrdered) {
entt::sparse_set set; entt::sparse_set set;
entt::entity entities[5u]{entt::entity{42}, entt::entity{12}, entt::entity{9}, entt::entity{7}, entt::entity{3}}; entt::entity entities[5u]{entt::entity{42}, entt::entity{12}, entt::entity{9}, entt::entity{7}, entt::entity{3}};
set.push(std::begin(entities), std::end(entities)); set.insert(std::begin(entities), std::end(entities));
set.sort(std::less{}); set.sort(std::less{});
ASSERT_TRUE(std::equal(std::rbegin(entities), std::rend(entities), set.begin(), set.end())); ASSERT_TRUE(std::equal(std::rbegin(entities), std::rend(entities), set.begin(), set.end()));
@ -1167,7 +1096,7 @@ TEST(SparseSet, SortReverse) {
entt::sparse_set set; entt::sparse_set set;
entt::entity entities[5u]{entt::entity{3}, entt::entity{7}, entt::entity{9}, entt::entity{12}, entt::entity{42}}; entt::entity entities[5u]{entt::entity{3}, entt::entity{7}, entt::entity{9}, entt::entity{12}, entt::entity{42}};
set.push(std::begin(entities), std::end(entities)); set.insert(std::begin(entities), std::end(entities));
set.sort(std::less{}); set.sort(std::less{});
ASSERT_TRUE(std::equal(std::begin(entities), std::end(entities), set.begin(), set.end())); ASSERT_TRUE(std::equal(std::begin(entities), std::end(entities), set.begin(), set.end()));
@ -1177,7 +1106,7 @@ TEST(SparseSet, SortUnordered) {
entt::sparse_set set; entt::sparse_set set;
entt::entity entities[5u]{entt::entity{9}, entt::entity{7}, entt::entity{3}, entt::entity{12}, entt::entity{42}}; entt::entity entities[5u]{entt::entity{9}, entt::entity{7}, entt::entity{3}, entt::entity{12}, entt::entity{42}};
set.push(std::begin(entities), std::end(entities)); set.insert(std::begin(entities), std::end(entities));
set.sort(std::less{}); set.sort(std::less{});
auto begin = set.begin(); auto begin = set.begin();
@ -1195,7 +1124,7 @@ TEST(SparseSet, SortRange) {
entt::sparse_set set{entt::deletion_policy::in_place}; entt::sparse_set set{entt::deletion_policy::in_place};
entt::entity entities[5u]{entt::entity{7}, entt::entity{9}, entt::entity{3}, entt::entity{12}, entt::entity{42}}; entt::entity entities[5u]{entt::entity{7}, entt::entity{9}, entt::entity{3}, entt::entity{12}, entt::entity{42}};
set.push(std::begin(entities), std::end(entities)); set.insert(std::begin(entities), std::end(entities));
set.erase(entities[0u]); set.erase(entities[0u]);
ASSERT_EQ(set.size(), 5u); ASSERT_EQ(set.size(), 5u);
@ -1210,7 +1139,7 @@ TEST(SparseSet, SortRange) {
set.clear(); set.clear();
set.compact(); set.compact();
set.push(std::begin(entities), std::end(entities)); set.insert(std::begin(entities), std::end(entities));
set.sort_n(0u, std::less{}); set.sort_n(0u, std::less{});
ASSERT_TRUE(std::equal(std::rbegin(entities), std::rend(entities), set.begin(), set.end())); ASSERT_TRUE(std::equal(std::rbegin(entities), std::rend(entities), set.begin(), set.end()));
@ -1238,7 +1167,7 @@ ENTT_DEBUG_TEST(SparseSetDeathTest, SortRange) {
entt::sparse_set set{entt::deletion_policy::in_place}; entt::sparse_set set{entt::deletion_policy::in_place};
entt::entity entity{42}; entt::entity entity{42};
set.push(entity); set.emplace(entity);
set.erase(entity); set.erase(entity);
ASSERT_DEATH(set.sort_n(0u, std::less{});, ""); ASSERT_DEATH(set.sort_n(0u, std::less{});, "");
@ -1250,11 +1179,11 @@ TEST(SparseSet, RespectDisjoint) {
entt::sparse_set rhs; entt::sparse_set rhs;
entt::entity lhs_entities[3u]{entt::entity{3}, entt::entity{12}, entt::entity{42}}; entt::entity lhs_entities[3u]{entt::entity{3}, entt::entity{12}, entt::entity{42}};
lhs.push(std::begin(lhs_entities), std::end(lhs_entities)); lhs.insert(std::begin(lhs_entities), std::end(lhs_entities));
ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.begin(), lhs.end())); ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.begin(), lhs.end()));
lhs.sort_as(rhs); lhs.respect(rhs);
ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.begin(), lhs.end())); ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.begin(), lhs.end()));
} }
@ -1264,15 +1193,15 @@ TEST(SparseSet, RespectOverlap) {
entt::sparse_set rhs; entt::sparse_set rhs;
entt::entity lhs_entities[3u]{entt::entity{3}, entt::entity{12}, entt::entity{42}}; entt::entity lhs_entities[3u]{entt::entity{3}, entt::entity{12}, entt::entity{42}};
lhs.push(std::begin(lhs_entities), std::end(lhs_entities)); lhs.insert(std::begin(lhs_entities), std::end(lhs_entities));
entt::entity rhs_entities[1u]{entt::entity{12}}; entt::entity rhs_entities[1u]{entt::entity{12}};
rhs.push(std::begin(rhs_entities), std::end(rhs_entities)); rhs.insert(std::begin(rhs_entities), std::end(rhs_entities));
ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.begin(), lhs.end())); ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.begin(), lhs.end()));
ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.begin(), rhs.end())); ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.begin(), rhs.end()));
lhs.sort_as(rhs); lhs.respect(rhs);
auto begin = lhs.begin(); auto begin = lhs.begin();
auto end = lhs.end(); auto end = lhs.end();
@ -1288,15 +1217,15 @@ TEST(SparseSet, RespectOrdered) {
entt::sparse_set rhs; entt::sparse_set rhs;
entt::entity lhs_entities[5u]{entt::entity{1}, entt::entity{2}, entt::entity{3}, entt::entity{4}, entt::entity{5}}; entt::entity lhs_entities[5u]{entt::entity{1}, entt::entity{2}, entt::entity{3}, entt::entity{4}, entt::entity{5}};
lhs.push(std::begin(lhs_entities), std::end(lhs_entities)); lhs.insert(std::begin(lhs_entities), std::end(lhs_entities));
entt::entity rhs_entities[6u]{entt::entity{6}, entt::entity{1}, entt::entity{2}, entt::entity{3}, entt::entity{4}, entt::entity{5}}; entt::entity rhs_entities[6u]{entt::entity{6}, entt::entity{1}, entt::entity{2}, entt::entity{3}, entt::entity{4}, entt::entity{5}};
rhs.push(std::begin(rhs_entities), std::end(rhs_entities)); rhs.insert(std::begin(rhs_entities), std::end(rhs_entities));
ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.begin(), lhs.end())); ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.begin(), lhs.end()));
ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.begin(), rhs.end())); ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.begin(), rhs.end()));
rhs.sort_as(lhs); rhs.respect(lhs);
ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.begin(), rhs.end())); ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.begin(), rhs.end()));
} }
@ -1306,15 +1235,15 @@ TEST(SparseSet, RespectReverse) {
entt::sparse_set rhs; entt::sparse_set rhs;
entt::entity lhs_entities[5u]{entt::entity{1}, entt::entity{2}, entt::entity{3}, entt::entity{4}, entt::entity{5}}; entt::entity lhs_entities[5u]{entt::entity{1}, entt::entity{2}, entt::entity{3}, entt::entity{4}, entt::entity{5}};
lhs.push(std::begin(lhs_entities), std::end(lhs_entities)); lhs.insert(std::begin(lhs_entities), std::end(lhs_entities));
entt::entity rhs_entities[6u]{entt::entity{5}, entt::entity{4}, entt::entity{3}, entt::entity{2}, entt::entity{1}, entt::entity{6}}; entt::entity rhs_entities[6u]{entt::entity{5}, entt::entity{4}, entt::entity{3}, entt::entity{2}, entt::entity{1}, entt::entity{6}};
rhs.push(std::begin(rhs_entities), std::end(rhs_entities)); rhs.insert(std::begin(rhs_entities), std::end(rhs_entities));
ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.begin(), lhs.end())); ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.begin(), lhs.end()));
ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.begin(), rhs.end())); ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.begin(), rhs.end()));
rhs.sort_as(lhs); rhs.respect(lhs);
auto begin = rhs.begin(); auto begin = rhs.begin();
auto end = rhs.end(); auto end = rhs.end();
@ -1333,15 +1262,15 @@ TEST(SparseSet, RespectUnordered) {
entt::sparse_set rhs; entt::sparse_set rhs;
entt::entity lhs_entities[5u]{entt::entity{1}, entt::entity{2}, entt::entity{3}, entt::entity{4}, entt::entity{5}}; entt::entity lhs_entities[5u]{entt::entity{1}, entt::entity{2}, entt::entity{3}, entt::entity{4}, entt::entity{5}};
lhs.push(std::begin(lhs_entities), std::end(lhs_entities)); lhs.insert(std::begin(lhs_entities), std::end(lhs_entities));
entt::entity rhs_entities[6u]{entt::entity{3}, entt::entity{2}, entt::entity{6}, entt::entity{1}, entt::entity{4}, entt::entity{5}}; entt::entity rhs_entities[6u]{entt::entity{3}, entt::entity{2}, entt::entity{6}, entt::entity{1}, entt::entity{4}, entt::entity{5}};
rhs.push(std::begin(rhs_entities), std::end(rhs_entities)); rhs.insert(std::begin(rhs_entities), std::end(rhs_entities));
ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.begin(), lhs.end())); ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.begin(), lhs.end()));
ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.begin(), rhs.end())); ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.begin(), rhs.end()));
rhs.sort_as(lhs); rhs.respect(lhs);
auto begin = rhs.begin(); auto begin = rhs.begin();
auto end = rhs.end(); auto end = rhs.end();
@ -1362,15 +1291,15 @@ TEST(SparseSet, RespectInvalid) {
entt::sparse_set rhs; entt::sparse_set rhs;
entt::entity lhs_entities[3u]{entt::entity{1}, entt::entity{2}, traits_type::construct(3, 1)}; entt::entity lhs_entities[3u]{entt::entity{1}, entt::entity{2}, traits_type::construct(3, 1)};
lhs.push(std::begin(lhs_entities), std::end(lhs_entities)); lhs.insert(std::begin(lhs_entities), std::end(lhs_entities));
entt::entity rhs_entities[3u]{entt::entity{2}, entt::entity{1}, traits_type::construct(3, 2)}; entt::entity rhs_entities[3u]{entt::entity{2}, entt::entity{1}, traits_type::construct(3, 2)};
rhs.push(std::begin(rhs_entities), std::end(rhs_entities)); rhs.insert(std::begin(rhs_entities), std::end(rhs_entities));
ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.begin(), lhs.end())); ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.begin(), lhs.end()));
ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.begin(), rhs.end())); ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.begin(), rhs.end()));
rhs.sort_as(lhs); rhs.respect(lhs);
auto begin = rhs.begin(); auto begin = rhs.begin();
auto end = rhs.end(); auto end = rhs.end();
@ -1386,7 +1315,7 @@ TEST(SparseSet, RespectInvalid) {
TEST(SparseSet, CanModifyDuringIteration) { TEST(SparseSet, CanModifyDuringIteration) {
entt::sparse_set set; entt::sparse_set set;
set.push(entt::entity{0}); set.emplace(entt::entity{0});
ASSERT_EQ(set.capacity(), 1u); ASSERT_EQ(set.capacity(), 1u);
@ -1409,8 +1338,8 @@ TEST(SparseSet, CustomAllocator) {
ASSERT_EQ(set.capacity(), 1u); ASSERT_EQ(set.capacity(), 1u);
set.push(entt::entity{0}); set.emplace(entt::entity{0});
set.push(entt::entity{1}); set.emplace(entt::entity{1});
entt::basic_sparse_set<entt::entity, test::throwing_allocator<entt::entity>> other{std::move(set), allocator}; entt::basic_sparse_set<entt::entity, test::throwing_allocator<entt::entity>> other{std::move(set), allocator};
@ -1460,11 +1389,11 @@ TEST(SparseSet, ThrowingAllocator) {
test::throwing_allocator<entt::entity>::trigger_on_allocate = true; test::throwing_allocator<entt::entity>::trigger_on_allocate = true;
ASSERT_THROW(set.push(entt::entity{0}), test::throwing_allocator<entt::entity>::exception_type); ASSERT_THROW(set.emplace(entt::entity{0}), test::throwing_allocator<entt::entity>::exception_type);
ASSERT_EQ(set.extent(), traits_type::page_size); ASSERT_EQ(set.extent(), traits_type::page_size);
ASSERT_EQ(set.capacity(), 0u); ASSERT_EQ(set.capacity(), 0u);
set.push(entt::entity{0}); set.emplace(entt::entity{0});
test::throwing_allocator<entt::entity>::trigger_on_allocate = true; test::throwing_allocator<entt::entity>::trigger_on_allocate = true;
ASSERT_THROW(set.reserve(2u), test::throwing_allocator<entt::entity>::exception_type); ASSERT_THROW(set.reserve(2u), test::throwing_allocator<entt::entity>::exception_type);
@ -1474,7 +1403,7 @@ TEST(SparseSet, ThrowingAllocator) {
test::throwing_allocator<entt::entity>::trigger_on_allocate = true; test::throwing_allocator<entt::entity>::trigger_on_allocate = true;
ASSERT_THROW(set.push(entt::entity{1}), test::throwing_allocator<entt::entity>::exception_type); ASSERT_THROW(set.emplace(entt::entity{1}), test::throwing_allocator<entt::entity>::exception_type);
ASSERT_EQ(set.extent(), traits_type::page_size); ASSERT_EQ(set.extent(), traits_type::page_size);
ASSERT_TRUE(set.contains(entt::entity{0})); ASSERT_TRUE(set.contains(entt::entity{0}));
ASSERT_FALSE(set.contains(entt::entity{1})); ASSERT_FALSE(set.contains(entt::entity{1}));
@ -1483,7 +1412,7 @@ TEST(SparseSet, ThrowingAllocator) {
entt::entity entities[2u]{entt::entity{1}, entt::entity{traits_type::page_size}}; entt::entity entities[2u]{entt::entity{1}, entt::entity{traits_type::page_size}};
test::throwing_allocator<entt::entity>::trigger_after_allocate = true; test::throwing_allocator<entt::entity>::trigger_after_allocate = true;
ASSERT_THROW(set.push(std::begin(entities), std::end(entities)), test::throwing_allocator<entt::entity>::exception_type); ASSERT_THROW(set.insert(std::begin(entities), std::end(entities)), test::throwing_allocator<entt::entity>::exception_type);
ASSERT_EQ(set.extent(), 2 * traits_type::page_size); ASSERT_EQ(set.extent(), 2 * traits_type::page_size);
ASSERT_TRUE(set.contains(entt::entity{0})); ASSERT_TRUE(set.contains(entt::entity{0}));
ASSERT_TRUE(set.contains(entt::entity{1})); ASSERT_TRUE(set.contains(entt::entity{1}));
@ -1491,7 +1420,7 @@ TEST(SparseSet, ThrowingAllocator) {
ASSERT_EQ(set.capacity(), 2u); ASSERT_EQ(set.capacity(), 2u);
ASSERT_EQ(set.size(), 2u); ASSERT_EQ(set.size(), 2u);
set.push(entities[1u]); set.emplace(entities[1u]);
ASSERT_TRUE(set.contains(entt::entity{traits_type::page_size})); ASSERT_TRUE(set.contains(entt::entity{traits_type::page_size}));
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,628 +0,0 @@
#include <type_traits>
#include <utility>
#include <gtest/gtest.h>
#include <entt/entity/storage.hpp>
#include "../common/config.h"
#include "../common/throwing_allocator.hpp"
TEST(StorageEntity, TypeAndPolicy) {
entt::storage<entt::entity> pool;
ASSERT_EQ(pool.type(), entt::type_id<entt::entity>());
ASSERT_EQ(pool.policy(), entt::deletion_policy::swap_and_pop);
}
TEST(StorageEntity, Functionalities) {
entt::entity entities[2u]{entt::entity{0}, entt::entity{1}};
entt::storage<entt::entity> pool;
ASSERT_TRUE(pool.empty());
ASSERT_EQ(pool.size(), 0u);
ASSERT_EQ(pool.in_use(), 0u);
ASSERT_EQ(*pool.push(entt::null), entities[0u]);
ASSERT_EQ(*pool.push(entt::tombstone), entities[1u]);
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.in_use(), 2u);
pool.in_use(1u);
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.in_use(), 1u);
ASSERT_NO_THROW(pool.get(entities[0u]));
ASSERT_EQ(pool.get_as_tuple(entities[0u]), std::tuple<>{});
pool.erase(entities[0u]);
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.in_use(), 0u);
}
ENTT_DEBUG_TEST(StorageEntityDeathTest, Get) {
entt::storage<entt::entity> pool;
pool.emplace(entt::entity{99});
ASSERT_DEATH(pool.get(entt::entity{3}), "");
ASSERT_DEATH([[maybe_unused]] auto tup = pool.get_as_tuple(entt::entity{3}), "");
ASSERT_NO_THROW(pool.get(entt::entity{99}));
ASSERT_NO_THROW([[maybe_unused]] auto tup = pool.get_as_tuple(entt::entity{99}));
pool.erase(entt::entity{99});
ASSERT_DEATH(pool.get(entt::entity{99}), "");
ASSERT_DEATH([[maybe_unused]] auto tup = pool.get_as_tuple(entt::entity{99}), "");
}
TEST(StorageEntity, Move) {
entt::storage<entt::entity> pool;
pool.push(entt::entity{1});
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.in_use(), 1u);
ASSERT_TRUE(std::is_move_constructible_v<decltype(pool)>);
ASSERT_TRUE(std::is_move_assignable_v<decltype(pool)>);
entt::storage<entt::entity> other{std::move(pool)};
ASSERT_EQ(pool.size(), 0u);
ASSERT_EQ(other.size(), 2u);
ASSERT_EQ(pool.in_use(), 0u);
ASSERT_EQ(other.in_use(), 1u);
ASSERT_EQ(pool.at(0u), static_cast<entt::entity>(entt::null));
ASSERT_EQ(other.at(0u), entt::entity{1});
pool = std::move(other);
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(other.size(), 0u);
ASSERT_EQ(pool.in_use(), 1u);
ASSERT_EQ(other.in_use(), 0u);
ASSERT_EQ(pool.at(0u), entt::entity{1});
ASSERT_EQ(other.at(0u), static_cast<entt::entity>(entt::null));
other = entt::storage<entt::entity>{};
other.push(entt::entity{3});
other = std::move(pool);
ASSERT_EQ(pool.size(), 0u);
ASSERT_EQ(other.size(), 2u);
ASSERT_EQ(pool.in_use(), 0u);
ASSERT_EQ(other.in_use(), 1u);
ASSERT_EQ(pool.at(0u), static_cast<entt::entity>(entt::null));
ASSERT_EQ(other.at(0u), entt::entity{1});
other.clear();
ASSERT_EQ(other.size(), 0u);
ASSERT_EQ(other.in_use(), 0u);
ASSERT_EQ(*other.push(entt::null), entt::entity{0});
}
TEST(StorageEntity, Swap) {
entt::storage<entt::entity> pool;
entt::storage<entt::entity> other;
pool.push(entt::entity{1});
other.push(entt::entity{2});
other.push(entt::entity{0});
other.erase(entt::entity{2});
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(other.size(), 3u);
ASSERT_EQ(pool.in_use(), 1u);
ASSERT_EQ(other.in_use(), 1u);
pool.swap(other);
ASSERT_EQ(pool.size(), 3u);
ASSERT_EQ(other.size(), 2u);
ASSERT_EQ(pool.in_use(), 1u);
ASSERT_EQ(other.in_use(), 1u);
ASSERT_EQ(pool.at(0u), entt::entity{0});
ASSERT_EQ(other.at(0u), entt::entity{1});
pool.clear();
other.clear();
ASSERT_EQ(pool.size(), 0u);
ASSERT_EQ(other.size(), 0u);
ASSERT_EQ(pool.in_use(), 0u);
ASSERT_EQ(other.in_use(), 0u);
ASSERT_EQ(*other.push(entt::null), entt::entity{0});
}
TEST(StorageEntity, Push) {
using traits_type = entt::entt_traits<entt::entity>;
entt::storage<entt::entity> pool;
ASSERT_EQ(*pool.push(entt::null), entt::entity{0});
ASSERT_EQ(*pool.push(entt::tombstone), entt::entity{1});
ASSERT_EQ(*pool.push(entt::entity{0}), entt::entity{2});
ASSERT_EQ(*pool.push(traits_type::construct(1, 1)), entt::entity{3});
ASSERT_EQ(*pool.push(traits_type::construct(5, 3)), traits_type::construct(5, 3));
ASSERT_LT(pool.index(entt::entity{0}), pool.in_use());
ASSERT_LT(pool.index(entt::entity{1}), pool.in_use());
ASSERT_LT(pool.index(entt::entity{2}), pool.in_use());
ASSERT_LT(pool.index(entt::entity{3}), pool.in_use());
ASSERT_GE(pool.index(entt::entity{4}), pool.in_use());
ASSERT_LT(pool.index(traits_type::construct(5, 3)), pool.in_use());
ASSERT_EQ(*pool.push(traits_type::construct(4, 42)), traits_type::construct(4, 42));
ASSERT_EQ(*pool.push(traits_type::construct(4, 43)), entt::entity{6});
entt::entity entities[2u]{entt::entity{1}, traits_type::construct(5, 3)};
pool.erase(entities, entities + 2u);
pool.erase(entt::entity{2});
ASSERT_EQ(pool.current(entities[0u]), 1);
ASSERT_EQ(pool.current(entities[1u]), 4);
ASSERT_EQ(pool.current(entt::entity{2}), 1);
ASSERT_LT(pool.index(entt::entity{0}), pool.in_use());
ASSERT_GE(pool.index(traits_type::construct(1, 1)), pool.in_use());
ASSERT_GE(pool.index(traits_type::construct(2, 1)), pool.in_use());
ASSERT_LT(pool.index(entt::entity{3}), pool.in_use());
ASSERT_LT(pool.index(traits_type::construct(4, 42)), pool.in_use());
ASSERT_GE(pool.index(traits_type::construct(5, 4)), pool.in_use());
ASSERT_EQ(*pool.push(entt::null), traits_type::construct(2, 1));
ASSERT_EQ(*pool.push(traits_type::construct(1, 3)), traits_type::construct(1, 3));
ASSERT_EQ(*pool.push(entt::null), traits_type::construct(5, 4));
ASSERT_EQ(*pool.push(entt::null), entt::entity{7});
}
TEST(StorageEntity, Emplace) {
using traits_type = entt::entt_traits<entt::entity>;
entt::storage<entt::entity> pool;
entt::entity entities[2u]{};
ASSERT_EQ(pool.emplace(), entt::entity{0});
ASSERT_EQ(pool.emplace(entt::null), entt::entity{1});
ASSERT_EQ(pool.emplace(entt::tombstone), entt::entity{2});
ASSERT_EQ(pool.emplace(entt::entity{0}), entt::entity{3});
ASSERT_EQ(pool.emplace(traits_type::construct(1, 1)), entt::entity{4});
ASSERT_EQ(pool.emplace(traits_type::construct(6, 3)), traits_type::construct(6, 3));
ASSERT_LT(pool.index(entt::entity{0}), pool.in_use());
ASSERT_LT(pool.index(entt::entity{1}), pool.in_use());
ASSERT_LT(pool.index(entt::entity{2}), pool.in_use());
ASSERT_LT(pool.index(entt::entity{3}), pool.in_use());
ASSERT_LT(pool.index(entt::entity{4}), pool.in_use());
ASSERT_GE(pool.index(entt::entity{5}), pool.in_use());
ASSERT_LT(pool.index(traits_type::construct(6, 3)), pool.in_use());
ASSERT_EQ(pool.emplace(traits_type::construct(5, 42)), traits_type::construct(5, 42));
ASSERT_EQ(pool.emplace(traits_type::construct(5, 43)), entt::entity{7});
pool.erase(entt::entity{2});
ASSERT_EQ(pool.emplace(), traits_type::construct(2, 1));
pool.erase(traits_type::construct(2, 1));
pool.insert(entities, entities + 2u);
ASSERT_EQ(entities[0u], traits_type::construct(2, 2));
ASSERT_EQ(entities[1u], entt::entity{8});
}
TEST(StorageEntity, Patch) {
entt::storage<entt::entity> pool;
const auto entity = pool.emplace();
int counter = 0;
auto callback = [&counter]() { ++counter; };
ASSERT_EQ(counter, 0);
pool.patch(entity);
pool.patch(entity, callback);
pool.patch(entity, callback, callback);
ASSERT_EQ(counter, 3);
}
ENTT_DEBUG_TEST(StorageEntityDeathTest, Patch) {
entt::storage<entt::entity> pool;
ASSERT_DEATH(pool.patch(entt::null), "");
}
TEST(StorageEntity, Insert) {
entt::storage<entt::entity> pool;
entt::entity entities[2u]{};
pool.insert(std::begin(entities), std::end(entities));
ASSERT_TRUE(pool.contains(entities[0u]));
ASSERT_TRUE(pool.contains(entities[1u]));
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.in_use(), 2u);
pool.erase(std::begin(entities), std::end(entities));
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.in_use(), 0u);
pool.insert(entities, entities + 1u);
ASSERT_TRUE(pool.contains(entities[0u]));
ASSERT_FALSE(pool.contains(entities[1u]));
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.in_use(), 1u);
}
TEST(StorageEntity, Pack) {
entt::storage<entt::entity> pool;
entt::entity entities[3u]{entt::entity{1}, entt::entity{3}, entt::entity{42}};
pool.push(entities, entities + 3u);
std::swap(entities[0u], entities[1u]);
const auto len = pool.pack(entities + 1u, entities + 3u);
auto it = pool.each().cbegin().base();
ASSERT_NE(it, pool.cbegin());
ASSERT_NE(it, pool.cend());
ASSERT_EQ(len, 2u);
ASSERT_NE(it + len, pool.cend());
ASSERT_EQ(it + len + 1u, pool.cend());
ASSERT_EQ(*it++, entities[1u]);
ASSERT_EQ(*it++, entities[2u]);
ASSERT_NE(it, pool.cend());
ASSERT_EQ(*it++, entities[0u]);
ASSERT_EQ(it, pool.cend());
}
TEST(StorageEntity, Iterable) {
using iterator = typename entt::storage<entt::entity>::iterable::iterator;
static_assert(std::is_same_v<iterator::value_type, std::tuple<entt::entity>>);
static_assert(std::is_same_v<typename iterator::pointer, entt::input_iterator_pointer<std::tuple<entt::entity>>>);
static_assert(std::is_same_v<typename iterator::reference, typename iterator::value_type>);
entt::storage<entt::entity> pool;
pool.emplace(entt::entity{1});
pool.emplace(entt::entity{3});
pool.emplace(entt::entity{42});
pool.erase(entt::entity{3});
auto iterable = pool.each();
iterator end{iterable.begin()};
iterator begin{};
begin = iterable.end();
std::swap(begin, end);
ASSERT_EQ(begin, iterable.begin());
ASSERT_EQ(end, iterable.end());
ASSERT_NE(begin, end);
ASSERT_NE(begin.base(), pool.begin());
ASSERT_EQ(begin.base(), pool.end() - pool.in_use());
ASSERT_EQ(end.base(), pool.end());
ASSERT_EQ(std::get<0>(*begin.operator->().operator->()), entt::entity{42});
ASSERT_EQ(std::get<0>(*begin), entt::entity{42});
ASSERT_EQ(begin++, iterable.begin());
ASSERT_EQ(begin.base(), pool.end() - 1);
ASSERT_EQ(++begin, iterable.end());
ASSERT_EQ(begin.base(), pool.end());
for(auto [entity]: iterable) {
static_assert(std::is_same_v<decltype(entity), entt::entity>);
ASSERT_TRUE(entity != entt::entity{3});
}
}
TEST(StorageEntity, ConstIterable) {
using iterator = typename entt::storage<entt::entity>::const_iterable::iterator;
static_assert(std::is_same_v<iterator::value_type, std::tuple<entt::entity>>);
static_assert(std::is_same_v<typename iterator::pointer, entt::input_iterator_pointer<std::tuple<entt::entity>>>);
static_assert(std::is_same_v<typename iterator::reference, typename iterator::value_type>);
entt::storage<entt::entity> pool;
pool.emplace(entt::entity{1});
pool.emplace(entt::entity{3});
pool.emplace(entt::entity{42});
pool.erase(entt::entity{3});
auto iterable = std::as_const(pool).each();
iterator end{iterable.cbegin()};
iterator begin{};
begin = iterable.cend();
std::swap(begin, end);
ASSERT_EQ(begin, iterable.cbegin());
ASSERT_EQ(end, iterable.cend());
ASSERT_NE(begin, end);
ASSERT_NE(begin.base(), pool.begin());
ASSERT_EQ(begin.base(), pool.end() - pool.in_use());
ASSERT_EQ(end.base(), pool.end());
ASSERT_EQ(std::get<0>(*begin.operator->().operator->()), entt::entity{42});
ASSERT_EQ(std::get<0>(*begin), entt::entity{42});
ASSERT_EQ(begin++, iterable.begin());
ASSERT_EQ(begin.base(), pool.end() - 1);
ASSERT_EQ(++begin, iterable.end());
ASSERT_EQ(begin.base(), pool.end());
for(auto [entity]: iterable) {
static_assert(std::is_same_v<decltype(entity), entt::entity>);
ASSERT_TRUE(entity != entt::entity{3});
}
}
TEST(StorageEntity, IterableIteratorConversion) {
entt::storage<entt::entity> pool;
pool.emplace(entt::entity{3});
typename entt::storage<entt::entity>::iterable::iterator it = pool.each().begin();
typename entt::storage<entt::entity>::const_iterable::iterator cit = it;
static_assert(std::is_same_v<decltype(*it), std::tuple<entt::entity>>);
static_assert(std::is_same_v<decltype(*cit), std::tuple<entt::entity>>);
ASSERT_EQ(it, cit);
ASSERT_NE(++cit, it);
}
TEST(StorageEntity, IterableAlgorithmCompatibility) {
entt::storage<entt::entity> pool;
pool.emplace(entt::entity{3});
const auto iterable = pool.each();
const auto it = std::find_if(iterable.begin(), iterable.end(), [](auto args) { return std::get<0>(args) == entt::entity{3}; });
ASSERT_EQ(std::get<0>(*it), entt::entity{3});
}
TEST(StorageEntity, ReverseIterable) {
using iterator = typename entt::storage<entt::entity>::reverse_iterable::iterator;
static_assert(std::is_same_v<iterator::value_type, std::tuple<entt::entity>>);
static_assert(std::is_same_v<typename iterator::pointer, entt::input_iterator_pointer<std::tuple<entt::entity>>>);
static_assert(std::is_same_v<typename iterator::reference, typename iterator::value_type>);
entt::storage<entt::entity> pool;
pool.emplace(entt::entity{1});
pool.emplace(entt::entity{3});
pool.emplace(entt::entity{42});
pool.erase(entt::entity{3});
auto iterable = pool.reach();
iterator end{iterable.begin()};
iterator begin{};
begin = iterable.end();
std::swap(begin, end);
ASSERT_EQ(begin, iterable.begin());
ASSERT_EQ(end, iterable.end());
ASSERT_NE(begin, end);
ASSERT_EQ(begin.base(), pool.rbegin());
ASSERT_EQ(end.base(), pool.rbegin() + pool.in_use());
ASSERT_NE(end.base(), pool.rend());
ASSERT_EQ(std::get<0>(*begin.operator->().operator->()), entt::entity{1});
ASSERT_EQ(std::get<0>(*begin), entt::entity{1});
ASSERT_EQ(begin++, iterable.begin());
ASSERT_EQ(begin.base(), pool.rbegin() + 1);
ASSERT_EQ(++begin, iterable.end());
ASSERT_EQ(begin.base(), pool.rbegin() + 2);
for(auto [entity]: iterable) {
static_assert(std::is_same_v<decltype(entity), entt::entity>);
ASSERT_TRUE(entity != entt::entity{3});
}
}
TEST(StorageEntity, ReverseConstIterable) {
using iterator = typename entt::storage<entt::entity>::const_reverse_iterable::iterator;
static_assert(std::is_same_v<iterator::value_type, std::tuple<entt::entity>>);
static_assert(std::is_same_v<typename iterator::pointer, entt::input_iterator_pointer<std::tuple<entt::entity>>>);
static_assert(std::is_same_v<typename iterator::reference, typename iterator::value_type>);
entt::storage<entt::entity> pool;
pool.emplace(entt::entity{1});
pool.emplace(entt::entity{3});
pool.emplace(entt::entity{42});
pool.erase(entt::entity{3});
auto iterable = std::as_const(pool).reach();
iterator end{iterable.cbegin()};
iterator begin{};
begin = iterable.cend();
std::swap(begin, end);
ASSERT_EQ(begin, iterable.cbegin());
ASSERT_EQ(end, iterable.cend());
ASSERT_NE(begin, end);
ASSERT_EQ(begin.base(), pool.rbegin());
ASSERT_EQ(end.base(), pool.rbegin() + pool.in_use());
ASSERT_NE(end.base(), pool.rend());
ASSERT_EQ(std::get<0>(*begin.operator->().operator->()), entt::entity{1});
ASSERT_EQ(std::get<0>(*begin), entt::entity{1});
ASSERT_EQ(begin++, iterable.begin());
ASSERT_EQ(begin.base(), pool.rbegin() + 1);
ASSERT_EQ(++begin, iterable.end());
ASSERT_EQ(begin.base(), pool.rbegin() + 2);
for(auto [entity]: iterable) {
static_assert(std::is_same_v<decltype(entity), entt::entity>);
ASSERT_TRUE(entity != entt::entity{3});
}
}
TEST(StorageEntity, ReverseIterableIteratorConversion) {
entt::storage<entt::entity> pool;
pool.emplace(entt::entity{3});
typename entt::storage<entt::entity>::reverse_iterable::iterator it = pool.reach().begin();
typename entt::storage<entt::entity>::const_reverse_iterable::iterator cit = it;
static_assert(std::is_same_v<decltype(*it), std::tuple<entt::entity>>);
static_assert(std::is_same_v<decltype(*cit), std::tuple<entt::entity>>);
ASSERT_EQ(it, cit);
ASSERT_NE(++cit, it);
}
TEST(StorageEntity, ReverseIterableAlgorithmCompatibility) {
entt::storage<entt::entity> pool;
pool.emplace(entt::entity{3});
const auto iterable = pool.reach();
const auto it = std::find_if(iterable.begin(), iterable.end(), [](auto args) { return std::get<0>(args) == entt::entity{3}; });
ASSERT_EQ(std::get<0>(*it), entt::entity{3});
}
TEST(StorageEntity, SwapElements) {
entt::storage<entt::entity> pool;
pool.push(entt::entity{0});
pool.push(entt::entity{1});
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.in_use(), 2u);
ASSERT_TRUE(pool.contains(entt::entity{0}));
ASSERT_TRUE(pool.contains(entt::entity{1}));
ASSERT_EQ(*pool.begin(), entt::entity{1});
ASSERT_EQ(*++pool.begin(), entt::entity{0});
pool.swap_elements(entt::entity{0}, entt::entity{1});
ASSERT_EQ(*pool.begin(), entt::entity{0});
ASSERT_EQ(*++pool.begin(), entt::entity{1});
}
ENTT_DEBUG_TEST(StorageEntityDeathTest, SwapElements) {
entt::storage<entt::entity> pool;
pool.push(entt::entity{1});
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.in_use(), 1u);
ASSERT_TRUE(pool.contains(entt::entity{0}));
ASSERT_TRUE(pool.contains(entt::entity{1}));
ASSERT_DEATH(pool.swap_elements(entt::entity{0}, entt::entity{1}), "");
}
ENTT_DEBUG_TEST(StorageEntityDeathTest, InUse) {
entt::storage<entt::entity> pool;
pool.push(entt::entity{0});
pool.push(entt::entity{1});
ASSERT_DEATH(pool.in_use(3u), "");
}
ENTT_DEBUG_TEST(StorageEntityDeathTest, SortAndRespect) {
entt::storage<entt::entity> pool;
entt::storage<entt::entity> other;
pool.push(entt::entity{1});
pool.push(entt::entity{2});
pool.erase(entt::entity{2});
other.push(entt::entity{2});
ASSERT_DEATH(pool.sort([](auto...) { return true; }), "");
ASSERT_DEATH(pool.sort_as(other), "");
}
TEST(StorageEntity, CustomAllocator) {
test::throwing_allocator<entt::entity> allocator{};
entt::basic_storage<entt::entity, entt::entity, test::throwing_allocator<entt::entity>> pool{allocator};
pool.reserve(1u);
ASSERT_EQ(pool.size(), 0u);
ASSERT_EQ(pool.in_use(), 0u);
pool.push(entt::entity{0});
pool.push(entt::entity{1});
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.in_use(), 2u);
decltype(pool) other{std::move(pool), allocator};
ASSERT_TRUE(pool.empty());
ASSERT_FALSE(other.empty());
ASSERT_EQ(pool.size(), 0u);
ASSERT_EQ(other.size(), 2u);
ASSERT_EQ(pool.in_use(), 0u);
ASSERT_EQ(other.in_use(), 2u);
pool = std::move(other);
ASSERT_FALSE(pool.empty());
ASSERT_TRUE(other.empty());
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(other.size(), 0u);
ASSERT_EQ(pool.in_use(), 2u);
ASSERT_EQ(other.in_use(), 0u);
pool.swap(other);
pool = std::move(other);
ASSERT_FALSE(pool.empty());
ASSERT_TRUE(other.empty());
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(other.size(), 0u);
ASSERT_EQ(pool.in_use(), 2u);
ASSERT_EQ(other.in_use(), 0u);
pool.clear();
ASSERT_EQ(pool.size(), 0u);
ASSERT_EQ(pool.in_use(), 0u);
}

View File

@ -1,6 +1,5 @@
#include <iterator> #include <iterator>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <entt/entity/mixin.hpp>
#include <entt/entity/registry.hpp> #include <entt/entity/registry.hpp>
#include <entt/entity/storage.hpp> #include <entt/entity/storage.hpp>
#include "../common/throwing_allocator.hpp" #include "../common/throwing_allocator.hpp"
@ -31,20 +30,9 @@ void listener(counter &counter, Registry &, typename Registry::entity_type) {
++counter.value; ++counter.value;
} }
struct empty_each_tag final {}; TEST(SighStorageMixin, GenericType) {
template<>
struct entt::basic_storage<empty_each_tag, entt::entity, std::allocator<empty_each_tag>>: entt::basic_storage<void, entt::entity, std::allocator<void>> {
basic_storage(const std::allocator<empty_each_tag> &) {}
[[nodiscard]] iterable each() noexcept {
return {internal::extended_storage_iterator{base_type::end()}, internal::extended_storage_iterator{base_type::end()}};
}
};
TEST(SighMixin, GenericType) {
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
entt::sigh_mixin<entt::storage<int>> pool; entt::sigh_storage_mixin<entt::storage<int>> pool;
entt::sparse_set &base = pool; entt::sparse_set &base = pool;
entt::registry registry; entt::registry registry;
@ -52,21 +40,10 @@ TEST(SighMixin, GenericType) {
counter on_destroy{}; counter on_destroy{};
pool.bind(entt::forward_as_any(registry)); pool.bind(entt::forward_as_any(registry));
ASSERT_TRUE(pool.empty());
pool.insert(entities, entities + 1u);
pool.erase(entities[0u]);
ASSERT_TRUE(pool.empty());
ASSERT_EQ(on_construct.value, 0);
ASSERT_EQ(on_destroy.value, 0);
pool.on_construct().connect<&listener<entt::registry>>(on_construct); pool.on_construct().connect<&listener<entt::registry>>(on_construct);
pool.on_destroy().connect<&listener<entt::registry>>(on_destroy); pool.on_destroy().connect<&listener<entt::registry>>(on_destroy);
ASSERT_NE(base.push(entities[0u]), base.end()); ASSERT_NE(base.emplace(entities[0u]), base.end());
pool.emplace(entities[1u]); pool.emplace(entities[1u]);
@ -84,7 +61,7 @@ TEST(SighMixin, GenericType) {
ASSERT_EQ(on_destroy.value, 2); ASSERT_EQ(on_destroy.value, 2);
ASSERT_TRUE(pool.empty()); ASSERT_TRUE(pool.empty());
ASSERT_NE(base.push(std::begin(entities), std::end(entities)), base.end()); ASSERT_NE(base.insert(std::begin(entities), std::end(entities)), base.end());
ASSERT_EQ(pool.get(entities[0u]), 0); ASSERT_EQ(pool.get(entities[0u]), 0);
ASSERT_EQ(pool.get(entities[1u]), 0); ASSERT_EQ(pool.get(entities[1u]), 0);
@ -118,9 +95,9 @@ TEST(SighMixin, GenericType) {
ASSERT_TRUE(pool.empty()); ASSERT_TRUE(pool.empty());
} }
TEST(SighMixin, StableType) { TEST(SighStorageMixin, StableType) {
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
entt::sigh_mixin<entt::storage<stable_type>> pool; entt::sigh_storage_mixin<entt::storage<stable_type>> pool;
entt::sparse_set &base = pool; entt::sparse_set &base = pool;
entt::registry registry; entt::registry registry;
@ -131,7 +108,7 @@ TEST(SighMixin, StableType) {
pool.on_construct().connect<&listener<entt::registry>>(on_construct); pool.on_construct().connect<&listener<entt::registry>>(on_construct);
pool.on_destroy().connect<&listener<entt::registry>>(on_destroy); pool.on_destroy().connect<&listener<entt::registry>>(on_destroy);
ASSERT_NE(base.push(entities[0u]), base.end()); ASSERT_NE(base.emplace(entities[0u]), base.end());
pool.emplace(entities[1u]); pool.emplace(entities[1u]);
@ -149,7 +126,7 @@ TEST(SighMixin, StableType) {
ASSERT_EQ(on_destroy.value, 2); ASSERT_EQ(on_destroy.value, 2);
ASSERT_FALSE(pool.empty()); ASSERT_FALSE(pool.empty());
ASSERT_NE(base.push(std::begin(entities), std::end(entities)), base.end()); ASSERT_NE(base.insert(std::begin(entities), std::end(entities)), base.end());
ASSERT_EQ(pool.get(entities[0u]).value, 0); ASSERT_EQ(pool.get(entities[0u]).value, 0);
ASSERT_EQ(pool.get(entities[1u]).value, 0); ASSERT_EQ(pool.get(entities[1u]).value, 0);
@ -183,9 +160,9 @@ TEST(SighMixin, StableType) {
ASSERT_FALSE(pool.empty()); ASSERT_FALSE(pool.empty());
} }
TEST(SighMixin, EmptyType) { TEST(SighStorageMixin, EmptyType) {
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
entt::sigh_mixin<entt::storage<empty_type>> pool; entt::sigh_storage_mixin<entt::storage<empty_type>> pool;
entt::sparse_set &base = pool; entt::sparse_set &base = pool;
entt::registry registry; entt::registry registry;
@ -196,7 +173,7 @@ TEST(SighMixin, EmptyType) {
pool.on_construct().connect<&listener<entt::registry>>(on_construct); pool.on_construct().connect<&listener<entt::registry>>(on_construct);
pool.on_destroy().connect<&listener<entt::registry>>(on_destroy); pool.on_destroy().connect<&listener<entt::registry>>(on_destroy);
ASSERT_NE(base.push(entities[0u]), base.end()); ASSERT_NE(base.emplace(entities[0u]), base.end());
pool.emplace(entities[1u]); pool.emplace(entities[1u]);
@ -214,7 +191,7 @@ TEST(SighMixin, EmptyType) {
ASSERT_EQ(on_destroy.value, 2); ASSERT_EQ(on_destroy.value, 2);
ASSERT_TRUE(pool.empty()); ASSERT_TRUE(pool.empty());
ASSERT_NE(base.push(std::begin(entities), std::end(entities)), base.end()); ASSERT_NE(base.insert(std::begin(entities), std::end(entities)), base.end());
ASSERT_TRUE(pool.contains(entities[0u])); ASSERT_TRUE(pool.contains(entities[0u]));
ASSERT_TRUE(pool.contains(entities[1u])); ASSERT_TRUE(pool.contains(entities[1u]));
@ -248,9 +225,9 @@ TEST(SighMixin, EmptyType) {
ASSERT_TRUE(pool.empty()); ASSERT_TRUE(pool.empty());
} }
TEST(SighMixin, NonDefaultConstructibleType) { TEST(SighStorageMixin, NonDefaultConstructibleType) {
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
entt::sigh_mixin<entt::storage<non_default_constructible>> pool; entt::sigh_storage_mixin<entt::storage<non_default_constructible>> pool;
entt::sparse_set &base = pool; entt::sparse_set &base = pool;
entt::registry registry; entt::registry registry;
@ -261,12 +238,12 @@ TEST(SighMixin, NonDefaultConstructibleType) {
pool.on_construct().connect<&listener<entt::registry>>(on_construct); pool.on_construct().connect<&listener<entt::registry>>(on_construct);
pool.on_destroy().connect<&listener<entt::registry>>(on_destroy); pool.on_destroy().connect<&listener<entt::registry>>(on_destroy);
ASSERT_EQ(base.push(entities[0u]), base.end()); ASSERT_EQ(base.emplace(entities[0u]), base.end());
pool.emplace(entities[1u], 3); pool.emplace(entities[1u], 3);
ASSERT_EQ(pool.size(), 1u); ASSERT_EQ(pool.size(), 1u);
ASSERT_EQ(on_construct.value, 1); ASSERT_EQ(on_construct.value, 2);
ASSERT_EQ(on_destroy.value, 0); ASSERT_EQ(on_destroy.value, 0);
ASSERT_FALSE(pool.empty()); ASSERT_FALSE(pool.empty());
@ -276,11 +253,11 @@ TEST(SighMixin, NonDefaultConstructibleType) {
base.erase(entities[1u]); base.erase(entities[1u]);
ASSERT_EQ(pool.size(), 0u); ASSERT_EQ(pool.size(), 0u);
ASSERT_EQ(on_construct.value, 1); ASSERT_EQ(on_construct.value, 2);
ASSERT_EQ(on_destroy.value, 1); ASSERT_EQ(on_destroy.value, 1);
ASSERT_TRUE(pool.empty()); ASSERT_TRUE(pool.empty());
ASSERT_EQ(base.push(std::begin(entities), std::end(entities)), base.end()); ASSERT_EQ(base.insert(std::begin(entities), std::end(entities)), base.end());
ASSERT_FALSE(pool.contains(entities[0u])); ASSERT_FALSE(pool.contains(entities[0u]));
ASSERT_FALSE(pool.contains(entities[1u])); ASSERT_FALSE(pool.contains(entities[1u]));
@ -289,7 +266,7 @@ TEST(SighMixin, NonDefaultConstructibleType) {
pool.insert(std::begin(entities), std::end(entities), 3); pool.insert(std::begin(entities), std::end(entities), 3);
ASSERT_EQ(pool.size(), 2u); ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(on_construct.value, 3); ASSERT_EQ(on_construct.value, 6);
ASSERT_EQ(on_destroy.value, 1); ASSERT_EQ(on_destroy.value, 1);
ASSERT_FALSE(pool.empty()); ASSERT_FALSE(pool.empty());
@ -299,13 +276,13 @@ TEST(SighMixin, NonDefaultConstructibleType) {
pool.erase(std::begin(entities), std::end(entities)); pool.erase(std::begin(entities), std::end(entities));
ASSERT_EQ(pool.size(), 0u); ASSERT_EQ(pool.size(), 0u);
ASSERT_EQ(on_construct.value, 3); ASSERT_EQ(on_construct.value, 6);
ASSERT_EQ(on_destroy.value, 3); ASSERT_EQ(on_destroy.value, 3);
ASSERT_TRUE(pool.empty()); ASSERT_TRUE(pool.empty());
} }
TEST(SighMixin, VoidType) { TEST(SighStorageMixin, VoidType) {
entt::sigh_mixin<entt::storage<void>> pool; entt::sigh_storage_mixin<entt::storage<void>> pool;
entt::registry registry; entt::registry registry;
counter on_construct{}; counter on_construct{};
@ -320,7 +297,7 @@ TEST(SighMixin, VoidType) {
ASSERT_EQ(pool.type(), entt::type_id<void>()); ASSERT_EQ(pool.type(), entt::type_id<void>());
ASSERT_TRUE(pool.contains(entt::entity{99})); ASSERT_TRUE(pool.contains(entt::entity{99}));
entt::sigh_mixin<entt::storage<void>> other{std::move(pool)}; entt::sigh_storage_mixin<entt::storage<void>> other{std::move(pool)};
ASSERT_FALSE(pool.contains(entt::entity{99})); ASSERT_FALSE(pool.contains(entt::entity{99}));
ASSERT_TRUE(other.contains(entt::entity{99})); ASSERT_TRUE(other.contains(entt::entity{99}));
@ -336,8 +313,8 @@ TEST(SighMixin, VoidType) {
ASSERT_EQ(on_destroy.value, 1); ASSERT_EQ(on_destroy.value, 1);
} }
TEST(SighMixin, Move) { TEST(SighStorageMixin, Move) {
entt::sigh_mixin<entt::storage<int>> pool; entt::sigh_storage_mixin<entt::storage<int>> pool;
entt::registry registry; entt::registry registry;
counter on_construct{}; counter on_construct{};
@ -353,7 +330,7 @@ TEST(SighMixin, Move) {
ASSERT_TRUE(std::is_move_assignable_v<decltype(pool)>); ASSERT_TRUE(std::is_move_assignable_v<decltype(pool)>);
ASSERT_EQ(pool.type(), entt::type_id<int>()); ASSERT_EQ(pool.type(), entt::type_id<int>());
entt::sigh_mixin<entt::storage<int>> other{std::move(pool)}; entt::sigh_storage_mixin<entt::storage<int>> other{std::move(pool)};
ASSERT_TRUE(pool.empty()); ASSERT_TRUE(pool.empty());
ASSERT_FALSE(other.empty()); ASSERT_FALSE(other.empty());
@ -370,7 +347,7 @@ TEST(SighMixin, Move) {
ASSERT_EQ(pool.get(entt::entity{3}), 3); ASSERT_EQ(pool.get(entt::entity{3}), 3);
ASSERT_EQ(other.at(0u), static_cast<entt::entity>(entt::null)); ASSERT_EQ(other.at(0u), static_cast<entt::entity>(entt::null));
other = entt::sigh_mixin<entt::storage<int>>{}; other = entt::sigh_storage_mixin<entt::storage<int>>{};
other.bind(entt::forward_as_any(registry)); other.bind(entt::forward_as_any(registry));
other.emplace(entt::entity{42}, 42); other.emplace(entt::entity{42}, 42);
@ -388,9 +365,9 @@ TEST(SighMixin, Move) {
ASSERT_EQ(on_destroy.value, 1); ASSERT_EQ(on_destroy.value, 1);
} }
TEST(SighMixin, Swap) { TEST(SighStorageMixin, Swap) {
entt::sigh_mixin<entt::storage<int>> pool; entt::sigh_storage_mixin<entt::storage<int>> pool;
entt::sigh_mixin<entt::storage<int>> other; entt::sigh_storage_mixin<entt::storage<int>> other;
entt::registry registry; entt::registry registry;
counter on_construct{}; counter on_construct{};
@ -434,100 +411,7 @@ TEST(SighMixin, Swap) {
ASSERT_EQ(on_destroy.value, 3); ASSERT_EQ(on_destroy.value, 3);
} }
TEST(SighMixin, EmptyEachStorage) { TEST(SighStorageMixin, CustomAllocator) {
entt::sigh_mixin<entt::storage<empty_each_tag>> pool;
entt::registry registry;
counter on_destroy{};
pool.bind(entt::forward_as_any(registry));
pool.on_destroy().connect<&listener<entt::registry>>(on_destroy);
ASSERT_TRUE(pool.empty());
ASSERT_EQ(on_destroy.value, 0);
pool.push(entt::entity{42});
ASSERT_FALSE(pool.empty());
ASSERT_EQ(on_destroy.value, 0);
ASSERT_NE(pool.begin(), pool.end());
ASSERT_EQ(pool.each().begin(), pool.each().end());
ASSERT_EQ(on_destroy.value, 0);
pool.clear();
ASSERT_EQ(pool.begin(), pool.end());
ASSERT_EQ(pool.each().begin(), pool.each().end());
// no signal at all because of the (fake) empty iterable
ASSERT_EQ(on_destroy.value, 0);
}
TEST(SighMixin, StorageEntity) {
using traits_type = entt::entt_traits<entt::entity>;
entt::sigh_mixin<entt::storage<entt::entity>> pool;
entt::registry registry;
counter on_construct{};
counter on_destroy{};
pool.bind(entt::forward_as_any(registry));
pool.on_construct().connect<&listener<entt::registry>>(on_construct);
pool.on_destroy().connect<&listener<entt::registry>>(on_destroy);
pool.push(entt::entity{1});
ASSERT_EQ(on_construct.value, 1);
ASSERT_EQ(on_destroy.value, 0);
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.in_use(), 1u);
pool.erase(entt::entity{1});
ASSERT_EQ(on_construct.value, 1);
ASSERT_EQ(on_destroy.value, 1);
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.in_use(), 0u);
pool.push(traits_type::construct(0, 2));
pool.push(traits_type::construct(2, 1));
ASSERT_TRUE(pool.contains(traits_type::construct(0, 2)));
ASSERT_TRUE(pool.contains(traits_type::construct(1, 1)));
ASSERT_TRUE(pool.contains(traits_type::construct(2, 1)));
ASSERT_EQ(on_construct.value, 3);
ASSERT_EQ(on_destroy.value, 1);
ASSERT_EQ(pool.size(), 3u);
ASSERT_EQ(pool.in_use(), 2u);
pool.clear();
ASSERT_EQ(pool.size(), 0u);
ASSERT_EQ(pool.in_use(), 0u);
ASSERT_EQ(on_construct.value, 3);
ASSERT_EQ(on_destroy.value, 3);
pool.emplace();
pool.emplace(entt::entity{0});
entt::entity entities[1u]{};
pool.insert(entities, entities + 1u);
ASSERT_EQ(on_construct.value, 6);
ASSERT_EQ(on_destroy.value, 3);
ASSERT_EQ(pool.size(), 3u);
ASSERT_EQ(pool.in_use(), 3u);
pool.clear();
ASSERT_EQ(pool.size(), 0u);
ASSERT_EQ(pool.in_use(), 0u);
}
TEST(SighMixin, CustomAllocator) {
auto test = [](auto pool, auto alloc) { auto test = [](auto pool, auto alloc) {
using registry_type = typename decltype(pool)::registry_type; using registry_type = typename decltype(pool)::registry_type;
registry_type registry; registry_type registry;
@ -582,12 +466,12 @@ TEST(SighMixin, CustomAllocator) {
test::throwing_allocator<entt::entity> allocator{}; test::throwing_allocator<entt::entity> allocator{};
test(entt::sigh_mixin<entt::basic_storage<int, entt::entity, test::throwing_allocator<int>>>{allocator}, allocator); test(entt::sigh_storage_mixin<entt::basic_storage<int, entt::entity, test::throwing_allocator<int>>>{allocator}, allocator);
test(entt::sigh_mixin<entt::basic_storage<std::true_type, entt::entity, test::throwing_allocator<std::true_type>>>{allocator}, allocator); test(entt::sigh_storage_mixin<entt::basic_storage<std::true_type, entt::entity, test::throwing_allocator<std::true_type>>>{allocator}, allocator);
test(entt::sigh_mixin<entt::basic_storage<stable_type, entt::entity, test::throwing_allocator<stable_type>>>{allocator}, allocator); test(entt::sigh_storage_mixin<entt::basic_storage<stable_type, entt::entity, test::throwing_allocator<stable_type>>>{allocator}, allocator);
} }
TEST(SighMixin, ThrowingAllocator) { TEST(SighStorageMixin, ThrowingAllocator) {
auto test = [](auto pool) { auto test = [](auto pool) {
using pool_allocator_type = typename decltype(pool)::allocator_type; using pool_allocator_type = typename decltype(pool)::allocator_type;
using value_type = typename decltype(pool)::value_type; using value_type = typename decltype(pool)::value_type;
@ -627,7 +511,7 @@ TEST(SighMixin, ThrowingAllocator) {
test::throwing_allocator<entt::entity>::trigger_on_allocate = true; test::throwing_allocator<entt::entity>::trigger_on_allocate = true;
ASSERT_THROW(base.push(entt::entity{0}), test::throwing_allocator<entt::entity>::exception_type); ASSERT_THROW(base.emplace(entt::entity{0}), test::throwing_allocator<entt::entity>::exception_type);
ASSERT_FALSE(base.contains(entt::entity{0})); ASSERT_FALSE(base.contains(entt::entity{0}));
ASSERT_TRUE(base.empty()); ASSERT_TRUE(base.empty());
@ -659,12 +543,12 @@ TEST(SighMixin, ThrowingAllocator) {
ASSERT_EQ(on_destroy.value, 1); ASSERT_EQ(on_destroy.value, 1);
}; };
test(entt::sigh_mixin<entt::basic_storage<int, entt::entity, test::throwing_allocator<int>>>{}); test(entt::sigh_storage_mixin<entt::basic_storage<int, entt::entity, test::throwing_allocator<int>>>{});
test(entt::sigh_mixin<entt::basic_storage<stable_type, entt::entity, test::throwing_allocator<stable_type>>>{}); test(entt::sigh_storage_mixin<entt::basic_storage<stable_type, entt::entity, test::throwing_allocator<stable_type>>>{});
} }
TEST(SighMixin, ThrowingComponent) { TEST(SighStorageMixin, ThrowingComponent) {
entt::sigh_mixin<entt::storage<test::throwing_type>> pool; entt::sigh_storage_mixin<entt::storage<test::throwing_type>> pool;
using registry_type = typename decltype(pool)::registry_type; using registry_type = typename decltype(pool)::registry_type;
registry_type registry; registry_type registry;

View File

@ -24,6 +24,7 @@ TEST(SingleComponentView, Functionalities) {
ASSERT_TRUE(view.empty()); ASSERT_TRUE(view.empty());
registry.emplace<int>(e1);
registry.emplace<char>(e1); registry.emplace<char>(e1);
ASSERT_NO_FATAL_FAILURE(view.begin()++); ASSERT_NO_FATAL_FAILURE(view.begin()++);
@ -69,42 +70,6 @@ TEST(SingleComponentView, Functionalities) {
ASSERT_FALSE(invalid); ASSERT_FALSE(invalid);
} }
TEST(SingleComponentView, InvalidView) {
entt::basic_view<entt::get_t<entt::storage<int>>, entt::exclude_t<>> view{};
ASSERT_FALSE(view);
ASSERT_EQ(view.size(), 0u);
ASSERT_TRUE(view.empty());
ASSERT_FALSE(view.contains(entt::null));
ASSERT_EQ(view.find(entt::null), view.end());
ASSERT_EQ(view.front(), static_cast<entt::entity>(entt::null));
ASSERT_EQ(view.back(), static_cast<entt::entity>(entt::null));
ASSERT_EQ(view.begin(), typename decltype(view)::iterator{});
ASSERT_EQ(view.begin(), view.end());
ASSERT_EQ(view.rbegin(), typename decltype(view)::reverse_iterator{});
ASSERT_EQ(view.rbegin(), view.rend());
auto iterable = view.each();
ASSERT_EQ(iterable.begin(), iterable.end());
ASSERT_EQ(iterable.cbegin(), iterable.cend());
view.each([](const int &) { FAIL(); });
view.each([](const entt::entity, const int &) { FAIL(); });
entt::storage<int> storage;
view.storage(storage);
ASSERT_TRUE(view);
view.each([](const int &) { FAIL(); });
view.each([](const entt::entity, const int &) { FAIL(); });
}
TEST(SingleComponentView, Constructors) { TEST(SingleComponentView, Constructors) {
entt::storage<int> storage{}; entt::storage<int> storage{};
@ -116,8 +81,7 @@ TEST(SingleComponentView, Constructors) {
ASSERT_TRUE(from_storage); ASSERT_TRUE(from_storage);
ASSERT_TRUE(from_tuple); ASSERT_TRUE(from_tuple);
ASSERT_NE(from_storage.handle(), nullptr); ASSERT_EQ(&from_storage.handle(), &from_tuple.handle());
ASSERT_EQ(from_storage.handle(), from_tuple.handle());
} }
TEST(SingleComponentView, Handle) { TEST(SingleComponentView, Handle) {
@ -125,19 +89,17 @@ TEST(SingleComponentView, Handle) {
const auto entity = registry.create(); const auto entity = registry.create();
auto view = registry.view<int>(); auto view = registry.view<int>();
auto *handle = view.handle(); auto &&handle = view.handle();
ASSERT_NE(handle, nullptr); ASSERT_TRUE(handle.empty());
ASSERT_FALSE(handle.contains(entity));
ASSERT_TRUE(handle->empty()); ASSERT_EQ(&handle, &view.handle());
ASSERT_FALSE(handle->contains(entity));
ASSERT_EQ(handle, view.handle());
registry.emplace<int>(entity); registry.emplace<int>(entity);
ASSERT_FALSE(handle->empty()); ASSERT_FALSE(handle.empty());
ASSERT_TRUE(handle->contains(entity)); ASSERT_TRUE(handle.contains(entity));
ASSERT_EQ(handle, view.handle()); ASSERT_EQ(&handle, &view.handle());
} }
TEST(SingleComponentView, LazyTypeFromConstRegistry) { TEST(SingleComponentView, LazyTypeFromConstRegistry) {
@ -149,8 +111,8 @@ TEST(SingleComponentView, LazyTypeFromConstRegistry) {
registry.emplace<empty_type>(entity); registry.emplace<empty_type>(entity);
registry.emplace<int>(entity); registry.emplace<int>(entity);
ASSERT_FALSE(cview); ASSERT_TRUE(cview);
ASSERT_FALSE(eview); ASSERT_TRUE(eview);
ASSERT_TRUE(cview.empty()); ASSERT_TRUE(cview.empty());
ASSERT_EQ(eview.size(), 0u); ASSERT_EQ(eview.size(), 0u);
@ -202,6 +164,14 @@ TEST(SingleComponentView, Contains) {
TEST(SingleComponentView, Empty) { TEST(SingleComponentView, Empty) {
entt::registry registry; entt::registry registry;
const auto e0 = registry.create();
registry.emplace<char>(e0);
registry.emplace<double>(e0);
const auto e1 = registry.create();
registry.emplace<char>(e1);
auto view = registry.view<int>(); auto view = registry.view<int>();
ASSERT_EQ(view.size(), 0u); ASSERT_EQ(view.size(), 0u);
@ -228,17 +198,15 @@ TEST(SingleComponentView, Each) {
auto it = iterable.begin(); auto it = iterable.begin();
ASSERT_EQ(it.base(), view.begin());
ASSERT_EQ((it++, ++it), iterable.end()); ASSERT_EQ((it++, ++it), iterable.end());
ASSERT_EQ(it.base(), view.end());
view.each([expected = 1](auto entt, int &value) mutable { view.each([expected = 1u](auto entt, int &value) mutable {
ASSERT_EQ(static_cast<int>(entt::to_integral(entt)), expected); ASSERT_EQ(entt::to_integral(entt), expected);
ASSERT_EQ(value, expected); ASSERT_EQ(value, expected);
--expected; --expected;
}); });
cview.each([expected = 1](const int &value) mutable { cview.each([expected = 1u](const int &value) mutable {
ASSERT_EQ(value, expected); ASSERT_EQ(value, expected);
--expected; --expected;
}); });
@ -251,7 +219,7 @@ TEST(SingleComponentView, Each) {
// do not use iterable, make sure an iterable view works when created from a temporary // do not use iterable, make sure an iterable view works when created from a temporary
for(auto [entt, value]: view.each()) { for(auto [entt, value]: view.each()) {
ASSERT_EQ(static_cast<int>(entt::to_integral(entt)), value); ASSERT_EQ(entt::to_integral(entt), value);
} }
} }
@ -366,13 +334,15 @@ TEST(SingleComponentView, Find) {
TEST(SingleComponentView, EmptyTypes) { TEST(SingleComponentView, EmptyTypes) {
entt::registry registry; entt::registry registry;
entt::entity entity = registry.create(); entt::entity entities[2u];
registry.emplace<int>(entity, 0); registry.create(std::begin(entities), std::end(entities));
registry.emplace<empty_type>(entity); registry.emplace<int>(entities[0u], 0);
registry.emplace<empty_type>(entities[0u]);
registry.emplace<char>(entities[1u], 'c');
registry.view<empty_type>().each([&](const auto entt) { registry.view<empty_type>().each([&](const auto entt) {
ASSERT_EQ(entity, entt); ASSERT_EQ(entities[0u], entt);
}); });
registry.view<empty_type>().each([check = true]() mutable { registry.view<empty_type>().each([check = true]() mutable {
@ -382,11 +352,11 @@ TEST(SingleComponentView, EmptyTypes) {
for(auto [entt]: registry.view<empty_type>().each()) { for(auto [entt]: registry.view<empty_type>().each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>); static_assert(std::is_same_v<decltype(entt), entt::entity>);
ASSERT_EQ(entity, entt); ASSERT_EQ(entities[0u], entt);
} }
registry.view<int>().each([&](const auto entt, int) { registry.view<int>().each([&](const auto entt, int) {
ASSERT_EQ(entity, entt); ASSERT_EQ(entities[0u], entt);
}); });
registry.view<int>().each([check = true](int) mutable { registry.view<int>().each([check = true](int) mutable {
@ -397,7 +367,7 @@ TEST(SingleComponentView, EmptyTypes) {
for(auto [entt, iv]: registry.view<int>().each()) { for(auto [entt, iv]: registry.view<int>().each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>); static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>); static_assert(std::is_same_v<decltype(iv), int &>);
ASSERT_EQ(entity, entt); ASSERT_EQ(entities[0u], entt);
} }
} }
@ -489,87 +459,35 @@ TEST(SingleComponentView, StableType) {
TEST(SingleComponentView, Storage) { TEST(SingleComponentView, Storage) {
entt::registry registry; entt::registry registry;
const auto entity = registry.create(); const auto entity = registry.create();
auto view = registry.view<int>(); const auto view = registry.view<int>();
auto cview = registry.view<const char>(); const auto cview = registry.view<const char>();
static_assert(std::is_same_v<decltype(view.storage()), entt::storage_type_t<int> *>); static_assert(std::is_same_v<decltype(view.storage()), entt::storage_type_t<int> &>);
static_assert(std::is_same_v<decltype(view.storage<0u>()), entt::storage_type_t<int> *>); static_assert(std::is_same_v<decltype(view.storage<0u>()), entt::storage_type_t<int> &>);
static_assert(std::is_same_v<decltype(view.storage<int>()), entt::storage_type_t<int> *>); static_assert(std::is_same_v<decltype(view.storage<int>()), entt::storage_type_t<int> &>);
static_assert(std::is_same_v<decltype(view.storage<const int>()), entt::storage_type_t<int> *>); static_assert(std::is_same_v<decltype(cview.storage()), const entt::storage_type_t<char> &>);
static_assert(std::is_same_v<decltype(cview.storage()), const entt::storage_type_t<char> *>); static_assert(std::is_same_v<decltype(cview.storage<0u>()), const entt::storage_type_t<char> &>);
static_assert(std::is_same_v<decltype(cview.storage<0u>()), const entt::storage_type_t<char> *>); static_assert(std::is_same_v<decltype(cview.storage<const char>()), const entt::storage_type_t<char> &>);
static_assert(std::is_same_v<decltype(cview.storage<char>()), const entt::storage_type_t<char> *>);
static_assert(std::is_same_v<decltype(cview.storage<const char>()), const entt::storage_type_t<char> *>);
ASSERT_TRUE(view);
ASSERT_TRUE(cview);
ASSERT_NE(view.storage<int>(), nullptr);
ASSERT_NE(cview.storage<0u>(), nullptr);
ASSERT_EQ(view.size(), 0u); ASSERT_EQ(view.size(), 0u);
ASSERT_EQ(cview.size(), 0u); ASSERT_EQ(cview.size(), 0u);
view.storage()->emplace(entity); view.storage().emplace(entity);
registry.emplace<char>(entity); registry.emplace<char>(entity);
ASSERT_EQ(view.size(), 1u); ASSERT_EQ(view.size(), 1u);
ASSERT_EQ(cview.size(), 1u); ASSERT_EQ(cview.size(), 1u);
ASSERT_TRUE(view.storage<int>()->contains(entity)); ASSERT_TRUE(view.storage<int>().contains(entity));
ASSERT_TRUE(cview.storage<0u>()->contains(entity)); ASSERT_TRUE(cview.storage<0u>().contains(entity));
ASSERT_TRUE((registry.all_of<int, char>(entity))); ASSERT_TRUE((registry.all_of<int, char>(entity)));
view.storage()->erase(entity); view.storage().erase(entity);
ASSERT_EQ(view.size(), 0u); ASSERT_EQ(view.size(), 0u);
ASSERT_EQ(cview.size(), 1u); ASSERT_EQ(cview.size(), 1u);
ASSERT_FALSE(view.storage<0u>()->contains(entity)); ASSERT_FALSE(view.storage<0u>().contains(entity));
ASSERT_TRUE(cview.storage<const char>()->contains(entity)); ASSERT_TRUE(cview.storage<const char>().contains(entity));
ASSERT_FALSE((registry.all_of<int, char>(entity))); ASSERT_FALSE((registry.all_of<int, char>(entity)));
view = {};
cview = {};
ASSERT_FALSE(view);
ASSERT_FALSE(cview);
ASSERT_EQ(view.storage<0u>(), nullptr);
ASSERT_EQ(cview.storage<const char>(), nullptr);
}
TEST(SingleComponentView, SwapStorage) {
using namespace entt::literals;
entt::registry registry;
entt::basic_view<entt::get_t<entt::storage<int>>, entt::exclude_t<>> view;
entt::basic_view<entt::get_t<const entt::storage<int>>, entt::exclude_t<>> cview;
ASSERT_FALSE(view);
ASSERT_FALSE(cview);
ASSERT_EQ(view.storage<0u>(), nullptr);
ASSERT_EQ(cview.storage<const int>(), nullptr);
const entt::entity entity{42u};
registry.emplace<int>(entity);
view.storage(registry.storage<int>());
cview.storage(registry.storage<int>());
ASSERT_TRUE(view);
ASSERT_TRUE(cview);
ASSERT_NE(view.storage<0u>(), nullptr);
ASSERT_NE(cview.storage<const int>(), nullptr);
ASSERT_EQ(view.size(), 1u);
ASSERT_EQ(cview.size(), 1u);
ASSERT_TRUE(view.contains(entity));
ASSERT_TRUE(cview.contains(entity));
view.storage(registry.storage<int>("other"_hs));
cview.storage(registry.storage<int>("other"_hs));
ASSERT_TRUE(view.empty());
ASSERT_TRUE(cview.empty());
} }
TEST(MultiComponentView, Functionalities) { TEST(MultiComponentView, Functionalities) {
@ -614,46 +532,6 @@ TEST(MultiComponentView, Functionalities) {
ASSERT_FALSE(invalid); ASSERT_FALSE(invalid);
} }
TEST(MultiComponentView, InvalidView) {
entt::basic_view<entt::get_t<entt::storage<int>>, entt::exclude_t<entt::storage<char>>> view{};
ASSERT_FALSE(view);
ASSERT_EQ(view.size_hint(), 0u);
ASSERT_FALSE(view.contains(entt::null));
ASSERT_EQ(view.find(entt::null), view.end());
ASSERT_EQ(view.front(), static_cast<entt::entity>(entt::null));
ASSERT_EQ(view.back(), static_cast<entt::entity>(entt::null));
ASSERT_EQ(view.begin(), typename decltype(view)::iterator{});
ASSERT_EQ(view.begin(), view.end());
auto iterable = view.each();
ASSERT_EQ(iterable.begin(), iterable.end());
ASSERT_EQ(iterable.cbegin(), iterable.cend());
view.each([](const int &) { FAIL(); });
view.each([](const entt::entity, const int &) { FAIL(); });
entt::storage<int> storage;
view.storage(storage);
ASSERT_FALSE(view);
view.each([](const int &) { FAIL(); });
view.each([](const entt::entity, const int &) { FAIL(); });
entt::storage<char> other;
view.storage(other);
ASSERT_TRUE(view);
view.each([](const int &) { FAIL(); });
view.each([](const entt::entity, const int &) { FAIL(); });
}
TEST(MultiComponentView, Constructors) { TEST(MultiComponentView, Constructors) {
entt::storage<int> storage{}; entt::storage<int> storage{};
@ -665,8 +543,7 @@ TEST(MultiComponentView, Constructors) {
ASSERT_TRUE(from_storage); ASSERT_TRUE(from_storage);
ASSERT_TRUE(from_tuple); ASSERT_TRUE(from_tuple);
ASSERT_NE(from_storage.handle(), nullptr); ASSERT_EQ(&from_storage.handle(), &from_tuple.handle());
ASSERT_EQ(from_storage.handle(), from_tuple.handle());
} }
TEST(MultiComponentView, Handle) { TEST(MultiComponentView, Handle) {
@ -674,34 +551,30 @@ TEST(MultiComponentView, Handle) {
const auto entity = registry.create(); const auto entity = registry.create();
auto view = registry.view<int, char>(); auto view = registry.view<int, char>();
auto *handle = view.handle(); auto &&handle = view.handle();
ASSERT_NE(handle, nullptr); ASSERT_TRUE(handle.empty());
ASSERT_FALSE(handle.contains(entity));
ASSERT_TRUE(handle->empty()); ASSERT_EQ(&handle, &view.handle());
ASSERT_FALSE(handle->contains(entity));
ASSERT_EQ(handle, view.handle());
registry.emplace<int>(entity); registry.emplace<int>(entity);
ASSERT_FALSE(handle->empty()); ASSERT_FALSE(handle.empty());
ASSERT_TRUE(handle->contains(entity)); ASSERT_TRUE(handle.contains(entity));
ASSERT_EQ(handle, view.handle()); ASSERT_EQ(&handle, &view.handle());
view.refresh(); view = view.refresh();
auto *other = view.handle(); auto &&other = view.handle();
ASSERT_NE(other, nullptr); ASSERT_TRUE(other.empty());
ASSERT_FALSE(other.contains(entity));
ASSERT_EQ(&other, &view.handle());
ASSERT_NE(&handle, &other);
ASSERT_TRUE(other->empty()); view = view.use<int>();
ASSERT_FALSE(other->contains(entity));
ASSERT_EQ(other, view.handle());
ASSERT_NE(handle, other);
view.use<int>(); ASSERT_NE(&other, &view.handle());
ASSERT_EQ(&handle, &view.handle());
ASSERT_NE(other, view.handle());
ASSERT_EQ(handle, view.handle());
} }
TEST(MultiComponentView, LazyTypesFromConstRegistry) { TEST(MultiComponentView, LazyTypesFromConstRegistry) {
@ -712,7 +585,7 @@ TEST(MultiComponentView, LazyTypesFromConstRegistry) {
registry.emplace<empty_type>(entity); registry.emplace<empty_type>(entity);
registry.emplace<int>(entity); registry.emplace<int>(entity);
ASSERT_FALSE(view); ASSERT_TRUE(view);
ASSERT_EQ(view.size_hint(), 0u); ASSERT_EQ(view.size_hint(), 0u);
ASSERT_FALSE(view.contains(entity)); ASSERT_FALSE(view.contains(entity));
@ -731,7 +604,7 @@ TEST(MultiComponentView, LazyExcludedTypeFromConstRegistry) {
auto view = std::as_const(registry).view<const int>(entt::exclude<char>); auto view = std::as_const(registry).view<const int>(entt::exclude<char>);
ASSERT_FALSE(view); ASSERT_TRUE(view);
ASSERT_EQ(view.size_hint(), 1u); ASSERT_EQ(view.size_hint(), 1u);
ASSERT_TRUE(view.contains(entity)); ASSERT_TRUE(view.contains(entity));
@ -810,6 +683,7 @@ TEST(MultiComponentView, SizeHint) {
entt::registry registry; entt::registry registry;
const auto e0 = registry.create(); const auto e0 = registry.create();
registry.emplace<double>(e0);
registry.emplace<int>(e0); registry.emplace<int>(e0);
registry.emplace<float>(e0); registry.emplace<float>(e0);
@ -831,10 +705,10 @@ TEST(MultiComponentView, Each) {
auto cview = std::as_const(registry).view<const int, const char>(); auto cview = std::as_const(registry).view<const int, const char>();
registry.emplace<int>(entity[0u], 0); registry.emplace<int>(entity[0u], 0);
registry.emplace<char>(entity[0u], static_cast<char>(0)); registry.emplace<char>(entity[0u], 0);
registry.emplace<int>(entity[1u], 1); registry.emplace<int>(entity[1u], 1);
registry.emplace<char>(entity[1u], static_cast<char>(1)); registry.emplace<char>(entity[1u], 1);
auto iterable = view.each(); auto iterable = view.each();
auto citerable = cview.each(); auto citerable = cview.each();
@ -845,18 +719,16 @@ TEST(MultiComponentView, Each) {
auto it = iterable.begin(); auto it = iterable.begin();
ASSERT_EQ(it.base(), view.begin());
ASSERT_EQ((it++, ++it), iterable.end()); ASSERT_EQ((it++, ++it), iterable.end());
ASSERT_EQ(it.base(), view.end());
view.each([expected = 1](auto entt, int &ivalue, char &cvalue) mutable { view.each([expected = 1u](auto entt, int &ivalue, char &cvalue) mutable {
ASSERT_EQ(static_cast<int>(entt::to_integral(entt)), expected); ASSERT_EQ(entt::to_integral(entt), expected);
ASSERT_EQ(ivalue, expected); ASSERT_EQ(ivalue, expected);
ASSERT_EQ(cvalue, expected); ASSERT_EQ(cvalue, expected);
--expected; --expected;
}); });
cview.each([expected = 1](const int &ivalue, const char &cvalue) mutable { cview.each([expected = 1u](const int &ivalue, const char &cvalue) mutable {
ASSERT_EQ(ivalue, expected); ASSERT_EQ(ivalue, expected);
ASSERT_EQ(cvalue, expected); ASSERT_EQ(cvalue, expected);
--expected; --expected;
@ -870,14 +742,13 @@ TEST(MultiComponentView, Each) {
// do not use iterable, make sure an iterable view works when created from a temporary // do not use iterable, make sure an iterable view works when created from a temporary
for(auto [entt, ivalue, cvalue]: registry.view<int, char>().each()) { for(auto [entt, ivalue, cvalue]: registry.view<int, char>().each()) {
ASSERT_EQ(static_cast<int>(entt::to_integral(entt)), ivalue); ASSERT_EQ(entt::to_integral(entt), ivalue);
ASSERT_EQ(static_cast<char>(entt::to_integral(entt)), cvalue); ASSERT_EQ(entt::to_integral(entt), cvalue);
} }
} }
TEST(MultiComponentView, EachWithSuggestedType) { TEST(MultiComponentView, EachWithSuggestedType) {
entt::registry registry; entt::registry registry;
auto view = registry.view<int, char>();
for(auto i = 0; i < 3; ++i) { for(auto i = 0; i < 3; ++i) {
const auto entity = registry.create(); const auto entity = registry.create();
@ -889,8 +760,7 @@ TEST(MultiComponentView, EachWithSuggestedType) {
const auto entity = registry.create(); const auto entity = registry.create();
registry.emplace<int>(entity, 99); registry.emplace<int>(entity, 99);
view.use<int>(); registry.view<int, char>().use<int>().each([value = 2](const auto curr, const auto) mutable {
view.each([value = 2](const auto curr, const auto) mutable {
ASSERT_EQ(curr, value--); ASSERT_EQ(curr, value--);
}); });
@ -898,8 +768,7 @@ TEST(MultiComponentView, EachWithSuggestedType) {
return lhs < rhs; return lhs < rhs;
}); });
view.use<0u>(); registry.view<int, char>().use<0u>().each([value = 0](const auto curr, const auto) mutable {
view.each([value = 0](const auto curr, const auto) mutable {
ASSERT_EQ(curr, value++); ASSERT_EQ(curr, value++);
}); });
@ -918,9 +787,8 @@ TEST(MultiComponentView, EachWithSuggestedType) {
}); });
value = {}; value = {};
view.use<int>();
for(auto &&curr: view.each()) { for(auto &&curr: registry.view<int, char>().use<int>().each()) {
ASSERT_EQ(std::get<1>(curr), static_cast<int>(value++)); ASSERT_EQ(std::get<1>(curr), static_cast<int>(value++));
} }
} }
@ -1087,10 +955,6 @@ TEST(MultiComponentView, ExcludedComponents) {
TEST(MultiComponentView, EmptyTypes) { TEST(MultiComponentView, EmptyTypes) {
entt::registry registry; entt::registry registry;
auto v1 = registry.view<int, char, empty_type>(entt::exclude<double>);
auto v2 = registry.view<int, empty_type, char>(entt::exclude<double>);
auto v3 = registry.view<empty_type, int, char>(entt::exclude<double>);
const auto entity = registry.create(); const auto entity = registry.create();
registry.emplace<int>(entity); registry.emplace<int>(entity);
registry.emplace<char>(entity); registry.emplace<char>(entity);
@ -1106,61 +970,57 @@ TEST(MultiComponentView, EmptyTypes) {
registry.emplace<int>(ignored); registry.emplace<int>(ignored);
registry.emplace<char>(ignored); registry.emplace<char>(ignored);
v1.each([entity](const auto entt, int, char) { registry.view<int, char, empty_type>(entt::exclude<double>).each([entity](const auto entt, int, char) {
ASSERT_EQ(entity, entt); ASSERT_EQ(entity, entt);
}); });
for(auto [entt, iv, cv]: v1.each()) { for(auto [entt, iv, cv]: registry.view<int, char, empty_type>(entt::exclude<double>).each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>); static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>); static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), char &>); static_assert(std::is_same_v<decltype(cv), char &>);
ASSERT_EQ(entity, entt); ASSERT_EQ(entity, entt);
} }
v2.each([check = true](int, char) mutable { registry.view<int, empty_type, char>(entt::exclude<double>).each([check = true](int, char) mutable {
ASSERT_TRUE(check); ASSERT_TRUE(check);
check = false; check = false;
}); });
for(auto [entt, iv, cv]: v2.each()) { for(auto [entt, iv, cv]: registry.view<int, empty_type, char>(entt::exclude<double>).each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>); static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>); static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), char &>); static_assert(std::is_same_v<decltype(cv), char &>);
ASSERT_EQ(entity, entt); ASSERT_EQ(entity, entt);
} }
v3.each([entity](const auto entt, int, char) { registry.view<empty_type, int, char>(entt::exclude<double>).each([entity](const auto entt, int, char) {
ASSERT_EQ(entity, entt); ASSERT_EQ(entity, entt);
}); });
for(auto [entt, iv, cv]: v3.each()) { for(auto [entt, iv, cv]: registry.view<empty_type, int, char>(entt::exclude<double>).each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>); static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>); static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), char &>); static_assert(std::is_same_v<decltype(cv), char &>);
ASSERT_EQ(entity, entt); ASSERT_EQ(entity, entt);
} }
v3.use<empty_type>(); registry.view<empty_type, int, char>(entt::exclude<double>).use<empty_type>().each([entity](const auto entt, int, char) {
v3.each([entity](const auto entt, int, char) {
ASSERT_EQ(entity, entt); ASSERT_EQ(entity, entt);
}); });
v3.use<0u>(); for(auto [entt, iv, cv]: registry.view<empty_type, int, char>(entt::exclude<double>).use<0u>().each()) {
for(auto [entt, iv, cv]: v3.each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>); static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>); static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), char &>); static_assert(std::is_same_v<decltype(cv), char &>);
ASSERT_EQ(entity, entt); ASSERT_EQ(entity, entt);
} }
v2.use<1u>(); registry.view<int, empty_type, char>(entt::exclude<double>).use<1u>().each([check = true](int, char) mutable {
v2.each([check = true](int, char) mutable {
ASSERT_TRUE(check); ASSERT_TRUE(check);
check = false; check = false;
}); });
v2.use<empty_type>(); for(auto [entt, iv, cv]: registry.view<int, empty_type, char>(entt::exclude<double>).use<empty_type>().each()) {
for(auto [entt, iv, cv]: v2.each()) {
static_assert(std::is_same_v<decltype(entt), entt::entity>); static_assert(std::is_same_v<decltype(entt), entt::entity>);
static_assert(std::is_same_v<decltype(iv), int &>); static_assert(std::is_same_v<decltype(iv), int &>);
static_assert(std::is_same_v<decltype(cv), char &>); static_assert(std::is_same_v<decltype(cv), char &>);
@ -1251,7 +1111,7 @@ TEST(MultiComponentView, StableType) {
ASSERT_EQ(view.size_hint(), 1u); ASSERT_EQ(view.size_hint(), 1u);
view.use<stable_type>(); view = view.use<stable_type>();
ASSERT_EQ(view.size_hint(), 2u); ASSERT_EQ(view.size_hint(), 2u);
ASSERT_FALSE(view.contains(entity)); ASSERT_FALSE(view.contains(entity));
@ -1286,9 +1146,7 @@ TEST(MultiComponentView, StableType) {
TEST(MultiComponentView, StableTypeWithExcludedComponent) { TEST(MultiComponentView, StableTypeWithExcludedComponent) {
entt::registry registry; entt::registry registry;
auto view = registry.view<stable_type>(entt::exclude<int>); auto view = registry.view<stable_type>(entt::exclude<int>).use<stable_type>();
view.use<stable_type>();
const auto entity = registry.create(); const auto entity = registry.create();
const auto other = registry.create(); const auto other = registry.create();
@ -1365,118 +1223,8 @@ TEST(MultiComponentView, SameComponentTypes) {
ASSERT_EQ(second, 9); ASSERT_EQ(second, 9);
} }
ASSERT_EQ(view.handle(), &storage); ASSERT_EQ(&view.handle(), &storage);
ASSERT_EQ(&view.use<1u>().handle(), &other);
view.use<1u>();
ASSERT_EQ(view.handle(), &other);
}
TEST(MultiComponentView, Storage) {
entt::registry registry;
const auto entity = registry.create();
auto view = registry.view<int, const char>(entt::exclude<double, const float>);
static_assert(std::is_same_v<decltype(view.storage<0u>()), entt::storage_type_t<int> *>);
static_assert(std::is_same_v<decltype(view.storage<int>()), entt::storage_type_t<int> *>);
static_assert(std::is_same_v<decltype(view.storage<const int>()), entt::storage_type_t<int> *>);
static_assert(std::is_same_v<decltype(view.storage<1u>()), const entt::storage_type_t<char> *>);
static_assert(std::is_same_v<decltype(view.storage<char>()), const entt::storage_type_t<char> *>);
static_assert(std::is_same_v<decltype(view.storage<const char>()), const entt::storage_type_t<char> *>);
static_assert(std::is_same_v<decltype(view.storage<2u>()), entt::storage_type_t<double> *>);
static_assert(std::is_same_v<decltype(view.storage<double>()), entt::storage_type_t<double> *>);
static_assert(std::is_same_v<decltype(view.storage<const double>()), entt::storage_type_t<double> *>);
static_assert(std::is_same_v<decltype(view.storage<3u>()), const entt::storage_type_t<float> *>);
static_assert(std::is_same_v<decltype(view.storage<float>()), const entt::storage_type_t<float> *>);
static_assert(std::is_same_v<decltype(view.storage<const float>()), const entt::storage_type_t<float> *>);
ASSERT_TRUE(view);
ASSERT_NE(view.storage<int>(), nullptr);
ASSERT_NE(view.storage<1u>(), nullptr);
ASSERT_NE(view.storage<double>(), nullptr);
ASSERT_NE(view.storage<3u>(), nullptr);
ASSERT_EQ(view.size_hint(), 0u);
view.storage<int>()->emplace(entity);
view.storage<double>()->emplace(entity);
registry.emplace<char>(entity);
registry.emplace<float>(entity);
ASSERT_EQ(view.size_hint(), 1u);
ASSERT_EQ(view.begin(), view.end());
ASSERT_TRUE(view.storage<int>()->contains(entity));
ASSERT_TRUE(view.storage<const char>()->contains(entity));
ASSERT_TRUE(view.storage<double>()->contains(entity));
ASSERT_TRUE(view.storage<const float>()->contains(entity));
ASSERT_TRUE((registry.all_of<int, char, double, float>(entity)));
view.storage<double>()->erase(entity);
registry.erase<float>(entity);
ASSERT_EQ(view.size_hint(), 1u);
ASSERT_NE(view.begin(), view.end());
ASSERT_TRUE(view.storage<const int>()->contains(entity));
ASSERT_TRUE(view.storage<char>()->contains(entity));
ASSERT_FALSE(view.storage<const double>()->contains(entity));
ASSERT_FALSE(view.storage<float>()->contains(entity));
ASSERT_TRUE((registry.all_of<int, char>(entity)));
ASSERT_FALSE((registry.any_of<double, float>(entity)));
view.storage<0u>()->erase(entity);
ASSERT_EQ(view.size_hint(), 0u);
ASSERT_EQ(view.begin(), view.end());
ASSERT_FALSE(view.storage<0u>()->contains(entity));
ASSERT_TRUE(view.storage<1u>()->contains(entity));
ASSERT_FALSE(view.storage<2u>()->contains(entity));
ASSERT_FALSE(view.storage<3u>()->contains(entity));
ASSERT_TRUE((registry.all_of<char>(entity)));
ASSERT_FALSE((registry.any_of<int, double, float>(entity)));
view = {};
ASSERT_FALSE(view);
ASSERT_EQ(view.storage<0u>(), nullptr);
ASSERT_EQ(view.storage<const char>(), nullptr);
ASSERT_EQ(view.storage<2u>(), nullptr);
ASSERT_EQ(view.storage<const float>(), nullptr);
}
TEST(MultiComponentView, SwapStorage) {
using namespace entt::literals;
entt::registry registry;
entt::basic_view<entt::get_t<entt::storage<int>>, entt::exclude_t<const entt::storage<char>>> view;
ASSERT_FALSE(view);
ASSERT_EQ(view.storage<0u>(), nullptr);
ASSERT_EQ(view.storage<const char>(), nullptr);
const entt::entity entity{42u};
registry.emplace<int>(entity);
registry.emplace<char>(entity);
view.storage(registry.storage<int>());
view.storage<1u>(registry.storage<char>());
ASSERT_TRUE(view);
ASSERT_NE(view.storage<int>(), nullptr);
ASSERT_NE(view.storage<1u>(), nullptr);
ASSERT_EQ(view.size_hint(), 1u);
ASSERT_FALSE(view.contains(entity));
view.storage(registry.storage<char>("other"_hs));
ASSERT_EQ(view.size_hint(), 1u);
ASSERT_TRUE(view.contains(entity));
view.storage(registry.storage<int>("empty"_hs));
ASSERT_EQ(view.size_hint(), 0u);
} }
TEST(View, Pipe) { TEST(View, Pipe) {
@ -1493,10 +1241,10 @@ TEST(View, Pipe) {
registry.emplace<char>(other); registry.emplace<char>(other);
registry.emplace<stable_type>(other); registry.emplace<stable_type>(other);
auto view1 = registry.view<int>(entt::exclude<const double>); const auto view1 = registry.view<int>(entt::exclude<const double>);
auto view2 = registry.view<const char>(entt::exclude<float>); const auto view2 = registry.view<const char>(entt::exclude<float>);
auto view3 = registry.view<empty_type>(); const auto view3 = registry.view<empty_type>();
auto view4 = registry.view<stable_type>(); const auto view4 = registry.view<stable_type>();
static_assert(std::is_same_v<entt::basic_view<entt::get_t<entt::storage_type_t<int>, const entt::storage_type_t<char>>, entt::exclude_t<const entt::storage_type_t<double>, entt::storage_type_t<float>>>, decltype(view1 | view2)>); static_assert(std::is_same_v<entt::basic_view<entt::get_t<entt::storage_type_t<int>, const entt::storage_type_t<char>>, entt::exclude_t<const entt::storage_type_t<double>, entt::storage_type_t<float>>>, decltype(view1 | view2)>);
static_assert(std::is_same_v<entt::basic_view<entt::get_t<const entt::storage_type_t<char>, entt::storage_type_t<int>>, entt::exclude_t<entt::storage_type_t<float>, const entt::storage_type_t<double>>>, decltype(view2 | view1)>); static_assert(std::is_same_v<entt::basic_view<entt::get_t<const entt::storage_type_t<char>, entt::storage_type_t<int>>, entt::exclude_t<entt::storage_type_t<float>, const entt::storage_type_t<double>>>, decltype(view2 | view1)>);
@ -1513,26 +1261,30 @@ TEST(View, Pipe) {
ASSERT_FALSE((view1 | view4 | view2).contains(entity)); ASSERT_FALSE((view1 | view4 | view2).contains(entity));
ASSERT_TRUE((view1 | view4 | view2).contains(other)); ASSERT_TRUE((view1 | view4 | view2).contains(other));
}
view1 = {};
view3 = {}; TEST(MultiComponentView, Storage) {
entt::registry registry;
ASSERT_FALSE(view1); const auto entity = registry.create();
ASSERT_TRUE(view2); const auto view = registry.view<int, const char>();
ASSERT_FALSE(view3);
ASSERT_TRUE(view4); static_assert(std::is_same_v<decltype(view.storage<0u>()), entt::storage_type_t<int> &>);
static_assert(std::is_same_v<decltype(view.storage<int>()), entt::storage_type_t<int> &>);
auto pack14 = view1 | view4; static_assert(std::is_same_v<decltype(view.storage<1u>()), const entt::storage_type_t<char> &>);
auto pack32 = view3 | view2; static_assert(std::is_same_v<decltype(view.storage<const char>()), const entt::storage_type_t<char> &>);
ASSERT_FALSE(pack14); ASSERT_EQ(view.size_hint(), 0u);
ASSERT_FALSE(pack32);
view.storage<int>().emplace(entity);
ASSERT_EQ(pack14.storage<int>(), nullptr); registry.emplace<char>(entity);
ASSERT_EQ(pack14.storage<const double>(), nullptr);
ASSERT_NE(pack14.storage<stable_type>(), nullptr); ASSERT_EQ(view.size_hint(), 1u);
ASSERT_TRUE(view.storage<const char>().contains(entity));
ASSERT_EQ(pack32.storage<empty_type>(), nullptr); ASSERT_TRUE((registry.all_of<int, char>(entity)));
ASSERT_NE(pack32.storage<const char>(), nullptr);
ASSERT_NE(pack32.storage<float>(), nullptr); view.storage<0u>().erase(entity);
ASSERT_EQ(view.size_hint(), 0u);
ASSERT_TRUE(view.storage<1u>().contains(entity));
ASSERT_FALSE((registry.all_of<int, char>(entity)));
} }

View File

@ -295,23 +295,6 @@ TEST(AdjacencyMatrix, EdgesDirected) {
ASSERT_EQ(++it, iterable.end()); ASSERT_EQ(++it, iterable.end());
} }
TEST(AdjacencyMatrix, EdgesBackwardOnlyDirected) {
entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{2u};
auto iterable = adjacency_matrix.edges();
ASSERT_EQ(iterable.begin(), iterable.end());
adjacency_matrix.insert(1u, 0u);
iterable = adjacency_matrix.edges();
ASSERT_NE(iterable.begin(), iterable.end());
auto it = iterable.begin();
ASSERT_EQ(*it++, std::make_pair(std::size_t{1u}, std::size_t{0u}));
ASSERT_EQ(it, iterable.end());
}
TEST(AdjacencyMatrix, EdgesUndirected) { TEST(AdjacencyMatrix, EdgesUndirected) {
entt::adjacency_matrix<entt::undirected_tag> adjacency_matrix{3u}; entt::adjacency_matrix<entt::undirected_tag> adjacency_matrix{3u};
auto iterable = adjacency_matrix.edges(); auto iterable = adjacency_matrix.edges();
@ -334,24 +317,6 @@ TEST(AdjacencyMatrix, EdgesUndirected) {
ASSERT_EQ(++it, iterable.end()); ASSERT_EQ(++it, iterable.end());
} }
TEST(AdjacencyMatrix, EdgesBackwardOnlyUndirected) {
entt::adjacency_matrix<entt::undirected_tag> adjacency_matrix{2u};
auto iterable = adjacency_matrix.edges();
ASSERT_EQ(iterable.begin(), iterable.end());
adjacency_matrix.insert(1u, 0u);
iterable = adjacency_matrix.edges();
ASSERT_NE(iterable.begin(), iterable.end());
auto it = iterable.begin();
ASSERT_EQ(*it++, std::make_pair(std::size_t{0u}, std::size_t{1u}));
ASSERT_EQ(*it++, std::make_pair(std::size_t{1u}, std::size_t{0u}));
ASSERT_EQ(it, iterable.end());
}
TEST(AdjacencyMatrix, OutEdgesDirected) { TEST(AdjacencyMatrix, OutEdgesDirected) {
entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{3u}; entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{3u};
auto iterable = adjacency_matrix.out_edges(0u); auto iterable = adjacency_matrix.out_edges(0u);
@ -375,28 +340,6 @@ TEST(AdjacencyMatrix, OutEdgesDirected) {
ASSERT_EQ(it, iterable.cend()); ASSERT_EQ(it, iterable.cend());
} }
TEST(AdjacencyMatrix, OutEdgesBackwardOnlyDirected) {
entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{2u};
auto iterable = adjacency_matrix.out_edges(1u);
ASSERT_EQ(iterable.begin(), iterable.end());
adjacency_matrix.insert(1u, 0u);
iterable = adjacency_matrix.out_edges(1u);
ASSERT_NE(iterable.begin(), iterable.end());
auto it = iterable.begin();
ASSERT_EQ(*it++, std::make_pair(std::size_t{1u}, std::size_t{0u}));
ASSERT_EQ(it, iterable.end());
iterable = adjacency_matrix.out_edges(0u);
it = iterable.cbegin();
ASSERT_EQ(it, iterable.cend());
}
TEST(AdjacencyMatrix, OutEdgesUndirected) { TEST(AdjacencyMatrix, OutEdgesUndirected) {
entt::adjacency_matrix<entt::undirected_tag> adjacency_matrix{3u}; entt::adjacency_matrix<entt::undirected_tag> adjacency_matrix{3u};
auto iterable = adjacency_matrix.out_edges(0u); auto iterable = adjacency_matrix.out_edges(0u);
@ -422,30 +365,6 @@ TEST(AdjacencyMatrix, OutEdgesUndirected) {
ASSERT_EQ(it, iterable.cend()); ASSERT_EQ(it, iterable.cend());
} }
TEST(AdjacencyMatrix, OutEdgesBackwardOnlyUndirected) {
entt::adjacency_matrix<entt::undirected_tag> adjacency_matrix{2u};
auto iterable = adjacency_matrix.out_edges(1u);
ASSERT_EQ(iterable.begin(), iterable.end());
adjacency_matrix.insert(1u, 0u);
iterable = adjacency_matrix.out_edges(1u);
ASSERT_NE(iterable.begin(), iterable.end());
auto it = iterable.begin();
ASSERT_EQ(*it++, std::make_pair(std::size_t{1u}, std::size_t{0u}));
ASSERT_EQ(it, iterable.end());
iterable = adjacency_matrix.out_edges(0u);
it = iterable.cbegin();
ASSERT_NE(it, iterable.cend());
ASSERT_EQ(*it++, std::make_pair(std::size_t{0u}, std::size_t{1u}));
ASSERT_EQ(it, iterable.cend());
}
TEST(AdjacencyMatrix, InEdgesDirected) { TEST(AdjacencyMatrix, InEdgesDirected) {
entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{3u}; entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{3u};
auto iterable = adjacency_matrix.in_edges(1u); auto iterable = adjacency_matrix.in_edges(1u);
@ -469,28 +388,6 @@ TEST(AdjacencyMatrix, InEdgesDirected) {
ASSERT_EQ(it, iterable.cend()); ASSERT_EQ(it, iterable.cend());
} }
TEST(AdjacencyMatrix, InEdgesBackwardOnlyDirected) {
entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{2u};
auto iterable = adjacency_matrix.in_edges(0u);
ASSERT_EQ(iterable.begin(), iterable.end());
adjacency_matrix.insert(1u, 0u);
iterable = adjacency_matrix.in_edges(0u);
ASSERT_NE(iterable.begin(), iterable.end());
auto it = iterable.begin();
ASSERT_EQ(*it++, std::make_pair(std::size_t{1u}, std::size_t{0u}));
ASSERT_EQ(it, iterable.end());
iterable = adjacency_matrix.in_edges(1u);
it = iterable.cbegin();
ASSERT_EQ(it, iterable.cend());
}
TEST(AdjacencyMatrix, InEdgesUndirected) { TEST(AdjacencyMatrix, InEdgesUndirected) {
entt::adjacency_matrix<entt::undirected_tag> adjacency_matrix{3u}; entt::adjacency_matrix<entt::undirected_tag> adjacency_matrix{3u};
auto iterable = adjacency_matrix.in_edges(1u); auto iterable = adjacency_matrix.in_edges(1u);
@ -506,7 +403,6 @@ TEST(AdjacencyMatrix, InEdgesUndirected) {
auto it = iterable.begin(); auto it = iterable.begin();
ASSERT_EQ(*it++, std::make_pair(std::size_t{0u}, std::size_t{1u})); ASSERT_EQ(*it++, std::make_pair(std::size_t{0u}, std::size_t{1u}));
ASSERT_EQ(*it++, std::make_pair(std::size_t{2u}, std::size_t{1u}));
ASSERT_EQ(it, iterable.end()); ASSERT_EQ(it, iterable.end());
iterable = adjacency_matrix.in_edges(0u); iterable = adjacency_matrix.in_edges(0u);
@ -517,30 +413,6 @@ TEST(AdjacencyMatrix, InEdgesUndirected) {
ASSERT_EQ(it, iterable.cend()); ASSERT_EQ(it, iterable.cend());
} }
TEST(AdjacencyMatrix, InEdgesBackwardOnlyUndirected) {
entt::adjacency_matrix<entt::undirected_tag> adjacency_matrix{2u};
auto iterable = adjacency_matrix.in_edges(0u);
ASSERT_EQ(iterable.begin(), iterable.end());
adjacency_matrix.insert(1u, 0u);
iterable = adjacency_matrix.in_edges(0u);
ASSERT_NE(iterable.begin(), iterable.end());
auto it = iterable.begin();
ASSERT_EQ(*it++, std::make_pair(std::size_t{1u}, std::size_t{0u}));
ASSERT_EQ(it, iterable.end());
iterable = adjacency_matrix.in_edges(1u);
it = iterable.cbegin();
ASSERT_NE(it, iterable.cend());
ASSERT_EQ(*it++, std::make_pair(std::size_t{0u}, std::size_t{1u}));
ASSERT_EQ(it, iterable.cend());
}
TEST(AdjacencyMatrix, ThrowingAllocator) { TEST(AdjacencyMatrix, ThrowingAllocator) {
using allocator = test::throwing_allocator<std::size_t>; using allocator = test::throwing_allocator<std::size_t>;
using exception = typename allocator::exception_type; using exception = typename allocator::exception_type;

View File

@ -1,7 +1,6 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <entt/core/hashed_string.hpp> #include <entt/core/hashed_string.hpp>
#include <entt/graph/flow.hpp> #include <entt/graph/flow.hpp>
#include "../common/config.h"
#include "../common/throwing_allocator.hpp" #include "../common/throwing_allocator.hpp"
TEST(Flow, Constructors) { TEST(Flow, Constructors) {
@ -25,9 +24,9 @@ TEST(Flow, Constructors) {
ASSERT_EQ(flow.size(), 0u); ASSERT_EQ(flow.size(), 0u);
ASSERT_EQ(other.size(), 3u); ASSERT_EQ(other.size(), 3u);
ASSERT_EQ(other[0u], 0u); ASSERT_EQ(other[0u], 0);
ASSERT_EQ(other[1u], 3u); ASSERT_EQ(other[1u], 3);
ASSERT_EQ(other[2u], 99u); ASSERT_EQ(other[2u], 99);
} }
TEST(Flow, Copy) { TEST(Flow, Copy) {
@ -42,9 +41,9 @@ TEST(Flow, Copy) {
ASSERT_EQ(flow.size(), 3u); ASSERT_EQ(flow.size(), 3u);
ASSERT_EQ(other.size(), 3u); ASSERT_EQ(other.size(), 3u);
ASSERT_EQ(other[0u], 0u); ASSERT_EQ(other[0u], 0);
ASSERT_EQ(other[1u], 3u); ASSERT_EQ(other[1u], 3);
ASSERT_EQ(other[2u], 99u); ASSERT_EQ(other[2u], 99);
flow.bind(1); flow.bind(1);
other.bind(2); other.bind(2);
@ -54,10 +53,10 @@ TEST(Flow, Copy) {
ASSERT_EQ(other.size(), 4u); ASSERT_EQ(other.size(), 4u);
ASSERT_EQ(flow.size(), 4u); ASSERT_EQ(flow.size(), 4u);
ASSERT_EQ(other[0u], 0u); ASSERT_EQ(other[0u], 0);
ASSERT_EQ(other[1u], 3u); ASSERT_EQ(other[1u], 3);
ASSERT_EQ(other[2u], 99u); ASSERT_EQ(other[2u], 99);
ASSERT_EQ(other[3u], 1u); ASSERT_EQ(other[3u], 1);
} }
TEST(Flow, Move) { TEST(Flow, Move) {
@ -72,9 +71,9 @@ TEST(Flow, Move) {
ASSERT_EQ(flow.size(), 0u); ASSERT_EQ(flow.size(), 0u);
ASSERT_EQ(other.size(), 3u); ASSERT_EQ(other.size(), 3u);
ASSERT_EQ(other[0u], 0u); ASSERT_EQ(other[0u], 0);
ASSERT_EQ(other[1u], 3u); ASSERT_EQ(other[1u], 3);
ASSERT_EQ(other[2u], 99u); ASSERT_EQ(other[2u], 99);
flow = {}; flow = {};
flow.bind(1); flow.bind(1);
@ -85,7 +84,7 @@ TEST(Flow, Move) {
ASSERT_EQ(other.size(), 1u); ASSERT_EQ(other.size(), 1u);
ASSERT_EQ(flow.size(), 0u); ASSERT_EQ(flow.size(), 0u);
ASSERT_EQ(other[0u], 1u); ASSERT_EQ(other[0u], 1);
} }
TEST(Flow, Swap) { TEST(Flow, Swap) {
@ -96,13 +95,13 @@ TEST(Flow, Swap) {
ASSERT_EQ(other.size(), 0u); ASSERT_EQ(other.size(), 0u);
ASSERT_EQ(flow.size(), 1u); ASSERT_EQ(flow.size(), 1u);
ASSERT_EQ(flow[0u], 7u); ASSERT_EQ(flow[0u], 7);
flow.swap(other); flow.swap(other);
ASSERT_EQ(other.size(), 1u); ASSERT_EQ(other.size(), 1u);
ASSERT_EQ(flow.size(), 0u); ASSERT_EQ(flow.size(), 0u);
ASSERT_EQ(other[0u], 7u); ASSERT_EQ(other[0u], 7);
} }
TEST(Flow, Clear) { TEST(Flow, Clear) {
@ -112,8 +111,8 @@ TEST(Flow, Clear) {
flow.bind(99); flow.bind(99);
ASSERT_EQ(flow.size(), 2u); ASSERT_EQ(flow.size(), 2u);
ASSERT_EQ(flow[0u], 0u); ASSERT_EQ(flow[0u], 0);
ASSERT_EQ(flow[1u], 99u); ASSERT_EQ(flow[1u], 99);
flow.clear(); flow.clear();
@ -271,57 +270,6 @@ TEST(Flow, Sync) {
ASSERT_EQ(it, last); ASSERT_EQ(it, last);
} }
ENTT_DEBUG_TEST(FlowDeathTest, NoBind) {
entt::flow flow{};
ASSERT_DEATH(flow.ro(42), "");
ASSERT_DEATH(flow.rw(42), "");
flow.bind(0);
ASSERT_NO_FATAL_FAILURE(flow.ro(1));
ASSERT_NO_FATAL_FAILURE(flow.rw(2));
}
TEST(Flow, DirectRebind) {
entt::flow flow{};
flow.bind(0).ro(10).rw(10).bind(1).ro(10);
auto graph = flow.graph();
ASSERT_EQ(flow.size(), 2u);
ASSERT_EQ(flow.size(), graph.size());
ASSERT_NE(graph.edges().cbegin(), graph.edges().cend());
ASSERT_TRUE(graph.contains(0u, 1u));
ASSERT_FALSE(graph.contains(1u, 0u));
}
TEST(Flow, DeferredRebind) {
entt::flow flow{};
flow.bind(0).ro(10).bind(1).ro(10).bind(0).rw(10);
auto graph = flow.graph();
ASSERT_EQ(flow.size(), 2u);
ASSERT_EQ(flow.size(), graph.size());
ASSERT_NE(graph.edges().cbegin(), graph.edges().cend());
ASSERT_FALSE(graph.contains(0u, 1u));
ASSERT_TRUE(graph.contains(1u, 0u));
}
TEST(Flow, Loop) {
entt::flow flow{};
flow.bind(0).rw(10).bind(1).ro(10).bind(0).rw(10);
auto graph = flow.graph();
ASSERT_EQ(flow.size(), 2u);
ASSERT_EQ(flow.size(), graph.size());
ASSERT_NE(graph.edges().cbegin(), graph.edges().cend());
ASSERT_TRUE(graph.contains(0u, 1u));
ASSERT_TRUE(graph.contains(1u, 0u));
}
TEST(Flow, ThrowingAllocator) { TEST(Flow, ThrowingAllocator) {
using allocator = test::throwing_allocator<entt::id_type>; using allocator = test::throwing_allocator<entt::id_type>;
using task_allocator = test::throwing_allocator<std::pair<std::size_t, entt::id_type>>; using task_allocator = test::throwing_allocator<std::pair<std::size_t, entt::id_type>>;

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