update subtree entt Merge commit '90ce4bda4e1dc23508bbd6b6923156cd5a370c18'

This commit is contained in:
Green Sky 2023-10-02 15:30:10 +02:00
commit 2f8eca71a4
No known key found for this signature in database
153 changed files with 24263 additions and 14220 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,27 +1,26 @@
* debugging tools (#60): the issue online already contains interesting tips on this, look at it
* work stealing job system (see #100) + mt scheduler based on const awareness for types
EXAMPLES
* filter on runtime values/variables (not only types)
* support to polymorphic types (see #859)
DOC:
* storage<void>
* custom storage/view
* examples (and credits) from @alanjfs :)
* update entity doc when the storage based model is in place
* in-place O(1) release/destroy for non-orphaned entities, out-of-sync model
TODO (high prio):
* remove the static storage from the const assure in the registry
* check natvis files (periodically :)
* 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:
* get rid of observers, storage based views made them pointless - document alternatives
* add storage getter for filters to views and groups
* exploit the tombstone mechanism to allow enabling/disabling entities (see bump, compact and clear for further details)
* basic_storage::bind for cross-registry setups (see and remove todo from entity_copy.cpp)
* process scheduler: reviews, use free lists internally
* dedicated entity storage, in-place O(1) release/destroy for non-orphaned entities, out-of-sync model
* entity-only and exclude-only views (both solved with entity storage and storage<void>)
* custom allocators all over (registry, ...)
* add test for maximum number of entities reached
* deprecate non-owning groups in favor of owning views and view packs, introduce lazy owning views
* 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,8 +2,22 @@
# Doxygen configuration (documentation)
#
set(DOXY_DEPS_DIRECTORY ${EnTT_SOURCE_DIR}/deps)
FetchContent_Declare(
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_CSS_DIRECTORY ${doxygen-awesome-css_INCLUDE_DIR})
set(DOXY_DOCS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
set(DOXY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})

View File

@ -1,4 +1,4 @@
# Doxyfile 1.9.4
# Doxyfile 1.9.6
# This file describes the settings to be used by the documentation system
# doxygen (www.doxygen.org) for a project.
@ -19,7 +19,8 @@
# configuration file:
# doxygen -x [configFile]
# Use doxygen to compare the used configuration file with the template
# configuration file without replacing the environment variables:
# configuration file without replacing the environment variables or CMake type
# replacement variables:
# doxygen -x_noenv [configFile]
#---------------------------------------------------------------------------
@ -85,7 +86,7 @@ CREATE_SUBDIRS = NO
# level increment doubles the number of directories, resulting in 4096
# 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
# numer of 16 directories.
# number of 16 directories.
# Minimum value: 0, maximum value: 8, default value: 8.
# This tag requires that the tag CREATE_SUBDIRS is set to YES.
@ -557,7 +558,8 @@ HIDE_UNDOC_MEMBERS = NO
# 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
# to NO, these classes will be included in the various overviews. This option
# has no effect if EXTRACT_ALL is enabled.
# will also hide undocumented C++ concepts if enabled. This option has no effect
# if EXTRACT_ALL is enabled.
# The default value is: NO.
HIDE_UNDOC_CLASSES = NO
@ -595,7 +597,8 @@ INTERNAL_DOCS = NO
# 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
# YES.
# The default value is: system dependent.
# Possible values are: SYSTEM, NO and YES.
# The default value is: SYSTEM.
CASE_SENSE_NAMES = YES
@ -847,6 +850,14 @@ WARN_IF_INCOMPLETE_DOC = 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
# 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
@ -905,10 +916,21 @@ INPUT = @DOXY_SOURCE_DIRECTORY@ \
# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
# documentation (see:
# https://www.gnu.org/software/libiconv/) for the list of possible encodings.
# See also: INPUT_FILE_ENCODING
# The default value is: 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
# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
# *.h) to filter out the source-files in the directories.
@ -945,7 +967,7 @@ RECURSIVE = YES
# Note that relative paths are relative to the directory from which doxygen is
# run.
EXCLUDE = @DOXY_DEPS_DIRECTORY@
EXCLUDE =
# 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
@ -1015,6 +1037,11 @@ IMAGE_PATH =
# code is scanned, but not when the output code is generated. If lines are added
# 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
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
# properly processed by doxygen.
@ -1056,6 +1083,15 @@ FILTER_SOURCE_PATTERNS =
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
#---------------------------------------------------------------------------
@ -1193,10 +1229,11 @@ CLANG_DATABASE_PATH =
ALPHABETICAL_INDEX = YES
# In case all classes in a project start with a common prefix, all classes will
# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
# can be used to specify a prefix (or a list of prefixes) that should be ignored
# while generating the index headers.
# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes)
# that should be ignored while generating the index headers. The IGNORE_PREFIX
# tag works for classes, function and member names. The entity will be placed in
# the alphabetical list under the first letter of the entity name that remains
# after removing the prefix.
# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
IGNORE_PREFIX =
@ -1265,7 +1302,7 @@ HTML_FOOTER =
# obsolete.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_STYLESHEET =
HTML_STYLESHEET = @DOXY_CSS_DIRECTORY@/doxygen-awesome.css
# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
# cascading style sheets that are included after the standard style sheets
@ -1275,7 +1312,12 @@ HTML_STYLESHEET =
# 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
# style sheet in the list overrules the setting of the previous ones in the
# list). For an example see the documentation.
# list).
# 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.
HTML_EXTRA_STYLESHEET =
@ -1290,6 +1332,19 @@ HTML_EXTRA_STYLESHEET =
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
# 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
@ -1653,17 +1708,6 @@ HTML_FORMULA_FORMAT = png
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
# to create new LaTeX commands to be used in formulas as building blocks. See
# the section "Including formulas" for details.
@ -2379,26 +2423,38 @@ HAVE_DOT = YES
DOT_NUM_THREADS = 0
# When you want a differently looking font in the dot files that doxygen
# generates you can specify the font name using DOT_FONTNAME. You need to make
# sure dot is able to find the font, which can be done by putting it in a
# standard location or by setting the DOTFONTPATH environment variable or by
# setting DOT_FONTPATH to the directory containing the font.
# The default value is: Helvetica.
# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of
# subgraphs. When you want a differently looking font in the dot files that
# doxygen generates you can specify fontname, fontcolor and fontsize attributes.
# For details please see <a href=https://graphviz.org/doc/info/attrs.html>Node,
# Edge and Graph Attributes specification</a> You need to make sure dot is able
# to find the font, which can be done by putting it in a standard location or by
# 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.
DOT_FONTNAME = Helvetica
DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10"
# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
# dot graphs.
# Minimum value: 4, maximum value: 24, default value: 10.
# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can
# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. <a
# href=https://graphviz.org/doc/info/arrows.html>Complete documentation about
# arrows shapes.</a>
# The default value is: labelfontname=Helvetica,labelfontsize=10.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_FONTSIZE = 10
DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10"
# By default doxygen will tell dot to use the default font as specified with
# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
# the path where dot can find it using this tag.
# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes
# around nodes set 'shape=plain' or 'shape=plaintext' <a
# href=https://www.graphviz.org/doc/info/shapes.html>Shapes specification</a>
# 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.
DOT_FONTPATH =
@ -2641,18 +2697,6 @@ DOT_GRAPH_MAX_NODES = 50
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
# 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,22 @@
# 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
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
@ -7,13 +24,18 @@ source projects based on `EnTT` and didn't hold back when it came to documenting
them.
Below an incomplete list of games, applications and articles that can be used as
a reference. Where I put the word _apparently_ means that the use of `EnTT` is
documented but the authors didn't make explicit announcements or contacted me
directly.
a reference.<br/>
Where I put the word _apparently_ means that the use of `EnTT` is documented but
the authors didn't make explicit announcements or contacted me directly.
I hope this list can grow much more in the future:
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.<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
[Mojang](https://mojang.com/): of course, **that** Minecraft, see the
open source attributions page for more details.
@ -103,7 +125,8 @@ I hope this list can grow much more in the future:
* [Confetti Party](https://github.com/hexerei/entt-confetti): C++ sample
application as a starting point using `EnTT` and `SDL2`.
* Engines and the like:
## Engines and the like:
* [Aether Engine](https://hadean.com/spatial-simulation/)
[v1.1+](https://docs.hadean.com/v1.1/Licenses/) by
[Hadean](https://hadean.com/): a library designed for spatially partitioning
@ -168,8 +191,19 @@ I hope this list can grow much more in the future:
engine based on `SDL2` and `EnTT`.
* [Ducktape](https://github.com/DucktapeEngine/Ducktape): an open source C++
2D & 3D game engine that focuses on being fast and powerful.
* [The Worst Engine](https://github.com/Parasik72/TWE): a game engine based on
OpenGL.
* [Ecsact](https://ecsact.dev/): a language aimed at describing ECS, with a
[runtime implementation](https://github.com/ecsact-dev/ecsact_rt_entt) based
on `EnTT`.
* [AGE (Arc Game Engine)](https://github.com/MohitSethi99/ArcGameEngine): an
open-source engine for building 2D & 3D real-time rendering and interactive
contents.
* [Kengine](https://github.com/phisko/kengine): the _Koala engine_ is a game
engine entirely implemented as an entity-component-ystem.
## Articles, videos and blog posts:
* Articles, videos and blog posts:
* [Some posts](https://skypjack.github.io/tags/#entt) on my personal
[blog](https://skypjack.github.io/) are about `EnTT`, for those who want to
know **more** on this project.
@ -193,6 +227,12 @@ I hope this list can grow much more in the future:
- ... And so on.
[Check out](https://www.youtube.com/channel/UCQ-W1KE9EYfdxhL6S4twUNw) the
_Game Engine Series_ by The Cherno for more videos.
* [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):
huge space battle built entirely from scratch.
* [Space Battle](https://github.com/vblanco20-1/ECS_SpaceBattle): huge space
@ -219,7 +259,8 @@ I hope this list can grow much more in the future:
MMO(RPG)s and its [follow-up](https://youtu.be/yGlZeopx2hU) episode about
player bots and full external ECS: a series definitely worth looking at.
* Any Other Business:
## Any Other Business:
* [ArcGIS Runtime SDKs](https://developers.arcgis.com/arcgis-runtime/) by
[Esri](https://www.esri.com/): they use `EnTT` for the internal ECS and the
cross platform C++ rendering engine. The SDKs are utilized by a lot of
@ -261,6 +302,3 @@ I hope this list can grow much more in the future:
* GitHub contains also
[many other examples](https://github.com/search?o=desc&q=%22skypjack%2Fentt%22&s=indexed&type=Code)
of use of `EnTT` from which to take inspiration if interested.
If you know of other resources out there that are about `EnTT`, feel free to
open an issue or a PR and I'll be glad to add them to this page.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -59,7 +59,7 @@ class basic_any {
};
template<typename Type>
static constexpr bool in_situ = Len && alignof(Type) <= Align && sizeof(Type) <= Len &&std::is_nothrow_move_constructible_v<Type>;
static constexpr bool in_situ = Len && alignof(Type) <= Align && sizeof(Type) <= Len && std::is_nothrow_move_constructible_v<Type>;
template<typename Type>
static const void *basic_vtable(const operation op, const basic_any &value, const void *other) {
@ -128,17 +128,17 @@ class basic_any {
vtable = basic_vtable<std::remove_cv_t<std::remove_reference_t<Type>>>;
if constexpr(std::is_lvalue_reference_v<Type>) {
static_assert(sizeof...(Args) == 1u && (std::is_lvalue_reference_v<Args> && ...), "Invalid arguments");
static_assert((std::is_lvalue_reference_v<Args> && ...) && (sizeof...(Args) == 1u), "Invalid arguments");
mode = std::is_const_v<std::remove_reference_t<Type>> ? policy::cref : policy::ref;
instance = (std::addressof(args), ...);
} else if constexpr(in_situ<std::remove_cv_t<std::remove_reference_t<Type>>>) {
if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v<std::remove_cv_t<std::remove_reference_t<Type>>>) {
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>>>)) {
new(&storage) std::remove_cv_t<std::remove_reference_t<Type>>{std::forward<Args>(args)...};
} else {
new(&storage) std::remove_cv_t<std::remove_reference_t<Type>>(std::forward<Args>(args)...);
}
} else {
if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v<std::remove_cv_t<std::remove_reference_t<Type>>>) {
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>>>)) {
instance = new std::remove_cv_t<std::remove_reference_t<Type>>{std::forward<Args>(args)...};
} else {
instance = new std::remove_cv_t<std::remove_reference_t<Type>>(std::forward<Args>(args)...);
@ -428,7 +428,7 @@ private:
* @return The element converted to the requested type.
*/
template<typename Type, std::size_t Len, std::size_t Align>
Type any_cast(const basic_any<Len, Align> &data) noexcept {
[[nodiscard]] Type any_cast(const basic_any<Len, Align> &data) noexcept {
const auto *const instance = any_cast<std::remove_reference_t<Type>>(&data);
ENTT_ASSERT(instance, "Invalid instance");
return static_cast<Type>(*instance);
@ -436,7 +436,7 @@ Type any_cast(const basic_any<Len, Align> &data) noexcept {
/*! @copydoc any_cast */
template<typename Type, std::size_t Len, std::size_t Align>
Type any_cast(basic_any<Len, Align> &data) noexcept {
[[nodiscard]] Type any_cast(basic_any<Len, Align> &data) noexcept {
// forces const on non-reference types to make them work also with wrappers for const references
auto *const instance = any_cast<std::remove_reference_t<const Type>>(&data);
ENTT_ASSERT(instance, "Invalid instance");
@ -445,7 +445,7 @@ Type any_cast(basic_any<Len, Align> &data) noexcept {
/*! @copydoc any_cast */
template<typename Type, std::size_t Len, std::size_t Align>
Type any_cast(basic_any<Len, Align> &&data) noexcept {
[[nodiscard]] Type any_cast(basic_any<Len, Align> &&data) noexcept {
if constexpr(std::is_copy_constructible_v<std::remove_cv_t<std::remove_reference_t<Type>>>) {
if(auto *const instance = any_cast<std::remove_reference_t<Type>>(&data); instance) {
return static_cast<Type>(std::move(*instance));
@ -461,14 +461,14 @@ Type any_cast(basic_any<Len, Align> &&data) noexcept {
/*! @copydoc any_cast */
template<typename Type, std::size_t Len, std::size_t Align>
const Type *any_cast(const basic_any<Len, Align> *data) noexcept {
[[nodiscard]] const Type *any_cast(const basic_any<Len, Align> *data) noexcept {
const auto &info = type_id<std::remove_cv_t<Type>>();
return static_cast<const Type *>(data->data(info));
}
/*! @copydoc any_cast */
template<typename Type, std::size_t Len, std::size_t Align>
Type *any_cast(basic_any<Len, Align> *data) noexcept {
[[nodiscard]] Type *any_cast(basic_any<Len, Align> *data) noexcept {
if constexpr(std::is_const_v<Type>) {
// last attempt to make wrappers for const references return their values
return any_cast<Type>(&std::as_const(*data));
@ -488,7 +488,7 @@ Type *any_cast(basic_any<Len, Align> *data) noexcept {
* @return A properly initialized wrapper for an object of the given type.
*/
template<typename Type, std::size_t Len = basic_any<>::length, std::size_t Align = basic_any<Len>::alignment, typename... Args>
basic_any<Len, Align> make_any(Args &&...args) {
[[nodiscard]] basic_any<Len, Align> make_any(Args &&...args) {
return basic_any<Len, Align>{std::in_place_type<Type>, std::forward<Args>(args)...};
}
@ -501,7 +501,7 @@ basic_any<Len, Align> make_any(Args &&...args) {
* @return A properly initialized and not necessarily owning wrapper.
*/
template<std::size_t Len = basic_any<>::length, std::size_t Align = basic_any<Len>::alignment, typename Type>
basic_any<Len, Align> forward_as_any(Type &&value) {
[[nodiscard]] basic_any<Len, Align> forward_as_any(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>
class basic_hashed_string: internal::basic_hashed_string<Char> {
using base_type = internal::basic_hashed_string<Char>;
using hs_traits = internal::fnv1a_traits<id_type>;
using traits_type = internal::fnv1a_traits<id_type>;
struct const_wrapper {
// 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
[[nodiscard]] static constexpr auto helper(const Char *str) noexcept {
base_type base{str, 0u, hs_traits::offset};
base_type base{str, 0u, traits_type::offset};
for(; str[base.length]; ++base.length) {
base.hash = (base.hash ^ static_cast<hs_traits::type>(str[base.length])) * hs_traits::prime;
base.hash = (base.hash ^ static_cast<traits_type::type>(str[base.length])) * traits_type::prime;
}
return base;
@ -90,10 +90,10 @@ class basic_hashed_string: internal::basic_hashed_string<Char> {
// FowlerNollVo hash function v. 1a - the good
[[nodiscard]] static constexpr auto helper(const Char *str, const std::size_t len) noexcept {
base_type base{str, len, hs_traits::offset};
base_type base{str, len, traits_type::offset};
for(size_type pos{}; pos < len; ++pos) {
base.hash = (base.hash ^ static_cast<hs_traits::type>(str[pos])) * hs_traits::prime;
base.hash = (base.hash ^ static_cast<traits_type::type>(str[pos])) * traits_type::prime;
}
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).
* @tparam Args Types of arguments to use to construct the object.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename 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.
*/
constexpr void operator()(pointer ptr) noexcept(std::is_nothrow_destructible_v<typename allocator_type::value_type>) {
using alloc_traits = typename std::allocator_traits<Allocator>;
using alloc_traits = std::allocator_traits<Allocator>;
alloc_traits::destroy(*this, to_address(ptr));
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.
* @param args Parameters to use to construct the instance.
*/
template<class... Args>
template<typename... Args>
constexpr forward_apply(Args &&...args) noexcept(std::is_nothrow_constructible_v<Func, Args...>)
: Func{std::forward<Args>(args)...} {}
@ -79,13 +79,13 @@ struct forward_apply: private Func {
* @param args Parameters to forward to the underlying function.
* @return Return value of the underlying function, if any.
*/
template<class Type>
template<typename Type>
constexpr decltype(auto) operator()(Type &&args) noexcept(noexcept(std::apply(std::declval<Func &>(), args))) {
return std::apply(static_cast<Func &>(*this), std::forward<Type>(args));
}
/*! @copydoc operator()() */
template<class Type>
template<typename Type>
constexpr decltype(auto) operator()(Type &&args) const noexcept(noexcept(std::apply(std::declval<const Func &>(), args))) {
return std::apply(static_cast<const Func &>(*this), std::forward<Type>(args));
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
#ifndef ENTT_ENTITY_FWD_HPP
#define ENTT_ENTITY_FWD_HPP
#include <cstdint>
#include <memory>
#include <type_traits>
#include "../core/fwd.hpp"
@ -11,6 +12,14 @@ namespace entt {
/*! @brief Default entity identifier. */
enum class entity : id_type {};
/*! @brief Storage deletion policy. */
enum class deletion_policy : std::uint8_t {
/*! @brief Swap-and-pop deletion policy. */
swap_and_pop = 0u,
/*! @brief In-place deletion policy. */
in_place = 1u
};
template<typename Entity = entity, typename = std::allocator<Entity>>
class basic_sparse_set;
@ -18,18 +27,18 @@ template<typename Type, typename = entity, typename = std::allocator<Type>, type
class basic_storage;
template<typename Type>
class sigh_storage_mixin;
class sigh_mixin;
/**
* @brief Provides a common way to define storage types.
* @tparam Type Storage value type.
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Entity A valid entity type.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Type, typename Entity = entity, typename Allocator = std::allocator<Type>, typename = void>
struct storage_type {
/*! @brief Type-to-storage conversion result. */
using type = sigh_storage_mixin<basic_storage<Type, Entity, Allocator>>;
using type = sigh_mixin<basic_storage<Type, Entity, Allocator>>;
};
/**
@ -42,7 +51,7 @@ using storage_type_t = typename storage_type<Args...>::type;
/**
* Type-to-storage conversion utility that preserves constness.
* @tparam Type Storage value type, eventually const.
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Entity A valid entity type.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Type, typename Entity = entity, typename Allocator = std::allocator<std::remove_const_t<Type>>>
@ -70,7 +79,7 @@ class basic_runtime_view;
template<typename, typename, typename>
class basic_group;
template<typename>
template<typename, typename Mask = std::uint32_t, typename = std::allocator<Mask>>
class basic_observer;
template<typename>
@ -93,7 +102,10 @@ class basic_continuous_loader;
* @tparam Type List of types.
*/
template<typename... Type>
using exclude_t = type_list<Type...>;
struct exclude_t final: type_list<Type...> {
/*! @brief Default constructor. */
explicit constexpr exclude_t() {}
};
/**
* @brief Variable template for exclusion lists.
@ -107,7 +119,10 @@ inline constexpr exclude_t<Type...> exclude{};
* @tparam Type List of types.
*/
template<typename... Type>
using get_t = type_list<Type...>;
struct get_t final: type_list<Type...> {
/*! @brief Default constructor. */
explicit constexpr get_t() {}
};
/**
* @brief Variable template for lists of observed components.
@ -121,7 +136,10 @@ inline constexpr get_t<Type...> get{};
* @tparam Type List of types.
*/
template<typename... Type>
using owned_t = type_list<Type...>;
struct owned_t final: type_list<Type...> {
/*! @brief Default constructor. */
explicit constexpr owned_t() {}
};
/**
* @brief Variable template for lists of owned components.
@ -130,6 +148,39 @@ using owned_t = type_list<Type...>;
template<typename... Type>
inline constexpr owned_t<Type...> owned{};
/**
* @brief Applies a given _function_ to a get list and generate a new list.
* @tparam Type Types provided by the get list.
* @tparam Op Unary operation as template class with a type member named `type`.
*/
template<typename... Type, template<typename...> class Op>
struct type_list_transform<get_t<Type...>, Op> {
/*! @brief Resulting get list after applying the transform function. */
using type = get_t<typename Op<Type>::type...>;
};
/**
* @brief Applies a given _function_ to an exclude list and generate a new list.
* @tparam Type Types provided by the exclude list.
* @tparam Op Unary operation as template class with a type member named `type`.
*/
template<typename... Type, template<typename...> class Op>
struct type_list_transform<exclude_t<Type...>, Op> {
/*! @brief Resulting exclude list after applying the transform function. */
using type = exclude_t<typename Op<Type>::type...>;
};
/**
* @brief Applies a given _function_ to an owned list and generate a new list.
* @tparam Type Types provided by the owned list.
* @tparam Op Unary operation as template class with a type member named `type`.
*/
template<typename... Type, template<typename...> class Op>
struct type_list_transform<owned_t<Type...>, Op> {
/*! @brief Resulting owned list after applying the transform function. */
using type = owned_t<typename Op<Type>::type...>;
};
/*! @brief Alias declaration for the most common use case. */
using sparse_set = basic_sparse_set<>;

View File

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

View File

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

View File

@ -3,10 +3,10 @@
#include <memory>
#include <type_traits>
#include <utility>
#include "../core/fwd.hpp"
#include "../core/type_traits.hpp"
#include "../signal/delegate.hpp"
#include "component.hpp"
#include "fwd.hpp"
#include "group.hpp"
#include "view.hpp"
@ -28,7 +28,7 @@ public:
/*! @brief Type of registry to convert. */
using registry_type = Registry;
/*! @brief Underlying entity identifier. */
using entity_type = std::remove_const_t<typename registry_type::entity_type>;
using entity_type = typename registry_type::entity_type;
/**
* @brief Constructs a converter for a given registry.
@ -71,7 +71,7 @@ public:
/*! @brief Type of registry to convert. */
using registry_type = Registry;
/*! @brief Underlying entity identifier. */
using entity_type = std::remove_const_t<typename registry_type::entity_type>;
using entity_type = typename registry_type::entity_type;
/**
* @brief Constructs a converter for a given registry.
@ -126,19 +126,133 @@ void invoke(Registry &reg, const typename Registry::entity_type entt) {
*/
template<typename Registry, typename Component>
typename Registry::entity_type to_entity(const Registry &reg, const Component &instance) {
const auto &storage = reg.template storage<Component>();
const typename Registry::base_type &base = storage;
if(const auto *storage = reg.template storage<Component>(); storage) {
constexpr auto page_size = std::remove_const_t<std::remove_pointer_t<decltype(storage)>>::traits_type::page_size;
const typename Registry::common_type &base = *storage;
const auto *addr = std::addressof(instance);
for(auto it = base.rbegin(), last = base.rend(); it < last; it += component_traits<Component>::page_size) {
if(const auto dist = (addr - std::addressof(storage.get(*it))); dist >= 0 && dist < static_cast<decltype(dist)>(component_traits<Component>::page_size)) {
for(auto it = base.rbegin(), last = base.rend(); it < last; it += page_size) {
if(const auto dist = (addr - std::addressof(storage->get(*it))); dist >= 0 && dist < static_cast<decltype(dist)>(page_size)) {
return *(it + dist);
}
}
}
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
#endif

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -24,6 +24,7 @@
#include "entity/group.hpp"
#include "entity/handle.hpp"
#include "entity/helper.hpp"
#include "entity/mixin.hpp"
#include "entity/observer.hpp"
#include "entity/organizer.hpp"
#include "entity/registry.hpp"
@ -31,7 +32,6 @@
#include "entity/snapshot.hpp"
#include "entity/sparse_set.hpp"
#include "entity/storage.hpp"
#include "entity/storage_mixin.hpp"
#include "entity/view.hpp"
#include "graph/adjacency_matrix.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 {
const auto it = matrix.cbegin();
const auto from = vertex * vert;
const auto to = vertex * vert + vert;
const auto to = from + vert;
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 {
const auto it = matrix.cbegin();
const auto from = vertex;
const auto to = vert * (vert - 1u) + vertex;
const auto to = vert * vert + from;
return {{it, vert, from, to, vert}, {it, vert, to, to, vert}};
}

View File

@ -32,6 +32,7 @@ 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 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 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) {
ENTT_ASSERT(index.first() < vertices.size(), "Invalid node");
@ -43,6 +44,76 @@ class basic_flow {
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:
/*! @brief Allocator type. */
using allocator_type = Allocator;
@ -50,6 +121,8 @@ public:
using size_type = std::size_t;
/*! @brief Iterable task list. */
using iterable = iterable_adaptor<typename task_container_type::const_iterator>;
/*! @brief Adjacency matrix type. */
using graph_type = adjacency_matrix_type;
/*! @brief Default constructor. */
basic_flow()
@ -124,9 +197,10 @@ public:
/*! @brief Clears the flow builder. */
void clear() noexcept {
index.first() = sync_on = {};
index.first() = {};
vertices.clear();
deps.clear();
sync_on = {};
}
/**
@ -245,72 +319,12 @@ public:
* @brief Generates a task graph for the current content.
* @return The adjacency matrix of the task graph.
*/
[[nodiscard]] adjacency_matrix<directed_tag> graph() const {
const auto length = vertices.size();
adjacency_matrix<directed_tag> matrix{length};
[[nodiscard]] graph_type graph() const {
graph_type matrix{vertices.size(), get_allocator()};
// creates the adjacency matrix
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;
}
}
}
}
// 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);
}
}
}
}
}
setup_graph(matrix);
transitive_closure(matrix);
transitive_reduction(matrix);
return matrix;
}

View File

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

View File

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

View File

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

View File

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

View File

@ -68,11 +68,7 @@ struct as_void_t final {
*/
template<typename Type>
struct is_meta_policy
: 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>> {};
: 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>> {};
/**
* @brief Helper variable template.

View File

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

View File

@ -30,7 +30,6 @@ struct meta_associative_container_traits;
/**
* @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.
* @tparam Type Potentially pointer-like type.
*/
template<typename>
struct is_meta_pointer_like: std::false_type {};

View File

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

View File

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

View File

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

View File

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

View File

@ -95,51 +95,51 @@ public:
return operator*();
}
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
friend constexpr std::ptrdiff_t operator-(const resource_cache_iterator<TLhs, ILhs> &, const resource_cache_iterator<TRhs, IRhs> &) noexcept;
template<typename... Lhs, typename... Rhs>
friend constexpr std::ptrdiff_t operator-(const resource_cache_iterator<Lhs...> &, const resource_cache_iterator<Rhs...> &) noexcept;
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
friend constexpr bool operator==(const resource_cache_iterator<TLhs, ILhs> &, const resource_cache_iterator<TRhs, IRhs> &) noexcept;
template<typename... Lhs, typename... Rhs>
friend constexpr bool operator==(const resource_cache_iterator<Lhs...> &, const resource_cache_iterator<Rhs...> &) noexcept;
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
friend constexpr bool operator<(const resource_cache_iterator<TLhs, ILhs> &, const resource_cache_iterator<TRhs, IRhs> &) noexcept;
template<typename... Lhs, typename... Rhs>
friend constexpr bool operator<(const resource_cache_iterator<Lhs...> &, const resource_cache_iterator<Rhs...> &) noexcept;
private:
It it;
};
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
[[nodiscard]] constexpr std::ptrdiff_t operator-(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) noexcept {
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr std::ptrdiff_t operator-(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
return lhs.it - rhs.it;
}
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
[[nodiscard]] constexpr bool operator==(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) noexcept {
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator==(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
return lhs.it == rhs.it;
}
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
[[nodiscard]] constexpr bool operator!=(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) noexcept {
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator!=(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
return !(lhs == rhs);
}
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
[[nodiscard]] constexpr bool operator<(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) noexcept {
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator<(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
return lhs.it < rhs.it;
}
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
[[nodiscard]] constexpr bool operator>(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) noexcept {
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator>(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
return rhs < lhs;
}
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
[[nodiscard]] constexpr bool operator<=(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) noexcept {
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator<=(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
return !(lhs > rhs);
}
template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
[[nodiscard]] constexpr bool operator>=(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) noexcept {
template<typename... Lhs, typename... Rhs>
[[nodiscard]] constexpr bool operator>=(const resource_cache_iterator<Lhs...> &lhs, const resource_cache_iterator<Rhs...> &rhs) noexcept {
return !(lhs < rhs);
}
@ -158,7 +158,7 @@ template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
*/
template<typename Type, typename Loader, typename Allocator>
class resource_cache {
using alloc_traits = typename 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");
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>;
@ -241,8 +241,7 @@ public:
/**
* @brief Returns an iterator to the beginning.
*
* The returned iterator points to the first instance of the cache. If the
* cache is empty, the returned iterator will be equal to `end()`.
* If the cache is empty, the returned iterator will be equal to `end()`.
*
* @return An iterator to the first instance of the internal cache.
*/
@ -262,11 +261,6 @@ public:
/**
* @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
* internal cache.
*/

View File

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

View File

@ -76,7 +76,13 @@ class delegate<Ret(Args...)> {
[[nodiscard]] auto wrap(std::index_sequence<Index...>) noexcept {
return [](const void *, Args... args) -> Ret {
[[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))...));
} 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))...));
}
};
}
@ -85,7 +91,13 @@ class delegate<Ret(Args...)> {
return [](const void *payload, Args... args) -> Ret {
[[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));
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))...));
} 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))...));
}
};
}
@ -94,7 +106,13 @@ class delegate<Ret(Args...)> {
return [](const void *payload, Args... args) -> Ret {
[[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));
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))...));
} 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))...));
}
};
}
@ -233,6 +251,14 @@ public:
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.
* @return An opaque pointer to the underlying data.

View File

@ -75,7 +75,7 @@ public:
template<typename... Args>
void enqueue(Args &&...args) {
if constexpr(std::is_aggregate_v<Type>) {
if constexpr(std::is_aggregate_v<Type> && (sizeof...(Args) != 0u || !std::is_default_constructible_v<Type>)) {
events.push_back(Type{std::forward<Args>(args)...});
} else {
events.emplace_back(std::forward<Args>(args)...);
@ -179,7 +179,9 @@ public:
* @param allocator The allocator to use.
*/
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.
@ -187,6 +189,8 @@ public:
* @return This dispatcher.
*/
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);
return *this;
}

View File

@ -76,7 +76,9 @@ public:
* @param allocator The allocator to use.
*/
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.
@ -84,6 +86,8 @@ public:
* @return This dispatcher.
*/
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);
return *this;
}

View File

@ -1,8 +1,8 @@
#ifndef ENTT_SIGNAL_SIGH_HPP
#define ENTT_SIGNAL_SIGH_HPP
#include <algorithm>
#include <functional>
#include <cstddef>
#include <memory>
#include <type_traits>
#include <utility>
#include <vector>
@ -56,7 +56,8 @@ class sigh<Ret(Args...), Allocator> {
friend class sink<sigh<Ret(Args...), Allocator>>;
using alloc_traits = std::allocator_traits<Allocator>;
using container_type = std::vector<delegate<Ret(Args...)>, typename alloc_traits::template rebind_alloc<delegate<Ret(Args...)>>>;
using delegate_type = delegate<Ret(Args...)>;
using container_type = std::vector<delegate_type, typename alloc_traits::template rebind_alloc<delegate_type>>;
public:
/*! @brief Allocator type. */
@ -168,8 +169,8 @@ public:
* @param args Arguments to use to invoke listeners.
*/
void publish(Args... args) const {
for(auto &&call: std::as_const(calls)) {
call(args...);
for(auto pos = calls.size(); pos; --pos) {
calls[pos - 1u](args...);
}
}
@ -189,24 +190,24 @@ public:
*/
template<typename Func>
void collect(Func func, Args... args) const {
for(auto &&call: calls) {
if constexpr(std::is_void_v<Ret>) {
for(auto pos = calls.size(); pos; --pos) {
if constexpr(std::is_void_v<Ret> || !std::is_invocable_v<Func, Ret>) {
calls[pos - 1u](args...);
if constexpr(std::is_invocable_r_v<bool, Func>) {
call(args...);
if(func()) {
break;
}
} else {
call(args...);
func();
}
} else {
if constexpr(std::is_invocable_r_v<bool, Func, Ret>) {
if(func(call(args...))) {
if(func(calls[pos - 1u](args...))) {
break;
}
} else {
func(call(args...));
func(calls[pos - 1u](args...));
}
}
}
@ -358,6 +359,7 @@ private:
template<typename Ret, typename... Args, typename Allocator>
class sink<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;
template<auto Candidate, typename Type>
@ -370,13 +372,14 @@ class sink<sigh<Ret(Args...), Allocator>> {
sink{*static_cast<signal_type *>(signal)}.disconnect<Candidate>();
}
auto before(delegate<Ret(Args...)> call) {
const auto &calls = signal->calls;
const auto it = std::find(calls.cbegin(), calls.cend(), std::move(call));
sink other{*this};
other.offset = calls.cend() - it;
return other;
template<typename Func>
void disconnect_if(Func callback) {
for(auto pos = signal->calls.size(); pos; --pos) {
if(auto &elem = signal->calls[pos - 1u]; callback(elem)) {
elem = std::move(signal->calls.back());
signal->calls.pop_back();
}
}
}
public:
@ -385,8 +388,7 @@ public:
* @param ref A valid reference to a signal object.
*/
sink(sigh<Ret(Args...), Allocator> &ref) noexcept
: offset{},
signal{&ref} {}
: signal{&ref} {}
/**
* @brief Returns false if at least a listener is connected to the sink.
@ -396,89 +398,9 @@ public:
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
* 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 Type Type of class or type of payload, if any.
* @param value_or_instance A valid object that fits the purpose, if any.
@ -488,9 +410,9 @@ public:
connection connect(Type &&...value_or_instance) {
disconnect<Candidate>(value_or_instance...);
delegate<Ret(Args...)> call{};
delegate_type call{};
call.template connect<Candidate>(value_or_instance...);
signal->calls.insert(signal->calls.end() - offset, std::move(call));
signal->calls.push_back(std::move(call));
delegate<void(void *)> conn{};
conn.template connect<&release<Candidate, Type...>>(value_or_instance...);
@ -506,21 +428,9 @@ public:
*/
template<auto Candidate, typename... Type>
void disconnect(Type &&...value_or_instance) {
auto &calls = signal->calls;
delegate<Ret(Args...)> call{};
delegate_type call{};
call.template connect<Candidate>(value_or_instance...);
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);
disconnect_if([&call](const auto &elem) { return elem == call; });
}
/**
@ -530,9 +440,7 @@ public:
*/
void disconnect(const void *value_or_instance) {
if(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());
disconnect_if([value_or_instance](const auto &elem) { return elem.data() == value_or_instance; });
}
}
@ -542,7 +450,6 @@ public:
}
private:
difference_type offset;
signal_type *signal;
};

View File

@ -51,7 +51,20 @@ function(SETUP_TARGET TARGET_NAME)
target_compile_options(
${TARGET_NAME}
PRIVATE
/EHsc /W1 /wd4996 /w14800
# vs2017 emits too many false positives for my tastes
$<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:Release>:/O2>
)
@ -94,21 +107,23 @@ function(SETUP_BASIC_TEST TEST_NAME TEST_SOURCES)
add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME})
endfunction()
function(SETUP_LIB_TEST TEST_NAME)
add_library(_${TEST_NAME} SHARED $<TARGET_OBJECTS:odr> lib/${TEST_NAME}/lib.cpp)
SETUP_TARGET(_${TEST_NAME} ENTT_API_EXPORT)
SETUP_BASIC_TEST(lib_${TEST_NAME} lib/${TEST_NAME}/main.cpp ENTT_API_IMPORT)
target_link_libraries(lib_${TEST_NAME} PRIVATE _${TEST_NAME})
function(SETUP_LIB_SHARED_TEST TEST_NAME SUB_PATH)
set(TARGET_NAME ${TEST_NAME}_${SUB_PATH})
add_library(_${TARGET_NAME} SHARED $<TARGET_OBJECTS:odr> lib/${TEST_NAME}/${SUB_PATH}/lib.cpp)
SETUP_TARGET(_${TARGET_NAME} ENTT_API_EXPORT)
SETUP_BASIC_TEST(lib_${TARGET_NAME} lib/${TEST_NAME}/${SUB_PATH}/main.cpp ENTT_API_IMPORT)
target_link_libraries(lib_${TARGET_NAME} PRIVATE _${TARGET_NAME})
endfunction()
function(SETUP_PLUGIN_TEST TEST_NAME)
add_library(_${TEST_NAME} MODULE $<TARGET_OBJECTS:odr> lib/${TEST_NAME}/plugin.cpp)
SETUP_TARGET(_${TEST_NAME} ${ARGVN})
SETUP_BASIC_TEST(lib_${TEST_NAME} lib/${TEST_NAME}/main.cpp PLUGIN="$<TARGET_FILE:_${TEST_NAME}>" ${ARGVN})
target_include_directories(_${TEST_NAME} PRIVATE ${cr_INCLUDE_DIR})
target_include_directories(lib_${TEST_NAME} PRIVATE ${cr_INCLUDE_DIR})
target_link_libraries(lib_${TEST_NAME} PRIVATE ${CMAKE_DL_LIBS})
add_dependencies(lib_${TEST_NAME} _${TEST_NAME})
function(SETUP_LIB_PLUGIN_TEST TEST_NAME SUB_PATH)
set(TARGET_NAME ${TEST_NAME}_${SUB_PATH})
add_library(_${TARGET_NAME} MODULE $<TARGET_OBJECTS:odr> lib/${TEST_NAME}/${SUB_PATH}/plugin.cpp)
SETUP_TARGET(_${TARGET_NAME} ${ARGVN})
SETUP_BASIC_TEST(lib_${TARGET_NAME} lib/${TEST_NAME}/${SUB_PATH}/main.cpp PLUGIN="$<TARGET_FILE:_${TARGET_NAME}>" ${ARGVN})
target_include_directories(_${TARGET_NAME} PRIVATE ${cr_INCLUDE_DIR})
target_include_directories(lib_${TARGET_NAME} PRIVATE ${cr_INCLUDE_DIR})
target_link_libraries(lib_${TARGET_NAME} PRIVATE ${CMAKE_DL_LIBS})
add_dependencies(lib_${TARGET_NAME} _${TARGET_NAME})
endfunction()
# Test benchmark
@ -142,19 +157,21 @@ if(ENTT_BUILD_LIB)
set(cr_INCLUDE_DIR ${cr_SOURCE_DIR})
endif()
SETUP_LIB_TEST(dispatcher)
SETUP_LIB_TEST(emitter)
SETUP_LIB_TEST(locator)
SETUP_LIB_TEST(meta)
SETUP_LIB_TEST(registry)
SETUP_LIB_SHARED_TEST(dispatcher shared)
SETUP_LIB_PLUGIN_TEST(dispatcher plugin)
SETUP_PLUGIN_TEST(dispatcher_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(emitter shared)
SETUP_LIB_PLUGIN_TEST(emitter plugin)
SETUP_PLUGIN_TEST(meta_plugin_std ENTT_STANDARD_CPP)
SETUP_LIB_SHARED_TEST(locator shared)
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()
# Test snapshot
@ -215,10 +232,11 @@ SETUP_BASIC_TEST(observer entt/entity/observer.cpp)
SETUP_BASIC_TEST(organizer entt/entity/organizer.cpp)
SETUP_BASIC_TEST(registry entt/entity/registry.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(sparse_set entt/entity/sparse_set.cpp)
SETUP_BASIC_TEST(storage entt/entity/storage.cpp)
SETUP_BASIC_TEST(storage_mixin entt/entity/storage_mixin.cpp)
SETUP_BASIC_TEST(storage_entity entt/entity/storage_entity.cpp)
SETUP_BASIC_TEST(view entt/entity/view.cpp)
# Test graph

View File

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

View File

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

View File

@ -100,7 +100,7 @@ TEST(DenseMap, Functionalities) {
}
TEST(DenseMap, Constructors) {
static constexpr std::size_t minimum_bucket_count = 8u;
constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<int, int> map;
ASSERT_EQ(map.bucket_count(), minimum_bucket_count);
@ -395,7 +395,7 @@ TEST(DenseMap, Insert) {
}
TEST(DenseMap, InsertRehash) {
static constexpr std::size_t minimum_bucket_count = 8u;
constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map;
ASSERT_EQ(map.size(), 0u);
@ -429,7 +429,7 @@ TEST(DenseMap, InsertRehash) {
}
TEST(DenseMap, InsertSameBucket) {
static constexpr std::size_t minimum_bucket_count = 8u;
constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map;
for(std::size_t next{}; next < minimum_bucket_count; ++next) {
@ -598,7 +598,7 @@ TEST(DenseMap, Emplace) {
}
TEST(DenseMap, EmplaceRehash) {
static constexpr std::size_t minimum_bucket_count = 8u;
constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map;
ASSERT_EQ(map.size(), 0u);
@ -633,7 +633,7 @@ TEST(DenseMap, EmplaceRehash) {
}
TEST(DenseMap, EmplaceSameBucket) {
static constexpr std::size_t minimum_bucket_count = 8u;
constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map;
for(std::size_t next{}; next < minimum_bucket_count; ++next) {
@ -681,7 +681,7 @@ TEST(DenseMap, TryEmplace) {
}
TEST(DenseMap, TryEmplaceRehash) {
static constexpr std::size_t minimum_bucket_count = 8u;
constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map;
ASSERT_EQ(map.size(), 0u);
@ -715,7 +715,7 @@ TEST(DenseMap, TryEmplaceRehash) {
}
TEST(DenseMap, TryEmplaceSameBucket) {
static constexpr std::size_t minimum_bucket_count = 8u;
constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map;
for(std::size_t next{}; next < minimum_bucket_count; ++next) {
@ -749,7 +749,7 @@ TEST(DenseMap, TryEmplaceMovableType) {
}
TEST(DenseMap, Erase) {
static constexpr std::size_t minimum_bucket_count = 8u;
constexpr std::size_t minimum_bucket_count = 8u;
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) {
@ -799,7 +799,7 @@ TEST(DenseMap, Erase) {
}
TEST(DenseMap, EraseWithMovableKeyValue) {
static constexpr std::size_t minimum_bucket_count = 8u;
constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::string, std::size_t> map;
map.emplace("0", 0u);
@ -817,7 +817,7 @@ TEST(DenseMap, EraseWithMovableKeyValue) {
}
TEST(DenseMap, EraseFromBucket) {
static constexpr std::size_t minimum_bucket_count = 8u;
constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map;
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::reference, std::pair<const std::size_t &, std::size_t &>>);
static constexpr std::size_t minimum_bucket_count = 8u;
constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map;
map.emplace(3u, 42u);
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::reference, std::pair<const std::size_t &, const std::size_t &>>);
static constexpr std::size_t minimum_bucket_count = 8u;
constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map;
map.emplace(3u, 42u);
map.emplace(3u + minimum_bucket_count, 99u);
@ -1064,7 +1064,7 @@ TEST(DenseMap, LocalIteratorConversion) {
}
TEST(DenseMap, Rehash) {
static constexpr std::size_t minimum_bucket_count = 8u;
constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<std::size_t, std::size_t, entt::identity> map;
map[32u] = 99u;
@ -1147,7 +1147,7 @@ TEST(DenseMap, Rehash) {
}
TEST(DenseMap, Reserve) {
static constexpr std::size_t minimum_bucket_count = 8u;
constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_map<int, int> map;
ASSERT_EQ(map.bucket_count(), minimum_bucket_count);
@ -1159,7 +1159,7 @@ TEST(DenseMap, Reserve) {
map.reserve(minimum_bucket_count);
ASSERT_EQ(map.bucket_count(), 2 * minimum_bucket_count);
ASSERT_EQ(map.bucket_count(), entt::next_power_of_two(std::ceil(minimum_bucket_count / map.max_load_factor())));
ASSERT_EQ(map.bucket_count(), entt::next_power_of_two(static_cast<std::size_t>(std::ceil(minimum_bucket_count / map.max_load_factor()))));
}
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_exception = typename packed_allocator::exception_type;
static constexpr std::size_t minimum_bucket_count = 8u;
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{};
packed_allocator::trigger_on_allocate = true;

View File

@ -98,7 +98,7 @@ TEST(DenseSet, Functionalities) {
}
TEST(DenseSet, Constructors) {
static constexpr std::size_t minimum_bucket_count = 8u;
constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<int> set;
ASSERT_EQ(set.bucket_count(), minimum_bucket_count);
@ -357,7 +357,7 @@ TEST(DenseSet, Insert) {
}
TEST(DenseSet, InsertRehash) {
static constexpr std::size_t minimum_bucket_count = 8u;
constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::size_t, entt::identity> set;
ASSERT_EQ(set.size(), 0u);
@ -388,7 +388,7 @@ TEST(DenseSet, InsertRehash) {
}
TEST(DenseSet, InsertSameBucket) {
static constexpr std::size_t minimum_bucket_count = 8u;
constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::size_t, entt::identity> set;
for(std::size_t next{}; next < minimum_bucket_count; ++next) {
@ -451,7 +451,7 @@ TEST(DenseSet, Emplace) {
}
TEST(DenseSet, EmplaceRehash) {
static constexpr std::size_t minimum_bucket_count = 8u;
constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::size_t, entt::identity> set;
ASSERT_EQ(set.size(), 0u);
@ -483,7 +483,7 @@ TEST(DenseSet, EmplaceRehash) {
}
TEST(DenseSet, EmplaceSameBucket) {
static constexpr std::size_t minimum_bucket_count = 8u;
constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::size_t, entt::identity> set;
for(std::size_t next{}; next < minimum_bucket_count; ++next) {
@ -503,7 +503,7 @@ TEST(DenseSet, EmplaceSameBucket) {
}
TEST(DenseSet, Erase) {
static constexpr std::size_t minimum_bucket_count = 8u;
constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::size_t, entt::identity> set;
for(std::size_t next{}, last = minimum_bucket_count + 1u; next < last; ++next) {
@ -553,7 +553,7 @@ TEST(DenseSet, Erase) {
}
TEST(DenseSet, EraseWithMovableKeyValue) {
static constexpr std::size_t minimum_bucket_count = 8u;
constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::string> set;
set.emplace("0");
@ -570,7 +570,7 @@ TEST(DenseSet, EraseWithMovableKeyValue) {
}
TEST(DenseSet, EraseFromBucket) {
static constexpr std::size_t minimum_bucket_count = 8u;
constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::size_t, entt::identity> set;
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::reference, const std::size_t &>);
static constexpr std::size_t minimum_bucket_count = 8u;
constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::size_t, entt::identity> set;
set.emplace(3u);
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::reference, const std::size_t &>);
static constexpr std::size_t minimum_bucket_count = 8u;
constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::size_t, entt::identity> set;
set.emplace(3u);
set.emplace(3u + minimum_bucket_count);
@ -790,7 +790,7 @@ TEST(DenseSet, LocalIteratorConversion) {
}
TEST(DenseSet, Rehash) {
static constexpr std::size_t minimum_bucket_count = 8u;
constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<std::size_t, entt::identity> set;
set.emplace(32u);
@ -865,7 +865,7 @@ TEST(DenseSet, Rehash) {
}
TEST(DenseSet, Reserve) {
static constexpr std::size_t minimum_bucket_count = 8u;
constexpr std::size_t minimum_bucket_count = 8u;
entt::dense_set<int> set;
ASSERT_EQ(set.bucket_count(), minimum_bucket_count);
@ -877,7 +877,7 @@ TEST(DenseSet, Reserve) {
set.reserve(minimum_bucket_count);
ASSERT_EQ(set.bucket_count(), 2 * minimum_bucket_count);
ASSERT_EQ(set.bucket_count(), entt::next_power_of_two(std::ceil(minimum_bucket_count / set.max_load_factor())));
ASSERT_EQ(set.bucket_count(), entt::next_power_of_two(static_cast<std::size_t>(std::ceil(minimum_bucket_count / set.max_load_factor()))));
}
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_exception = typename packed_allocator::exception_type;
static constexpr std::size_t minimum_bucket_count = 8u;
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{};
packed_allocator::trigger_on_allocate = true;

View File

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

View File

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

View File

@ -24,6 +24,12 @@ TEST(ToAddress, Functionalities) {
TEST(PoccaPocmaAndPocs, Functionalities) {
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 :)
entt::propagate_on_container_copy_assignment(lhs, rhs);
entt::propagate_on_container_move_assignment(lhs, rhs);
@ -31,8 +37,8 @@ TEST(PoccaPocmaAndPocs, Functionalities) {
}
ENTT_DEBUG_TEST(PoccaPocmaAndPocsDeathTest, Functionalities) {
using pocs = std::false_type;
test::basic_test_allocator<int, pocs> lhs, rhs;
test::basic_test_allocator<int, std::false_type> lhs, rhs;
ASSERT_DEATH(entt::propagate_on_container_swap(lhs, rhs), "");
}
@ -60,8 +66,8 @@ TEST(NextPowerOfTwo, Functionalities) {
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(33u), 64u);
ASSERT_EQ(entt::next_power_of_two(std::pow(2, 16)), std::pow(2, 16));
ASSERT_EQ(entt::next_power_of_two(std::pow(2, 16) + 1u), std::pow(2, 17));
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(static_cast<std::size_t>(std::pow(2, 16) + 1u)), static_cast<std::size_t>(std::pow(2, 17)));
}
ENTT_DEBUG_TEST(NextPowerOfTwoDeathTest, Functionalities) {

View File

@ -34,5 +34,5 @@ TEST(Tuple, UnwrapTuple) {
TEST(Tuple, ForwardApply) {
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{[](auto... args) { return (args + ...); }}(std::make_tuple('a', 1u)), 'b');
ASSERT_EQ(entt::forward_apply{[](auto... args) { return (args + ...); }}(std::make_tuple('a', 1)), '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>, 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_unique_t<entt::type_list_cat_t<type, type>>, entt::type_list<int, char>>);
static_assert(std::is_same_v<entt::type_list_unique_t<entt::type_list_cat_t<type, type>>, type>);
static_assert(entt::type_list_contains_v<type, int>);
static_assert(entt::type_list_contains_v<type, char>);
@ -112,6 +112,14 @@ 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>, 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::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) {
@ -125,10 +133,33 @@ 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>, 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_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<1u, value> == 2);
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) {
@ -183,6 +214,7 @@ TEST(IsEqualityComparable, Functionalities) {
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<int[3u]>);
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<std::vector<not_comparable>>);

View File

@ -30,50 +30,43 @@ struct entt::component_traits<traits_based> {
};
TEST(Component, VoidType) {
using traits = entt::component_traits<void>;
using traits_type = entt::component_traits<void>;
static_assert(traits::in_place_delete);
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);
static_assert(!traits_type::in_place_delete);
static_assert(traits_type::page_size == 0u);
}
TEST(Component, Empty) {
using traits = entt::component_traits<empty>;
using traits_type = entt::component_traits<empty>;
static_assert(!traits::in_place_delete);
static_assert(entt::ignore_as_empty_v<typename traits::type>);
static_assert(traits::page_size == 0u);
static_assert(!traits_type::in_place_delete);
static_assert(traits_type::page_size == 0u);
}
TEST(Component, NonEmpty) {
using traits = entt::component_traits<non_empty>;
using traits_type = entt::component_traits<non_empty>;
static_assert(!traits::in_place_delete);
static_assert(!entt::ignore_as_empty_v<typename traits::type>);
static_assert(traits::page_size == ENTT_PACKED_PAGE);
static_assert(!traits_type::in_place_delete);
static_assert(traits_type::page_size == ENTT_PACKED_PAGE);
}
TEST(Component, NonMovable) {
using traits = entt::component_traits<non_movable>;
using traits_type = entt::component_traits<non_movable>;
static_assert(traits::in_place_delete);
static_assert(!entt::ignore_as_empty_v<typename traits::type>);
static_assert(traits::page_size == ENTT_PACKED_PAGE);
static_assert(traits_type::in_place_delete);
static_assert(traits_type::page_size == ENTT_PACKED_PAGE);
}
TEST(Component, SelfContained) {
using traits = entt::component_traits<self_contained>;
using traits_type = entt::component_traits<self_contained>;
static_assert(traits::in_place_delete);
static_assert(!entt::ignore_as_empty_v<typename traits::type>);
static_assert(traits::page_size == 4u);
static_assert(traits_type::in_place_delete);
static_assert(traits_type::page_size == 4u);
}
TEST(Component, TraitsBased) {
using traits = entt::component_traits<traits_based>;
using traits_type = entt::component_traits<traits_based>;
static_assert(!traits::in_place_delete);
static_assert(!entt::ignore_as_empty_v<typename traits::type>);
static_assert(traits::page_size == 8u);
static_assert(!traits_type::in_place_delete);
static_assert(traits_type::page_size == 8u);
}

View File

@ -29,6 +29,12 @@ TEST(Entity, Traits) {
ASSERT_EQ(traits_type::combine(entt::tombstone, entt::null), tombstone);
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) {

View File

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

View File

@ -16,6 +16,10 @@ struct stable_type {
int value;
};
void sigh_callback(int &value) {
++value;
}
TEST(Helper, AsView) {
entt::registry registry;
const entt::registry cregistry;
@ -48,7 +52,7 @@ TEST(Helper, Invoke) {
TEST(Helper, ToEntity) {
entt::registry registry;
const entt::entity null = entt::null;
constexpr auto page_size = entt::component_traits<int>::page_size;
constexpr auto page_size = entt::storage_type_t<int>::traits_type::page_size;
const int value = 42;
ASSERT_EQ(entt::to_entity(registry, 42), null);
@ -88,7 +92,7 @@ TEST(Helper, ToEntity) {
TEST(Helper, ToEntityStableType) {
entt::registry registry;
const entt::entity null = entt::null;
constexpr auto page_size = entt::component_traits<stable_type>::page_size;
constexpr auto page_size = entt::storage_type_t<stable_type>::traits_type::page_size;
const stable_type value{42};
ASSERT_EQ(entt::to_entity(registry, stable_type{42}), null);
@ -124,3 +128,44 @@ TEST(Helper, ToEntityStableType) {
ASSERT_EQ(entt::to_entity(registry, stable_type{42}), 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,5 +1,7 @@
#include <cstddef>
#include <utility>
#include <gtest/gtest.h>
#include <entt/core/type_info.hpp>
#include <entt/entity/organizer.hpp>
#include <entt/entity/registry.hpp>
@ -20,7 +22,7 @@ struct clazz {
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 &registry) {
void to_args_integrity(entt::view<entt::get_t<int>> view, std::size_t &value, entt::registry &) {
value = view.size();
}
@ -363,6 +365,10 @@ TEST(Organizer, Prepare) {
ASSERT_FALSE(registry.ctx().contains<char>());
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) {
vertex.prepare(registry);
}
@ -370,6 +376,10 @@ TEST(Organizer, Prepare) {
ASSERT_FALSE(registry.ctx().contains<int>());
ASSERT_FALSE(registry.ctx().contains<char>());
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) {
@ -428,5 +438,5 @@ TEST(Organizer, ToArgsIntegrity) {
auto graph = organizer.graph();
graph[0u].callback()(graph[0u].data(), registry);
ASSERT_EQ(registry.ctx().at<std::size_t>(), 0u);
ASSERT_EQ(registry.ctx().get<std::size_t>(), 0u);
}

View File

@ -40,18 +40,16 @@ struct aggregate {
};
struct listener {
template<typename Component>
template<typename Type>
static void sort(entt::registry &registry) {
registry.sort<Component>([](auto lhs, auto rhs) { return lhs < rhs; });
registry.sort<Type>([](auto lhs, auto rhs) { return lhs < rhs; });
}
template<typename Component>
void incr(const entt::registry &, entt::entity entity) {
last = entity;
++counter;
}
template<typename Component>
void decr(const entt::registry &, entt::entity entity) {
last = entity;
--counter;
@ -75,11 +73,11 @@ struct destruction_order {
destruction_order(const entt::registry &ref, bool &ctx)
: registry{&ref},
ctx_check{&ctx} {
*ctx_check = (registry->ctx().find<int>() != nullptr);
*ctx_check = (registry->ctx().find<ctx_check_type>() != nullptr);
}
~destruction_order() {
*ctx_check = *ctx_check && (registry->ctx().find<int>() != nullptr);
*ctx_check = *ctx_check && (registry->ctx().find<ctx_check_type>() != nullptr);
}
private:
@ -87,6 +85,22 @@ private:
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) {
entt::registry registry;
auto &ctx = registry.ctx();
@ -126,8 +140,8 @@ TEST(Registry, Context) {
ASSERT_EQ(ctx.emplace<const int>(0), 42);
ASSERT_EQ(ctx.find<const int>(), cctx.find<int>());
ASSERT_EQ(ctx.at<int>(), cctx.at<const int>());
ASSERT_EQ(ctx.at<int>(), 42);
ASSERT_EQ(ctx.get<int>(), cctx.get<const int>());
ASSERT_EQ(ctx.get<int>(), 42);
ASSERT_EQ(ctx.find<double>(), nullptr);
ASSERT_EQ(cctx.find<double>(), nullptr);
@ -139,8 +153,8 @@ TEST(Registry, Context) {
ASSERT_EQ(ctx.insert_or_assign<const int>(0), 0);
ASSERT_EQ(ctx.find<const int>(), cctx.find<int>());
ASSERT_EQ(ctx.at<int>(), cctx.at<const int>());
ASSERT_EQ(ctx.at<int>(), 0);
ASSERT_EQ(ctx.get<int>(), cctx.get<const int>());
ASSERT_EQ(ctx.get<int>(), 0);
}
TEST(Registry, ContextHint) {
@ -376,13 +390,12 @@ TEST(Registry, Functionalities) {
TEST(Registry, Constructors) {
entt::registry registry;
entt::registry other{42};
const entt::entity entity = entt::tombstone;
ASSERT_TRUE(registry.empty());
ASSERT_TRUE(other.empty());
ASSERT_EQ(registry.released(), entity);
ASSERT_EQ(other.released(), entity);
ASSERT_EQ(registry.released(), 0u);
ASSERT_EQ(other.released(), 0u);
}
TEST(Registry, Move) {
@ -486,6 +499,8 @@ TEST(Registry, Identifiers) {
}
TEST(Registry, Data) {
using traits_type = entt::entt_traits<entt::entity>;
entt::registry registry;
ASSERT_EQ(std::as_const(registry).data(), nullptr);
@ -497,8 +512,8 @@ TEST(Registry, Data) {
const auto other = registry.create();
registry.release(entity);
ASSERT_NE(*std::as_const(registry).data(), entity);
ASSERT_EQ(*(std::as_const(registry).data() + 1u), other);
ASSERT_EQ(*std::as_const(registry).data(), other);
ASSERT_EQ(*(std::as_const(registry).data() + 1u), traits_type::next(entity));
}
TEST(Registry, CreateManyEntitiesAtOnce) {
@ -533,7 +548,7 @@ TEST(Registry, CreateManyEntitiesAtOnceWithListener) {
entt::entity entities[3];
listener listener;
registry.on_construct<int>().connect<&listener::incr<int>>(listener);
registry.on_construct<int>().connect<&listener::incr>(listener);
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), 'c');
@ -542,8 +557,8 @@ TEST(Registry, CreateManyEntitiesAtOnceWithListener) {
ASSERT_EQ(registry.get<char>(entities[1]), 'c');
ASSERT_EQ(listener.counter, 3);
registry.on_construct<int>().disconnect<&listener::incr<int>>(listener);
registry.on_construct<empty_type>().connect<&listener::incr<empty_type>>(listener);
registry.on_construct<int>().disconnect<&listener::incr>(listener);
registry.on_construct<empty_type>().connect<&listener::incr>(listener);
registry.create(std::begin(entities), std::end(entities));
registry.insert(std::begin(entities), std::end(entities), 'a');
registry.insert<empty_type>(std::begin(entities), std::end(entities));
@ -560,8 +575,9 @@ TEST(Registry, CreateWithHint) {
auto e3 = registry.create(entt::entity{3});
auto e2 = registry.create(entt::entity{3});
ASSERT_EQ(e2, entt::entity{2});
ASSERT_FALSE(registry.valid(entt::entity{1}));
ASSERT_EQ(e2, entt::entity{1});
ASSERT_FALSE(registry.valid(entt::entity{0}));
ASSERT_FALSE(registry.valid(entt::entity{2}));
ASSERT_EQ(e3, entt::entity{3});
registry.release(e2);
@ -572,10 +588,10 @@ TEST(Registry, CreateWithHint) {
e2 = registry.create();
auto e1 = registry.create(entt::entity{2});
ASSERT_EQ(traits_type::to_entity(e2), 2u);
ASSERT_EQ(traits_type::to_entity(e2), 1u);
ASSERT_EQ(traits_type::to_version(e2), 1u);
ASSERT_EQ(traits_type::to_entity(e1), 1u);
ASSERT_EQ(traits_type::to_entity(e1), 2u);
ASSERT_EQ(traits_type::to_version(e1), 0u);
registry.release(e1);
@ -640,6 +656,14 @@ TEST(Registry, CreateDestroyReleaseCornerCase) {
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) {
entt::registry registry;
@ -666,7 +690,7 @@ ENTT_DEBUG_TEST(RegistryDeathTest, DestroyVersion) {
ASSERT_DEATH(registry.destroy(entity, 3), "");
}
TEST(Registry, RangeDestroy) {
TEST(Registry, DestroyRange) {
entt::registry registry;
const auto iview = registry.view<int>();
const auto icview = registry.view<int, char>();
@ -722,6 +746,24 @@ TEST(Registry, RangeDestroy) {
ASSERT_FALSE(registry.valid(entities[1u]));
ASSERT_FALSE(registry.valid(entities[2u]));
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) {
@ -792,7 +834,7 @@ ENTT_DEBUG_TEST(RegistryDeathTest, ReleaseVersion) {
ASSERT_DEATH(registry.release(entity, 3), "");
}
TEST(Registry, RangeRelease) {
TEST(Registry, ReleaseRange) {
entt::registry registry;
entt::entity entities[3u];
@ -930,10 +972,12 @@ TEST(Registry, Orphans) {
TEST(Registry, View) {
entt::registry registry;
auto mview = registry.view<int, char>();
entt::entity entities[3u];
auto iview = registry.view<int>();
auto cview = registry.view<char>();
entt::entity entities[3u];
auto mview = registry.view<int, char>();
auto fview = registry.view<int>(entt::exclude<char>);
registry.create(std::begin(entities), std::end(entities));
@ -948,10 +992,55 @@ TEST(Registry, View) {
ASSERT_EQ(iview.size(), 3u);
ASSERT_EQ(cview.size(), 2u);
std::size_t cnt{};
mview.each([&cnt](auto...) { ++cnt; });
ASSERT_EQ(mview.size_hint(), 3u);
ASSERT_EQ(fview.size_hint(), 3u);
ASSERT_EQ(cnt, 2u);
mview.refresh();
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) {
@ -964,7 +1053,7 @@ TEST(Registry, NonOwningGroupInitOnFirstUse) {
registry.emplace<char>(entities[2u], 'c');
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; });
ASSERT_FALSE((registry.owned<int, char>()));
@ -974,7 +1063,7 @@ TEST(Registry, NonOwningGroupInitOnFirstUse) {
TEST(Registry, NonOwningGroupInitOnEmplace) {
entt::registry registry;
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.insert<int>(std::begin(entities), std::end(entities), 0);
@ -1097,7 +1186,7 @@ TEST(Registry, CleanViewAfterRemoveAndClear) {
TEST(Registry, CleanNonOwningGroupViewAfterRemoveAndClear) {
entt::registry registry;
auto group = registry.group<>(entt::get<int, char>);
auto group = registry.group(entt::get<int, char>);
const auto entity = registry.create();
registry.emplace<int>(entity, 0);
@ -1188,107 +1277,21 @@ TEST(Registry, CleanPartialOwningGroupViewAfterRemoveAndClear) {
ASSERT_EQ(group.size(), 0u);
}
TEST(Registry, NestedGroups) {
ENTT_DEBUG_TEST(RegistryDeathTest, NestedGroups) {
entt::registry registry;
entt::entity entities[10];
registry.group<int, double>(entt::get<char>);
registry.create(std::begin(entities), std::end(entities));
registry.insert<int>(std::begin(entities), std::end(entities));
registry.insert<char>(std::begin(entities), std::end(entities));
const auto g1 = registry.group<int>(entt::get<char>, entt::exclude<double>);
ASSERT_DEATH(registry.group<int>(entt::get<char>), "");
ASSERT_DEATH(registry.group<int>(entt::get<char, double>), "");
ASSERT_DEATH(registry.group<int>(entt::get<char>, entt::exclude<double>), "");
ASSERT_DEATH((registry.group<int, double>()), "");
}
ASSERT_TRUE(registry.sortable(g1));
ASSERT_EQ(g1.size(), 10u);
ENTT_DEBUG_TEST(RegistryDeathTest, ConflictingGroups) {
entt::registry registry;
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]);
}
ASSERT_EQ(g1.size(), 5u);
ASSERT_EQ(g2.size(), 10u);
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]));
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);
registry.group<char>(entt::get<int>, entt::exclude<double>);
ASSERT_DEATH(registry.group<char>(entt::get<float>, entt::exclude<double>), "");
}
TEST(Registry, SortSingle) {
@ -1379,10 +1382,10 @@ TEST(Registry, Signals) {
entt::entity entities[2u];
listener listener;
registry.on_construct<empty_type>().connect<&listener::incr<empty_type>>(listener);
registry.on_destroy<empty_type>().connect<&listener::decr<empty_type>>(listener);
registry.on_construct<int>().connect<&listener::incr<int>>(listener);
registry.on_destroy<int>().connect<&listener::decr<int>>(listener);
registry.on_construct<empty_type>().connect<&listener::incr>(listener);
registry.on_destroy<empty_type>().connect<&listener::decr>(listener);
registry.on_construct<int>().connect<&listener::incr>(listener);
registry.on_destroy<int>().connect<&listener::decr>(listener);
registry.create(std::begin(entities), std::end(entities));
registry.insert<empty_type>(std::begin(entities), std::end(entities));
@ -1400,16 +1403,16 @@ TEST(Registry, Signals) {
ASSERT_EQ(listener.counter, 2);
ASSERT_EQ(listener.last, entities[0u]);
registry.on_destroy<empty_type>().disconnect<&listener::decr<empty_type>>(listener);
registry.on_destroy<int>().disconnect<&listener::decr<int>>(listener);
registry.on_destroy<empty_type>().disconnect<&listener::decr>(listener);
registry.on_destroy<int>().disconnect<&listener::decr>(listener);
registry.erase<empty_type, int>(entities[1u]);
ASSERT_EQ(listener.counter, 2);
ASSERT_EQ(listener.last, entities[0u]);
registry.on_construct<empty_type>().disconnect<&listener::incr<empty_type>>(listener);
registry.on_construct<int>().disconnect<&listener::incr<int>>(listener);
registry.on_construct<empty_type>().disconnect<&listener::incr>(listener);
registry.on_construct<int>().disconnect<&listener::incr>(listener);
registry.emplace<empty_type>(entities[1u]);
registry.emplace<int>(entities[1u]);
@ -1417,8 +1420,8 @@ TEST(Registry, Signals) {
ASSERT_EQ(listener.counter, 2);
ASSERT_EQ(listener.last, entities[0u]);
registry.on_construct<int>().connect<&listener::incr<int>>(listener);
registry.on_destroy<int>().connect<&listener::decr<int>>(listener);
registry.on_construct<int>().connect<&listener::incr>(listener);
registry.on_destroy<int>().connect<&listener::decr>(listener);
registry.emplace<int>(entities[0u]);
registry.erase<int>(entities[1u]);
@ -1426,8 +1429,8 @@ TEST(Registry, Signals) {
ASSERT_EQ(listener.counter, 2);
ASSERT_EQ(listener.last, entities[1u]);
registry.on_construct<empty_type>().connect<&listener::incr<empty_type>>(listener);
registry.on_destroy<empty_type>().connect<&listener::decr<empty_type>>(listener);
registry.on_construct<empty_type>().connect<&listener::incr>(listener);
registry.on_destroy<empty_type>().connect<&listener::decr>(listener);
registry.erase<empty_type>(entities[1u]);
registry.emplace<empty_type>(entities[0u]);
@ -1454,8 +1457,8 @@ TEST(Registry, Signals) {
ASSERT_EQ(listener.counter, 2);
ASSERT_EQ(listener.last, entities[0u]);
registry.on_destroy<empty_type>().disconnect<&listener::decr<empty_type>>(listener);
registry.on_destroy<int>().disconnect<&listener::decr<int>>(listener);
registry.on_destroy<empty_type>().disconnect<&listener::decr>(listener);
registry.on_destroy<int>().disconnect<&listener::decr>(listener);
registry.emplace_or_replace<empty_type>(entities[0u]);
registry.emplace_or_replace<int>(entities[0u]);
@ -1463,8 +1466,8 @@ TEST(Registry, Signals) {
ASSERT_EQ(listener.counter, 2);
ASSERT_EQ(listener.last, entities[0u]);
registry.on_update<empty_type>().connect<&listener::incr<empty_type>>(listener);
registry.on_update<int>().connect<&listener::incr<int>>(listener);
registry.on_update<empty_type>().connect<&listener::incr>(listener);
registry.on_update<int>().connect<&listener::incr>(listener);
registry.emplace_or_replace<empty_type>(entities[0u]);
registry.emplace_or_replace<int>(entities[0u]);
@ -1479,6 +1482,84 @@ TEST(Registry, Signals) {
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) {
entt::registry registry;
const auto entity = registry.create();
@ -1576,6 +1657,17 @@ TEST(Registry, Erase) {
ASSERT_EQ(registry.storage<char>().size(), 0u);
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<char>(std::begin(entities), std::end(entities));
@ -1643,6 +1735,39 @@ TEST(Registry, StableErase) {
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) {
entt::registry registry;
const auto iview = registry.view<int>();
@ -1689,6 +1814,18 @@ TEST(Registry, Remove) {
ASSERT_EQ(registry.storage<char>().size(), 0u);
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<char>(std::begin(entities), std::end(entities));
@ -1791,7 +1928,7 @@ TEST(Registry, NonOwningGroupInterleaved) {
registry.emplace<int>(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();
registry.emplace<int>(entity);
@ -1845,7 +1982,7 @@ TEST(Registry, PartialOwningGroupInterleaved) {
TEST(Registry, NonOwningGroupSortInterleaved) {
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();
registry.emplace<int>(e0, 0);
@ -2009,7 +2146,7 @@ TEST(Registry, ScramblingPoolsIsAllowed) {
// thanks to @andranik3949 for pointing out this missing test
registry.view<const int>().each([](const auto entity, const auto &value) {
ASSERT_EQ(entt::to_integral(entity), value);
ASSERT_EQ(static_cast<int>(entt::to_integral(entity)), value);
});
}
@ -2021,7 +2158,7 @@ TEST(Registry, RuntimePools) {
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(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(std::as_const(registry).storage("other"_hs)), const entt::storage_type_t<empty_type>::base_type *>);
@ -2030,7 +2167,7 @@ TEST(Registry, RuntimePools) {
ASSERT_EQ(std::as_const(registry).storage("rehto"_hs), nullptr);
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(storage.contains(entity));
@ -2070,6 +2207,7 @@ TEST(Registry, Storage) {
entt::registry registry;
const auto entity = registry.create();
auto &storage = registry.storage<int>("int"_hs);
storage.emplace(entity);
@ -2149,8 +2287,7 @@ TEST(Registry, RegistryStorageIterator) {
TEST(Registry, RegistryStorageIteratorConversion) {
entt::registry registry;
const auto entity = registry.create();
registry.emplace<int>(entity);
registry.storage<int>();
auto proxy = registry.storage();
auto cproxy = std::as_const(registry).storage();
@ -2176,6 +2313,22 @@ TEST(Registry, RegistryStorageIteratorConversion) {
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) {
entt::registry registry;
const auto entity = registry.create();

View File

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

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,628 @@
#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

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

View File

@ -295,6 +295,23 @@ TEST(AdjacencyMatrix, EdgesDirected) {
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) {
entt::adjacency_matrix<entt::undirected_tag> adjacency_matrix{3u};
auto iterable = adjacency_matrix.edges();
@ -317,6 +334,24 @@ TEST(AdjacencyMatrix, EdgesUndirected) {
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) {
entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{3u};
auto iterable = adjacency_matrix.out_edges(0u);
@ -340,6 +375,28 @@ TEST(AdjacencyMatrix, OutEdgesDirected) {
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) {
entt::adjacency_matrix<entt::undirected_tag> adjacency_matrix{3u};
auto iterable = adjacency_matrix.out_edges(0u);
@ -365,6 +422,30 @@ TEST(AdjacencyMatrix, OutEdgesUndirected) {
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) {
entt::adjacency_matrix<entt::directed_tag> adjacency_matrix{3u};
auto iterable = adjacency_matrix.in_edges(1u);
@ -388,6 +469,28 @@ TEST(AdjacencyMatrix, InEdgesDirected) {
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) {
entt::adjacency_matrix<entt::undirected_tag> adjacency_matrix{3u};
auto iterable = adjacency_matrix.in_edges(1u);
@ -403,6 +506,7 @@ TEST(AdjacencyMatrix, InEdgesUndirected) {
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{2u}, std::size_t{1u}));
ASSERT_EQ(it, iterable.end());
iterable = adjacency_matrix.in_edges(0u);
@ -413,6 +517,30 @@ TEST(AdjacencyMatrix, InEdgesUndirected) {
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) {
using allocator = test::throwing_allocator<std::size_t>;
using exception = typename allocator::exception_type;

View File

@ -1,6 +1,7 @@
#include <gtest/gtest.h>
#include <entt/core/hashed_string.hpp>
#include <entt/graph/flow.hpp>
#include "../common/config.h"
#include "../common/throwing_allocator.hpp"
TEST(Flow, Constructors) {
@ -24,9 +25,9 @@ TEST(Flow, Constructors) {
ASSERT_EQ(flow.size(), 0u);
ASSERT_EQ(other.size(), 3u);
ASSERT_EQ(other[0u], 0);
ASSERT_EQ(other[1u], 3);
ASSERT_EQ(other[2u], 99);
ASSERT_EQ(other[0u], 0u);
ASSERT_EQ(other[1u], 3u);
ASSERT_EQ(other[2u], 99u);
}
TEST(Flow, Copy) {
@ -41,9 +42,9 @@ TEST(Flow, Copy) {
ASSERT_EQ(flow.size(), 3u);
ASSERT_EQ(other.size(), 3u);
ASSERT_EQ(other[0u], 0);
ASSERT_EQ(other[1u], 3);
ASSERT_EQ(other[2u], 99);
ASSERT_EQ(other[0u], 0u);
ASSERT_EQ(other[1u], 3u);
ASSERT_EQ(other[2u], 99u);
flow.bind(1);
other.bind(2);
@ -53,10 +54,10 @@ TEST(Flow, Copy) {
ASSERT_EQ(other.size(), 4u);
ASSERT_EQ(flow.size(), 4u);
ASSERT_EQ(other[0u], 0);
ASSERT_EQ(other[1u], 3);
ASSERT_EQ(other[2u], 99);
ASSERT_EQ(other[3u], 1);
ASSERT_EQ(other[0u], 0u);
ASSERT_EQ(other[1u], 3u);
ASSERT_EQ(other[2u], 99u);
ASSERT_EQ(other[3u], 1u);
}
TEST(Flow, Move) {
@ -71,9 +72,9 @@ TEST(Flow, Move) {
ASSERT_EQ(flow.size(), 0u);
ASSERT_EQ(other.size(), 3u);
ASSERT_EQ(other[0u], 0);
ASSERT_EQ(other[1u], 3);
ASSERT_EQ(other[2u], 99);
ASSERT_EQ(other[0u], 0u);
ASSERT_EQ(other[1u], 3u);
ASSERT_EQ(other[2u], 99u);
flow = {};
flow.bind(1);
@ -84,7 +85,7 @@ TEST(Flow, Move) {
ASSERT_EQ(other.size(), 1u);
ASSERT_EQ(flow.size(), 0u);
ASSERT_EQ(other[0u], 1);
ASSERT_EQ(other[0u], 1u);
}
TEST(Flow, Swap) {
@ -95,13 +96,13 @@ TEST(Flow, Swap) {
ASSERT_EQ(other.size(), 0u);
ASSERT_EQ(flow.size(), 1u);
ASSERT_EQ(flow[0u], 7);
ASSERT_EQ(flow[0u], 7u);
flow.swap(other);
ASSERT_EQ(other.size(), 1u);
ASSERT_EQ(flow.size(), 0u);
ASSERT_EQ(other[0u], 7);
ASSERT_EQ(other[0u], 7u);
}
TEST(Flow, Clear) {
@ -111,8 +112,8 @@ TEST(Flow, Clear) {
flow.bind(99);
ASSERT_EQ(flow.size(), 2u);
ASSERT_EQ(flow[0u], 0);
ASSERT_EQ(flow[1u], 99);
ASSERT_EQ(flow[0u], 0u);
ASSERT_EQ(flow[1u], 99u);
flow.clear();
@ -270,6 +271,57 @@ TEST(Flow, Sync) {
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) {
using allocator = test::throwing_allocator<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